Add tactic seal action lock
This commit is contained in:
@@ -606,6 +606,9 @@ func try_cast_selected_skill(skill_id: String, target_cell: Vector2i) -> bool:
|
||||
return false
|
||||
if caster.get("acted", false):
|
||||
return false
|
||||
if _unit_has_skill_lock(caster):
|
||||
_emit_log("%s cannot use tactics while sealed." % caster["name"])
|
||||
return false
|
||||
if not _unit_has_skill(caster, skill_id):
|
||||
return false
|
||||
if int(caster.get("mp", 0)) < int(skill.get("mp_cost", 0)):
|
||||
@@ -729,14 +732,16 @@ func get_skill_preview(caster_id: String, skill_id: String, target_cell: Vector2
|
||||
|
||||
var target := get_unit_at(target_cell)
|
||||
var skill_kind := str(skill.get("kind", "damage"))
|
||||
var skill_locked := _unit_has_skill_lock(caster)
|
||||
var preview := {
|
||||
"skill_id": skill_id,
|
||||
"skill_name": str(skill.get("name", skill_id)),
|
||||
"kind": skill_kind,
|
||||
"mp_cost": int(skill.get("mp_cost", 0)),
|
||||
"has_mp": int(caster.get("mp", 0)) >= int(skill.get("mp_cost", 0)),
|
||||
"in_range": _skill_cells_from(caster, skill).has(target_cell),
|
||||
"valid_target": _skill_target_valid(caster, target, skill)
|
||||
"in_range": not skill_locked and _skill_cells_from(caster, skill).has(target_cell),
|
||||
"valid_target": not skill_locked and _skill_target_valid(caster, target, skill),
|
||||
"skill_locked": skill_locked
|
||||
}
|
||||
if target.is_empty():
|
||||
return preview
|
||||
@@ -817,6 +822,13 @@ func get_status_effect_summary(unit_id: String) -> String:
|
||||
return _format_active_status_effects(unit)
|
||||
|
||||
|
||||
func is_unit_skill_sealed(unit_id: String) -> bool:
|
||||
var unit := get_unit(unit_id)
|
||||
if unit.is_empty():
|
||||
return false
|
||||
return _unit_has_skill_lock(unit)
|
||||
|
||||
|
||||
func _effective_stat(unit: Dictionary, stat: String) -> int:
|
||||
var base_value := int(unit.get(stat, 0))
|
||||
for effect in _active_status_effects(unit):
|
||||
@@ -850,6 +862,8 @@ func _skill_has_support_effects(skill: Dictionary) -> bool:
|
||||
return true
|
||||
if effect_type == "damage_over_time" and int(effect.get("amount", 0)) > 0:
|
||||
return true
|
||||
if effect_type == "action_lock" and str(effect.get("action", "skill")) == "skill" and not str(effect.get("status", "seal")).strip_edges().is_empty():
|
||||
return true
|
||||
return false
|
||||
|
||||
|
||||
@@ -887,6 +901,18 @@ func _resolve_skill_support(caster: Dictionary, target: Dictionary, skill_id: St
|
||||
"amount": damage,
|
||||
"remaining_phases": max(1, int(effect.get("duration_turns", 1)))
|
||||
})
|
||||
elif effect_type == "action_lock" and str(effect.get("action", "skill")) == "skill":
|
||||
var status := str(effect.get("status", "seal"))
|
||||
if status.strip_edges().is_empty():
|
||||
continue
|
||||
status_effects.append({
|
||||
"source_skill": skill_id,
|
||||
"name": str(skill.get("name", skill_id)),
|
||||
"type": effect_type,
|
||||
"status": status,
|
||||
"action": "skill",
|
||||
"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"],
|
||||
@@ -935,6 +961,9 @@ func _format_support_effects(skill: Dictionary) -> String:
|
||||
continue
|
||||
var status_name := _status_display_name(str(effect.get("status", "poison")))
|
||||
parts.append("%s -%d HP for %s" % [status_name, damage, duration_text])
|
||||
elif effect_type == "action_lock" and str(effect.get("action", "skill")) == "skill":
|
||||
var status_name := _status_display_name(str(effect.get("status", "seal")))
|
||||
parts.append("%s tactics for %s" % [status_name, duration_text])
|
||||
if parts.is_empty():
|
||||
return "No support effect."
|
||||
return _join_strings(parts, ", ")
|
||||
@@ -957,12 +986,19 @@ func _format_support_popup(skill: Dictionary) -> String:
|
||||
var damage := int(effect.get("amount", 0))
|
||||
if damage > 0:
|
||||
parts.append(_status_display_name(str(effect.get("status", "poison"))).to_upper())
|
||||
elif effect_type == "action_lock" and str(effect.get("action", "skill")) == "skill":
|
||||
parts.append(_status_display_name(str(effect.get("status", "seal"))).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", "")) == "action_lock" and str(effect.get("action", "skill")) == "skill":
|
||||
return str(effect.get("status", "seal"))
|
||||
for effect in skill.get("effects", []):
|
||||
if typeof(effect) != TYPE_DICTIONARY:
|
||||
continue
|
||||
@@ -985,6 +1021,9 @@ func _format_active_status_effects(unit: Dictionary) -> String:
|
||||
if amount > 0:
|
||||
parts.append("%s -%d HP" % [_status_display_name(str(effect.get("status", "poison"))), amount])
|
||||
continue
|
||||
if effect_type == "action_lock" and str(effect.get("action", "skill")) == "skill":
|
||||
parts.append("%s tactics" % _status_display_name(str(effect.get("status", "seal"))))
|
||||
continue
|
||||
var stat := str(effect.get("stat", ""))
|
||||
if not _is_supported_status_stat(stat) or amount == 0:
|
||||
continue
|
||||
@@ -1004,6 +1043,13 @@ func _is_supported_status_stat(stat: String) -> bool:
|
||||
return stat == "atk" or stat == "def" or stat == "int" or stat == "agi"
|
||||
|
||||
|
||||
func _unit_has_skill_lock(unit: Dictionary) -> bool:
|
||||
for effect in _active_status_effects(unit):
|
||||
if str(effect.get("type", "")) == "action_lock" and str(effect.get("action", "skill")) == "skill":
|
||||
return true
|
||||
return false
|
||||
|
||||
|
||||
func try_wait_selected() -> bool:
|
||||
var unit := get_selected_unit()
|
||||
if unit.is_empty() or battle_status != STATUS_ACTIVE:
|
||||
@@ -1125,6 +1171,8 @@ func get_skill_cells(unit_id: String, skill_id: String) -> Array[Vector2i]:
|
||||
return []
|
||||
if unit.get("team", "") == TEAM_PLAYER and not unit.get("deployed", true):
|
||||
return []
|
||||
if _unit_has_skill_lock(unit):
|
||||
return []
|
||||
if not _unit_has_skill(unit, skill_id):
|
||||
return []
|
||||
if int(unit.get("mp", 0)) < int(skill.get("mp_cost", 0)):
|
||||
@@ -1180,6 +1228,8 @@ func get_first_usable_skill_id(unit_id: String) -> String:
|
||||
var unit := get_unit(unit_id)
|
||||
if unit.is_empty() or typeof(unit.get("skills", [])) != TYPE_ARRAY:
|
||||
return ""
|
||||
if _unit_has_skill_lock(unit):
|
||||
return ""
|
||||
for skill_id in unit.get("skills", []):
|
||||
var normalized := str(skill_id)
|
||||
var skill := get_skill_def(normalized)
|
||||
@@ -1197,6 +1247,8 @@ func get_usable_skill_ids(unit_id: String) -> Array:
|
||||
return result
|
||||
if unit.get("team", "") == TEAM_PLAYER and not unit.get("deployed", true):
|
||||
return result
|
||||
if _unit_has_skill_lock(unit):
|
||||
return result
|
||||
for skill_id in unit.get("skills", []):
|
||||
var normalized := str(skill_id)
|
||||
var skill := get_skill_def(normalized)
|
||||
@@ -2005,6 +2057,8 @@ func _try_enemy_skill_action(enemy: Dictionary) -> bool:
|
||||
return false
|
||||
if typeof(enemy.get("skills", [])) != TYPE_ARRAY:
|
||||
return false
|
||||
if _unit_has_skill_lock(enemy):
|
||||
return false
|
||||
|
||||
var finishing_action := _best_ai_damage_skill_action(enemy, true)
|
||||
if not finishing_action.is_empty() and _execute_ai_skill_action(enemy, finishing_action):
|
||||
@@ -2133,6 +2187,14 @@ func _support_ai_score(caster: Dictionary, target: Dictionary, skill: Dictionary
|
||||
var duration: int = max(1, int(effect.get("duration_turns", 1)))
|
||||
score += damage * duration * 5
|
||||
score += _ai_target_priority_score(target)
|
||||
elif effect_type == "action_lock" and not target_is_ally and str(effect.get("action", "skill")) == "skill":
|
||||
if _unit_has_skill_lock(target):
|
||||
continue
|
||||
var threat_score := _unit_skill_threat_score(target)
|
||||
if threat_score <= 0:
|
||||
continue
|
||||
score += 18 + threat_score
|
||||
score += _ai_target_priority_score(target)
|
||||
return score
|
||||
|
||||
|
||||
@@ -2148,6 +2210,29 @@ func _support_ai_stat_weight(stat: String) -> int:
|
||||
return 1
|
||||
|
||||
|
||||
func _unit_skill_threat_score(unit: Dictionary) -> int:
|
||||
if typeof(unit.get("skills", [])) != TYPE_ARRAY:
|
||||
return 0
|
||||
if int(unit.get("mp", 0)) <= 0:
|
||||
return 0
|
||||
|
||||
var score := 0
|
||||
for skill_id in unit.get("skills", []):
|
||||
var skill := get_skill_def(str(skill_id))
|
||||
if skill.is_empty():
|
||||
continue
|
||||
if int(unit.get("mp", 0)) < int(skill.get("mp_cost", 0)):
|
||||
continue
|
||||
var kind := str(skill.get("kind", "damage"))
|
||||
if kind == "damage":
|
||||
score += 12
|
||||
elif kind == "heal":
|
||||
score += 9
|
||||
elif kind == "support":
|
||||
score += 7
|
||||
return score
|
||||
|
||||
|
||||
func _best_ai_damage_skill_action(caster: Dictionary, defeating_only := false) -> Dictionary:
|
||||
var best_action := {}
|
||||
var best_score := -1
|
||||
@@ -2193,6 +2278,8 @@ func _execute_ai_skill_action(caster: Dictionary, action: Dictionary) -> bool:
|
||||
return false
|
||||
if not _unit_has_skill(caster, skill_id):
|
||||
return false
|
||||
if _unit_has_skill_lock(caster):
|
||||
return false
|
||||
if int(caster.get("mp", 0)) < int(skill.get("mp_cost", 0)):
|
||||
return false
|
||||
if not target.get("alive", false):
|
||||
|
||||
Reference in New Issue
Block a user