Add poison damage over time tactics
This commit is contained in:
@@ -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:
|
||||
|
||||
@@ -1051,6 +1051,8 @@ func _floating_text_color(kind: String) -> Color:
|
||||
return Color(1.0, 0.88, 0.28)
|
||||
if kind == "debuff":
|
||||
return Color(0.80, 0.52, 1.0)
|
||||
if kind == "poison":
|
||||
return Color(0.54, 0.95, 0.36)
|
||||
if kind == "miss" or kind == "fade":
|
||||
return Color(0.82, 0.84, 0.90)
|
||||
if kind == "defeat":
|
||||
@@ -1138,7 +1140,7 @@ func _skill_target_preview_badge(selected: Dictionary, target: Dictionary) -> Di
|
||||
return _make_target_preview_badge("+%d HP" % heal, "heal")
|
||||
if kind == "support":
|
||||
var effect_text := str(preview.get("effect_text", "Support"))
|
||||
var badge_kind := "debuff" if effect_text.contains("-") else "support"
|
||||
var badge_kind := "poison" if effect_text.contains("Poison") else ("debuff" if effect_text.contains("-") else "support")
|
||||
return _make_target_preview_badge(_compact_support_preview_text(effect_text), badge_kind)
|
||||
|
||||
var result_text := "KO" if bool(preview.get("would_defeat", false)) else "-%d" % int(preview.get("damage", 0))
|
||||
@@ -1185,6 +1187,9 @@ func _compact_support_preview_text(effect_text: String) -> String:
|
||||
var until_index := part.find(" until ")
|
||||
if until_index >= 0:
|
||||
part = part.substr(0, until_index)
|
||||
var for_index := part.find(" for ")
|
||||
if for_index >= 0:
|
||||
part = part.substr(0, for_index)
|
||||
if not part.is_empty() and part != "No support effect.":
|
||||
parts.append(part)
|
||||
if parts.is_empty():
|
||||
@@ -1205,6 +1210,8 @@ func _target_preview_badge_color(kind: String) -> Color:
|
||||
return Color(1.0, 0.88, 0.30)
|
||||
if kind == "debuff":
|
||||
return Color(0.78, 0.55, 1.0)
|
||||
if kind == "poison":
|
||||
return Color(0.54, 0.95, 0.36)
|
||||
return Color(0.78, 0.80, 0.86)
|
||||
|
||||
|
||||
@@ -3245,13 +3252,16 @@ func _format_tactic_effect_text(skill: Dictionary) -> String:
|
||||
for effect in skill.get("effects", []):
|
||||
if typeof(effect) != TYPE_DICTIONARY:
|
||||
continue
|
||||
if str(effect.get("type", "")) != "stat_bonus":
|
||||
continue
|
||||
var effect_type := str(effect.get("type", ""))
|
||||
var amount := int(effect.get("amount", 0))
|
||||
if amount == 0:
|
||||
continue
|
||||
var sign := "+" if amount > 0 else ""
|
||||
parts.append("%s %s%d" % [str(effect.get("stat", "")).to_upper(), sign, amount])
|
||||
if effect_type == "stat_bonus":
|
||||
if amount == 0:
|
||||
continue
|
||||
var sign := "+" if amount > 0 else ""
|
||||
parts.append("%s %s%d" % [str(effect.get("stat", "")).to_upper(), sign, amount])
|
||||
elif effect_type == "damage_over_time" and amount > 0:
|
||||
var status_name := str(effect.get("status", "status")).replace("_", " ").capitalize()
|
||||
parts.append("%s -%d" % [status_name, amount])
|
||||
if not parts.is_empty():
|
||||
return _join_strings(parts, ", ")
|
||||
return "P%d" % int(skill.get("power", 0))
|
||||
|
||||
Reference in New Issue
Block a user