Show side event markers
This commit is contained in:
@@ -602,6 +602,7 @@
|
|||||||
"when": {
|
"when": {
|
||||||
"type": "unit_reaches_tile",
|
"type": "unit_reaches_tile",
|
||||||
"team": "player",
|
"team": "player",
|
||||||
|
"label": "북숲 보급고",
|
||||||
"pos": [
|
"pos": [
|
||||||
4,
|
4,
|
||||||
2
|
2
|
||||||
@@ -641,6 +642,7 @@
|
|||||||
"when": {
|
"when": {
|
||||||
"type": "unit_reaches_tile",
|
"type": "unit_reaches_tile",
|
||||||
"team": "player",
|
"team": "player",
|
||||||
|
"label": "마을 보급",
|
||||||
"cells": [
|
"cells": [
|
||||||
[
|
[
|
||||||
6,
|
6,
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -613,7 +613,7 @@ Movement-triggered pickups can use `grant_item` for one item or `grant_items` fo
|
|||||||
{
|
{
|
||||||
"id": "northern_woods_cache",
|
"id": "northern_woods_cache",
|
||||||
"once": true,
|
"once": true,
|
||||||
"when": { "type": "unit_reaches_tile", "team": "player", "pos": [3, 2] },
|
"when": { "type": "unit_reaches_tile", "team": "player", "label": "북숲 보급고", "pos": [3, 2] },
|
||||||
"actions": [
|
"actions": [
|
||||||
{ "type": "log", "text": "The northern woods cache is recovered." },
|
{ "type": "log", "text": "The northern woods cache is recovered." },
|
||||||
{ "type": "grant_items", "items": ["bean", { "item_id": "antidote", "count": 2 }] }
|
{ "type": "grant_items", "items": ["bean", { "item_id": "antidote", "count": 2 }] }
|
||||||
@@ -621,6 +621,8 @@ Movement-triggered pickups can use `grant_item` for one item or `grant_items` fo
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Rewarding `unit_reaches_tile` events are also exposed as optional side-event markers on the battlefield when they are not already objective cells. Use `when.label` to keep the map marker and tile-info text readable in Korean; otherwise the marker falls back to a short supply or gold label derived from its reward action.
|
||||||
|
|
||||||
Movement-triggered gold caches can use `grant_gold` with a positive `amount`. The gold is shown immediately through log and floating feedback, but it stays battle-only until victory commits the battle result.
|
Movement-triggered gold caches can use `grant_gold` with a positive `amount`. The gold is shown immediately through log and floating feedback, but it stays battle-only until victory commits the battle result.
|
||||||
|
|
||||||
```json
|
```json
|
||||||
|
|||||||
@@ -1749,6 +1749,67 @@ func get_objective_cell_label(cell: Vector2i) -> String:
|
|||||||
return _objective_cell_label_for_condition(battle_conditions.get("victory", {}), cell)
|
return _objective_cell_label_for_condition(battle_conditions.get("victory", {}), cell)
|
||||||
|
|
||||||
|
|
||||||
|
func get_event_markers() -> Array[Dictionary]:
|
||||||
|
var result: Array[Dictionary] = []
|
||||||
|
var objective_cells := get_objective_cells()
|
||||||
|
for event in battle_events:
|
||||||
|
var event_id := str(event.get("id", ""))
|
||||||
|
if bool(event.get("once", false)) and fired_event_ids.has(event_id):
|
||||||
|
continue
|
||||||
|
var when: Dictionary = event.get("when", {})
|
||||||
|
if str(when.get("type", "")) != "unit_reaches_tile":
|
||||||
|
continue
|
||||||
|
if not _event_gate_open(when) or not _campaign_flags_match(when.get("campaign_flags", {})):
|
||||||
|
continue
|
||||||
|
if not _event_has_reward_action(event):
|
||||||
|
continue
|
||||||
|
var cells: Array[Vector2i] = []
|
||||||
|
for cell in _condition_cells(when):
|
||||||
|
if not objective_cells.has(cell) and not cells.has(cell):
|
||||||
|
cells.append(cell)
|
||||||
|
if cells.is_empty():
|
||||||
|
continue
|
||||||
|
result.append({
|
||||||
|
"id": event_id,
|
||||||
|
"label": _event_marker_label(event),
|
||||||
|
"kind": _event_marker_kind(event),
|
||||||
|
"cells": cells
|
||||||
|
})
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
func get_event_marker_cells() -> Array[Vector2i]:
|
||||||
|
var result: Array[Vector2i] = []
|
||||||
|
for marker in get_event_markers():
|
||||||
|
var cells = marker.get("cells", [])
|
||||||
|
if typeof(cells) != TYPE_ARRAY:
|
||||||
|
continue
|
||||||
|
for cell in cells:
|
||||||
|
if typeof(cell) == TYPE_VECTOR2I and not result.has(cell):
|
||||||
|
result.append(cell)
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
func get_event_marker_label(cell: Vector2i) -> String:
|
||||||
|
if not is_inside(cell):
|
||||||
|
return ""
|
||||||
|
for marker in get_event_markers():
|
||||||
|
var cells = marker.get("cells", [])
|
||||||
|
if typeof(cells) == TYPE_ARRAY and cells.has(cell):
|
||||||
|
return str(marker.get("label", "전장 표식"))
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
|
func get_event_marker_kind(cell: Vector2i) -> String:
|
||||||
|
if not is_inside(cell):
|
||||||
|
return ""
|
||||||
|
for marker in get_event_markers():
|
||||||
|
var cells = marker.get("cells", [])
|
||||||
|
if typeof(cells) == TYPE_ARRAY and cells.has(cell):
|
||||||
|
return str(marker.get("kind", "event"))
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
func get_objective_progress_lines(include_defeat_risks := false, include_turn_limit := true) -> Array[String]:
|
func get_objective_progress_lines(include_defeat_risks := false, include_turn_limit := true) -> Array[String]:
|
||||||
var result: Array[String] = []
|
var result: Array[String] = []
|
||||||
_append_condition_progress_lines(battle_conditions.get("victory", {}), result, "victory")
|
_append_condition_progress_lines(battle_conditions.get("victory", {}), result, "victory")
|
||||||
@@ -4260,6 +4321,47 @@ func _objective_cell_label_for_after_event(event_id: String, cell: Vector2i) ->
|
|||||||
return ""
|
return ""
|
||||||
|
|
||||||
|
|
||||||
|
func _event_has_reward_action(event: Dictionary) -> bool:
|
||||||
|
for action in event.get("actions", []):
|
||||||
|
if typeof(action) != TYPE_DICTIONARY:
|
||||||
|
continue
|
||||||
|
var action_type := str((action as Dictionary).get("type", ""))
|
||||||
|
if action_type == "grant_item" or action_type == "grant_items" or action_type == "grant_gold":
|
||||||
|
return true
|
||||||
|
return false
|
||||||
|
|
||||||
|
|
||||||
|
func _event_marker_label(event: Dictionary) -> String:
|
||||||
|
var when: Dictionary = event.get("when", {})
|
||||||
|
var label := _condition_destination_label(when)
|
||||||
|
if label != "전장 표식":
|
||||||
|
return label
|
||||||
|
var kind := _event_marker_kind(event)
|
||||||
|
if kind == "gold":
|
||||||
|
return "군자금"
|
||||||
|
if kind == "supply":
|
||||||
|
return "보급"
|
||||||
|
return "전장 표식"
|
||||||
|
|
||||||
|
|
||||||
|
func _event_marker_kind(event: Dictionary) -> String:
|
||||||
|
var has_item := false
|
||||||
|
var has_gold := false
|
||||||
|
for action in event.get("actions", []):
|
||||||
|
if typeof(action) != TYPE_DICTIONARY:
|
||||||
|
continue
|
||||||
|
var action_type := str((action as Dictionary).get("type", ""))
|
||||||
|
if action_type == "grant_item" or action_type == "grant_items":
|
||||||
|
has_item = true
|
||||||
|
elif action_type == "grant_gold":
|
||||||
|
has_gold = true
|
||||||
|
if has_gold and not has_item:
|
||||||
|
return "gold"
|
||||||
|
if has_item:
|
||||||
|
return "supply"
|
||||||
|
return "event"
|
||||||
|
|
||||||
|
|
||||||
func _find_turn_limit(condition_group) -> int:
|
func _find_turn_limit(condition_group) -> int:
|
||||||
if typeof(condition_group) == TYPE_ARRAY:
|
if typeof(condition_group) == TYPE_ARRAY:
|
||||||
var best_limit := 0
|
var best_limit := 0
|
||||||
|
|||||||
@@ -505,6 +505,7 @@ func _draw() -> void:
|
|||||||
_draw_recovery_terrain_markers(_visible_cell_bounds())
|
_draw_recovery_terrain_markers(_visible_cell_bounds())
|
||||||
_draw_units()
|
_draw_units()
|
||||||
_draw_objective_foreground_markers()
|
_draw_objective_foreground_markers()
|
||||||
|
_draw_event_foreground_markers()
|
||||||
_draw_target_selection_markers()
|
_draw_target_selection_markers()
|
||||||
_draw_attack_effects()
|
_draw_attack_effects()
|
||||||
_draw_target_preview_badge()
|
_draw_target_preview_badge()
|
||||||
@@ -3275,7 +3276,7 @@ func _draw_objective_seal_marker(rect: Rect2) -> void:
|
|||||||
func _draw_objective_foreground_markers() -> void:
|
func _draw_objective_foreground_markers() -> void:
|
||||||
var font := _draw_ui_font(true)
|
var font := _draw_ui_font(true)
|
||||||
for cell in state.get_objective_cells():
|
for cell in state.get_objective_cells():
|
||||||
if not state.is_inside(cell):
|
if not state.is_inside(cell) or not _cell_is_in_visible_bounds(cell):
|
||||||
continue
|
continue
|
||||||
var rect := _rect_for_cell(cell)
|
var rect := _rect_for_cell(cell)
|
||||||
_draw_objective_corner_brackets(rect)
|
_draw_objective_corner_brackets(rect)
|
||||||
@@ -3285,6 +3286,58 @@ func _draw_objective_foreground_markers() -> void:
|
|||||||
_draw_ink_text(font, label_rect.position + Vector2(0, 12), "목", HORIZONTAL_ALIGNMENT_CENTER, label_rect.size.x, 11, UI_PARCHMENT_TEXT)
|
_draw_ink_text(font, label_rect.position + Vector2(0, 12), "목", HORIZONTAL_ALIGNMENT_CENTER, label_rect.size.x, 11, UI_PARCHMENT_TEXT)
|
||||||
|
|
||||||
|
|
||||||
|
func _draw_event_foreground_markers() -> void:
|
||||||
|
var font := _draw_ui_font(true)
|
||||||
|
for marker in state.get_event_markers():
|
||||||
|
var cells = marker.get("cells", [])
|
||||||
|
if typeof(cells) != TYPE_ARRAY:
|
||||||
|
continue
|
||||||
|
for cell in cells:
|
||||||
|
if typeof(cell) != TYPE_VECTOR2I or not state.is_inside(cell) or not _cell_is_in_visible_bounds(cell):
|
||||||
|
continue
|
||||||
|
_draw_event_foreground_marker(_rect_for_cell(cell), str(marker.get("label", "보급")), str(marker.get("kind", "event")), font)
|
||||||
|
|
||||||
|
|
||||||
|
func _draw_event_foreground_marker(rect: Rect2, label: String, kind: String, font: Font) -> void:
|
||||||
|
var color := _event_marker_color(kind)
|
||||||
|
var marker_rect := _event_marker_rect(rect)
|
||||||
|
draw_rect(marker_rect, Color(0.035, 0.022, 0.012, 0.76))
|
||||||
|
draw_rect(marker_rect.grow(-2.0), Color(color.r, color.g, color.b, 0.72))
|
||||||
|
draw_rect(marker_rect, Color(UI_OLD_BRONZE.r, UI_OLD_BRONZE.g, UI_OLD_BRONZE.b, 0.88), false, 1.2)
|
||||||
|
var text := _event_marker_abbrev(label, kind)
|
||||||
|
_draw_ink_text(font, marker_rect.position + Vector2(0, 12), text, HORIZONTAL_ALIGNMENT_CENTER, marker_rect.size.x, 11, UI_PARCHMENT_TEXT)
|
||||||
|
var knot := marker_rect.get_center() + Vector2(0.0, -8.0)
|
||||||
|
draw_line(knot + Vector2(-5.0, 0.0), knot + Vector2(5.0, 0.0), Color(0.10, 0.05, 0.02, 0.70), 1.2)
|
||||||
|
draw_line(knot + Vector2(0.0, -4.0), knot + Vector2(0.0, 4.0), Color(0.10, 0.05, 0.02, 0.56), 1.0)
|
||||||
|
|
||||||
|
|
||||||
|
func _event_marker_rect(cell_rect: Rect2) -> Rect2:
|
||||||
|
return Rect2(cell_rect.position + Vector2(4.0, 26.0), Vector2(23.0, 17.0))
|
||||||
|
|
||||||
|
|
||||||
|
func _event_marker_color(kind: String) -> Color:
|
||||||
|
if kind == "gold":
|
||||||
|
return Color(0.92, 0.62, 0.18, 1.0)
|
||||||
|
if kind == "supply":
|
||||||
|
return Color(0.38, 0.62, 0.34, 1.0)
|
||||||
|
return Color(UI_MUTED_JADE.r, UI_MUTED_JADE.g, UI_MUTED_JADE.b, 1.0)
|
||||||
|
|
||||||
|
|
||||||
|
func _event_marker_abbrev(label: String, kind: String) -> String:
|
||||||
|
if kind == "gold":
|
||||||
|
return "금"
|
||||||
|
var text := label.strip_edges()
|
||||||
|
if text.is_empty():
|
||||||
|
return "보" if kind == "supply" else "표"
|
||||||
|
if text.contains("마을"):
|
||||||
|
return "마"
|
||||||
|
if text.contains("숲"):
|
||||||
|
return "숲"
|
||||||
|
if text.contains("보급"):
|
||||||
|
return "보"
|
||||||
|
return text.substr(0, 1)
|
||||||
|
|
||||||
|
|
||||||
func _draw_objective_corner_brackets(rect: Rect2) -> void:
|
func _draw_objective_corner_brackets(rect: Rect2) -> void:
|
||||||
var bracket := 12.0
|
var bracket := 12.0
|
||||||
var inset := 4.0
|
var inset := 4.0
|
||||||
@@ -3380,6 +3433,8 @@ func _draw_units() -> void:
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
var rect := _visual_rect_for_unit(unit)
|
var rect := _visual_rect_for_unit(unit)
|
||||||
|
if not rect.intersects(_map_view_rect().grow(TILE_SIZE)):
|
||||||
|
continue
|
||||||
var center := rect.position + rect.size * 0.5
|
var center := rect.position + rect.size * 0.5
|
||||||
var team_color := PLAYER_COLOR if unit.get("team", "") == BattleState.TEAM_PLAYER else ENEMY_COLOR
|
var team_color := PLAYER_COLOR if unit.get("team", "") == BattleState.TEAM_PLAYER else ENEMY_COLOR
|
||||||
var acted := bool(unit.get("acted", false))
|
var acted := bool(unit.get("acted", false))
|
||||||
@@ -5543,6 +5598,9 @@ func _update_cell_info() -> void:
|
|||||||
var objective_cell_label := state.get_objective_cell_label(cell)
|
var objective_cell_label := state.get_objective_cell_label(cell)
|
||||||
if not objective_cell_label.is_empty():
|
if not objective_cell_label.is_empty():
|
||||||
text += "\n목표: %s" % objective_cell_label
|
text += "\n목표: %s" % objective_cell_label
|
||||||
|
var event_marker_label := state.get_event_marker_label(cell)
|
||||||
|
if not event_marker_label.is_empty():
|
||||||
|
text += "\n표식: %s" % event_marker_label
|
||||||
if show_threat_overlay:
|
if show_threat_overlay:
|
||||||
var threat_names := state.get_threatening_unit_names(cell, BattleState.TEAM_ENEMY)
|
var threat_names := state.get_threatening_unit_names(cell, BattleState.TEAM_ENEMY)
|
||||||
if not threat_names.is_empty():
|
if not threat_names.is_empty():
|
||||||
|
|||||||
@@ -172,6 +172,7 @@ func _init() -> void:
|
|||||||
_check_event_gated_objective_markers(failures)
|
_check_event_gated_objective_markers(failures)
|
||||||
_check_event_gated_multi_cell_objective_markers(failures)
|
_check_event_gated_multi_cell_objective_markers(failures)
|
||||||
_check_turn_gated_objective_does_not_create_marker(failures)
|
_check_turn_gated_objective_does_not_create_marker(failures)
|
||||||
|
_check_opening_battle_side_event_markers(failures)
|
||||||
_check_opening_battle_requires_castle_capture(failures)
|
_check_opening_battle_requires_castle_capture(failures)
|
||||||
_check_opening_battle_capture_events(failures)
|
_check_opening_battle_capture_events(failures)
|
||||||
_check_sishui_gate_extended_pressure(failures)
|
_check_sishui_gate_extended_pressure(failures)
|
||||||
@@ -327,6 +328,42 @@ func _check_turn_gated_objective_does_not_create_marker(failures: Array[String])
|
|||||||
failures.append("turn-gated objective should not create map markers: %s" % str(marker_cells))
|
failures.append("turn-gated objective should not create map markers: %s" % str(marker_cells))
|
||||||
|
|
||||||
|
|
||||||
|
func _check_opening_battle_side_event_markers(failures: Array[String]) -> void:
|
||||||
|
var state = BattleStateScript.new()
|
||||||
|
if not state.load_battle("res://data/scenarios/001_yellow_turbans.json"):
|
||||||
|
failures.append("opening side event marker smoke could not load scenario")
|
||||||
|
return
|
||||||
|
|
||||||
|
var cache_cell := Vector2i(4, 2)
|
||||||
|
var village_cell := Vector2i(6, 5)
|
||||||
|
var initial_cells := state.get_event_marker_cells()
|
||||||
|
if not initial_cells.has(cache_cell):
|
||||||
|
failures.append("opening battle should mark the northern woods supply cache: %s" % str(initial_cells))
|
||||||
|
if not initial_cells.has(village_cell):
|
||||||
|
failures.append("opening battle should mark village supply side event cells: %s" % str(initial_cells))
|
||||||
|
if state.get_event_marker_label(cache_cell) != "북숲 보급고":
|
||||||
|
failures.append("northern woods cache marker should expose Korean label: %s" % state.get_event_marker_label(cache_cell))
|
||||||
|
if state.get_event_marker_kind(cache_cell) != "supply":
|
||||||
|
failures.append("northern woods cache marker should be a supply marker")
|
||||||
|
if state.get_event_marker_label(village_cell) != "마을 보급":
|
||||||
|
failures.append("village supply marker should expose Korean label: %s" % state.get_event_marker_label(village_cell))
|
||||||
|
if state.get_event_marker_kind(village_cell) != "supply":
|
||||||
|
failures.append("village supply marker should be a supply marker")
|
||||||
|
|
||||||
|
var cao_cao: Dictionary = state.get_unit("cao_cao")
|
||||||
|
cao_cao["pos"] = cache_cell
|
||||||
|
state._run_events("unit_reaches_tile", BattleStateScript.TEAM_PLAYER, 1, cao_cao)
|
||||||
|
if state.get_event_marker_cells().has(cache_cell):
|
||||||
|
failures.append("northern woods supply marker should clear after pickup")
|
||||||
|
if int(state.get_inventory_snapshot().get("bean", 0)) < 1:
|
||||||
|
failures.append("northern woods supply marker event should grant a Bean")
|
||||||
|
|
||||||
|
cao_cao["pos"] = village_cell
|
||||||
|
state._run_events("unit_reaches_tile", BattleStateScript.TEAM_PLAYER, 2, cao_cao)
|
||||||
|
if state.get_event_marker_cells().has(village_cell):
|
||||||
|
failures.append("village supply marker should clear after securing the village")
|
||||||
|
|
||||||
|
|
||||||
func _check_opening_battle_requires_castle_capture(failures: Array[String]) -> void:
|
func _check_opening_battle_requires_castle_capture(failures: Array[String]) -> void:
|
||||||
var state = BattleStateScript.new()
|
var state = BattleStateScript.new()
|
||||||
if not state.load_battle("res://data/scenarios/001_yellow_turbans.json"):
|
if not state.load_battle("res://data/scenarios/001_yellow_turbans.json"):
|
||||||
|
|||||||
@@ -3696,8 +3696,23 @@ func _check_terrain_and_unit_presentation(failures: Array[String]) -> void:
|
|||||||
scene._update_cell_info()
|
scene._update_cell_info()
|
||||||
if scene.cell_info_label == null or not scene.cell_info_label.text.contains("목표: 성채 장악") or not scene.cell_info_label.text.contains("회복 +8"):
|
if scene.cell_info_label == null or not scene.cell_info_label.text.contains("목표: 성채 장악") or not scene.cell_info_label.text.contains("회복 +8"):
|
||||||
failures.append("castle objective cell info should expose capture label and healing: %s" % scene.cell_info_label.text)
|
failures.append("castle objective cell info should expose capture label and healing: %s" % scene.cell_info_label.text)
|
||||||
|
scene.hover_cell = Vector2i(4, 2)
|
||||||
|
scene._update_cell_info()
|
||||||
|
if scene.cell_info_label == null or not scene.cell_info_label.text.contains("표식: 북숲 보급고"):
|
||||||
|
failures.append("side event marker cell info should expose the supply label: %s" % scene.cell_info_label.text)
|
||||||
scene.cell_info_label.free()
|
scene.cell_info_label.free()
|
||||||
scene.cell_info_label = null
|
scene.cell_info_label = null
|
||||||
|
var event_marker_rect: Rect2 = scene._event_marker_rect(scene._rect_for_cell(Vector2i(4, 2)))
|
||||||
|
if not scene._rect_for_cell(Vector2i(4, 2)).encloses(event_marker_rect):
|
||||||
|
failures.append("Side event marker should stay inside its tile: %s" % str(event_marker_rect))
|
||||||
|
if scene._event_marker_abbrev("북숲 보급고", "supply") != "숲":
|
||||||
|
failures.append("Northern woods marker should use a compact forest glyph")
|
||||||
|
if scene._event_marker_abbrev("마을 보급", "supply") != "마":
|
||||||
|
failures.append("Village supply marker should use a compact village glyph")
|
||||||
|
if scene._event_marker_abbrev("", "supply") != "보":
|
||||||
|
failures.append("Supply marker fallback should stay concise")
|
||||||
|
if scene._event_marker_abbrev("군자금", "gold") != "금":
|
||||||
|
failures.append("Gold marker should use a compact gold glyph")
|
||||||
var background_fill: Color = scene._terrain_fill_color(Vector2i(0, 0), "G", true)
|
var background_fill: Color = scene._terrain_fill_color(Vector2i(0, 0), "G", true)
|
||||||
var fallback_fill: Color = scene._terrain_fill_color(Vector2i(0, 0), "G", false)
|
var fallback_fill: Color = scene._terrain_fill_color(Vector2i(0, 0), "G", false)
|
||||||
if background_fill.a >= 0.20:
|
if background_fill.a >= 0.20:
|
||||||
|
|||||||
Reference in New Issue
Block a user