diff --git a/game/pretense/pretenseluagenerator.py b/game/pretense/pretenseluagenerator.py index d29880f0..605e257f 100644 --- a/game/pretense/pretenseluagenerator.py +++ b/game/pretense/pretenseluagenerator.py @@ -1,6 +1,7 @@ from __future__ import annotations import logging +import os import random from abc import ABC, abstractmethod from dataclasses import dataclass @@ -14,10 +15,12 @@ from dcs.triggers import TriggerStart from dcs.vehicles import AirDefence from game.ato import FlightType +from game.dcs.aircrafttype import AircraftType from game.missiongenerator.luagenerator import LuaGenerator from game.missiongenerator.missiondata import MissionData from game.plugins import LuaPluginManager -from game.theater import Airfield, OffMapSpawn +from game.theater import Airfield, OffMapSpawn, TheaterGroundObject +from game.theater.iadsnetwork.iadsrole import IadsRole from game.utils import escape_string_for_lua if TYPE_CHECKING: @@ -63,12 +66,180 @@ class PretenseLuaGenerator(LuaGenerator): ewrj_triggers = [ x for x in self.mission.triggerrules.triggers if isinstance(x, TriggerStart) ] + self.generate_pretense_plugin_data() self.generate_plugin_data() self.inject_plugins() for t in ewrj_triggers: self.mission.triggerrules.triggers.remove(t) self.mission.triggerrules.triggers.append(t) + def generate_plugin_data(self) -> None: + lua_data = LuaData("dcsRetribution") + + install_path = lua_data.add_item("installPath") + install_path.set_value(os.path.abspath(".")) + + lua_data.add_item("Airbases") + carriers_object = lua_data.add_item("Carriers") + + for carrier in self.mission_data.carriers: + carrier_item = carriers_object.add_item() + carrier_item.add_key_value("dcsGroupName", carrier.group_name) + carrier_item.add_key_value("unit_name", carrier.unit_name) + carrier_item.add_key_value("callsign", carrier.callsign) + carrier_item.add_key_value("radio", str(carrier.freq.mhz)) + carrier_item.add_key_value( + "tacan", str(carrier.tacan.number) + carrier.tacan.band.name + ) + + tankers_object = lua_data.add_item("Tankers") + for tanker in self.mission_data.tankers: + tanker_item = tankers_object.add_item() + tanker_item.add_key_value("dcsGroupName", tanker.group_name) + tanker_item.add_key_value("callsign", tanker.callsign) + tanker_item.add_key_value("variant", tanker.variant) + tanker_item.add_key_value("radio", str(tanker.freq.mhz)) + if tanker.tacan is not None: + tanker_item.add_key_value( + "tacan", str(tanker.tacan.number) + tanker.tacan.band.name + ) + + awacs_object = lua_data.add_item("AWACs") + for awacs in self.mission_data.awacs: + awacs_item = awacs_object.add_item() + awacs_item.add_key_value("dcsGroupName", awacs.group_name) + awacs_item.add_key_value("callsign", awacs.callsign) + awacs_item.add_key_value("radio", str(awacs.freq.mhz)) + + jtacs_object = lua_data.add_item("JTACs") + for jtac in self.mission_data.jtacs: + jtac_item = jtacs_object.add_item() + jtac_item.add_key_value("dcsGroupName", jtac.group_name) + jtac_item.add_key_value("callsign", jtac.callsign) + jtac_item.add_key_value("zone", jtac.region) + jtac_item.add_key_value("dcsUnit", jtac.unit_name) + jtac_item.add_key_value("laserCode", jtac.code) + jtac_item.add_key_value("radio", str(jtac.freq.mhz)) + jtac_item.add_key_value("modulation", jtac.freq.modulation.name) + + logistics_object = lua_data.add_item("Logistics") + logistics_flights = logistics_object.add_item("flights") + crates_object = logistics_object.add_item("crates") + spawnable_crates: dict[str, str] = {} + transports: list[AircraftType] = [] + for logistic_info in self.mission_data.logistics: + if logistic_info.transport not in transports: + transports.append(logistic_info.transport) + coalition_color = "blue" if logistic_info.blue else "red" + logistics_item = logistics_flights.add_item() + logistics_item.add_data_array("pilot_names", logistic_info.pilot_names) + logistics_item.add_key_value("pickup_zone", logistic_info.pickup_zone) + logistics_item.add_key_value("drop_off_zone", logistic_info.drop_off_zone) + logistics_item.add_key_value("target_zone", logistic_info.target_zone) + logistics_item.add_key_value("side", str(2 if logistic_info.blue else 1)) + logistics_item.add_key_value("logistic_unit", logistic_info.logistic_unit) + logistics_item.add_key_value( + "aircraft_type", logistic_info.transport.dcs_id + ) + logistics_item.add_key_value( + "preload", "true" if logistic_info.preload else "false" + ) + for cargo in logistic_info.cargo: + if cargo.unit_type not in spawnable_crates: + spawnable_crates[cargo.unit_type] = str(200 + len(spawnable_crates)) + crate_weight = spawnable_crates[cargo.unit_type] + for i in range(cargo.amount): + cargo_item = crates_object.add_item() + cargo_item.add_key_value("weight", crate_weight) + cargo_item.add_key_value("coalition", coalition_color) + cargo_item.add_key_value("zone", cargo.spawn_zone) + transport_object = logistics_object.add_item("transports") + for transport in transports: + transport_item = transport_object.add_item() + transport_item.add_key_value("aircraft_type", transport.dcs_id) + transport_item.add_key_value("cabin_size", str(transport.cabin_size)) + transport_item.add_key_value( + "troops", "true" if transport.cabin_size > 0 else "false" + ) + transport_item.add_key_value( + "crates", "true" if transport.can_carry_crates else "false" + ) + spawnable_crates_object = logistics_object.add_item("spawnable_crates") + for unit, weight in spawnable_crates.items(): + crate_item = spawnable_crates_object.add_item() + crate_item.add_key_value("unit", unit) + crate_item.add_key_value("weight", weight) + + target_points = lua_data.add_item("TargetPoints") + for flight in self.mission_data.flights: + if flight.friendly and flight.flight_type in [ + FlightType.ANTISHIP, + FlightType.DEAD, + FlightType.SEAD, + FlightType.STRIKE, + ]: + flight_type = str(flight.flight_type) + flight_target = flight.package.target + if flight_target: + flight_target_name = None + flight_target_type = None + if isinstance(flight_target, TheaterGroundObject): + flight_target_name = flight_target.obj_name + flight_target_type = ( + flight_type + f" TGT ({flight_target.category})" + ) + elif hasattr(flight_target, "name"): + flight_target_name = flight_target.name + flight_target_type = flight_type + " TGT (Airbase)" + target_item = target_points.add_item() + if flight_target_name: + target_item.add_key_value("name", flight_target_name) + if flight_target_type: + target_item.add_key_value("type", flight_target_type) + target_item.add_key_value( + "positionX", str(flight_target.position.x) + ) + target_item.add_key_value( + "positionY", str(flight_target.position.y) + ) + + for cp in self.game.theater.controlpoints: + coalition_object = ( + lua_data.get_or_create_item("BlueAA") + if cp.captured + else lua_data.get_or_create_item("RedAA") + ) + for ground_object in cp.ground_objects: + for g in ground_object.groups: + threat_range = g.max_threat_range() + + if not threat_range: + continue + + aa_item = coalition_object.add_item() + aa_item.add_key_value("name", ground_object.name) + aa_item.add_key_value("range", str(threat_range.meters)) + aa_item.add_key_value("positionX", str(ground_object.position.x)) + aa_item.add_key_value("positionY", str(ground_object.position.y)) + + # Generate IADS Lua Item + iads_object = lua_data.add_item("IADS") + for node in self.game.theater.iads_network.skynet_nodes(self.game): + coalition = iads_object.get_or_create_item("BLUE" if node.player else "RED") + iads_type = coalition.get_or_create_item(node.iads_role.value) + iads_element = iads_type.add_item() + iads_element.add_key_value("dcsGroupName", node.dcs_name) + if node.iads_role in [IadsRole.SAM, IadsRole.SAM_AS_EWR]: + # add additional SkynetProperties to SAM Sites + for property, value in node.properties.items(): + iads_element.add_key_value(property, value) + for role, connections in node.connections.items(): + iads_element.add_data_array(role, connections) + + trigger = TriggerStart(comment="Set DCS Retribution data") + trigger.add_action(DoScript(String(lua_data.create_operations_lua()))) + self.mission.triggerrules.triggers.append(trigger) + @staticmethod def generate_sam_from_preset( preset: str, cp_side_str: str, cp_name_trimmed: str @@ -650,9 +821,7 @@ class PretenseLuaGenerator(LuaGenerator): return lua_string_connman - def generate_plugin_data(self) -> None: - self.mission.triggerrules.triggers.clear() - + def generate_pretense_plugin_data(self) -> None: self.inject_plugin_script("base", "mist_4_5_107.lua", "mist_4_5_107") self.inject_plugin_script( "pretense", "pretense_compiled.lua", "pretense_compiled" @@ -660,6 +829,23 @@ class PretenseLuaGenerator(LuaGenerator): trigger = TriggerStart(comment="Pretense init") + lua_string_config = "" + + lua_string_config += ( + f"Config.maxDistFromFront = " + + str(self.game.settings.pretense_maxdistfromfront_distance * 1000) + + "\n" + ) + lua_string_config += ( + f"Config.closeOverride = " + + str(self.game.settings.pretense_closeoverride_distance * 1000) + + "\n" + ) + if self.game.settings.pretense_do_not_generate_sead_missions: + lua_string_config += "Config.disablePlayerSead = true\n" + else: + lua_string_config += "Config.disablePlayerSead = false\n" + init_header_file = open("./resources/plugins/pretense/init_header.lua", "r") init_header = init_header_file.read() @@ -687,10 +873,10 @@ class PretenseLuaGenerator(LuaGenerator): + str(cp_side) + " }\n" ) - lua_string_zones += f"zones.{cp_name_trimmed}.keepActive = true\n" max_resource = 20000 is_helo_spawn = "false" is_plane_spawn = "false" + is_keep_active = "false" if cp.has_helipads: is_helo_spawn = "true" max_resource = 30000 @@ -701,9 +887,12 @@ class PretenseLuaGenerator(LuaGenerator): is_helo_spawn = "true" is_plane_spawn = "true" max_resource = 40000 + if cp.is_lha: + is_keep_active = "true" if isinstance(cp, Airfield) or cp.is_carrier: is_helo_spawn = "true" is_plane_spawn = "true" + is_keep_active = "true" max_resource = 50000 lua_string_zones += ( f"zones.{cp_name_trimmed}.maxResource = {max_resource}\n" @@ -714,6 +903,9 @@ class PretenseLuaGenerator(LuaGenerator): lua_string_zones += ( f"zones.{cp_name_trimmed}.isPlaneSpawn = " + is_plane_spawn + "\n" ) + lua_string_zones += ( + f"zones.{cp_name_trimmed}.keepActive = " + is_keep_active + "\n" + ) if cp.is_fleet: lua_string_zones += self.generate_pretense_zone_sea(cp.name) else: @@ -805,7 +997,8 @@ class PretenseLuaGenerator(LuaGenerator): init_footer = init_footer_file.read() lua_string = ( - init_header + lua_string_config + + init_header + lua_string_zones + lua_string_connman + init_body_1 @@ -854,7 +1047,7 @@ class PretenseLuaGenerator(LuaGenerator): def inject_plugins(self) -> None: for plugin in LuaPluginManager.plugins(): - if plugin.enabled and plugin.identifier not in ("base"): + if plugin.enabled: plugin.inject_scripts(self) plugin.inject_configuration(self) diff --git a/game/pretense/pretensemissiongenerator.py b/game/pretense/pretensemissiongenerator.py index ae08b766..e67ece34 100644 --- a/game/pretense/pretensemissiongenerator.py +++ b/game/pretense/pretensemissiongenerator.py @@ -101,6 +101,7 @@ class PretenseMissionGenerator(MissionGenerator): self.generate_ground_conflicts() self.generate_air_units(tgo_generator) + self.mission.triggerrules.triggers.clear() PretenseTriggerGenerator(self.mission, self.game).generate() ForcedOptionsGenerator(self.mission, self.game).generate() VisualsGenerator(self.mission, self.game).generate() diff --git a/game/pretense/pretensetriggergenerator.py b/game/pretense/pretensetriggergenerator.py index 4eeae332..740c17c6 100644 --- a/game/pretense/pretensetriggergenerator.py +++ b/game/pretense/pretensetriggergenerator.py @@ -152,9 +152,72 @@ class PretenseTriggerGenerator: v += 1 self.mission.triggerrules.triggers.append(mark_trigger) - def _generate_pretense_zone_triggers( + def _generate_capture_triggers( self, player_coalition: str, enemy_coalition: str ) -> None: + """Creates a pair of triggers for each control point of `cls.capture_zone_types`. + One for the initial capture of a control point, and one if it is recaptured. + Directly appends to the global `base_capture_events` var declared by `dcs_libaration.lua` + """ + for cp in self.game.theater.controlpoints: + if isinstance(cp, self.capture_zone_types) and not cp.is_carrier: + if cp.captured: + attacking_coalition = enemy_coalition + attack_coalition_int = 1 # 1 is the Event int for Red + defending_coalition = player_coalition + defend_coalition_int = 2 # 2 is the Event int for Blue + else: + attacking_coalition = player_coalition + attack_coalition_int = 2 + defending_coalition = enemy_coalition + defend_coalition_int = 1 + + trigger_zone = self.mission.triggers.add_triggerzone( + cp.position, + radius=TRIGGER_RADIUS_CAPTURE, + hidden=False, + name="CAPTURE", + ) + flag = self.get_capture_zone_flag() + capture_trigger = TriggerCondition(Event.NoEvent, "Capture Trigger") + capture_trigger.add_condition( + AllOfCoalitionOutsideZone( + defending_coalition, trigger_zone.id, unit_type="GROUND" + ) + ) + capture_trigger.add_condition( + PartOfCoalitionInZone( + attacking_coalition, trigger_zone.id, unit_type="GROUND" + ) + ) + capture_trigger.add_condition(FlagIsFalse(flag=flag)) + script_string = String( + f'base_capture_events[#base_capture_events + 1] = "{cp.id}||{attack_coalition_int}||{cp.full_name}"' + ) + capture_trigger.add_action(DoScript(script_string)) + capture_trigger.add_action(SetFlag(flag=flag)) + self.mission.triggerrules.triggers.append(capture_trigger) + + recapture_trigger = TriggerCondition(Event.NoEvent, "Capture Trigger") + recapture_trigger.add_condition( + AllOfCoalitionOutsideZone( + attacking_coalition, trigger_zone.id, unit_type="GROUND" + ) + ) + recapture_trigger.add_condition( + PartOfCoalitionInZone( + defending_coalition, trigger_zone.id, unit_type="GROUND" + ) + ) + recapture_trigger.add_condition(FlagIsTrue(flag=flag)) + script_string = String( + f'base_capture_events[#base_capture_events + 1] = "{cp.id}||{defend_coalition_int}||{cp.full_name}"' + ) + recapture_trigger.add_action(DoScript(script_string)) + recapture_trigger.add_action(ClearFlag(flag=flag)) + self.mission.triggerrules.triggers.append(recapture_trigger) + + def _generate_pretense_zone_triggers(self) -> None: """Creates a pair of triggers for each control point of `cls.capture_zone_types`. One for the initial capture of a control point, and one if it is recaptured. Directly appends to the global `base_capture_events` var declared by `dcs_libaration.lua` @@ -166,7 +229,7 @@ class PretenseTriggerGenerator: trigger_radius = TRIGGER_RADIUS_CAPTURE if not isinstance(cp, OffMapSpawn): zone_color = {1: 0.0, 2: 0.0, 3: 0.0, 4: 0.15} - trigger_zone = self.mission.triggers.add_triggerzone( + self.mission.triggers.add_triggerzone( cp.position, radius=trigger_radius, hidden=False, @@ -180,7 +243,7 @@ class PretenseTriggerGenerator: continue tgo_num += 1 zone_color = {1: 1.0, 2: 1.0, 3: 1.0, 4: 0.15} - trigger_zone = self.mission.triggers.add_triggerzone( + self.mission.triggers.add_triggerzone( tgo.position, radius=TRIGGER_RADIUS_PRETENSE_TGO, hidden=False, @@ -189,7 +252,7 @@ class PretenseTriggerGenerator: ) for helipad in cp.helipads + cp.helipads_invisible + cp.helipads_quad: zone_color = {1: 1.0, 2: 1.0, 3: 1.0, 4: 0.15} - trigger_zone = self.mission.triggers.add_triggerzone( + self.mission.triggers.add_triggerzone( position=helipad, radius=TRIGGER_RADIUS_PRETENSE_HELI, hidden=False, @@ -206,7 +269,7 @@ class PretenseTriggerGenerator: supply_position = origin_position.point_from_heading( convoy_heading, 300 ) - trigger_zone = self.mission.triggers.add_triggerzone( + self.mission.triggers.add_triggerzone( supply_position, radius=TRIGGER_RADIUS_PRETENSE_TGO, hidden=False, @@ -219,6 +282,8 @@ class PretenseTriggerGenerator: ] for airfield in airfields: cp_airport = self.mission.terrain.airport_by_id(airfield.airport.id) + if cp_airport is None: + continue cp_name_trimmed = "".join( [i for i in cp_airport.name.lower() if i.isalnum()] ) @@ -263,7 +328,8 @@ class PretenseTriggerGenerator: self._set_skill(player_coalition, enemy_coalition) self._set_allegiances(player_coalition, enemy_coalition) - self._generate_pretense_zone_triggers(player_coalition, enemy_coalition) + self._generate_pretense_zone_triggers() + self._generate_capture_triggers(player_coalition, enemy_coalition) @classmethod def get_capture_zone_flag(cls) -> int: