Sequence enemy turn presentation
This commit is contained in:
@@ -56,6 +56,8 @@ const MINIMAP_PADDING := 8.0
|
||||
const MINIMAP_TITLE_HEIGHT := 18.0
|
||||
const MINIMAP_MARGIN := Vector2(18, 18)
|
||||
const MAP_FOCUS_MARGIN := 96.0
|
||||
const BATTLE_PRESENTATION_STEP_GAP := 0.08
|
||||
const BATTLE_PRESENTATION_FEEDBACK_LAG := 0.10
|
||||
const UNIT_MOVE_ANIMATION_DURATION := 0.18
|
||||
const TARGET_PREVIEW_BADGE_SIZE := Vector2(104, 22)
|
||||
const LOW_HP_WARNING_RATIO := 0.35
|
||||
@@ -431,6 +433,9 @@ var floating_texts: Array[Dictionary] = []
|
||||
var objective_notice_timer := 0.0
|
||||
var unit_motion_by_unit: Dictionary = {}
|
||||
var unit_action_motion_by_unit: Dictionary = {}
|
||||
var battle_presentation_sequence_active := false
|
||||
var battle_presentation_delay_cursor := 0.0
|
||||
var battle_presentation_feedback_delay_by_unit := {}
|
||||
var battle_background_path := ""
|
||||
var battle_background_texture: Texture2D
|
||||
var ui_regular_font: Font
|
||||
@@ -2628,6 +2633,7 @@ func _handle_key(event: InputEventKey) -> void:
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
var needs_redraw := false
|
||||
var had_sequence_presentation := _has_active_battle_presentation_sequence()
|
||||
if battle_started and state.battle_status == BattleState.STATUS_ACTIVE and not _is_dialogue_visible():
|
||||
needs_redraw = true
|
||||
if _update_edge_scroll(delta):
|
||||
@@ -2642,6 +2648,7 @@ func _process(delta: float) -> void:
|
||||
for popup in floating_texts:
|
||||
var next_popup := popup.duplicate(true)
|
||||
next_popup["age"] = float(next_popup.get("age", 0.0)) + delta
|
||||
_activate_presentation_focus_if_ready(next_popup)
|
||||
if float(next_popup["age"]) < float(next_popup.get("duration", FLOATING_TEXT_LIFETIME)):
|
||||
active_texts.append(next_popup)
|
||||
floating_texts = active_texts
|
||||
@@ -2653,6 +2660,7 @@ func _process(delta: float) -> void:
|
||||
var motion: Dictionary = unit_motion_by_unit[unit_id]
|
||||
var next_motion := motion.duplicate(true)
|
||||
next_motion["age"] = float(next_motion.get("age", 0.0)) + delta
|
||||
_activate_presentation_focus_if_ready(next_motion)
|
||||
if float(next_motion["age"]) < float(next_motion.get("duration", UNIT_MOVE_ANIMATION_DURATION)):
|
||||
active_motion[unit_id] = next_motion
|
||||
unit_motion_by_unit = active_motion
|
||||
@@ -2664,6 +2672,7 @@ func _process(delta: float) -> void:
|
||||
var motion: Dictionary = unit_action_motion_by_unit[unit_id]
|
||||
var next_motion := motion.duplicate(true)
|
||||
next_motion["age"] = float(next_motion.get("age", 0.0)) + delta
|
||||
_activate_presentation_focus_if_ready(next_motion)
|
||||
if float(next_motion["age"]) < float(next_motion.get("duration", UNIT_ATTACK_ANIMATION_DURATION)):
|
||||
active_actions[unit_id] = next_motion
|
||||
unit_action_motion_by_unit = active_actions
|
||||
@@ -2671,6 +2680,10 @@ func _process(delta: float) -> void:
|
||||
|
||||
if needs_redraw:
|
||||
queue_redraw()
|
||||
if had_sequence_presentation and not _has_active_battle_presentation_sequence():
|
||||
if status_label != null:
|
||||
_update_hud()
|
||||
queue_redraw()
|
||||
|
||||
|
||||
func _can_select(unit: Dictionary) -> bool:
|
||||
@@ -3586,12 +3599,13 @@ func _unit_pixel_facing(unit: Dictionary) -> int:
|
||||
var unit_id := str(unit.get("id", ""))
|
||||
if not unit_id.is_empty() and unit_action_motion_by_unit.has(unit_id):
|
||||
var action_motion: Dictionary = unit_action_motion_by_unit[unit_id]
|
||||
var from_cell: Vector2i = action_motion.get("from", unit.get("pos", Vector2i.ZERO))
|
||||
var to_cell: Vector2i = action_motion.get("to", from_cell)
|
||||
if to_cell.x < from_cell.x:
|
||||
return -1
|
||||
if to_cell.x > from_cell.x:
|
||||
return 1
|
||||
if float(action_motion.get("age", 0.0)) >= 0.0:
|
||||
var from_cell: Vector2i = action_motion.get("from", unit.get("pos", Vector2i.ZERO))
|
||||
var to_cell: Vector2i = action_motion.get("to", from_cell)
|
||||
if to_cell.x < from_cell.x:
|
||||
return -1
|
||||
if to_cell.x > from_cell.x:
|
||||
return 1
|
||||
if unit.has("facing_x"):
|
||||
return state.unit_facing_x(unit)
|
||||
if unit.get("team", "") == BattleState.TEAM_ENEMY:
|
||||
@@ -4098,6 +4112,8 @@ func _draw_attack_effects() -> void:
|
||||
var motion: Dictionary = unit_action_motion_by_unit[unit_id]
|
||||
var duration: float = maxf(0.01, float(motion.get("duration", UNIT_ATTACK_ANIMATION_DURATION)))
|
||||
var age := float(motion.get("age", 0.0))
|
||||
if age < 0.0:
|
||||
continue
|
||||
var progress := clampf(age / duration, 0.0, 1.0)
|
||||
var profile := str(motion.get("profile", "slash"))
|
||||
var peak := _action_motion_effect_peak(profile, progress)
|
||||
@@ -4311,6 +4327,8 @@ func _draw_floating_texts() -> void:
|
||||
for popup in floating_texts:
|
||||
var duration: float = maxf(0.01, float(popup.get("duration", FLOATING_TEXT_LIFETIME)))
|
||||
var age := float(popup.get("age", 0.0))
|
||||
if age < 0.0:
|
||||
continue
|
||||
var progress := clampf(age / duration, 0.0, 1.0)
|
||||
var alpha := 1.0 - progress
|
||||
var color := _floating_text_color(str(popup.get("kind", "")))
|
||||
@@ -4844,6 +4862,67 @@ func _focus_board_on_cell(cell: Vector2i, force_center := false) -> bool:
|
||||
return true
|
||||
|
||||
|
||||
func _activate_presentation_focus_if_ready(entry: Dictionary) -> void:
|
||||
if bool(entry.get("focused", false)):
|
||||
return
|
||||
if float(entry.get("age", 0.0)) < 0.0:
|
||||
return
|
||||
var focus_cell: Vector2i = entry.get("focus_cell", Vector2i(-1, -1))
|
||||
if state.is_inside(focus_cell):
|
||||
_focus_board_on_cell(focus_cell, bool(entry.get("force_focus", false)))
|
||||
var sfx_key := str(entry.get("sfx", ""))
|
||||
if not sfx_key.is_empty() and not bool(entry.get("sfx_played", false)) and is_inside_tree():
|
||||
_play_sfx(sfx_key)
|
||||
entry["sfx_played"] = true
|
||||
entry["focused"] = true
|
||||
|
||||
|
||||
func _begin_battle_presentation_sequence() -> void:
|
||||
battle_presentation_sequence_active = true
|
||||
battle_presentation_delay_cursor = 0.0
|
||||
battle_presentation_feedback_delay_by_unit.clear()
|
||||
|
||||
|
||||
func _end_battle_presentation_sequence() -> void:
|
||||
battle_presentation_sequence_active = false
|
||||
battle_presentation_delay_cursor = 0.0
|
||||
|
||||
|
||||
func _presentation_delay_for_step(duration: float) -> float:
|
||||
if not battle_presentation_sequence_active:
|
||||
return 0.0
|
||||
var delay := battle_presentation_delay_cursor
|
||||
battle_presentation_delay_cursor += maxf(0.01, duration) + BATTLE_PRESENTATION_STEP_GAP
|
||||
return delay
|
||||
|
||||
|
||||
func _presentation_feedback_delay_for_unit(unit_id: String) -> float:
|
||||
if battle_presentation_feedback_delay_by_unit.has(unit_id):
|
||||
return maxf(0.0, float(battle_presentation_feedback_delay_by_unit[unit_id]))
|
||||
if battle_presentation_sequence_active:
|
||||
return maxf(0.0, battle_presentation_delay_cursor)
|
||||
return 0.0
|
||||
|
||||
|
||||
func _clear_presentation_state() -> void:
|
||||
battle_presentation_sequence_active = false
|
||||
battle_presentation_delay_cursor = 0.0
|
||||
battle_presentation_feedback_delay_by_unit.clear()
|
||||
|
||||
|
||||
func _has_active_battle_presentation_sequence() -> bool:
|
||||
for motion in unit_motion_by_unit.values():
|
||||
if typeof(motion) == TYPE_DICTIONARY and bool((motion as Dictionary).get("sequence", false)):
|
||||
return true
|
||||
for motion in unit_action_motion_by_unit.values():
|
||||
if typeof(motion) == TYPE_DICTIONARY and bool((motion as Dictionary).get("sequence", false)):
|
||||
return true
|
||||
for popup in floating_texts:
|
||||
if typeof(popup) == TYPE_DICTIONARY and bool((popup as Dictionary).get("sequence", false)):
|
||||
return true
|
||||
return false
|
||||
|
||||
|
||||
func _is_minimap_visible() -> bool:
|
||||
return battle_started and state.map_size.x > 0 and state.map_size.y > 0 and state.battle_status == BattleState.STATUS_ACTIVE
|
||||
|
||||
@@ -5788,14 +5867,20 @@ func _on_combat_feedback_requested(unit_id: String, text: String, kind: String)
|
||||
var cell: Vector2i = unit.get("pos", Vector2i(-1, -1))
|
||||
if not state.is_inside(cell):
|
||||
return
|
||||
_focus_board_on_cell(cell, true)
|
||||
var delay := _presentation_feedback_delay_for_unit(unit_id)
|
||||
if delay <= 0.0:
|
||||
_focus_board_on_cell(cell, true)
|
||||
var jitter := float((floating_texts.size() % 3) - 1) * 12.0
|
||||
floating_texts.append({
|
||||
"text": text,
|
||||
"kind": kind,
|
||||
"pos": _floating_text_origin(cell) + Vector2(jitter, 0),
|
||||
"age": 0.0,
|
||||
"duration": FLOATING_TEXT_LIFETIME
|
||||
"age": -delay,
|
||||
"duration": FLOATING_TEXT_LIFETIME,
|
||||
"focus_cell": cell,
|
||||
"force_focus": true,
|
||||
"focused": delay <= 0.0,
|
||||
"sequence": delay > 0.0
|
||||
})
|
||||
queue_redraw()
|
||||
|
||||
@@ -5803,12 +5888,20 @@ func _on_combat_feedback_requested(unit_id: String, text: String, kind: String)
|
||||
func _on_unit_motion_requested(unit_id: String, from_cell: Vector2i, to_cell: Vector2i) -> void:
|
||||
if unit_id.is_empty() or from_cell == to_cell:
|
||||
return
|
||||
_focus_board_on_cell(to_cell)
|
||||
var duration := UNIT_MOVE_ANIMATION_DURATION
|
||||
var delay := _presentation_delay_for_step(duration)
|
||||
battle_presentation_feedback_delay_by_unit[unit_id] = delay + duration + BATTLE_PRESENTATION_FEEDBACK_LAG
|
||||
if delay <= 0.0:
|
||||
_focus_board_on_cell(to_cell)
|
||||
unit_motion_by_unit[unit_id] = {
|
||||
"from": from_cell,
|
||||
"to": to_cell,
|
||||
"age": 0.0,
|
||||
"duration": UNIT_MOVE_ANIMATION_DURATION
|
||||
"age": -delay,
|
||||
"duration": duration,
|
||||
"focus_cell": to_cell,
|
||||
"force_focus": false,
|
||||
"focused": delay <= 0.0,
|
||||
"sequence": battle_presentation_sequence_active or delay > 0.0
|
||||
}
|
||||
queue_redraw()
|
||||
|
||||
@@ -5821,7 +5914,12 @@ func _on_unit_action_motion_requested(unit_id: String, from_cell: Vector2i, to_c
|
||||
if from_cell == to_cell and not _action_motion_allows_same_cell(profile):
|
||||
return
|
||||
var focus_cell := to_cell if state.is_inside(to_cell) else from_cell
|
||||
_focus_board_on_cell(focus_cell, true)
|
||||
var duration := _action_motion_duration(profile)
|
||||
var delay := _presentation_delay_for_step(duration)
|
||||
battle_presentation_feedback_delay_by_unit[unit_id] = delay + duration * 0.58
|
||||
if delay <= 0.0:
|
||||
_focus_board_on_cell(focus_cell, true)
|
||||
var sfx_key := _action_motion_sfx(profile)
|
||||
unit_action_motion_by_unit[unit_id] = {
|
||||
"from": from_cell,
|
||||
"to": to_cell,
|
||||
@@ -5832,11 +5930,17 @@ func _on_unit_action_motion_requested(unit_id: String, from_cell: Vector2i, to_c
|
||||
"vfx_signature": _action_motion_vfx_signature(profile),
|
||||
"impact_cue": _action_motion_impact_cue(profile),
|
||||
"impact_radius": _action_motion_impact_radius(profile),
|
||||
"age": 0.0,
|
||||
"duration": _action_motion_duration(profile)
|
||||
"age": -delay,
|
||||
"duration": duration,
|
||||
"focus_cell": focus_cell,
|
||||
"force_focus": true,
|
||||
"focused": delay <= 0.0,
|
||||
"sfx": sfx_key,
|
||||
"sfx_played": delay <= 0.0,
|
||||
"sequence": battle_presentation_sequence_active or delay > 0.0
|
||||
}
|
||||
if is_inside_tree():
|
||||
_play_sfx(_action_motion_sfx(profile))
|
||||
if delay <= 0.0 and is_inside_tree():
|
||||
_play_sfx(sfx_key)
|
||||
queue_redraw()
|
||||
|
||||
|
||||
@@ -6313,7 +6417,9 @@ func _on_end_turn_pressed() -> void:
|
||||
_hide_item_menu()
|
||||
_hide_equip_menu()
|
||||
_hide_post_move_picker()
|
||||
_begin_battle_presentation_sequence()
|
||||
state.end_player_turn()
|
||||
_end_battle_presentation_sequence()
|
||||
|
||||
|
||||
func _on_tactic_pressed() -> void:
|
||||
@@ -6428,6 +6534,8 @@ func _on_restart_pressed() -> void:
|
||||
post_battle_dialogue_started = false
|
||||
floating_texts.clear()
|
||||
unit_motion_by_unit.clear()
|
||||
unit_action_motion_by_unit.clear()
|
||||
_clear_presentation_state()
|
||||
shop_sell_mode = false
|
||||
selected_skill_id = ""
|
||||
selected_item_id = ""
|
||||
@@ -6474,6 +6582,8 @@ func _load_scenario(scenario_id: String) -> void:
|
||||
post_battle_dialogue_started = false
|
||||
floating_texts.clear()
|
||||
unit_motion_by_unit.clear()
|
||||
unit_action_motion_by_unit.clear()
|
||||
_clear_presentation_state()
|
||||
selected_skill_id = ""
|
||||
selected_item_id = ""
|
||||
_clear_pending_move_state()
|
||||
@@ -7525,6 +7635,8 @@ func _reload_from_campaign_state() -> void:
|
||||
post_battle_dialogue_started = false
|
||||
floating_texts.clear()
|
||||
unit_motion_by_unit.clear()
|
||||
unit_action_motion_by_unit.clear()
|
||||
_clear_presentation_state()
|
||||
selected_skill_id = ""
|
||||
selected_item_id = ""
|
||||
_clear_pending_move_state()
|
||||
@@ -8278,7 +8390,13 @@ func _sale_price_for_item(item: Dictionary) -> int:
|
||||
|
||||
|
||||
func _is_input_locked() -> bool:
|
||||
return campaign_complete_screen or _is_dialogue_visible() or not battle_started or state.battle_status != BattleState.STATUS_ACTIVE
|
||||
return (
|
||||
campaign_complete_screen
|
||||
or _is_dialogue_visible()
|
||||
or _has_active_battle_presentation_sequence()
|
||||
or not battle_started
|
||||
or state.battle_status != BattleState.STATUS_ACTIVE
|
||||
)
|
||||
|
||||
|
||||
func _apply_battle_result_once() -> void:
|
||||
@@ -8760,6 +8878,8 @@ func _show_campaign_complete() -> void:
|
||||
post_battle_dialogue_started = true
|
||||
floating_texts.clear()
|
||||
unit_motion_by_unit.clear()
|
||||
unit_action_motion_by_unit.clear()
|
||||
_clear_presentation_state()
|
||||
move_cells.clear()
|
||||
attack_cells.clear()
|
||||
skill_cells.clear()
|
||||
|
||||
Reference in New Issue
Block a user