Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions apps/game/localization/moonlit.csv
Original file line number Diff line number Diff line change
Expand Up @@ -379,3 +379,27 @@ STORY_CYCLE_8_A,셋으로는 모자란다. 밤마다 다시 먹힌다,Three is n
STORY_CYCLE_8_B,봉화를 하나로 이어야 한다. 끝까지 가자,The beacons must join into one line. Go to the end,烽火をひとつに繋がねばならない。最後まで行こう,必须把烽火连成一条线。走到最后,必須把烽火連成一條線。走到最後
STORY_CYCLE_9_A,여덟 번째 선이 이어졌다. 약속은 지켰다,The eighth line is joined. The promise is kept,八本目の線が繋がった。約束は守った,第八道界线连上了。承诺已经兑现,第八道界線連上了。承諾已經兌現
STORY_CYCLE_9_B,여기부터는 지도에 없는 밤이다. 얼마나 깊은지 보자,Past here the night is off the map. Let us see how deep it goes,ここから先は地図にない夜だ。どこまで深いか見てやろう,再往前是地图上没有的黑夜。看看它有多深,再往前是地圖上沒有的黑夜。看看它有多深
STORY_OPEN_A,파수단은 무너졌다. 나만 남았다,The Wardens fell. Only I remain,見張り団は倒れた。残ったのは私だけだ,守烽人倒下了。只剩下我,守烽人倒下了。只剩下我
STORY_OPEN_B,봉화를 잇는 게 내가 밤에 진 빚이다,Joining the beacons is my debt to the night,烽火を繋ぐのが夜への借りだ,连起烽火是我欠黑夜的债,連起烽火是我欠黑夜的債
STORY_EPITAPH_WIN,선이 이어졌다. 이 땅의 밤은 다시 지도에 있다,The line is joined. This land's night is on the map again,線は繋がった。この地の夜は再び地図にある,界线连上了。这片土地的夜重回地图,界線連上了。這片土地的夜重回地圖
STORY_EPITAPH_ESCAPE,오늘 밤은 여기까지다. 불씨를 간직하자,Tonight ends here. Keep the ember,今宵はここまでだ。火種を守ろう,今夜到此为止。守住火种,今夜到此為止。守住火種
STORY_EPITAPH_LOSE,빚은 다음 밤으로 넘어간다,The debt passes to the next night,借りは次の夜へ渡る,债传给了下一夜,債傳給了下一夜
VOICE_MEET_DRIFTER_1,밤박쥐다 · 움직이는 빛을 문다,Night bats · they bite what moves and glows,夜蝠だ · 動く光に噛みつく,夜蝠 · 专咬会动的光,夜蝠 · 專咬會動的光
VOICE_MEET_EMBER_1,꺼지지 않은 불씨다 · 타는 데 동무를 구한다,A cinder unquenched · it wants company in burning,消え残った火種だ · 燃える仲間を求めている,没熄透的火种 · 想找个一起烧的伴,沒熄透的火種 · 想找個一起燒的伴
VOICE_MEET_CASTER_1,주목을 구해 주문을 외운다 · 그 탄은 기도다,It chants to be noticed · its shots are prayers,注目を求めて呪文を唱える · その弾は祈りだ,它念咒求关注 · 那些弹是祷告,它念咒求關注 · 那些彈是禱告
VOICE_MEET_WEAVER_1,빛 위에 어둠을 깁는다 · 꿰매는 중이라 믿는다,Stitching dark over light · it thinks it mends,光の上に闇を縫う · 繕っているつもりだ,它把黑暗缝在光上 · 以为自己在缝补,它把黑暗縫在光上 · 以為自己在縫補
VOICE_MEET_STALKER_1,지기였던 걸 기억한다 · 잃은 불을 사냥한다,Once a keeper · it hunts the flame it lost,守り人だったことを覚えている · 失くした火を狩る,它记得自己当过守烽人 · 在猎它弄丢的火,它記得自己當過守烽人 · 在獵它弄丟的火
VOICE_MEET_SWARM_1,날개 달린 허기다 · 생각 없이 입만 있다,Hunger with wings · mouths without minds,翼ある飢えだ · 考えはなく口だけだ,长翅膀的饥饿 · 没脑子只有嘴,長翅膀的飢餓 · 沒腦子只有嘴
VOICE_MEET_WISP_1,호기심 많은 불씨 · 몰리면 무서워 쏜다,Curious little flame · it stings when crowded,好奇心の強い小さな火 · 群れると怖くて刺す,好奇的小火 · 被围住吓到就蜇人,好奇的小火 · 被圍住嚇到就蜇人
VOICE_MEET_GUARDIAN_FOREST_1,숲이 빚은 추격자 · 돌진도 빛의 기억이다,Forest pursuer · even its charges remember light,森が練った追跡者 · 突進すら光の記憶だ,森林捏出的追猎者 · 连冲撞都是光的记忆,森林捏出的追獵者 · 連衝撞都是光的記憶
VOICE_MEET_GUARDIAN_FIELD_1,들에 깔린 빛이 날개가 됐다 · 쏘는 것도 빛이다,Field light grew wings · its shots are light too,野に散った光が翼になった · 放つ弾も光だ,撒在原野的光长出了翅膀 · 射出的也是光,撒在原野的光長出了翅膀 · 射出的也是光
VOICE_MEET_GUARDIAN_CAMP_1,화덕의 불을 삼킨 장군 · 갑옷 아래 아직 온기가 있다,Hearthswallower · warm under the armor,かまどの火を飲んだ将 · 甲の下はまだ温かい,吞了灶火的将军 · 铠甲下面还暖着,吞了灶火的將軍 · 盔甲下面還暖著
VOICE_MEET_GUARDIAN_FOREST_THORN_1,가시왕 · 오래된 빛일수록 가시가 깊다,The Thorn King · older light · deeper thorns,茨王 · 古い光ほど茨は深い,荆棘王 · 光越老刺越深,荊棘王 · 光越老刺越深
VOICE_MEET_GUARDIAN_FIELD_STORM_1,폭풍의 날개 · 화가 난 밤이 벼락을 얹었다,Storm wings · the angry night loaded lightning,嵐の翼 · 怒った夜が雷を載せた,风暴之翼 · 发怒的夜装上了雷,風暴之翼 · 發怒的夜裝上了雷
VOICE_MEET_GUARDIAN_CAMP_SIEGE_1,공성 장군 · 문을 두드리던 게 이제 문을 부순다,Siege general · what knocked now breaks in,攻城将 · 叩いていたものが今は押し入る,攻城将军 · 从前敲门的如今破门,攻城將軍 · 從前敲門的如今破門
VOICE_CYCLE_3,지도 밖 밤은 깊이를 모른다 · 한 선씩 긋자,No bottom out here · draw one line at a time,地図外れの夜に底はない · 一本ずつ線を引こう,地图外的夜没底 · 一条一条画线吧,地圖外的夜沒底 · 一條一條畫線吧
VOICE_CYCLE_4,꺼진 봉화가 밤을 먹인다 · 굶겨라,Each snuffed beacon feeds the night · starve it,消える烽火が夜を肥やす · 飢えさせろ,灭一座烽火夜就饱一分 · 饿着它,滅一座烽火夜就飽一分 · 餓著它
VOICE_CYCLE_5,수호자도 지친다 · 빛은 지치지 않는다,Even guardians tire · light does not,守護者も疲れる · 光は疲れない,守护者也会累 · 光不会,守護者也會累 · 光不會
VOICE_CYCLE_6,몇 번째 밤인지 세지 마라 · 선이 몇 개인지 세라,Do not count the nights · count the lines,夜を数えるな · 線を数えろ,别数第几夜 · 数几条线,別數第幾夜 · 數幾條線
VOICE_CALL_DARK_1,어둠을 부른다 · 날개들이 대답한다,It calls the dark · wings answer,闇を呼ぶ · 翼が答える,它在喊黑暗 · 翅膀应了,它在喊黑暗 · 翅膀應了
VOICE_CALL_DARK_2,혼자 못 이기겠지 · 그래서 무리를 부른다,It cannot win alone · so it calls the pack,独りでは勝てない · だから群れを呼ぶ,单挑赢不了 · 所以喊群,單挑贏不了 · 所以喊群
33 changes: 25 additions & 8 deletions apps/game/scenes/ui/result_panel.tscn
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,31 @@ anchor_bottom = 0.5
offset_top = -150.0
offset_bottom = -112.0
grow_horizontal = 2
grow_vertical = 2
grow_vertical = 0
mouse_filter = 2
theme_override_colors/font_color = Color(1, 0.95, 0.82, 1)
theme_override_fonts/font = ExtResource("font")
theme_override_font_sizes/font_size = 33
text = "RESULT_WIN"
horizontal_alignment = 1

[node name="Epitaph" type="Label" parent="."]
layout_mode = 1
anchors_preset = 14
anchor_top = 0.5
anchor_right = 1.0
anchor_bottom = 0.5
offset_top = -112.0
offset_bottom = -94.0
grow_horizontal = 2
grow_vertical = 2
mouse_filter = 2
theme_override_colors/font_color = Color(0.7, 0.75, 0.88, 1)
theme_override_fonts/font = ExtResource("font")
theme_override_font_sizes/font_size = 11
horizontal_alignment = 1
vertical_alignment = 1

[node name="Detail" type="Label" parent="."]
layout_mode = 1
anchors_preset = 8
Expand All @@ -56,7 +73,7 @@ offset_top = -94.0
offset_right = 47.0
offset_bottom = 16.0
grow_horizontal = 2
grow_vertical = 2
grow_vertical = 1
mouse_filter = 2
theme_override_colors/font_color = Color(0.83, 0.87, 0.96, 1)
theme_override_fonts/font = ExtResource("font")
Expand Down Expand Up @@ -90,8 +107,8 @@ anchors_preset = 14
anchor_top = 0.5
anchor_right = 1.0
anchor_bottom = 0.5
offset_top = 25.0
offset_bottom = 43.0
offset_top = 33.0
offset_bottom = 51.0
grow_horizontal = 2
grow_vertical = 2
mouse_filter = 2
Expand All @@ -106,8 +123,8 @@ anchors_preset = 14
anchor_top = 0.5
anchor_right = 1.0
anchor_bottom = 0.5
offset_top = 46.0
offset_bottom = 64.0
offset_top = 54.0
offset_bottom = 72.0
grow_horizontal = 2
grow_vertical = 2
mouse_filter = 2
Expand All @@ -124,9 +141,9 @@ anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
offset_left = -279.0
offset_top = 76.0
offset_top = 84.0
offset_right = 279.0
offset_bottom = 108.0
offset_bottom = 116.0
grow_horizontal = 2
grow_vertical = 2
theme_override_constants/separation = 10
Expand Down
75 changes: 70 additions & 5 deletions apps/game/scripts/actors/spirit.gd
Original file line number Diff line number Diff line change
Expand Up @@ -83,14 +83,23 @@ const FOREST_FOLLOWUP_WINDUP: float = 0.46
const FOREST_CHARGE_SECONDS: float = 0.40
const FOREST_BETWEEN_CHARGES: float = 0.24
const FOREST_RECOVER_SECONDS: float = 0.82
## After the charge combo lands, slam a shockwave ring. Charges punish standing
## still; the ring punishes the sidestep they taught — hold ground in the gap.
const FOREST_RING_WINDUP: float = 0.78

const FIELD_STRAFE_SECONDS: float = 2.15
const FIELD_VOLLEY_WINDUP: float = 0.78
const FIELD_RECOVER_SECONDS: float = 0.82
## Strafe used to be dead time. One aimed bolt per strafe keeps the dodge honest.
const FIELD_POKE_INTERVAL: float = 2.1
const FIELD_POKE_WINDUP: float = 0.35

const CAMP_APPROACH_SECONDS: float = 2.35
const CAMP_VOLLEY_WINDUP: float = 1.0
const CAMP_RECOVER_SECONDS: float = 1.45
## The second fan re-aims at where the player fled. One fan is a checkpoint;
## two is a question.
const CAMP_FOLLOWUP_WINDUP: float = 0.55

enum GuardianMove {
CHASE,
Expand Down Expand Up @@ -162,6 +171,9 @@ var _guardian_move_haste: float = 1.0
var _guardian_combo_left: int = 0
## Field barrage 0=cross, 1=radial with a safe gap.
var _guardian_pattern: int = 0
## Time until the field guardian's next aimed bolt while strafing.
## Starts half-ready so the opening strafe pokes once, never on frame one.
var _guardian_poke_left: float = FIELD_POKE_INTERVAL * 0.5
## Short white edge flash when camp armor blocks damage.
var _camp_guard_flash: float = 0.0
var _telegraph: Telegraph = Telegraph.NONE
Expand Down Expand Up @@ -884,9 +896,27 @@ func _do_guardian_forest(delta: float, to_player: Vector2) -> Vector2:
velocity = _locked * _guardian_charge_speed()
if _guardian_left <= 0.0:
velocity *= 0.2
var rest: float = FOREST_BETWEEN_CHARGES \
if _guardian_combo_left > 0 else FOREST_RECOVER_SECONDS
_start_guardian_move(GuardianMove.RECOVER, rest)
if _guardian_combo_left > 0:
_start_guardian_move(
GuardianMove.RECOVER, FOREST_BETWEEN_CHARGES)
else:
# Combo spent. Slam the ring before resting — the gap faces
# the player, so the answer is the opposite of the charges.
_locked = inward
_guardian_pattern = 1
_start_guardian_move(
GuardianMove.VOLLEY_WINDUP, FOREST_RING_WINDUP)
return _locked

GuardianMove.VOLLEY_WINDUP:
velocity = velocity.lerp(Vector2.ZERO, 7.0 * delta)
_set_telegraph(Telegraph.VOLLEY, _locked,
1.0 - _guardian_left / _guardian_move_duration)
if _guardian_left <= 0.0:
_fire_forest_ring()
_clear_telegraph()
_start_guardian_move(
GuardianMove.RECOVER, FOREST_RECOVER_SECONDS)
return _locked

_:
Expand All @@ -911,10 +941,22 @@ func _do_guardian_field(delta: float, to_player: Vector2) -> Vector2:
match _guardian_move:
GuardianMove.CHASE:
var wish: Vector2 = _field_strafe(delta, to_player)
# Transition first. When the poke timer and the strafe end on the
# same tick, firing the poke as well stacks a bolt onto the volley.
if _guardian_left <= 0.0:
_locked = inward
_start_guardian_move(
GuardianMove.VOLLEY_WINDUP, FIELD_VOLLEY_WINDUP)
return wish
_guardian_poke_left -= delta * _guardian_move_haste
if _guardian_poke_left <= FIELD_POKE_WINDUP \
and _guardian_poke_left > 0.0:
_set_telegraph(Telegraph.AIM, inward,
1.0 - _guardian_poke_left / FIELD_POKE_WINDUP)
if _guardian_poke_left <= 0.0:
_clear_telegraph()
_fire(inward)
_guardian_poke_left = FIELD_POKE_INTERVAL
return wish

GuardianMove.VOLLEY_WINDUP:
Expand All @@ -932,6 +974,7 @@ func _do_guardian_field(delta: float, to_player: Vector2) -> Vector2:
_:
velocity = velocity.lerp(Vector2.ZERO, 5.0 * delta)
if _guardian_left <= 0.0:
_guardian_poke_left = FIELD_POKE_INTERVAL * 0.5
_start_guardian_move(
GuardianMove.CHASE, FIELD_STRAFE_SECONDS)
return inward
Expand Down Expand Up @@ -965,6 +1008,7 @@ func _do_guardian_camp(delta: float, to_player: Vector2) -> Vector2:
kind.turn_rate * delta)
if _guardian_left <= 0.0:
_locked = inward
_guardian_combo_left = 1
_start_guardian_move(
GuardianMove.VOLLEY_WINDUP, CAMP_VOLLEY_WINDUP)
return inward
Expand All @@ -976,8 +1020,14 @@ func _do_guardian_camp(delta: float, to_player: Vector2) -> Vector2:
if _guardian_left <= 0.0:
_fire_camp_fan()
_clear_telegraph()
_start_guardian_move(
GuardianMove.RECOVER, _camp_recover_seconds())
if _guardian_combo_left > 0:
_guardian_combo_left -= 1
_locked = inward
_start_guardian_move(
GuardianMove.VOLLEY_WINDUP, CAMP_FOLLOWUP_WINDUP)
else:
_start_guardian_move(
GuardianMove.RECOVER, _camp_recover_seconds())
return _locked

_:
Expand Down Expand Up @@ -1024,6 +1074,21 @@ func _fire_field_volley() -> void:
bolt.set_speed_scale(0.72)


## Shockwave ring after the charge combo. Fewer spokes than the field radial,
## same promise: the gap faces the player, so the dodge is to hold ground.
func _fire_forest_ring() -> void:
var base: float = _locked.angle()
var extra: int = maxi(guardian_cycle - 1, 0)
if kind != null:
extra += kind.guardian_volley_extra
var spokes: int = 8 + mini(extra, 4 + _late_cycles())
var hole: int = 2 if extra <= 0 else 1
for i in spokes:
if i <= hole or i >= spokes - hole:
continue
_fire(Vector2.RIGHT.rotated(base + TAU * float(i) / float(spokes)))


## Aim fan. Higher cycles add spokes and narrow the safe lane.
func _fire_camp_fan() -> void:
var base: float = _locked.angle()
Expand Down
Loading
Loading