diff --git a/changelog.md b/changelog.md index 44dd7c2e..4bdc94bb 100644 --- a/changelog.md +++ b/changelog.md @@ -12,6 +12,7 @@ Saves from 5.0.0 are compatible with 5.1.0 ## Fixes * **[Campaign]** Fixed some minor issues in campaigns which generated error messages in the log. +* **[Campaign]** Changed the way how map object / scenery kills where tracked. This fixes issues with kill recognition after map updates from ED which change the object ids and therefore prevent correct kill recognition. * **[Mission Generation]** Fixed incorrect radio specification for the AN/ARC-222. * **[Mission Generation]** Fixed mission scripting error when using a dedicated server. * **[Mission Generation]** Fixed an issue where empty convoys lead to an index error when a point capture made a pending transfer of units not completable anymore. diff --git a/game/debriefing.py b/game/debriefing.py index e756f043..55e066cd 100644 --- a/game/debriefing.py +++ b/game/debriefing.py @@ -104,6 +104,9 @@ class StateData: #: Names of vehicle (and ship) units that were killed during the mission. killed_ground_units: List[str] + #: Names of map objects that were killed during the mission. + killed_map_objects: list[str] + #: List of descriptions of destroyed statics. Format of each element is a mapping of #: the coordinate type ("x", "y", "z", "type", "orientation") to the value. destroyed_statics: List[dict[str, Union[float, str]]] @@ -122,6 +125,7 @@ class StateData: # Also normalize dead map objects (which are ints) to strings. The unit map # only stores strings. killed_ground_units=list({str(u) for u in data["killed_ground_units"]}), + killed_map_objects=data["killed_map_objects"], destroyed_statics=data["destroyed_objects_positions"], base_capture_events=data["base_capture_events"], ) @@ -323,6 +327,16 @@ class Debriefing: losses.enemy_airlifts.append(airlift_unit) continue + # Find killed map objects and mark them as loss + for map_object in self.state_data.killed_map_objects: + building = self.unit_map.building_or_fortification(map_object) + if building is not None: + if building.ground_object.control_point.captured: + losses.player_buildings.append(building) + else: + losses.enemy_buildings.append(building) + continue + return losses def base_capture_events(self) -> List[BaseCaptureEvent]: diff --git a/game/theater/theatergroundobject.py b/game/theater/theatergroundobject.py index d3bfae64..0f433612 100644 --- a/game/theater/theatergroundobject.py +++ b/game/theater/theatergroundobject.py @@ -316,16 +316,6 @@ class SceneryGroundObject(BuildingGroundObject): is_fob_structure=False, ) self.zone = zone - try: - # In the default TriggerZone using "assign as..." in the DCS Mission Editor, - # property three has the scenery's object ID as its value. - self.map_object_id = self.zone.properties[3]["value"] - except (IndexError, KeyError): - logging.exception( - "Invalid TriggerZone for Scenery definition. The third property must " - "be the map object ID." - ) - raise class FactoryGroundObject(BuildingGroundObject): diff --git a/game/unitmap.py b/game/unitmap.py index a1d5c110..afd5f30e 100644 --- a/game/unitmap.py +++ b/game/unitmap.py @@ -217,7 +217,7 @@ class UnitMap: self.buildings[name] = Building(ground_object) def add_scenery(self, ground_object: SceneryGroundObject) -> None: - name = str(ground_object.map_object_id) + name = str(ground_object.zone.name) if name in self.buildings: raise RuntimeError( f"Duplicate TGO unit: {name}. TriggerZone name: " diff --git a/gen/groundobjectsgen.py b/gen/groundobjectsgen.py index 0caeb5ac..ce9b945e 100644 --- a/gen/groundobjectsgen.py +++ b/gen/groundobjectsgen.py @@ -24,7 +24,8 @@ from typing import ( ) from dcs import Mission, Point, unitgroup -from dcs.action import SceneryDestructionZone +from dcs.action import SceneryDestructionZone, DoScript +from dcs.condition import MapObjectIsDead from dcs.country import Country from dcs.point import StaticPoint from dcs.statics import Fortification, fortification_map, warehouse_map @@ -35,7 +36,8 @@ from dcs.task import ( OptAlarmState, FireAtPoint, ) -from dcs.triggers import TriggerStart, TriggerZone +from dcs.translation import String +from dcs.triggers import TriggerStart, TriggerZone, Event, TriggerOnce from dcs.unit import Ship, Unit, Vehicle, InvisibleFARP from dcs.unitgroup import ShipGroup, StaticGroup, VehicleGroup from dcs.unittype import StaticType, ShipType, VehicleType @@ -297,7 +299,9 @@ class SceneryGenerator(BuildingSiteGenerator): # this trigger rule is applied. Otherwise you can kill a # structure twice. if self.ground_object.is_dead: - self.generate_dead_trigger_rule(trigger_zone) + self.generate_destruction_trigger_rule(trigger_zone) + else: + self.generate_on_dead_trigger_rule(trigger_zone) # Tell Liberation to manage this groundobjectsgen as part of the campaign. self.register_scenery() @@ -325,7 +329,7 @@ class SceneryGenerator(BuildingSiteGenerator): zone.properties, ) - def generate_dead_trigger_rule(self, trigger_zone: TriggerZone) -> None: + def generate_destruction_trigger_rule(self, trigger_zone: TriggerZone) -> None: # Add destruction zone trigger t = TriggerStart(comment="Destruction") t.actions.append( @@ -333,6 +337,17 @@ class SceneryGenerator(BuildingSiteGenerator): ) self.m.triggerrules.triggers.append(t) + def generate_on_dead_trigger_rule(self, trigger_zone: TriggerZone) -> None: + # Add a TriggerRule with the MapObjectIsDead condition to recognize killed + # map objects and add them to the state.json with a DoScript + t = TriggerOnce(Event.NoEvent, f"MapObjectIsDead Trigger {trigger_zone.id}") + t.add_condition(MapObjectIsDead(trigger_zone.id)) + script_string = String( + f'killed_map_objects[#killed_map_objects + 1] = "{trigger_zone.name}"' + ) + t.actions.append(DoScript(script_string)) + self.m.triggerrules.triggers.append(t) + def register_scenery(self) -> None: scenery = self.ground_object assert isinstance(scenery, SceneryGroundObject) diff --git a/resources/plugins/base/dcs_liberation.lua b/resources/plugins/base/dcs_liberation.lua index 765edc61..9bcfb6a7 100644 --- a/resources/plugins/base/dcs_liberation.lua +++ b/resources/plugins/base/dcs_liberation.lua @@ -4,10 +4,11 @@ local WRITESTATE_SCHEDULE_IN_SECONDS = 60 logger = mist.Logger:new("DCSLiberation", "info") logger:info("Check that json.lua is loaded : json = "..tostring(json)) -killed_aircrafts = {} -killed_ground_units = {} +killed_aircrafts = {} -- killed aircraft will be added via S_EVENT_CRASH event +killed_ground_units = {} -- killed units will be added via S_EVENT_DEAD event base_capture_events = {} -destroyed_objects_positions = {} +destroyed_objects_positions = {} -- will be added via S_EVENT_DEAD event +killed_map_objects = {} -- killed map objects will be added via TriggerRules mission_ended = false local function ends_with(str, ending) @@ -35,6 +36,7 @@ function write_state() ["base_capture_events"] = base_capture_events, ["mission_ended"] = mission_ended, ["destroyed_objects_positions"] = destroyed_objects_positions, + ["killed_map_objects"] = killed_map_objects, } if not json then local message = string.format("Unable to save DCS Liberation state to %s, JSON library is not loaded !", _debriefing_file_location)