Add objective progress tracker

This commit is contained in:
2026-06-18 13:21:39 +09:00
parent 7ad34697f9
commit f15caf49e7
9 changed files with 379 additions and 5 deletions

View File

@@ -1423,6 +1423,20 @@ func get_objective_cells() -> Array[Vector2i]:
return result
func get_objective_progress_lines(include_defeat_risks := false) -> Array[String]:
var result: Array[String] = []
_append_condition_progress_lines(battle_conditions.get("victory", {}), result, "victory")
if include_defeat_risks:
_append_condition_progress_lines(battle_conditions.get("defeat", {}), result, "defeat")
else:
_append_turn_limit_progress_line(result)
return result
func get_objective_progress_text(include_defeat_risks := false) -> String:
return _join_strings(get_objective_progress_lines(include_defeat_risks), " | ")
func get_first_skill_id(unit_id: String) -> String:
var unit := get_unit(unit_id)
if unit.is_empty() or typeof(unit.get("skills", [])) != TYPE_ARRAY:
@@ -3120,6 +3134,270 @@ func _is_condition_met(condition) -> bool:
return false
func _append_condition_progress_lines(condition_group, result: Array[String], mode: String, pending_parent := false) -> void:
if typeof(condition_group) == TYPE_ARRAY:
for condition in condition_group:
_append_condition_progress_lines(condition, result, mode, pending_parent)
return
if typeof(condition_group) != TYPE_DICTIONARY:
return
var pending := pending_parent or not _condition_gate_open(condition_group)
if not _condition_gate_open(condition_group):
_append_after_event_progress_line(result, str(condition_group.get("after_event", "")))
var condition_type := str(condition_group.get("type", ""))
if condition_type == "all" or condition_type == "any":
_append_condition_progress_lines(condition_group.get("conditions", []), result, mode, pending)
return
if condition_type == "all_units_defeated":
_append_all_units_progress_line(result, str(condition_group.get("team", "")), mode, pending)
elif condition_type == "all_enemies_defeated":
_append_all_units_progress_line(result, TEAM_ENEMY, mode, pending)
elif condition_type == "all_players_defeated":
_append_all_units_progress_line(result, TEAM_PLAYER, mode, pending)
elif condition_type == "any_unit_defeated":
_append_target_units_progress_line(result, condition_group.get("unit_ids", []), mode, pending)
elif condition_type == "any_officer_defeated":
_append_target_officers_progress_line(
result,
condition_group.get("officer_ids", []),
str(condition_group.get("team", "")),
mode,
pending
)
elif condition_type == "unit_reaches_tile":
_append_destination_progress_line(result, condition_group, pending)
elif condition_type == "turn_limit" or condition_type == "turn_reached":
_append_turn_condition_progress_line(result, condition_group, mode, pending)
func _append_all_units_progress_line(result: Array[String], team: String, mode: String, pending := false) -> void:
var total := _count_deployed_units(team)
if total <= 0:
return
var living := _count_living_deployed_units(team)
if mode == "defeat":
_append_progress_line(result, "%s remaining %d/%d" % [_condition_team_label(team), living, total], pending)
return
_append_progress_line(result, "%s defeated %d/%d" % [_condition_team_label(team), total - living, total], pending)
func _append_target_units_progress_line(result: Array[String], unit_ids, mode: String, pending := false) -> void:
var counts := _count_condition_units(unit_ids)
var total := int(counts.get("total", 0))
if total <= 0:
return
var defeated := int(counts.get("defeated", 0))
if mode == "defeat" and total == 1:
var unit_name := _condition_unit_display_name(unit_ids[0])
var status := "defeated" if defeated > 0 else "safe"
_append_progress_line(result, "%s %s" % [unit_name, status], pending)
return
var label := "Protected losses" if mode == "defeat" else "Target units defeated"
var suffix := " (defeat at 1)" if mode == "defeat" else ""
_append_progress_line(result, "%s %d/%d%s" % [label, defeated, total, suffix], pending)
func _append_target_officers_progress_line(
result: Array[String],
officer_ids,
team: String,
mode: String,
pending := false
) -> void:
var counts := _count_condition_officers(officer_ids, team)
var total := int(counts.get("total", 0))
if total <= 0:
return
var defeated := int(counts.get("defeated", 0))
if mode == "defeat" and total == 1:
var officer_name := _condition_officer_display_name(str(officer_ids[0]), team)
var status := "defeated" if defeated > 0 else "safe"
_append_progress_line(result, "%s %s" % [officer_name, status], pending)
return
var label := "Officer losses" if mode == "defeat" else "Target officers defeated"
var suffix := " (defeat at 1)" if mode == "defeat" else ""
_append_progress_line(result, "%s %d/%d%s" % [label, defeated, total, suffix], pending)
func _append_destination_progress_line(result: Array[String], condition: Dictionary, pending := false) -> void:
var target := _condition_cell(condition.get("pos", []))
if not is_inside(target):
return
var status := "reached" if _unit_reaches_tile(condition) else "not reached"
_append_progress_line(result, "Destination %s: %s" % [_format_cell(target), status], pending)
func _append_turn_limit_progress_line(result: Array[String]) -> void:
var limit := get_turn_limit()
if limit <= 0:
return
var turns_left: int = maxi(0, limit - turn_number + 1)
_append_progress_line(result, "Turns left %d" % turns_left)
func _append_after_event_progress_line(result: Array[String], event_id: String) -> void:
if event_id.is_empty() or fired_event_ids.has(event_id):
return
var event := _event_by_id(event_id)
if event.is_empty():
return
var when: Dictionary = event.get("when", {})
var trigger_type := str(when.get("type", ""))
if trigger_type == "unit_reaches_tile":
_append_event_destination_progress_line(result, when)
elif trigger_type == "turn_start":
var target_turn := int(when.get("turn", 0))
if target_turn > 0:
_append_progress_line(result, "Objective unlocks on Turn %d" % target_turn)
func _append_event_destination_progress_line(result: Array[String], when: Dictionary) -> void:
var target := _condition_cell(when.get("pos", []))
if not is_inside(target):
return
var status := "reached" if _unit_reaches_tile(when) else "not reached"
_append_progress_line(result, "Reach %s: %s" % [_format_cell(target), status])
func _append_turn_condition_progress_line(result: Array[String], condition: Dictionary, mode: String, pending := false) -> void:
var condition_type := str(condition.get("type", ""))
if condition_type == "turn_limit":
var limit := int(condition.get("turn", condition.get("limit", 0)))
if limit <= 0:
return
_append_progress_line(result, "Turns left %d" % maxi(0, limit - turn_number + 1), pending)
elif condition_type == "turn_reached":
var target_turn := int(condition.get("turn", 0))
if target_turn <= 0:
return
if mode == "defeat":
var target_team := str(condition.get("team", ""))
var limit_turn := target_turn - 1 if target_team == TEAM_PLAYER else target_turn
_append_progress_line(result, "Turns left %d" % maxi(0, limit_turn - turn_number + 1), pending)
return
_append_progress_line(result, "Turn %d/%d" % [min(turn_number, target_turn), target_turn], pending)
func _append_progress_line(result: Array[String], text: String, pending := false) -> void:
if text.is_empty():
return
if pending:
text += " (pending)"
if not result.has(text):
result.append(text)
func _count_deployed_units(team := "") -> int:
var count := 0
for unit in units:
if not unit.get("deployed", true):
continue
if not team.is_empty() and unit.get("team", "") != team:
continue
count += 1
return count
func _count_living_deployed_units(team := "") -> int:
var count := 0
for unit in units:
if not unit.get("deployed", true):
continue
if not unit.get("alive", false):
continue
if not team.is_empty() and unit.get("team", "") != team:
continue
count += 1
return count
func _count_condition_units(unit_ids) -> Dictionary:
var result := {"total": 0, "defeated": 0}
if typeof(unit_ids) != TYPE_ARRAY:
return result
var seen := {}
for unit_id in unit_ids:
var normalized_id := str(unit_id)
if normalized_id.is_empty() or seen.has(normalized_id):
continue
seen[normalized_id] = true
var unit := get_unit(normalized_id)
if unit.is_empty() or not unit.get("deployed", true):
continue
result["total"] = int(result["total"]) + 1
if not unit.get("alive", false):
result["defeated"] = int(result["defeated"]) + 1
return result
func _count_condition_officers(officer_ids, team := "") -> Dictionary:
var result := {"total": 0, "defeated": 0}
if typeof(officer_ids) != TYPE_ARRAY:
return result
var seen := {}
for officer_id in officer_ids:
var normalized_id := str(officer_id)
if normalized_id.is_empty() or seen.has(normalized_id):
continue
seen[normalized_id] = true
if not _condition_officer_exists(normalized_id, team):
continue
result["total"] = int(result["total"]) + 1
if _is_officer_defeated(normalized_id, team):
result["defeated"] = int(result["defeated"]) + 1
return result
func _condition_officer_exists(officer_id: String, team := "") -> bool:
if officer_id.is_empty():
return false
for unit in units:
if not team.is_empty() and unit.get("team", "") != team:
continue
if not unit.get("deployed", true):
continue
if str(unit.get("officer_id", "")) == officer_id:
return true
return false
func _condition_unit_display_name(unit_id: String) -> String:
var unit := get_unit(unit_id)
if unit.is_empty():
return unit_id
return str(unit.get("name", unit_id))
func _condition_officer_display_name(officer_id: String, team := "") -> String:
for unit in units:
if not team.is_empty() and unit.get("team", "") != team:
continue
if not unit.get("deployed", true):
continue
if str(unit.get("officer_id", "")) == officer_id:
return str(unit.get("name", officer_id))
var officer: Dictionary = data_catalog.officers.get(officer_id, {})
return str(officer.get("name", officer_id))
func _condition_team_label(team: String) -> String:
if team == TEAM_ENEMY:
return "Enemies"
if team == TEAM_PLAYER:
return "Allies"
return "Units"
func _event_by_id(event_id: String) -> Dictionary:
if event_id.is_empty():
return {}
for event in battle_events:
if str(event.get("id", "")) == event_id:
return event
return {}
func _is_turn_limit_exceeded(condition: Dictionary) -> bool:
var limit := int(condition.get("turn", condition.get("limit", 0)))
if limit <= 0: