Add snare movement lock tactic
This commit is contained in:
@@ -558,6 +558,9 @@ func try_move_selected(cell: Vector2i) -> bool:
|
||||
return false
|
||||
if unit.get("moved", false) or unit.get("acted", false):
|
||||
return false
|
||||
if _unit_has_action_lock(unit, "move"):
|
||||
_emit_log("%s cannot move while snared." % unit["name"])
|
||||
return false
|
||||
if not get_movement_range(unit["id"]).has(cell):
|
||||
return false
|
||||
if not get_unit_at(cell).is_empty():
|
||||
@@ -606,7 +609,7 @@ 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):
|
||||
if _unit_has_action_lock(caster, "skill"):
|
||||
_emit_log("%s cannot use tactics while sealed." % caster["name"])
|
||||
return false
|
||||
if not _unit_has_skill(caster, skill_id):
|
||||
@@ -732,7 +735,7 @@ 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 skill_locked := _unit_has_action_lock(caster, "skill")
|
||||
var preview := {
|
||||
"skill_id": skill_id,
|
||||
"skill_name": str(skill.get("name", skill_id)),
|
||||
@@ -826,7 +829,7 @@ 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)
|
||||
return _unit_has_action_lock(unit, "skill")
|
||||
|
||||
|
||||
func _effective_stat(unit: Dictionary, stat: String) -> int:
|
||||
@@ -862,7 +865,7 @@ 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():
|
||||
if effect_type == "action_lock" and not str(effect.get("action", "")).strip_edges().is_empty() and not str(effect.get("status", "")).strip_edges().is_empty():
|
||||
return true
|
||||
return false
|
||||
|
||||
@@ -901,16 +904,17 @@ 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():
|
||||
elif effect_type == "action_lock":
|
||||
var action := str(effect.get("action", ""))
|
||||
var status := str(effect.get("status", ""))
|
||||
if action.strip_edges().is_empty() or 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",
|
||||
"action": action,
|
||||
"remaining_phases": max(1, int(effect.get("duration_turns", 1)))
|
||||
})
|
||||
target["status_effects"] = status_effects
|
||||
@@ -961,9 +965,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])
|
||||
elif effect_type == "action_lock":
|
||||
var status_name := _status_display_name(str(effect.get("status", "status")))
|
||||
parts.append("%s %s for %s" % [status_name, _action_lock_display_name(str(effect.get("action", ""))), duration_text])
|
||||
if parts.is_empty():
|
||||
return "No support effect."
|
||||
return _join_strings(parts, ", ")
|
||||
@@ -986,8 +990,8 @@ 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())
|
||||
elif effect_type == "action_lock":
|
||||
parts.append(_status_display_name(str(effect.get("status", "status"))).to_upper())
|
||||
if parts.is_empty():
|
||||
return "Support"
|
||||
return _join_strings(parts, ", ")
|
||||
@@ -997,8 +1001,8 @@ 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"))
|
||||
if str(effect.get("type", "")) == "action_lock":
|
||||
return str(effect.get("status", "status"))
|
||||
for effect in skill.get("effects", []):
|
||||
if typeof(effect) != TYPE_DICTIONARY:
|
||||
continue
|
||||
@@ -1021,8 +1025,11 @@ 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"))))
|
||||
if effect_type == "action_lock":
|
||||
parts.append("%s %s" % [
|
||||
_status_display_name(str(effect.get("status", "status"))),
|
||||
_action_lock_display_name(str(effect.get("action", "")))
|
||||
])
|
||||
continue
|
||||
var stat := str(effect.get("stat", ""))
|
||||
if not _is_supported_status_stat(stat) or amount == 0:
|
||||
@@ -1043,13 +1050,24 @@ 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:
|
||||
func _unit_has_action_lock(unit: Dictionary, action: String) -> bool:
|
||||
var normalized_action := action.strip_edges().to_lower()
|
||||
if normalized_action.is_empty():
|
||||
return false
|
||||
for effect in _active_status_effects(unit):
|
||||
if str(effect.get("type", "")) == "action_lock" and str(effect.get("action", "skill")) == "skill":
|
||||
if str(effect.get("type", "")) == "action_lock" and str(effect.get("action", "")).strip_edges().to_lower() == normalized_action:
|
||||
return true
|
||||
return false
|
||||
|
||||
|
||||
func _action_lock_display_name(action: String) -> String:
|
||||
if action == "skill":
|
||||
return "tactics"
|
||||
if action == "move":
|
||||
return "movement"
|
||||
return "action"
|
||||
|
||||
|
||||
func try_wait_selected() -> bool:
|
||||
var unit := get_selected_unit()
|
||||
if unit.is_empty() or battle_status != STATUS_ACTIVE:
|
||||
@@ -1148,6 +1166,8 @@ func get_movement_range(unit_id: String) -> Array[Vector2i]:
|
||||
return []
|
||||
if unit.get("team", "") == TEAM_PLAYER and not unit.get("deployed", true):
|
||||
return []
|
||||
if _unit_has_action_lock(unit, "move"):
|
||||
return []
|
||||
return _movement_range_for_unit(unit)
|
||||
|
||||
|
||||
@@ -1171,7 +1191,7 @@ 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):
|
||||
if _unit_has_action_lock(unit, "skill"):
|
||||
return []
|
||||
if not _unit_has_skill(unit, skill_id):
|
||||
return []
|
||||
@@ -1228,7 +1248,7 @@ 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):
|
||||
if _unit_has_action_lock(unit, "skill"):
|
||||
return ""
|
||||
for skill_id in unit.get("skills", []):
|
||||
var normalized := str(skill_id)
|
||||
@@ -1247,7 +1267,7 @@ 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):
|
||||
if _unit_has_action_lock(unit, "skill"):
|
||||
return result
|
||||
for skill_id in unit.get("skills", []):
|
||||
var normalized := str(skill_id)
|
||||
@@ -1624,6 +1644,8 @@ func get_turn_limit() -> int:
|
||||
|
||||
|
||||
func _movement_range_for_unit(unit: Dictionary) -> Array[Vector2i]:
|
||||
if _unit_has_action_lock(unit, "move"):
|
||||
return []
|
||||
var start: Vector2i = unit["pos"]
|
||||
var frontier: Array[Vector2i] = [start]
|
||||
var costs := {start: 0}
|
||||
@@ -2053,7 +2075,7 @@ 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):
|
||||
if _unit_has_action_lock(enemy, "skill"):
|
||||
return false
|
||||
|
||||
var finishing_action := _best_ai_damage_skill_action(enemy, true)
|
||||
@@ -2183,10 +2205,11 @@ 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):
|
||||
elif effect_type == "action_lock" and not target_is_ally:
|
||||
var action := str(effect.get("action", ""))
|
||||
if _unit_has_action_lock(target, action):
|
||||
continue
|
||||
var threat_score := _unit_skill_threat_score(target)
|
||||
var threat_score := _unit_action_threat_score(target, action)
|
||||
if threat_score <= 0:
|
||||
continue
|
||||
score += 18 + threat_score
|
||||
@@ -2229,6 +2252,17 @@ func _unit_skill_threat_score(unit: Dictionary) -> int:
|
||||
return score
|
||||
|
||||
|
||||
func _unit_action_threat_score(unit: Dictionary, action: String) -> int:
|
||||
if action == "skill":
|
||||
return _unit_skill_threat_score(unit)
|
||||
if action == "move":
|
||||
var move_score := int(unit.get("move", 0)) * 5
|
||||
if int(unit.get("range", 1)) <= 1:
|
||||
move_score += 10
|
||||
return move_score
|
||||
return 0
|
||||
|
||||
|
||||
func _best_ai_damage_skill_action(caster: Dictionary, defeating_only := false) -> Dictionary:
|
||||
var best_action := {}
|
||||
var best_score := -1
|
||||
@@ -2274,7 +2308,7 @@ 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):
|
||||
if _unit_has_action_lock(caster, "skill"):
|
||||
return false
|
||||
if int(caster.get("mp", 0)) < int(skill.get("mp_cost", 0)):
|
||||
return false
|
||||
|
||||
Reference in New Issue
Block a user