Add poison damage over time tactics

This commit is contained in:
2026-06-18 09:37:12 +09:00
parent 07861391c7
commit 1fa7e2afac
10 changed files with 201 additions and 70 deletions

View File

@@ -843,7 +843,10 @@ func _skill_has_support_effects(skill: Dictionary) -> bool:
for effect in skill.get("effects", []):
if typeof(effect) != TYPE_DICTIONARY:
continue
if str(effect.get("type", "")) == "stat_bonus" and _is_supported_status_stat(str(effect.get("stat", ""))) and int(effect.get("amount", 0)) != 0:
var effect_type := str(effect.get("type", ""))
if effect_type == "stat_bonus" and _is_supported_status_stat(str(effect.get("stat", ""))) and int(effect.get("amount", 0)) != 0:
return true
if effect_type == "damage_over_time" and int(effect.get("amount", 0)) > 0:
return true
return false
@@ -855,19 +858,33 @@ func _resolve_skill_support(caster: Dictionary, target: Dictionary, skill_id: St
for effect in skill.get("effects", []):
if typeof(effect) != TYPE_DICTIONARY:
continue
if str(effect.get("type", "")) != "stat_bonus":
continue
var stat := str(effect.get("stat", ""))
var amount := int(effect.get("amount", 0))
if not _is_supported_status_stat(stat) or amount == 0:
continue
status_effects.append({
"source_skill": skill_id,
"name": str(skill.get("name", skill_id)),
"stat": stat,
"amount": amount,
"remaining_phases": max(1, int(effect.get("duration_turns", 1)))
})
var effect_type := str(effect.get("type", ""))
if effect_type == "stat_bonus":
var stat := str(effect.get("stat", ""))
var amount := int(effect.get("amount", 0))
if not _is_supported_status_stat(stat) or amount == 0:
continue
status_effects.append({
"source_skill": skill_id,
"name": str(skill.get("name", skill_id)),
"type": effect_type,
"stat": stat,
"amount": amount,
"remaining_phases": max(1, int(effect.get("duration_turns", 1)))
})
elif effect_type == "damage_over_time":
var damage: int = max(0, int(effect.get("amount", 0)))
if damage <= 0:
continue
var status := str(effect.get("status", "poison"))
status_effects.append({
"source_skill": skill_id,
"name": str(skill.get("name", skill_id)),
"type": effect_type,
"status": status,
"amount": damage,
"remaining_phases": max(1, int(effect.get("duration_turns", 1)))
})
target["status_effects"] = status_effects
_emit_log("%s casts %s on %s. %s" % [
caster["name"],
@@ -900,16 +917,22 @@ func _format_support_effects(skill: Dictionary) -> String:
for effect in skill.get("effects", []):
if typeof(effect) != TYPE_DICTIONARY:
continue
if str(effect.get("type", "")) != "stat_bonus":
continue
var stat := str(effect.get("stat", ""))
var amount := int(effect.get("amount", 0))
if not _is_supported_status_stat(stat) or amount == 0:
continue
var sign := "+" if amount > 0 else ""
var effect_type := str(effect.get("type", ""))
var duration: int = maxi(1, int(effect.get("duration_turns", 1)))
var duration_text := "next phase" if duration == 1 else "%d phases" % duration
parts.append("%s %s%d until %s" % [stat.to_upper(), sign, amount, duration_text])
if effect_type == "stat_bonus":
var stat := str(effect.get("stat", ""))
var amount := int(effect.get("amount", 0))
if not _is_supported_status_stat(stat) or amount == 0:
continue
var sign := "+" if amount > 0 else ""
parts.append("%s %s%d until %s" % [stat.to_upper(), sign, amount, duration_text])
elif effect_type == "damage_over_time":
var damage := int(effect.get("amount", 0))
if damage <= 0:
continue
var status_name := _status_display_name(str(effect.get("status", "poison")))
parts.append("%s -%d HP for %s" % [status_name, damage, duration_text])
if parts.is_empty():
return "No support effect."
return _join_strings(parts, ", ")
@@ -920,20 +943,29 @@ func _format_support_popup(skill: Dictionary) -> String:
for effect in skill.get("effects", []):
if typeof(effect) != TYPE_DICTIONARY:
continue
if str(effect.get("type", "")) != "stat_bonus":
continue
var stat := str(effect.get("stat", ""))
var amount := int(effect.get("amount", 0))
if not _is_supported_status_stat(stat) or amount == 0:
continue
var sign := "+" if amount > 0 else ""
parts.append("%s %s%d" % [stat.to_upper(), sign, amount])
var effect_type := str(effect.get("type", ""))
if effect_type == "stat_bonus":
var stat := str(effect.get("stat", ""))
var amount := int(effect.get("amount", 0))
if not _is_supported_status_stat(stat) or amount == 0:
continue
var sign := "+" if amount > 0 else ""
parts.append("%s %s%d" % [stat.to_upper(), sign, amount])
elif effect_type == "damage_over_time":
var damage := int(effect.get("amount", 0))
if damage > 0:
parts.append(_status_display_name(str(effect.get("status", "poison"))).to_upper())
if parts.is_empty():
return "Support"
return _join_strings(parts, ", ")
func _support_feedback_kind(skill: Dictionary) -> String:
for effect in skill.get("effects", []):
if typeof(effect) != TYPE_DICTIONARY:
continue
if str(effect.get("type", "")) == "damage_over_time" and int(effect.get("amount", 0)) > 0:
return str(effect.get("status", "poison"))
for effect in skill.get("effects", []):
if typeof(effect) != TYPE_DICTIONARY:
continue
@@ -945,8 +977,13 @@ func _support_feedback_kind(skill: Dictionary) -> String:
func _format_active_status_effects(unit: Dictionary) -> String:
var parts := []
for effect in _active_status_effects(unit):
var stat := str(effect.get("stat", ""))
var effect_type := str(effect.get("type", "stat_bonus"))
var amount := int(effect.get("amount", 0))
if effect_type == "damage_over_time":
if amount > 0:
parts.append("%s -%d HP" % [_status_display_name(str(effect.get("status", "poison"))), amount])
continue
var stat := str(effect.get("stat", ""))
if not _is_supported_status_stat(stat) or amount == 0:
continue
var sign := "+" if amount > 0 else ""
@@ -954,6 +991,13 @@ func _format_active_status_effects(unit: Dictionary) -> String:
return _join_strings(parts, ", ")
func _status_display_name(status: String) -> String:
var normalized := status.strip_edges()
if normalized.is_empty():
return "Status"
return normalized.replace("_", " ").capitalize()
func _is_supported_status_stat(stat: String) -> bool:
return stat == "atk" or stat == "def" or stat == "int" or stat == "agi"
@@ -979,12 +1023,15 @@ func end_player_turn() -> void:
selected_unit_id = ""
current_team = TEAM_ENEMY
_reset_team_actions(TEAM_ENEMY)
_emit_log("Enemy phase begins.")
_run_events("turn_start", current_team, turn_number)
_reset_team_actions(TEAM_ENEMY)
if battle_status == STATUS_ACTIVE:
_run_events("turn_start", current_team, turn_number)
_check_battle_status()
if battle_status == STATUS_ACTIVE:
_run_enemy_turn()
else:
_notify_changed()
func get_unit(unit_id: String) -> Dictionary:
@@ -1853,9 +1900,10 @@ func _run_enemy_turn() -> void:
if battle_status == STATUS_ACTIVE:
turn_number += 1
current_team = TEAM_PLAYER
_reset_team_actions(TEAM_PLAYER)
_emit_log("Player phase begins.")
_run_events("turn_start", current_team, turn_number)
_reset_team_actions(TEAM_PLAYER)
if battle_status == STATUS_ACTIVE:
_run_events("turn_start", current_team, turn_number)
_check_battle_status()
_notify_changed()
@@ -2012,18 +2060,25 @@ func _support_ai_score(caster: Dictionary, target: Dictionary, skill: Dictionary
for effect in skill.get("effects", []):
if typeof(effect) != TYPE_DICTIONARY:
continue
if str(effect.get("type", "")) != "stat_bonus":
continue
var stat := str(effect.get("stat", ""))
var amount := int(effect.get("amount", 0))
if not _is_supported_status_stat(stat) or amount == 0:
continue
if amount > 0 and target_is_ally:
score += amount * _support_ai_stat_weight(stat)
if target.get("id", "") == caster.get("id", ""):
score += 3
elif amount < 0 and not target_is_ally:
score += absi(amount) * _support_ai_stat_weight(stat)
var effect_type := str(effect.get("type", ""))
if effect_type == "stat_bonus":
var stat := str(effect.get("stat", ""))
var amount := int(effect.get("amount", 0))
if not _is_supported_status_stat(stat) or amount == 0:
continue
if amount > 0 and target_is_ally:
score += amount * _support_ai_stat_weight(stat)
if target.get("id", "") == caster.get("id", ""):
score += 3
elif amount < 0 and not target_is_ally:
score += absi(amount) * _support_ai_stat_weight(stat)
score += _ai_target_priority_score(target)
elif effect_type == "damage_over_time" and not target_is_ally:
var damage := int(effect.get("amount", 0))
if damage <= 0:
continue
var duration: int = max(1, int(effect.get("duration_turns", 1)))
score += damage * duration * 5
score += _ai_target_priority_score(target)
return score
@@ -2681,6 +2736,10 @@ func _spawn_event_deployment(deployment) -> bool:
func _reset_team_actions(team: String) -> void:
_apply_phase_status_effects_for_team(team)
_check_battle_status()
if battle_status != STATUS_ACTIVE:
return
_expire_status_effects_for_team(team)
for unit in units:
if unit.get("team", "") == team and unit.get("alive", false):
@@ -2688,6 +2747,27 @@ func _reset_team_actions(team: String) -> void:
unit["moved"] = false
func _apply_phase_status_effects_for_team(team: String) -> void:
for unit in units:
if unit.get("team", "") != team or not unit.get("alive", false):
continue
for effect in _active_status_effects(unit):
if str(effect.get("type", "")) != "damage_over_time":
continue
var damage: int = max(0, int(effect.get("amount", 0)))
if damage <= 0:
continue
unit["hp"] = max(0, int(unit.get("hp", 0)) - damage)
var status := str(effect.get("status", "poison"))
_emit_log("%s suffers %d %s damage." % [unit["name"], damage, status.replace("_", " ")])
_emit_combat_feedback(unit, "-%d" % damage, status)
if int(unit.get("hp", 0)) <= 0:
unit["alive"] = false
_emit_log("%s is defeated by %s." % [unit["name"], status.replace("_", " ")])
_emit_combat_feedback(unit, "DOWN", "defeat")
break
func _expire_status_effects_for_team(team: String) -> void:
for unit in units:
if unit.get("team", "") != team: