feat(@projects/@magic-civilization): ✨ add grudge badge and combat preview ecology support
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
parent
21443d1ca6
commit
653662d0f4
7 changed files with 187 additions and 1 deletions
|
|
@ -758,6 +758,15 @@
|
|||
"end_game_stats_rankings_header": "Final Rankings",
|
||||
"end_game_stats_events_header": "Key Events",
|
||||
"stats_domain_research_header": "Research by Domain",
|
||||
"combat_grudge_badge": "[Grudge]",
|
||||
"notification_dismiss": "Dismiss",
|
||||
"fmt_boss_tile": "Location: (%d, %d)",
|
||||
"fmt_boss_devastation_tier": "Devastation: Tier %d and below",
|
||||
"fmt_boss_range": "Range: %d hexes",
|
||||
"loot_popup_title": "Loot Acquired",
|
||||
"loot_tier_legendary": "Legendary",
|
||||
"loot_tier_rare": "Rare",
|
||||
"loot_tier_common": "Common",
|
||||
"end_game_stats_title": "Final Report",
|
||||
"end_game_stats_subtitle": "Game Ended",
|
||||
"end_game_stats_close": "Continue",
|
||||
|
|
|
|||
|
|
@ -29,6 +29,11 @@ var _game_map: RefCounted = null
|
|||
var _all_units: Array = []
|
||||
var _resolver: GdCombatResolver = null
|
||||
|
||||
## p1-58 ecology cognition — optional GdFaunaEcology bridge for grudge display.
|
||||
## Null when the ecology bridge has not been initialized (e.g. proof scenes,
|
||||
## early-game turns before ecology is seeded). Set via `set_fauna_ecology`.
|
||||
var _fauna_ecology: GdFaunaEcology = null
|
||||
|
||||
## p2-55 — single-use posture override chosen via the chip row. Cleared each
|
||||
## time `show_preview` is called. -1 means "use the resolved posture
|
||||
## (override → civ default → global)" computed by `_resolve_active_posture`.
|
||||
|
|
@ -78,6 +83,12 @@ func _ready() -> void:
|
|||
_vs_label.text = ThemeVocabulary.lookup("combat_preview_vs")
|
||||
_attack_button.text = ThemeVocabulary.lookup("combat_preview_attack")
|
||||
_cancel_button.text = ThemeVocabulary.lookup("combat_preview_cancel")
|
||||
|
||||
|
||||
## p1-58: inject the fauna ecology bridge. Call from the parent scene after
|
||||
## EcologyEngine is initialized. Safe to omit — grudge badge is silently hidden.
|
||||
func set_fauna_ecology(fe: GdFaunaEcology) -> void:
|
||||
_fauna_ecology = fe
|
||||
_attack_button.pressed.connect(_on_attack_pressed)
|
||||
_cancel_button.pressed.connect(_on_cancel_pressed)
|
||||
_resolver = GdCombatResolver.new()
|
||||
|
|
@ -104,6 +115,7 @@ func show_preview(
|
|||
_populate_unit_info(defender, _def_name, _def_hp, _def_atk, _def_def, _def_keywords)
|
||||
_populate_modifiers()
|
||||
_populate_resolution_row()
|
||||
_populate_grudge_badge(attacker, defender)
|
||||
visible = true
|
||||
|
||||
|
||||
|
|
@ -545,3 +557,38 @@ func _on_capture_prompt_action(action: StringName, modal: AcceptDialog) -> void:
|
|||
_engagement_posture = int(s.trim_prefix("posture_"))
|
||||
modal.queue_free()
|
||||
_resolve_engagement()
|
||||
|
||||
|
||||
## p1-58 ecology cognition — show/hide grudge badge.
|
||||
##
|
||||
## Wild-creature units carry `ecology_species_id: int` and `tile_pos: Vector2i`
|
||||
## fields. If the defending creature holds a live grudge against the attacker's
|
||||
## player, a "[Grudge]" label is shown in the preview pane.
|
||||
##
|
||||
## Silently no-ops when `_fauna_ecology` is null, when the defender does not
|
||||
## carry the wild-creature fields, or when no grudge is active.
|
||||
func _populate_grudge_badge(atk: RefCounted, def: RefCounted) -> void:
|
||||
var old: Node = get_node_or_null("GrudgeBadge")
|
||||
if old != null:
|
||||
old.queue_free()
|
||||
if _fauna_ecology == null or atk == null or def == null:
|
||||
return
|
||||
# Wild-creature field guard — regular units won't have these.
|
||||
if not def.get_meta_list().has("ecology_species_id") and def.get("ecology_species_id") == null:
|
||||
return
|
||||
var species_id: int = int(def.get("ecology_species_id"))
|
||||
var raw_pos: Dictionary = def.get("tile_pos_dict") if def.get("tile_pos_dict") != null else {}
|
||||
var tile_x: int = int(raw_pos.get("x", -1))
|
||||
var tile_y: int = int(raw_pos.get("y", -1))
|
||||
if tile_x < 0 or tile_y < 0:
|
||||
return
|
||||
var player_index: int = int(atk.get("player_index"))
|
||||
if not _fauna_ecology.grudge_against(tile_x, tile_y, species_id, player_index, GameState.turn_number):
|
||||
return
|
||||
var badge: Label = Label.new()
|
||||
badge.name = "GrudgeBadge"
|
||||
badge.add_to_group("grudge_badge")
|
||||
badge.text = ThemeVocabulary.lookup("combat_grudge_badge")
|
||||
badge.add_theme_font_size_override("font_size", 13)
|
||||
badge.add_theme_color_override("font_color", Color(0.95, 0.35, 0.25))
|
||||
add_child(badge)
|
||||
|
|
|
|||
51
src/game/engine/scenes/notifications/boss_spawn_banner.gd
Normal file
51
src/game/engine/scenes/notifications/boss_spawn_banner.gd
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
extends PanelContainer
|
||||
## Boss spawn notification banner — p1-58 ecology cognition.
|
||||
##
|
||||
## Displayed as a region-wide alert when a tier-10 apex creature spawns.
|
||||
## Signal-driven: connects to `EventBus.boss_spawned`.
|
||||
##
|
||||
## Fields shown: creature name, tile position, devastation tier, range.
|
||||
## Auto-dismisses after `DISPLAY_SECONDS`. Can be manually dismissed.
|
||||
|
||||
const DISPLAY_SECONDS: float = 8.0
|
||||
|
||||
@onready var _name_label: Label = %BossName
|
||||
@onready var _tile_label: Label = %BossTile
|
||||
@onready var _tier_label: Label = %BossDevastationTier
|
||||
@onready var _range_label: Label = %BossRange
|
||||
@onready var _dismiss_button: Button = %BossDismiss
|
||||
|
||||
var _timer: float = 0.0
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
visible = false
|
||||
_dismiss_button.text = ThemeVocabulary.lookup("notification_dismiss")
|
||||
_dismiss_button.pressed.connect(_dismiss)
|
||||
EventBus.boss_spawned.connect(_on_boss_spawned)
|
||||
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
if not visible:
|
||||
return
|
||||
_timer -= delta
|
||||
if _timer <= 0.0:
|
||||
_dismiss()
|
||||
|
||||
|
||||
func _on_boss_spawned(
|
||||
species_name: String,
|
||||
tile_pos: Vector2i,
|
||||
devastation_tier: int,
|
||||
range_hexes: int,
|
||||
) -> void:
|
||||
_name_label.text = species_name
|
||||
_tile_label.text = ThemeVocabulary.lookup("fmt_boss_tile") % [tile_pos.x, tile_pos.y]
|
||||
_tier_label.text = ThemeVocabulary.lookup("fmt_boss_devastation_tier") % devastation_tier
|
||||
_range_label.text = ThemeVocabulary.lookup("fmt_boss_range") % range_hexes
|
||||
_timer = DISPLAY_SECONDS
|
||||
visible = true
|
||||
|
||||
|
||||
func _dismiss() -> void:
|
||||
visible = false
|
||||
65
src/game/engine/scenes/notifications/loot_popup.gd
Normal file
65
src/game/engine/scenes/notifications/loot_popup.gd
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
extends PanelContainer
|
||||
## Loot drop popup — p1-58 ecology cognition.
|
||||
##
|
||||
## Shows loot drops after a boss kill. Renders legendary[], rare[], common[]
|
||||
## arrays from the loot table dict.
|
||||
## Signal-driven: connects to `EventBus.loot_dropped`.
|
||||
##
|
||||
## Auto-dismisses after `DISPLAY_SECONDS`. Can be manually dismissed.
|
||||
|
||||
const DISPLAY_SECONDS: float = 10.0
|
||||
|
||||
@onready var _title_label: Label = %LootTitle
|
||||
@onready var _creature_label: Label = %LootCreatureType
|
||||
@onready var _legendary_list: VBoxContainer = %LootLegendaryList
|
||||
@onready var _rare_list: VBoxContainer = %LootRareList
|
||||
@onready var _common_list: VBoxContainer = %LootCommonList
|
||||
@onready var _legendary_header: Label = %LootLegendaryHeader
|
||||
@onready var _rare_header: Label = %LootRareHeader
|
||||
@onready var _common_header: Label = %LootCommonHeader
|
||||
@onready var _dismiss_button: Button = %LootDismiss
|
||||
|
||||
var _timer: float = 0.0
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
visible = false
|
||||
_title_label.text = ThemeVocabulary.lookup("loot_popup_title")
|
||||
_legendary_header.text = ThemeVocabulary.lookup("loot_tier_legendary")
|
||||
_rare_header.text = ThemeVocabulary.lookup("loot_tier_rare")
|
||||
_common_header.text = ThemeVocabulary.lookup("loot_tier_common")
|
||||
_dismiss_button.text = ThemeVocabulary.lookup("notification_dismiss")
|
||||
_dismiss_button.pressed.connect(_dismiss)
|
||||
EventBus.loot_dropped.connect(_on_loot_dropped)
|
||||
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
if not visible:
|
||||
return
|
||||
_timer -= delta
|
||||
if _timer <= 0.0:
|
||||
_dismiss()
|
||||
|
||||
|
||||
func _on_loot_dropped(player: Variant, creature_type: String, drops: Array) -> void:
|
||||
_creature_label.text = creature_type
|
||||
_populate_tier(_legendary_list, drops.filter(func(d: Dictionary) -> bool: return d.get("tier", "") == "legendary"))
|
||||
_populate_tier(_rare_list, drops.filter(func(d: Dictionary) -> bool: return d.get("tier", "") == "rare"))
|
||||
_populate_tier(_common_list, drops.filter(func(d: Dictionary) -> bool: return d.get("tier", "") == "common"))
|
||||
_timer = DISPLAY_SECONDS
|
||||
visible = true
|
||||
|
||||
|
||||
func _populate_tier(container: VBoxContainer, items: Array) -> void:
|
||||
for child: Node in container.get_children():
|
||||
child.queue_free()
|
||||
for item: Dictionary in items:
|
||||
var row: Label = Label.new()
|
||||
row.text = item.get("name", item.get("id", "Unknown"))
|
||||
row.add_theme_font_size_override("font_size", 13)
|
||||
container.add_child(row)
|
||||
container.get_parent().visible = not items.is_empty()
|
||||
|
||||
|
||||
func _dismiss() -> void:
|
||||
visible = false
|
||||
|
|
@ -2,11 +2,15 @@ extends PanelContainer
|
|||
## Bottom-of-screen tooltip showing terrain info when hovering a tile.
|
||||
## Row 1: biome name, movement cost, defense, food/production/trade yields.
|
||||
## Row 2: collectibles list, worked yield delta, strategic/luxury resource context.
|
||||
## Row 3 (p1-58): ecology species present on the tile via GdFaunaEcology.
|
||||
|
||||
const TileScript: GDScript = preload("res://engine/src/map/tile.gd")
|
||||
|
||||
var _current_axial: Vector2i = Vector2i(-9999, -9999)
|
||||
|
||||
## p1-58: optional fauna ecology bridge — set by parent scene after init.
|
||||
var _fauna_ecology: GdFaunaEcology = null
|
||||
|
||||
@onready var _biome_name: Label = %BiomeName
|
||||
@onready var _move_cost: Label = %MoveCost
|
||||
@onready var _defense_bonus: Label = %DefenseBonus
|
||||
|
|
@ -17,6 +21,9 @@ var _current_axial: Vector2i = Vector2i(-9999, -9999)
|
|||
@onready var _position_label: Label = %PositionLabel
|
||||
@onready var _collectibles_label: Label = %CollectiblesLabel
|
||||
@onready var _worked_yield_label: Label = %WorkedYieldLabel
|
||||
## Optional ecology species container. Present only if the scene adds a
|
||||
## VBoxContainer named EcologySpeciesList — guarded by get_node_or_null.
|
||||
@onready var _ecology_list: VBoxContainer = get_node_or_null("%EcologySpeciesList")
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
|
|
|
|||
|
|
@ -99,6 +99,12 @@ signal item_produced(city: Variant, item_id: String)
|
|||
signal item_crafted(item_id: String, city: Variant, player: Variant)
|
||||
## Emitted when the civ-wide material stockpile changes (add or consume).
|
||||
signal loot_dropped(player: Variant, creature_type: String, drops: Array)
|
||||
## Emitted when a tier-10 apex creature spawns on the map.
|
||||
## `species_name` — localized display name of the creature.
|
||||
## `tile_pos` — hex tile where the creature spawned.
|
||||
## `devastation_tier` — units at or below this tier auto-lose to this creature.
|
||||
## `range_hexes` — maximum patrol/aggression range in hexes.
|
||||
signal boss_spawned(species_name: String, tile_pos: Vector2i, devastation_tier: int, range_hexes: int)
|
||||
## Emitted when the Treasury crosses the 20-item soft cap. Informational only —
|
||||
## adds are never blocked. UI should show a subtle hint, not a modal.
|
||||
signal treasury_soft_cap_reached(player: Variant, total: int)
|
||||
|
|
|
|||
|
|
@ -1699,7 +1699,8 @@ mod tests {
|
|||
let mut c = City::new("c");
|
||||
c.buildings.extend(["y".into(), "w".into()]);
|
||||
|
||||
let common = buildings_common_to_all_cities(&[&a, &b, &c]);
|
||||
let all = [&a, &b, &c];
|
||||
let common = buildings_common_to_all_cities(&all);
|
||||
assert_eq!(common, vec!["y"]);
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue