Add tactic cast motion effects
This commit is contained in:
@@ -122,6 +122,7 @@ Godot 4 tactical RPG prototype inspired by classic turn-based Romance of the Thr
|
|||||||
- Floating combat text for damage, recovery, misses, support effects, poison ticks, action locks, level-ups, and promotions.
|
- Floating combat text for damage, recovery, misses, support effects, poison ticks, action locks, level-ups, and promotions.
|
||||||
- Short unit slide animation for board movement.
|
- Short unit slide animation for board movement.
|
||||||
- Board attacks now request class-specific motion profiles, with infantry strikes, archer traces, cavalry charges, command flashes, and heavy warrior/bandit impacts drawn by the battle scene.
|
- Board attacks now request class-specific motion profiles, with infantry strikes, archer traces, cavalry charges, command flashes, and heavy warrior/bandit impacts drawn by the battle scene.
|
||||||
|
- Damage, healing, and support tactics now request distinct cast motion profiles with flare, sigil, and banner-style battlefield effects.
|
||||||
- Battle maps can use a high-resolution background image with translucent terrain overlays, terrain-specific detail patterns, connected roads, shoreline/castle/forest/hill edge blends, transparent generic-enemy class art, and token marks.
|
- Battle maps can use a high-resolution background image with translucent terrain overlays, terrain-specific detail patterns, connected roads, shoreline/castle/forest/hill edge blends, transparent generic-enemy class art, and token marks.
|
||||||
- Hover target preview badges for attacks, tactics, and items.
|
- Hover target preview badges for attacks, tactics, and items.
|
||||||
- Low-HP board unit warning rings, HP bar color states, and board status pips plus HUD duration summaries for support, debuff, poison, seal, snare, and disarm effects.
|
- Low-HP board unit warning rings, HP bar color states, and board status pips plus HUD duration summaries for support, debuff, poison, seal, snare, and disarm effects.
|
||||||
|
|||||||
@@ -754,6 +754,7 @@ func try_cast_selected_skill(skill_id: String, target_cell: Vector2i) -> bool:
|
|||||||
if skill_kind == "support" and not _skill_has_support_effects(skill):
|
if skill_kind == "support" and not _skill_has_support_effects(skill):
|
||||||
return false
|
return false
|
||||||
|
|
||||||
|
_emit_skill_motion(caster, target_cell, skill_kind)
|
||||||
caster["mp"] = max(0, int(caster.get("mp", 0)) - int(skill.get("mp_cost", 0)))
|
caster["mp"] = max(0, int(caster.get("mp", 0)) - int(skill.get("mp_cost", 0)))
|
||||||
if skill_kind == "heal":
|
if skill_kind == "heal":
|
||||||
var total_healed := 0
|
var total_healed := 0
|
||||||
@@ -3153,6 +3154,7 @@ func _execute_ai_skill_action(caster: Dictionary, action: Dictionary) -> bool:
|
|||||||
if skill_kind == "support" and not _skill_has_support_effects(skill):
|
if skill_kind == "support" and not _skill_has_support_effects(skill):
|
||||||
return false
|
return false
|
||||||
|
|
||||||
|
_emit_skill_motion(caster, target_cell, skill_kind)
|
||||||
caster["mp"] = max(0, int(caster.get("mp", 0)) - int(skill.get("mp_cost", 0)))
|
caster["mp"] = max(0, int(caster.get("mp", 0)) - int(skill.get("mp_cost", 0)))
|
||||||
if skill_kind == "heal":
|
if skill_kind == "heal":
|
||||||
var total_healed := 0
|
var total_healed := 0
|
||||||
@@ -3267,6 +3269,17 @@ func _resolve_skill_damage(caster: Dictionary, target: Dictionary, skill: Dictio
|
|||||||
return false
|
return false
|
||||||
|
|
||||||
|
|
||||||
|
func _emit_skill_motion(caster: Dictionary, target_cell: Vector2i, skill_kind: String) -> void:
|
||||||
|
if caster.is_empty() or not is_inside(target_cell):
|
||||||
|
return
|
||||||
|
unit_action_motion_requested.emit(
|
||||||
|
str(caster.get("id", "")),
|
||||||
|
caster.get("pos", Vector2i(-1, -1)),
|
||||||
|
target_cell,
|
||||||
|
"skill_%s" % skill_kind
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
func _handle_unit_defeated(unit: Dictionary) -> void:
|
func _handle_unit_defeated(unit: Dictionary) -> void:
|
||||||
if unit.is_empty():
|
if unit.is_empty():
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ const UNIT_CAVALRY_ATTACK_DURATION := 0.42
|
|||||||
const UNIT_ARROW_ATTACK_DURATION := 0.36
|
const UNIT_ARROW_ATTACK_DURATION := 0.36
|
||||||
const UNIT_COMMAND_ATTACK_DURATION := 0.38
|
const UNIT_COMMAND_ATTACK_DURATION := 0.38
|
||||||
const UNIT_HEAVY_ATTACK_DURATION := 0.34
|
const UNIT_HEAVY_ATTACK_DURATION := 0.34
|
||||||
|
const UNIT_TACTIC_CAST_DURATION := 0.42
|
||||||
const DIALOGUE_PANEL_POSITION := Vector2(96, 448)
|
const DIALOGUE_PANEL_POSITION := Vector2(96, 448)
|
||||||
const DIALOGUE_PANEL_SIZE := Vector2(1040, 210)
|
const DIALOGUE_PANEL_SIZE := Vector2(1040, 210)
|
||||||
const DIALOGUE_PORTRAIT_SIZE := Vector2(164, 186)
|
const DIALOGUE_PORTRAIT_SIZE := Vector2(164, 186)
|
||||||
@@ -2051,6 +2052,12 @@ func _draw_action_effect(profile: String, start: Vector2, end: Vector2, progress
|
|||||||
_draw_command_attack_effect(start, end, progress, peak, team_color)
|
_draw_command_attack_effect(start, end, progress, peak, team_color)
|
||||||
elif profile == "heavy_strike":
|
elif profile == "heavy_strike":
|
||||||
_draw_heavy_attack_effect(end, peak, team_color)
|
_draw_heavy_attack_effect(end, peak, team_color)
|
||||||
|
elif profile == "tactic_damage":
|
||||||
|
_draw_tactic_damage_effect(start, end, progress, peak, team_color)
|
||||||
|
elif profile == "tactic_heal":
|
||||||
|
_draw_tactic_heal_effect(start, end, progress, peak, team_color)
|
||||||
|
elif profile == "tactic_support":
|
||||||
|
_draw_tactic_support_effect(start, end, progress, peak, team_color)
|
||||||
else:
|
else:
|
||||||
_draw_slash_attack_effect(end, peak, team_color)
|
_draw_slash_attack_effect(end, peak, team_color)
|
||||||
|
|
||||||
@@ -2119,6 +2126,47 @@ func _draw_heavy_attack_effect(end: Vector2, peak: float, team_color: Color) ->
|
|||||||
draw_arc(end, 10.0 + 18.0 * peak, 0.0, TAU, 48, Color(1.0, 0.68, 0.24, 0.26 * peak), 3.0)
|
draw_arc(end, 10.0 + 18.0 * peak, 0.0, TAU, 48, Color(1.0, 0.68, 0.24, 0.26 * peak), 3.0)
|
||||||
|
|
||||||
|
|
||||||
|
func _draw_tactic_damage_effect(start: Vector2, end: Vector2, progress: float, peak: float, team_color: Color) -> void:
|
||||||
|
var caster_color := team_color.lightened(0.58)
|
||||||
|
caster_color.a = 0.34 * peak
|
||||||
|
var fire_color := Color(1.0, 0.38, 0.18, 0.52 * peak)
|
||||||
|
draw_arc(start, 12.0 + 7.0 * peak, progress * TAU, progress * TAU + PI * 1.55, 36, caster_color, 2.0)
|
||||||
|
draw_line(start, end, Color(1.0, 0.62, 0.28, 0.30 * peak), 2.0)
|
||||||
|
for index in range(3):
|
||||||
|
var angle := progress * TAU + float(index) * TAU / 3.0
|
||||||
|
var spark := Vector2(cos(angle), sin(angle))
|
||||||
|
draw_line(end - spark * (5.0 + 4.0 * peak), end + spark * (12.0 + 6.0 * peak), fire_color, 2.0)
|
||||||
|
draw_circle(end, 6.0 + 10.0 * peak, Color(1.0, 0.50, 0.18, 0.18 * peak))
|
||||||
|
|
||||||
|
|
||||||
|
func _draw_tactic_heal_effect(start: Vector2, end: Vector2, progress: float, peak: float, team_color: Color) -> void:
|
||||||
|
var line_color := Color(0.48, 1.0, 0.60, 0.24 * peak)
|
||||||
|
if start.distance_to(end) > 0.01:
|
||||||
|
draw_line(start, end, line_color, 2.0)
|
||||||
|
var ring_color := Color(0.42, 1.0, 0.54, 0.42 * peak)
|
||||||
|
draw_arc(end, 9.0 + 12.0 * peak, -progress * TAU, -progress * TAU + PI * 1.70, 44, ring_color, 2.0)
|
||||||
|
draw_arc(end, 19.0 - 5.0 * peak, progress * TAU, progress * TAU + PI * 1.25, 40, Color(0.88, 1.0, 0.72, 0.32 * peak), 2.0)
|
||||||
|
draw_line(end + Vector2(-7, 0), end + Vector2(7, 0), Color(0.80, 1.0, 0.78, 0.54 * peak), 2.5)
|
||||||
|
draw_line(end + Vector2(0, -7), end + Vector2(0, 7), Color(0.80, 1.0, 0.78, 0.54 * peak), 2.5)
|
||||||
|
var caster_glow := team_color.lightened(0.62)
|
||||||
|
caster_glow.a = 0.20 * peak
|
||||||
|
draw_circle(start, 6.0 + 4.0 * peak, caster_glow)
|
||||||
|
|
||||||
|
|
||||||
|
func _draw_tactic_support_effect(start: Vector2, end: Vector2, progress: float, peak: float, team_color: Color) -> void:
|
||||||
|
var banner := team_color.lightened(0.48)
|
||||||
|
banner.a = 0.46 * peak
|
||||||
|
if start.distance_to(end) > 0.01:
|
||||||
|
draw_line(start, end, Color(1.0, 0.86, 0.30, 0.20 * peak), 1.5)
|
||||||
|
draw_line(end + Vector2(-10, -16), end + Vector2(-10, 12), banner, 2.5)
|
||||||
|
draw_rect(Rect2(end + Vector2(-8, -15), Vector2(20, 12)), Color(1.0, 0.82, 0.22, 0.28 * peak))
|
||||||
|
draw_arc(end, 12.0 + 8.0 * peak, progress * TAU, progress * TAU + PI * 1.45, 36, Color(1.0, 0.92, 0.38, 0.36 * peak), 2.0)
|
||||||
|
for index in range(3):
|
||||||
|
var angle := -PI * 0.5 + float(index - 1) * 0.52
|
||||||
|
var ray := Vector2(cos(angle), sin(angle))
|
||||||
|
draw_line(end + ray * 7.0, end + ray * (18.0 + 5.0 * peak), Color(1.0, 0.96, 0.62, 0.25 * peak), 1.5)
|
||||||
|
|
||||||
|
|
||||||
func _draw_slash_attack_effect(end: Vector2, peak: float, team_color: Color) -> void:
|
func _draw_slash_attack_effect(end: Vector2, peak: float, team_color: Color) -> void:
|
||||||
var flash := team_color.lightened(0.45)
|
var flash := team_color.lightened(0.45)
|
||||||
flash.a = 0.55 * peak
|
flash.a = 0.55 * peak
|
||||||
@@ -2164,6 +2212,15 @@ func _draw_action_impact_cue(cue: String, start: Vector2, end: Vector2, progress
|
|||||||
var angle := float(index) * PI * 0.5 + progress * PI * 0.25
|
var angle := float(index) * PI * 0.5 + progress * PI * 0.25
|
||||||
var crack := Vector2(cos(angle), sin(angle))
|
var crack := Vector2(cos(angle), sin(angle))
|
||||||
draw_line(end + crack * radius * 0.16, end + crack * radius * (0.54 + 0.16 * peak), Color(0.96, 0.82, 0.42, 0.34 * peak), 2.0)
|
draw_line(end + crack * radius * 0.16, end + crack * radius * (0.54 + 0.16 * peak), Color(0.96, 0.82, 0.42, 0.34 * peak), 2.0)
|
||||||
|
elif cue == "tactic_damage":
|
||||||
|
draw_arc(end, radius * (0.35 + peak * 0.38), 0.0, TAU, 44, Color(1.0, 0.34, 0.18, 0.28 * peak), 3.0)
|
||||||
|
draw_arc(end, radius * 0.62, progress * TAU, progress * TAU + PI * 1.20, 36, Color(1.0, 0.82, 0.32, 0.34 * peak), 2.0)
|
||||||
|
elif cue == "tactic_heal":
|
||||||
|
draw_arc(end, radius * (0.40 + peak * 0.24), 0.0, TAU, 48, Color(0.48, 1.0, 0.54, 0.30 * peak), 2.5)
|
||||||
|
draw_circle(end, radius * 0.14 + radius * 0.10 * peak, Color(0.78, 1.0, 0.74, 0.22 * peak))
|
||||||
|
elif cue == "tactic_support":
|
||||||
|
draw_arc(end, radius * (0.48 + peak * 0.18), -PI * 0.10, PI * 1.10, 40, Color(1.0, 0.88, 0.28, 0.30 * peak), 2.5)
|
||||||
|
draw_line(end + Vector2(-radius * 0.38, radius * 0.26), end + Vector2(radius * 0.38, radius * 0.26), Color(1.0, 0.94, 0.50, 0.30 * peak), 2.0)
|
||||||
else:
|
else:
|
||||||
draw_line(end - side * radius * 0.48, end + side * radius * 0.48, hot, 2.0)
|
draw_line(end - side * radius * 0.48, end + side * radius * 0.48, hot, 2.0)
|
||||||
draw_circle(end, radius * 0.32 + radius * 0.18 * peak, Color(1.0, 0.82, 0.34, 0.18 * peak))
|
draw_circle(end, radius * 0.32 + radius * 0.18 * peak, Color(1.0, 0.82, 0.34, 0.18 * peak))
|
||||||
@@ -3219,10 +3276,12 @@ func _on_unit_motion_requested(unit_id: String, from_cell: Vector2i, to_cell: Ve
|
|||||||
|
|
||||||
|
|
||||||
func _on_unit_action_motion_requested(unit_id: String, from_cell: Vector2i, to_cell: Vector2i, action_kind: String) -> void:
|
func _on_unit_action_motion_requested(unit_id: String, from_cell: Vector2i, to_cell: Vector2i, action_kind: String) -> void:
|
||||||
if unit_id.is_empty() or from_cell == to_cell:
|
if unit_id.is_empty():
|
||||||
return
|
return
|
||||||
var unit := state.get_unit(unit_id)
|
var unit := state.get_unit(unit_id)
|
||||||
var profile := _unit_action_motion_profile(unit, from_cell, to_cell, action_kind)
|
var profile := _unit_action_motion_profile(unit, from_cell, to_cell, action_kind)
|
||||||
|
if from_cell == to_cell and not _action_motion_allows_same_cell(profile):
|
||||||
|
return
|
||||||
unit_action_motion_by_unit[unit_id] = {
|
unit_action_motion_by_unit[unit_id] = {
|
||||||
"from": from_cell,
|
"from": from_cell,
|
||||||
"to": to_cell,
|
"to": to_cell,
|
||||||
@@ -3242,6 +3301,12 @@ func _on_unit_action_motion_requested(unit_id: String, from_cell: Vector2i, to_c
|
|||||||
|
|
||||||
|
|
||||||
func _unit_action_motion_profile(unit: Dictionary, from_cell: Vector2i, to_cell: Vector2i, _action_kind: String) -> String:
|
func _unit_action_motion_profile(unit: Dictionary, from_cell: Vector2i, to_cell: Vector2i, _action_kind: String) -> String:
|
||||||
|
if _action_kind == "skill_heal":
|
||||||
|
return "tactic_heal"
|
||||||
|
if _action_kind == "skill_support":
|
||||||
|
return "tactic_support"
|
||||||
|
if _action_kind.begins_with("skill_"):
|
||||||
|
return "tactic_damage"
|
||||||
var class_id := str(unit.get("class_id", ""))
|
var class_id := str(unit.get("class_id", ""))
|
||||||
var distance := absi(from_cell.x - to_cell.x) + absi(from_cell.y - to_cell.y)
|
var distance := absi(from_cell.x - to_cell.x) + absi(from_cell.y - to_cell.y)
|
||||||
if class_id.contains("archer") or class_id.contains("marksman") or (int(unit.get("range", 1)) > 1 and distance > 1):
|
if class_id.contains("archer") or class_id.contains("marksman") or (int(unit.get("range", 1)) > 1 and distance > 1):
|
||||||
@@ -3260,6 +3325,8 @@ func _unit_action_motion_profile(unit: Dictionary, from_cell: Vector2i, to_cell:
|
|||||||
func _action_motion_style(profile: String) -> String:
|
func _action_motion_style(profile: String) -> String:
|
||||||
if profile == "arrow":
|
if profile == "arrow":
|
||||||
return "ranged"
|
return "ranged"
|
||||||
|
if profile.begins_with("tactic_"):
|
||||||
|
return "tactic"
|
||||||
return "melee"
|
return "melee"
|
||||||
|
|
||||||
|
|
||||||
@@ -3274,6 +3341,8 @@ func _action_motion_duration(profile: String) -> float:
|
|||||||
return UNIT_COMMAND_ATTACK_DURATION
|
return UNIT_COMMAND_ATTACK_DURATION
|
||||||
if profile == "heavy_strike":
|
if profile == "heavy_strike":
|
||||||
return UNIT_HEAVY_ATTACK_DURATION
|
return UNIT_HEAVY_ATTACK_DURATION
|
||||||
|
if profile.begins_with("tactic_"):
|
||||||
|
return UNIT_TACTIC_CAST_DURATION
|
||||||
return UNIT_ATTACK_ANIMATION_DURATION
|
return UNIT_ATTACK_ANIMATION_DURATION
|
||||||
|
|
||||||
|
|
||||||
@@ -3288,9 +3357,15 @@ func _action_motion_lunge_scale(profile: String) -> float:
|
|||||||
return 0.20
|
return 0.20
|
||||||
if profile == "heavy_strike":
|
if profile == "heavy_strike":
|
||||||
return 1.15
|
return 1.15
|
||||||
|
if profile.begins_with("tactic_"):
|
||||||
|
return 0.12
|
||||||
return 1.0
|
return 1.0
|
||||||
|
|
||||||
|
|
||||||
|
func _action_motion_allows_same_cell(profile: String) -> bool:
|
||||||
|
return profile.begins_with("tactic_")
|
||||||
|
|
||||||
|
|
||||||
func _action_motion_vfx_signature(profile: String) -> String:
|
func _action_motion_vfx_signature(profile: String) -> String:
|
||||||
if profile == "arrow":
|
if profile == "arrow":
|
||||||
return "piercing_trace"
|
return "piercing_trace"
|
||||||
@@ -3302,6 +3377,12 @@ func _action_motion_vfx_signature(profile: String) -> String:
|
|||||||
return "command_ring"
|
return "command_ring"
|
||||||
if profile == "heavy_strike":
|
if profile == "heavy_strike":
|
||||||
return "shock_crack"
|
return "shock_crack"
|
||||||
|
if profile == "tactic_damage":
|
||||||
|
return "tactic_flare"
|
||||||
|
if profile == "tactic_heal":
|
||||||
|
return "healing_sigil"
|
||||||
|
if profile == "tactic_support":
|
||||||
|
return "order_banner"
|
||||||
return "slash_flash"
|
return "slash_flash"
|
||||||
|
|
||||||
|
|
||||||
@@ -3316,6 +3397,12 @@ func _action_motion_impact_cue(profile: String) -> String:
|
|||||||
return "command"
|
return "command"
|
||||||
if profile == "heavy_strike":
|
if profile == "heavy_strike":
|
||||||
return "crush"
|
return "crush"
|
||||||
|
if profile == "tactic_damage":
|
||||||
|
return "tactic_damage"
|
||||||
|
if profile == "tactic_heal":
|
||||||
|
return "tactic_heal"
|
||||||
|
if profile == "tactic_support":
|
||||||
|
return "tactic_support"
|
||||||
return "slash"
|
return "slash"
|
||||||
|
|
||||||
|
|
||||||
@@ -3330,6 +3417,12 @@ func _action_motion_impact_radius(profile: String) -> float:
|
|||||||
return 24.0
|
return 24.0
|
||||||
if profile == "heavy_strike":
|
if profile == "heavy_strike":
|
||||||
return 28.0
|
return 28.0
|
||||||
|
if profile == "tactic_damage":
|
||||||
|
return 26.0
|
||||||
|
if profile == "tactic_heal":
|
||||||
|
return 23.0
|
||||||
|
if profile == "tactic_support":
|
||||||
|
return 24.0
|
||||||
return 16.0
|
return 16.0
|
||||||
|
|
||||||
|
|
||||||
@@ -3344,6 +3437,8 @@ func _action_motion_sfx(profile: String) -> String:
|
|||||||
return "skill_cast"
|
return "skill_cast"
|
||||||
if profile == "heavy_strike":
|
if profile == "heavy_strike":
|
||||||
return "slash"
|
return "slash"
|
||||||
|
if profile.begins_with("tactic_"):
|
||||||
|
return "skill_cast"
|
||||||
return "slash"
|
return "slash"
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -449,20 +449,34 @@ func _check_attack_motion_profiles(failures: Array[String]) -> void:
|
|||||||
_check_synthetic_attack_profile(failures, scene, "commander", "command_strike")
|
_check_synthetic_attack_profile(failures, scene, "commander", "command_strike")
|
||||||
_check_synthetic_attack_profile(failures, scene, "champion", "heavy_strike")
|
_check_synthetic_attack_profile(failures, scene, "champion", "heavy_strike")
|
||||||
_check_synthetic_attack_profile(failures, scene, "bandit", "heavy_strike")
|
_check_synthetic_attack_profile(failures, scene, "bandit", "heavy_strike")
|
||||||
|
_check_skill_action_profile(failures, scene, "skill_damage", "tactic_damage", "tactic_flare")
|
||||||
|
_check_skill_action_profile(failures, scene, "skill_heal", "tactic_heal", "healing_sigil")
|
||||||
|
_check_skill_action_profile(failures, scene, "skill_support", "tactic_support", "order_banner")
|
||||||
scene._on_unit_action_motion_requested("xiahou_dun", Vector2i(1, 4), Vector2i(2, 4), "attack")
|
scene._on_unit_action_motion_requested("xiahou_dun", Vector2i(1, 4), Vector2i(2, 4), "attack")
|
||||||
_check_stored_attack_motion(failures, scene, "xiahou_dun", "cavalry_charge", "melee", "attack", "charge_dust")
|
_check_stored_attack_motion(failures, scene, "xiahou_dun", "cavalry_charge", "melee", "attack", "charge_dust")
|
||||||
scene._on_unit_action_motion_requested("yellow_turban_3", Vector2i(7, 6), Vector2i(7, 4), "attack")
|
scene._on_unit_action_motion_requested("yellow_turban_3", Vector2i(7, 6), Vector2i(7, 4), "attack")
|
||||||
_check_stored_attack_motion(failures, scene, "yellow_turban_3", "arrow", "ranged", "attack", "piercing_trace")
|
_check_stored_attack_motion(failures, scene, "yellow_turban_3", "arrow", "ranged", "attack", "piercing_trace")
|
||||||
|
scene._on_unit_action_motion_requested("cao_cao", Vector2i(1, 3), Vector2i(3, 3), "skill_damage")
|
||||||
|
_check_stored_attack_motion(failures, scene, "cao_cao", "tactic_damage", "tactic", "skill_damage", "tactic_flare")
|
||||||
|
scene._on_unit_action_motion_requested("cao_cao", Vector2i(1, 3), Vector2i(1, 3), "skill_heal")
|
||||||
|
_check_stored_attack_motion(failures, scene, "cao_cao", "tactic_heal", "tactic", "skill_heal", "healing_sigil")
|
||||||
_check_attack_vfx_signature(failures, scene, "arrow", "piercing_trace")
|
_check_attack_vfx_signature(failures, scene, "arrow", "piercing_trace")
|
||||||
_check_attack_vfx_signature(failures, scene, "cavalry_charge", "charge_dust")
|
_check_attack_vfx_signature(failures, scene, "cavalry_charge", "charge_dust")
|
||||||
_check_attack_vfx_signature(failures, scene, "infantry_strike", "shield_spark")
|
_check_attack_vfx_signature(failures, scene, "infantry_strike", "shield_spark")
|
||||||
_check_attack_vfx_signature(failures, scene, "command_strike", "command_ring")
|
_check_attack_vfx_signature(failures, scene, "command_strike", "command_ring")
|
||||||
_check_attack_vfx_signature(failures, scene, "heavy_strike", "shock_crack")
|
_check_attack_vfx_signature(failures, scene, "heavy_strike", "shock_crack")
|
||||||
|
_check_attack_vfx_signature(failures, scene, "tactic_damage", "tactic_flare")
|
||||||
|
_check_attack_vfx_signature(failures, scene, "tactic_heal", "healing_sigil")
|
||||||
|
_check_attack_vfx_signature(failures, scene, "tactic_support", "order_banner")
|
||||||
_check_unique_attack_vfx_signatures(failures, scene)
|
_check_unique_attack_vfx_signatures(failures, scene)
|
||||||
if scene._action_motion_duration("cavalry_charge") <= scene._action_motion_duration("slash"):
|
if scene._action_motion_duration("cavalry_charge") <= scene._action_motion_duration("slash"):
|
||||||
failures.append("cavalry charge animation should last longer than default slash")
|
failures.append("cavalry charge animation should last longer than default slash")
|
||||||
|
if scene._action_motion_duration("tactic_damage") <= scene._action_motion_duration("command_strike"):
|
||||||
|
failures.append("tactic cast animation should linger longer than command physical strike")
|
||||||
if scene._action_motion_lunge_scale("arrow") >= scene._action_motion_lunge_scale("slash"):
|
if scene._action_motion_lunge_scale("arrow") >= scene._action_motion_lunge_scale("slash"):
|
||||||
failures.append("arrow animation should keep the archer mostly planted")
|
failures.append("arrow animation should keep the archer mostly planted")
|
||||||
|
if scene._action_motion_lunge_scale("tactic_damage") >= scene._action_motion_lunge_scale("arrow"):
|
||||||
|
failures.append("tactic animation should keep the caster planted like a spell cast")
|
||||||
if scene._action_motion_impact_radius("heavy_strike") <= scene._action_motion_impact_radius("infantry_strike"):
|
if scene._action_motion_impact_radius("heavy_strike") <= scene._action_motion_impact_radius("infantry_strike"):
|
||||||
failures.append("heavy strike impact radius should exceed infantry strike")
|
failures.append("heavy strike impact radius should exceed infantry strike")
|
||||||
if scene._action_motion_impact_radius("cavalry_charge") <= scene._action_motion_impact_radius("arrow"):
|
if scene._action_motion_impact_radius("cavalry_charge") <= scene._action_motion_impact_radius("arrow"):
|
||||||
@@ -473,6 +487,8 @@ func _check_attack_motion_profiles(failures: Array[String]) -> void:
|
|||||||
failures.append("infantry animation should use slash SFX")
|
failures.append("infantry animation should use slash SFX")
|
||||||
if scene._action_motion_sfx("heavy_strike") != "slash":
|
if scene._action_motion_sfx("heavy_strike") != "slash":
|
||||||
failures.append("heavy animation should use swing SFX before hit resolution")
|
failures.append("heavy animation should use swing SFX before hit resolution")
|
||||||
|
if scene._action_motion_sfx("tactic_damage") != "skill_cast":
|
||||||
|
failures.append("tactic animation should use skill cast SFX")
|
||||||
scene.free()
|
scene.free()
|
||||||
|
|
||||||
|
|
||||||
@@ -496,6 +512,16 @@ func _check_synthetic_attack_profile(failures: Array[String], scene, class_id: S
|
|||||||
failures.append("%s synthetic attack profile mismatch: expected %s got %s" % [class_id, expected_profile, profile])
|
failures.append("%s synthetic attack profile mismatch: expected %s got %s" % [class_id, expected_profile, profile])
|
||||||
|
|
||||||
|
|
||||||
|
func _check_skill_action_profile(failures: Array[String], scene, action_kind: String, expected_profile: String, expected_signature: String) -> void:
|
||||||
|
var profile: String = scene._unit_action_motion_profile({"class_id": "hero", "range": 1}, Vector2i(0, 0), Vector2i(0, 0), action_kind)
|
||||||
|
if profile != expected_profile:
|
||||||
|
failures.append("%s action profile mismatch: expected %s got %s" % [action_kind, expected_profile, profile])
|
||||||
|
if not scene._action_motion_allows_same_cell(profile):
|
||||||
|
failures.append("%s should allow same-cell tactic motion" % profile)
|
||||||
|
if scene._action_motion_vfx_signature(profile) != expected_signature:
|
||||||
|
failures.append("%s action VFX mismatch: expected %s got %s" % [action_kind, expected_signature, scene._action_motion_vfx_signature(profile)])
|
||||||
|
|
||||||
|
|
||||||
func _check_attack_vfx_signature(failures: Array[String], scene, profile: String, expected_signature: String) -> void:
|
func _check_attack_vfx_signature(failures: Array[String], scene, profile: String, expected_signature: String) -> void:
|
||||||
var signature: String = scene._action_motion_vfx_signature(profile)
|
var signature: String = scene._action_motion_vfx_signature(profile)
|
||||||
if signature != expected_signature:
|
if signature != expected_signature:
|
||||||
@@ -569,6 +595,38 @@ func _check_attack_motion_signal(failures: Array[String]) -> void:
|
|||||||
return
|
return
|
||||||
if not motions.has("cao_cao|attack|6,2|7,2"):
|
if not motions.has("cao_cao|attack|6,2|7,2"):
|
||||||
failures.append("attack motion signal missing expected Cao Cao attack: %s" % str(motions))
|
failures.append("attack motion signal missing expected Cao Cao attack: %s" % str(motions))
|
||||||
|
_check_skill_motion_signal(failures, "spark", "skill_damage", Vector2i(3, 3), func(skill_state) -> void:
|
||||||
|
var enemy: Dictionary = skill_state.get_unit("yellow_turban_1")
|
||||||
|
enemy["pos"] = Vector2i(3, 3)
|
||||||
|
)
|
||||||
|
_check_skill_motion_signal(failures, "mend", "skill_heal", Vector2i(1, 3), func(skill_state) -> void:
|
||||||
|
var caster: Dictionary = skill_state.get_unit("cao_cao")
|
||||||
|
caster["hp"] = max(1, int(caster.get("hp", 1)) - 10)
|
||||||
|
)
|
||||||
|
_check_skill_motion_signal(failures, "guard_order", "skill_support", Vector2i(1, 3), func(_skill_state) -> void:
|
||||||
|
pass
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
func _check_skill_motion_signal(failures: Array[String], skill_id: String, expected_kind: String, target_cell: Vector2i, setup: Callable) -> void:
|
||||||
|
var state = BattleStateScript.new()
|
||||||
|
if not state.load_battle("res://data/scenarios/001_yellow_turbans.json"):
|
||||||
|
failures.append("could not load battle for %s motion" % skill_id)
|
||||||
|
return
|
||||||
|
setup.call(state)
|
||||||
|
var motions: Array[String] = []
|
||||||
|
state.unit_action_motion_requested.connect(func(unit_id: String, from_cell: Vector2i, to_cell: Vector2i, action_kind: String) -> void:
|
||||||
|
motions.append("%s|%s|%d,%d|%d,%d" % [unit_id, action_kind, from_cell.x, from_cell.y, to_cell.x, to_cell.y])
|
||||||
|
)
|
||||||
|
if not state.select_unit("cao_cao"):
|
||||||
|
failures.append("could not select Cao Cao for %s motion" % skill_id)
|
||||||
|
return
|
||||||
|
if not state.try_cast_selected_skill(skill_id, target_cell):
|
||||||
|
failures.append("%s should cast successfully for motion check" % skill_id)
|
||||||
|
return
|
||||||
|
var expected := "cao_cao|%s|1,3|%d,%d" % [expected_kind, target_cell.x, target_cell.y]
|
||||||
|
if not motions.has(expected):
|
||||||
|
failures.append("%s motion signal missing `%s`: %s" % [skill_id, expected, str(motions)])
|
||||||
|
|
||||||
|
|
||||||
func _check_camp_conversation_effect(failures: Array[String], conversation: Dictionary, expected_item_id: String, expected_count: int) -> void:
|
func _check_camp_conversation_effect(failures: Array[String], conversation: Dictionary, expected_item_id: String, expected_count: int) -> void:
|
||||||
|
|||||||
Reference in New Issue
Block a user