diff --git a/public/resources/buildings/defense_special.json b/public/resources/buildings/defense_special.json index 38a03bdf..dd6e74fe 100644 --- a/public/resources/buildings/defense_special.json +++ b/public/resources/buildings/defense_special.json @@ -91,6 +91,14 @@ "type": "ranged_defense", "value": 8, "scope": "city" + }, + { + "type": "city_bombard_strength", + "value": 12 + }, + { + "type": "city_bombard_range", + "value": 2 } ], "requires_building": "walls", diff --git a/src/game/engine/src/modules/combat/combat_resolver.gd b/src/game/engine/src/modules/combat/combat_resolver.gd index b9383d51..51437a57 100644 --- a/src/game/engine/src/modules/combat/combat_resolver.gd +++ b/src/game/engine/src/modules/combat/combat_resolver.gd @@ -143,6 +143,16 @@ func _apply_resolve_results( if infusion_system != null: infusion_system.on_unit_killed(defender) + # City bombard retaliation: city deals damage to melee attackers + if defender is CityScript and not attacker_killed: + var bombard_dmg: int = _compute_city_bombard(defender, attacker) + if bombard_dmg > 0: + attacker.hp = maxi(0, attacker.hp - bombard_dmg) + result["city_bombard_damage"] = bombard_dmg + if attacker.hp <= 0: + attacker_killed = true + CombatUtilsScript.handle_unit_death(attacker, defender, all_units) + if result.get("captured", false) and defender is CityScript: CombatUtilsScript.capture_city(defender, attacker, defender.owner, all_units) @@ -307,6 +317,24 @@ func _count_flanking(attacker: RefCounted, near_def: Array) -> int: ## Terrain defense bonus from the tile the unit stands on. +## Compute city bombard retaliation damage. +## City strength = population * 3 + building bombard bonuses. +## Damage = 15 * (city_strength / attacker_strength), clamped [5, 30]. +func _compute_city_bombard(city: RefCounted, attacker: RefCounted) -> int: + var city_str: float = float(city.population) * 3.0 + # Add castle bombard bonus if present + if city.has_building("castle"): + var bdata: Dictionary = DataLoader.get_building("castle") + for effect: Dictionary in bdata.get("effects", []): + if effect.get("type", "") == "city_bombard_strength": + city_str += float(effect.get("value", 0)) + if city_str <= 0.0: + return 0 + var atk_str: float = float(attacker.attack) if attacker is UnitScript else 10.0 + var ratio: float = city_str / maxf(atk_str, 1.0) + return clampi(roundi(15.0 * ratio), 5, 30) + + func _get_terrain_defense(unit: RefCounted, game_map: RefCounted) -> int: if game_map == null: return 0 diff --git a/src/game/engine/src/modules/management/turn_processor.gd b/src/game/engine/src/modules/management/turn_processor.gd index 4211225a..0b323b0c 100644 --- a/src/game/engine/src/modules/management/turn_processor.gd +++ b/src/game/engine/src/modules/management/turn_processor.gd @@ -88,6 +88,7 @@ func _process_production(player: RefCounted) -> void: # Player EventBus.city_unit_completed.emit(city_ref, unit) elif item_type == "building": c.add_building(item_id) + _apply_building_bonuses(c, item_id) EventBus.city_building_completed.emit(city_ref, item_id) elif item_type == "item": var i_data: Dictionary = DataLoader.get_item(item_id) @@ -211,6 +212,17 @@ func _process_growth(player: RefCounted) -> void: # Player c.process_growth(tile_json) +func _apply_building_bonuses(city: CityScript, building_id: String) -> void: + var bdata: Dictionary = DataLoader.get_building(building_id) + var effects: Array = bdata.get("effects", []) + for effect: Dictionary in effects: + var etype: String = effect.get("type", "") + var value: int = int(effect.get("value", 0)) + if etype == "hp_bonus" and value > 0: + city.set_max_hp(city.max_hp + value) + city.heal(value) + + func _process_city_healing(player: RefCounted) -> void: for city_ref: Variant in player.cities: if city_ref is CityScript: