Add first support tactic
This commit is contained in:
@@ -18,6 +18,7 @@ const EXP_COUNTER := 5
|
||||
const EXP_MISS := 2
|
||||
const EXP_SKILL := 12
|
||||
const EXP_HEAL := 8
|
||||
const EXP_SUPPORT := 8
|
||||
const EXP_DEFEAT_BONUS := 30
|
||||
const EXP_LEVEL := 100
|
||||
const BASE_HIT_CHANCE := 90
|
||||
@@ -453,6 +454,7 @@ func _prepare_unit(source_unit: Dictionary) -> Dictionary:
|
||||
unit["controllable"] = bool(unit.get("controllable", true))
|
||||
unit["persist_progression"] = bool(unit.get("persist_progression", true))
|
||||
unit["ai_target_priority"] = max(0, int(unit.get("ai_target_priority", 0)))
|
||||
unit["status_effects"] = []
|
||||
unit["acted"] = false
|
||||
unit["moved"] = false
|
||||
return unit
|
||||
@@ -593,12 +595,17 @@ func try_cast_selected_skill(skill_id: String, target_cell: Vector2i) -> bool:
|
||||
if skill_kind == "heal" and calculate_skill_heal(caster, target, skill) <= 0:
|
||||
_emit_log("%s is already at full HP." % target["name"])
|
||||
return false
|
||||
if skill_kind == "support" and not _skill_has_support_effects(skill):
|
||||
return false
|
||||
|
||||
caster["mp"] = max(0, int(caster.get("mp", 0)) - int(skill.get("mp_cost", 0)))
|
||||
if skill_kind == "heal":
|
||||
var healed := _resolve_skill_heal(caster, target, skill)
|
||||
if healed > 0:
|
||||
_grant_experience(caster, EXP_HEAL)
|
||||
elif skill_kind == "support":
|
||||
if _resolve_skill_support(caster, target, skill_id, skill):
|
||||
_grant_experience(caster, EXP_SUPPORT)
|
||||
else:
|
||||
var defeated := _resolve_skill_damage(caster, target, skill)
|
||||
_grant_experience(caster, EXP_SKILL + (EXP_DEFEAT_BONUS if defeated else 0))
|
||||
@@ -712,6 +719,9 @@ func get_skill_preview(caster_id: String, skill_id: String, target_cell: Vector2
|
||||
var heal_amount := calculate_skill_heal(caster, target, skill)
|
||||
preview["heal"] = heal_amount
|
||||
preview["target_hp_after"] = min(int(target.get("max_hp", 1)), int(target.get("hp", 0)) + heal_amount)
|
||||
elif skill_kind == "support":
|
||||
preview["effect_text"] = _format_support_effects(skill)
|
||||
preview["already_active"] = _has_status_effect_from_skill(target, skill_id)
|
||||
else:
|
||||
var damage := calculate_skill_damage(caster, target, skill)
|
||||
preview["damage"] = damage
|
||||
@@ -748,29 +758,151 @@ func get_item_preview(user_id: String, item_id: String, target_cell: Vector2i) -
|
||||
|
||||
func calculate_damage(attacker: Dictionary, target: Dictionary) -> int:
|
||||
var terrain_defense := get_terrain_defense(target["pos"])
|
||||
return max(1, int(attacker["atk"]) - int(target["def"]) - terrain_defense)
|
||||
return max(1, _effective_stat(attacker, "atk") - _effective_stat(target, "def") - terrain_defense)
|
||||
|
||||
|
||||
func calculate_hit_chance(attacker: Dictionary, target: Dictionary) -> int:
|
||||
var terrain_avoid := get_terrain_avoid(target["pos"])
|
||||
var agility_delta := int(attacker.get("agi", 0)) - int(target.get("agi", 0))
|
||||
var agility_delta := _effective_stat(attacker, "agi") - _effective_stat(target, "agi")
|
||||
return clampi(BASE_HIT_CHANCE + agility_delta - terrain_avoid, MIN_HIT_CHANCE, 100)
|
||||
|
||||
|
||||
func calculate_skill_damage(caster: Dictionary, target: Dictionary, skill: Dictionary) -> int:
|
||||
var terrain_defense := get_terrain_defense(target["pos"])
|
||||
var stat_name := str(skill.get("stat", "int"))
|
||||
var stat_value := int(caster.get(stat_name, 0))
|
||||
return max(1, int(skill.get("power", 1)) + stat_value - int(target.get("def", 0)) - terrain_defense)
|
||||
var stat_value := _effective_stat(caster, stat_name)
|
||||
return max(1, int(skill.get("power", 1)) + stat_value - _effective_stat(target, "def") - terrain_defense)
|
||||
|
||||
|
||||
func calculate_skill_heal(caster: Dictionary, target: Dictionary, skill: Dictionary) -> int:
|
||||
var stat_name := str(skill.get("stat", "int"))
|
||||
var raw_heal := int(skill.get("power", 1)) + int(caster.get(stat_name, 0))
|
||||
var raw_heal := int(skill.get("power", 1)) + _effective_stat(caster, stat_name)
|
||||
var missing_hp := int(target.get("max_hp", 1)) - int(target.get("hp", 0))
|
||||
return max(0, min(raw_heal, missing_hp))
|
||||
|
||||
|
||||
func get_status_effect_summary(unit_id: String) -> String:
|
||||
var unit := get_unit(unit_id)
|
||||
if unit.is_empty():
|
||||
return ""
|
||||
return _format_active_status_effects(unit)
|
||||
|
||||
|
||||
func _effective_stat(unit: Dictionary, stat: String) -> int:
|
||||
var base_value := int(unit.get(stat, 0))
|
||||
for effect in _active_status_effects(unit):
|
||||
if str(effect.get("stat", "")) != stat:
|
||||
continue
|
||||
base_value += int(effect.get("amount", 0))
|
||||
if stat == "atk":
|
||||
return max(1, base_value)
|
||||
return max(0, base_value)
|
||||
|
||||
|
||||
func _active_status_effects(unit: Dictionary) -> Array:
|
||||
var result := []
|
||||
if typeof(unit.get("status_effects", [])) != TYPE_ARRAY:
|
||||
return result
|
||||
for effect in unit.get("status_effects", []):
|
||||
if typeof(effect) != TYPE_DICTIONARY:
|
||||
continue
|
||||
if int(effect.get("remaining_phases", 0)) <= 0:
|
||||
continue
|
||||
result.append(effect)
|
||||
return result
|
||||
|
||||
|
||||
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:
|
||||
return true
|
||||
return false
|
||||
|
||||
|
||||
func _resolve_skill_support(caster: Dictionary, target: Dictionary, skill_id: String, skill: Dictionary) -> bool:
|
||||
if not _skill_has_support_effects(skill):
|
||||
return false
|
||||
var status_effects := _status_effects_without_skill(target, skill_id)
|
||||
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)))
|
||||
})
|
||||
target["status_effects"] = status_effects
|
||||
_emit_log("%s casts %s on %s. %s" % [
|
||||
caster["name"],
|
||||
skill.get("name", "Skill"),
|
||||
target["name"],
|
||||
_format_support_effects(skill)
|
||||
])
|
||||
return true
|
||||
|
||||
|
||||
func _status_effects_without_skill(unit: Dictionary, skill_id: String) -> Array:
|
||||
var result := []
|
||||
for effect in _active_status_effects(unit):
|
||||
if str(effect.get("source_skill", "")) == skill_id:
|
||||
continue
|
||||
result.append(effect)
|
||||
return result
|
||||
|
||||
|
||||
func _has_status_effect_from_skill(unit: Dictionary, skill_id: String) -> bool:
|
||||
for effect in _active_status_effects(unit):
|
||||
if str(effect.get("source_skill", "")) == skill_id:
|
||||
return true
|
||||
return false
|
||||
|
||||
|
||||
func _format_support_effects(skill: Dictionary) -> String:
|
||||
var parts := []
|
||||
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 duration := max(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 parts.is_empty():
|
||||
return "No support effect."
|
||||
return _join_strings(parts, ", ")
|
||||
|
||||
|
||||
func _format_active_status_effects(unit: Dictionary) -> String:
|
||||
var parts := []
|
||||
for effect in _active_status_effects(unit):
|
||||
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])
|
||||
return _join_strings(parts, ", ")
|
||||
|
||||
|
||||
func _is_supported_status_stat(stat: String) -> bool:
|
||||
return stat == "atk" or stat == "def" or stat == "int" or stat == "agi"
|
||||
|
||||
|
||||
func try_wait_selected() -> bool:
|
||||
var unit := get_selected_unit()
|
||||
if unit.is_empty() or battle_status != STATUS_ACTIVE:
|
||||
@@ -2366,12 +2498,36 @@ func _spawn_event_deployment(deployment) -> bool:
|
||||
|
||||
|
||||
func _reset_team_actions(team: String) -> void:
|
||||
_expire_status_effects_for_team(team)
|
||||
for unit in units:
|
||||
if unit.get("team", "") == team and unit.get("alive", false):
|
||||
unit["acted"] = false
|
||||
unit["moved"] = false
|
||||
|
||||
|
||||
func _expire_status_effects_for_team(team: String) -> void:
|
||||
for unit in units:
|
||||
if unit.get("team", "") != team:
|
||||
continue
|
||||
if typeof(unit.get("status_effects", [])) != TYPE_ARRAY:
|
||||
continue
|
||||
var kept_effects := []
|
||||
var expired_names := []
|
||||
for effect in _active_status_effects(unit):
|
||||
var next_remaining := int(effect.get("remaining_phases", 1)) - 1
|
||||
if next_remaining > 0:
|
||||
var kept_effect: Dictionary = effect.duplicate(true)
|
||||
kept_effect["remaining_phases"] = next_remaining
|
||||
kept_effects.append(kept_effect)
|
||||
else:
|
||||
var effect_name := str(effect.get("name", "Support"))
|
||||
if not expired_names.has(effect_name):
|
||||
expired_names.append(effect_name)
|
||||
unit["status_effects"] = kept_effects
|
||||
for expired_effect_name in expired_names:
|
||||
_emit_log("%s's %s fades." % [unit["name"], expired_effect_name])
|
||||
|
||||
|
||||
func _manhattan(a: Vector2i, b: Vector2i) -> int:
|
||||
return absi(a.x - b.x) + absi(a.y - b.y)
|
||||
|
||||
@@ -2380,6 +2536,15 @@ func _format_cell(cell: Vector2i) -> String:
|
||||
return "(%d, %d)" % [cell.x + 1, cell.y + 1]
|
||||
|
||||
|
||||
func _join_strings(values: Array, delimiter: String) -> String:
|
||||
var text := ""
|
||||
for value in values:
|
||||
if not text.is_empty():
|
||||
text += delimiter
|
||||
text += str(value)
|
||||
return text
|
||||
|
||||
|
||||
func _emit_log(message: String) -> void:
|
||||
log_added.emit(message)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user