Account for counter risk in auto attacks
This commit is contained in:
@@ -3618,16 +3618,20 @@ func _append_auto_attack_target_marker_entries(result: Array[Dictionary], select
|
||||
var target_cell: Vector2i = target.get("pos", Vector2i(-1, -1))
|
||||
if not state.is_inside(target_cell):
|
||||
continue
|
||||
var badge := _auto_attack_target_preview_badge_for_origin(selected, target, attack_origin)
|
||||
if badge.is_empty():
|
||||
var preview := _auto_attack_preview_for_origin(selected, target, attack_origin)
|
||||
if preview.is_empty():
|
||||
continue
|
||||
result.append({
|
||||
"cell": target_cell,
|
||||
"target_id": target_id,
|
||||
"text": "행공",
|
||||
"kind": str(badge.get("kind", "advance")),
|
||||
"kind": _auto_attack_preview_badge_kind(preview),
|
||||
"origin": attack_origin,
|
||||
"requires_move": true
|
||||
"requires_move": true,
|
||||
"damage": int(preview.get("damage", 0)),
|
||||
"counter_damage": int(preview.get("counter_damage", 0)),
|
||||
"counter_in_range": bool(preview.get("counter_in_range", false)),
|
||||
"counter_would_defeat": bool(preview.get("counter_would_defeat", false))
|
||||
})
|
||||
|
||||
|
||||
@@ -3756,6 +3760,8 @@ func _best_auto_attack_origin(selected: Dictionary, target: Dictionary) -> Vecto
|
||||
return Vector2i(-1, -1)
|
||||
var best_cell := Vector2i(-1, -1)
|
||||
var best_score := -999999
|
||||
var best_preview := {}
|
||||
var best_move_distance := 999999
|
||||
for cell in state.get_movement_range(unit_id):
|
||||
if not state.is_inside(cell):
|
||||
continue
|
||||
@@ -3764,19 +3770,50 @@ func _best_auto_attack_origin(selected: Dictionary, target: Dictionary) -> Vecto
|
||||
var blocker := state.get_unit_at(cell)
|
||||
if not blocker.is_empty() and str(blocker.get("id", "")) != unit_id:
|
||||
continue
|
||||
if not _can_attack_target_from(selected, cell, target_cell):
|
||||
var preview := _auto_attack_preview_for_origin(selected, target, cell)
|
||||
if preview.is_empty():
|
||||
continue
|
||||
var candidate_attacker := selected.duplicate(true)
|
||||
candidate_attacker["pos"] = cell
|
||||
var damage: int = state.calculate_damage(candidate_attacker, target)
|
||||
var move_distance: int = _manhattan(current_cell, cell)
|
||||
var score: int = damage * 20 - move_distance
|
||||
if score > best_score:
|
||||
var score: int = _auto_attack_origin_score(preview, move_distance)
|
||||
if best_cell == Vector2i(-1, -1) or _auto_attack_origin_is_better(score, preview, move_distance, best_score, best_preview, best_move_distance):
|
||||
best_score = score
|
||||
best_preview = preview
|
||||
best_move_distance = move_distance
|
||||
best_cell = cell
|
||||
return best_cell
|
||||
|
||||
|
||||
func _auto_attack_origin_score(preview: Dictionary, move_distance: int) -> int:
|
||||
var score := int(preview.get("damage", 0)) * 100 + int(preview.get("hit_chance", 0)) - move_distance
|
||||
if bool(preview.get("would_defeat", false)):
|
||||
score += 100000
|
||||
if bool(preview.get("counter_in_range", false)):
|
||||
score -= 1500
|
||||
score -= int(preview.get("counter_damage", 0)) * 80
|
||||
score -= int(preview.get("counter_hit_chance", 0)) * 2
|
||||
if bool(preview.get("counter_would_defeat", false)):
|
||||
score -= 100000
|
||||
return score
|
||||
|
||||
|
||||
func _auto_attack_origin_is_better(score: int, preview: Dictionary, move_distance: int, best_score: int, best_preview: Dictionary, best_move_distance: int) -> bool:
|
||||
if score != best_score:
|
||||
return score > best_score
|
||||
var counter_defeat := bool(preview.get("counter_would_defeat", false))
|
||||
var best_counter_defeat := bool(best_preview.get("counter_would_defeat", false))
|
||||
if counter_defeat != best_counter_defeat:
|
||||
return not counter_defeat
|
||||
var counter_damage := int(preview.get("counter_damage", 0))
|
||||
var best_counter_damage := int(best_preview.get("counter_damage", 0))
|
||||
if counter_damage != best_counter_damage:
|
||||
return counter_damage < best_counter_damage
|
||||
var damage := int(preview.get("damage", 0))
|
||||
var best_damage := int(best_preview.get("damage", 0))
|
||||
if damage != best_damage:
|
||||
return damage > best_damage
|
||||
return move_distance < best_move_distance
|
||||
|
||||
|
||||
func _can_attack_target_from(unit: Dictionary, origin: Vector2i, target_cell: Vector2i) -> bool:
|
||||
var attack_range: int = maxi(1, int(unit.get("range", 1)))
|
||||
var min_range: int = clampi(int(unit.get("min_range", mini(1, attack_range))), 1, attack_range)
|
||||
@@ -4825,16 +4862,56 @@ func _auto_attack_target_preview_badge(selected: Dictionary, target: Dictionary)
|
||||
|
||||
|
||||
func _auto_attack_target_preview_badge_for_origin(selected: Dictionary, target: Dictionary, attack_origin: Vector2i) -> Dictionary:
|
||||
var preview := _auto_attack_preview_for_origin(selected, target, attack_origin)
|
||||
if preview.is_empty():
|
||||
return {}
|
||||
var target_cell: Vector2i = preview.get("target_cell", Vector2i(-1, -1))
|
||||
var damage := int(preview.get("damage", 0))
|
||||
var hit_chance := int(preview.get("hit_chance", 0))
|
||||
var result_text := "행공 퇴각" if bool(preview.get("would_defeat", false)) else "행공 -%d" % damage
|
||||
var directional_text := _format_directional_badge_suffix(preview)
|
||||
var counter_text := _format_auto_attack_counter_badge_suffix(preview)
|
||||
return _make_target_preview_badge_at("%s%s %d%%%s" % [result_text, directional_text, hit_chance, counter_text], _auto_attack_preview_badge_kind(preview), target_cell)
|
||||
|
||||
|
||||
func _auto_attack_preview_for_origin(selected: Dictionary, target: Dictionary, attack_origin: Vector2i) -> Dictionary:
|
||||
var target_cell: Vector2i = target.get("pos", Vector2i(-1, -1))
|
||||
if not state.is_inside(attack_origin) or not state.is_inside(target_cell):
|
||||
if selected.is_empty() or target.is_empty() or not state.is_inside(attack_origin) or not state.is_inside(target_cell):
|
||||
return {}
|
||||
if not _can_attack_target_from(selected, attack_origin, target_cell):
|
||||
return {}
|
||||
var candidate_attacker := _auto_attack_candidate_after_move(selected, target_cell, attack_origin)
|
||||
var damage := state.calculate_damage(candidate_attacker, target)
|
||||
var hit_chance := state.calculate_hit_chance(candidate_attacker, target)
|
||||
var result_text := "행공 퇴각" if int(target.get("hp", 0)) - damage <= 0 else "행공 -%d" % damage
|
||||
var directional_text := _format_directional_badge_suffix_for_units(candidate_attacker, target)
|
||||
var counter_text := _format_auto_attack_counter_badge_suffix(candidate_attacker, target, damage)
|
||||
return _make_target_preview_badge_at("%s%s %d%%%s" % [result_text, directional_text, hit_chance, counter_text], _auto_attack_preview_badge_kind(candidate_attacker, target, damage), target_cell)
|
||||
var target_hp_after := maxi(0, int(target.get("hp", 0)) - damage)
|
||||
var directional_info := state.get_directional_attack_info(candidate_attacker, target)
|
||||
var counter_in_range := false
|
||||
var counter_damage := 0
|
||||
var counter_hit_chance := 0
|
||||
var counter_would_defeat := false
|
||||
if bool(target.get("controllable", true)) and (target_hp_after > 0 or hit_chance < 100):
|
||||
counter_in_range = _can_attack_target_from(target, target_cell, attack_origin)
|
||||
if counter_in_range:
|
||||
var counter_attacker := target.duplicate(true)
|
||||
_face_unit_snapshot_toward_cell(counter_attacker, attack_origin)
|
||||
counter_damage = state.calculate_damage(counter_attacker, candidate_attacker)
|
||||
counter_hit_chance = state.calculate_hit_chance(counter_attacker, candidate_attacker)
|
||||
counter_would_defeat = int(candidate_attacker.get("hp", 0)) - counter_damage <= 0
|
||||
return {
|
||||
"origin": attack_origin,
|
||||
"target_cell": target_cell,
|
||||
"damage": damage,
|
||||
"hit_chance": hit_chance,
|
||||
"target_hp_after": target_hp_after,
|
||||
"would_defeat": target_hp_after <= 0,
|
||||
"directional_attack": str(directional_info.get("kind", BattleState.DIRECTIONAL_FRONT_ATTACK)),
|
||||
"directional_bonus": int(directional_info.get("bonus", 0)),
|
||||
"directional_label": str(directional_info.get("label", "")),
|
||||
"counter_in_range": counter_in_range,
|
||||
"counter_damage": counter_damage,
|
||||
"counter_hit_chance": counter_hit_chance,
|
||||
"counter_would_defeat": counter_would_defeat
|
||||
}
|
||||
|
||||
|
||||
func _auto_attack_candidate_after_move(selected: Dictionary, target_cell: Vector2i, attack_origin: Vector2i) -> Dictionary:
|
||||
@@ -4853,39 +4930,22 @@ func _face_unit_snapshot_toward_cell(unit: Dictionary, target_cell: Vector2i) ->
|
||||
unit["facing_x"] = -1 if target_cell.x < from_cell.x else 1
|
||||
|
||||
|
||||
func _format_directional_badge_suffix_for_units(attacker: Dictionary, target: Dictionary) -> String:
|
||||
var directional_info := state.get_directional_attack_info(attacker, target)
|
||||
return _format_directional_badge_suffix({
|
||||
"directional_attack": str(directional_info.get("kind", BattleState.DIRECTIONAL_FRONT_ATTACK)),
|
||||
"directional_bonus": int(directional_info.get("bonus", 0)),
|
||||
"directional_label": str(directional_info.get("label", ""))
|
||||
})
|
||||
|
||||
|
||||
func _format_auto_attack_counter_badge_suffix(candidate_attacker: Dictionary, target: Dictionary, damage: int) -> String:
|
||||
if int(target.get("hp", 0)) - damage <= 0:
|
||||
func _format_auto_attack_counter_badge_suffix(preview: Dictionary) -> String:
|
||||
if not bool(preview.get("counter_in_range", false)):
|
||||
return ""
|
||||
var origin: Vector2i = candidate_attacker.get("pos", Vector2i(-1, -1))
|
||||
var target_cell: Vector2i = target.get("pos", Vector2i(-1, -1))
|
||||
if not _can_attack_target_from(target, target_cell, origin):
|
||||
return ""
|
||||
var counter_damage := state.calculate_damage(target, candidate_attacker)
|
||||
if int(candidate_attacker.get("hp", 0)) - counter_damage <= 0:
|
||||
if bool(preview.get("counter_would_defeat", false)):
|
||||
return " 반격!"
|
||||
return " 반%d" % counter_damage
|
||||
return " 반%d" % int(preview.get("counter_damage", 0))
|
||||
|
||||
|
||||
func _auto_attack_preview_badge_kind(candidate_attacker: Dictionary, target: Dictionary, damage: int) -> String:
|
||||
if int(target.get("hp", 0)) - damage <= 0:
|
||||
func _auto_attack_preview_badge_kind(preview: Dictionary) -> String:
|
||||
if bool(preview.get("would_defeat", false)) and not bool(preview.get("counter_in_range", false)):
|
||||
return "advance"
|
||||
var origin: Vector2i = candidate_attacker.get("pos", Vector2i(-1, -1))
|
||||
var target_cell: Vector2i = target.get("pos", Vector2i(-1, -1))
|
||||
if not _can_attack_target_from(target, target_cell, origin):
|
||||
return "advance"
|
||||
var counter_damage := state.calculate_damage(target, candidate_attacker)
|
||||
if int(candidate_attacker.get("hp", 0)) - counter_damage <= 0:
|
||||
if bool(preview.get("counter_would_defeat", false)):
|
||||
return "danger"
|
||||
return "counter"
|
||||
if bool(preview.get("counter_in_range", false)):
|
||||
return "counter"
|
||||
return "advance"
|
||||
|
||||
|
||||
func _skill_target_preview_badge(selected: Dictionary, target: Dictionary) -> Dictionary:
|
||||
|
||||
Reference in New Issue
Block a user