From 8402e5d9bb120f309e72fb1c4d717326e4ad0117 Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Sat, 9 Sep 2023 15:15:42 +0300 Subject: [PATCH 001/243] Copied three files as templates/inheritance for generating Pretense campaigns from Retribution campaigns: - missiongenerator.py - aircraftgenerator.py - triggergenerator.py --- game/pretense/pretenseaircraftgenerator.py | 296 ++++++++++++++++++ game/pretense/pretensemissiongenerator.py | 343 +++++++++++++++++++++ game/pretense/pretensetriggergenerator.py | 264 ++++++++++++++++ 3 files changed, 903 insertions(+) create mode 100644 game/pretense/pretenseaircraftgenerator.py create mode 100644 game/pretense/pretensemissiongenerator.py create mode 100644 game/pretense/pretensetriggergenerator.py diff --git a/game/pretense/pretenseaircraftgenerator.py b/game/pretense/pretenseaircraftgenerator.py new file mode 100644 index 00000000..7caa91c3 --- /dev/null +++ b/game/pretense/pretenseaircraftgenerator.py @@ -0,0 +1,296 @@ +from __future__ import annotations + +import logging +from datetime import datetime +from functools import cached_property +from typing import Any, Dict, List, TYPE_CHECKING, Tuple + +from dcs import Point +from dcs.action import AITaskPush +from dcs.condition import FlagIsTrue, GroupDead, Or, FlagIsFalse +from dcs.country import Country +from dcs.mission import Mission +from dcs.terrain.terrain import NoParkingSlotError +from dcs.triggers import TriggerOnce, Event +from dcs.unit import Skill +from dcs.unitgroup import FlyingGroup, StaticGroup + +from game.ato.airtaaskingorder import AirTaskingOrder +from game.ato.flight import Flight +from game.ato.flightstate import Completed, WaitingForStart +from game.ato.flighttype import FlightType +from game.ato.package import Package +from game.ato.starttype import StartType +from game.missiongenerator.lasercoderegistry import LaserCodeRegistry +from game.missiongenerator.missiondata import MissionData +from game.radio.radios import RadioRegistry +from game.radio.tacan import TacanRegistry +from game.runways import RunwayData +from game.settings import Settings +from game.theater.controlpoint import ( + Airfield, + ControlPoint, + Fob, +) +from game.unitmap import UnitMap +from .aircraftpainter import AircraftPainter +from .flightdata import FlightData +from .flightgroupconfigurator import FlightGroupConfigurator +from .flightgroupspawner import FlightGroupSpawner +from ...data.weapons import WeaponType + +if TYPE_CHECKING: + from game import Game + from game.squadrons import Squadron + + +class AircraftGenerator: + def __init__( + self, + mission: Mission, + settings: Settings, + game: Game, + time: datetime, + radio_registry: RadioRegistry, + tacan_registry: TacanRegistry, + laser_code_registry: LaserCodeRegistry, + unit_map: UnitMap, + mission_data: MissionData, + helipads: dict[ControlPoint, list[StaticGroup]], + ground_spawns_roadbase: dict[ControlPoint, list[Tuple[StaticGroup, Point]]], + ground_spawns: dict[ControlPoint, list[Tuple[StaticGroup, Point]]], + ) -> None: + self.mission = mission + self.settings = settings + self.game = game + self.time = time + self.radio_registry = radio_registry + self.tacan_registy = tacan_registry + self.laser_code_registry = laser_code_registry + self.unit_map = unit_map + self.flights: List[FlightData] = [] + self.mission_data = mission_data + self.helipads = helipads + self.ground_spawns_roadbase = ground_spawns_roadbase + self.ground_spawns = ground_spawns + + self.ewrj_package_dict: Dict[int, List[FlyingGroup[Any]]] = {} + self.ewrj = settings.plugins.get("ewrj") + self.need_ecm = settings.plugin_option("ewrj.ecm_required") + + @cached_property + def use_client(self) -> bool: + """True if Client should be used instead of Player.""" + blue_clients = self.client_slots_in_ato(self.game.blue.ato) + red_clients = self.client_slots_in_ato(self.game.red.ato) + return blue_clients + red_clients > 1 + + @staticmethod + def client_slots_in_ato(ato: AirTaskingOrder) -> int: + total = 0 + for package in ato.packages: + for flight in package.flights: + total += flight.client_count + return total + + def clear_parking_slots(self) -> None: + for cp in self.game.theater.controlpoints: + for parking_slot in cp.parking_slots: + parking_slot.unit_id = None + + def generate_flights( + self, + country: Country, + ato: AirTaskingOrder, + dynamic_runways: Dict[str, RunwayData], + ) -> None: + """Adds aircraft to the mission for every flight in the ATO. + + Aircraft generation is done by walking the ATO and spawning each flight in turn. + After the flight is generated the group is added to the UnitMap so aircraft + deaths can be tracked. + + Args: + country: The country from the mission to use for this ATO. + ato: The ATO to spawn aircraft for. + dynamic_runways: Runway data for carriers and FARPs. + """ + self._reserve_frequencies_and_tacan(ato) + + for package in reversed(sorted(ato.packages, key=lambda x: x.time_over_target)): + logging.info(f"Generating package for target: {package.target.name}") + if not package.flights: + continue + for flight in package.flights: + if flight.alive: + if not flight.squadron.location.runway_is_operational(): + logging.warning( + f"Runway not operational, skipping flight: {flight.flight_type}" + ) + flight.return_pilots_and_aircraft() + continue + logging.info(f"Generating flight: {flight.unit_type}") + group = self.create_and_configure_flight( + flight, country, dynamic_runways + ) + self.unit_map.add_aircraft(group, flight) + if ( + package.primary_flight is not None + and package.primary_flight.flight_plan.is_formation( + package.primary_flight.flight_plan + ) + ): + splittrigger = TriggerOnce(Event.NoEvent, f"Split-{id(package)}") + splittrigger.add_condition(FlagIsTrue(flag=f"split-{id(package)}")) + splittrigger.add_condition(Or()) + splittrigger.add_condition(FlagIsFalse(flag=f"split-{id(package)}")) + splittrigger.add_condition(GroupDead(package.primary_flight.group_id)) + for flight in package.flights: + if flight.flight_type in [ + FlightType.ESCORT, + FlightType.SEAD_ESCORT, + ]: + splittrigger.add_action(AITaskPush(flight.group_id, 1)) + if len(splittrigger.actions) > 0: + self.mission.triggerrules.triggers.append(splittrigger) + + def spawn_unused_aircraft( + self, player_country: Country, enemy_country: Country + ) -> None: + for control_point in self.game.theater.controlpoints: + if not ( + isinstance(control_point, Airfield) or isinstance(control_point, Fob) + ): + continue + + if control_point.captured: + country = player_country + else: + country = enemy_country + + for squadron in control_point.squadrons: + try: + self._spawn_unused_for(squadron, country) + except NoParkingSlotError: + # If we run out of parking, stop spawning aircraft at this base. + break + + def _spawn_unused_for(self, squadron: Squadron, country: Country) -> None: + assert isinstance(squadron.location, Airfield) or isinstance( + squadron.location, Fob + ) + if ( + squadron.coalition.player + and self.game.settings.perf_disable_untasked_blufor_aircraft + ): + return + elif ( + not squadron.coalition.player + and self.game.settings.perf_disable_untasked_opfor_aircraft + ): + return + + for _ in range(squadron.untasked_aircraft): + # Creating a flight even those this isn't a fragged mission lets us + # reuse the existing debriefing code. + # TODO: Special flight type? + flight = Flight( + Package(squadron.location, self.game.db.flights), + squadron, + 1, + FlightType.BARCAP, + StartType.COLD, + divert=None, + claim_inv=False, + ) + flight.state = Completed(flight, self.game.settings) + + group = FlightGroupSpawner( + flight, + country, + self.mission, + self.helipads, + self.ground_spawns_roadbase, + self.ground_spawns, + self.mission_data, + ).create_idle_aircraft() + if group: + if ( + not squadron.coalition.player + and squadron.aircraft.flyable + and ( + self.game.settings.enable_squadron_pilot_limits + or squadron.number_of_available_pilots > 0 + ) + and self.game.settings.untasked_opfor_client_slots + ): + flight.state = WaitingForStart( + flight, self.game.settings, self.game.conditions.start_time + ) + group.uncontrolled = False + group.units[0].skill = Skill.Client + AircraftPainter(flight, group).apply_livery() + self.unit_map.add_aircraft(group, flight) + + def create_and_configure_flight( + self, flight: Flight, country: Country, dynamic_runways: Dict[str, RunwayData] + ) -> FlyingGroup[Any]: + """Creates and configures the flight group in the mission.""" + group = FlightGroupSpawner( + flight, + country, + self.mission, + self.helipads, + self.ground_spawns_roadbase, + self.ground_spawns, + self.mission_data, + ).create_flight_group() + self.flights.append( + FlightGroupConfigurator( + flight, + group, + self.game, + self.mission, + self.time, + self.radio_registry, + self.tacan_registy, + self.laser_code_registry, + self.mission_data, + dynamic_runways, + self.use_client, + ).configure() + ) + + if self.ewrj: + self._track_ewrj_flight(flight, group) + + return group + + def _track_ewrj_flight(self, flight: Flight, group: FlyingGroup[Any]) -> None: + if not self.ewrj_package_dict.get(id(flight.package)): + self.ewrj_package_dict[id(flight.package)] = [] + if ( + flight.package.primary_flight + and flight is flight.package.primary_flight + or flight.client_count + and ( + not self.need_ecm + or flight.loadout.has_weapon_of_type(WeaponType.JAMMER) + ) + ): + self.ewrj_package_dict[id(flight.package)].append(group) + + def _reserve_frequencies_and_tacan(self, ato: AirTaskingOrder) -> None: + for package in ato.packages: + if package.frequency is None: + continue + if package.frequency not in self.radio_registry.allocated_channels: + self.radio_registry.reserve(package.frequency) + for f in package.flights: + if ( + f.frequency + and f.frequency not in self.radio_registry.allocated_channels + ): + self.radio_registry.reserve(f.frequency) + if f.tacan and f.tacan not in self.tacan_registy.allocated_channels: + self.tacan_registy.mark_unavailable(f.tacan) diff --git a/game/pretense/pretensemissiongenerator.py b/game/pretense/pretensemissiongenerator.py new file mode 100644 index 00000000..85c5e5c9 --- /dev/null +++ b/game/pretense/pretensemissiongenerator.py @@ -0,0 +1,343 @@ +from __future__ import annotations + +import logging +from datetime import datetime +from pathlib import Path +from typing import TYPE_CHECKING, cast + +import dcs.lua +from dcs import Mission, Point +from dcs.coalition import Coalition +from dcs.countries import country_dict +from dcs.task import OptReactOnThreat + +from game.atcdata import AtcData +from game.dcs.beacons import Beacons +from game.dcs.helpers import unit_type_from_name +from game.missiongenerator.aircraft.aircraftgenerator import ( + AircraftGenerator, +) +from game.naming import namegen +from game.radio.radios import RadioFrequency, RadioRegistry, MHz +from game.radio.tacan import TacanRegistry +from game.theater import Airfield +from game.theater.bullseye import Bullseye +from game.unitmap import UnitMap +from .briefinggenerator import BriefingGenerator, MissionInfoGenerator +from .cargoshipgenerator import CargoShipGenerator +from .convoygenerator import ConvoyGenerator +from .drawingsgenerator import DrawingsGenerator +from .environmentgenerator import EnvironmentGenerator +from .flotgenerator import FlotGenerator +from .forcedoptionsgenerator import ForcedOptionsGenerator +from .frontlineconflictdescription import FrontLineConflictDescription +from .kneeboard import KneeboardGenerator +from .lasercoderegistry import LaserCodeRegistry +from .luagenerator import LuaGenerator +from .missiondata import MissionData +from .tgogenerator import TgoGenerator +from .triggergenerator import TriggerGenerator +from .visualsgenerator import VisualsGenerator +from ..radio.TacanContainer import TacanContainer + +if TYPE_CHECKING: + from game import Game + + +class MissionGenerator: + def __init__(self, game: Game, time: datetime) -> None: + self.game = game + self.time = time + self.mission = Mission(game.theater.terrain) + self.unit_map = UnitMap() + + self.mission_data = MissionData() + + self.laser_code_registry = LaserCodeRegistry() + self.radio_registry = RadioRegistry() + self.tacan_registry = TacanRegistry() + + self.generation_started = False + + self.p_country = country_dict[self.game.blue.faction.country.id]() + self.e_country = country_dict[self.game.red.faction.country.id]() + + with open("resources/default_options.lua", "r", encoding="utf-8") as f: + options = dcs.lua.loads(f.read())["options"] + ext_view = game.settings.external_views_allowed + options["miscellaneous"]["f11_free_camera"] = ext_view + options["difficulty"]["spectatorExternalViews"] = ext_view + self.mission.options.load_from_dict(options) + + def generate_miz(self, output: Path) -> UnitMap: + if self.generation_started: + raise RuntimeError( + "Mission has already begun generating. To reset, create a new " + "MissionSimulation." + ) + self.generation_started = True + + self.setup_mission_coalitions() + self.add_airfields_to_unit_map() + self.initialize_registries() + + EnvironmentGenerator(self.mission, self.game.conditions, self.time).generate() + + tgo_generator = TgoGenerator( + self.mission, + self.game, + self.radio_registry, + self.tacan_registry, + self.unit_map, + self.mission_data, + ) + tgo_generator.generate() + + ConvoyGenerator(self.mission, self.game, self.unit_map).generate() + CargoShipGenerator(self.mission, self.game, self.unit_map).generate() + + self.generate_destroyed_units() + + # Generate ground conflicts first so the JTACs get the first laser code (1688) + # rather than the first player flight with a TGP. + self.generate_ground_conflicts() + self.generate_air_units(tgo_generator) + + TriggerGenerator(self.mission, self.game).generate() + ForcedOptionsGenerator(self.mission, self.game).generate() + VisualsGenerator(self.mission, self.game).generate() + LuaGenerator(self.game, self.mission, self.mission_data).generate() + DrawingsGenerator(self.mission, self.game).generate() + + self.setup_combined_arms() + + self.notify_info_generators() + + # TODO: Shouldn't this be first? + namegen.reset_numbers() + self.mission.save(output) + + return self.unit_map + + @staticmethod + def _configure_ewrj(gen: AircraftGenerator) -> None: + for groups in gen.ewrj_package_dict.values(): + optrot = groups[0].points[0].tasks[0] + assert isinstance(optrot, OptReactOnThreat) + if ( + len(groups) == 1 + and optrot.value != OptReactOnThreat.Values.PassiveDefense + ): + # primary flight with no EWR-Jamming capability + continue + for group in groups: + group.points[0].tasks[0] = OptReactOnThreat( + OptReactOnThreat.Values.PassiveDefense + ) + + def setup_mission_coalitions(self) -> None: + self.mission.coalition["blue"] = Coalition( + "blue", bullseye=self.game.blue.bullseye.to_pydcs() + ) + self.mission.coalition["red"] = Coalition( + "red", bullseye=self.game.red.bullseye.to_pydcs() + ) + self.mission.coalition["neutrals"] = Coalition( + "neutrals", bullseye=Bullseye(Point(0, 0, self.mission.terrain)).to_pydcs() + ) + + self.mission.coalition["blue"].add_country(self.p_country) + self.mission.coalition["red"].add_country(self.e_country) + + belligerents = {self.p_country.id, self.e_country.id} + for country_id in country_dict.keys(): + if country_id not in belligerents: + c = country_dict[country_id]() + self.mission.coalition["neutrals"].add_country(c) + + def add_airfields_to_unit_map(self) -> None: + for control_point in self.game.theater.controlpoints: + if isinstance(control_point, Airfield): + self.unit_map.add_airfield(control_point) + + def initialize_registries(self) -> None: + unique_map_frequencies: set[RadioFrequency] = set() + self.initialize_tacan_registry(unique_map_frequencies) + self.initialize_radio_registry(unique_map_frequencies) + # Allocate UHF/VHF Guard Freq first! + unique_map_frequencies.add(MHz(243)) + unique_map_frequencies.add(MHz(121, 500)) + for frequency in unique_map_frequencies: + self.radio_registry.reserve(frequency) + + def initialize_tacan_registry( + self, unique_map_frequencies: set[RadioFrequency] + ) -> None: + """ + Dedup beacon/radio frequencies, since some maps have some frequencies + used multiple times. + """ + for beacon in Beacons.iter_theater(self.game.theater): + unique_map_frequencies.add(beacon.frequency) + if beacon.is_tacan: + if beacon.channel is None: + logging.warning(f"TACAN beacon has no channel: {beacon.callsign}") + else: + self.tacan_registry.mark_unavailable(beacon.tacan_channel) + for cp in self.game.theater.controlpoints: + if isinstance(cp, TacanContainer) and cp.tacan is not None: + self.tacan_registry.mark_unavailable(cp.tacan) + + def initialize_radio_registry( + self, unique_map_frequencies: set[RadioFrequency] + ) -> None: + for airport in self.game.theater.terrain.airport_list(): + if (atc := AtcData.from_pydcs(airport)) is not None: + unique_map_frequencies.add(atc.hf) + unique_map_frequencies.add(atc.vhf_fm) + unique_map_frequencies.add(atc.vhf_am) + unique_map_frequencies.add(atc.uhf) + # No need to reserve ILS or TACAN because those are in the + # beacon list. + + def generate_ground_conflicts(self) -> None: + """Generate FLOTs and JTACs for each active front line.""" + for front_line in self.game.theater.conflicts(): + player_cp = front_line.blue_cp + enemy_cp = front_line.red_cp + conflict = FrontLineConflictDescription.frontline_cas_conflict( + front_line, self.game.theater, self.game.settings + ) + # Generate frontline ops + player_gp = self.game.ground_planners[player_cp.id].units_per_cp[ + enemy_cp.id + ] + enemy_gp = self.game.ground_planners[enemy_cp.id].units_per_cp[player_cp.id] + ground_conflict_gen = FlotGenerator( + self.mission, + conflict, + self.game, + player_gp, + enemy_gp, + player_cp.stances[enemy_cp.id], + enemy_cp.stances[player_cp.id], + self.unit_map, + self.radio_registry, + self.mission_data, + self.laser_code_registry, + ) + ground_conflict_gen.generate() + + def generate_air_units(self, tgo_generator: TgoGenerator) -> None: + """Generate the air units for the Operation""" + + # Generate Aircraft Activity on the map + aircraft_generator = AircraftGenerator( + self.mission, + self.game.settings, + self.game, + self.time, + self.radio_registry, + self.tacan_registry, + self.laser_code_registry, + self.unit_map, + mission_data=self.mission_data, + helipads=tgo_generator.helipads, + ground_spawns_roadbase=tgo_generator.ground_spawns_roadbase, + ground_spawns=tgo_generator.ground_spawns, + ) + + aircraft_generator.clear_parking_slots() + + aircraft_generator.generate_flights( + self.p_country, + self.game.blue.ato, + tgo_generator.runways, + ) + aircraft_generator.generate_flights( + self.e_country, + self.game.red.ato, + tgo_generator.runways, + ) + aircraft_generator.spawn_unused_aircraft( + self.p_country, + self.e_country, + ) + + self.mission_data.flights = aircraft_generator.flights + + for flight in aircraft_generator.flights: + if not flight.client_units: + continue + flight.aircraft_type.assign_channels_for_flight(flight, self.mission_data) + + if self.game.settings.plugins.get("ewrj"): + self._configure_ewrj(aircraft_generator) + + def generate_destroyed_units(self) -> None: + """Add destroyed units to the Mission""" + if not self.game.settings.perf_destroyed_units: + return + + for d in self.game.get_destroyed_units(): + try: + type_name = d["type"] + if not isinstance(type_name, str): + raise TypeError( + "Expected the type of the destroyed static to be a string" + ) + utype = unit_type_from_name(type_name) + except KeyError: + logging.warning(f"Destroyed unit has no type: {d}") + continue + + pos = Point(cast(float, d["x"]), cast(float, d["z"]), self.mission.terrain) + if utype is not None and not self.game.position_culled(pos): + self.mission.static_group( + country=self.p_country, + name="", + _type=utype, + hidden=True, + position=pos, + heading=d["orientation"], + dead=True, + ) + + def notify_info_generators( + self, + ) -> None: + """Generates subscribed MissionInfoGenerator objects.""" + mission_data = self.mission_data + gens: list[MissionInfoGenerator] = [ + KneeboardGenerator(self.mission, self.game), + BriefingGenerator(self.mission, self.game), + ] + for gen in gens: + for dynamic_runway in mission_data.runways: + gen.add_dynamic_runway(dynamic_runway) + + for tanker in mission_data.tankers: + if tanker.blue: + gen.add_tanker(tanker) + + for aewc in mission_data.awacs: + if aewc.blue: + gen.add_awacs(aewc) + + for jtac in mission_data.jtacs: + if jtac.blue: + gen.add_jtac(jtac) + + for flight in mission_data.flights: + gen.add_flight(flight) + gen.generate() + + def setup_combined_arms(self) -> None: + settings = self.game.settings + commanders = settings.tactical_commander_count + self.mission.groundControl.pilot_can_control_vehicles = commanders > 0 + + self.mission.groundControl.blue_game_masters = settings.game_masters_count + self.mission.groundControl.blue_tactical_commander = commanders + self.mission.groundControl.blue_jtac = settings.jtac_count + self.mission.groundControl.blue_observer = settings.observer_count diff --git a/game/pretense/pretensetriggergenerator.py b/game/pretense/pretensetriggergenerator.py new file mode 100644 index 00000000..cc31b4e5 --- /dev/null +++ b/game/pretense/pretensetriggergenerator.py @@ -0,0 +1,264 @@ +from __future__ import annotations + +import logging +from typing import TYPE_CHECKING, List + +from dcs import Point +from dcs.action import ( + ClearFlag, + DoScript, + MarkToAll, + SetFlag, + RemoveSceneObjects, + RemoveSceneObjectsMask, + SceneryDestructionZone, + Smoke, +) +from dcs.condition import ( + AllOfCoalitionOutsideZone, + FlagIsFalse, + FlagIsTrue, + PartOfCoalitionInZone, + TimeAfter, + TimeSinceFlag, +) +from dcs.mission import Mission +from dcs.task import Option +from dcs.translation import String +from dcs.triggers import Event, TriggerCondition, TriggerOnce +from dcs.unit import Skill + +from game.theater import Airfield +from game.theater.controlpoint import Fob, TRIGGER_RADIUS_CAPTURE + +if TYPE_CHECKING: + from game.game import Game + +PUSH_TRIGGER_SIZE = 3000 +PUSH_TRIGGER_ACTIVATION_AGL = 25 + +REGROUP_ZONE_DISTANCE = 12000 +REGROUP_ALT = 5000 + +TRIGGER_WAYPOINT_OFFSET = 2 +TRIGGER_MIN_DISTANCE_FROM_START = 10000 +# modified since we now have advanced SAM units +TRIGGER_RADIUS_MINIMUM = 3000000 + +TRIGGER_RADIUS_SMALL = 50000 +TRIGGER_RADIUS_MEDIUM = 100000 +TRIGGER_RADIUS_LARGE = 150000 +TRIGGER_RADIUS_ALL_MAP = 3000000 +TRIGGER_RADIUS_CLEAR_SCENERY = 1000 + + +class Silence(Option): + Key = 7 + + +class TriggerGenerator: + capture_zone_types = (Fob, Airfield) + capture_zone_flag = 600 + + def __init__(self, mission: Mission, game: Game) -> None: + self.mission = mission + self.game = game + + def _set_allegiances(self, player_coalition: str, enemy_coalition: str) -> None: + """ + Set airbase initial coalition + """ + + # Empty neutrals airports + airfields = [ + cp for cp in self.game.theater.controlpoints if isinstance(cp, Airfield) + ] + airport_ids = {cp.airport.id for cp in airfields} + for airport in self.mission.terrain.airport_list(): + if airport.id not in airport_ids: + airport.unlimited_fuel = False + airport.unlimited_munitions = False + airport.unlimited_aircrafts = False + airport.gasoline_init = 0 + airport.methanol_mixture_init = 0 + airport.diesel_init = 0 + airport.jet_init = 0 + airport.operating_level_air = 0 + airport.operating_level_equipment = 0 + airport.operating_level_fuel = 0 + + for airport in self.mission.terrain.airport_list(): + if airport.id not in airport_ids: + airport.unlimited_fuel = True + airport.unlimited_munitions = True + airport.unlimited_aircrafts = True + + for airfield in airfields: + cp_airport = self.mission.terrain.airport_by_id(airfield.airport.id) + if cp_airport is None: + raise RuntimeError( + f"Could not find {airfield.airport.name} in the mission" + ) + cp_airport.set_coalition( + airfield.captured and player_coalition or enemy_coalition + ) + + def _set_skill(self, player_coalition: str, enemy_coalition: str) -> None: + """ + Set skill level for all aircraft in the mission + """ + for coalition_name, coalition in self.mission.coalition.items(): + if coalition_name == player_coalition: + skill_level = Skill(self.game.settings.player_skill) + elif coalition_name == enemy_coalition: + skill_level = Skill(self.game.settings.enemy_vehicle_skill) + else: + continue + + for country in coalition.countries.values(): + for vehicle_group in country.vehicle_group: + vehicle_group.set_skill(skill_level) + + def _gen_markers(self) -> None: + """ + Generate markers on F10 map for each existing objective + """ + if self.game.settings.generate_marks: + mark_trigger = TriggerOnce(Event.NoEvent, "Marks generator") + mark_trigger.add_condition(TimeAfter(1)) + v = 10 + for cp in self.game.theater.controlpoints: + seen = set() + for ground_object in cp.ground_objects: + if ground_object.obj_name in seen: + continue + + seen.add(ground_object.obj_name) + for location in ground_object.mark_locations: + zone = self.mission.triggers.add_triggerzone( + location, radius=10, hidden=True, name="MARK" + ) + if cp.captured: + name = ground_object.obj_name + " [ALLY]" + else: + name = ground_object.obj_name + " [ENEMY]" + mark_trigger.add_action(MarkToAll(v, zone.id, String(name))) + v += 1 + self.mission.triggerrules.triggers.append(mark_trigger) + + def _generate_clear_statics_trigger(self, scenery_clear_zones: List[Point]) -> None: + for zone_center in scenery_clear_zones: + trigger_zone = self.mission.triggers.add_triggerzone( + zone_center, + radius=TRIGGER_RADIUS_CLEAR_SCENERY, + hidden=False, + name="CLEAR", + ) + clear_trigger = TriggerCondition(Event.NoEvent, "Clear Trigger") + clear_flag = self.get_capture_zone_flag() + clear_trigger.add_condition(TimeSinceFlag(clear_flag, 30)) + clear_trigger.add_action(ClearFlag(clear_flag)) + clear_trigger.add_action(SetFlag(clear_flag)) + clear_trigger.add_action( + RemoveSceneObjects( + objects_mask=RemoveSceneObjectsMask.OBJECTS_ONLY, + zone=trigger_zone.id, + ) + ) + clear_trigger.add_action( + SceneryDestructionZone(destruction_level=100, zone=trigger_zone.id) + ) + self.mission.triggerrules.triggers.append(clear_trigger) + + enable_clear_trigger = TriggerOnce(Event.NoEvent, "Enable Clear Trigger") + enable_clear_trigger.add_condition(TimeAfter(30)) + enable_clear_trigger.add_action(ClearFlag(clear_flag)) + enable_clear_trigger.add_action(SetFlag(clear_flag)) + # clear_trigger.add_action(MessageToAll(text=String("Enable clear trigger"),)) + self.mission.triggerrules.triggers.append(enable_clear_trigger) + + 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(self) -> None: + player_coalition = "blue" + enemy_coalition = "red" + + self._set_skill(player_coalition, enemy_coalition) + self._set_allegiances(player_coalition, enemy_coalition) + self._gen_markers() + self._generate_capture_triggers(player_coalition, enemy_coalition) + if self.game.settings.ground_start_scenery_remove_triggers: + try: + self._generate_clear_statics_trigger(self.game.scenery_clear_zones) + self.game.scenery_clear_zones.clear() + except AttributeError: + logging.info(f"Unable to create Clear Statics triggers") + + @classmethod + def get_capture_zone_flag(cls) -> int: + flag = cls.capture_zone_flag + cls.capture_zone_flag += 1 + return flag From db95fc17dc6dc015635197871367040a473864b4 Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Sat, 9 Sep 2023 16:04:44 +0300 Subject: [PATCH 002/243] Will now generate control point trigger zones and AI aircraft for the Pretense campaign. --- game/pretense/pretenseaircraftgenerator.py | 195 ++++++++------------- game/pretense/pretensemissiongenerator.py | 127 +++++--------- game/pretense/pretensetriggergenerator.py | 97 +--------- qt_ui/uiconstants.py | 1 + qt_ui/windows/QLiberationWindow.py | 16 ++ 5 files changed, 143 insertions(+), 293 deletions(-) diff --git a/game/pretense/pretenseaircraftgenerator.py b/game/pretense/pretenseaircraftgenerator.py index 7caa91c3..f6c79c8b 100644 --- a/game/pretense/pretenseaircraftgenerator.py +++ b/game/pretense/pretenseaircraftgenerator.py @@ -1,6 +1,7 @@ from __future__ import annotations import logging +import random from datetime import datetime from functools import cached_property from typing import Any, Dict, List, TYPE_CHECKING, Tuple @@ -33,18 +34,17 @@ from game.theater.controlpoint import ( Fob, ) from game.unitmap import UnitMap -from .aircraftpainter import AircraftPainter -from .flightdata import FlightData -from .flightgroupconfigurator import FlightGroupConfigurator -from .flightgroupspawner import FlightGroupSpawner -from ...data.weapons import WeaponType +from game.missiongenerator.aircraft.aircraftpainter import AircraftPainter +from game.missiongenerator.aircraft.flightdata import FlightData +from game.missiongenerator.aircraft.flightgroupspawner import FlightGroupSpawner +from game.data.weapons import WeaponType if TYPE_CHECKING: from game import Game from game.squadrons import Squadron -class AircraftGenerator: +class PretenseAircraftGenerator: def __init__( self, mission: Mission, @@ -101,6 +101,7 @@ class AircraftGenerator: def generate_flights( self, country: Country, + cp: ControlPoint, ato: AirTaskingOrder, dynamic_runways: Dict[str, RunwayData], ) -> None: @@ -115,6 +116,61 @@ class AircraftGenerator: ato: The ATO to spawn aircraft for. dynamic_runways: Runway data for carriers and FARPs. """ + + num_of_sead = 0 + num_of_cas = 0 + num_of_strike = 0 + num_of_cap = 0 + for squadron in cp.squadrons: + squadron.owned_aircraft += 1 + squadron.untasked_aircraft += 1 + package = Package(cp, squadron.flight_db, auto_asap=False) + mission_types = squadron.auto_assignable_mission_types + if ( + FlightType.TRANSPORT in mission_types + or FlightType.AIR_ASSAULT in mission_types + ): + flight_type = FlightType.TRANSPORT + elif ( + FlightType.SEAD in mission_types + or FlightType.SEAD_SWEEP + or FlightType.SEAD_ESCORT in mission_types + ) and num_of_sead < 2: + flight_type = FlightType.SEAD + num_of_sead += 1 + elif FlightType.DEAD in mission_types and num_of_sead < 2: + flight_type = FlightType.DEAD + num_of_sead += 1 + elif ( + FlightType.CAS in mission_types or FlightType.BAI in mission_types + ) and num_of_cas < 2: + flight_type = FlightType.CAS + num_of_cas += 1 + elif ( + FlightType.STRIKE in mission_types + or FlightType.OCA_RUNWAY in mission_types + or FlightType.OCA_AIRCRAFT in mission_types + ) and num_of_strike < 2: + flight_type = FlightType.STRIKE + num_of_strike += 1 + elif ( + FlightType.BARCAP in mission_types + or FlightType.TARCAP in mission_types + or FlightType.ESCORT in mission_types + ) and num_of_cap < 2: + flight_type = FlightType.BARCAP + num_of_cap += 1 + else: + flight_type = random.choice(list(mission_types)) + flight = Flight( + package, squadron, 1, flight_type, StartType.COLD, divert=cp + ) + flight.state = WaitingForStart( + flight, self.game.settings, self.game.conditions.start_time + ) + package.add_flight(flight) + ato.add_package(package) + self._reserve_frequencies_and_tacan(ato) for package in reversed(sorted(ato.packages, key=lambda x: x.time_over_target)): @@ -134,103 +190,6 @@ class AircraftGenerator: flight, country, dynamic_runways ) self.unit_map.add_aircraft(group, flight) - if ( - package.primary_flight is not None - and package.primary_flight.flight_plan.is_formation( - package.primary_flight.flight_plan - ) - ): - splittrigger = TriggerOnce(Event.NoEvent, f"Split-{id(package)}") - splittrigger.add_condition(FlagIsTrue(flag=f"split-{id(package)}")) - splittrigger.add_condition(Or()) - splittrigger.add_condition(FlagIsFalse(flag=f"split-{id(package)}")) - splittrigger.add_condition(GroupDead(package.primary_flight.group_id)) - for flight in package.flights: - if flight.flight_type in [ - FlightType.ESCORT, - FlightType.SEAD_ESCORT, - ]: - splittrigger.add_action(AITaskPush(flight.group_id, 1)) - if len(splittrigger.actions) > 0: - self.mission.triggerrules.triggers.append(splittrigger) - - def spawn_unused_aircraft( - self, player_country: Country, enemy_country: Country - ) -> None: - for control_point in self.game.theater.controlpoints: - if not ( - isinstance(control_point, Airfield) or isinstance(control_point, Fob) - ): - continue - - if control_point.captured: - country = player_country - else: - country = enemy_country - - for squadron in control_point.squadrons: - try: - self._spawn_unused_for(squadron, country) - except NoParkingSlotError: - # If we run out of parking, stop spawning aircraft at this base. - break - - def _spawn_unused_for(self, squadron: Squadron, country: Country) -> None: - assert isinstance(squadron.location, Airfield) or isinstance( - squadron.location, Fob - ) - if ( - squadron.coalition.player - and self.game.settings.perf_disable_untasked_blufor_aircraft - ): - return - elif ( - not squadron.coalition.player - and self.game.settings.perf_disable_untasked_opfor_aircraft - ): - return - - for _ in range(squadron.untasked_aircraft): - # Creating a flight even those this isn't a fragged mission lets us - # reuse the existing debriefing code. - # TODO: Special flight type? - flight = Flight( - Package(squadron.location, self.game.db.flights), - squadron, - 1, - FlightType.BARCAP, - StartType.COLD, - divert=None, - claim_inv=False, - ) - flight.state = Completed(flight, self.game.settings) - - group = FlightGroupSpawner( - flight, - country, - self.mission, - self.helipads, - self.ground_spawns_roadbase, - self.ground_spawns, - self.mission_data, - ).create_idle_aircraft() - if group: - if ( - not squadron.coalition.player - and squadron.aircraft.flyable - and ( - self.game.settings.enable_squadron_pilot_limits - or squadron.number_of_available_pilots > 0 - ) - and self.game.settings.untasked_opfor_client_slots - ): - flight.state = WaitingForStart( - flight, self.game.settings, self.game.conditions.start_time - ) - group.uncontrolled = False - group.units[0].skill = Skill.Client - AircraftPainter(flight, group).apply_livery() - self.unit_map.add_aircraft(group, flight) def create_and_configure_flight( self, flight: Flight, country: Country, dynamic_runways: Dict[str, RunwayData] @@ -245,21 +204,21 @@ class AircraftGenerator: self.ground_spawns, self.mission_data, ).create_flight_group() - self.flights.append( - FlightGroupConfigurator( - flight, - group, - self.game, - self.mission, - self.time, - self.radio_registry, - self.tacan_registy, - self.laser_code_registry, - self.mission_data, - dynamic_runways, - self.use_client, - ).configure() - ) + # self.flights.append( + # FlightGroupConfigurator( + # flight, + # group, + # self.game, + # self.mission, + # self.time, + # self.radio_registry, + # self.tacan_registy, + # self.laser_code_registry, + # self.mission_data, + # dynamic_runways, + # self.use_client, + # ).configure() + # ) if self.ewrj: self._track_ewrj_flight(flight, group) diff --git a/game/pretense/pretensemissiongenerator.py b/game/pretense/pretensemissiongenerator.py index 85c5e5c9..16478f27 100644 --- a/game/pretense/pretensemissiongenerator.py +++ b/game/pretense/pretensemissiongenerator.py @@ -6,6 +6,7 @@ from pathlib import Path from typing import TYPE_CHECKING, cast import dcs.lua +from dataclasses import field from dcs import Mission, Point from dcs.coalition import Coalition from dcs.countries import country_dict @@ -13,38 +14,40 @@ from dcs.task import OptReactOnThreat from game.atcdata import AtcData from game.dcs.beacons import Beacons -from game.dcs.helpers import unit_type_from_name -from game.missiongenerator.aircraft.aircraftgenerator import ( - AircraftGenerator, -) + from game.naming import namegen from game.radio.radios import RadioFrequency, RadioRegistry, MHz from game.radio.tacan import TacanRegistry from game.theater import Airfield from game.theater.bullseye import Bullseye from game.unitmap import UnitMap -from .briefinggenerator import BriefingGenerator, MissionInfoGenerator -from .cargoshipgenerator import CargoShipGenerator -from .convoygenerator import ConvoyGenerator -from .drawingsgenerator import DrawingsGenerator -from .environmentgenerator import EnvironmentGenerator -from .flotgenerator import FlotGenerator -from .forcedoptionsgenerator import ForcedOptionsGenerator -from .frontlineconflictdescription import FrontLineConflictDescription -from .kneeboard import KneeboardGenerator -from .lasercoderegistry import LaserCodeRegistry -from .luagenerator import LuaGenerator -from .missiondata import MissionData -from .tgogenerator import TgoGenerator -from .triggergenerator import TriggerGenerator -from .visualsgenerator import VisualsGenerator +from game.pretense.pretenseaircraftgenerator import PretenseAircraftGenerator +from game.missiongenerator.briefinggenerator import ( + BriefingGenerator, + MissionInfoGenerator, +) +from game.missiongenerator.convoygenerator import ConvoyGenerator +from game.missiongenerator.environmentgenerator import EnvironmentGenerator +from game.missiongenerator.flotgenerator import FlotGenerator +from game.missiongenerator.forcedoptionsgenerator import ForcedOptionsGenerator +from game.missiongenerator.frontlineconflictdescription import ( + FrontLineConflictDescription, +) +from game.missiongenerator.kneeboard import KneeboardGenerator +from game.missiongenerator.lasercoderegistry import LaserCodeRegistry +from game.missiongenerator.luagenerator import LuaGenerator +from game.missiongenerator.missiondata import MissionData +from game.missiongenerator.tgogenerator import TgoGenerator +from .pretensetriggergenerator import PretenseTriggerGenerator +from game.missiongenerator.visualsgenerator import VisualsGenerator +from ..ato import Flight from ..radio.TacanContainer import TacanContainer if TYPE_CHECKING: from game import Game -class MissionGenerator: +class PretenseMissionGenerator: def __init__(self, game: Game, time: datetime) -> None: self.game = game self.time = time @@ -94,20 +97,16 @@ class MissionGenerator: tgo_generator.generate() ConvoyGenerator(self.mission, self.game, self.unit_map).generate() - CargoShipGenerator(self.mission, self.game, self.unit_map).generate() - - self.generate_destroyed_units() # Generate ground conflicts first so the JTACs get the first laser code (1688) # rather than the first player flight with a TGP. self.generate_ground_conflicts() self.generate_air_units(tgo_generator) - TriggerGenerator(self.mission, self.game).generate() + PretenseTriggerGenerator(self.mission, self.game).generate() ForcedOptionsGenerator(self.mission, self.game).generate() VisualsGenerator(self.mission, self.game).generate() LuaGenerator(self.game, self.mission, self.mission_data).generate() - DrawingsGenerator(self.mission, self.game).generate() self.setup_combined_arms() @@ -119,22 +118,6 @@ class MissionGenerator: return self.unit_map - @staticmethod - def _configure_ewrj(gen: AircraftGenerator) -> None: - for groups in gen.ewrj_package_dict.values(): - optrot = groups[0].points[0].tasks[0] - assert isinstance(optrot, OptReactOnThreat) - if ( - len(groups) == 1 - and optrot.value != OptReactOnThreat.Values.PassiveDefense - ): - # primary flight with no EWR-Jamming capability - continue - for group in groups: - group.points[0].tasks[0] = OptReactOnThreat( - OptReactOnThreat.Values.PassiveDefense - ) - def setup_mission_coalitions(self) -> None: self.mission.coalition["blue"] = Coalition( "blue", bullseye=self.game.blue.bullseye.to_pydcs() @@ -232,7 +215,7 @@ class MissionGenerator: """Generate the air units for the Operation""" # Generate Aircraft Activity on the map - aircraft_generator = AircraftGenerator( + aircraft_generator = PretenseAircraftGenerator( self.mission, self.game.settings, self.game, @@ -247,22 +230,25 @@ class MissionGenerator: ground_spawns=tgo_generator.ground_spawns, ) + # Clear parking slots and ATOs aircraft_generator.clear_parking_slots() + self.game.blue.ato.clear() + self.game.red.ato.clear() - aircraft_generator.generate_flights( - self.p_country, - self.game.blue.ato, - tgo_generator.runways, - ) - aircraft_generator.generate_flights( - self.e_country, - self.game.red.ato, - tgo_generator.runways, - ) - aircraft_generator.spawn_unused_aircraft( - self.p_country, - self.e_country, - ) + for cp in self.game.theater.controlpoints: + if cp.captured: + ato = self.game.blue.ato + cp_country = self.p_country + else: + ato = self.game.red.ato + cp_country = self.e_country + print(f"Generating flights for {cp_country.name} at {cp}") + aircraft_generator.generate_flights( + cp_country, + cp, + ato, + tgo_generator.runways, + ) self.mission_data.flights = aircraft_generator.flights @@ -274,35 +260,6 @@ class MissionGenerator: if self.game.settings.plugins.get("ewrj"): self._configure_ewrj(aircraft_generator) - def generate_destroyed_units(self) -> None: - """Add destroyed units to the Mission""" - if not self.game.settings.perf_destroyed_units: - return - - for d in self.game.get_destroyed_units(): - try: - type_name = d["type"] - if not isinstance(type_name, str): - raise TypeError( - "Expected the type of the destroyed static to be a string" - ) - utype = unit_type_from_name(type_name) - except KeyError: - logging.warning(f"Destroyed unit has no type: {d}") - continue - - pos = Point(cast(float, d["x"]), cast(float, d["z"]), self.mission.terrain) - if utype is not None and not self.game.position_culled(pos): - self.mission.static_group( - country=self.p_country, - name="", - _type=utype, - hidden=True, - position=pos, - heading=d["orientation"], - dead=True, - ) - def notify_info_generators( self, ) -> None: diff --git a/game/pretense/pretensetriggergenerator.py b/game/pretense/pretensetriggergenerator.py index cc31b4e5..11932f87 100644 --- a/game/pretense/pretensetriggergenerator.py +++ b/game/pretense/pretensetriggergenerator.py @@ -56,7 +56,7 @@ class Silence(Option): Key = 7 -class TriggerGenerator: +class PretenseTriggerGenerator: capture_zone_types = (Fob, Airfield) capture_zone_flag = 600 @@ -146,38 +146,7 @@ class TriggerGenerator: v += 1 self.mission.triggerrules.triggers.append(mark_trigger) - def _generate_clear_statics_trigger(self, scenery_clear_zones: List[Point]) -> None: - for zone_center in scenery_clear_zones: - trigger_zone = self.mission.triggers.add_triggerzone( - zone_center, - radius=TRIGGER_RADIUS_CLEAR_SCENERY, - hidden=False, - name="CLEAR", - ) - clear_trigger = TriggerCondition(Event.NoEvent, "Clear Trigger") - clear_flag = self.get_capture_zone_flag() - clear_trigger.add_condition(TimeSinceFlag(clear_flag, 30)) - clear_trigger.add_action(ClearFlag(clear_flag)) - clear_trigger.add_action(SetFlag(clear_flag)) - clear_trigger.add_action( - RemoveSceneObjects( - objects_mask=RemoveSceneObjectsMask.OBJECTS_ONLY, - zone=trigger_zone.id, - ) - ) - clear_trigger.add_action( - SceneryDestructionZone(destruction_level=100, zone=trigger_zone.id) - ) - self.mission.triggerrules.triggers.append(clear_trigger) - - enable_clear_trigger = TriggerOnce(Event.NoEvent, "Enable Clear Trigger") - enable_clear_trigger.add_condition(TimeAfter(30)) - enable_clear_trigger.add_action(ClearFlag(clear_flag)) - enable_clear_trigger.add_action(SetFlag(clear_flag)) - # clear_trigger.add_action(MessageToAll(text=String("Enable clear trigger"),)) - self.mission.triggerrules.triggers.append(enable_clear_trigger) - - def _generate_capture_triggers( + def _generate_pretense_zone_triggers( self, player_coalition: str, enemy_coalition: str ) -> None: """Creates a pair of triggers for each control point of `cls.capture_zone_types`. @@ -185,62 +154,16 @@ class TriggerGenerator: 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 + if isinstance(cp, self.capture_zone_types) and not cp.is_fleet: + zone_color = {1: 0.0, 2: 0.0, 3: 0.0, 4: 0.149} trigger_zone = self.mission.triggers.add_triggerzone( cp.position, radius=TRIGGER_RADIUS_CAPTURE, hidden=False, - name="CAPTURE", + name=cp.name, + color=zone_color, ) - 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(self) -> None: player_coalition = "blue" @@ -249,13 +172,7 @@ class TriggerGenerator: self._set_skill(player_coalition, enemy_coalition) self._set_allegiances(player_coalition, enemy_coalition) self._gen_markers() - self._generate_capture_triggers(player_coalition, enemy_coalition) - if self.game.settings.ground_start_scenery_remove_triggers: - try: - self._generate_clear_statics_trigger(self.game.scenery_clear_zones) - self.game.scenery_clear_zones.clear() - except AttributeError: - logging.info(f"Unable to create Clear Statics triggers") + self._generate_pretense_zone_triggers(player_coalition, enemy_coalition) @classmethod def get_capture_zone_flag(cls) -> int: diff --git a/qt_ui/uiconstants.py b/qt_ui/uiconstants.py index dcb95b1a..8edc69a0 100644 --- a/qt_ui/uiconstants.py +++ b/qt_ui/uiconstants.py @@ -32,6 +32,7 @@ def load_icons(): "./resources/ui/misc/" + get_theme_icons() + "/github.png" ) ICONS["Ukraine"] = QPixmap("./resources/ui/misc/ukraine.png") + ICONS["Pretense"] = QPixmap("./resources/ui/misc/pretense.png") ICONS["Control Points"] = QPixmap( "./resources/ui/misc/" + get_theme_icons() + "/circle.png" diff --git a/qt_ui/windows/QLiberationWindow.py b/qt_ui/windows/QLiberationWindow.py index c7feb6be..e6e090ef 100644 --- a/qt_ui/windows/QLiberationWindow.py +++ b/qt_ui/windows/QLiberationWindow.py @@ -21,6 +21,7 @@ from game import Game, VERSION, persistency, Migrator from game.debriefing import Debriefing from game.game import TurnState from game.layout import LAYOUTS +from game.pretense.pretensemissiongenerator import PretenseMissionGenerator from game.server import EventStream, GameContext from game.server.dependencies import QtCallbacks, QtContext from game.theater import ControlPoint, MissionTarget, TheaterGroundObject @@ -41,6 +42,7 @@ from qt_ui.windows.groundobject.QGroundObjectMenu import QGroundObjectMenu from qt_ui.windows.infos.QInfoPanel import QInfoPanel from qt_ui.windows.logs.QLogsWindow import QLogsWindow from qt_ui.windows.newgame.QNewGameWizard import NewGameWizard +from qt_ui.windows.newgame.QNewPretenseWizard import NewPretenseWizard from qt_ui.windows.notes.QNotesWindow import QNotesWindow from qt_ui.windows.preferences.QLiberationPreferencesWindow import ( QLiberationPreferencesWindow, @@ -193,6 +195,10 @@ class QLiberationWindow(QMainWindow): lambda: webbrowser.open_new_tab("https://shdwp.github.io/ukraine/") ) + self.newPretenseAction = QAction("&New Pretense Campaign", self) + self.newPretenseAction.setIcon(QIcon(CONST.ICONS["Pretense"])) + self.newPretenseAction.triggered.connect(self.newPretenseCampaign) + self.openLogsAction = QAction("Show &logs", self) self.openLogsAction.triggered.connect(self.showLogsDialog) @@ -234,6 +240,7 @@ class QLiberationWindow(QMainWindow): self.links_bar.addAction(self.openDiscordAction) self.links_bar.addAction(self.openGithubAction) self.links_bar.addAction(self.ukraineAction) + self.links_bar.addAction(self.newPretenseAction) self.actions_bar = self.addToolBar("Actions") self.actions_bar.addAction(self.openSettingsAction) @@ -303,6 +310,15 @@ class QLiberationWindow(QMainWindow): wizard.show() wizard.accepted.connect(lambda: self.onGameGenerated(wizard.generatedGame)) + def newPretenseCampaign(self): + output = persistency.mission_path_for("pretense_campaign.miz") + PretenseMissionGenerator( + self.game, self.game.conditions.start_time + ).generate_miz(output) + title = "Pretense campaign generated" + msg = f"A Pretense campaign mission has been successfully generated in {output}" + QMessageBox.information(QApplication.focusWidget(), title, msg, QMessageBox.Ok) + def openFile(self): if self.game is not None and self.game.savepath: save_dir = self.game.savepath From d6ab4e98918a121bd628c8567fe2ee4be14cfbb6 Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Sat, 9 Sep 2023 16:12:18 +0300 Subject: [PATCH 003/243] Copied flightgroupspawner.py as a template/inheritance for generating Pretense campaigns from Retribution campaigns. --- game/pretense/pretenseflightgroupspawner.py | 433 ++++++++++++++++++++ 1 file changed, 433 insertions(+) create mode 100644 game/pretense/pretenseflightgroupspawner.py diff --git a/game/pretense/pretenseflightgroupspawner.py b/game/pretense/pretenseflightgroupspawner.py new file mode 100644 index 00000000..f3b19e87 --- /dev/null +++ b/game/pretense/pretenseflightgroupspawner.py @@ -0,0 +1,433 @@ +import logging +import random +from typing import Any, Union, Tuple, Optional + +from dcs import Mission +from dcs.country import Country +from dcs.mapping import Vector2, Point +from dcs.mission import StartType as DcsStartType +from dcs.planes import F_14A, Su_33 +from dcs.point import PointAction +from dcs.ships import KUZNECOW +from dcs.terrain import NoParkingSlotError +from dcs.unitgroup import ( + FlyingGroup, + ShipGroup, + StaticGroup, + HelicopterGroup, + PlaneGroup, +) + +from game.ato import Flight +from game.ato.flightstate import InFlight +from game.ato.starttype import StartType +from game.ato.traveltime import GroundSpeed +from game.missiongenerator.missiondata import MissionData +from game.naming import namegen +from game.theater import Airfield, ControlPoint, Fob, NavalControlPoint, OffMapSpawn +from game.utils import feet, meters + +WARM_START_HELI_ALT = meters(500) +WARM_START_ALTITUDE = meters(3000) + +# In-flight spawns are MSL for the first waypoint (this can maybe be changed to AGL, but +# AGL waypoints have different piloting behavior, so we need to check whether that's +# safe to do first), so spawn them high enough that they're unlikely to be near (or +# under) the ground, or any nearby obstacles. The highest airfield in DCS is Kerman in +# PG at 5700ft. This could still be too low if there are tall obstacles near the +# airfield, but the lowest we can push this the better to avoid spawning helicopters +# well above the altitude for WP1. +MINIMUM_MID_MISSION_SPAWN_ALTITUDE_MSL = feet(6000) +MINIMUM_MID_MISSION_SPAWN_ALTITUDE_AGL = feet(500) + +STACK_SEPARATION = feet(200) + +RTB_ALTITUDE = meters(800) +RTB_DISTANCE = 5000 +HELI_ALT = 500 + + +class FlightGroupSpawner: + def __init__( + self, + flight: Flight, + country: Country, + mission: Mission, + helipads: dict[ControlPoint, list[StaticGroup]], + ground_spawns_roadbase: dict[ControlPoint, list[Tuple[StaticGroup, Point]]], + ground_spawns: dict[ControlPoint, list[Tuple[StaticGroup, Point]]], + mission_data: MissionData, + ) -> None: + self.flight = flight + self.country = country + self.mission = mission + self.helipads = helipads + self.ground_spawns_roadbase = ground_spawns_roadbase + self.ground_spawns = ground_spawns + self.mission_data = mission_data + + def create_flight_group(self) -> FlyingGroup[Any]: + """Creates the group for the flight and adds it to the mission. + + Each flight is spawned according to its FlightState at the time of mission + generation. Aircraft that are WaitingForStart will be set up based on their + StartType with a delay. Note that delays are actually created during waypoint + generation. + + Aircraft that are *not* WaitingForStart will be spawned in their current state. + We cannot spawn aircraft mid-taxi, so when the simulated state is near the end + of a long taxi period the aircraft will be spawned in their parking spot. This + could lead to problems but that's what loiter points are for. The other pre- + flight states have the same problem but are much shorter and more easily covered + by the loiter time. Player flights that are spawned near the end of their cold + start have the biggest problem but players are able to cut corners to make up + for lost time. + + Aircraft that are already in the air will be spawned at their estimated + location, speed, and altitude based on their flight plan. + """ + if ( + self.flight.state.is_waiting_for_start + or self.flight.state.spawn_type is not StartType.IN_FLIGHT + ): + grp = self.generate_flight_at_departure() + self.flight.group_id = grp.id + return grp + grp = self.generate_mid_mission() + self.flight.group_id = grp.id + return grp + + def create_idle_aircraft(self) -> Optional[FlyingGroup[Any]]: + group = None + if ( + self.flight.is_helo + or self.flight.is_lha + and isinstance(self.flight.squadron.location, Fob) + ): + group = self._generate_at_cp_helipad( + name=namegen.next_aircraft_name(self.country, self.flight), + cp=self.flight.squadron.location, + ) + elif isinstance(self.flight.squadron.location, Fob): + group = self._generate_at_cp_ground_spawn( + name=namegen.next_aircraft_name(self.country, self.flight), + cp=self.flight.squadron.location, + ) + elif isinstance(self.flight.squadron.location, Airfield): + group = self._generate_at_airfield( + name=namegen.next_aircraft_name(self.country, self.flight), + airfield=self.flight.squadron.location, + ) + if group: + group.uncontrolled = True + return group + + @property + def start_type(self) -> StartType: + return self.flight.state.spawn_type + + def generate_flight_at_departure(self) -> FlyingGroup[Any]: + name = namegen.next_aircraft_name(self.country, self.flight) + cp = self.flight.departure + try: + if self.start_type is StartType.IN_FLIGHT: + group = self._generate_over_departure(name, cp) + return group + elif isinstance(cp, NavalControlPoint): + group_name = cp.get_carrier_group_name() + carrier_group = self.mission.find_group(group_name) + if not isinstance(carrier_group, ShipGroup): + raise RuntimeError( + f"Carrier group {carrier_group} is a " + f"{carrier_group.__class__.__name__}, expected a ShipGroup" + ) + return self._generate_at_group(name, carrier_group) + elif isinstance(cp, Fob): + is_heli = self.flight.squadron.aircraft.helicopter + is_vtol = not is_heli and self.flight.squadron.aircraft.lha_capable + if not is_heli and not is_vtol and not cp.has_ground_spawns: + raise RuntimeError( + f"Cannot spawn fixed-wing aircraft at {cp} because of insufficient ground spawn slots." + ) + pilot_count = len(self.flight.roster.pilots) + if ( + not is_heli + and self.flight.roster.player_count != pilot_count + and not self.flight.coalition.game.settings.ground_start_ai_planes + ): + raise RuntimeError( + f"Fixed-wing aircraft at {cp} must be piloted by humans exclusively because" + f' the "AI fixed-wing aircraft can use roadbases / bases with only ground' + f' spawns" setting is currently disabled.' + ) + if cp.has_helipads and (is_heli or is_vtol): + pad_group = self._generate_at_cp_helipad(name, cp) + if pad_group is not None: + return pad_group + if cp.has_ground_spawns and (self.flight.client_count > 0 or is_heli): + pad_group = self._generate_at_cp_ground_spawn(name, cp) + if pad_group is not None: + return pad_group + return self._generate_over_departure(name, cp) + elif isinstance(cp, Airfield): + is_heli = self.flight.squadron.aircraft.helicopter + if cp.has_helipads and is_heli: + pad_group = self._generate_at_cp_helipad(name, cp) + if pad_group is not None: + return pad_group + if ( + cp.has_ground_spawns + and len(self.ground_spawns[cp]) + + len(self.ground_spawns_roadbase[cp]) + >= self.flight.count + and (self.flight.client_count > 0 or is_heli) + ): + pad_group = self._generate_at_cp_ground_spawn(name, cp) + if pad_group is not None: + return pad_group + return self._generate_at_airfield(name, cp) + else: + raise NotImplementedError( + f"Aircraft spawn behavior not implemented for {cp} ({cp.__class__})" + ) + except NoParkingSlotError: + # Generated when there is no place on Runway or on Parking Slots + logging.warning( + "No room on runway or parking slots. Starting from the air." + ) + self.flight.start_type = StartType.IN_FLIGHT + group = self._generate_over_departure(name, cp) + return group + + def generate_mid_mission(self) -> FlyingGroup[Any]: + assert isinstance(self.flight.state, InFlight) + name = namegen.next_aircraft_name(self.country, self.flight) + speed = self.flight.state.estimate_speed() + pos = self.flight.state.estimate_position() + pos += Vector2(random.randint(100, 1000), random.randint(100, 1000)) + alt, alt_type = self.flight.state.estimate_altitude() + cp = self.flight.squadron.location.id + + if cp not in self.mission_data.cp_stack: + self.mission_data.cp_stack[cp] = MINIMUM_MID_MISSION_SPAWN_ALTITUDE_AGL + + # We don't know where the ground is, so just make sure that any aircraft + # spawning at an MSL altitude is spawned at some minimum altitude. + # https://github.com/dcs-liberation/dcs_liberation/issues/1941 + if alt_type == "BARO" and alt < MINIMUM_MID_MISSION_SPAWN_ALTITUDE_MSL: + alt = MINIMUM_MID_MISSION_SPAWN_ALTITUDE_MSL + + # Set a minimum AGL value for 'alt' if needed, + # otherwise planes might crash in trees and stuff. + if alt_type == "RADIO" and alt < self.mission_data.cp_stack[cp]: + alt = self.mission_data.cp_stack[cp] + self.mission_data.cp_stack[cp] += STACK_SEPARATION + + group = self.mission.flight_group( + country=self.country, + name=name, + aircraft_type=self.flight.unit_type.dcs_unit_type, + airport=None, + position=pos, + altitude=alt.meters, + speed=speed.kph, + maintask=None, + group_size=self.flight.count, + ) + + group.points[0].alt_type = alt_type + return group + + def _generate_at_airfield(self, name: str, airfield: Airfield) -> FlyingGroup[Any]: + # TODO: Delayed runway starts should be converted to air starts for multiplayer. + # Runway starts do not work with late activated aircraft in multiplayer. Instead + # of spawning on the runway the aircraft will spawn on the taxiway, potentially + # somewhere that they don't fit anyway. We should either upgrade these to air + # starts or (less likely) downgrade to warm starts to avoid the issue when the + # player is generating the mission for multiplayer (which would need a new + # option). + self.flight.unit_type.dcs_unit_type.load_payloads() + return self.mission.flight_group_from_airport( + country=self.country, + name=name, + aircraft_type=self.flight.unit_type.dcs_unit_type, + airport=airfield.airport, + maintask=None, + start_type=self._start_type_at_airfield(airfield), + group_size=self.flight.count, + parking_slots=None, + ) + + def _generate_over_departure( + self, name: str, origin: ControlPoint + ) -> FlyingGroup[Any]: + at = origin.position + + alt_type = "RADIO" + if isinstance(origin, OffMapSpawn): + alt = self.flight.flight_plan.waypoints[0].alt + alt_type = self.flight.flight_plan.waypoints[0].alt_type + elif self.flight.unit_type.helicopter: + alt = WARM_START_HELI_ALT + else: + if origin.id not in self.mission_data.cp_stack: + min_alt = MINIMUM_MID_MISSION_SPAWN_ALTITUDE_AGL + self.mission_data.cp_stack[origin.id] = min_alt + alt = self.mission_data.cp_stack[origin.id] + self.mission_data.cp_stack[origin.id] += STACK_SEPARATION + + speed = GroundSpeed.for_flight(self.flight, alt) + pos = at + Vector2(random.randint(100, 1000), random.randint(100, 1000)) + + group = self.mission.flight_group( + country=self.country, + name=name, + aircraft_type=self.flight.unit_type.dcs_unit_type, + airport=None, + position=pos, + altitude=alt.meters, + speed=speed.kph, + maintask=None, + group_size=self.flight.count, + ) + + group.points[0].alt_type = alt_type + return group + + def _generate_at_group( + self, name: str, at: Union[ShipGroup, StaticGroup] + ) -> FlyingGroup[Any]: + return self.mission.flight_group_from_unit( + country=self.country, + name=name, + aircraft_type=self.flight.unit_type.dcs_unit_type, + pad_group=at, + maintask=None, + start_type=self._start_type_at_group(at), + group_size=self.flight.count, + ) + + def _generate_at_cp_helipad( + self, name: str, cp: ControlPoint + ) -> Optional[FlyingGroup[Any]]: + try: + helipad = self.helipads[cp].pop() + except IndexError as ex: + logging.warning("Not enough helipads available at " + str(ex)) + if isinstance(cp, Airfield): + return self._generate_at_airfield(name, cp) + else: + return None + # raise RuntimeError(f"Not enough helipads available at {cp}") from ex + + group = self._generate_at_group(name, helipad) + + # Note : A bit dirty, need better support in pydcs + group.points[0].action = PointAction.FromGroundArea + group.points[0].type = "TakeOffGround" + group.units[0].heading = helipad.units[0].heading + if self.start_type is not StartType.COLD: + group.points[0].action = PointAction.FromGroundAreaHot + group.points[0].type = "TakeOffGroundHot" + + wpt = group.waypoint("LANDING") + if wpt: + hpad = self.helipads[self.flight.arrival].pop(0) + wpt.helipad_id = hpad.units[0].id + wpt.link_unit = hpad.units[0].id + self.helipads[self.flight.arrival].append(hpad) + + for i in range(self.flight.count - 1): + try: + helipad = self.helipads[cp].pop() + terrain = cp.coalition.game.theater.terrain + group.units[1 + i].position = Point( + helipad.x, helipad.y, terrain=terrain + ) + group.units[1 + i].heading = helipad.units[0].heading + except IndexError as ex: + logging.warning("Not enough helipads available at " + str(ex)) + if isinstance(cp, Airfield): + return self._generate_at_airfield(name, cp) + else: + if isinstance(group, HelicopterGroup): + self.country.helicopter_group.remove(group) + elif isinstance(group, PlaneGroup): + self.country.plane_group.remove(group) + return None + return group + + def _generate_at_cp_ground_spawn( + self, name: str, cp: ControlPoint + ) -> Optional[FlyingGroup[Any]]: + try: + if len(self.ground_spawns_roadbase[cp]) > 0: + ground_spawn = self.ground_spawns_roadbase[cp].pop() + else: + ground_spawn = self.ground_spawns[cp].pop() + except IndexError as ex: + logging.warning("Not enough STOL slots available at " + str(ex)) + return None + # raise RuntimeError(f"Not enough STOL slots available at {cp}") from ex + + group = self._generate_at_group(name, ground_spawn[0]) + + # Note : A bit dirty, need better support in pydcs + group.points[0].action = PointAction.FromGroundArea + group.points[0].type = "TakeOffGround" + group.units[0].heading = ground_spawn[0].units[0].heading + + try: + cp.coalition.game.scenery_clear_zones + except AttributeError: + cp.coalition.game.scenery_clear_zones = [] + cp.coalition.game.scenery_clear_zones.append(ground_spawn[1]) + + for i in range(self.flight.count - 1): + try: + terrain = cp.coalition.game.theater.terrain + if len(self.ground_spawns_roadbase[cp]) > 0: + ground_spawn = self.ground_spawns_roadbase[cp].pop() + else: + ground_spawn = self.ground_spawns[cp].pop() + group.units[1 + i].position = Point( + ground_spawn[0].x, ground_spawn[0].y, terrain=terrain + ) + group.units[1 + i].heading = ground_spawn[0].units[0].heading + except IndexError as ex: + raise RuntimeError(f"Not enough STOL slots available at {cp}") from ex + return group + + def dcs_start_type(self) -> DcsStartType: + if self.start_type is StartType.RUNWAY: + return DcsStartType.Runway + elif self.start_type is StartType.COLD: + return DcsStartType.Cold + elif self.start_type is StartType.WARM: + return DcsStartType.Warm + raise ValueError(f"There is no pydcs StartType matching {self.start_type}") + + def _start_type_at_airfield( + self, + airfield: Airfield, + ) -> DcsStartType: + return self.dcs_start_type() + + def _start_type_at_group( + self, + at: Union[ShipGroup, StaticGroup], + ) -> DcsStartType: + group_units = at.units + # Setting Su-33s starting from the non-supercarrier Kuznetsov to take off from + # runway to work around a DCS AI issue preventing Su-33s from taking off when + # set to "Takeoff from ramp" (#1352) + # Also setting the F-14A AI variant to start from cats since they are reported + # to have severe pathfinding problems when doing ramp starts (#1927) + if self.flight.unit_type.dcs_unit_type == F_14A or ( + self.flight.unit_type.dcs_unit_type == Su_33 + and group_units[0] is not None + and group_units[0].type == KUZNECOW.id + ): + return DcsStartType.Runway + else: + return self.dcs_start_type() From 676e4dcebfcbce53d90fa02ca1166e328aba3929 Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Sat, 9 Sep 2023 19:52:16 +0300 Subject: [PATCH 004/243] Implemented generating trigger zones for supply routes, theater ground objects and helicopter supply points. Implemented name generator for Pretense air units. --- game/pretense/pretenseaircraftgenerator.py | 5 +- game/pretense/pretenseflightgroupspawner.py | 354 ++------------------ game/pretense/pretensetriggergenerator.py | 44 ++- 3 files changed, 76 insertions(+), 327 deletions(-) diff --git a/game/pretense/pretenseaircraftgenerator.py b/game/pretense/pretenseaircraftgenerator.py index f6c79c8b..334a8871 100644 --- a/game/pretense/pretenseaircraftgenerator.py +++ b/game/pretense/pretenseaircraftgenerator.py @@ -36,7 +36,6 @@ from game.theater.controlpoint import ( from game.unitmap import UnitMap from game.missiongenerator.aircraft.aircraftpainter import AircraftPainter from game.missiongenerator.aircraft.flightdata import FlightData -from game.missiongenerator.aircraft.flightgroupspawner import FlightGroupSpawner from game.data.weapons import WeaponType if TYPE_CHECKING: @@ -194,8 +193,10 @@ class PretenseAircraftGenerator: def create_and_configure_flight( self, flight: Flight, country: Country, dynamic_runways: Dict[str, RunwayData] ) -> FlyingGroup[Any]: + from game.pretense.pretenseflightgroupspawner import PretenseFlightGroupSpawner + """Creates and configures the flight group in the mission.""" - group = FlightGroupSpawner( + group = PretenseFlightGroupSpawner( flight, country, self.mission, diff --git a/game/pretense/pretenseflightgroupspawner.py b/game/pretense/pretenseflightgroupspawner.py index f3b19e87..908edcc0 100644 --- a/game/pretense/pretenseflightgroupspawner.py +++ b/game/pretense/pretenseflightgroupspawner.py @@ -1,53 +1,40 @@ import logging -import random -from typing import Any, Union, Tuple, Optional +import re +from typing import Any, Tuple from dcs import Mission from dcs.country import Country from dcs.mapping import Vector2, Point -from dcs.mission import StartType as DcsStartType -from dcs.planes import F_14A, Su_33 -from dcs.point import PointAction -from dcs.ships import KUZNECOW from dcs.terrain import NoParkingSlotError from dcs.unitgroup import ( FlyingGroup, ShipGroup, StaticGroup, - HelicopterGroup, - PlaneGroup, ) from game.ato import Flight -from game.ato.flightstate import InFlight from game.ato.starttype import StartType -from game.ato.traveltime import GroundSpeed +from game.missiongenerator.aircraft.flightgroupspawner import FlightGroupSpawner from game.missiongenerator.missiondata import MissionData -from game.naming import namegen -from game.theater import Airfield, ControlPoint, Fob, NavalControlPoint, OffMapSpawn +from game.naming import NameGenerator +from game.theater import Airfield, ControlPoint, Fob, NavalControlPoint from game.utils import feet, meters -WARM_START_HELI_ALT = meters(500) -WARM_START_ALTITUDE = meters(3000) -# In-flight spawns are MSL for the first waypoint (this can maybe be changed to AGL, but -# AGL waypoints have different piloting behavior, so we need to check whether that's -# safe to do first), so spawn them high enough that they're unlikely to be near (or -# under) the ground, or any nearby obstacles. The highest airfield in DCS is Kerman in -# PG at 5700ft. This could still be too low if there are tall obstacles near the -# airfield, but the lowest we can push this the better to avoid spawning helicopters -# well above the altitude for WP1. -MINIMUM_MID_MISSION_SPAWN_ALTITUDE_MSL = feet(6000) -MINIMUM_MID_MISSION_SPAWN_ALTITUDE_AGL = feet(500) - -STACK_SEPARATION = feet(200) - -RTB_ALTITUDE = meters(800) -RTB_DISTANCE = 5000 -HELI_ALT = 500 +class PretenseNameGenerator(NameGenerator): + @classmethod + def next_pretense_aircraft_name(cls, cp: ControlPoint, flight: Flight) -> str: + cls.aircraft_number += 1 + cp_name_trimmed = "".join([i for i in cp.name.lower() if i.isalnum()]) + return "{}-{}-{}".format( + cp_name_trimmed, str(flight.flight_type).lower(), cls.aircraft_number + ) -class FlightGroupSpawner: +namegen = PretenseNameGenerator + + +class PretenseFlightGroupSpawner(FlightGroupSpawner): def __init__( self, flight: Flight, @@ -58,6 +45,16 @@ class FlightGroupSpawner: ground_spawns: dict[ControlPoint, list[Tuple[StaticGroup, Point]]], mission_data: MissionData, ) -> None: + super().__init__( + flight, + country, + mission, + helipads, + ground_spawns_roadbase, + ground_spawns, + mission_data, + ) + self.flight = flight self.country = country self.mission = mission @@ -66,69 +63,11 @@ class FlightGroupSpawner: self.ground_spawns = ground_spawns self.mission_data = mission_data - def create_flight_group(self) -> FlyingGroup[Any]: - """Creates the group for the flight and adds it to the mission. - - Each flight is spawned according to its FlightState at the time of mission - generation. Aircraft that are WaitingForStart will be set up based on their - StartType with a delay. Note that delays are actually created during waypoint - generation. - - Aircraft that are *not* WaitingForStart will be spawned in their current state. - We cannot spawn aircraft mid-taxi, so when the simulated state is near the end - of a long taxi period the aircraft will be spawned in their parking spot. This - could lead to problems but that's what loiter points are for. The other pre- - flight states have the same problem but are much shorter and more easily covered - by the loiter time. Player flights that are spawned near the end of their cold - start have the biggest problem but players are able to cut corners to make up - for lost time. - - Aircraft that are already in the air will be spawned at their estimated - location, speed, and altitude based on their flight plan. - """ - if ( - self.flight.state.is_waiting_for_start - or self.flight.state.spawn_type is not StartType.IN_FLIGHT - ): - grp = self.generate_flight_at_departure() - self.flight.group_id = grp.id - return grp - grp = self.generate_mid_mission() - self.flight.group_id = grp.id - return grp - - def create_idle_aircraft(self) -> Optional[FlyingGroup[Any]]: - group = None - if ( - self.flight.is_helo - or self.flight.is_lha - and isinstance(self.flight.squadron.location, Fob) - ): - group = self._generate_at_cp_helipad( - name=namegen.next_aircraft_name(self.country, self.flight), - cp=self.flight.squadron.location, - ) - elif isinstance(self.flight.squadron.location, Fob): - group = self._generate_at_cp_ground_spawn( - name=namegen.next_aircraft_name(self.country, self.flight), - cp=self.flight.squadron.location, - ) - elif isinstance(self.flight.squadron.location, Airfield): - group = self._generate_at_airfield( - name=namegen.next_aircraft_name(self.country, self.flight), - airfield=self.flight.squadron.location, - ) - if group: - group.uncontrolled = True - return group - - @property - def start_type(self) -> StartType: - return self.flight.state.spawn_type - def generate_flight_at_departure(self) -> FlyingGroup[Any]: - name = namegen.next_aircraft_name(self.country, self.flight) cp = self.flight.departure + name = namegen.next_pretense_aircraft_name(cp, self.flight) + + print(name) try: if self.start_type is StartType.IN_FLIGHT: group = self._generate_over_departure(name, cp) @@ -198,236 +137,3 @@ class FlightGroupSpawner: self.flight.start_type = StartType.IN_FLIGHT group = self._generate_over_departure(name, cp) return group - - def generate_mid_mission(self) -> FlyingGroup[Any]: - assert isinstance(self.flight.state, InFlight) - name = namegen.next_aircraft_name(self.country, self.flight) - speed = self.flight.state.estimate_speed() - pos = self.flight.state.estimate_position() - pos += Vector2(random.randint(100, 1000), random.randint(100, 1000)) - alt, alt_type = self.flight.state.estimate_altitude() - cp = self.flight.squadron.location.id - - if cp not in self.mission_data.cp_stack: - self.mission_data.cp_stack[cp] = MINIMUM_MID_MISSION_SPAWN_ALTITUDE_AGL - - # We don't know where the ground is, so just make sure that any aircraft - # spawning at an MSL altitude is spawned at some minimum altitude. - # https://github.com/dcs-liberation/dcs_liberation/issues/1941 - if alt_type == "BARO" and alt < MINIMUM_MID_MISSION_SPAWN_ALTITUDE_MSL: - alt = MINIMUM_MID_MISSION_SPAWN_ALTITUDE_MSL - - # Set a minimum AGL value for 'alt' if needed, - # otherwise planes might crash in trees and stuff. - if alt_type == "RADIO" and alt < self.mission_data.cp_stack[cp]: - alt = self.mission_data.cp_stack[cp] - self.mission_data.cp_stack[cp] += STACK_SEPARATION - - group = self.mission.flight_group( - country=self.country, - name=name, - aircraft_type=self.flight.unit_type.dcs_unit_type, - airport=None, - position=pos, - altitude=alt.meters, - speed=speed.kph, - maintask=None, - group_size=self.flight.count, - ) - - group.points[0].alt_type = alt_type - return group - - def _generate_at_airfield(self, name: str, airfield: Airfield) -> FlyingGroup[Any]: - # TODO: Delayed runway starts should be converted to air starts for multiplayer. - # Runway starts do not work with late activated aircraft in multiplayer. Instead - # of spawning on the runway the aircraft will spawn on the taxiway, potentially - # somewhere that they don't fit anyway. We should either upgrade these to air - # starts or (less likely) downgrade to warm starts to avoid the issue when the - # player is generating the mission for multiplayer (which would need a new - # option). - self.flight.unit_type.dcs_unit_type.load_payloads() - return self.mission.flight_group_from_airport( - country=self.country, - name=name, - aircraft_type=self.flight.unit_type.dcs_unit_type, - airport=airfield.airport, - maintask=None, - start_type=self._start_type_at_airfield(airfield), - group_size=self.flight.count, - parking_slots=None, - ) - - def _generate_over_departure( - self, name: str, origin: ControlPoint - ) -> FlyingGroup[Any]: - at = origin.position - - alt_type = "RADIO" - if isinstance(origin, OffMapSpawn): - alt = self.flight.flight_plan.waypoints[0].alt - alt_type = self.flight.flight_plan.waypoints[0].alt_type - elif self.flight.unit_type.helicopter: - alt = WARM_START_HELI_ALT - else: - if origin.id not in self.mission_data.cp_stack: - min_alt = MINIMUM_MID_MISSION_SPAWN_ALTITUDE_AGL - self.mission_data.cp_stack[origin.id] = min_alt - alt = self.mission_data.cp_stack[origin.id] - self.mission_data.cp_stack[origin.id] += STACK_SEPARATION - - speed = GroundSpeed.for_flight(self.flight, alt) - pos = at + Vector2(random.randint(100, 1000), random.randint(100, 1000)) - - group = self.mission.flight_group( - country=self.country, - name=name, - aircraft_type=self.flight.unit_type.dcs_unit_type, - airport=None, - position=pos, - altitude=alt.meters, - speed=speed.kph, - maintask=None, - group_size=self.flight.count, - ) - - group.points[0].alt_type = alt_type - return group - - def _generate_at_group( - self, name: str, at: Union[ShipGroup, StaticGroup] - ) -> FlyingGroup[Any]: - return self.mission.flight_group_from_unit( - country=self.country, - name=name, - aircraft_type=self.flight.unit_type.dcs_unit_type, - pad_group=at, - maintask=None, - start_type=self._start_type_at_group(at), - group_size=self.flight.count, - ) - - def _generate_at_cp_helipad( - self, name: str, cp: ControlPoint - ) -> Optional[FlyingGroup[Any]]: - try: - helipad = self.helipads[cp].pop() - except IndexError as ex: - logging.warning("Not enough helipads available at " + str(ex)) - if isinstance(cp, Airfield): - return self._generate_at_airfield(name, cp) - else: - return None - # raise RuntimeError(f"Not enough helipads available at {cp}") from ex - - group = self._generate_at_group(name, helipad) - - # Note : A bit dirty, need better support in pydcs - group.points[0].action = PointAction.FromGroundArea - group.points[0].type = "TakeOffGround" - group.units[0].heading = helipad.units[0].heading - if self.start_type is not StartType.COLD: - group.points[0].action = PointAction.FromGroundAreaHot - group.points[0].type = "TakeOffGroundHot" - - wpt = group.waypoint("LANDING") - if wpt: - hpad = self.helipads[self.flight.arrival].pop(0) - wpt.helipad_id = hpad.units[0].id - wpt.link_unit = hpad.units[0].id - self.helipads[self.flight.arrival].append(hpad) - - for i in range(self.flight.count - 1): - try: - helipad = self.helipads[cp].pop() - terrain = cp.coalition.game.theater.terrain - group.units[1 + i].position = Point( - helipad.x, helipad.y, terrain=terrain - ) - group.units[1 + i].heading = helipad.units[0].heading - except IndexError as ex: - logging.warning("Not enough helipads available at " + str(ex)) - if isinstance(cp, Airfield): - return self._generate_at_airfield(name, cp) - else: - if isinstance(group, HelicopterGroup): - self.country.helicopter_group.remove(group) - elif isinstance(group, PlaneGroup): - self.country.plane_group.remove(group) - return None - return group - - def _generate_at_cp_ground_spawn( - self, name: str, cp: ControlPoint - ) -> Optional[FlyingGroup[Any]]: - try: - if len(self.ground_spawns_roadbase[cp]) > 0: - ground_spawn = self.ground_spawns_roadbase[cp].pop() - else: - ground_spawn = self.ground_spawns[cp].pop() - except IndexError as ex: - logging.warning("Not enough STOL slots available at " + str(ex)) - return None - # raise RuntimeError(f"Not enough STOL slots available at {cp}") from ex - - group = self._generate_at_group(name, ground_spawn[0]) - - # Note : A bit dirty, need better support in pydcs - group.points[0].action = PointAction.FromGroundArea - group.points[0].type = "TakeOffGround" - group.units[0].heading = ground_spawn[0].units[0].heading - - try: - cp.coalition.game.scenery_clear_zones - except AttributeError: - cp.coalition.game.scenery_clear_zones = [] - cp.coalition.game.scenery_clear_zones.append(ground_spawn[1]) - - for i in range(self.flight.count - 1): - try: - terrain = cp.coalition.game.theater.terrain - if len(self.ground_spawns_roadbase[cp]) > 0: - ground_spawn = self.ground_spawns_roadbase[cp].pop() - else: - ground_spawn = self.ground_spawns[cp].pop() - group.units[1 + i].position = Point( - ground_spawn[0].x, ground_spawn[0].y, terrain=terrain - ) - group.units[1 + i].heading = ground_spawn[0].units[0].heading - except IndexError as ex: - raise RuntimeError(f"Not enough STOL slots available at {cp}") from ex - return group - - def dcs_start_type(self) -> DcsStartType: - if self.start_type is StartType.RUNWAY: - return DcsStartType.Runway - elif self.start_type is StartType.COLD: - return DcsStartType.Cold - elif self.start_type is StartType.WARM: - return DcsStartType.Warm - raise ValueError(f"There is no pydcs StartType matching {self.start_type}") - - def _start_type_at_airfield( - self, - airfield: Airfield, - ) -> DcsStartType: - return self.dcs_start_type() - - def _start_type_at_group( - self, - at: Union[ShipGroup, StaticGroup], - ) -> DcsStartType: - group_units = at.units - # Setting Su-33s starting from the non-supercarrier Kuznetsov to take off from - # runway to work around a DCS AI issue preventing Su-33s from taking off when - # set to "Takeoff from ramp" (#1352) - # Also setting the F-14A AI variant to start from cats since they are reported - # to have severe pathfinding problems when doing ramp starts (#1927) - if self.flight.unit_type.dcs_unit_type == F_14A or ( - self.flight.unit_type.dcs_unit_type == Su_33 - and group_units[0] is not None - and group_units[0].type == KUZNECOW.id - ): - return DcsStartType.Runway - else: - return self.dcs_start_type() diff --git a/game/pretense/pretensetriggergenerator.py b/game/pretense/pretensetriggergenerator.py index 11932f87..e5ea05e9 100644 --- a/game/pretense/pretensetriggergenerator.py +++ b/game/pretense/pretensetriggergenerator.py @@ -50,6 +50,9 @@ TRIGGER_RADIUS_MEDIUM = 100000 TRIGGER_RADIUS_LARGE = 150000 TRIGGER_RADIUS_ALL_MAP = 3000000 TRIGGER_RADIUS_CLEAR_SCENERY = 1000 +TRIGGER_RADIUS_PRETENSE_TGO = 500 +TRIGGER_RADIUS_PRETENSE_SUPPLY = 500 +TRIGGER_RADIUS_PRETENSE_HELI = 1000 class Silence(Option): @@ -156,7 +159,7 @@ class PretenseTriggerGenerator: for cp in self.game.theater.controlpoints: if isinstance(cp, self.capture_zone_types) and not cp.is_fleet: - zone_color = {1: 0.0, 2: 0.0, 3: 0.0, 4: 0.149} + zone_color = {1: 0.0, 2: 0.0, 3: 0.0, 4: 0.15} trigger_zone = self.mission.triggers.add_triggerzone( cp.position, radius=TRIGGER_RADIUS_CAPTURE, @@ -164,6 +167,45 @@ class PretenseTriggerGenerator: name=cp.name, color=zone_color, ) + cp_name_trimmed = "".join([i for i in cp.name.lower() if i.isalnum()]) + tgo_num = 0 + for tgo in cp.ground_objects: + tgo_num += 1 + zone_color = {1: 1.0, 2: 1.0, 3: 1.0, 4: 0.15} + trigger_zone = self.mission.triggers.add_triggerzone( + tgo.position, + radius=TRIGGER_RADIUS_PRETENSE_TGO, + hidden=False, + name=f"{cp_name_trimmed}-{tgo_num}", + color=zone_color, + ) + 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( + position=helipad, + radius=TRIGGER_RADIUS_PRETENSE_HELI, + hidden=False, + name=f"{cp_name_trimmed}-hsp", + color=zone_color, + ) + break + for supply_route in cp.convoy_routes.values(): + tgo_num += 1 + zone_color = {1: 1.0, 2: 1.0, 3: 1.0, 4: 0.15} + origin_position = supply_route[0] + next_position = supply_route[1] + convoy_heading = origin_position.heading_between_point(next_position) + supply_position = origin_position.point_from_heading( + convoy_heading, 300 + ) + trigger_zone = self.mission.triggers.add_triggerzone( + supply_position, + radius=TRIGGER_RADIUS_PRETENSE_TGO, + hidden=False, + name=f"{cp_name_trimmed}-sp", + color=zone_color, + ) + break def generate(self) -> None: player_coalition = "blue" From 86e32b1990aae3a9b8129edd759f104e8071d39e Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Sat, 9 Sep 2023 23:02:30 +0300 Subject: [PATCH 005/243] Copied tgogenerator.py as a template/inheritance for generating Pretense campaigns from Retribution campaigns. --- game/pretense/pretensetgogenerator.py | 1182 +++++++++++++++++++++++++ 1 file changed, 1182 insertions(+) create mode 100644 game/pretense/pretensetgogenerator.py diff --git a/game/pretense/pretensetgogenerator.py b/game/pretense/pretensetgogenerator.py new file mode 100644 index 00000000..326a1ded --- /dev/null +++ b/game/pretense/pretensetgogenerator.py @@ -0,0 +1,1182 @@ +"""Generators for creating the groups for ground objectives. + +The classes in this file are responsible for creating the vehicle groups, ship +groups, statics, missile sites, and AA sites for the mission. Each of these +objectives is defined in the Theater by a TheaterGroundObject. These classes +create the pydcs groups and statics for those areas and add them to the mission. +""" +from __future__ import annotations + +import logging +import random +from collections import defaultdict +from typing import Any, Dict, Iterator, List, Optional, TYPE_CHECKING, Type, Tuple + +import dcs.vehicles +from dcs import Mission, Point, unitgroup +from dcs.action import DoScript, SceneryDestructionZone +from dcs.condition import MapObjectIsDead +from dcs.countries import * +from dcs.country import Country +from dcs.point import StaticPoint, PointAction +from dcs.ships import ( + CVN_71, + CVN_72, + CVN_73, + CVN_75, + Stennis, + Forrestal, + LHA_Tarawa, +) +from dcs.statics import Fortification +from dcs.task import ( + ActivateBeaconCommand, + ActivateICLSCommand, + ActivateLink4Command, + ActivateACLSCommand, + EPLRS, + FireAtPoint, + OptAlarmState, +) +from dcs.translation import String +from dcs.triggers import Event, TriggerOnce, TriggerStart, TriggerZone +from dcs.unit import Unit, InvisibleFARP, BaseFARP, SingleHeliPad, FARP +from dcs.unitgroup import MovingGroup, ShipGroup, StaticGroup, VehicleGroup +from dcs.unittype import ShipType, VehicleType +from dcs.vehicles import vehicle_map, Unarmed + +from game.missiongenerator.groundforcepainter import ( + NavalForcePainter, + GroundForcePainter, +) +from game.missiongenerator.missiondata import CarrierInfo, MissionData +from game.point_with_heading import PointWithHeading +from game.radio.RadioFrequencyContainer import RadioFrequencyContainer +from game.radio.radios import RadioFrequency, RadioRegistry +from game.radio.tacan import TacanBand, TacanChannel, TacanRegistry, TacanUsage +from game.runways import RunwayData +from game.theater import ( + ControlPoint, + TheaterGroundObject, + TheaterUnit, + NavalControlPoint, +) +from game.theater.theatergroundobject import ( + CarrierGroundObject, + GenericCarrierGroundObject, + LhaGroundObject, + MissileSiteGroundObject, +) +from game.theater.theatergroup import SceneryUnit, IadsGroundGroup +from game.unitmap import UnitMap +from game.utils import Heading, feet, knots, mps + +if TYPE_CHECKING: + from game import Game + +FARP_FRONTLINE_DISTANCE = 10000 +AA_CP_MIN_DISTANCE = 40000 + + +def farp_truck_types_for_country( + country_id: int, +) -> Tuple[Type[VehicleType], Type[VehicleType]]: + soviet_tankers: List[Type[VehicleType]] = [ + Unarmed.ATMZ_5, + Unarmed.ATZ_10, + Unarmed.ATZ_5, + Unarmed.ATZ_60_Maz, + Unarmed.TZ_22_KrAZ, + ] + soviet_trucks: List[Type[VehicleType]] = [ + Unarmed.S_75_ZIL, + Unarmed.GAZ_3308, + Unarmed.GAZ_66, + Unarmed.KAMAZ_Truck, + Unarmed.KrAZ6322, + Unarmed.Ural_375, + Unarmed.Ural_375_PBU, + Unarmed.Ural_4320_31, + Unarmed.Ural_4320T, + Unarmed.ZIL_135, + ] + + axis_trucks: List[Type[VehicleType]] = [Unarmed.Blitz_36_6700A] + + us_tankers: List[Type[VehicleType]] = [Unarmed.M978_HEMTT_Tanker] + us_trucks: List[Type[VehicleType]] = [Unarmed.M_818] + uk_trucks: List[Type[VehicleType]] = [Unarmed.Bedford_MWD] + + if country_id in [ + Abkhazia.id, + Algeria.id, + Bahrain.id, + Belarus.id, + Belgium.id, + Bulgaria.id, + China.id, + Croatia.id, + Cuba.id, + Cyprus.id, + CzechRepublic.id, + Egypt.id, + Ethiopia.id, + Finland.id, + GDR.id, + Georgia.id, + Ghana.id, + Greece.id, + Hungary.id, + India.id, + Insurgents.id, + Iraq.id, + Jordan.id, + Kazakhstan.id, + Lebanon.id, + Libya.id, + Morocco.id, + Nigeria.id, + NorthKorea.id, + Poland.id, + Romania.id, + Russia.id, + Serbia.id, + Slovakia.id, + Slovenia.id, + SouthAfrica.id, + SouthOssetia.id, + Sudan.id, + Syria.id, + Tunisia.id, + USSR.id, + Ukraine.id, + Venezuela.id, + Vietnam.id, + Yemen.id, + Yugoslavia.id, + ]: + tanker_type = random.choice(soviet_tankers) + ammo_truck_type = random.choice(soviet_trucks) + elif country_id in [ItalianSocialRepublic.id, ThirdReich.id]: + tanker_type = random.choice(soviet_tankers) + ammo_truck_type = random.choice(axis_trucks) + elif country_id in [ + Argentina.id, + Australia.id, + Austria.id, + Bolivia.id, + Brazil.id, + Canada.id, + Chile.id, + Denmark.id, + Ecuador.id, + France.id, + Germany.id, + Honduras.id, + Indonesia.id, + Iran.id, + Israel.id, + Italy.id, + Japan.id, + Kuwait.id, + Malaysia.id, + Mexico.id, + Norway.id, + Oman.id, + Pakistan.id, + Peru.id, + Philippines.id, + Portugal.id, + Qatar.id, + SaudiArabia.id, + SouthKorea.id, + Spain.id, + Sweden.id, + Switzerland.id, + Thailand.id, + TheNetherlands.id, + Turkey.id, + USA.id, + USAFAggressors.id, + UnitedArabEmirates.id, + ]: + tanker_type = random.choice(us_tankers) + ammo_truck_type = random.choice(us_trucks) + elif country_id in [UK.id]: + tanker_type = random.choice(us_tankers) + ammo_truck_type = random.choice(uk_trucks) + elif country_id in [CombinedJointTaskForcesBlue.id]: + tanker_types = us_tankers + truck_types = us_trucks + uk_trucks + + tanker_type = random.choice(tanker_types) + ammo_truck_type = random.choice(truck_types) + elif country_id in [CombinedJointTaskForcesRed.id]: + tanker_types = us_tankers + truck_types = us_trucks + uk_trucks + + tanker_type = random.choice(tanker_types) + ammo_truck_type = random.choice(truck_types) + elif country_id in [UnitedNationsPeacekeepers.id]: + tanker_types = soviet_tankers + us_tankers + truck_types = soviet_trucks + us_trucks + uk_trucks + + tanker_type = random.choice(tanker_types) + ammo_truck_type = random.choice(truck_types) + else: + tanker_types = soviet_tankers + us_tankers + truck_types = soviet_trucks + us_trucks + uk_trucks + axis_trucks + + tanker_type = random.choice(tanker_types) + ammo_truck_type = random.choice(truck_types) + + return tanker_type, ammo_truck_type + + +class GroundObjectGenerator: + """generates the DCS groups and units from the TheaterGroundObject""" + + def __init__( + self, + ground_object: TheaterGroundObject, + country: Country, + game: Game, + mission: Mission, + unit_map: UnitMap, + ) -> None: + self.ground_object = ground_object + self.country = country + self.game = game + self.m = mission + self.unit_map = unit_map + + @property + def culled(self) -> bool: + return self.game.iads_considerate_culling(self.ground_object) + + def generate(self) -> None: + if self.culled: + return + for group in self.ground_object.groups: + vehicle_units = [] + ship_units = [] + # Split the different unit types to be compliant to dcs limitation + for unit in group.units: + if unit.is_static: + if isinstance(unit, SceneryUnit): + # Special handling for scenery objects + self.add_trigger_zone_for_scenery(unit) + if ( + self.game.settings.plugin_option("skynetiads") + and isinstance(group, IadsGroundGroup) + and group.iads_role.participate + ): + # Generate a unit which can be controlled by skynet + self.generate_iads_command_unit(unit) + else: + # Create a static group for each static unit + self.create_static_group(unit) + elif unit.is_vehicle and unit.alive: + # All alive Vehicles + vehicle_units.append(unit) + elif unit.is_ship and unit.alive: + # All alive Ships + ship_units.append(unit) + if vehicle_units: + self.create_vehicle_group(group.group_name, vehicle_units) + if ship_units: + self.create_ship_group(group.group_name, ship_units) + + def create_vehicle_group( + self, group_name: str, units: list[TheaterUnit] + ) -> VehicleGroup: + vehicle_group: Optional[VehicleGroup] = None + for unit in units: + assert issubclass(unit.type, VehicleType) + faction = unit.ground_object.control_point.coalition.faction + if vehicle_group is None: + vehicle_group = self.m.vehicle_group( + self.country, + group_name, + unit.type, + position=unit.position, + heading=unit.position.heading.degrees, + ) + vehicle_group.units[0].player_can_drive = True + self.enable_eplrs(vehicle_group, unit.type) + vehicle_group.units[0].name = unit.unit_name + self.set_alarm_state(vehicle_group) + GroundForcePainter(faction, vehicle_group.units[0]).apply_livery() + else: + vehicle_unit = self.m.vehicle(unit.unit_name, unit.type) + vehicle_unit.player_can_drive = True + vehicle_unit.position = unit.position + vehicle_unit.heading = unit.position.heading.degrees + GroundForcePainter(faction, vehicle_unit).apply_livery() + vehicle_group.add_unit(vehicle_unit) + self._register_theater_unit(unit, vehicle_group.units[-1]) + if vehicle_group is None: + raise RuntimeError(f"Error creating VehicleGroup for {group_name}") + return vehicle_group + + def create_ship_group( + self, + group_name: str, + units: list[TheaterUnit], + frequency: Optional[RadioFrequency] = None, + ) -> ShipGroup: + ship_group: Optional[ShipGroup] = None + for unit in units: + assert issubclass(unit.type, ShipType) + faction = unit.ground_object.control_point.coalition.faction + if ship_group is None: + ship_group = self.m.ship_group( + self.country, + group_name, + unit.type, + position=unit.position, + heading=unit.position.heading.degrees, + ) + if frequency: + ship_group.set_frequency(frequency.hertz) + ship_group.units[0].name = unit.unit_name + self.set_alarm_state(ship_group) + NavalForcePainter(faction, ship_group.units[0]).apply_livery() + else: + ship_unit = self.m.ship(unit.unit_name, unit.type) + if frequency: + ship_unit.set_frequency(frequency.hertz) + ship_unit.position = unit.position + ship_unit.heading = unit.position.heading.degrees + NavalForcePainter(faction, ship_unit).apply_livery() + ship_group.add_unit(ship_unit) + self._register_theater_unit(unit, ship_group.units[-1]) + if ship_group is None: + raise RuntimeError(f"Error creating ShipGroup for {group_name}") + return ship_group + + def create_static_group(self, unit: TheaterUnit) -> None: + static_group = self.m.static_group( + country=self.country, + name=unit.unit_name, + _type=unit.type, + position=unit.position, + heading=unit.position.heading.degrees, + dead=not unit.alive, + ) + self._register_theater_unit(unit, static_group.units[0]) + + @staticmethod + def enable_eplrs(group: VehicleGroup, unit_type: Type[VehicleType]) -> None: + if unit_type.eplrs: + group.points[0].tasks.append(EPLRS(group.id)) + + def set_alarm_state(self, group: MovingGroup[Any]) -> None: + if self.game.settings.perf_red_alert_state: + group.points[0].tasks.append(OptAlarmState(2)) + else: + group.points[0].tasks.append(OptAlarmState(1)) + + def _register_theater_unit( + self, + theater_unit: TheaterUnit, + dcs_unit: Unit, + ) -> None: + self.unit_map.add_theater_unit_mapping(theater_unit, dcs_unit) + + def add_trigger_zone_for_scenery(self, scenery: SceneryUnit) -> None: + # Align the trigger zones to the faction color on the DCS briefing/F10 map. + color = ( + {1: 0.2, 2: 0.7, 3: 1, 4: 0.15} + if scenery.ground_object.is_friendly(to_player=True) + else {1: 1, 2: 0.2, 3: 0.2, 4: 0.15} + ) + + # Create the smallest valid size trigger zone (16 feet) so that risk of overlap + # is minimized. As long as the triggerzone is over the scenery object, we're ok. + smallest_valid_radius = feet(16).meters + + trigger_zone = self.m.triggers.add_triggerzone( + scenery.zone.position, + smallest_valid_radius, + scenery.zone.hidden, + scenery.zone.name, + color, + scenery.zone.properties, + ) + # DCS only visually shows a scenery object is dead when + # this trigger rule is applied. Otherwise you can kill a + # structure twice. + if not scenery.alive: + self.generate_destruction_trigger_rule(trigger_zone) + else: + self.generate_on_dead_trigger_rule(trigger_zone) + + self.unit_map.add_scenery(scenery, trigger_zone) + + def generate_destruction_trigger_rule(self, trigger_zone: TriggerZone) -> None: + # Add destruction zone trigger + t = TriggerStart(comment="Destruction") + t.actions.append( + SceneryDestructionZone(destruction_level=100, zone=trigger_zone.id) + ) + 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'dead_events[#dead_events + 1] = "{trigger_zone.name}"') + t.actions.append(DoScript(script_string)) + self.m.triggerrules.triggers.append(t) + + def generate_iads_command_unit(self, unit: SceneryUnit) -> None: + # Creates a static Infantry Unit next to a scenery object. This is needed + # because skynet can not use map objects as Comms, Power or Command and needs a + # "real" unit to function correctly + self.m.static_group( + country=self.country, + name=unit.unit_name, + _type=dcs.vehicles.Infantry.Soldier_M4, + position=unit.position, + heading=unit.position.heading.degrees, + dead=not unit.alive, # Also spawn as dead! + ) + + +class MissileSiteGenerator(GroundObjectGenerator): + @property + def culled(self) -> bool: + # Don't cull missile sites - their range is long enough to make them easily + # culled despite being a threat. + return False + + def generate(self) -> None: + super(MissileSiteGenerator, self).generate() + + if not self.game.settings.generate_fire_tasks_for_missile_sites: + return + + # Note : Only the SCUD missiles group can fire (V1 site cannot fire in game right now) + # TODO : Should be pre-planned ? + # TODO : Add delay to task to spread fire task over mission duration ? + for group in self.ground_object.groups: + vg = self.m.find_group(group.group_name) + if vg is not None: + targets = self.possible_missile_targets() + if targets: + target = random.choice(targets) + real_target = target.point_from_heading( + Heading.random().degrees, random.randint(0, 2500) + ) + vg.points[0].add_task(FireAtPoint(real_target)) + logging.info("Set up fire task for missile group.") + else: + logging.info( + "Couldn't setup missile site to fire, no valid target in range." + ) + else: + logging.info( + "Couldn't setup missile site to fire, group was not generated." + ) + + def possible_missile_targets(self) -> List[Point]: + """ + Find enemy control points in range + :return: List of possible missile targets + """ + targets: List[Point] = [] + for cp in self.game.theater.controlpoints: + if cp.captured != self.ground_object.control_point.captured: + distance = cp.position.distance_to_point(self.ground_object.position) + if distance < self.missile_site_range: + targets.append(cp.position) + return targets + + @property + def missile_site_range(self) -> int: + """ + Get the missile site range + :return: Missile site range + """ + site_range = 0 + for group in self.ground_object.groups: + vg = self.m.find_group(group.group_name) + if vg is not None: + for u in vg.units: + if u.type in vehicle_map: + if vehicle_map[u.type].threat_range > site_range: + site_range = vehicle_map[u.type].threat_range + return site_range + + +class GenericCarrierGenerator(GroundObjectGenerator): + """Base type for carrier group generation. + + Used by both CV(N) groups and LHA groups. + """ + + def __init__( + self, + ground_object: GenericCarrierGroundObject, + control_point: NavalControlPoint, + country: Country, + game: Game, + mission: Mission, + radio_registry: RadioRegistry, + tacan_registry: TacanRegistry, + icls_alloc: Iterator[int], + runways: Dict[str, RunwayData], + unit_map: UnitMap, + mission_data: MissionData, + ) -> None: + super().__init__(ground_object, country, game, mission, unit_map) + self.ground_object = ground_object + self.control_point = control_point + self.radio_registry = radio_registry + self.tacan_registry = tacan_registry + self.icls_alloc = icls_alloc + self.runways = runways + self.mission_data = mission_data + + def generate(self) -> None: + if self.control_point.frequency is not None: + atc = self.control_point.frequency + if atc not in self.radio_registry.allocated_channels: + self.radio_registry.reserve(atc) + else: + atc = self.radio_registry.alloc_uhf() + + for g_id, group in enumerate(self.ground_object.groups): + if not group.units: + logging.warning(f"Found empty carrier group in {self.control_point}") + continue + + ship_units = [] + for unit in group.units: + if unit.alive: + # All alive Ships + ship_units.append(unit) + + if not ship_units: + # Empty array (no alive units), skip this group + continue + + ship_group = self.create_ship_group(group.group_name, ship_units, atc) + + # Always steam into the wind, even if the carrier is being moved. + # There are multiple unsimulated hours between turns, so we can + # count those as the time the carrier uses to move and the mission + # time as the recovery window. + brc = self.steam_into_wind(ship_group) + + # Set Carrier Specific Options + if g_id == 0 and self.control_point.runway_is_operational(): + # Get Correct unit type for the carrier. + # This will upgrade to super carrier if option is enabled + carrier_type = self.carrier_type + if carrier_type is None: + raise RuntimeError( + f"Error generating carrier group for {self.control_point.name}" + ) + ship_group.units[0].type = carrier_type.id + if self.control_point.tacan is None: + tacan = self.tacan_registry.alloc_for_band( + TacanBand.X, TacanUsage.TransmitReceive + ) + else: + tacan = self.control_point.tacan + if self.control_point.tcn_name is None: + tacan_callsign = self.tacan_callsign() + else: + tacan_callsign = self.control_point.tcn_name + link4 = None + link4carriers = [Stennis, CVN_71, CVN_72, CVN_73, CVN_75, Forrestal] + if carrier_type in link4carriers: + if self.control_point.link4 is None: + link4 = self.radio_registry.alloc_uhf() + else: + link4 = self.control_point.link4 + icls = None + icls_name = self.control_point.icls_name + if carrier_type in link4carriers or carrier_type == LHA_Tarawa: + if self.control_point.icls_channel is None: + icls = next(self.icls_alloc) + else: + icls = self.control_point.icls_channel + self.activate_beacons( + ship_group, tacan, tacan_callsign, icls, icls_name, link4 + ) + self.add_runway_data( + brc or Heading.from_degrees(0), atc, tacan, tacan_callsign, icls + ) + self.mission_data.carriers.append( + CarrierInfo( + group_name=ship_group.name, + unit_name=ship_group.units[0].name, + callsign=tacan_callsign, + freq=atc, + tacan=tacan, + blue=self.control_point.captured, + ) + ) + + @property + def carrier_type(self) -> Optional[Type[ShipType]]: + return self.control_point.get_carrier_group_type() + + def steam_into_wind(self, group: ShipGroup) -> Optional[Heading]: + wind = self.game.conditions.weather.wind.at_0m + brc = Heading.from_degrees(wind.direction).opposite + # Aim for 25kts over the deck. + carrier_speed = knots(25) - mps(wind.speed) + for attempt in range(5): + point = group.points[0].position.point_from_heading( + brc.degrees, 100000 - attempt * 20000 + ) + if self.game.theater.is_in_sea(point): + group.points[0].speed = carrier_speed.meters_per_second + group.add_waypoint(point, carrier_speed.kph) + # Rotate the whole ground object to the new course + self.ground_object.rotate(brc) + return brc + return None + + def tacan_callsign(self) -> str: + raise NotImplementedError + + @staticmethod + def activate_beacons( + group: ShipGroup, + tacan: TacanChannel, + callsign: str, + icls: Optional[int] = None, + icls_name: Optional[str] = None, + link4: Optional[RadioFrequency] = None, + ) -> None: + group.points[0].tasks.append( + ActivateBeaconCommand( + channel=tacan.number, + modechannel=tacan.band.value, + callsign=callsign, + unit_id=group.units[0].id, + aa=False, + ) + ) + if icls is not None: + icls_name = "" if icls_name is None else icls_name + group.points[0].tasks.append( + ActivateICLSCommand(icls, group.units[0].id, icls_name) + ) + if link4 is not None: + group.points[0].tasks.append( + ActivateLink4Command(link4.hertz, group.units[0].id) + ) + group.points[0].tasks.append(ActivateACLSCommand(unit_id=group.units[0].id)) + + def add_runway_data( + self, + brc: Heading, + atc: RadioFrequency, + tacan: TacanChannel, + callsign: str, + icls: Optional[int], + ) -> None: + # This relies on one control point mapping exactly + # to one LHA, carrier, or other usable "runway". + # This isn't wholly true, since the DD escorts of + # the carrier group are valid for helicopters, but + # they aren't exposed as such to the game. Should + # clean this up so that's possible. We can't use the + # unit name since it's an arbitrary ID. + self.runways[self.control_point.full_name] = RunwayData( + self.control_point.name, + brc, + f"{brc.degrees:03}", + atc=atc, + tacan=tacan, + tacan_callsign=callsign, + icls=icls, + ) + + +class CarrierGenerator(GenericCarrierGenerator): + """Generator for CV(N) groups.""" + + def tacan_callsign(self) -> str: + # TODO: Assign these properly. + return random.choice( + [ + "STE", + "CVN", + "CVH", + "CCV", + "ACC", + "ARC", + "GER", + "ABR", + "LIN", + "TRU", + ] + ) + + +class LhaGenerator(GenericCarrierGenerator): + """Generator for LHA groups.""" + + def tacan_callsign(self) -> str: + # TODO: Assign these properly. + return random.choice( + [ + "LHD", + "LHA", + "LHB", + "LHC", + "LHD", + "LDS", + ] + ) + + +class HelipadGenerator: + """ + Generates helipads for given control point + """ + + def __init__( + self, + mission: Mission, + cp: ControlPoint, + game: Game, + radio_registry: RadioRegistry, + tacan_registry: TacanRegistry, + ): + self.m = mission + self.cp = cp + self.game = game + self.radio_registry = radio_registry + self.tacan_registry = tacan_registry + self.helipads: list[StaticGroup] = [] + + def create_helipad( + self, i: int, helipad: PointWithHeading, helipad_type: str + ) -> None: + # Note: Helipad are generated as neutral object in order not to interfere with + # capture triggers + pad: BaseFARP + neutral_country = self.m.country(self.game.neutral_country.name) + country = self.m.country( + self.game.coalition_for(self.cp.captured).faction.country.name + ) + + name = f"{self.cp.name} {helipad_type} {i}" + logging.info("Generating helipad static : " + name) + terrain = self.m.terrain + if helipad_type == "SINGLE_HELIPAD": + pad = SingleHeliPad( + unit_id=self.m.next_unit_id(), name=name, terrain=terrain + ) + number_of_pads = 1 + elif helipad_type == "FARP": + pad = FARP(unit_id=self.m.next_unit_id(), name=name, terrain=terrain) + number_of_pads = 4 + else: + pad = InvisibleFARP( + unit_id=self.m.next_unit_id(), name=name, terrain=terrain + ) + number_of_pads = 1 + pad.position = Point(helipad.x, helipad.y, terrain=terrain) + pad.heading = helipad.heading.degrees + + # Set FREQ + if isinstance(self.cp, RadioFrequencyContainer) and self.cp.frequency: + if isinstance(pad, BaseFARP): + pad.heliport_frequency = self.cp.frequency.mhz + + sg = unitgroup.StaticGroup(self.m.next_group_id(), name) + sg.add_unit(pad) + sp = StaticPoint(pad.position) + sg.add_point(sp) + neutral_country.add_static_group(sg) + + if number_of_pads > 1: + self.append_helipad(pad, name, helipad.heading.degrees, 60, 0, 0) + self.append_helipad(pad, name, helipad.heading.degrees + 180, 20, 0, 0) + self.append_helipad( + pad, name, helipad.heading.degrees + 90, 60, helipad.heading.degrees, 20 + ) + self.append_helipad( + pad, + name, + helipad.heading.degrees + 90, + 60, + helipad.heading.degrees + 180, + 60, + ) + else: + self.helipads.append(sg) + + # Generate a FARP Ammo and Fuel stack for each pad + self.m.static_group( + country=country, + name=(name + "_fuel"), + _type=Fortification.FARP_Fuel_Depot, + position=pad.position.point_from_heading(helipad.heading.degrees, 35), + heading=pad.heading + 180, + ) + self.m.static_group( + country=country, + name=(name + "_ammo"), + _type=Fortification.FARP_Ammo_Dump_Coating, + position=pad.position.point_from_heading( + helipad.heading.degrees, 35 + ).point_from_heading(helipad.heading.degrees + 90, 10), + heading=pad.heading + 90, + ) + self.m.static_group( + country=country, + name=(name + "_ws"), + _type=Fortification.Windsock, + position=helipad.point_from_heading(helipad.heading.degrees + 45, 35), + heading=pad.heading, + ) + + def append_helipad( + self, + pad: BaseFARP, + name: str, + heading_1: int, + distance_1: int, + heading_2: int, + distance_2: int, + ) -> None: + new_pad = InvisibleFARP(pad._terrain) + new_pad.position = pad.position.point_from_heading(heading_1, distance_1) + new_pad.position = new_pad.position.point_from_heading(heading_2, distance_2) + sg = unitgroup.StaticGroup(self.m.next_group_id(), name) + sg.add_unit(new_pad) + self.helipads.append(sg) + + def generate(self) -> None: + for i, helipad in enumerate(self.cp.helipads): + self.create_helipad(i, helipad, "SINGLE_HELIPAD") + for i, helipad in enumerate(self.cp.helipads_quad): + self.create_helipad(i, helipad, "FARP") + for i, helipad in enumerate(self.cp.helipads_invisible): + self.create_helipad(i, helipad, "Invisible FARP") + + +class GroundSpawnRoadbaseGenerator: + """ + Generates Highway strip starting positions for given control point + """ + + def __init__( + self, + mission: Mission, + cp: ControlPoint, + game: Game, + radio_registry: RadioRegistry, + tacan_registry: TacanRegistry, + ): + self.m = mission + self.cp = cp + self.game = game + self.radio_registry = radio_registry + self.tacan_registry = tacan_registry + self.ground_spawns_roadbase: list[Tuple[StaticGroup, Point]] = [] + + def create_ground_spawn_roadbase( + self, i: int, ground_spawn: Tuple[PointWithHeading, Point] + ) -> None: + # Note: FARPs are generated as neutral object in order not to interfere with + # capture triggers + neutral_country = self.m.country(self.game.neutral_country.name) + country = self.m.country( + self.game.coalition_for(self.cp.captured).faction.country.name + ) + terrain = self.cp.coalition.game.theater.terrain + + name = f"{self.cp.name} roadbase spawn {i}" + logging.info("Generating Roadbase Spawn static : " + name) + + pad = InvisibleFARP(unit_id=self.m.next_unit_id(), name=name, terrain=terrain) + + pad.position = Point(ground_spawn[0].x, ground_spawn[0].y, terrain=terrain) + pad.heading = ground_spawn[0].heading.degrees + sg = unitgroup.StaticGroup(self.m.next_group_id(), name) + sg.add_unit(pad) + sp = StaticPoint(pad.position) + sg.add_point(sp) + neutral_country.add_static_group(sg) + + self.ground_spawns_roadbase.append((sg, ground_spawn[1])) + + # tanker_type: Type[VehicleType] + # ammo_truck_type: Type[VehicleType] + + tanker_type, ammo_truck_type = farp_truck_types_for_country(country.id) + + # Generate ammo truck/farp and fuel truck/stack for each pad + if self.game.settings.ground_start_trucks_roadbase: + self.m.vehicle_group( + country=country, + name=(name + "_fuel"), + _type=tanker_type, + position=pad.position.point_from_heading( + ground_spawn[0].heading.degrees + 90, 35 + ), + group_size=1, + heading=pad.heading + 315, + move_formation=PointAction.OffRoad, + ) + self.m.vehicle_group( + country=country, + name=(name + "_ammo"), + _type=ammo_truck_type, + position=pad.position.point_from_heading( + ground_spawn[0].heading.degrees + 90, 35 + ).point_from_heading(ground_spawn[0].heading.degrees + 180, 10), + group_size=1, + heading=pad.heading + 315, + move_formation=PointAction.OffRoad, + ) + else: + self.m.static_group( + country=country, + name=(name + "_fuel"), + _type=Fortification.FARP_Fuel_Depot, + position=pad.position.point_from_heading( + ground_spawn[0].heading.degrees + 90, 35 + ), + heading=pad.heading + 270, + ) + self.m.static_group( + country=country, + name=(name + "_ammo"), + _type=Fortification.FARP_Ammo_Dump_Coating, + position=pad.position.point_from_heading( + ground_spawn[0].heading.degrees + 90, 35 + ).point_from_heading(ground_spawn[0].heading.degrees + 180, 10), + heading=pad.heading + 180, + ) + + def generate(self) -> None: + try: + for i, ground_spawn in enumerate(self.cp.ground_spawns_roadbase): + self.create_ground_spawn_roadbase(i, ground_spawn) + except AttributeError: + self.ground_spawns_roadbase = [] + + +class GroundSpawnGenerator: + """ + Generates STOL aircraft starting positions for given control point + """ + + def __init__( + self, + mission: Mission, + cp: ControlPoint, + game: Game, + radio_registry: RadioRegistry, + tacan_registry: TacanRegistry, + ): + self.m = mission + self.cp = cp + self.game = game + self.radio_registry = radio_registry + self.tacan_registry = tacan_registry + self.ground_spawns: list[Tuple[StaticGroup, Point]] = [] + + def create_ground_spawn( + self, i: int, vtol_pad: Tuple[PointWithHeading, Point] + ) -> None: + # Note: FARPs are generated as neutral object in order not to interfere with + # capture triggers + neutral_country = self.m.country(self.game.neutral_country.name) + country = self.m.country( + self.game.coalition_for(self.cp.captured).faction.country.name + ) + terrain = self.cp.coalition.game.theater.terrain + + name = f"{self.cp.name} ground spawn {i}" + logging.info("Generating Ground Spawn static : " + name) + + pad = InvisibleFARP(unit_id=self.m.next_unit_id(), name=name, terrain=terrain) + + pad.position = Point(vtol_pad[0].x, vtol_pad[0].y, terrain=terrain) + pad.heading = vtol_pad[0].heading.degrees + sg = unitgroup.StaticGroup(self.m.next_group_id(), name) + sg.add_unit(pad) + sp = StaticPoint(pad.position) + sg.add_point(sp) + neutral_country.add_static_group(sg) + + self.ground_spawns.append((sg, vtol_pad[1])) + + # tanker_type: Type[VehicleType] + # ammo_truck_type: Type[VehicleType] + + tanker_type, ammo_truck_type = farp_truck_types_for_country(country.id) + + # Generate a FARP Ammo and Fuel stack for each pad + if self.game.settings.ground_start_trucks: + self.m.vehicle_group( + country=country, + name=(name + "_fuel"), + _type=tanker_type, + position=pad.position.point_from_heading( + vtol_pad[0].heading.degrees - 175, 35 + ), + group_size=1, + heading=pad.heading + 45, + move_formation=PointAction.OffRoad, + ) + self.m.vehicle_group( + country=country, + name=(name + "_ammo"), + _type=ammo_truck_type, + position=pad.position.point_from_heading( + vtol_pad[0].heading.degrees - 185, 35 + ), + group_size=1, + heading=pad.heading + 45, + move_formation=PointAction.OffRoad, + ) + else: + self.m.static_group( + country=country, + name=(name + "_fuel"), + _type=Fortification.FARP_Fuel_Depot, + position=pad.position.point_from_heading( + vtol_pad[0].heading.degrees - 180, 45 + ), + heading=pad.heading, + ) + self.m.static_group( + country=country, + name=(name + "_ammo"), + _type=Fortification.FARP_Ammo_Dump_Coating, + position=pad.position.point_from_heading( + vtol_pad[0].heading.degrees - 180, 35 + ), + heading=pad.heading + 270, + ) + + def generate(self) -> None: + try: + for i, vtol_pad in enumerate(self.cp.ground_spawns): + self.create_ground_spawn(i, vtol_pad) + except AttributeError: + self.ground_spawns = [] + + +class TgoGenerator: + """Creates DCS groups and statics for the theater during mission generation. + + Most of the work of group/static generation is delegated to the other + generator classes. This class is responsible for finding each of the + locations for spawning ground objects, determining their types, and creating + the appropriate generators. + """ + + def __init__( + self, + mission: Mission, + game: Game, + radio_registry: RadioRegistry, + tacan_registry: TacanRegistry, + unit_map: UnitMap, + mission_data: MissionData, + ) -> None: + self.m = mission + self.game = game + self.radio_registry = radio_registry + self.tacan_registry = tacan_registry + self.unit_map = unit_map + self.icls_alloc = iter(range(1, 21)) + self.runways: Dict[str, RunwayData] = {} + self.helipads: dict[ControlPoint, list[StaticGroup]] = defaultdict(list) + self.ground_spawns_roadbase: dict[ + ControlPoint, list[Tuple[StaticGroup, Point]] + ] = defaultdict(list) + self.ground_spawns: dict[ + ControlPoint, list[Tuple[StaticGroup, Point]] + ] = defaultdict(list) + self.mission_data = mission_data + + def generate(self) -> None: + for cp in self.game.theater.controlpoints: + country = self.m.country(cp.coalition.faction.country.name) + + # Generate helipads + helipad_gen = HelipadGenerator( + self.m, cp, self.game, self.radio_registry, self.tacan_registry + ) + helipad_gen.generate() + self.helipads[cp] = helipad_gen.helipads + + # Generate Highway Strip slots + ground_spawn_roadbase_gen = GroundSpawnRoadbaseGenerator( + self.m, cp, self.game, self.radio_registry, self.tacan_registry + ) + ground_spawn_roadbase_gen.generate() + self.ground_spawns_roadbase[ + cp + ] = ground_spawn_roadbase_gen.ground_spawns_roadbase + random.shuffle(self.ground_spawns_roadbase[cp]) + + # Generate STOL pads + ground_spawn_gen = GroundSpawnGenerator( + self.m, cp, self.game, self.radio_registry, self.tacan_registry + ) + ground_spawn_gen.generate() + self.ground_spawns[cp] = ground_spawn_gen.ground_spawns + random.shuffle(self.ground_spawns[cp]) + + for ground_object in cp.ground_objects: + generator: GroundObjectGenerator + if isinstance(ground_object, CarrierGroundObject) and isinstance( + cp, NavalControlPoint + ): + generator = CarrierGenerator( + ground_object, + cp, + country, + self.game, + self.m, + self.radio_registry, + self.tacan_registry, + self.icls_alloc, + self.runways, + self.unit_map, + self.mission_data, + ) + elif isinstance(ground_object, LhaGroundObject) and isinstance( + cp, NavalControlPoint + ): + generator = LhaGenerator( + ground_object, + cp, + country, + self.game, + self.m, + self.radio_registry, + self.tacan_registry, + self.icls_alloc, + self.runways, + self.unit_map, + self.mission_data, + ) + elif isinstance(ground_object, MissileSiteGroundObject): + generator = MissileSiteGenerator( + ground_object, country, self.game, self.m, self.unit_map + ) + else: + generator = GroundObjectGenerator( + ground_object, country, self.game, self.m, self.unit_map + ) + generator.generate() + self.mission_data.runways = list(self.runways.values()) From d09cb70948828e98f1c5412d64f5dd31b4de673a Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Sun, 10 Sep 2023 14:05:23 +0300 Subject: [PATCH 006/243] Will now generate ground units for the Pretense campaign. --- game/pretense/pretensemissiongenerator.py | 3 +- game/pretense/pretensetgogenerator.py | 1082 +++------------------ game/pretense/pretensetriggergenerator.py | 2 + 3 files changed, 159 insertions(+), 928 deletions(-) diff --git a/game/pretense/pretensemissiongenerator.py b/game/pretense/pretensemissiongenerator.py index 16478f27..b0b61ce2 100644 --- a/game/pretense/pretensemissiongenerator.py +++ b/game/pretense/pretensemissiongenerator.py @@ -38,6 +38,7 @@ from game.missiongenerator.lasercoderegistry import LaserCodeRegistry from game.missiongenerator.luagenerator import LuaGenerator from game.missiongenerator.missiondata import MissionData from game.missiongenerator.tgogenerator import TgoGenerator +from .pretensetgogenerator import PretenseTgoGenerator from .pretensetriggergenerator import PretenseTriggerGenerator from game.missiongenerator.visualsgenerator import VisualsGenerator from ..ato import Flight @@ -86,7 +87,7 @@ class PretenseMissionGenerator: EnvironmentGenerator(self.mission, self.game.conditions, self.time).generate() - tgo_generator = TgoGenerator( + tgo_generator = PretenseTgoGenerator( self.mission, self.game, self.radio_registry, diff --git a/game/pretense/pretensetgogenerator.py b/game/pretense/pretensetgogenerator.py index 326a1ded..5c4ef4e2 100644 --- a/game/pretense/pretensetgogenerator.py +++ b/game/pretense/pretensetgogenerator.py @@ -45,11 +45,23 @@ from dcs.unitgroup import MovingGroup, ShipGroup, StaticGroup, VehicleGroup from dcs.unittype import ShipType, VehicleType from dcs.vehicles import vehicle_map, Unarmed +from game.data.units import UnitClass +from game.dcs.groundunittype import GroundUnitType from game.missiongenerator.groundforcepainter import ( NavalForcePainter, GroundForcePainter, ) from game.missiongenerator.missiondata import CarrierInfo, MissionData +from game.missiongenerator.tgogenerator import ( + TgoGenerator, + HelipadGenerator, + GroundSpawnRoadbaseGenerator, + GroundSpawnGenerator, + GroundObjectGenerator, + CarrierGenerator, + LhaGenerator, + MissileSiteGenerator, +) from game.point_with_heading import PointWithHeading from game.radio.RadioFrequencyContainer import RadioFrequencyContainer from game.radio.radios import RadioFrequency, RadioRegistry @@ -67,7 +79,7 @@ from game.theater.theatergroundobject import ( LhaGroundObject, MissileSiteGroundObject, ) -from game.theater.theatergroup import SceneryUnit, IadsGroundGroup +from game.theater.theatergroup import SceneryUnit, IadsGroundGroup, TheaterGroup from game.unitmap import UnitMap from game.utils import Heading, feet, knots, mps @@ -76,164 +88,10 @@ if TYPE_CHECKING: FARP_FRONTLINE_DISTANCE = 10000 AA_CP_MIN_DISTANCE = 40000 +PRETENSE_GROUND_UNIT_GROUP_SIZE = 4 -def farp_truck_types_for_country( - country_id: int, -) -> Tuple[Type[VehicleType], Type[VehicleType]]: - soviet_tankers: List[Type[VehicleType]] = [ - Unarmed.ATMZ_5, - Unarmed.ATZ_10, - Unarmed.ATZ_5, - Unarmed.ATZ_60_Maz, - Unarmed.TZ_22_KrAZ, - ] - soviet_trucks: List[Type[VehicleType]] = [ - Unarmed.S_75_ZIL, - Unarmed.GAZ_3308, - Unarmed.GAZ_66, - Unarmed.KAMAZ_Truck, - Unarmed.KrAZ6322, - Unarmed.Ural_375, - Unarmed.Ural_375_PBU, - Unarmed.Ural_4320_31, - Unarmed.Ural_4320T, - Unarmed.ZIL_135, - ] - - axis_trucks: List[Type[VehicleType]] = [Unarmed.Blitz_36_6700A] - - us_tankers: List[Type[VehicleType]] = [Unarmed.M978_HEMTT_Tanker] - us_trucks: List[Type[VehicleType]] = [Unarmed.M_818] - uk_trucks: List[Type[VehicleType]] = [Unarmed.Bedford_MWD] - - if country_id in [ - Abkhazia.id, - Algeria.id, - Bahrain.id, - Belarus.id, - Belgium.id, - Bulgaria.id, - China.id, - Croatia.id, - Cuba.id, - Cyprus.id, - CzechRepublic.id, - Egypt.id, - Ethiopia.id, - Finland.id, - GDR.id, - Georgia.id, - Ghana.id, - Greece.id, - Hungary.id, - India.id, - Insurgents.id, - Iraq.id, - Jordan.id, - Kazakhstan.id, - Lebanon.id, - Libya.id, - Morocco.id, - Nigeria.id, - NorthKorea.id, - Poland.id, - Romania.id, - Russia.id, - Serbia.id, - Slovakia.id, - Slovenia.id, - SouthAfrica.id, - SouthOssetia.id, - Sudan.id, - Syria.id, - Tunisia.id, - USSR.id, - Ukraine.id, - Venezuela.id, - Vietnam.id, - Yemen.id, - Yugoslavia.id, - ]: - tanker_type = random.choice(soviet_tankers) - ammo_truck_type = random.choice(soviet_trucks) - elif country_id in [ItalianSocialRepublic.id, ThirdReich.id]: - tanker_type = random.choice(soviet_tankers) - ammo_truck_type = random.choice(axis_trucks) - elif country_id in [ - Argentina.id, - Australia.id, - Austria.id, - Bolivia.id, - Brazil.id, - Canada.id, - Chile.id, - Denmark.id, - Ecuador.id, - France.id, - Germany.id, - Honduras.id, - Indonesia.id, - Iran.id, - Israel.id, - Italy.id, - Japan.id, - Kuwait.id, - Malaysia.id, - Mexico.id, - Norway.id, - Oman.id, - Pakistan.id, - Peru.id, - Philippines.id, - Portugal.id, - Qatar.id, - SaudiArabia.id, - SouthKorea.id, - Spain.id, - Sweden.id, - Switzerland.id, - Thailand.id, - TheNetherlands.id, - Turkey.id, - USA.id, - USAFAggressors.id, - UnitedArabEmirates.id, - ]: - tanker_type = random.choice(us_tankers) - ammo_truck_type = random.choice(us_trucks) - elif country_id in [UK.id]: - tanker_type = random.choice(us_tankers) - ammo_truck_type = random.choice(uk_trucks) - elif country_id in [CombinedJointTaskForcesBlue.id]: - tanker_types = us_tankers - truck_types = us_trucks + uk_trucks - - tanker_type = random.choice(tanker_types) - ammo_truck_type = random.choice(truck_types) - elif country_id in [CombinedJointTaskForcesRed.id]: - tanker_types = us_tankers - truck_types = us_trucks + uk_trucks - - tanker_type = random.choice(tanker_types) - ammo_truck_type = random.choice(truck_types) - elif country_id in [UnitedNationsPeacekeepers.id]: - tanker_types = soviet_tankers + us_tankers - truck_types = soviet_trucks + us_trucks + uk_trucks - - tanker_type = random.choice(tanker_types) - ammo_truck_type = random.choice(truck_types) - else: - tanker_types = soviet_tankers + us_tankers - truck_types = soviet_trucks + us_trucks + uk_trucks + axis_trucks - - tanker_type = random.choice(tanker_types) - ammo_truck_type = random.choice(truck_types) - - return tanker_type, ammo_truck_type - - -class GroundObjectGenerator: +class PretenseGroundObjectGenerator(GroundObjectGenerator): """generates the DCS groups and units from the TheaterGroundObject""" def __init__( @@ -244,6 +102,14 @@ class GroundObjectGenerator: mission: Mission, unit_map: UnitMap, ) -> None: + super().__init__( + ground_object, + country, + game, + mission, + unit_map, + ) + self.ground_object = ground_object self.country = country self.game = game @@ -254,6 +120,51 @@ class GroundObjectGenerator: def culled(self) -> bool: return self.game.iads_considerate_culling(self.ground_object) + def ground_unit_of_class(self, unit_class: UnitClass) -> Optional[GroundUnitType]: + faction_units = ( + set(self.ground_object.coalition.faction.frontline_units) + | set(self.ground_object.coalition.faction.artillery_units) + | set(self.ground_object.coalition.faction.logistics_units) + ) + of_class = list({u for u in faction_units if u.unit_class is unit_class}) + + if len(of_class) > 0: + return random.choice(of_class) + else: + return None + + def generate_ground_unit_of_class( + self, + unit_class: UnitClass, + group: TheaterGroup, + vehicle_units: list[TheaterUnit], + cp_name: str, + group_role: str, + max_num: int, + ): + if self.ground_object.coalition.faction.has_access_to_unit_class(unit_class): + unit_type = self.ground_unit_of_class(unit_class) + if unit_type is not None and len(vehicle_units) < max_num: + group_id = self.game.next_group_id() + group_name = f"{cp_name}-{group_role}-{group_id}" + + spread_out_heading = random.randrange(1, 360) + spread_out_position = group.position.point_from_heading( + spread_out_heading, 30 + ) + ground_unit_pos = PointWithHeading.from_point( + spread_out_position, group.position.heading + ) + + theater_unit = TheaterUnit( + group_id, + group_name, + unit_type.dcs_unit_type, + ground_unit_pos, + group.ground_object, + ) + vehicle_units.append(theater_unit) + def generate(self) -> None: if self.culled: return @@ -262,27 +173,88 @@ class GroundObjectGenerator: ship_units = [] # Split the different unit types to be compliant to dcs limitation for unit in group.units: + cp_name_trimmed = "".join( + [ + i + for i in self.ground_object.control_point.name.lower() + if i.isalnum() + ] + ) + if unit.is_static: - if isinstance(unit, SceneryUnit): - # Special handling for scenery objects - self.add_trigger_zone_for_scenery(unit) - if ( - self.game.settings.plugin_option("skynetiads") - and isinstance(group, IadsGroundGroup) - and group.iads_role.participate - ): - # Generate a unit which can be controlled by skynet - self.generate_iads_command_unit(unit) - else: - # Create a static group for each static unit - self.create_static_group(unit) + # Add supply convoy + self.generate_ground_unit_of_class( + UnitClass.LOGISTICS, + group, + vehicle_units, + cp_name_trimmed, + "supply", + PRETENSE_GROUND_UNIT_GROUP_SIZE, + ) elif unit.is_vehicle and unit.alive: - # All alive Vehicles - vehicle_units.append(unit) + # Add armor group + self.generate_ground_unit_of_class( + UnitClass.TANK, + group, + vehicle_units, + cp_name_trimmed, + "assault", + PRETENSE_GROUND_UNIT_GROUP_SIZE - 3, + ) + self.generate_ground_unit_of_class( + UnitClass.ATGM, + group, + vehicle_units, + cp_name_trimmed, + "assault", + PRETENSE_GROUND_UNIT_GROUP_SIZE - 2, + ) + self.generate_ground_unit_of_class( + UnitClass.APC, + group, + vehicle_units, + cp_name_trimmed, + "assault", + PRETENSE_GROUND_UNIT_GROUP_SIZE - 1, + ) + self.generate_ground_unit_of_class( + UnitClass.IFV, + group, + vehicle_units, + cp_name_trimmed, + "assault", + PRETENSE_GROUND_UNIT_GROUP_SIZE, + ) + self.generate_ground_unit_of_class( + UnitClass.ARTILLERY, + group, + vehicle_units, + cp_name_trimmed, + "assault", + PRETENSE_GROUND_UNIT_GROUP_SIZE, + ) + self.generate_ground_unit_of_class( + UnitClass.RECON, + group, + vehicle_units, + cp_name_trimmed, + "assault", + PRETENSE_GROUND_UNIT_GROUP_SIZE, + ) + if random.randrange(0, 100) > 75: + self.generate_ground_unit_of_class( + UnitClass.SHORAD, + group, + vehicle_units, + cp_name_trimmed, + "assault", + PRETENSE_GROUND_UNIT_GROUP_SIZE, + ) elif unit.is_ship and unit.alive: # All alive Ships ship_units.append(unit) if vehicle_units: + print(f"Generating vehicle group {vehicle_units}") self.create_vehicle_group(group.group_name, vehicle_units) if ship_units: self.create_ship_group(group.group_name, ship_units) @@ -319,761 +291,8 @@ class GroundObjectGenerator: raise RuntimeError(f"Error creating VehicleGroup for {group_name}") return vehicle_group - def create_ship_group( - self, - group_name: str, - units: list[TheaterUnit], - frequency: Optional[RadioFrequency] = None, - ) -> ShipGroup: - ship_group: Optional[ShipGroup] = None - for unit in units: - assert issubclass(unit.type, ShipType) - faction = unit.ground_object.control_point.coalition.faction - if ship_group is None: - ship_group = self.m.ship_group( - self.country, - group_name, - unit.type, - position=unit.position, - heading=unit.position.heading.degrees, - ) - if frequency: - ship_group.set_frequency(frequency.hertz) - ship_group.units[0].name = unit.unit_name - self.set_alarm_state(ship_group) - NavalForcePainter(faction, ship_group.units[0]).apply_livery() - else: - ship_unit = self.m.ship(unit.unit_name, unit.type) - if frequency: - ship_unit.set_frequency(frequency.hertz) - ship_unit.position = unit.position - ship_unit.heading = unit.position.heading.degrees - NavalForcePainter(faction, ship_unit).apply_livery() - ship_group.add_unit(ship_unit) - self._register_theater_unit(unit, ship_group.units[-1]) - if ship_group is None: - raise RuntimeError(f"Error creating ShipGroup for {group_name}") - return ship_group - def create_static_group(self, unit: TheaterUnit) -> None: - static_group = self.m.static_group( - country=self.country, - name=unit.unit_name, - _type=unit.type, - position=unit.position, - heading=unit.position.heading.degrees, - dead=not unit.alive, - ) - self._register_theater_unit(unit, static_group.units[0]) - - @staticmethod - def enable_eplrs(group: VehicleGroup, unit_type: Type[VehicleType]) -> None: - if unit_type.eplrs: - group.points[0].tasks.append(EPLRS(group.id)) - - def set_alarm_state(self, group: MovingGroup[Any]) -> None: - if self.game.settings.perf_red_alert_state: - group.points[0].tasks.append(OptAlarmState(2)) - else: - group.points[0].tasks.append(OptAlarmState(1)) - - def _register_theater_unit( - self, - theater_unit: TheaterUnit, - dcs_unit: Unit, - ) -> None: - self.unit_map.add_theater_unit_mapping(theater_unit, dcs_unit) - - def add_trigger_zone_for_scenery(self, scenery: SceneryUnit) -> None: - # Align the trigger zones to the faction color on the DCS briefing/F10 map. - color = ( - {1: 0.2, 2: 0.7, 3: 1, 4: 0.15} - if scenery.ground_object.is_friendly(to_player=True) - else {1: 1, 2: 0.2, 3: 0.2, 4: 0.15} - ) - - # Create the smallest valid size trigger zone (16 feet) so that risk of overlap - # is minimized. As long as the triggerzone is over the scenery object, we're ok. - smallest_valid_radius = feet(16).meters - - trigger_zone = self.m.triggers.add_triggerzone( - scenery.zone.position, - smallest_valid_radius, - scenery.zone.hidden, - scenery.zone.name, - color, - scenery.zone.properties, - ) - # DCS only visually shows a scenery object is dead when - # this trigger rule is applied. Otherwise you can kill a - # structure twice. - if not scenery.alive: - self.generate_destruction_trigger_rule(trigger_zone) - else: - self.generate_on_dead_trigger_rule(trigger_zone) - - self.unit_map.add_scenery(scenery, trigger_zone) - - def generate_destruction_trigger_rule(self, trigger_zone: TriggerZone) -> None: - # Add destruction zone trigger - t = TriggerStart(comment="Destruction") - t.actions.append( - SceneryDestructionZone(destruction_level=100, zone=trigger_zone.id) - ) - 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'dead_events[#dead_events + 1] = "{trigger_zone.name}"') - t.actions.append(DoScript(script_string)) - self.m.triggerrules.triggers.append(t) - - def generate_iads_command_unit(self, unit: SceneryUnit) -> None: - # Creates a static Infantry Unit next to a scenery object. This is needed - # because skynet can not use map objects as Comms, Power or Command and needs a - # "real" unit to function correctly - self.m.static_group( - country=self.country, - name=unit.unit_name, - _type=dcs.vehicles.Infantry.Soldier_M4, - position=unit.position, - heading=unit.position.heading.degrees, - dead=not unit.alive, # Also spawn as dead! - ) - - -class MissileSiteGenerator(GroundObjectGenerator): - @property - def culled(self) -> bool: - # Don't cull missile sites - their range is long enough to make them easily - # culled despite being a threat. - return False - - def generate(self) -> None: - super(MissileSiteGenerator, self).generate() - - if not self.game.settings.generate_fire_tasks_for_missile_sites: - return - - # Note : Only the SCUD missiles group can fire (V1 site cannot fire in game right now) - # TODO : Should be pre-planned ? - # TODO : Add delay to task to spread fire task over mission duration ? - for group in self.ground_object.groups: - vg = self.m.find_group(group.group_name) - if vg is not None: - targets = self.possible_missile_targets() - if targets: - target = random.choice(targets) - real_target = target.point_from_heading( - Heading.random().degrees, random.randint(0, 2500) - ) - vg.points[0].add_task(FireAtPoint(real_target)) - logging.info("Set up fire task for missile group.") - else: - logging.info( - "Couldn't setup missile site to fire, no valid target in range." - ) - else: - logging.info( - "Couldn't setup missile site to fire, group was not generated." - ) - - def possible_missile_targets(self) -> List[Point]: - """ - Find enemy control points in range - :return: List of possible missile targets - """ - targets: List[Point] = [] - for cp in self.game.theater.controlpoints: - if cp.captured != self.ground_object.control_point.captured: - distance = cp.position.distance_to_point(self.ground_object.position) - if distance < self.missile_site_range: - targets.append(cp.position) - return targets - - @property - def missile_site_range(self) -> int: - """ - Get the missile site range - :return: Missile site range - """ - site_range = 0 - for group in self.ground_object.groups: - vg = self.m.find_group(group.group_name) - if vg is not None: - for u in vg.units: - if u.type in vehicle_map: - if vehicle_map[u.type].threat_range > site_range: - site_range = vehicle_map[u.type].threat_range - return site_range - - -class GenericCarrierGenerator(GroundObjectGenerator): - """Base type for carrier group generation. - - Used by both CV(N) groups and LHA groups. - """ - - def __init__( - self, - ground_object: GenericCarrierGroundObject, - control_point: NavalControlPoint, - country: Country, - game: Game, - mission: Mission, - radio_registry: RadioRegistry, - tacan_registry: TacanRegistry, - icls_alloc: Iterator[int], - runways: Dict[str, RunwayData], - unit_map: UnitMap, - mission_data: MissionData, - ) -> None: - super().__init__(ground_object, country, game, mission, unit_map) - self.ground_object = ground_object - self.control_point = control_point - self.radio_registry = radio_registry - self.tacan_registry = tacan_registry - self.icls_alloc = icls_alloc - self.runways = runways - self.mission_data = mission_data - - def generate(self) -> None: - if self.control_point.frequency is not None: - atc = self.control_point.frequency - if atc not in self.radio_registry.allocated_channels: - self.radio_registry.reserve(atc) - else: - atc = self.radio_registry.alloc_uhf() - - for g_id, group in enumerate(self.ground_object.groups): - if not group.units: - logging.warning(f"Found empty carrier group in {self.control_point}") - continue - - ship_units = [] - for unit in group.units: - if unit.alive: - # All alive Ships - ship_units.append(unit) - - if not ship_units: - # Empty array (no alive units), skip this group - continue - - ship_group = self.create_ship_group(group.group_name, ship_units, atc) - - # Always steam into the wind, even if the carrier is being moved. - # There are multiple unsimulated hours between turns, so we can - # count those as the time the carrier uses to move and the mission - # time as the recovery window. - brc = self.steam_into_wind(ship_group) - - # Set Carrier Specific Options - if g_id == 0 and self.control_point.runway_is_operational(): - # Get Correct unit type for the carrier. - # This will upgrade to super carrier if option is enabled - carrier_type = self.carrier_type - if carrier_type is None: - raise RuntimeError( - f"Error generating carrier group for {self.control_point.name}" - ) - ship_group.units[0].type = carrier_type.id - if self.control_point.tacan is None: - tacan = self.tacan_registry.alloc_for_band( - TacanBand.X, TacanUsage.TransmitReceive - ) - else: - tacan = self.control_point.tacan - if self.control_point.tcn_name is None: - tacan_callsign = self.tacan_callsign() - else: - tacan_callsign = self.control_point.tcn_name - link4 = None - link4carriers = [Stennis, CVN_71, CVN_72, CVN_73, CVN_75, Forrestal] - if carrier_type in link4carriers: - if self.control_point.link4 is None: - link4 = self.radio_registry.alloc_uhf() - else: - link4 = self.control_point.link4 - icls = None - icls_name = self.control_point.icls_name - if carrier_type in link4carriers or carrier_type == LHA_Tarawa: - if self.control_point.icls_channel is None: - icls = next(self.icls_alloc) - else: - icls = self.control_point.icls_channel - self.activate_beacons( - ship_group, tacan, tacan_callsign, icls, icls_name, link4 - ) - self.add_runway_data( - brc or Heading.from_degrees(0), atc, tacan, tacan_callsign, icls - ) - self.mission_data.carriers.append( - CarrierInfo( - group_name=ship_group.name, - unit_name=ship_group.units[0].name, - callsign=tacan_callsign, - freq=atc, - tacan=tacan, - blue=self.control_point.captured, - ) - ) - - @property - def carrier_type(self) -> Optional[Type[ShipType]]: - return self.control_point.get_carrier_group_type() - - def steam_into_wind(self, group: ShipGroup) -> Optional[Heading]: - wind = self.game.conditions.weather.wind.at_0m - brc = Heading.from_degrees(wind.direction).opposite - # Aim for 25kts over the deck. - carrier_speed = knots(25) - mps(wind.speed) - for attempt in range(5): - point = group.points[0].position.point_from_heading( - brc.degrees, 100000 - attempt * 20000 - ) - if self.game.theater.is_in_sea(point): - group.points[0].speed = carrier_speed.meters_per_second - group.add_waypoint(point, carrier_speed.kph) - # Rotate the whole ground object to the new course - self.ground_object.rotate(brc) - return brc - return None - - def tacan_callsign(self) -> str: - raise NotImplementedError - - @staticmethod - def activate_beacons( - group: ShipGroup, - tacan: TacanChannel, - callsign: str, - icls: Optional[int] = None, - icls_name: Optional[str] = None, - link4: Optional[RadioFrequency] = None, - ) -> None: - group.points[0].tasks.append( - ActivateBeaconCommand( - channel=tacan.number, - modechannel=tacan.band.value, - callsign=callsign, - unit_id=group.units[0].id, - aa=False, - ) - ) - if icls is not None: - icls_name = "" if icls_name is None else icls_name - group.points[0].tasks.append( - ActivateICLSCommand(icls, group.units[0].id, icls_name) - ) - if link4 is not None: - group.points[0].tasks.append( - ActivateLink4Command(link4.hertz, group.units[0].id) - ) - group.points[0].tasks.append(ActivateACLSCommand(unit_id=group.units[0].id)) - - def add_runway_data( - self, - brc: Heading, - atc: RadioFrequency, - tacan: TacanChannel, - callsign: str, - icls: Optional[int], - ) -> None: - # This relies on one control point mapping exactly - # to one LHA, carrier, or other usable "runway". - # This isn't wholly true, since the DD escorts of - # the carrier group are valid for helicopters, but - # they aren't exposed as such to the game. Should - # clean this up so that's possible. We can't use the - # unit name since it's an arbitrary ID. - self.runways[self.control_point.full_name] = RunwayData( - self.control_point.name, - brc, - f"{brc.degrees:03}", - atc=atc, - tacan=tacan, - tacan_callsign=callsign, - icls=icls, - ) - - -class CarrierGenerator(GenericCarrierGenerator): - """Generator for CV(N) groups.""" - - def tacan_callsign(self) -> str: - # TODO: Assign these properly. - return random.choice( - [ - "STE", - "CVN", - "CVH", - "CCV", - "ACC", - "ARC", - "GER", - "ABR", - "LIN", - "TRU", - ] - ) - - -class LhaGenerator(GenericCarrierGenerator): - """Generator for LHA groups.""" - - def tacan_callsign(self) -> str: - # TODO: Assign these properly. - return random.choice( - [ - "LHD", - "LHA", - "LHB", - "LHC", - "LHD", - "LDS", - ] - ) - - -class HelipadGenerator: - """ - Generates helipads for given control point - """ - - def __init__( - self, - mission: Mission, - cp: ControlPoint, - game: Game, - radio_registry: RadioRegistry, - tacan_registry: TacanRegistry, - ): - self.m = mission - self.cp = cp - self.game = game - self.radio_registry = radio_registry - self.tacan_registry = tacan_registry - self.helipads: list[StaticGroup] = [] - - def create_helipad( - self, i: int, helipad: PointWithHeading, helipad_type: str - ) -> None: - # Note: Helipad are generated as neutral object in order not to interfere with - # capture triggers - pad: BaseFARP - neutral_country = self.m.country(self.game.neutral_country.name) - country = self.m.country( - self.game.coalition_for(self.cp.captured).faction.country.name - ) - - name = f"{self.cp.name} {helipad_type} {i}" - logging.info("Generating helipad static : " + name) - terrain = self.m.terrain - if helipad_type == "SINGLE_HELIPAD": - pad = SingleHeliPad( - unit_id=self.m.next_unit_id(), name=name, terrain=terrain - ) - number_of_pads = 1 - elif helipad_type == "FARP": - pad = FARP(unit_id=self.m.next_unit_id(), name=name, terrain=terrain) - number_of_pads = 4 - else: - pad = InvisibleFARP( - unit_id=self.m.next_unit_id(), name=name, terrain=terrain - ) - number_of_pads = 1 - pad.position = Point(helipad.x, helipad.y, terrain=terrain) - pad.heading = helipad.heading.degrees - - # Set FREQ - if isinstance(self.cp, RadioFrequencyContainer) and self.cp.frequency: - if isinstance(pad, BaseFARP): - pad.heliport_frequency = self.cp.frequency.mhz - - sg = unitgroup.StaticGroup(self.m.next_group_id(), name) - sg.add_unit(pad) - sp = StaticPoint(pad.position) - sg.add_point(sp) - neutral_country.add_static_group(sg) - - if number_of_pads > 1: - self.append_helipad(pad, name, helipad.heading.degrees, 60, 0, 0) - self.append_helipad(pad, name, helipad.heading.degrees + 180, 20, 0, 0) - self.append_helipad( - pad, name, helipad.heading.degrees + 90, 60, helipad.heading.degrees, 20 - ) - self.append_helipad( - pad, - name, - helipad.heading.degrees + 90, - 60, - helipad.heading.degrees + 180, - 60, - ) - else: - self.helipads.append(sg) - - # Generate a FARP Ammo and Fuel stack for each pad - self.m.static_group( - country=country, - name=(name + "_fuel"), - _type=Fortification.FARP_Fuel_Depot, - position=pad.position.point_from_heading(helipad.heading.degrees, 35), - heading=pad.heading + 180, - ) - self.m.static_group( - country=country, - name=(name + "_ammo"), - _type=Fortification.FARP_Ammo_Dump_Coating, - position=pad.position.point_from_heading( - helipad.heading.degrees, 35 - ).point_from_heading(helipad.heading.degrees + 90, 10), - heading=pad.heading + 90, - ) - self.m.static_group( - country=country, - name=(name + "_ws"), - _type=Fortification.Windsock, - position=helipad.point_from_heading(helipad.heading.degrees + 45, 35), - heading=pad.heading, - ) - - def append_helipad( - self, - pad: BaseFARP, - name: str, - heading_1: int, - distance_1: int, - heading_2: int, - distance_2: int, - ) -> None: - new_pad = InvisibleFARP(pad._terrain) - new_pad.position = pad.position.point_from_heading(heading_1, distance_1) - new_pad.position = new_pad.position.point_from_heading(heading_2, distance_2) - sg = unitgroup.StaticGroup(self.m.next_group_id(), name) - sg.add_unit(new_pad) - self.helipads.append(sg) - - def generate(self) -> None: - for i, helipad in enumerate(self.cp.helipads): - self.create_helipad(i, helipad, "SINGLE_HELIPAD") - for i, helipad in enumerate(self.cp.helipads_quad): - self.create_helipad(i, helipad, "FARP") - for i, helipad in enumerate(self.cp.helipads_invisible): - self.create_helipad(i, helipad, "Invisible FARP") - - -class GroundSpawnRoadbaseGenerator: - """ - Generates Highway strip starting positions for given control point - """ - - def __init__( - self, - mission: Mission, - cp: ControlPoint, - game: Game, - radio_registry: RadioRegistry, - tacan_registry: TacanRegistry, - ): - self.m = mission - self.cp = cp - self.game = game - self.radio_registry = radio_registry - self.tacan_registry = tacan_registry - self.ground_spawns_roadbase: list[Tuple[StaticGroup, Point]] = [] - - def create_ground_spawn_roadbase( - self, i: int, ground_spawn: Tuple[PointWithHeading, Point] - ) -> None: - # Note: FARPs are generated as neutral object in order not to interfere with - # capture triggers - neutral_country = self.m.country(self.game.neutral_country.name) - country = self.m.country( - self.game.coalition_for(self.cp.captured).faction.country.name - ) - terrain = self.cp.coalition.game.theater.terrain - - name = f"{self.cp.name} roadbase spawn {i}" - logging.info("Generating Roadbase Spawn static : " + name) - - pad = InvisibleFARP(unit_id=self.m.next_unit_id(), name=name, terrain=terrain) - - pad.position = Point(ground_spawn[0].x, ground_spawn[0].y, terrain=terrain) - pad.heading = ground_spawn[0].heading.degrees - sg = unitgroup.StaticGroup(self.m.next_group_id(), name) - sg.add_unit(pad) - sp = StaticPoint(pad.position) - sg.add_point(sp) - neutral_country.add_static_group(sg) - - self.ground_spawns_roadbase.append((sg, ground_spawn[1])) - - # tanker_type: Type[VehicleType] - # ammo_truck_type: Type[VehicleType] - - tanker_type, ammo_truck_type = farp_truck_types_for_country(country.id) - - # Generate ammo truck/farp and fuel truck/stack for each pad - if self.game.settings.ground_start_trucks_roadbase: - self.m.vehicle_group( - country=country, - name=(name + "_fuel"), - _type=tanker_type, - position=pad.position.point_from_heading( - ground_spawn[0].heading.degrees + 90, 35 - ), - group_size=1, - heading=pad.heading + 315, - move_formation=PointAction.OffRoad, - ) - self.m.vehicle_group( - country=country, - name=(name + "_ammo"), - _type=ammo_truck_type, - position=pad.position.point_from_heading( - ground_spawn[0].heading.degrees + 90, 35 - ).point_from_heading(ground_spawn[0].heading.degrees + 180, 10), - group_size=1, - heading=pad.heading + 315, - move_formation=PointAction.OffRoad, - ) - else: - self.m.static_group( - country=country, - name=(name + "_fuel"), - _type=Fortification.FARP_Fuel_Depot, - position=pad.position.point_from_heading( - ground_spawn[0].heading.degrees + 90, 35 - ), - heading=pad.heading + 270, - ) - self.m.static_group( - country=country, - name=(name + "_ammo"), - _type=Fortification.FARP_Ammo_Dump_Coating, - position=pad.position.point_from_heading( - ground_spawn[0].heading.degrees + 90, 35 - ).point_from_heading(ground_spawn[0].heading.degrees + 180, 10), - heading=pad.heading + 180, - ) - - def generate(self) -> None: - try: - for i, ground_spawn in enumerate(self.cp.ground_spawns_roadbase): - self.create_ground_spawn_roadbase(i, ground_spawn) - except AttributeError: - self.ground_spawns_roadbase = [] - - -class GroundSpawnGenerator: - """ - Generates STOL aircraft starting positions for given control point - """ - - def __init__( - self, - mission: Mission, - cp: ControlPoint, - game: Game, - radio_registry: RadioRegistry, - tacan_registry: TacanRegistry, - ): - self.m = mission - self.cp = cp - self.game = game - self.radio_registry = radio_registry - self.tacan_registry = tacan_registry - self.ground_spawns: list[Tuple[StaticGroup, Point]] = [] - - def create_ground_spawn( - self, i: int, vtol_pad: Tuple[PointWithHeading, Point] - ) -> None: - # Note: FARPs are generated as neutral object in order not to interfere with - # capture triggers - neutral_country = self.m.country(self.game.neutral_country.name) - country = self.m.country( - self.game.coalition_for(self.cp.captured).faction.country.name - ) - terrain = self.cp.coalition.game.theater.terrain - - name = f"{self.cp.name} ground spawn {i}" - logging.info("Generating Ground Spawn static : " + name) - - pad = InvisibleFARP(unit_id=self.m.next_unit_id(), name=name, terrain=terrain) - - pad.position = Point(vtol_pad[0].x, vtol_pad[0].y, terrain=terrain) - pad.heading = vtol_pad[0].heading.degrees - sg = unitgroup.StaticGroup(self.m.next_group_id(), name) - sg.add_unit(pad) - sp = StaticPoint(pad.position) - sg.add_point(sp) - neutral_country.add_static_group(sg) - - self.ground_spawns.append((sg, vtol_pad[1])) - - # tanker_type: Type[VehicleType] - # ammo_truck_type: Type[VehicleType] - - tanker_type, ammo_truck_type = farp_truck_types_for_country(country.id) - - # Generate a FARP Ammo and Fuel stack for each pad - if self.game.settings.ground_start_trucks: - self.m.vehicle_group( - country=country, - name=(name + "_fuel"), - _type=tanker_type, - position=pad.position.point_from_heading( - vtol_pad[0].heading.degrees - 175, 35 - ), - group_size=1, - heading=pad.heading + 45, - move_formation=PointAction.OffRoad, - ) - self.m.vehicle_group( - country=country, - name=(name + "_ammo"), - _type=ammo_truck_type, - position=pad.position.point_from_heading( - vtol_pad[0].heading.degrees - 185, 35 - ), - group_size=1, - heading=pad.heading + 45, - move_formation=PointAction.OffRoad, - ) - else: - self.m.static_group( - country=country, - name=(name + "_fuel"), - _type=Fortification.FARP_Fuel_Depot, - position=pad.position.point_from_heading( - vtol_pad[0].heading.degrees - 180, 45 - ), - heading=pad.heading, - ) - self.m.static_group( - country=country, - name=(name + "_ammo"), - _type=Fortification.FARP_Ammo_Dump_Coating, - position=pad.position.point_from_heading( - vtol_pad[0].heading.degrees - 180, 35 - ), - heading=pad.heading + 270, - ) - - def generate(self) -> None: - try: - for i, vtol_pad in enumerate(self.cp.ground_spawns): - self.create_ground_spawn(i, vtol_pad) - except AttributeError: - self.ground_spawns = [] - - -class TgoGenerator: +class PretenseTgoGenerator(TgoGenerator): """Creates DCS groups and statics for the theater during mission generation. Most of the work of group/static generation is delegated to the other @@ -1091,6 +310,15 @@ class TgoGenerator: unit_map: UnitMap, mission_data: MissionData, ) -> None: + super().__init__( + mission, + game, + radio_registry, + tacan_registry, + unit_map, + mission_data, + ) + self.m = mission self.game = game self.radio_registry = radio_registry @@ -1175,7 +403,7 @@ class TgoGenerator: ground_object, country, self.game, self.m, self.unit_map ) else: - generator = GroundObjectGenerator( + generator = PretenseGroundObjectGenerator( ground_object, country, self.game, self.m, self.unit_map ) generator.generate() diff --git a/game/pretense/pretensetriggergenerator.py b/game/pretense/pretensetriggergenerator.py index e5ea05e9..43fbb1de 100644 --- a/game/pretense/pretensetriggergenerator.py +++ b/game/pretense/pretensetriggergenerator.py @@ -170,6 +170,8 @@ class PretenseTriggerGenerator: cp_name_trimmed = "".join([i for i in cp.name.lower() if i.isalnum()]) tgo_num = 0 for tgo in cp.ground_objects: + if cp.is_fleet or tgo.sea_object: + 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( From 94d755e9a0eb89613fe759f341e1e19bac155eed Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Sun, 10 Sep 2023 14:09:24 +0300 Subject: [PATCH 007/243] Cleaned up some of my recent Pretense code. --- game/pretense/pretenseaircraftgenerator.py | 2 +- game/pretense/pretensemissiongenerator.py | 3 --- game/pretense/pretensetgogenerator.py | 6 +++--- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/game/pretense/pretenseaircraftgenerator.py b/game/pretense/pretenseaircraftgenerator.py index 334a8871..5abec403 100644 --- a/game/pretense/pretenseaircraftgenerator.py +++ b/game/pretense/pretenseaircraftgenerator.py @@ -132,7 +132,7 @@ class PretenseAircraftGenerator: flight_type = FlightType.TRANSPORT elif ( FlightType.SEAD in mission_types - or FlightType.SEAD_SWEEP + or FlightType.SEAD_SWEEP in mission_types or FlightType.SEAD_ESCORT in mission_types ) and num_of_sead < 2: flight_type = FlightType.SEAD diff --git a/game/pretense/pretensemissiongenerator.py b/game/pretense/pretensemissiongenerator.py index b0b61ce2..d39b338c 100644 --- a/game/pretense/pretensemissiongenerator.py +++ b/game/pretense/pretensemissiongenerator.py @@ -258,9 +258,6 @@ class PretenseMissionGenerator: continue flight.aircraft_type.assign_channels_for_flight(flight, self.mission_data) - if self.game.settings.plugins.get("ewrj"): - self._configure_ewrj(aircraft_generator) - def notify_info_generators( self, ) -> None: diff --git a/game/pretense/pretensetgogenerator.py b/game/pretense/pretensetgogenerator.py index 5c4ef4e2..56d84f54 100644 --- a/game/pretense/pretensetgogenerator.py +++ b/game/pretense/pretensetgogenerator.py @@ -141,7 +141,7 @@ class PretenseGroundObjectGenerator(GroundObjectGenerator): cp_name: str, group_role: str, max_num: int, - ): + ) -> None: if self.ground_object.coalition.faction.has_access_to_unit_class(unit_class): unit_type = self.ground_unit_of_class(unit_class) if unit_type is not None and len(vehicle_units) < max_num: @@ -169,8 +169,8 @@ class PretenseGroundObjectGenerator(GroundObjectGenerator): if self.culled: return for group in self.ground_object.groups: - vehicle_units = [] - ship_units = [] + vehicle_units: list[TheaterUnit] = [] + ship_units: list[TheaterUnit] = [] # Split the different unit types to be compliant to dcs limitation for unit in group.units: cp_name_trimmed = "".join( From 9135a91bb996f9f22d5d22c360c0d887d693b55e Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Sun, 10 Sep 2023 14:10:54 +0300 Subject: [PATCH 008/243] Added Pretense icon, credit: Dzsekeb, original author of DCS Pretense. Used with permission. --- resources/ui/misc/pretense.png | Bin 0 -> 29600 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 resources/ui/misc/pretense.png diff --git a/resources/ui/misc/pretense.png b/resources/ui/misc/pretense.png new file mode 100644 index 0000000000000000000000000000000000000000..0daefbc3ae016cb08ebe5884382ac6aec382520a GIT binary patch literal 29600 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 literal 0 HcmV?d00001 From c8f78c1bf4732148808325a0e975d475ccc93cd0 Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Sun, 10 Sep 2023 14:39:26 +0300 Subject: [PATCH 009/243] Copied luagenerator.py as a template/inheritance for generating Pretense campaigns from Retribution campaigns. --- game/pretense/pretenseluagenerator.py | 392 ++++++++++++++++++++++++++ 1 file changed, 392 insertions(+) create mode 100644 game/pretense/pretenseluagenerator.py diff --git a/game/pretense/pretenseluagenerator.py b/game/pretense/pretenseluagenerator.py new file mode 100644 index 00000000..f8698ec7 --- /dev/null +++ b/game/pretense/pretenseluagenerator.py @@ -0,0 +1,392 @@ +from __future__ import annotations + +import logging +import os +from abc import ABC, abstractmethod +from pathlib import Path +from typing import TYPE_CHECKING, Optional + +from dcs import Mission +from dcs.action import DoScript, DoScriptFile +from dcs.translation import String +from dcs.triggers import TriggerStart + +from game.ato import FlightType +from game.dcs.aircrafttype import AircraftType +from game.plugins import LuaPluginManager +from game.theater import TheaterGroundObject +from game.theater.iadsnetwork.iadsrole import IadsRole +from game.utils import escape_string_for_lua +from .missiondata import MissionData + +if TYPE_CHECKING: + from game import Game + + +class LuaGenerator: + def __init__( + self, + game: Game, + mission: Mission, + mission_data: MissionData, + ) -> None: + self.game = game + self.mission = mission + self.mission_data = mission_data + self.plugin_scripts: list[str] = [] + + def generate(self) -> None: + ewrj_triggers = [ + x for x in self.mission.triggerrules.triggers if isinstance(x, TriggerStart) + ] + 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)) + 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) + + def inject_lua_trigger(self, contents: str, comment: str) -> None: + trigger = TriggerStart(comment=comment) + trigger.add_action(DoScript(String(contents))) + self.mission.triggerrules.triggers.append(trigger) + + def bypass_plugin_script(self, mnemonic: str) -> None: + self.plugin_scripts.append(mnemonic) + + def inject_plugin_script( + self, plugin_mnemonic: str, script: str, script_mnemonic: str + ) -> None: + if script_mnemonic in self.plugin_scripts: + logging.debug(f"Skipping already loaded {script} for {plugin_mnemonic}") + return + + self.plugin_scripts.append(script_mnemonic) + + plugin_path = Path("./resources/plugins", plugin_mnemonic) + + script_path = Path(plugin_path, script) + if not script_path.exists(): + logging.error(f"Cannot find {script_path} for plugin {plugin_mnemonic}") + return + + trigger = TriggerStart(comment=f"Load {script_mnemonic}") + filename = script_path.resolve() + fileref = self.mission.map_resource.add_resource_file(filename) + trigger.add_action(DoScriptFile(fileref)) + self.mission.triggerrules.triggers.append(trigger) + + def inject_plugins(self) -> None: + for plugin in LuaPluginManager.plugins(): + if plugin.enabled: + plugin.inject_scripts(self) + plugin.inject_configuration(self) + + +class LuaValue: + key: Optional[str] + value: str | list[str] + + def __init__(self, key: Optional[str], value: str | list[str]): + self.key = key + self.value = value + + def serialize(self) -> str: + serialized_value = self.key + " = " if self.key else "" + if isinstance(self.value, str): + serialized_value += f'"{escape_string_for_lua(self.value)}"' + else: + escaped_values = [f'"{escape_string_for_lua(v)}"' for v in self.value] + serialized_value += "{" + ", ".join(escaped_values) + "}" + return serialized_value + + +class LuaItem(ABC): + value: LuaValue | list[LuaValue] + name: Optional[str] + + def __init__(self, name: Optional[str]): + self.value = [] + self.name = name + + def set_value(self, value: str) -> None: + self.value = LuaValue(None, value) + + def set_data_array(self, values: list[str]) -> None: + self.value = LuaValue(None, values) + + def add_data_array(self, key: str, values: list[str]) -> None: + self._add_value(LuaValue(key, values)) + + def add_key_value(self, key: str, value: str) -> None: + self._add_value(LuaValue(key, value)) + + def _add_value(self, value: LuaValue) -> None: + if isinstance(self.value, list): + self.value.append(value) + else: + self.value = value + + @abstractmethod + def add_item(self, item_name: Optional[str] = None) -> LuaItem: + """adds a new item to the LuaArray without checking the existence""" + raise NotImplementedError + + @abstractmethod + def get_item(self, item_name: str) -> Optional[LuaItem]: + """gets item from LuaArray. Returns None if it does not exist""" + raise NotImplementedError + + @abstractmethod + def get_or_create_item(self, item_name: Optional[str] = None) -> LuaItem: + """gets item from the LuaArray or creates one if it does not exist already""" + raise NotImplementedError + + @abstractmethod + def serialize(self) -> str: + if isinstance(self.value, LuaValue): + return self.value.serialize() + else: + serialized_data = [d.serialize() for d in self.value] + return "{" + ", ".join(serialized_data) + "}" + + +class LuaData(LuaItem): + objects: list[LuaData] + base_name: Optional[str] + + def __init__(self, name: Optional[str], is_base_name: bool = True): + self.objects = [] + self.base_name = name if is_base_name else None + super().__init__(name) + + def add_item(self, item_name: Optional[str] = None) -> LuaItem: + item = LuaData(item_name, False) + self.objects.append(item) + return item + + def get_item(self, item_name: str) -> Optional[LuaItem]: + for lua_object in self.objects: + if lua_object.name == item_name: + return lua_object + return None + + def get_or_create_item(self, item_name: Optional[str] = None) -> LuaItem: + if item_name: + item = self.get_item(item_name) + if item: + return item + return self.add_item(item_name) + + def serialize(self, level: int = 0) -> str: + """serialize the LuaData to a string""" + serialized_data: list[str] = [] + serialized_name = "" + linebreak = "\n" + tab = "\t" + tab_end = "" + for _ in range(level): + tab += "\t" + tab_end += "\t" + if self.base_name: + # Only used for initialization of the object in lua + serialized_name += self.base_name + " = " + if self.objects: + # nested objects + serialized_objects = [o.serialize(level + 1) for o in self.objects] + if self.name: + if self.name is not self.base_name: + serialized_name += self.name + " = " + serialized_data.append( + serialized_name + + "{" + + linebreak + + tab + + ("," + linebreak + tab).join(serialized_objects) + + linebreak + + tab_end + + "}" + ) + else: + # key with value + if self.name: + serialized_data.append(self.name + " = " + super().serialize()) + # only value + else: + serialized_data.append(super().serialize()) + + return "\n".join(serialized_data) + + def create_operations_lua(self) -> str: + """crates the liberation lua script for the dcs mission""" + lua_prefix = """ +-- setting configuration table +env.info("DCSRetribution|: setting configuration table") +""" + + return lua_prefix + self.serialize() From db993697f0c6352bd4c3242f5827654d003ca4a1 Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Sun, 10 Sep 2023 14:57:06 +0300 Subject: [PATCH 010/243] Lua scripts from Pretense Caucasus 1.3.5, credit: Dzsekeb, original author of DCS Pretense. Used with permission. --- resources/plugins/pretense/init.lua | 4670 ++++++ .../plugins/pretense/pretense_compiled.lua | 13350 ++++++++++++++++ 2 files changed, 18020 insertions(+) create mode 100644 resources/plugins/pretense/init.lua create mode 100644 resources/plugins/pretense/pretense_compiled.lua diff --git a/resources/plugins/pretense/init.lua b/resources/plugins/pretense/init.lua new file mode 100644 index 00000000..9df8452d --- /dev/null +++ b/resources/plugins/pretense/init.lua @@ -0,0 +1,4670 @@ + + +local savefile = 'pretense_1.1.json' +if lfs then + local dir = lfs.writedir()..'Missions/Saves/' + lfs.mkdir(dir) + savefile = dir..savefile + env.info('Pretense - Save file path: '..savefile) +end + + +do + TemplateDB.templates["infantry-red"] = { + units = { + "BTR_D", + "T-90", + "T-90", + "Infantry AK ver2", + "Infantry AK", + "Infantry AK", + "Paratrooper RPG-16", + "Infantry AK ver3", + "SA-18 Igla manpad" + }, + skill = "Excellent", + dataCategory= TemplateDB.type.group + } + + TemplateDB.templates["infantry-blue"] = { + units = { + "M1045 HMMWV TOW", + "Soldier stinger", + "Soldier M4 GRG", + "Soldier M4 GRG", + "Soldier M4 GRG", + "Soldier M4 GRG", + "Soldier M4 GRG", + "M1043 HMMWV Armament" + }, + skill = "Excellent", + dataCategory= TemplateDB.type.group + } + + TemplateDB.templates["defense-red"] = { + units = { + "Infantry AK ver2", + "Infantry AK", + "Infantry AK ver3", + "Paratrooper RPG-16", + "SA-18 Igla manpad" + }, + skill = "Excellent", + dataCategory= TemplateDB.type.group + } + + TemplateDB.templates["defense-blue"] = { + units = { + "Soldier M4 GRG", + "Soldier M4 GRG", + "Soldier M4 GRG", + "Soldier RPG", + "Soldier stinger", + }, + skill = "Excellent", + dataCategory= TemplateDB.type.group + } + + TemplateDB.templates["shorad-red"] = { + units = { + "Strela-10M3", + "Strela-10M3", + "Ural-4320T", + "2S6 Tunguska" + }, + maxDist = 300, + skill = "Excellent", + dataCategory= TemplateDB.type.group + } + + TemplateDB.templates["shorad-blue"] = { + units = { + "Roland ADS", + "M48 Chaparral", + "M 818", + "Gepard", + "Gepard" + }, + maxDist = 300, + skill = "Excellent", + dataCategory= TemplateDB.type.group + } + + TemplateDB.templates["sam-red"] = { + units = { + "p-19 s-125 sr", + "Ural-4320T", + "Ural-4320T", + "S_75M_Volhov", + "S_75M_Volhov", + "S_75M_Volhov", + "S_75M_Volhov", + "S_75M_Volhov", + "Tor 9A331", + "SNR_75V" + }, + maxDist = 300, + skill = "Excellent", + dataCategory= TemplateDB.type.group + } + + TemplateDB.templates["sam-blue"] = { + units = { + "Hawk pcp", + "Hawk cwar", + "Hawk ln", + "Hawk ln", + "Hawk ln", + "Hawk ln", + "Hawk ln", + "Hawk tr", + "M 818", + "Hawk sr" + }, + maxDist = 300, + skill = "Good", + dataCategory= TemplateDB.type.group + } + + TemplateDB.templates["patriot"] = { + units = { + "Patriot cp", + "Patriot str", + "M 818", + "M 818", + "Patriot ln", + "Patriot ln", + "Patriot ln", + "Patriot ln", + "Patriot str", + "Patriot str", + "Patriot str", + "Patriot EPP", + "Patriot ECS", + "Patriot AMG" + }, + maxDist = 300, + skill = "Good", + dataCategory= TemplateDB.type.group + } + + TemplateDB.templates["sa3"] = { + units = { + "p-19 s-125 sr", + "snr s-125 tr", + "5p73 s-125 ln", + "5p73 s-125 ln", + "Ural-4320T", + "5p73 s-125 ln", + "5p73 s-125 ln" + }, + maxDist = 300, + skill = "Excellent", + dataCategory= TemplateDB.type.group + } + + TemplateDB.templates["sa6"] = { + units = { + "Kub 1S91 str", + "Kub 2P25 ln", + "Kub 2P25 ln", + "Kub 2P25 ln", + "Kub 2P25 ln", + "2S6 Tunguska", + "Ural-4320T", + "2S6 Tunguska", + "Kub 2P25 ln" + }, + maxDist = 300, + skill = "Excellent", + dataCategory= TemplateDB.type.group + } + + TemplateDB.templates["sa10"] = { + units = { + "S-300PS 54K6 cp", + "S-300PS 5P85C ln", + "S-300PS 5P85C ln", + "S-300PS 5P85C ln", + "GAZ-66", + "GAZ-66", + "GAZ-66", + "S-300PS 5P85C ln", + "S-300PS 5P85C ln", + "S-300PS 5P85C ln", + "S-300PS 40B6MD sr", + "S-300PS 40B6M tr", + "S-300PS 64H6E sr" + }, + maxDist = 300, + skill = "Excellent", + dataCategory= TemplateDB.type.group + } + + TemplateDB.templates["sa5"] = { + units = { + "RLS_19J6", + "Ural-4320T", + "Ural-4320T", + "RPC_5N62V", + "S-200_Launcher", + "S-200_Launcher", + "S-200_Launcher", + "S-200_Launcher", + "S-200_Launcher", + "S-200_Launcher" + }, + maxDist = 300, + skill = "Excellent", + dataCategory= TemplateDB.type.group + } + + TemplateDB.templates["sa11"] = { + units = { + "SA-11 Buk SR 9S18M1", + "SA-11 Buk LN 9A310M1", + "SA-11 Buk LN 9A310M1", + "SA-11 Buk LN 9A310M1", + "SA-11 Buk LN 9A310M1", + "SA-11 Buk LN 9A310M1", + "2S6 Tunguska", + "SA-11 Buk SR 9S18M1", + "GAZ-66", + "GAZ-66", + "SA-11 Buk CC 9S470M1" + }, + maxDist = 300, + skill = "Excellent", + dataCategory= TemplateDB.type.group + } + + TemplateDB.templates["nasams"] = { + units = { + "NASAMS_Command_Post", + "NASAMS_Radar_MPQ64F1", + "Vulcan", + "M 818", + "M 818", + "Roland ADS", + "Roland ADS", + "NASAMS_LN_C", + "NASAMS_LN_C", + "NASAMS_LN_C", + "NASAMS_LN_C", + "NASAMS_Radar_MPQ64F1", + "NASAMS_Radar_MPQ64F1", + "NASAMS_Radar_MPQ64F1" + }, + maxDist = 300, + skill = "Excellent", + dataCategory= TemplateDB.type.group + } +end + +presets = { + upgrades = { + basic = { + tent = Preset:new({ + display = 'Tent', + cost = 1500, + type = 'upgrade', + template = "tent" + }), + comPost = Preset:new({ + display = 'Barracks', + cost = 1500, + type = 'upgrade', + template = "barracks" + }), + outpost = Preset:new({ + display = 'Outpost', + cost = 1500, + type = 'upgrade', + template = "outpost" + }) + }, + attack = { + ammoCache = Preset:new({ + display = 'Ammo Cache', + cost = 1500, + type = 'upgrade', + template = "ammo-cache" + }), + ammoDepot = Preset:new({ + display = 'Ammo Depot', + cost = 2000, + type = 'upgrade', + template = "ammo-depot" + }) + }, + supply = { + fuelCache = Preset:new({ + display = 'Fuel Cache', + cost = 1500, + type = 'upgrade', + template = "fuel-cache" + }), + fuelTank = Preset:new({ + display = 'Fuel Tank', + cost = 1500, + type = 'upgrade', + template = "fuel-tank-big" + }), + fuelTankFarp = Preset:new({ + display = 'Fuel Tank', + cost = 1500, + type = 'upgrade', + template = "fuel-tank-small" + }), + factory1 = Preset:new({ + display='Factory', + cost = 2000, + type ='upgrade', + income = 20, + template = "factory-1" + }), + factory2 = Preset:new({ + display='Factory', + cost = 2000, + type ='upgrade', + income = 20, + template = "factory-2" + }), + factoryTank = Preset:new({ + display='Storage Tank', + cost = 1500, + type ='upgrade', + income = 10, + template = "chem-tank" + }), + ammoDepot = Preset:new({ + display = 'Ammo Depot', + cost = 2000, + type = 'upgrade', + income = 40, + template = "ammo-depot" + }), + oilPump = Preset:new({ + display = 'Oil Pump', + cost = 1500, + type = 'upgrade', + income = 20, + template = "oil-pump" + }), + hangar = Preset:new({ + display = 'Hangar', + cost = 2000, + type = 'upgrade', + income = 30, + template = "hangar" + }), + excavator = Preset:new({ + display = 'Excavator', + cost = 2000, + type = 'upgrade', + income = 20, + template = "excavator" + }), + farm1 = Preset:new({ + display = 'Farm House', + cost = 2000, + type = 'upgrade', + income = 40, + template = "farm-house-1" + }), + farm2 = Preset:new({ + display = 'Farm House', + cost = 2000, + type = 'upgrade', + income = 40, + template = "farm-house-2" + }), + refinery1 = Preset:new({ + display='Refinery', + cost = 2000, + type ='upgrade', + income = 100, + template = "factory-1" + }), + powerplant1 = Preset:new({ + display='Power Plant', + cost = 1500, + type ='upgrade', + income = 25, + template = "factory-1" + }), + powerplant2 = Preset:new({ + display='Power Plant', + cost = 1500, + type ='upgrade', + income = 25, + template = "factory-2" + }), + antenna = Preset:new({ + display='Antenna', + cost = 1000, + type ='upgrade', + income = 10, + template = "antenna" + }), + hq = Preset:new({ + display='HQ Building', + cost = 2000, + type ='upgrade', + income = 50, + template = "tv-tower" + }) + }, + airdef = { + comCenter = Preset:new({ + display = 'Command Center', + cost = 2500, + type = 'upgrade', + template = "command-center" + }) + } + }, + defenses = { + red = { + infantry = Preset:new({ + display = 'Infantry', + cost=2000, + type='defense', + template='infantry-red', + }), + shorad = Preset:new({ + display = 'SAM', + cost=2500, + type='defense', + template='shorad-red', + }), + sam = Preset:new({ + display = 'SAM', + cost=3000, + type='defense', + template='sam-red', + }), + sa10 = Preset:new({ + display = 'SAM', + cost=3000, + type='defense', + template='sa10', + }), + sa5 = Preset:new({ + display = 'SAM', + cost=3000, + type='defense', + template='sa5', + }), + sa3 = Preset:new({ + display = 'SAM', + cost=3000, + type='defense', + template='sa3', + }), + sa6 = Preset:new({ + display = 'SAM', + cost=3000, + type='defense', + template='sa6', + }), + sa11 = Preset:new({ + display = 'SAM', + cost=3000, + type='defense', + template='sa11', + }) + }, + blue = { + infantry = Preset:new({ + display = 'Infantry', + cost=2000, + type='defense', + template='infantry-blue', + }), + shorad = Preset:new({ + display = 'SAM', + cost=2500, + type='defense', + template='shorad-blue', + }), + sam = Preset:new({ + display = 'SAM', + cost=3000, + type='defense', + template='sam-blue', + }), + patriot = Preset:new({ + display = 'SAM', + cost=3000, + type='defense', + template='patriot', + }), + nasams = Preset:new({ + display = 'SAM', + cost=3000, + type='defense', + template='nasams', + }) + } + }, + missions = { + supply = { + convoy = Preset:new({ + display = 'Supply convoy', + cost = 4000, + type = 'mission', + missionType = ZoneCommand.missionTypes.supply_convoy + }), + convoy_escorted = Preset:new({ + display = 'Supply convoy', + cost = 3000, + type = 'mission', + missionType = ZoneCommand.missionTypes.supply_convoy + }), + helo = Preset:new({ + display = 'Supply helicopter', + cost = 2500, + type='mission', + missionType = ZoneCommand.missionTypes.supply_air + }), + transfer = Preset:new({ + display = 'Supply transfer', + cost = 1000, + type='mission', + missionType = ZoneCommand.missionTypes.supply_transfer + }) + }, + attack = { + surface = Preset:new({ + display = 'Ground assault', + cost = 100, + type = 'mission', + missionType = ZoneCommand.missionTypes.assault, + }), + cas = Preset:new({ + display = 'CAS', + cost = 200, + type='mission', + missionType = ZoneCommand.missionTypes.cas + }), + bai = Preset:new({ + display = 'BAI', + cost = 200, + type='mission', + missionType = ZoneCommand.missionTypes.bai + }), + strike = Preset:new({ + display = 'Strike', + cost = 300, + type='mission', + missionType = ZoneCommand.missionTypes.strike + }), + sead = Preset:new({ + display = 'SEAD', + cost = 200, + type='mission', + missionType = ZoneCommand.missionTypes.sead + }), + helo = Preset:new({ + display = 'CAS', + cost = 100, + type='mission', + missionType = ZoneCommand.missionTypes.cas_helo + }) + }, + patrol={ + aircraft = Preset:new({ + display= "Patrol", + cost = 100, + type='mission', + missionType = ZoneCommand.missionTypes.patrol + }) + }, + support ={ + awacs = Preset:new({ + display= "AWACS", + cost = 300, + type='mission', + bias='5', + missionType = ZoneCommand.missionTypes.awacs + }), + tanker = Preset:new({ + display= "Tanker", + cost = 200, + type='mission', + bias='2', + missionType = ZoneCommand.missionTypes.tanker + }) + } + }, + special = { + red = { + infantry = Preset:new({ + display = 'Infantry', + cost=-1, + type='defense', + template='defense-red', + }), + }, + blue = { + infantry = Preset:new({ + display = 'Infantry', + cost=-1, + type='defense', + template='defense-blue', + }) + } + } +} + +zones = {} +do + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Batumi.lua ]]----------------- + +zones.batumi = ZoneCommand:new('Batumi') +zones.batumi.initialState = { side=2 } +zones.batumi.keepActive = true +zones.batumi.isHeloSpawn = true +zones.batumi.isPlaneSpawn = true +zones.batumi.maxResource = 50000 +zones.batumi:defineUpgrades({ + [1] = { --red side + presets.upgrades.basic.comPost:extend({ + name = 'batumi-com-red', + products = { + presets.special.red.infantry:extend({ name='batumi-defense-red'}), + presets.defenses.red.infantry:extend({ name='batumi-garrison-red' }) + } + }), + }, + [2] = --blue side + { + presets.upgrades.basic.comPost:extend({ + name = 'batumi-com-blue', + products = { + presets.special.blue.infantry:extend({ name='batumi-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='batumi-garrison-blue' }) + } + }), + presets.upgrades.supply.fuelTank:extend({ + name = 'batumi-fueltank-blue', + products = { + presets.missions.supply.convoy_escorted:extend({ name='batumi-supply-convoy-1'}), + presets.missions.supply.helo:extend({ name='batumi-supply-blue-1' }), + presets.missions.supply.transfer:extend({name='batumi-transfer-blue'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name = 'batumi-mission-command-blue', + products = { + presets.defenses.blue.shorad:extend({ name='batumi-sam-blue' }), + presets.missions.attack.sead:extend({name='batumi-sead-blue-1', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.attack.cas:extend({name='batumi-cas-blue-1', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.bai:extend({name='batumi-cas-blue-1', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.strike:extend({name='batumi-strike-blue-1', altitude=20000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.patrol.aircraft:extend({name='batumi-patrol-blue-1', altitude=25000, range=25}), + presets.missions.support.awacs:extend({name='batumi-awacs-blue', altitude=30000, freq=257.5}), + presets.missions.support.tanker:extend({name='batumi-tanker-blue', altitude=25000, freq=257, tacan='37', variant="Drogue"}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Batumi.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Mike.lua ]]----------------- + +zones.mike = ZoneCommand:new("Mike") +zones.mike.initialState = { side=1 } +zones.mike.keepActive = true +zones.mike:defineUpgrades({ + [1] = + { + presets.upgrades.basic.tent:extend({ + name='mike-tent-red', + products = { + presets.special.red.infantry:extend({ name='mike-defense-red'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='mike-comcenter-red', + products = { + presets.defenses.red.sam:extend({ name='mike-sam-red'}) + } + }) + }, + [2] = + { + presets.upgrades.basic.tent:extend({ + name='mike-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='mike-defense-blue'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='mike-comcenter-blue', + products = { + presets.defenses.blue.sam:extend({ name='mike-sam-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Mike.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Tyrnyauz.lua ]]----------------- + +zones.tyrnyauz = ZoneCommand:new("Tyrnyauz") +zones.tyrnyauz.initialState = { side=1 } +zones.tyrnyauz.isHeloSpawn = true +zones.tyrnyauz:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='tyrnyauz-tent-red', + products = { + presets.special.red.infantry:extend({ name='tyrnyauz-defense-red'}), + presets.defenses.red.infantry:extend({ name='tyrnyauz-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='tyrnyauz-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='tyrnyauz-supply-red'}), + presets.missions.supply.helo:extend({name='tyrnyauz-supply-red-2'}), + presets.missions.supply.transfer:extend({name='tyrnyauz-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='tyrnyauz-ammo-red', + products = { + presets.missions.attack.surface:extend({name='tyrnyauz-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='tyrnyauz-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='tyrnyauz-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='tyrnyauz-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='tyrnyauz-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='tyrnyauz-supply-blue'}), + presets.missions.supply.helo:extend({name='tyrnyauz-supply-blue-2'}), + presets.missions.supply.transfer:extend({name='tyrnyauz-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='tyrnyauz-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='tyrnyauz-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Tyrnyauz.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/India.lua ]]----------------- + +zones.india = ZoneCommand:new("India") +zones.india.initialState = nil +zones.india:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='india-tent-red', + products = { + presets.special.red.infantry:extend({ name='india-defense-red'}), + presets.defenses.red.infantry:extend({ name='india-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='india-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='india-supply-red'}), + presets.missions.supply.transfer:extend({name='india-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='india-ammo-red', + products = { + presets.missions.attack.surface:extend({name='india-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='india-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='india-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='india-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='india-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='india-supply-blue'}), + presets.missions.supply.transfer:extend({name='india-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='india-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='india-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/India.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/IntelCenter.lua ]]----------------- + +zones.intelcenter = ZoneCommand:new("Intel Center") +zones.intelcenter.initialState = { side=1 } +zones.intelcenter:defineUpgrades({ + [1] = + { + presets.upgrades.basic.tent:extend({ + name='intelcenter-tent-red', + products = { + presets.special.red.infantry:extend({ name='intelcenter-defense-red'}), + presets.defenses.red.infantry:extend({ name='intelcenter-garrison-red'}) + } + }), + presets.upgrades.supply.hq:extend({ + name='intelcenter-hq-red', + products = { + presets.missions.supply.convoy:extend({ name='intelcenter-supply-red'}), + presets.missions.supply.convoy:extend({ name='intelcenter-supply-red-1'}), + presets.missions.supply.transfer:extend({name='intelcenter-transfer-red'}) + } + }), + presets.upgrades.supply.antenna:extend({ + name='intelcenter-antenna-red', + products = { + } + }), + presets.upgrades.supply.antenna:extend({ + name='intelcenter-antenna-red-1', + products = { + } + }), + presets.upgrades.supply.antenna:extend({ + name='intelcenter-antenna-red-2', + products = { + } + }) + }, + [2] = + { + presets.upgrades.basic.tent:extend({ + name='intelcenter-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='intelcenter-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='intelcenter-garrison-blue'}) + } + }), + presets.upgrades.supply.hq:extend({ + name='intelcenter-hq-blue', + products = { + presets.missions.supply.convoy:extend({ name='intelcenter-supply-blue'}), + presets.missions.supply.convoy:extend({ name='intelcenter-supply-blue-1'}), + presets.missions.supply.transfer:extend({name='intelcenter-transfer-blue'}) + } + }), + presets.upgrades.supply.antenna:extend({ + name='intelcenter-antenna-blue', + products = { + } + }), + presets.upgrades.supply.antenna:extend({ + name='intelcenter-antenna-blue-1', + products = { + } + }), + presets.upgrades.supply.antenna:extend({ + name='intelcenter-antenna-blue-2', + products = { + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/IntelCenter.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Mineralnye.lua ]]----------------- + +zones.mineralnye = ZoneCommand:new("Mineralnye") +zones.mineralnye.initialState = { side=1 } +zones.mineralnye.keepActive = true +zones.mineralnye.isHeloSpawn = true +zones.mineralnye.isPlaneSpawn = true +zones.mineralnye:defineUpgrades({ + [1] = + { + presets.upgrades.basic.comPost:extend({ + name='mineralnye-compost-red', + products = { + presets.special.red.infantry:extend({ name='mineralnye-defense-red'}), + presets.defenses.red.infantry:extend({ name='mineralnye-garrison-red'}) + } + }), + presets.upgrades.supply.fuelTank:extend({ + name='mineralnye-fuel-red', + products = { + presets.missions.supply.helo:extend({name='mineralnye-supply-red'}), + presets.missions.supply.helo:extend({name='mineralnye-supply-red-1'}), + presets.missions.supply.transfer:extend({name='mineralnye-transfer-red'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='mineralnye-comcenter-red', + products = { + presets.defenses.red.sa11:extend({ name='mineralnye-airdef-red'}), + presets.missions.attack.cas:extend({name='mineralnye-cas-red', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.bai:extend({name='mineralnye-cas-red', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.strike:extend({name='mineralnye-strike-red', altitude=30000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.attack.strike:extend({name='mineralnye-strike-red-1', altitude=30000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.patrol.aircraft:extend({name='mineralnye-patrol-red', altitude=25000, range=25}) + } + }) + }, + [2] = + { + presets.upgrades.basic.comPost:extend({ + name='mineralnye-compost-blue', + products = { + presets.special.blue.infantry:extend({ name='mineralnye-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='mineralnye-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelTank:extend({ + name='mineralnye-fuel-blue', + products = { + presets.missions.supply.helo:extend({name='mineralnye-supply-blue'}), + presets.missions.supply.helo:extend({name='mineralnye-supply-blue-1'}), + presets.missions.supply.transfer:extend({name='mineralnye-transfer-blue'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='mineralnye-comcenter-blue', + products = { + presets.defenses.blue.nasams:extend({ name='mineralnye-airdef-blue'}), + presets.missions.attack.cas:extend({name='mineralnye-cas-blue', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.bai:extend({name='mineralnye-cas-blue', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.strike:extend({name='mineralnye-strike-blue', altitude=30000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.attack.strike:extend({name='mineralnye-strike-blue-1', altitude=30000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.patrol.aircraft:extend({name='mineralnye-patrol-blue', altitude=25000, range=25}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Mineralnye.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/PowerPlant.lua ]]----------------- + +zones.powerplant = ZoneCommand:new("Power Plant") +zones.powerplant.initialState = { side=1 } +zones.powerplant:defineUpgrades({ + [1] = + { + presets.upgrades.basic.tent:extend({ + name='powerplant-tent-red', + products = { + presets.special.red.infantry:extend({ name='powerplant-defense-red'}), + presets.defenses.red.infantry:extend({ name='powerplant-garrison-red'}) + } + }), + presets.upgrades.supply.powerplant1:extend({ + name='powerplant-building-red-1', + products = { + presets.missions.supply.convoy:extend({ name='powerplant-supply-red'}), + presets.missions.supply.transfer:extend({name='powerplant-transfer-red'}) + } + }), + presets.upgrades.supply.powerplant2:extend({ + name='powerplant-building-red-2', + products = { + presets.missions.supply.convoy:extend({ name='powerplant-supply-red-1'}), + presets.missions.supply.transfer:extend({name='powerplant-transfer-red'}) + } + }) + }, + [2] = + { + presets.upgrades.basic.tent:extend({ + name='powerplant-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='powerplant-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='powerplant-garrison-blue'}) + } + }), + presets.upgrades.supply.powerplant1:extend({ + name='powerplant-building-blue-1', + products = { + presets.missions.supply.convoy:extend({ name='powerplant-supply-blue'}), + presets.missions.supply.transfer:extend({name='powerplant-transfer-blue'}) + } + }), + presets.upgrades.supply.powerplant2:extend({ + name='powerplant-building-blue-2', + products = { + presets.missions.supply.convoy:extend({ name='powerplant-supply-blue-1'}), + presets.missions.supply.transfer:extend({name='powerplant-transfer-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/PowerPlant.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Zugdidi.lua ]]----------------- + +zones.zugdidi = ZoneCommand:new("Zugdidi") +zones.zugdidi.initialState = { side=1 } +zones.zugdidi:defineUpgrades({ + [1] = + { + presets.upgrades.basic.comPost:extend({ + name='zugdidi-compost-red', + products = { + presets.missions.supply.transfer:extend({name='zugdidi-transfer-red'}), + presets.special.red.infantry:extend({ name='zugdidi-defense-red'}), + presets.defenses.red.infantry:extend({ name='zugdidi-garrison-red'}), + presets.missions.attack.surface:extend({name='zugdidi-attack-red'}), + presets.missions.supply.convoy:extend({name='zugdidi-supply-red'}) + } + }), + presets.upgrades.supply.hangar:extend({ + name='zugdidi-hangar-red-1', + products = { + presets.missions.supply.helo:extend({name='zugdidi-supply-red-1'}) + } + }), + presets.upgrades.supply.hangar:extend({ + name='zugdidi-hangar-red-2', + products = { + presets.missions.supply.helo:extend({name='zugdidi-supply-red-2'}) + } + }), + presets.upgrades.supply.hangar:extend({ + name='zugdidi-hangar-red-3', + products = { + presets.missions.supply.helo:extend({name='zugdidi-supply-red-3'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='zugdidi-comcenter-red', + products = { + presets.defenses.red.sa6:extend({ name='zugdidi-sam-red'}) + } + }) + }, + [2] = + { + presets.upgrades.basic.tent:extend({ + name='zugdidi-compost-blue', + products = { + presets.missions.supply.transfer:extend({name='zugdidi-transfer-blue'}), + presets.special.blue.infantry:extend({ name='zugdidi-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='zugdidi-garrison-blue'}), + presets.missions.attack.surface:extend({name='zugdidi-attack-blue'}), + presets.missions.supply.convoy:extend({name='zugdidi-supply-blue'}) + } + }), + presets.upgrades.supply.hangar:extend({ + name='zugdidi-hangar-blue-1', + products = { + presets.missions.supply.helo:extend({name='zugdidi-supply-blue-1'}) + } + }), + presets.upgrades.supply.hangar:extend({ + name='zugdidi-hangar-blue-2', + products = { + presets.missions.supply.helo:extend({name='zugdidi-supply-blue-2'}) + } + }), + presets.upgrades.supply.hangar:extend({ + name='zugdidi-hangar-blue-3', + products = { + presets.missions.supply.helo:extend({name='zugdidi-supply-blue-3'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='zugdidi-comcenter-blue', + products = { + presets.defenses.blue.shorad:extend({ name='zugdidi-sam-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Zugdidi.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Babugent.lua ]]----------------- + +zones.babugent = ZoneCommand:new("Babugent") +zones.babugent.initialState = { side=1 } +zones.babugent:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='babugent-tent-red', + products = { + presets.special.red.infantry:extend({ name='babugent-defense-red'}), + presets.defenses.red.infantry:extend({ name='babugent-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='babugent-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='babugent-supply-red'}), + presets.missions.supply.helo:extend({name='babugent-supply-red-2'}), + presets.missions.supply.transfer:extend({name='babugent-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='babugent-ammo-red', + products = { + presets.missions.attack.surface:extend({name='babugent-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='babugent-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='babugent-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='babugent-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='babugent-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='babugent-supply-blue'}), + presets.missions.supply.helo:extend({name='babugent-supply-blue-2'}), + presets.missions.supply.transfer:extend({name='babugent-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='babugent-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='babugent-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Babugent.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Kislovodsk.lua ]]----------------- + +zones.kislovodsk = ZoneCommand:new("Kislovodsk") +zones.kislovodsk.initialState = { side=1 } +zones.kislovodsk:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='kislovodsk-tent-red', + products = { + presets.special.red.infantry:extend({ name='kislovodsk-defense-red'}), + presets.defenses.red.infantry:extend({ name='kislovodsk-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='kislovodsk-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='kislovodsk-supply-red'}), + presets.missions.supply.transfer:extend({name='kislovodsk-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='kislovodsk-ammo-red', + products = { + presets.missions.attack.surface:extend({name='kislovodsk-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='kislovodsk-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='kislovodsk-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='kislovodsk-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='kislovodsk-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='kislovodsk-supply-blue'}), + presets.missions.supply.transfer:extend({name='kislovodsk-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='kislovodsk-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='kislovodsk-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Kislovodsk.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Gudauta.lua ]]----------------- + +zones.gudauta = ZoneCommand:new("Gudauta") +zones.gudauta.initialState = { side=1 } +zones.gudauta.keepActive = true +zones.gudauta.maxResource = 50000 +zones.gudauta:defineUpgrades({ + [1] = + { + presets.upgrades.basic.comPost:extend({ + name='gudauta-compost-red', + products = { + presets.special.red.infantry:extend({ name='gudauta-defense-red'}), + presets.defenses.red.infantry:extend({ name='gudauta-garrison-red'}) + } + }), + presets.upgrades.supply.fuelTank:extend({ + name='gudauta-fuel-red', + products = { + presets.missions.supply.helo:extend({name='gudauta-supply-red'}), + presets.missions.supply.helo:extend({name='gudauta-supply-red-1'}), + presets.missions.supply.transfer:extend({name='gudauta-transfer-red'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='gudauta-comcenter-red', + products = { + presets.defenses.red.sam:extend({ name='gudauta-airdef-red'}), + presets.missions.attack.sead:extend({name='gudauta-sead-red', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.attack.sead:extend({name='gudauta-sead-red-1', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.attack.cas:extend({name='gudauta-cas-red', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.bai:extend({name='gudauta-cas-red', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.patrol.aircraft:extend({name='gudauta-patrol-red', altitude=25000, range=25}) + } + }) + }, + [2] = + { + presets.upgrades.basic.comPost:extend({ + name='gudauta-compost-blue', + products = { + presets.special.blue.infantry:extend({ name='gudauta-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='gudauta-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelTank:extend({ + name='gudauta-fuel-blue', + products = { + presets.missions.supply.helo:extend({name='gudauta-supply-blue'}), + presets.missions.supply.helo:extend({name='gudauta-supply-blue-1'}), + presets.missions.supply.transfer:extend({name='gudauta-transfer-blue'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='gudauta-comcenter-blue', + products = { + presets.defenses.blue.sam:extend({ name='gudauta-airdef-blue'}), + presets.missions.attack.sead:extend({name='gudauta-sead-blue', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.attack.sead:extend({name='gudauta-sead-blue-1', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.attack.cas:extend({name='gudauta-cas-blue', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.bai:extend({name='gudauta-cas-blue', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.patrol.aircraft:extend({name='gudauta-patrol-blue', altitude=25000, range=25}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Gudauta.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Distillery.lua ]]----------------- + +zones.distillery = ZoneCommand:new("Distillery") +zones.distillery.initialState = { side=1 } +zones.distillery:defineUpgrades({ + [1] = + { + presets.upgrades.basic.tent:extend({ + name='distillery-tent-red', + products = { + presets.special.red.infantry:extend({ name='distillery-defense-red'}), + presets.defenses.red.infantry:extend({ name='distillery-garrison-red'}) + } + }), + presets.upgrades.supply.factory1:extend({ + name='distillery-prod-red-1', + products = { + presets.missions.supply.convoy:extend({ name='distillery-supply-red-1'}), + presets.missions.supply.transfer:extend({name='distillery-transfer-red'}) + } + }), + presets.upgrades.supply.factory2:extend({ + name='distillery-prod-red-2', + products = { + presets.missions.supply.convoy:extend({ name='distillery-supply-red-2', cost=2000}), + presets.missions.supply.transfer:extend({name='distillery-transfer-red2'}) + } + }), + presets.upgrades.supply.factoryTank:extend({ + name='distillery-tank-red-1', + products = { + } + }), + presets.upgrades.supply.factoryTank:extend({ + name='distillery-tank-red-2', + products = { + } + }), + presets.upgrades.supply.factoryTank:extend({ + name='distillery-tank-red-3', + products = { + } + }) + }, + [2] = + { + presets.upgrades.basic.tent:extend({ + name='distillery-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='distillery-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='distillery-garrison-blue'}) + } + }), + presets.upgrades.supply.factory1:extend({ + name='distillery-prod-blue-1', + products = { + presets.missions.supply.convoy:extend({ name='distillery-supply-blue-1'}), + presets.missions.supply.transfer:extend({name='distillery-transfer-blue'}) + } + }), + presets.upgrades.supply.factory2:extend({ + name='distillery-prod-blue-2', + products = { + presets.missions.supply.convoy:extend({ name='distillery-supply-blue-2', cost=2000}), + presets.missions.supply.transfer:extend({name='distillery-transfer-blue2'}) + } + }), + presets.upgrades.supply.factoryTank:extend({ + name='distillery-tank-blue-1', + products = { + } + }), + presets.upgrades.supply.factoryTank:extend({ + name='distillery-tank-blue-2', + products = { + } + }), + presets.upgrades.supply.factoryTank:extend({ + name='distillery-tank-blue-3', + products = { + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Distillery.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Sochi.lua ]]----------------- + +zones.sochi = ZoneCommand:new("Sochi") +zones.sochi.initialState = { side=1 } +zones.sochi.keepActive = true +zones.sochi.maxResource = 50000 +zones.sochi:defineUpgrades({ + [1] = + { + presets.upgrades.basic.comPost:extend({ + name='sochi-compost-red', + products = { + presets.special.red.infantry:extend({ name='sochi-defense-red'}), + presets.defenses.red.infantry:extend({ name='sochi-garrison-red'}) + } + }), + presets.upgrades.supply.fuelTank:extend({ + name='sochi-fuel-red', + products = { + presets.missions.supply.helo:extend({name='sochi-supply-red-1'}), + presets.missions.supply.helo:extend({name='sochi-supply-red-2'}), + presets.missions.supply.convoy_escorted:extend({name='sochi-supply-red-3'}), + presets.missions.supply.transfer:extend({name='sochi-transfer-red'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='sochi-comcenter-red', + products = { + presets.defenses.red.sa10:extend({ name='sochi-airdef-red'}), + presets.missions.attack.sead:extend({name='sochi-sead-red', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.attack.strike:extend({name='sochi-strike-red', altitude=30000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.patrol.aircraft:extend({name='sochi-patrol-red', altitude=25000, range=25}), + presets.missions.patrol.aircraft:extend({name='sochi-patrol-red-1', altitude=25000, range=25}), + presets.missions.attack.cas:extend({name='sochi-cas-red', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.bai:extend({name='sochi-cas-red', altitude=10000, expend=AI.Task.WeaponExpend.ONE}) + } + }) + }, + [2] = + { + presets.upgrades.basic.comPost:extend({ + name='sochi-compost-blue', + products = { + presets.special.blue.infantry:extend({ name='sochi-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='sochi-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelTank:extend({ + name='sochi-fuel-blue', + products = { + presets.missions.supply.helo:extend({name='sochi-supply-blue-1'}), + presets.missions.supply.helo:extend({name='sochi-supply-blue-2'}), + presets.missions.supply.convoy_escorted:extend({name='sochi-supply-blue-3'}), + presets.missions.supply.transfer:extend({name='sochi-transfer-blue'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='sochi-comcenter-blue', + products = { + presets.defenses.blue.patriot:extend({ name='sochi-airdef-blue'}), + presets.missions.attack.sead:extend({name='sochi-sead-blue', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.attack.strike:extend({name='sochi-strike-blue', altitude=30000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.patrol.aircraft:extend({name='sochi-patrol-blue', altitude=25000, range=25}), + presets.missions.attack.cas:extend({name='sochi-cas-blue', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.bai:extend({name='sochi-cas-blue', altitude=10000, expend=AI.Task.WeaponExpend.ONE}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Sochi.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Golf.lua ]]----------------- + +zones.golf = ZoneCommand:new("Golf") +zones.golf.initialState = nil +zones.golf.isHeloSpawn = true +zones.golf:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='golf-tent-red', + products = { + presets.special.red.infantry:extend({ name='golf-defense-red'}), + presets.defenses.red.infantry:extend({ name='golf-garrison-red'}) + } + }), + presets.upgrades.supply.fuelTank:extend({ + name='golf-fuel-red', + products = { + presets.missions.supply.helo:extend({name='golf-supply-red'}), + presets.missions.supply.helo:extend({name='golf-supply-red-1'}), + presets.missions.supply.transfer:extend({name='golf-transfer-red'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='golf-comcenter-red', + products = { + presets.defenses.red.shorad:extend({name='golf-sam-red'}), + presets.missions.attack.helo:extend({name='golf-cas-red', altitude=200, expend=AI.Task.WeaponExpend.HALF }) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='golf-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='golf-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='golf-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelTankFarp:extend({ + name='golf-fuel-blue', + products = { + presets.missions.supply.helo:extend({name='golf-supply-blue'}), + presets.missions.supply.helo:extend({name='golf-supply-blue-1'}), + presets.missions.supply.transfer:extend({name='golf-transfer-blue'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='golf-comcenter-blue', + products = { + presets.defenses.blue.shorad:extend({name='golf-sam-blue'}), + presets.missions.attack.helo:extend({name='golf-cas-blue', altitude=200, expend=AI.Task.WeaponExpend.HALF }) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Golf.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Charlie.lua ]]----------------- + +zones.charlie = ZoneCommand:new("Charlie") +zones.charlie.initialState = { side=2 } +zones.charlie.keepActive = true +zones.charlie:defineUpgrades({ + [1] = + { + presets.upgrades.basic.tent:extend({ + name='charlie-tent-red', + products = { + presets.special.red.infantry:extend({ name='charlie-defense-red'}), + presets.defenses.red.infantry:extend({ name='charlie-garrison-red'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='charlie-comcenter-red', + products = { + presets.defenses.red.sam:extend({ name='charlie-sam-red'}) + } + }) + }, + [2] = + { + presets.upgrades.basic.tent:extend({ + name='charlie-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='charlie-defense-red'}), + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='charlie-comcenter-blue', + products = { + presets.defenses.blue.sam:extend({ name='charlie-sam-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Charlie.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Lentehi.lua ]]----------------- + +zones.lentehi = ZoneCommand:new("Lentehi") +zones.lentehi.initialState = { side=1 } +zones.lentehi:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='lentehi-tent-red', + products = { + presets.special.red.infantry:extend({ name='lentehi-defense-red'}), + presets.defenses.red.infantry:extend({ name='lentehi-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='lentehi-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='lentehi-supply-red'}), + presets.missions.supply.helo:extend({name='lentehi-supply-red-2'}), + presets.missions.supply.transfer:extend({name='lentehi-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='lentehi-ammo-red', + products = { + presets.missions.attack.surface:extend({name='lentehi-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='lentehi-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='lentehi-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='lentehi-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='lentehi-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='lentehi-supply-blue'}), + presets.missions.supply.helo:extend({name='lentehi-supply-blue-2'}), + presets.missions.supply.transfer:extend({name='lentehi-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='lentehi-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='lentehi-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Lentehi.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Refinery.lua ]]----------------- + +zones.refinery = ZoneCommand:new("Refinery") +zones.refinery.initialState = { side=1 } +zones.refinery:defineUpgrades({ + [1] = + { + presets.upgrades.basic.tent:extend({ + name='refinery-tent-red', + products = { + presets.special.red.infantry:extend({ name='refinery-defense-red'}), + presets.defenses.red.infantry:extend({ name='refinery-garrison-red'}) + } + }), + presets.upgrades.supply.refinery1:extend({ + name='refinery-building-red', + products = { + presets.missions.supply.convoy:extend({ name='refinery-supply-red'}), + presets.missions.supply.convoy:extend({ name='refinery-supply-red-1'}), + presets.missions.supply.helo:extend({ name='refinery-supply-red-2'}), + presets.missions.supply.transfer:extend({name='refinery-transfer-red'}) + } + }) + }, + [2] = + { + presets.upgrades.basic.tent:extend({ + name='refinery-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='refinery-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='refinery-garrison-blue'}) + } + }), + presets.upgrades.supply.refinery1:extend({ + name='refinery-building-blue', + products = { + presets.missions.supply.convoy:extend({ name='refinery-supply-blue'}), + presets.missions.supply.convoy:extend({ name='refinery-supply-blue-1'}), + presets.missions.supply.helo:extend({ name='refinery-supply-blue-2'}), + presets.missions.supply.transfer:extend({name='refinery-transfer-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Refinery.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Mozdok.lua ]]----------------- + +zones.mozdok = ZoneCommand:new("Mozdok") +zones.mozdok.initialState = { side=1 } +zones.mozdok.keepActive = true +zones.mozdok.maxResource = 50000 +zones.mozdok:defineUpgrades({ + [1] = + { + presets.upgrades.basic.comPost:extend({ + name='mozdok-compost-red', + products = { + presets.special.red.infantry:extend({ name='mozdok-defense-red'}), + presets.defenses.red.infantry:extend({ name='mozdok-garrison-red'}) + } + }), + presets.upgrades.supply.fuelTank:extend({ + name='mozdok-fuel-red', + products = { + presets.missions.supply.helo:extend({name='mozdok-supply-red-1'}), + presets.missions.supply.helo:extend({name='mozdok-supply-red-2'}), + presets.missions.supply.convoy_escorted:extend({name='mozdok-supply-red-3'}), + presets.missions.supply.transfer:extend({name='mozdok-transfer-red'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='mozdok-comcenter-red', + products = { + presets.defenses.red.sa10:extend({ name='mozdok-airdef-red'}), + presets.missions.patrol.aircraft:extend({name='mozdok-patrol-red', altitude=25000, range=25}), + presets.missions.attack.cas:extend({name='mozdok-cas-red', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.bai:extend({name='mozdok-cas-red', altitude=10000, expend=AI.Task.WeaponExpend.ONE}) + } + }) + }, + [2] = + { + presets.upgrades.basic.comPost:extend({ + name='mozdok-compost-blue', + products = { + presets.special.blue.infantry:extend({ name='mozdok-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='mozdok-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelTank:extend({ + name='mozdok-fuel-blue', + products = { + presets.missions.supply.helo:extend({name='mozdok-supply-blue-1'}), + presets.missions.supply.helo:extend({name='mozdok-supply-blue-2'}), + presets.missions.supply.convoy_escorted:extend({name='mozdok-supply-blue-3'}), + presets.missions.supply.transfer:extend({name='mozdok-transfer-blue'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='mozdok-comcenter-blue', + products = { + presets.defenses.blue.shorad:extend({ name='mozdok-airdef-blue'}), + presets.missions.patrol.aircraft:extend({name='mozdok-patrol-blue', altitude=25000, range=25}), + presets.missions.attack.cas:extend({name='mozdok-cas-blue', altitude=15000, cost=50, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.cas:extend({name='mozdok-cas-blue-1', altitude=15000, cost=50, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.bai:extend({name='mozdok-cas-blue', altitude=15000, cost=50, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.bai:extend({name='mozdok-cas-blue-1', altitude=15000, cost=50, expend=AI.Task.WeaponExpend.ONE}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Mozdok.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Lima.lua ]]----------------- + +zones.lima = ZoneCommand:new("Lima") +zones.lima.initialState = { side=1 } +zones.lima:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='lima-tent-red', + products = { + presets.special.red.infantry:extend({ name='lima-defense-red'}), + presets.defenses.red.infantry:extend({ name='lima-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='lima-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='lima-supply-red'}), + presets.missions.supply.helo:extend({name='lima-supply-red-1'}), + presets.missions.supply.transfer:extend({name='lima-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='lima-ammo-red', + products = { + presets.missions.attack.surface:extend({name='lima-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='lima-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='lima-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='lima-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='lima-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='lima-supply-blue'}), + presets.missions.supply.helo:extend({name='lima-supply-blue-1'}), + presets.missions.supply.transfer:extend({name='lima-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='lima-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='lima-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Lima.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Oscar.lua ]]----------------- + +zones.oscar = ZoneCommand:new("Oscar") +zones.oscar.initialState = { side=1 } +zones.oscar:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='oscar-tent-red', + products = { + presets.special.red.infantry:extend({ name='oscar-defense-red'}), + presets.defenses.red.infantry:extend({ name='oscar-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='oscar-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='oscar-supply-red'}), + presets.missions.supply.transfer:extend({name='oscar-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='oscar-ammo-red', + products = { + presets.missions.attack.surface:extend({name='oscar-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='oscar-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='oscar-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='oscar-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='oscar-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='oscar-supply-blue'}), + presets.missions.supply.transfer:extend({name='oscar-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='oscar-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='oscar-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Oscar.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Nalchik.lua ]]----------------- + +zones.nalchik = ZoneCommand:new("Nalchik") +zones.nalchik.initialState = { side=1 } +zones.nalchik.keepActive = true +zones.nalchik.isHeloSpawn = true +zones.nalchik.isPlaneSpawn = true +zones.nalchik:defineUpgrades({ + [1] = + { + presets.upgrades.basic.comPost:extend({ + name='nalchik-compost-red', + products = { + presets.special.red.infantry:extend({ name='nalchik-defense-red'}), + presets.defenses.red.infantry:extend({ name='nalchik-garrison-red'}) + } + }), + presets.upgrades.supply.fuelTank:extend({ + name='nalchik-fuel-red', + products = { + presets.missions.supply.helo:extend({name='nalchik-supply-red-1'}), + presets.missions.supply.helo:extend({name='nalchik-supply-red-2'}), + presets.missions.supply.transfer:extend({name='nalchik-transfer-red'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='nalchik-comcenter-red', + products = { + presets.defenses.red.sa3:extend({ name='nalchik-airdef-red'}), + presets.missions.attack.sead:extend({name='nalchik-sead-red', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.attack.cas:extend({name='nalchik-cas-red', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.bai:extend({name='nalchik-cas-red', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.strike:extend({name='nalchik-strike-red', altitude=30000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.patrol.aircraft:extend({name='nalchik-patrol-red', altitude=25000, range=25}), + presets.missions.patrol.aircraft:extend({name='nalchik-patrol-red-2', altitude=25000, range=25}), + presets.missions.support.awacs:extend({name='nalchik-awacs-red', altitude=30000, freq=251.2}), + presets.missions.support.tanker:extend({name='nalchik-tanker-red', altitude=30000, freq=252.2, tacan='40', variant='Drogue'}) + } + }) + }, + [2] = + { + presets.upgrades.basic.comPost:extend({ + name='nalchik-compost-blue', + products = { + presets.special.blue.infantry:extend({ name='nalchik-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='nalchik-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelTank:extend({ + name='nalchik-fuel-blue', + products = { + presets.missions.supply.helo:extend({name='nalchik-supply-blue-1'}), + presets.missions.supply.helo:extend({name='nalchik-supply-blue-2'}), + presets.missions.supply.transfer:extend({name='nalchik-transfer-blue'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='nalchik-comcenter-blue', + products = { + presets.defenses.blue.nasams:extend({ name='nalchik-airdef-blue'}), + presets.missions.support.awacs:extend({name='nalchik-awacs-blue', altitude=30000, freq=259.5}), + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Nalchik.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Digora.lua ]]----------------- + +zones.digora = ZoneCommand:new("Digora") +zones.digora.initialState = { side=1 } +zones.digora:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='digora-tent-red', + products = { + presets.special.red.infantry:extend({ name='digora-defense-red'}), + presets.defenses.red.infantry:extend({ name='digora-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='digora-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='digora-supply-red'}), + presets.missions.supply.transfer:extend({name='digora-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='digora-ammo-red', + products = { + presets.missions.attack.surface:extend({name='digora-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='digora-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='digora-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='digora-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='digora-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='digora-supply-blue'}), + presets.missions.supply.transfer:extend({name='digora-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='digora-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='digora-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Digora.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Uniform.lua ]]----------------- + +zones.uniform = ZoneCommand:new("Uniform") +zones.uniform.initialState = { side=1 } +zones.uniform:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='uniform-tent-red', + products = { + presets.special.red.infantry:extend({ name='uniform-defense-red'}), + presets.defenses.red.infantry:extend({ name='uniform-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='uniform-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='uniform-supply-red'}), + presets.missions.supply.transfer:extend({name='uniform-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='uniform-ammo-red', + products = { + presets.missions.attack.surface:extend({name='uniform-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='uniform-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='uniform-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='uniform-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='uniform-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='uniform-supply-blue'}), + presets.missions.supply.transfer:extend({name='uniform-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='uniform-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='uniform-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Uniform.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Factory.lua ]]----------------- + +zones.factory = ZoneCommand:new("Factory") +zones.factory.initialState = { side=2 } +zones.factory:defineUpgrades({ + [1] = + { + presets.upgrades.basic.tent:extend({ + name='factory-tent-red', + products = { + presets.special.red.infantry:extend({ name='factory-defense-red'}), + presets.defenses.red.infantry:extend({ name='factory-garrison-red'}) + } + }), + presets.upgrades.supply.factory1:extend({ + name='factory-prod-red-1', + products = { + presets.missions.supply.convoy:extend({ name='factory-supply-red-1'}), + presets.missions.supply.transfer:extend({name='factory-transfer-red'}) + } + }), + presets.upgrades.supply.factory2:extend({ + name='factory-prod-red-2', + products = { + presets.missions.supply.convoy:extend({ name='factory-supply-red-2', cost=2000}), + presets.missions.supply.transfer:extend({name='factory-transfer-red2'}) + } + }), + presets.upgrades.supply.factoryTank:extend({ + name='factory-tank-red-1', + products = { + } + }), + presets.upgrades.supply.factoryTank:extend({ + name='factory-tank-red-2', + products = { + } + }), + presets.upgrades.supply.factoryTank:extend({ + name='factory-tank-red-3', + products = { + } + }) + }, + [2] = + { + presets.upgrades.basic.tent:extend({ + name='factory-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='factory-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='factory-garrison-blue'}) + } + }), + presets.upgrades.supply.factory1:extend({ + name='factory-prod-blue-1', + products = { + presets.missions.supply.convoy:extend({ name='factory-supply-blue-1'}), + presets.missions.supply.transfer:extend({name='factory-transfer-blue'}) + } + }), + presets.upgrades.supply.factory2:extend({ + name='factory-prod-blue-2', + products = { + presets.missions.supply.convoy:extend({ name='factory-supply-blue-2', cost=2000}), + presets.missions.supply.transfer:extend({name='factory-transfer-blue2'}) + } + }), + presets.upgrades.supply.factoryTank:extend({ + name='factory-tank-blue-1', + products = { + } + }), + presets.upgrades.supply.factoryTank:extend({ + name='factory-tank-blue-2', + products = { + } + }), + presets.upgrades.supply.factoryTank:extend({ + name='factory-tank-blue-3', + products = { + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Factory.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Senaki.lua ]]----------------- + +zones.senaki = ZoneCommand:new("Senaki") +zones.senaki.initialState = { side=1 } +zones.senaki.keepActive = true +zones.senaki.isHeloSpawn = true +zones.senaki.isPlaneSpawn = true +zones.senaki.maxResource = 50000 +zones.senaki:defineUpgrades({ + [1] = + { + presets.upgrades.basic.comPost:extend({ + name='senaki-compost-red', + products = { + presets.special.red.infantry:extend({ name='senaki-defense-red'}), + presets.defenses.red.infantry:extend({ name='senaki-garrison-red'}) + } + }), + presets.upgrades.supply.fuelTank:extend({ + name='senaki-fuel-red', + products = { + presets.missions.supply.helo:extend({name='senaki-supply-red-1'}), + presets.missions.supply.helo:extend({name='senaki-supply-red-2'}), + presets.missions.supply.transfer:extend({name='senaki-transfer-red'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='senaki-comcenter-red', + products = { + presets.defenses.red.sa3:extend({ name='senaki-airdef-red'}), + presets.missions.attack.sead:extend({name='senaki-sead-red', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.attack.cas:extend({name='senaki-cas-red', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.bai:extend({name='senaki-cas-red', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.strike:extend({name='senaki-strike-red', altitude=20000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.patrol.aircraft:extend({name='senaki-patrol-red', altitude=25000, range=25}), + presets.missions.patrol.aircraft:extend({name='senaki-patrol-red-2', altitude=20000, range=25}) + } + }) + }, + [2] = + { + presets.upgrades.basic.comPost:extend({ + name='senaki-compost-blue', + products = { + presets.special.blue.infantry:extend({ name='senaki-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='senaki-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelTank:extend({ + name='senaki-fuel-blue', + products = { + presets.missions.supply.helo:extend({name='senaki-supply-blue-1'}), + presets.missions.supply.helo:extend({name='senaki-supply-blue-2'}), + presets.missions.supply.transfer:extend({name='senaki-transfer-blue'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='senaki-comcenter-blue', + products = { + presets.defenses.blue.shorad:extend({ name='senaki-airdef-blue'}), + presets.missions.attack.sead:extend({name='senaki-sead-blue', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.attack.cas:extend({name='senaki-cas-blue', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.bai:extend({name='senaki-cas-blue', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.strike:extend({name='senaki-strike-blue', altitude=20000, expend=AI.Task.WeaponExpend.TWO}), + presets.missions.patrol.aircraft:extend({name='senaki-patrol-blue', altitude=25000, range=25}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Senaki.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Kutaisi.lua ]]----------------- + +zones.kutaisi = ZoneCommand:new("Kutaisi") +zones.kutaisi.initialState = { side=1 } +zones.kutaisi.keepActive = true +zones.kutaisi.maxResource = 50000 +zones.kutaisi:defineUpgrades({ + [1] = + { + presets.upgrades.basic.comPost:extend({ + name='kutaisi-compost-red', + products = { + presets.special.red.infantry:extend({ name='kutaisi-defense-red'}), + presets.defenses.red.infantry:extend({ name='kutaisi-garrison-red'}) + } + }), + presets.upgrades.supply.fuelTank:extend({ + name='kutaisi-fuel-red', + products = { + presets.missions.supply.helo:extend({name='kutaisi-supply-red-1'}), + presets.missions.supply.helo:extend({name='kutaisi-supply-red-2'}), + presets.missions.supply.transfer:extend({name='kutaisi-transfer-red'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='kutaisi-comcenter-red', + products = { + presets.defenses.red.shorad:extend({ name='kutaisi-airdef-red'}), + presets.missions.attack.sead:extend({name='kutaisi-sead-red', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.attack.cas:extend({name='kutaisi-cas-red', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.bai:extend({name='kutaisi-cas-red', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.strike:extend({name='kutaisi-strike-red', altitude=20000, expend=AI.Task.WeaponExpend.HALF}), + presets.missions.patrol.aircraft:extend({name='kutaisi-patrol-red', altitude=25000, range=25}), + presets.missions.patrol.aircraft:extend({name='kutaisi-patrol-red-2', altitude=25000, range=25}) + } + }) + }, + [2] = + { + presets.upgrades.basic.comPost:extend({ + name='kutaisi-compost-blue', + products = { + presets.special.blue.infantry:extend({ name='kutaisi-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='kutaisi-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelTank:extend({ + name='kutaisi-fuel-blue', + products = { + presets.missions.supply.helo:extend({name='kutaisi-supply-blue-1'}), + presets.missions.supply.helo:extend({name='kutaisi-supply-blue-2'}), + presets.missions.supply.transfer:extend({name='kutaisi-transfer-blue'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='kutaisi-comcenter-blue', + products = { + presets.defenses.blue.shorad:extend({ name='kutaisi-airdef-blue'}), + presets.missions.attack.sead:extend({name='kutaisi-sead-blue', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.attack.cas:extend({name='kutaisi-cas-blue', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.bai:extend({name='kutaisi-cas-blue', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.strike:extend({name='kutaisi-strike-blue', altitude=20000, expend=AI.Task.WeaponExpend.TWO}), + presets.missions.patrol.aircraft:extend({name='kutaisi-patrol-blue', altitude=25000, range=25}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Kutaisi.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Prohladniy.lua ]]----------------- + +zones.prohladniy = ZoneCommand:new("Prohladniy") +zones.prohladniy.initialState = { side=1 } +zones.prohladniy:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='prohladniy-tent-red', + products = { + presets.special.red.infantry:extend({ name='prohladniy-defense-red'}), + presets.defenses.red.infantry:extend({ name='prohladniy-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='prohladniy-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='prohladniy-supply-red'}), + presets.missions.supply.transfer:extend({name='prohladniy-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='prohladniy-ammo-red', + products = { + presets.missions.attack.surface:extend({name='prohladniy-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='prohladniy-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='prohladniy-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='prohladniy-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='prohladniy-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='prohladniy-supply-blue'}), + presets.missions.supply.transfer:extend({name='prohladniy-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='prohladniy-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='prohladniy-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Prohladniy.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Tallyk.lua ]]----------------- + +zones.tallyk = ZoneCommand:new("Tallyk") +zones.tallyk.initialState = { side=1 } +zones.tallyk.keepActive = true +zones.tallyk:defineUpgrades({ + [1] = + { + presets.upgrades.basic.tent:extend({ + name='tallyk-tent-red', + products = { + presets.special.red.infantry:extend({ name='tallyk-defense-red'}), + presets.defenses.red.infantry:extend({ name='tallyk-garrison-red'}), + presets.missions.attack.surface:extend({name='tallyk-assault-red'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='tallyk-comcenter-red', + products = { + presets.defenses.red.sam:extend({ name='tallyk-sam-red'}) + } + }) + }, + [2] = + { + presets.upgrades.basic.tent:extend({ + name='tallyk-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='tallyk-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='tallyk-garrison-blue'}), + presets.missions.attack.surface:extend({name='tallyk-assault-blue'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='tallyk-comcenter-blue', + products = { + presets.defenses.blue.sam:extend({ name='tallyk-sam-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Tallyk.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Terek.lua ]]----------------- + +zones.terek = ZoneCommand:new("Terek") +zones.terek.initialState = { side=1 } +zones.terek:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='terek-tent-red', + products = { + presets.special.red.infantry:extend({ name='terek-defense-red'}), + presets.defenses.red.infantry:extend({ name='terek-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='terek-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='terek-supply-red'}), + presets.missions.supply.transfer:extend({name='terek-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='terek-ammo-red', + products = { + presets.missions.attack.surface:extend({name='terek-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='terek-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='terek-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='terek-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='terek-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='terek-supply-blue'}), + presets.missions.supply.transfer:extend({name='terek-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='terek-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='terek-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Terek.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Humara.lua ]]----------------- + +zones.humara = ZoneCommand:new("Humara") +zones.humara.initialState = { side=1 } +zones.humara:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='humara-tent-red', + products = { + presets.special.red.infantry:extend({ name='humara-defense-red'}), + presets.defenses.red.infantry:extend({ name='humara-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='humara-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='humara-supply-red'}), + presets.missions.supply.transfer:extend({name='humara-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='humara-ammo-red', + products = { + presets.missions.attack.surface:extend({name='humara-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='humara-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='humara-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='humara-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='humara-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='humara-supply-blue'}), + presets.missions.supply.transfer:extend({name='humara-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='humara-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='humara-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Humara.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Ochamchira.lua ]]----------------- + +zones.ochamchira = ZoneCommand:new("Ochamchira") +zones.ochamchira.initialState = { side=1 } +zones.ochamchira:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='ochamchira-tent-red', + products = { + presets.special.red.infantry:extend({ name='ochamchira-defense-red'}), + presets.defenses.red.infantry:extend({ name='ochamchira-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='ochamchira-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='ochamchira-supply-red'}), + presets.missions.supply.transfer:extend({name='ochamchira-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='ochamchira-ammo-red', + products = { + presets.missions.attack.surface:extend({name='ochamchira-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='ochamchira-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='ochamchira-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='ochamchira-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='ochamchira-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='ochamchira-supply-blue'}), + presets.missions.supply.transfer:extend({name='ochamchira-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='ochamchira-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='ochamchira-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Ochamchira.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/November.lua ]]----------------- + +zones.november = ZoneCommand:new("November") +zones.november.initialState = { side=1 } +zones.november.isHeloSpawn = true +zones.november:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='november-tent-red', + products = { + presets.special.red.infantry:extend({ name='november-defense-red'}), + presets.defenses.red.infantry:extend({ name='november-garrison-red'}) + } + }), + presets.upgrades.supply.fuelTankFarp:extend({ + name='november-fuel-red', + products = { + presets.missions.supply.helo:extend({name='november-supply-red'}), + presets.missions.supply.helo:extend({name='november-supply-red-1'}), + presets.missions.supply.transfer:extend({name='november-transfer-red'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='november-comcenter-red', + products = { + presets.defenses.red.shorad:extend({name='november-sam-red'}), + presets.missions.attack.helo:extend({name='november-cas-red', altitude=200, expend=AI.Task.WeaponExpend.HALF }) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='november-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='november-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='november-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelTankFarp:extend({ + name='november-fuel-blue', + products = { + presets.missions.supply.helo:extend({name='november-supply-blue'}), + presets.missions.supply.helo:extend({name='november-supply-blue-1'}), + presets.missions.supply.transfer:extend({name='november-transfer-blue'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='november-comcenter-blue', + products = { + presets.defenses.blue.shorad:extend({name='november-sam-blue'}), + presets.missions.attack.helo:extend({name='november-cas-blue', altitude=200, expend=AI.Task.WeaponExpend.HALF }) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/November.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/XRay.lua ]]----------------- + +zones.xray = ZoneCommand:new("XRay") +zones.xray.initialState = { side=1 } +zones.xray:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='xray-tent-red', + products = { + presets.special.red.infantry:extend({ name='xray-defense-red'}), + presets.defenses.red.infantry:extend({ name='xray-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='xray-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='xray-supply-red'}), + presets.missions.supply.helo:extend({name='xray-supply-red-2'}), + presets.missions.supply.transfer:extend({name='xray-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='xray-ammo-red', + products = { + presets.missions.attack.surface:extend({name='xray-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='xray-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='xray-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='xray-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='xray-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='xray-supply-blue'}), + presets.missions.supply.helo:extend({name='xray-supply-blue-2'}), + presets.missions.supply.transfer:extend({name='xray-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='xray-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='xray-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/XRay.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Whiskey.lua ]]----------------- + +zones.whiskey = ZoneCommand:new("Whiskey") +zones.whiskey.initialState = { side=1 } +zones.whiskey:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='whiskey-tent-red', + products = { + presets.special.red.infantry:extend({ name='whiskey-defense-red'}), + presets.defenses.red.infantry:extend({ name='whiskey-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='whiskey-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='whiskey-supply-red'}), + presets.missions.supply.transfer:extend({name='whiskey-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='whiskey-ammo-red', + products = { + presets.missions.attack.surface:extend({name='whiskey-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='whiskey-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='whiskey-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='whiskey-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='whiskey-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='whiskey-supply-blue'}), + presets.missions.supply.transfer:extend({name='whiskey-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='whiskey-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='whiskey-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Whiskey.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Mine.lua ]]----------------- + +zones.mine = ZoneCommand:new("Mine") +zones.mine.initialState = { side=1 } +zones.mine:defineUpgrades({ + [1] = + { + presets.upgrades.basic.tent:extend({ + name='mine-tent-red', + products = { + presets.special.red.infantry:extend({ name='mine-defense-red'}), + presets.defenses.red.infantry:extend({ name='mine-garrison-red'}) + } + }), + presets.upgrades.supply.excavator:extend({ + name='mine-excavator-red-1', + products = { + presets.missions.supply.convoy:extend({ name='mine-supply-red'}), + presets.missions.supply.transfer:extend({name='mine-transfer-red'}) + } + }), + presets.upgrades.supply.excavator:extend({ + name='mine-excavator-red-2', + products = { + presets.missions.supply.convoy:extend({ name='mine-supply-red'}), + presets.missions.supply.transfer:extend({name='mine-transfer-red'}) + } + }), + presets.upgrades.supply.excavator:extend({ + name='mine-excavator-red-3', + products = { + presets.missions.supply.convoy:extend({ name='mine-supply-red'}), + presets.missions.supply.transfer:extend({name='mine-transfer-red'}) + } + }) + }, + [2] = + { + presets.upgrades.basic.tent:extend({ + name='mine-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='mine-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='mine-garrison-blue'}) + } + }), + presets.upgrades.supply.excavator:extend({ + name='mine-excavator-blue-1', + products = { + presets.missions.supply.convoy:extend({ name='mine-supply-blue'}), + presets.missions.supply.transfer:extend({name='mine-transfer-blue'}) + } + }), + presets.upgrades.supply.excavator:extend({ + name='mine-excavator-blue-2', + products = { + presets.missions.supply.convoy:extend({ name='mine-supply-blue'}), + presets.missions.supply.transfer:extend({name='mine-transfer-blue'}) + } + }), + presets.upgrades.supply.excavator:extend({ + name='mine-excavator-blue-3', + products = { + presets.missions.supply.convoy:extend({ name='mine-supply-blue'}), + presets.missions.supply.transfer:extend({name='mine-transfer-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Mine.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Papa.lua ]]----------------- + +zones.papa = ZoneCommand:new("Papa") +zones.papa.initialState = { side=1 } +zones.papa.keepActive = true +zones.papa:defineUpgrades({ + [1] = + { + presets.upgrades.basic.tent:extend({ + name='papa-tent-red', + products = { + presets.special.red.infantry:extend({ name='papa-defense-red'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='papa-comcenter-red', + products = { + presets.defenses.red.sam:extend({ name='papa-sam-red'}) + } + }) + }, + [2] = + { + presets.upgrades.basic.tent:extend({ + name='papa-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='papa-defense-blue'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='papa-comcenter-blue', + products = { + presets.defenses.blue.sam:extend({ name='papa-sam-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Papa.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Sukhumi.lua ]]----------------- + +zones.sukhumi = ZoneCommand:new("Sukhumi") +zones.sukhumi.initialState = { side=1 } +zones.sukhumi.keepActive = true +zones.sukhumi.maxResource = 50000 +zones.sukhumi:defineUpgrades({ + [1] = + { + presets.upgrades.basic.comPost:extend({ + name='sukhumi-compost-red', + products = { + presets.special.red.infantry:extend({ name='sukhumi-defense-red'}), + presets.defenses.red.infantry:extend({ name='sukhumi-garrison-red'}) + } + }), + presets.upgrades.supply.fuelTank:extend({ + name='sukhumi-fuel-red', + products = { + presets.missions.supply.helo:extend({name='sukhumi-supply-red-1'}), + presets.missions.supply.helo:extend({name='sukhumi-supply-red-2'}), + presets.missions.supply.transfer:extend({name='sukhumi-transfer-red'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='sukhumi-comcenter-red', + products = { + presets.defenses.red.sa11:extend({ name='sukhumi-airdef-red'}), + presets.missions.attack.sead:extend({name='sukhumi-sead-red', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.attack.cas:extend({name='sukhumi-cas-red', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.bai:extend({name='sukhumi-cas-red', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.strike:extend({name='sukhumi-strike-red', altitude=20000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.patrol.aircraft:extend({name='sukhumi-patrol-red', altitude=25000, range=25}), + presets.missions.patrol.aircraft:extend({name='sukhumi-patrol-red-2', altitude=25000, range=25}) + } + }) + }, + [2] = + { + presets.upgrades.basic.comPost:extend({ + name='sukhumi-compost-blue', + products = { + presets.special.blue.infantry:extend({ name='sukhumi-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='sukhumi-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelTank:extend({ + name='sukhumi-fuel-blue', + products = { + presets.missions.supply.helo:extend({name='sukhumi-supply-blue-1'}), + presets.missions.supply.helo:extend({name='sukhumi-supply-blue-2'}), + presets.missions.supply.transfer:extend({name='sukhumi-transfer-blue'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='sukhumi-comcenter-blue', + products = { + presets.defenses.blue.nasams:extend({ name='sukhumi-airdef-blue'}), + presets.missions.attack.sead:extend({name='sukhumi-sead-blue', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.attack.cas:extend({name='sukhumi-cas-blue', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.bai:extend({name='sukhumi-cas-blue', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.strike:extend({name='sukhumi-strike-blue', altitude=20000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.patrol.aircraft:extend({name='sukhumi-patrol-blue', altitude=25000, range=25}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Sukhumi.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Farm.lua ]]----------------- + +zones.farm = ZoneCommand:new("Farm") +zones.farm.initialState = { side=1 } +zones.farm:defineUpgrades({ + [1] = + { + presets.upgrades.basic.tent:extend({ + name='farm-tent-red', + products = { + presets.special.red.infantry:extend({ name='farm-defense-red'}), + presets.defenses.red.infantry:extend({ name='farm-garrison-red'}) + } + }), + presets.upgrades.supply.farm1:extend({ + name='farm-prod-red-1', + products = { + presets.missions.supply.convoy:extend({ name='farm-supply-red'}), + presets.missions.supply.transfer:extend({name='farm-transfer-red'}) + } + }), + presets.upgrades.supply.farm2:extend({ + name='farm-prod-red-2', + products = { + presets.missions.supply.convoy:extend({ name='farm-supply-red'}), + presets.missions.supply.transfer:extend({name='farm-transfer-red'}) + } + }) + }, + [2] = + { + presets.upgrades.basic.tent:extend({ + name='farm-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='farm-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='farm-garrison-blue'}) + } + }), + presets.upgrades.supply.farm1:extend({ + name='farm-prod-blue-1', + products = { + presets.missions.supply.convoy:extend({ name='farm-supply-blue'}), + presets.missions.supply.transfer:extend({name='farm-transfer-blue'}) + } + }), + presets.upgrades.supply.farm2:extend({ + name='farm-prod-blue-2', + products = { + presets.missions.supply.convoy:extend({ name='farm-supply-blue'}), + presets.missions.supply.transfer:extend({name='farm-transfer-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Farm.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Romeo.lua ]]----------------- + +zones.romeo = ZoneCommand:new("Romeo") +zones.romeo.initialState = { side=1 } +zones.romeo:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='romeo-tent-red', + products = { + presets.special.red.infantry:extend({ name='romeo-defense-red'}), + presets.defenses.red.infantry:extend({ name='romeo-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='romeo-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='romeo-supply-red'}), + presets.missions.supply.transfer:extend({name='romeo-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='romeo-ammo-red', + products = { + presets.missions.attack.surface:extend({name='romeo-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='romeo-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='romeo-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='romeo-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='romeo-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='romeo-supply-blue'}), + presets.missions.supply.transfer:extend({name='romeo-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='romeo-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='romeo-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Romeo.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Zulu.lua ]]----------------- + +zones.zulu = ZoneCommand:new("Zulu") +zones.zulu.initialState = { side=1 } +zones.zulu:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='zulu-tent-red', + products = { + presets.special.red.infantry:extend({ name='zulu-defense-red'}), + presets.defenses.red.infantry:extend({ name='zulu-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='zulu-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='zulu-supply-red'}), + presets.missions.supply.transfer:extend({name='zulu-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='zulu-ammo-red', + products = { + presets.missions.attack.surface:extend({name='zulu-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='zulu-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='zulu-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='zulu-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='zulu-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='zulu-supply-blue'}), + presets.missions.supply.transfer:extend({name='zulu-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='zulu-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='zulu-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Zulu.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Yankee.lua ]]----------------- + +zones.yankee = ZoneCommand:new("Yankee") +zones.yankee.initialState = { side=1 } +zones.yankee:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='yankee-tent-red', + products = { + presets.special.red.infantry:extend({ name='yankee-defense-red'}), + presets.defenses.red.infantry:extend({ name='yankee-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='yankee-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='yankee-supply-red'}), + presets.missions.supply.transfer:extend({name='yankee-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='yankee-ammo-red', + products = { + presets.missions.attack.surface:extend({name='yankee-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='yankee-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='yankee-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='yankee-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='yankee-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='yankee-supply-blue'}), + presets.missions.supply.transfer:extend({name='yankee-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='yankee-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='yankee-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Yankee.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Malgobek.lua ]]----------------- + +zones.malgobek = ZoneCommand:new("Malgobek") +zones.malgobek.initialState = { side=1 } +zones.malgobek:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='malgobek-tent-red', + products = { + presets.special.red.infantry:extend({ name='malgobek-defense-red'}), + presets.defenses.red.infantry:extend({ name='malgobek-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='malgobek-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='malgobek-supply-red'}), + presets.missions.supply.transfer:extend({name='malgobek-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='malgobek-ammo-red', + products = { + presets.missions.attack.surface:extend({name='malgobek-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='malgobek-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='malgobek-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='malgobek-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='malgobek-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='malgobek-supply-blue'}), + presets.missions.supply.transfer:extend({name='malgobek-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='malgobek-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='malgobek-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Malgobek.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Kilo.lua ]]----------------- + +zones.kilo = ZoneCommand:new("Kilo") +zones.kilo.initialState = { side=1 } +zones.kilo:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='kilo-tent-red', + products = { + presets.special.red.infantry:extend({ name='kilo-defense-red'}), + presets.defenses.red.infantry:extend({ name='kilo-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='kilo-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='kilo-supply-red'}), + presets.missions.supply.transfer:extend({name='kilo-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='kilo-ammo-red', + products = { + presets.missions.attack.surface:extend({name='kilo-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='kilo-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='kilo-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='kilo-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='kilo-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='kilo-supply-blue'}), + presets.missions.supply.transfer:extend({name='kilo-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='kilo-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='kilo-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Kilo.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Quebec.lua ]]----------------- + +zones.quebec = ZoneCommand:new("Quebec") +zones.quebec.initialState = { side=1 } +zones.quebec.keepActive = true +zones.quebec:defineUpgrades({ + [1] = + { + presets.upgrades.basic.tent:extend({ + name='quebec-tent-red', + products = { + presets.special.red.infantry:extend({ name='quebec-defense-red'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='quebec-comcenter-red', + products = { + presets.defenses.red.sam:extend({ name='quebec-sam-red'}) + } + }) + }, + [2] = + { + presets.upgrades.basic.tent:extend({ + name='quebec-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='quebec-defense-blue'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='quebec-comcenter-blue', + products = { + presets.defenses.blue.sam:extend({ name='quebec-sam-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Quebec.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/OilFields.lua ]]----------------- + +zones.oilfields = ZoneCommand:new("Oil Fields") +zones.oilfields.initialState = { side=1 } +zones.oilfields:defineUpgrades({ + [1] = { + presets.upgrades.basic.outpost:extend({ + name='oilfields-outpost-red', + products = { + presets.special.red.infantry:extend({ name='oilfields-defense-red'}), + presets.defenses.red.infantry:extend({ name='oilfields-garrison-red'}) + } + }), + presets.upgrades.supply.oilPump:extend({ + name='oilfields-pump-red-1', + products = { + presets.missions.supply.transfer:extend({name='oilfields-transfer-red1'}) + } + }), + presets.upgrades.supply.oilPump:extend({ + name='oilfields-pump-red-2', + products = { + presets.missions.supply.convoy:extend({name='oilfields-supply-red-1'}) + } + }), + presets.upgrades.supply.oilPump:extend({ + name='oilfields-pump-red-3', + products = { + presets.missions.supply.transfer:extend({name='oilfields-transfer-red2'}) + } + }), + presets.upgrades.supply.oilPump:extend({ + name='oilfields-pump-red-4', + products = { + presets.missions.supply.convoy:extend({name='oilfields-supply-red-2'}) + } + }) + }, + [2] = { + presets.upgrades.basic.outpost:extend({ + name='oilfields-outpost-blue', + products = { + presets.special.blue.infantry:extend({ name='oilfields-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='oilfields-garrison-blue'}) + } + }), + presets.upgrades.supply.oilPump:extend({ + name='oilfields-pump-blue-1', + products = { + presets.missions.supply.transfer:extend({name='oilfields-transfer-blue1'}) + } + }), + presets.upgrades.supply.oilPump:extend({ + name='oilfields-pump-blue-2', + products = { + presets.missions.supply.convoy:extend({name='oilfields-supply-blue-1'}) + } + }), + presets.upgrades.supply.oilPump:extend({ + name='oilfields-pump-blue-3', + products = { + presets.missions.supply.transfer:extend({name='oilfields-transfer-blue2'}) + } + }), + presets.upgrades.supply.oilPump:extend({ + name='oilfields-pump-blue-4', + products = { + presets.missions.supply.convoy:extend({name='oilfields-supply-blue-2'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/OilFields.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Echo.lua ]]----------------- + +zones.echo = ZoneCommand:new("Echo") +zones.echo.initialState = { side=2 } +zones.echo:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='echo-tent-red', + products = { + presets.special.red.infantry:extend({ name='echo-defense-red'}), + presets.defenses.red.infantry:extend({ name='echo-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='echo-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='echo-supply-red'}), + presets.missions.supply.transfer:extend({name='echo-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='echo-ammo-red', + products = { + presets.missions.attack.surface:extend({name='echo-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='echo-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='echo-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='echo-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='echo-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='echo-supply-blue'}), + presets.missions.supply.transfer:extend({name='echo-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='echo-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='echo-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Echo.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Kobuleti.lua ]]----------------- + +zones.kobuleti = ZoneCommand:new("Kobuleti") +zones.kobuleti.initialState = { side=2 } +zones.kobuleti.keepActive = true +zones.kobuleti.isHeloSpawn = true +zones.kobuleti.isPlaneSpawn = true +zones.kobuleti.maxResource = 50000 +zones.kobuleti:defineUpgrades({ + [1] = + { + presets.upgrades.basic.comPost:extend({ + name='kobuleti-compost-red', + products = { + presets.special.red.infantry:extend({ name='kobuleti-defense-red'}), + presets.defenses.red.infantry:extend({ name='kobuleti-garrison-red'}), + presets.missions.attack.surface:extend({ name='kobuleti-assault-red'}) + } + }), + presets.upgrades.supply.fuelTank:extend({ + name='kobuleti-fuel-red', + products = { + presets.missions.supply.helo:extend({name='kobuleti-supply-red-1'}), + presets.missions.supply.helo:extend({name='kobuleti-supply-red-2'}), + presets.missions.supply.transfer:extend({name='kobuleti-transfer-red'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='kobuleti-comcenter-red', + products = { + presets.defenses.red.shorad:extend({ name='kobuleti-airdef-red'}), + presets.missions.attack.sead:extend({name='kobuleti-sead-red', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.attack.cas:extend({name='kobuleti-cas-red', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.bai:extend({name='kobuleti-cas-red', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.strike:extend({name='kobuleti-strike-red', altitude=20000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.patrol.aircraft:extend({name='kobuleti-patrol-red', altitude=25000, range=25}) + } + }) + }, + [2] = + { + presets.upgrades.basic.comPost:extend({ + name='kobuleti-compost-blue', + products = { + presets.special.blue.infantry:extend({ name='kobuleti-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='kobuleti-garrison-blue'}), + presets.missions.attack.surface:extend({ name='kobuleti-assault-blue'}) + } + }), + presets.upgrades.supply.fuelTank:extend({ + name='kobuleti-fuel-blue', + products = { + presets.missions.supply.helo:extend({name='kobuleti-supply-blue-1'}), + presets.missions.supply.helo:extend({name='kobuleti-supply-blue-2'}), + presets.missions.supply.transfer:extend({name='kobuleti-transfer-blue'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='kobuleti-comcenter-blue', + products = { + presets.defenses.blue.shorad:extend({ name='kobuleti-airdef-blue'}), + presets.missions.attack.sead:extend({name='kobuleti-sead-blue', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.attack.cas:extend({name='kobuleti-cas-blue', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.bai:extend({name='kobuleti-cas-blue', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.strike:extend({name='kobuleti-strike-blue', altitude=20000, expend=AI.Task.WeaponExpend.TWO}), + presets.missions.patrol.aircraft:extend({name='kobuleti-patrol-blue', altitude=25000, range=25}), + presets.missions.support.awacs:extend({name='kobuleti-awacs-blue', altitude=30000, freq=258.5}), + presets.missions.support.tanker:extend({name='kobuleti-tanker-blue', altitude=23000, freq=258, tacan='38', variant='Boom'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Kobuleti.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Alpha.lua ]]----------------- + +zones.alpha = ZoneCommand:new('Alpha') +zones.alpha.initialState = { side=2 } +zones.alpha:defineUpgrades({ + [1] = --red side + { + presets.upgrades.basic.tent:extend({ + name = 'alpha-tent-red', + products = { + presets.special.red.infantry:extend({ name='alpha-defense-red'}), + presets.defenses.red.infantry:extend({ name='alpha-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name = 'alpha-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({ name='alpha-supply-red'}), + presets.missions.supply.transfer:extend({name='alpha-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name = 'alpha-ammo-red', + products = { + presets.missions.attack.surface:extend({ name='alpha-assault-red'}) + } + }) + }, + [2] = --blue side + { + presets.upgrades.basic.tent:extend({ + name = 'alpha-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='alpha-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='alpha-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name = 'alpha-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({ name='alpha-supply-blue'}), + presets.missions.supply.transfer:extend({name='alpha-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name = 'alpha-ammo-blue', + products = { + presets.missions.attack.surface:extend({ name='alpha-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Alpha.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Foxtrot.lua ]]----------------- + +zones.foxtrot = ZoneCommand:new("Foxtrot") +zones.foxtrot.initialState = { side=2 } +zones.foxtrot:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='foxtrot-tent-red', + products = { + presets.special.red.infantry:extend({ name='foxtrot-defense-red'}), + presets.defenses.red.infantry:extend({ name='foxtrot-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='foxtrot-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='foxtrot-supply-red'}), + presets.missions.supply.transfer:extend({name='foxtrot-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='foxtrot-ammo-red', + products = { + presets.missions.attack.surface:extend({name='foxtrot-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='foxtrot-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='foxtrot-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='foxtrot-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='foxtrot-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='foxtrot-supply-blue'}), + presets.missions.supply.transfer:extend({name='foxtrot-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='foxtrot-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='foxtrot-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Foxtrot.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Sierra.lua ]]----------------- + +zones.sierra = ZoneCommand:new("Sierra") +zones.sierra.initialState = { side=1 } +zones.sierra.isHeloSpawn = true +zones.sierra:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='sierra-tent-red', + products = { + presets.special.red.infantry:extend({ name='sierra-defense-red'}), + presets.defenses.red.infantry:extend({ name='sierra-garrison-red'}) + } + }), + presets.upgrades.supply.fuelTankFarp:extend({ + name='sierra-fuel-red', + products = { + presets.missions.supply.helo:extend({name='sierra-supply-red'}), + presets.missions.supply.helo:extend({name='sierra-supply-red-1'}), + presets.missions.supply.transfer:extend({name='sierra-transfer-red'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='sierra-comcenter-red', + products = { + presets.defenses.red.shorad:extend({name='sierra-sam-red'}), + presets.missions.attack.helo:extend({name='sierra-cas-red', altitude=200, expend=AI.Task.WeaponExpend.HALF }) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='sierra-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='sierra-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='sierra-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelTankFarp:extend({ + name='sierra-fuel-blue', + products = { + presets.missions.supply.helo:extend({name='sierra-supply-blue'}), + presets.missions.supply.helo:extend({name='sierra-supply-blue-1'}), + presets.missions.supply.transfer:extend({name='sierra-transfer-blue'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='sierra-comcenter-blue', + products = { + presets.defenses.blue.shorad:extend({name='sierra-sam-blue'}), + presets.missions.attack.helo:extend({name='sierra-cas-blue', altitude=200, expend=AI.Task.WeaponExpend.HALF }) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Sierra.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Oni.lua ]]----------------- + +zones.oni = ZoneCommand:new("Oni") +zones.oni.initialState = { side=1 } +zones.oni:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='oni-tent-red', + products = { + presets.special.red.infantry:extend({ name='oni-defense-red'}), + presets.defenses.red.infantry:extend({ name='oni-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='oni-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='oni-supply-red'}), + presets.missions.supply.helo:extend({name='oni-supply-red-2'}), + presets.missions.supply.transfer:extend({name='oni-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='oni-ammo-red', + products = { + presets.missions.attack.surface:extend({name='oni-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='oni-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='oni-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='oni-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='oni-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='oni-supply-blue'}), + presets.missions.supply.helo:extend({name='oni-supply-blue-2'}), + presets.missions.supply.transfer:extend({name='oni-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='oni-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='oni-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Oni.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Hotel.lua ]]----------------- + +zones.hotel = ZoneCommand:new("Hotel") +zones.hotel.initialState = nil +zones.hotel:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='hotel-tent-red', + products = { + presets.special.red.infantry:extend({ name='hotel-defense-red'}), + presets.defenses.red.infantry:extend({ name='hotel-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='hotel-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='hotel-supply-red'}), + presets.missions.supply.transfer:extend({name='hotel-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='hotel-ammo-red', + products = { + presets.missions.attack.surface:extend({name='hotel-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='hotel-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='hotel-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='hotel-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='hotel-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='hotel-supply-blue'}), + presets.missions.supply.transfer:extend({name='hotel-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='hotel-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='hotel-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Hotel.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Victor.lua ]]----------------- + +zones.victor = ZoneCommand:new("Victor") +zones.victor.initialState = { side=1 } +zones.victor:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='victor-tent-red', + products = { + presets.special.red.infantry:extend({ name='victor-defense-red'}), + presets.defenses.red.infantry:extend({ name='victor-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='victor-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='victor-supply-red'}), + presets.missions.supply.helo:extend({name='victor-supply-red-2'}), + presets.missions.supply.transfer:extend({name='victor-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='victor-ammo-red', + products = { + presets.missions.attack.surface:extend({name='victor-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='victor-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='victor-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='victor-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='victor-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='victor-supply-blue'}), + presets.missions.supply.helo:extend({name='victor-supply-blue-2'}), + presets.missions.supply.transfer:extend({name='victor-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='victor-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='victor-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Victor.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Tango.lua ]]----------------- + +zones.tango = ZoneCommand:new("Tango") +zones.tango.initialState = { side=1 } +zones.tango.isHeloSpawn = true +zones.tango:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='tango-tent-red', + products = { + presets.special.red.infantry:extend({ name='tango-defense-red'}), + presets.defenses.red.infantry:extend({ name='tango-garrison-red'}) + } + }), + presets.upgrades.supply.fuelTankFarp:extend({ + name='tango-fuel-red', + products = { + presets.missions.supply.helo:extend({name='tango-supply-red'}), + presets.missions.supply.helo:extend({name='tango-supply-red-1'}), + presets.missions.supply.transfer:extend({name='tango-transfer-red'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='tango-comcenter-red', + products = { + presets.defenses.red.shorad:extend({name='tango-sam-red'}), + presets.missions.attack.helo:extend({name='tango-cas-red', altitude=200, expend=AI.Task.WeaponExpend.HALF }) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='tango-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='tango-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='tango-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelTankFarp:extend({ + name='tango-fuel-blue', + products = { + presets.missions.supply.helo:extend({name='tango-supply-blue'}), + presets.missions.supply.helo:extend({name='tango-supply-blue-1'}), + presets.missions.supply.transfer:extend({name='tango-transfer-blue'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='tango-comcenter-blue', + products = { + presets.defenses.blue.shorad:extend({name='tango-sam-blue'}), + presets.missions.attack.helo:extend({name='tango-cas-blue', altitude=200, expend=AI.Task.WeaponExpend.HALF }) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Tango.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Unal.lua ]]----------------- + +zones.unal = ZoneCommand:new("Unal") +zones.unal.initialState = { side=1 } +zones.unal.isHeloSpawn = true +zones.unal:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='unal-tent-red', + products = { + presets.special.red.infantry:extend({ name='unal-defense-red'}), + presets.defenses.red.infantry:extend({ name='unal-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='unal-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='unal-supply-red'}), + presets.missions.supply.helo:extend({name='unal-supply-red-2'}), + presets.missions.supply.transfer:extend({name='unal-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='unal-ammo-red', + products = { + presets.missions.attack.surface:extend({name='unal-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='unal-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='unal-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='unal-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='unal-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='unal-supply-blue'}), + presets.missions.supply.helo:extend({name='unal-supply-blue-2'}), + presets.missions.supply.transfer:extend({name='unal-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='unal-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='unal-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Unal.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Beslan.lua ]]----------------- + +zones.beslan = ZoneCommand:new("Beslan") +zones.beslan.initialState = { side=1 } +zones.beslan.keepActive = true +zones.beslan.maxResource = 50000 +zones.beslan:defineUpgrades({ + [1] = + { + presets.upgrades.basic.comPost:extend({ + name='beslan-compost-red', + products = { + presets.special.red.infantry:extend({ name='beslan-defense-red'}), + presets.defenses.red.infantry:extend({ name='beslan-garrison-red'}) + } + }), + presets.upgrades.supply.fuelTank:extend({ + name='beslan-fuel-red', + products = { + presets.missions.supply.helo:extend({name='beslan-supply-red-1'}), + presets.missions.supply.helo:extend({name='beslan-supply-red-2'}), + presets.missions.supply.convoy_escorted:extend({name='beslan-supply-red-3'}), + presets.missions.supply.transfer:extend({name='beslan-transfer-red'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='beslan-comcenter-red', + products = { + presets.defenses.red.sa5:extend({ name='beslan-airdef-red'}), + presets.missions.attack.sead:extend({name='beslan-sead-red', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.attack.strike:extend({name='beslan-strike-red', altitude=30000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.attack.strike:extend({name='beslan-strike-red-1', altitude=30000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.patrol.aircraft:extend({name='beslan-patrol-red', altitude=25000, range=25}) + } + }) + }, + [2] = + { + presets.upgrades.basic.comPost:extend({ + name='beslan-compost-blue', + products = { + presets.special.blue.infantry:extend({ name='beslan-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='beslan-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelTank:extend({ + name='beslan-fuel-blue', + products = { + presets.missions.supply.helo:extend({name='beslan-supply-blue-1'}), + presets.missions.supply.helo:extend({name='beslan-supply-blue-2'}), + presets.missions.supply.convoy_escorted:extend({name='beslan-supply-blue-3'}), + presets.missions.supply.transfer:extend({name='beslan-transfer-blue'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='beslan-comcenter-blue', + products = { + presets.defenses.blue.patriot:extend({ name='beslan-airdef-blue'}), + presets.missions.attack.sead:extend({name='beslan-sead-blue', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.attack.strike:extend({name='beslan-strike-blue', altitude=30000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.attack.strike:extend({name='beslan-strike-blue-1', altitude=30000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.patrol.aircraft:extend({name='beslan-patrol-blue', altitude=25000, range=25}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Beslan.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Bravo.lua ]]----------------- + +zones.bravo = ZoneCommand:new("Bravo") +zones.bravo.initialState = { side=2 } +zones.bravo:defineUpgrades({ + [1] = + { + presets.upgrades.basic.comPost:extend({ + name='bravo-compost-red', + products = { + presets.special.red.infantry:extend({ name='bravo-defense-red'}), + presets.defenses.red.infantry:extend({ name='bravo-garrison-red'}) + } + }), + presets.upgrades.supply.fuelTankFarp:extend({ + name='bravo-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({ name='bravo-supply-red'}), + presets.missions.supply.transfer:extend({name='bravo-transfer-red'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='bravo-comcenter-red', + products = { + presets.defenses.red.shorad:extend({ name='bravo-airdef-red'}), + presets.missions.attack.helo:extend({name='bravo-attack-red', altitude=200, expend=AI.Task.WeaponExpend.HALF}) + } + }) + }, + [2] = + { + presets.upgrades.basic.comPost:extend({ + name='bravo-compost-blue', + products = { + presets.special.blue.infantry:extend({ name='bravo-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='bravo-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelTankFarp:extend({ + name='bravo-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({ name='bravo-supply-blue'}), + presets.missions.supply.transfer:extend({name='bravo-transfer-blue'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='bravo-comcenter-blue', + products = { + presets.defenses.blue.shorad:extend({ name='bravo-airdef-blue'}), + presets.missions.attack.helo:extend({name='bravo-attack-blue', altitude=200, expend=AI.Task.WeaponExpend.HALF }) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Bravo.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/WeaponDepot.lua ]]----------------- + +zones.weapondepot = ZoneCommand:new("Weapon Depot") +zones.weapondepot.initialState = { side=1 } +zones.weapondepot:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='weapons-tent-red', + products = { + presets.special.red.infantry:extend({ name='weapons-defense-red'}), + presets.defenses.red.infantry:extend({ name='weapons-garrison-red'}) + } + }), + presets.upgrades.supply.ammoDepot:extend({ + name='weapons-ammo-red-1', + products = { + presets.missions.supply.convoy:extend({name='weapons-supply-red-1'}), + presets.missions.supply.transfer:extend({name='weapons-transfer-red-1'}) + } + }), + presets.upgrades.supply.ammoDepot:extend({ + name='weapons-ammo-red-2', + products = { + presets.missions.supply.convoy:extend({name='weapons-supply-red-2'}), + presets.missions.supply.transfer:extend({name='weapons-transfer-red-2'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='weapons-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='weapons-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='weapons-garrison-blue'}) + } + }), + presets.upgrades.supply.ammoDepot:extend({ + name='weapons-ammo-blue-1', + products = { + presets.missions.supply.convoy:extend({name='weapons-supply-blue-1'}), + presets.missions.supply.transfer:extend({name='weapons-transfer-blue-1'}) + } + }), + presets.upgrades.supply.ammoDepot:extend({ + name='weapons-ammo-blue-2', + products = { + presets.missions.supply.convoy:extend({name='weapons-supply-blue-2'}), + presets.missions.supply.transfer:extend({name='weapons-transfer-blue-2'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/WeaponDepot.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Delta.lua ]]----------------- + +zones.delta = ZoneCommand:new("Delta") +zones.delta.initialState = { side=2 } +zones.delta:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='delta-tent-red', + products = { + presets.special.red.infantry:extend({ name='delta-defense-red'}), + presets.defenses.red.infantry:extend({ name='delta-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='delta-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='delta-supply-red'}), + presets.missions.supply.transfer:extend({name='delta-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='delta-ammo-red', + products = { + presets.missions.attack.surface:extend({name='delta-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='delta-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='delta-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='delta-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='delta-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='delta-supply-blue'}), + presets.missions.supply.transfer:extend({name='delta-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='delta-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='delta-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Delta.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Cherkessk.lua ]]----------------- + +zones.cherkessk = ZoneCommand:new("Cherkessk") +zones.cherkessk.initialState = { side=1 } +zones.cherkessk.isHeloSpawn = true +zones.cherkessk:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='cherkessk-tent-red', + products = { + presets.special.red.infantry:extend({ name='cherkessk-defense-red'}), + presets.defenses.red.infantry:extend({ name='cherkessk-garrison-red'}) + } + }), + presets.upgrades.supply.fuelTankFarp:extend({ + name='cherkessk-fuel-red', + products = { + presets.missions.supply.helo:extend({name='cherkessk-supply-red'}), + presets.missions.supply.helo:extend({name='cherkessk-supply-red-1'}), + presets.missions.supply.transfer:extend({name='cherkessk-transfer-red'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='cherkessk-comcenter-red', + products = { + presets.defenses.red.shorad:extend({name='cherkessk-sam-red'}), + presets.missions.attack.helo:extend({name='cherkessk-cas-red', altitude=200, expend=AI.Task.WeaponExpend.HALF }), + presets.missions.attack.helo:extend({name='cherkessk-cas-red-1', altitude=200, expend=AI.Task.WeaponExpend.HALF }), + presets.missions.attack.surface:extend({name='cherkessk-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='cherkessk-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='cherkessk-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='cherkessk-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelTankFarp:extend({ + name='cherkessk-fuel-blue', + products = { + presets.missions.supply.helo:extend({name='cherkessk-supply-blue'}), + presets.missions.supply.helo:extend({name='cherkessk-supply-blue-1'}), + presets.missions.supply.transfer:extend({name='cherkessk-transfer-blue'}), + presets.missions.attack.surface:extend({name='cherkessk-assault-blue'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='cherkessk-comcenter-blue', + products = { + presets.defenses.blue.shorad:extend({name='cherkessk-sam-blue'}), + presets.missions.attack.helo:extend({name='cherkessk-cas-blue', altitude=200, expend=AI.Task.WeaponExpend.HALF }), + presets.missions.attack.helo:extend({name='cherkessk-cas-blue-1', altitude=200, expend=AI.Task.WeaponExpend.HALF }) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Cherkessk.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Juliett.lua ]]----------------- + +zones.juliett = ZoneCommand:new("Juliett") +zones.initialState = nil +zones.juliett:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='juliett-tent-red', + products = { + presets.special.red.infantry:extend({ name='juliett-defense-red'}), + presets.defenses.red.infantry:extend({ name='juliett-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='juliett-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='juliett-supply-red'}), + presets.missions.supply.transfer:extend({name='juliett-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='juliett-ammo-red', + products = { + presets.missions.attack.surface:extend({name='juliett-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='juliett-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='juliett-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='juliett-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='juliett-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='juliett-supply-blue'}), + presets.missions.supply.transfer:extend({name='juliett-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='juliett-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='juliett-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Juliett.lua ]]----------------- + + + + cm = ConnectionManager:new() + cm:addConnection('Batumi', 'Alpha') + cm:addConnection('Alpha', 'Bravo') + cm:addConnection('Bravo', 'Kobuleti') + cm:addConnection('Bravo', 'Factory') + cm:addConnection('Kobuleti', 'Factory') + cm:addConnection('Kobuleti', 'Charlie') + cm:addConnection('Foxtrot', 'Charlie') + cm:addConnection('Foxtrot', 'Kobuleti') + cm:addConnection('Delta','Foxtrot') + cm:addConnection('Delta','Kobuleti') + cm:addConnection('Delta','Factory') + cm:addConnection('Echo','Charlie') + cm:addConnection('Golf','Echo') + cm:addConnection('Golf','Foxtrot') + cm:addConnection('India','Delta') + cm:addConnection('Hotel','Golf') + cm:addConnection('Hotel','Foxtrot') + cm:addConnection('Hotel','Delta') + cm:addConnection('Hotel','India') + cm:addConnection('Juliett','Echo') + cm:addConnection('Juliett','Golf') + cm:addConnection('Senaki','Juliett') + cm:addConnection('Senaki','Golf') + cm:addConnection('Senaki','Hotel') + cm:addConnection('Kutaisi','Hotel') + cm:addConnection('Kutaisi','India') + cm:addConnection('Kilo','Juliett') + cm:addConnection('Mike','Kutaisi') + cm:addConnection('Mike','Senaki') + cm:addConnection('Romeo','Mike') + cm:addConnection('Romeo','Kutaisi') + cm:addConnection('Weapon Depot','Juliett') + cm:addConnection('Weapon Depot','Senaki') + cm:addConnection('Weapon Depot','Kilo') + cm:addConnection('November','Weapon Depot') + cm:addConnection('November','Senaki') + cm:addConnection('November','Mike') + cm:addConnection('Oil Fields','Romeo') + cm:addConnection('Quebec','Kilo') + cm:addConnection('Zugdidi','Weapon Depot') + cm:addConnection('Zugdidi','Quebec') + cm:addConnection('Zugdidi','November') + cm:addConnection('Zugdidi','Kilo') + cm:addConnection('Distillery','November') + cm:addConnection('Distillery','Mike') + cm:addConnection('Zugdidi','Papa') + cm:addConnection('November','Papa') + cm:addConnection('Sierra','Papa') + cm:addConnection('Sierra','Zugdidi') + cm:addConnection('Sierra','Uniform') + cm:addConnection('Mine','Uniform') + cm:addConnection('Tango','Quebec') + cm:addConnection('Tango','Zugdidi') + cm:addConnection('Sierra','Tango') + cm:addConnection('Whiskey','Tango') + cm:addConnection('Ochamchira','Tango') + cm:addConnection('Ochamchira','Whiskey') + cm:addConnection('Ochamchira','Farm') + cm:addConnection('Ochamchira','Zulu') + cm:addConnection('Farm','Zulu') + cm:addConnection('Sukhumi','Zulu') + cm:addConnection('Lentehi','Distillery', true, 3000) + cm:addConnection('Lentehi','Babugent', true, 5000) + cm:addConnection('Nalchik','Babugent') + cm:addConnection('Victor','Distillery', true, 2000) + cm:addConnection('Victor','Romeo') + cm:addConnection('Victor','Lentehi') + cm:addConnection('Victor','Oil Fields', true, 2000) + cm:addConnection('Victor','Oni') + cm:addConnection('Unal','Oni', true, 4500) + cm:addConnection('Beslan','Unal') + cm:addConnection('Digora','Beslan') + cm:addConnection('Digora','Unal') + cm:addConnection('Digora','Babugent') + cm:addConnection('Terek','Digora') + cm:addConnection('Terek','Nalchik') + cm:addConnection('Terek','Beslan') + cm:addConnection('Prohladniy','Terek') + cm:addConnection('Prohladniy','Nalchik') + cm:addConnection('Malgobek','Terek') + cm:addConnection('Malgobek','Beslan') + cm:addConnection('Lima','Mine') + cm:addConnection('Lima','Lentehi', true, 4000) + cm:addConnection('Tyrnyauz','Lima', true, 4000) + cm:addConnection('Tyrnyauz','Nalchik') + cm:addConnection('XRay','Sukhumi') + cm:addConnection('Oscar','Sukhumi') + cm:addConnection('Oscar','XRay') + cm:addConnection('Mozdok','Malgobek') + cm:addConnection('Mozdok','Prohladniy') + cm:addConnection('Gudauta','Oscar') + cm:addConnection('Yankee','Gudauta') + cm:addConnection('Sochi','Yankee') + cm:addConnection('Refinery','XRay', true, 4000) + cm:addConnection('Refinery','Humara') + cm:addConnection('Intel Center','Tyrnyauz') + cm:addConnection('Intel Center','Nalchik') + cm:addConnection('Intel Center','Prohladniy') + cm:addConnection('Intel Center','Kislovodsk') + cm:addConnection('Mineralnye','Intel Center') + cm:addConnection('Kislovodsk','Mineralnye') + cm:addConnection('Tallyk','Mineralnye') + cm:addConnection('Tallyk','Kislovodsk') + cm:addConnection('Power Plant','Mineralnye') + cm:addConnection('Power Plant','Tallyk') + cm:addConnection('Cherkessk','Tallyk') + cm:addConnection('Cherkessk','Power Plant') + cm:addConnection('Cherkessk','Humara') +end + +ZoneCommand.setNeighbours(cm) + +bm = BattlefieldManager:new() + +mc = MarkerCommands:new() + +pt = PlayerTracker:new(mc) + +mt = MissionTracker:new(pt, mc) + +st = SquadTracker:new() + +ct = CSARTracker:new() + +pl = PlayerLogistics:new(mt, pt, st, ct) + +gci = GCI:new(2) + +gm = GroupMonitor:new(cm) +ZoneCommand.groupMonitor = gm + +-- PlayerLogistics:registerSquadGroup(squadType, groupname, weight,cost,jobtime,extracttime, squadSize) +pl:registerSquadGroup(PlayerLogistics.infantryTypes.capture, 'capture-squad', 700, 200, 60, 60*30, 4) +pl:registerSquadGroup(PlayerLogistics.infantryTypes.sabotage, 'sabotage-squad', 800, 500, 60*5, 60*30, 4) +pl:registerSquadGroup(PlayerLogistics.infantryTypes.ambush, 'ambush-squad', 900, 300, 60*20, 60*30, 5) +pl:registerSquadGroup(PlayerLogistics.infantryTypes.engineer, 'engineer-squad', 200, 1000,60, 60*30, 2) +pl:registerSquadGroup(PlayerLogistics.infantryTypes.manpads, 'manpads-squad', 900, 500, 60*20, 60*30, 5) +pl:registerSquadGroup(PlayerLogistics.infantryTypes.spy, 'spy-squad', 100, 300, 60*10, 60*30, 1) +pl:registerSquadGroup(PlayerLogistics.infantryTypes.rapier, 'rapier-squad', 1200,2000,60*60, 60*30, 8) + +Group.getByName('jtacDrone'):destroy() +CommandFunctions.jtac = JTAC:new({name = 'jtacDrone'}) + +pm = PersistenceManager:new(savefile, gm, st, ct, pl) +pm:load() + +if pm:canRestore() then + pm:restoreZones() + pm:restoreAIMissions() + pm:restoreBattlefield() + pm:restoreCsar() + pm:restoreSquads() +else + --initial states + Starter.start(zones) +end + +timer.scheduleFunction(function(param, time) + pm:save() + env.info("Mission state saved") + return time+60 +end, zones, timer.getTime()+60) + + +--make sure support units are present where needed +ensureSpawn = { + ['golf-farp-suport'] = zones.golf, + ['november-farp-suport'] = zones.november, + ['tango-farp-suport'] = zones.tango, + ['sierra-farp-suport'] = zones.sierra, + ['cherkessk-farp-suport'] = zones.cherkessk, + ['unal-farp-suport'] = zones.unal, + ['tyrnyauz-farp-suport'] = zones.tyrnyauz +} + +for grname, zn in pairs(ensureSpawn) do + local g = Group.getByName(grname) + if g then g:destroy() end +end + +timer.scheduleFunction(function(param, time) + + for grname, zn in pairs(ensureSpawn) do + local g = Group.getByName(grname) + if zn.side == 2 then + if not g then + local err, msg = pcall(mist.respawnGroup,grname,true) + if not err then + env.info("ERROR spawning "..grname) + env.info(msg) + end + end + else + if g then g:destroy() end + end + end + + return time+30 +end, {}, timer.getTime()+30) + + +--supply injection +local blueSupply = {'offmap-supply-blue-1','offmap-supply-blue-2','offmap-supply-blue-3','offmap-supply-blue-4','offmap-supply-blue-5'} +local redSupply = {'offmap-supply-red-1','offmap-supply-red-2','offmap-supply-red-3','offmap-supply-red-4','offmap-supply-red-5'} +local offmapZones = { + zones.batumi, + zones.sochi, + zones.nalchik, + zones.beslan, + zones.mozdok, + zones.mineralnye, +-- zones.senaki, +-- zones.sukhumi, +-- zones.gudauta, +-- zones.kobuleti, +} + +supplyPointRegistry = { + blue = {}, + red = {} +} + +for i,v in ipairs(blueSupply) do + local g = Group.getByName(v) + if g then + supplyPointRegistry.blue[v] = g:getUnit(1):getPoint() + end +end + +for i,v in ipairs(redSupply) do + local g = Group.getByName(v) + if g then + supplyPointRegistry.red[v] = g:getUnit(1):getPoint() + end +end + +offmapSupplyRegistry = {} +timer.scheduleFunction(function(param, time) + local availableBlue = {} + for i,v in ipairs(param.blue) do + if offmapSupplyRegistry[v] == nil then + table.insert(availableBlue, v) + end + end + + local availableRed = {} + for i,v in ipairs(param.red) do + if offmapSupplyRegistry[v] == nil then + table.insert(availableRed, v) + end + end + + local redtargets = {} + local bluetargets = {} + for _, zn in ipairs(param.offmapZones) do + if zn:needsSupplies(3000) then + local isOnRoute = false + for _,data in pairs(offmapSupplyRegistry) do + if data.zone.name == zn.name then + isOnRoute = true + break + end + end + if not isOnRoute then + if zn.side == 1 then + table.insert(redtargets, zn) + elseif zn.side == 2 then + table.insert(bluetargets, zn) + end + end + end + end + + if #availableRed > 0 and #redtargets > 0 then + local zn = redtargets[math.random(1,#redtargets)] + + local red = nil + local minD = 999999999 + for i,v in ipairs(availableRed) do + local d = mist.utils.get2DDist(zn.zone.point, supplyPointRegistry.red[v]) + if d < minD then + red = v + minD = d + end + end + + if not red then red = availableRed[math.random(1,#availableRed)] end + + local gr = red + red = nil + mist.respawnGroup(gr, true) + offmapSupplyRegistry[gr] = {zone = zn, assigned = timer.getAbsTime()} + env.info(gr..' was deployed') + timer.scheduleFunction(function(param,time) + local g = Group.getByName(param.group) + TaskExtensions.landAtAirfield(g, param.target.zone.point) + env.info(param.group..' going to '..param.target.name) + end, {group=gr, target=zn}, timer.getTime()+2) + end + + if #availableBlue > 0 and #bluetargets>0 then + local zn = bluetargets[math.random(1,#bluetargets)] + + local blue = nil + local minD = 999999999 + for i,v in ipairs(availableBlue) do + local d = mist.utils.get2DDist(zn.zone.point, supplyPointRegistry.blue[v]) + if d < minD then + blue = v + minD = d + end + end + + if not blue then blue = availableBlue[math.random(1,#availableBlue)] end + + local gr = blue + blue = nil + mist.respawnGroup(gr, true) + offmapSupplyRegistry[gr] = {zone = zn, assigned = timer.getAbsTime()} + env.info(gr..' was deployed') + timer.scheduleFunction(function(param,time) + local g = Group.getByName(param.group) + TaskExtensions.landAtAirfield(g, param.target.zone.point) + env.info(param.group..' going to '..param.target.name) + end, {group=gr, target=zn}, timer.getTime()+2) + end + + return time+(60*5) +end, {blue = blueSupply, red = redSupply, offmapZones = offmapZones}, timer.getTime()+60) + + + +timer.scheduleFunction(function(param, time) + + for groupname,data in pairs(offmapSupplyRegistry) do + local gr = Group.getByName(groupname) + if not gr then + offmapSupplyRegistry[groupname] = nil + env.info(groupname..' was destroyed') + end + + if gr and ((timer.getAbsTime() - data.assigned) > (60*60)) then + gr:destroy() + offmapSupplyRegistry[groupname] = nil + env.info(groupname..' despawned due to being alive for too long') + end + + if gr and Utils.allGroupIsLanded(gr) and Utils.someOfGroupInZone(gr, data.zone.name) then + data.zone:addResource(15000) + gr:destroy() + offmapSupplyRegistry[groupname] = nil + env.info(groupname..' landed at '..data.zone.name..' and delivered 15000 resources') + end + end + + return time+180 +end, {}, timer.getTime()+180) \ No newline at end of file diff --git a/resources/plugins/pretense/pretense_compiled.lua b/resources/plugins/pretense/pretense_compiled.lua new file mode 100644 index 00000000..4d3a04f2 --- /dev/null +++ b/resources/plugins/pretense/pretense_compiled.lua @@ -0,0 +1,13350 @@ +--[[ +Pretense Dynamic Mission Engine +## Description: + +Pretense Dynamic Mission Engine (PDME) is a the heart and soul of the Pretense missions. +You are allowed to use and modify this script for personal or private use. +Please do not share modified versions of this script. +Please do not reupload missions that use this script. +Please do not charge money for access to missions using this script. + +## Links: + +ED Forums Post: + +Pretense Manual: + +If you'd like to buy me a beer: + +Makes use of Mission scripting tools (Mist): + +@script PDME +@author Dzsekeb + +]]-- + +-----------------[[ Config.lua ]]----------------- + +Config = Config or {} +Config.lossCompensation = Config.lossCompensation or 1.1 -- gives advantage to the side with less zones. Set to 0 to disable +Config.randomBoost = Config.randomBoost or 0.0004 -- adds a random factor to build speeds that changes every 30 minutes, set to 0 to disable +Config.buildSpeed = Config.buildSpeed or 10 -- structure and defense build speed +Config.supplyBuildSpeed = Config.supplyBuildSpeed or 85 -- supply helicopters and convoys build speed +Config.missionBuildSpeedReduction = Config.missionBuildSpeedReduction or 0.12 -- reduction of build speed in case of ai missions +Config.maxDistFromFront = Config.maxDistFromFront or 129640 -- max distance in meters from front after which zone is forced into low activity state (export mode) + +Config.missions = Config.missions or {} + +-----------------[[ END OF Config.lua ]]----------------- + + + +-----------------[[ Utils.lua ]]----------------- + +Utils = {} +do + local JSON = (loadfile('Scripts/JSON.lua'))() + + function Utils.getPointOnSurface(point) + return {x = point.x, y = land.getHeight({x = point.x, y = point.z}), z= point.z} + end + + function Utils.getTableSize(tbl) + local cnt = 0 + for i,v in pairs(tbl) do cnt=cnt+1 end + return cnt + end + + Utils.cache = {} + Utils.cache.groups = {} + function Utils.getOriginalGroup(groupName) + if Utils.cache.groups[groupName] then + return Utils.cache.groups[groupName] + end + + for _,coalition in pairs(env.mission.coalition) do + for _,country in pairs(coalition.country) do + local tocheck = {} + table.insert(tocheck, country.plane) + table.insert(tocheck, country.helicopter) + table.insert(tocheck, country.ship) + table.insert(tocheck, country.vehicle) + table.insert(tocheck, country.static) + + for _, checkGroup in ipairs(tocheck) do + for _,item in pairs(checkGroup.group) do + Utils.cache.groups[item.name] = item + if item.name == groupName then + return item + end + end + end + end + end + end + + function Utils.getBearing(fromvec, tovec) + local fx = fromvec.x + local fy = fromvec.z + + local tx = tovec.x + local ty = tovec.z + + local brg = math.atan2(ty - fy, tx - fx) + + + if brg < 0 then + brg = brg + 2 * math.pi + end + + brg = brg * 180 / math.pi + + + return brg + end + + function Utils.getHeadingDiff(heading1, heading2) -- heading1 + result == heading2 + local diff = heading1 - heading2 + local absDiff = math.abs(diff) + local complementaryAngle = 360 - absDiff + + if absDiff <= 180 then + return -diff + elseif heading1 > heading2 then + return complementaryAngle + else + return -complementaryAngle + end + end + + function Utils.getAGL(object) + local pt = object:getPoint() + return pt.y - land.getHeight({ x = pt.x, y = pt.z }) + end + + function Utils.round(number) + return math.floor(number+0.5) + end + + function Utils.isLanded(unit, ignorespeed) + --return (Utils.getAGL(unit)<5 and mist.vec.mag(unit:getVelocity())<0.10) + + if ignorespeed then + return not unit:inAir() + else + return (not unit:inAir() and mist.vec.mag(unit:getVelocity())<1) + end + end + + function Utils.isGroupActive(group) + if group and group:getSize()>0 and group:getController():hasTask() then + return not Utils.allGroupIsLanded(group, true) + else + return false + end + end + + function Utils.isInAir(unit) + --return Utils.getAGL(unit)>5 + return unit:inAir() + end + + function Utils.isInZone(unit, zonename) + local zn = CustomZone:getByName(zonename) + if zn then + return zn:isInside(unit:getPosition().p) + end + + return false + end + + function Utils.isCrateSettledInZone(crate, zonename) + local zn = CustomZone:getByName(zonename) + if zn and crate then + return (zn:isInside(crate:getPosition().p) and Utils.getAGL(crate)<1) + end + + return false + end + + function Utils.someOfGroupInZone(group, zonename) + for i,v in pairs(group:getUnits()) do + if Utils.isInZone(v, zonename) then + return true + end + end + + return false + end + + function Utils.allGroupIsLanded(group, ignorespeed) + for i,v in pairs(group:getUnits()) do + if not Utils.isLanded(v, ignorespeed) then + return false + end + end + + return true + end + + function Utils.someOfGroupInAir(group) + for i,v in pairs(group:getUnits()) do + if Utils.isInAir(v) then + return true + end + end + + return false + end + + Utils.canAccessFS = true + function Utils.saveTable(filename, data) + if not Utils.canAccessFS then + return + end + + if not io then + Utils.canAccessFS = false + trigger.action.outText('Persistance disabled', 30) + return + end + + local str = JSON:encode(data) + -- local str = 'return (function() local tbl = {}' + -- for i,v in pairs(data) do + -- str = str..'\ntbl[\''..i..'\'] = '..Utils.serializeValue(v) + -- end + + -- str = str..'\nreturn tbl end)()' + + local File = io.open(filename, "w") + File:write(str) + File:close() + end + + function Utils.serializeValue(value) + local res = '' + if type(value)=='number' or type(value)=='boolean' then + res = res..tostring(value) + elseif type(value)=='string' then + res = res..'\''..value..'\'' + elseif type(value)=='table' then + res = res..'{ ' + for i,v in pairs(value) do + if type(i)=='number' then + res = res..'['..i..']='..Utils.serializeValue(v)..',' + else + res = res..'[\''..i..'\']='..Utils.serializeValue(v)..',' + end + end + res = res:sub(1,-2) + res = res..' }' + end + return res + end + + function Utils.loadTable(filename) + if not Utils.canAccessFS then + return + end + + if not lfs then + Utils.canAccessFS = false + trigger.action.outText('Persistance disabled', 30) + return + end + + if lfs.attributes(filename) then + local File = io.open(filename, "r") + local str = File:read('*all') + File:close() + + return JSON:decode(str) + end + end + + function Utils.merge(table1, table2) + local result = {} + for i,v in pairs(table1) do + result[i] = v + end + + for i,v in pairs(table2) do + result[i] = v + end + + return result + end + + function Utils.log(func) + return function(p1,p2,p3,p4,p5,p6,p7,p8,p9,p10) + local err, msg = pcall(func,p1,p2,p3,p4,p5,p6,p7,p8,p9,p10) + if not err then + env.info("ERROR - callFunc\n"..msg) + env.info('Traceback\n'..debug.traceback()) + end + end + end +end + + + +-----------------[[ END OF Utils.lua ]]----------------- + + + +-----------------[[ MenuRegistry.lua ]]----------------- + +MenuRegistry = {} + +do + MenuRegistry.menus = {} + function MenuRegistry:register(order, registerfunction, context) + for i=1,order,1 do + if not MenuRegistry.menus[i] then MenuRegistry.menus[i] = {func = function() end, context = {}} end + end + + MenuRegistry.menus[order] = {func = registerfunction, context = context} + end + + local ev = {} + function ev:onEvent(event) + if event.id == world.event.S_EVENT_BIRTH and event.initiator and event.initiator.getPlayerName then + local player = event.initiator:getPlayerName() + if player then + env.info('MenuRegistry - creating menus for player: '..player) + for i,v in ipairs(MenuRegistry.menus) do + local err, msg = pcall(v.func, event, v.context) + if not err then + env.info("MenuRegistry - ERROR :\n"..msg) + env.info('Traceback\n'..debug.traceback()) + end + end + end + end + end + + world.addEventHandler(ev) + + + function MenuRegistry.showTargetZoneMenu(groupid, name, action, targetside, minDistToFront) + local executeAction = function(act, params) + local err = act(params) + if not err then + missionCommands.removeItemForGroup(params.groupid, params.menu) + end + end + + local menu = missionCommands.addSubMenuForGroup(groupid, name) + local sub1 = nil + local zones = ZoneCommand.getAllZones() + + local zns = {} + for i,v in pairs(zones) do + if not targetside or v.side == targetside then + if not minDistToFront or v.distToFront <= minDistToFront then + table.insert(zns, v) + end + end + end + + table.sort(zns, function(a,b) return a.name < b.name end) + + local count = 0 + for i,v in ipairs(zns) do + count = count + 1 + if count<10 then + missionCommands.addCommandForGroup(groupid, v.name, menu, executeAction, action, {zone = v, menu=menu, groupid=groupid}) + elseif count==10 then + sub1 = missionCommands.addSubMenuForGroup(groupid, "More", menu) + missionCommands.addCommandForGroup(groupid, v.name, sub1, executeAction, action, {zone = v, menu=menu, groupid=groupid}) + elseif count%9==1 then + sub1 = missionCommands.addSubMenuForGroup(groupid, "More", sub1) + missionCommands.addCommandForGroup(groupid, v.name, sub1, executeAction, action, {zone = v, menu=menu, groupid=groupid}) + else + missionCommands.addCommandForGroup(groupid, v.name, sub1, executeAction, action, {zone = v, menu=menu, groupid=groupid}) + end + end + + return menu + end +end + +-----------------[[ END OF MenuRegistry.lua ]]----------------- + + + +-----------------[[ CustomZone.lua ]]----------------- + + +CustomZone = {} +do + function CustomZone:getByName(name) + local obj = {} + obj.name = name + + local zd = nil + for _,v in ipairs(env.mission.triggers.zones) do + if v.name == name then + zd = v + break + end + end + + if not zd then + return nil + end + + obj.type = zd.type -- 2 == quad, 0 == circle + if obj.type == 2 then + obj.vertices = {} + for _,v in ipairs(zd.verticies) do + local vertex = { + x = v.x, + y = 0, + z = v.y + } + table.insert(obj.vertices, vertex) + end + end + + obj.radius = zd.radius + obj.point = { + x = zd.x, + y = 0, + z = zd.y + } + + setmetatable(obj, self) + self.__index = self + return obj + end + + function CustomZone:isQuad() + return self.type==2 + end + + function CustomZone:isCircle() + return self.type==0 + end + + function CustomZone:isInside(point) + if self:isCircle() then + local dist = mist.utils.get2DDist(point, self.point) + return dist 0 then + return true + end + end + end + end + end + + function GroupMonitor:sendHome(trackedGroup) + if trackedGroup.home == nil then + env.info("GroupMonitor - sendHome "..trackedGroup.name..' does not have home set') + return + end + + if trackedGroup.returning then return end + + + local gr = Group.getByName(trackedGroup.name) + if gr then + if trackedGroup.product.missionType == ZoneCommand.missionTypes.cas_helo then + local hsp = trigger.misc.getZone(trackedGroup.home.name..'-hsp') + if not hsp then + hsp = trigger.misc.getZone(trackedGroup.home.name) + end + + local alt = self.connectionManager:getHeliAlt(trackedGroup.target.name, trackedGroup.home.name) + TaskExtensions.landAtPointFromAir(gr, {x=hsp.point.x, y=hsp.point.z}, alt) + else + local homeZn = trigger.misc.getZone(trackedGroup.home.name) + TaskExtensions.landAtAirfield(gr, {x=homeZn.point.x, y=homeZn.point.z}) + end + + local cnt = gr:getController() + cnt:setOption(0,4) -- force ai hold fire + cnt:setOption(1, 4) -- force reaction on threat to allow abort + + trackedGroup.returning = true + env.info('GroupMonitor - sendHome ['..trackedGroup.name..'] returning home') + end + end + + function GroupMonitor:registerGroup(product, target, home, savedData) + self.groups[product.name] = {name = product.name, lastStateTime = timer.getAbsTime(), product = product, target = target, home = home} + + if savedData and savedData.state ~= 'uninitialized' then + env.info('GroupMonitor - registerGroup ['..product.name..'] restored state '..savedData.state..' dur:'..savedData.lastStateDuration) + self.groups[product.name].state = savedData.state + self.groups[product.name].lastStateTime = timer.getAbsTime() - savedData.lastStateDuration + end + end + + function GroupMonitor:start() + timer.scheduleFunction(function(param, time) + local self = param.context + + for i,v in pairs(self.groups) do + local isDead = false + if v.product.missionType == 'supply_convoy' or v.product.missionType == 'assault' then + isDead = self:processSurface(v) + if isDead then + MissionTargetRegistry.removeBaiTarget(v) --safety measure in case group is dead + end + else + isDead = self:processAir(v) + end + + if isDead then + self.groups[i] = nil + end + end + + return time+10 + end, {context = self}, timer.getTime()+1) + end + + function GroupMonitor:getGroup(name) + return self.groups[name] + end + + function GroupMonitor:processSurface(group) -- states: [started, enroute, atdestination, siege] + local gr = Group.getByName(group.name) + if not gr then return true end + if gr:getSize()==0 then + gr:destroy() + return true + end + + if not group.state then + group.state = 'started' + lastStateTime = timer.getAbsTime() + env.info('GroupMonitor: processSurface ['..group.name..'] starting') + end + + if group.state =='started' then + if gr then + local firstUnit = gr:getUnit(1):getName() + local z = ZoneCommand.getZoneOfUnit(firstUnit) + + if not z then + env.info('GroupMonitor: processSurface ['..group.name..'] is enroute') + group.state = 'enroute' + group.lastStateTime = timer.getAbsTime() + MissionTargetRegistry.addBaiTarget(group) + elseif timer.getAbsTime() - group.lastStateTime > GroupMonitor.blockedDespawnTime then + env.info('GroupMonitor: processSurface ['..group.name..'] despawned due to blockage') + gr:destroy() + local todeliver = math.floor(group.product.cost) + z:addResource(todeliver) + return true + end + end + elseif group.state =='enroute' then + if gr then + local firstUnit = gr:getUnit(1):getName() + local z = ZoneCommand.getZoneOfUnit(firstUnit) + + if z and (z.name==group.target.name or z.name==group.home.name) then + MissionTargetRegistry.removeBaiTarget(group) + + if group.product.missionType == 'supply_convoy' then + env.info('GroupMonitor: processSurface ['..group.name..'] has arrived at destination') + group.state = 'atdestination' + group.lastStateTime = timer.getAbsTime() + z:capture(gr:getCoalition()) + local percentSurvived = gr:getSize()/gr:getInitialSize() + local todeliver = math.floor(group.product.cost * percentSurvived) + z:addResource(todeliver) + env.info('GroupMonitor: processSurface ['..group.name..'] has supplied ['..z.name..'] with ['..todeliver..']') + elseif group.product.missionType == 'assault' then + if z.side == gr:getCoalition() then + env.info('GroupMonitor: processSurface ['..group.name..'] has arrived at destination') + group.state = 'atdestination' + group.lastStateTime = timer.getAbsTime() + local percentSurvived = gr:getSize()/gr:getInitialSize() + local torecover = math.floor(group.product.cost * percentSurvived * GroupMonitor.recoveryReduction) + z:addResource(torecover) + env.info('GroupMonitor: processSurface ['..z.name..'] has recovered ['..torecover..'] from ['..group.name..']') + elseif z.side == 0 then + env.info('GroupMonitor: processSurface ['..group.name..'] has arrived at destination') + group.state = 'atdestination' + group.lastStateTime = timer.getAbsTime() + z:capture(gr:getCoalition()) + env.info('GroupMonitor: processSurface ['..group.name..'] has captured ['..z.name..']') + elseif z.side ~= gr:getCoalition() and z.side ~= 0 then + env.info('GroupMonitor: processSurface ['..group.name..'] starting siege') + group.state = 'siege' + group.lastStateTime = timer.getAbsTime() + end + end + else + if group.product.missionType == 'supply_convoy' then + if not group.returning and group.target and group.target.side ~= group.product.side and group.target.side ~= 0 then + local supplyPoint = trigger.misc.getZone(group.home.name..'-sp') + if not supplyPoint then + supplyPoint = trigger.misc.getZone(group.home.name) + end + + if supplyPoint then + group.returning = true + env.info('GroupMonitor: processSurface ['..group.name..'] returning home') + TaskExtensions.moveOnRoadToPoint(gr, {x=supplyPoint.point.x, y=supplyPoint.point.z}) + end + end + elseif group.product.missionType == 'assault' then + local frUnit = gr:getUnit(1) + if frUnit then + local controller = frUnit:getController() + local targets = controller:getDetectedTargets() + + local shouldstop = false + if #targets > 0 then + for _,tgt in ipairs(targets) do + if tgt.visible and tgt.object then + if tgt.object.getCoalition and tgt.object:getCoalition()~=frUnit:getCoalition() and + tgt.object.getCategory and tgt.object:getCategory() == 1 then + local dist = mist.utils.get3DDist(frUnit:getPoint(), tgt.object:getPoint()) + if dist < 1000 then + if not group.isstopped then + env.info('GroupMonitor: processSurface ['..group.name..'] stopping to engage targets') + --gr:getController():setCommand({id = 'StopRoute', params = { value = true}}) + TaskExtensions.stopAndDisperse(gr) + group.isstopped = true + end + shouldstop = true + break + end + end + end + end + end + + if not shouldstop and group.isstopped then + env.info('GroupMonitor: processSurface ['..group.name..'] resuming mission') + --gr:getController():setCommand({id = 'StopRoute', params = { value = false}}) + local tp = { + x = group.target.zone.point.x, + y = group.target.zone.point.z + } + + TaskExtensions.moveOnRoadToPointAndAssault(gr, tp, group.target.built) + group.isstopped = false + end + end + end + end + end + elseif group.state == 'atdestination' then + if timer.getAbsTime() - group.lastStateTime > GroupMonitor.atDestinationDespawnTime then + + if gr then + local firstUnit = gr:getUnit(1):getName() + local z = ZoneCommand.getZoneOfUnit(firstUnit) + if z and z.side == 0 then + env.info('GroupMonitor: processSurface ['..group.name..'] is at neutral zone') + z:capture(gr:getCoalition()) + env.info('GroupMonitor: processSurface ['..group.name..'] has captured ['..z.name..']') + else + env.info('GroupMonitor: processSurface ['..group.name..'] starting siege') + group.state = 'siege' + group.lastStateTime = timer.getAbsTime() + end + + env.info('GroupMonitor: processSurface ['..group.name..'] despawned after arriving at destination') + gr:destroy() + return true + end + end + elseif group.state == 'siege' then + if group.product.missionType ~= 'assault' then + group.state = 'atdestination' + group.lastStateTime = timer.getAbsTime() + else + if timer.getAbsTime() - group.lastStateTime > GroupMonitor.siegeExplosiveTime then + if gr then + local firstUnit = gr:getUnit(1):getName() + local z = ZoneCommand.getZoneOfUnit(firstUnit) + local success = false + + if z then + for i,v in pairs(z.built) do + if v.type == 'upgrade' and v.side ~= gr:getCoalition() then + local st = StaticObject.getByName(v.name) + if not st then st = Group.getByName(v.name) end + local pos = st:getPoint() + trigger.action.explosion(pos, GroupMonitor.siegeExplosiveStrength) + group.lastStateTime = timer.getAbsTime() + success = true + env.info('GroupMonitor: processSurface ['..group.name..'] detonating structure at '..z.name) + break + end + end + end + + if not success then + env.info('GroupMonitor: processSurface ['..group.name..'] no targets to detonate, switching to atdestination') + group.state = 'atdestination' + group.lastStateTime = timer.getAbsTime() + end + end + end + end + end + end + + function GroupMonitor:processAir(group)-- states: [takeoff, inair, landed] + local gr = Group.getByName(group.name) + if not gr then return true end + if gr:getSize()==0 then + gr:destroy() + return true + end + --[[ + if group.product.missionType == 'cas' or group.product.missionType == 'cas_helo' or group.product.missionType == 'strike' or group.product.missionType == 'sead' then + if MissionTargetRegistry.isZoneTargeted(group.target) and group.product.side == 2 and not group.returning then + env.info('GroupMonitor - mission ['..group.name..'] to ['..group.target..'] canceled due to player mission') + + GroupMonitor.sendHome(group) + end + end + ]]-- + + if not group.state then + group.state = 'takeoff' + env.info('GroupMonitor: processAir ['..group.name..'] taking off') + end + + if group.state =='takeoff' then + if timer.getAbsTime() - group.lastStateTime > GroupMonitor.blockedDespawnTime then + if gr and Utils.allGroupIsLanded(gr) then + env.info('GroupMonitor: processAir ['..group.name..'] is blocked, despawning') + local frUnit = gr:getUnit(1) + if frUnit then + local firstUnit = frUnit:getName() + local z = ZoneCommand.getZoneOfUnit(firstUnit) + if z then + z:addResource(group.product.cost) + env.info('GroupMonitor: processAir ['..z.name..'] has recovered ['..group.product.cost..'] from ['..group.name..']') + end + end + + gr:destroy() + return true + end + elseif gr and Utils.someOfGroupInAir(gr) then + env.info('GroupMonitor: processAir ['..group.name..'] is in the air') + group.state = 'inair' + group.lastStateTime = timer.getAbsTime() + end + elseif group.state =='inair' then + if gr and Utils.allGroupIsLanded(gr) then + env.info('GroupMonitor: processAir ['..group.name..'] has landed') + group.state = 'landed' + group.lastStateTime = timer.getAbsTime() + + local unit = gr:getUnit(1) + if unit then + local firstUnit = unit:getName() + local z = ZoneCommand.getZoneOfUnit(firstUnit) + + if group.product.missionType == 'supply_air' then + if z then + z:capture(gr:getCoalition()) + z:addResource(group.product.cost) + env.info('GroupMonitor: processAir ['..group.name..'] has supplied ['..z.name..'] with ['..group.product.cost..']') + end + else + if z and z.side == gr:getCoalition() then + local percentSurvived = gr:getSize()/gr:getInitialSize() + local torecover = math.floor(group.product.cost * percentSurvived * GroupMonitor.recoveryReduction) + z:addResource(torecover) + env.info('GroupMonitor: processAir ['..z.name..'] has recovered ['..torecover..'] from ['..group.name..']') + end + end + else + env.info('GroupMonitor: processAir ['..group.name..'] size ['..gr:getSize()..'] has no unit 1') + end + elseif gr then + if GroupMonitor.isAirAttack(group.product.missionType) and not group.returning then + if not GroupMonitor.hasWeapons(gr) then + env.info('GroupMonitor: processAir ['..group.name..'] size ['..gr:getSize()..'] has no weapons outside of shells') + self:sendHome(group) + elseif group.product.missionType == ZoneCommand.missionTypes.cas_helo then + local frUnit = gr:getUnit(1) + local controller = frUnit:getController() + local targets = controller:getDetectedTargets() + + local tgtToEngage = {} + if #targets > 0 then + for _,tgt in ipairs(targets) do + if tgt.visible and tgt.object and tgt.object.isExist and tgt.object:isExist() then + if tgt.object.getCategory and tgt.object:getCategory() == Object.Category.UNIT and + tgt.object.getCoalition and tgt.object:getCoalition()~=frUnit:getCoalition() and + tgt.object:getDesc().category == Unit.Category.GROUND_UNIT then + + local dist = mist.utils.get3DDist(frUnit:getPoint(), tgt.object:getPoint()) + if dist < 2000 then + table.insert(tgtToEngage, tgt.object) + end + end + end + end + end + + if not group.isengaging and #tgtToEngage > 0 then + env.info('GroupMonitor: processAir ['..group.name..'] engaging targets') + TaskExtensions.heloEngageTargets(gr, tgtToEngage, group.product.expend) + group.isengaging = true + group.startedEngaging = timer.getAbsTime() + elseif group.isengaging and #tgtToEngage == 0 and group.startedEngaging and (timer.getAbsTime() - group.startedEngaging) > 60*5 then + env.info('GroupMonitor: processAir ['..group.name..'] resuming mission') + if group.returning then + group.returning = nil + self:sendHome(group) + else + local homePos = group.home.zone.point + TaskExtensions.executeHeloCasMission(gr, group.target.built, group.product.expend, group.product.altitude, {homePos = homePos}) + end + group.isengaging = false + end + end + elseif group.product.missionType == 'supply_air' then + if not group.returning and group.target and group.target.side ~= group.product.side and group.target.side ~= 0 then + local supplyPoint = trigger.misc.getZone(group.home.name..'-hsp') + if not supplyPoint then + supplyPoint = trigger.misc.getZone(group.home.name) + end + + if supplyPoint then + group.returning = true + local alt = self.connectionManager:getHeliAlt(group.target.name, group.home.name) + TaskExtensions.landAtPointFromAir(gr, {x=supplyPoint.point.x, y=supplyPoint.point.z}, alt) + env.info('GroupMonitor: processAir ['..group.name..'] returning home') + end + end + end + end + elseif group.state =='landed' then + if timer.getAbsTime() - group.lastStateTime > GroupMonitor.landedDespawnTime then + if gr then + env.info('GroupMonitor: processAir ['..group.name..'] despawned after landing') + gr:destroy() + return true + end + end + end + end +end + +-----------------[[ END OF GroupMonitor.lua ]]----------------- + + + +-----------------[[ ConnectionManager.lua ]]----------------- + +ConnectionManager = {} +do + ConnectionManager.currentLineIndex = 5000 + function ConnectionManager:new() + local obj = {} + obj.connections = {} + obj.zoneConnections = {} + obj.heliAlts = {} + obj.blockedRoads = {} + setmetatable(obj, self) + self.__index = self + + return obj + end + + function ConnectionManager:addConnection(f, t, blockedRoad, heliAlt) + local i = ConnectionManager.currentLineIndex + ConnectionManager.currentLineIndex = ConnectionManager.currentLineIndex + 1 + + table.insert(self.connections, {from=f, to=t, index=i}) + self.zoneConnections[f] = self.zoneConnections[f] or {} + self.zoneConnections[t] = self.zoneConnections[t] or {} + self.zoneConnections[f][t] = true + self.zoneConnections[t][f] = true + + if heliAlt then + self.heliAlts[f] = self.heliAlts[f] or {} + self.heliAlts[t] = self.heliAlts[t] or {} + self.heliAlts[f][t] = heliAlt + self.heliAlts[t][f] = heliAlt + end + + if blockedRoad then + self.blockedRoads[f] = self.blockedRoads[f] or {} + self.blockedRoads[t] = self.blockedRoads[t] or {} + self.blockedRoads[f][t] = true + self.blockedRoads[t][f] = true + end + + local from = CustomZone:getByName(f) + local to = CustomZone:getByName(t) + + if not from then env.info("ConnectionManager - addConnection: missing zone "..f) end + if not to then env.info("ConnectionManager - addConnection: missing zone "..t) end + + if blockedRoad then + trigger.action.lineToAll(-1, i, from.point, to.point, {1,1,1,0.5}, 3) + else + trigger.action.lineToAll(-1, i, from.point, to.point, {1,1,1,0.5}, 2) + end + end + + function ConnectionManager:getConnectionsOfZone(zonename) + if not self.zoneConnections[zonename] then return {} end + + local connections = {} + for i,v in pairs(self.zoneConnections[zonename]) do + table.insert(connections, i) + end + + return connections + end + + function ConnectionManager:isRoadBlocked(f,t) + if self.blockedRoads[f] then + return self.blockedRoads[f][t] + end + + if self.blockedRoads[t] then + return self.blockedRoads[t][f] + end + end + + function ConnectionManager:getHeliAltSimple(f,t) + if self.heliAlts[f] then + if self.heliAlts[f][t] then + return self.heliAlts[f][t] + end + end + + if self.heliAlts[t] then + if self.heliAlts[t][f] then + return self.heliAlts[t][f] + end + end + end + + function ConnectionManager:getHeliAlt(f,t) + local alt = self:getHeliAltSimple(f,t) + if alt then return alt end + + if self.heliAlts[f] then + local max = -1 + for zn,_ in pairs(self.heliAlts[f]) do + local alt = self:getHeliAltSimple(f, zn) + if alt then + if alt > max then + max = alt + end + end + + alt = self:getHeliAltSimple(zn, t) + if alt then + if alt > max then + max = alt + end + end + end + + if max > 0 then return max end + end + + if self.heliAlts[t] then + local max = -1 + for zn,_ in pairs(self.heliAlts[t]) do + local alt = self:getHeliAltSimple(t, zn) + if alt then + if alt > max then + max = alt + end + end + + alt = self:getHeliAltSimple(zn, f) + if alt then + if alt > max then + max = alt + end + end + end + + if max > 0 then return max end + end + end +end + +-----------------[[ END OF ConnectionManager.lua ]]----------------- + + + +-----------------[[ TaskExtensions.lua ]]----------------- + +TaskExtensions = {} +do + function TaskExtensions.getAttackTask(targetName, expend, altitude) + local tgt = Group.getByName(targetName) + if tgt then + return { + id = 'AttackGroup', + params = { + groupId = tgt:getID(), + expend = expend, + weaponType = Weapon.flag.AnyWeapon, + groupAttack = true, + altitudeEnabled = (altitude ~= nil), + altitude = altitude + } + } + else + tgt = StaticObject.getByName(targetName) + if not tgt then tgt = Unit.getByName(targetName) end + if tgt then + return { + id = 'AttackUnit', + params = { + unitId = tgt:getID(), + expend = expend, + weaponType = Weapon.flag.AnyWeapon, + groupAttack = true, + altitudeEnabled = (altitude ~= nil), + altitude = altitude + } + } + end + end + end + + function TaskExtensions.getDefaultWaypoints(startPos, task, tgpos, reactivated) + local defwp = { + id='Mission', + params = { + route = { + airborne = true, + points = {} + } + } + } + + if reactivated then + table.insert(defwp.params.route.points, { + type= AI.Task.WaypointType.TURNING_POINT, + x = reactivated.currentPos.x, + y = reactivated.currentPos.z, + speed = 257, + action = AI.Task.TurnMethod.FLY_OVER_POINT, + alt = 4572, + alt_type = AI.Task.AltitudeType.BARO, + task = task + }) + else + table.insert(defwp.params.route.points, { + type= AI.Task.WaypointType.TAKEOFF, + x = startPos.x, + y = startPos.z, + speed = 0, + action = AI.Task.TurnMethod.FIN_POINT, + alt = 0, + alt_type = AI.Task.AltitudeType.RADIO + }) + + table.insert(defwp.params.route.points, { + type= AI.Task.WaypointType.TURNING_POINT, + x = startPos.x, + y = startPos.z, + speed = 257, + action = AI.Task.TurnMethod.FLY_OVER_POINT, + alt = 4572, + alt_type = AI.Task.AltitudeType.BARO, + task = task + }) + end + + if tgpos then + table.insert(defwp.params.route.points, { + type= AI.Task.WaypointType.TURNING_POINT, + x = tgpos.x, + y = tgpos.z, + speed = 257, + action = AI.Task.TurnMethod.FLY_OVER_POINT, + alt = 4572, + alt_type = AI.Task.AltitudeType.BARO, + task = task + }) + end + + table.insert(defwp.params.route.points, { + type= AI.Task.WaypointType.LAND, + x = startPos.x, + y = startPos.z, + speed = 257, + action = AI.Task.TurnMethod.FIN_POINT, + alt = 0, + alt_type = AI.Task.AltitudeType.RADIO + }) + + return defwp + end + + function TaskExtensions.executeSeadMission(group,targets, expend, altitude, reactivated) + if not group then return end + if not group:isExist() or group:getSize()==0 then return end + local startPos = group:getUnit(1):getPoint() + + if reactivated then + reactivated.currentPos = startPos + startPos = reactivated.homePos + end + + local expCount = AI.Task.WeaponExpend.ALL + if expend then + expCount = expend + end + + local alt = 4572 + if altitude then + alt = altitude/3.281 + end + + local viable = {} + for i,v in pairs(targets) do + if v.type == 'defense' and v.side ~= group:getCoalition() then + local gr = Group.getByName(v.name) + for _,unit in ipairs(gr:getUnits()) do + if unit:hasAttribute('SAM SR') or unit:hasAttribute('SAM TR') then + table.insert(viable, unit:getName()) + end + end + end + end + + local attack = { + id = 'ComboTask', + params = { + tasks = { + { + id = 'EngageTargets', + params = { + targetTypes = {'SAM SR', 'SAM TR'} + } + } + } + } + } + + for i,v in ipairs(viable) do + local task = TaskExtensions.getAttackTask(v, expCount, alt) + table.insert(attack.params.tasks, task) + end + + local firstunitpos = nil + local tgt = viable[1] + if tgt then + firstunitpos = Unit.getByName(tgt):getPoint() + end + + local mis = TaskExtensions.getDefaultWaypoints(startPos, attack, firstunitpos, reactivated) + + group:getController():setTask(mis) + TaskExtensions.setDefaultAG(group) + end + + function TaskExtensions.executeStrikeMission(group,targets, expend, altitude, reactivated) + if not group then return end + if not group:isExist() or group:getSize()==0 then return end + local startPos = group:getUnit(1):getPoint() + + if reactivated then + reactivated.currentPos = startPos + startPos = reactivated.homePos + end + + local expCount = AI.Task.WeaponExpend.ALL + if expend then + expCount = expend + end + + local alt = 4572 + if altitude then + alt = altitude/3.281 + end + + local attack = { + id = 'ComboTask', + params = { + tasks = { + } + } + } + + for i,v in pairs(targets) do + if v.type == 'upgrade' and v.side ~= group:getCoalition() then + local task = TaskExtensions.getAttackTask(v.name, expCount, alt) + table.insert(attack.params.tasks, task) + end + end + + local mis = TaskExtensions.getDefaultWaypoints(startPos, attack, nil, reactivated) + + group:getController():setTask(mis) + TaskExtensions.setDefaultAG(group) + end + + function TaskExtensions.executeCasMission(group, targets, expend, altitude, reactivated) + if not group then return end + if not group:isExist() or group:getSize()==0 then return end + local startPos = group:getUnit(1):getPoint() + + if reactivated then + reactivated.currentPos = startPos + startPos = reactivated.homePos + end + + local attack = { + id = 'ComboTask', + params = { + tasks = { + } + } + } + + local expCount = AI.Task.WeaponExpend.ONE + if expend then + expCount = expend + end + + local alt = 4572 + if altitude then + alt = altitude/3.281 + end + + for i,v in pairs(targets) do + if v.type == 'defense' then + local g = Group.getByName(i) + if g and g:getCoalition()~=group:getCoalition() then + local task = TaskExtensions.getAttackTask(i, expCount, alt) + table.insert(attack.params.tasks, task) + end + end + end + + local mis = TaskExtensions.getDefaultWaypoints(startPos, attack, nil, reactivated) + + group:getController():setTask(mis) + TaskExtensions.setDefaultAG(group) + end + + function TaskExtensions.executeBaiMission(group, targets, expend, altitude, reactivated) + if not group then return end + if not group:isExist() or group:getSize()==0 then return end + local startPos = group:getUnit(1):getPoint() + + if reactivated then + reactivated.currentPos = startPos + startPos = reactivated.homePos + end + + local attack = { + id = 'ComboTask', + params = { + tasks = { + { + id = 'EngageTargets', + params = { + targetTypes = {'Vehicles'} + } + } + } + } + } + + local expCount = AI.Task.WeaponExpend.ONE + if expend then + expCount = expend + end + + local alt = 4572 + if altitude then + alt = altitude/3.281 + end + + for i,v in pairs(targets) do + if v.type == 'mission' and (v.missionType == 'assault' or v.missionType == 'supply_convoy') then + local g = Group.getByName(i) + if g and g:getSize()>0 and g:getCoalition()~=group:getCoalition() then + local task = TaskExtensions.getAttackTask(i, expCount, alt) + table.insert(attack.params.tasks, task) + end + end + end + + local mis = TaskExtensions.getDefaultWaypoints(startPos, attack, nil, reactivated) + + group:getController():setTask(mis) + TaskExtensions.setDefaultAG(group) + end + + function TaskExtensions.heloEngageTargets(group, targets, expend) + if not group then return end + if not group:isExist() or group:getSize()==0 then return end + local startPos = group:getUnit(1):getPoint() + + local attack = { + id = 'ComboTask', + params = { + tasks = { + } + } + } + + local expCount = AI.Task.WeaponExpend.ONE + if expend then + expCount = expend + end + + for i,v in pairs(targets) do + local task = { + id = 'AttackUnit', + params = { + unitId = v:getID(), + expend = expend, + weaponType = Weapon.flag.AnyWeapon, + groupAttack = true + } + } + + table.insert(attack.params.tasks, task) + end + + group:getController():pushTask(attack) + end + + function TaskExtensions.executeHeloCasMission(group, targets, expend, altitude, reactivated) + if not group then return end + if not group:isExist() or group:getSize()==0 then return end + local startPos = group:getUnit(1):getPoint() + + if reactivated then + reactivated.currentPos = startPos + startPos = reactivated.homePos + end + + local attack = { + id = 'ComboTask', + params = { + tasks = { + } + } + } + + local expCount = AI.Task.WeaponExpend.ONE + if expend then + expCount = expend + end + + local alt = 61 + if altitude then + alt = altitude/3.281 + end + + for i,v in pairs(targets) do + if v.type == 'defense' then + local g = Group.getByName(i) + if g and g:getCoalition()~=group:getCoalition() then + local task = TaskExtensions.getAttackTask(i, expCount, alt) + table.insert(attack.params.tasks, task) + end + end + end + + local land = { + id='Land', + params = { + point = {x = startPos.x, y=startPos.z} + } + } + + local mis = { + id='Mission', + params = { + route = { + airborne = true, + points = {} + } + } + } + + if reactivated then + table.insert(mis.params.route.points, { + type= AI.Task.WaypointType.TURNING_POINT, + x = reactivated.currentPos.x+1000, + y = reactivated.currentPos.z+1000, + speed = 257, + action = AI.Task.TurnMethod.FLY_OVER_POINT, + alt = alt, + alt_type = AI.Task.AltitudeType.RADIO, + task = attack + }) + else + table.insert(mis.params.route.points, { + type= AI.Task.WaypointType.TAKEOFF, + x = startPos.x, + y = startPos.z, + speed = 0, + action = AI.Task.TurnMethod.FIN_POINT, + alt = 0, + alt_type = AI.Task.AltitudeType.RADIO, + }) + + table.insert(mis.params.route.points, { + type= AI.Task.WaypointType.TURNING_POINT, + x = startPos.x+1000, + y = startPos.z+1000, + speed = 257, + action = AI.Task.TurnMethod.FLY_OVER_POINT, + alt = alt, + alt_type = AI.Task.AltitudeType.RADIO, + task = attack + }) + end + + table.insert(mis.params.route.points, { + type= AI.Task.WaypointType.TURNING_POINT, + x = startPos.x, + y = startPos.z, + speed = 257, + action = AI.Task.TurnMethod.FIN_POINT, + alt = alt, + alt_type = AI.Task.AltitudeType.RADIO, + task = land + }) + + group:getController():setTask(mis) + TaskExtensions.setDefaultAG(group) + end + + function TaskExtensions.executeTankerMission(group, point, altitude, frequency, tacan, reactivated) + if not group then return end + if not group:isExist() or group:getSize()==0 then return end + local startPos = group:getUnit(1):getPoint() + if reactivated then + reactivated.currentPos = startPos + startPos = reactivated.homePos + end + + local alt = 4572 + if altitude then + alt = altitude/3.281 + end + + local freq = 259500000 + if frequency then + freq = math.floor(frequency*1000000) + end + + local setfreq = { + id = 'SetFrequency', + params = { + frequency = freq, + modulation = 0 + } + } + + local setbeacon = { + id = 'ActivateBeacon', + params = { + type = 4, -- TACAN type + system = 4, -- Tanker TACAN + name = 'tacan task', + callsign = group:getUnit(1):getCallsign():sub(1,3), + frequency = tacan + } + } + + local distFromPoint = 20000 + local theta = math.random() * 2 * math.pi + + local dx = distFromPoint * math.cos(theta) + local dy = distFromPoint * math.sin(theta) + + local pos1 = { + x = point.x + dx, + y = point.z + dy + } + + local pos2 = { + x = point.x - dx, + y = point.z - dy + } + + local orbit = { + id = 'Orbit', + params = { + pattern = AI.Task.OrbitPattern.RACE_TRACK, + point = pos1, + point2 = pos2, + speed = 195, + altitude = alt + } + } + + local script = { + id = "WrappedAction", + params = { + action = { + id = "Script", + params = + { + command = "trigger.action.outTextForCoalition("..group:getCoalition()..", 'Tanker on station. "..(freq/1000000).." AM', 15)", + } + } + } + } + + local tanker = { + id = 'Tanker', + params = { + } + } + + local task = { + id='Mission', + params = { + route = { + airborne = true, + points = {} + } + } + } + + if reactivated then + table.insert(task.params.route.points, { + type= AI.Task.WaypointType.TURNING_POINT, + x = pos1.x, + y = pos1.y, + speed = 450, + action = AI.Task.TurnMethod.FLY_OVER_POINT, + alt = alt, + alt_type = AI.Task.AltitudeType.BARO, + task = tanker + }) + else + table.insert(task.params.route.points, { + type= AI.Task.WaypointType.TAKEOFF, + x = startPos.x, + y = startPos.z, + speed = 0, + action = AI.Task.TurnMethod.FIN_POINT, + alt = 0, + alt_type = AI.Task.AltitudeType.RADIO, + task = tanker + }) + end + + table.insert(task.params.route.points, { + type= AI.Task.WaypointType.TURNING_POINT, + x = pos1.x, + y = pos1.y, + speed = 195, + action = AI.Task.TurnMethod.FLY_OVER_POINT, + alt = alt, + alt_type = AI.Task.AltitudeType.BARO, + task = { + id = 'ComboTask', + params = { + tasks = { + script + } + } + } + }) + + table.insert(task.params.route.points, { + type= AI.Task.WaypointType.TURNING_POINT, + x = pos2.x, + y = pos2.y, + speed = 195, + action = AI.Task.TurnMethod.FLY_OVER_POINT, + alt = alt, + alt_type = AI.Task.AltitudeType.BARO, + task = { + id = 'ComboTask', + params = { + tasks = { + orbit + } + } + } + }) + + table.insert(task.params.route.points, { + type= AI.Task.WaypointType.LAND, + x = startPos.x, + y = startPos.z, + speed = 450, + action = AI.Task.TurnMethod.FIN_POINT, + alt = 0, + alt_type = AI.Task.AltitudeType.RADIO + }) + + group:getController():setTask(task) + group:getController():setOption(AI.Option.Air.id.REACTION_ON_THREAT, AI.Option.Air.val.REACTION_ON_THREAT.PASSIVE_DEFENCE) + group:getController():setCommand(setfreq) + group:getController():setCommand(setbeacon) + end + + function TaskExtensions.executeAwacsMission(group, point, altitude, frequency, reactivated) + if not group then return end + if not group:isExist() or group:getSize()==0 then return end + local startPos = group:getUnit(1):getPoint() + if reactivated then + reactivated.currentPos = startPos + startPos = reactivated.homePos + end + + local alt = 4572 + if altitude then + alt = altitude/3.281 + end + + local freq = 259500000 + if frequency then + freq = math.floor(frequency*1000000) + end + + local setfreq = { + id = 'SetFrequency', + params = { + frequency = freq, + modulation = 0 + } + } + + local distFromPoint = 10000 + local theta = math.random() * 2 * math.pi + + local dx = distFromPoint * math.cos(theta) + local dy = distFromPoint * math.sin(theta) + + local pos1 = { + x = point.x + dx, + y = point.z + dy + } + + local pos2 = { + x = point.x - dx, + y = point.z - dy + } + + local orbit = { + id = 'Orbit', + params = { + pattern = AI.Task.OrbitPattern.RACE_TRACK, + point = pos1, + point2 = pos2, + altitude = alt + } + } + + local script = { + id = "WrappedAction", + params = { + action = { + id = "Script", + params = + { + command = "trigger.action.outTextForCoalition("..group:getCoalition()..", 'AWACS on station. "..(freq/1000000).." AM', 15)", + } + } + } + } + + local awacs = { + id = 'AWACS', + params = { + } + } + + local task = { + id='Mission', + params = { + route = { + airborne = true, + points = {} + } + } + } + + if reactivated then + table.insert(task.params.route.points, { + type= AI.Task.WaypointType.TURNING_POINT, + x = pos1.x, + y = pos1.y, + speed = 257, + action = AI.Task.TurnMethod.FLY_OVER_POINT, + alt = alt, + alt_type = AI.Task.AltitudeType.BARO, + task = awacs + }) + else + table.insert(task.params.route.points, { + type= AI.Task.WaypointType.TAKEOFF, + x = startPos.x, + y = startPos.z, + speed = 0, + action = AI.Task.TurnMethod.FIN_POINT, + alt = 0, + alt_type = AI.Task.AltitudeType.RADIO, + task = awacs + }) + end + + table.insert(task.params.route.points, { + type= AI.Task.WaypointType.TURNING_POINT, + x = pos1.x, + y = pos1.y, + speed = 257, + action = AI.Task.TurnMethod.FLY_OVER_POINT, + alt = alt, + alt_type = AI.Task.AltitudeType.BARO, + task = { + id = 'ComboTask', + params = { + tasks = { + script + } + } + } + }) + + table.insert(task.params.route.points, { + type= AI.Task.WaypointType.TURNING_POINT, + x = pos2.x, + y = pos2.y, + speed = 257, + action = AI.Task.TurnMethod.FLY_OVER_POINT, + alt = alt, + alt_type = AI.Task.AltitudeType.BARO, + task = { + id = 'ComboTask', + params = { + tasks = { + orbit + } + } + } + }) + + table.insert(task.params.route.points, { + type= AI.Task.WaypointType.LAND, + x = startPos.x, + y = startPos.z, + speed = 257, + action = AI.Task.TurnMethod.FIN_POINT, + alt = 0, + alt_type = AI.Task.AltitudeType.RADIO + }) + + group:getController():setTask(task) + group:getController():setOption(AI.Option.Air.id.REACTION_ON_THREAT, AI.Option.Air.val.REACTION_ON_THREAT.PASSIVE_DEFENCE) + group:getController():setCommand(setfreq) + end + + function TaskExtensions.executePatrolMission(group, point, altitude, range, reactivated) + if not group then return end + if not group:isExist() or group:getSize()==0 then return end + local startPos = group:getUnit(1):getPoint() + + if reactivated then + reactivated.currentPos = startPos + startPos = reactivated.homePos + end + + local rng = 25 * 1852 + if range then + rng = range * 1852 + end + + local alt = 4572 + if altitude then + alt = altitude/3.281 + end + + local search = { + id = 'EngageTargets', + params = { + maxDist = rng, + targetTypes = { 'Planes', 'Helicopters' } + } + } + + local distFromPoint = 10000 + local theta = math.random() * 2 * math.pi + + local dx = distFromPoint * math.cos(theta) + local dy = distFromPoint * math.sin(theta) + + local p1 = { + x = point.x + dx, + y = point.z + dy + } + + local p2 = { + x = point.x - dx, + y = point.z - dy + } + + local orbit = { + id = 'Orbit', + params = { + pattern = AI.Task.OrbitPattern.RACE_TRACK, + point = p1, + point2 = p2, + speed = 154, + altitude = alt + } + } + + local task = { + id='Mission', + params = { + route = { + airborne = true, + points = {} + } + } + } + + if not reactivated then + table.insert(task.params.route.points, { + type= AI.Task.WaypointType.TAKEOFF, + x = startPos.x, + y = startPos.z, + speed = 0, + action = AI.Task.TurnMethod.FIN_POINT, + alt = 0, + alt_type = AI.Task.AltitudeType.RADIO, + task = search + }) + else + table.insert(task.params.route.points, { + type= AI.Task.WaypointType.TURNING_POINT, + x = reactivated.currentPos.x, + y = reactivated.currentPos.z, + speed = 257, + action = AI.Task.TurnMethod.FIN_POINT, + alt = alt, + alt_type = AI.Task.AltitudeType.BARO, + task = search + }) + end + + table.insert(task.params.route.points, { + type= AI.Task.WaypointType.TURNING_POINT, + x = p1.x, + y = p1.y, + speed = 257, + action = AI.Task.TurnMethod.FLY_OVER_POINT, + alt = alt, + alt_type = AI.Task.AltitudeType.BARO + }) + + table.insert(task.params.route.points, { + type= AI.Task.WaypointType.TURNING_POINT, + x = p2.x, + y = p2.y, + speed = 257, + action = AI.Task.TurnMethod.FLY_OVER_POINT, + alt = alt, + alt_type = AI.Task.AltitudeType.BARO, + task = orbit + }) + + table.insert(task.params.route.points, { + type= AI.Task.WaypointType.LAND, + x = startPos.x, + y = startPos.z, + speed = 257, + action = AI.Task.TurnMethod.FIN_POINT, + alt = 0, + alt_type = AI.Task.AltitudeType.RADIO + }) + + group:getController():setTask(task) + TaskExtensions.setDefaultAA(group) + end + + function TaskExtensions.setDefaultAA(group) + group:getController():setOption(AI.Option.Air.id.PROHIBIT_AG, true) + group:getController():setOption(AI.Option.Air.id.JETT_TANKS_IF_EMPTY, true) + group:getController():setOption(AI.Option.Air.id.PROHIBIT_JETT, true) + group:getController():setOption(AI.Option.Air.id.REACTION_ON_THREAT, AI.Option.Air.val.REACTION_ON_THREAT.EVADE_FIRE) + group:getController():setOption(AI.Option.Air.id.MISSILE_ATTACK, AI.Option.Air.val.MISSILE_ATTACK.MAX_RANGE) + + local weapons = 268402688 -- AnyMissile + group:getController():setOption(AI.Option.Air.id.RTB_ON_OUT_OF_AMMO, weapons) + end + + function TaskExtensions.setDefaultAG(group) + --group:getController():setOption(AI.Option.Air.id.PROHIBIT_AA, true) + group:getController():setOption(AI.Option.Air.id.JETT_TANKS_IF_EMPTY, true) + group:getController():setOption(AI.Option.Air.id.PROHIBIT_JETT, true) + group:getController():setOption(AI.Option.Air.id.REACTION_ON_THREAT, AI.Option.Air.val.REACTION_ON_THREAT.EVADE_FIRE) + + local weapons = 2147485694 + 30720 + 4161536 -- AnyBomb + AnyRocket + AnyASM + group:getController():setOption(AI.Option.Air.id.RTB_ON_OUT_OF_AMMO, weapons) + end + + function TaskExtensions.stopAndDisperse(group) + if not group then return end + if not group:isExist() or group:getSize()==0 then return end + local pos = group:getUnit(1):getPoint() + group:getController():setTask({ + id='Mission', + params = { + route = { + points = { + [1] = { + type= AI.Task.WaypointType.TURNING_POINT, + x = pos.x, + y = pos.z, + speed = 1000, + action = AI.Task.VehicleFormation.OFF_ROAD + }, + [2] = { + type= AI.Task.WaypointType.TURNING_POINT, + x = pos.x+math.random(25), + y = pos.z+math.random(25), + speed = 1000, + action = AI.Task.VehicleFormation.DIAMOND + }, + } + } + } + }) + end + + function TaskExtensions.moveOnRoadToPointAndAssault(group, point, targets) + if not group or not point then return end + if not group:isExist() or group:getSize()==0 then return end + local startPos = group:getUnit(1):getPoint() + + local srx, sry = land.getClosestPointOnRoads('roads', startPos.x, startPos.z) + local erx, ery = land.getClosestPointOnRoads('roads', point.x, point.y) + + local mis = { + id='Mission', + params = { + route = { + points = { + [1] = { + type= AI.Task.WaypointType.TURNING_POINT, + x = srx, + y = sry, + speed = 1000, + action = AI.Task.VehicleFormation.ON_ROAD + }, + [2] = { + type= AI.Task.WaypointType.TURNING_POINT, + x = erx, + y = ery, + speed = 1000, + action = AI.Task.VehicleFormation.ON_ROAD + }, + [3] = { + type= AI.Task.WaypointType.TURNING_POINT, + x = point.x, + y = point.y, + speed = 1000, + action = AI.Task.VehicleFormation.DIAMOND + } + } + } + } + } + + for i,v in pairs(targets) do + if v.type == 'defense' then + local group = Group.getByName(v.name) + if group then + for i,v in ipairs(group:getUnits()) do + local unpos = v:getPoint() + local pnt = {x=unpos.x, y = unpos.z} + + table.insert(mis.params.route.points, { + type= AI.Task.WaypointType.TURNING_POINT, + x = pnt.x, + y = pnt.y, + speed = 10, + action = AI.Task.VehicleFormation.DIAMOND + }) + end + end + end + end + group:getController():setTask(mis) + end + + function TaskExtensions.moveOnRoadToPoint(group, point) -- point = {x,y} + if not group or not point then return end + if not group:isExist() or group:getSize()==0 then return end + local startPos = group:getUnit(1):getPoint() + + local srx, sry = land.getClosestPointOnRoads('roads', startPos.x, startPos.z) + local erx, ery = land.getClosestPointOnRoads('roads', point.x, point.y) + + local mis = { + id='Mission', + params = { + route = { + points = { + [1] = { + type= AI.Task.WaypointType.TURNING_POINT, + x = srx, + y = sry, + speed = 1000, + action = AI.Task.VehicleFormation.ON_ROAD + }, + [2] = { + type= AI.Task.WaypointType.TURNING_POINT, + x = erx, + y = ery, + speed = 1000, + action = AI.Task.VehicleFormation.ON_ROAD + }, + [3] = { + type= AI.Task.WaypointType.TURNING_POINT, + x = point.x, + y = point.y, + speed = 1000, + action = AI.Task.VehicleFormation.OFF_ROAD + } + } + } + } + } + group:getController():setTask(mis) + end + + function TaskExtensions.landAtPointFromAir(group, point, alt) + if not group or not point then return end + if not group:isExist() or group:getSize()==0 then return end + local startPos = group:getUnit(1):getPoint() + + local atype = AI.Task.AltitudeType.RADIO + if alt then + atype = AI.Task.AltitudeType.BARO + else + alt = 500 + end + + local land = { + id='Land', + params = { + point = point + } + } + + local mis = { + id='Mission', + params = { + route = { + airborne = true, + points = { + [1] = { + type= AI.Task.WaypointType.TURNING_POINT, + x = startPos.x, + y = startPos.z, + speed = 500, + action = AI.Task.TurnMethod.FIN_POINT, + alt = alt, + alt_type = atype + }, + [2] = { + type= AI.Task.WaypointType.TURNING_POINT, + x = point.x, + y = point.y, + speed = 257, + action = AI.Task.TurnMethod.FIN_POINT, + alt = alt, + alt_type = atype, + task = land + } + } + } + } + } + + group:getController():setTask(mis) + end + + function TaskExtensions.landAtPoint(group, point, alt) -- point = {x,y} + if not group or not point then return end + if not group:isExist() or group:getSize()==0 then return end + local startPos = group:getUnit(1):getPoint() + + local atype = AI.Task.AltitudeType.RADIO + if alt then + atype = AI.Task.AltitudeType.BARO + else + alt = 500 + end + + local land = { + id='Land', + params = { + point = point + } + } + + local mis = { + id='Mission', + params = { + route = { + airborne = true, + points = { + [1] = { + type = AI.Task.WaypointType.TAKEOFF, + x = startPos.x, + y = startPos.z, + speed = 0, + action = AI.Task.TurnMethod.FIN_POINT, + alt = alt, + alt_type = atype + }, + [2] = { + type = AI.Task.WaypointType.TURNING_POINT, + x = point.x, + y = point.y, + speed = 257, + action = AI.Task.TurnMethod.FIN_POINT, + alt = alt, + alt_type = atype, + task = land + } + } + } + } + } + + group:getController():setTask(mis) + end + + function TaskExtensions.landAtAirfield(group, point) -- point = {x,y} + if not group or not point then return end + if not group:isExist() or group:getSize()==0 then return end + + local mis = { + id='Mission', + params = { + route = { + airborne = true, + points = { + [1] = { + type= AI.Task.WaypointType.TURNING_POINT, + x = point.x, + y = point.z, + speed = 257, + action = AI.Task.TurnMethod.FIN_POINT, + alt = 4572, + alt_type = AI.Task.AltitudeType.BARO + }, + [2] = { + type= AI.Task.WaypointType.LAND, + x = point.x, + y = point.z, + speed = 257, + action = AI.Task.TurnMethod.FIN_POINT, + alt = 0, + alt_type = AI.Task.AltitudeType.RADIO + } + } + } + } + } + + group:getController():setTask(mis) + end +end + +-----------------[[ END OF TaskExtensions.lua ]]----------------- + + + +-----------------[[ PlayerLogistics.lua ]]----------------- + +PlayerLogistics = {} +do + PlayerLogistics.allowedTypes = {} + PlayerLogistics.allowedTypes['Mi-24P'] = { supplies = true, personCapacity = 8 } + PlayerLogistics.allowedTypes['Mi-8MT'] = { supplies = true, personCapacity = 24 } + PlayerLogistics.allowedTypes['UH-1H'] = { supplies = true, personCapacity = 12} + PlayerLogistics.allowedTypes['Hercules'] = { supplies = true, personCapacity = 92 } + PlayerLogistics.allowedTypes['UH-60L'] = { supplies = true, personCapacity = 12 } + PlayerLogistics.allowedTypes['Ka-50'] = { supplies = false } + PlayerLogistics.allowedTypes['Ka-50_3'] = { supplies = false } + PlayerLogistics.allowedTypes['SA342L'] = { supplies = false, personCapacity = 2} + PlayerLogistics.allowedTypes['SA342M'] = { supplies = false, personCapacity = 2} + PlayerLogistics.allowedTypes['SA342Minigun'] = { supplies = false, personCapacity = 2} + PlayerLogistics.allowedTypes['AH-64D_BLK_II'] = { supplies = false } + + PlayerLogistics.infantryTypes = { + capture = 'capture', + sabotage = 'sabotage', + ambush = 'ambush', + engineer = 'engineer', + manpads = 'manpads', + spy = 'spy', + rapier = 'rapier', + extractable = 'extractable' + } + + function PlayerLogistics.getInfantryName(infType) + if infType==PlayerLogistics.infantryTypes.capture then + return "Capture Squad" + elseif infType==PlayerLogistics.infantryTypes.sabotage then + return "Sabotage Squad" + elseif infType==PlayerLogistics.infantryTypes.ambush then + return "Ambush Squad" + elseif infType==PlayerLogistics.infantryTypes.engineer then + return "Engineer" + elseif infType==PlayerLogistics.infantryTypes.manpads then + return "MANPADS" + elseif infType==PlayerLogistics.infantryTypes.spy then + return "Spy" + elseif infType==PlayerLogistics.infantryTypes.rapier then + return "Rapier SAM" + elseif infType==PlayerLogistics.infantryTypes.extractable then + return "Extracted infantry" + end + + return "INVALID SQUAD" + end + + function PlayerLogistics:new(misTracker, plyTracker, squadTracker, csarTracker) + local obj = {} + obj.groupMenus = {} -- groupid = path + obj.carriedCargo = {} -- groupid = source + obj.carriedInfantry = {} -- groupid = source + obj.carriedPilots = {} --groupid = source + obj.registeredSquadGroups = {} + obj.lastLoaded = {} -- groupid = zonename + obj.missionTracker = misTracker + obj.playerTracker = plyTracker + obj.squadTracker = squadTracker + obj.csarTracker = csarTracker + + obj.hercTracker = { + cargos = {}, + cargoCheckFunctions = {} + } + + obj.hercPreparedDrops = {} + + setmetatable(obj, self) + self.__index = self + + obj:start() + + return obj + end + + function PlayerLogistics:registerSquadGroup(squadType, groupname, weight, cost, jobtime, extracttime, squadSize) + self.registeredSquadGroups[squadType] = { name=groupname, type=squadType, weight=weight, cost=cost, jobtime=jobtime, extracttime=extracttime, size = squadSize} + end + + function PlayerLogistics:start() + if not ZoneCommand then return end + + MenuRegistry:register(3, function(event, context) + if event.id == world.event.S_EVENT_BIRTH and event.initiator and event.initiator.getPlayerName then + local player = event.initiator:getPlayerName() + if player then + local unitType = event.initiator:getDesc()['typeName'] + local groupid = event.initiator:getGroup():getID() + local groupname = event.initiator:getGroup():getName() + + local logistics = context.allowedTypes[unitType] + if logistics and (logistics.supplies or logistics.personCapacity)then + + if context.groupMenus[groupid] then + missionCommands.removeItemForGroup(groupid, context.groupMenus[groupid]) + context.groupMenus[groupid] = nil + end + + if not context.groupMenus[groupid] then + local size = event.initiator:getGroup():getSize() + if size > 1 then + trigger.action.outText('WARNING: group '..groupname..' has '..size..' units. Logistics will only function for group leader', 10) + end + + local cargomenu = missionCommands.addSubMenuForGroup(groupid, 'Logistics') + if logistics.supplies then + local supplyMenu = missionCommands.addSubMenuForGroup(groupid, 'Supplies', cargomenu) + local loadMenu = missionCommands.addSubMenuForGroup(groupid, 'Load', supplyMenu) + missionCommands.addCommandForGroup(groupid, 'Load 100 supplies', loadMenu, Utils.log(context.loadSupplies), context, {group=groupname, amount=100}) + missionCommands.addCommandForGroup(groupid, 'Load 500 supplies', loadMenu, Utils.log(context.loadSupplies), context, {group=groupname, amount=500}) + missionCommands.addCommandForGroup(groupid, 'Load 1000 supplies', loadMenu, Utils.log(context.loadSupplies), context, {group=groupname, amount=1000}) + missionCommands.addCommandForGroup(groupid, 'Load 2000 supplies', loadMenu, Utils.log(context.loadSupplies), context, {group=groupname, amount=2000}) + missionCommands.addCommandForGroup(groupid, 'Load 5000 supplies', loadMenu, Utils.log(context.loadSupplies), context, {group=groupname, amount=5000}) + + local unloadMenu = missionCommands.addSubMenuForGroup(groupid, 'Unload', supplyMenu) + missionCommands.addCommandForGroup(groupid, 'Unload 100 supplies', unloadMenu, Utils.log(context.unloadSupplies), context, {group=groupname, amount=100}) + missionCommands.addCommandForGroup(groupid, 'Unload 500 supplies', unloadMenu, Utils.log(context.unloadSupplies), context, {group=groupname, amount=500}) + missionCommands.addCommandForGroup(groupid, 'Unload 1000 supplies', unloadMenu, Utils.log(context.unloadSupplies), context, {group=groupname, amount=1000}) + missionCommands.addCommandForGroup(groupid, 'Unload 2000 supplies', unloadMenu, Utils.log(context.unloadSupplies), context, {group=groupname, amount=2000}) + missionCommands.addCommandForGroup(groupid, 'Unload 5000 supplies', unloadMenu, Utils.log(context.unloadSupplies), context, {group=groupname, amount=5000}) + missionCommands.addCommandForGroup(groupid, 'Unload all supplies', unloadMenu, Utils.log(context.unloadSupplies), context, {group=groupname, amount=9999999}) + end + + local sqs = {} + for sqType,_ in pairs(context.registeredSquadGroups) do + table.insert(sqs,sqType) + end + table.sort(sqs) + + if logistics.personCapacity then + local infMenu = missionCommands.addSubMenuForGroup(groupid, 'Infantry', cargomenu) + + local loadInfMenu = missionCommands.addSubMenuForGroup(groupid, 'Load', infMenu) + for _,sqType in ipairs(sqs) do + local menuName = 'Load '..PlayerLogistics.getInfantryName(sqType) + missionCommands.addCommandForGroup(groupid, menuName, loadInfMenu, Utils.log(context.loadInfantry), context, {group=groupname, type=sqType}) + end + + local unloadInfMenu = missionCommands.addSubMenuForGroup(groupid, 'Unload', infMenu) + for _,sqType in ipairs(sqs) do + local menuName = 'Unload '..PlayerLogistics.getInfantryName(sqType) + missionCommands.addCommandForGroup(groupid, menuName, unloadInfMenu, Utils.log(context.unloadInfantry), context, {group=groupname, type=sqType}) + end + missionCommands.addCommandForGroup(groupid, 'Unload Extracted squad', unloadInfMenu, Utils.log(context.unloadInfantry), context, {group=groupname, type=PlayerLogistics.infantryTypes.extractable}) + + missionCommands.addCommandForGroup(groupid, 'Extract squad', infMenu, Utils.log(context.extractSquad), context, groupname) + missionCommands.addCommandForGroup(groupid, 'Unload all', infMenu, Utils.log(context.unloadInfantry), context, {group=groupname}) + + local csarMenu = missionCommands.addSubMenuForGroup(groupid, 'CSAR', cargomenu) + missionCommands.addCommandForGroup(groupid, 'Show info (closest)', csarMenu, Utils.log(context.showPilot), context, groupname) + missionCommands.addCommandForGroup(groupid, 'Smoke marker (closest)', csarMenu, Utils.log(context.smokePilot), context, groupname) + missionCommands.addCommandForGroup(groupid, 'Flare (closest)', csarMenu, Utils.log(context.flarePilot), context, groupname) + missionCommands.addCommandForGroup(groupid, 'Extract pilot', csarMenu, Utils.log(context.extractPilot), context, groupname) + missionCommands.addCommandForGroup(groupid, 'Unload pilots', csarMenu, Utils.log(context.unloadPilots), context, groupname) + end + + missionCommands.addCommandForGroup(groupid, 'Cargo status', cargomenu, Utils.log(context.cargoStatus), context, groupname) + missionCommands.addCommandForGroup(groupid, 'Unload Everything', cargomenu, Utils.log(context.unloadAll), context, groupname) + + if unitType == 'Hercules' then + local loadmasterMenu = missionCommands.addSubMenuForGroup(groupid, 'Loadmaster', cargomenu) + + for _,sqType in ipairs(sqs) do + local menuName = 'Prepare '..PlayerLogistics.getInfantryName(sqType) + missionCommands.addCommandForGroup(groupid, menuName, loadmasterMenu, Utils.log(context.hercPrepareDrop), context, {group=groupname, type=sqType}) + end + + missionCommands.addCommandForGroup(groupid, 'Prepare Supplies', loadmasterMenu, Utils.log(context.hercPrepareDrop), context, {group=groupname, type='supplies'}) + end + + + context.groupMenus[groupid] = cargomenu + end + + if context.carriedCargo[groupid] then + context.carriedCargo[groupid] = 0 + end + + if context.carriedInfantry[groupid] then + context.carriedInfantry[groupid] = {} + end + + if context.carriedPilots[groupid] then + context.carriedPilots[groupid] = {} + end + + if context.lastLoaded[groupid] then + context.lastLoaded[groupid] = nil + end + + if context.hercPreparedDrops[groupid] then + context.hercPreparedDrops[groupid] = nil + end + end + end + elseif (event.id == world.event.S_EVENT_PLAYER_LEAVE_UNIT or event.id == world.event.S_EVENT_DEAD) and event.initiator and event.initiator.getPlayerName then + local player = event.initiator:getPlayerName() + if player then + local groupid = event.initiator:getGroup():getID() + + if context.groupMenus[groupid] then + missionCommands.removeItemForGroup(groupid, context.groupMenus[groupid]) + context.groupMenus[groupid] = nil + end + end + end + end, self) + + local ev = {} + ev.context = self + function ev:onEvent(event) + if event.id == world.event.S_EVENT_SHOT and event.initiator and event.initiator:isExist() then + local unitName = event.initiator:getName() + local groupId = event.initiator:getGroup():getID() + local name = event.weapon:getDesc().typeName + if name == 'weapons.bombs.Generic Crate [20000lb]' then + local prepared = self.context.hercPreparedDrops[groupId] + + if not prepared then + prepared = 'supplies' + + if self.context.carriedInfantry[groupId] then + for _,v in ipairs(self.context.carriedInfantry[groupId]) do + if v.type ~= PlayerLogistics.infantryTypes.extractable then + prepared = v.type + break + end + end + end + + env.info('PlayerLogistics - Hercules - auto preparing '..prepared) + end + + if prepared then + if prepared == 'supplies' then + env.info('PlayerLogistics - Hercules - supplies getting dropped') + local carried = self.context.carriedCargo[groupId] + local amount = 0 + if carried and carried > 0 then + amount = 9000 + if carried < amount then + amount = carried + end + end + + if amount > 0 then + self.context.carriedCargo[groupId] = math.max(0,self.context.carriedCargo[groupId] - amount) + if not self.context.hercTracker.cargos[unitName] then + self.context.hercTracker.cargos[unitName] = {} + end + + table.insert(self.context.hercTracker.cargos[unitName],{ + object = event.weapon, + supply = amount, + lastLoaded = self.context.lastLoaded[groupId], + unit = event.initiator + }) + + env.info('PlayerLogistics - Hercules - '..unitName..'deployed crate with '..amount..' supplies') + self.context:processHercCargos(unitName) + self.context.hercPreparedDrops[groupId] = nil + trigger.action.outTextForUnit(event.initiator:getID(), 'Crate with '..amount..' supplies deployed', 10) + else + trigger.action.outTextForUnit(event.initiator:getID(), 'Empty crate deployed', 10) + end + else + env.info('PlayerLogistics - Hercules - searching for prepared infantry') + local toDrop = nil + local remaining = {} + if self.context.carriedInfantry[groupId] then + for _,v in ipairs(self.context.carriedInfantry[groupId]) do + if v.type == prepared and toDrop == nil then + toDrop = v + else + table.insert(remaining, v) + end + end + end + + + if toDrop then + env.info('PlayerLogistics - Hercules - dropping '..toDrop.type) + if not self.context.hercTracker.cargos[unitName] then + self.context.hercTracker.cargos[unitName] = {} + end + + table.insert(self.context.hercTracker.cargos[unitName],{ + object = event.weapon, + squad = toDrop, + lastLoaded = self.context.lastLoaded[groupId], + unit = event.initiator + }) + + env.info('PlayerLogistics - Hercules - '..unitName..'deployed crate with '..toDrop.type) + self.context:processHercCargos(unitName) + self.context.hercPreparedDrops[groupId] = nil + + local squadName = PlayerLogistics.getInfantryName(prepared) + trigger.action.outTextForUnit(event.initiator:getID(), squadName..' crate deployed.', 10) + self.context.carriedInfantry[groupId] = remaining + local weight = self.context:getCarriedPersonWeight(event.initiator:getGroup():getName()) + trigger.action.setUnitInternalCargo(event.initiator:getName(), weight) + else + trigger.action.outTextForUnit(event.initiator:getID(), 'Empty crate deployed', 10) + end + end + else + trigger.action.outTextForUnit(event.initiator:getID(), 'Empty crate deployed', 10) + end + end + end + end + + world.addEventHandler(ev) + end + + function PlayerLogistics:processHercCargos(unitName) + if not self.hercTracker.cargoCheckFunctions[unitName] then + env.info('PlayerLogistics - Hercules - start tracking cargos of '..unitName) + self.hercTracker.cargoCheckFunctions[unitName] = timer.scheduleFunction(function(params, time) + local reschedule = params.context:checkHercCargo(params.unitName, time) + if not reschedule then + params.context.hercTracker.cargoCheckFunctions[params.unitName] = nil + end + + return reschedule + end, {unitName=unitName, context = self}, timer.getTime() + 0.1) + end + end + + function PlayerLogistics:checkHercCargo(unitName, time) + local cargos = self.hercTracker.cargos[unitName] + if cargos and #cargos > 0 then + local remaining = {} + for _,cargo in ipairs(cargos) do + if cargo.object and cargo.object:isExist() then + local alt = Utils.getAGL(cargo.object) + if alt < 5 then + self:deliverHercCargo(cargo) + else + table.insert(remaining, cargo) + end + else + env.info('PlayerLogistics - Hercules - cargo crashed') + if cargo.unit and cargo.unit:isExist() then + trigger.action.outTextForUnit(cargo.unit:getID(), 'Cargo drop of '..cargo.unit:getPlayerName()..' crashed', 10) + end + end + end + + if #remaining > 0 then + self.hercTracker.cargos[unitName] = remaining + return time + 0.1 + end + end + end + + function PlayerLogistics:deliverHercCargo(cargo) + if cargo.object and cargo.object:isExist() then + if cargo.supply then + local zone = ZoneCommand.getZoneOfWeapon(cargo.object) + if zone then + zone:addResource(cargo.supply) + cargo.object:destroy() + env.info('PlayerLogistics - Hercules - '..cargo.supply..' delivered to '..zone.name) + + self:awardSupplyXP(cargo.lastLoaded, zone, cargo.unit, cargo.supply) + end + elseif cargo.squad then + local pos = Utils.getPointOnSurface(cargo.object:getPoint()) + local surface = land.getSurfaceType(pos) + if surface == land.SurfaceType.LAND or surface == land.SurfaceType.ROAD or surface == land.SurfaceType.RUNWAY then + local zn = ZoneCommand.getZoneOfPoint(pos) + + local lastLoad = cargo.squad.loadedAt + if lastLoad and zn and zn.side == cargo.object:getCoalition() and zn.name==lastLoad.name then + if self.registeredSquadGroups[cargo.squad.type] then + local cost = self.registeredSquadGroups[cargo.squad.type].cost + zn:addResource(cost) + zn:refreshText() + if cargo.unit and cargo.unit:isExist() then + local squadName = PlayerLogistics.getInfantryName(cargo.squad.type) + trigger.action.outTextForUnit(cargo.unit:getID(), squadName..' unloaded', 10) + end + end + else + local error = self.squadTracker:spawnInfantry(self.registeredSquadGroups[cargo.squad.type], pos) + if not error then + cargo.object:destroy() + env.info('PlayerLogistics - Hercules - '..cargo.squad.type..' deployed') + + local squadName = PlayerLogistics.getInfantryName(cargo.squad.type) + trigger.action.outTextForUnit(cargo.unit:getID(), squadName..' deployed', 10) + + if cargo.unit and cargo.unit:isExist() and cargo.unit.getPlayerName then + local player = cargo.unit:getPlayerName() + local xp = RewardDefinitions.actions.squadDeploy + + self.playerTracker:addStat(player, math.floor(xp), PlayerTracker.statTypes.xp) + + if zn then + self.missionTracker:tallyUnloadSquad(player, zn.name, cargo.squad.type) + else + self.missionTracker:tallyUnloadSquad(player, '', cargo.squad.type) + end + trigger.action.outTextForUnit(cargo.unit:getID(), '+'..math.floor(xp)..' XP', 10) + end + end + end + end + end + end + end + + function PlayerLogistics:hercPrepareDrop(params) + local groupname = params.group + local type = params.type + local gr = Group.getByName(groupname) + if gr then + local un = gr:getUnit(1) + + if type == 'supplies' then + local cargo = self.carriedCargo[gr:getID()] + if cargo and cargo > 0 then + self.hercPreparedDrops[gr:getID()] = type + trigger.action.outTextForUnit(un:getID(), 'Supply drop prepared', 10) + else + trigger.action.outTextForUnit(un:getID(), 'No supplies onboard the aircraft', 10) + end + else + local exists = false + if self.carriedInfantry[gr:getID()] then + for i,v in ipairs(self.carriedInfantry[gr:getID()]) do + if v.type == type then + exists = true + break + end + end + end + + if exists then + self.hercPreparedDrops[gr:getID()] = type + local squadName = PlayerLogistics.getInfantryName(type) + trigger.action.outTextForUnit(un:getID(), squadName..' drop prepared', 10) + else + local squadName = PlayerLogistics.getInfantryName(type) + trigger.action.outTextForUnit(un:getID(), 'No '..squadName..' onboard the aircraft', 10) + end + end + end + end + + function PlayerLogistics:awardSupplyXP(lastLoad, zone, unit, amount) + if lastLoad and zone.name~=lastLoad.name then + if unit and unit.isExist and unit:isExist() and unit.getPlayerName then + local player = unit:getPlayerName() + local xp = amount*RewardDefinitions.actions.supplyRatio + + local totalboost = 0 + local dist = mist.utils.get2DDist(lastLoad.zone.point, zone.zone.point) + if dist > 15000 then + local extradist = math.max(dist - 15000, 85000) + local kmboost = extradist/85000 + local actualboost = xp * kmboost * 1 + totalboost = totalboost + actualboost + end + + local both = true + if zone:criticalOnSupplies() then + local actualboost = xp * RewardDefinitions.actions.supplyBoost + totalboost = totalboost + actualboost + else + both = false + end + + if zone.distToFront == 0 then + local actualboost = xp * RewardDefinitions.actions.supplyBoost + totalboost = totalboost + actualboost + else + both = false + end + + if both then + local actualboost = xp * 1 + totalboost = totalboost + actualboost + end + + xp = xp + totalboost + + if lastLoad.distToFront >= zone.distToFront then + xp = xp * 0.25 + end + + self.playerTracker:addStat(player, math.floor(xp), PlayerTracker.statTypes.xp) + self.missionTracker:tallySupplies(player, amount, zone.name) + trigger.action.outTextForUnit(unit:getID(), '+'..math.floor(xp)..' XP', 10) + end + end + end + + function PlayerLogistics.markWithSmoke(zonename) + local zone = CustomZone:getByName(zonename) + local p = Utils.getPointOnSurface(zone.point) + trigger.action.smoke(p, 0) + end + + function PlayerLogistics.getWeight(supplies) + return math.floor(supplies) + end + + function PlayerLogistics:getCarriedPersonWeight(groupname) + local gr = Group.getByName(groupname) + local un = gr:getUnit(1) + if un then + if not PlayerLogistics.allowedTypes[un:getDesc().typeName] then return 0 end + + local max = PlayerLogistics.allowedTypes[un:getDesc().typeName].personCapacity + + local pilotWeight = 0 + local squadWeight = 0 + if not self.carriedPilots[gr:getID()] then self.carriedPilots[gr:getID()] = {} end + local pilots = self.carriedPilots[gr:getID()] + if pilots then + pilotWeight = 100 * #pilots + end + + if not self.carriedInfantry[gr:getID()] then self.carriedInfantry[gr:getID()] = {} end + local squads = self.carriedInfantry[gr:getID()] + if squads then + for _,squad in ipairs(squads) do + squadWeight = squadWeight + squad.weight + end + end + + return pilotWeight + squadWeight + end + end + + function PlayerLogistics:getOccupiedPersonCapacity(groupname) + local gr = Group.getByName(groupname) + local un = gr:getUnit(1) + if un then + if not PlayerLogistics.allowedTypes[un:getDesc().typeName] then return 0 end + if self.carriedCargo[gr:getID()] and self.carriedCargo[gr:getID()] > 0 then return 0 end + + local max = PlayerLogistics.allowedTypes[un:getDesc().typeName].personCapacity + + local pilotCount = 0 + local squadCount = 0 + if not self.carriedPilots[gr:getID()] then self.carriedPilots[gr:getID()] = {} end + local pilots = self.carriedPilots[gr:getID()] + if pilots then + pilotCount = #pilots + end + + if not self.carriedInfantry[gr:getID()] then self.carriedInfantry[gr:getID()] = {} end + local squads = self.carriedInfantry[gr:getID()] + if squads then + for _,squad in ipairs(squads) do + squadCount = squadCount + squad.size + end + end + + local total = pilotCount + squadCount + + return total + end + end + + function PlayerLogistics:getRemainingPersonCapacity(groupname) + local gr = Group.getByName(groupname) + local un = gr:getUnit(1) + if un then + if not PlayerLogistics.allowedTypes[un:getDesc().typeName] then return 0 end + if self.carriedCargo[gr:getID()] and self.carriedCargo[gr:getID()] > 0 then return 0 end + + local max = PlayerLogistics.allowedTypes[un:getDesc().typeName].personCapacity + + local total = self:getOccupiedPersonCapacity(groupname) + + return max - total + end + end + + function PlayerLogistics:canFitCargo(groupname) + local gr = Group.getByName(groupname) + local un = gr:getUnit(1) + if un then + if not PlayerLogistics.allowedTypes[un:getDesc().typeName] then return false end + return self:getOccupiedPersonCapacity(groupname) == 0 + end + end + + function PlayerLogistics:canFitPersonnel(groupname, toFit) + local gr = Group.getByName(groupname) + local un = gr:getUnit(1) + if un then + if not PlayerLogistics.allowedTypes[un:getDesc().typeName] then return false end + + return self:getRemainingPersonCapacity(groupname) >= toFit + end + end + + function PlayerLogistics:showPilot(groupname) + local gr = Group.getByName(groupname) + if gr then + local un = gr:getUnit(1) + if un then + local data = self.csarTracker:getClosestPilot(un:getPoint()) + + if not data then + trigger.action.outTextForUnit(un:getID(), 'No pilots in need of extraction', 10) + return + end + + local pos = data.pilot:getUnit(1):getPoint() + local brg = math.floor(Utils.getBearing(un:getPoint(), data.pilot:getUnit(1):getPoint())) + local dist = data.dist + local dstft = math.floor(dist/0.3048) + + local msg = data.name..' requesting extraction' + msg = msg..'\n\n Distance: ' + if dist>1000 then + local dstkm = string.format('%.2f',dist/1000) + local dstnm = string.format('%.2f',dist/1852) + + msg = msg..dstkm..'km | '..dstnm..'nm' + else + local dstft = math.floor(dist/0.3048) + msg = msg..math.floor(dist)..'m | '..dstft..'ft' + end + + msg = msg..'\n Bearing: '..brg + + trigger.action.outTextForUnit(un:getID(), msg, 10) + end + end + end + + function PlayerLogistics:smokePilot(groupname) + local gr = Group.getByName(groupname) + if gr then + local un = gr:getUnit(1) + if un then + local data = self.csarTracker:getClosestPilot(un:getPoint()) + + if not data or data.dist >= 5000 then + trigger.action.outTextForUnit(un:getID(), 'No pilots nearby', 10) + return + end + + self.csarTracker:markPilot(data) + trigger.action.outTextForUnit(un:getID(), 'Location of '..data.name..' marked with green smoke.', 10) + end + end + end + + function PlayerLogistics:flarePilot(groupname) + local gr = Group.getByName(groupname) + if gr then + local un = gr:getUnit(1) + if un then + local data = self.csarTracker:getClosestPilot(un:getPoint()) + + if not data or data.dist >= 5000 then + trigger.action.outTextForUnit(un:getID(), 'No pilots nearby', 10) + return + end + + self.csarTracker:flarePilot(data) + trigger.action.outTextForUnit(un:getID(), data.name..' has deployed a green flare', 10) + end + end + end + + function PlayerLogistics:unloadPilots(groupname) + local gr = Group.getByName(groupname) + if gr then + local un = gr:getUnit(1) + if un then + local pilots = self.carriedPilots[gr:getID()] + if not pilots or #pilots==0 then + trigger.action.outTextForUnit(un:getID(), 'No pilots onboard', 10) + return + end + + if Utils.isInAir(un) then + trigger.action.outTextForUnit(un:getID(), 'Can not unload pilot while in air', 10) + return + end + + if not self:isCargoDoorOpen(un) then + trigger.action.outTextForUnit(un:getID(), 'Can not unload pilot while cargo door closed', 10) + return + end + + local zn = ZoneCommand.getZoneOfUnit(un:getName()) + if not zn then + trigger.action.outTextForUnit(un:getID(), 'Can only unload extracted pilots while within a friendly zone', 10) + return + end + + if zn.side ~= 0 and zn.side ~= un:getCoalition()then + trigger.action.outTextForUnit(un:getID(), 'Can only unload extracted pilots while within a friendly zone', 10) + return + end + + zn:addResource(200*#pilots) + zn:refreshText() + + if un.getPlayerName then + local player = un:getPlayerName() + + local xp = #pilots*RewardDefinitions.actions.pilotExtract + + self.playerTracker:addStat(player, math.floor(xp), PlayerTracker.statTypes.xp) + self.missionTracker:tallyUnloadPilot(player, zn.name) + trigger.action.outTextForUnit(un:getID(), '+'..math.floor(xp)..' XP', 10) + end + + self.carriedPilots[gr:getID()] = {} + trigger.action.setUnitInternalCargo(un:getName(), 0) + trigger.action.outTextForUnit(un:getID(), 'Pilots unloaded', 10) + end + end + end + + function PlayerLogistics:extractPilot(groupname) + local gr = Group.getByName(groupname) + if gr then + local un = gr:getUnit(1) + if un then + if not self:canFitPersonnel(groupname, 1) then + trigger.action.outTextForUnit(un:getID(), 'Not enough free space onboard. (Need 1)', 10) + return + end + + timer.scheduleFunction(function(param,time) + local self = param.context + local un = param.unit + if not un then return end + if not un:isExist() then return end + local gr = un:getGroup() + + local data = self.csarTracker:getClosestPilot(un:getPoint()) + + if not data or data.dist > 500 then + trigger.action.outTextForUnit(un:getID(), 'There is no pilot nearby that needs extraction', 10) + return + else + if not self:isCargoDoorOpen(un) then + trigger.action.outTextForUnit(un:getID(), 'Cargo door closed', 1) + elseif Utils.getAGL(un) > 70 then + trigger.action.outTextForUnit(un:getID(), 'Altitude too high (< 70 m). Current: '..string.format('%.2f',Utils.getAGL(un))..' m', 1) + elseif mist.vec.mag(un:getVelocity())>5 then + trigger.action.outTextForUnit(un:getID(), 'Moving too fast (< 5 m/s). Current: '..string.format('%.2f',mist.vec.mag(un:getVelocity()))..' m/s', 1) + else + if data.dist > 100 then + trigger.action.outTextForUnit(un:getID(), 'Too far (< 100m). Current: '..string.format('%.2f',data.dist)..' m', 1) + else + if not self.carriedPilots[gr:getID()] then self.carriedPilots[gr:getID()] = {} end + table.insert(self.carriedPilots[gr:getID()], data.name) + local player = un:getPlayerName() + self.missionTracker:tallyLoadPilot(player, data) + self.csarTracker:removePilot(data.name) + local weight = self:getCarriedPersonWeight(gr:getName()) + trigger.action.setUnitInternalCargo(un:getName(), weight) + trigger.action.outTextForUnit(un:getID(), data.name..' onboard. ('..weight..' kg)', 10) + return + end + end + end + + param.trys = param.trys - 1 + if param.trys > 0 then + return time+1 + end + end, {context = self, unit = un, trys = 60}, timer.getTime()+0.1) + end + end + end + + function PlayerLogistics:extractSquad(groupname) + local gr = Group.getByName(groupname) + if gr then + local un = gr:getUnit(1) + if un then + + if Utils.isInAir(un) then + trigger.action.outTextForUnit(un:getID(), 'Can not load infantry while in air', 10) + return + end + + if not self:isCargoDoorOpen(un) then + trigger.action.outTextForUnit(un:getID(), 'Can not load infantry while cargo door closed', 10) + return + end + + local squad, distance = self.squadTracker:getClosestExtractableSquad(un:getPoint()) + if squad and distance < 50 then + local squadgr = Group.getByName(squad.name) + if squadgr and squadgr:isExist() then + local sqsize = squadgr:getSize() + if not self:canFitPersonnel(groupname, sqsize) then + trigger.action.outTextForUnit(un:getID(), 'Not enough free space onboard. (Need '..sqsize..')', 10) + return + end + + if not self.carriedInfantry[gr:getID()] then self.carriedInfantry[gr:getID()] = {} end + table.insert(self.carriedInfantry[gr:getID()],{type = PlayerLogistics.infantryTypes.extractable, size = sqsize, weight = sqsize * 100}) + + local weight = self:getCarriedPersonWeight(gr:getName()) + + trigger.action.setUnitInternalCargo(un:getName(), weight) + + local loadedInfName = PlayerLogistics.getInfantryName(PlayerLogistics.infantryTypes.extractable) + trigger.action.outTextForUnit(un:getID(), loadedInfName..' onboard. ('..weight..' kg)', 10) + + local player = un:getPlayerName() + self.missionTracker:tallyLoadSquad(player, squad) + self.squadTracker:removeSquad(squad.name) + + squadgr:destroy() + end + else + trigger.action.outTextForUnit(un:getID(), 'There is no infantry nearby that is ready to be extracted.', 10) + end + end + end + end + + function PlayerLogistics:loadInfantry(params) + if not ZoneCommand then return end + + local gr = Group.getByName(params.group) + local squadType = params.type + local squadName = PlayerLogistics.getInfantryName(squadType) + + local squadCost = 0 + local squadSize = 999999 + local squadWeight = 0 + if self.registeredSquadGroups[squadType] then + squadCost = self.registeredSquadGroups[squadType].cost + squadSize = self.registeredSquadGroups[squadType].size + squadWeight = self.registeredSquadGroups[squadType].weight + end + + if gr then + local un = gr:getUnit(1) + if un then + if Utils.isInAir(un) then + trigger.action.outTextForUnit(un:getID(), 'Can not load infantry while in air', 10) + return + end + + local zn = ZoneCommand.getZoneOfUnit(un:getName()) + if not zn then + trigger.action.outTextForUnit(un:getID(), 'Can only load infantry while within a friendly zone', 10) + return + end + + if zn.side ~= un:getCoalition() then + trigger.action.outTextForUnit(un:getID(), 'Can only load infantry while within a friendly zone', 10) + return + end + + if not self:isCargoDoorOpen(un) then + trigger.action.outTextForUnit(un:getID(), 'Can not load infantry while cargo door closed', 10) + return + end + + if zn:criticalOnSupplies() then + trigger.action.outTextForUnit(un:getID(), 'Can not load infantry, zone is low on resources', 10) + return + end + + if zn.resource < zn.spendTreshold + squadCost then + trigger.action.outTextForUnit(un:getID(), 'Can not afford to load '..squadName..' (Cost: '..squadCost..'). Resources would fall to a critical level.', 10) + return + end + + if not self:canFitPersonnel(params.group, squadSize) then + trigger.action.outTextForUnit(un:getID(), 'Not enough free space on board. (Need '..squadSize..')', 10) + return + end + + zn:removeResource(squadCost) + zn:refreshText() + if not self.carriedInfantry[gr:getID()] then self.carriedInfantry[gr:getID()] = {} end + table.insert(self.carriedInfantry[gr:getID()],{ type = squadType, size = squadSize, weight = squadWeight, loadedAt = zn }) + self.lastLoaded[gr:getID()] = zn + + local weight = self:getCarriedPersonWeight(gr:getName()) + trigger.action.setUnitInternalCargo(un:getName(), weight) + + local loadedInfName = PlayerLogistics.getInfantryName(squadType) + trigger.action.outTextForUnit(un:getID(), loadedInfName..' onboard. ('..weight..' kg)', 10) + end + end + end + + function PlayerLogistics:unloadInfantry(params) + if not ZoneCommand then return end + local groupname = params.group + local sqtype = params.type + + local gr = Group.getByName(groupname) + if gr then + local un = gr:getUnit(1) + if un then + if Utils.isInAir(un) then + trigger.action.outTextForUnit(un:getID(), 'Can not unload infantry while in air', 10) + return + end + + if not self:isCargoDoorOpen(un) then + trigger.action.outTextForUnit(un:getID(), 'Can not unload infantry while cargo door closed', 10) + return + end + + local carriedSquads = self.carriedInfantry[gr:getID()] + if not carriedSquads or #carriedSquads == 0 then + trigger.action.outTextForUnit(un:getID(), 'No infantry onboard', 10) + return + end + + local toUnload = carriedSquads + local remaining = {} + if sqtype then + toUnload = {} + local sqToUnload = nil + for _,sq in ipairs(carriedSquads) do + if sq.type == sqtype and not sqToUnload then + sqToUnload = sq + else + table.insert(remaining, sq) + end + end + + if sqToUnload then toUnload = { sqToUnload } end + end + + if #toUnload == 0 then + if sqtype then + local squadName = PlayerLogistics.getInfantryName(sqtype) + trigger.action.outTextForUnit(un:getID(), 'No '..squadName..' onboard.', 10) + else + trigger.action.outTextForUnit(un:getID(), 'No infantry onboard.', 10) + end + + return + end + + local zn = ZoneCommand.getZoneOfUnit(un:getName()) + + for _, sq in ipairs(toUnload) do + local squadName = PlayerLogistics.getInfantryName(sq.type) + local lastLoad = sq.loadedAt + if lastLoad and zn and zn.side == un:getCoalition() and zn.name==lastLoad.name then + if self.registeredSquadGroups[sq.type] then + local cost = self.registeredSquadGroups[sq.type].cost + zn:addResource(cost) + zn:refreshText() + trigger.action.outTextForUnit(un:getID(), squadName..' unloaded', 10) + end + else + if sq.type == PlayerLogistics.infantryTypes.extractable then + if not zn then + trigger.action.outTextForUnit(un:getID(), 'Can only unload extracted infantry while within a friendly zone', 10) + table.insert(remaining, sq) + elseif zn.side ~= un:getCoalition() then + trigger.action.outTextForUnit(un:getID(), 'Can only unload extracted infantry while within a friendly zone', 10) + table.insert(remaining, sq) + else + trigger.action.outTextForUnit(un:getID(), 'Infantry recovered', 10) + zn:addResource(200) + zn:refreshText() + + if un.getPlayerName then + local player = un:getPlayerName() + local xp = RewardDefinitions.actions.squadExtract + + self.playerTracker:addStat(player, math.floor(xp), PlayerTracker.statTypes.xp) + self.missionTracker:tallyUnloadSquad(player, zn.name, sq.type) + trigger.action.outTextForUnit(un:getID(), '+'..math.floor(xp)..' XP', 10) + end + end + elseif self.registeredSquadGroups[sq.type] then + local pos = Utils.getPointOnSurface(un:getPoint()) + + local error = self.squadTracker:spawnInfantry(self.registeredSquadGroups[sq.type], pos) + + if not error then + trigger.action.outTextForUnit(un:getID(), squadName..' deployed', 10) + + if un.getPlayerName then + local player = un:getPlayerName() + local xp = RewardDefinitions.actions.squadDeploy + + self.playerTracker:addStat(player, math.floor(xp), PlayerTracker.statTypes.xp) + + if zn then + self.missionTracker:tallyUnloadSquad(player, zn.name, sq.type) + else + self.missionTracker:tallyUnloadSquad(player, '', sq.type) + end + trigger.action.outTextForUnit(un:getID(), '+'..math.floor(xp)..' XP', 10) + end + else + trigger.action.outTextForUnit(un:getID(), 'Failed to deploy squad, no suitable location nearby', 10) + table.insert(remaining, sq) + end + else + trigger.action.outText("ERROR: SQUAD TYPE NOT REGISTERED", 60) + end + end + end + + self.carriedInfantry[gr:getID()] = remaining + local weight = self:getCarriedPersonWeight(groupname) + trigger.action.setUnitInternalCargo(un:getName(), weight) + end + end + end + + function PlayerLogistics:unloadAll(groupname) + local gr = Group.getByName(groupname) + if gr then + local un = gr:getUnit(1) + if un then + local cargo = self.carriedCargo[gr:getID()] + local squad = self.carriedInfantry[gr:getID()] + local pilot = self.carriedPilots[gr:getID()] + + if cargo and cargo>0 then + self:unloadSupplies({group=groupname, amount=9999999}) + end + + if squad and #squad>0 then + self:unloadInfantry({group=groupname}) + end + + if pilot and #pilot>0 then + self:unloadPilots(groupname) + end + end + end + end + + function PlayerLogistics:cargoStatus(groupName) + local gr = Group.getByName(groupName) + if gr then + local un = gr:getUnit(1) + if un then + local onboard = self.carriedCargo[gr:getID()] + if onboard and onboard > 0 then + local weight = self.getWeight(onboard) + trigger.action.outTextForUnit(un:getID(), onboard..' supplies onboard. ('..weight..' kg)', 10) + else + local msg = '' + local squads = self.carriedInfantry[gr:getID()] + if squads and #squads>0 then + msg = msg..'Squads:\n' + + for _,squad in ipairs(squads) do + local infName = PlayerLogistics.getInfantryName(squad.type) + msg = msg..' \n'..infName..' (Size: '..squad.size..')' + end + end + + local pilots = self.carriedPilots[gr:getID()] + if pilots and #pilots>0 then + msg = msg.."\n\nPilots:\n" + for i,v in ipairs(pilots) do + msg = msg..'\n '..v + end + + end + + local max = PlayerLogistics.allowedTypes[un:getDesc().typeName].personCapacity + local occupied = self:getOccupiedPersonCapacity(groupName) + + msg = msg..'\n\nCapacity: '..occupied..'/'..max + + msg = msg..'\n('..self:getCarriedPersonWeight(groupName)..' kg)' + + if un:getDesc().typeName == 'Hercules' then + local preped = self.hercPreparedDrops[gr:getID()] + if preped then + if preped == 'supplies' then + msg = msg..'\nSupplies prepared for next drop.' + else + local squadName = PlayerLogistics.getInfantryName(preped) + msg = msg..'\n'..squadName..' prepared for next drop.' + end + end + end + + trigger.action.outTextForUnit(un:getID(), msg, 10) + end + end + end + end + + function PlayerLogistics:isCargoDoorOpen(unit) + if unit then + local tp = unit:getDesc().typeName + if tp == "Mi-8MT" then + if unit:getDrawArgumentValue(86) == 1 then return true end + if unit:getDrawArgumentValue(38) > 0.85 then return true end + elseif tp == "UH-1H" then + if unit:getDrawArgumentValue(43) == 1 then return true end + if unit:getDrawArgumentValue(44) == 1 then return true end + elseif tp == "Mi-24P" then + if unit:getDrawArgumentValue(38) == 1 then return true end + if unit:getDrawArgumentValue(86) == 1 then return true end + elseif tp == "Hercules" then + if unit:getDrawArgumentValue(1215) == 1 and unit:getDrawArgumentValue(1216) == 1 then return true end + elseif tp == "UH-60L" then + if unit:getDrawArgumentValue(401) == 1 then return true end + if unit:getDrawArgumentValue(402) == 1 then return true end + elseif tp == "SA342Mistral" then + if unit:getDrawArgumentValue(34) == 1 then return true end + if unit:getDrawArgumentValue(38) == 1 then return true end + elseif tp == "SA342L" then + if unit:getDrawArgumentValue(34) == 1 then return true end + if unit:getDrawArgumentValue(38) == 1 then return true end + elseif tp == "SA342M" then + if unit:getDrawArgumentValue(34) == 1 then return true end + if unit:getDrawArgumentValue(38) == 1 then return true end + elseif tp == "SA342Minigun" then + if unit:getDrawArgumentValue(34) == 1 then return true end + if unit:getDrawArgumentValue(38) == 1 then return true end + else + return true + end + end + end + + function PlayerLogistics:loadSupplies(params) + if not ZoneCommand then return end + + local groupName = params.group + local amount = params.amount + + local gr = Group.getByName(groupName) + if gr then + local un = gr:getUnit(1) + if un then + if not self:canFitCargo(groupName) then + trigger.action.outTextForUnit(un:getID(), 'Can not load cargo. Personnel onboard.', 10) + return + end + + if Utils.isInAir(un) then + trigger.action.outTextForUnit(un:getID(), 'Can not load supplies while in air', 10) + return + end + + local zn = ZoneCommand.getZoneOfUnit(un:getName()) + if not zn then + trigger.action.outTextForUnit(un:getID(), 'Can only load supplies while within a friendly zone', 10) + return + end + + if zn.side ~= un:getCoalition() then + trigger.action.outTextForUnit(un:getID(), 'Can only load supplies while within a friendly zone', 10) + return + end + + if not self:isCargoDoorOpen(un) then + trigger.action.outTextForUnit(un:getID(), 'Can not load supplies while cargo door closed', 10) + return + end + + if zn:criticalOnSupplies() then + trigger.action.outTextForUnit(un:getID(), 'Can not load supplies, zone is low on resources', 10) + return + end + + if zn.resource < zn.spendTreshold + amount then + trigger.action.outTextForUnit(un:getID(), 'Can not load supplies if resources would fall to a critical level.', 10) + return + end + + local carried = self.carriedCargo[gr:getID()] or 0 + if amount > zn.resource then + amount = zn.resource + end + + zn:removeResource(amount) + zn:refreshText() + self.carriedCargo[gr:getID()] = carried + amount + self.lastLoaded[gr:getID()] = zn + local onboard = self.carriedCargo[gr:getID()] + local weight = self.getWeight(onboard) + + if un:getDesc().typeName == "Hercules" then + local loadedInCrates = 0 + local ammo = un:getAmmo() + for _,load in ipairs(ammo) do + if load.desc.typeName == 'weapons.bombs.Generic Crate [20000lb]' then + loadedInCrates = 9000 * load.count + end + end + + local internal = 0 + if weight > loadedInCrates then + internal = weight - loadedInCrates + end + + trigger.action.setUnitInternalCargo(un:getName(), internal) + else + trigger.action.setUnitInternalCargo(un:getName(), weight) + end + + trigger.action.outTextForUnit(un:getID(), amount..' supplies loaded', 10) + trigger.action.outTextForUnit(un:getID(), onboard..' supplies onboard. ('..weight..' kg)', 10) + end + end + end + + function PlayerLogistics:unloadSupplies(params) + if not ZoneCommand then return end + + local groupName = params.group + local amount = params.amount + + local gr = Group.getByName(groupName) + if gr then + local un = gr:getUnit(1) + if un then + if Utils.isInAir(un) then + trigger.action.outTextForUnit(un:getID(), 'Can not unload supplies while in air', 10) + return + end + + local zn = ZoneCommand.getZoneOfUnit(un:getName()) + if not zn then + trigger.action.outTextForUnit(un:getID(), 'Can only unload supplies while within a friendly zone', 10) + return + end + + if zn.side ~= 0 and zn.side ~= un:getCoalition()then + trigger.action.outTextForUnit(un:getID(), 'Can only unload supplies while within a friendly zone', 10) + return + end + + if not self:isCargoDoorOpen(un) then + trigger.action.outTextForUnit(un:getID(), 'Can not unload supplies while cargo door closed', 10) + return + end + + if not self.carriedCargo[gr:getID()] or self.carriedCargo[gr:getID()] == 0 then + trigger.action.outTextForUnit(un:getID(), 'No supplies loaded', 10) + return + end + + local carried = self.carriedCargo[gr:getID()] + if amount > carried then + amount = carried + end + + self.carriedCargo[gr:getID()] = carried-amount + zn:addResource(amount) + + local lastLoad = self.lastLoaded[gr:getID()] + self:awardSupplyXP(lastLoad, zn, un, amount) + + zn:refreshText() + local onboard = self.carriedCargo[gr:getID()] + local weight = self.getWeight(onboard) + + if un:getDesc().typeName == "Hercules" then + local loadedInCrates = 0 + local ammo = un:getAmmo() + for _,load in ipairs(ammo) do + if load.desc.typeName == 'weapons.bombs.Generic Crate [20000lb]' then + loadedInCrates = 9000 * load.count + end + end + + local internal = 0 + if weight > loadedInCrates then + internal = weight - loadedInCrates + end + + trigger.action.setUnitInternalCargo(un:getName(), internal) + else + trigger.action.setUnitInternalCargo(un:getName(), weight) + end + + trigger.action.outTextForUnit(un:getID(), amount..' supplies unloaded', 10) + trigger.action.outTextForUnit(un:getID(), onboard..' supplies remaining onboard. ('..weight..' kg)', 10) + end + end + end +end + +-----------------[[ END OF PlayerLogistics.lua ]]----------------- + + + +-----------------[[ MarkerCommands.lua ]]----------------- + +MarkerCommands = {} +do + function MarkerCommands:new() + local obj = {} + obj.commands = {} --{command=string, action=function} + + setmetatable(obj, self) + self.__index = self + + obj:start() + + return obj + end + + function MarkerCommands:addCommand(command, action, hasParam, state) + table.insert(self.commands, {command = command, action = action, hasParam = hasParam, state = state}) + end + + function MarkerCommands:start() + local markEditedEvent = {} + markEditedEvent.context = self + function markEditedEvent:onEvent(event) + if event.id == 26 and event.text and (event.coalition == 1 or event.coalition == 2) then -- mark changed + local success = false + env.info('MarkerCommands - input: '..event.text) + + for i,v in ipairs(self.context.commands) do + if (not v.hasParam) and event.text == v.command then + success = v.action(event, nil, v.state) + break + elseif v.hasParam and event.text:find('^'..v.command..':') then + local param = event.text:gsub('^'..v.command..':', '') + success = v.action(event, param, v.state) + break + end + end + + if success then + trigger.action.removeMark(event.idx) + end + end + end + + world.addEventHandler(markEditedEvent) + end +end + + +-----------------[[ END OF MarkerCommands.lua ]]----------------- + + + +-----------------[[ ZoneCommand.lua ]]----------------- + +ZoneCommand = {} +do + ZoneCommand.currentZoneIndex = 1000 + ZoneCommand.allZones = {} + ZoneCommand.buildSpeed = Config.buildSpeed + ZoneCommand.supplyBuildSpeed = Config.supplyBuildSpeed + ZoneCommand.missionValidChance = 0.9 + ZoneCommand.missionBuildSpeedReduction = Config.missionBuildSpeedReduction + ZoneCommand.revealTime = 0 + ZoneCommand.staticRegistry = {} + + ZoneCommand.modes = { + normal = 'normal', + supply = 'supply', + export = 'export' + } + + ZoneCommand.productTypes = { + upgrade = 'upgrade', + mission = 'mission', + defense = 'defense' + } + + ZoneCommand.missionTypes = { + supply_air = 'supply_air', + supply_convoy = 'supply_convoy', + cas = 'cas', + cas_helo = 'cas_helo', + strike = 'strike', + patrol = 'patrol', + sead = 'sead', + assault = 'assault', + bai = 'bai', + supply_transfer = 'supply_transfer', + awacs = 'awacs', + tanker = 'tanker' + } + + function ZoneCommand:new(zonename) + local obj = {} + obj.name = zonename + obj.side = 0 + obj.resource = 0 + obj.resourceChange = 0 + obj.maxResource = 20000 + obj.spendTreshold = 5000 + obj.keepActive = false + obj.boostScale = 1.0 + obj.extraBuildResources = 0 + obj.reservedMissions = {} + obj.isHeloSpawn = false + obj.isPlaneSpawn = false + + obj.connectionManager = nil + + obj.zone = CustomZone:getByName(zonename) + obj.products = {} + obj.mode = 'normal' + --[[ + normal: buys whatever it can + supply: buys only supply missions + export: supply mode, but also sells all defense groups from the zone + ]]-- + obj.index = ZoneCommand.currentZoneIndex + ZoneCommand.currentZoneIndex = ZoneCommand.currentZoneIndex + 1 + + obj.built = {} + obj.income = 0 + + --group restrictions + obj.spawns = {} + for i,v in pairs(mist.DBs.groupsByName) do + if v.units[1].skill == 'Client' then + local zn = obj.zone + local pos3d = { + x = v.units[1].point.x, + y = 0, + z = v.units[1].point.y + } + + if zn and zn:isInside(pos3d) then + local coa = 0 + if v.coalition=='blue' then + coa = 2 + elseif v.coalition=='red' then + coa = 1 + end + + table.insert(obj.spawns, {name=i, side=coa}) + end + end + end + + --draw graphics + local color = {0.7,0.7,0.7,0.3} + if obj.side == 1 then + color = {1,0,0,0.3} + elseif obj.side == 2 then + color = {0,0,1,0.3} + end + + obj.zone:draw(obj.index, color, color) + + local point = obj.zone.point + + if obj.zone:isCircle() then + point = { + x = obj.zone.point.x, + y = obj.zone.point.y, + z = obj.zone.point.z + obj.zone.radius + } + elseif obj.zone:isQuad() then + local largestZ = obj.zone.vertices[1].z + local largestX = obj.zone.vertices[1].x + for i=2,4,1 do + if obj.zone.vertices[i].z > largestZ then + largestZ = obj.zone.vertices[i].z + largestX = obj.zone.vertices[i].x + end + end + + point = { + x = largestX, + y = obj.zone.point.y, + z = largestZ + } + end + + --trigger.action.textToAll(1,1000+obj.index,point, {0,0,0,0.8}, {1,1,1,0.5}, 15, true, '') + --trigger.action.textToAll(2,2000+obj.index,point, {0,0,0,0.8}, {1,1,1,0.5}, 15, true, '') + trigger.action.textToAll(-1,2000+obj.index,point, {0,0,0,0.8}, {1,1,1,0.5}, 15, true, '') --show blue to all + setmetatable(obj, self) + self.__index = self + + obj:refreshText() + obj:start() + obj:refreshSpawnBlocking() + ZoneCommand.allZones[obj.name] = obj + return obj + end + + function ZoneCommand:refreshSpawnBlocking() + for _,v in ipairs(self.spawns) do + trigger.action.setUserFlag(v.name, v.side ~= self.side) + end + end + + function ZoneCommand.setNeighbours(conManager) + for name,zone in pairs(ZoneCommand.allZones) do + zone.connectionManager = conManager + local neighbours = conManager:getConnectionsOfZone(name) + zone.neighbours = {} + for _,zname in ipairs(neighbours) do + zone.neighbours[zname] = ZoneCommand.getZoneByName(zname) + end + end + end + + function ZoneCommand.getZoneByName(name) + if not name then return nil end + return ZoneCommand.allZones[name] + end + + function ZoneCommand.getAllZones() + return ZoneCommand.allZones + end + + function ZoneCommand.getZoneOfUnit(unitname) + local un = Unit.getByName(unitname) + + if not un then + return nil + end + + for i,v in pairs(ZoneCommand.allZones) do + if Utils.isInZone(un, i) then + return v + end + end + + return nil + end + + function ZoneCommand.getZoneOfWeapon(weapon) + if not weapon then + return nil + end + + for i,v in pairs(ZoneCommand.allZones) do + if Utils.isInZone(weapon, i) then + return v + end + end + + return nil + end + + function ZoneCommand.getClosestZoneToPoint(point) + local minDist = 9999999 + local closest = nil + for i,v in pairs(ZoneCommand.allZones) do + local d = mist.utils.get2DDist(v.zone.point, point) + if d < minDist then + minDist = d + closest = v + end + end + + return closest, minDist + end + + function ZoneCommand.getZoneOfPoint(point) + for i,v in pairs(ZoneCommand.allZones) do + local z = CustomZone:getByName(i) + if z and z:isInside(point) then + return v + end + end + + return nil + end + + function ZoneCommand:boostProduction(amount) + self.extraBuildResources = self.extraBuildResources + amount + env.info('ZoneCommand:boostProduction - '..self.name..' production boosted by '..amount..' to a total of '..self.extraBuildResources) + end + + function ZoneCommand:sabotage(explosionSize, sourcePoint) + local minDist = 99999999 + local closest = nil + for i,v in pairs(self.built) do + if v.type == 'upgrade' then + local st = StaticObject.getByName(v.name) + if not st then st = Group.getByName(v.name) end + local pos = st:getPoint() + + local d = mist.utils.get2DDist(pos, sourcePoint) + if d < minDist then + minDist = d; + closest = pos + end + end + end + + if closest then + trigger.action.explosion(closest, explosionSize) + env.info('ZoneCommand:sabotage - Structure has been sabotaged at '..self.name) + end + + local damagedResources = math.random(2000,5000) + self:removeResource(damagedResources) + self:refreshText() + end + + function ZoneCommand:refreshText() + local build = '' + if self.currentBuild then + local job = '' + local display = self.currentBuild.product.display + if self.currentBuild.product.type == 'upgrade' then + job = display + elseif self.currentBuild.product.type == 'defense' then + if self.currentBuild.isRepair then + job = display..' (repair)' + else + job = display + end + elseif self.currentBuild.product.type == 'mission' then + job = display + end + + build = '\n['..job..' '..math.min(math.floor((self.currentBuild.progress/self.currentBuild.product.cost)*100),100)..'%]' + end + + local mBuild = '' + if self.currentMissionBuild then + local job = '' + local display = self.currentMissionBuild.product.display + job = display + + mBuild = '\n['..job..' '..math.min(math.floor((self.currentMissionBuild.progress/self.currentMissionBuild.product.cost)*100),100)..'%]' + end + + local status='' + if self.side ~= 0 and self:criticalOnSupplies() then + status = '(!)' + end + + local color = {0.3,0.3,0.3,1} + if self.side == 1 then + color = {0.7,0,0,1} + elseif self.side == 2 then + color = {0,0,0.7,1} + end + + --trigger.action.setMarkupColor(1000+self.index, color) + trigger.action.setMarkupColor(2000+self.index, color) + + local label = '['..self.resource..'/'..self.maxResource..']'..status..build..mBuild + + if self.side == 1 then + --trigger.action.setMarkupText(1000+self.index, self.name..label) + + if self.revealTime > 0 then + trigger.action.setMarkupText(2000+self.index, self.name..label) + else + trigger.action.setMarkupText(2000+self.index, self.name) + end + elseif self.side == 2 then + --if self.revealTime > 0 then + -- trigger.action.setMarkupText(1000+self.index, self.name..label) + --else + -- trigger.action.setMarkupText(1000+self.index, self.name) + --end + trigger.action.setMarkupText(2000+self.index, self.name..label) + elseif self.side == 0 then + --trigger.action.setMarkupText(1000+self.index, ' '..self.name..' ') + trigger.action.setMarkupText(2000+self.index, ' '..self.name..' ') + end + end + + function ZoneCommand:setSide(side) + self.side = side + self:refreshSpawnBlocking() + + if side == 0 then + self.revealTime = 0 + end + + local color = {0.7,0.7,0.7,0.3} + if self.side==1 then + color = {1,0,0,0.3} + elseif self.side==2 then + color = {0,0,1,0.3} + end + + trigger.action.setMarkupColorFill(self.index, color) + trigger.action.setMarkupColor(self.index, color) + trigger.action.setMarkupTypeLine(self.index, 1) + + if self.side == 2 and (self.isHeloSpawn or self.isPlaneSpawn) then + trigger.action.setMarkupTypeLine(self.index, 2) + trigger.action.setMarkupColor(self.index, {0,1,0,1}) + end + + self:refreshText() + end + + function ZoneCommand:addResource(amount) + self.resource = self.resource+amount + self.resource = math.floor(math.min(self.resource, self.maxResource)) + end + + function ZoneCommand:removeResource(amount) + self.resource = self.resource-amount + self.resource = math.floor(math.max(self.resource, 0)) + end + + function ZoneCommand:reveal() + self.revealTime = 60*30 + self:refreshText() + end + + function ZoneCommand:needsSupplies(sendamount) + return self.resource + sendamount= cost then + self:removeResource(cost) + else + break + end + end + + self:instantBuild(v) + + for i2,v2 in ipairs(v.products) do + if (v2.type == 'defense' or v2.type=='upgrade') and v2.cost > 0 then + if useCost then + local cost = v2.cost * useCost + if self.resource >= cost then + self:removeResource(cost) + else + break + end + end + + self:instantBuild(v2) + end + end + end + end + + function ZoneCommand:start() + timer.scheduleFunction(function(param, time) + local self = param.context + local initialRes = self.resource + + --generate income + if self.side ~= 0 then + self:addResource(self.income) + end + + --untrack destroyed zone upgrades + for i,v in pairs(self.built) do + local u = Group.getByName(i) + if u and u:getSize() == 0 then + u:destroy() + self.built[i] = nil + end + + if not u then + u = StaticObject.getByName(i) + if u and u:getLife()<1 then + u:destroy() + self.built[i] = nil + end + end + + if not u then + self.built[i] = nil + end + end + + --upkeep costs for defenses + for i,v in pairs(self.built) do + if v.type == 'defense' and v.upkeep then + v.strikes = v.strikes or 0 + if self.resource >= v.upkeep then + self:removeResource(v.upkeep) + v.strikes = 0 + else + if v.strikes < 6 then + v.strikes = v.strikes+1 + else + local u = Group.getByName(i) + if u then + v.strikes = nil + u:destroy() + self.built[i] = nil + end + end + end + elseif v.type == 'upgrade' and v.income then + self:addResource(v.income) + end + end + + --check if zone should be reverted to neutral + local hasUpgrade = false + for i,v in pairs(self.built) do + if v.type=='upgrade' then + hasUpgrade = true + break + end + end + + if not hasUpgrade and self.side ~= 0 then + local sidetxt = "Neutral" + if self.side == 1 then + sidetxt = "Red" + elseif self.side == 2 then + sidetxt = "Blue" + end + + trigger.action.outText(sidetxt.." has lost control of "..self.name, 15) + + self:setSide(0) + self.mode = 'normal' + self.currentBuild = nil + self.currentMissionBuild = nil + end + + --sell defenses if export mode + if self.side ~= 0 and self.mode == 'export' then + for i,v in pairs(self.built) do + if v.type=='defense' then + local g = Group.getByName(i) + if g then g:destroy() end + self:addResource(math.floor(v.cost/2)) + self.built[i] = nil + end + end + end + + self:verifyBuildValid() + self:chooseBuild() + self:progressBuild() + + self.resourceChange = self.resource - initialRes + self:refreshText() + + --use revealTime resource + if self.revealTime > 0 then + self.revealTime = math.max(0,self.revealTime-10) + end + + return time+10 + end, {context = self}, timer.getTime()+1) + end + + function ZoneCommand:verifyBuildValid() + if self.currentBuild then + if self.side == 0 then + self.currentBuild = nil + env.info('ZoneCommand:verifyBuildValid - stopping build, zone is neutral') + end + + if self.mode == 'export' or self.mode == 'supply' then + if not (self.currentBuild.product.type == ZoneCommand.productTypes.upgrade or + self.currentBuild.product.missionType == ZoneCommand.missionTypes.supply_air or + self.currentBuild.product.missionType == ZoneCommand.missionTypes.supply_convoy or + self.currentBuild.product.missionType == ZoneCommand.missionTypes.supply_transfer) then + env.info('ZoneCommand:verifyBuildValid - stopping build, mode is '..self.mode..' but mission is not supply') + self.currentBuild = nil + end + end + + if self.currentBuild and (self.currentBuild.product.type == 'defense' or self.currentBuild.product.type == 'mission') then + for i,v in ipairs(self.upgrades[self.currentBuild.side]) do + for i2,v2 in ipairs(v.products) do + if v2.name == self.currentBuild.product.name then + local g = Group.getByName(v.name) + if not g then g = StaticObject.getByName(v.name) end + + if not g then + env.info('ZoneCommand:verifyBuildValid - stopping build, required upgrade no longer exists') + self.currentBuild = nil + break + end + end + end + + if not self.currentBuild then + break + end + end + end + end + + if self.currentMissionBuild then + if self.side == 0 then + self.currentMissionBuild = nil + env.info('ZoneCommand:verifyBuildValid - stopping mission build, zone is neutral') + end + + if (self.mode == 'export' and not self.keepActive) or self.mode == 'supply' then + env.info('ZoneCommand:verifyBuildValid - stopping mission build, mode is '..self.mode..'') + self.currentMissionBuild = nil + end + + if self.currentMissionBuild and self.currentMissionBuild.product.type == 'mission' then + for i,v in ipairs(self.upgrades[self.currentMissionBuild.side]) do + for i2,v2 in ipairs(v.products) do + if v2.name == self.currentMissionBuild.product.name then + local g = Group.getByName(v.name) + if not g then g = StaticObject.getByName(v.name) end + + if not g then + env.info('ZoneCommand:verifyBuildValid - stopping mission build, required upgrade no longer exists') + self.currentMissionBuild = nil + break + end + end + end + + if not self.currentMissionBuild then + break + end + end + end + end + end + + function ZoneCommand:chooseBuild() + local treshhold = self.spendTreshold + --local treshhold = 0 + if self.side ~= 0 and self.currentBuild == nil then + local canAfford = {} + for _,v in ipairs(self.upgrades[self.side]) do + local u = Group.getByName(v.name) + if not u then u = StaticObject.getByName(v.name) end + + if not u then + table.insert(canAfford, {product = v, reason='upgrade'}) + elseif u ~= nil then + for _,v2 in ipairs(v.products) do + if v2.type == 'mission' then + if self.resource > treshhold and + (v2.missionType == ZoneCommand.missionTypes.supply_air or + v2.missionType == ZoneCommand.missionTypes.supply_convoy or + v2.missionType == ZoneCommand.missionTypes.supply_transfer) then + if self:isMissionValid(v2) and math.random() < ZoneCommand.missionValidChance then + table.insert(canAfford, {product = v2, reason='mission'}) + if v2.bias then + for _=1,v2.bias,1 do + table.insert(canAfford, {product = v2, reason='mission'}) + end + end + end + end + elseif v2.type=='defense' and self.mode ~='export' and self.mode ~='supply' and v2.cost > 0 then + local g = Group.getByName(v2.name) + if not g then + table.insert(canAfford, {product = v2, reason='defense'}) + elseif g:getSize() < (g:getInitialSize()*math.random(40,100)/100) then + table.insert(canAfford, {product = v2, reason='repair'}) + end + end + end + end + end + + if #canAfford > 0 then + local choice = math.random(1, #canAfford) + + if canAfford[choice] then + local p = canAfford[choice] + if p.reason == 'repair' then + self:queueBuild(p.product, self.side, true) + else + self:queueBuild(p.product, self.side) + end + end + end + end + + if self.side ~= 0 and self.currentMissionBuild == nil then + local canMission = {} + for _,v in ipairs(self.upgrades[self.side]) do + local u = Group.getByName(v.name) + if not u then u = StaticObject.getByName(v.name) end + if u ~= nil then + for _,v2 in ipairs(v.products) do + if v2.type == 'mission' then + if v2.missionType ~= ZoneCommand.missionTypes.supply_air and + v2.missionType ~= ZoneCommand.missionTypes.supply_convoy and + v2.missionType ~= ZoneCommand.missionTypes.supply_transfer then + if self:isMissionValid(v2) and math.random() < ZoneCommand.missionValidChance then + table.insert(canMission, {product = v2, reason='mission'}) + if v2.bias then + for _=1,v2.bias,1 do + table.insert(canMission, {product = v2, reason='mission'}) + end + end + end + end + end + end + end + end + + if #canMission > 0 then + local choice = math.random(1, #canMission) + + if canMission[choice] then + local p = canMission[choice] + self:queueBuild(p.product, self.side) + end + end + end + end + + function ZoneCommand:progressBuild() + if self.currentBuild and self.currentBuild.side ~= self.side then + env.info('ZoneCommand:progressBuild '..self.name..' - stopping build, zone changed owner') + self.currentBuild = nil + end + + if self.currentMissionBuild and self.currentMissionBuild.side ~= self.side then + env.info('ZoneCommand:progressBuild '..self.name..' - stopping mission build, zone changed owner') + self.currentMissionBuild = nil + end + + if self.currentBuild then + if self.currentBuild.product.type == 'mission' and not self:isMissionValid(self.currentBuild.product) then + env.info('ZoneCommand:progressBuild '..self.name..' - stopping build, mission no longer valid') + self.currentBuild = nil + else + local cost = self.currentBuild.product.cost + if self.currentBuild.isRepair then + cost = math.floor(self.currentBuild.product.cost/2) + end + + if self.currentBuild.progress < cost then + if self.currentBuild.isRepair and not Group.getByName(self.currentBuild.product.name) then + env.info('ZoneCommand:progressBuild '..self.name..' - stopping build, group to repair no longer exists') + self.currentBuild = nil + else + if self.currentBuild.isRepair then + local gr = Group.getByName(self.currentBuild.product.name) + if gr and self.currentBuild.unitcount and gr:getSize() < self.currentBuild.unitcount then + env.info('ZoneCommand:progressBuild '..self.name..' - restarting build, group to repair has casualties') + self.currentBuild.unitcount = gr:getSize() + self:addResource(self.currentBuild.progress) + self.currentBuild.progress = 0 + end + end + + local step = math.floor(ZoneCommand.buildSpeed * self.boostScale) + if self.currentBuild.product.type == ZoneCommand.productTypes.mission then + if self.currentBuild.product.missionType == ZoneCommand.missionTypes.supply_air or + self.currentBuild.product.missionType == ZoneCommand.missionTypes.supply_convoy or + self.currentBuild.product.missionType == ZoneCommand.missionTypes.supply_transfer then + step = math.floor(ZoneCommand.supplyBuildSpeed * self.boostScale) + + if self.currentBuild.product.missionType == ZoneCommand.missionTypes.supply_transfer then + step = math.floor(step*2) + end + end + end + + if step > self.resource then step = 1 end + if step <= self.resource then + self:removeResource(step) + self.currentBuild.progress = self.currentBuild.progress + step + + if self.extraBuildResources > 0 then + local extrastep = step + if self.extraBuildResources < extrastep then + extrastep = self.extraBuildResources + end + + self.extraBuildResources = math.max(self.extraBuildResources - extrastep, 0) + self.currentBuild.progress = self.currentBuild.progress + extrastep + + env.info('ZoneCommand:progressBuild - '..self.name..' consumed '..extrastep..' extra resources, remaining '..self.extraBuildResources) + end + end + end + else + if self.currentBuild.product.type == 'mission' then + if self:isMissionValid(self.currentBuild.product) then + self:activateMission(self.currentBuild.product) + else + self:addResource(self.currentBuild.product.cost) + end + elseif self.currentBuild.product.type == 'defense' or self.currentBuild.product.type=='upgrade' then + if self.currentBuild.isRepair then + if Group.getByName(self.currentBuild.product.name) then + self.zone:spawnGroup(self.currentBuild.product) + end + else + self.zone:spawnGroup(self.currentBuild.product) + end + + self.built[self.currentBuild.product.name] = self.currentBuild.product + end + + self.currentBuild = nil + end + end + end + + if self.currentMissionBuild then + if self.currentMissionBuild.product.type == 'mission' and not self:isMissionValid(self.currentMissionBuild.product) then + env.info('ZoneCommand:progressBuild '..self.name..' - stopping build, mission no longer valid') + self.currentMissionBuild = nil + else + local cost = self.currentMissionBuild.product.cost + + if self.currentMissionBuild.progress < cost then + local step = math.floor(ZoneCommand.buildSpeed * self.boostScale) + + if step > self.resource then step = 1 end + + local progress = step*self.missionBuildSpeedReduction + local reducedCost = math.max(1, math.floor(progress)) + if reducedCost <= self.resource then + self:removeResource(reducedCost) + self.currentMissionBuild.progress = self.currentMissionBuild.progress + progress + end + else + if self:isMissionValid(self.currentMissionBuild.product) then + self:activateMission(self.currentMissionBuild.product) + else + self:addResource(self.currentMissionBuild.product.cost) + end + + self.currentMissionBuild = nil + end + end + end + end + + function ZoneCommand:queueBuild(product, side, isRepair, progress) + if product.type ~= ZoneCommand.productTypes.mission or + (product.missionType == ZoneCommand.missionTypes.supply_air or + product.missionType == ZoneCommand.missionTypes.supply_convoy or + product.missionType == ZoneCommand.missionTypes.supply_transfer) then + + local unitcount = nil + if isRepair then + local g = Group.getByName(product.name) + if g then + unitcount = g:getSize() + env.info('ZoneCommand:queueBuild - '..self.name..' '..product.name..' has '..unitcount..' units') + end + end + + self.currentBuild = { product = product, progress = (progress or 0), side = side, isRepair = isRepair, unitcount = unitcount} + env.info('ZoneCommand:queueBuild - '..self.name..' chose '..product.name..'('..product.display..') as its build') + else + self.currentMissionBuild = { product = product, progress = (progress or 0), side = side} + env.info('ZoneCommand:queueBuild - '..self.name..' chose '..product.name..'('..product.display..') as its mission build') + end + end + + function ZoneCommand:reserveMission(product) + self.reservedMissions[product.name] = product + end + + function ZoneCommand:unReserveMission(product) + self.reservedMissions[product.name] = nil + end + + function ZoneCommand:isMissionValid(product) + if Group.getByName(product.name) then return false end + + if self.reservedMissions[product.name] then + return false + end + + if product.missionType == ZoneCommand.missionTypes.supply_convoy then + if self.distToFront == nil then return false end + + for _,tgt in pairs(self.neighbours) do + if self:isSupplyMissionValid(product, tgt) then + return true + end + end + elseif product.missionType == ZoneCommand.missionTypes.supply_transfer then + if self.distToFront == nil then return false end + for _,tgt in pairs(self.neighbours) do + if self:isSupplyTransferMissionValid(product, tgt) then + return true + end + end + elseif product.missionType == ZoneCommand.missionTypes.supply_air then + if self.distToFront == nil then return false end + + for _,tgt in pairs(self.neighbours) do + if self:isSupplyMissionValid(product, tgt) then + return true + else + for _,subtgt in pairs(tgt.neighbours) do + if subtgt.name ~= self.name and self:isSupplyMissionValid(product, subtgt) then + local dist = mist.utils.get2DDist(self.zone.point, subtgt.zone.point) + if dist < 50000 then + return true + end + end + end + end + end + elseif product.missionType == ZoneCommand.missionTypes.assault then + if self.mode ~= ZoneCommand.modes.normal then return false end + for _,tgt in pairs(self.neighbours) do + if self:isAssaultMissionValid(product, tgt) then + return true + end + end + elseif product.missionType == ZoneCommand.missionTypes.cas then + if self.mode ~= ZoneCommand.modes.normal and not self.keepActive then return false end + + for _,tgt in pairs(ZoneCommand.getAllZones()) do + if self:isCasMissionValid(product, tgt) then + return true + end + end + elseif product.missionType == ZoneCommand.missionTypes.cas_helo then + if self.mode ~= ZoneCommand.modes.normal and not self.keepActive then return false end + + for _,tgt in pairs(self.neighbours) do + if self:isCasMissionValid(product, tgt) then + return true + end + end + elseif product.missionType == ZoneCommand.missionTypes.strike then + if self.mode ~= ZoneCommand.modes.normal and not self.keepActive then return false end + + for _,tgt in pairs(ZoneCommand.getAllZones()) do + if self:isStrikeMissionValid(product, tgt) then + return true + end + end + elseif product.missionType == ZoneCommand.missionTypes.sead then + if self.mode ~= ZoneCommand.modes.normal and not self.keepActive then return false end + + for _,tgt in pairs(ZoneCommand.getAllZones()) do + if self:isSeadMissionValid(product, tgt) then + return true + end + end + elseif product.missionType == ZoneCommand.missionTypes.patrol then + if self.mode ~= ZoneCommand.modes.normal and not self.keepActive then return false end + + for _,tgt in pairs(ZoneCommand.getAllZones()) do + if self:isPatrolMissionValid(product, tgt) then + return true + end + end + elseif product.missionType == ZoneCommand.missionTypes.bai then + if not ZoneCommand.groupMonitor then return false end + if self.mode ~= ZoneCommand.modes.normal and not self.keepActive then return false end + + for _,tgt in pairs(ZoneCommand.groupMonitor.groups) do + if self:isBaiMissionValid(product, tgt) then + return true + end + end + elseif product.missionType == ZoneCommand.missionTypes.awacs then + if not ZoneCommand.groupMonitor then return false end + if self.mode ~= ZoneCommand.modes.normal and not self.keepActive then return false end + for _,tgt in pairs(ZoneCommand.getAllZones()) do + if self:isAwacsMissionValid(product, tgt) then + return true + end + end + elseif product.missionType == ZoneCommand.missionTypes.tanker then + if not ZoneCommand.groupMonitor then return false end + if self.mode ~= ZoneCommand.modes.normal and not self.keepActive then return false end + if not self.distToFront or self.distToFront == 0 then return false end + for _,tgt in pairs(ZoneCommand.getAllZones()) do + if self:isTankerMissionValid(product, tgt) then + return true + end + end + end + end + + function ZoneCommand:activateMission(product) + if product.missionType == ZoneCommand.missionTypes.supply_convoy then + self:activateSupplyConvoyMission(product) + elseif product.missionType == ZoneCommand.missionTypes.assault then + self:activateAssaultMission(product) + elseif product.missionType == ZoneCommand.missionTypes.supply_air then + self:activateAirSupplyMission(product) + elseif product.missionType == ZoneCommand.missionTypes.supply_transfer then + self:activateSupplyTransferMission(product) + elseif product.missionType == ZoneCommand.missionTypes.cas then + self:activateCasMission(product) + elseif product.missionType == ZoneCommand.missionTypes.cas_helo then + self:activateCasMission(product, true) + elseif product.missionType == ZoneCommand.missionTypes.strike then + self:activateStrikeMission(product) + elseif product.missionType == ZoneCommand.missionTypes.sead then + self:activateSeadMission(product) + elseif product.missionType == ZoneCommand.missionTypes.patrol then + self:activatePatrolMission(product) + elseif product.missionType == ZoneCommand.missionTypes.bai then + self:activateBaiMission(product) + elseif product.missionType == ZoneCommand.missionTypes.awacs then + self:activateAwacsMission(product) + elseif product.missionType == ZoneCommand.missionTypes.tanker then + self:activateTankerMission(product) + end + + env.info('ZoneCommand:activateMission - '..self.name..' activating mission '..product.name..'('..product.display..')') + end + + function ZoneCommand:reActivateMission(savedData) + local product = self:getProductByName(savedData.productName) + + if product.missionType == ZoneCommand.missionTypes.supply_convoy then + self:reActivateSupplyConvoyMission(product, savedData) + elseif product.missionType == ZoneCommand.missionTypes.assault then + self:reActivateAssaultMission(product, savedData) + elseif product.missionType == ZoneCommand.missionTypes.supply_air then + self:reActivateAirSupplyMission(product, savedData) + elseif product.missionType == ZoneCommand.missionTypes.supply_transfer then + self:reActivateSupplyTransferMission(product, savedData) + elseif product.missionType == ZoneCommand.missionTypes.cas then + self:reActivateCasMission(product, nil, savedData) + elseif product.missionType == ZoneCommand.missionTypes.cas_helo then + self:reActivateCasMission(product, true, savedData) + elseif product.missionType == ZoneCommand.missionTypes.strike then + self:reActivateStrikeMission(product, savedData) + elseif product.missionType == ZoneCommand.missionTypes.sead then + self:reActivateSeadMission(product, savedData) + elseif product.missionType == ZoneCommand.missionTypes.patrol then + self:reActivatePatrolMission(product, savedData) + elseif product.missionType == ZoneCommand.missionTypes.bai then + self:reActivateBaiMission(product, savedData) + elseif product.missionType == ZoneCommand.missionTypes.awacs then + self:reActivateAwacsMission(product, savedData) + elseif product.missionType == ZoneCommand.missionTypes.tanker then + self:reActivateTankerMission(product, savedData) + end + + env.info('ZoneCommand:reActivateMission - '..self.name..' reactivating mission '..product.name..'('..product.display..')') + end + + local function getDefaultPos(savedData, isAir) + local action = 'Off Road' + local speed = 0 + if isAir then + action = 'Turning Point' + speed = 250 + end + + local vars = { + groupName = savedData.productName, + point = savedData.position, + action = 'respawn', + heading = savedData.heading, + initTasks = false, + route = { + [1] = { + alt = savedData.position.y, + type = 'Turning Point', + action = action, + alt_type = 'BARO', + x = savedData.position.x, + y = savedData.position.z, + speed = speed + } + } + } + + return vars + end + + local function teleportToPos(groupName, pos) + if pos.y == nil then + pos.y = land.getHeight({ x = pos.x, y = pos.z }) + end + + local vars = { + groupName = groupName, + point = pos, + action = 'respawn', + initTasks = false + } + + mist.teleportToPoint(vars) + end + + function ZoneCommand:reActivateSupplyConvoyMission(product, savedData) + local zone = ZoneCommand.getZoneByName(savedData.lastMission.zoneName) + + local supplyPoint = trigger.misc.getZone(zone.name..'-sp') + if not supplyPoint then + supplyPoint = trigger.misc.getZone(zone.name) + end + if supplyPoint then + mist.teleportToPoint(getDefaultPos(savedData, false)) + if ZoneCommand.groupMonitor then + ZoneCommand.groupMonitor:registerGroup(product, zone, self, savedData) + end + + product.lastMission = {zoneName = zone.name} + timer.scheduleFunction(function(param) + local gr = Group.getByName(param.name) + TaskExtensions.moveOnRoadToPoint(gr, param.point) + end, {name=product.name, point={ x=supplyPoint.point.x, y = supplyPoint.point.z}}, timer.getTime()+1) + end + end + + function ZoneCommand:reActivateAssaultMission(product, savedData) + local zone = ZoneCommand.getZoneByName(savedData.lastMission.zoneName) + + local supplyPoint = trigger.misc.getZone(zone.name..'-sp') + if not supplyPoint then + supplyPoint = trigger.misc.getZone(zone.name) + end + if supplyPoint then + mist.teleportToPoint(getDefaultPos(savedData, false)) + if ZoneCommand.groupMonitor then + ZoneCommand.groupMonitor:registerGroup(product, zone, self, savedData) + end + + local tgtPoint = trigger.misc.getZone(zone.name) + + if tgtPoint then + product.lastMission = {zoneName = zone.name} + timer.scheduleFunction(function(param) + local gr = Group.getByName(param.name) + TaskExtensions.moveOnRoadToPointAndAssault(gr, param.point, param.targets) + end, {name=product.name, point={ x=tgtPoint.point.x, y = tgtPoint.point.z}, targets=zone.built}, timer.getTime()+1) + end + end + end + + function ZoneCommand:reActivateAirSupplyMission(product, savedData) + local zone = ZoneCommand.getZoneByName(savedData.lastMission.zoneName) + + mist.teleportToPoint(getDefaultPos(savedData, true)) + if ZoneCommand.groupMonitor then + ZoneCommand.groupMonitor:registerGroup(product, zone, self, savedData) + end + + local supplyPoint = trigger.misc.getZone(zone.name..'-hsp') + if not supplyPoint then + supplyPoint = trigger.misc.getZone(zone.name) + end + + if supplyPoint then + product.lastMission = {zoneName = zone.name} + local alt = self.connectionManager:getHeliAlt(self.name, zone.name) + timer.scheduleFunction(function(param) + local gr = Group.getByName(param.name) + TaskExtensions.landAtPoint(gr, param.point, param.alt) + end, {name=product.name, point={ x=supplyPoint.point.x, y = supplyPoint.point.z}, alt = alt}, timer.getTime()+1) + end + end + + function ZoneCommand:reActivateSupplyTransferMission(product, savedData) + -- not needed + end + + function ZoneCommand:reActivateCasMission(product, isHelo, savedData) + local zone = ZoneCommand.getZoneByName(savedData.lastMission.zoneName) + + mist.teleportToPoint(getDefaultPos(savedData, true)) + if ZoneCommand.groupMonitor then + ZoneCommand.groupMonitor:registerGroup(product, zone, self, savedData) + end + + local homePos = trigger.misc.getZone(savedData.homeName).point + + if zone then + product.lastMission = {zoneName = zone.name} + timer.scheduleFunction(function(param) + local gr = Group.getByName(param.prod.name) + if param.helo then + TaskExtensions.executeHeloCasMission(gr, param.targets, param.prod.expend, param.prod.altitude, {homePos = homePos}) + else + TaskExtensions.executeCasMission(gr, param.targets, param.prod.expend, param.prod.altitude, {homePos = homePos}) + end + end, {prod=product, targets=zone.built, helo = isHelo, homePos = homePos}, timer.getTime()+1) + end + end + + function ZoneCommand:reActivateStrikeMission(product, savedData) + local zone = ZoneCommand.getZoneByName(savedData.lastMission.zoneName) + + mist.teleportToPoint(getDefaultPos(savedData, true)) + + if ZoneCommand.groupMonitor then + ZoneCommand.groupMonitor:registerGroup(product, zone, self, savedData) + end + + local homePos = trigger.misc.getZone(savedData.homeName).point + + if zone then + product.lastMission = {zoneName = zone.name} + timer.scheduleFunction(function(param) + local gr = Group.getByName(param.prod.name) + TaskExtensions.executeStrikeMission(gr, param.targets, param.prod.expend, param.prod.altitude, {homePos = homePos}) + end, {prod=product, targets=zone.built, homePos = homePos}, timer.getTime()+1) + end + end + + function ZoneCommand:reActivateSeadMission(product, savedData) + local zone = ZoneCommand.getZoneByName(savedData.lastMission.zoneName) + + mist.teleportToPoint(getDefaultPos(savedData, true)) + if ZoneCommand.groupMonitor then + ZoneCommand.groupMonitor:registerGroup(product, zone, self, savedData) + end + + local homePos = trigger.misc.getZone(savedData.homeName).point + + if zone then + product.lastMission = {zoneName = zone.name} + timer.scheduleFunction(function(param) + local gr = Group.getByName(param.prod.name) + TaskExtensions.executeSeadMission(gr, param.targets, param.prod.expend, param.prod.altitude, {homePos = homePos}) + end, {prod=product, targets=zone.built, homePos = homePos}, timer.getTime()+1) + end + end + + function ZoneCommand:reActivatePatrolMission(product, savedData) + + local zn1 = ZoneCommand.getZoneByName(savedData.lastMission.zone1name) + + mist.teleportToPoint(getDefaultPos(savedData, true)) + if ZoneCommand.groupMonitor then + ZoneCommand.groupMonitor:registerGroup(product, zn1, self, savedData) + end + + local homePos = trigger.misc.getZone(savedData.homeName).point + + if zn1 then + product.lastMission = {zone1name = zn1.name} + timer.scheduleFunction(function(param) + local gr = Group.getByName(param.prod.name) + + local point = trigger.misc.getZone(param.zone1.name).point + + TaskExtensions.executePatrolMission(gr, point, param.prod.altitude, param.prod.range, {homePos = param.homePos}) + end, {prod=product, zone1 = zn1, homePos = homePos}, timer.getTime()+1) + end + end + + function ZoneCommand:reActivateBaiMission(product, savedData) + local targets = {} + local hasTarget = false + for _,tgt in pairs(ZoneCommand.groupMonitor.groups) do + if self:isBaiMissionValid(product, tgt) then + targets[tgt.product.name] = tgt.product + hasTarget = true + end + end + + local homePos = trigger.misc.getZone(savedData.homeName).point + + if hasTarget then + mist.teleportToPoint(getDefaultPos(savedData, true)) + if ZoneCommand.groupMonitor then + ZoneCommand.groupMonitor:registerGroup(product, nil, self, savedData) + end + + product.lastMission = { active = true } + timer.scheduleFunction(function(param) + local gr = Group.getByName(param.prod.name) + TaskExtensions.executeBaiMission(gr, param.targets, param.prod.expend, param.prod.altitude, {homePos = param.homePos}) + end, {prod=product, targets=targets, homePos = homePos}, timer.getTime()+1) + end + end + + function ZoneCommand:reActivateAwacsMission(product, savedData) + + local zone = ZoneCommand.getZoneByName(savedData.lastMission.zoneName) + local homePos = trigger.misc.getZone(savedData.homeName).point + + mist.teleportToPoint(getDefaultPos(savedData, true)) + if ZoneCommand.groupMonitor then + ZoneCommand.groupMonitor:registerGroup(product, nil, self, savedData) + end + timer.scheduleFunction(function(param) + local gr = Group.getByName(param.prod.name) + if gr then + local un = gr:getUnit(1) + if un then + local callsign = un:getCallsign() + RadioFrequencyTracker.registerRadio(param.prod.name, '[AWACS] '..callsign, param.prod.freq..' AM') + end + + local point = trigger.misc.getZone(param.target.name).point + product.lastMission = { zoneName = param.target.name } + TaskExtensions.executeAwacsMission(gr, point, param.prod.altitude, param.prod.freq, {homePos = param.homePos}) + end + end, {prod=product, target=zone, homePos = homePos}, timer.getTime()+1) + end + + function ZoneCommand:reActivateTankerMission(product, savedData) + + local zone = ZoneCommand.getZoneByName(savedData.lastMission.zoneName) + + local homePos = trigger.misc.getZone(savedData.homeName).point + mist.teleportToPoint(getDefaultPos(savedData, true)) + if ZoneCommand.groupMonitor then + ZoneCommand.groupMonitor:registerGroup(product, zone, self, savedData) + end + + timer.scheduleFunction(function(param) + local gr = Group.getByName(param.prod.name) + if gr then + local un = gr:getUnit(1) + if un then + local callsign = un:getCallsign() + RadioFrequencyTracker.registerRadio(param.prod.name, '[Tanker('..param.prod.variant..')] '..callsign, param.prod.freq..' AM | TCN '..param.prod.tacan..'X') + end + + local point = trigger.misc.getZone(param.target.name).point + product.lastMission = { zoneName = param.target.name } + TaskExtensions.executeTankerMission(gr, point, param.prod.altitude, param.prod.freq, param.prod.tacan, {homePos = param.homePos}) + end + end, {prod=product, target=zone, homePos = homePos}, timer.getTime()+1) + end + + function ZoneCommand:isBaiMissionValid(product, tgtgroup) + if product.side == tgtgroup.product.side then return false end + if tgtgroup.product.type ~= ZoneCommand.productTypes.mission then return false end + if tgtgroup.product.missionType == ZoneCommand.missionTypes.assault then return true end + if tgtgroup.product.missionType == ZoneCommand.missionTypes.supply_convoy then return true end + end + + function ZoneCommand:activateBaiMission(product) + --{name = product.name, lastStateTime = timer.getAbsTime(), product = product, target = target} + local targets = {} + local hasTarget = false + for _,tgt in pairs(ZoneCommand.groupMonitor.groups) do + if self:isBaiMissionValid(product, tgt) then + targets[tgt.product.name] = tgt.product + hasTarget = true + end + end + + if hasTarget then + local og = Utils.getOriginalGroup(product.name) + if og then + teleportToPos(product.name, {x=og.x, z=og.y}) + env.info("ZoneCommand - activateBaiMission teleporting to OG pos") + else + mist.respawnGroup(product.name, true) + env.info("ZoneCommand - activateBaiMission fallback to respawnGroup") + end + + if ZoneCommand.groupMonitor then + ZoneCommand.groupMonitor:registerGroup(product, nil, self) + end + + product.lastMission = { active = true } + timer.scheduleFunction(function(param) + local gr = Group.getByName(param.prod.name) + TaskExtensions.executeBaiMission(gr, param.targets, param.prod.expend, param.prod.altitude) + end, {prod=product, targets=targets}, timer.getTime()+1) + + env.info("ZoneCommand - "..product.name.." targeting convoys") + end + end + + local function prioritizeSupplyTargets(a,b) + --if a:criticalOnSupplies() and not b:criticalOnSupplies() then return true end + --if b:criticalOnSupplies() and not a:criticalOnSupplies() then return false end + + if a.distToFront~=nil and b.distToFront == nil then + return true + elseif a.distToFront == nil and b.distToFront ~= nil then + return false + elseif a.distToFront == b.distToFront then + return a.resource < b.resource + else + return a.distToFront 1 and target.distToFront > 1 then return false end -- skip regular missions if not close to front + + if self.mode == 'normal' and self.distToFront == 0 and target.distToFront == 0 then + return target:needsSupplies(product.cost*0.5) + end + + if target:needsSupplies(product.cost*0.5) and target.distToFront < self.distToFront then + return true + elseif target:criticalOnSupplies() and self.distToFront>=target.distToFront then + return true + end + + if target.mode == 'normal' and target:needsSupplies(product.cost*0.5) then + return true + end + end + end + + function ZoneCommand:activateSupplyConvoyMission(product) + local tgtzones = {} + for _,v in pairs(self.neighbours) do + if (v.side == 0 or v.side==product.side) then + table.insert(tgtzones, v) + end + end + + if #tgtzones == 0 then + env.info('ZoneCommand:activateSupplyConvoyMission - '..self.name..' no valid tgtzones') + return + end + + table.sort(tgtzones, prioritizeSupplyTargets) + + if BattlefieldManager and BattlefieldManager.priorityZones[self.side] then + local prioZone = BattlefieldManager.priorityZones[self.side] + if prioZone.side == 0 and self.neighbours[prioZone.name] and self:isSupplyMissionValid(product, prioZone) then + tgtzones = { prioZone } + end + end + + for i,v in ipairs(tgtzones) do + if self:isSupplyMissionValid(product, v) then + + local supplyPoint = trigger.misc.getZone(v.name..'-sp') + if not supplyPoint then + supplyPoint = trigger.misc.getZone(v.name) + end + + if supplyPoint then + + local og = Utils.getOriginalGroup(product.name) + if og then + teleportToPos(product.name, {x=og.x, z=og.y}) + env.info("ZoneCommand - activateSupplyConvoyMission teleporting to OG pos") + else + mist.respawnGroup(product.name, true) + env.info("ZoneCommand - activateSupplyConvoyMission fallback to respawnGroup") + end + + if ZoneCommand.groupMonitor then + ZoneCommand.groupMonitor:registerGroup(product, v, self) + end + + product.lastMission = {zoneName = v.name} + timer.scheduleFunction(function(param) + local gr = Group.getByName(param.name) + TaskExtensions.moveOnRoadToPoint(gr, param.point) + end, {name=product.name, point={ x=supplyPoint.point.x, y = supplyPoint.point.z}}, timer.getTime()+1) + + env.info("ZoneCommand - "..product.name.." targeting "..v.name) + end + + break + end + end + end + + function ZoneCommand:isAssaultMissionValid(product, target) + if not self.connectionManager then + env.info("ZoneCommand - ERROR missing connection manager") + end + + if product.missionType == ZoneCommand.missionTypes.assault then + if self.connectionManager:isRoadBlocked(self.name, target.name) then + return false + end + end + + if target.side ~= product.side and target.side ~= 0 then + return true + end + end + + function ZoneCommand:activateAssaultMission(product) + local tgtzones = {} + for _,v in pairs(self.neighbours) do + table.insert(tgtzones, {zone = v, rank = math.random()}) + end + + table.sort(tgtzones, function(a,b) return a.rank < b.rank end) + + local sorted = {} + for i,v in ipairs(tgtzones) do + table.insert(sorted, v.zone) + end + tgtzones = sorted + + if BattlefieldManager and BattlefieldManager.priorityZones[self.side] then + local prioZone = BattlefieldManager.priorityZones[self.side] + if self.neighbours[prioZone.name] and self:isAssaultMissionValid(product, prioZone) then + tgtzones = { prioZone } + end + end + + for i,v in ipairs(tgtzones) do + if self:isAssaultMissionValid(product, v) then + + local og = Utils.getOriginalGroup(product.name) + if og then + teleportToPos(product.name, {x=og.x, z=og.y}) + env.info("ZoneCommand - activateAssaultMission teleporting to OG pos") + else + mist.respawnGroup(product.name, true) + env.info("ZoneCommand - activateAssaultMission fallback to respawnGroup") + end + + if ZoneCommand.groupMonitor then + ZoneCommand.groupMonitor:registerGroup(product, v, self) + end + + local tgtPoint = trigger.misc.getZone(v.name) + + if tgtPoint then + product.lastMission = {zoneName = v.name} + timer.scheduleFunction(function(param) + local gr = Group.getByName(param.name) + TaskExtensions.moveOnRoadToPointAndAssault(gr, param.point, param.targets) + end, {name=product.name, point={ x=tgtPoint.point.x, y = tgtPoint.point.z}, targets=v.built}, timer.getTime()+1) + + env.info("ZoneCommand - "..product.name.." targeting "..v.name) + end + + break + end + end + end + + function ZoneCommand:isAwacsMissionValid(product, target) + if target.side ~= product.side then return false end + if target.name == self.name then return false end + if not target.distToFront or target.distToFront ~= 4 then return false end + + return true + end + + function ZoneCommand:activateAwacsMission(product) + local tgtzones = {} + for _,v in pairs(ZoneCommand.getAllZones()) do + if self:isAwacsMissionValid(product, v) then + table.insert(tgtzones, v) + end + end + + local choice1 = math.random(1,#tgtzones) + local zn = tgtzones[choice1] + + local og = Utils.getOriginalGroup(product.name) + if og then + teleportToPos(product.name, {x=og.x, z=og.y}) + env.info("ZoneCommand - activateAwacsMission teleporting to OG pos") + else + mist.respawnGroup(product.name, true) + env.info("ZoneCommand - activateAwacsMission fallback to respawnGroup") + end + + if ZoneCommand.groupMonitor then + ZoneCommand.groupMonitor:registerGroup(product, zn, self) + end + timer.scheduleFunction(function(param) + local gr = Group.getByName(param.prod.name) + if gr then + local un = gr:getUnit(1) + if un then + local callsign = un:getCallsign() + RadioFrequencyTracker.registerRadio(param.prod.name, '[AWACS] '..callsign, param.prod.freq..' AM') + end + + local point = trigger.misc.getZone(param.target.name).point + product.lastMission = { zoneName = param.target.name } + TaskExtensions.executeAwacsMission(gr, point, param.prod.altitude, param.prod.freq) + + end + end, {prod=product, target=zn}, timer.getTime()+1) + + env.info("ZoneCommand - "..product.name.." targeting "..zn.name) + end + + function ZoneCommand:isTankerMissionValid(product, target) + if target.side ~= product.side then return false end + if target.name == self.name then return false end + if not target.distToFront or target.distToFront ~= 4 then return false end + + return true + end + + function ZoneCommand:activateTankerMission(product) + + local tgtzones = {} + for _,v in pairs(ZoneCommand.getAllZones()) do + if self:isTankerMissionValid(product, v) then + table.insert(tgtzones, v) + end + end + + local choice1 = math.random(1,#tgtzones) + local zn = tgtzones[choice1] + table.remove(tgtzones, choice1) + + local og = Utils.getOriginalGroup(product.name) + if og then + teleportToPos(product.name, {x=og.x, z=og.y}) + env.info("ZoneCommand - activateTankerMission teleporting to OG pos") + else + mist.respawnGroup(product.name, true) + env.info("ZoneCommand - activateTankerMission fallback to respawnGroup") + end + + if ZoneCommand.groupMonitor then + ZoneCommand.groupMonitor:registerGroup(product, zn, self) + end + + timer.scheduleFunction(function(param) + local gr = Group.getByName(param.prod.name) + if gr then + local un = gr:getUnit(1) + if un then + local callsign = un:getCallsign() + RadioFrequencyTracker.registerRadio(param.prod.name, '[Tanker('..param.prod.variant..')] '..callsign, param.prod.freq..' AM | TCN '..param.prod.tacan..'X') + end + + local point = trigger.misc.getZone(param.target.name).point + product.lastMission = { zoneName = param.target.name } + TaskExtensions.executeTankerMission(gr, point, param.prod.altitude, param.prod.freq, param.prod.tacan) + end + end, {prod=product, target=zn}, timer.getTime()+1) + + env.info("ZoneCommand - "..product.name.." targeting "..zn.name) + end + + function ZoneCommand:isPatrolMissionValid(product, target) + --if target.side ~= product.side then return false end + if target.name == self.name then return false end + if not target.distToFront or target.distToFront > 1 then return false end + if target.side ~= product.side and target.side ~= 0 then return false end + local dist = mist.utils.get2DDist(self.zone.point, target.zone.point) + if dist > 150000 then return false end + + return true + end + + function ZoneCommand:activatePatrolMission(product) + local tgtzones = {} + for _,v in pairs(ZoneCommand.getAllZones()) do + if self:isPatrolMissionValid(product, v) then + table.insert(tgtzones, v) + end + end + + local choice1 = math.random(1,#tgtzones) + local zn1 = tgtzones[choice1] + + if BattlefieldManager and BattlefieldManager.priorityZones[self.side] then + local prioZone = BattlefieldManager.priorityZones[self.side] + if self:isPatrolMissionValid(product, prioZone) then + zn1 = prioZone + end + end + + local og = Utils.getOriginalGroup(product.name) + if og then + teleportToPos(product.name, {x=og.x, z=og.y}) + env.info("ZoneCommand - activatePatrolMission teleporting to OG pos") + else + mist.respawnGroup(product.name, true) + env.info("ZoneCommand - activatePatrolMission fallback to respawnGroup") + end + + if ZoneCommand.groupMonitor then + ZoneCommand.groupMonitor:registerGroup(product, zn1, self) + end + + if zn1 then + product.lastMission = {zone1name = zn1.name} + timer.scheduleFunction(function(param) + local gr = Group.getByName(param.prod.name) + + local point = trigger.misc.getZone(param.zone1.name).point + + TaskExtensions.executePatrolMission(gr, point, param.prod.altitude, param.prod.range) + end, {prod=product, zone1 = zn1}, timer.getTime()+1) + + env.info("ZoneCommand - "..product.name.." targeting "..zn1.name) + end + end + + function ZoneCommand:isSeadMissionValid(product, target) + if target.side == 0 then return false end + if not target.distToFront or target.distToFront > 1 then return false end + + --if MissionTargetRegistry.isZoneTargeted(target.name) then return false end + + return target:hasEnemySAMRadar(product) + end + + function ZoneCommand:hasEnemySAMRadar(product) + if product.side == 1 then + return self:hasSAMRadarOnSide(2) + elseif product.side == 2 then + return self:hasSAMRadarOnSide(1) + end + end + + function ZoneCommand:hasSAMRadarOnSide(side) + for i,v in pairs(self.built) do + if v.type == ZoneCommand.productTypes.defense and v.side == side then + local gr = Group.getByName(v.name) + if gr then + for _,unit in ipairs(gr:getUnits()) do + if unit:hasAttribute('SAM SR') or unit:hasAttribute('SAM TR') then + return true + end + end + end + end + end + end + + function ZoneCommand:hasRunway() + local zones = self:getRunwayZones() + return #zones > 0 + end + + function ZoneCommand:getRunwayZones() + local runways = {} + for i=1,10,1 do + local name = self.name..'-runway-'..i + local zone = trigger.misc.getZone(name) + if zone then + runways[i] = {name = name, zone = zone} + else + break + end + end + + return runways + end + + function ZoneCommand:getRandomUnitWithAttributeOnSide(attributes, side) + local available = {} + for i,v in pairs(self.built) do + if v.type == ZoneCommand.productTypes.upgrade and v.side == side then + local st = StaticObject.getByName(v.name) + if st then + for _,a in ipairs(attributes) do + if a == "Buildings" and ZoneCommand.staticRegistry[v.name] then -- dcs does not consider all statics buildings so we compensate + table.insert(available, v) + end + end + end + elseif v.type == ZoneCommand.productTypes.defense and v.side == side then + local gr = Group.getByName(v.name) + if gr then + for _,unit in ipairs(gr:getUnits()) do + for _,a in ipairs(attributes) do + if unit:hasAttribute(a) then + table.insert(available, v) + end + end + end + end + end + end + + if #available > 0 then + return available[math.random(1, #available)] + end + end + + function ZoneCommand:hasUnitWithAttributeOnSide(attributes, side, amount) + local count = 0 + + for i,v in pairs(self.built) do + if v.type == ZoneCommand.productTypes.upgrade and v.side == side then + local st = StaticObject.getByName(v.name) + if st then + for _,a in ipairs(attributes) do + if a == "Buildings" and ZoneCommand.staticRegistry[v.name] then -- dcs does not consider all statics buildings so we compensate + if amount==nil then + return true + else + count = count + 1 + if count >= amount then return true end + end + end + end + end + elseif v.type == ZoneCommand.productTypes.defense and v.side == side then + local gr = Group.getByName(v.name) + if gr then + for _,unit in ipairs(gr:getUnits()) do + for _,a in ipairs(attributes) do + if unit:hasAttribute(a) then + if amount==nil then + return true + else + count = count + 1 + if count >= amount then return true end + end + end + end + end + end + end + end + end + + function ZoneCommand:getUnitCountWithAttributeOnSide(attributes, side) + local count = 0 + + for i,v in pairs(self.built) do + if v.type == ZoneCommand.productTypes.upgrade and v.side == side then + local st = StaticObject.getByName(v.name) + if st then + for _,a in ipairs(attributes) do + if a == "Buildings" and ZoneCommand.staticRegistry[v.name] then + count = count + 1 + break + end + end + end + elseif v.type == ZoneCommand.productTypes.defense and v.side == side then + local gr = Group.getByName(v.name) + if gr then + for _,unit in ipairs(gr:getUnits()) do + for _,a in ipairs(attributes) do + if unit:hasAttribute(a) then + count = count + 1 + break + end + end + end + end + end + end + + return count + end + + function ZoneCommand:activateSeadMission(product) + local tgtzones = {} + for _,v in pairs(ZoneCommand.getAllZones()) do + if self:isSeadMissionValid(product, v) then + table.insert(tgtzones, v) + end + end + + local choice = math.random(1,#tgtzones) + local target = tgtzones[choice] + + if BattlefieldManager and BattlefieldManager.priorityZones[self.side] then + local prioZone = BattlefieldManager.priorityZones[self.side] + if self:isSeadMissionValid(product, prioZone) then + target = prioZone + end + end + + local og = Utils.getOriginalGroup(product.name) + if og then + teleportToPos(product.name, {x=og.x, z=og.y}) + env.info("ZoneCommand - activateSeadMission teleporting to OG pos") + else + mist.respawnGroup(product.name, true) + env.info("ZoneCommand - activateSeadMission fallback to respawnGroup") + end + + if ZoneCommand.groupMonitor then + ZoneCommand.groupMonitor:registerGroup(product, target, self) + end + + if target then + product.lastMission = {zoneName = target.name} + timer.scheduleFunction(function(param) + local gr = Group.getByName(param.prod.name) + TaskExtensions.executeSeadMission(gr, param.targets, param.prod.expend, param.prod.altitude) + end, {prod=product, targets=target.built}, timer.getTime()+1) + + env.info("ZoneCommand - "..product.name.." targeting "..target.name) + end + end + + function ZoneCommand:isStrikeMissionValid(product, target) + if target.side == 0 then return false end + if target.side == product.side then return false end + if not target.distToFront or target.distToFront > 0 then return false end + + if target:hasEnemySAMRadar(product) then return false end + + --if MissionTargetRegistry.isZoneTargeted(target.name) then return false end + + for i,v in pairs(target.built) do + if v.type == ZoneCommand.productTypes.upgrade and v.side ~= product.side then + return true + end + end + end + + function ZoneCommand:activateStrikeMission(product) + local tgtzones = {} + for _,v in pairs(ZoneCommand.getAllZones()) do + if self:isStrikeMissionValid(product, v) then + table.insert(tgtzones, v) + end + end + + local choice = math.random(1,#tgtzones) + local target = tgtzones[choice] + + if BattlefieldManager and BattlefieldManager.priorityZones[self.side] then + local prioZone = BattlefieldManager.priorityZones[self.side] + if self:isStrikeMissionValid(product, prioZone) then + target = prioZone + end + end + + local og = Utils.getOriginalGroup(product.name) + if og then + teleportToPos(product.name, {x=og.x, z=og.y}) + env.info("ZoneCommand - activateStrikeMission teleporting to OG pos") + else + mist.respawnGroup(product.name, true) + env.info("ZoneCommand - activateStrikeMission fallback to respawnGroup") + end + + if ZoneCommand.groupMonitor then + ZoneCommand.groupMonitor:registerGroup(product, target, self) + end + + if target then + product.lastMission = {zoneName = target.name} + timer.scheduleFunction(function(param) + local gr = Group.getByName(param.prod.name) + TaskExtensions.executeStrikeMission(gr, param.targets, param.prod.expend, param.prod.altitude) + end, {prod=product, targets=target.built}, timer.getTime()+1) + + env.info("ZoneCommand - "..product.name.." targeting "..target.name) + end + end + + function ZoneCommand:isCasMissionValid(product, target) + if target.side == product.side then return false end + if not target.distToFront or target.distToFront > 0 then return false end + + if target:hasEnemySAMRadar(product) then return false end + + --if MissionTargetRegistry.isZoneTargeted(target.name) then return false end + + for i,v in pairs(target.built) do + if v.type == ZoneCommand.productTypes.defense and v.side ~= product.side then + return true + end + end + end + + function ZoneCommand:activateCasMission(product, ishelo) + local viablezones = {} + if ishelo then + viablezones = self.neighbours + else + viablezones = ZoneCommand.getAllZones() + end + + local tgtzones = {} + for _,v in pairs(viablezones) do + if self:isCasMissionValid(product, v) then + table.insert(tgtzones, v) + end + end + + local choice = math.random(1,#tgtzones) + local target = tgtzones[choice] + + if BattlefieldManager and BattlefieldManager.priorityZones[self.side] then + local prioZone = BattlefieldManager.priorityZones[self.side] + if viablezones[prioZone.name] and self:isCasMissionValid(product, prioZone) then + target = prioZone + end + end + + local og = Utils.getOriginalGroup(product.name) + if og then + teleportToPos(product.name, {x=og.x, z=og.y}) + env.info("ZoneCommand - activateCasMission teleporting to OG pos") + else + mist.respawnGroup(product.name, true) + env.info("ZoneCommand - activateCasMission fallback to respawnGroup") + end + + if ZoneCommand.groupMonitor then + ZoneCommand.groupMonitor:registerGroup(product, target, self) + end + + if target then + product.lastMission = {zoneName = target.name} + timer.scheduleFunction(function(param) + local gr = Group.getByName(param.prod.name) + if param.helo then + TaskExtensions.executeHeloCasMission(gr, param.targets, param.prod.expend, param.prod.altitude) + else + TaskExtensions.executeCasMission(gr, param.targets, param.prod.expend, param.prod.altitude) + end + end, {prod=product, targets=target.built, helo = ishelo}, timer.getTime()+1) + + env.info("ZoneCommand - "..product.name.." targeting "..target.name) + end + end + + function ZoneCommand:defineUpgrades(upgrades) + self.upgrades = upgrades + + for side,sd in ipairs(self.upgrades) do + for _,v in ipairs(sd) do + v.side = side + + local cat = TemplateDB.getData(v.template) + if cat.dataCategory == TemplateDB.type.static then + ZoneCommand.staticRegistry[v.name] = true + end + + for _,v2 in ipairs(v.products) do + v2.side = side + + if v2.type == "mission" then + local gr = Group.getByName(v2.name) + + if not gr then + if v2.missionType ~= ZoneCommand.missionTypes.supply_transfer then + env.info("ZoneCommand - ERROR declared group does not exist in mission: ".. v2.name) + end + else + gr:destroy() + end + end + end + end + end + end + + function ZoneCommand:getProductByName(name) + for i,v in ipairs(self.upgrades) do + for i2,v2 in ipairs(v) do + if v2.name == name then + return v2 + else + for i3,v3 in ipairs(v2.products) do + if v3.name == name then + return v3 + end + end + end + end + end + + return nil + end + + function ZoneCommand:cleanup() + local zn = trigger.misc.getZone(self.name) + local pos = { + x = zn.point.x, + y = land.getHeight({x = zn.point.x, y = zn.point.z}), + z= zn.point.z + } + local radius = zn.radius*2 + world.removeJunk({id = world.VolumeType.SPHERE,params = {point = pos, radius = radius}}) + end +end + + + + +-----------------[[ END OF ZoneCommand.lua ]]----------------- + + + +-----------------[[ BattlefieldManager.lua ]]----------------- + +BattlefieldManager = {} +do + BattlefieldManager.closeOverride = 27780 -- 15nm + BattlefieldManager.farOverride = Config.maxDistFromFront -- default 100nm + BattlefieldManager.boostScale = {[0] = 1.0, [1]=1.0, [2]=1.0} + BattlefieldManager.noRedZones = false + BattlefieldManager.noBlueZones = false + + BattlefieldManager.priorityZones = { + [1] = nil, + [2] = nil + } + + BattlefieldManager.overridePriorityZones = { + [1] = nil, + [2] = nil + } + + function BattlefieldManager:new() + local obj = {} + + setmetatable(obj, self) + self.__index = self + obj:start() + return obj + end + + function BattlefieldManager:start() + timer.scheduleFunction(function(param, time) + local self = param.context + + local zones = ZoneCommand.getAllZones() + local torank = {} + + --reset ranks and define frontline + for name,zone in pairs(zones) do + zone.distToFront = nil + zone.closestEnemyDist = nil + + if zone.neighbours then + for nName, nZone in pairs(zone.neighbours) do + if zone.side ~= nZone.side then + zone.distToFront = 0 + end + end + end + + --set dist to closest enemy + for name2,zone2 in pairs(zones) do + if zone.side ~= zone2.side then + local dist = mist.utils.get2DDist(zone.zone.point, zone2.zone.point) + if not zone.closestEnemyDist or dist < zone.closestEnemyDist then + zone.closestEnemyDist = dist + end + end + end + end + + for name,zone in pairs(zones) do + if zone.distToFront == 0 then + for nName, nZone in pairs(zone.neighbours) do + if nZone.distToFrount == nil then + table.insert(torank, nZone) + end + end + end + end + + -- build ranks of every other zone + while #torank > 0 do + local nexttorank = {} + for _,zone in ipairs(torank) do + if not zone.distToFront then + local minrank = 999 + for nName,nZone in pairs(zone.neighbours) do + if nZone.distToFront then + if nZone.distToFront BattlefieldManager.farOverride and zone.distToFront > 3 then + zone.mode = ZoneCommand.modes.export + else + if zone.mode ~= ZoneCommand.modes.normal then + zone:fullBuild(1.0) + end + zone.mode = ZoneCommand.modes.normal + end + else + if not zone.distToFront or zone.distToFront == 0 or (zone.closestEnemyDist and zone.closestEnemyDist < BattlefieldManager.closeOverride) then + if zone.mode ~= ZoneCommand.modes.normal then + zone:fullBuild(1.0) + end + zone.mode = ZoneCommand.modes.normal + elseif zone.distToFront == 1 then + zone.mode = ZoneCommand.modes.supply + elseif zone.distToFront > 1 then + zone.mode = ZoneCommand.modes.export + end + end + + zone.boostScale = self.boostScale[zone.side] + end + + return time+60 + end, {context = self}, timer.getTime()+1) + + timer.scheduleFunction(function(param, time) + local self = param.context + + local zones = ZoneCommand.getAllZones() + + local noRed = true + local noBlue = true + for name, zone in pairs(zones) do + if zone.side == 1 then + noRed = false + elseif zone.side == 2 then + noBlue = false + end + + if not noRed and not noBlue then + break + end + end + + if noRed then + BattlefieldManager.noRedZones = true + end + + if noBlue then + BattlefieldManager.noBlueZones = true + end + + return time+10 + end, {context = self}, timer.getTime()+1) + + timer.scheduleFunction(function(param, time) + local self = param.context + + local zones = ZoneCommand.getAllZones() + + local frontLineRed = {} + local frontLineBlue = {} + for name, zone in pairs(zones) do + if zone.distToFront == 0 then + if zone.side == 1 then + table.insert(frontLineRed, zone) + elseif zone.side == 2 then + table.insert(frontLineBlue, zone) + else + table.insert(frontLineRed, zone) + table.insert(frontLineBlue, zone) + end + end + end + + if BattlefieldManager.overridePriorityZones[1] and BattlefieldManager.overridePriorityZones[1].ticks > 0 then + BattlefieldManager.priorityZones[1] = BattlefieldManager.overridePriorityZones[1].zone + BattlefieldManager.overridePriorityZones[1].ticks = BattlefieldManager.overridePriorityZones[1].ticks - 1 + else + local redChangeChance = 1 + if BattlefieldManager.priorityZones[1] and BattlefieldManager.priorityZones[1].side ~= 1 then + redChangeChance = 0.1 + end + + if #frontLineBlue > 0 then + if math.random() <= redChangeChance then + BattlefieldManager.priorityZones[1] = frontLineBlue[math.random(1,#frontLineBlue)] + end + else + BattlefieldManager.priorityZones[1] = nil + end + end + + if BattlefieldManager.overridePriorityZones[2] and BattlefieldManager.overridePriorityZones[2].ticks > 0 then + BattlefieldManager.priorityZones[2] = BattlefieldManager.overridePriorityZones[2].zone + BattlefieldManager.overridePriorityZones[2].ticks = BattlefieldManager.overridePriorityZones[2].ticks - 1 + else + local blueChangeChance = 1 + if BattlefieldManager.priorityZones[2] and BattlefieldManager.priorityZones[2].side ~= 2 then + blueChangeChance = 0.1 + end + + if #frontLineRed > 0 then + if math.random() <= blueChangeChance then + BattlefieldManager.priorityZones[2] = frontLineRed[math.random(1,#frontLineRed)] + end + else + BattlefieldManager.priorityZones[2] = nil + end + end + + if BattlefieldManager.priorityZones[1] then + env.info('BattlefieldManager - red priority: '..BattlefieldManager.priorityZones[1].name) + else + env.info('BattlefieldManager - red no priority') + end + + if BattlefieldManager.priorityZones[2] then + env.info('BattlefieldManager - blue priority: '..BattlefieldManager.priorityZones[2].name) + else + env.info('BattlefieldManager - blue no priority') + end + + if BattlefieldManager.overridePriorityZones[1] and BattlefieldManager.overridePriorityZones[1].ticks == 0 then + BattlefieldManager.overridePriorityZones[1] = nil + end + + if BattlefieldManager.overridePriorityZones[2] and BattlefieldManager.overridePriorityZones[2].ticks == 0 then + BattlefieldManager.overridePriorityZones[2] = nil + end + + return time+(60*30) + end, {context = self}, timer.getTime()+10) + + timer.scheduleFunction(function(param, time) + local x = math.random(-50,50) -- the lower limit benefits blue, higher limit benefits red, adjust to increase limit of random boost variance, default (-50,50) + local boostIntensity = Config.randomBoost -- adjusts the intensity of the random boost variance, default value = 0.0004 + local factor = (x*x*x*boostIntensity)/100 -- the farther x is the higher the factor, negative beneifts blue, pozitive benefits red + param.context.boostScale[1] = 1.0+factor + param.context.boostScale[2] = 1.0-factor + + local red = 0 + local blue = 0 + for i,v in pairs(ZoneCommand.getAllZones()) do + if v.side == 1 then + red = red + 1 + elseif v.side == 2 then + blue = blue + 1 + end + + --v:cleanup() + end + + -- push factor towards coalition with less zones (up to 0.5) + local multiplier = Config.lossCompensation -- adjust this to boost losing side production(higher means losing side gains more advantage) (default 1.25) + local total = red + blue + local redp = (0.5-(red/total))*multiplier + local bluep = (0.5-(blue/total))*multiplier + + -- cap factor to avoid increasing difficulty until the end + redp = math.min(redp, 0.15) + bluep = math.max(bluep, -0.15) + + param.context.boostScale[1] = param.context.boostScale[1] + redp + param.context.boostScale[2] = param.context.boostScale[2] + bluep + + --limit to numbers above 0 + param.context.boostScale[1] = math.max(0.01,param.context.boostScale[1]) + param.context.boostScale[2] = math.max(0.01,param.context.boostScale[2]) + + env.info('BattlefieldManager - power red = '..param.context.boostScale[1]) + env.info('BattlefieldManager - power blue = '..param.context.boostScale[2]) + + return time+(60*30) + end, {context = self}, timer.getTime()+1) + end + + function BattlefieldManager.overridePriority(side, zone, ticks) + BattlefieldManager.overridePriorityZones[side] = { zone = zone, ticks = ticks } + BattlefieldManager.priorityZones[side] = zone + + env.info('BattlefieldManager.overridePriority - '..side..' focusing on '..zone.name) + end +end + +-----------------[[ END OF BattlefieldManager.lua ]]----------------- + + + +-----------------[[ Preset.lua ]]----------------- + +Preset = {} +do + function Preset:new(obj) + setmetatable(obj, self) + self.__index = self + return obj + end + + function Preset:extend(new) + return Preset:new(Utils.merge(self, new)) + end +end + +-----------------[[ END OF Preset.lua ]]----------------- + + + +-----------------[[ PlayerTracker.lua ]]----------------- + +PlayerTracker = {} +do + PlayerTracker.savefile = 'player_stats.json' + PlayerTracker.statTypes = { + xp = 'XP', + cmd = "CMD" + } + + PlayerTracker.cmdShopTypes = { + smoke = 'smoke', + prio = 'prio', + jtac = 'jtac', + bribe1 = 'bribe1', + bribe2 = 'bribe2', + } + + PlayerTracker.cmdShopPrices = { + [PlayerTracker.cmdShopTypes.smoke] = 1, + [PlayerTracker.cmdShopTypes.prio] = 2, + [PlayerTracker.cmdShopTypes.jtac] = 3, + [PlayerTracker.cmdShopTypes.bribe1] = 1, + [PlayerTracker.cmdShopTypes.bribe2] = 3, + } + + function PlayerTracker:new(markerCommands) + local obj = {} + obj.markerCommands = markerCommands + obj.stats = {} + obj.tempStats = {} + obj.groupMenus = {} + obj.groupShopMenus = {} + obj.groupTgtMenus = {} + obj.playerAircraft = {} + obj.playerWeaponStock = {} + + if lfs then + local dir = lfs.writedir()..'Missions/Saves/' + lfs.mkdir(dir) + PlayerTracker.savefile = dir..PlayerTracker.savefile + env.info('Pretense - Player stats file path: '..PlayerTracker.savefile) + end + + local save = Utils.loadTable(PlayerTracker.savefile) + if save then + obj.stats = save.stats or {} + obj.playerWeaponStock = save.playerWeaponStock or {} + end + + setmetatable(obj, self) + self.__index = self + + obj:init() + + return obj + end + + function PlayerTracker:init() + local ev = {} + ev.context = self + function ev:onEvent(event) + if not event.initiator then return end + if not event.initiator.getPlayerName then return end + if not event.initiator.getCoalition then return end + + local player = event.initiator:getPlayerName() + if not player then return end + + if event.id==world.event.S_EVENT_PLAYER_ENTER_UNIT then + if event.initiator and event.initiator:getCategory() == Object.Category.UNIT and + (event.initiator:getDesc().category == Unit.Category.AIRPLANE or event.initiator:getDesc().category == Unit.Category.HELICOPTER) then + + local pname = event.initiator:getPlayerName() + if pname then + local gr = event.initiator:getGroup() + if trigger.misc.getUserFlag(gr:getName())==1 then + trigger.action.outTextForGroup(gr:getID(), 'Can not spawn as '..gr:getName()..' in enemy/neutral zone',5) + event.initiator:destroy() + end + end + end + end + + if event.id == world.event.S_EVENT_BIRTH then + -- init stats for player if not exist + if not self.context.stats[player] then + self.context.stats[player] = {} + end + + -- reset temp track for player + self.context.tempStats[player] = nil + -- reset playeraircraft + self.context.playerAircraft[player] = nil + end + + if event.id == world.event.S_EVENT_KILL then + local target = event.target + + if not target then return end + if not target.getCoalition then return end + + if target:getCoalition() == event.initiator:getCoalition() then return end + + local xpkey = PlayerTracker.statTypes.xp + local award = PlayerTracker.getXP(target) + + local instantxp = math.floor(award*0.25) + local tempxp = award - instantxp + + self.context:addStat(player, instantxp, PlayerTracker.statTypes.xp) + local msg = '[XP] '..self.context.stats[player][xpkey]..' (+'..instantxp..')' + env.info("PlayerTracker.kill - "..player..' awarded '..tostring(instantxp)..' xp') + + self.context:addTempStat(player, tempxp, PlayerTracker.statTypes.xp) + msg = msg..'\n+'..tempxp..' XP (unclaimed)' + env.info("PlayerTracker.kill - "..player..' awarded '..tostring(tempxp)..' xp (unclaimed)') + + trigger.action.outTextForUnit(event.initiator:getID(), msg, 5) + end + + if event.id==world.event.S_EVENT_EJECTION then + self.context.stats[player] = self.context.stats[player] or {} + local ts = self.context.tempStats[player] + if ts then + local un = event.initiator + local key = PlayerTracker.statTypes.xp + local xp = self.context.tempStats[player][key] + if xp then + local isFree = event.initiator:getGroup():getName():find("(FREE)") + trigger.action.outTextForUnit(un:getID(), 'Ejection. 30\% XP claimed', 5) + self.context:addStat(player, math.floor(xp*0.3), PlayerTracker.statTypes.xp) + trigger.action.outTextForUnit(un:getID(), '[XP] '..self.context.stats[player][key]..' (+'..math.floor(xp*0.3)..')', 5) + end + + self.context.tempStats[player] = nil + end + end + + if event.id==world.event.S_EVENT_TAKEOFF then + local un = event.initiator + local zn = ZoneCommand.getZoneOfUnit(event.initiator:getName()) + env.info('PlayerTracker - '..player..' took off in '..tostring(un:getID())..' '..un:getName()) + if un and zn and zn.side == un:getCoalition() then + timer.scheduleFunction(function(param, time) + local un = param.unit + if not un or not un:isExist() then return end + local player = param.player + local inAir = Utils.isInAir(un) + env.info('PlayerTracker - '..player..' checking if in air: '..tostring(inAir)) + if inAir and param.context.playerAircraft[player] == nil then + if param.context.playerAircraft[player] == nil then + param.context.playerAircraft[player] = { unitID = un:getID() } + end + end + end, {player = player, unit = event.initiator, context = self.context}, timer.getTime()+10) + end + end + + if event.id==world.event.S_EVENT_LAND then + local un = event.initiator + local zn = ZoneCommand.getZoneOfUnit(event.initiator:getName()) + local aircraft = self.context.playerAircraft[player] + env.info('PlayerTracker - '..player..' landed in '..tostring(un:getID())..' '..un:getName()) + if aircraft and un and zn and zn.side == un:getCoalition() then + trigger.action.outTextForUnit(event.initiator:getID(), "Wait 10 seconds to validate landing...", 10) + timer.scheduleFunction(function(param, time) + local un = param.unit + if not un or not un:isExist() then return end + + local player = param.player + local isLanded = Utils.isLanded(un, true) + local zn = ZoneCommand.getZoneOfUnit(un:getName()) + + env.info('PlayerTracker - '..player..' checking if landed: '..tostring(isLanded)) + + if isLanded then + if self.context.tempStats[player] then + if zn and zn.side == un:getCoalition() then + self.context.stats[player] = self.context.stats[player] or {} + + trigger.action.outTextForUnit(un:getID(), 'Rewards claimed', 5) + for _,key in pairs(PlayerTracker.statTypes) do + local value = self.context.tempStats[player][key] + env.info("PlayerTracker.landing - "..player..' redeeming '..tostring(value)..' '..key) + if value then + self.context:commitTempStat(player, key) + trigger.action.outTextForUnit(un:getID(), key..' +'..value..'', 5) + end + end + end + end + + local aircraft = param.context.playerAircraft[player] + if aircraft and aircraft.unitID == un:getID() then + param.context.playerAircraft[player] = nil + end + end + end, {player = player, unit = event.initiator, context = self.context}, timer.getTime()+10) + end + + end + end + + world.addEventHandler(ev) + self:periodicSave() + self:menuSetup() + end + + function PlayerTracker:addTempStat(player, amount, stattype) + self.tempStats[player] = self.tempStats[player] or {} + self.tempStats[player][stattype] = self.tempStats[player][stattype] or 0 + self.tempStats[player][stattype] = self.tempStats[player][stattype] + amount + end + + function PlayerTracker:addStat(player, amount, stattype) + self.stats[player] = self.stats[player] or {} + self.stats[player][stattype] = self.stats[player][stattype] or 0 + + if stattype == PlayerTracker.statTypes.xp then + local cur = self:getRank(self.stats[player][stattype]) + if cur then + local nxt = self:getRank(self.stats[player][stattype] + amount) + if nxt and cur.rank < nxt.rank then + trigger.action.outText(player..' has leveled up to rank: '..nxt.name, 10) + if nxt.cmdAward and nxt.cmdAward > 0 then + self:addStat(player, nxt.cmdAward, PlayerTracker.statTypes.cmd) + trigger.action.outText(player.." awarded "..nxt.cmdAward.." CMD tokens", 10) + env.info("PlayerTracker.addStat - Awarded "..player.." "..nxt.cmdAward.." CMD tokens for rank up to "..nxt.name) + end + end + end + end + + self.stats[player][stattype] = self.stats[player][stattype] + amount + end + + function PlayerTracker:commitTempStat(player, statkey) + local value = self.tempStats[player][statkey] + if value then + self:addStat(player, value, statkey) + + self.tempStats[player][statkey] = nil + end + end + + function PlayerTracker:addRankRewards(player, unit, isTemp) + local rank = self:getPlayerRank(player) + if not rank then return end + + local cmdChance = rank.cmdChance + if cmdChance > 0 then + local die = math.random() + if die <= cmdChance then + if isTemp then + self:addTempStat(player, 1, PlayerTracker.statTypes.cmd) + else + self:addStat(player, 1, PlayerTracker.statTypes.cmd) + end + + local msg = "" + if isTemp then + msg = '+1 CMD (unclaimed)' + else + msg = '[CMD] '..self.stats[player][PlayerTracker.statTypes.cmd]..' (+1)' + end + + trigger.action.outTextForUnit(unit:getID(), msg, 5) + env.info("PlayerTracker.addRankRewards - Awarded "..player.." a CMD token with chance "..cmdChance.." die roll "..die) + end + end + end + + function PlayerTracker.getXP(unit) + local xp = 30 + + if unit:hasAttribute('Planes') then xp = xp + 20 end + if unit:hasAttribute('Helicopters') then xp = xp + 20 end + if unit:hasAttribute('Infantry') then xp = xp + 10 end + if unit:hasAttribute('SAM SR') then xp = xp + 15 end + if unit:hasAttribute('SAM TR') then xp = xp + 15 end + if unit:hasAttribute('IR Guided SAM') then xp = xp + 10 end + if unit:hasAttribute('Ships') then xp = xp + 20 end + if unit:hasAttribute('Buildings') then xp = xp + 30 end + if unit:hasAttribute('Tanks') then xp = xp + 10 end + + return xp + end + + function PlayerTracker:menuSetup() + + MenuRegistry:register(1, function(event, context) + if event.id == world.event.S_EVENT_BIRTH and event.initiator and event.initiator.getPlayerName then + local player = event.initiator:getPlayerName() + if player then + local groupid = event.initiator:getGroup():getID() + local groupname = event.initiator:getGroup():getName() + + if context.groupMenus[groupid] then + missionCommands.removeItemForGroup(groupid, context.groupMenus[groupid]) + context.groupMenus[groupid] = nil + end + + if not context.groupMenus[groupid] then + + local menu = missionCommands.addSubMenuForGroup(groupid, 'Information') + missionCommands.addCommandForGroup(groupid, 'Player', menu, Utils.log(context.showGroupStats), context, groupname) + missionCommands.addCommandForGroup(groupid, 'Frequencies', menu, Utils.log(context.showFrequencies), context, groupname) + + context.groupMenus[groupid] = menu + end + end + elseif (event.id == world.event.S_EVENT_PLAYER_LEAVE_UNIT or event.id == world.event.S_EVENT_DEAD) and event.initiator and event.initiator.getPlayerName then + local player = event.initiator:getPlayerName() + if player then + local groupid = event.initiator:getGroup():getID() + + if context.groupMenus[groupid] then + missionCommands.removeItemForGroup(groupid, context.groupMenus[groupid]) + context.groupMenus[groupid] = nil + end + end + end + end, self) + + MenuRegistry:register(4, function(event, context) + if event.id == world.event.S_EVENT_BIRTH and event.initiator and event.initiator.getPlayerName then + local player = event.initiator:getPlayerName() + if player then + local rank = context:getPlayerRank(player) + if not rank then return end + + if rank.cmdChance > 0 then + local groupid = event.initiator:getGroup():getID() + local groupname = event.initiator:getGroup():getName() + + if context.groupShopMenus[groupid] then + missionCommands.removeItemForGroup(groupid, context.groupShopMenus[groupid]) + context.groupShopMenus[groupid] = nil + end + + if context.groupTgtMenus[groupid] then + missionCommands.removeItemForGroup(groupid, context.groupTgtMenus[groupid]) + context.groupTgtMenus[groupid] = nil + end + + if not context.groupShopMenus[groupid] then + + local menu = missionCommands.addSubMenuForGroup(groupid, 'Command & Control') + missionCommands.addCommandForGroup(groupid, 'Deploy Smoke ['..PlayerTracker.cmdShopPrices[PlayerTracker.cmdShopTypes.smoke]..' CMD]', menu, Utils.log(context.buyCommand), context, groupname, PlayerTracker.cmdShopTypes.smoke) + missionCommands.addCommandForGroup(groupid, 'Hack enemy comms ['..PlayerTracker.cmdShopPrices[PlayerTracker.cmdShopTypes.bribe1]..' CMD]', menu, Utils.log(context.buyCommand), context, groupname, PlayerTracker.cmdShopTypes.bribe1) + missionCommands.addCommandForGroup(groupid, 'Prioritize zone ['..PlayerTracker.cmdShopPrices[PlayerTracker.cmdShopTypes.prio]..' CMD]', menu, Utils.log(context.buyCommand), context, groupname, PlayerTracker.cmdShopTypes.prio) + missionCommands.addCommandForGroup(groupid, 'Bribe enemy officer ['..PlayerTracker.cmdShopPrices[PlayerTracker.cmdShopTypes.bribe2]..' CMD]', menu, Utils.log(context.buyCommand), context, groupname, PlayerTracker.cmdShopTypes.bribe2) + + if CommandFunctions.jtac then + missionCommands.addCommandForGroup(groupid, 'Deploy JTAC ['..PlayerTracker.cmdShopPrices[PlayerTracker.cmdShopTypes.jtac]..' CMD]', menu, Utils.log(context.buyCommand), context, groupname, PlayerTracker.cmdShopTypes.jtac) + end + + context.groupShopMenus[groupid] = menu + end + end + end + elseif (event.id == world.event.S_EVENT_PLAYER_LEAVE_UNIT or event.id == world.event.S_EVENT_DEAD) and event.initiator and event.initiator.getPlayerName then + local player = event.initiator:getPlayerName() + if player then + local groupid = event.initiator:getGroup():getID() + + if context.groupShopMenus[groupid] then + missionCommands.removeItemForGroup(groupid, context.groupShopMenus[groupid]) + context.groupShopMenus[groupid] = nil + end + + if context.groupTgtMenus[groupid] then + missionCommands.removeItemForGroup(groupid, context.groupTgtMenus[groupid]) + context.groupTgtMenus[groupid] = nil + end + end + end + end, self) + + self.markerCommands:addCommand('stats',function(event, _, state) + local unit = nil + if event.initiator then + unit = event.initiator + elseif world.getPlayer() then + unit = world.getPlayer() + end + + if not unit then return false end + + state:showGroupStats(unit:getGroup():getName()) + return true + end, false, self) + + self.markerCommands:addCommand('freqs',function(event, _, state) + local unit = nil + if event.initiator then + unit = event.initiator + elseif world.getPlayer() then + unit = world.getPlayer() + end + + if not unit then return false end + + state:showFrequencies(unit:getGroup():getName()) + return true + end, false, self) + end + + function PlayerTracker:buyCommand(groupname, itemType) + local gr = Group.getByName(groupname) + if gr and gr:getSize()>0 then + local un = gr:getUnit(1) + if un then + local player = un:getPlayerName() + local cost = PlayerTracker.cmdShopPrices[itemType] + local cmdTokens = self.stats[player][PlayerTracker.statTypes.cmd] + + if cmdTokens and cost <= cmdTokens then + if self.groupTgtMenus[gr:getID()] then + missionCommands.removeItemForGroup(gr:getID(), self.groupTgtMenus[gr:getID()]) + self.groupTgtMenus[gr:getID()] = nil + end + + if itemType == PlayerTracker.cmdShopTypes.smoke then + + self.groupTgtMenus[gr:getID()] = MenuRegistry.showTargetZoneMenu(gr:getID(), "Smoke Marker target", function(params) + CommandFunctions.smokeTargets(params.zone, 5) + trigger.action.outTextForGroup(params.groupid, "Targets marked at "..params.zone.name.." with red smoke", 5) + end, 1, 1) + trigger.action.outTextForGroup(gr:getID(), "Select target from radio menu",10) + + elseif itemType == PlayerTracker.cmdShopTypes.jtac then + + self.groupTgtMenus[gr:getID()] = MenuRegistry.showTargetZoneMenu(gr:getID(), "JTAC target", function(params) + + CommandFunctions.spawnJtac(params.zone) + trigger.action.outTextForGroup(params.groupid, "Reaper orbiting "..params.zone.name,5) + + end, 1, 1) + trigger.action.outTextForGroup(gr:getID(), "Select target from radio menu",10) + + elseif itemType== PlayerTracker.cmdShopTypes.prio then + + self.groupTgtMenus[gr:getID()] = MenuRegistry.showTargetZoneMenu(gr:getID(), "Priority zone", function(params) + BattlefieldManager.overridePriority(2, params.zone, 2) + trigger.action.outTextForGroup(params.groupid, "Blue is concentrating efforts on "..params.zone.name.." for the next hour", 5) + end, nil, 1) + trigger.action.outTextForGroup(gr:getID(), "Select target from radio menu",10) + + elseif itemType== PlayerTracker.cmdShopTypes.bribe1 then + + timer.scheduleFunction(function(params, time) + local count = 0 + for i,v in pairs(ZoneCommand.getAllZones()) do + if v.side == 1 and v.distToFront <= 1 then + if math.random()<0.5 then + v:reveal() + count = count + 1 + end + end + end + if count > 0 then + trigger.action.outTextForGroup(params.groupid, "Intercepted enemy communications have revealed information on "..count.." enemy zones",20) + else + trigger.action.outTextForGroup(params.groupid, "No useful information has been intercepted",20) + end + end, {groupid=gr:getID()}, timer.getTime()+60) + + trigger.action.outTextForGroup(gr:getID(), "Attempting to intercept enemy comms...",60) + + elseif itemType == PlayerTracker.cmdShopTypes.bribe2 then + timer.scheduleFunction(function(params, time) + local count = 0 + for i,v in pairs(ZoneCommand.getAllZones()) do + if v.side == 1 then + if math.random()<0.5 then + v:reveal() + count = count + 1 + end + end + end + + if count > 0 then + trigger.action.outTextForGroup(params.groupid, "Bribed officer has shared intel on "..count.." enemy zones",20) + else + trigger.action.outTextForGroup(params.groupid, "Bribed officer has stopped responding to attempted communications.",20) + end + end, {groupid=gr:getID()}, timer.getTime()+(60*5)) + + trigger.action.outTextForGroup(gr:getID(), "Bribe has been transfered to enemy officer. Waiting for contact...",20) + end + + self.stats[player][PlayerTracker.statTypes.cmd] = self.stats[player][PlayerTracker.statTypes.cmd] - cost + else + trigger.action.outTextForUnit(un:getID(), "Insufficient CMD to buy selected item", 5) + end + end + end + end + + function PlayerTracker:showFrequencies(groupname) + local gr = Group.getByName(groupname) + if gr then + for i,v in pairs(gr:getUnits()) do + if v.getPlayerName and v:getPlayerName() then + local message = RadioFrequencyTracker.getRadioFrequencyMessage(gr:getCoalition()) + trigger.action.outTextForUnit(v:getID(), message, 20) + end + end + end + end + + function PlayerTracker:showGroupStats(groupname) + local gr = Group.getByName(groupname) + if gr then + for i,v in pairs(gr:getUnits()) do + if v.getPlayerName and v:getPlayerName() then + local player = v:getPlayerName() + local message = '['..player..']\n' + + local stats = self.stats[player] + if stats then + local xp = stats[PlayerTracker.statTypes.xp] + if xp then + local rank, nextRank = self:getRank(xp) + + message = message ..'\nXP: '..xp + + if rank then + message = message..'\nRank: '..rank.name + end + + if nextRank then + message = message..'\nXP needed for promotion: '..(nextRank.requiredXP-xp) + end + end + + local cmd = stats[PlayerTracker.statTypes.cmd] + if cmd then + message = message ..'\n\nCMD: '..cmd + end + end + + local tstats = self.tempStats[player] + if tstats then + message = message..'\n' + local tempxp = tstats[PlayerTracker.statTypes.xp] + if tempxp and tempxp > 0 then + message = message .. '\nUnclaimed XP: '..tempxp + end + end + + trigger.action.outTextForUnit(v:getID(), message, 10) + end + end + end + end + + function PlayerTracker:periodicSave() + timer.scheduleFunction(function(param, time) + local tosave = {} + tosave.stats = param.stats + tosave.playerWeaponStock = param.playerWeaponStock + + --temp mission stat tracking + tosave.zones = {} + tosave.zones.red = {} + tosave.zones.blue = {} + tosave.zones.neutral = {} + for i,v in pairs(ZoneCommand.getAllZones()) do + if v.side == 1 then + table.insert(tosave.zones.red,v.name) + elseif v.side == 2 then + table.insert(tosave.zones.blue,v.name) + elseif v.side == 0 then + table.insert(tosave.zones.neutral,v.name) + end + end + + tosave.players = {} + for i,v in ipairs(coalition.getPlayers(2)) do + if v and v:isExist() and v.getPlayerName then + table.insert(tosave.players, {name=v:getPlayerName(), unit=v:getDesc().typeName}) + end + end + + --end mission stat tracking + + Utils.saveTable(PlayerTracker.savefile, tosave) + env.info("PlayerTracker - state saved") + return time+60 + end, self, timer.getTime()+60) + end + + PlayerTracker.ranks = {} + PlayerTracker.ranks[1] = { rank=1, name='E-1 Airman basic', requiredXP = 0, cmdChance = 0, cmdAward=0} + PlayerTracker.ranks[2] = { rank=2, name='E-2 Airman', requiredXP = 2000, cmdChance = 0, cmdAward=0} + PlayerTracker.ranks[3] = { rank=3, name='E-3 Airman first class', requiredXP = 4500, cmdChance = 0, cmdAward=0} + PlayerTracker.ranks[4] = { rank=4, name='E-4 Senior airman', requiredXP = 7700, cmdChance = 0, cmdAward=0} + PlayerTracker.ranks[5] = { rank=5, name='E-5 Staff sergeant', requiredXP = 11800, cmdChance = 0, cmdAward=0} + PlayerTracker.ranks[6] = { rank=6, name='E-6 Technical sergeant', requiredXP = 17000, cmdChance = 0.01, cmdAward=1} + PlayerTracker.ranks[7] = { rank=7, name='E-7 Master sergeant', requiredXP = 23500, cmdChance = 0.02, cmdAward=1} + PlayerTracker.ranks[8] = { rank=8, name='E-8 Senior master sergeant', requiredXP = 31500, cmdChance = 0.03, cmdAward=1} + PlayerTracker.ranks[9] = { rank=9, name='E-9 Chief master sergeant', requiredXP = 42000, cmdChance = 0.05, cmdAward=1} + PlayerTracker.ranks[10] = { rank=10, name='O-1 Second lieutenant', requiredXP = 52800, cmdChance = 0.08, cmdAward=2} + PlayerTracker.ranks[11] = { rank=11, name='O-2 First lieutenant', requiredXP = 66500, cmdChance = 0.10, cmdAward=2} + PlayerTracker.ranks[12] = { rank=12, name='O-3 Captain', requiredXP = 82500, cmdChance = 0.14, cmdAward=2} + PlayerTracker.ranks[13] = { rank=13, name='O-4 Major', requiredXP = 101000, cmdChance = 0.17, cmdAward=2} + PlayerTracker.ranks[14] = { rank=14, name='O-5 Lieutenant colonel', requiredXP = 122200, cmdChance = 0.22, cmdAward=3} + PlayerTracker.ranks[15] = { rank=15, name='O-6 Colonel', requiredXP = 146300, cmdChance = 0.26, cmdAward=3} + PlayerTracker.ranks[16] = { rank=16, name='O-7 Brigadier general', requiredXP = 173500, cmdChance = 0.32, cmdAward=3} + PlayerTracker.ranks[17] = { rank=17, name='O-8 Major general', requiredXP = 204000, cmdChance = 0.37, cmdAward=4} + PlayerTracker.ranks[18] = { rank=18, name='O-9 Lieutenant general', requiredXP = 238000, cmdChance = 0.43, cmdAward=4} + PlayerTracker.ranks[19] = { rank=19, name='O-10 General', requiredXP = 275700, cmdChance = 0.50, cmdAward=5} + + function PlayerTracker:getPlayerRank(playername) + if self.stats[playername] then + local xp = self.stats[playername][PlayerTracker.statTypes.xp] + if xp then + return self:getRank(xp) + end + end + end + + function PlayerTracker:getRank(xp) + local rank = nil + local nextRank = nil + for _, rnk in ipairs(PlayerTracker.ranks) do + if rnk.requiredXP <= xp then + rank = rnk + else + nextRank = rnk + break + end + end + + return rank, nextRank + end +end + +-----------------[[ END OF PlayerTracker.lua ]]----------------- + + + +-----------------[[ MissionTargetRegistry.lua ]]----------------- + +MissionTargetRegistry = {} +do + MissionTargetRegistry.playerTargetZones = {} + + function MissionTargetRegistry.addZone(zone) + MissionTargetRegistry.playerTargetZones[zone] = true + end + + function MissionTargetRegistry.removeZone(zone) + MissionTargetRegistry.playerTargetZones[zone] = nil + end + + function MissionTargetRegistry.isZoneTargeted(zone) + return MissionTargetRegistry.playerTargetZones[zone] ~= nil + end + + MissionTargetRegistry.baiTargets = {} + + function MissionTargetRegistry.addBaiTarget(target) + MissionTargetRegistry.baiTargets[target.name] = target + env.info('MissionTargetRegistry - bai target added '..target.name) + end + + function MissionTargetRegistry.baiTargetsAvailable(coalition) + local targets = {} + for i,v in pairs(MissionTargetRegistry.baiTargets) do + if v.product.side == coalition then + local tgt = Group.getByName(v.name) + + if not tgt or not tgt:isExist() or tgt:getSize()==0 then + MissionTargetRegistry.removeBaiTarget(v) + elseif not v.state or v.state ~= 'enroute' then + MissionTargetRegistry.removeBaiTarget(v) + else + table.insert(targets, v) + end + end + end + + return #targets > 0 + end + + function MissionTargetRegistry.getRandomBaiTarget(coalition) + local targets = {} + for i,v in pairs(MissionTargetRegistry.baiTargets) do + if v.product.side == coalition then + local tgt = Group.getByName(v.name) + + if not tgt or not tgt:isExist() or tgt:getSize()==0 then + MissionTargetRegistry.removeBaiTarget(v) + elseif not v.state or v.state ~= 'enroute' then + MissionTargetRegistry.removeBaiTarget(v) + else + table.insert(targets, v) + end + end + end + + if #targets == 0 then return end + + local dice = math.random(1,#targets) + + return targets[dice] + end + + function MissionTargetRegistry.removeBaiTarget(target) + MissionTargetRegistry.baiTargets[target.name] = nil + env.info('MissionTargetRegistry - bai target removed '..target.name) + end + + MissionTargetRegistry.strikeTargetExpireTime = 30*60 + MissionTargetRegistry.strikeTargets = {} + + function MissionTargetRegistry.addStrikeTarget(target, zone, isDeep) + MissionTargetRegistry.strikeTargets[target.name] = {data=target, zone=zone, addedTime = timer.getAbsTime(), isDeep = isDeep} + env.info('MissionTargetRegistry - strike target added '..target.name) + end + + function MissionTargetRegistry.strikeTargetsAvailable(coalition, isDeep) + for i,v in pairs(MissionTargetRegistry.strikeTargets) do + if v.data.side == coalition then + local tgt = StaticObject.getByName(v.data.name) + if not tgt then tgt = Group.getByName(v.data.name) end + + if not tgt or not tgt:isExist() then + MissionTargetRegistry.removeStrikeTarget(v) + elseif timer.getAbsTime() - v.addedTime > MissionTargetRegistry.strikeTargetExpireTime then + MissionTargetRegistry.removeStrikeTarget(v) + elseif v.isDeep == isDeep then + return true + end + end + end + + return false + end + + function MissionTargetRegistry.getRandomStrikeTarget(coalition, isDeep) + local targets = {} + for i,v in pairs(MissionTargetRegistry.strikeTargets) do + if v.data.side == coalition then + local tgt = StaticObject.getByName(v.data.name) + if not tgt then tgt = Group.getByName(v.data.name) end + + if not tgt or not tgt:isExist() then + MissionTargetRegistry.removeStrikeTarget(v) + elseif timer.getAbsTime() - v.addedTime > MissionTargetRegistry.strikeTargetExpireTime then + MissionTargetRegistry.removeStrikeTarget(v) + elseif v.isDeep == isDeep then + table.insert(targets, v) + end + end + end + + if #targets == 0 then return end + + local dice = math.random(1,#targets) + + return targets[dice] + end + + function MissionTargetRegistry.removeStrikeTarget(target) + MissionTargetRegistry.strikeTargets[target.data.name] = nil + env.info('MissionTargetRegistry - strike target removed '..target.data.name) + end + + MissionTargetRegistry.extractableSquads = {} + + function MissionTargetRegistry.addSquad(squad) + MissionTargetRegistry.extractableSquads[squad.name] = squad + env.info('MissionTargetRegistry - squad added '..squad.name) + end + + function MissionTargetRegistry.squadsReadyToExtract() + for i,v in pairs(MissionTargetRegistry.extractableSquads) do + local gr = Group.getByName(i) + if gr and gr:isExist() and gr:getSize() > 0 then + return true + end + end + + return false + end + + function MissionTargetRegistry.getRandomSquad() + local targets = {} + for i,v in pairs(MissionTargetRegistry.extractableSquads) do + local gr = Group.getByName(i) + if gr and gr:isExist() and gr:getSize() > 0 then + table.insert(targets, v) + end + end + + if #targets == 0 then return end + + local dice = math.random(1,#targets) + + return targets[dice] + end + + function MissionTargetRegistry.removeSquad(squad) + MissionTargetRegistry.extractableSquads[squad.name] = nil + env.info('MissionTargetRegistry - squad removed '..squad.name) + end + + MissionTargetRegistry.extractablePilots = {} + + function MissionTargetRegistry.addPilot(pilot) + MissionTargetRegistry.extractablePilots[pilot.name] = pilot + env.info('MissionTargetRegistry - pilot added '..pilot.name) + end + + function MissionTargetRegistry.pilotsAvailableToExtract() + for i,v in pairs(MissionTargetRegistry.extractablePilots) do + if v.pilot:isExist() and v.pilot:getSize() > 0 and v.remainingTime > 30*60 then + return true + end + end + + return false + end + + function MissionTargetRegistry.getRandomPilot() + local targets = {} + for i,v in pairs(MissionTargetRegistry.extractablePilots) do + if v.pilot:isExist() and v.pilot:getSize() > 0 and v.remainingTime > 30*60 then + table.insert(targets, v) + end + end + + if #targets == 0 then return end + + local dice = math.random(1,#targets) + + return targets[dice] + end + + function MissionTargetRegistry.removePilot(pilot) + MissionTargetRegistry.extractablePilots[pilot.name] = nil + env.info('MissionTargetRegistry - pilot removed '..pilot.name) + end +end + +-----------------[[ END OF MissionTargetRegistry.lua ]]----------------- + + + +-----------------[[ RadioFrequencyTracker.lua ]]----------------- + +RadioFrequencyTracker = {} + +do + RadioFrequencyTracker.radios = {} + + function RadioFrequencyTracker.registerRadio(groupname, name, frequency) + RadioFrequencyTracker.radios[groupname] = {name = name, frequency = frequency} + end + + function RadioFrequencyTracker.getRadioFrequencyMessage(side) + local radios ={} + for i,v in pairs(RadioFrequencyTracker.radios) do + local gr = Group.getByName(i) + if gr and gr:getCoalition()==side then + table.insert(radios, v) + else + RadioFrequencyTracker.radios[i] = nil + end + end + + table.sort(radios, function (a,b) return a.name < b.name end) + + local msg = 'Active frequencies:' + for i,v in ipairs(radios) do + msg = msg..'\n '..v.name..' ['..v.frequency..']' + end + + return msg + end +end + + +-----------------[[ END OF RadioFrequencyTracker.lua ]]----------------- + + + +-----------------[[ PersistenceManager.lua ]]----------------- + +PersistenceManager = {} + +do + + function PersistenceManager:new(path, groupManager, squadTracker, csarTracker, playerLogistics) + local obj = { + path = path, + groupManager = groupManager, + squadTracker = squadTracker, + csarTracker = csarTracker, + playerLogistics = playerLogistics, + data = nil + } + + setmetatable(obj, self) + self.__index = self + return obj + end + + function PersistenceManager:restoreZones() + local save = self.data + for i,v in pairs(save.zones) do + local z = ZoneCommand.getZoneByName(i) + if z then + z:setSide(v.side) + z.resource = v.resource + z.revealTime = v.revealTime + z.extraBuildResources = v.extraBuildResources + z.mode = v.mode + z.distToFront = v.distToFront + z.closestEnemyDist = v.closestEnemyDist + for name,data in pairs(v.built) do + local pr = z:getProductByName(name) + z:instantBuild(pr) + + if pr.type == 'defense' and type(data) == "table" then + local unitTypes = {} + for _,typeName in ipairs(data) do + if not unitTypes[typeName] then + unitTypes[typeName] = 0 + end + unitTypes[typeName] = unitTypes[typeName] + 1 + end + + timer.scheduleFunction(function(param, time) + local gr = Group.getByName(param.name) + if gr then + local types = param.data + local toKill = {} + for _,un in ipairs(gr:getUnits()) do + local tp = un:getDesc().typeName + if types[tp] and types[tp] > 0 then + types[tp] = types[tp] - 1 + else + table.insert(toKill, un) + end + end + + for _,un in ipairs(toKill) do + un:destroy() + end + end + end, {data=unitTypes, name=name}, timer.getTime()+2) + end + end + + if v.currentBuild then + local pr = z:getProductByName(v.currentBuild.name) + z:queueBuild(pr, v.currentBuild.side, v.currentBuild.isRepair, v.currentBuild.progress) + end + + if v.currentMissionBuild then + local pr = z:getProductByName(v.currentMissionBuild.name) + z:queueBuild(pr, v.currentMissionBuild.side, false, v.currentMissionBuild.progress) + end + + z:refreshText() + end + end + + end + + function PersistenceManager:restoreAIMissions() + local save = self.data + local instantBuildStates = { + ['uninitialized'] = true, + ['takeoff'] = true, + } + + local reActivateStates = { + ['inair'] = true, + ['enroute'] = true, + ['atdestination'] = true, + ['siege'] = true + } + + for i,v in pairs(save.activeGroups) do + if v.homeName then + if instantBuildStates[v.state] then + local z = ZoneCommand.getZoneByName(v.homeName) + if z then + local pr = z:getProductByName(v.productName) + if z.side == pr.side then + z:instantBuild(pr) + end + end + elseif v.lastMission and reActivateStates[v.state] then + timer.scheduleFunction(function(param, time) + local z = ZoneCommand.getZoneByName(param.homeName) + if z then + z:reActivateMission(param) + end + end, v, timer.getTime()+3) + end + end + end + end + + function PersistenceManager:restoreBattlefield() + local save = self.data + if save.battlefieldManager then + if save.battlefieldManager.priorityZones then + if save.battlefieldManager.priorityZones['1'] then + BattlefieldManager.priorityZones[1] = ZoneCommand.getZoneByName(save.battlefieldManager.priorityZones[1]) + end + + + if save.battlefieldManager.priorityZones['2'] then + BattlefieldManager.priorityZones[2] = ZoneCommand.getZoneByName(save.battlefieldManager.priorityZones[2]) + end + end + + if save.battlefieldManager.overridePriorityZones then + if save.battlefieldManager.overridePriorityZones['1'] then + BattlefieldManager.overridePriorityZones[1] = { + zone = ZoneCommand.getZoneByName(save.battlefieldManager.overridePriorityZones['1'].zone), + ticks = save.battlefieldManager.overridePriorityZones['1'].ticks + } + end + + if save.battlefieldManager.overridePriorityZones['2'] then + BattlefieldManager.overridePriorityZones[2] = { + zone = ZoneCommand.getZoneByName(save.battlefieldManager.overridePriorityZones['2'].zone), + ticks = save.battlefieldManager.overridePriorityZones['2'].ticks + } + end + end + end + end + + function PersistenceManager:restoreCsar() + local save = self.data + if save.csarTracker then + for i,v in pairs(save.csarTracker) do + self.csarTracker:restorePilot(v) + end + end + end + + function PersistenceManager:restoreSquads() + local save = self.data + if save.squadTracker then + for i,v in pairs(save.squadTracker) do + local sdata = self.playerLogistics.registeredSquadGroups[v.type] + if sdata then + v.data = sdata + self.squadTracker:restoreInfantry(v) + end + end + end + end + + function PersistenceManager:canRestore() + return self.data ~= nil + end + + function PersistenceManager:load() + self.data = Utils.loadTable(self.path) + end + + function PersistenceManager:save() + local tosave = {} + + tosave.zones = {} + for i,v in pairs(ZoneCommand.getAllZones()) do + + tosave.zones[i] = { + name = v.name, + side = v.side, + resource = v.resource, + mode = v.mode, + distToFront = v.distToFront, + closestEnemyDist = v.closestEnemyDist, + extraBuildResources = v.extraBuildResources, + revealTime = v.revealTime, + built = {} + } + + for n,b in pairs(v.built) do + if b.type == 'defense' then + local typeList = {} + local gr = Group.getByName(b.name) + for _,unit in ipairs(gr:getUnits()) do + table.insert(typeList, unit:getDesc().typeName) + end + + tosave.zones[i].built[n] = typeList + else + tosave.zones[i].built[n] = true + end + + end + + if v.currentBuild then + tosave.zones[i].currentBuild = { + name = v.currentBuild.product.name, + progress = v.currentBuild.progress, + side = v.currentBuild.side, + isRepair = v.currentBuild.isRepair + } + end + + if v.currentMissionBuild then + tosave.zones[i].currentMissionBuild = { + name = v.currentMissionBuild.product.name, + progress = v.currentMissionBuild.progress, + side = v.currentMissionBuild.side + } + end + end + + tosave.activeGroups = {} + for i,v in pairs(self.groupManager.groups) do + tosave.activeGroups[i] = { + productName = v.product.name, + type = v.product.missionType + } + + local gr = Group.getByName(v.product.name) + if gr and gr:getSize()>0 then + local un = gr:getUnit(1) + if un then + tosave.activeGroups[i].position = un:getPoint() + tosave.activeGroups[i].lastMission = v.product.lastMission + tosave.activeGroups[i].heading = math.atan2(un:getPosition().x.z, un:getPosition().x.x) + end + end + + if v.target then + tosave.activeGroups[i].targetName = v.target.name + end + + if v.home then + tosave.activeGroups[i].homeName = v.home.name + end + + if v.state then + tosave.activeGroups[i].state = v.state + tosave.activeGroups[i].lastStateDuration = timer.getAbsTime() - v.lastStateTime + else + tosave.activeGroups[i].state = 'uninitialized' + tosave.activeGroups[i].lastStateDuration = 0 + end + end + + tosave.battlefieldManager = { + priorityZones = {}, + overridePriorityZones = {} + } + + if BattlefieldManager.priorityZones[1] then + tosave.battlefieldManager.priorityZones['1'] = BattlefieldManager.priorityZones[1].name + end + + if BattlefieldManager.priorityZones[2] then + tosave.battlefieldManager.priorityZones['2'] = BattlefieldManager.priorityZones[2].name + end + + if BattlefieldManager.overridePriorityZones[1] then + tosave.battlefieldManager.overridePriorityZones['1'] = { + zone = BattlefieldManager.overridePriorityZones[1].zone.name, + ticks = BattlefieldManager.overridePriorityZones[1].ticks + } + end + + if BattlefieldManager.overridePriorityZones[2] then + tosave.battlefieldManager.overridePriorityZones['2'] = { + zone = BattlefieldManager.overridePriorityZones[2].zone.name, + ticks = BattlefieldManager.overridePriorityZones[2].ticks + } + end + + + tosave.csarTracker = {} + + for i,v in pairs(self.csarTracker.activePilots) do + if v.pilot:isExist() and v.pilot:getSize()>0 and v.remainingTime>60 then + tosave.csarTracker[i] = { + name = v.name, + remainingTime = v.remainingTime, + pos = v.pilot:getUnit(1):getPoint() + } + end + end + + tosave.squadTracker = {} + + for i,v in pairs(self.squadTracker.activeInfantrySquads) do + tosave.squadTracker[i] = { + state = v.state, + remainingStateTime = v.remainingStateTime, + position = v.position, + name = v.name, + type = v.data.type + } + end + + Utils.saveTable(self.path, tosave) + end +end + +-----------------[[ END OF PersistenceManager.lua ]]----------------- + + + +-----------------[[ TemplateDB.lua ]]----------------- + +TemplateDB = {} + +do + TemplateDB.type = { + group = 'group', + static = 'static', + } + + TemplateDB.templates = {} + function TemplateDB.getData(objtype) + return TemplateDB.templates[objtype] + end + + TemplateDB.templates["pilot-replacement"] = { + units = { "Soldier M4 GRG" }, + skill = "Good", + dataCategory= TemplateDB.type.group + } + + TemplateDB.templates["capture-squad"] = { + units = { + "Soldier M4 GRG", + "Soldier M4 GRG", + "Soldier M249", + "Soldier M4 GRG" + }, + skill = "Good", + dataCategory= TemplateDB.type.group + } + + TemplateDB.templates["sabotage-squad"] = { + units = { + "Soldier M4 GRG", + "Soldier M249", + "Soldier M249", + "Soldier M4 GRG" + }, + skill = "Good", + dataCategory= TemplateDB.type.group + } + + TemplateDB.templates["ambush-squad"] = { + units = { + "Soldier RPG", + "Soldier RPG", + "Soldier M249", + "Soldier M4 GRG", + "Soldier M4 GRG" + }, + skill = "Good", + invisible = true, + dataCategory= TemplateDB.type.group + } + + TemplateDB.templates["manpads-squad"] = { + units = { + "Soldier M4 GRG", + "Soldier M249", + "Soldier stinger", + "Soldier stinger", + "Soldier M4 GRG" + }, + skill = "Good", + dataCategory= TemplateDB.type.group + } + + TemplateDB.templates["engineer-squad"] = { + units = { + "Soldier M4 GRG", + "Soldier M4 GRG" + }, + skill = "Good", + dataCategory= TemplateDB.type.group + } + + TemplateDB.templates["spy-squad"] = { + units = { + "Infantry AK" + }, + skill = "Good", + invisible = true, + dataCategory= TemplateDB.type.group + } + + TemplateDB.templates["rapier-squad"] = { + units = { + "rapier_fsa_blindfire_radar", + "rapier_fsa_optical_tracker_unit", + "rapier_fsa_launcher", + "rapier_fsa_launcher", + "Soldier M4 GRG", + "Soldier M4 GRG" + }, + skill = "Excellent", + dataCategory= TemplateDB.type.group + } + + TemplateDB.templates["tent"] = { type="FARP Tent", category="Fortifications", shape="PalatkaB", dataCategory=TemplateDB.type.static } + + TemplateDB.templates["barracks"] = { type="house1arm", category="Fortifications", shape=nil, dataCategory=TemplateDB.type.static } + + TemplateDB.templates["outpost"] = { type="outpost", category="Fortifications", shape=nil, dataCategory=TemplateDB.type.static } + + TemplateDB.templates["ammo-cache"] = { type="FARP Ammo Dump Coating", category="Fortifications", shape="SetkaKP", dataCategory=TemplateDB.type.static } + + TemplateDB.templates["ammo-depot"] = { type=".Ammunition depot", category="Warehouses", shape="SkladC", dataCategory=TemplateDB.type.static } + + TemplateDB.templates["fuel-cache"] = { type="FARP Fuel Depot", category="Fortifications", shape="GSM Rus", dataCategory=TemplateDB.type.static } + + TemplateDB.templates["fuel-tank-small"] = { type="Fuel tank", category="Fortifications", shape="toplivo-bak", dataCategory=TemplateDB.type.static } + + TemplateDB.templates["fuel-tank-big"] = { type="Tank", category="Warehouses", shape="bak", dataCategory=TemplateDB.type.static } + + TemplateDB.templates["chem-tank"] = { type="Chemical tank A", category="Fortifications", shape="him_bak_a", dataCategory=TemplateDB.type.static } + + TemplateDB.templates["factory-1"] = { type="Tech combine", category="Fortifications", shape="kombinat", dataCategory=TemplateDB.type.static } + + TemplateDB.templates["factory-2"] = { type="Workshop A", category="Fortifications", shape="tec_a", dataCategory=TemplateDB.type.static } + + TemplateDB.templates["oil-pump"] = { type="Pump station", category="Fortifications", shape="nasos", dataCategory=TemplateDB.type.static } + + TemplateDB.templates["hangar"] = { type="Hangar A", category="Fortifications", shape="angar_a", dataCategory=TemplateDB.type.static } + + TemplateDB.templates["excavator"] = { type="345 Excavator", category="Fortifications", shape="cat_3451", dataCategory=TemplateDB.type.static } + + TemplateDB.templates["farm-house-1"] = { type="Farm A", category="Fortifications", shape="ferma_a", dataCategory=TemplateDB.type.static } + + TemplateDB.templates["farm-house-2"] = { type="Farm B", category="Fortifications", shape="ferma_b", dataCategory=TemplateDB.type.static } + + TemplateDB.templates["antenna"] = { type="Comms tower M", category="Fortifications", shape="tele_bash_m", dataCategory=TemplateDB.type.static } + + TemplateDB.templates["tv-tower"] = { type="TV tower", category="Fortifications", shape="tele_bash", dataCategory=TemplateDB.type.static } + + TemplateDB.templates["command-center"] = { type=".Command Center", category="Fortifications", shape="ComCenter", dataCategory=TemplateDB.type.static } + + TemplateDB.templates["military-staff"] = { type="Military staff", category="Fortifications", shape="aviashtab", dataCategory=TemplateDB.type.static } +end + +-----------------[[ END OF TemplateDB.lua ]]----------------- + + + +-----------------[[ Spawner.lua ]]----------------- + +Spawner = {} + +do + function Spawner.createPilot(name, pos) + local groupData = Spawner.getData("pilot-replacement", name, pos, nil, 5, { + [land.SurfaceType.LAND] = true, + [land.SurfaceType.ROAD] = true, + [land.SurfaceType.RUNWAY] = true, + }) + + return coalition.addGroup(country.id.CJTF_BLUE, Group.Category.GROUND, groupData) + end + + function Spawner.createObject(name, objType, pos, side, minDist, maxDist, surfaceTypes, zone) + if zone then + zone = CustomZone:getByName(zone) -- expand zone name to CustomZone object + end + + local data = Spawner.getData(objType, name, pos, minDist, maxDist, surfaceTypes, zone) + + if not data then return end + + local cnt = country.id.CJTF_BLUE + if side == 1 then + cnt = country.id.CJTF_RED + end + + if data.dataCategory == TemplateDB.type.static then + return coalition.addStaticObject(cnt, data) + elseif data.dataCategory == TemplateDB.type.group then + return coalition.addGroup(cnt, Group.Category.GROUND, data) + end + end + + function Spawner.getUnit(unitType, name, pos, skill, minDist, maxDist, surfaceTypes, zone) + local nudgedPos = nil + for i=1,500,1 do + nudgedPos = mist.getRandPointInCircle(pos, maxDist, minDist) + + if zone then + if zone:isInside(nudgedPos) and surfaceTypes[land.getSurfaceType(nudgedPos)] then + break + end + else + if surfaceTypes[land.getSurfaceType(nudgedPos)] then + break + end + end + + if i==500 then env.info('Spawner - ERROR: failed to find good location') end + end + + return { + ["type"] = unitType, + ["skill"] = skill, + ["coldAtStart"] = false, + ["x"] = nudgedPos.x, + ["y"] = nudgedPos.y, + ["name"] = name, + ['heading'] = math.random()*math.pi*2, + ["playerCanDrive"] = false + } + end + + function Spawner.getData(objtype, name, pos, minDist, maxDist, surfaceTypes, zone) + if not maxDist then maxDist = 150 end + if not surfaceTypes then surfaceTypes = { [land.SurfaceType.LAND]=true } end + + local data = TemplateDB.getData(objtype) + if not data then + env.info("Spawner - ERROR: cant find group data "..tostring(objtype).." for group name "..name) + return + end + + local spawnData = {} + + if data.dataCategory == TemplateDB.type.static then + if not surfaceTypes[land.getSurfaceType(pos)] then + for i=1,500,1 do + pos = mist.getRandPointInCircle(pos, maxDist) + + if zone then + if zone:isInside(pos) and surfaceTypes[land.getSurfaceType(pos)] then + break + end + else + if surfaceTypes[land.getSurfaceType(pos)] then + break + end + end + + if i==500 then env.info('Spawner - ERROR: failed to find good location') end + end + end + + spawnData = { + ["type"] = data.type, + ["name"] = name, + ["shape_name"] = data.shape, + ["category"] = data.category, + ["x"] = pos.x, + ["y"] = pos.y, + ['heading'] = math.random()*math.pi*2 + } + elseif data.dataCategory== TemplateDB.type.group then + spawnData = { + ["units"] = {}, + ["name"] = name, + ["task"] = "Ground Nothing", + ["route"] = { + ["points"]={ + { + ["x"] = pos.x, + ["y"] = pos.y, + ["action"] = "Off Road", + ["speed"] = 0, + ["type"] = "Turning Point", + ["ETA"] = 0, + ["formation_template"] = "", + ["task"] = Spawner.getDefaultTask(data.invisible) + } + } + } + } + + if data.minDist then + minDist = data.minDist + end + + if data.maxDist then + maxDist = data.maxDist + end + + for i,v in ipairs(data.units) do + table.insert(spawnData.units, Spawner.getUnit(v, name.."-"..i, pos, data.skill, minDist, maxDist, surfaceTypes, zone)) + end + end + + spawnData.dataCategory = data.dataCategory + + return spawnData + end + + function Spawner.getDefaultTask(invisible) + local defTask = { + ["id"] = "ComboTask", + ["params"] = + { + ["tasks"] = + { + [1] = + { + ["enabled"] = true, + ["auto"] = false, + ["id"] = "WrappedAction", + ["number"] = 1, + ["params"] = + { + ["action"] = + { + ["id"] = "Option", + ["params"] = + { + ["name"] = 9, + ["value"] = 2, + }, + }, + }, + }, + [2] = + { + ["enabled"] = true, + ["auto"] = false, + ["id"] = "WrappedAction", + ["number"] = 2, + ["params"] = + { + ["action"] = + { + ["id"] = "Option", + ["params"] = + { + ["name"] = 0, + ["value"] = 0, + } + } + } + } + } + } + } + + if invisible then + table.insert(defTask.params.tasks, { + ["number"] = 3, + ["auto"] = false, + ["id"] = "WrappedAction", + ["enabled"] = true, + ["params"] = + { + ["action"] = + { + ["id"] = "SetInvisible", + ["params"] = + { + ["value"] = true, + } + } + } + }) + end + + return defTask + end +end + +-----------------[[ END OF Spawner.lua ]]----------------- + + + +-----------------[[ CommandFunctions.lua ]]----------------- + +CommandFunctions = {} + +do + CommandFunctions.jtac = nil + + function CommandFunctions.spawnJtac(zone) + if CommandFunctions.jtac then + CommandFunctions.jtac:deployAtZone(zone) + CommandFunctions.jtac:showMenu() + CommandFunctions.jtac:setLifeTime(60) + end + end + + function CommandFunctions.smokeTargets(zone, count) + local units = {} + for i,v in pairs(zone.built) do + local g = Group.getByName(v.name) + if g then + for i2,v2 in ipairs(g:getUnits()) do + table.insert(units, v2) + end + else + local s = StaticObject.getByName(v.name) + if s then + table.insert(units, s) + end + end + end + + local tgts = {} + for i=1,count,1 do + if #units > 0 then + local selected = math.random(1,#units) + table.insert(tgts, units[selected]) + table.remove(units, selected) + end + end + + for i,v in ipairs(tgts) do + local pos = v:getPoint() + trigger.action.smoke(pos, 1) + end + end +end + +-----------------[[ END OF CommandFunctions.lua ]]----------------- + + + +-----------------[[ JTAC.lua ]]----------------- + +JTAC = {} +do + JTAC.categories = {} + JTAC.categories['SAM'] = {'SAM SR', 'SAM TR', 'IR Guided SAM','SAM LL','SAM CC'} + JTAC.categories['Infantry'] = {'Infantry'} + JTAC.categories['Armor'] = {'Tanks','IFV','APC'} + JTAC.categories['Support'] = {'Unarmed vehicles','Artillery'} + JTAC.categories['Structures'] = {'StaticObjects'} + + --{name = 'groupname'} + function JTAC:new(obj) + obj = obj or {} + obj.lasers = {tgt=nil, ir=nil} + obj.target = nil + obj.timerReference = nil + obj.remainingLife = nil + obj.tgtzone = nil + obj.priority = nil + obj.jtacMenu = nil + obj.laserCode = 1688 + obj.side = Group.getByName(obj.name):getCoalition() + setmetatable(obj, self) + self.__index = self + obj:initCodeListener() + return obj + end + + function JTAC:initCodeListener() + local ev = {} + ev.context = self + function ev:onEvent(event) + if event.id == 26 then + if event.text:find('^jtac%-code:') then + local s = event.text:gsub('^jtac%-code:', '') + local code = tonumber(s) + self.context:setCode(code) + trigger.action.removeMark(event.idx) + end + end + end + + world.addEventHandler(ev) + end + + function JTAC:setCode(code) + if code>=1111 and code <= 1788 then + self.laserCode = code + trigger.action.outTextForCoalition(self.side, 'JTAC code set to '..code, 10) + else + trigger.action.outTextForCoalition(self.side, 'Invalid laser code. Must be between 1111 and 1788 ', 10) + end + end + + function JTAC:showMenu() + local gr = Group.getByName(self.name) + if not gr then + return + end + + if not self.jtacMenu then + self.jtacMenu = missionCommands.addSubMenuForCoalition(self.side, 'JTAC') + + missionCommands.addCommandForCoalition(self.side, 'Target report', self.jtacMenu, function(dr) + if Group.getByName(dr.name) then + dr:printTarget(true) + else + missionCommands.removeItemForCoalition(dr.side, dr.jtacMenu) + dr.jtacMenu = nil + end + end, self) + + missionCommands.addCommandForCoalition(self.side, 'Next Target', self.jtacMenu, function(dr) + if Group.getByName(dr.name) then + dr:searchTarget() + else + missionCommands.removeItemForCoalition(dr.side, dr.jtacMenu) + dr.jtacMenu = nil + end + end, self) + + missionCommands.addCommandForCoalition(self.side, 'Deploy Smoke', self.jtacMenu, function(dr) + if Group.getByName(dr.name) then + local tgtunit = Unit.getByName(dr.target) + if not tgtunit then + tgtunit = StaticObject.getByName(dr.target) + end + + if tgtunit then + trigger.action.smoke(tgtunit:getPoint(), 3) + trigger.action.outTextForCoalition(dr.side, 'JTAC target marked with ORANGE smoke', 10) + end + else + missionCommands.removeItemForCoalition(dr.side, dr.jtacMenu) + dr.jtacMenu = nil + end + end, self) + + local priomenu = missionCommands.addSubMenuForCoalition(self.side, 'Set Priority', self.jtacMenu) + for i,v in pairs(JTAC.categories) do + missionCommands.addCommandForCoalition(self.side, i, priomenu, function(dr, cat) + if Group.getByName(dr.name) then + dr:setPriority(cat) + dr:searchTarget() + else + missionCommands.removeItemForCoalition(dr.side, dr.jtacMenu) + dr.jtacMenu = nil + end + end, self, i) + end + + local dial = missionCommands.addSubMenuForCoalition(self.side, 'Set Laser Code', self.jtacMenu) + for i2=1,7,1 do + local digit2 = missionCommands.addSubMenuForCoalition(self.side, '1'..i2..'__', dial) + for i3=1,9,1 do + local digit3 = missionCommands.addSubMenuForCoalition(self.side, '1'..i2..i3..'_', digit2) + for i4=1,9,1 do + local digit4 = missionCommands.addSubMenuForCoalition(self.side, '1'..i2..i3..i4, digit3) + local code = tonumber('1'..i2..i3..i4) + missionCommands.addCommandForCoalition(self.side, 'Accept', digit4, Utils.log(self.setCode), self, code) + end + end + end + + missionCommands.addCommandForCoalition(self.side, "Clear", priomenu, function(dr) + if Group.getByName(dr.name) then + dr:clearPriority() + dr:searchTarget() + else + missionCommands.removeItemForCoalition(dr.side, dr.jtacMenu) + dr.jtacMenu = nil + end + end, self) + end + end + + function JTAC:setPriority(prio) + self.priority = JTAC.categories[prio] + self.prioname = prio + end + + function JTAC:clearPriority() + self.priority = nil + end + + function JTAC:setTarget(unit) + + if self.lasers.tgt then + self.lasers.tgt:destroy() + self.lasers.tgt = nil + end + + if self.lasers.ir then + self.lasers.ir:destroy() + self.lasers.ir = nil + end + + local me = Group.getByName(self.name) + if not me then return end + + local pnt = unit:getPoint() + self.lasers.tgt = Spot.createLaser(me:getUnit(1), { x = 0, y = 2.0, z = 0 }, pnt, self.laserCode) + self.lasers.ir = Spot.createInfraRed(me:getUnit(1), { x = 0, y = 2.0, z = 0 }, pnt) + + self.target = unit:getName() + end + + function JTAC:setLifeTime(minutes) + self.remainingLife = minutes + + timer.scheduleFunction(function(param, time) + if param.remainingLife == nil then return end + + local gr = Group.getByName(self.name) + if not gr then + param.remainingLife = nil + return + end + + param.remainingLife = param.remainingLife - 1 + if param.remainingLife < 0 then + param:clearTarget() + return + end + + return time+60 + end, self, timer.getTime()+60) + end + + function JTAC:printTarget(makeitlast) + local toprint = '' + if self.target and self.tgtzone then + local tgtunit = Unit.getByName(self.target) + local isStructure = false + if not tgtunit then + tgtunit = StaticObject.getByName(self.target) + isStructure = true + end + + if tgtunit then + local pnt = tgtunit:getPoint() + local tgttype = "Unidentified" + if isStructure then + tgttype = "Structure" + else + tgttype = tgtunit:getTypeName() + end + + if self.priority then + toprint = 'Priority targets: '..self.prioname..'\n' + end + + toprint = toprint..'Lasing '..tgttype..' at '..self.tgtzone.name..'\nCode: '..self.laserCode..'\n' + local lat,lon,alt = coord.LOtoLL(pnt) + local mgrs = coord.LLtoMGRS(coord.LOtoLL(pnt)) + toprint = toprint..'\nDDM: '.. mist.tostringLL(lat,lon,3) + toprint = toprint..'\nDMS: '.. mist.tostringLL(lat,lon,2,true) + toprint = toprint..'\nMGRS: '.. mist.tostringMGRS(mgrs, 5) + toprint = toprint..'\n\nAlt: '..math.floor(alt)..'m'..' | '..math.floor(alt*3.280839895)..'ft' + else + makeitlast = false + toprint = 'No Target' + end + else + makeitlast = false + toprint = 'No target' + end + + local gr = Group.getByName(self.name) + if makeitlast then + trigger.action.outTextForCoalition(gr:getCoalition(), toprint, 60) + else + trigger.action.outTextForCoalition(gr:getCoalition(), toprint, 10) + end + end + + function JTAC:clearTarget() + self.target = nil + + if self.lasers.tgt then + self.lasers.tgt:destroy() + self.lasers.tgt = nil + end + + if self.lasers.ir then + self.lasers.ir:destroy() + self.lasers.ir = nil + end + + if self.timerReference then + mist.removeFunction(self.timerReference) + self.timerReference = nil + end + + local gr = Group.getByName(self.name) + if gr then + gr:destroy() + missionCommands.removeItemForCoalition(self.side, self.jtacMenu) + self.jtacMenu = nil + end + end + + function JTAC:searchTarget() + local gr = Group.getByName(self.name) + if gr then + if self.tgtzone and self.tgtzone.side~=0 and self.tgtzone.side~=gr:getCoalition() then + local viabletgts = {} + for i,v in pairs(self.tgtzone.built) do + local tgtgr = Group.getByName(v.name) + if tgtgr and tgtgr:getSize()>0 then + for i2,v2 in ipairs(tgtgr:getUnits()) do + if v2:getLife()>=1 then + table.insert(viabletgts, v2) + end + end + else + tgtgr = StaticObject.getByName(v.name) + if tgtgr then + table.insert(viabletgts, tgtgr) + end + end + end + + if self.priority then + local priorityTargets = {} + for i,v in ipairs(viabletgts) do + for i2,v2 in ipairs(self.priority) do + if v2 == "StaticObjects" and ZoneCommand.staticRegistry[v:getName()] then + table.insert(priorityTargets, v) + break + elseif v:hasAttribute(v2) and v:getLife()>=1 then + table.insert(priorityTargets, v) + break + end + end + end + + if #priorityTargets>0 then + viabletgts = priorityTargets + else + self:clearPriority() + trigger.action.outTextForCoalition(gr:getCoalition(), 'JTAC: No priority targets found', 10) + end + end + + if #viabletgts>0 then + local chosentgt = math.random(1, #viabletgts) + self:setTarget(viabletgts[chosentgt]) + self:printTarget() + else + self:clearTarget() + end + else + self:clearTarget() + end + end + end + + function JTAC:searchIfNoTarget() + if Group.getByName(self.name) then + if not self.target or (not Unit.getByName(self.target) and not StaticObject.getByName(self.target)) then + self:searchTarget() + elseif self.target then + local un = Unit.getByName(self.target) + if un then + if un:getLife()>=1 then + self:setTarget(un) + else + self:searchTarget() + end + else + local st = StaticObject.getByName(self.target) + if st then + self:setTarget(st) + end + end + end + else + self:clearTarget() + end + end + + function JTAC:deployAtZone(zoneCom) + self.remainingLife = nil + self.tgtzone = zoneCom + local p = CustomZone:getByName(self.tgtzone.name).point + local vars = {} + vars.gpName = self.name + vars.action = 'respawn' + vars.point = {x=p.x, y=5000, z = p.z} + mist.teleportToPoint(vars) + + mist.scheduleFunction(self.setOrbit, {self, self.tgtzone.zone, p}, timer.getTime()+1) + + if not self.timerReference then + self.timerReference = mist.scheduleFunction(self.searchIfNoTarget, {self}, timer.getTime()+5, 5) + end + end + + function JTAC:setOrbit(zonename, point) + local gr = Group.getByName(self.name) + if not gr then + return + end + + local cnt = gr:getController() + cnt:setCommand({ + id = 'SetInvisible', + params = { + value = true + } + }) + + cnt:setTask({ + id = 'Orbit', + params = { + pattern = 'Circle', + point = {x = point.x, y=point.z}, + altitude = 5000 + } + }) + + self:searchTarget() + end +end + +-----------------[[ END OF JTAC.lua ]]----------------- + + + +-----------------[[ Objectives/Objective.lua ]]----------------- + +Objective = {} + +do + Objective.types = { + fly_to_zone_seq = 'fly_to_zone_seq', -- any of playerlist inside [zone] in sequence + recon_zone = 'recon_zone', -- within X km, facing Y angle +-, % of enemy units in LOS progress faster + destroy_attr = 'destroy_attr', -- any of playerlist kill event on target with any of [attribute] + destroy_attr_at_zone = 'destroy_attr_at_zone', -- any of playerlist kill event on target at [zone] with any of [attribute] + clear_attr_at_zone = 'clear_attr_at_zone', -- [zone] does not have any units with [attribute] + destroy_structure = 'destroy_structure', -- [structure] is killed by any player (getDesc().displayName or getDesc().typeName:gsub('%.','') must match) + destroy_group = 'destroy_group', -- [group] is missing from mission AND any player killed unit from group at least once + supply = 'supply', -- any of playerlist unload [amount] supply at [zone] + extract_pilot = 'extract_pilot', -- players extracted specific ejected pilots + extract_squad = 'extract_squad', -- players extracted specific squad + unloaded_pilot_or_squad = 'unloaded_pilot_or_squad', -- unloaded pilot or squad + deploy_squad = 'deploy_squad', --deploy squad at zone + escort = 'escort', -- escort convoy + protect = 'protect', -- protect other mission + air_kill_bonus = 'air_kill_bonus', -- award bonus for air kills + bomb_in_zone = 'bomb_in_zone', -- bombs tallied inside zone + player_close_to_zone = 'player_close_to_zone' -- player is close to point + } + + function Objective:new(type) + + local obj = { + type = type, + mission = nil, + param = {}, + isComplete = false, + isFailed = false + } + + setmetatable(obj, self) + self.__index = self + + return obj + end + + function Objective:initialize(mission, param) + self.mission = mission + self:validateParameters(param) + self.param = param + end + + function Objective:getType() + return self.type + end + + function Objective:validateParameters(param) + for i,v in pairs(self.requiredParams) do + if v and param[i] == nil then + env.error("Objective - missing parameter: "..i..' in '..self:getType(), true) + end + end + end + + -- virtual + Objective.requiredParams = {} + + function Objective:getText() + env.error("Objective - getText not implemented") + return "NOT IMPLEMENTED" + end + + function Objective:update() + env.error("Objective - update not implemented") + end + + function Objective:checkFail() + env.error("Objective - checkFail not implemented") + end + --end virtual +end + +-----------------[[ END OF Objectives/Objective.lua ]]----------------- + + + +-----------------[[ Objectives/ObjAirKillBonus.lua ]]----------------- + +ObjAirKillBonus = Objective:new(Objective.types.air_kill_bonus) +do + ObjAirKillBonus.requiredParams = { + ['attr'] = true, + ['bonus'] = true, + ['count'] = true, + ['linkedObjectives'] = true + } + + function ObjAirKillBonus:getText() + local msg = 'Destroy: ' + for _,v in ipairs(self.param.attr) do + msg = msg..v..', ' + end + msg = msg:sub(1,#msg-2) + msg = msg..'\n Kills increase mission reward (Ends when other objectives are completed)' + msg = msg..'\n Kills: '..self.param.count + return msg + end + + function ObjAirKillBonus:update() + if not self.isComplete and not self.isFailed then + local allcomplete = true + for _,obj in pairs(self.param.linkedObjectives) do + if obj.isFailed then self.isFailed = true end + if not obj.isComplete then allcomplete = false end + end + + self.isComplete = allcomplete + end + end + + function ObjAirKillBonus:checkFail() + if not self.isComplete and not self.isFailed then + local allcomplete = true + for _,obj in pairs(self.param.linkedObjectives) do + if obj.isFailed then self.isFailed = true end + if not obj.isComplete then allcomplete = false end + end + + self.isComplete = allcomplete + end + end +end + +-----------------[[ END OF Objectives/ObjAirKillBonus.lua ]]----------------- + + + +-----------------[[ Objectives/ObjBombInsideZone.lua ]]----------------- + +ObjBombInsideZone = Objective:new(Objective.types.bomb_in_zone) +do + ObjBombInsideZone.requiredParams = { + ['targetZone'] = true, + ['max'] = true, + ['required'] = true, + ['dropped'] = true, + ['isFinishStarted'] = true, + ['bonus'] = true + } + + function ObjBombInsideZone:getText() + local msg = 'Bomb runways at '..self.param.targetZone.name..'\n' + + local ratio = self.param.dropped/self.param.required + local percent = string.format('%.1f',ratio*100) + + msg = msg..'\n Runway bombed: '..percent..'%\n' + + msg = msg..'\n Cluster bombs do not deal enough damage to complete this mission' + + return msg + end + + function ObjBombInsideZone:update() + if not self.isComplete and not self.isFailed then + if self.param.targetZone.side ~= 1 then + self.isFailed = true + self.mission.failureReason = self.param.targetZone.name..' is no longer controlled by the enemy.' + end + + if not self.param.isFinishStarted then + if self.param.dropped >= self.param.required then + self.param.isFinishStarted = true + timer.scheduleFunction(function(o) + o.isComplete = true + end, self, timer.getTime()+5) + end + end + end + end + + function ObjBombInsideZone:checkFail() + if not self.isComplete and not self.isFailed then + if self.param.targetZone.side ~= 1 then + self.isFailed = true + end + end + end +end + +-----------------[[ END OF Objectives/ObjBombInsideZone.lua ]]----------------- + + + +-----------------[[ Objectives/ObjClearZoneOfUnitsWithAttribute.lua ]]----------------- + +ObjClearZoneOfUnitsWithAttribute = Objective:new(Objective.types.clear_attr_at_zone) +do + ObjClearZoneOfUnitsWithAttribute.requiredParams = { + ['attr'] = true, + ['tgtzone'] = true + } + + function ObjClearZoneOfUnitsWithAttribute:getText() + local msg = 'Clear '..self.param.tgtzone.name..' of: ' + for _,v in ipairs(self.param.attr) do + msg = msg..v..', ' + end + msg = msg:sub(1,#msg-2) + msg = msg..'\n Progress: '..self.param.tgtzone:getUnitCountWithAttributeOnSide(self.param.attr, 1)..' left' + return msg + end + + function ObjClearZoneOfUnitsWithAttribute:update() + if not self.isComplete and not self.isFailed then + local zn = self.param.tgtzone + if zn.side ~= 1 or not zn:hasUnitWithAttributeOnSide(self.param.attr, 1) then + self.isComplete = true + return true + end + end + end + + function ObjClearZoneOfUnitsWithAttribute:checkFail() + -- can not fail + end +end + +-----------------[[ END OF Objectives/ObjClearZoneOfUnitsWithAttribute.lua ]]----------------- + + + +-----------------[[ Objectives/ObjDestroyGroup.lua ]]----------------- + +ObjDestroyGroup = Objective:new(Objective.types.destroy_group) +do + ObjDestroyGroup.requiredParams = { + ['target'] = true, + ['targetUnitNames'] = true, + ['lastUpdate'] = true + } + + function ObjDestroyGroup:getText() + local msg = 'Destroy '..self.param.target.product.display..' before it reaches its destination.\n' + + local gr = Group.getByName(self.param.target.name) + if gr and gr:getSize()>0 then + local killcount = 0 + for i,v in pairs(self.param.targetUnitNames) do + if v == true then + killcount = killcount + 1 + end + end + + msg = msg..'\n '..gr:getSize()..' units remaining. (killed '..killcount..')\n' + for name, unit in pairs(self.mission.players) do + if unit and unit:isExist() then + local tgtUnit = gr:getUnit(1) + local dist = mist.utils.get2DDist(unit:getPoint(), tgtUnit:getPoint()) + + local m = '\n '..name..': Distance: ' + m = m..string.format('%.2f',dist/1000)..'km' + m = m..' Bearing: '..math.floor(Utils.getBearing(unit:getPoint(), tgtUnit:getPoint())) + msg = msg..m + end + end + end + + return msg + end + + function ObjDestroyGroup:update() + if not self.isComplete and not self.isFailed then + local target = self.param.target + local exists = false + local gr = Group.getByName(target.name) + + if gr and gr:getSize() > 0 then + local updateFrequency = 5 -- seconds + local shouldUpdateMsg = (timer.getAbsTime() - self.param.lastUpdate) > updateFrequency + + if shouldUpdateMsg then + for _, unit in pairs(self.mission.players) do + if unit and unit:isExist() then + local tgtUnit = gr:getUnit(1) + local dist = mist.utils.get2DDist(unit:getPoint(), tgtUnit:getPoint()) + local dstkm = string.format('%.2f',dist/1000) + local dstnm = string.format('%.2f',dist/1852) + + local m = 'Distance: ' + m = m..dstkm..'km | '..dstnm..'nm' + + m = m..'\nBearing: '..math.floor(Utils.getBearing(unit:getPoint(), tgtUnit:getPoint())) + trigger.action.outTextForUnit(unit:getID(), m, updateFrequency) + end + end + + self.param.lastUpdate = timer.getAbsTime() + end + elseif target.state == 'enroute' then + for i,v in pairs(self.param.targetUnitNames) do + if v == true then + self.isComplete = true + return true + end + end + + self.isFailed = true + self.mission.failureReason = 'Convoy was killed by someone else.' + return true + else + self.isFailed = true + self.mission.failureReason = 'Convoy has reached its destination.' + return true + end + end + end + + function ObjDestroyGroup:checkFail() + if not self.isComplete and not self.isFailed then + local target = self.param.target + local gr = Group.getByName(target.name) + + if target.state ~= 'enroute' or not gr or gr:getSize() == 0 then + self.isFailed = true + end + end + end +end + +-----------------[[ END OF Objectives/ObjDestroyGroup.lua ]]----------------- + + + +-----------------[[ Objectives/ObjDestroyStructure.lua ]]----------------- + +ObjDestroyStructure = Objective:new(Objective.types.destroy_structure) +do + ObjDestroyStructure.requiredParams = { + ['target']=true, + ['tgtzone']=true, + ['killed']=true + } + + function ObjDestroyStructure:getText() + local msg = 'Destroy '..self.param.target.display..' at '..self.param.tgtzone.name..'\n' + + local point = nil + local st = StaticObject.getByName(self.param.target.name) + if st then + point = st:getPoint() + else + st = Group.getByName(self.param.target.name) + if st and st:getSize()>0 then + point = st:getUnit(1):getPoint() + end + end + + if point then + local lat,lon,alt = coord.LOtoLL(point) + local mgrs = coord.LLtoMGRS(coord.LOtoLL(point)) + msg = msg..'\n DDM: '.. mist.tostringLL(lat,lon,3) + msg = msg..'\n DMS: '.. mist.tostringLL(lat,lon,2,true) + msg = msg..'\n MGRS: '.. mist.tostringMGRS(mgrs, 5) + msg = msg..'\n Altitude: '..math.floor(alt)..'m'..' | '..math.floor(alt*3.280839895)..'ft' + end + + return msg + end + + function ObjDestroyStructure:update() + if not self.isComplete and not self.isFailed then + if self.param.killed then + self.isComplete = true + return true + end + + local target = self.param.target + local exists = false + local st = StaticObject.getByName(target.name) + if st then + exists = true + else + st = Group.getByName(target.name) + if st and st:getSize()>0 then + exists = true + end + end + + if not exists then + if not self.firstFailure then + self.firstFailure = timer.getAbsTime() + end + end + + if self.firstFailure and (timer.getAbsTime() - self.firstFailure > 1*60) then + self.isFailed = true + self.mission.failureReason = 'Structure was destoyed by someone else.' + return true + end + end + end + + function ObjDestroyStructure:checkFail() + if not self.isComplete and not self.isFailed then + local target = self.param.target + local exists = false + local st = StaticObject.getByName(target.name) + if st then + exists = true + else + st = Group.getByName(target.name) + if st and st:getSize()>0 then + exists = true + end + end + + if not exists then + self.isFailed = true + end + end + end +end + +-----------------[[ END OF Objectives/ObjDestroyStructure.lua ]]----------------- + + + +-----------------[[ Objectives/ObjDestroyUnitsWithAttribute.lua ]]----------------- + +ObjDestroyUnitsWithAttribute = Objective:new(Objective.types.destroy_attr) +do + ObjDestroyUnitsWithAttribute.requiredParams = { + ['attr'] = true, + ['amount'] = true, + ['killed'] = true + } + + function ObjDestroyUnitsWithAttribute:getText() + local msg = 'Destroy: ' + for _,v in ipairs(self.param.attr) do + msg = msg..v..', ' + end + msg = msg:sub(1,#msg-2) + msg = msg..'\n Progress: '..self.param.killed..'/'..self.param.amount + return msg + end + + function ObjDestroyUnitsWithAttribute:update() + if not self.isComplete and not self.isFailed then + if self.param.killed >= self.param.amount then + self.isComplete = true + return true + end + end + end + + function ObjDestroyUnitsWithAttribute:checkFail() + -- can not fail + end +end + +-----------------[[ END OF Objectives/ObjDestroyUnitsWithAttribute.lua ]]----------------- + + + +-----------------[[ Objectives/ObjDestroyUnitsWithAttributeAtZone.lua ]]----------------- + +ObjDestroyUnitsWithAttributeAtZone = Objective:new(Objective.types.destroy_attr_at_zone) +do + ObjDestroyUnitsWithAttributeAtZone.requiredParams = { + ['attr']=true, + ['amount'] = true, + ['killed'] = true, + ['tgtzone'] = true + } + + function ObjDestroyUnitsWithAttributeAtZone:getText() + local msg = 'Destroy at '..self.param.tgtzone.name..': ' + for _,v in ipairs(self.param.attr) do + msg = msg..v..', ' + end + msg = msg:sub(1,#msg-2) + msg = msg..'\n Progress: '..self.param.killed..'/'..self.param.amount + return msg + end + + function ObjDestroyUnitsWithAttributeAtZone:update() + if not self.isComplete and not self.isFailed then + if self.param.killed >= self.param.amount then + self.isComplete = true + return true + end + + local zn = self.param.tgtzone + if zn.side ~= 1 or not zn:hasUnitWithAttributeOnSide(self.param.attr, 1) then + if self.firstFailure == nil then + self.firstFailure = timer.getAbsTime() + else + if timer.getAbsTime() - self.firstFailure > 5*60 then + self.isFailed = true + self.mission.failureReason = zn.name..' no longer has targets matching the description.' + return true + end + end + else + if self.firstFailure ~= nil then + self.firstFailure = nil + end + end + end + end + + function ObjDestroyUnitsWithAttributeAtZone:checkFail() + if not self.isComplete and not self.isFailed then + local zn = self.param.tgtzone + if zn.side ~= 1 or not zn:hasUnitWithAttributeOnSide(self.param.attr, 1) then + self.isFailed = true + end + end + end +end + +-----------------[[ END OF Objectives/ObjDestroyUnitsWithAttributeAtZone.lua ]]----------------- + + + +-----------------[[ Objectives/ObjEscortGroup.lua ]]----------------- + +ObjEscortGroup = Objective:new(Objective.types.escort) +do + ObjEscortGroup.requiredParams = { + ['maxAmount']=true, + ['amount'] = true, + ['proxDist']= true, + ['target'] = true, + ['lastUpdate']= true + } + + function ObjEscortGroup:getText() + local msg = 'Stay in close proximity of the convoy' + + local gr = Group.getByName(self.param.target.name) + if gr and gr:getSize()>0 then + local grunit = gr:getUnit(1) + local lat,lon,alt = coord.LOtoLL(grunit:getPoint()) + local mgrs = coord.LLtoMGRS(coord.LOtoLL(grunit:getPoint())) + msg = msg..'\n DDM: '.. mist.tostringLL(lat,lon,3) + msg = msg..'\n DMS: '.. mist.tostringLL(lat,lon,2,true) + msg = msg..'\n MGRS: '.. mist.tostringMGRS(mgrs, 5) + end + + local prg = math.floor(((self.param.maxAmount - self.param.amount)/self.param.maxAmount)*100) + msg = msg.. '\n Progress: '..prg..'%' + return msg + end + + function ObjEscortGroup:update() + if not self.isComplete and not self.isFailed then + local gr = Group.getByName(self.param.target.name) + if not gr or gr:getSize()==0 then + self.isFailed = true + self.mission.failureReason = 'Group has been destroyed.' + return true + end + local grunit = gr:getUnit(1) + + if self.param.target.state == 'atdestination' or self.param.target.state == 'siege' then + for name, unit in pairs(self.mission.players) do + if unit and unit:isExist() then + local dist = mist.utils.get3DDist(unit:getPoint(), grunit:getPoint()) + if dist < self.param.proxDist then + self.isComplete = true + break + end + end + end + + if not self.isComplete then + self.isFailed = true + self.mission.failureReason = 'Group has reached its destination without an escort.' + end + end + + if not self.isComplete and not self.isFailed then + local plycount = Utils.getTableSize(self.mission.players) + if plycount == 0 then plycount = 1 end + local updateFrequency = 5 -- seconds + local shouldUpdateMsg = (timer.getAbsTime() - self.param.lastUpdate) > updateFrequency + for name, unit in pairs(self.mission.players) do + if unit and unit:isExist() then + local dist = mist.utils.get3DDist(unit:getPoint(), grunit:getPoint()) + if dist < self.param.proxDist then + self.param.amount = self.param.amount - (1/plycount) + + if shouldUpdateMsg then + local prg = string.format('%.1f',((self.param.maxAmount - self.param.amount)/self.param.maxAmount)*100) + trigger.action.outTextForUnit(unit:getID(), 'Progress: '..prg..'%', updateFrequency) + end + else + if shouldUpdateMsg then + local m = 'Distance: ' + if dist>1000 then + local dstkm = string.format('%.2f',dist/1000) + local dstnm = string.format('%.2f',dist/1852) + + m = m..dstkm..'km | '..dstnm..'nm' + else + local dstft = math.floor(dist/0.3048) + m = m..math.floor(dist)..'m | '..dstft..'ft' + end + + m = m..'\nBearing: '..math.floor(Utils.getBearing(unit:getPoint(), grunit:getPoint())) + trigger.action.outTextForUnit(unit:getID(), m, updateFrequency) + end + end + end + end + + if shouldUpdateMsg then + self.param.lastUpdate = timer.getAbsTime() + end + end + + if self.param.amount <= 0 then + self.isComplete = true + return true + end + end + end + + function ObjEscortGroup:checkFail() + if not self.isComplete and not self.isFailed then + local tg = self.param.target + local gr = Group.getByName(tg.name) + if not gr or gr:getSize() == 0 then + self.isFailed = true + end + + if self.mission.state == Mission.states.new then + if tg.state == 'enroute' and (timer.getAbsTime() - tg.lastStateTime) >= 7*60 then + self.isFailed = true + end + end + end + end +end + +-----------------[[ END OF Objectives/ObjEscortGroup.lua ]]----------------- + + + +-----------------[[ Objectives/ObjFlyToZoneSequence.lua ]]----------------- + +ObjFlyToZoneSequence = Objective:new(Objective.types.fly_to_zone_seq) +do + ObjFlyToZoneSequence.requiredParams = { + ['waypoints'] = true, + ['failZones'] = true + } + + function ObjFlyToZoneSequence:getText() + local msg = 'Fly route: ' + + for i,v in ipairs(self.param.waypoints) do + if v.complete then + msg = msg..'\n [✓] '..i..'. '..v.zone.name + else + msg = msg..'\n --> '..i..'. '..v.zone.name + end + end + return msg + end + + function ObjFlyToZoneSequence:update() + if not self.isComplete and not self.isFailed then + if self.param.failZones[1] then + for _,zn in ipairs(self.param.failZones[1]) do + if zn.side ~= 1 then + self.isFailed = true + self.mission.failureReason = zn.name..' is no longer controlled by the enemy.' + break + end + end + end + + if self.param.failZones[2] then + for _,zn in ipairs(self.param.failZones[2]) do + if zn.side ~= 2 then + self.isFailed = true + self.mission.failureReason = zn.name..' was lost.' + break + end + end + end + + if not self.isFailed then + local firstWP = nil + local nextWP = nil + for i,leg in ipairs(self.param.waypoints) do + if not leg.complete then + firstWP = leg + nextWP = self.param.waypoints[i+1] + break + end + end + + if firstWP then + local point = firstWP.zone.zone.point + local range = 3000 --meters + local allInside = true + for p,u in pairs(self.mission.players) do + if u and u:isExist() then + if Utils.isLanded(u,true) then + allInside = false + break + end + + local pos = u:getPoint() + local dist = mist.utils.get2DDist(point, pos) + if dist > range then + allInside = false + break + end + end + end + + if allInside then + firstWP.complete = true + self.mission:pushMessageToPlayers(firstWP.zone.name..' reached') + if nextWP then + self.mission:pushMessageToPlayers('Next point: '..nextWP.zone.name) + end + end + else + self.isComplete = true + return true + end + end + end + end + + function ObjFlyToZoneSequence:checkFail() + if not self.isComplete and not self.isFailed then + if self.param.failZones[1] then + for _,zn in ipairs(self.param.failZones[1]) do + if zn.side ~= 1 then + self.isFailed = true + break + end + end + end + + if self.param.failZones[2] then + for _,zn in ipairs(self.param.failZones[2]) do + if zn.side ~= 2 then + self.isFailed = true + break + end + end + end + end + end +end + +-----------------[[ END OF Objectives/ObjFlyToZoneSequence.lua ]]----------------- + + + +-----------------[[ Objectives/ObjProtectMission.lua ]]----------------- + +ObjProtectMission = Objective:new(Objective.types.protect) +do + ObjProtectMission.requiredParams = { + ['mis'] = true + } + + function ObjProtectMission:getText() + local msg = 'Prevent enemy aircraft from interfering with '..self.param.mis:getMissionName()..' mission.' + + if self.param.mis.info and self.param.mis.info.targetzone then + msg = msg..'\n Target zone: '..self.param.mis.info.targetzone.name + end + + msg = msg..'\n Protect players: ' + for i,v in pairs(self.param.mis.players) do + msg = msg..'\n '..i + end + + msg = msg..'\n Mission success depends on '..self.param.mis:getMissionName()..' mission success.' + return msg + end + + function ObjProtectMission:update() + if not self.isComplete and not self.isFailed then + if self.param.mis.state == Mission.states.failed then + self.isFailed = true + self.mission.failureReason = "Failed to protect players of "..self.param.mis.name.." mission." + end + + if self.param.mis.state == Mission.states.completed then + self.isComplete = true + end + end + end + + function ObjProtectMission:checkFail() + if not self.isComplete and not self.isFailed then + if self.param.mis.state == Mission.states.failed then + self.isFailed = true + end + + if self.param.mis.state == Mission.states.completed then + if self.state == Mission.states.new or + self.state == Mission.states.preping or + self.state == Mission.states.comencing then + + self.isFailed = true + end + end + end + end +end + +-----------------[[ END OF Objectives/ObjProtectMission.lua ]]----------------- + + + +-----------------[[ Objectives/ObjReconZone.lua ]]----------------- + +ObjReconZone = Objective:new(Objective.types.recon_zone) +do + ObjReconZone.requiredParams = { + ['target'] = true, + ['maxAmount'] = true, + ['amount'] = true, + ['allowedDeviation'] = true, + ['proxDist'] = true, + ['lastUpdate'] = true, + ['failZones'] = true + } + + function ObjReconZone:getText() + local msg = 'Stay within range of '..self.param.target.name..' and observe the enemy.' + + local prg = math.floor(((self.param.maxAmount - self.param.amount)/self.param.maxAmount)*100) + msg = msg.. '\n Progress: '..prg..'%' + return msg + end + + function ObjReconZone:update() + if not self.isComplete and not self.isFailed then + if self.param.failZones[1] then + for _,zn in ipairs(self.param.failZones[1]) do + if zn.side ~= 1 then + self.isFailed = true + self.mission.failureReason = zn.name..' is no longer controlled by the enemy.' + break + end + end + end + + if self.param.failZones[2] then + for _,zn in ipairs(self.param.failZones[2]) do + if zn.side ~= 2 then + self.isFailed = true + self.mission.failureReason = zn.name..' was lost.' + break + end + end + end + + if not self.isFailed then + local plycount = Utils.getTableSize(self.mission.players) + if plycount == 0 then plycount = 1 end + local updateFrequency = 5 -- seconds + local shouldUpdateMsg = (timer.getAbsTime() - self.param.lastUpdate) > updateFrequency + + for name, unit in pairs(self.mission.players) do + if unit and unit:isExist() then + local dist = mist.utils.get2DDist(unit:getPoint(), self.param.target.zone.point) + if dist < self.param.proxDist then + local unitPos = unit:getPosition() + local unitheading = math.deg(math.atan2(unitPos.x.z, unitPos.x.x)) + local bearing = Utils.getBearing(unit:getPoint(), self.param.target.zone.point) + + local diff = Utils.getHeadingDiff(unitheading, bearing) + + if math.abs(diff) <= self.param.allowedDeviation then + local unitsCount = 0 + local visibleCount = 0 + for _,product in pairs(self.param.target.built) do + if product.side ~= unit:getCoalition() then + local gr = Group.getByName(product.name) + if gr then + for _,enemyUnit in ipairs(gr:getUnits()) do + unitsCount = unitsCount+1 + local from = unit:getPoint() + from.y = from.y+1.5 + local to = enemyUnit:getPoint() + to.y = to.y+1.5 + if land.isVisible(from, to) then + visibleCount = visibleCount+1 + end + end + else + local st = StaticObject.getByName(product.name) + if st then + unitsCount = unitsCount+1 + local from = unit:getPoint() + from.y = from.y+1.5 + local to = st:getPoint() + to.y = to.y+1.5 + if land.isVisible(from, to) then + visibleCount = visibleCount+1 + end + end + end + end + end + + local percentVisible = 0 + if unitsCount > 0 and visibleCount > 0 then + percentVisible = visibleCount/unitsCount + if percentVisible > 0.5 then + self.param.amount = self.param.amount - percentVisible + else + + end + env.info('Scout_Helo - player can see '..string.format('%.2f',percentVisible)..'%') + end + + + if shouldUpdateMsg then + if visibleCount == 0 then + local prg = string.format('%.1f',((self.param.maxAmount - self.param.amount)/self.param.maxAmount)*100) + trigger.action.outTextForUnit(unit:getID(), 'No enemy visible.\nProgress: '..prg..'%', updateFrequency) + else + local percent = string.format('%.1f',percentVisible*100) + + local prg = string.format('%.1f',((self.param.maxAmount - self.param.amount)/self.param.maxAmount)*100) + trigger.action.outTextForUnit(unit:getID(), percent..'% of enemies visible.\nProgress: '..prg..'%', updateFrequency) + end + end + else + if shouldUpdateMsg then + local m = 'Within range\nTurn heading: '..math.floor(Utils.getBearing(unit:getPoint(), self.param.target.zone.point)) + trigger.action.outTextForUnit(unit:getID(), m, updateFrequency) + end + end + else + if shouldUpdateMsg then + local dstkm = string.format('%.2f',dist/1000) + local dstnm = string.format('%.2f',dist/1852) + + local m = 'Distance: ' + m = m..dstkm..'km | '..dstnm..'nm' + + m = m..'\nBearing: '..math.floor(Utils.getBearing(unit:getPoint(), self.param.target.zone.point)) + trigger.action.outTextForUnit(unit:getID(), m, updateFrequency) + end + end + end + end + + if shouldUpdateMsg then + self.param.lastUpdate = timer.getAbsTime() + end + + if self.param.amount <= 0 then + self.isComplete = true + return true + end + end + end + end + + function ObjReconZone:checkFail() + if not self.isComplete and not self.isFailed then + if self.param.failZones[1] then + for _,zn in ipairs(self.param.failZones[1]) do + if zn.side ~= 1 then + self.isFailed = true + break + end + end + end + + if self.param.failZones[2] then + for _,zn in ipairs(self.param.failZones[2]) do + if zn.side ~= 2 then + self.isFailed = true + break + end + end + end + end + end +end + +-----------------[[ END OF Objectives/ObjReconZone.lua ]]----------------- + + + +-----------------[[ Objectives/ObjSupplyZone.lua ]]----------------- + +ObjSupplyZone = Objective:new(Objective.types.supply) +do + ObjSupplyZone.requiredParams = { + ['amount']=true, + ['delivered']=true, + ['tgtzone']=true + } + + function ObjSupplyZone:getText() + local msg = 'Deliver '..self.param.amount..' to '..self.param.tgtzone.name..': ' + msg = msg..'\n Progress: '..self.param.delivered..'/'..self.param.amount + return msg + end + + function ObjSupplyZone:update() + if not self.isComplete and not self.isFailed then + if self.param.delivered >= self.param.amount then + self.isComplete = true + return true + end + + local zn = self.param.tgtzone + if zn.side ~= 2 then + self.isFailed = true + self.mission.failureReason = zn.name..' was lost.' + return true + end + end + end + + function ObjSupplyZone:checkFail() + if not self.isComplete and not self.isFailed then + local zn = self.param.tgtzone + if zn.side ~= 2 then + self.isFailed = true + return true + end + end + end +end + +-----------------[[ END OF Objectives/ObjSupplyZone.lua ]]----------------- + + + +-----------------[[ Objectives/ObjExtractSquad.lua ]]----------------- + +ObjExtractSquad = Objective:new(Objective.types.extract_squad) +do + ObjExtractSquad.requiredParams = { + ['target']=true, + ['loadedBy']=false, + ['lastUpdate']= true + } + + function ObjExtractSquad:getText() + local infName = PlayerLogistics.getInfantryName(self.param.target.data.type) + local msg = 'Extract '..infName..' '..self.param.target.name..'\n' + + if not self.param.loadedBy then + local gr = Group.getByName(self.param.target.name) + if gr and gr:getSize()>0 then + local point = gr:getUnit(1):getPoint() + + local lat,lon,alt = coord.LOtoLL(point) + local mgrs = coord.LLtoMGRS(coord.LOtoLL(point)) + msg = msg..'\n DDM: '.. mist.tostringLL(lat,lon,3) + msg = msg..'\n DMS: '.. mist.tostringLL(lat,lon,2,true) + msg = msg..'\n MGRS: '.. mist.tostringMGRS(mgrs, 5) + msg = msg..'\n Altitude: '..math.floor(alt)..'m'..' | '..math.floor(alt*3.280839895)..'ft' + end + end + + return msg + end + + function ObjExtractSquad:update() + if not self.isComplete and not self.isFailed then + + if self.param.loadedBy then + self.isComplete = true + return true + else + local target = self.param.target + + local gr = Group.getByName(target.name) + if not gr or gr:getSize()==0 then + self.isFailed = true + self.mission.failureReason = 'Squad was not rescued in time, and went MIA.' + return true + end + end + + local updateFrequency = 5 -- seconds + local shouldUpdateMsg = (timer.getAbsTime() - self.param.lastUpdate) > updateFrequency + if shouldUpdateMsg then + for name, unit in pairs(self.mission.players) do + if unit and unit:isExist() then + local gr = Group.getByName(self.param.target.name) + local un = gr:getUnit(1) + local dist = mist.utils.get3DDist(unit:getPoint(), un:getPoint()) + local m = 'Distance: ' + if dist>1000 then + local dstkm = string.format('%.2f',dist/1000) + local dstnm = string.format('%.2f',dist/1852) + + m = m..dstkm..'km | '..dstnm..'nm' + else + local dstft = math.floor(dist/0.3048) + m = m..math.floor(dist)..'m | '..dstft..'ft' + end + + m = m..'\nBearing: '..math.floor(Utils.getBearing(unit:getPoint(), un:getPoint())) + trigger.action.outTextForUnit(unit:getID(), m, updateFrequency) + end + end + + self.param.lastUpdate = timer.getAbsTime() + end + end + end + + function ObjExtractSquad:checkFail() + if not self.isComplete and not self.isFailed then + local target = self.param.target + + local gr = Group.getByName(target.name) + if not gr or not gr:isExist() or gr:getSize()==0 then + self.isFailed = true + return true + end + end + end +end + +-----------------[[ END OF Objectives/ObjExtractSquad.lua ]]----------------- + + + +-----------------[[ Objectives/ObjExtractPilot.lua ]]----------------- + +ObjExtractPilot = Objective:new(Objective.types.extract_pilot) +do + ObjExtractPilot.requiredParams = { + ['target']=true, + ['loadedBy']=false, + ['lastUpdate']= true + } + + function ObjExtractPilot:getText() + local msg = 'Rescue '..self.param.target.name..'\n' + + if not self.param.loadedBy then + + if self.param.target.pilot:isExist() then + local point = self.param.target.pilot:getUnit(1):getPoint() + + local lat,lon,alt = coord.LOtoLL(point) + local mgrs = coord.LLtoMGRS(coord.LOtoLL(point)) + msg = msg..'\n DDM: '.. mist.tostringLL(lat,lon,3) + msg = msg..'\n DMS: '.. mist.tostringLL(lat,lon,2,true) + msg = msg..'\n MGRS: '.. mist.tostringMGRS(mgrs, 5) + msg = msg..'\n Altitude: '..math.floor(alt)..'m'..' | '..math.floor(alt*3.280839895)..'ft' + end + end + + return msg + end + + function ObjExtractPilot:update() + if not self.isComplete and not self.isFailed then + + if self.param.loadedBy then + self.isComplete = true + return true + else + if not self.param.target.pilot:isExist() or self.param.target.remainingTime <= 0 then + self.isFailed = true + self.mission.failureReason = 'Pilot was not rescued in time, and went MIA.' + return true + end + end + + local updateFrequency = 5 -- seconds + local shouldUpdateMsg = (timer.getAbsTime() - self.param.lastUpdate) > updateFrequency + if shouldUpdateMsg then + for name, unit in pairs(self.mission.players) do + if unit and unit:isExist() then + local gr = Group.getByName(self.param.target.name) + if gr and gr:getSize() > 0 then + local un = gr:getUnit(1) + if un then + local dist = mist.utils.get3DDist(unit:getPoint(), un:getPoint()) + local m = 'Distance: ' + if dist>1000 then + local dstkm = string.format('%.2f',dist/1000) + local dstnm = string.format('%.2f',dist/1852) + + m = m..dstkm..'km | '..dstnm..'nm' + else + local dstft = math.floor(dist/0.3048) + m = m..math.floor(dist)..'m | '..dstft..'ft' + end + + m = m..'\nBearing: '..math.floor(Utils.getBearing(unit:getPoint(), un:getPoint())) + trigger.action.outTextForUnit(unit:getID(), m, updateFrequency) + end + end + end + end + + self.param.lastUpdate = timer.getAbsTime() + end + end + end + + function ObjExtractPilot:checkFail() + if not self.isComplete and not self.isFailed then + if not self.param.target.pilot:isExist() or self.param.target.remainingTime <= 0 then + self.isFailed = true + return true + end + end + end +end + +-----------------[[ END OF Objectives/ObjExtractPilot.lua ]]----------------- + + + +-----------------[[ Objectives/ObjUnloadExtractedPilotOrSquad.lua ]]----------------- + +ObjUnloadExtractedPilotOrSquad = Objective:new(Objective.types.unloaded_pilot_or_squad) +do + ObjUnloadExtractedPilotOrSquad.requiredParams = { + ['targetZone']=false, + ['extractObjective']=true, + ['unloadedAt']=false + } + + function ObjUnloadExtractedPilotOrSquad:getText() + local msg = 'Drop off personnel ' + if self.param.targetZone then + msg = msg..'at '..self.param.targetZone.name..'\n' + else + msg = msg..'at a friendly zone\n' + end + + return msg + end + + function ObjUnloadExtractedPilotOrSquad:update() + if not self.isComplete and not self.isFailed then + + if self.param.extractObjective.isComplete and self.param.unloadedAt then + if self.param.targetZone then + if self.param.unloadedAt == self.param.targetZone.name then + self.isComplete = true + return true + else + self.isFailed = true + self.mission.failureReason = 'Personnel dropped off at wrong zone.' + return true + end + else + self.isComplete = true + return true + end + end + + if self.param.extractObjective.isFailed then + self.isFailed = true + return true + end + + if self.param.targetZone and self.param.targetZone.side ~= 2 then + self.isFailed = true + self.mission.failureReason = self.param.targetZone.name..' was lost.' + return true + end + end + end + + function ObjUnloadExtractedPilotOrSquad:checkFail() + if not self.isComplete and not self.isFailed then + + if self.param.extractObjective.isFailed then + self.isFailed = true + return true + end + + if self.param.targetZone and self.param.targetZone.side ~= 2 then + self.isFailed = true + return true + end + end + end +end + +-----------------[[ END OF Objectives/ObjUnloadExtractedPilotOrSquad.lua ]]----------------- + + + +-----------------[[ Objectives/ObjPlayerCloseToZone.lua ]]----------------- + +ObjPlayerCloseToZone = Objective:new(Objective.types.player_close_to_zone) +do + ObjPlayerCloseToZone.requiredParams = { + ['target']=true, + ['range'] = true, + ['amount']= true, + ['maxAmount'] = true, + ['lastUpdate']= true + } + + function ObjPlayerCloseToZone:getText() + local msg = 'Patrol area around '..self.param.target.name + + local prg = math.floor(((self.param.maxAmount - self.param.amount)/self.param.maxAmount)*100) + msg = msg.. '\n Progress: '..prg..'%' + return msg + end + + function ObjPlayerCloseToZone:update() + if not self.isComplete and not self.isFailed then + + if self.param.target.side ~= 2 then + self.isFailed = true + self.mission.failureReason = self.param.target.name..' was lost.' + return true + end + + local plycount = Utils.getTableSize(self.mission.players) + if plycount == 0 then plycount = 1 end + local updateFrequency = 5 -- seconds + local shouldUpdateMsg = (timer.getAbsTime() - self.param.lastUpdate) > updateFrequency + for name, unit in pairs(self.mission.players) do + if unit and unit:isExist() and Utils.isInAir(unit) then + local dist = mist.utils.get2DDist(unit:getPoint(), self.param.target.zone.point) + if dist < self.param.range then + self.param.amount = self.param.amount - (1/plycount) + + if shouldUpdateMsg then + local prg = string.format('%.1f',((self.param.maxAmount - self.param.amount)/self.param.maxAmount)*100) + trigger.action.outTextForUnit(unit:getID(), '['..self.param.target.name..'] Progress: '..prg..'%', updateFrequency) + end + end + end + end + + if shouldUpdateMsg then + self.param.lastUpdate = timer.getAbsTime() + end + + if self.param.amount <= 0 then + self.isComplete = true + for name, unit in pairs(self.mission.players) do + if unit and unit:isExist() and Utils.isInAir(unit) then + trigger.action.outTextForUnit(unit:getID(), '['..self.param.target.name..'] Complete', updateFrequency) + end + end + return true + end + end + end + + function ObjPlayerCloseToZone:checkFail() + if not self.isComplete and not self.isFailed then + if self.param.target.side ~= 2 then + self.isFailed = true + end + end + end +end + +-----------------[[ END OF Objectives/ObjPlayerCloseToZone.lua ]]----------------- + + + +-----------------[[ Objectives/ObjDeploySquad.lua ]]----------------- + +ObjDeploySquad = Objective:new(Objective.types.deploy_squad) +do + ObjDeploySquad.requiredParams = { + ['squadType']=true, + ['targetZone']=true, + ['requiredZoneSide']=true, + ['unloadedType']=false, + ['unloadedAt']=false + } + + function ObjDeploySquad:getText() + local infName = PlayerLogistics.getInfantryName(self.param.squadType) + local msg = 'Deploy '..infName..' at '..self.param.targetZone.name + return msg + end + + function ObjDeploySquad:update() + if not self.isComplete and not self.isFailed then + + if self.param.unloadedType and self.param.unloadedAt then + if self.param.targetZone.name == self.param.unloadedAt then + if self.param.squadType == self.param.unloadedType then + self.isComplete = true + return true + end + end + end + + if self.param.targetZone.side ~= self.param.requiredZoneSide then + self.isFailed = true + + local side = '' + if self.param.requiredZoneSide == 0 then side = 'neutral' + elseif self.param.requiredZoneSide == 1 then side = 'controlled by Red' + elseif self.param.requiredZoneSide == 2 then side = 'controlled by Blue' + end + + self.mission.failureReason = self.param.targetZone.name..' is no longer '..side + return true + end + end + end + + function ObjDeploySquad:checkFail() + if not self.isComplete and not self.isFailed then + if self.param.targetZone.side ~= self.param.requiredZoneSide then + self.isFailed = true + return true + end + end + end +end + +-----------------[[ END OF Objectives/ObjDeploySquad.lua ]]----------------- + + + +-----------------[[ Missions/Mission.lua ]]----------------- + +Mission = {} +do + Mission.states = { + new = 'new', -- mission was just generated and is listed publicly + preping = 'preping', -- mission was accepted by a player, was delisted, and player recieved a join code that can be shared + comencing = 'comencing', -- a player that is subscribed to the mission has taken off, join code is invalidated + active = 'active', -- all players subscribed to the mission have taken off, objective can now be accomplished + completed = 'completed', -- mission objective was completed, players need to land to claim rewards + failed = 'failed' -- mission lost all players OR mission objective no longer possible to accomplish + } + + --[[ + new -> preping -> comencing -> active -> completed + | | | |-> failed + | | |->failed + | |->failed + |->failed + --]] + + Mission.types = { + cap_easy = 'cap_easy', -- fly over zn A-B-A-B-A-B OR destroy few enemy aircraft + cap_medium = 'cap_medium', -- fly over zn A-B-A-B-A-B AND destroy few enemy aircraft -- push list of aircraft within range of target zones + tarcap = 'tarcap', -- protect other mission, air kills increase reward + --tarcap = 'tarcap', -- update target mission list after all other missions are in + + cas_easy = 'cas_easy', -- destroy small amount of ground units + cas_medium = 'cas_medium', -- destroy large amount of ground units + cas_hard = 'cas_hard', -- destroy all defenses at zone A + bai = 'bai', -- destroy any enemy convoy - show "last" location of convoi (BRA or LatLon) update every 30 seconds + + sead = 'sead', -- destroy any SAM TR or SAM SR at zone A + dead = 'dead', -- destroy all SAM TR or SAM SR, or IR Guided SAM at zone A + + strike_veryeasy = 'strike_veryeasy', -- destroy 1 building + strike_easy = 'strike_easy', -- destroy any structure at zone A + strike_medium = 'strike_medium',-- destroy specific structure at zone A - show LatLon and Alt in mission description + strike_hard = 'strike_hard', -- destroy all structures at zone A and turn it neutral + deep_strike = 'deep_strike', -- destroy specific structure taken from strike queue - show LatLon and Alt in mission description + + recon_plane ='recon_plane', -- overly target zone and survive + recon_plane_deep = 'recon_plane_deep', -- overfly zone where distance from front == 2, add target to a strike queue, stays there until building exists, or 1hr passes + anti_runway = 'anti_runway', -- drop at least X anti runway bombs on runway zone (if player unit launches correct weapon, track, if agl>10m check if in zone, tally), define list of runway zones somewhere + + supply_easy = 'supply_easy', -- transfer resources to zone A(low supply) + supply_hard = 'supply_hard', -- transfer resources to zone A(low supply), high resource number + escort = 'escort', -- follow and protect friendly convoy until they get to target OR 10 minutes pass + csar = 'csar', -- extract specific pilot to friendly zone, track friendly pilots ejected + scout_helo = 'scout_helo', -- within X km, facing Y angle +-, % of enemy units in LOS progress faster + extraction = 'extraction', -- extract a deployed squad to friendly zone, generate mission if squad has extractionReady state + deploy_squad = 'deploy_squad', -- deploy squad to zone + } + + Mission.completion_type = { + any = 'any', + all = 'all' + } + + function Mission:new(id, type) + local expire = math.random(60*15, 60*30) + + local obj = { + missionID = id, + type = type, + name = '', + description = '', + failureReason = nil, + state = Mission.states.new, + expireTime = expire, + lastStateTime = timer.getAbsTime(), + objectives = {}, + completionType = Mission.completion_type.any, + rewards = {}, + players = {}, + info = {} + } + + setmetatable(obj, self) + self.__index = self + + if obj.getExpireTime then obj.expireTime = obj:getExpireTime() end + if obj.getMissionName then obj.name = obj:getMissionName() end + if obj.generateObjectives then obj:generateObjectives() end + if obj.generateRewards then obj:generateRewards() end + + return obj + end + + function Mission:updateState(newstate) + env.info('Mission - code'..self.missionID..' updateState state changed from '..self.state..' to '..newstate) + self.state = newstate + self.lastStateTime = timer.getAbsTime() + if self.state == self.states.preping then + if self.info.targetzone then + MissionTargetRegistry.addZone(self.info.targetzone.name) + end + elseif self.state == self.states.completed or self.state == self.states.failed then + if self.info.targetzone then + MissionTargetRegistry.removeZone(self.info.targetzone.name) + end + end + end + + function Mission:pushMessageToPlayers(msg, duration) + if not duration then + duration = 10 + end + + for _,un in pairs(self.players) do + if un and un:isExist() then + trigger.action.outTextForUnit(un:getID(), msg, duration) + end + end + end + + function Mission:pushSoundToPlayers(sound) + for _,un in pairs(self.players) do + if un and un:isExist() then + --trigger.action.outSoundForUnit(un:getID(), sound) -- does not work correctly in multiplayer + trigger.action.outSoundForGroup(un:getGroup():getID(), sound) + end + end + end + + function Mission:removePlayer(player) + for pl,un in pairs(self.players) do + if pl == player then + self.players[pl] = nil + break + end + end + end + + function Mission:isInstantReward() + return false + end + + function Mission:hasPlayers() + return Utils.getTableSize(self.players) > 0 + end + + function Mission:getPlayerUnit(player) + return self.players[player] + end + + function Mission:addPlayer(player, unit) + self.players[player] = unit + end + + function Mission:checkFailConditions() + if self.state == Mission.states.active then return end + + for _,obj in ipairs(self.objectives) do + local shouldBreak = obj:checkFail() + + if shouldBreak then break end + end + end + + function Mission:updateObjectives() + if self.state ~= self.states.active then return end + + for _,obj in ipairs(self.objectives) do + local shouldBreak = obj:update() + + if obj.isFailed and self.objectiveFailedCallback then self:objectiveFailedCallback(obj) end + if not obj.isFailed and obj.isComplete and self.objectiveCompletedCallback then self:objectiveCompletedCallback(obj) end + + if shouldBreak then break end + end + end + + function Mission:updateIsFailed() + self:checkFailConditions() + + local allFailed = true + for _,obj in ipairs(self.objectives) do + if self.state == Mission.states.new then + if obj.isFailed then + self:updateState(Mission.states.failed) + env.info("Mission code"..self.missionID.." objective cancelled:\n"..obj:getText()) + break + end + end + + if self.completionType == Mission.completion_type.all then + if obj.isFailed then + self:updateState(Mission.states.failed) + env.info("Mission code"..self.missionID.." (all) objective failed:\n"..obj:getText()) + break + end + end + + if not obj.isFailed then + allFailed = false + end + end + + if self.completionType == Mission.completion_type.any and allFailed then + self:updateState(Mission.states.failed) + env.info("Mission code"..self.missionID.." all objectives failed") + end + end + + function Mission:updateIsCompleted() + if self.completionType == self.completion_type.any then + for _,obj in ipairs(self.objectives) do + if obj.isComplete then + self:updateState(self.states.completed) + env.info("Mission code"..self.missionID.." (any) objective completed:\n"..obj:getText()) + break + end + end + elseif self.completionType == self.completion_type.all then + local allComplete = true + for _,obj in ipairs(self.objectives) do + if not obj.isComplete then + allComplete = false + break + end + end + + if allComplete then + self:updateState(self.states.completed) + env.info("Mission code"..self.missionID.." all objectives complete") + end + end + end + + function Mission:tallyWeapon(weapon) + for _,obj in ipairs(self.objectives) do + if not obj.isComplete and not obj.isFailed then + if obj.type == ObjBombInsideZone:getType() then + for i,v in ipairs(obj.param.targetZone:getRunwayZones()) do + if Utils.isInZone(weapon, v.name) then + if obj.param.dropped < obj.param.max then + obj.param.dropped = obj.param.dropped + 1 + if obj.param.dropped > obj.param.required then + for _,rew in ipairs(self.rewards) do + if obj.param.bonus[rew.type] then + rew.amount = rew.amount + obj.param.bonus[rew.type] + + if rew.type == PlayerTracker.statTypes.xp then + self:pushMessageToPlayers("Bonus: + "..obj.param.bonus[rew.type]..' XP') + end + end + end + end + end + break + end + end + end + end + end + end + + function Mission:tallyKill(kill) + for _,obj in ipairs(self.objectives) do + if not obj.isComplete and not obj.isFailed then + if obj.type == ObjDestroyUnitsWithAttribute:getType() then + for _,a in ipairs(obj.param.attr) do + if kill:hasAttribute(a) then + obj.param.killed = obj.param.killed + 1 + break + elseif a == 'Buildings' and ZoneCommand and ZoneCommand.staticRegistry[kill:getName()] then + obj.param.killed = obj.param.killed + 1 + break + end + end + elseif obj.type == ObjDestroyStructure:getType() then + if obj.param.target.name == kill:getName() then + obj.param.killed = true + end + elseif obj.type == ObjDestroyGroup:getType() then + if kill.getName then + if obj.param.targetUnitNames[kill:getName()] ~= nil then + obj.param.targetUnitNames[kill:getName()] = true + end + end + elseif obj.type == ObjAirKillBonus:getType() then + for _,a in ipairs(obj.param.attr) do + if kill:hasAttribute(a) then + for _,rew in ipairs(self.rewards) do + if obj.param.bonus[rew.type] then + rew.amount = rew.amount + obj.param.bonus[rew.type] + obj.param.count = obj.param.count + 1 + if rew.type == PlayerTracker.statTypes.xp then + self:pushMessageToPlayers("Reward increased: + "..obj.param.bonus[rew.type]..' XP') + end + end + end + break + elseif a == 'Buildings' and ZoneCommand and ZoneCommand.staticRegistry[kill:getName()] then + for _,rew in ipairs(self.rewards) do + if obj.param.bonus[rew.type] then + rew.amount = rew.amount + obj.param.bonus[rew.type] + obj.param.count = obj.param.count + 1 + + if rew.type == PlayerTracker.statTypes.xp then + self:pushMessageToPlayers("Reward increased: + "..obj.param.bonus[rew.type]..' XP') + end + end + end + break + end + end + elseif obj.type == ObjDestroyUnitsWithAttributeAtZone:getType() then + local zn = obj.param.tgtzone + if zn then + local validzone = false + if Utils.isInZone(kill, zn.name) then + validzone = true + else + for nm,_ in pairs(zn.built) do + local gr = Group.getByName(nm) + if gr then + for _,un in ipairs(gr:getUnits()) do + if un:getID() == kill:getID() then + validzone = true + break + end + end + end + + if validzone then break end + end + end + + if validzone then + for _,a in ipairs(obj.param.attr) do + if kill:hasAttribute(a) then + obj.param.killed = obj.param.killed + 1 + break + elseif a == 'Buildings' and ZoneCommand and ZoneCommand.staticRegistry[kill:getName()] then + obj.param.killed = obj.param.killed + 1 + break + end + end + end + end + end + end + end + end + + function Mission:isUnitTypeAllowed(unit) + return true + end + + function Mission:tallySupplies(amount, zonename) + for _,obj in ipairs(self.objectives) do + if not obj.isComplete and not obj.isFailed then + if obj.type == ObjSupplyZone:getType() then + if obj.param.tgtzone.name == zonename then + obj.param.delivered = obj.param.delivered + amount + end + end + end + end + end + + function Mission:tallyLoadPilot(player, pilot) + for _,obj in ipairs(self.objectives) do + if not obj.isComplete and not obj.isFailed then + if obj.type == ObjExtractPilot:getType() then + if obj.param.target.name == pilot.name then + obj.param.loadedBy = player + end + end + end + end + end + + function Mission:tallyUnloadPilot(player, zonename) + for _,obj in ipairs(self.objectives) do + if not obj.isComplete and not obj.isFailed then + if obj.type == ObjUnloadExtractedPilotOrSquad:getType() then + if obj.param.extractObjective.param.loadedBy == player then + obj.param.unloadedAt = zonename + end + end + end + end + end + + function Mission:tallyLoadSquad(player, squad) + for _,obj in ipairs(self.objectives) do + if not obj.isComplete and not obj.isFailed then + if obj.type == ObjExtractSquad:getType() then + if obj.param.target.name == squad.name then + obj.param.loadedBy = player + end + end + end + end + end + + function Mission:tallyUnloadSquad(player, zonename, unloadedType) + for _,obj in ipairs(self.objectives) do + if not obj.isComplete and not obj.isFailed then + if obj.type == ObjUnloadExtractedPilotOrSquad:getType() then + if obj.param.extractObjective.param.loadedBy == player and unloadedType == PlayerLogistics.infantryTypes.extractable then + obj.param.unloadedAt = zonename + end + elseif obj.type == ObjDeploySquad:getType() then + obj.param.unloadedType = unloadedType + obj.param.unloadedAt = zonename + end + end + end + end + + function Mission:getBriefDescription() + local msg = '~~~~~'..self.name..' ['..self.missionID..']~~~~~\n'..self.description..'\n' + + msg = msg..' Reward:' + + for _,r in ipairs(self.rewards) do + msg = msg..' ['..r.type..': '..r.amount..']' + end + + return msg + end + + function Mission:generateRewards() + if not self.type then return end + + local rewardDef = RewardDefinitions.missions[self.type] + + self.rewards = {} + table.insert(self.rewards, { + type = PlayerTracker.statTypes.xp, + amount = math.random(rewardDef.xp.low,rewardDef.xp.high)*50 + }) + end + + function Mission:getDetailedDescription() + local msg = '['..self.name..']' + + if self.state == Mission.states.comencing or self.state == Mission.states.preping then + msg = msg..'\nJoin code ['..self.missionID..']' + end + + msg = msg..'\nReward:' + + for _,r in ipairs(self.rewards) do + msg = msg..' ['..r.type..': '..r.amount..']' + end + msg = msg..'\n' + + if #self.objectives>1 then + msg = msg..'\nObjectives: ' + if self.completionType == Mission.completion_type.all then + msg = msg..'(Complete ALL)\n' + elseif self.completionType == Mission.completion_type.any then + msg = msg..'(Complete ONE)\n' + end + elseif #self.objectives==1 then + msg = msg..'\nObjective: \n' + end + + for i,v in ipairs(self.objectives) do + local obj = v:getText() + if v.isComplete then + obj = '[✓]'..obj + elseif v.isFailed then + obj = '[X]'..obj + else + obj = '[ ]'..obj + end + + msg = msg..'\n'..obj..'\n' + end + + msg = msg..'\nPlayers:' + for i,_ in pairs(self.players) do + msg = msg..'\n '..i + end + + return msg + end +end + +-----------------[[ END OF Missions/Mission.lua ]]----------------- + + + +-----------------[[ Missions/CAP_Easy.lua ]]----------------- + +CAP_Easy = Mission:new() +do + function CAP_Easy.canCreate() + local zoneNum = 0 + for _,zone in pairs(ZoneCommand.getAllZones()) do + if zone.side == 2 and zone.distToFront == 0 then + zoneNum = zoneNum + 1 + end + + if zoneNum >= 2 then return true end + end + end + + function CAP_Easy:getMissionName() + return 'CAP' + end + + function CAP_Easy:isUnitTypeAllowed(unit) + return unit:hasAttribute('Planes') + end + + function CAP_Easy:generateObjectives() + self.completionType = Mission.completion_type.any + local description = '' + local viableZones = {} + for _,zone in pairs(ZoneCommand.getAllZones()) do + if zone.side == 2 and zone.distToFront == 0 then + table.insert(viableZones, zone) + end + end + + if #viableZones >= 2 then + local choice1 = math.random(1,#viableZones) + local zn1 = viableZones[choice1] + + local patrol1 = ObjPlayerCloseToZone:new() + patrol1:initialize(self, { + target = zn1, + range = 20000, + amount = 15*60, + maxAmount = 15*60, + lastUpdate = 0 + }) + + table.insert(self.objectives, patrol1) + description = description..' Patrol airspace near '..zn1.name..'\n OR\n' + end + + local kills = ObjDestroyUnitsWithAttribute:new() + kills:initialize(self, { + attr = {'Planes', 'Helicopters'}, + amount = math.random(2,4), + killed = 0 + }) + + table.insert(self.objectives, kills) + description = description..' Kill '..kills.param.amount..' aircraft' + self.description = self.description..description + end +end + +-----------------[[ END OF Missions/CAP_Easy.lua ]]----------------- + + + +-----------------[[ Missions/CAP_Medium.lua ]]----------------- + +CAP_Medium = Mission:new() +do + function CAP_Medium.canCreate() + local zoneNum = 0 + for _,zone in pairs(ZoneCommand.getAllZones()) do + if zone.side == 2 and zone.distToFront == 0 then + zoneNum = zoneNum + 1 + end + + if zoneNum >= 2 then return true end + end + end + + function CAP_Medium:getMissionName() + return 'CAP' + end + + function CAP_Medium:isUnitTypeAllowed(unit) + return unit:hasAttribute('Planes') + end + + function CAP_Medium:generateObjectives() + self.completionType = Mission.completion_type.all + local description = '' + local viableZones = {} + for _,zone in pairs(ZoneCommand.getAllZones()) do + if zone.side == 2 and zone.distToFront == 0 then + table.insert(viableZones, zone) + end + end + + if #viableZones >= 2 then + local choice1 = math.random(1,#viableZones) + local zn1 = viableZones[choice1] + table.remove(viableZones,choice1) + local choice2 = math.random(1,#viableZones) + local zn2 = viableZones[choice2] + + local patrol1 = ObjPlayerCloseToZone:new() + patrol1:initialize(self, { + target = zn1, + range = 20000, + amount = 10*60, + maxAmount = 10*60, + lastUpdate = 0 + }) + + table.insert(self.objectives, patrol1) + + local patrol2 = ObjPlayerCloseToZone:new() + patrol2:initialize(self, { + target = zn2, + range = 20000, + amount = 10*60, + maxAmount = 10*60, + lastUpdate = 0 + }) + + table.insert(self.objectives, patrol2) + description = description..' Patrol airspace near '..zn1.name..' and '..zn2.name..'\n' + + local rewardDef = RewardDefinitions.missions[self.type] + + local kills = ObjAirKillBonus:new() + kills:initialize(self, { + attr = {'Planes', 'Helicopters'}, + bonus = { + [PlayerTracker.statTypes.xp] = rewardDef.xp.boost + }, + count = 0, + linkedObjectives = {patrol1, patrol2} + }) + + table.insert(self.objectives, kills) + description = description..' Aircraft kills increase reward' + end + + self.description = self.description..description + end +end + +-----------------[[ END OF Missions/CAP_Medium.lua ]]----------------- + + + +-----------------[[ Missions/CAS_Easy.lua ]]----------------- + +CAS_Easy = Mission:new() +do + function CAS_Easy.canCreate() + return true + end + + function CAS_Easy:getMissionName() + return 'CAS' + end + + function CAS_Easy:generateObjectives() + self.completionType = Mission.completion_type.all + local description = '' + + local kills = ObjDestroyUnitsWithAttribute:new() + kills:initialize(self, { + attr = {'Ground Units'}, + amount = math.random(3,6), + killed = 0 + }) + + table.insert(self.objectives, kills) + description = description..' Destroy '..kills.param.amount..' Ground Units' + self.description = self.description..description + end +end + +-----------------[[ END OF Missions/CAS_Easy.lua ]]----------------- + + + +-----------------[[ Missions/CAS_Medium.lua ]]----------------- + +CAS_Medium = Mission:new() +do + function CAS_Medium.canCreate() + return true + end + + function CAS_Medium:getMissionName() + return 'CAS' + end + + function CAS_Medium:generateObjectives() + self.completionType = Mission.completion_type.all + local description = '' + + local kills = ObjDestroyUnitsWithAttribute:new() + kills:initialize(self, { + attr = {'Ground Units'}, + amount = math.random(8,12), + killed = 0 + }) + + table.insert(self.objectives, kills) + description = description..' Destroy '..kills.param.amount..' Ground Units' + self.description = self.description..description + end +end + +-----------------[[ END OF Missions/CAS_Medium.lua ]]----------------- + + + +-----------------[[ Missions/CAS_Hard.lua ]]----------------- + +CAS_Hard = Mission:new() +do + function CAS_Hard.canCreate() + for _,zone in pairs(ZoneCommand.getAllZones()) do + if zone.side == 1 and zone.distToFront and zone.distToFront <=1 and zone:hasUnitWithAttributeOnSide({"Ground Units"}, 1, 6) then + if not MissionTargetRegistry.isZoneTargeted(zone.name) then + return true + end + end + end + end + + function CAS_Hard:getMissionName() + return 'CAS' + end + + function CAS_Hard:generateObjectives() + self.completionType = Mission.completion_type.all + local description = '' + local viableZones = {} + for _,zone in pairs(ZoneCommand.getAllZones()) do + if zone.side == 1 and zone.distToFront == 0 and zone:hasUnitWithAttributeOnSide({"Ground Units"}, 1, 6) then + if not MissionTargetRegistry.isZoneTargeted(zone.name) then + table.insert(viableZones, zone) + end + end + end + + if #viableZones == 0 then + for _,zone in pairs(ZoneCommand.getAllZones()) do + if zone.side == 1 and zone.distToFront == 1 and zone:hasUnitWithAttributeOnSide({"Ground Units"}, 1, 6) then + if not MissionTargetRegistry.isZoneTargeted(zone.name) then + table.insert(viableZones, zone) + end + end + end + end + + if #viableZones > 0 then + local choice = math.random(1,#viableZones) + local zn = viableZones[choice] + + local kill = ObjDestroyUnitsWithAttributeAtZone:new() + kill:initialize(self, { + attr = {"Ground Units"}, + amount = 1, + killed = 0, + tgtzone = zn + }) + table.insert(self.objectives, kill) + + local clear = ObjClearZoneOfUnitsWithAttribute:new() + clear:initialize(self, { + attr = {"Ground Units"}, + tgtzone = zn + }) + table.insert(self.objectives, clear) + + description = description..' Clear '..zn.name..' of ground units' + self.info = { + targetzone = zn + } + end + self.description = self.description..description + end +end + +-----------------[[ END OF Missions/CAS_Hard.lua ]]----------------- + + + +-----------------[[ Missions/SEAD.lua ]]----------------- + +SEAD = Mission:new() +do + function SEAD.canCreate() + for _,zone in pairs(ZoneCommand.getAllZones()) do + if zone.side == 1 and zone.distToFront and zone.distToFront <=1 and zone:hasSAMRadarOnSide(1) then + if not MissionTargetRegistry.isZoneTargeted(zone.name) then + return true + end + end + end + end + + function SEAD:getMissionName() + return 'SEAD' + end + + function SEAD:generateObjectives() + self.completionType = Mission.completion_type.all + local description = '' + local viableZones = {} + for _,zone in pairs(ZoneCommand.getAllZones()) do + if zone.side == 1 and zone.distToFront == 0 and zone:hasSAMRadarOnSide(1) then + if not MissionTargetRegistry.isZoneTargeted(zone.name) then + table.insert(viableZones, zone) + end + end + end + + if #viableZones == 0 then + for _,zone in pairs(ZoneCommand.getAllZones()) do + if zone.side == 1 and zone.distToFront == 1 and zone:hasSAMRadarOnSide(1) then + if not MissionTargetRegistry.isZoneTargeted(zone.name) then + table.insert(viableZones, zone) + end + end + end + end + + if #viableZones > 0 then + local choice = math.random(1,#viableZones) + local zn = viableZones[choice] + + local kill = ObjDestroyUnitsWithAttributeAtZone:new() + kill:initialize(self, { + attr = {'SAM SR','SAM TR'}, + amount = 1, + killed = 0, + tgtzone = zn + }) + + table.insert(self.objectives, kill) + description = description..' Destroy '..kill.param.amount..' Search Radar or Track Radar at '..zn.name + self.info = { + targetzone = zn + } + end + self.description = self.description..description + end +end + +-----------------[[ END OF Missions/SEAD.lua ]]----------------- + + + +-----------------[[ Missions/DEAD.lua ]]----------------- + +DEAD = Mission:new() +do + function DEAD.canCreate() + for _,zone in pairs(ZoneCommand.getAllZones()) do + if zone.side == 1 and zone.distToFront and zone.distToFront <=1 and zone:hasUnitWithAttributeOnSide({"Air Defence"}, 1, 4) then + if not MissionTargetRegistry.isZoneTargeted(zone.name) then + return true + end + end + end + end + + function DEAD:getMissionName() + return 'DEAD' + end + + function DEAD:generateObjectives() + self.completionType = Mission.completion_type.all + local description = '' + local viableZones = {} + for _,zone in pairs(ZoneCommand.getAllZones()) do + if zone.side == 1 and zone.distToFront <=1 and zone:hasUnitWithAttributeOnSide({"Air Defence"}, 1, 4) then + if not MissionTargetRegistry.isZoneTargeted(zone.name) then + table.insert(viableZones, zone) + end + end + end + + if #viableZones > 0 then + local choice = math.random(1,#viableZones) + local zn = viableZones[choice] + + local kill = ObjDestroyUnitsWithAttributeAtZone:new() + kill:initialize(self, { + attr = {"Air Defence"}, + amount = 1, + killed = 0, + tgtzone = zn + }) + table.insert(self.objectives, kill) + + local clear = ObjClearZoneOfUnitsWithAttribute:new() + clear:initialize(self, { + attr = {"Air Defence"}, + tgtzone = zn + }) + table.insert(self.objectives, clear) + + description = description..' Clear '..zn.name..' of any Air Defenses' + self.info = { + targetzone = zn + } + end + self.description = self.description..description + end +end + +-----------------[[ END OF Missions/DEAD.lua ]]----------------- + + + +-----------------[[ Missions/Supply_Easy.lua ]]----------------- + +Supply_Easy = Mission:new() +do + function Supply_Easy.canCreate() + for _,zone in pairs(ZoneCommand.getAllZones()) do + if zone.side == 2 and zone.distToFront and zone.distToFront <=1 and zone:criticalOnSupplies() then + return true + end + end + end + + function Supply_Easy:getMissionName() + return "Supply delivery" + end + + function Supply_Easy:isInstantReward() + return true + end + + function Supply_Easy:isUnitTypeAllowed(unit) + if PlayerLogistics then + local unitType = unit:getDesc()['typeName'] + return PlayerLogistics.allowedTypes[unitType] and PlayerLogistics.allowedTypes[unitType].supplies + end + end + + function Supply_Easy:generateObjectives() + self.completionType = Mission.completion_type.all + local description = '' + + local viableZones = {} + for _,zone in pairs(ZoneCommand.getAllZones()) do + if zone.side == 2 and zone.distToFront <=1 and zone:criticalOnSupplies() then + table.insert(viableZones, zone) + end + end + + if #viableZones > 0 then + local choice = math.random(1,#viableZones) + local zn = viableZones[choice] + + local deliver = ObjSupplyZone:new() + deliver:initialize(self, { + amount = math.random(2,6)*250, + delivered = 0, + tgtzone = zn + }) + + table.insert(self.objectives, deliver) + description = description..' Deliver '..deliver.param.amount..' of supplies to '..zn.name + self.info = { + targetzone = zn + } + end + + self.description = self.description..description + end +end + +-----------------[[ END OF Missions/Supply_Easy.lua ]]----------------- + + + +-----------------[[ Missions/Supply_Hard.lua ]]----------------- + +Supply_Hard = Mission:new() +do + function Supply_Hard.canCreate() + for _,zone in pairs(ZoneCommand.getAllZones()) do + if zone.side == 2 and zone.distToFront and zone.distToFront <=1 and zone:criticalOnSupplies() then + return true + end + end + end + + function Supply_Hard:getMissionName() + return "Supply delivery" + end + + function Supply_Hard:isInstantReward() + return true + end + + function Supply_Hard:isUnitTypeAllowed(unit) + if PlayerLogistics then + local unitType = unit:getDesc()['typeName'] + return PlayerLogistics.allowedTypes[unitType] and PlayerLogistics.allowedTypes[unitType].supplies + end + end + + function Supply_Hard:generateObjectives() + self.completionType = Mission.completion_type.all + local description = '' + + local viableZones = {} + for _,zone in pairs(ZoneCommand.getAllZones()) do + if zone.side == 2 and zone.distToFront == 0 and zone:criticalOnSupplies() then + table.insert(viableZones, zone) + end + end + + if #viableZones == 0 then + for _,zone in pairs(ZoneCommand.getAllZones()) do + if zone.side == 2 and zone.distToFront == 1 and zone:criticalOnSupplies() then + table.insert(viableZones, zone) + end + end + end + + if #viableZones > 0 then + local choice = math.random(1,#viableZones) + local zn = viableZones[choice] + + local deliver = ObjSupplyZone:new() + deliver:initialize(self, { + amount = math.random(18,24)*250, + delivered = 0, + tgtzone = zn + }) + + table.insert(self.objectives, deliver) + description = description..' Deliver '..deliver.param.amount..' of supplies to '..zn.name + self.info = { + targetzone = zn + } + end + + self.description = self.description..description + end +end + +-----------------[[ END OF Missions/Supply_Hard.lua ]]----------------- + + + +-----------------[[ Missions/Strike_VeryEasy.lua ]]----------------- + +Strike_VeryEasy = Mission:new() +do + function Strike_VeryEasy.canCreate() + return true + end + + function Strike_VeryEasy:getMissionName() + return 'Strike' + end + + function Strike_VeryEasy:generateObjectives() + self.completionType = Mission.completion_type.all + local description = '' + + local kills = ObjDestroyUnitsWithAttribute:new() + kills:initialize(self, { + attr = {'Buildings'}, + amount = 1, + killed = 0 + }) + + table.insert(self.objectives, kills) + description = description..' Destroy '..kills.param.amount..' building' + self.description = self.description..description + end +end + +-----------------[[ END OF Missions/Strike_VeryEasy.lua ]]----------------- + + + +-----------------[[ Missions/Strike_Easy.lua ]]----------------- + +Strike_Easy = Mission:new() +do + function Strike_Easy.canCreate() + for _,zone in pairs(ZoneCommand.getAllZones()) do + if zone.side == 1 and zone.distToFront and zone.distToFront <=1 and zone:hasUnitWithAttributeOnSide({'Buildings'}, 1) then + if not MissionTargetRegistry.isZoneTargeted(zone.name) then + return true + end + end + end + end + + function Strike_Easy:getMissionName() + return 'Strike' + end + + function Strike_Easy:generateObjectives() + self.completionType = Mission.completion_type.all + local description = '' + local viableZones = {} + for _,zone in pairs(ZoneCommand.getAllZones()) do + if zone.side == 1 and zone.distToFront == 0 and zone:hasUnitWithAttributeOnSide({'Buildings'}, 1) then + if not MissionTargetRegistry.isZoneTargeted(zone.name) then + table.insert(viableZones, zone) + end + end + end + + if #viableZones == 0 then + for _,zone in pairs(ZoneCommand.getAllZones()) do + if zone.side == 1 and zone.distToFront == 1 and zone:hasUnitWithAttributeOnSide({'Buildings'}, 1) then + if not MissionTargetRegistry.isZoneTargeted(zone.name) then + table.insert(viableZones, zone) + end + end + end + end + + if #viableZones > 0 then + local choice = math.random(1,#viableZones) + local zn = viableZones[choice] + + local kill = ObjDestroyUnitsWithAttributeAtZone:new() + kill:initialize(self, { + attr = {'Buildings'}, + amount = 1, + killed = 0, + tgtzone = zn + }) + + table.insert(self.objectives, kill) + description = description..' Destroy '..kill.param.amount..' Building at '..zn.name + self.info = { + targetzone = zn + } + end + self.description = self.description..description + end +end + +-----------------[[ END OF Missions/Strike_Easy.lua ]]----------------- + + + +-----------------[[ Missions/Strike_Medium.lua ]]----------------- + +Strike_Medium = Mission:new() +do + function Strike_Medium.canCreate() + return MissionTargetRegistry.strikeTargetsAvailable(1, false) + end + + function Strike_Medium:getMissionName() + return 'Strike' + end + + function Strike_Medium:generateObjectives() + self.completionType = Mission.completion_type.all + local description = '' + local viableZones = {} + + local tgt = MissionTargetRegistry.getRandomStrikeTarget(1, false) + + if tgt then + local chozenTarget = tgt.data + local zn = tgt.zone + + local kill = ObjDestroyStructure:new() + kill:initialize(self, { + target = chozenTarget, + tgtzone = zn, + killed = false + }) + + table.insert(self.objectives, kill) + description = description..' Destroy '..chozenTarget.display..' at '..zn.name + self.info = { + targetzone = zn + } + + MissionTargetRegistry.removeStrikeTarget(tgt) + end + self.description = self.description..description + end +end + +-----------------[[ END OF Missions/Strike_Medium.lua ]]----------------- + + + +-----------------[[ Missions/Strike_Hard.lua ]]----------------- + +Strike_Hard = Mission:new() +do + function Strike_Hard.canCreate() + for _,zone in pairs(ZoneCommand.getAllZones()) do + if zone.side == 1 and zone.distToFront and zone.distToFront <=1 and zone:hasUnitWithAttributeOnSide({"Buildings"}, 1, 3) then + if not MissionTargetRegistry.isZoneTargeted(zone.name) then + return true + end + end + end + end + + function Strike_Hard:getMissionName() + return 'Strike' + end + + function Strike_Hard:generateObjectives() + self.completionType = Mission.completion_type.all + local description = '' + local viableZones = {} + for _,zone in pairs(ZoneCommand.getAllZones()) do + if zone.side == 1 and zone.distToFront == 0 and zone:hasUnitWithAttributeOnSide({"Buildings"}, 1, 3) then + if not MissionTargetRegistry.isZoneTargeted(zone.name) then + table.insert(viableZones, zone) + end + end + end + + if #viableZones == 0 then + for _,zone in pairs(ZoneCommand.getAllZones()) do + if zone.side == 1 and zone.distToFront == 1 and zone:hasUnitWithAttributeOnSide({"Buildings"}, 1, 3) then + if not MissionTargetRegistry.isZoneTargeted(zone.name) then + table.insert(viableZones, zone) + end + end + end + end + + if #viableZones > 0 then + local choice = math.random(1,#viableZones) + local zn = viableZones[choice] + + local kill = ObjDestroyUnitsWithAttributeAtZone:new() + kill:initialize(self, { + attr = {"Buildings"}, + amount = 1, + killed = 0, + tgtzone = zn + }) + + table.insert(self.objectives, kill) + + local clear = ObjClearZoneOfUnitsWithAttribute:new() + clear:initialize(self, { + attr = {"Buildings"}, + tgtzone = zn + }) + table.insert(self.objectives, clear) + + description = description..' Destroy every structure at '..zn.name + self.info = { + targetzone = zn + } + end + self.description = self.description..description + end +end + +-----------------[[ END OF Missions/Strike_Hard.lua ]]----------------- + + + +-----------------[[ Missions/Deep_Strike.lua ]]----------------- + +Deep_Strike = Mission:new() +do + function Deep_Strike.canCreate() + return MissionTargetRegistry.strikeTargetsAvailable(1, true) + end + + function Deep_Strike:getMissionName() + return 'Deep Strike' + end + + function Deep_Strike:generateObjectives() + self.completionType = Mission.completion_type.all + local description = '' + local viableZones = {} + + local tgt = MissionTargetRegistry.getRandomStrikeTarget(1, true) + + if tgt then + local chozenTarget = tgt.data + local zn = tgt.zone + + local kill = ObjDestroyStructure:new() + kill:initialize(self, { + target = chozenTarget, + tgtzone = zn, + killed = false + }) + + table.insert(self.objectives, kill) + description = description..' Destroy '..chozenTarget.display..' at '..zn.name + self.info = { + targetzone = zn + } + + MissionTargetRegistry.removeStrikeTarget(tgt) + end + self.description = self.description..description + end +end + +-----------------[[ END OF Missions/Deep_Strike.lua ]]----------------- + + + +-----------------[[ Missions/Escort.lua ]]----------------- + +Escort = Mission:new() +do + function Escort.canCreate() + local currentTime = timer.getAbsTime() + for _,gr in pairs(ZoneCommand.groupMonitor.groups) do + if gr.product.side == 2 and gr.product.type == 'mission' and (gr.product.missionType == 'supply_convoy' or gr.product.missionType == 'assault') then + local z = gr.target + if z.distToFront == 0 and z.side~= 2 then + if gr.state == nil or gr.state == 'started' or (gr.state == 'enroute' and (currentTime - gr.lastStateTime < 7*60)) then + return true + end + end + end + end + end + + function Escort:getMissionName() + return "Escort convoy" + end + + function Escort:isUnitTypeAllowed(unit) + return unit:hasAttribute('Helicopters') + end + + function Escort:generateObjectives() + self.completionType = Mission.completion_type.all + local description = '' + + local currentTime = timer.getAbsTime() + local viableConvoys = {} + for _,gr in pairs(ZoneCommand.groupMonitor.groups) do + if gr.product.side == 2 and gr.product.type == 'mission' and (gr.product.missionType == 'supply_convoy' or gr.product.missionType == 'assault') then + local z = gr.target + if z.distToFront == 0 and z.side ~= 2 then + if gr.state == nil or gr.state == 'started' or (gr.state == 'enroute' and (currentTime - gr.lastStateTime < 7*60)) then + table.insert(viableConvoys, gr) + end + end + end + end + + if #viableConvoys > 0 then + local choice = math.random(1,#viableConvoys) + local convoy = viableConvoys[choice] + + local escort = ObjEscortGroup:new() + escort:initialize(self, { + maxAmount = 60*7, + amount = 60*7, + proxDist = 400, + target = convoy, + lastUpdate = timer.getAbsTime() + }) + + table.insert(self.objectives, escort) + + local nearzone = "" + local gr = Group.getByName(convoy.name) + if gr and gr:getSize()>0 then + local un = gr:getUnit(1) + local closest = ZoneCommand.getClosestZoneToPoint(un:getPoint()) + if closest then + nearzone = ' near '..closest.name..'' + end + end + + description = description..' Escort convoy'..nearzone..' on route to their destination' + --description = description..'\n Target will be assigned after accepting mission' + + end + + self.description = self.description..description + end +end + +-----------------[[ END OF Missions/Escort.lua ]]----------------- + + + +-----------------[[ Missions/TARCAP.lua ]]----------------- + +TARCAP = Mission:new() +do + TARCAP.relevantMissions = { + Mission.types.cas_hard, + Mission.types.dead, + Mission.types.sead, + Mission.types.strike_easy, + Mission.types.strike_hard + } + + function TARCAP:new(id, type, activeMissions) + self = Mission.new(self, id, type) + self:generateObjectivesOverload(activeMissions) + return self + end + + function TARCAP.canCreate(activeMissions) + for _,mis in pairs(activeMissions) do + for _,tp in ipairs(TARCAP.relevantMissions) do + if mis.type == tp then return true end + end + end + end + + function TARCAP:getMissionName() + return 'TARCAP' + end + + function TARCAP:isUnitTypeAllowed(unit) + return unit:hasAttribute('Planes') + end + + function TARCAP:generateObjectivesOverload(activeMissions) + self.completionType = Mission.completion_type.any + local description = '' + local viableMissions = {} + for _,mis in pairs(activeMissions) do + for _,tp in ipairs(TARCAP.relevantMissions) do + if mis.type == tp then + table.insert(viableMissions, mis) + break + end + end + end + + if #viableMissions >= 1 then + local choice = math.random(1,#viableMissions) + local mis = viableMissions[choice] + + local protect = ObjProtectMission:new() + protect:initialize(self, { + mis = mis + }) + + table.insert(self.objectives, protect) + description = description..' Prevent enemy aircraft from interfering with friendly '..mis:getMissionName()..' mission' + if mis.info and mis.info.targetzone then + description = description..' at '..mis.info.targetzone.name + end + + local rewardDef = RewardDefinitions.missions[self.type] + + local kills = ObjAirKillBonus:new() + kills:initialize(self, { + attr = {'Planes'}, + bonus = { + [PlayerTracker.statTypes.xp] = rewardDef.xp.boost + }, + count = 0, + linkedObjectives = {protect} + }) + + table.insert(self.objectives, kills) + + description = description..'\n Aircraft kills increase reward' + end + + self.description = self.description..description + end +end + +-----------------[[ END OF Missions/TARCAP.lua ]]----------------- + + + +-----------------[[ Missions/Recon_Plane.lua ]]----------------- + +Recon_Plane = Mission:new() +do + function Recon_Plane.canCreate() + local zoneNum = 0 + for _,zone in pairs(ZoneCommand.getAllZones()) do + if zone.side == 1 and zone.distToFront == 0 then + return true + end + end + end + + function Recon_Plane:getMissionName() + return 'Recon' + end + + function Recon_Plane:isUnitTypeAllowed(unit) + return unit:hasAttribute('Planes') + end + + function Recon_Plane:generateObjectives() + self.completionType = Mission.completion_type.any + local description = '' + local viableZones = {} + local secondaryViableZones = {} + for _,zone in pairs(ZoneCommand.getAllZones()) do + if zone.side == 1 and zone.distToFront == 0 then + if zone.revealTime <= 60*5 then + table.insert(viableZones, zone) + else + table.insert(secondaryViableZones, zone) + end + end + end + + if #viableZones == 0 then + viableZones = secondaryViableZones + end + + + if #viableZones > 0 then + local choice1 = math.random(1,#viableZones) + local zn1 = viableZones[choice1] + + local recon = ObjFlyToZoneSequence:new() + recon:initialize(self,{ + waypoints = { + [1] = {zone = zn1, complete = false} + }, + failZones = { + [1] = {zn1} + } + }) + + table.insert(self.objectives, recon) + description = description..' Overfly '..zn1.name..'\n' + end + + self.description = self.description..description + end + + function Recon_Plane:objectiveCompletedCallback(objective) + if objective.type == ObjFlyToZoneSequence:getType() then + local firstWP = objective.param.waypoints[1] + + if firstWP and firstWP.zone:hasUnitWithAttributeOnSide({'Buildings'}, 1) then + local tgt = firstWP.zone:getRandomUnitWithAttributeOnSide({'Buildings'}, 1) + if tgt then + MissionTargetRegistry.addStrikeTarget(tgt, firstWP.zone, true) + self:pushMessageToPlayers(tgt.display..' discovered at '..firstWP.zone.name) + firstWP.zone:reveal() + end + end + end + end +end + +-----------------[[ END OF Missions/Recon_Plane.lua ]]----------------- + + + +-----------------[[ Missions/Deep_Recon_Plane.lua ]]----------------- + +Deep_Recon_Plane = Mission:new() +do + function Deep_Recon_Plane.canCreate() + local zoneNum = 0 + for _,zone in pairs(ZoneCommand.getAllZones()) do + if zone.side == 1 and zone.distToFront == 2 then + return true + end + end + end + + function Deep_Recon_Plane:getMissionName() + return 'Deep Recon' + end + + function Deep_Recon_Plane:isUnitTypeAllowed(unit) + return unit:hasAttribute('Planes') + end + + function Deep_Recon_Plane:generateObjectives() + self.completionType = Mission.completion_type.any + local description = '' + local viableZones = {} + local secondaryViableZones = {} + for _,zone in pairs(ZoneCommand.getAllZones()) do + if zone.side == 1 and zone.distToFront == 2 then + if zone.revealTime <= 60*5 then + table.insert(viableZones, zone) + else + table.insert(secondaryViableZones, zone) + end + end + end + + if #viableZones == 0 then + viableZones = secondaryViableZones + end + + if #viableZones > 0 then + local choice1 = math.random(1,#viableZones) + local zn1 = viableZones[choice1] + + local recon = ObjFlyToZoneSequence:new() + recon:initialize(self, { + waypoints = { + [1] = {zone = zn1, complete = false} + }, + failZones = { + [1] = {zn1} + } + }) + + table.insert(self.objectives, recon) + description = description..' Overfly '..zn1.name..'\n' + end + + self.description = self.description..description + end + + function Deep_Recon_Plane:objectiveCompletedCallback(objective) + if objective.type == ObjFlyToZoneSequence:getType() then + local firstWP = objective.param.waypoints[1] + + if firstWP and firstWP.zone:hasUnitWithAttributeOnSide({'Buildings'}, 1) then + local tgt = firstWP.zone:getRandomUnitWithAttributeOnSide({'Buildings'}, 1) + if tgt then + MissionTargetRegistry.addStrikeTarget(tgt, firstWP.zone, true) + self:pushMessageToPlayers(tgt.display..' discovered at '..firstWP.zone.name) + firstWP.zone:reveal() + end + end + end + end +end + +-----------------[[ END OF Missions/Deep_Recon_Plane.lua ]]----------------- + + + +-----------------[[ Missions/Scout_Helo.lua ]]----------------- + +Scout_Helo = Mission:new() +do + function Scout_Helo.canCreate() + local zoneNum = 0 + for _,zone in pairs(ZoneCommand.getAllZones()) do + if zone.side == 1 and zone.distToFront == 0 then + return true + end + end + end + + function Scout_Helo:getMissionName() + return 'Recon' + end + + function Scout_Helo:isUnitTypeAllowed(unit) + return unit:hasAttribute('Helicopters') + end + + function Scout_Helo:generateObjectives() + self.completionType = Mission.completion_type.any + local description = '' + local viableZones = {} + local secondaryViableZones = {} + for _,zone in pairs(ZoneCommand.getAllZones()) do + if zone.side == 1 and zone.distToFront == 0 then + if zone.revealTime <= 60*5 then + table.insert(viableZones, zone) + else + table.insert(secondaryViableZones, zone) + end + end + end + + if #viableZones == 0 then + viableZones = secondaryViableZones + end + + if #viableZones > 0 then + local choice1 = math.random(1,#viableZones) + local zn1 = viableZones[choice1] + + local recon = ObjReconZone:new() + recon:initialize(self, { + target = zn1, + maxAmount = 60*1.5, + amount = 60*1.5, + allowedDeviation = 20, + proxDist = 10000, + lastUpdate = timer.getAbsTime(), + failZones = { + [1] = {zn1} + } + }) + + table.insert(self.objectives, recon) + description = description..' Observe enemies at '..zn1.name..'\n' + end + + self.description = self.description..description + end + + function Scout_Helo:objectiveCompletedCallback(objective) + if objective.type == ObjReconZone:getType() then + if objective.param.target:hasUnitWithAttributeOnSide({'Buildings'}, 1) then + local tgt = objective.param.target:getRandomUnitWithAttributeOnSide({'Buildings'}, 1) + if tgt then + MissionTargetRegistry.addStrikeTarget(tgt, objective.param.target, false) + self:pushMessageToPlayers(tgt.display..' discovered at '..objective.param.target.name) + objective.param.target:reveal() + end + end + end + end +end + +-----------------[[ END OF Missions/Scout_Helo.lua ]]----------------- + + + +-----------------[[ Missions/BAI.lua ]]----------------- + +BAI = Mission:new() +do + function BAI.canCreate() + return MissionTargetRegistry.baiTargetsAvailable(1) + end + + function BAI:getMissionName() + return 'BAI' + end + + function BAI:generateObjectives() + self.completionType = Mission.completion_type.all + local description = '' + local viableZones = {} + + local tgt = MissionTargetRegistry.getRandomBaiTarget(1) + + if tgt then + + local gr = Group.getByName(tgt.name) + if gr and gr:getSize()>0 then + local units = {} + for i,v in ipairs(gr:getUnits()) do + units[v:getName()] = false + end + + local kill = ObjDestroyGroup:new() + kill:initialize(self, { + target = tgt, + targetUnitNames = units, + lastUpdate = timer.getAbsTime() + }) + + table.insert(self.objectives, kill) + + local nearzone = "" + local un = gr:getUnit(1) + local closest = ZoneCommand.getClosestZoneToPoint(un:getPoint()) + if closest then + nearzone = ' near '..closest.name..'' + end + + description = description..' Destroy '..tgt.product.display..nearzone..' before it reaches its destination.' + end + + MissionTargetRegistry.removeBaiTarget(tgt) + end + self.description = self.description..description + end +end + +-----------------[[ END OF Missions/BAI.lua ]]----------------- + + + +-----------------[[ Missions/Anti_Runway.lua ]]----------------- + +Anti_Runway = Mission:new() +do + function Anti_Runway.canCreate() + for _,zone in pairs(ZoneCommand.getAllZones()) do + if zone.side == 1 and zone.distToFront <=2 and zone:hasRunway() then + return true + end + end + end + + function Anti_Runway:getMissionName() + return 'Runway Attack' + end + + function Anti_Runway:generateObjectives() + self.completionType = Mission.completion_type.all + local description = '' + local viableZones = {} + + local tgts = {} + for _,zone in pairs(ZoneCommand.getAllZones()) do + if zone.side == 1 and zone.distToFront <=2 and zone:hasRunway() then + if not MissionTargetRegistry.isZoneTargeted(zone.name) then + table.insert(tgts, zone) + end + end + end + + if #tgts > 0 then + local tgt = tgts[math.random(1,#tgts)] + + local rewardDef = RewardDefinitions.missions[self.type] + + local bomb = ObjBombInsideZone:new() + bomb:initialize(self,{ + targetZone = tgt, + max = 20, + required = 5, + dropped = 0, + isFinishStarted = false, + bonus = { + [PlayerTracker.statTypes.xp] = rewardDef.xp.boost + } + }) + + table.insert(self.objectives, bomb) + description = description..' Bomb runway at '..bomb.param.targetZone.name + end + self.description = self.description..description + end +end + +-----------------[[ END OF Missions/Anti_Runway.lua ]]----------------- + + + +-----------------[[ Missions/CSAR.lua ]]----------------- + +CSAR = Mission:new() +do + function CSAR.canCreate() + return MissionTargetRegistry.pilotsAvailableToExtract() + end + + function CSAR:getMissionName() + return 'CSAR' + end + + function CSAR:isInstantReward() + return true + end + + function CSAR:isUnitTypeAllowed(unit) + if PlayerLogistics then + local unitType = unit:getDesc()['typeName'] + return PlayerLogistics.allowedTypes[unitType] and PlayerLogistics.allowedTypes[unitType].personCapacity and PlayerLogistics.allowedTypes[unitType].personCapacity > 0 + end + end + + function CSAR:generateObjectives() + self.completionType = Mission.completion_type.all + local description = '' + + if MissionTargetRegistry.pilotsAvailableToExtract() then + local tgt = MissionTargetRegistry.getRandomPilot() + + local extract = ObjExtractPilot:new() + extract:initialize(self, { + target = tgt, + loadedBy = nil, + lastUpdate = timer.getAbsTime() + }) + table.insert(self.objectives, extract) + + local unload = ObjUnloadExtractedPilotOrSquad:new() + unload:initialize(self, { + extractObjective = extract + }) + table.insert(self.objectives, unload) + + local nearzone = '' + local closest = ZoneCommand.getClosestZoneToPoint(tgt.pilot:getUnit(1):getPoint()) + if closest then + nearzone = ' near '..closest.name..'' + end + + description = description..' Rescue '..tgt.name..nearzone..' and deliver them to a friendly zone' + --local mgrs = coord.LLtoMGRS(coord.LOtoLL(tgt.pilot:getUnit(1):getPoint())) + --local grid = mist.tostringMGRS(mgrs, 2):gsub(' ','') + --description = description..' ['..grid..']' + end + self.description = self.description..description + end +end + +-----------------[[ END OF Missions/CSAR.lua ]]----------------- + + + +-----------------[[ Missions/Extraction.lua ]]----------------- + +Extraction = Mission:new() +do + function Extraction.canCreate() + return MissionTargetRegistry.squadsReadyToExtract() + end + + function Extraction:getMissionName() + return 'Extraction' + end + + function Extraction:isInstantReward() + return true + end + + function Extraction:isUnitTypeAllowed(unit) + if PlayerLogistics then + local unitType = unit:getDesc()['typeName'] + return PlayerLogistics.allowedTypes[unitType] and PlayerLogistics.allowedTypes[unitType].personCapacity + end + end + + function Extraction:generateObjectives() + self.completionType = Mission.completion_type.all + local description = '' + + if MissionTargetRegistry.squadsReadyToExtract() then + local tgt = MissionTargetRegistry.getRandomSquad() + if tgt then + local extract = ObjExtractSquad:new() + extract:initialize(self, { + target = tgt, + loadedBy = nil, + lastUpdate = timer.getAbsTime() + }) + table.insert(self.objectives, extract) + + local unload = ObjUnloadExtractedPilotOrSquad:new() + unload:initialize(self, { + extractObjective = extract + }) + table.insert(self.objectives, unload) + + local infName = PlayerLogistics.getInfantryName(tgt.data.type) + + + local nearzone = '' + local gr = Group.getByName(tgt.name) + if gr and gr:isExist() and gr:getSize()>0 then + local un = gr:getUnit(1) + local closest = ZoneCommand.getClosestZoneToPoint(un:getPoint()) + if closest then + nearzone = ' near '..closest.name..'' + end + --local mgrs = coord.LLtoMGRS(coord.LOtoLL(un:getPoint())) + --local grid = mist.tostringMGRS(mgrs, 2):gsub(' ','') + --description = description..' ['..grid..']' + end + + description = description..' Extract '..infName..nearzone..' to a friendly zone' + end + end + self.description = self.description..description + end +end + +-----------------[[ END OF Missions/Extraction.lua ]]----------------- + + + +-----------------[[ Missions/DeploySquad.lua ]]----------------- + +DeploySquad = Mission:new() +do + function DeploySquad.canCreate() + for _,zone in pairs(ZoneCommand.getAllZones()) do + if zone.distToFront and zone.distToFront == 0 then + if not MissionTargetRegistry.isZoneTargeted(zone.name) then + return true + end + end + end + end + + function DeploySquad:getMissionName() + return 'Deploy infantry' + end + + function DeploySquad:isInstantReward() + local friendlyDeployments = { + [PlayerLogistics.infantryTypes.engineer] = true, + } + + if self.objectives and self.objectives[1] then + local sqType = self.objectives[1].param.squadType + if friendlyDeployments[sqType] then + return true + end + end + + return false + end + + function DeploySquad:isUnitTypeAllowed(unit) + if PlayerLogistics then + local unitType = unit:getDesc()['typeName'] + return PlayerLogistics.allowedTypes[unitType] and PlayerLogistics.allowedTypes[unitType].personCapacity + end + end + + function DeploySquad:generateObjectives() + self.completionType = Mission.completion_type.all + local description = '' + + local viableZones = {} + for _,zone in pairs(ZoneCommand.getAllZones()) do + if zone.distToFront and zone.distToFront == 0 then + if not MissionTargetRegistry.isZoneTargeted(zone.name) then + table.insert(viableZones, zone) + end + end + end + + if #viableZones > 0 then + local tgt = viableZones[math.random(1,#viableZones)] + if tgt then + local squadType = nil + + if tgt.side == 0 then + squadType = PlayerLogistics.infantryTypes.capture + elseif tgt.side == 1 then + if math.random()>0.5 then + squadType = PlayerLogistics.infantryTypes.sabotage + else + squadType = PlayerLogistics.infantryTypes.spy + end + elseif tgt.side == 2 then + squadType = PlayerLogistics.infantryTypes.engineer + end + + local deploy = ObjDeploySquad:new() + deploy:initialize(self, { + squadType = squadType, + targetZone = tgt, + requiredZoneSide = tgt.side, + unloadedType = nil, + unloadedAt = nil + }) + table.insert(self.objectives, deploy) + + local infName = PlayerLogistics.getInfantryName(squadType) + + description = description..' Deploy '..infName..' to '..tgt.name + + self.info = { + targetzone = tgt + } + end + end + self.description = self.description..description + end +end + +-----------------[[ END OF Missions/DeploySquad.lua ]]----------------- + + + +-----------------[[ RewardDefinitions.lua ]]----------------- + +RewardDefinitions = {} + +do + RewardDefinitions.missions = { + [Mission.types.cap_easy] = { xp = { low = 10, high = 20, boost = 0 } }, + [Mission.types.cap_medium] = { xp = { low = 10, high = 20, boost = 100 } }, + [Mission.types.tarcap] = { xp = { low = 10, high = 10, boost = 150 } }, + [Mission.types.cas_easy] = { xp = { low = 10, high = 20, boost = 0 } }, + [Mission.types.cas_medium] = { xp = { low = 20, high = 30, boost = 0 } }, + [Mission.types.cas_hard] = { xp = { low = 30, high = 40, boost = 0 } }, + [Mission.types.bai] = { xp = { low = 20, high = 30, boost = 0 } }, + [Mission.types.sead] = { xp = { low = 10, high = 20, boost = 0 } }, + [Mission.types.dead] = { xp = { low = 30, high = 40, boost = 0 } }, + [Mission.types.strike_veryeasy] = { xp = { low = 5, high = 10, boost = 0 } }, + [Mission.types.strike_easy] = { xp = { low = 10, high = 20, boost = 0 } }, + [Mission.types.strike_medium] = { xp = { low = 20, high = 30, boost = 0 } }, + [Mission.types.strike_hard] = { xp = { low = 30, high = 40, boost = 0 } }, + [Mission.types.deep_strike] = { xp = { low = 30, high = 40, boost = 0 } }, + [Mission.types.recon_plane] = { xp = { low = 20, high = 30, boost = 0 } }, + [Mission.types.recon_plane_deep]= { xp = { low = 30, high = 40, boost = 0 } }, + [Mission.types.anti_runway] = { xp = { low = 20, high = 30, boost = 25 } }, + [Mission.types.supply_easy] = { xp = { low = 10, high = 20, boost = 0 } }, + [Mission.types.supply_hard] = { xp = { low = 20, high = 30, boost = 0 } }, + [Mission.types.escort] = { xp = { low = 20, high = 30, boost = 0 } }, + [Mission.types.scout_helo] = { xp = { low = 20, high = 30, boost = 0 } }, + [Mission.types.csar] = { xp = { low = 20, high = 30, boost = 0 } }, + [Mission.types.extraction] = { xp = { low = 20, high = 30, boost = 0 } }, + [Mission.types.deploy_squad] = { xp = { low = 20, high = 30, boost = 0 } } + } + + RewardDefinitions.actions = { + pilotExtract = 100, + squadDeploy = 150, + squadExtract = 150, + supplyRatio = 0.06, + supplyBoost = 0.5 + } +end + +-----------------[[ END OF RewardDefinitions.lua ]]----------------- + + + +-----------------[[ MissionTracker.lua ]]----------------- + +MissionTracker = {} +do + MissionTracker.maxMissionCount = { + [Mission.types.cap_easy] = 1, + [Mission.types.cap_medium] = 1, + [Mission.types.cas_easy] = 1, + [Mission.types.cas_medium] = 1, + [Mission.types.cas_hard] = 1, + [Mission.types.sead] = 1, + [Mission.types.supply_easy] = 1, + [Mission.types.supply_hard] = 1, + [Mission.types.strike_veryeasy] = 1, + [Mission.types.strike_easy] = 1, + [Mission.types.strike_medium] = 3, + [Mission.types.strike_hard] = 1, + [Mission.types.dead] = 1, + [Mission.types.escort] = 1, + [Mission.types.tarcap] = 1, + [Mission.types.recon_plane] = 1, + [Mission.types.recon_plane_deep] = 1, + [Mission.types.deep_strike] = 3, + [Mission.types.scout_helo] = 1, + [Mission.types.bai] = 1, + [Mission.types.anti_runway] = 1, + [Mission.types.csar] = 1, + [Mission.types.extraction] = 1, + [Mission.types.deploy_squad] = 1, + } + + if Config.missions then + for i,v in pairs(Config.missions) do + if MissionTracker.maxMissionCount[i] then + MissionTracker.maxMissionCount[i] = v + end + end + end + + MissionTracker.missionBoardSize = 10 + + function MissionTracker:new(playerTracker, markerCommands) + local obj = {} + obj.playerTracker = playerTracker + obj.markerCommands = markerCommands + obj.groupMenus = {} + obj.missionIDPool = {} + obj.missionBoard = {} + obj.activeMissions = {} + + setmetatable(obj, self) + self.__index = self + + obj.markerCommands:addCommand('list', function(event, _, state) + if event.initiator then + state:printMissionBoard(event.initiator:getID(), nil, event.initiator:getGroup():getName()) + elseif world.getPlayer() then + local unit = world.getPlayer() + state:printMissionBoard(unit:getID(), nil, event.initiator:getGroup():getName()) + end + return true + end, nil, obj) + + obj.markerCommands:addCommand('help', function(event, _, state) + if event.initiator then + state:printHelp(event.initiator:getID()) + elseif world.getPlayer() then + local unit = world.getPlayer() + state:printHelp(unit:getID()) + end + return true + end, nil, obj) + + obj.markerCommands:addCommand('active', function(event, _, state) + if event.initiator then + state:printActiveMission(event.initiator:getID(), nil, event.initiator:getPlayerName()) + elseif world.getPlayer() then + state:printActiveMission(nil, nil, world.getPlayer():getPlayerName()) + end + return true + end, nil, obj) + + obj.markerCommands:addCommand('accept',function(event, code, state) + local numcode = tonumber(code) + if not numcode or numcode<1000 or numcode > 9999 then return false end + + local player = '' + local unit = nil + if event.initiator then + player = event.initiator:getPlayerName() + unit = event.initiator + elseif world.getPlayer() then + player = world.getPlayer():getPlayerName() + unit = world.getPlayer() + end + + return state:activateMission(numcode, player, unit) + end, true, obj) + + obj.markerCommands:addCommand('join',function(event, code, state) + local numcode = tonumber(code) + if not numcode or numcode<1000 or numcode > 9999 then return false end + + local player = '' + local unit = nil + if event.initiator then + player = event.initiator:getPlayerName() + unit = event.initiator + elseif world.getPlayer() then + player = world.getPlayer():getPlayerName() + unit = world.getPlayer() + end + + return state:joinMission(numcode, player, unit) + end, true, obj) + + obj.markerCommands:addCommand('leave',function(event, _, state) + local player = '' + if event.initiator then + player = event.initiator:getPlayerName() + elseif world.getPlayer() then + player = world.getPlayer():getPlayerName() + end + + return state:leaveMission(player) + end, nil, obj) + + obj:menuSetup() + obj:start() + return obj + end + + function MissionTracker:menuSetup() + MenuRegistry:register(2, function(event, context) + if event.id == world.event.S_EVENT_BIRTH and event.initiator and event.initiator.getPlayerName then + local player = event.initiator:getPlayerName() + if player then + local groupid = event.initiator:getGroup():getID() + local groupname = event.initiator:getGroup():getName() + + if context.groupMenus[groupid] then + missionCommands.removeItemForGroup(groupid, context.groupMenus[groupid]) + context.groupMenus[groupid] = nil + end + + if not context.groupMenus[groupid] then + local menu = missionCommands.addSubMenuForGroup(groupid, 'Missions') + missionCommands.addCommandForGroup(groupid, 'List Missions', menu, Utils.log(context.printMissionBoard), context, nil, groupid, groupname) + missionCommands.addCommandForGroup(groupid, 'Active Mission', menu, Utils.log(context.printActiveMission), context, nil, groupid, nil, groupname) + + local dial = missionCommands.addSubMenuForGroup(groupid, 'Dial Code', menu) + for i1=1,5,1 do + local digit1 = missionCommands.addSubMenuForGroup(groupid, i1..'___', dial) + for i2=1,5,1 do + local digit2 = missionCommands.addSubMenuForGroup(groupid, i1..i2..'__', digit1) + for i3=1,5,1 do + local digit3 = missionCommands.addSubMenuForGroup(groupid, i1..i2..i3..'_', digit2) + for i4=1,5,1 do + local code = tonumber(i1..i2..i3..i4) + local digit4 = missionCommands.addCommandForGroup(groupid, i1..i2..i3..i4, digit3, Utils.log(context.activateOrJoinMissionForGroup), context, code, groupname) + end + end + end + end + + local leavemenu = missionCommands.addSubMenuForGroup(groupid, 'Leave Mission', menu) + missionCommands.addCommandForGroup(groupid, 'Confirm to leave mission', leavemenu, Utils.log(context.leaveMission), context, player) + missionCommands.addCommandForGroup(groupid, 'Cancel', leavemenu, function() end) + + missionCommands.addCommandForGroup(groupid, 'Help', menu, Utils.log(context.printHelp), context, nil, groupid) + + context.groupMenus[groupid] = menu + end + end + elseif (event.id == world.event.S_EVENT_PLAYER_LEAVE_UNIT or event.id == world.event.S_EVENT_DEAD) and event.initiator and event.initiator.getPlayerName then + local player = event.initiator:getPlayerName() + if player then + local groupid = event.initiator:getGroup():getID() + + if context.groupMenus[groupid] then + missionCommands.removeItemForGroup(groupid, context.groupMenus[groupid]) + context.groupMenus[groupid] = nil + end + end + end + end, self) + end + + function MissionTracker:printHelp(unitid, groupid) + local msg = 'Missions can only be accepted or joined while landed at a friendly zone.\n' + msg = msg.. 'Rewards from mission completion need to be claimed by landing at a friendly zone.\n\n' + msg = msg.. 'Accept mission:\n' + msg = msg.. ' Each mission has a 4 digit code listed next to its name.\n To accept a mission, either dial its code from the mission radio menu,\n or create a marker on the map and set its text to:\n' + msg = msg.. ' accept:code\n' + msg = msg.. ' (ex. accept:4126)\n\n' + msg = msg.. 'Join mission:\n' + msg = msg.. ' You can team up with other players, by joining a mission they already accepted.\n' + msg = msg.. ' Missions can only be joined if all players who are already part of that mission\n have not taken off yet.\n' + msg = msg.. ' When a mission is completed each player has to land to claim their reward individually.\n' + msg = msg.. ' To join a mission, ask for the join code from a player who is already part of the mission,\n dial it in from the mission radio menu,\n or create a marker on the map and set its text to:\n' + msg = msg.. ' join:code\n' + msg = msg.. ' (ex. join:4126)\n\n' + msg = msg.. 'Map marker commands:\n' + msg = msg.. ' list - displays mission board\n' + msg = msg.. ' accept:code - accepts mission with corresponding code\n' + msg = msg.. ' join:code - joins other players mission with corresponding code\n' + msg = msg.. ' active - displays active mission\n' + msg = msg.. ' leave - leaves active mission\n' + msg = msg.. ' help - displays this message' + + if unitid then + trigger.action.outTextForUnit(unitid, msg, 30) + elseif groupid then + trigger.action.outTextForGroup(groupid, msg, 30) + else + --trigger.action.outText(msg, 30) + end + end + + function MissionTracker:printActiveMission(unitid, groupid, playername, groupname) + if not playername and groupname then + env.info('MissionTracker - printActiveMission: '..tostring(groupname)..' requested group print.') + local gr = Group.getByName(groupname) + for i,v in ipairs(gr:getUnits()) do + if v.getPlayerName and v:getPlayerName() then + self:printActiveMission(v:getID(), gr:getID(), v:getPlayerName()) + end + end + return + end + + local mis = nil + for i,v in pairs(self.activeMissions) do + for pl,un in pairs(v.players) do + if pl == playername then + mis = v + break + end + end + + if mis then break end + end + + local msg = '' + if mis then + msg = mis:getDetailedDescription() + else + msg = 'No active mission' + end + + if unitid then + trigger.action.outTextForUnit(unitid, msg, 30) + elseif groupid then + trigger.action.outTextForGroup(groupid, msg, 30) + else + --trigger.action.outText(msg, 30) + end + end + + function MissionTracker:printMissionBoard(unitid, groupid, groupname) + local gr = Group.getByName(groupname) + local un = gr:getUnit(1) + + local msg = 'Mission Board\n' + local empty = true + local invalidCount = 0 + for i,v in pairs(self.missionBoard) do + if v:isUnitTypeAllowed(un) then + empty = false + msg = msg..'\n'..v:getBriefDescription()..'\n' + else + invalidCount = invalidCount + 1 + end + end + + if empty then + msg = msg..'\n No missions available' + end + + if invalidCount > 0 then + msg = msg..'\n'..invalidCount..' additional missions are not compatible with current aircraft\n' + end + + if unitid then + trigger.action.outTextForUnit(unitid, msg, 30) + elseif groupid then + trigger.action.outTextForGroup(groupid, msg, 30) + else + --trigger.action.outText(msg, 30) + end + end + + function MissionTracker:getNewMissionID() + if #self.missionIDPool == 0 then + for i=1111,5555,1 do + if not tostring(i):find('[06789]') then + if not self.missionBoard[i] and not self.activeMissions[i] then + table.insert(self.missionIDPool, i) + end + end + end + end + + local choice = math.random(1,#self.missionIDPool) + local newId = self.missionIDPool[choice] + table.remove(self.missionIDPool,choice) + return newId + end + + function MissionTracker:start() + timer.scheduleFunction(function(param, time) + for code,mis in pairs(param.missionBoard) do + if timer.getAbsTime() - mis.lastStateTime > mis.expireTime then + param.missionBoard[code].state = Mission.states.failed + param.missionBoard[code] = nil + env.info('Mission code'..code..' expired.') + else + mis:updateIsFailed() + if mis.state == Mission.states.failed then + param.missionBoard[code]=nil + env.info('Mission code'..code..' canceled due to objectives failed') + trigger.action.outTextForCoalition(2,'Mission ['..mis.missionID..'] '..mis.name..' was cancelled',5) + end + end + end + + local misCount = Utils.getTableSize(param.missionBoard) + local toGen = MissionTracker.missionBoardSize-misCount + if toGen > 0 then + local validMissions = {} + for _,v in pairs(Mission.types) do + if self:canCreateMission(v) then + table.insert(validMissions,v) + end + end + + if #validMissions > 0 then + for i=1,toGen,1 do + if #validMissions > 0 then + local choice = math.random(1,#validMissions) + local misType = validMissions[choice] + table.remove(validMissions, choice) + param:generateMission(misType) + else + break + end + end + end + end + + return time+1 + end, self, timer.getTime()+1) + + timer.scheduleFunction(function(param, time) + for code,mis in pairs(param.activeMissions) do + -- check if players exist and in same unit as when joined + -- remove from mission if false + for pl,un in pairs(mis.players) do + if not un or + not un:isExist() then + + mis:removePlayer(pl) + env.info('Mission code'..code..' removing player '..pl..', unit no longer exists') + end + end + + -- check if mission has 0 players, delete mission if true + if not mis:hasPlayers() then + param.activeMissions[code]:updateState(Mission.states.failed) + param.activeMissions[code] = nil + env.info('Mission code'..code..' canceled due to no players') + else + --check if mission objectives can still be completed, cancel mission if not + mis:updateIsFailed() + mis:updateIsCompleted() + + if mis.state == Mission.states.preping then + --check if any player in air and move to comencing if true + for pl,un in pairs(mis.players) do + if Utils.isInAir(un) then + mis:updateState(Mission.states.comencing) + mis:pushMessageToPlayers(mis.name..' mission is starting') + break + end + end + elseif mis.state == Mission.states.comencing then + --check if all players in air and move to active if true + --if all players landed, move to preping + local allInAir = true + local allLanded = true + for pl,un in pairs(mis.players) do + if Utils.isInAir(un) then + allLanded = false + else + allInAir = false + end + end + + if allLanded then + mis:updateState(Mission.states.preping) + mis:pushMessageToPlayers(mis.name..' mission is in the prep phase') + end + + if allInAir then + mis:updateState(Mission.states.active) + mis:pushMessageToPlayers(mis.name..' mission has started') + local missionstatus = mis:getDetailedDescription() + mis:pushMessageToPlayers(missionstatus) + end + elseif mis.state == Mission.states.active then + mis:updateObjectives() + elseif mis.state == Mission.states.completed then + local isInstant = mis:isInstantReward() + if isInstant then + mis:pushMessageToPlayers(mis.name..' mission complete.', 60) + else + mis:pushMessageToPlayers(mis.name..' mission complete. Land to claim rewards.', 60) + end + + for _,reward in ipairs(mis.rewards) do + for p,_ in pairs(mis.players) do + if isInstant then + param.playerTracker:addStat(p, reward.amount, reward.type) + else + param.playerTracker:addTempStat(p, reward.amount, reward.type) + end + end + + if isInstant then + mis:pushMessageToPlayers('+'..reward.amount..' '..reward.type) + end + end + + for p,u in pairs(mis.players) do + param.playerTracker:addRankRewards(p,u, not isInstant) + end + + mis:pushSoundToPlayers("success.ogg") + param.activeMissions[code] = nil + env.info('Mission code'..code..' removed due to completion') + elseif mis.state == Mission.states.failed then + local msg = mis.name..' mission failed.' + if mis.failureReason then + msg = msg..'\n'..mis.failureReason + end + + mis:pushMessageToPlayers(msg, 60) + + mis:pushSoundToPlayers("fail.ogg") + param.activeMissions[code] = nil + env.info('Mission code'..code..' removed due to failure') + end + end + end + + return time+1 + end, self, timer.getTime()+1) + + local ev = {} + ev.context = self + function ev:onEvent(event) + if event.id == world.event.S_EVENT_KILL and event.initiator and event.target and event.initiator.getPlayerName then + local player = event.initiator:getPlayerName() + if player and + event.initiator:isExist() and + event.initiator.getCoalition and + event.target.getCoalition and + event.initiator:getCoalition() ~= event.target:getCoalition() then + self.context:tallyKill(player, event.target) + end + end + + if event.id == world.event.S_EVENT_SHOT and event.initiator and event.weapon and event.initiator.getPlayerName then + local player = event.initiator:getPlayerName() + if player and event.initiator:isExist() and event.weapon:isExist() then + self.context:tallyWeapon(player, event.weapon) + end + end + end + + world.addEventHandler(ev) + end + + function MissionTracker:generateMission(misType) + local misid = self:getNewMissionID() + env.info('MissionTracker - generating mission type ['..misType..'] id code['..misid..']') + + local newmis = nil + if misType == Mission.types.cap_easy then + newmis = CAP_Easy:new(misid, misType) + elseif misType == Mission.types.cap_medium then + newmis = CAP_Medium:new(misid, misType) + elseif misType == Mission.types.cas_easy then + newmis = CAS_Easy:new(misid, misType) + elseif misType == Mission.types.cas_medium then + newmis = CAS_Medium:new(misid, misType) + elseif misType == Mission.types.cas_hard then + newmis = CAS_Hard:new(misid, misType) + elseif misType == Mission.types.sead then + newmis = SEAD:new(misid, misType) + elseif misType == Mission.types.supply_easy then + newmis = Supply_Easy:new(misid, misType) + elseif misType == Mission.types.supply_hard then + newmis = Supply_Hard:new(misid, misType) + elseif misType == Mission.types.strike_veryeasy then + newmis = Strike_VeryEasy:new(misid, misType) + elseif misType == Mission.types.strike_easy then + newmis = Strike_Easy:new(misid, misType) + elseif misType == Mission.types.strike_medium then + newmis = Strike_Medium:new(misid, misType) + elseif misType == Mission.types.strike_hard then + newmis = Strike_Hard:new(misid, misType) + elseif misType == Mission.types.deep_strike then + newmis = Deep_Strike:new(misid, misType) + elseif misType == Mission.types.dead then + newmis = DEAD:new(misid, misType) + elseif misType == Mission.types.escort then + newmis = Escort:new(misid, misType) + elseif misType == Mission.types.tarcap then + newmis = TARCAP:new(misid, misType, self.activeMissions) + elseif misType == Mission.types.recon_plane then + newmis = Recon_Plane:new(misid, misType) + elseif misType == Mission.types.recon_plane_deep then + newmis = Deep_Recon_Plane:new(misid, misType) + elseif misType == Mission.types.scout_helo then + newmis = Scout_Helo:new(misid, misType) + elseif misType == Mission.types.bai then + newmis = BAI:new(misid, misType) + elseif misType == Mission.types.anti_runway then + newmis = Anti_Runway:new(misid, misType) + elseif misType == Mission.types.csar then + newmis = CSAR:new(misid, misType) + elseif misType == Mission.types.extraction then + newmis = Extraction:new(misid, misType) + elseif misType == Mission.types.deploy_squad then + newmis = DeploySquad:new(misid, misType) + end + + if not newmis then return end + + if #newmis.objectives == 0 then return end + + self.missionBoard[misid] = newmis + env.info('MissionTracker - generated mission id code'..misid..' \n'..newmis.description) + trigger.action.outTextForCoalition(2,'New mission available: '..newmis.name,5) + end + + function MissionTracker:tallyWeapon(player, weapon) + for _,m in pairs(self.activeMissions) do + if m.players[player] then + if m.state == Mission.states.active then + if weapon:getDesc().category == Weapon.Category.BOMB then + timer.scheduleFunction(function (params, time) + if not params.weapon:isExist() then + return nil -- weapon despawned + end + + local alt = Utils.getAGL(params.weapon) + if alt < 5 then + params.mission:tallyWeapon(params.weapon) + return nil + end + + if alt < 20 then + return time+0.01 + end + + return time+0.1 + end, {player = player, weapon = weapon, mission = m}, timer.getTime()+0.1) + end + end + end + end + end + + function MissionTracker:tallyKill(player,kill) + env.info("MissionTracker - tallyKill: "..player.." killed "..kill:getName()) + for _,m in pairs(self.activeMissions) do + if m.players[player] then + if m.state == Mission.states.active then + m:tallyKill(kill) + end + end + end + end + + function MissionTracker:tallySupplies(player, amount, zonename) + env.info("MissionTracker - tallySupplies: "..player.." delivered "..amount.." of supplies to "..zonename) + for _,m in pairs(self.activeMissions) do + if m.players[player] then + if m.state == Mission.states.active then + m:tallySupplies(amount, zonename) + end + end + end + end + + function MissionTracker:tallyLoadPilot(player, pilot) + env.info("MissionTracker - tallyLoadPilot: "..player.." loaded pilot "..pilot.name) + for _,m in pairs(self.activeMissions) do + if m.players[player] then + if m.state == Mission.states.active then + m:tallyLoadPilot(player, pilot) + end + end + end + end + + function MissionTracker:tallyUnloadPilot(player, zonename) + env.info("MissionTracker - tallyUnloadPilot: "..player.." unloaded pilots at "..zonename) + for _,m in pairs(self.activeMissions) do + if m.players[player] then + if m.state == Mission.states.active then + m:tallyUnloadPilot(player, zonename) + end + end + end + end + + function MissionTracker:tallyLoadSquad(player, squad) + env.info("MissionTracker - tallyLoadSquad: "..player.." loaded squad "..squad.name) + for _,m in pairs(self.activeMissions) do + if m.players[player] then + if m.state == Mission.states.active then + m:tallyLoadSquad(player, squad) + end + end + end + end + + function MissionTracker:tallyUnloadSquad(player, zonename, squadType) + env.info("MissionTracker - tallyUnloadSquad: "..player.." unloaded "..squadType.." squad at "..zonename) + for _,m in pairs(self.activeMissions) do + if m.players[player] then + if m.state == Mission.states.active then + m:tallyUnloadSquad(player, zonename, squadType) + end + end + end + end + + function MissionTracker:activateOrJoinMissionForGroup(code, groupname) + if groupname then + env.info('MissionTracker - activateOrJoinMissionForGroup: '..tostring(groupname)..' requested activate or join '..code) + local gr = Group.getByName(groupname) + for i,v in ipairs(gr:getUnits()) do + if v.getPlayerName and v:getPlayerName() then + local mis = self.activeMissions[code] + if mis then + self:joinMission(code, v:getPlayerName(), v) + else + self:activateMission(code, v:getPlayerName(), v) + end + return + end + end + end + end + + function MissionTracker:activateMission(code, player, unit) + if not unit or not unit:isExist() or not Utils.isLanded(unit, true) then + if unit and unit:isExist() then trigger.action.outTextForUnit(unit:getID(), 'Can only accept mission while landed', 5) end + return false + end + + local zn = ZoneCommand.getZoneOfUnit(unit:getName()) + if not zn or zn.side ~= unit:getCoalition() then + trigger.action.outTextForUnit(unit:getID(), 'Can only accept mission while inside friendly zone', 5) + return false + end + + for c,m in pairs(self.activeMissions) do + if m:getPlayerUnit(player) then + trigger.action.outTextForUnit(unit:getID(), 'A mission is already active.', 5) + return false + end + end + + local mis = self.missionBoard[code] + if not mis then + trigger.action.outTextForUnit(unit:getID(), 'Invalid mission code', 5) + return false + end + + if mis.state ~= Mission.states.new then + trigger.action.outTextForUnit(unit:getID(), 'Invalid mission.', 5) + return false + end + + if not mis:isUnitTypeAllowed(unit) then + trigger.action.outTextForUnit(unit:getID(), 'Current aircraft type is not compatible with this mission.', 5) + return false + end + + self.missionBoard[code] = nil + + trigger.action.outTextForCoalition(2,'Mission ['..mis.missionID..'] '..mis.name..' was accepted by '..player,5) + mis:updateState(Mission.states.preping) + mis.missionID = self:getNewMissionID() + mis:addPlayer(player, unit) + + mis:pushMessageToPlayers(mis.name..' accepted.\nJoin code: ['..mis.missionID..']') + + env.info('Mission code'..code..' changed to code'..mis.missionID) + env.info('Mission code'..mis.missionID..' accepted by '..player) + self.activeMissions[mis.missionID] = mis + return true + end + + function MissionTracker:joinMission(code, player, unit) + if not unit or not unit:isExist() or not Utils.isLanded(unit, true) then + if unit and unit:isExist() then trigger.action.outTextForUnit(unit:getID(), 'Can only join mission while landed', 5) end + return false + end + + local zn = ZoneCommand.getZoneOfUnit(unit:getName()) + if not zn or zn.side ~= unit:getCoalition() then + trigger.action.outTextForUnit(unit:getID(), 'Can only join mission while inside friendly zone', 5) + return false + end + + for c,m in pairs(self.activeMissions) do + if m:getPlayerUnit(player) then + trigger.action.outTextForUnit(unit:getID(), 'A mission is already active.', 5) + return false + end + end + + local mis = self.activeMissions[code] + if not mis then + trigger.action.outTextForUnit(unit:getID(), 'Invalid mission code', 5) + return false + end + + if mis.state ~= Mission.states.preping then + trigger.action.outTextForUnit(unit:getID(), 'Mission can only be joined if its members have not taken off yet.', 5) + return false + end + + if not mis:isUnitTypeAllowed(unit) then + trigger.action.outTextForUnit(unit:getID(), 'Current aircraft type is not compatible with this mission.', 5) + return false + end + + mis:addPlayer(player, unit) + mis:pushMessageToPlayers(player..' has joined mission '..mis.name) + env.info('Mission code'..code..' joined by '..player) + return true + end + + function MissionTracker:leaveMission(player) + for _,mis in pairs(self.activeMissions) do + if mis:getPlayerUnit(player) then + mis:pushMessageToPlayers(player..' has left mission '..mis.name) + mis:removePlayer(player) + env.info('Mission code'..mis.missionID..' left by '..player) + if not mis:hasPlayers() then + self.activeMissions[mis.missionID]:updateState(Mission.states.failed) + self.activeMissions[mis.missionID] = nil + env.info('Mission code'..mis.missionID..' canceled due to all players leaving') + end + + break + end + end + + return true + end + + function MissionTracker:canCreateMission(misType) + if not MissionTracker.maxMissionCount[misType] then return false end + + local missionCount = 0 + for i,v in pairs(self.missionBoard) do + if v.type == misType then missionCount = missionCount + 1 end + end + + for i,v in pairs(self.activeMissions) do + if v.type == misType then missionCount = missionCount + 1 end + end + + if missionCount >= MissionTracker.maxMissionCount[misType] then return false end + + if misType == Mission.types.cap_easy then + return CAP_Easy.canCreate() + elseif misType == Mission.types.cap_medium then + return CAP_Medium.canCreate() + elseif misType == Mission.types.cas_easy then + return CAS_Easy.canCreate() + elseif misType == Mission.types.cas_medium then + return CAS_Medium.canCreate() + elseif misType == Mission.types.sead then + return SEAD.canCreate() + elseif misType == Mission.types.dead then + return DEAD.canCreate() + elseif misType == Mission.types.cas_hard then + return CAS_Hard.canCreate() + elseif misType == Mission.types.supply_easy then + return Supply_Easy.canCreate() + elseif misType == Mission.types.supply_hard then + return Supply_Hard.canCreate() + elseif misType == Mission.types.strike_veryeasy then + return Strike_VeryEasy.canCreate() + elseif misType == Mission.types.strike_easy then + return Strike_Easy.canCreate() + elseif misType == Mission.types.strike_medium then + return Strike_Medium.canCreate() + elseif misType == Mission.types.strike_hard then + return Strike_Hard.canCreate() + elseif misType == Mission.types.deep_strike then + return Deep_Strike.canCreate() + elseif misType == Mission.types.escort then + return Escort.canCreate() + elseif misType == Mission.types.tarcap then + return TARCAP.canCreate(self.activeMissions) + elseif misType == Mission.types.recon_plane then + return Recon_Plane.canCreate() + elseif misType == Mission.types.recon_plane_deep then + return Deep_Recon_Plane.canCreate() + elseif misType == Mission.types.scout_helo then + return Scout_Helo.canCreate() + elseif misType == Mission.types.bai then + return BAI.canCreate() + elseif misType == Mission.types.anti_runway then + return Anti_Runway.canCreate() + elseif misType == Mission.types.csar then + return CSAR.canCreate() + elseif misType == Mission.types.extraction then + return Extraction.canCreate() + elseif misType == Mission.types.deploy_squad then + return DeploySquad.canCreate() + end + + return false + end + +end + + +-----------------[[ END OF MissionTracker.lua ]]----------------- + + + +-----------------[[ SquadTracker.lua ]]----------------- + +SquadTracker = {} +do + function SquadTracker:new() + local obj = {} + obj.activeInfantrySquads = {} + setmetatable(obj, self) + self.__index = self + + obj:start() + return obj + end + + SquadTracker.infantryCallsigns = { + adjectives = {"Sapphire", "Emerald", "Whisper", "Vortex", "Blaze", "Nova", "Silent", "Zephyr", "Radiant", "Shadow", "Lively", "Dynamo", "Dusk", "Rapid", "Stellar", "Tundra", "Obsidian", "Cascade", "Zenith", "Solar"}, + nouns = {"Journey", "Quasar", "Galaxy", "Moonbeam", "Comet", "Starling", "Serenade", "Raven", "Breeze", "Echo", "Avalanche", "Harmony", "Stardust", "Horizon", "Firefly", "Solstice", "Labyrinth", "Whisper", "Cosmos", "Mystique"} + } + + function SquadTracker:generateCallsign() + local adjective = self.infantryCallsigns.adjectives[math.random(1,#self.infantryCallsigns.adjectives)] + local noun = self.infantryCallsigns.nouns[math.random(1,#self.infantryCallsigns.nouns)] + + local callsign = adjective..noun + + if self.activeInfantrySquads[callsign] then + for i=1,1000,1 do + local try = callsign..'-'..i + if not self.activeInfantrySquads[try] then + callsign = try + break + end + end + end + + if not self.activeInfantrySquads[callsign] then + return callsign + end + end + + function SquadTracker:restoreInfantry(save) + + Spawner.createObject(save.name, save.data.name, save.position, 2, 10, 20,{ + [land.SurfaceType.LAND] = true, + [land.SurfaceType.ROAD] = true, + [land.SurfaceType.RUNWAY] = true, + }) + + self.activeInfantrySquads[save.name] = { + name = save.name, + position = save.position, + state = save.state, + remainingStateTime=save.remainingStateTime, + data = save.data + } + + if save.state == "extractReady" then + MissionTargetRegistry.addSquad(self.activeInfantrySquads[save.name]) + end + + env.info('SquadTracker - '..save.name..'('..save.data.type..') restored') + end + + function SquadTracker:spawnInfantry(infantryData, position) + local callsign = self:generateCallsign() + if callsign then + Spawner.createObject(callsign, infantryData.name, position, 2, 10, 20,{ + [land.SurfaceType.LAND] = true, + [land.SurfaceType.ROAD] = true, + [land.SurfaceType.RUNWAY] = true, + }) + + self:registerInfantry(infantryData, callsign, position) + end + end + + function SquadTracker:registerInfantry(infantryData, groupname, position) + self.activeInfantrySquads[groupname] = {name = groupname, position = position, state = "deployed", remainingStateTime=0, data = infantryData} + + env.info('SquadTracker - '..groupname..'('..infantryData.type..') deployed') + end + + function SquadTracker:start() + if not ZoneCommand then return end + + timer.scheduleFunction(function(param, time) + local self = param.context + + for i,v in pairs(self.activeInfantrySquads) do + local remove = self:processInfantrySquad(v) + if remove then + MissionTargetRegistry.removeSquad(v) + self.activeInfantrySquads[v.name] = nil + end + end + + return time+10 + end, {context = self}, timer.getTime()+1) + end + + function SquadTracker:removeSquad(squadname) + local squad = self.activeInfantrySquads[squadname] + if squad then + MissionTargetRegistry.removeSquad(squad) + squad.state = 'extracted' + squad.remainingStateTime = 0 + self.activeInfantrySquads[squadname] = nil + end + end + + function SquadTracker:getClosestExtractableSquad(sourcePoint) + local minDist = 99999999 + local squad = nil + + for i,v in pairs(self.activeInfantrySquads) do + if v.state == 'extractReady' then + local gr = Group.getByName(v.name) + if gr and gr:getSize()>0 then + local dist = mist.utils.get2DDist(sourcePoint, gr:getUnit(1):getPoint()) + if dist (5*60) then + if gr:getSize()>0 then + local unPos = gr:getUnit(1):getPoint() + local p = Utils.getPointOnSurface(unPos) + p.x = p.x + math.random(-5,5) + p.z = p.z + math.random(-5,5) + trigger.action.smoke(p, trigger.smokeColor.Green) + squad.lastMarkerDeployedTime = timer.getAbsTime() + end + end + end + end +end + +-----------------[[ END OF SquadTracker.lua ]]----------------- + + + +-----------------[[ CSARTracker.lua ]]----------------- + +CSARTracker = {} +do + function CSARTracker:new() + local obj = {} + obj.activePilots = {} + setmetatable(obj, self) + self.__index = self + + obj:start() + return obj + end + + function CSARTracker:start() + if not ZoneCommand then return end + + local ev = {} + ev.context = self + function ev:onEvent(event) + if event.id == world.event.S_EVENT_LANDING_AFTER_EJECTION then + if event.initiator and event.initiator:isExist() then + if event.initiator:getCoalition() == 2 then + local z = ZoneCommand.getZoneOfPoint(event.initiator:getPoint()) + if not z then + local name = self.context:generateCallsign() + if name then + local pos = { + x = event.initiator:getPoint().x, + y = event.initiator:getPoint().z + } + + if pos.x ~= 0 and pos.y ~= 0 then + local srfType = land.getSurfaceType(pos) + if srfType ~= land.SurfaceType.WATER and srfType ~= land.SurfaceType.SHALLOW_WATER then + local gr = Spawner.createPilot(name, pos) + self.context:addPilot(name, gr) + end + end + end + end + end + + event.initiator:destroy() + end + end + end + + world.addEventHandler(ev) + + timer.scheduleFunction(function(param, time) + for i,v in pairs(param.context.activePilots) do + v.remainingTime = v.remainingTime - 10 + if not v.pilot:isExist() or v.remainingTime <=0 then + param.context:removePilot(i) + end + end + + return time+10 + end, {context = self}, timer.getTime()+1) + end + + function CSARTracker:markPilot(data) + local pilot = data.pilot + if pilot:isExist() then + local pos = pilot:getUnit(1):getPoint() + local p = Utils.getPointOnSurface(pos) + p.x = p.x + math.random(-5,5) + p.z = p.z + math.random(-5,5) + trigger.action.smoke(p, trigger.smokeColor.Green) + end + end + + function CSARTracker:flarePilot(data) + local pilot = data.pilot + if pilot:isExist() then + local pos = pilot:getUnit(1):getPoint() + local p = Utils.getPointOnSurface(pos) + trigger.action.signalFlare(p, trigger.flareColor.Green, math.random(1,360)) + end + end + + function CSARTracker:removePilot(name) + local data = self.activePilots[name] + if data.pilot and data.pilot:isExist() then data.pilot:destroy() end + + MissionTargetRegistry.removePilot(data) + self.activePilots[name] = nil + end + + function CSARTracker:addPilot(name, pilot) + self.activePilots[name] = {pilot = pilot, name = name, remainingTime = 45*60} + MissionTargetRegistry.addPilot(self.activePilots[name]) + end + + function CSARTracker:restorePilot(save) + local gr = Spawner.createPilot(save.name, save.pos) + + self.activePilots[save.name] = { + pilot = gr, + name = save.name, + remainingTime = save.remainingTime + } + + MissionTargetRegistry.addPilot(self.activePilots[save.name]) + end + + function CSARTracker:getClosestPilot(toPosition) + local minDist = 99999999 + local data = nil + local name = nil + + for i,v in pairs(self.activePilots) do + if v.pilot:isExist() and v.remainingTime > 0 then + local dist = mist.utils.get2DDist(toPosition, v.pilot:getUnit(1):getPoint()) + if dist 0 then + local msg = "Warning radius set to "..warningRadius + if metric then + msg=msg.."km" + else + msg=msg.."nm" + end + + if metric then + warningRadius = warningRadius * 1000 + else + warningRadius = warningRadius * 1852 + end + + self.players[name] = { + unit = unit, + warningRadius = warningRadius, + metric = metric + } + + trigger.action.outTextForUnit(unit:getID(), msg, 10) + else + self.players[name] = nil + trigger.action.outTextForUnit(unit:getID(), "GCI Reports disabled", 10) + end + end + + function GCI:start() + MenuRegistry:register(5, function(event, context) + if event.id == world.event.S_EVENT_BIRTH and event.initiator and event.initiator.getPlayerName then + local player = event.initiator:getPlayerName() + if player then + local groupid = event.initiator:getGroup():getID() + local groupname = event.initiator:getGroup():getName() + local unit = event.initiator + + if context.groupMenus[groupid] then + missionCommands.removeItemForGroup(groupid, context.groupMenus[groupid]) + context.groupMenus[groupid] = nil + end + + if not context.groupMenus[groupid] then + + local menu = missionCommands.addSubMenuForGroup(groupid, 'GCI') + local setWR = missionCommands.addSubMenuForGroup(groupid, 'Set Warning Radius', menu) + local kmMenu = missionCommands.addSubMenuForGroup(groupid, 'KM', setWR) + local nmMenu = missionCommands.addSubMenuForGroup(groupid, 'NM', setWR) + + missionCommands.addCommandForGroup(groupid, '10 KM', kmMenu, Utils.log(context.registerPlayer), context, player, unit, 10, true) + missionCommands.addCommandForGroup(groupid, '25 KM', kmMenu, Utils.log(context.registerPlayer), context, player, unit, 25, true) + missionCommands.addCommandForGroup(groupid, '50 KM', kmMenu, Utils.log(context.registerPlayer), context, player, unit, 50, true) + missionCommands.addCommandForGroup(groupid, '100 KM', kmMenu, Utils.log(context.registerPlayer), context, player, unit, 100, true) + missionCommands.addCommandForGroup(groupid, '150 KM', kmMenu, Utils.log(context.registerPlayer), context, player, unit, 150, true) + + missionCommands.addCommandForGroup(groupid, '5 NM', nmMenu, Utils.log(context.registerPlayer), context, player, unit, 5, false) + missionCommands.addCommandForGroup(groupid, '10 NM', nmMenu, Utils.log(context.registerPlayer), context, player, unit, 10, false) + missionCommands.addCommandForGroup(groupid, '25 NM', nmMenu, Utils.log(context.registerPlayer), context, player, unit, 25, false) + missionCommands.addCommandForGroup(groupid, '50 NM', nmMenu, Utils.log(context.registerPlayer), context, player, unit, 50, false) + missionCommands.addCommandForGroup(groupid, '80 NM', nmMenu, Utils.log(context.registerPlayer), context, player, unit, 80, false) + missionCommands.addCommandForGroup(groupid, 'Disable', menu, Utils.log(context.registerPlayer), context, player, unit, 0, false) + + context.groupMenus[groupid] = menu + end + end + elseif (event.id == world.event.S_EVENT_PLAYER_LEAVE_UNIT or event.id == world.event.S_EVENT_DEAD) and event.initiator and event.initiator.getPlayerName then + local player = event.initiator:getPlayerName() + if player then + local groupid = event.initiator:getGroup():getID() + + if context.groupMenus[groupid] then + missionCommands.removeItemForGroup(groupid, context.groupMenus[groupid]) + context.groupMenus[groupid] = nil + end + end + end + end, self) + + timer.scheduleFunction(function(param, time) + local self = param.context + local allunits = coalition.getGroups(self.side) + + local radars = {} + for _,g in ipairs(allunits) do + for _,u in ipairs(g:getUnits()) do + for _,a in ipairs(self.radarTypes) do + if u:hasAttribute(a) then + table.insert(radars, u) + break + end + end + end + end + + self.radars = radars + env.info("GCI - tracking "..#radars.." radar enabled units") + + return time+10 + end, {context = self}, timer.getTime()+1) + + timer.scheduleFunction(function(param, time) + local self = param.context + + local plyCount = 0 + for i,v in pairs(self.players) do + if not v.unit or not v.unit:isExist() then + self.players[i] = nil + else + plyCount = plyCount + 1 + end + end + + env.info("GCI - reporting to "..plyCount.." players") + if plyCount >0 then + local dect = {} + local dcount = 0 + for _,u in ipairs(self.radars) do + if u:isExist() then + local detected = u:getController():getDetectedTargets(Controller.Detection.RADAR) + for _,d in ipairs(detected) do + if d and d.object and d.object.isExist and d.object:isExist() and + d.object:getCategory() == Object.Category.UNIT and + d.object.getCoalition and + d.object:getCoalition() == self.tgtSide then + + if not dect[d.object:getName()] then + dect[d.object:getName()] = d.object + dcount = dcount + 1 + end + end + end + end + end + + env.info("GCI - aware of "..dcount.." enemy units") + + for name, data in pairs(self.players) do + if data.unit and data.unit:isExist() then + local closeUnits = {} + + local wr = data.warningRadius + if wr > 0 then + for _,dt in pairs(dect) do + if dt:isExist() then + local tgtPnt = dt:getPoint() + local dist = mist.utils.get2DDist(data.unit:getPoint(), tgtPnt) + if dist <= wr then + local brg = math.floor(Utils.getBearing(data.unit:getPoint(), tgtPnt)) + + local myPos = data.unit:getPosition() + local tgtPos = dt:getPosition() + local tgtHeading = math.deg(math.atan2(tgtPos.x.z, tgtPos.x.x)) + local tgtBearing = Utils.getBearing(tgtPos.p, myPos.p) + + local diff = math.abs(Utils.getHeadingDiff(tgtBearing, tgtHeading)) + local aspect = '' + local priority = 1 + if diff <= 30 then + aspect = "Hot" + priority = 1 + elseif diff <= 60 then + aspect = "Flanking" + priority = 1 + elseif diff <= 120 then + aspect = "Beaming" + priority = 2 + else + aspect = "Cold" + priority = 3 + end + + table.insert(closeUnits, { + type = dt:getDesc().typeName, + bearing = brg, + range = dist, + altitude = tgtPnt.y, + score = dist*priority, + aspect = aspect + }) + end + end + end + end + + env.info("GCI - "..#closeUnits.." enemy units within "..wr.."m of "..name) + if #closeUnits > 0 then + table.sort(closeUnits, function(a, b) return a.range < b.range end) + + local msg = "GCI Report:\n" + local count = 0 + for _,tgt in ipairs(closeUnits) do + if data.metric then + local km = tgt.range/1000 + if km < 1 then + msg = msg..'\n'..tgt.type..' MERGED' + else + msg = msg..'\n'..tgt.type..' BRA: '..tgt.bearing..' for ' + msg = msg..Utils.round(km)..'km at ' + msg = msg..(Utils.round(tgt.altitude/250)*250)..'m, ' + msg = msg..tostring(tgt.aspect) + end + else + local nm = tgt.range/1852 + if nm < 1 then + msg = msg..'\n'..tgt.type..' MERGED' + else + msg = msg..'\n'..tgt.type..' BRA: '..tgt.bearing..' for ' + msg = msg..Utils.round(nm)..'nm at ' + msg = msg..(Utils.round((tgt.altitude/0.3048)/1000)*1000)..'ft, ' + msg = msg..tostring(tgt.aspect) + end + end + + count = count + 1 + if count >= 10 then break end + end + + trigger.action.outTextForUnit(data.unit:getID(), msg, 19) + end + else + self.players[name] = nil + end + end + end + + return time+20 + end, {context = self}, timer.getTime()+6) + end +end + +-----------------[[ END OF GCI.lua ]]----------------- + + + +-----------------[[ Starter.lua ]]----------------- + +Starter = {} +do + Starter.neutralChance = 0.1 + + function Starter.start(zones) + if Starter.shouldRandomize() then + Starter.randomize(zones) + else + Starter.normalStart(zones) + end + end + + function Starter.randomize(zones) + local startZones = {} + for _,z in pairs(zones) do + if z.isHeloSpawn and z.isPlaneSpawn then + table.insert(startZones, z) + end + end + + if #startZones > 0 then + local sz = startZones[math.random(1,#startZones)] + + sz:capture(2, true) + Starter.captureNeighbours(sz, math.random(1,3)) + end + + for _,z in pairs(zones) do + if z.side == 0 then + if math.random() > Starter.neutralChance then + z:capture(1,true) + end + end + + if z.side ~= 0 then + z:fullUpgrade(math.random(1,30)/100) + end + end + end + + function Starter.captureNeighbours(zone, stepsLeft) + if stepsLeft > 0 then + for _,v in pairs(zone.neighbours) do + if v.side == 0 then + if math.random() > Starter.neutralChance then + v:capture(2,true) + end + Starter.captureNeighbours(v, stepsLeft-1) + end + end + end + end + + function Starter.shouldRandomize() + if lfs then + local filename = lfs.writedir()..'Missions/Saves/randomize.lua' + if lfs.attributes(filename) then + return true + end + end + end + + function Starter.normalStart(zones) + for _,z in pairs(zones) do + local i = z.initialState + if i then + if i.side and i.side ~= 0 then + z:capture(i.side, true) + z:fullUpgrade() + z:boostProduction(math.random(1,200)) + end + end + end + end +end + +-----------------[[ END OF Starter.lua ]]----------------- + From 842c2f8aaf1e67e1cf82cb07fd9b781846dc2150 Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Sun, 10 Sep 2023 16:11:30 +0300 Subject: [PATCH 011/243] Copied init.lua into init_header.lua, init_body.lua and init_footer.lua The init.lua script will be generated by 1. injecting the header, then 2. generating the ZoneCommand zones, then 3. generating ConnectionManager connections, then 4. injecting init_body.lua, then 5. generating sypply injection and finally by 6. injecting init_footer.lua --- .../pretense/{init.lua => init_body.lua} | 0 resources/plugins/pretense/init_footer.lua | 4670 +++++++++++++++++ resources/plugins/pretense/init_header.lua | 4670 +++++++++++++++++ 3 files changed, 9340 insertions(+) rename resources/plugins/pretense/{init.lua => init_body.lua} (100%) create mode 100644 resources/plugins/pretense/init_footer.lua create mode 100644 resources/plugins/pretense/init_header.lua diff --git a/resources/plugins/pretense/init.lua b/resources/plugins/pretense/init_body.lua similarity index 100% rename from resources/plugins/pretense/init.lua rename to resources/plugins/pretense/init_body.lua diff --git a/resources/plugins/pretense/init_footer.lua b/resources/plugins/pretense/init_footer.lua new file mode 100644 index 00000000..9df8452d --- /dev/null +++ b/resources/plugins/pretense/init_footer.lua @@ -0,0 +1,4670 @@ + + +local savefile = 'pretense_1.1.json' +if lfs then + local dir = lfs.writedir()..'Missions/Saves/' + lfs.mkdir(dir) + savefile = dir..savefile + env.info('Pretense - Save file path: '..savefile) +end + + +do + TemplateDB.templates["infantry-red"] = { + units = { + "BTR_D", + "T-90", + "T-90", + "Infantry AK ver2", + "Infantry AK", + "Infantry AK", + "Paratrooper RPG-16", + "Infantry AK ver3", + "SA-18 Igla manpad" + }, + skill = "Excellent", + dataCategory= TemplateDB.type.group + } + + TemplateDB.templates["infantry-blue"] = { + units = { + "M1045 HMMWV TOW", + "Soldier stinger", + "Soldier M4 GRG", + "Soldier M4 GRG", + "Soldier M4 GRG", + "Soldier M4 GRG", + "Soldier M4 GRG", + "M1043 HMMWV Armament" + }, + skill = "Excellent", + dataCategory= TemplateDB.type.group + } + + TemplateDB.templates["defense-red"] = { + units = { + "Infantry AK ver2", + "Infantry AK", + "Infantry AK ver3", + "Paratrooper RPG-16", + "SA-18 Igla manpad" + }, + skill = "Excellent", + dataCategory= TemplateDB.type.group + } + + TemplateDB.templates["defense-blue"] = { + units = { + "Soldier M4 GRG", + "Soldier M4 GRG", + "Soldier M4 GRG", + "Soldier RPG", + "Soldier stinger", + }, + skill = "Excellent", + dataCategory= TemplateDB.type.group + } + + TemplateDB.templates["shorad-red"] = { + units = { + "Strela-10M3", + "Strela-10M3", + "Ural-4320T", + "2S6 Tunguska" + }, + maxDist = 300, + skill = "Excellent", + dataCategory= TemplateDB.type.group + } + + TemplateDB.templates["shorad-blue"] = { + units = { + "Roland ADS", + "M48 Chaparral", + "M 818", + "Gepard", + "Gepard" + }, + maxDist = 300, + skill = "Excellent", + dataCategory= TemplateDB.type.group + } + + TemplateDB.templates["sam-red"] = { + units = { + "p-19 s-125 sr", + "Ural-4320T", + "Ural-4320T", + "S_75M_Volhov", + "S_75M_Volhov", + "S_75M_Volhov", + "S_75M_Volhov", + "S_75M_Volhov", + "Tor 9A331", + "SNR_75V" + }, + maxDist = 300, + skill = "Excellent", + dataCategory= TemplateDB.type.group + } + + TemplateDB.templates["sam-blue"] = { + units = { + "Hawk pcp", + "Hawk cwar", + "Hawk ln", + "Hawk ln", + "Hawk ln", + "Hawk ln", + "Hawk ln", + "Hawk tr", + "M 818", + "Hawk sr" + }, + maxDist = 300, + skill = "Good", + dataCategory= TemplateDB.type.group + } + + TemplateDB.templates["patriot"] = { + units = { + "Patriot cp", + "Patriot str", + "M 818", + "M 818", + "Patriot ln", + "Patriot ln", + "Patriot ln", + "Patriot ln", + "Patriot str", + "Patriot str", + "Patriot str", + "Patriot EPP", + "Patriot ECS", + "Patriot AMG" + }, + maxDist = 300, + skill = "Good", + dataCategory= TemplateDB.type.group + } + + TemplateDB.templates["sa3"] = { + units = { + "p-19 s-125 sr", + "snr s-125 tr", + "5p73 s-125 ln", + "5p73 s-125 ln", + "Ural-4320T", + "5p73 s-125 ln", + "5p73 s-125 ln" + }, + maxDist = 300, + skill = "Excellent", + dataCategory= TemplateDB.type.group + } + + TemplateDB.templates["sa6"] = { + units = { + "Kub 1S91 str", + "Kub 2P25 ln", + "Kub 2P25 ln", + "Kub 2P25 ln", + "Kub 2P25 ln", + "2S6 Tunguska", + "Ural-4320T", + "2S6 Tunguska", + "Kub 2P25 ln" + }, + maxDist = 300, + skill = "Excellent", + dataCategory= TemplateDB.type.group + } + + TemplateDB.templates["sa10"] = { + units = { + "S-300PS 54K6 cp", + "S-300PS 5P85C ln", + "S-300PS 5P85C ln", + "S-300PS 5P85C ln", + "GAZ-66", + "GAZ-66", + "GAZ-66", + "S-300PS 5P85C ln", + "S-300PS 5P85C ln", + "S-300PS 5P85C ln", + "S-300PS 40B6MD sr", + "S-300PS 40B6M tr", + "S-300PS 64H6E sr" + }, + maxDist = 300, + skill = "Excellent", + dataCategory= TemplateDB.type.group + } + + TemplateDB.templates["sa5"] = { + units = { + "RLS_19J6", + "Ural-4320T", + "Ural-4320T", + "RPC_5N62V", + "S-200_Launcher", + "S-200_Launcher", + "S-200_Launcher", + "S-200_Launcher", + "S-200_Launcher", + "S-200_Launcher" + }, + maxDist = 300, + skill = "Excellent", + dataCategory= TemplateDB.type.group + } + + TemplateDB.templates["sa11"] = { + units = { + "SA-11 Buk SR 9S18M1", + "SA-11 Buk LN 9A310M1", + "SA-11 Buk LN 9A310M1", + "SA-11 Buk LN 9A310M1", + "SA-11 Buk LN 9A310M1", + "SA-11 Buk LN 9A310M1", + "2S6 Tunguska", + "SA-11 Buk SR 9S18M1", + "GAZ-66", + "GAZ-66", + "SA-11 Buk CC 9S470M1" + }, + maxDist = 300, + skill = "Excellent", + dataCategory= TemplateDB.type.group + } + + TemplateDB.templates["nasams"] = { + units = { + "NASAMS_Command_Post", + "NASAMS_Radar_MPQ64F1", + "Vulcan", + "M 818", + "M 818", + "Roland ADS", + "Roland ADS", + "NASAMS_LN_C", + "NASAMS_LN_C", + "NASAMS_LN_C", + "NASAMS_LN_C", + "NASAMS_Radar_MPQ64F1", + "NASAMS_Radar_MPQ64F1", + "NASAMS_Radar_MPQ64F1" + }, + maxDist = 300, + skill = "Excellent", + dataCategory= TemplateDB.type.group + } +end + +presets = { + upgrades = { + basic = { + tent = Preset:new({ + display = 'Tent', + cost = 1500, + type = 'upgrade', + template = "tent" + }), + comPost = Preset:new({ + display = 'Barracks', + cost = 1500, + type = 'upgrade', + template = "barracks" + }), + outpost = Preset:new({ + display = 'Outpost', + cost = 1500, + type = 'upgrade', + template = "outpost" + }) + }, + attack = { + ammoCache = Preset:new({ + display = 'Ammo Cache', + cost = 1500, + type = 'upgrade', + template = "ammo-cache" + }), + ammoDepot = Preset:new({ + display = 'Ammo Depot', + cost = 2000, + type = 'upgrade', + template = "ammo-depot" + }) + }, + supply = { + fuelCache = Preset:new({ + display = 'Fuel Cache', + cost = 1500, + type = 'upgrade', + template = "fuel-cache" + }), + fuelTank = Preset:new({ + display = 'Fuel Tank', + cost = 1500, + type = 'upgrade', + template = "fuel-tank-big" + }), + fuelTankFarp = Preset:new({ + display = 'Fuel Tank', + cost = 1500, + type = 'upgrade', + template = "fuel-tank-small" + }), + factory1 = Preset:new({ + display='Factory', + cost = 2000, + type ='upgrade', + income = 20, + template = "factory-1" + }), + factory2 = Preset:new({ + display='Factory', + cost = 2000, + type ='upgrade', + income = 20, + template = "factory-2" + }), + factoryTank = Preset:new({ + display='Storage Tank', + cost = 1500, + type ='upgrade', + income = 10, + template = "chem-tank" + }), + ammoDepot = Preset:new({ + display = 'Ammo Depot', + cost = 2000, + type = 'upgrade', + income = 40, + template = "ammo-depot" + }), + oilPump = Preset:new({ + display = 'Oil Pump', + cost = 1500, + type = 'upgrade', + income = 20, + template = "oil-pump" + }), + hangar = Preset:new({ + display = 'Hangar', + cost = 2000, + type = 'upgrade', + income = 30, + template = "hangar" + }), + excavator = Preset:new({ + display = 'Excavator', + cost = 2000, + type = 'upgrade', + income = 20, + template = "excavator" + }), + farm1 = Preset:new({ + display = 'Farm House', + cost = 2000, + type = 'upgrade', + income = 40, + template = "farm-house-1" + }), + farm2 = Preset:new({ + display = 'Farm House', + cost = 2000, + type = 'upgrade', + income = 40, + template = "farm-house-2" + }), + refinery1 = Preset:new({ + display='Refinery', + cost = 2000, + type ='upgrade', + income = 100, + template = "factory-1" + }), + powerplant1 = Preset:new({ + display='Power Plant', + cost = 1500, + type ='upgrade', + income = 25, + template = "factory-1" + }), + powerplant2 = Preset:new({ + display='Power Plant', + cost = 1500, + type ='upgrade', + income = 25, + template = "factory-2" + }), + antenna = Preset:new({ + display='Antenna', + cost = 1000, + type ='upgrade', + income = 10, + template = "antenna" + }), + hq = Preset:new({ + display='HQ Building', + cost = 2000, + type ='upgrade', + income = 50, + template = "tv-tower" + }) + }, + airdef = { + comCenter = Preset:new({ + display = 'Command Center', + cost = 2500, + type = 'upgrade', + template = "command-center" + }) + } + }, + defenses = { + red = { + infantry = Preset:new({ + display = 'Infantry', + cost=2000, + type='defense', + template='infantry-red', + }), + shorad = Preset:new({ + display = 'SAM', + cost=2500, + type='defense', + template='shorad-red', + }), + sam = Preset:new({ + display = 'SAM', + cost=3000, + type='defense', + template='sam-red', + }), + sa10 = Preset:new({ + display = 'SAM', + cost=3000, + type='defense', + template='sa10', + }), + sa5 = Preset:new({ + display = 'SAM', + cost=3000, + type='defense', + template='sa5', + }), + sa3 = Preset:new({ + display = 'SAM', + cost=3000, + type='defense', + template='sa3', + }), + sa6 = Preset:new({ + display = 'SAM', + cost=3000, + type='defense', + template='sa6', + }), + sa11 = Preset:new({ + display = 'SAM', + cost=3000, + type='defense', + template='sa11', + }) + }, + blue = { + infantry = Preset:new({ + display = 'Infantry', + cost=2000, + type='defense', + template='infantry-blue', + }), + shorad = Preset:new({ + display = 'SAM', + cost=2500, + type='defense', + template='shorad-blue', + }), + sam = Preset:new({ + display = 'SAM', + cost=3000, + type='defense', + template='sam-blue', + }), + patriot = Preset:new({ + display = 'SAM', + cost=3000, + type='defense', + template='patriot', + }), + nasams = Preset:new({ + display = 'SAM', + cost=3000, + type='defense', + template='nasams', + }) + } + }, + missions = { + supply = { + convoy = Preset:new({ + display = 'Supply convoy', + cost = 4000, + type = 'mission', + missionType = ZoneCommand.missionTypes.supply_convoy + }), + convoy_escorted = Preset:new({ + display = 'Supply convoy', + cost = 3000, + type = 'mission', + missionType = ZoneCommand.missionTypes.supply_convoy + }), + helo = Preset:new({ + display = 'Supply helicopter', + cost = 2500, + type='mission', + missionType = ZoneCommand.missionTypes.supply_air + }), + transfer = Preset:new({ + display = 'Supply transfer', + cost = 1000, + type='mission', + missionType = ZoneCommand.missionTypes.supply_transfer + }) + }, + attack = { + surface = Preset:new({ + display = 'Ground assault', + cost = 100, + type = 'mission', + missionType = ZoneCommand.missionTypes.assault, + }), + cas = Preset:new({ + display = 'CAS', + cost = 200, + type='mission', + missionType = ZoneCommand.missionTypes.cas + }), + bai = Preset:new({ + display = 'BAI', + cost = 200, + type='mission', + missionType = ZoneCommand.missionTypes.bai + }), + strike = Preset:new({ + display = 'Strike', + cost = 300, + type='mission', + missionType = ZoneCommand.missionTypes.strike + }), + sead = Preset:new({ + display = 'SEAD', + cost = 200, + type='mission', + missionType = ZoneCommand.missionTypes.sead + }), + helo = Preset:new({ + display = 'CAS', + cost = 100, + type='mission', + missionType = ZoneCommand.missionTypes.cas_helo + }) + }, + patrol={ + aircraft = Preset:new({ + display= "Patrol", + cost = 100, + type='mission', + missionType = ZoneCommand.missionTypes.patrol + }) + }, + support ={ + awacs = Preset:new({ + display= "AWACS", + cost = 300, + type='mission', + bias='5', + missionType = ZoneCommand.missionTypes.awacs + }), + tanker = Preset:new({ + display= "Tanker", + cost = 200, + type='mission', + bias='2', + missionType = ZoneCommand.missionTypes.tanker + }) + } + }, + special = { + red = { + infantry = Preset:new({ + display = 'Infantry', + cost=-1, + type='defense', + template='defense-red', + }), + }, + blue = { + infantry = Preset:new({ + display = 'Infantry', + cost=-1, + type='defense', + template='defense-blue', + }) + } + } +} + +zones = {} +do + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Batumi.lua ]]----------------- + +zones.batumi = ZoneCommand:new('Batumi') +zones.batumi.initialState = { side=2 } +zones.batumi.keepActive = true +zones.batumi.isHeloSpawn = true +zones.batumi.isPlaneSpawn = true +zones.batumi.maxResource = 50000 +zones.batumi:defineUpgrades({ + [1] = { --red side + presets.upgrades.basic.comPost:extend({ + name = 'batumi-com-red', + products = { + presets.special.red.infantry:extend({ name='batumi-defense-red'}), + presets.defenses.red.infantry:extend({ name='batumi-garrison-red' }) + } + }), + }, + [2] = --blue side + { + presets.upgrades.basic.comPost:extend({ + name = 'batumi-com-blue', + products = { + presets.special.blue.infantry:extend({ name='batumi-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='batumi-garrison-blue' }) + } + }), + presets.upgrades.supply.fuelTank:extend({ + name = 'batumi-fueltank-blue', + products = { + presets.missions.supply.convoy_escorted:extend({ name='batumi-supply-convoy-1'}), + presets.missions.supply.helo:extend({ name='batumi-supply-blue-1' }), + presets.missions.supply.transfer:extend({name='batumi-transfer-blue'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name = 'batumi-mission-command-blue', + products = { + presets.defenses.blue.shorad:extend({ name='batumi-sam-blue' }), + presets.missions.attack.sead:extend({name='batumi-sead-blue-1', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.attack.cas:extend({name='batumi-cas-blue-1', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.bai:extend({name='batumi-cas-blue-1', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.strike:extend({name='batumi-strike-blue-1', altitude=20000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.patrol.aircraft:extend({name='batumi-patrol-blue-1', altitude=25000, range=25}), + presets.missions.support.awacs:extend({name='batumi-awacs-blue', altitude=30000, freq=257.5}), + presets.missions.support.tanker:extend({name='batumi-tanker-blue', altitude=25000, freq=257, tacan='37', variant="Drogue"}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Batumi.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Mike.lua ]]----------------- + +zones.mike = ZoneCommand:new("Mike") +zones.mike.initialState = { side=1 } +zones.mike.keepActive = true +zones.mike:defineUpgrades({ + [1] = + { + presets.upgrades.basic.tent:extend({ + name='mike-tent-red', + products = { + presets.special.red.infantry:extend({ name='mike-defense-red'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='mike-comcenter-red', + products = { + presets.defenses.red.sam:extend({ name='mike-sam-red'}) + } + }) + }, + [2] = + { + presets.upgrades.basic.tent:extend({ + name='mike-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='mike-defense-blue'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='mike-comcenter-blue', + products = { + presets.defenses.blue.sam:extend({ name='mike-sam-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Mike.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Tyrnyauz.lua ]]----------------- + +zones.tyrnyauz = ZoneCommand:new("Tyrnyauz") +zones.tyrnyauz.initialState = { side=1 } +zones.tyrnyauz.isHeloSpawn = true +zones.tyrnyauz:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='tyrnyauz-tent-red', + products = { + presets.special.red.infantry:extend({ name='tyrnyauz-defense-red'}), + presets.defenses.red.infantry:extend({ name='tyrnyauz-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='tyrnyauz-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='tyrnyauz-supply-red'}), + presets.missions.supply.helo:extend({name='tyrnyauz-supply-red-2'}), + presets.missions.supply.transfer:extend({name='tyrnyauz-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='tyrnyauz-ammo-red', + products = { + presets.missions.attack.surface:extend({name='tyrnyauz-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='tyrnyauz-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='tyrnyauz-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='tyrnyauz-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='tyrnyauz-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='tyrnyauz-supply-blue'}), + presets.missions.supply.helo:extend({name='tyrnyauz-supply-blue-2'}), + presets.missions.supply.transfer:extend({name='tyrnyauz-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='tyrnyauz-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='tyrnyauz-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Tyrnyauz.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/India.lua ]]----------------- + +zones.india = ZoneCommand:new("India") +zones.india.initialState = nil +zones.india:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='india-tent-red', + products = { + presets.special.red.infantry:extend({ name='india-defense-red'}), + presets.defenses.red.infantry:extend({ name='india-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='india-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='india-supply-red'}), + presets.missions.supply.transfer:extend({name='india-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='india-ammo-red', + products = { + presets.missions.attack.surface:extend({name='india-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='india-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='india-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='india-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='india-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='india-supply-blue'}), + presets.missions.supply.transfer:extend({name='india-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='india-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='india-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/India.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/IntelCenter.lua ]]----------------- + +zones.intelcenter = ZoneCommand:new("Intel Center") +zones.intelcenter.initialState = { side=1 } +zones.intelcenter:defineUpgrades({ + [1] = + { + presets.upgrades.basic.tent:extend({ + name='intelcenter-tent-red', + products = { + presets.special.red.infantry:extend({ name='intelcenter-defense-red'}), + presets.defenses.red.infantry:extend({ name='intelcenter-garrison-red'}) + } + }), + presets.upgrades.supply.hq:extend({ + name='intelcenter-hq-red', + products = { + presets.missions.supply.convoy:extend({ name='intelcenter-supply-red'}), + presets.missions.supply.convoy:extend({ name='intelcenter-supply-red-1'}), + presets.missions.supply.transfer:extend({name='intelcenter-transfer-red'}) + } + }), + presets.upgrades.supply.antenna:extend({ + name='intelcenter-antenna-red', + products = { + } + }), + presets.upgrades.supply.antenna:extend({ + name='intelcenter-antenna-red-1', + products = { + } + }), + presets.upgrades.supply.antenna:extend({ + name='intelcenter-antenna-red-2', + products = { + } + }) + }, + [2] = + { + presets.upgrades.basic.tent:extend({ + name='intelcenter-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='intelcenter-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='intelcenter-garrison-blue'}) + } + }), + presets.upgrades.supply.hq:extend({ + name='intelcenter-hq-blue', + products = { + presets.missions.supply.convoy:extend({ name='intelcenter-supply-blue'}), + presets.missions.supply.convoy:extend({ name='intelcenter-supply-blue-1'}), + presets.missions.supply.transfer:extend({name='intelcenter-transfer-blue'}) + } + }), + presets.upgrades.supply.antenna:extend({ + name='intelcenter-antenna-blue', + products = { + } + }), + presets.upgrades.supply.antenna:extend({ + name='intelcenter-antenna-blue-1', + products = { + } + }), + presets.upgrades.supply.antenna:extend({ + name='intelcenter-antenna-blue-2', + products = { + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/IntelCenter.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Mineralnye.lua ]]----------------- + +zones.mineralnye = ZoneCommand:new("Mineralnye") +zones.mineralnye.initialState = { side=1 } +zones.mineralnye.keepActive = true +zones.mineralnye.isHeloSpawn = true +zones.mineralnye.isPlaneSpawn = true +zones.mineralnye:defineUpgrades({ + [1] = + { + presets.upgrades.basic.comPost:extend({ + name='mineralnye-compost-red', + products = { + presets.special.red.infantry:extend({ name='mineralnye-defense-red'}), + presets.defenses.red.infantry:extend({ name='mineralnye-garrison-red'}) + } + }), + presets.upgrades.supply.fuelTank:extend({ + name='mineralnye-fuel-red', + products = { + presets.missions.supply.helo:extend({name='mineralnye-supply-red'}), + presets.missions.supply.helo:extend({name='mineralnye-supply-red-1'}), + presets.missions.supply.transfer:extend({name='mineralnye-transfer-red'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='mineralnye-comcenter-red', + products = { + presets.defenses.red.sa11:extend({ name='mineralnye-airdef-red'}), + presets.missions.attack.cas:extend({name='mineralnye-cas-red', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.bai:extend({name='mineralnye-cas-red', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.strike:extend({name='mineralnye-strike-red', altitude=30000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.attack.strike:extend({name='mineralnye-strike-red-1', altitude=30000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.patrol.aircraft:extend({name='mineralnye-patrol-red', altitude=25000, range=25}) + } + }) + }, + [2] = + { + presets.upgrades.basic.comPost:extend({ + name='mineralnye-compost-blue', + products = { + presets.special.blue.infantry:extend({ name='mineralnye-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='mineralnye-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelTank:extend({ + name='mineralnye-fuel-blue', + products = { + presets.missions.supply.helo:extend({name='mineralnye-supply-blue'}), + presets.missions.supply.helo:extend({name='mineralnye-supply-blue-1'}), + presets.missions.supply.transfer:extend({name='mineralnye-transfer-blue'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='mineralnye-comcenter-blue', + products = { + presets.defenses.blue.nasams:extend({ name='mineralnye-airdef-blue'}), + presets.missions.attack.cas:extend({name='mineralnye-cas-blue', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.bai:extend({name='mineralnye-cas-blue', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.strike:extend({name='mineralnye-strike-blue', altitude=30000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.attack.strike:extend({name='mineralnye-strike-blue-1', altitude=30000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.patrol.aircraft:extend({name='mineralnye-patrol-blue', altitude=25000, range=25}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Mineralnye.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/PowerPlant.lua ]]----------------- + +zones.powerplant = ZoneCommand:new("Power Plant") +zones.powerplant.initialState = { side=1 } +zones.powerplant:defineUpgrades({ + [1] = + { + presets.upgrades.basic.tent:extend({ + name='powerplant-tent-red', + products = { + presets.special.red.infantry:extend({ name='powerplant-defense-red'}), + presets.defenses.red.infantry:extend({ name='powerplant-garrison-red'}) + } + }), + presets.upgrades.supply.powerplant1:extend({ + name='powerplant-building-red-1', + products = { + presets.missions.supply.convoy:extend({ name='powerplant-supply-red'}), + presets.missions.supply.transfer:extend({name='powerplant-transfer-red'}) + } + }), + presets.upgrades.supply.powerplant2:extend({ + name='powerplant-building-red-2', + products = { + presets.missions.supply.convoy:extend({ name='powerplant-supply-red-1'}), + presets.missions.supply.transfer:extend({name='powerplant-transfer-red'}) + } + }) + }, + [2] = + { + presets.upgrades.basic.tent:extend({ + name='powerplant-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='powerplant-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='powerplant-garrison-blue'}) + } + }), + presets.upgrades.supply.powerplant1:extend({ + name='powerplant-building-blue-1', + products = { + presets.missions.supply.convoy:extend({ name='powerplant-supply-blue'}), + presets.missions.supply.transfer:extend({name='powerplant-transfer-blue'}) + } + }), + presets.upgrades.supply.powerplant2:extend({ + name='powerplant-building-blue-2', + products = { + presets.missions.supply.convoy:extend({ name='powerplant-supply-blue-1'}), + presets.missions.supply.transfer:extend({name='powerplant-transfer-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/PowerPlant.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Zugdidi.lua ]]----------------- + +zones.zugdidi = ZoneCommand:new("Zugdidi") +zones.zugdidi.initialState = { side=1 } +zones.zugdidi:defineUpgrades({ + [1] = + { + presets.upgrades.basic.comPost:extend({ + name='zugdidi-compost-red', + products = { + presets.missions.supply.transfer:extend({name='zugdidi-transfer-red'}), + presets.special.red.infantry:extend({ name='zugdidi-defense-red'}), + presets.defenses.red.infantry:extend({ name='zugdidi-garrison-red'}), + presets.missions.attack.surface:extend({name='zugdidi-attack-red'}), + presets.missions.supply.convoy:extend({name='zugdidi-supply-red'}) + } + }), + presets.upgrades.supply.hangar:extend({ + name='zugdidi-hangar-red-1', + products = { + presets.missions.supply.helo:extend({name='zugdidi-supply-red-1'}) + } + }), + presets.upgrades.supply.hangar:extend({ + name='zugdidi-hangar-red-2', + products = { + presets.missions.supply.helo:extend({name='zugdidi-supply-red-2'}) + } + }), + presets.upgrades.supply.hangar:extend({ + name='zugdidi-hangar-red-3', + products = { + presets.missions.supply.helo:extend({name='zugdidi-supply-red-3'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='zugdidi-comcenter-red', + products = { + presets.defenses.red.sa6:extend({ name='zugdidi-sam-red'}) + } + }) + }, + [2] = + { + presets.upgrades.basic.tent:extend({ + name='zugdidi-compost-blue', + products = { + presets.missions.supply.transfer:extend({name='zugdidi-transfer-blue'}), + presets.special.blue.infantry:extend({ name='zugdidi-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='zugdidi-garrison-blue'}), + presets.missions.attack.surface:extend({name='zugdidi-attack-blue'}), + presets.missions.supply.convoy:extend({name='zugdidi-supply-blue'}) + } + }), + presets.upgrades.supply.hangar:extend({ + name='zugdidi-hangar-blue-1', + products = { + presets.missions.supply.helo:extend({name='zugdidi-supply-blue-1'}) + } + }), + presets.upgrades.supply.hangar:extend({ + name='zugdidi-hangar-blue-2', + products = { + presets.missions.supply.helo:extend({name='zugdidi-supply-blue-2'}) + } + }), + presets.upgrades.supply.hangar:extend({ + name='zugdidi-hangar-blue-3', + products = { + presets.missions.supply.helo:extend({name='zugdidi-supply-blue-3'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='zugdidi-comcenter-blue', + products = { + presets.defenses.blue.shorad:extend({ name='zugdidi-sam-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Zugdidi.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Babugent.lua ]]----------------- + +zones.babugent = ZoneCommand:new("Babugent") +zones.babugent.initialState = { side=1 } +zones.babugent:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='babugent-tent-red', + products = { + presets.special.red.infantry:extend({ name='babugent-defense-red'}), + presets.defenses.red.infantry:extend({ name='babugent-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='babugent-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='babugent-supply-red'}), + presets.missions.supply.helo:extend({name='babugent-supply-red-2'}), + presets.missions.supply.transfer:extend({name='babugent-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='babugent-ammo-red', + products = { + presets.missions.attack.surface:extend({name='babugent-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='babugent-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='babugent-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='babugent-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='babugent-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='babugent-supply-blue'}), + presets.missions.supply.helo:extend({name='babugent-supply-blue-2'}), + presets.missions.supply.transfer:extend({name='babugent-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='babugent-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='babugent-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Babugent.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Kislovodsk.lua ]]----------------- + +zones.kislovodsk = ZoneCommand:new("Kislovodsk") +zones.kislovodsk.initialState = { side=1 } +zones.kislovodsk:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='kislovodsk-tent-red', + products = { + presets.special.red.infantry:extend({ name='kislovodsk-defense-red'}), + presets.defenses.red.infantry:extend({ name='kislovodsk-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='kislovodsk-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='kislovodsk-supply-red'}), + presets.missions.supply.transfer:extend({name='kislovodsk-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='kislovodsk-ammo-red', + products = { + presets.missions.attack.surface:extend({name='kislovodsk-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='kislovodsk-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='kislovodsk-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='kislovodsk-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='kislovodsk-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='kislovodsk-supply-blue'}), + presets.missions.supply.transfer:extend({name='kislovodsk-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='kislovodsk-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='kislovodsk-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Kislovodsk.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Gudauta.lua ]]----------------- + +zones.gudauta = ZoneCommand:new("Gudauta") +zones.gudauta.initialState = { side=1 } +zones.gudauta.keepActive = true +zones.gudauta.maxResource = 50000 +zones.gudauta:defineUpgrades({ + [1] = + { + presets.upgrades.basic.comPost:extend({ + name='gudauta-compost-red', + products = { + presets.special.red.infantry:extend({ name='gudauta-defense-red'}), + presets.defenses.red.infantry:extend({ name='gudauta-garrison-red'}) + } + }), + presets.upgrades.supply.fuelTank:extend({ + name='gudauta-fuel-red', + products = { + presets.missions.supply.helo:extend({name='gudauta-supply-red'}), + presets.missions.supply.helo:extend({name='gudauta-supply-red-1'}), + presets.missions.supply.transfer:extend({name='gudauta-transfer-red'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='gudauta-comcenter-red', + products = { + presets.defenses.red.sam:extend({ name='gudauta-airdef-red'}), + presets.missions.attack.sead:extend({name='gudauta-sead-red', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.attack.sead:extend({name='gudauta-sead-red-1', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.attack.cas:extend({name='gudauta-cas-red', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.bai:extend({name='gudauta-cas-red', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.patrol.aircraft:extend({name='gudauta-patrol-red', altitude=25000, range=25}) + } + }) + }, + [2] = + { + presets.upgrades.basic.comPost:extend({ + name='gudauta-compost-blue', + products = { + presets.special.blue.infantry:extend({ name='gudauta-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='gudauta-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelTank:extend({ + name='gudauta-fuel-blue', + products = { + presets.missions.supply.helo:extend({name='gudauta-supply-blue'}), + presets.missions.supply.helo:extend({name='gudauta-supply-blue-1'}), + presets.missions.supply.transfer:extend({name='gudauta-transfer-blue'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='gudauta-comcenter-blue', + products = { + presets.defenses.blue.sam:extend({ name='gudauta-airdef-blue'}), + presets.missions.attack.sead:extend({name='gudauta-sead-blue', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.attack.sead:extend({name='gudauta-sead-blue-1', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.attack.cas:extend({name='gudauta-cas-blue', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.bai:extend({name='gudauta-cas-blue', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.patrol.aircraft:extend({name='gudauta-patrol-blue', altitude=25000, range=25}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Gudauta.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Distillery.lua ]]----------------- + +zones.distillery = ZoneCommand:new("Distillery") +zones.distillery.initialState = { side=1 } +zones.distillery:defineUpgrades({ + [1] = + { + presets.upgrades.basic.tent:extend({ + name='distillery-tent-red', + products = { + presets.special.red.infantry:extend({ name='distillery-defense-red'}), + presets.defenses.red.infantry:extend({ name='distillery-garrison-red'}) + } + }), + presets.upgrades.supply.factory1:extend({ + name='distillery-prod-red-1', + products = { + presets.missions.supply.convoy:extend({ name='distillery-supply-red-1'}), + presets.missions.supply.transfer:extend({name='distillery-transfer-red'}) + } + }), + presets.upgrades.supply.factory2:extend({ + name='distillery-prod-red-2', + products = { + presets.missions.supply.convoy:extend({ name='distillery-supply-red-2', cost=2000}), + presets.missions.supply.transfer:extend({name='distillery-transfer-red2'}) + } + }), + presets.upgrades.supply.factoryTank:extend({ + name='distillery-tank-red-1', + products = { + } + }), + presets.upgrades.supply.factoryTank:extend({ + name='distillery-tank-red-2', + products = { + } + }), + presets.upgrades.supply.factoryTank:extend({ + name='distillery-tank-red-3', + products = { + } + }) + }, + [2] = + { + presets.upgrades.basic.tent:extend({ + name='distillery-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='distillery-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='distillery-garrison-blue'}) + } + }), + presets.upgrades.supply.factory1:extend({ + name='distillery-prod-blue-1', + products = { + presets.missions.supply.convoy:extend({ name='distillery-supply-blue-1'}), + presets.missions.supply.transfer:extend({name='distillery-transfer-blue'}) + } + }), + presets.upgrades.supply.factory2:extend({ + name='distillery-prod-blue-2', + products = { + presets.missions.supply.convoy:extend({ name='distillery-supply-blue-2', cost=2000}), + presets.missions.supply.transfer:extend({name='distillery-transfer-blue2'}) + } + }), + presets.upgrades.supply.factoryTank:extend({ + name='distillery-tank-blue-1', + products = { + } + }), + presets.upgrades.supply.factoryTank:extend({ + name='distillery-tank-blue-2', + products = { + } + }), + presets.upgrades.supply.factoryTank:extend({ + name='distillery-tank-blue-3', + products = { + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Distillery.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Sochi.lua ]]----------------- + +zones.sochi = ZoneCommand:new("Sochi") +zones.sochi.initialState = { side=1 } +zones.sochi.keepActive = true +zones.sochi.maxResource = 50000 +zones.sochi:defineUpgrades({ + [1] = + { + presets.upgrades.basic.comPost:extend({ + name='sochi-compost-red', + products = { + presets.special.red.infantry:extend({ name='sochi-defense-red'}), + presets.defenses.red.infantry:extend({ name='sochi-garrison-red'}) + } + }), + presets.upgrades.supply.fuelTank:extend({ + name='sochi-fuel-red', + products = { + presets.missions.supply.helo:extend({name='sochi-supply-red-1'}), + presets.missions.supply.helo:extend({name='sochi-supply-red-2'}), + presets.missions.supply.convoy_escorted:extend({name='sochi-supply-red-3'}), + presets.missions.supply.transfer:extend({name='sochi-transfer-red'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='sochi-comcenter-red', + products = { + presets.defenses.red.sa10:extend({ name='sochi-airdef-red'}), + presets.missions.attack.sead:extend({name='sochi-sead-red', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.attack.strike:extend({name='sochi-strike-red', altitude=30000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.patrol.aircraft:extend({name='sochi-patrol-red', altitude=25000, range=25}), + presets.missions.patrol.aircraft:extend({name='sochi-patrol-red-1', altitude=25000, range=25}), + presets.missions.attack.cas:extend({name='sochi-cas-red', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.bai:extend({name='sochi-cas-red', altitude=10000, expend=AI.Task.WeaponExpend.ONE}) + } + }) + }, + [2] = + { + presets.upgrades.basic.comPost:extend({ + name='sochi-compost-blue', + products = { + presets.special.blue.infantry:extend({ name='sochi-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='sochi-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelTank:extend({ + name='sochi-fuel-blue', + products = { + presets.missions.supply.helo:extend({name='sochi-supply-blue-1'}), + presets.missions.supply.helo:extend({name='sochi-supply-blue-2'}), + presets.missions.supply.convoy_escorted:extend({name='sochi-supply-blue-3'}), + presets.missions.supply.transfer:extend({name='sochi-transfer-blue'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='sochi-comcenter-blue', + products = { + presets.defenses.blue.patriot:extend({ name='sochi-airdef-blue'}), + presets.missions.attack.sead:extend({name='sochi-sead-blue', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.attack.strike:extend({name='sochi-strike-blue', altitude=30000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.patrol.aircraft:extend({name='sochi-patrol-blue', altitude=25000, range=25}), + presets.missions.attack.cas:extend({name='sochi-cas-blue', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.bai:extend({name='sochi-cas-blue', altitude=10000, expend=AI.Task.WeaponExpend.ONE}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Sochi.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Golf.lua ]]----------------- + +zones.golf = ZoneCommand:new("Golf") +zones.golf.initialState = nil +zones.golf.isHeloSpawn = true +zones.golf:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='golf-tent-red', + products = { + presets.special.red.infantry:extend({ name='golf-defense-red'}), + presets.defenses.red.infantry:extend({ name='golf-garrison-red'}) + } + }), + presets.upgrades.supply.fuelTank:extend({ + name='golf-fuel-red', + products = { + presets.missions.supply.helo:extend({name='golf-supply-red'}), + presets.missions.supply.helo:extend({name='golf-supply-red-1'}), + presets.missions.supply.transfer:extend({name='golf-transfer-red'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='golf-comcenter-red', + products = { + presets.defenses.red.shorad:extend({name='golf-sam-red'}), + presets.missions.attack.helo:extend({name='golf-cas-red', altitude=200, expend=AI.Task.WeaponExpend.HALF }) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='golf-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='golf-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='golf-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelTankFarp:extend({ + name='golf-fuel-blue', + products = { + presets.missions.supply.helo:extend({name='golf-supply-blue'}), + presets.missions.supply.helo:extend({name='golf-supply-blue-1'}), + presets.missions.supply.transfer:extend({name='golf-transfer-blue'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='golf-comcenter-blue', + products = { + presets.defenses.blue.shorad:extend({name='golf-sam-blue'}), + presets.missions.attack.helo:extend({name='golf-cas-blue', altitude=200, expend=AI.Task.WeaponExpend.HALF }) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Golf.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Charlie.lua ]]----------------- + +zones.charlie = ZoneCommand:new("Charlie") +zones.charlie.initialState = { side=2 } +zones.charlie.keepActive = true +zones.charlie:defineUpgrades({ + [1] = + { + presets.upgrades.basic.tent:extend({ + name='charlie-tent-red', + products = { + presets.special.red.infantry:extend({ name='charlie-defense-red'}), + presets.defenses.red.infantry:extend({ name='charlie-garrison-red'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='charlie-comcenter-red', + products = { + presets.defenses.red.sam:extend({ name='charlie-sam-red'}) + } + }) + }, + [2] = + { + presets.upgrades.basic.tent:extend({ + name='charlie-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='charlie-defense-red'}), + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='charlie-comcenter-blue', + products = { + presets.defenses.blue.sam:extend({ name='charlie-sam-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Charlie.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Lentehi.lua ]]----------------- + +zones.lentehi = ZoneCommand:new("Lentehi") +zones.lentehi.initialState = { side=1 } +zones.lentehi:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='lentehi-tent-red', + products = { + presets.special.red.infantry:extend({ name='lentehi-defense-red'}), + presets.defenses.red.infantry:extend({ name='lentehi-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='lentehi-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='lentehi-supply-red'}), + presets.missions.supply.helo:extend({name='lentehi-supply-red-2'}), + presets.missions.supply.transfer:extend({name='lentehi-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='lentehi-ammo-red', + products = { + presets.missions.attack.surface:extend({name='lentehi-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='lentehi-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='lentehi-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='lentehi-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='lentehi-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='lentehi-supply-blue'}), + presets.missions.supply.helo:extend({name='lentehi-supply-blue-2'}), + presets.missions.supply.transfer:extend({name='lentehi-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='lentehi-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='lentehi-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Lentehi.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Refinery.lua ]]----------------- + +zones.refinery = ZoneCommand:new("Refinery") +zones.refinery.initialState = { side=1 } +zones.refinery:defineUpgrades({ + [1] = + { + presets.upgrades.basic.tent:extend({ + name='refinery-tent-red', + products = { + presets.special.red.infantry:extend({ name='refinery-defense-red'}), + presets.defenses.red.infantry:extend({ name='refinery-garrison-red'}) + } + }), + presets.upgrades.supply.refinery1:extend({ + name='refinery-building-red', + products = { + presets.missions.supply.convoy:extend({ name='refinery-supply-red'}), + presets.missions.supply.convoy:extend({ name='refinery-supply-red-1'}), + presets.missions.supply.helo:extend({ name='refinery-supply-red-2'}), + presets.missions.supply.transfer:extend({name='refinery-transfer-red'}) + } + }) + }, + [2] = + { + presets.upgrades.basic.tent:extend({ + name='refinery-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='refinery-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='refinery-garrison-blue'}) + } + }), + presets.upgrades.supply.refinery1:extend({ + name='refinery-building-blue', + products = { + presets.missions.supply.convoy:extend({ name='refinery-supply-blue'}), + presets.missions.supply.convoy:extend({ name='refinery-supply-blue-1'}), + presets.missions.supply.helo:extend({ name='refinery-supply-blue-2'}), + presets.missions.supply.transfer:extend({name='refinery-transfer-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Refinery.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Mozdok.lua ]]----------------- + +zones.mozdok = ZoneCommand:new("Mozdok") +zones.mozdok.initialState = { side=1 } +zones.mozdok.keepActive = true +zones.mozdok.maxResource = 50000 +zones.mozdok:defineUpgrades({ + [1] = + { + presets.upgrades.basic.comPost:extend({ + name='mozdok-compost-red', + products = { + presets.special.red.infantry:extend({ name='mozdok-defense-red'}), + presets.defenses.red.infantry:extend({ name='mozdok-garrison-red'}) + } + }), + presets.upgrades.supply.fuelTank:extend({ + name='mozdok-fuel-red', + products = { + presets.missions.supply.helo:extend({name='mozdok-supply-red-1'}), + presets.missions.supply.helo:extend({name='mozdok-supply-red-2'}), + presets.missions.supply.convoy_escorted:extend({name='mozdok-supply-red-3'}), + presets.missions.supply.transfer:extend({name='mozdok-transfer-red'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='mozdok-comcenter-red', + products = { + presets.defenses.red.sa10:extend({ name='mozdok-airdef-red'}), + presets.missions.patrol.aircraft:extend({name='mozdok-patrol-red', altitude=25000, range=25}), + presets.missions.attack.cas:extend({name='mozdok-cas-red', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.bai:extend({name='mozdok-cas-red', altitude=10000, expend=AI.Task.WeaponExpend.ONE}) + } + }) + }, + [2] = + { + presets.upgrades.basic.comPost:extend({ + name='mozdok-compost-blue', + products = { + presets.special.blue.infantry:extend({ name='mozdok-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='mozdok-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelTank:extend({ + name='mozdok-fuel-blue', + products = { + presets.missions.supply.helo:extend({name='mozdok-supply-blue-1'}), + presets.missions.supply.helo:extend({name='mozdok-supply-blue-2'}), + presets.missions.supply.convoy_escorted:extend({name='mozdok-supply-blue-3'}), + presets.missions.supply.transfer:extend({name='mozdok-transfer-blue'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='mozdok-comcenter-blue', + products = { + presets.defenses.blue.shorad:extend({ name='mozdok-airdef-blue'}), + presets.missions.patrol.aircraft:extend({name='mozdok-patrol-blue', altitude=25000, range=25}), + presets.missions.attack.cas:extend({name='mozdok-cas-blue', altitude=15000, cost=50, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.cas:extend({name='mozdok-cas-blue-1', altitude=15000, cost=50, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.bai:extend({name='mozdok-cas-blue', altitude=15000, cost=50, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.bai:extend({name='mozdok-cas-blue-1', altitude=15000, cost=50, expend=AI.Task.WeaponExpend.ONE}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Mozdok.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Lima.lua ]]----------------- + +zones.lima = ZoneCommand:new("Lima") +zones.lima.initialState = { side=1 } +zones.lima:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='lima-tent-red', + products = { + presets.special.red.infantry:extend({ name='lima-defense-red'}), + presets.defenses.red.infantry:extend({ name='lima-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='lima-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='lima-supply-red'}), + presets.missions.supply.helo:extend({name='lima-supply-red-1'}), + presets.missions.supply.transfer:extend({name='lima-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='lima-ammo-red', + products = { + presets.missions.attack.surface:extend({name='lima-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='lima-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='lima-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='lima-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='lima-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='lima-supply-blue'}), + presets.missions.supply.helo:extend({name='lima-supply-blue-1'}), + presets.missions.supply.transfer:extend({name='lima-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='lima-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='lima-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Lima.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Oscar.lua ]]----------------- + +zones.oscar = ZoneCommand:new("Oscar") +zones.oscar.initialState = { side=1 } +zones.oscar:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='oscar-tent-red', + products = { + presets.special.red.infantry:extend({ name='oscar-defense-red'}), + presets.defenses.red.infantry:extend({ name='oscar-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='oscar-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='oscar-supply-red'}), + presets.missions.supply.transfer:extend({name='oscar-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='oscar-ammo-red', + products = { + presets.missions.attack.surface:extend({name='oscar-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='oscar-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='oscar-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='oscar-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='oscar-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='oscar-supply-blue'}), + presets.missions.supply.transfer:extend({name='oscar-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='oscar-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='oscar-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Oscar.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Nalchik.lua ]]----------------- + +zones.nalchik = ZoneCommand:new("Nalchik") +zones.nalchik.initialState = { side=1 } +zones.nalchik.keepActive = true +zones.nalchik.isHeloSpawn = true +zones.nalchik.isPlaneSpawn = true +zones.nalchik:defineUpgrades({ + [1] = + { + presets.upgrades.basic.comPost:extend({ + name='nalchik-compost-red', + products = { + presets.special.red.infantry:extend({ name='nalchik-defense-red'}), + presets.defenses.red.infantry:extend({ name='nalchik-garrison-red'}) + } + }), + presets.upgrades.supply.fuelTank:extend({ + name='nalchik-fuel-red', + products = { + presets.missions.supply.helo:extend({name='nalchik-supply-red-1'}), + presets.missions.supply.helo:extend({name='nalchik-supply-red-2'}), + presets.missions.supply.transfer:extend({name='nalchik-transfer-red'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='nalchik-comcenter-red', + products = { + presets.defenses.red.sa3:extend({ name='nalchik-airdef-red'}), + presets.missions.attack.sead:extend({name='nalchik-sead-red', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.attack.cas:extend({name='nalchik-cas-red', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.bai:extend({name='nalchik-cas-red', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.strike:extend({name='nalchik-strike-red', altitude=30000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.patrol.aircraft:extend({name='nalchik-patrol-red', altitude=25000, range=25}), + presets.missions.patrol.aircraft:extend({name='nalchik-patrol-red-2', altitude=25000, range=25}), + presets.missions.support.awacs:extend({name='nalchik-awacs-red', altitude=30000, freq=251.2}), + presets.missions.support.tanker:extend({name='nalchik-tanker-red', altitude=30000, freq=252.2, tacan='40', variant='Drogue'}) + } + }) + }, + [2] = + { + presets.upgrades.basic.comPost:extend({ + name='nalchik-compost-blue', + products = { + presets.special.blue.infantry:extend({ name='nalchik-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='nalchik-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelTank:extend({ + name='nalchik-fuel-blue', + products = { + presets.missions.supply.helo:extend({name='nalchik-supply-blue-1'}), + presets.missions.supply.helo:extend({name='nalchik-supply-blue-2'}), + presets.missions.supply.transfer:extend({name='nalchik-transfer-blue'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='nalchik-comcenter-blue', + products = { + presets.defenses.blue.nasams:extend({ name='nalchik-airdef-blue'}), + presets.missions.support.awacs:extend({name='nalchik-awacs-blue', altitude=30000, freq=259.5}), + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Nalchik.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Digora.lua ]]----------------- + +zones.digora = ZoneCommand:new("Digora") +zones.digora.initialState = { side=1 } +zones.digora:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='digora-tent-red', + products = { + presets.special.red.infantry:extend({ name='digora-defense-red'}), + presets.defenses.red.infantry:extend({ name='digora-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='digora-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='digora-supply-red'}), + presets.missions.supply.transfer:extend({name='digora-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='digora-ammo-red', + products = { + presets.missions.attack.surface:extend({name='digora-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='digora-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='digora-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='digora-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='digora-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='digora-supply-blue'}), + presets.missions.supply.transfer:extend({name='digora-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='digora-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='digora-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Digora.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Uniform.lua ]]----------------- + +zones.uniform = ZoneCommand:new("Uniform") +zones.uniform.initialState = { side=1 } +zones.uniform:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='uniform-tent-red', + products = { + presets.special.red.infantry:extend({ name='uniform-defense-red'}), + presets.defenses.red.infantry:extend({ name='uniform-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='uniform-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='uniform-supply-red'}), + presets.missions.supply.transfer:extend({name='uniform-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='uniform-ammo-red', + products = { + presets.missions.attack.surface:extend({name='uniform-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='uniform-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='uniform-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='uniform-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='uniform-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='uniform-supply-blue'}), + presets.missions.supply.transfer:extend({name='uniform-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='uniform-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='uniform-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Uniform.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Factory.lua ]]----------------- + +zones.factory = ZoneCommand:new("Factory") +zones.factory.initialState = { side=2 } +zones.factory:defineUpgrades({ + [1] = + { + presets.upgrades.basic.tent:extend({ + name='factory-tent-red', + products = { + presets.special.red.infantry:extend({ name='factory-defense-red'}), + presets.defenses.red.infantry:extend({ name='factory-garrison-red'}) + } + }), + presets.upgrades.supply.factory1:extend({ + name='factory-prod-red-1', + products = { + presets.missions.supply.convoy:extend({ name='factory-supply-red-1'}), + presets.missions.supply.transfer:extend({name='factory-transfer-red'}) + } + }), + presets.upgrades.supply.factory2:extend({ + name='factory-prod-red-2', + products = { + presets.missions.supply.convoy:extend({ name='factory-supply-red-2', cost=2000}), + presets.missions.supply.transfer:extend({name='factory-transfer-red2'}) + } + }), + presets.upgrades.supply.factoryTank:extend({ + name='factory-tank-red-1', + products = { + } + }), + presets.upgrades.supply.factoryTank:extend({ + name='factory-tank-red-2', + products = { + } + }), + presets.upgrades.supply.factoryTank:extend({ + name='factory-tank-red-3', + products = { + } + }) + }, + [2] = + { + presets.upgrades.basic.tent:extend({ + name='factory-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='factory-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='factory-garrison-blue'}) + } + }), + presets.upgrades.supply.factory1:extend({ + name='factory-prod-blue-1', + products = { + presets.missions.supply.convoy:extend({ name='factory-supply-blue-1'}), + presets.missions.supply.transfer:extend({name='factory-transfer-blue'}) + } + }), + presets.upgrades.supply.factory2:extend({ + name='factory-prod-blue-2', + products = { + presets.missions.supply.convoy:extend({ name='factory-supply-blue-2', cost=2000}), + presets.missions.supply.transfer:extend({name='factory-transfer-blue2'}) + } + }), + presets.upgrades.supply.factoryTank:extend({ + name='factory-tank-blue-1', + products = { + } + }), + presets.upgrades.supply.factoryTank:extend({ + name='factory-tank-blue-2', + products = { + } + }), + presets.upgrades.supply.factoryTank:extend({ + name='factory-tank-blue-3', + products = { + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Factory.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Senaki.lua ]]----------------- + +zones.senaki = ZoneCommand:new("Senaki") +zones.senaki.initialState = { side=1 } +zones.senaki.keepActive = true +zones.senaki.isHeloSpawn = true +zones.senaki.isPlaneSpawn = true +zones.senaki.maxResource = 50000 +zones.senaki:defineUpgrades({ + [1] = + { + presets.upgrades.basic.comPost:extend({ + name='senaki-compost-red', + products = { + presets.special.red.infantry:extend({ name='senaki-defense-red'}), + presets.defenses.red.infantry:extend({ name='senaki-garrison-red'}) + } + }), + presets.upgrades.supply.fuelTank:extend({ + name='senaki-fuel-red', + products = { + presets.missions.supply.helo:extend({name='senaki-supply-red-1'}), + presets.missions.supply.helo:extend({name='senaki-supply-red-2'}), + presets.missions.supply.transfer:extend({name='senaki-transfer-red'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='senaki-comcenter-red', + products = { + presets.defenses.red.sa3:extend({ name='senaki-airdef-red'}), + presets.missions.attack.sead:extend({name='senaki-sead-red', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.attack.cas:extend({name='senaki-cas-red', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.bai:extend({name='senaki-cas-red', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.strike:extend({name='senaki-strike-red', altitude=20000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.patrol.aircraft:extend({name='senaki-patrol-red', altitude=25000, range=25}), + presets.missions.patrol.aircraft:extend({name='senaki-patrol-red-2', altitude=20000, range=25}) + } + }) + }, + [2] = + { + presets.upgrades.basic.comPost:extend({ + name='senaki-compost-blue', + products = { + presets.special.blue.infantry:extend({ name='senaki-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='senaki-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelTank:extend({ + name='senaki-fuel-blue', + products = { + presets.missions.supply.helo:extend({name='senaki-supply-blue-1'}), + presets.missions.supply.helo:extend({name='senaki-supply-blue-2'}), + presets.missions.supply.transfer:extend({name='senaki-transfer-blue'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='senaki-comcenter-blue', + products = { + presets.defenses.blue.shorad:extend({ name='senaki-airdef-blue'}), + presets.missions.attack.sead:extend({name='senaki-sead-blue', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.attack.cas:extend({name='senaki-cas-blue', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.bai:extend({name='senaki-cas-blue', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.strike:extend({name='senaki-strike-blue', altitude=20000, expend=AI.Task.WeaponExpend.TWO}), + presets.missions.patrol.aircraft:extend({name='senaki-patrol-blue', altitude=25000, range=25}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Senaki.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Kutaisi.lua ]]----------------- + +zones.kutaisi = ZoneCommand:new("Kutaisi") +zones.kutaisi.initialState = { side=1 } +zones.kutaisi.keepActive = true +zones.kutaisi.maxResource = 50000 +zones.kutaisi:defineUpgrades({ + [1] = + { + presets.upgrades.basic.comPost:extend({ + name='kutaisi-compost-red', + products = { + presets.special.red.infantry:extend({ name='kutaisi-defense-red'}), + presets.defenses.red.infantry:extend({ name='kutaisi-garrison-red'}) + } + }), + presets.upgrades.supply.fuelTank:extend({ + name='kutaisi-fuel-red', + products = { + presets.missions.supply.helo:extend({name='kutaisi-supply-red-1'}), + presets.missions.supply.helo:extend({name='kutaisi-supply-red-2'}), + presets.missions.supply.transfer:extend({name='kutaisi-transfer-red'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='kutaisi-comcenter-red', + products = { + presets.defenses.red.shorad:extend({ name='kutaisi-airdef-red'}), + presets.missions.attack.sead:extend({name='kutaisi-sead-red', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.attack.cas:extend({name='kutaisi-cas-red', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.bai:extend({name='kutaisi-cas-red', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.strike:extend({name='kutaisi-strike-red', altitude=20000, expend=AI.Task.WeaponExpend.HALF}), + presets.missions.patrol.aircraft:extend({name='kutaisi-patrol-red', altitude=25000, range=25}), + presets.missions.patrol.aircraft:extend({name='kutaisi-patrol-red-2', altitude=25000, range=25}) + } + }) + }, + [2] = + { + presets.upgrades.basic.comPost:extend({ + name='kutaisi-compost-blue', + products = { + presets.special.blue.infantry:extend({ name='kutaisi-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='kutaisi-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelTank:extend({ + name='kutaisi-fuel-blue', + products = { + presets.missions.supply.helo:extend({name='kutaisi-supply-blue-1'}), + presets.missions.supply.helo:extend({name='kutaisi-supply-blue-2'}), + presets.missions.supply.transfer:extend({name='kutaisi-transfer-blue'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='kutaisi-comcenter-blue', + products = { + presets.defenses.blue.shorad:extend({ name='kutaisi-airdef-blue'}), + presets.missions.attack.sead:extend({name='kutaisi-sead-blue', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.attack.cas:extend({name='kutaisi-cas-blue', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.bai:extend({name='kutaisi-cas-blue', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.strike:extend({name='kutaisi-strike-blue', altitude=20000, expend=AI.Task.WeaponExpend.TWO}), + presets.missions.patrol.aircraft:extend({name='kutaisi-patrol-blue', altitude=25000, range=25}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Kutaisi.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Prohladniy.lua ]]----------------- + +zones.prohladniy = ZoneCommand:new("Prohladniy") +zones.prohladniy.initialState = { side=1 } +zones.prohladniy:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='prohladniy-tent-red', + products = { + presets.special.red.infantry:extend({ name='prohladniy-defense-red'}), + presets.defenses.red.infantry:extend({ name='prohladniy-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='prohladniy-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='prohladniy-supply-red'}), + presets.missions.supply.transfer:extend({name='prohladniy-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='prohladniy-ammo-red', + products = { + presets.missions.attack.surface:extend({name='prohladniy-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='prohladniy-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='prohladniy-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='prohladniy-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='prohladniy-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='prohladniy-supply-blue'}), + presets.missions.supply.transfer:extend({name='prohladniy-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='prohladniy-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='prohladniy-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Prohladniy.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Tallyk.lua ]]----------------- + +zones.tallyk = ZoneCommand:new("Tallyk") +zones.tallyk.initialState = { side=1 } +zones.tallyk.keepActive = true +zones.tallyk:defineUpgrades({ + [1] = + { + presets.upgrades.basic.tent:extend({ + name='tallyk-tent-red', + products = { + presets.special.red.infantry:extend({ name='tallyk-defense-red'}), + presets.defenses.red.infantry:extend({ name='tallyk-garrison-red'}), + presets.missions.attack.surface:extend({name='tallyk-assault-red'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='tallyk-comcenter-red', + products = { + presets.defenses.red.sam:extend({ name='tallyk-sam-red'}) + } + }) + }, + [2] = + { + presets.upgrades.basic.tent:extend({ + name='tallyk-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='tallyk-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='tallyk-garrison-blue'}), + presets.missions.attack.surface:extend({name='tallyk-assault-blue'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='tallyk-comcenter-blue', + products = { + presets.defenses.blue.sam:extend({ name='tallyk-sam-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Tallyk.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Terek.lua ]]----------------- + +zones.terek = ZoneCommand:new("Terek") +zones.terek.initialState = { side=1 } +zones.terek:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='terek-tent-red', + products = { + presets.special.red.infantry:extend({ name='terek-defense-red'}), + presets.defenses.red.infantry:extend({ name='terek-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='terek-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='terek-supply-red'}), + presets.missions.supply.transfer:extend({name='terek-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='terek-ammo-red', + products = { + presets.missions.attack.surface:extend({name='terek-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='terek-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='terek-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='terek-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='terek-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='terek-supply-blue'}), + presets.missions.supply.transfer:extend({name='terek-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='terek-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='terek-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Terek.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Humara.lua ]]----------------- + +zones.humara = ZoneCommand:new("Humara") +zones.humara.initialState = { side=1 } +zones.humara:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='humara-tent-red', + products = { + presets.special.red.infantry:extend({ name='humara-defense-red'}), + presets.defenses.red.infantry:extend({ name='humara-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='humara-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='humara-supply-red'}), + presets.missions.supply.transfer:extend({name='humara-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='humara-ammo-red', + products = { + presets.missions.attack.surface:extend({name='humara-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='humara-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='humara-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='humara-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='humara-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='humara-supply-blue'}), + presets.missions.supply.transfer:extend({name='humara-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='humara-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='humara-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Humara.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Ochamchira.lua ]]----------------- + +zones.ochamchira = ZoneCommand:new("Ochamchira") +zones.ochamchira.initialState = { side=1 } +zones.ochamchira:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='ochamchira-tent-red', + products = { + presets.special.red.infantry:extend({ name='ochamchira-defense-red'}), + presets.defenses.red.infantry:extend({ name='ochamchira-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='ochamchira-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='ochamchira-supply-red'}), + presets.missions.supply.transfer:extend({name='ochamchira-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='ochamchira-ammo-red', + products = { + presets.missions.attack.surface:extend({name='ochamchira-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='ochamchira-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='ochamchira-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='ochamchira-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='ochamchira-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='ochamchira-supply-blue'}), + presets.missions.supply.transfer:extend({name='ochamchira-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='ochamchira-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='ochamchira-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Ochamchira.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/November.lua ]]----------------- + +zones.november = ZoneCommand:new("November") +zones.november.initialState = { side=1 } +zones.november.isHeloSpawn = true +zones.november:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='november-tent-red', + products = { + presets.special.red.infantry:extend({ name='november-defense-red'}), + presets.defenses.red.infantry:extend({ name='november-garrison-red'}) + } + }), + presets.upgrades.supply.fuelTankFarp:extend({ + name='november-fuel-red', + products = { + presets.missions.supply.helo:extend({name='november-supply-red'}), + presets.missions.supply.helo:extend({name='november-supply-red-1'}), + presets.missions.supply.transfer:extend({name='november-transfer-red'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='november-comcenter-red', + products = { + presets.defenses.red.shorad:extend({name='november-sam-red'}), + presets.missions.attack.helo:extend({name='november-cas-red', altitude=200, expend=AI.Task.WeaponExpend.HALF }) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='november-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='november-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='november-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelTankFarp:extend({ + name='november-fuel-blue', + products = { + presets.missions.supply.helo:extend({name='november-supply-blue'}), + presets.missions.supply.helo:extend({name='november-supply-blue-1'}), + presets.missions.supply.transfer:extend({name='november-transfer-blue'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='november-comcenter-blue', + products = { + presets.defenses.blue.shorad:extend({name='november-sam-blue'}), + presets.missions.attack.helo:extend({name='november-cas-blue', altitude=200, expend=AI.Task.WeaponExpend.HALF }) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/November.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/XRay.lua ]]----------------- + +zones.xray = ZoneCommand:new("XRay") +zones.xray.initialState = { side=1 } +zones.xray:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='xray-tent-red', + products = { + presets.special.red.infantry:extend({ name='xray-defense-red'}), + presets.defenses.red.infantry:extend({ name='xray-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='xray-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='xray-supply-red'}), + presets.missions.supply.helo:extend({name='xray-supply-red-2'}), + presets.missions.supply.transfer:extend({name='xray-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='xray-ammo-red', + products = { + presets.missions.attack.surface:extend({name='xray-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='xray-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='xray-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='xray-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='xray-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='xray-supply-blue'}), + presets.missions.supply.helo:extend({name='xray-supply-blue-2'}), + presets.missions.supply.transfer:extend({name='xray-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='xray-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='xray-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/XRay.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Whiskey.lua ]]----------------- + +zones.whiskey = ZoneCommand:new("Whiskey") +zones.whiskey.initialState = { side=1 } +zones.whiskey:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='whiskey-tent-red', + products = { + presets.special.red.infantry:extend({ name='whiskey-defense-red'}), + presets.defenses.red.infantry:extend({ name='whiskey-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='whiskey-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='whiskey-supply-red'}), + presets.missions.supply.transfer:extend({name='whiskey-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='whiskey-ammo-red', + products = { + presets.missions.attack.surface:extend({name='whiskey-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='whiskey-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='whiskey-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='whiskey-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='whiskey-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='whiskey-supply-blue'}), + presets.missions.supply.transfer:extend({name='whiskey-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='whiskey-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='whiskey-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Whiskey.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Mine.lua ]]----------------- + +zones.mine = ZoneCommand:new("Mine") +zones.mine.initialState = { side=1 } +zones.mine:defineUpgrades({ + [1] = + { + presets.upgrades.basic.tent:extend({ + name='mine-tent-red', + products = { + presets.special.red.infantry:extend({ name='mine-defense-red'}), + presets.defenses.red.infantry:extend({ name='mine-garrison-red'}) + } + }), + presets.upgrades.supply.excavator:extend({ + name='mine-excavator-red-1', + products = { + presets.missions.supply.convoy:extend({ name='mine-supply-red'}), + presets.missions.supply.transfer:extend({name='mine-transfer-red'}) + } + }), + presets.upgrades.supply.excavator:extend({ + name='mine-excavator-red-2', + products = { + presets.missions.supply.convoy:extend({ name='mine-supply-red'}), + presets.missions.supply.transfer:extend({name='mine-transfer-red'}) + } + }), + presets.upgrades.supply.excavator:extend({ + name='mine-excavator-red-3', + products = { + presets.missions.supply.convoy:extend({ name='mine-supply-red'}), + presets.missions.supply.transfer:extend({name='mine-transfer-red'}) + } + }) + }, + [2] = + { + presets.upgrades.basic.tent:extend({ + name='mine-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='mine-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='mine-garrison-blue'}) + } + }), + presets.upgrades.supply.excavator:extend({ + name='mine-excavator-blue-1', + products = { + presets.missions.supply.convoy:extend({ name='mine-supply-blue'}), + presets.missions.supply.transfer:extend({name='mine-transfer-blue'}) + } + }), + presets.upgrades.supply.excavator:extend({ + name='mine-excavator-blue-2', + products = { + presets.missions.supply.convoy:extend({ name='mine-supply-blue'}), + presets.missions.supply.transfer:extend({name='mine-transfer-blue'}) + } + }), + presets.upgrades.supply.excavator:extend({ + name='mine-excavator-blue-3', + products = { + presets.missions.supply.convoy:extend({ name='mine-supply-blue'}), + presets.missions.supply.transfer:extend({name='mine-transfer-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Mine.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Papa.lua ]]----------------- + +zones.papa = ZoneCommand:new("Papa") +zones.papa.initialState = { side=1 } +zones.papa.keepActive = true +zones.papa:defineUpgrades({ + [1] = + { + presets.upgrades.basic.tent:extend({ + name='papa-tent-red', + products = { + presets.special.red.infantry:extend({ name='papa-defense-red'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='papa-comcenter-red', + products = { + presets.defenses.red.sam:extend({ name='papa-sam-red'}) + } + }) + }, + [2] = + { + presets.upgrades.basic.tent:extend({ + name='papa-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='papa-defense-blue'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='papa-comcenter-blue', + products = { + presets.defenses.blue.sam:extend({ name='papa-sam-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Papa.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Sukhumi.lua ]]----------------- + +zones.sukhumi = ZoneCommand:new("Sukhumi") +zones.sukhumi.initialState = { side=1 } +zones.sukhumi.keepActive = true +zones.sukhumi.maxResource = 50000 +zones.sukhumi:defineUpgrades({ + [1] = + { + presets.upgrades.basic.comPost:extend({ + name='sukhumi-compost-red', + products = { + presets.special.red.infantry:extend({ name='sukhumi-defense-red'}), + presets.defenses.red.infantry:extend({ name='sukhumi-garrison-red'}) + } + }), + presets.upgrades.supply.fuelTank:extend({ + name='sukhumi-fuel-red', + products = { + presets.missions.supply.helo:extend({name='sukhumi-supply-red-1'}), + presets.missions.supply.helo:extend({name='sukhumi-supply-red-2'}), + presets.missions.supply.transfer:extend({name='sukhumi-transfer-red'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='sukhumi-comcenter-red', + products = { + presets.defenses.red.sa11:extend({ name='sukhumi-airdef-red'}), + presets.missions.attack.sead:extend({name='sukhumi-sead-red', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.attack.cas:extend({name='sukhumi-cas-red', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.bai:extend({name='sukhumi-cas-red', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.strike:extend({name='sukhumi-strike-red', altitude=20000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.patrol.aircraft:extend({name='sukhumi-patrol-red', altitude=25000, range=25}), + presets.missions.patrol.aircraft:extend({name='sukhumi-patrol-red-2', altitude=25000, range=25}) + } + }) + }, + [2] = + { + presets.upgrades.basic.comPost:extend({ + name='sukhumi-compost-blue', + products = { + presets.special.blue.infantry:extend({ name='sukhumi-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='sukhumi-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelTank:extend({ + name='sukhumi-fuel-blue', + products = { + presets.missions.supply.helo:extend({name='sukhumi-supply-blue-1'}), + presets.missions.supply.helo:extend({name='sukhumi-supply-blue-2'}), + presets.missions.supply.transfer:extend({name='sukhumi-transfer-blue'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='sukhumi-comcenter-blue', + products = { + presets.defenses.blue.nasams:extend({ name='sukhumi-airdef-blue'}), + presets.missions.attack.sead:extend({name='sukhumi-sead-blue', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.attack.cas:extend({name='sukhumi-cas-blue', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.bai:extend({name='sukhumi-cas-blue', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.strike:extend({name='sukhumi-strike-blue', altitude=20000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.patrol.aircraft:extend({name='sukhumi-patrol-blue', altitude=25000, range=25}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Sukhumi.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Farm.lua ]]----------------- + +zones.farm = ZoneCommand:new("Farm") +zones.farm.initialState = { side=1 } +zones.farm:defineUpgrades({ + [1] = + { + presets.upgrades.basic.tent:extend({ + name='farm-tent-red', + products = { + presets.special.red.infantry:extend({ name='farm-defense-red'}), + presets.defenses.red.infantry:extend({ name='farm-garrison-red'}) + } + }), + presets.upgrades.supply.farm1:extend({ + name='farm-prod-red-1', + products = { + presets.missions.supply.convoy:extend({ name='farm-supply-red'}), + presets.missions.supply.transfer:extend({name='farm-transfer-red'}) + } + }), + presets.upgrades.supply.farm2:extend({ + name='farm-prod-red-2', + products = { + presets.missions.supply.convoy:extend({ name='farm-supply-red'}), + presets.missions.supply.transfer:extend({name='farm-transfer-red'}) + } + }) + }, + [2] = + { + presets.upgrades.basic.tent:extend({ + name='farm-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='farm-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='farm-garrison-blue'}) + } + }), + presets.upgrades.supply.farm1:extend({ + name='farm-prod-blue-1', + products = { + presets.missions.supply.convoy:extend({ name='farm-supply-blue'}), + presets.missions.supply.transfer:extend({name='farm-transfer-blue'}) + } + }), + presets.upgrades.supply.farm2:extend({ + name='farm-prod-blue-2', + products = { + presets.missions.supply.convoy:extend({ name='farm-supply-blue'}), + presets.missions.supply.transfer:extend({name='farm-transfer-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Farm.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Romeo.lua ]]----------------- + +zones.romeo = ZoneCommand:new("Romeo") +zones.romeo.initialState = { side=1 } +zones.romeo:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='romeo-tent-red', + products = { + presets.special.red.infantry:extend({ name='romeo-defense-red'}), + presets.defenses.red.infantry:extend({ name='romeo-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='romeo-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='romeo-supply-red'}), + presets.missions.supply.transfer:extend({name='romeo-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='romeo-ammo-red', + products = { + presets.missions.attack.surface:extend({name='romeo-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='romeo-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='romeo-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='romeo-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='romeo-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='romeo-supply-blue'}), + presets.missions.supply.transfer:extend({name='romeo-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='romeo-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='romeo-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Romeo.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Zulu.lua ]]----------------- + +zones.zulu = ZoneCommand:new("Zulu") +zones.zulu.initialState = { side=1 } +zones.zulu:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='zulu-tent-red', + products = { + presets.special.red.infantry:extend({ name='zulu-defense-red'}), + presets.defenses.red.infantry:extend({ name='zulu-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='zulu-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='zulu-supply-red'}), + presets.missions.supply.transfer:extend({name='zulu-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='zulu-ammo-red', + products = { + presets.missions.attack.surface:extend({name='zulu-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='zulu-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='zulu-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='zulu-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='zulu-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='zulu-supply-blue'}), + presets.missions.supply.transfer:extend({name='zulu-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='zulu-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='zulu-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Zulu.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Yankee.lua ]]----------------- + +zones.yankee = ZoneCommand:new("Yankee") +zones.yankee.initialState = { side=1 } +zones.yankee:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='yankee-tent-red', + products = { + presets.special.red.infantry:extend({ name='yankee-defense-red'}), + presets.defenses.red.infantry:extend({ name='yankee-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='yankee-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='yankee-supply-red'}), + presets.missions.supply.transfer:extend({name='yankee-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='yankee-ammo-red', + products = { + presets.missions.attack.surface:extend({name='yankee-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='yankee-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='yankee-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='yankee-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='yankee-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='yankee-supply-blue'}), + presets.missions.supply.transfer:extend({name='yankee-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='yankee-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='yankee-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Yankee.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Malgobek.lua ]]----------------- + +zones.malgobek = ZoneCommand:new("Malgobek") +zones.malgobek.initialState = { side=1 } +zones.malgobek:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='malgobek-tent-red', + products = { + presets.special.red.infantry:extend({ name='malgobek-defense-red'}), + presets.defenses.red.infantry:extend({ name='malgobek-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='malgobek-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='malgobek-supply-red'}), + presets.missions.supply.transfer:extend({name='malgobek-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='malgobek-ammo-red', + products = { + presets.missions.attack.surface:extend({name='malgobek-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='malgobek-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='malgobek-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='malgobek-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='malgobek-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='malgobek-supply-blue'}), + presets.missions.supply.transfer:extend({name='malgobek-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='malgobek-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='malgobek-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Malgobek.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Kilo.lua ]]----------------- + +zones.kilo = ZoneCommand:new("Kilo") +zones.kilo.initialState = { side=1 } +zones.kilo:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='kilo-tent-red', + products = { + presets.special.red.infantry:extend({ name='kilo-defense-red'}), + presets.defenses.red.infantry:extend({ name='kilo-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='kilo-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='kilo-supply-red'}), + presets.missions.supply.transfer:extend({name='kilo-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='kilo-ammo-red', + products = { + presets.missions.attack.surface:extend({name='kilo-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='kilo-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='kilo-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='kilo-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='kilo-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='kilo-supply-blue'}), + presets.missions.supply.transfer:extend({name='kilo-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='kilo-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='kilo-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Kilo.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Quebec.lua ]]----------------- + +zones.quebec = ZoneCommand:new("Quebec") +zones.quebec.initialState = { side=1 } +zones.quebec.keepActive = true +zones.quebec:defineUpgrades({ + [1] = + { + presets.upgrades.basic.tent:extend({ + name='quebec-tent-red', + products = { + presets.special.red.infantry:extend({ name='quebec-defense-red'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='quebec-comcenter-red', + products = { + presets.defenses.red.sam:extend({ name='quebec-sam-red'}) + } + }) + }, + [2] = + { + presets.upgrades.basic.tent:extend({ + name='quebec-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='quebec-defense-blue'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='quebec-comcenter-blue', + products = { + presets.defenses.blue.sam:extend({ name='quebec-sam-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Quebec.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/OilFields.lua ]]----------------- + +zones.oilfields = ZoneCommand:new("Oil Fields") +zones.oilfields.initialState = { side=1 } +zones.oilfields:defineUpgrades({ + [1] = { + presets.upgrades.basic.outpost:extend({ + name='oilfields-outpost-red', + products = { + presets.special.red.infantry:extend({ name='oilfields-defense-red'}), + presets.defenses.red.infantry:extend({ name='oilfields-garrison-red'}) + } + }), + presets.upgrades.supply.oilPump:extend({ + name='oilfields-pump-red-1', + products = { + presets.missions.supply.transfer:extend({name='oilfields-transfer-red1'}) + } + }), + presets.upgrades.supply.oilPump:extend({ + name='oilfields-pump-red-2', + products = { + presets.missions.supply.convoy:extend({name='oilfields-supply-red-1'}) + } + }), + presets.upgrades.supply.oilPump:extend({ + name='oilfields-pump-red-3', + products = { + presets.missions.supply.transfer:extend({name='oilfields-transfer-red2'}) + } + }), + presets.upgrades.supply.oilPump:extend({ + name='oilfields-pump-red-4', + products = { + presets.missions.supply.convoy:extend({name='oilfields-supply-red-2'}) + } + }) + }, + [2] = { + presets.upgrades.basic.outpost:extend({ + name='oilfields-outpost-blue', + products = { + presets.special.blue.infantry:extend({ name='oilfields-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='oilfields-garrison-blue'}) + } + }), + presets.upgrades.supply.oilPump:extend({ + name='oilfields-pump-blue-1', + products = { + presets.missions.supply.transfer:extend({name='oilfields-transfer-blue1'}) + } + }), + presets.upgrades.supply.oilPump:extend({ + name='oilfields-pump-blue-2', + products = { + presets.missions.supply.convoy:extend({name='oilfields-supply-blue-1'}) + } + }), + presets.upgrades.supply.oilPump:extend({ + name='oilfields-pump-blue-3', + products = { + presets.missions.supply.transfer:extend({name='oilfields-transfer-blue2'}) + } + }), + presets.upgrades.supply.oilPump:extend({ + name='oilfields-pump-blue-4', + products = { + presets.missions.supply.convoy:extend({name='oilfields-supply-blue-2'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/OilFields.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Echo.lua ]]----------------- + +zones.echo = ZoneCommand:new("Echo") +zones.echo.initialState = { side=2 } +zones.echo:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='echo-tent-red', + products = { + presets.special.red.infantry:extend({ name='echo-defense-red'}), + presets.defenses.red.infantry:extend({ name='echo-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='echo-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='echo-supply-red'}), + presets.missions.supply.transfer:extend({name='echo-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='echo-ammo-red', + products = { + presets.missions.attack.surface:extend({name='echo-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='echo-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='echo-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='echo-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='echo-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='echo-supply-blue'}), + presets.missions.supply.transfer:extend({name='echo-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='echo-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='echo-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Echo.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Kobuleti.lua ]]----------------- + +zones.kobuleti = ZoneCommand:new("Kobuleti") +zones.kobuleti.initialState = { side=2 } +zones.kobuleti.keepActive = true +zones.kobuleti.isHeloSpawn = true +zones.kobuleti.isPlaneSpawn = true +zones.kobuleti.maxResource = 50000 +zones.kobuleti:defineUpgrades({ + [1] = + { + presets.upgrades.basic.comPost:extend({ + name='kobuleti-compost-red', + products = { + presets.special.red.infantry:extend({ name='kobuleti-defense-red'}), + presets.defenses.red.infantry:extend({ name='kobuleti-garrison-red'}), + presets.missions.attack.surface:extend({ name='kobuleti-assault-red'}) + } + }), + presets.upgrades.supply.fuelTank:extend({ + name='kobuleti-fuel-red', + products = { + presets.missions.supply.helo:extend({name='kobuleti-supply-red-1'}), + presets.missions.supply.helo:extend({name='kobuleti-supply-red-2'}), + presets.missions.supply.transfer:extend({name='kobuleti-transfer-red'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='kobuleti-comcenter-red', + products = { + presets.defenses.red.shorad:extend({ name='kobuleti-airdef-red'}), + presets.missions.attack.sead:extend({name='kobuleti-sead-red', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.attack.cas:extend({name='kobuleti-cas-red', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.bai:extend({name='kobuleti-cas-red', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.strike:extend({name='kobuleti-strike-red', altitude=20000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.patrol.aircraft:extend({name='kobuleti-patrol-red', altitude=25000, range=25}) + } + }) + }, + [2] = + { + presets.upgrades.basic.comPost:extend({ + name='kobuleti-compost-blue', + products = { + presets.special.blue.infantry:extend({ name='kobuleti-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='kobuleti-garrison-blue'}), + presets.missions.attack.surface:extend({ name='kobuleti-assault-blue'}) + } + }), + presets.upgrades.supply.fuelTank:extend({ + name='kobuleti-fuel-blue', + products = { + presets.missions.supply.helo:extend({name='kobuleti-supply-blue-1'}), + presets.missions.supply.helo:extend({name='kobuleti-supply-blue-2'}), + presets.missions.supply.transfer:extend({name='kobuleti-transfer-blue'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='kobuleti-comcenter-blue', + products = { + presets.defenses.blue.shorad:extend({ name='kobuleti-airdef-blue'}), + presets.missions.attack.sead:extend({name='kobuleti-sead-blue', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.attack.cas:extend({name='kobuleti-cas-blue', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.bai:extend({name='kobuleti-cas-blue', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.strike:extend({name='kobuleti-strike-blue', altitude=20000, expend=AI.Task.WeaponExpend.TWO}), + presets.missions.patrol.aircraft:extend({name='kobuleti-patrol-blue', altitude=25000, range=25}), + presets.missions.support.awacs:extend({name='kobuleti-awacs-blue', altitude=30000, freq=258.5}), + presets.missions.support.tanker:extend({name='kobuleti-tanker-blue', altitude=23000, freq=258, tacan='38', variant='Boom'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Kobuleti.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Alpha.lua ]]----------------- + +zones.alpha = ZoneCommand:new('Alpha') +zones.alpha.initialState = { side=2 } +zones.alpha:defineUpgrades({ + [1] = --red side + { + presets.upgrades.basic.tent:extend({ + name = 'alpha-tent-red', + products = { + presets.special.red.infantry:extend({ name='alpha-defense-red'}), + presets.defenses.red.infantry:extend({ name='alpha-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name = 'alpha-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({ name='alpha-supply-red'}), + presets.missions.supply.transfer:extend({name='alpha-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name = 'alpha-ammo-red', + products = { + presets.missions.attack.surface:extend({ name='alpha-assault-red'}) + } + }) + }, + [2] = --blue side + { + presets.upgrades.basic.tent:extend({ + name = 'alpha-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='alpha-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='alpha-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name = 'alpha-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({ name='alpha-supply-blue'}), + presets.missions.supply.transfer:extend({name='alpha-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name = 'alpha-ammo-blue', + products = { + presets.missions.attack.surface:extend({ name='alpha-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Alpha.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Foxtrot.lua ]]----------------- + +zones.foxtrot = ZoneCommand:new("Foxtrot") +zones.foxtrot.initialState = { side=2 } +zones.foxtrot:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='foxtrot-tent-red', + products = { + presets.special.red.infantry:extend({ name='foxtrot-defense-red'}), + presets.defenses.red.infantry:extend({ name='foxtrot-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='foxtrot-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='foxtrot-supply-red'}), + presets.missions.supply.transfer:extend({name='foxtrot-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='foxtrot-ammo-red', + products = { + presets.missions.attack.surface:extend({name='foxtrot-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='foxtrot-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='foxtrot-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='foxtrot-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='foxtrot-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='foxtrot-supply-blue'}), + presets.missions.supply.transfer:extend({name='foxtrot-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='foxtrot-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='foxtrot-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Foxtrot.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Sierra.lua ]]----------------- + +zones.sierra = ZoneCommand:new("Sierra") +zones.sierra.initialState = { side=1 } +zones.sierra.isHeloSpawn = true +zones.sierra:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='sierra-tent-red', + products = { + presets.special.red.infantry:extend({ name='sierra-defense-red'}), + presets.defenses.red.infantry:extend({ name='sierra-garrison-red'}) + } + }), + presets.upgrades.supply.fuelTankFarp:extend({ + name='sierra-fuel-red', + products = { + presets.missions.supply.helo:extend({name='sierra-supply-red'}), + presets.missions.supply.helo:extend({name='sierra-supply-red-1'}), + presets.missions.supply.transfer:extend({name='sierra-transfer-red'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='sierra-comcenter-red', + products = { + presets.defenses.red.shorad:extend({name='sierra-sam-red'}), + presets.missions.attack.helo:extend({name='sierra-cas-red', altitude=200, expend=AI.Task.WeaponExpend.HALF }) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='sierra-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='sierra-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='sierra-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelTankFarp:extend({ + name='sierra-fuel-blue', + products = { + presets.missions.supply.helo:extend({name='sierra-supply-blue'}), + presets.missions.supply.helo:extend({name='sierra-supply-blue-1'}), + presets.missions.supply.transfer:extend({name='sierra-transfer-blue'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='sierra-comcenter-blue', + products = { + presets.defenses.blue.shorad:extend({name='sierra-sam-blue'}), + presets.missions.attack.helo:extend({name='sierra-cas-blue', altitude=200, expend=AI.Task.WeaponExpend.HALF }) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Sierra.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Oni.lua ]]----------------- + +zones.oni = ZoneCommand:new("Oni") +zones.oni.initialState = { side=1 } +zones.oni:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='oni-tent-red', + products = { + presets.special.red.infantry:extend({ name='oni-defense-red'}), + presets.defenses.red.infantry:extend({ name='oni-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='oni-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='oni-supply-red'}), + presets.missions.supply.helo:extend({name='oni-supply-red-2'}), + presets.missions.supply.transfer:extend({name='oni-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='oni-ammo-red', + products = { + presets.missions.attack.surface:extend({name='oni-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='oni-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='oni-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='oni-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='oni-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='oni-supply-blue'}), + presets.missions.supply.helo:extend({name='oni-supply-blue-2'}), + presets.missions.supply.transfer:extend({name='oni-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='oni-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='oni-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Oni.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Hotel.lua ]]----------------- + +zones.hotel = ZoneCommand:new("Hotel") +zones.hotel.initialState = nil +zones.hotel:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='hotel-tent-red', + products = { + presets.special.red.infantry:extend({ name='hotel-defense-red'}), + presets.defenses.red.infantry:extend({ name='hotel-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='hotel-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='hotel-supply-red'}), + presets.missions.supply.transfer:extend({name='hotel-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='hotel-ammo-red', + products = { + presets.missions.attack.surface:extend({name='hotel-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='hotel-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='hotel-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='hotel-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='hotel-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='hotel-supply-blue'}), + presets.missions.supply.transfer:extend({name='hotel-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='hotel-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='hotel-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Hotel.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Victor.lua ]]----------------- + +zones.victor = ZoneCommand:new("Victor") +zones.victor.initialState = { side=1 } +zones.victor:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='victor-tent-red', + products = { + presets.special.red.infantry:extend({ name='victor-defense-red'}), + presets.defenses.red.infantry:extend({ name='victor-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='victor-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='victor-supply-red'}), + presets.missions.supply.helo:extend({name='victor-supply-red-2'}), + presets.missions.supply.transfer:extend({name='victor-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='victor-ammo-red', + products = { + presets.missions.attack.surface:extend({name='victor-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='victor-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='victor-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='victor-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='victor-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='victor-supply-blue'}), + presets.missions.supply.helo:extend({name='victor-supply-blue-2'}), + presets.missions.supply.transfer:extend({name='victor-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='victor-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='victor-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Victor.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Tango.lua ]]----------------- + +zones.tango = ZoneCommand:new("Tango") +zones.tango.initialState = { side=1 } +zones.tango.isHeloSpawn = true +zones.tango:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='tango-tent-red', + products = { + presets.special.red.infantry:extend({ name='tango-defense-red'}), + presets.defenses.red.infantry:extend({ name='tango-garrison-red'}) + } + }), + presets.upgrades.supply.fuelTankFarp:extend({ + name='tango-fuel-red', + products = { + presets.missions.supply.helo:extend({name='tango-supply-red'}), + presets.missions.supply.helo:extend({name='tango-supply-red-1'}), + presets.missions.supply.transfer:extend({name='tango-transfer-red'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='tango-comcenter-red', + products = { + presets.defenses.red.shorad:extend({name='tango-sam-red'}), + presets.missions.attack.helo:extend({name='tango-cas-red', altitude=200, expend=AI.Task.WeaponExpend.HALF }) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='tango-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='tango-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='tango-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelTankFarp:extend({ + name='tango-fuel-blue', + products = { + presets.missions.supply.helo:extend({name='tango-supply-blue'}), + presets.missions.supply.helo:extend({name='tango-supply-blue-1'}), + presets.missions.supply.transfer:extend({name='tango-transfer-blue'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='tango-comcenter-blue', + products = { + presets.defenses.blue.shorad:extend({name='tango-sam-blue'}), + presets.missions.attack.helo:extend({name='tango-cas-blue', altitude=200, expend=AI.Task.WeaponExpend.HALF }) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Tango.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Unal.lua ]]----------------- + +zones.unal = ZoneCommand:new("Unal") +zones.unal.initialState = { side=1 } +zones.unal.isHeloSpawn = true +zones.unal:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='unal-tent-red', + products = { + presets.special.red.infantry:extend({ name='unal-defense-red'}), + presets.defenses.red.infantry:extend({ name='unal-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='unal-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='unal-supply-red'}), + presets.missions.supply.helo:extend({name='unal-supply-red-2'}), + presets.missions.supply.transfer:extend({name='unal-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='unal-ammo-red', + products = { + presets.missions.attack.surface:extend({name='unal-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='unal-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='unal-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='unal-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='unal-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='unal-supply-blue'}), + presets.missions.supply.helo:extend({name='unal-supply-blue-2'}), + presets.missions.supply.transfer:extend({name='unal-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='unal-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='unal-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Unal.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Beslan.lua ]]----------------- + +zones.beslan = ZoneCommand:new("Beslan") +zones.beslan.initialState = { side=1 } +zones.beslan.keepActive = true +zones.beslan.maxResource = 50000 +zones.beslan:defineUpgrades({ + [1] = + { + presets.upgrades.basic.comPost:extend({ + name='beslan-compost-red', + products = { + presets.special.red.infantry:extend({ name='beslan-defense-red'}), + presets.defenses.red.infantry:extend({ name='beslan-garrison-red'}) + } + }), + presets.upgrades.supply.fuelTank:extend({ + name='beslan-fuel-red', + products = { + presets.missions.supply.helo:extend({name='beslan-supply-red-1'}), + presets.missions.supply.helo:extend({name='beslan-supply-red-2'}), + presets.missions.supply.convoy_escorted:extend({name='beslan-supply-red-3'}), + presets.missions.supply.transfer:extend({name='beslan-transfer-red'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='beslan-comcenter-red', + products = { + presets.defenses.red.sa5:extend({ name='beslan-airdef-red'}), + presets.missions.attack.sead:extend({name='beslan-sead-red', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.attack.strike:extend({name='beslan-strike-red', altitude=30000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.attack.strike:extend({name='beslan-strike-red-1', altitude=30000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.patrol.aircraft:extend({name='beslan-patrol-red', altitude=25000, range=25}) + } + }) + }, + [2] = + { + presets.upgrades.basic.comPost:extend({ + name='beslan-compost-blue', + products = { + presets.special.blue.infantry:extend({ name='beslan-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='beslan-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelTank:extend({ + name='beslan-fuel-blue', + products = { + presets.missions.supply.helo:extend({name='beslan-supply-blue-1'}), + presets.missions.supply.helo:extend({name='beslan-supply-blue-2'}), + presets.missions.supply.convoy_escorted:extend({name='beslan-supply-blue-3'}), + presets.missions.supply.transfer:extend({name='beslan-transfer-blue'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='beslan-comcenter-blue', + products = { + presets.defenses.blue.patriot:extend({ name='beslan-airdef-blue'}), + presets.missions.attack.sead:extend({name='beslan-sead-blue', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.attack.strike:extend({name='beslan-strike-blue', altitude=30000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.attack.strike:extend({name='beslan-strike-blue-1', altitude=30000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.patrol.aircraft:extend({name='beslan-patrol-blue', altitude=25000, range=25}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Beslan.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Bravo.lua ]]----------------- + +zones.bravo = ZoneCommand:new("Bravo") +zones.bravo.initialState = { side=2 } +zones.bravo:defineUpgrades({ + [1] = + { + presets.upgrades.basic.comPost:extend({ + name='bravo-compost-red', + products = { + presets.special.red.infantry:extend({ name='bravo-defense-red'}), + presets.defenses.red.infantry:extend({ name='bravo-garrison-red'}) + } + }), + presets.upgrades.supply.fuelTankFarp:extend({ + name='bravo-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({ name='bravo-supply-red'}), + presets.missions.supply.transfer:extend({name='bravo-transfer-red'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='bravo-comcenter-red', + products = { + presets.defenses.red.shorad:extend({ name='bravo-airdef-red'}), + presets.missions.attack.helo:extend({name='bravo-attack-red', altitude=200, expend=AI.Task.WeaponExpend.HALF}) + } + }) + }, + [2] = + { + presets.upgrades.basic.comPost:extend({ + name='bravo-compost-blue', + products = { + presets.special.blue.infantry:extend({ name='bravo-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='bravo-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelTankFarp:extend({ + name='bravo-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({ name='bravo-supply-blue'}), + presets.missions.supply.transfer:extend({name='bravo-transfer-blue'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='bravo-comcenter-blue', + products = { + presets.defenses.blue.shorad:extend({ name='bravo-airdef-blue'}), + presets.missions.attack.helo:extend({name='bravo-attack-blue', altitude=200, expend=AI.Task.WeaponExpend.HALF }) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Bravo.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/WeaponDepot.lua ]]----------------- + +zones.weapondepot = ZoneCommand:new("Weapon Depot") +zones.weapondepot.initialState = { side=1 } +zones.weapondepot:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='weapons-tent-red', + products = { + presets.special.red.infantry:extend({ name='weapons-defense-red'}), + presets.defenses.red.infantry:extend({ name='weapons-garrison-red'}) + } + }), + presets.upgrades.supply.ammoDepot:extend({ + name='weapons-ammo-red-1', + products = { + presets.missions.supply.convoy:extend({name='weapons-supply-red-1'}), + presets.missions.supply.transfer:extend({name='weapons-transfer-red-1'}) + } + }), + presets.upgrades.supply.ammoDepot:extend({ + name='weapons-ammo-red-2', + products = { + presets.missions.supply.convoy:extend({name='weapons-supply-red-2'}), + presets.missions.supply.transfer:extend({name='weapons-transfer-red-2'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='weapons-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='weapons-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='weapons-garrison-blue'}) + } + }), + presets.upgrades.supply.ammoDepot:extend({ + name='weapons-ammo-blue-1', + products = { + presets.missions.supply.convoy:extend({name='weapons-supply-blue-1'}), + presets.missions.supply.transfer:extend({name='weapons-transfer-blue-1'}) + } + }), + presets.upgrades.supply.ammoDepot:extend({ + name='weapons-ammo-blue-2', + products = { + presets.missions.supply.convoy:extend({name='weapons-supply-blue-2'}), + presets.missions.supply.transfer:extend({name='weapons-transfer-blue-2'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/WeaponDepot.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Delta.lua ]]----------------- + +zones.delta = ZoneCommand:new("Delta") +zones.delta.initialState = { side=2 } +zones.delta:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='delta-tent-red', + products = { + presets.special.red.infantry:extend({ name='delta-defense-red'}), + presets.defenses.red.infantry:extend({ name='delta-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='delta-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='delta-supply-red'}), + presets.missions.supply.transfer:extend({name='delta-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='delta-ammo-red', + products = { + presets.missions.attack.surface:extend({name='delta-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='delta-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='delta-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='delta-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='delta-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='delta-supply-blue'}), + presets.missions.supply.transfer:extend({name='delta-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='delta-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='delta-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Delta.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Cherkessk.lua ]]----------------- + +zones.cherkessk = ZoneCommand:new("Cherkessk") +zones.cherkessk.initialState = { side=1 } +zones.cherkessk.isHeloSpawn = true +zones.cherkessk:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='cherkessk-tent-red', + products = { + presets.special.red.infantry:extend({ name='cherkessk-defense-red'}), + presets.defenses.red.infantry:extend({ name='cherkessk-garrison-red'}) + } + }), + presets.upgrades.supply.fuelTankFarp:extend({ + name='cherkessk-fuel-red', + products = { + presets.missions.supply.helo:extend({name='cherkessk-supply-red'}), + presets.missions.supply.helo:extend({name='cherkessk-supply-red-1'}), + presets.missions.supply.transfer:extend({name='cherkessk-transfer-red'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='cherkessk-comcenter-red', + products = { + presets.defenses.red.shorad:extend({name='cherkessk-sam-red'}), + presets.missions.attack.helo:extend({name='cherkessk-cas-red', altitude=200, expend=AI.Task.WeaponExpend.HALF }), + presets.missions.attack.helo:extend({name='cherkessk-cas-red-1', altitude=200, expend=AI.Task.WeaponExpend.HALF }), + presets.missions.attack.surface:extend({name='cherkessk-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='cherkessk-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='cherkessk-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='cherkessk-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelTankFarp:extend({ + name='cherkessk-fuel-blue', + products = { + presets.missions.supply.helo:extend({name='cherkessk-supply-blue'}), + presets.missions.supply.helo:extend({name='cherkessk-supply-blue-1'}), + presets.missions.supply.transfer:extend({name='cherkessk-transfer-blue'}), + presets.missions.attack.surface:extend({name='cherkessk-assault-blue'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='cherkessk-comcenter-blue', + products = { + presets.defenses.blue.shorad:extend({name='cherkessk-sam-blue'}), + presets.missions.attack.helo:extend({name='cherkessk-cas-blue', altitude=200, expend=AI.Task.WeaponExpend.HALF }), + presets.missions.attack.helo:extend({name='cherkessk-cas-blue-1', altitude=200, expend=AI.Task.WeaponExpend.HALF }) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Cherkessk.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Juliett.lua ]]----------------- + +zones.juliett = ZoneCommand:new("Juliett") +zones.initialState = nil +zones.juliett:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='juliett-tent-red', + products = { + presets.special.red.infantry:extend({ name='juliett-defense-red'}), + presets.defenses.red.infantry:extend({ name='juliett-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='juliett-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='juliett-supply-red'}), + presets.missions.supply.transfer:extend({name='juliett-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='juliett-ammo-red', + products = { + presets.missions.attack.surface:extend({name='juliett-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='juliett-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='juliett-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='juliett-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='juliett-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='juliett-supply-blue'}), + presets.missions.supply.transfer:extend({name='juliett-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='juliett-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='juliett-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Juliett.lua ]]----------------- + + + + cm = ConnectionManager:new() + cm:addConnection('Batumi', 'Alpha') + cm:addConnection('Alpha', 'Bravo') + cm:addConnection('Bravo', 'Kobuleti') + cm:addConnection('Bravo', 'Factory') + cm:addConnection('Kobuleti', 'Factory') + cm:addConnection('Kobuleti', 'Charlie') + cm:addConnection('Foxtrot', 'Charlie') + cm:addConnection('Foxtrot', 'Kobuleti') + cm:addConnection('Delta','Foxtrot') + cm:addConnection('Delta','Kobuleti') + cm:addConnection('Delta','Factory') + cm:addConnection('Echo','Charlie') + cm:addConnection('Golf','Echo') + cm:addConnection('Golf','Foxtrot') + cm:addConnection('India','Delta') + cm:addConnection('Hotel','Golf') + cm:addConnection('Hotel','Foxtrot') + cm:addConnection('Hotel','Delta') + cm:addConnection('Hotel','India') + cm:addConnection('Juliett','Echo') + cm:addConnection('Juliett','Golf') + cm:addConnection('Senaki','Juliett') + cm:addConnection('Senaki','Golf') + cm:addConnection('Senaki','Hotel') + cm:addConnection('Kutaisi','Hotel') + cm:addConnection('Kutaisi','India') + cm:addConnection('Kilo','Juliett') + cm:addConnection('Mike','Kutaisi') + cm:addConnection('Mike','Senaki') + cm:addConnection('Romeo','Mike') + cm:addConnection('Romeo','Kutaisi') + cm:addConnection('Weapon Depot','Juliett') + cm:addConnection('Weapon Depot','Senaki') + cm:addConnection('Weapon Depot','Kilo') + cm:addConnection('November','Weapon Depot') + cm:addConnection('November','Senaki') + cm:addConnection('November','Mike') + cm:addConnection('Oil Fields','Romeo') + cm:addConnection('Quebec','Kilo') + cm:addConnection('Zugdidi','Weapon Depot') + cm:addConnection('Zugdidi','Quebec') + cm:addConnection('Zugdidi','November') + cm:addConnection('Zugdidi','Kilo') + cm:addConnection('Distillery','November') + cm:addConnection('Distillery','Mike') + cm:addConnection('Zugdidi','Papa') + cm:addConnection('November','Papa') + cm:addConnection('Sierra','Papa') + cm:addConnection('Sierra','Zugdidi') + cm:addConnection('Sierra','Uniform') + cm:addConnection('Mine','Uniform') + cm:addConnection('Tango','Quebec') + cm:addConnection('Tango','Zugdidi') + cm:addConnection('Sierra','Tango') + cm:addConnection('Whiskey','Tango') + cm:addConnection('Ochamchira','Tango') + cm:addConnection('Ochamchira','Whiskey') + cm:addConnection('Ochamchira','Farm') + cm:addConnection('Ochamchira','Zulu') + cm:addConnection('Farm','Zulu') + cm:addConnection('Sukhumi','Zulu') + cm:addConnection('Lentehi','Distillery', true, 3000) + cm:addConnection('Lentehi','Babugent', true, 5000) + cm:addConnection('Nalchik','Babugent') + cm:addConnection('Victor','Distillery', true, 2000) + cm:addConnection('Victor','Romeo') + cm:addConnection('Victor','Lentehi') + cm:addConnection('Victor','Oil Fields', true, 2000) + cm:addConnection('Victor','Oni') + cm:addConnection('Unal','Oni', true, 4500) + cm:addConnection('Beslan','Unal') + cm:addConnection('Digora','Beslan') + cm:addConnection('Digora','Unal') + cm:addConnection('Digora','Babugent') + cm:addConnection('Terek','Digora') + cm:addConnection('Terek','Nalchik') + cm:addConnection('Terek','Beslan') + cm:addConnection('Prohladniy','Terek') + cm:addConnection('Prohladniy','Nalchik') + cm:addConnection('Malgobek','Terek') + cm:addConnection('Malgobek','Beslan') + cm:addConnection('Lima','Mine') + cm:addConnection('Lima','Lentehi', true, 4000) + cm:addConnection('Tyrnyauz','Lima', true, 4000) + cm:addConnection('Tyrnyauz','Nalchik') + cm:addConnection('XRay','Sukhumi') + cm:addConnection('Oscar','Sukhumi') + cm:addConnection('Oscar','XRay') + cm:addConnection('Mozdok','Malgobek') + cm:addConnection('Mozdok','Prohladniy') + cm:addConnection('Gudauta','Oscar') + cm:addConnection('Yankee','Gudauta') + cm:addConnection('Sochi','Yankee') + cm:addConnection('Refinery','XRay', true, 4000) + cm:addConnection('Refinery','Humara') + cm:addConnection('Intel Center','Tyrnyauz') + cm:addConnection('Intel Center','Nalchik') + cm:addConnection('Intel Center','Prohladniy') + cm:addConnection('Intel Center','Kislovodsk') + cm:addConnection('Mineralnye','Intel Center') + cm:addConnection('Kislovodsk','Mineralnye') + cm:addConnection('Tallyk','Mineralnye') + cm:addConnection('Tallyk','Kislovodsk') + cm:addConnection('Power Plant','Mineralnye') + cm:addConnection('Power Plant','Tallyk') + cm:addConnection('Cherkessk','Tallyk') + cm:addConnection('Cherkessk','Power Plant') + cm:addConnection('Cherkessk','Humara') +end + +ZoneCommand.setNeighbours(cm) + +bm = BattlefieldManager:new() + +mc = MarkerCommands:new() + +pt = PlayerTracker:new(mc) + +mt = MissionTracker:new(pt, mc) + +st = SquadTracker:new() + +ct = CSARTracker:new() + +pl = PlayerLogistics:new(mt, pt, st, ct) + +gci = GCI:new(2) + +gm = GroupMonitor:new(cm) +ZoneCommand.groupMonitor = gm + +-- PlayerLogistics:registerSquadGroup(squadType, groupname, weight,cost,jobtime,extracttime, squadSize) +pl:registerSquadGroup(PlayerLogistics.infantryTypes.capture, 'capture-squad', 700, 200, 60, 60*30, 4) +pl:registerSquadGroup(PlayerLogistics.infantryTypes.sabotage, 'sabotage-squad', 800, 500, 60*5, 60*30, 4) +pl:registerSquadGroup(PlayerLogistics.infantryTypes.ambush, 'ambush-squad', 900, 300, 60*20, 60*30, 5) +pl:registerSquadGroup(PlayerLogistics.infantryTypes.engineer, 'engineer-squad', 200, 1000,60, 60*30, 2) +pl:registerSquadGroup(PlayerLogistics.infantryTypes.manpads, 'manpads-squad', 900, 500, 60*20, 60*30, 5) +pl:registerSquadGroup(PlayerLogistics.infantryTypes.spy, 'spy-squad', 100, 300, 60*10, 60*30, 1) +pl:registerSquadGroup(PlayerLogistics.infantryTypes.rapier, 'rapier-squad', 1200,2000,60*60, 60*30, 8) + +Group.getByName('jtacDrone'):destroy() +CommandFunctions.jtac = JTAC:new({name = 'jtacDrone'}) + +pm = PersistenceManager:new(savefile, gm, st, ct, pl) +pm:load() + +if pm:canRestore() then + pm:restoreZones() + pm:restoreAIMissions() + pm:restoreBattlefield() + pm:restoreCsar() + pm:restoreSquads() +else + --initial states + Starter.start(zones) +end + +timer.scheduleFunction(function(param, time) + pm:save() + env.info("Mission state saved") + return time+60 +end, zones, timer.getTime()+60) + + +--make sure support units are present where needed +ensureSpawn = { + ['golf-farp-suport'] = zones.golf, + ['november-farp-suport'] = zones.november, + ['tango-farp-suport'] = zones.tango, + ['sierra-farp-suport'] = zones.sierra, + ['cherkessk-farp-suport'] = zones.cherkessk, + ['unal-farp-suport'] = zones.unal, + ['tyrnyauz-farp-suport'] = zones.tyrnyauz +} + +for grname, zn in pairs(ensureSpawn) do + local g = Group.getByName(grname) + if g then g:destroy() end +end + +timer.scheduleFunction(function(param, time) + + for grname, zn in pairs(ensureSpawn) do + local g = Group.getByName(grname) + if zn.side == 2 then + if not g then + local err, msg = pcall(mist.respawnGroup,grname,true) + if not err then + env.info("ERROR spawning "..grname) + env.info(msg) + end + end + else + if g then g:destroy() end + end + end + + return time+30 +end, {}, timer.getTime()+30) + + +--supply injection +local blueSupply = {'offmap-supply-blue-1','offmap-supply-blue-2','offmap-supply-blue-3','offmap-supply-blue-4','offmap-supply-blue-5'} +local redSupply = {'offmap-supply-red-1','offmap-supply-red-2','offmap-supply-red-3','offmap-supply-red-4','offmap-supply-red-5'} +local offmapZones = { + zones.batumi, + zones.sochi, + zones.nalchik, + zones.beslan, + zones.mozdok, + zones.mineralnye, +-- zones.senaki, +-- zones.sukhumi, +-- zones.gudauta, +-- zones.kobuleti, +} + +supplyPointRegistry = { + blue = {}, + red = {} +} + +for i,v in ipairs(blueSupply) do + local g = Group.getByName(v) + if g then + supplyPointRegistry.blue[v] = g:getUnit(1):getPoint() + end +end + +for i,v in ipairs(redSupply) do + local g = Group.getByName(v) + if g then + supplyPointRegistry.red[v] = g:getUnit(1):getPoint() + end +end + +offmapSupplyRegistry = {} +timer.scheduleFunction(function(param, time) + local availableBlue = {} + for i,v in ipairs(param.blue) do + if offmapSupplyRegistry[v] == nil then + table.insert(availableBlue, v) + end + end + + local availableRed = {} + for i,v in ipairs(param.red) do + if offmapSupplyRegistry[v] == nil then + table.insert(availableRed, v) + end + end + + local redtargets = {} + local bluetargets = {} + for _, zn in ipairs(param.offmapZones) do + if zn:needsSupplies(3000) then + local isOnRoute = false + for _,data in pairs(offmapSupplyRegistry) do + if data.zone.name == zn.name then + isOnRoute = true + break + end + end + if not isOnRoute then + if zn.side == 1 then + table.insert(redtargets, zn) + elseif zn.side == 2 then + table.insert(bluetargets, zn) + end + end + end + end + + if #availableRed > 0 and #redtargets > 0 then + local zn = redtargets[math.random(1,#redtargets)] + + local red = nil + local minD = 999999999 + for i,v in ipairs(availableRed) do + local d = mist.utils.get2DDist(zn.zone.point, supplyPointRegistry.red[v]) + if d < minD then + red = v + minD = d + end + end + + if not red then red = availableRed[math.random(1,#availableRed)] end + + local gr = red + red = nil + mist.respawnGroup(gr, true) + offmapSupplyRegistry[gr] = {zone = zn, assigned = timer.getAbsTime()} + env.info(gr..' was deployed') + timer.scheduleFunction(function(param,time) + local g = Group.getByName(param.group) + TaskExtensions.landAtAirfield(g, param.target.zone.point) + env.info(param.group..' going to '..param.target.name) + end, {group=gr, target=zn}, timer.getTime()+2) + end + + if #availableBlue > 0 and #bluetargets>0 then + local zn = bluetargets[math.random(1,#bluetargets)] + + local blue = nil + local minD = 999999999 + for i,v in ipairs(availableBlue) do + local d = mist.utils.get2DDist(zn.zone.point, supplyPointRegistry.blue[v]) + if d < minD then + blue = v + minD = d + end + end + + if not blue then blue = availableBlue[math.random(1,#availableBlue)] end + + local gr = blue + blue = nil + mist.respawnGroup(gr, true) + offmapSupplyRegistry[gr] = {zone = zn, assigned = timer.getAbsTime()} + env.info(gr..' was deployed') + timer.scheduleFunction(function(param,time) + local g = Group.getByName(param.group) + TaskExtensions.landAtAirfield(g, param.target.zone.point) + env.info(param.group..' going to '..param.target.name) + end, {group=gr, target=zn}, timer.getTime()+2) + end + + return time+(60*5) +end, {blue = blueSupply, red = redSupply, offmapZones = offmapZones}, timer.getTime()+60) + + + +timer.scheduleFunction(function(param, time) + + for groupname,data in pairs(offmapSupplyRegistry) do + local gr = Group.getByName(groupname) + if not gr then + offmapSupplyRegistry[groupname] = nil + env.info(groupname..' was destroyed') + end + + if gr and ((timer.getAbsTime() - data.assigned) > (60*60)) then + gr:destroy() + offmapSupplyRegistry[groupname] = nil + env.info(groupname..' despawned due to being alive for too long') + end + + if gr and Utils.allGroupIsLanded(gr) and Utils.someOfGroupInZone(gr, data.zone.name) then + data.zone:addResource(15000) + gr:destroy() + offmapSupplyRegistry[groupname] = nil + env.info(groupname..' landed at '..data.zone.name..' and delivered 15000 resources') + end + end + + return time+180 +end, {}, timer.getTime()+180) \ No newline at end of file diff --git a/resources/plugins/pretense/init_header.lua b/resources/plugins/pretense/init_header.lua new file mode 100644 index 00000000..9df8452d --- /dev/null +++ b/resources/plugins/pretense/init_header.lua @@ -0,0 +1,4670 @@ + + +local savefile = 'pretense_1.1.json' +if lfs then + local dir = lfs.writedir()..'Missions/Saves/' + lfs.mkdir(dir) + savefile = dir..savefile + env.info('Pretense - Save file path: '..savefile) +end + + +do + TemplateDB.templates["infantry-red"] = { + units = { + "BTR_D", + "T-90", + "T-90", + "Infantry AK ver2", + "Infantry AK", + "Infantry AK", + "Paratrooper RPG-16", + "Infantry AK ver3", + "SA-18 Igla manpad" + }, + skill = "Excellent", + dataCategory= TemplateDB.type.group + } + + TemplateDB.templates["infantry-blue"] = { + units = { + "M1045 HMMWV TOW", + "Soldier stinger", + "Soldier M4 GRG", + "Soldier M4 GRG", + "Soldier M4 GRG", + "Soldier M4 GRG", + "Soldier M4 GRG", + "M1043 HMMWV Armament" + }, + skill = "Excellent", + dataCategory= TemplateDB.type.group + } + + TemplateDB.templates["defense-red"] = { + units = { + "Infantry AK ver2", + "Infantry AK", + "Infantry AK ver3", + "Paratrooper RPG-16", + "SA-18 Igla manpad" + }, + skill = "Excellent", + dataCategory= TemplateDB.type.group + } + + TemplateDB.templates["defense-blue"] = { + units = { + "Soldier M4 GRG", + "Soldier M4 GRG", + "Soldier M4 GRG", + "Soldier RPG", + "Soldier stinger", + }, + skill = "Excellent", + dataCategory= TemplateDB.type.group + } + + TemplateDB.templates["shorad-red"] = { + units = { + "Strela-10M3", + "Strela-10M3", + "Ural-4320T", + "2S6 Tunguska" + }, + maxDist = 300, + skill = "Excellent", + dataCategory= TemplateDB.type.group + } + + TemplateDB.templates["shorad-blue"] = { + units = { + "Roland ADS", + "M48 Chaparral", + "M 818", + "Gepard", + "Gepard" + }, + maxDist = 300, + skill = "Excellent", + dataCategory= TemplateDB.type.group + } + + TemplateDB.templates["sam-red"] = { + units = { + "p-19 s-125 sr", + "Ural-4320T", + "Ural-4320T", + "S_75M_Volhov", + "S_75M_Volhov", + "S_75M_Volhov", + "S_75M_Volhov", + "S_75M_Volhov", + "Tor 9A331", + "SNR_75V" + }, + maxDist = 300, + skill = "Excellent", + dataCategory= TemplateDB.type.group + } + + TemplateDB.templates["sam-blue"] = { + units = { + "Hawk pcp", + "Hawk cwar", + "Hawk ln", + "Hawk ln", + "Hawk ln", + "Hawk ln", + "Hawk ln", + "Hawk tr", + "M 818", + "Hawk sr" + }, + maxDist = 300, + skill = "Good", + dataCategory= TemplateDB.type.group + } + + TemplateDB.templates["patriot"] = { + units = { + "Patriot cp", + "Patriot str", + "M 818", + "M 818", + "Patriot ln", + "Patriot ln", + "Patriot ln", + "Patriot ln", + "Patriot str", + "Patriot str", + "Patriot str", + "Patriot EPP", + "Patriot ECS", + "Patriot AMG" + }, + maxDist = 300, + skill = "Good", + dataCategory= TemplateDB.type.group + } + + TemplateDB.templates["sa3"] = { + units = { + "p-19 s-125 sr", + "snr s-125 tr", + "5p73 s-125 ln", + "5p73 s-125 ln", + "Ural-4320T", + "5p73 s-125 ln", + "5p73 s-125 ln" + }, + maxDist = 300, + skill = "Excellent", + dataCategory= TemplateDB.type.group + } + + TemplateDB.templates["sa6"] = { + units = { + "Kub 1S91 str", + "Kub 2P25 ln", + "Kub 2P25 ln", + "Kub 2P25 ln", + "Kub 2P25 ln", + "2S6 Tunguska", + "Ural-4320T", + "2S6 Tunguska", + "Kub 2P25 ln" + }, + maxDist = 300, + skill = "Excellent", + dataCategory= TemplateDB.type.group + } + + TemplateDB.templates["sa10"] = { + units = { + "S-300PS 54K6 cp", + "S-300PS 5P85C ln", + "S-300PS 5P85C ln", + "S-300PS 5P85C ln", + "GAZ-66", + "GAZ-66", + "GAZ-66", + "S-300PS 5P85C ln", + "S-300PS 5P85C ln", + "S-300PS 5P85C ln", + "S-300PS 40B6MD sr", + "S-300PS 40B6M tr", + "S-300PS 64H6E sr" + }, + maxDist = 300, + skill = "Excellent", + dataCategory= TemplateDB.type.group + } + + TemplateDB.templates["sa5"] = { + units = { + "RLS_19J6", + "Ural-4320T", + "Ural-4320T", + "RPC_5N62V", + "S-200_Launcher", + "S-200_Launcher", + "S-200_Launcher", + "S-200_Launcher", + "S-200_Launcher", + "S-200_Launcher" + }, + maxDist = 300, + skill = "Excellent", + dataCategory= TemplateDB.type.group + } + + TemplateDB.templates["sa11"] = { + units = { + "SA-11 Buk SR 9S18M1", + "SA-11 Buk LN 9A310M1", + "SA-11 Buk LN 9A310M1", + "SA-11 Buk LN 9A310M1", + "SA-11 Buk LN 9A310M1", + "SA-11 Buk LN 9A310M1", + "2S6 Tunguska", + "SA-11 Buk SR 9S18M1", + "GAZ-66", + "GAZ-66", + "SA-11 Buk CC 9S470M1" + }, + maxDist = 300, + skill = "Excellent", + dataCategory= TemplateDB.type.group + } + + TemplateDB.templates["nasams"] = { + units = { + "NASAMS_Command_Post", + "NASAMS_Radar_MPQ64F1", + "Vulcan", + "M 818", + "M 818", + "Roland ADS", + "Roland ADS", + "NASAMS_LN_C", + "NASAMS_LN_C", + "NASAMS_LN_C", + "NASAMS_LN_C", + "NASAMS_Radar_MPQ64F1", + "NASAMS_Radar_MPQ64F1", + "NASAMS_Radar_MPQ64F1" + }, + maxDist = 300, + skill = "Excellent", + dataCategory= TemplateDB.type.group + } +end + +presets = { + upgrades = { + basic = { + tent = Preset:new({ + display = 'Tent', + cost = 1500, + type = 'upgrade', + template = "tent" + }), + comPost = Preset:new({ + display = 'Barracks', + cost = 1500, + type = 'upgrade', + template = "barracks" + }), + outpost = Preset:new({ + display = 'Outpost', + cost = 1500, + type = 'upgrade', + template = "outpost" + }) + }, + attack = { + ammoCache = Preset:new({ + display = 'Ammo Cache', + cost = 1500, + type = 'upgrade', + template = "ammo-cache" + }), + ammoDepot = Preset:new({ + display = 'Ammo Depot', + cost = 2000, + type = 'upgrade', + template = "ammo-depot" + }) + }, + supply = { + fuelCache = Preset:new({ + display = 'Fuel Cache', + cost = 1500, + type = 'upgrade', + template = "fuel-cache" + }), + fuelTank = Preset:new({ + display = 'Fuel Tank', + cost = 1500, + type = 'upgrade', + template = "fuel-tank-big" + }), + fuelTankFarp = Preset:new({ + display = 'Fuel Tank', + cost = 1500, + type = 'upgrade', + template = "fuel-tank-small" + }), + factory1 = Preset:new({ + display='Factory', + cost = 2000, + type ='upgrade', + income = 20, + template = "factory-1" + }), + factory2 = Preset:new({ + display='Factory', + cost = 2000, + type ='upgrade', + income = 20, + template = "factory-2" + }), + factoryTank = Preset:new({ + display='Storage Tank', + cost = 1500, + type ='upgrade', + income = 10, + template = "chem-tank" + }), + ammoDepot = Preset:new({ + display = 'Ammo Depot', + cost = 2000, + type = 'upgrade', + income = 40, + template = "ammo-depot" + }), + oilPump = Preset:new({ + display = 'Oil Pump', + cost = 1500, + type = 'upgrade', + income = 20, + template = "oil-pump" + }), + hangar = Preset:new({ + display = 'Hangar', + cost = 2000, + type = 'upgrade', + income = 30, + template = "hangar" + }), + excavator = Preset:new({ + display = 'Excavator', + cost = 2000, + type = 'upgrade', + income = 20, + template = "excavator" + }), + farm1 = Preset:new({ + display = 'Farm House', + cost = 2000, + type = 'upgrade', + income = 40, + template = "farm-house-1" + }), + farm2 = Preset:new({ + display = 'Farm House', + cost = 2000, + type = 'upgrade', + income = 40, + template = "farm-house-2" + }), + refinery1 = Preset:new({ + display='Refinery', + cost = 2000, + type ='upgrade', + income = 100, + template = "factory-1" + }), + powerplant1 = Preset:new({ + display='Power Plant', + cost = 1500, + type ='upgrade', + income = 25, + template = "factory-1" + }), + powerplant2 = Preset:new({ + display='Power Plant', + cost = 1500, + type ='upgrade', + income = 25, + template = "factory-2" + }), + antenna = Preset:new({ + display='Antenna', + cost = 1000, + type ='upgrade', + income = 10, + template = "antenna" + }), + hq = Preset:new({ + display='HQ Building', + cost = 2000, + type ='upgrade', + income = 50, + template = "tv-tower" + }) + }, + airdef = { + comCenter = Preset:new({ + display = 'Command Center', + cost = 2500, + type = 'upgrade', + template = "command-center" + }) + } + }, + defenses = { + red = { + infantry = Preset:new({ + display = 'Infantry', + cost=2000, + type='defense', + template='infantry-red', + }), + shorad = Preset:new({ + display = 'SAM', + cost=2500, + type='defense', + template='shorad-red', + }), + sam = Preset:new({ + display = 'SAM', + cost=3000, + type='defense', + template='sam-red', + }), + sa10 = Preset:new({ + display = 'SAM', + cost=3000, + type='defense', + template='sa10', + }), + sa5 = Preset:new({ + display = 'SAM', + cost=3000, + type='defense', + template='sa5', + }), + sa3 = Preset:new({ + display = 'SAM', + cost=3000, + type='defense', + template='sa3', + }), + sa6 = Preset:new({ + display = 'SAM', + cost=3000, + type='defense', + template='sa6', + }), + sa11 = Preset:new({ + display = 'SAM', + cost=3000, + type='defense', + template='sa11', + }) + }, + blue = { + infantry = Preset:new({ + display = 'Infantry', + cost=2000, + type='defense', + template='infantry-blue', + }), + shorad = Preset:new({ + display = 'SAM', + cost=2500, + type='defense', + template='shorad-blue', + }), + sam = Preset:new({ + display = 'SAM', + cost=3000, + type='defense', + template='sam-blue', + }), + patriot = Preset:new({ + display = 'SAM', + cost=3000, + type='defense', + template='patriot', + }), + nasams = Preset:new({ + display = 'SAM', + cost=3000, + type='defense', + template='nasams', + }) + } + }, + missions = { + supply = { + convoy = Preset:new({ + display = 'Supply convoy', + cost = 4000, + type = 'mission', + missionType = ZoneCommand.missionTypes.supply_convoy + }), + convoy_escorted = Preset:new({ + display = 'Supply convoy', + cost = 3000, + type = 'mission', + missionType = ZoneCommand.missionTypes.supply_convoy + }), + helo = Preset:new({ + display = 'Supply helicopter', + cost = 2500, + type='mission', + missionType = ZoneCommand.missionTypes.supply_air + }), + transfer = Preset:new({ + display = 'Supply transfer', + cost = 1000, + type='mission', + missionType = ZoneCommand.missionTypes.supply_transfer + }) + }, + attack = { + surface = Preset:new({ + display = 'Ground assault', + cost = 100, + type = 'mission', + missionType = ZoneCommand.missionTypes.assault, + }), + cas = Preset:new({ + display = 'CAS', + cost = 200, + type='mission', + missionType = ZoneCommand.missionTypes.cas + }), + bai = Preset:new({ + display = 'BAI', + cost = 200, + type='mission', + missionType = ZoneCommand.missionTypes.bai + }), + strike = Preset:new({ + display = 'Strike', + cost = 300, + type='mission', + missionType = ZoneCommand.missionTypes.strike + }), + sead = Preset:new({ + display = 'SEAD', + cost = 200, + type='mission', + missionType = ZoneCommand.missionTypes.sead + }), + helo = Preset:new({ + display = 'CAS', + cost = 100, + type='mission', + missionType = ZoneCommand.missionTypes.cas_helo + }) + }, + patrol={ + aircraft = Preset:new({ + display= "Patrol", + cost = 100, + type='mission', + missionType = ZoneCommand.missionTypes.patrol + }) + }, + support ={ + awacs = Preset:new({ + display= "AWACS", + cost = 300, + type='mission', + bias='5', + missionType = ZoneCommand.missionTypes.awacs + }), + tanker = Preset:new({ + display= "Tanker", + cost = 200, + type='mission', + bias='2', + missionType = ZoneCommand.missionTypes.tanker + }) + } + }, + special = { + red = { + infantry = Preset:new({ + display = 'Infantry', + cost=-1, + type='defense', + template='defense-red', + }), + }, + blue = { + infantry = Preset:new({ + display = 'Infantry', + cost=-1, + type='defense', + template='defense-blue', + }) + } + } +} + +zones = {} +do + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Batumi.lua ]]----------------- + +zones.batumi = ZoneCommand:new('Batumi') +zones.batumi.initialState = { side=2 } +zones.batumi.keepActive = true +zones.batumi.isHeloSpawn = true +zones.batumi.isPlaneSpawn = true +zones.batumi.maxResource = 50000 +zones.batumi:defineUpgrades({ + [1] = { --red side + presets.upgrades.basic.comPost:extend({ + name = 'batumi-com-red', + products = { + presets.special.red.infantry:extend({ name='batumi-defense-red'}), + presets.defenses.red.infantry:extend({ name='batumi-garrison-red' }) + } + }), + }, + [2] = --blue side + { + presets.upgrades.basic.comPost:extend({ + name = 'batumi-com-blue', + products = { + presets.special.blue.infantry:extend({ name='batumi-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='batumi-garrison-blue' }) + } + }), + presets.upgrades.supply.fuelTank:extend({ + name = 'batumi-fueltank-blue', + products = { + presets.missions.supply.convoy_escorted:extend({ name='batumi-supply-convoy-1'}), + presets.missions.supply.helo:extend({ name='batumi-supply-blue-1' }), + presets.missions.supply.transfer:extend({name='batumi-transfer-blue'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name = 'batumi-mission-command-blue', + products = { + presets.defenses.blue.shorad:extend({ name='batumi-sam-blue' }), + presets.missions.attack.sead:extend({name='batumi-sead-blue-1', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.attack.cas:extend({name='batumi-cas-blue-1', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.bai:extend({name='batumi-cas-blue-1', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.strike:extend({name='batumi-strike-blue-1', altitude=20000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.patrol.aircraft:extend({name='batumi-patrol-blue-1', altitude=25000, range=25}), + presets.missions.support.awacs:extend({name='batumi-awacs-blue', altitude=30000, freq=257.5}), + presets.missions.support.tanker:extend({name='batumi-tanker-blue', altitude=25000, freq=257, tacan='37', variant="Drogue"}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Batumi.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Mike.lua ]]----------------- + +zones.mike = ZoneCommand:new("Mike") +zones.mike.initialState = { side=1 } +zones.mike.keepActive = true +zones.mike:defineUpgrades({ + [1] = + { + presets.upgrades.basic.tent:extend({ + name='mike-tent-red', + products = { + presets.special.red.infantry:extend({ name='mike-defense-red'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='mike-comcenter-red', + products = { + presets.defenses.red.sam:extend({ name='mike-sam-red'}) + } + }) + }, + [2] = + { + presets.upgrades.basic.tent:extend({ + name='mike-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='mike-defense-blue'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='mike-comcenter-blue', + products = { + presets.defenses.blue.sam:extend({ name='mike-sam-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Mike.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Tyrnyauz.lua ]]----------------- + +zones.tyrnyauz = ZoneCommand:new("Tyrnyauz") +zones.tyrnyauz.initialState = { side=1 } +zones.tyrnyauz.isHeloSpawn = true +zones.tyrnyauz:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='tyrnyauz-tent-red', + products = { + presets.special.red.infantry:extend({ name='tyrnyauz-defense-red'}), + presets.defenses.red.infantry:extend({ name='tyrnyauz-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='tyrnyauz-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='tyrnyauz-supply-red'}), + presets.missions.supply.helo:extend({name='tyrnyauz-supply-red-2'}), + presets.missions.supply.transfer:extend({name='tyrnyauz-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='tyrnyauz-ammo-red', + products = { + presets.missions.attack.surface:extend({name='tyrnyauz-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='tyrnyauz-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='tyrnyauz-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='tyrnyauz-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='tyrnyauz-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='tyrnyauz-supply-blue'}), + presets.missions.supply.helo:extend({name='tyrnyauz-supply-blue-2'}), + presets.missions.supply.transfer:extend({name='tyrnyauz-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='tyrnyauz-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='tyrnyauz-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Tyrnyauz.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/India.lua ]]----------------- + +zones.india = ZoneCommand:new("India") +zones.india.initialState = nil +zones.india:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='india-tent-red', + products = { + presets.special.red.infantry:extend({ name='india-defense-red'}), + presets.defenses.red.infantry:extend({ name='india-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='india-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='india-supply-red'}), + presets.missions.supply.transfer:extend({name='india-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='india-ammo-red', + products = { + presets.missions.attack.surface:extend({name='india-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='india-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='india-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='india-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='india-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='india-supply-blue'}), + presets.missions.supply.transfer:extend({name='india-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='india-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='india-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/India.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/IntelCenter.lua ]]----------------- + +zones.intelcenter = ZoneCommand:new("Intel Center") +zones.intelcenter.initialState = { side=1 } +zones.intelcenter:defineUpgrades({ + [1] = + { + presets.upgrades.basic.tent:extend({ + name='intelcenter-tent-red', + products = { + presets.special.red.infantry:extend({ name='intelcenter-defense-red'}), + presets.defenses.red.infantry:extend({ name='intelcenter-garrison-red'}) + } + }), + presets.upgrades.supply.hq:extend({ + name='intelcenter-hq-red', + products = { + presets.missions.supply.convoy:extend({ name='intelcenter-supply-red'}), + presets.missions.supply.convoy:extend({ name='intelcenter-supply-red-1'}), + presets.missions.supply.transfer:extend({name='intelcenter-transfer-red'}) + } + }), + presets.upgrades.supply.antenna:extend({ + name='intelcenter-antenna-red', + products = { + } + }), + presets.upgrades.supply.antenna:extend({ + name='intelcenter-antenna-red-1', + products = { + } + }), + presets.upgrades.supply.antenna:extend({ + name='intelcenter-antenna-red-2', + products = { + } + }) + }, + [2] = + { + presets.upgrades.basic.tent:extend({ + name='intelcenter-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='intelcenter-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='intelcenter-garrison-blue'}) + } + }), + presets.upgrades.supply.hq:extend({ + name='intelcenter-hq-blue', + products = { + presets.missions.supply.convoy:extend({ name='intelcenter-supply-blue'}), + presets.missions.supply.convoy:extend({ name='intelcenter-supply-blue-1'}), + presets.missions.supply.transfer:extend({name='intelcenter-transfer-blue'}) + } + }), + presets.upgrades.supply.antenna:extend({ + name='intelcenter-antenna-blue', + products = { + } + }), + presets.upgrades.supply.antenna:extend({ + name='intelcenter-antenna-blue-1', + products = { + } + }), + presets.upgrades.supply.antenna:extend({ + name='intelcenter-antenna-blue-2', + products = { + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/IntelCenter.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Mineralnye.lua ]]----------------- + +zones.mineralnye = ZoneCommand:new("Mineralnye") +zones.mineralnye.initialState = { side=1 } +zones.mineralnye.keepActive = true +zones.mineralnye.isHeloSpawn = true +zones.mineralnye.isPlaneSpawn = true +zones.mineralnye:defineUpgrades({ + [1] = + { + presets.upgrades.basic.comPost:extend({ + name='mineralnye-compost-red', + products = { + presets.special.red.infantry:extend({ name='mineralnye-defense-red'}), + presets.defenses.red.infantry:extend({ name='mineralnye-garrison-red'}) + } + }), + presets.upgrades.supply.fuelTank:extend({ + name='mineralnye-fuel-red', + products = { + presets.missions.supply.helo:extend({name='mineralnye-supply-red'}), + presets.missions.supply.helo:extend({name='mineralnye-supply-red-1'}), + presets.missions.supply.transfer:extend({name='mineralnye-transfer-red'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='mineralnye-comcenter-red', + products = { + presets.defenses.red.sa11:extend({ name='mineralnye-airdef-red'}), + presets.missions.attack.cas:extend({name='mineralnye-cas-red', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.bai:extend({name='mineralnye-cas-red', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.strike:extend({name='mineralnye-strike-red', altitude=30000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.attack.strike:extend({name='mineralnye-strike-red-1', altitude=30000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.patrol.aircraft:extend({name='mineralnye-patrol-red', altitude=25000, range=25}) + } + }) + }, + [2] = + { + presets.upgrades.basic.comPost:extend({ + name='mineralnye-compost-blue', + products = { + presets.special.blue.infantry:extend({ name='mineralnye-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='mineralnye-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelTank:extend({ + name='mineralnye-fuel-blue', + products = { + presets.missions.supply.helo:extend({name='mineralnye-supply-blue'}), + presets.missions.supply.helo:extend({name='mineralnye-supply-blue-1'}), + presets.missions.supply.transfer:extend({name='mineralnye-transfer-blue'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='mineralnye-comcenter-blue', + products = { + presets.defenses.blue.nasams:extend({ name='mineralnye-airdef-blue'}), + presets.missions.attack.cas:extend({name='mineralnye-cas-blue', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.bai:extend({name='mineralnye-cas-blue', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.strike:extend({name='mineralnye-strike-blue', altitude=30000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.attack.strike:extend({name='mineralnye-strike-blue-1', altitude=30000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.patrol.aircraft:extend({name='mineralnye-patrol-blue', altitude=25000, range=25}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Mineralnye.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/PowerPlant.lua ]]----------------- + +zones.powerplant = ZoneCommand:new("Power Plant") +zones.powerplant.initialState = { side=1 } +zones.powerplant:defineUpgrades({ + [1] = + { + presets.upgrades.basic.tent:extend({ + name='powerplant-tent-red', + products = { + presets.special.red.infantry:extend({ name='powerplant-defense-red'}), + presets.defenses.red.infantry:extend({ name='powerplant-garrison-red'}) + } + }), + presets.upgrades.supply.powerplant1:extend({ + name='powerplant-building-red-1', + products = { + presets.missions.supply.convoy:extend({ name='powerplant-supply-red'}), + presets.missions.supply.transfer:extend({name='powerplant-transfer-red'}) + } + }), + presets.upgrades.supply.powerplant2:extend({ + name='powerplant-building-red-2', + products = { + presets.missions.supply.convoy:extend({ name='powerplant-supply-red-1'}), + presets.missions.supply.transfer:extend({name='powerplant-transfer-red'}) + } + }) + }, + [2] = + { + presets.upgrades.basic.tent:extend({ + name='powerplant-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='powerplant-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='powerplant-garrison-blue'}) + } + }), + presets.upgrades.supply.powerplant1:extend({ + name='powerplant-building-blue-1', + products = { + presets.missions.supply.convoy:extend({ name='powerplant-supply-blue'}), + presets.missions.supply.transfer:extend({name='powerplant-transfer-blue'}) + } + }), + presets.upgrades.supply.powerplant2:extend({ + name='powerplant-building-blue-2', + products = { + presets.missions.supply.convoy:extend({ name='powerplant-supply-blue-1'}), + presets.missions.supply.transfer:extend({name='powerplant-transfer-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/PowerPlant.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Zugdidi.lua ]]----------------- + +zones.zugdidi = ZoneCommand:new("Zugdidi") +zones.zugdidi.initialState = { side=1 } +zones.zugdidi:defineUpgrades({ + [1] = + { + presets.upgrades.basic.comPost:extend({ + name='zugdidi-compost-red', + products = { + presets.missions.supply.transfer:extend({name='zugdidi-transfer-red'}), + presets.special.red.infantry:extend({ name='zugdidi-defense-red'}), + presets.defenses.red.infantry:extend({ name='zugdidi-garrison-red'}), + presets.missions.attack.surface:extend({name='zugdidi-attack-red'}), + presets.missions.supply.convoy:extend({name='zugdidi-supply-red'}) + } + }), + presets.upgrades.supply.hangar:extend({ + name='zugdidi-hangar-red-1', + products = { + presets.missions.supply.helo:extend({name='zugdidi-supply-red-1'}) + } + }), + presets.upgrades.supply.hangar:extend({ + name='zugdidi-hangar-red-2', + products = { + presets.missions.supply.helo:extend({name='zugdidi-supply-red-2'}) + } + }), + presets.upgrades.supply.hangar:extend({ + name='zugdidi-hangar-red-3', + products = { + presets.missions.supply.helo:extend({name='zugdidi-supply-red-3'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='zugdidi-comcenter-red', + products = { + presets.defenses.red.sa6:extend({ name='zugdidi-sam-red'}) + } + }) + }, + [2] = + { + presets.upgrades.basic.tent:extend({ + name='zugdidi-compost-blue', + products = { + presets.missions.supply.transfer:extend({name='zugdidi-transfer-blue'}), + presets.special.blue.infantry:extend({ name='zugdidi-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='zugdidi-garrison-blue'}), + presets.missions.attack.surface:extend({name='zugdidi-attack-blue'}), + presets.missions.supply.convoy:extend({name='zugdidi-supply-blue'}) + } + }), + presets.upgrades.supply.hangar:extend({ + name='zugdidi-hangar-blue-1', + products = { + presets.missions.supply.helo:extend({name='zugdidi-supply-blue-1'}) + } + }), + presets.upgrades.supply.hangar:extend({ + name='zugdidi-hangar-blue-2', + products = { + presets.missions.supply.helo:extend({name='zugdidi-supply-blue-2'}) + } + }), + presets.upgrades.supply.hangar:extend({ + name='zugdidi-hangar-blue-3', + products = { + presets.missions.supply.helo:extend({name='zugdidi-supply-blue-3'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='zugdidi-comcenter-blue', + products = { + presets.defenses.blue.shorad:extend({ name='zugdidi-sam-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Zugdidi.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Babugent.lua ]]----------------- + +zones.babugent = ZoneCommand:new("Babugent") +zones.babugent.initialState = { side=1 } +zones.babugent:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='babugent-tent-red', + products = { + presets.special.red.infantry:extend({ name='babugent-defense-red'}), + presets.defenses.red.infantry:extend({ name='babugent-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='babugent-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='babugent-supply-red'}), + presets.missions.supply.helo:extend({name='babugent-supply-red-2'}), + presets.missions.supply.transfer:extend({name='babugent-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='babugent-ammo-red', + products = { + presets.missions.attack.surface:extend({name='babugent-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='babugent-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='babugent-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='babugent-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='babugent-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='babugent-supply-blue'}), + presets.missions.supply.helo:extend({name='babugent-supply-blue-2'}), + presets.missions.supply.transfer:extend({name='babugent-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='babugent-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='babugent-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Babugent.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Kislovodsk.lua ]]----------------- + +zones.kislovodsk = ZoneCommand:new("Kislovodsk") +zones.kislovodsk.initialState = { side=1 } +zones.kislovodsk:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='kislovodsk-tent-red', + products = { + presets.special.red.infantry:extend({ name='kislovodsk-defense-red'}), + presets.defenses.red.infantry:extend({ name='kislovodsk-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='kislovodsk-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='kislovodsk-supply-red'}), + presets.missions.supply.transfer:extend({name='kislovodsk-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='kislovodsk-ammo-red', + products = { + presets.missions.attack.surface:extend({name='kislovodsk-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='kislovodsk-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='kislovodsk-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='kislovodsk-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='kislovodsk-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='kislovodsk-supply-blue'}), + presets.missions.supply.transfer:extend({name='kislovodsk-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='kislovodsk-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='kislovodsk-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Kislovodsk.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Gudauta.lua ]]----------------- + +zones.gudauta = ZoneCommand:new("Gudauta") +zones.gudauta.initialState = { side=1 } +zones.gudauta.keepActive = true +zones.gudauta.maxResource = 50000 +zones.gudauta:defineUpgrades({ + [1] = + { + presets.upgrades.basic.comPost:extend({ + name='gudauta-compost-red', + products = { + presets.special.red.infantry:extend({ name='gudauta-defense-red'}), + presets.defenses.red.infantry:extend({ name='gudauta-garrison-red'}) + } + }), + presets.upgrades.supply.fuelTank:extend({ + name='gudauta-fuel-red', + products = { + presets.missions.supply.helo:extend({name='gudauta-supply-red'}), + presets.missions.supply.helo:extend({name='gudauta-supply-red-1'}), + presets.missions.supply.transfer:extend({name='gudauta-transfer-red'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='gudauta-comcenter-red', + products = { + presets.defenses.red.sam:extend({ name='gudauta-airdef-red'}), + presets.missions.attack.sead:extend({name='gudauta-sead-red', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.attack.sead:extend({name='gudauta-sead-red-1', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.attack.cas:extend({name='gudauta-cas-red', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.bai:extend({name='gudauta-cas-red', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.patrol.aircraft:extend({name='gudauta-patrol-red', altitude=25000, range=25}) + } + }) + }, + [2] = + { + presets.upgrades.basic.comPost:extend({ + name='gudauta-compost-blue', + products = { + presets.special.blue.infantry:extend({ name='gudauta-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='gudauta-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelTank:extend({ + name='gudauta-fuel-blue', + products = { + presets.missions.supply.helo:extend({name='gudauta-supply-blue'}), + presets.missions.supply.helo:extend({name='gudauta-supply-blue-1'}), + presets.missions.supply.transfer:extend({name='gudauta-transfer-blue'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='gudauta-comcenter-blue', + products = { + presets.defenses.blue.sam:extend({ name='gudauta-airdef-blue'}), + presets.missions.attack.sead:extend({name='gudauta-sead-blue', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.attack.sead:extend({name='gudauta-sead-blue-1', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.attack.cas:extend({name='gudauta-cas-blue', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.bai:extend({name='gudauta-cas-blue', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.patrol.aircraft:extend({name='gudauta-patrol-blue', altitude=25000, range=25}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Gudauta.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Distillery.lua ]]----------------- + +zones.distillery = ZoneCommand:new("Distillery") +zones.distillery.initialState = { side=1 } +zones.distillery:defineUpgrades({ + [1] = + { + presets.upgrades.basic.tent:extend({ + name='distillery-tent-red', + products = { + presets.special.red.infantry:extend({ name='distillery-defense-red'}), + presets.defenses.red.infantry:extend({ name='distillery-garrison-red'}) + } + }), + presets.upgrades.supply.factory1:extend({ + name='distillery-prod-red-1', + products = { + presets.missions.supply.convoy:extend({ name='distillery-supply-red-1'}), + presets.missions.supply.transfer:extend({name='distillery-transfer-red'}) + } + }), + presets.upgrades.supply.factory2:extend({ + name='distillery-prod-red-2', + products = { + presets.missions.supply.convoy:extend({ name='distillery-supply-red-2', cost=2000}), + presets.missions.supply.transfer:extend({name='distillery-transfer-red2'}) + } + }), + presets.upgrades.supply.factoryTank:extend({ + name='distillery-tank-red-1', + products = { + } + }), + presets.upgrades.supply.factoryTank:extend({ + name='distillery-tank-red-2', + products = { + } + }), + presets.upgrades.supply.factoryTank:extend({ + name='distillery-tank-red-3', + products = { + } + }) + }, + [2] = + { + presets.upgrades.basic.tent:extend({ + name='distillery-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='distillery-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='distillery-garrison-blue'}) + } + }), + presets.upgrades.supply.factory1:extend({ + name='distillery-prod-blue-1', + products = { + presets.missions.supply.convoy:extend({ name='distillery-supply-blue-1'}), + presets.missions.supply.transfer:extend({name='distillery-transfer-blue'}) + } + }), + presets.upgrades.supply.factory2:extend({ + name='distillery-prod-blue-2', + products = { + presets.missions.supply.convoy:extend({ name='distillery-supply-blue-2', cost=2000}), + presets.missions.supply.transfer:extend({name='distillery-transfer-blue2'}) + } + }), + presets.upgrades.supply.factoryTank:extend({ + name='distillery-tank-blue-1', + products = { + } + }), + presets.upgrades.supply.factoryTank:extend({ + name='distillery-tank-blue-2', + products = { + } + }), + presets.upgrades.supply.factoryTank:extend({ + name='distillery-tank-blue-3', + products = { + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Distillery.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Sochi.lua ]]----------------- + +zones.sochi = ZoneCommand:new("Sochi") +zones.sochi.initialState = { side=1 } +zones.sochi.keepActive = true +zones.sochi.maxResource = 50000 +zones.sochi:defineUpgrades({ + [1] = + { + presets.upgrades.basic.comPost:extend({ + name='sochi-compost-red', + products = { + presets.special.red.infantry:extend({ name='sochi-defense-red'}), + presets.defenses.red.infantry:extend({ name='sochi-garrison-red'}) + } + }), + presets.upgrades.supply.fuelTank:extend({ + name='sochi-fuel-red', + products = { + presets.missions.supply.helo:extend({name='sochi-supply-red-1'}), + presets.missions.supply.helo:extend({name='sochi-supply-red-2'}), + presets.missions.supply.convoy_escorted:extend({name='sochi-supply-red-3'}), + presets.missions.supply.transfer:extend({name='sochi-transfer-red'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='sochi-comcenter-red', + products = { + presets.defenses.red.sa10:extend({ name='sochi-airdef-red'}), + presets.missions.attack.sead:extend({name='sochi-sead-red', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.attack.strike:extend({name='sochi-strike-red', altitude=30000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.patrol.aircraft:extend({name='sochi-patrol-red', altitude=25000, range=25}), + presets.missions.patrol.aircraft:extend({name='sochi-patrol-red-1', altitude=25000, range=25}), + presets.missions.attack.cas:extend({name='sochi-cas-red', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.bai:extend({name='sochi-cas-red', altitude=10000, expend=AI.Task.WeaponExpend.ONE}) + } + }) + }, + [2] = + { + presets.upgrades.basic.comPost:extend({ + name='sochi-compost-blue', + products = { + presets.special.blue.infantry:extend({ name='sochi-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='sochi-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelTank:extend({ + name='sochi-fuel-blue', + products = { + presets.missions.supply.helo:extend({name='sochi-supply-blue-1'}), + presets.missions.supply.helo:extend({name='sochi-supply-blue-2'}), + presets.missions.supply.convoy_escorted:extend({name='sochi-supply-blue-3'}), + presets.missions.supply.transfer:extend({name='sochi-transfer-blue'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='sochi-comcenter-blue', + products = { + presets.defenses.blue.patriot:extend({ name='sochi-airdef-blue'}), + presets.missions.attack.sead:extend({name='sochi-sead-blue', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.attack.strike:extend({name='sochi-strike-blue', altitude=30000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.patrol.aircraft:extend({name='sochi-patrol-blue', altitude=25000, range=25}), + presets.missions.attack.cas:extend({name='sochi-cas-blue', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.bai:extend({name='sochi-cas-blue', altitude=10000, expend=AI.Task.WeaponExpend.ONE}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Sochi.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Golf.lua ]]----------------- + +zones.golf = ZoneCommand:new("Golf") +zones.golf.initialState = nil +zones.golf.isHeloSpawn = true +zones.golf:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='golf-tent-red', + products = { + presets.special.red.infantry:extend({ name='golf-defense-red'}), + presets.defenses.red.infantry:extend({ name='golf-garrison-red'}) + } + }), + presets.upgrades.supply.fuelTank:extend({ + name='golf-fuel-red', + products = { + presets.missions.supply.helo:extend({name='golf-supply-red'}), + presets.missions.supply.helo:extend({name='golf-supply-red-1'}), + presets.missions.supply.transfer:extend({name='golf-transfer-red'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='golf-comcenter-red', + products = { + presets.defenses.red.shorad:extend({name='golf-sam-red'}), + presets.missions.attack.helo:extend({name='golf-cas-red', altitude=200, expend=AI.Task.WeaponExpend.HALF }) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='golf-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='golf-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='golf-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelTankFarp:extend({ + name='golf-fuel-blue', + products = { + presets.missions.supply.helo:extend({name='golf-supply-blue'}), + presets.missions.supply.helo:extend({name='golf-supply-blue-1'}), + presets.missions.supply.transfer:extend({name='golf-transfer-blue'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='golf-comcenter-blue', + products = { + presets.defenses.blue.shorad:extend({name='golf-sam-blue'}), + presets.missions.attack.helo:extend({name='golf-cas-blue', altitude=200, expend=AI.Task.WeaponExpend.HALF }) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Golf.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Charlie.lua ]]----------------- + +zones.charlie = ZoneCommand:new("Charlie") +zones.charlie.initialState = { side=2 } +zones.charlie.keepActive = true +zones.charlie:defineUpgrades({ + [1] = + { + presets.upgrades.basic.tent:extend({ + name='charlie-tent-red', + products = { + presets.special.red.infantry:extend({ name='charlie-defense-red'}), + presets.defenses.red.infantry:extend({ name='charlie-garrison-red'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='charlie-comcenter-red', + products = { + presets.defenses.red.sam:extend({ name='charlie-sam-red'}) + } + }) + }, + [2] = + { + presets.upgrades.basic.tent:extend({ + name='charlie-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='charlie-defense-red'}), + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='charlie-comcenter-blue', + products = { + presets.defenses.blue.sam:extend({ name='charlie-sam-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Charlie.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Lentehi.lua ]]----------------- + +zones.lentehi = ZoneCommand:new("Lentehi") +zones.lentehi.initialState = { side=1 } +zones.lentehi:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='lentehi-tent-red', + products = { + presets.special.red.infantry:extend({ name='lentehi-defense-red'}), + presets.defenses.red.infantry:extend({ name='lentehi-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='lentehi-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='lentehi-supply-red'}), + presets.missions.supply.helo:extend({name='lentehi-supply-red-2'}), + presets.missions.supply.transfer:extend({name='lentehi-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='lentehi-ammo-red', + products = { + presets.missions.attack.surface:extend({name='lentehi-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='lentehi-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='lentehi-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='lentehi-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='lentehi-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='lentehi-supply-blue'}), + presets.missions.supply.helo:extend({name='lentehi-supply-blue-2'}), + presets.missions.supply.transfer:extend({name='lentehi-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='lentehi-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='lentehi-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Lentehi.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Refinery.lua ]]----------------- + +zones.refinery = ZoneCommand:new("Refinery") +zones.refinery.initialState = { side=1 } +zones.refinery:defineUpgrades({ + [1] = + { + presets.upgrades.basic.tent:extend({ + name='refinery-tent-red', + products = { + presets.special.red.infantry:extend({ name='refinery-defense-red'}), + presets.defenses.red.infantry:extend({ name='refinery-garrison-red'}) + } + }), + presets.upgrades.supply.refinery1:extend({ + name='refinery-building-red', + products = { + presets.missions.supply.convoy:extend({ name='refinery-supply-red'}), + presets.missions.supply.convoy:extend({ name='refinery-supply-red-1'}), + presets.missions.supply.helo:extend({ name='refinery-supply-red-2'}), + presets.missions.supply.transfer:extend({name='refinery-transfer-red'}) + } + }) + }, + [2] = + { + presets.upgrades.basic.tent:extend({ + name='refinery-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='refinery-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='refinery-garrison-blue'}) + } + }), + presets.upgrades.supply.refinery1:extend({ + name='refinery-building-blue', + products = { + presets.missions.supply.convoy:extend({ name='refinery-supply-blue'}), + presets.missions.supply.convoy:extend({ name='refinery-supply-blue-1'}), + presets.missions.supply.helo:extend({ name='refinery-supply-blue-2'}), + presets.missions.supply.transfer:extend({name='refinery-transfer-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Refinery.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Mozdok.lua ]]----------------- + +zones.mozdok = ZoneCommand:new("Mozdok") +zones.mozdok.initialState = { side=1 } +zones.mozdok.keepActive = true +zones.mozdok.maxResource = 50000 +zones.mozdok:defineUpgrades({ + [1] = + { + presets.upgrades.basic.comPost:extend({ + name='mozdok-compost-red', + products = { + presets.special.red.infantry:extend({ name='mozdok-defense-red'}), + presets.defenses.red.infantry:extend({ name='mozdok-garrison-red'}) + } + }), + presets.upgrades.supply.fuelTank:extend({ + name='mozdok-fuel-red', + products = { + presets.missions.supply.helo:extend({name='mozdok-supply-red-1'}), + presets.missions.supply.helo:extend({name='mozdok-supply-red-2'}), + presets.missions.supply.convoy_escorted:extend({name='mozdok-supply-red-3'}), + presets.missions.supply.transfer:extend({name='mozdok-transfer-red'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='mozdok-comcenter-red', + products = { + presets.defenses.red.sa10:extend({ name='mozdok-airdef-red'}), + presets.missions.patrol.aircraft:extend({name='mozdok-patrol-red', altitude=25000, range=25}), + presets.missions.attack.cas:extend({name='mozdok-cas-red', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.bai:extend({name='mozdok-cas-red', altitude=10000, expend=AI.Task.WeaponExpend.ONE}) + } + }) + }, + [2] = + { + presets.upgrades.basic.comPost:extend({ + name='mozdok-compost-blue', + products = { + presets.special.blue.infantry:extend({ name='mozdok-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='mozdok-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelTank:extend({ + name='mozdok-fuel-blue', + products = { + presets.missions.supply.helo:extend({name='mozdok-supply-blue-1'}), + presets.missions.supply.helo:extend({name='mozdok-supply-blue-2'}), + presets.missions.supply.convoy_escorted:extend({name='mozdok-supply-blue-3'}), + presets.missions.supply.transfer:extend({name='mozdok-transfer-blue'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='mozdok-comcenter-blue', + products = { + presets.defenses.blue.shorad:extend({ name='mozdok-airdef-blue'}), + presets.missions.patrol.aircraft:extend({name='mozdok-patrol-blue', altitude=25000, range=25}), + presets.missions.attack.cas:extend({name='mozdok-cas-blue', altitude=15000, cost=50, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.cas:extend({name='mozdok-cas-blue-1', altitude=15000, cost=50, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.bai:extend({name='mozdok-cas-blue', altitude=15000, cost=50, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.bai:extend({name='mozdok-cas-blue-1', altitude=15000, cost=50, expend=AI.Task.WeaponExpend.ONE}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Mozdok.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Lima.lua ]]----------------- + +zones.lima = ZoneCommand:new("Lima") +zones.lima.initialState = { side=1 } +zones.lima:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='lima-tent-red', + products = { + presets.special.red.infantry:extend({ name='lima-defense-red'}), + presets.defenses.red.infantry:extend({ name='lima-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='lima-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='lima-supply-red'}), + presets.missions.supply.helo:extend({name='lima-supply-red-1'}), + presets.missions.supply.transfer:extend({name='lima-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='lima-ammo-red', + products = { + presets.missions.attack.surface:extend({name='lima-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='lima-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='lima-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='lima-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='lima-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='lima-supply-blue'}), + presets.missions.supply.helo:extend({name='lima-supply-blue-1'}), + presets.missions.supply.transfer:extend({name='lima-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='lima-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='lima-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Lima.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Oscar.lua ]]----------------- + +zones.oscar = ZoneCommand:new("Oscar") +zones.oscar.initialState = { side=1 } +zones.oscar:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='oscar-tent-red', + products = { + presets.special.red.infantry:extend({ name='oscar-defense-red'}), + presets.defenses.red.infantry:extend({ name='oscar-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='oscar-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='oscar-supply-red'}), + presets.missions.supply.transfer:extend({name='oscar-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='oscar-ammo-red', + products = { + presets.missions.attack.surface:extend({name='oscar-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='oscar-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='oscar-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='oscar-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='oscar-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='oscar-supply-blue'}), + presets.missions.supply.transfer:extend({name='oscar-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='oscar-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='oscar-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Oscar.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Nalchik.lua ]]----------------- + +zones.nalchik = ZoneCommand:new("Nalchik") +zones.nalchik.initialState = { side=1 } +zones.nalchik.keepActive = true +zones.nalchik.isHeloSpawn = true +zones.nalchik.isPlaneSpawn = true +zones.nalchik:defineUpgrades({ + [1] = + { + presets.upgrades.basic.comPost:extend({ + name='nalchik-compost-red', + products = { + presets.special.red.infantry:extend({ name='nalchik-defense-red'}), + presets.defenses.red.infantry:extend({ name='nalchik-garrison-red'}) + } + }), + presets.upgrades.supply.fuelTank:extend({ + name='nalchik-fuel-red', + products = { + presets.missions.supply.helo:extend({name='nalchik-supply-red-1'}), + presets.missions.supply.helo:extend({name='nalchik-supply-red-2'}), + presets.missions.supply.transfer:extend({name='nalchik-transfer-red'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='nalchik-comcenter-red', + products = { + presets.defenses.red.sa3:extend({ name='nalchik-airdef-red'}), + presets.missions.attack.sead:extend({name='nalchik-sead-red', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.attack.cas:extend({name='nalchik-cas-red', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.bai:extend({name='nalchik-cas-red', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.strike:extend({name='nalchik-strike-red', altitude=30000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.patrol.aircraft:extend({name='nalchik-patrol-red', altitude=25000, range=25}), + presets.missions.patrol.aircraft:extend({name='nalchik-patrol-red-2', altitude=25000, range=25}), + presets.missions.support.awacs:extend({name='nalchik-awacs-red', altitude=30000, freq=251.2}), + presets.missions.support.tanker:extend({name='nalchik-tanker-red', altitude=30000, freq=252.2, tacan='40', variant='Drogue'}) + } + }) + }, + [2] = + { + presets.upgrades.basic.comPost:extend({ + name='nalchik-compost-blue', + products = { + presets.special.blue.infantry:extend({ name='nalchik-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='nalchik-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelTank:extend({ + name='nalchik-fuel-blue', + products = { + presets.missions.supply.helo:extend({name='nalchik-supply-blue-1'}), + presets.missions.supply.helo:extend({name='nalchik-supply-blue-2'}), + presets.missions.supply.transfer:extend({name='nalchik-transfer-blue'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='nalchik-comcenter-blue', + products = { + presets.defenses.blue.nasams:extend({ name='nalchik-airdef-blue'}), + presets.missions.support.awacs:extend({name='nalchik-awacs-blue', altitude=30000, freq=259.5}), + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Nalchik.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Digora.lua ]]----------------- + +zones.digora = ZoneCommand:new("Digora") +zones.digora.initialState = { side=1 } +zones.digora:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='digora-tent-red', + products = { + presets.special.red.infantry:extend({ name='digora-defense-red'}), + presets.defenses.red.infantry:extend({ name='digora-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='digora-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='digora-supply-red'}), + presets.missions.supply.transfer:extend({name='digora-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='digora-ammo-red', + products = { + presets.missions.attack.surface:extend({name='digora-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='digora-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='digora-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='digora-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='digora-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='digora-supply-blue'}), + presets.missions.supply.transfer:extend({name='digora-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='digora-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='digora-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Digora.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Uniform.lua ]]----------------- + +zones.uniform = ZoneCommand:new("Uniform") +zones.uniform.initialState = { side=1 } +zones.uniform:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='uniform-tent-red', + products = { + presets.special.red.infantry:extend({ name='uniform-defense-red'}), + presets.defenses.red.infantry:extend({ name='uniform-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='uniform-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='uniform-supply-red'}), + presets.missions.supply.transfer:extend({name='uniform-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='uniform-ammo-red', + products = { + presets.missions.attack.surface:extend({name='uniform-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='uniform-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='uniform-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='uniform-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='uniform-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='uniform-supply-blue'}), + presets.missions.supply.transfer:extend({name='uniform-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='uniform-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='uniform-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Uniform.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Factory.lua ]]----------------- + +zones.factory = ZoneCommand:new("Factory") +zones.factory.initialState = { side=2 } +zones.factory:defineUpgrades({ + [1] = + { + presets.upgrades.basic.tent:extend({ + name='factory-tent-red', + products = { + presets.special.red.infantry:extend({ name='factory-defense-red'}), + presets.defenses.red.infantry:extend({ name='factory-garrison-red'}) + } + }), + presets.upgrades.supply.factory1:extend({ + name='factory-prod-red-1', + products = { + presets.missions.supply.convoy:extend({ name='factory-supply-red-1'}), + presets.missions.supply.transfer:extend({name='factory-transfer-red'}) + } + }), + presets.upgrades.supply.factory2:extend({ + name='factory-prod-red-2', + products = { + presets.missions.supply.convoy:extend({ name='factory-supply-red-2', cost=2000}), + presets.missions.supply.transfer:extend({name='factory-transfer-red2'}) + } + }), + presets.upgrades.supply.factoryTank:extend({ + name='factory-tank-red-1', + products = { + } + }), + presets.upgrades.supply.factoryTank:extend({ + name='factory-tank-red-2', + products = { + } + }), + presets.upgrades.supply.factoryTank:extend({ + name='factory-tank-red-3', + products = { + } + }) + }, + [2] = + { + presets.upgrades.basic.tent:extend({ + name='factory-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='factory-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='factory-garrison-blue'}) + } + }), + presets.upgrades.supply.factory1:extend({ + name='factory-prod-blue-1', + products = { + presets.missions.supply.convoy:extend({ name='factory-supply-blue-1'}), + presets.missions.supply.transfer:extend({name='factory-transfer-blue'}) + } + }), + presets.upgrades.supply.factory2:extend({ + name='factory-prod-blue-2', + products = { + presets.missions.supply.convoy:extend({ name='factory-supply-blue-2', cost=2000}), + presets.missions.supply.transfer:extend({name='factory-transfer-blue2'}) + } + }), + presets.upgrades.supply.factoryTank:extend({ + name='factory-tank-blue-1', + products = { + } + }), + presets.upgrades.supply.factoryTank:extend({ + name='factory-tank-blue-2', + products = { + } + }), + presets.upgrades.supply.factoryTank:extend({ + name='factory-tank-blue-3', + products = { + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Factory.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Senaki.lua ]]----------------- + +zones.senaki = ZoneCommand:new("Senaki") +zones.senaki.initialState = { side=1 } +zones.senaki.keepActive = true +zones.senaki.isHeloSpawn = true +zones.senaki.isPlaneSpawn = true +zones.senaki.maxResource = 50000 +zones.senaki:defineUpgrades({ + [1] = + { + presets.upgrades.basic.comPost:extend({ + name='senaki-compost-red', + products = { + presets.special.red.infantry:extend({ name='senaki-defense-red'}), + presets.defenses.red.infantry:extend({ name='senaki-garrison-red'}) + } + }), + presets.upgrades.supply.fuelTank:extend({ + name='senaki-fuel-red', + products = { + presets.missions.supply.helo:extend({name='senaki-supply-red-1'}), + presets.missions.supply.helo:extend({name='senaki-supply-red-2'}), + presets.missions.supply.transfer:extend({name='senaki-transfer-red'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='senaki-comcenter-red', + products = { + presets.defenses.red.sa3:extend({ name='senaki-airdef-red'}), + presets.missions.attack.sead:extend({name='senaki-sead-red', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.attack.cas:extend({name='senaki-cas-red', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.bai:extend({name='senaki-cas-red', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.strike:extend({name='senaki-strike-red', altitude=20000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.patrol.aircraft:extend({name='senaki-patrol-red', altitude=25000, range=25}), + presets.missions.patrol.aircraft:extend({name='senaki-patrol-red-2', altitude=20000, range=25}) + } + }) + }, + [2] = + { + presets.upgrades.basic.comPost:extend({ + name='senaki-compost-blue', + products = { + presets.special.blue.infantry:extend({ name='senaki-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='senaki-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelTank:extend({ + name='senaki-fuel-blue', + products = { + presets.missions.supply.helo:extend({name='senaki-supply-blue-1'}), + presets.missions.supply.helo:extend({name='senaki-supply-blue-2'}), + presets.missions.supply.transfer:extend({name='senaki-transfer-blue'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='senaki-comcenter-blue', + products = { + presets.defenses.blue.shorad:extend({ name='senaki-airdef-blue'}), + presets.missions.attack.sead:extend({name='senaki-sead-blue', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.attack.cas:extend({name='senaki-cas-blue', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.bai:extend({name='senaki-cas-blue', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.strike:extend({name='senaki-strike-blue', altitude=20000, expend=AI.Task.WeaponExpend.TWO}), + presets.missions.patrol.aircraft:extend({name='senaki-patrol-blue', altitude=25000, range=25}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Senaki.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Kutaisi.lua ]]----------------- + +zones.kutaisi = ZoneCommand:new("Kutaisi") +zones.kutaisi.initialState = { side=1 } +zones.kutaisi.keepActive = true +zones.kutaisi.maxResource = 50000 +zones.kutaisi:defineUpgrades({ + [1] = + { + presets.upgrades.basic.comPost:extend({ + name='kutaisi-compost-red', + products = { + presets.special.red.infantry:extend({ name='kutaisi-defense-red'}), + presets.defenses.red.infantry:extend({ name='kutaisi-garrison-red'}) + } + }), + presets.upgrades.supply.fuelTank:extend({ + name='kutaisi-fuel-red', + products = { + presets.missions.supply.helo:extend({name='kutaisi-supply-red-1'}), + presets.missions.supply.helo:extend({name='kutaisi-supply-red-2'}), + presets.missions.supply.transfer:extend({name='kutaisi-transfer-red'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='kutaisi-comcenter-red', + products = { + presets.defenses.red.shorad:extend({ name='kutaisi-airdef-red'}), + presets.missions.attack.sead:extend({name='kutaisi-sead-red', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.attack.cas:extend({name='kutaisi-cas-red', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.bai:extend({name='kutaisi-cas-red', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.strike:extend({name='kutaisi-strike-red', altitude=20000, expend=AI.Task.WeaponExpend.HALF}), + presets.missions.patrol.aircraft:extend({name='kutaisi-patrol-red', altitude=25000, range=25}), + presets.missions.patrol.aircraft:extend({name='kutaisi-patrol-red-2', altitude=25000, range=25}) + } + }) + }, + [2] = + { + presets.upgrades.basic.comPost:extend({ + name='kutaisi-compost-blue', + products = { + presets.special.blue.infantry:extend({ name='kutaisi-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='kutaisi-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelTank:extend({ + name='kutaisi-fuel-blue', + products = { + presets.missions.supply.helo:extend({name='kutaisi-supply-blue-1'}), + presets.missions.supply.helo:extend({name='kutaisi-supply-blue-2'}), + presets.missions.supply.transfer:extend({name='kutaisi-transfer-blue'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='kutaisi-comcenter-blue', + products = { + presets.defenses.blue.shorad:extend({ name='kutaisi-airdef-blue'}), + presets.missions.attack.sead:extend({name='kutaisi-sead-blue', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.attack.cas:extend({name='kutaisi-cas-blue', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.bai:extend({name='kutaisi-cas-blue', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.strike:extend({name='kutaisi-strike-blue', altitude=20000, expend=AI.Task.WeaponExpend.TWO}), + presets.missions.patrol.aircraft:extend({name='kutaisi-patrol-blue', altitude=25000, range=25}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Kutaisi.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Prohladniy.lua ]]----------------- + +zones.prohladniy = ZoneCommand:new("Prohladniy") +zones.prohladniy.initialState = { side=1 } +zones.prohladniy:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='prohladniy-tent-red', + products = { + presets.special.red.infantry:extend({ name='prohladniy-defense-red'}), + presets.defenses.red.infantry:extend({ name='prohladniy-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='prohladniy-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='prohladniy-supply-red'}), + presets.missions.supply.transfer:extend({name='prohladniy-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='prohladniy-ammo-red', + products = { + presets.missions.attack.surface:extend({name='prohladniy-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='prohladniy-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='prohladniy-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='prohladniy-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='prohladniy-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='prohladniy-supply-blue'}), + presets.missions.supply.transfer:extend({name='prohladniy-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='prohladniy-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='prohladniy-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Prohladniy.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Tallyk.lua ]]----------------- + +zones.tallyk = ZoneCommand:new("Tallyk") +zones.tallyk.initialState = { side=1 } +zones.tallyk.keepActive = true +zones.tallyk:defineUpgrades({ + [1] = + { + presets.upgrades.basic.tent:extend({ + name='tallyk-tent-red', + products = { + presets.special.red.infantry:extend({ name='tallyk-defense-red'}), + presets.defenses.red.infantry:extend({ name='tallyk-garrison-red'}), + presets.missions.attack.surface:extend({name='tallyk-assault-red'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='tallyk-comcenter-red', + products = { + presets.defenses.red.sam:extend({ name='tallyk-sam-red'}) + } + }) + }, + [2] = + { + presets.upgrades.basic.tent:extend({ + name='tallyk-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='tallyk-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='tallyk-garrison-blue'}), + presets.missions.attack.surface:extend({name='tallyk-assault-blue'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='tallyk-comcenter-blue', + products = { + presets.defenses.blue.sam:extend({ name='tallyk-sam-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Tallyk.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Terek.lua ]]----------------- + +zones.terek = ZoneCommand:new("Terek") +zones.terek.initialState = { side=1 } +zones.terek:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='terek-tent-red', + products = { + presets.special.red.infantry:extend({ name='terek-defense-red'}), + presets.defenses.red.infantry:extend({ name='terek-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='terek-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='terek-supply-red'}), + presets.missions.supply.transfer:extend({name='terek-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='terek-ammo-red', + products = { + presets.missions.attack.surface:extend({name='terek-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='terek-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='terek-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='terek-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='terek-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='terek-supply-blue'}), + presets.missions.supply.transfer:extend({name='terek-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='terek-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='terek-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Terek.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Humara.lua ]]----------------- + +zones.humara = ZoneCommand:new("Humara") +zones.humara.initialState = { side=1 } +zones.humara:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='humara-tent-red', + products = { + presets.special.red.infantry:extend({ name='humara-defense-red'}), + presets.defenses.red.infantry:extend({ name='humara-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='humara-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='humara-supply-red'}), + presets.missions.supply.transfer:extend({name='humara-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='humara-ammo-red', + products = { + presets.missions.attack.surface:extend({name='humara-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='humara-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='humara-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='humara-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='humara-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='humara-supply-blue'}), + presets.missions.supply.transfer:extend({name='humara-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='humara-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='humara-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Humara.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Ochamchira.lua ]]----------------- + +zones.ochamchira = ZoneCommand:new("Ochamchira") +zones.ochamchira.initialState = { side=1 } +zones.ochamchira:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='ochamchira-tent-red', + products = { + presets.special.red.infantry:extend({ name='ochamchira-defense-red'}), + presets.defenses.red.infantry:extend({ name='ochamchira-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='ochamchira-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='ochamchira-supply-red'}), + presets.missions.supply.transfer:extend({name='ochamchira-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='ochamchira-ammo-red', + products = { + presets.missions.attack.surface:extend({name='ochamchira-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='ochamchira-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='ochamchira-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='ochamchira-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='ochamchira-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='ochamchira-supply-blue'}), + presets.missions.supply.transfer:extend({name='ochamchira-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='ochamchira-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='ochamchira-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Ochamchira.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/November.lua ]]----------------- + +zones.november = ZoneCommand:new("November") +zones.november.initialState = { side=1 } +zones.november.isHeloSpawn = true +zones.november:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='november-tent-red', + products = { + presets.special.red.infantry:extend({ name='november-defense-red'}), + presets.defenses.red.infantry:extend({ name='november-garrison-red'}) + } + }), + presets.upgrades.supply.fuelTankFarp:extend({ + name='november-fuel-red', + products = { + presets.missions.supply.helo:extend({name='november-supply-red'}), + presets.missions.supply.helo:extend({name='november-supply-red-1'}), + presets.missions.supply.transfer:extend({name='november-transfer-red'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='november-comcenter-red', + products = { + presets.defenses.red.shorad:extend({name='november-sam-red'}), + presets.missions.attack.helo:extend({name='november-cas-red', altitude=200, expend=AI.Task.WeaponExpend.HALF }) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='november-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='november-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='november-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelTankFarp:extend({ + name='november-fuel-blue', + products = { + presets.missions.supply.helo:extend({name='november-supply-blue'}), + presets.missions.supply.helo:extend({name='november-supply-blue-1'}), + presets.missions.supply.transfer:extend({name='november-transfer-blue'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='november-comcenter-blue', + products = { + presets.defenses.blue.shorad:extend({name='november-sam-blue'}), + presets.missions.attack.helo:extend({name='november-cas-blue', altitude=200, expend=AI.Task.WeaponExpend.HALF }) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/November.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/XRay.lua ]]----------------- + +zones.xray = ZoneCommand:new("XRay") +zones.xray.initialState = { side=1 } +zones.xray:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='xray-tent-red', + products = { + presets.special.red.infantry:extend({ name='xray-defense-red'}), + presets.defenses.red.infantry:extend({ name='xray-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='xray-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='xray-supply-red'}), + presets.missions.supply.helo:extend({name='xray-supply-red-2'}), + presets.missions.supply.transfer:extend({name='xray-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='xray-ammo-red', + products = { + presets.missions.attack.surface:extend({name='xray-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='xray-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='xray-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='xray-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='xray-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='xray-supply-blue'}), + presets.missions.supply.helo:extend({name='xray-supply-blue-2'}), + presets.missions.supply.transfer:extend({name='xray-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='xray-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='xray-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/XRay.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Whiskey.lua ]]----------------- + +zones.whiskey = ZoneCommand:new("Whiskey") +zones.whiskey.initialState = { side=1 } +zones.whiskey:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='whiskey-tent-red', + products = { + presets.special.red.infantry:extend({ name='whiskey-defense-red'}), + presets.defenses.red.infantry:extend({ name='whiskey-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='whiskey-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='whiskey-supply-red'}), + presets.missions.supply.transfer:extend({name='whiskey-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='whiskey-ammo-red', + products = { + presets.missions.attack.surface:extend({name='whiskey-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='whiskey-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='whiskey-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='whiskey-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='whiskey-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='whiskey-supply-blue'}), + presets.missions.supply.transfer:extend({name='whiskey-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='whiskey-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='whiskey-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Whiskey.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Mine.lua ]]----------------- + +zones.mine = ZoneCommand:new("Mine") +zones.mine.initialState = { side=1 } +zones.mine:defineUpgrades({ + [1] = + { + presets.upgrades.basic.tent:extend({ + name='mine-tent-red', + products = { + presets.special.red.infantry:extend({ name='mine-defense-red'}), + presets.defenses.red.infantry:extend({ name='mine-garrison-red'}) + } + }), + presets.upgrades.supply.excavator:extend({ + name='mine-excavator-red-1', + products = { + presets.missions.supply.convoy:extend({ name='mine-supply-red'}), + presets.missions.supply.transfer:extend({name='mine-transfer-red'}) + } + }), + presets.upgrades.supply.excavator:extend({ + name='mine-excavator-red-2', + products = { + presets.missions.supply.convoy:extend({ name='mine-supply-red'}), + presets.missions.supply.transfer:extend({name='mine-transfer-red'}) + } + }), + presets.upgrades.supply.excavator:extend({ + name='mine-excavator-red-3', + products = { + presets.missions.supply.convoy:extend({ name='mine-supply-red'}), + presets.missions.supply.transfer:extend({name='mine-transfer-red'}) + } + }) + }, + [2] = + { + presets.upgrades.basic.tent:extend({ + name='mine-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='mine-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='mine-garrison-blue'}) + } + }), + presets.upgrades.supply.excavator:extend({ + name='mine-excavator-blue-1', + products = { + presets.missions.supply.convoy:extend({ name='mine-supply-blue'}), + presets.missions.supply.transfer:extend({name='mine-transfer-blue'}) + } + }), + presets.upgrades.supply.excavator:extend({ + name='mine-excavator-blue-2', + products = { + presets.missions.supply.convoy:extend({ name='mine-supply-blue'}), + presets.missions.supply.transfer:extend({name='mine-transfer-blue'}) + } + }), + presets.upgrades.supply.excavator:extend({ + name='mine-excavator-blue-3', + products = { + presets.missions.supply.convoy:extend({ name='mine-supply-blue'}), + presets.missions.supply.transfer:extend({name='mine-transfer-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Mine.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Papa.lua ]]----------------- + +zones.papa = ZoneCommand:new("Papa") +zones.papa.initialState = { side=1 } +zones.papa.keepActive = true +zones.papa:defineUpgrades({ + [1] = + { + presets.upgrades.basic.tent:extend({ + name='papa-tent-red', + products = { + presets.special.red.infantry:extend({ name='papa-defense-red'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='papa-comcenter-red', + products = { + presets.defenses.red.sam:extend({ name='papa-sam-red'}) + } + }) + }, + [2] = + { + presets.upgrades.basic.tent:extend({ + name='papa-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='papa-defense-blue'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='papa-comcenter-blue', + products = { + presets.defenses.blue.sam:extend({ name='papa-sam-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Papa.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Sukhumi.lua ]]----------------- + +zones.sukhumi = ZoneCommand:new("Sukhumi") +zones.sukhumi.initialState = { side=1 } +zones.sukhumi.keepActive = true +zones.sukhumi.maxResource = 50000 +zones.sukhumi:defineUpgrades({ + [1] = + { + presets.upgrades.basic.comPost:extend({ + name='sukhumi-compost-red', + products = { + presets.special.red.infantry:extend({ name='sukhumi-defense-red'}), + presets.defenses.red.infantry:extend({ name='sukhumi-garrison-red'}) + } + }), + presets.upgrades.supply.fuelTank:extend({ + name='sukhumi-fuel-red', + products = { + presets.missions.supply.helo:extend({name='sukhumi-supply-red-1'}), + presets.missions.supply.helo:extend({name='sukhumi-supply-red-2'}), + presets.missions.supply.transfer:extend({name='sukhumi-transfer-red'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='sukhumi-comcenter-red', + products = { + presets.defenses.red.sa11:extend({ name='sukhumi-airdef-red'}), + presets.missions.attack.sead:extend({name='sukhumi-sead-red', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.attack.cas:extend({name='sukhumi-cas-red', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.bai:extend({name='sukhumi-cas-red', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.strike:extend({name='sukhumi-strike-red', altitude=20000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.patrol.aircraft:extend({name='sukhumi-patrol-red', altitude=25000, range=25}), + presets.missions.patrol.aircraft:extend({name='sukhumi-patrol-red-2', altitude=25000, range=25}) + } + }) + }, + [2] = + { + presets.upgrades.basic.comPost:extend({ + name='sukhumi-compost-blue', + products = { + presets.special.blue.infantry:extend({ name='sukhumi-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='sukhumi-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelTank:extend({ + name='sukhumi-fuel-blue', + products = { + presets.missions.supply.helo:extend({name='sukhumi-supply-blue-1'}), + presets.missions.supply.helo:extend({name='sukhumi-supply-blue-2'}), + presets.missions.supply.transfer:extend({name='sukhumi-transfer-blue'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='sukhumi-comcenter-blue', + products = { + presets.defenses.blue.nasams:extend({ name='sukhumi-airdef-blue'}), + presets.missions.attack.sead:extend({name='sukhumi-sead-blue', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.attack.cas:extend({name='sukhumi-cas-blue', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.bai:extend({name='sukhumi-cas-blue', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.strike:extend({name='sukhumi-strike-blue', altitude=20000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.patrol.aircraft:extend({name='sukhumi-patrol-blue', altitude=25000, range=25}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Sukhumi.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Farm.lua ]]----------------- + +zones.farm = ZoneCommand:new("Farm") +zones.farm.initialState = { side=1 } +zones.farm:defineUpgrades({ + [1] = + { + presets.upgrades.basic.tent:extend({ + name='farm-tent-red', + products = { + presets.special.red.infantry:extend({ name='farm-defense-red'}), + presets.defenses.red.infantry:extend({ name='farm-garrison-red'}) + } + }), + presets.upgrades.supply.farm1:extend({ + name='farm-prod-red-1', + products = { + presets.missions.supply.convoy:extend({ name='farm-supply-red'}), + presets.missions.supply.transfer:extend({name='farm-transfer-red'}) + } + }), + presets.upgrades.supply.farm2:extend({ + name='farm-prod-red-2', + products = { + presets.missions.supply.convoy:extend({ name='farm-supply-red'}), + presets.missions.supply.transfer:extend({name='farm-transfer-red'}) + } + }) + }, + [2] = + { + presets.upgrades.basic.tent:extend({ + name='farm-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='farm-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='farm-garrison-blue'}) + } + }), + presets.upgrades.supply.farm1:extend({ + name='farm-prod-blue-1', + products = { + presets.missions.supply.convoy:extend({ name='farm-supply-blue'}), + presets.missions.supply.transfer:extend({name='farm-transfer-blue'}) + } + }), + presets.upgrades.supply.farm2:extend({ + name='farm-prod-blue-2', + products = { + presets.missions.supply.convoy:extend({ name='farm-supply-blue'}), + presets.missions.supply.transfer:extend({name='farm-transfer-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Farm.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Romeo.lua ]]----------------- + +zones.romeo = ZoneCommand:new("Romeo") +zones.romeo.initialState = { side=1 } +zones.romeo:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='romeo-tent-red', + products = { + presets.special.red.infantry:extend({ name='romeo-defense-red'}), + presets.defenses.red.infantry:extend({ name='romeo-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='romeo-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='romeo-supply-red'}), + presets.missions.supply.transfer:extend({name='romeo-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='romeo-ammo-red', + products = { + presets.missions.attack.surface:extend({name='romeo-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='romeo-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='romeo-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='romeo-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='romeo-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='romeo-supply-blue'}), + presets.missions.supply.transfer:extend({name='romeo-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='romeo-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='romeo-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Romeo.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Zulu.lua ]]----------------- + +zones.zulu = ZoneCommand:new("Zulu") +zones.zulu.initialState = { side=1 } +zones.zulu:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='zulu-tent-red', + products = { + presets.special.red.infantry:extend({ name='zulu-defense-red'}), + presets.defenses.red.infantry:extend({ name='zulu-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='zulu-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='zulu-supply-red'}), + presets.missions.supply.transfer:extend({name='zulu-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='zulu-ammo-red', + products = { + presets.missions.attack.surface:extend({name='zulu-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='zulu-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='zulu-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='zulu-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='zulu-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='zulu-supply-blue'}), + presets.missions.supply.transfer:extend({name='zulu-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='zulu-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='zulu-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Zulu.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Yankee.lua ]]----------------- + +zones.yankee = ZoneCommand:new("Yankee") +zones.yankee.initialState = { side=1 } +zones.yankee:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='yankee-tent-red', + products = { + presets.special.red.infantry:extend({ name='yankee-defense-red'}), + presets.defenses.red.infantry:extend({ name='yankee-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='yankee-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='yankee-supply-red'}), + presets.missions.supply.transfer:extend({name='yankee-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='yankee-ammo-red', + products = { + presets.missions.attack.surface:extend({name='yankee-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='yankee-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='yankee-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='yankee-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='yankee-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='yankee-supply-blue'}), + presets.missions.supply.transfer:extend({name='yankee-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='yankee-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='yankee-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Yankee.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Malgobek.lua ]]----------------- + +zones.malgobek = ZoneCommand:new("Malgobek") +zones.malgobek.initialState = { side=1 } +zones.malgobek:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='malgobek-tent-red', + products = { + presets.special.red.infantry:extend({ name='malgobek-defense-red'}), + presets.defenses.red.infantry:extend({ name='malgobek-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='malgobek-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='malgobek-supply-red'}), + presets.missions.supply.transfer:extend({name='malgobek-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='malgobek-ammo-red', + products = { + presets.missions.attack.surface:extend({name='malgobek-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='malgobek-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='malgobek-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='malgobek-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='malgobek-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='malgobek-supply-blue'}), + presets.missions.supply.transfer:extend({name='malgobek-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='malgobek-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='malgobek-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Malgobek.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Kilo.lua ]]----------------- + +zones.kilo = ZoneCommand:new("Kilo") +zones.kilo.initialState = { side=1 } +zones.kilo:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='kilo-tent-red', + products = { + presets.special.red.infantry:extend({ name='kilo-defense-red'}), + presets.defenses.red.infantry:extend({ name='kilo-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='kilo-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='kilo-supply-red'}), + presets.missions.supply.transfer:extend({name='kilo-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='kilo-ammo-red', + products = { + presets.missions.attack.surface:extend({name='kilo-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='kilo-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='kilo-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='kilo-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='kilo-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='kilo-supply-blue'}), + presets.missions.supply.transfer:extend({name='kilo-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='kilo-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='kilo-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Kilo.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Quebec.lua ]]----------------- + +zones.quebec = ZoneCommand:new("Quebec") +zones.quebec.initialState = { side=1 } +zones.quebec.keepActive = true +zones.quebec:defineUpgrades({ + [1] = + { + presets.upgrades.basic.tent:extend({ + name='quebec-tent-red', + products = { + presets.special.red.infantry:extend({ name='quebec-defense-red'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='quebec-comcenter-red', + products = { + presets.defenses.red.sam:extend({ name='quebec-sam-red'}) + } + }) + }, + [2] = + { + presets.upgrades.basic.tent:extend({ + name='quebec-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='quebec-defense-blue'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='quebec-comcenter-blue', + products = { + presets.defenses.blue.sam:extend({ name='quebec-sam-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Quebec.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/OilFields.lua ]]----------------- + +zones.oilfields = ZoneCommand:new("Oil Fields") +zones.oilfields.initialState = { side=1 } +zones.oilfields:defineUpgrades({ + [1] = { + presets.upgrades.basic.outpost:extend({ + name='oilfields-outpost-red', + products = { + presets.special.red.infantry:extend({ name='oilfields-defense-red'}), + presets.defenses.red.infantry:extend({ name='oilfields-garrison-red'}) + } + }), + presets.upgrades.supply.oilPump:extend({ + name='oilfields-pump-red-1', + products = { + presets.missions.supply.transfer:extend({name='oilfields-transfer-red1'}) + } + }), + presets.upgrades.supply.oilPump:extend({ + name='oilfields-pump-red-2', + products = { + presets.missions.supply.convoy:extend({name='oilfields-supply-red-1'}) + } + }), + presets.upgrades.supply.oilPump:extend({ + name='oilfields-pump-red-3', + products = { + presets.missions.supply.transfer:extend({name='oilfields-transfer-red2'}) + } + }), + presets.upgrades.supply.oilPump:extend({ + name='oilfields-pump-red-4', + products = { + presets.missions.supply.convoy:extend({name='oilfields-supply-red-2'}) + } + }) + }, + [2] = { + presets.upgrades.basic.outpost:extend({ + name='oilfields-outpost-blue', + products = { + presets.special.blue.infantry:extend({ name='oilfields-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='oilfields-garrison-blue'}) + } + }), + presets.upgrades.supply.oilPump:extend({ + name='oilfields-pump-blue-1', + products = { + presets.missions.supply.transfer:extend({name='oilfields-transfer-blue1'}) + } + }), + presets.upgrades.supply.oilPump:extend({ + name='oilfields-pump-blue-2', + products = { + presets.missions.supply.convoy:extend({name='oilfields-supply-blue-1'}) + } + }), + presets.upgrades.supply.oilPump:extend({ + name='oilfields-pump-blue-3', + products = { + presets.missions.supply.transfer:extend({name='oilfields-transfer-blue2'}) + } + }), + presets.upgrades.supply.oilPump:extend({ + name='oilfields-pump-blue-4', + products = { + presets.missions.supply.convoy:extend({name='oilfields-supply-blue-2'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/OilFields.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Echo.lua ]]----------------- + +zones.echo = ZoneCommand:new("Echo") +zones.echo.initialState = { side=2 } +zones.echo:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='echo-tent-red', + products = { + presets.special.red.infantry:extend({ name='echo-defense-red'}), + presets.defenses.red.infantry:extend({ name='echo-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='echo-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='echo-supply-red'}), + presets.missions.supply.transfer:extend({name='echo-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='echo-ammo-red', + products = { + presets.missions.attack.surface:extend({name='echo-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='echo-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='echo-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='echo-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='echo-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='echo-supply-blue'}), + presets.missions.supply.transfer:extend({name='echo-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='echo-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='echo-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Echo.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Kobuleti.lua ]]----------------- + +zones.kobuleti = ZoneCommand:new("Kobuleti") +zones.kobuleti.initialState = { side=2 } +zones.kobuleti.keepActive = true +zones.kobuleti.isHeloSpawn = true +zones.kobuleti.isPlaneSpawn = true +zones.kobuleti.maxResource = 50000 +zones.kobuleti:defineUpgrades({ + [1] = + { + presets.upgrades.basic.comPost:extend({ + name='kobuleti-compost-red', + products = { + presets.special.red.infantry:extend({ name='kobuleti-defense-red'}), + presets.defenses.red.infantry:extend({ name='kobuleti-garrison-red'}), + presets.missions.attack.surface:extend({ name='kobuleti-assault-red'}) + } + }), + presets.upgrades.supply.fuelTank:extend({ + name='kobuleti-fuel-red', + products = { + presets.missions.supply.helo:extend({name='kobuleti-supply-red-1'}), + presets.missions.supply.helo:extend({name='kobuleti-supply-red-2'}), + presets.missions.supply.transfer:extend({name='kobuleti-transfer-red'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='kobuleti-comcenter-red', + products = { + presets.defenses.red.shorad:extend({ name='kobuleti-airdef-red'}), + presets.missions.attack.sead:extend({name='kobuleti-sead-red', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.attack.cas:extend({name='kobuleti-cas-red', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.bai:extend({name='kobuleti-cas-red', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.strike:extend({name='kobuleti-strike-red', altitude=20000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.patrol.aircraft:extend({name='kobuleti-patrol-red', altitude=25000, range=25}) + } + }) + }, + [2] = + { + presets.upgrades.basic.comPost:extend({ + name='kobuleti-compost-blue', + products = { + presets.special.blue.infantry:extend({ name='kobuleti-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='kobuleti-garrison-blue'}), + presets.missions.attack.surface:extend({ name='kobuleti-assault-blue'}) + } + }), + presets.upgrades.supply.fuelTank:extend({ + name='kobuleti-fuel-blue', + products = { + presets.missions.supply.helo:extend({name='kobuleti-supply-blue-1'}), + presets.missions.supply.helo:extend({name='kobuleti-supply-blue-2'}), + presets.missions.supply.transfer:extend({name='kobuleti-transfer-blue'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='kobuleti-comcenter-blue', + products = { + presets.defenses.blue.shorad:extend({ name='kobuleti-airdef-blue'}), + presets.missions.attack.sead:extend({name='kobuleti-sead-blue', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.attack.cas:extend({name='kobuleti-cas-blue', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.bai:extend({name='kobuleti-cas-blue', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.strike:extend({name='kobuleti-strike-blue', altitude=20000, expend=AI.Task.WeaponExpend.TWO}), + presets.missions.patrol.aircraft:extend({name='kobuleti-patrol-blue', altitude=25000, range=25}), + presets.missions.support.awacs:extend({name='kobuleti-awacs-blue', altitude=30000, freq=258.5}), + presets.missions.support.tanker:extend({name='kobuleti-tanker-blue', altitude=23000, freq=258, tacan='38', variant='Boom'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Kobuleti.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Alpha.lua ]]----------------- + +zones.alpha = ZoneCommand:new('Alpha') +zones.alpha.initialState = { side=2 } +zones.alpha:defineUpgrades({ + [1] = --red side + { + presets.upgrades.basic.tent:extend({ + name = 'alpha-tent-red', + products = { + presets.special.red.infantry:extend({ name='alpha-defense-red'}), + presets.defenses.red.infantry:extend({ name='alpha-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name = 'alpha-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({ name='alpha-supply-red'}), + presets.missions.supply.transfer:extend({name='alpha-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name = 'alpha-ammo-red', + products = { + presets.missions.attack.surface:extend({ name='alpha-assault-red'}) + } + }) + }, + [2] = --blue side + { + presets.upgrades.basic.tent:extend({ + name = 'alpha-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='alpha-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='alpha-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name = 'alpha-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({ name='alpha-supply-blue'}), + presets.missions.supply.transfer:extend({name='alpha-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name = 'alpha-ammo-blue', + products = { + presets.missions.attack.surface:extend({ name='alpha-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Alpha.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Foxtrot.lua ]]----------------- + +zones.foxtrot = ZoneCommand:new("Foxtrot") +zones.foxtrot.initialState = { side=2 } +zones.foxtrot:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='foxtrot-tent-red', + products = { + presets.special.red.infantry:extend({ name='foxtrot-defense-red'}), + presets.defenses.red.infantry:extend({ name='foxtrot-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='foxtrot-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='foxtrot-supply-red'}), + presets.missions.supply.transfer:extend({name='foxtrot-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='foxtrot-ammo-red', + products = { + presets.missions.attack.surface:extend({name='foxtrot-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='foxtrot-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='foxtrot-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='foxtrot-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='foxtrot-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='foxtrot-supply-blue'}), + presets.missions.supply.transfer:extend({name='foxtrot-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='foxtrot-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='foxtrot-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Foxtrot.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Sierra.lua ]]----------------- + +zones.sierra = ZoneCommand:new("Sierra") +zones.sierra.initialState = { side=1 } +zones.sierra.isHeloSpawn = true +zones.sierra:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='sierra-tent-red', + products = { + presets.special.red.infantry:extend({ name='sierra-defense-red'}), + presets.defenses.red.infantry:extend({ name='sierra-garrison-red'}) + } + }), + presets.upgrades.supply.fuelTankFarp:extend({ + name='sierra-fuel-red', + products = { + presets.missions.supply.helo:extend({name='sierra-supply-red'}), + presets.missions.supply.helo:extend({name='sierra-supply-red-1'}), + presets.missions.supply.transfer:extend({name='sierra-transfer-red'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='sierra-comcenter-red', + products = { + presets.defenses.red.shorad:extend({name='sierra-sam-red'}), + presets.missions.attack.helo:extend({name='sierra-cas-red', altitude=200, expend=AI.Task.WeaponExpend.HALF }) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='sierra-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='sierra-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='sierra-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelTankFarp:extend({ + name='sierra-fuel-blue', + products = { + presets.missions.supply.helo:extend({name='sierra-supply-blue'}), + presets.missions.supply.helo:extend({name='sierra-supply-blue-1'}), + presets.missions.supply.transfer:extend({name='sierra-transfer-blue'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='sierra-comcenter-blue', + products = { + presets.defenses.blue.shorad:extend({name='sierra-sam-blue'}), + presets.missions.attack.helo:extend({name='sierra-cas-blue', altitude=200, expend=AI.Task.WeaponExpend.HALF }) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Sierra.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Oni.lua ]]----------------- + +zones.oni = ZoneCommand:new("Oni") +zones.oni.initialState = { side=1 } +zones.oni:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='oni-tent-red', + products = { + presets.special.red.infantry:extend({ name='oni-defense-red'}), + presets.defenses.red.infantry:extend({ name='oni-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='oni-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='oni-supply-red'}), + presets.missions.supply.helo:extend({name='oni-supply-red-2'}), + presets.missions.supply.transfer:extend({name='oni-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='oni-ammo-red', + products = { + presets.missions.attack.surface:extend({name='oni-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='oni-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='oni-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='oni-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='oni-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='oni-supply-blue'}), + presets.missions.supply.helo:extend({name='oni-supply-blue-2'}), + presets.missions.supply.transfer:extend({name='oni-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='oni-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='oni-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Oni.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Hotel.lua ]]----------------- + +zones.hotel = ZoneCommand:new("Hotel") +zones.hotel.initialState = nil +zones.hotel:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='hotel-tent-red', + products = { + presets.special.red.infantry:extend({ name='hotel-defense-red'}), + presets.defenses.red.infantry:extend({ name='hotel-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='hotel-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='hotel-supply-red'}), + presets.missions.supply.transfer:extend({name='hotel-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='hotel-ammo-red', + products = { + presets.missions.attack.surface:extend({name='hotel-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='hotel-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='hotel-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='hotel-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='hotel-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='hotel-supply-blue'}), + presets.missions.supply.transfer:extend({name='hotel-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='hotel-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='hotel-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Hotel.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Victor.lua ]]----------------- + +zones.victor = ZoneCommand:new("Victor") +zones.victor.initialState = { side=1 } +zones.victor:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='victor-tent-red', + products = { + presets.special.red.infantry:extend({ name='victor-defense-red'}), + presets.defenses.red.infantry:extend({ name='victor-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='victor-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='victor-supply-red'}), + presets.missions.supply.helo:extend({name='victor-supply-red-2'}), + presets.missions.supply.transfer:extend({name='victor-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='victor-ammo-red', + products = { + presets.missions.attack.surface:extend({name='victor-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='victor-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='victor-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='victor-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='victor-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='victor-supply-blue'}), + presets.missions.supply.helo:extend({name='victor-supply-blue-2'}), + presets.missions.supply.transfer:extend({name='victor-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='victor-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='victor-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Victor.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Tango.lua ]]----------------- + +zones.tango = ZoneCommand:new("Tango") +zones.tango.initialState = { side=1 } +zones.tango.isHeloSpawn = true +zones.tango:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='tango-tent-red', + products = { + presets.special.red.infantry:extend({ name='tango-defense-red'}), + presets.defenses.red.infantry:extend({ name='tango-garrison-red'}) + } + }), + presets.upgrades.supply.fuelTankFarp:extend({ + name='tango-fuel-red', + products = { + presets.missions.supply.helo:extend({name='tango-supply-red'}), + presets.missions.supply.helo:extend({name='tango-supply-red-1'}), + presets.missions.supply.transfer:extend({name='tango-transfer-red'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='tango-comcenter-red', + products = { + presets.defenses.red.shorad:extend({name='tango-sam-red'}), + presets.missions.attack.helo:extend({name='tango-cas-red', altitude=200, expend=AI.Task.WeaponExpend.HALF }) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='tango-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='tango-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='tango-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelTankFarp:extend({ + name='tango-fuel-blue', + products = { + presets.missions.supply.helo:extend({name='tango-supply-blue'}), + presets.missions.supply.helo:extend({name='tango-supply-blue-1'}), + presets.missions.supply.transfer:extend({name='tango-transfer-blue'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='tango-comcenter-blue', + products = { + presets.defenses.blue.shorad:extend({name='tango-sam-blue'}), + presets.missions.attack.helo:extend({name='tango-cas-blue', altitude=200, expend=AI.Task.WeaponExpend.HALF }) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Tango.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Unal.lua ]]----------------- + +zones.unal = ZoneCommand:new("Unal") +zones.unal.initialState = { side=1 } +zones.unal.isHeloSpawn = true +zones.unal:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='unal-tent-red', + products = { + presets.special.red.infantry:extend({ name='unal-defense-red'}), + presets.defenses.red.infantry:extend({ name='unal-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='unal-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='unal-supply-red'}), + presets.missions.supply.helo:extend({name='unal-supply-red-2'}), + presets.missions.supply.transfer:extend({name='unal-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='unal-ammo-red', + products = { + presets.missions.attack.surface:extend({name='unal-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='unal-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='unal-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='unal-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='unal-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='unal-supply-blue'}), + presets.missions.supply.helo:extend({name='unal-supply-blue-2'}), + presets.missions.supply.transfer:extend({name='unal-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='unal-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='unal-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Unal.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Beslan.lua ]]----------------- + +zones.beslan = ZoneCommand:new("Beslan") +zones.beslan.initialState = { side=1 } +zones.beslan.keepActive = true +zones.beslan.maxResource = 50000 +zones.beslan:defineUpgrades({ + [1] = + { + presets.upgrades.basic.comPost:extend({ + name='beslan-compost-red', + products = { + presets.special.red.infantry:extend({ name='beslan-defense-red'}), + presets.defenses.red.infantry:extend({ name='beslan-garrison-red'}) + } + }), + presets.upgrades.supply.fuelTank:extend({ + name='beslan-fuel-red', + products = { + presets.missions.supply.helo:extend({name='beslan-supply-red-1'}), + presets.missions.supply.helo:extend({name='beslan-supply-red-2'}), + presets.missions.supply.convoy_escorted:extend({name='beslan-supply-red-3'}), + presets.missions.supply.transfer:extend({name='beslan-transfer-red'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='beslan-comcenter-red', + products = { + presets.defenses.red.sa5:extend({ name='beslan-airdef-red'}), + presets.missions.attack.sead:extend({name='beslan-sead-red', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.attack.strike:extend({name='beslan-strike-red', altitude=30000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.attack.strike:extend({name='beslan-strike-red-1', altitude=30000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.patrol.aircraft:extend({name='beslan-patrol-red', altitude=25000, range=25}) + } + }) + }, + [2] = + { + presets.upgrades.basic.comPost:extend({ + name='beslan-compost-blue', + products = { + presets.special.blue.infantry:extend({ name='beslan-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='beslan-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelTank:extend({ + name='beslan-fuel-blue', + products = { + presets.missions.supply.helo:extend({name='beslan-supply-blue-1'}), + presets.missions.supply.helo:extend({name='beslan-supply-blue-2'}), + presets.missions.supply.convoy_escorted:extend({name='beslan-supply-blue-3'}), + presets.missions.supply.transfer:extend({name='beslan-transfer-blue'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='beslan-comcenter-blue', + products = { + presets.defenses.blue.patriot:extend({ name='beslan-airdef-blue'}), + presets.missions.attack.sead:extend({name='beslan-sead-blue', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.attack.strike:extend({name='beslan-strike-blue', altitude=30000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.attack.strike:extend({name='beslan-strike-blue-1', altitude=30000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.patrol.aircraft:extend({name='beslan-patrol-blue', altitude=25000, range=25}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Beslan.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Bravo.lua ]]----------------- + +zones.bravo = ZoneCommand:new("Bravo") +zones.bravo.initialState = { side=2 } +zones.bravo:defineUpgrades({ + [1] = + { + presets.upgrades.basic.comPost:extend({ + name='bravo-compost-red', + products = { + presets.special.red.infantry:extend({ name='bravo-defense-red'}), + presets.defenses.red.infantry:extend({ name='bravo-garrison-red'}) + } + }), + presets.upgrades.supply.fuelTankFarp:extend({ + name='bravo-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({ name='bravo-supply-red'}), + presets.missions.supply.transfer:extend({name='bravo-transfer-red'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='bravo-comcenter-red', + products = { + presets.defenses.red.shorad:extend({ name='bravo-airdef-red'}), + presets.missions.attack.helo:extend({name='bravo-attack-red', altitude=200, expend=AI.Task.WeaponExpend.HALF}) + } + }) + }, + [2] = + { + presets.upgrades.basic.comPost:extend({ + name='bravo-compost-blue', + products = { + presets.special.blue.infantry:extend({ name='bravo-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='bravo-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelTankFarp:extend({ + name='bravo-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({ name='bravo-supply-blue'}), + presets.missions.supply.transfer:extend({name='bravo-transfer-blue'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='bravo-comcenter-blue', + products = { + presets.defenses.blue.shorad:extend({ name='bravo-airdef-blue'}), + presets.missions.attack.helo:extend({name='bravo-attack-blue', altitude=200, expend=AI.Task.WeaponExpend.HALF }) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Bravo.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/WeaponDepot.lua ]]----------------- + +zones.weapondepot = ZoneCommand:new("Weapon Depot") +zones.weapondepot.initialState = { side=1 } +zones.weapondepot:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='weapons-tent-red', + products = { + presets.special.red.infantry:extend({ name='weapons-defense-red'}), + presets.defenses.red.infantry:extend({ name='weapons-garrison-red'}) + } + }), + presets.upgrades.supply.ammoDepot:extend({ + name='weapons-ammo-red-1', + products = { + presets.missions.supply.convoy:extend({name='weapons-supply-red-1'}), + presets.missions.supply.transfer:extend({name='weapons-transfer-red-1'}) + } + }), + presets.upgrades.supply.ammoDepot:extend({ + name='weapons-ammo-red-2', + products = { + presets.missions.supply.convoy:extend({name='weapons-supply-red-2'}), + presets.missions.supply.transfer:extend({name='weapons-transfer-red-2'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='weapons-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='weapons-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='weapons-garrison-blue'}) + } + }), + presets.upgrades.supply.ammoDepot:extend({ + name='weapons-ammo-blue-1', + products = { + presets.missions.supply.convoy:extend({name='weapons-supply-blue-1'}), + presets.missions.supply.transfer:extend({name='weapons-transfer-blue-1'}) + } + }), + presets.upgrades.supply.ammoDepot:extend({ + name='weapons-ammo-blue-2', + products = { + presets.missions.supply.convoy:extend({name='weapons-supply-blue-2'}), + presets.missions.supply.transfer:extend({name='weapons-transfer-blue-2'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/WeaponDepot.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Delta.lua ]]----------------- + +zones.delta = ZoneCommand:new("Delta") +zones.delta.initialState = { side=2 } +zones.delta:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='delta-tent-red', + products = { + presets.special.red.infantry:extend({ name='delta-defense-red'}), + presets.defenses.red.infantry:extend({ name='delta-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='delta-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='delta-supply-red'}), + presets.missions.supply.transfer:extend({name='delta-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='delta-ammo-red', + products = { + presets.missions.attack.surface:extend({name='delta-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='delta-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='delta-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='delta-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='delta-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='delta-supply-blue'}), + presets.missions.supply.transfer:extend({name='delta-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='delta-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='delta-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Delta.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Cherkessk.lua ]]----------------- + +zones.cherkessk = ZoneCommand:new("Cherkessk") +zones.cherkessk.initialState = { side=1 } +zones.cherkessk.isHeloSpawn = true +zones.cherkessk:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='cherkessk-tent-red', + products = { + presets.special.red.infantry:extend({ name='cherkessk-defense-red'}), + presets.defenses.red.infantry:extend({ name='cherkessk-garrison-red'}) + } + }), + presets.upgrades.supply.fuelTankFarp:extend({ + name='cherkessk-fuel-red', + products = { + presets.missions.supply.helo:extend({name='cherkessk-supply-red'}), + presets.missions.supply.helo:extend({name='cherkessk-supply-red-1'}), + presets.missions.supply.transfer:extend({name='cherkessk-transfer-red'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='cherkessk-comcenter-red', + products = { + presets.defenses.red.shorad:extend({name='cherkessk-sam-red'}), + presets.missions.attack.helo:extend({name='cherkessk-cas-red', altitude=200, expend=AI.Task.WeaponExpend.HALF }), + presets.missions.attack.helo:extend({name='cherkessk-cas-red-1', altitude=200, expend=AI.Task.WeaponExpend.HALF }), + presets.missions.attack.surface:extend({name='cherkessk-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='cherkessk-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='cherkessk-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='cherkessk-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelTankFarp:extend({ + name='cherkessk-fuel-blue', + products = { + presets.missions.supply.helo:extend({name='cherkessk-supply-blue'}), + presets.missions.supply.helo:extend({name='cherkessk-supply-blue-1'}), + presets.missions.supply.transfer:extend({name='cherkessk-transfer-blue'}), + presets.missions.attack.surface:extend({name='cherkessk-assault-blue'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='cherkessk-comcenter-blue', + products = { + presets.defenses.blue.shorad:extend({name='cherkessk-sam-blue'}), + presets.missions.attack.helo:extend({name='cherkessk-cas-blue', altitude=200, expend=AI.Task.WeaponExpend.HALF }), + presets.missions.attack.helo:extend({name='cherkessk-cas-blue-1', altitude=200, expend=AI.Task.WeaponExpend.HALF }) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Cherkessk.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Juliett.lua ]]----------------- + +zones.juliett = ZoneCommand:new("Juliett") +zones.initialState = nil +zones.juliett:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='juliett-tent-red', + products = { + presets.special.red.infantry:extend({ name='juliett-defense-red'}), + presets.defenses.red.infantry:extend({ name='juliett-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='juliett-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='juliett-supply-red'}), + presets.missions.supply.transfer:extend({name='juliett-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='juliett-ammo-red', + products = { + presets.missions.attack.surface:extend({name='juliett-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='juliett-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='juliett-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='juliett-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='juliett-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='juliett-supply-blue'}), + presets.missions.supply.transfer:extend({name='juliett-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='juliett-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='juliett-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Juliett.lua ]]----------------- + + + + cm = ConnectionManager:new() + cm:addConnection('Batumi', 'Alpha') + cm:addConnection('Alpha', 'Bravo') + cm:addConnection('Bravo', 'Kobuleti') + cm:addConnection('Bravo', 'Factory') + cm:addConnection('Kobuleti', 'Factory') + cm:addConnection('Kobuleti', 'Charlie') + cm:addConnection('Foxtrot', 'Charlie') + cm:addConnection('Foxtrot', 'Kobuleti') + cm:addConnection('Delta','Foxtrot') + cm:addConnection('Delta','Kobuleti') + cm:addConnection('Delta','Factory') + cm:addConnection('Echo','Charlie') + cm:addConnection('Golf','Echo') + cm:addConnection('Golf','Foxtrot') + cm:addConnection('India','Delta') + cm:addConnection('Hotel','Golf') + cm:addConnection('Hotel','Foxtrot') + cm:addConnection('Hotel','Delta') + cm:addConnection('Hotel','India') + cm:addConnection('Juliett','Echo') + cm:addConnection('Juliett','Golf') + cm:addConnection('Senaki','Juliett') + cm:addConnection('Senaki','Golf') + cm:addConnection('Senaki','Hotel') + cm:addConnection('Kutaisi','Hotel') + cm:addConnection('Kutaisi','India') + cm:addConnection('Kilo','Juliett') + cm:addConnection('Mike','Kutaisi') + cm:addConnection('Mike','Senaki') + cm:addConnection('Romeo','Mike') + cm:addConnection('Romeo','Kutaisi') + cm:addConnection('Weapon Depot','Juliett') + cm:addConnection('Weapon Depot','Senaki') + cm:addConnection('Weapon Depot','Kilo') + cm:addConnection('November','Weapon Depot') + cm:addConnection('November','Senaki') + cm:addConnection('November','Mike') + cm:addConnection('Oil Fields','Romeo') + cm:addConnection('Quebec','Kilo') + cm:addConnection('Zugdidi','Weapon Depot') + cm:addConnection('Zugdidi','Quebec') + cm:addConnection('Zugdidi','November') + cm:addConnection('Zugdidi','Kilo') + cm:addConnection('Distillery','November') + cm:addConnection('Distillery','Mike') + cm:addConnection('Zugdidi','Papa') + cm:addConnection('November','Papa') + cm:addConnection('Sierra','Papa') + cm:addConnection('Sierra','Zugdidi') + cm:addConnection('Sierra','Uniform') + cm:addConnection('Mine','Uniform') + cm:addConnection('Tango','Quebec') + cm:addConnection('Tango','Zugdidi') + cm:addConnection('Sierra','Tango') + cm:addConnection('Whiskey','Tango') + cm:addConnection('Ochamchira','Tango') + cm:addConnection('Ochamchira','Whiskey') + cm:addConnection('Ochamchira','Farm') + cm:addConnection('Ochamchira','Zulu') + cm:addConnection('Farm','Zulu') + cm:addConnection('Sukhumi','Zulu') + cm:addConnection('Lentehi','Distillery', true, 3000) + cm:addConnection('Lentehi','Babugent', true, 5000) + cm:addConnection('Nalchik','Babugent') + cm:addConnection('Victor','Distillery', true, 2000) + cm:addConnection('Victor','Romeo') + cm:addConnection('Victor','Lentehi') + cm:addConnection('Victor','Oil Fields', true, 2000) + cm:addConnection('Victor','Oni') + cm:addConnection('Unal','Oni', true, 4500) + cm:addConnection('Beslan','Unal') + cm:addConnection('Digora','Beslan') + cm:addConnection('Digora','Unal') + cm:addConnection('Digora','Babugent') + cm:addConnection('Terek','Digora') + cm:addConnection('Terek','Nalchik') + cm:addConnection('Terek','Beslan') + cm:addConnection('Prohladniy','Terek') + cm:addConnection('Prohladniy','Nalchik') + cm:addConnection('Malgobek','Terek') + cm:addConnection('Malgobek','Beslan') + cm:addConnection('Lima','Mine') + cm:addConnection('Lima','Lentehi', true, 4000) + cm:addConnection('Tyrnyauz','Lima', true, 4000) + cm:addConnection('Tyrnyauz','Nalchik') + cm:addConnection('XRay','Sukhumi') + cm:addConnection('Oscar','Sukhumi') + cm:addConnection('Oscar','XRay') + cm:addConnection('Mozdok','Malgobek') + cm:addConnection('Mozdok','Prohladniy') + cm:addConnection('Gudauta','Oscar') + cm:addConnection('Yankee','Gudauta') + cm:addConnection('Sochi','Yankee') + cm:addConnection('Refinery','XRay', true, 4000) + cm:addConnection('Refinery','Humara') + cm:addConnection('Intel Center','Tyrnyauz') + cm:addConnection('Intel Center','Nalchik') + cm:addConnection('Intel Center','Prohladniy') + cm:addConnection('Intel Center','Kislovodsk') + cm:addConnection('Mineralnye','Intel Center') + cm:addConnection('Kislovodsk','Mineralnye') + cm:addConnection('Tallyk','Mineralnye') + cm:addConnection('Tallyk','Kislovodsk') + cm:addConnection('Power Plant','Mineralnye') + cm:addConnection('Power Plant','Tallyk') + cm:addConnection('Cherkessk','Tallyk') + cm:addConnection('Cherkessk','Power Plant') + cm:addConnection('Cherkessk','Humara') +end + +ZoneCommand.setNeighbours(cm) + +bm = BattlefieldManager:new() + +mc = MarkerCommands:new() + +pt = PlayerTracker:new(mc) + +mt = MissionTracker:new(pt, mc) + +st = SquadTracker:new() + +ct = CSARTracker:new() + +pl = PlayerLogistics:new(mt, pt, st, ct) + +gci = GCI:new(2) + +gm = GroupMonitor:new(cm) +ZoneCommand.groupMonitor = gm + +-- PlayerLogistics:registerSquadGroup(squadType, groupname, weight,cost,jobtime,extracttime, squadSize) +pl:registerSquadGroup(PlayerLogistics.infantryTypes.capture, 'capture-squad', 700, 200, 60, 60*30, 4) +pl:registerSquadGroup(PlayerLogistics.infantryTypes.sabotage, 'sabotage-squad', 800, 500, 60*5, 60*30, 4) +pl:registerSquadGroup(PlayerLogistics.infantryTypes.ambush, 'ambush-squad', 900, 300, 60*20, 60*30, 5) +pl:registerSquadGroup(PlayerLogistics.infantryTypes.engineer, 'engineer-squad', 200, 1000,60, 60*30, 2) +pl:registerSquadGroup(PlayerLogistics.infantryTypes.manpads, 'manpads-squad', 900, 500, 60*20, 60*30, 5) +pl:registerSquadGroup(PlayerLogistics.infantryTypes.spy, 'spy-squad', 100, 300, 60*10, 60*30, 1) +pl:registerSquadGroup(PlayerLogistics.infantryTypes.rapier, 'rapier-squad', 1200,2000,60*60, 60*30, 8) + +Group.getByName('jtacDrone'):destroy() +CommandFunctions.jtac = JTAC:new({name = 'jtacDrone'}) + +pm = PersistenceManager:new(savefile, gm, st, ct, pl) +pm:load() + +if pm:canRestore() then + pm:restoreZones() + pm:restoreAIMissions() + pm:restoreBattlefield() + pm:restoreCsar() + pm:restoreSquads() +else + --initial states + Starter.start(zones) +end + +timer.scheduleFunction(function(param, time) + pm:save() + env.info("Mission state saved") + return time+60 +end, zones, timer.getTime()+60) + + +--make sure support units are present where needed +ensureSpawn = { + ['golf-farp-suport'] = zones.golf, + ['november-farp-suport'] = zones.november, + ['tango-farp-suport'] = zones.tango, + ['sierra-farp-suport'] = zones.sierra, + ['cherkessk-farp-suport'] = zones.cherkessk, + ['unal-farp-suport'] = zones.unal, + ['tyrnyauz-farp-suport'] = zones.tyrnyauz +} + +for grname, zn in pairs(ensureSpawn) do + local g = Group.getByName(grname) + if g then g:destroy() end +end + +timer.scheduleFunction(function(param, time) + + for grname, zn in pairs(ensureSpawn) do + local g = Group.getByName(grname) + if zn.side == 2 then + if not g then + local err, msg = pcall(mist.respawnGroup,grname,true) + if not err then + env.info("ERROR spawning "..grname) + env.info(msg) + end + end + else + if g then g:destroy() end + end + end + + return time+30 +end, {}, timer.getTime()+30) + + +--supply injection +local blueSupply = {'offmap-supply-blue-1','offmap-supply-blue-2','offmap-supply-blue-3','offmap-supply-blue-4','offmap-supply-blue-5'} +local redSupply = {'offmap-supply-red-1','offmap-supply-red-2','offmap-supply-red-3','offmap-supply-red-4','offmap-supply-red-5'} +local offmapZones = { + zones.batumi, + zones.sochi, + zones.nalchik, + zones.beslan, + zones.mozdok, + zones.mineralnye, +-- zones.senaki, +-- zones.sukhumi, +-- zones.gudauta, +-- zones.kobuleti, +} + +supplyPointRegistry = { + blue = {}, + red = {} +} + +for i,v in ipairs(blueSupply) do + local g = Group.getByName(v) + if g then + supplyPointRegistry.blue[v] = g:getUnit(1):getPoint() + end +end + +for i,v in ipairs(redSupply) do + local g = Group.getByName(v) + if g then + supplyPointRegistry.red[v] = g:getUnit(1):getPoint() + end +end + +offmapSupplyRegistry = {} +timer.scheduleFunction(function(param, time) + local availableBlue = {} + for i,v in ipairs(param.blue) do + if offmapSupplyRegistry[v] == nil then + table.insert(availableBlue, v) + end + end + + local availableRed = {} + for i,v in ipairs(param.red) do + if offmapSupplyRegistry[v] == nil then + table.insert(availableRed, v) + end + end + + local redtargets = {} + local bluetargets = {} + for _, zn in ipairs(param.offmapZones) do + if zn:needsSupplies(3000) then + local isOnRoute = false + for _,data in pairs(offmapSupplyRegistry) do + if data.zone.name == zn.name then + isOnRoute = true + break + end + end + if not isOnRoute then + if zn.side == 1 then + table.insert(redtargets, zn) + elseif zn.side == 2 then + table.insert(bluetargets, zn) + end + end + end + end + + if #availableRed > 0 and #redtargets > 0 then + local zn = redtargets[math.random(1,#redtargets)] + + local red = nil + local minD = 999999999 + for i,v in ipairs(availableRed) do + local d = mist.utils.get2DDist(zn.zone.point, supplyPointRegistry.red[v]) + if d < minD then + red = v + minD = d + end + end + + if not red then red = availableRed[math.random(1,#availableRed)] end + + local gr = red + red = nil + mist.respawnGroup(gr, true) + offmapSupplyRegistry[gr] = {zone = zn, assigned = timer.getAbsTime()} + env.info(gr..' was deployed') + timer.scheduleFunction(function(param,time) + local g = Group.getByName(param.group) + TaskExtensions.landAtAirfield(g, param.target.zone.point) + env.info(param.group..' going to '..param.target.name) + end, {group=gr, target=zn}, timer.getTime()+2) + end + + if #availableBlue > 0 and #bluetargets>0 then + local zn = bluetargets[math.random(1,#bluetargets)] + + local blue = nil + local minD = 999999999 + for i,v in ipairs(availableBlue) do + local d = mist.utils.get2DDist(zn.zone.point, supplyPointRegistry.blue[v]) + if d < minD then + blue = v + minD = d + end + end + + if not blue then blue = availableBlue[math.random(1,#availableBlue)] end + + local gr = blue + blue = nil + mist.respawnGroup(gr, true) + offmapSupplyRegistry[gr] = {zone = zn, assigned = timer.getAbsTime()} + env.info(gr..' was deployed') + timer.scheduleFunction(function(param,time) + local g = Group.getByName(param.group) + TaskExtensions.landAtAirfield(g, param.target.zone.point) + env.info(param.group..' going to '..param.target.name) + end, {group=gr, target=zn}, timer.getTime()+2) + end + + return time+(60*5) +end, {blue = blueSupply, red = redSupply, offmapZones = offmapZones}, timer.getTime()+60) + + + +timer.scheduleFunction(function(param, time) + + for groupname,data in pairs(offmapSupplyRegistry) do + local gr = Group.getByName(groupname) + if not gr then + offmapSupplyRegistry[groupname] = nil + env.info(groupname..' was destroyed') + end + + if gr and ((timer.getAbsTime() - data.assigned) > (60*60)) then + gr:destroy() + offmapSupplyRegistry[groupname] = nil + env.info(groupname..' despawned due to being alive for too long') + end + + if gr and Utils.allGroupIsLanded(gr) and Utils.someOfGroupInZone(gr, data.zone.name) then + data.zone:addResource(15000) + gr:destroy() + offmapSupplyRegistry[groupname] = nil + env.info(groupname..' landed at '..data.zone.name..' and delivered 15000 resources') + end + end + + return time+180 +end, {}, timer.getTime()+180) \ No newline at end of file From f500ec916e821384e30e6a97f376d97a504d6d7e Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Sun, 10 Sep 2023 16:18:22 +0300 Subject: [PATCH 012/243] Split init.lua code into three pieces. --- resources/plugins/pretense/init_body.lua | 4564 -------------------- resources/plugins/pretense/init_footer.lua | 4530 ------------------- resources/plugins/pretense/init_header.lua | 4050 +---------------- 3 files changed, 1 insertion(+), 13143 deletions(-) diff --git a/resources/plugins/pretense/init_body.lua b/resources/plugins/pretense/init_body.lua index 9df8452d..a4452456 100644 --- a/resources/plugins/pretense/init_body.lua +++ b/resources/plugins/pretense/init_body.lua @@ -1,4426 +1,3 @@ - - -local savefile = 'pretense_1.1.json' -if lfs then - local dir = lfs.writedir()..'Missions/Saves/' - lfs.mkdir(dir) - savefile = dir..savefile - env.info('Pretense - Save file path: '..savefile) -end - - -do - TemplateDB.templates["infantry-red"] = { - units = { - "BTR_D", - "T-90", - "T-90", - "Infantry AK ver2", - "Infantry AK", - "Infantry AK", - "Paratrooper RPG-16", - "Infantry AK ver3", - "SA-18 Igla manpad" - }, - skill = "Excellent", - dataCategory= TemplateDB.type.group - } - - TemplateDB.templates["infantry-blue"] = { - units = { - "M1045 HMMWV TOW", - "Soldier stinger", - "Soldier M4 GRG", - "Soldier M4 GRG", - "Soldier M4 GRG", - "Soldier M4 GRG", - "Soldier M4 GRG", - "M1043 HMMWV Armament" - }, - skill = "Excellent", - dataCategory= TemplateDB.type.group - } - - TemplateDB.templates["defense-red"] = { - units = { - "Infantry AK ver2", - "Infantry AK", - "Infantry AK ver3", - "Paratrooper RPG-16", - "SA-18 Igla manpad" - }, - skill = "Excellent", - dataCategory= TemplateDB.type.group - } - - TemplateDB.templates["defense-blue"] = { - units = { - "Soldier M4 GRG", - "Soldier M4 GRG", - "Soldier M4 GRG", - "Soldier RPG", - "Soldier stinger", - }, - skill = "Excellent", - dataCategory= TemplateDB.type.group - } - - TemplateDB.templates["shorad-red"] = { - units = { - "Strela-10M3", - "Strela-10M3", - "Ural-4320T", - "2S6 Tunguska" - }, - maxDist = 300, - skill = "Excellent", - dataCategory= TemplateDB.type.group - } - - TemplateDB.templates["shorad-blue"] = { - units = { - "Roland ADS", - "M48 Chaparral", - "M 818", - "Gepard", - "Gepard" - }, - maxDist = 300, - skill = "Excellent", - dataCategory= TemplateDB.type.group - } - - TemplateDB.templates["sam-red"] = { - units = { - "p-19 s-125 sr", - "Ural-4320T", - "Ural-4320T", - "S_75M_Volhov", - "S_75M_Volhov", - "S_75M_Volhov", - "S_75M_Volhov", - "S_75M_Volhov", - "Tor 9A331", - "SNR_75V" - }, - maxDist = 300, - skill = "Excellent", - dataCategory= TemplateDB.type.group - } - - TemplateDB.templates["sam-blue"] = { - units = { - "Hawk pcp", - "Hawk cwar", - "Hawk ln", - "Hawk ln", - "Hawk ln", - "Hawk ln", - "Hawk ln", - "Hawk tr", - "M 818", - "Hawk sr" - }, - maxDist = 300, - skill = "Good", - dataCategory= TemplateDB.type.group - } - - TemplateDB.templates["patriot"] = { - units = { - "Patriot cp", - "Patriot str", - "M 818", - "M 818", - "Patriot ln", - "Patriot ln", - "Patriot ln", - "Patriot ln", - "Patriot str", - "Patriot str", - "Patriot str", - "Patriot EPP", - "Patriot ECS", - "Patriot AMG" - }, - maxDist = 300, - skill = "Good", - dataCategory= TemplateDB.type.group - } - - TemplateDB.templates["sa3"] = { - units = { - "p-19 s-125 sr", - "snr s-125 tr", - "5p73 s-125 ln", - "5p73 s-125 ln", - "Ural-4320T", - "5p73 s-125 ln", - "5p73 s-125 ln" - }, - maxDist = 300, - skill = "Excellent", - dataCategory= TemplateDB.type.group - } - - TemplateDB.templates["sa6"] = { - units = { - "Kub 1S91 str", - "Kub 2P25 ln", - "Kub 2P25 ln", - "Kub 2P25 ln", - "Kub 2P25 ln", - "2S6 Tunguska", - "Ural-4320T", - "2S6 Tunguska", - "Kub 2P25 ln" - }, - maxDist = 300, - skill = "Excellent", - dataCategory= TemplateDB.type.group - } - - TemplateDB.templates["sa10"] = { - units = { - "S-300PS 54K6 cp", - "S-300PS 5P85C ln", - "S-300PS 5P85C ln", - "S-300PS 5P85C ln", - "GAZ-66", - "GAZ-66", - "GAZ-66", - "S-300PS 5P85C ln", - "S-300PS 5P85C ln", - "S-300PS 5P85C ln", - "S-300PS 40B6MD sr", - "S-300PS 40B6M tr", - "S-300PS 64H6E sr" - }, - maxDist = 300, - skill = "Excellent", - dataCategory= TemplateDB.type.group - } - - TemplateDB.templates["sa5"] = { - units = { - "RLS_19J6", - "Ural-4320T", - "Ural-4320T", - "RPC_5N62V", - "S-200_Launcher", - "S-200_Launcher", - "S-200_Launcher", - "S-200_Launcher", - "S-200_Launcher", - "S-200_Launcher" - }, - maxDist = 300, - skill = "Excellent", - dataCategory= TemplateDB.type.group - } - - TemplateDB.templates["sa11"] = { - units = { - "SA-11 Buk SR 9S18M1", - "SA-11 Buk LN 9A310M1", - "SA-11 Buk LN 9A310M1", - "SA-11 Buk LN 9A310M1", - "SA-11 Buk LN 9A310M1", - "SA-11 Buk LN 9A310M1", - "2S6 Tunguska", - "SA-11 Buk SR 9S18M1", - "GAZ-66", - "GAZ-66", - "SA-11 Buk CC 9S470M1" - }, - maxDist = 300, - skill = "Excellent", - dataCategory= TemplateDB.type.group - } - - TemplateDB.templates["nasams"] = { - units = { - "NASAMS_Command_Post", - "NASAMS_Radar_MPQ64F1", - "Vulcan", - "M 818", - "M 818", - "Roland ADS", - "Roland ADS", - "NASAMS_LN_C", - "NASAMS_LN_C", - "NASAMS_LN_C", - "NASAMS_LN_C", - "NASAMS_Radar_MPQ64F1", - "NASAMS_Radar_MPQ64F1", - "NASAMS_Radar_MPQ64F1" - }, - maxDist = 300, - skill = "Excellent", - dataCategory= TemplateDB.type.group - } -end - -presets = { - upgrades = { - basic = { - tent = Preset:new({ - display = 'Tent', - cost = 1500, - type = 'upgrade', - template = "tent" - }), - comPost = Preset:new({ - display = 'Barracks', - cost = 1500, - type = 'upgrade', - template = "barracks" - }), - outpost = Preset:new({ - display = 'Outpost', - cost = 1500, - type = 'upgrade', - template = "outpost" - }) - }, - attack = { - ammoCache = Preset:new({ - display = 'Ammo Cache', - cost = 1500, - type = 'upgrade', - template = "ammo-cache" - }), - ammoDepot = Preset:new({ - display = 'Ammo Depot', - cost = 2000, - type = 'upgrade', - template = "ammo-depot" - }) - }, - supply = { - fuelCache = Preset:new({ - display = 'Fuel Cache', - cost = 1500, - type = 'upgrade', - template = "fuel-cache" - }), - fuelTank = Preset:new({ - display = 'Fuel Tank', - cost = 1500, - type = 'upgrade', - template = "fuel-tank-big" - }), - fuelTankFarp = Preset:new({ - display = 'Fuel Tank', - cost = 1500, - type = 'upgrade', - template = "fuel-tank-small" - }), - factory1 = Preset:new({ - display='Factory', - cost = 2000, - type ='upgrade', - income = 20, - template = "factory-1" - }), - factory2 = Preset:new({ - display='Factory', - cost = 2000, - type ='upgrade', - income = 20, - template = "factory-2" - }), - factoryTank = Preset:new({ - display='Storage Tank', - cost = 1500, - type ='upgrade', - income = 10, - template = "chem-tank" - }), - ammoDepot = Preset:new({ - display = 'Ammo Depot', - cost = 2000, - type = 'upgrade', - income = 40, - template = "ammo-depot" - }), - oilPump = Preset:new({ - display = 'Oil Pump', - cost = 1500, - type = 'upgrade', - income = 20, - template = "oil-pump" - }), - hangar = Preset:new({ - display = 'Hangar', - cost = 2000, - type = 'upgrade', - income = 30, - template = "hangar" - }), - excavator = Preset:new({ - display = 'Excavator', - cost = 2000, - type = 'upgrade', - income = 20, - template = "excavator" - }), - farm1 = Preset:new({ - display = 'Farm House', - cost = 2000, - type = 'upgrade', - income = 40, - template = "farm-house-1" - }), - farm2 = Preset:new({ - display = 'Farm House', - cost = 2000, - type = 'upgrade', - income = 40, - template = "farm-house-2" - }), - refinery1 = Preset:new({ - display='Refinery', - cost = 2000, - type ='upgrade', - income = 100, - template = "factory-1" - }), - powerplant1 = Preset:new({ - display='Power Plant', - cost = 1500, - type ='upgrade', - income = 25, - template = "factory-1" - }), - powerplant2 = Preset:new({ - display='Power Plant', - cost = 1500, - type ='upgrade', - income = 25, - template = "factory-2" - }), - antenna = Preset:new({ - display='Antenna', - cost = 1000, - type ='upgrade', - income = 10, - template = "antenna" - }), - hq = Preset:new({ - display='HQ Building', - cost = 2000, - type ='upgrade', - income = 50, - template = "tv-tower" - }) - }, - airdef = { - comCenter = Preset:new({ - display = 'Command Center', - cost = 2500, - type = 'upgrade', - template = "command-center" - }) - } - }, - defenses = { - red = { - infantry = Preset:new({ - display = 'Infantry', - cost=2000, - type='defense', - template='infantry-red', - }), - shorad = Preset:new({ - display = 'SAM', - cost=2500, - type='defense', - template='shorad-red', - }), - sam = Preset:new({ - display = 'SAM', - cost=3000, - type='defense', - template='sam-red', - }), - sa10 = Preset:new({ - display = 'SAM', - cost=3000, - type='defense', - template='sa10', - }), - sa5 = Preset:new({ - display = 'SAM', - cost=3000, - type='defense', - template='sa5', - }), - sa3 = Preset:new({ - display = 'SAM', - cost=3000, - type='defense', - template='sa3', - }), - sa6 = Preset:new({ - display = 'SAM', - cost=3000, - type='defense', - template='sa6', - }), - sa11 = Preset:new({ - display = 'SAM', - cost=3000, - type='defense', - template='sa11', - }) - }, - blue = { - infantry = Preset:new({ - display = 'Infantry', - cost=2000, - type='defense', - template='infantry-blue', - }), - shorad = Preset:new({ - display = 'SAM', - cost=2500, - type='defense', - template='shorad-blue', - }), - sam = Preset:new({ - display = 'SAM', - cost=3000, - type='defense', - template='sam-blue', - }), - patriot = Preset:new({ - display = 'SAM', - cost=3000, - type='defense', - template='patriot', - }), - nasams = Preset:new({ - display = 'SAM', - cost=3000, - type='defense', - template='nasams', - }) - } - }, - missions = { - supply = { - convoy = Preset:new({ - display = 'Supply convoy', - cost = 4000, - type = 'mission', - missionType = ZoneCommand.missionTypes.supply_convoy - }), - convoy_escorted = Preset:new({ - display = 'Supply convoy', - cost = 3000, - type = 'mission', - missionType = ZoneCommand.missionTypes.supply_convoy - }), - helo = Preset:new({ - display = 'Supply helicopter', - cost = 2500, - type='mission', - missionType = ZoneCommand.missionTypes.supply_air - }), - transfer = Preset:new({ - display = 'Supply transfer', - cost = 1000, - type='mission', - missionType = ZoneCommand.missionTypes.supply_transfer - }) - }, - attack = { - surface = Preset:new({ - display = 'Ground assault', - cost = 100, - type = 'mission', - missionType = ZoneCommand.missionTypes.assault, - }), - cas = Preset:new({ - display = 'CAS', - cost = 200, - type='mission', - missionType = ZoneCommand.missionTypes.cas - }), - bai = Preset:new({ - display = 'BAI', - cost = 200, - type='mission', - missionType = ZoneCommand.missionTypes.bai - }), - strike = Preset:new({ - display = 'Strike', - cost = 300, - type='mission', - missionType = ZoneCommand.missionTypes.strike - }), - sead = Preset:new({ - display = 'SEAD', - cost = 200, - type='mission', - missionType = ZoneCommand.missionTypes.sead - }), - helo = Preset:new({ - display = 'CAS', - cost = 100, - type='mission', - missionType = ZoneCommand.missionTypes.cas_helo - }) - }, - patrol={ - aircraft = Preset:new({ - display= "Patrol", - cost = 100, - type='mission', - missionType = ZoneCommand.missionTypes.patrol - }) - }, - support ={ - awacs = Preset:new({ - display= "AWACS", - cost = 300, - type='mission', - bias='5', - missionType = ZoneCommand.missionTypes.awacs - }), - tanker = Preset:new({ - display= "Tanker", - cost = 200, - type='mission', - bias='2', - missionType = ZoneCommand.missionTypes.tanker - }) - } - }, - special = { - red = { - infantry = Preset:new({ - display = 'Infantry', - cost=-1, - type='defense', - template='defense-red', - }), - }, - blue = { - infantry = Preset:new({ - display = 'Infantry', - cost=-1, - type='defense', - template='defense-blue', - }) - } - } -} - -zones = {} -do - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Batumi.lua ]]----------------- - -zones.batumi = ZoneCommand:new('Batumi') -zones.batumi.initialState = { side=2 } -zones.batumi.keepActive = true -zones.batumi.isHeloSpawn = true -zones.batumi.isPlaneSpawn = true -zones.batumi.maxResource = 50000 -zones.batumi:defineUpgrades({ - [1] = { --red side - presets.upgrades.basic.comPost:extend({ - name = 'batumi-com-red', - products = { - presets.special.red.infantry:extend({ name='batumi-defense-red'}), - presets.defenses.red.infantry:extend({ name='batumi-garrison-red' }) - } - }), - }, - [2] = --blue side - { - presets.upgrades.basic.comPost:extend({ - name = 'batumi-com-blue', - products = { - presets.special.blue.infantry:extend({ name='batumi-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='batumi-garrison-blue' }) - } - }), - presets.upgrades.supply.fuelTank:extend({ - name = 'batumi-fueltank-blue', - products = { - presets.missions.supply.convoy_escorted:extend({ name='batumi-supply-convoy-1'}), - presets.missions.supply.helo:extend({ name='batumi-supply-blue-1' }), - presets.missions.supply.transfer:extend({name='batumi-transfer-blue'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name = 'batumi-mission-command-blue', - products = { - presets.defenses.blue.shorad:extend({ name='batumi-sam-blue' }), - presets.missions.attack.sead:extend({name='batumi-sead-blue-1', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.attack.cas:extend({name='batumi-cas-blue-1', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.bai:extend({name='batumi-cas-blue-1', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.strike:extend({name='batumi-strike-blue-1', altitude=20000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.patrol.aircraft:extend({name='batumi-patrol-blue-1', altitude=25000, range=25}), - presets.missions.support.awacs:extend({name='batumi-awacs-blue', altitude=30000, freq=257.5}), - presets.missions.support.tanker:extend({name='batumi-tanker-blue', altitude=25000, freq=257, tacan='37', variant="Drogue"}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Batumi.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Mike.lua ]]----------------- - -zones.mike = ZoneCommand:new("Mike") -zones.mike.initialState = { side=1 } -zones.mike.keepActive = true -zones.mike:defineUpgrades({ - [1] = - { - presets.upgrades.basic.tent:extend({ - name='mike-tent-red', - products = { - presets.special.red.infantry:extend({ name='mike-defense-red'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='mike-comcenter-red', - products = { - presets.defenses.red.sam:extend({ name='mike-sam-red'}) - } - }) - }, - [2] = - { - presets.upgrades.basic.tent:extend({ - name='mike-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='mike-defense-blue'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='mike-comcenter-blue', - products = { - presets.defenses.blue.sam:extend({ name='mike-sam-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Mike.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Tyrnyauz.lua ]]----------------- - -zones.tyrnyauz = ZoneCommand:new("Tyrnyauz") -zones.tyrnyauz.initialState = { side=1 } -zones.tyrnyauz.isHeloSpawn = true -zones.tyrnyauz:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='tyrnyauz-tent-red', - products = { - presets.special.red.infantry:extend({ name='tyrnyauz-defense-red'}), - presets.defenses.red.infantry:extend({ name='tyrnyauz-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='tyrnyauz-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='tyrnyauz-supply-red'}), - presets.missions.supply.helo:extend({name='tyrnyauz-supply-red-2'}), - presets.missions.supply.transfer:extend({name='tyrnyauz-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='tyrnyauz-ammo-red', - products = { - presets.missions.attack.surface:extend({name='tyrnyauz-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='tyrnyauz-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='tyrnyauz-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='tyrnyauz-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='tyrnyauz-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='tyrnyauz-supply-blue'}), - presets.missions.supply.helo:extend({name='tyrnyauz-supply-blue-2'}), - presets.missions.supply.transfer:extend({name='tyrnyauz-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='tyrnyauz-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='tyrnyauz-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Tyrnyauz.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/India.lua ]]----------------- - -zones.india = ZoneCommand:new("India") -zones.india.initialState = nil -zones.india:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='india-tent-red', - products = { - presets.special.red.infantry:extend({ name='india-defense-red'}), - presets.defenses.red.infantry:extend({ name='india-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='india-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='india-supply-red'}), - presets.missions.supply.transfer:extend({name='india-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='india-ammo-red', - products = { - presets.missions.attack.surface:extend({name='india-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='india-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='india-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='india-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='india-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='india-supply-blue'}), - presets.missions.supply.transfer:extend({name='india-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='india-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='india-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/India.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/IntelCenter.lua ]]----------------- - -zones.intelcenter = ZoneCommand:new("Intel Center") -zones.intelcenter.initialState = { side=1 } -zones.intelcenter:defineUpgrades({ - [1] = - { - presets.upgrades.basic.tent:extend({ - name='intelcenter-tent-red', - products = { - presets.special.red.infantry:extend({ name='intelcenter-defense-red'}), - presets.defenses.red.infantry:extend({ name='intelcenter-garrison-red'}) - } - }), - presets.upgrades.supply.hq:extend({ - name='intelcenter-hq-red', - products = { - presets.missions.supply.convoy:extend({ name='intelcenter-supply-red'}), - presets.missions.supply.convoy:extend({ name='intelcenter-supply-red-1'}), - presets.missions.supply.transfer:extend({name='intelcenter-transfer-red'}) - } - }), - presets.upgrades.supply.antenna:extend({ - name='intelcenter-antenna-red', - products = { - } - }), - presets.upgrades.supply.antenna:extend({ - name='intelcenter-antenna-red-1', - products = { - } - }), - presets.upgrades.supply.antenna:extend({ - name='intelcenter-antenna-red-2', - products = { - } - }) - }, - [2] = - { - presets.upgrades.basic.tent:extend({ - name='intelcenter-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='intelcenter-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='intelcenter-garrison-blue'}) - } - }), - presets.upgrades.supply.hq:extend({ - name='intelcenter-hq-blue', - products = { - presets.missions.supply.convoy:extend({ name='intelcenter-supply-blue'}), - presets.missions.supply.convoy:extend({ name='intelcenter-supply-blue-1'}), - presets.missions.supply.transfer:extend({name='intelcenter-transfer-blue'}) - } - }), - presets.upgrades.supply.antenna:extend({ - name='intelcenter-antenna-blue', - products = { - } - }), - presets.upgrades.supply.antenna:extend({ - name='intelcenter-antenna-blue-1', - products = { - } - }), - presets.upgrades.supply.antenna:extend({ - name='intelcenter-antenna-blue-2', - products = { - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/IntelCenter.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Mineralnye.lua ]]----------------- - -zones.mineralnye = ZoneCommand:new("Mineralnye") -zones.mineralnye.initialState = { side=1 } -zones.mineralnye.keepActive = true -zones.mineralnye.isHeloSpawn = true -zones.mineralnye.isPlaneSpawn = true -zones.mineralnye:defineUpgrades({ - [1] = - { - presets.upgrades.basic.comPost:extend({ - name='mineralnye-compost-red', - products = { - presets.special.red.infantry:extend({ name='mineralnye-defense-red'}), - presets.defenses.red.infantry:extend({ name='mineralnye-garrison-red'}) - } - }), - presets.upgrades.supply.fuelTank:extend({ - name='mineralnye-fuel-red', - products = { - presets.missions.supply.helo:extend({name='mineralnye-supply-red'}), - presets.missions.supply.helo:extend({name='mineralnye-supply-red-1'}), - presets.missions.supply.transfer:extend({name='mineralnye-transfer-red'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='mineralnye-comcenter-red', - products = { - presets.defenses.red.sa11:extend({ name='mineralnye-airdef-red'}), - presets.missions.attack.cas:extend({name='mineralnye-cas-red', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.bai:extend({name='mineralnye-cas-red', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.strike:extend({name='mineralnye-strike-red', altitude=30000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.attack.strike:extend({name='mineralnye-strike-red-1', altitude=30000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.patrol.aircraft:extend({name='mineralnye-patrol-red', altitude=25000, range=25}) - } - }) - }, - [2] = - { - presets.upgrades.basic.comPost:extend({ - name='mineralnye-compost-blue', - products = { - presets.special.blue.infantry:extend({ name='mineralnye-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='mineralnye-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelTank:extend({ - name='mineralnye-fuel-blue', - products = { - presets.missions.supply.helo:extend({name='mineralnye-supply-blue'}), - presets.missions.supply.helo:extend({name='mineralnye-supply-blue-1'}), - presets.missions.supply.transfer:extend({name='mineralnye-transfer-blue'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='mineralnye-comcenter-blue', - products = { - presets.defenses.blue.nasams:extend({ name='mineralnye-airdef-blue'}), - presets.missions.attack.cas:extend({name='mineralnye-cas-blue', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.bai:extend({name='mineralnye-cas-blue', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.strike:extend({name='mineralnye-strike-blue', altitude=30000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.attack.strike:extend({name='mineralnye-strike-blue-1', altitude=30000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.patrol.aircraft:extend({name='mineralnye-patrol-blue', altitude=25000, range=25}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Mineralnye.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/PowerPlant.lua ]]----------------- - -zones.powerplant = ZoneCommand:new("Power Plant") -zones.powerplant.initialState = { side=1 } -zones.powerplant:defineUpgrades({ - [1] = - { - presets.upgrades.basic.tent:extend({ - name='powerplant-tent-red', - products = { - presets.special.red.infantry:extend({ name='powerplant-defense-red'}), - presets.defenses.red.infantry:extend({ name='powerplant-garrison-red'}) - } - }), - presets.upgrades.supply.powerplant1:extend({ - name='powerplant-building-red-1', - products = { - presets.missions.supply.convoy:extend({ name='powerplant-supply-red'}), - presets.missions.supply.transfer:extend({name='powerplant-transfer-red'}) - } - }), - presets.upgrades.supply.powerplant2:extend({ - name='powerplant-building-red-2', - products = { - presets.missions.supply.convoy:extend({ name='powerplant-supply-red-1'}), - presets.missions.supply.transfer:extend({name='powerplant-transfer-red'}) - } - }) - }, - [2] = - { - presets.upgrades.basic.tent:extend({ - name='powerplant-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='powerplant-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='powerplant-garrison-blue'}) - } - }), - presets.upgrades.supply.powerplant1:extend({ - name='powerplant-building-blue-1', - products = { - presets.missions.supply.convoy:extend({ name='powerplant-supply-blue'}), - presets.missions.supply.transfer:extend({name='powerplant-transfer-blue'}) - } - }), - presets.upgrades.supply.powerplant2:extend({ - name='powerplant-building-blue-2', - products = { - presets.missions.supply.convoy:extend({ name='powerplant-supply-blue-1'}), - presets.missions.supply.transfer:extend({name='powerplant-transfer-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/PowerPlant.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Zugdidi.lua ]]----------------- - -zones.zugdidi = ZoneCommand:new("Zugdidi") -zones.zugdidi.initialState = { side=1 } -zones.zugdidi:defineUpgrades({ - [1] = - { - presets.upgrades.basic.comPost:extend({ - name='zugdidi-compost-red', - products = { - presets.missions.supply.transfer:extend({name='zugdidi-transfer-red'}), - presets.special.red.infantry:extend({ name='zugdidi-defense-red'}), - presets.defenses.red.infantry:extend({ name='zugdidi-garrison-red'}), - presets.missions.attack.surface:extend({name='zugdidi-attack-red'}), - presets.missions.supply.convoy:extend({name='zugdidi-supply-red'}) - } - }), - presets.upgrades.supply.hangar:extend({ - name='zugdidi-hangar-red-1', - products = { - presets.missions.supply.helo:extend({name='zugdidi-supply-red-1'}) - } - }), - presets.upgrades.supply.hangar:extend({ - name='zugdidi-hangar-red-2', - products = { - presets.missions.supply.helo:extend({name='zugdidi-supply-red-2'}) - } - }), - presets.upgrades.supply.hangar:extend({ - name='zugdidi-hangar-red-3', - products = { - presets.missions.supply.helo:extend({name='zugdidi-supply-red-3'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='zugdidi-comcenter-red', - products = { - presets.defenses.red.sa6:extend({ name='zugdidi-sam-red'}) - } - }) - }, - [2] = - { - presets.upgrades.basic.tent:extend({ - name='zugdidi-compost-blue', - products = { - presets.missions.supply.transfer:extend({name='zugdidi-transfer-blue'}), - presets.special.blue.infantry:extend({ name='zugdidi-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='zugdidi-garrison-blue'}), - presets.missions.attack.surface:extend({name='zugdidi-attack-blue'}), - presets.missions.supply.convoy:extend({name='zugdidi-supply-blue'}) - } - }), - presets.upgrades.supply.hangar:extend({ - name='zugdidi-hangar-blue-1', - products = { - presets.missions.supply.helo:extend({name='zugdidi-supply-blue-1'}) - } - }), - presets.upgrades.supply.hangar:extend({ - name='zugdidi-hangar-blue-2', - products = { - presets.missions.supply.helo:extend({name='zugdidi-supply-blue-2'}) - } - }), - presets.upgrades.supply.hangar:extend({ - name='zugdidi-hangar-blue-3', - products = { - presets.missions.supply.helo:extend({name='zugdidi-supply-blue-3'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='zugdidi-comcenter-blue', - products = { - presets.defenses.blue.shorad:extend({ name='zugdidi-sam-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Zugdidi.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Babugent.lua ]]----------------- - -zones.babugent = ZoneCommand:new("Babugent") -zones.babugent.initialState = { side=1 } -zones.babugent:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='babugent-tent-red', - products = { - presets.special.red.infantry:extend({ name='babugent-defense-red'}), - presets.defenses.red.infantry:extend({ name='babugent-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='babugent-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='babugent-supply-red'}), - presets.missions.supply.helo:extend({name='babugent-supply-red-2'}), - presets.missions.supply.transfer:extend({name='babugent-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='babugent-ammo-red', - products = { - presets.missions.attack.surface:extend({name='babugent-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='babugent-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='babugent-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='babugent-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='babugent-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='babugent-supply-blue'}), - presets.missions.supply.helo:extend({name='babugent-supply-blue-2'}), - presets.missions.supply.transfer:extend({name='babugent-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='babugent-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='babugent-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Babugent.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Kislovodsk.lua ]]----------------- - -zones.kislovodsk = ZoneCommand:new("Kislovodsk") -zones.kislovodsk.initialState = { side=1 } -zones.kislovodsk:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='kislovodsk-tent-red', - products = { - presets.special.red.infantry:extend({ name='kislovodsk-defense-red'}), - presets.defenses.red.infantry:extend({ name='kislovodsk-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='kislovodsk-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='kislovodsk-supply-red'}), - presets.missions.supply.transfer:extend({name='kislovodsk-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='kislovodsk-ammo-red', - products = { - presets.missions.attack.surface:extend({name='kislovodsk-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='kislovodsk-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='kislovodsk-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='kislovodsk-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='kislovodsk-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='kislovodsk-supply-blue'}), - presets.missions.supply.transfer:extend({name='kislovodsk-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='kislovodsk-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='kislovodsk-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Kislovodsk.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Gudauta.lua ]]----------------- - -zones.gudauta = ZoneCommand:new("Gudauta") -zones.gudauta.initialState = { side=1 } -zones.gudauta.keepActive = true -zones.gudauta.maxResource = 50000 -zones.gudauta:defineUpgrades({ - [1] = - { - presets.upgrades.basic.comPost:extend({ - name='gudauta-compost-red', - products = { - presets.special.red.infantry:extend({ name='gudauta-defense-red'}), - presets.defenses.red.infantry:extend({ name='gudauta-garrison-red'}) - } - }), - presets.upgrades.supply.fuelTank:extend({ - name='gudauta-fuel-red', - products = { - presets.missions.supply.helo:extend({name='gudauta-supply-red'}), - presets.missions.supply.helo:extend({name='gudauta-supply-red-1'}), - presets.missions.supply.transfer:extend({name='gudauta-transfer-red'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='gudauta-comcenter-red', - products = { - presets.defenses.red.sam:extend({ name='gudauta-airdef-red'}), - presets.missions.attack.sead:extend({name='gudauta-sead-red', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.attack.sead:extend({name='gudauta-sead-red-1', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.attack.cas:extend({name='gudauta-cas-red', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.bai:extend({name='gudauta-cas-red', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.patrol.aircraft:extend({name='gudauta-patrol-red', altitude=25000, range=25}) - } - }) - }, - [2] = - { - presets.upgrades.basic.comPost:extend({ - name='gudauta-compost-blue', - products = { - presets.special.blue.infantry:extend({ name='gudauta-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='gudauta-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelTank:extend({ - name='gudauta-fuel-blue', - products = { - presets.missions.supply.helo:extend({name='gudauta-supply-blue'}), - presets.missions.supply.helo:extend({name='gudauta-supply-blue-1'}), - presets.missions.supply.transfer:extend({name='gudauta-transfer-blue'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='gudauta-comcenter-blue', - products = { - presets.defenses.blue.sam:extend({ name='gudauta-airdef-blue'}), - presets.missions.attack.sead:extend({name='gudauta-sead-blue', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.attack.sead:extend({name='gudauta-sead-blue-1', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.attack.cas:extend({name='gudauta-cas-blue', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.bai:extend({name='gudauta-cas-blue', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.patrol.aircraft:extend({name='gudauta-patrol-blue', altitude=25000, range=25}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Gudauta.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Distillery.lua ]]----------------- - -zones.distillery = ZoneCommand:new("Distillery") -zones.distillery.initialState = { side=1 } -zones.distillery:defineUpgrades({ - [1] = - { - presets.upgrades.basic.tent:extend({ - name='distillery-tent-red', - products = { - presets.special.red.infantry:extend({ name='distillery-defense-red'}), - presets.defenses.red.infantry:extend({ name='distillery-garrison-red'}) - } - }), - presets.upgrades.supply.factory1:extend({ - name='distillery-prod-red-1', - products = { - presets.missions.supply.convoy:extend({ name='distillery-supply-red-1'}), - presets.missions.supply.transfer:extend({name='distillery-transfer-red'}) - } - }), - presets.upgrades.supply.factory2:extend({ - name='distillery-prod-red-2', - products = { - presets.missions.supply.convoy:extend({ name='distillery-supply-red-2', cost=2000}), - presets.missions.supply.transfer:extend({name='distillery-transfer-red2'}) - } - }), - presets.upgrades.supply.factoryTank:extend({ - name='distillery-tank-red-1', - products = { - } - }), - presets.upgrades.supply.factoryTank:extend({ - name='distillery-tank-red-2', - products = { - } - }), - presets.upgrades.supply.factoryTank:extend({ - name='distillery-tank-red-3', - products = { - } - }) - }, - [2] = - { - presets.upgrades.basic.tent:extend({ - name='distillery-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='distillery-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='distillery-garrison-blue'}) - } - }), - presets.upgrades.supply.factory1:extend({ - name='distillery-prod-blue-1', - products = { - presets.missions.supply.convoy:extend({ name='distillery-supply-blue-1'}), - presets.missions.supply.transfer:extend({name='distillery-transfer-blue'}) - } - }), - presets.upgrades.supply.factory2:extend({ - name='distillery-prod-blue-2', - products = { - presets.missions.supply.convoy:extend({ name='distillery-supply-blue-2', cost=2000}), - presets.missions.supply.transfer:extend({name='distillery-transfer-blue2'}) - } - }), - presets.upgrades.supply.factoryTank:extend({ - name='distillery-tank-blue-1', - products = { - } - }), - presets.upgrades.supply.factoryTank:extend({ - name='distillery-tank-blue-2', - products = { - } - }), - presets.upgrades.supply.factoryTank:extend({ - name='distillery-tank-blue-3', - products = { - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Distillery.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Sochi.lua ]]----------------- - -zones.sochi = ZoneCommand:new("Sochi") -zones.sochi.initialState = { side=1 } -zones.sochi.keepActive = true -zones.sochi.maxResource = 50000 -zones.sochi:defineUpgrades({ - [1] = - { - presets.upgrades.basic.comPost:extend({ - name='sochi-compost-red', - products = { - presets.special.red.infantry:extend({ name='sochi-defense-red'}), - presets.defenses.red.infantry:extend({ name='sochi-garrison-red'}) - } - }), - presets.upgrades.supply.fuelTank:extend({ - name='sochi-fuel-red', - products = { - presets.missions.supply.helo:extend({name='sochi-supply-red-1'}), - presets.missions.supply.helo:extend({name='sochi-supply-red-2'}), - presets.missions.supply.convoy_escorted:extend({name='sochi-supply-red-3'}), - presets.missions.supply.transfer:extend({name='sochi-transfer-red'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='sochi-comcenter-red', - products = { - presets.defenses.red.sa10:extend({ name='sochi-airdef-red'}), - presets.missions.attack.sead:extend({name='sochi-sead-red', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.attack.strike:extend({name='sochi-strike-red', altitude=30000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.patrol.aircraft:extend({name='sochi-patrol-red', altitude=25000, range=25}), - presets.missions.patrol.aircraft:extend({name='sochi-patrol-red-1', altitude=25000, range=25}), - presets.missions.attack.cas:extend({name='sochi-cas-red', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.bai:extend({name='sochi-cas-red', altitude=10000, expend=AI.Task.WeaponExpend.ONE}) - } - }) - }, - [2] = - { - presets.upgrades.basic.comPost:extend({ - name='sochi-compost-blue', - products = { - presets.special.blue.infantry:extend({ name='sochi-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='sochi-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelTank:extend({ - name='sochi-fuel-blue', - products = { - presets.missions.supply.helo:extend({name='sochi-supply-blue-1'}), - presets.missions.supply.helo:extend({name='sochi-supply-blue-2'}), - presets.missions.supply.convoy_escorted:extend({name='sochi-supply-blue-3'}), - presets.missions.supply.transfer:extend({name='sochi-transfer-blue'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='sochi-comcenter-blue', - products = { - presets.defenses.blue.patriot:extend({ name='sochi-airdef-blue'}), - presets.missions.attack.sead:extend({name='sochi-sead-blue', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.attack.strike:extend({name='sochi-strike-blue', altitude=30000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.patrol.aircraft:extend({name='sochi-patrol-blue', altitude=25000, range=25}), - presets.missions.attack.cas:extend({name='sochi-cas-blue', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.bai:extend({name='sochi-cas-blue', altitude=10000, expend=AI.Task.WeaponExpend.ONE}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Sochi.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Golf.lua ]]----------------- - -zones.golf = ZoneCommand:new("Golf") -zones.golf.initialState = nil -zones.golf.isHeloSpawn = true -zones.golf:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='golf-tent-red', - products = { - presets.special.red.infantry:extend({ name='golf-defense-red'}), - presets.defenses.red.infantry:extend({ name='golf-garrison-red'}) - } - }), - presets.upgrades.supply.fuelTank:extend({ - name='golf-fuel-red', - products = { - presets.missions.supply.helo:extend({name='golf-supply-red'}), - presets.missions.supply.helo:extend({name='golf-supply-red-1'}), - presets.missions.supply.transfer:extend({name='golf-transfer-red'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='golf-comcenter-red', - products = { - presets.defenses.red.shorad:extend({name='golf-sam-red'}), - presets.missions.attack.helo:extend({name='golf-cas-red', altitude=200, expend=AI.Task.WeaponExpend.HALF }) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='golf-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='golf-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='golf-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelTankFarp:extend({ - name='golf-fuel-blue', - products = { - presets.missions.supply.helo:extend({name='golf-supply-blue'}), - presets.missions.supply.helo:extend({name='golf-supply-blue-1'}), - presets.missions.supply.transfer:extend({name='golf-transfer-blue'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='golf-comcenter-blue', - products = { - presets.defenses.blue.shorad:extend({name='golf-sam-blue'}), - presets.missions.attack.helo:extend({name='golf-cas-blue', altitude=200, expend=AI.Task.WeaponExpend.HALF }) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Golf.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Charlie.lua ]]----------------- - -zones.charlie = ZoneCommand:new("Charlie") -zones.charlie.initialState = { side=2 } -zones.charlie.keepActive = true -zones.charlie:defineUpgrades({ - [1] = - { - presets.upgrades.basic.tent:extend({ - name='charlie-tent-red', - products = { - presets.special.red.infantry:extend({ name='charlie-defense-red'}), - presets.defenses.red.infantry:extend({ name='charlie-garrison-red'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='charlie-comcenter-red', - products = { - presets.defenses.red.sam:extend({ name='charlie-sam-red'}) - } - }) - }, - [2] = - { - presets.upgrades.basic.tent:extend({ - name='charlie-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='charlie-defense-red'}), - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='charlie-comcenter-blue', - products = { - presets.defenses.blue.sam:extend({ name='charlie-sam-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Charlie.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Lentehi.lua ]]----------------- - -zones.lentehi = ZoneCommand:new("Lentehi") -zones.lentehi.initialState = { side=1 } -zones.lentehi:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='lentehi-tent-red', - products = { - presets.special.red.infantry:extend({ name='lentehi-defense-red'}), - presets.defenses.red.infantry:extend({ name='lentehi-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='lentehi-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='lentehi-supply-red'}), - presets.missions.supply.helo:extend({name='lentehi-supply-red-2'}), - presets.missions.supply.transfer:extend({name='lentehi-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='lentehi-ammo-red', - products = { - presets.missions.attack.surface:extend({name='lentehi-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='lentehi-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='lentehi-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='lentehi-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='lentehi-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='lentehi-supply-blue'}), - presets.missions.supply.helo:extend({name='lentehi-supply-blue-2'}), - presets.missions.supply.transfer:extend({name='lentehi-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='lentehi-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='lentehi-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Lentehi.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Refinery.lua ]]----------------- - -zones.refinery = ZoneCommand:new("Refinery") -zones.refinery.initialState = { side=1 } -zones.refinery:defineUpgrades({ - [1] = - { - presets.upgrades.basic.tent:extend({ - name='refinery-tent-red', - products = { - presets.special.red.infantry:extend({ name='refinery-defense-red'}), - presets.defenses.red.infantry:extend({ name='refinery-garrison-red'}) - } - }), - presets.upgrades.supply.refinery1:extend({ - name='refinery-building-red', - products = { - presets.missions.supply.convoy:extend({ name='refinery-supply-red'}), - presets.missions.supply.convoy:extend({ name='refinery-supply-red-1'}), - presets.missions.supply.helo:extend({ name='refinery-supply-red-2'}), - presets.missions.supply.transfer:extend({name='refinery-transfer-red'}) - } - }) - }, - [2] = - { - presets.upgrades.basic.tent:extend({ - name='refinery-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='refinery-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='refinery-garrison-blue'}) - } - }), - presets.upgrades.supply.refinery1:extend({ - name='refinery-building-blue', - products = { - presets.missions.supply.convoy:extend({ name='refinery-supply-blue'}), - presets.missions.supply.convoy:extend({ name='refinery-supply-blue-1'}), - presets.missions.supply.helo:extend({ name='refinery-supply-blue-2'}), - presets.missions.supply.transfer:extend({name='refinery-transfer-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Refinery.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Mozdok.lua ]]----------------- - -zones.mozdok = ZoneCommand:new("Mozdok") -zones.mozdok.initialState = { side=1 } -zones.mozdok.keepActive = true -zones.mozdok.maxResource = 50000 -zones.mozdok:defineUpgrades({ - [1] = - { - presets.upgrades.basic.comPost:extend({ - name='mozdok-compost-red', - products = { - presets.special.red.infantry:extend({ name='mozdok-defense-red'}), - presets.defenses.red.infantry:extend({ name='mozdok-garrison-red'}) - } - }), - presets.upgrades.supply.fuelTank:extend({ - name='mozdok-fuel-red', - products = { - presets.missions.supply.helo:extend({name='mozdok-supply-red-1'}), - presets.missions.supply.helo:extend({name='mozdok-supply-red-2'}), - presets.missions.supply.convoy_escorted:extend({name='mozdok-supply-red-3'}), - presets.missions.supply.transfer:extend({name='mozdok-transfer-red'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='mozdok-comcenter-red', - products = { - presets.defenses.red.sa10:extend({ name='mozdok-airdef-red'}), - presets.missions.patrol.aircraft:extend({name='mozdok-patrol-red', altitude=25000, range=25}), - presets.missions.attack.cas:extend({name='mozdok-cas-red', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.bai:extend({name='mozdok-cas-red', altitude=10000, expend=AI.Task.WeaponExpend.ONE}) - } - }) - }, - [2] = - { - presets.upgrades.basic.comPost:extend({ - name='mozdok-compost-blue', - products = { - presets.special.blue.infantry:extend({ name='mozdok-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='mozdok-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelTank:extend({ - name='mozdok-fuel-blue', - products = { - presets.missions.supply.helo:extend({name='mozdok-supply-blue-1'}), - presets.missions.supply.helo:extend({name='mozdok-supply-blue-2'}), - presets.missions.supply.convoy_escorted:extend({name='mozdok-supply-blue-3'}), - presets.missions.supply.transfer:extend({name='mozdok-transfer-blue'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='mozdok-comcenter-blue', - products = { - presets.defenses.blue.shorad:extend({ name='mozdok-airdef-blue'}), - presets.missions.patrol.aircraft:extend({name='mozdok-patrol-blue', altitude=25000, range=25}), - presets.missions.attack.cas:extend({name='mozdok-cas-blue', altitude=15000, cost=50, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.cas:extend({name='mozdok-cas-blue-1', altitude=15000, cost=50, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.bai:extend({name='mozdok-cas-blue', altitude=15000, cost=50, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.bai:extend({name='mozdok-cas-blue-1', altitude=15000, cost=50, expend=AI.Task.WeaponExpend.ONE}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Mozdok.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Lima.lua ]]----------------- - -zones.lima = ZoneCommand:new("Lima") -zones.lima.initialState = { side=1 } -zones.lima:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='lima-tent-red', - products = { - presets.special.red.infantry:extend({ name='lima-defense-red'}), - presets.defenses.red.infantry:extend({ name='lima-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='lima-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='lima-supply-red'}), - presets.missions.supply.helo:extend({name='lima-supply-red-1'}), - presets.missions.supply.transfer:extend({name='lima-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='lima-ammo-red', - products = { - presets.missions.attack.surface:extend({name='lima-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='lima-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='lima-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='lima-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='lima-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='lima-supply-blue'}), - presets.missions.supply.helo:extend({name='lima-supply-blue-1'}), - presets.missions.supply.transfer:extend({name='lima-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='lima-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='lima-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Lima.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Oscar.lua ]]----------------- - -zones.oscar = ZoneCommand:new("Oscar") -zones.oscar.initialState = { side=1 } -zones.oscar:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='oscar-tent-red', - products = { - presets.special.red.infantry:extend({ name='oscar-defense-red'}), - presets.defenses.red.infantry:extend({ name='oscar-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='oscar-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='oscar-supply-red'}), - presets.missions.supply.transfer:extend({name='oscar-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='oscar-ammo-red', - products = { - presets.missions.attack.surface:extend({name='oscar-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='oscar-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='oscar-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='oscar-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='oscar-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='oscar-supply-blue'}), - presets.missions.supply.transfer:extend({name='oscar-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='oscar-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='oscar-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Oscar.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Nalchik.lua ]]----------------- - -zones.nalchik = ZoneCommand:new("Nalchik") -zones.nalchik.initialState = { side=1 } -zones.nalchik.keepActive = true -zones.nalchik.isHeloSpawn = true -zones.nalchik.isPlaneSpawn = true -zones.nalchik:defineUpgrades({ - [1] = - { - presets.upgrades.basic.comPost:extend({ - name='nalchik-compost-red', - products = { - presets.special.red.infantry:extend({ name='nalchik-defense-red'}), - presets.defenses.red.infantry:extend({ name='nalchik-garrison-red'}) - } - }), - presets.upgrades.supply.fuelTank:extend({ - name='nalchik-fuel-red', - products = { - presets.missions.supply.helo:extend({name='nalchik-supply-red-1'}), - presets.missions.supply.helo:extend({name='nalchik-supply-red-2'}), - presets.missions.supply.transfer:extend({name='nalchik-transfer-red'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='nalchik-comcenter-red', - products = { - presets.defenses.red.sa3:extend({ name='nalchik-airdef-red'}), - presets.missions.attack.sead:extend({name='nalchik-sead-red', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.attack.cas:extend({name='nalchik-cas-red', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.bai:extend({name='nalchik-cas-red', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.strike:extend({name='nalchik-strike-red', altitude=30000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.patrol.aircraft:extend({name='nalchik-patrol-red', altitude=25000, range=25}), - presets.missions.patrol.aircraft:extend({name='nalchik-patrol-red-2', altitude=25000, range=25}), - presets.missions.support.awacs:extend({name='nalchik-awacs-red', altitude=30000, freq=251.2}), - presets.missions.support.tanker:extend({name='nalchik-tanker-red', altitude=30000, freq=252.2, tacan='40', variant='Drogue'}) - } - }) - }, - [2] = - { - presets.upgrades.basic.comPost:extend({ - name='nalchik-compost-blue', - products = { - presets.special.blue.infantry:extend({ name='nalchik-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='nalchik-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelTank:extend({ - name='nalchik-fuel-blue', - products = { - presets.missions.supply.helo:extend({name='nalchik-supply-blue-1'}), - presets.missions.supply.helo:extend({name='nalchik-supply-blue-2'}), - presets.missions.supply.transfer:extend({name='nalchik-transfer-blue'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='nalchik-comcenter-blue', - products = { - presets.defenses.blue.nasams:extend({ name='nalchik-airdef-blue'}), - presets.missions.support.awacs:extend({name='nalchik-awacs-blue', altitude=30000, freq=259.5}), - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Nalchik.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Digora.lua ]]----------------- - -zones.digora = ZoneCommand:new("Digora") -zones.digora.initialState = { side=1 } -zones.digora:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='digora-tent-red', - products = { - presets.special.red.infantry:extend({ name='digora-defense-red'}), - presets.defenses.red.infantry:extend({ name='digora-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='digora-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='digora-supply-red'}), - presets.missions.supply.transfer:extend({name='digora-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='digora-ammo-red', - products = { - presets.missions.attack.surface:extend({name='digora-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='digora-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='digora-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='digora-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='digora-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='digora-supply-blue'}), - presets.missions.supply.transfer:extend({name='digora-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='digora-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='digora-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Digora.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Uniform.lua ]]----------------- - -zones.uniform = ZoneCommand:new("Uniform") -zones.uniform.initialState = { side=1 } -zones.uniform:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='uniform-tent-red', - products = { - presets.special.red.infantry:extend({ name='uniform-defense-red'}), - presets.defenses.red.infantry:extend({ name='uniform-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='uniform-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='uniform-supply-red'}), - presets.missions.supply.transfer:extend({name='uniform-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='uniform-ammo-red', - products = { - presets.missions.attack.surface:extend({name='uniform-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='uniform-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='uniform-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='uniform-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='uniform-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='uniform-supply-blue'}), - presets.missions.supply.transfer:extend({name='uniform-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='uniform-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='uniform-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Uniform.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Factory.lua ]]----------------- - -zones.factory = ZoneCommand:new("Factory") -zones.factory.initialState = { side=2 } -zones.factory:defineUpgrades({ - [1] = - { - presets.upgrades.basic.tent:extend({ - name='factory-tent-red', - products = { - presets.special.red.infantry:extend({ name='factory-defense-red'}), - presets.defenses.red.infantry:extend({ name='factory-garrison-red'}) - } - }), - presets.upgrades.supply.factory1:extend({ - name='factory-prod-red-1', - products = { - presets.missions.supply.convoy:extend({ name='factory-supply-red-1'}), - presets.missions.supply.transfer:extend({name='factory-transfer-red'}) - } - }), - presets.upgrades.supply.factory2:extend({ - name='factory-prod-red-2', - products = { - presets.missions.supply.convoy:extend({ name='factory-supply-red-2', cost=2000}), - presets.missions.supply.transfer:extend({name='factory-transfer-red2'}) - } - }), - presets.upgrades.supply.factoryTank:extend({ - name='factory-tank-red-1', - products = { - } - }), - presets.upgrades.supply.factoryTank:extend({ - name='factory-tank-red-2', - products = { - } - }), - presets.upgrades.supply.factoryTank:extend({ - name='factory-tank-red-3', - products = { - } - }) - }, - [2] = - { - presets.upgrades.basic.tent:extend({ - name='factory-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='factory-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='factory-garrison-blue'}) - } - }), - presets.upgrades.supply.factory1:extend({ - name='factory-prod-blue-1', - products = { - presets.missions.supply.convoy:extend({ name='factory-supply-blue-1'}), - presets.missions.supply.transfer:extend({name='factory-transfer-blue'}) - } - }), - presets.upgrades.supply.factory2:extend({ - name='factory-prod-blue-2', - products = { - presets.missions.supply.convoy:extend({ name='factory-supply-blue-2', cost=2000}), - presets.missions.supply.transfer:extend({name='factory-transfer-blue2'}) - } - }), - presets.upgrades.supply.factoryTank:extend({ - name='factory-tank-blue-1', - products = { - } - }), - presets.upgrades.supply.factoryTank:extend({ - name='factory-tank-blue-2', - products = { - } - }), - presets.upgrades.supply.factoryTank:extend({ - name='factory-tank-blue-3', - products = { - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Factory.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Senaki.lua ]]----------------- - -zones.senaki = ZoneCommand:new("Senaki") -zones.senaki.initialState = { side=1 } -zones.senaki.keepActive = true -zones.senaki.isHeloSpawn = true -zones.senaki.isPlaneSpawn = true -zones.senaki.maxResource = 50000 -zones.senaki:defineUpgrades({ - [1] = - { - presets.upgrades.basic.comPost:extend({ - name='senaki-compost-red', - products = { - presets.special.red.infantry:extend({ name='senaki-defense-red'}), - presets.defenses.red.infantry:extend({ name='senaki-garrison-red'}) - } - }), - presets.upgrades.supply.fuelTank:extend({ - name='senaki-fuel-red', - products = { - presets.missions.supply.helo:extend({name='senaki-supply-red-1'}), - presets.missions.supply.helo:extend({name='senaki-supply-red-2'}), - presets.missions.supply.transfer:extend({name='senaki-transfer-red'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='senaki-comcenter-red', - products = { - presets.defenses.red.sa3:extend({ name='senaki-airdef-red'}), - presets.missions.attack.sead:extend({name='senaki-sead-red', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.attack.cas:extend({name='senaki-cas-red', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.bai:extend({name='senaki-cas-red', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.strike:extend({name='senaki-strike-red', altitude=20000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.patrol.aircraft:extend({name='senaki-patrol-red', altitude=25000, range=25}), - presets.missions.patrol.aircraft:extend({name='senaki-patrol-red-2', altitude=20000, range=25}) - } - }) - }, - [2] = - { - presets.upgrades.basic.comPost:extend({ - name='senaki-compost-blue', - products = { - presets.special.blue.infantry:extend({ name='senaki-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='senaki-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelTank:extend({ - name='senaki-fuel-blue', - products = { - presets.missions.supply.helo:extend({name='senaki-supply-blue-1'}), - presets.missions.supply.helo:extend({name='senaki-supply-blue-2'}), - presets.missions.supply.transfer:extend({name='senaki-transfer-blue'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='senaki-comcenter-blue', - products = { - presets.defenses.blue.shorad:extend({ name='senaki-airdef-blue'}), - presets.missions.attack.sead:extend({name='senaki-sead-blue', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.attack.cas:extend({name='senaki-cas-blue', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.bai:extend({name='senaki-cas-blue', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.strike:extend({name='senaki-strike-blue', altitude=20000, expend=AI.Task.WeaponExpend.TWO}), - presets.missions.patrol.aircraft:extend({name='senaki-patrol-blue', altitude=25000, range=25}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Senaki.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Kutaisi.lua ]]----------------- - -zones.kutaisi = ZoneCommand:new("Kutaisi") -zones.kutaisi.initialState = { side=1 } -zones.kutaisi.keepActive = true -zones.kutaisi.maxResource = 50000 -zones.kutaisi:defineUpgrades({ - [1] = - { - presets.upgrades.basic.comPost:extend({ - name='kutaisi-compost-red', - products = { - presets.special.red.infantry:extend({ name='kutaisi-defense-red'}), - presets.defenses.red.infantry:extend({ name='kutaisi-garrison-red'}) - } - }), - presets.upgrades.supply.fuelTank:extend({ - name='kutaisi-fuel-red', - products = { - presets.missions.supply.helo:extend({name='kutaisi-supply-red-1'}), - presets.missions.supply.helo:extend({name='kutaisi-supply-red-2'}), - presets.missions.supply.transfer:extend({name='kutaisi-transfer-red'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='kutaisi-comcenter-red', - products = { - presets.defenses.red.shorad:extend({ name='kutaisi-airdef-red'}), - presets.missions.attack.sead:extend({name='kutaisi-sead-red', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.attack.cas:extend({name='kutaisi-cas-red', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.bai:extend({name='kutaisi-cas-red', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.strike:extend({name='kutaisi-strike-red', altitude=20000, expend=AI.Task.WeaponExpend.HALF}), - presets.missions.patrol.aircraft:extend({name='kutaisi-patrol-red', altitude=25000, range=25}), - presets.missions.patrol.aircraft:extend({name='kutaisi-patrol-red-2', altitude=25000, range=25}) - } - }) - }, - [2] = - { - presets.upgrades.basic.comPost:extend({ - name='kutaisi-compost-blue', - products = { - presets.special.blue.infantry:extend({ name='kutaisi-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='kutaisi-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelTank:extend({ - name='kutaisi-fuel-blue', - products = { - presets.missions.supply.helo:extend({name='kutaisi-supply-blue-1'}), - presets.missions.supply.helo:extend({name='kutaisi-supply-blue-2'}), - presets.missions.supply.transfer:extend({name='kutaisi-transfer-blue'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='kutaisi-comcenter-blue', - products = { - presets.defenses.blue.shorad:extend({ name='kutaisi-airdef-blue'}), - presets.missions.attack.sead:extend({name='kutaisi-sead-blue', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.attack.cas:extend({name='kutaisi-cas-blue', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.bai:extend({name='kutaisi-cas-blue', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.strike:extend({name='kutaisi-strike-blue', altitude=20000, expend=AI.Task.WeaponExpend.TWO}), - presets.missions.patrol.aircraft:extend({name='kutaisi-patrol-blue', altitude=25000, range=25}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Kutaisi.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Prohladniy.lua ]]----------------- - -zones.prohladniy = ZoneCommand:new("Prohladniy") -zones.prohladniy.initialState = { side=1 } -zones.prohladniy:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='prohladniy-tent-red', - products = { - presets.special.red.infantry:extend({ name='prohladniy-defense-red'}), - presets.defenses.red.infantry:extend({ name='prohladniy-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='prohladniy-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='prohladniy-supply-red'}), - presets.missions.supply.transfer:extend({name='prohladniy-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='prohladniy-ammo-red', - products = { - presets.missions.attack.surface:extend({name='prohladniy-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='prohladniy-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='prohladniy-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='prohladniy-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='prohladniy-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='prohladniy-supply-blue'}), - presets.missions.supply.transfer:extend({name='prohladniy-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='prohladniy-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='prohladniy-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Prohladniy.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Tallyk.lua ]]----------------- - -zones.tallyk = ZoneCommand:new("Tallyk") -zones.tallyk.initialState = { side=1 } -zones.tallyk.keepActive = true -zones.tallyk:defineUpgrades({ - [1] = - { - presets.upgrades.basic.tent:extend({ - name='tallyk-tent-red', - products = { - presets.special.red.infantry:extend({ name='tallyk-defense-red'}), - presets.defenses.red.infantry:extend({ name='tallyk-garrison-red'}), - presets.missions.attack.surface:extend({name='tallyk-assault-red'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='tallyk-comcenter-red', - products = { - presets.defenses.red.sam:extend({ name='tallyk-sam-red'}) - } - }) - }, - [2] = - { - presets.upgrades.basic.tent:extend({ - name='tallyk-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='tallyk-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='tallyk-garrison-blue'}), - presets.missions.attack.surface:extend({name='tallyk-assault-blue'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='tallyk-comcenter-blue', - products = { - presets.defenses.blue.sam:extend({ name='tallyk-sam-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Tallyk.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Terek.lua ]]----------------- - -zones.terek = ZoneCommand:new("Terek") -zones.terek.initialState = { side=1 } -zones.terek:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='terek-tent-red', - products = { - presets.special.red.infantry:extend({ name='terek-defense-red'}), - presets.defenses.red.infantry:extend({ name='terek-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='terek-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='terek-supply-red'}), - presets.missions.supply.transfer:extend({name='terek-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='terek-ammo-red', - products = { - presets.missions.attack.surface:extend({name='terek-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='terek-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='terek-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='terek-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='terek-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='terek-supply-blue'}), - presets.missions.supply.transfer:extend({name='terek-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='terek-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='terek-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Terek.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Humara.lua ]]----------------- - -zones.humara = ZoneCommand:new("Humara") -zones.humara.initialState = { side=1 } -zones.humara:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='humara-tent-red', - products = { - presets.special.red.infantry:extend({ name='humara-defense-red'}), - presets.defenses.red.infantry:extend({ name='humara-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='humara-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='humara-supply-red'}), - presets.missions.supply.transfer:extend({name='humara-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='humara-ammo-red', - products = { - presets.missions.attack.surface:extend({name='humara-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='humara-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='humara-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='humara-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='humara-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='humara-supply-blue'}), - presets.missions.supply.transfer:extend({name='humara-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='humara-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='humara-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Humara.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Ochamchira.lua ]]----------------- - -zones.ochamchira = ZoneCommand:new("Ochamchira") -zones.ochamchira.initialState = { side=1 } -zones.ochamchira:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='ochamchira-tent-red', - products = { - presets.special.red.infantry:extend({ name='ochamchira-defense-red'}), - presets.defenses.red.infantry:extend({ name='ochamchira-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='ochamchira-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='ochamchira-supply-red'}), - presets.missions.supply.transfer:extend({name='ochamchira-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='ochamchira-ammo-red', - products = { - presets.missions.attack.surface:extend({name='ochamchira-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='ochamchira-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='ochamchira-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='ochamchira-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='ochamchira-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='ochamchira-supply-blue'}), - presets.missions.supply.transfer:extend({name='ochamchira-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='ochamchira-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='ochamchira-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Ochamchira.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/November.lua ]]----------------- - -zones.november = ZoneCommand:new("November") -zones.november.initialState = { side=1 } -zones.november.isHeloSpawn = true -zones.november:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='november-tent-red', - products = { - presets.special.red.infantry:extend({ name='november-defense-red'}), - presets.defenses.red.infantry:extend({ name='november-garrison-red'}) - } - }), - presets.upgrades.supply.fuelTankFarp:extend({ - name='november-fuel-red', - products = { - presets.missions.supply.helo:extend({name='november-supply-red'}), - presets.missions.supply.helo:extend({name='november-supply-red-1'}), - presets.missions.supply.transfer:extend({name='november-transfer-red'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='november-comcenter-red', - products = { - presets.defenses.red.shorad:extend({name='november-sam-red'}), - presets.missions.attack.helo:extend({name='november-cas-red', altitude=200, expend=AI.Task.WeaponExpend.HALF }) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='november-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='november-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='november-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelTankFarp:extend({ - name='november-fuel-blue', - products = { - presets.missions.supply.helo:extend({name='november-supply-blue'}), - presets.missions.supply.helo:extend({name='november-supply-blue-1'}), - presets.missions.supply.transfer:extend({name='november-transfer-blue'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='november-comcenter-blue', - products = { - presets.defenses.blue.shorad:extend({name='november-sam-blue'}), - presets.missions.attack.helo:extend({name='november-cas-blue', altitude=200, expend=AI.Task.WeaponExpend.HALF }) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/November.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/XRay.lua ]]----------------- - -zones.xray = ZoneCommand:new("XRay") -zones.xray.initialState = { side=1 } -zones.xray:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='xray-tent-red', - products = { - presets.special.red.infantry:extend({ name='xray-defense-red'}), - presets.defenses.red.infantry:extend({ name='xray-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='xray-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='xray-supply-red'}), - presets.missions.supply.helo:extend({name='xray-supply-red-2'}), - presets.missions.supply.transfer:extend({name='xray-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='xray-ammo-red', - products = { - presets.missions.attack.surface:extend({name='xray-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='xray-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='xray-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='xray-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='xray-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='xray-supply-blue'}), - presets.missions.supply.helo:extend({name='xray-supply-blue-2'}), - presets.missions.supply.transfer:extend({name='xray-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='xray-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='xray-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/XRay.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Whiskey.lua ]]----------------- - -zones.whiskey = ZoneCommand:new("Whiskey") -zones.whiskey.initialState = { side=1 } -zones.whiskey:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='whiskey-tent-red', - products = { - presets.special.red.infantry:extend({ name='whiskey-defense-red'}), - presets.defenses.red.infantry:extend({ name='whiskey-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='whiskey-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='whiskey-supply-red'}), - presets.missions.supply.transfer:extend({name='whiskey-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='whiskey-ammo-red', - products = { - presets.missions.attack.surface:extend({name='whiskey-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='whiskey-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='whiskey-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='whiskey-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='whiskey-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='whiskey-supply-blue'}), - presets.missions.supply.transfer:extend({name='whiskey-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='whiskey-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='whiskey-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Whiskey.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Mine.lua ]]----------------- - -zones.mine = ZoneCommand:new("Mine") -zones.mine.initialState = { side=1 } -zones.mine:defineUpgrades({ - [1] = - { - presets.upgrades.basic.tent:extend({ - name='mine-tent-red', - products = { - presets.special.red.infantry:extend({ name='mine-defense-red'}), - presets.defenses.red.infantry:extend({ name='mine-garrison-red'}) - } - }), - presets.upgrades.supply.excavator:extend({ - name='mine-excavator-red-1', - products = { - presets.missions.supply.convoy:extend({ name='mine-supply-red'}), - presets.missions.supply.transfer:extend({name='mine-transfer-red'}) - } - }), - presets.upgrades.supply.excavator:extend({ - name='mine-excavator-red-2', - products = { - presets.missions.supply.convoy:extend({ name='mine-supply-red'}), - presets.missions.supply.transfer:extend({name='mine-transfer-red'}) - } - }), - presets.upgrades.supply.excavator:extend({ - name='mine-excavator-red-3', - products = { - presets.missions.supply.convoy:extend({ name='mine-supply-red'}), - presets.missions.supply.transfer:extend({name='mine-transfer-red'}) - } - }) - }, - [2] = - { - presets.upgrades.basic.tent:extend({ - name='mine-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='mine-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='mine-garrison-blue'}) - } - }), - presets.upgrades.supply.excavator:extend({ - name='mine-excavator-blue-1', - products = { - presets.missions.supply.convoy:extend({ name='mine-supply-blue'}), - presets.missions.supply.transfer:extend({name='mine-transfer-blue'}) - } - }), - presets.upgrades.supply.excavator:extend({ - name='mine-excavator-blue-2', - products = { - presets.missions.supply.convoy:extend({ name='mine-supply-blue'}), - presets.missions.supply.transfer:extend({name='mine-transfer-blue'}) - } - }), - presets.upgrades.supply.excavator:extend({ - name='mine-excavator-blue-3', - products = { - presets.missions.supply.convoy:extend({ name='mine-supply-blue'}), - presets.missions.supply.transfer:extend({name='mine-transfer-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Mine.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Papa.lua ]]----------------- - -zones.papa = ZoneCommand:new("Papa") -zones.papa.initialState = { side=1 } -zones.papa.keepActive = true -zones.papa:defineUpgrades({ - [1] = - { - presets.upgrades.basic.tent:extend({ - name='papa-tent-red', - products = { - presets.special.red.infantry:extend({ name='papa-defense-red'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='papa-comcenter-red', - products = { - presets.defenses.red.sam:extend({ name='papa-sam-red'}) - } - }) - }, - [2] = - { - presets.upgrades.basic.tent:extend({ - name='papa-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='papa-defense-blue'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='papa-comcenter-blue', - products = { - presets.defenses.blue.sam:extend({ name='papa-sam-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Papa.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Sukhumi.lua ]]----------------- - -zones.sukhumi = ZoneCommand:new("Sukhumi") -zones.sukhumi.initialState = { side=1 } -zones.sukhumi.keepActive = true -zones.sukhumi.maxResource = 50000 -zones.sukhumi:defineUpgrades({ - [1] = - { - presets.upgrades.basic.comPost:extend({ - name='sukhumi-compost-red', - products = { - presets.special.red.infantry:extend({ name='sukhumi-defense-red'}), - presets.defenses.red.infantry:extend({ name='sukhumi-garrison-red'}) - } - }), - presets.upgrades.supply.fuelTank:extend({ - name='sukhumi-fuel-red', - products = { - presets.missions.supply.helo:extend({name='sukhumi-supply-red-1'}), - presets.missions.supply.helo:extend({name='sukhumi-supply-red-2'}), - presets.missions.supply.transfer:extend({name='sukhumi-transfer-red'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='sukhumi-comcenter-red', - products = { - presets.defenses.red.sa11:extend({ name='sukhumi-airdef-red'}), - presets.missions.attack.sead:extend({name='sukhumi-sead-red', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.attack.cas:extend({name='sukhumi-cas-red', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.bai:extend({name='sukhumi-cas-red', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.strike:extend({name='sukhumi-strike-red', altitude=20000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.patrol.aircraft:extend({name='sukhumi-patrol-red', altitude=25000, range=25}), - presets.missions.patrol.aircraft:extend({name='sukhumi-patrol-red-2', altitude=25000, range=25}) - } - }) - }, - [2] = - { - presets.upgrades.basic.comPost:extend({ - name='sukhumi-compost-blue', - products = { - presets.special.blue.infantry:extend({ name='sukhumi-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='sukhumi-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelTank:extend({ - name='sukhumi-fuel-blue', - products = { - presets.missions.supply.helo:extend({name='sukhumi-supply-blue-1'}), - presets.missions.supply.helo:extend({name='sukhumi-supply-blue-2'}), - presets.missions.supply.transfer:extend({name='sukhumi-transfer-blue'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='sukhumi-comcenter-blue', - products = { - presets.defenses.blue.nasams:extend({ name='sukhumi-airdef-blue'}), - presets.missions.attack.sead:extend({name='sukhumi-sead-blue', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.attack.cas:extend({name='sukhumi-cas-blue', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.bai:extend({name='sukhumi-cas-blue', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.strike:extend({name='sukhumi-strike-blue', altitude=20000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.patrol.aircraft:extend({name='sukhumi-patrol-blue', altitude=25000, range=25}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Sukhumi.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Farm.lua ]]----------------- - -zones.farm = ZoneCommand:new("Farm") -zones.farm.initialState = { side=1 } -zones.farm:defineUpgrades({ - [1] = - { - presets.upgrades.basic.tent:extend({ - name='farm-tent-red', - products = { - presets.special.red.infantry:extend({ name='farm-defense-red'}), - presets.defenses.red.infantry:extend({ name='farm-garrison-red'}) - } - }), - presets.upgrades.supply.farm1:extend({ - name='farm-prod-red-1', - products = { - presets.missions.supply.convoy:extend({ name='farm-supply-red'}), - presets.missions.supply.transfer:extend({name='farm-transfer-red'}) - } - }), - presets.upgrades.supply.farm2:extend({ - name='farm-prod-red-2', - products = { - presets.missions.supply.convoy:extend({ name='farm-supply-red'}), - presets.missions.supply.transfer:extend({name='farm-transfer-red'}) - } - }) - }, - [2] = - { - presets.upgrades.basic.tent:extend({ - name='farm-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='farm-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='farm-garrison-blue'}) - } - }), - presets.upgrades.supply.farm1:extend({ - name='farm-prod-blue-1', - products = { - presets.missions.supply.convoy:extend({ name='farm-supply-blue'}), - presets.missions.supply.transfer:extend({name='farm-transfer-blue'}) - } - }), - presets.upgrades.supply.farm2:extend({ - name='farm-prod-blue-2', - products = { - presets.missions.supply.convoy:extend({ name='farm-supply-blue'}), - presets.missions.supply.transfer:extend({name='farm-transfer-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Farm.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Romeo.lua ]]----------------- - -zones.romeo = ZoneCommand:new("Romeo") -zones.romeo.initialState = { side=1 } -zones.romeo:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='romeo-tent-red', - products = { - presets.special.red.infantry:extend({ name='romeo-defense-red'}), - presets.defenses.red.infantry:extend({ name='romeo-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='romeo-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='romeo-supply-red'}), - presets.missions.supply.transfer:extend({name='romeo-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='romeo-ammo-red', - products = { - presets.missions.attack.surface:extend({name='romeo-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='romeo-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='romeo-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='romeo-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='romeo-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='romeo-supply-blue'}), - presets.missions.supply.transfer:extend({name='romeo-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='romeo-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='romeo-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Romeo.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Zulu.lua ]]----------------- - -zones.zulu = ZoneCommand:new("Zulu") -zones.zulu.initialState = { side=1 } -zones.zulu:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='zulu-tent-red', - products = { - presets.special.red.infantry:extend({ name='zulu-defense-red'}), - presets.defenses.red.infantry:extend({ name='zulu-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='zulu-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='zulu-supply-red'}), - presets.missions.supply.transfer:extend({name='zulu-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='zulu-ammo-red', - products = { - presets.missions.attack.surface:extend({name='zulu-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='zulu-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='zulu-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='zulu-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='zulu-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='zulu-supply-blue'}), - presets.missions.supply.transfer:extend({name='zulu-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='zulu-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='zulu-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Zulu.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Yankee.lua ]]----------------- - -zones.yankee = ZoneCommand:new("Yankee") -zones.yankee.initialState = { side=1 } -zones.yankee:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='yankee-tent-red', - products = { - presets.special.red.infantry:extend({ name='yankee-defense-red'}), - presets.defenses.red.infantry:extend({ name='yankee-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='yankee-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='yankee-supply-red'}), - presets.missions.supply.transfer:extend({name='yankee-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='yankee-ammo-red', - products = { - presets.missions.attack.surface:extend({name='yankee-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='yankee-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='yankee-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='yankee-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='yankee-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='yankee-supply-blue'}), - presets.missions.supply.transfer:extend({name='yankee-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='yankee-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='yankee-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Yankee.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Malgobek.lua ]]----------------- - -zones.malgobek = ZoneCommand:new("Malgobek") -zones.malgobek.initialState = { side=1 } -zones.malgobek:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='malgobek-tent-red', - products = { - presets.special.red.infantry:extend({ name='malgobek-defense-red'}), - presets.defenses.red.infantry:extend({ name='malgobek-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='malgobek-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='malgobek-supply-red'}), - presets.missions.supply.transfer:extend({name='malgobek-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='malgobek-ammo-red', - products = { - presets.missions.attack.surface:extend({name='malgobek-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='malgobek-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='malgobek-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='malgobek-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='malgobek-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='malgobek-supply-blue'}), - presets.missions.supply.transfer:extend({name='malgobek-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='malgobek-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='malgobek-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Malgobek.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Kilo.lua ]]----------------- - -zones.kilo = ZoneCommand:new("Kilo") -zones.kilo.initialState = { side=1 } -zones.kilo:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='kilo-tent-red', - products = { - presets.special.red.infantry:extend({ name='kilo-defense-red'}), - presets.defenses.red.infantry:extend({ name='kilo-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='kilo-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='kilo-supply-red'}), - presets.missions.supply.transfer:extend({name='kilo-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='kilo-ammo-red', - products = { - presets.missions.attack.surface:extend({name='kilo-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='kilo-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='kilo-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='kilo-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='kilo-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='kilo-supply-blue'}), - presets.missions.supply.transfer:extend({name='kilo-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='kilo-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='kilo-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Kilo.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Quebec.lua ]]----------------- - -zones.quebec = ZoneCommand:new("Quebec") -zones.quebec.initialState = { side=1 } -zones.quebec.keepActive = true -zones.quebec:defineUpgrades({ - [1] = - { - presets.upgrades.basic.tent:extend({ - name='quebec-tent-red', - products = { - presets.special.red.infantry:extend({ name='quebec-defense-red'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='quebec-comcenter-red', - products = { - presets.defenses.red.sam:extend({ name='quebec-sam-red'}) - } - }) - }, - [2] = - { - presets.upgrades.basic.tent:extend({ - name='quebec-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='quebec-defense-blue'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='quebec-comcenter-blue', - products = { - presets.defenses.blue.sam:extend({ name='quebec-sam-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Quebec.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/OilFields.lua ]]----------------- - -zones.oilfields = ZoneCommand:new("Oil Fields") -zones.oilfields.initialState = { side=1 } -zones.oilfields:defineUpgrades({ - [1] = { - presets.upgrades.basic.outpost:extend({ - name='oilfields-outpost-red', - products = { - presets.special.red.infantry:extend({ name='oilfields-defense-red'}), - presets.defenses.red.infantry:extend({ name='oilfields-garrison-red'}) - } - }), - presets.upgrades.supply.oilPump:extend({ - name='oilfields-pump-red-1', - products = { - presets.missions.supply.transfer:extend({name='oilfields-transfer-red1'}) - } - }), - presets.upgrades.supply.oilPump:extend({ - name='oilfields-pump-red-2', - products = { - presets.missions.supply.convoy:extend({name='oilfields-supply-red-1'}) - } - }), - presets.upgrades.supply.oilPump:extend({ - name='oilfields-pump-red-3', - products = { - presets.missions.supply.transfer:extend({name='oilfields-transfer-red2'}) - } - }), - presets.upgrades.supply.oilPump:extend({ - name='oilfields-pump-red-4', - products = { - presets.missions.supply.convoy:extend({name='oilfields-supply-red-2'}) - } - }) - }, - [2] = { - presets.upgrades.basic.outpost:extend({ - name='oilfields-outpost-blue', - products = { - presets.special.blue.infantry:extend({ name='oilfields-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='oilfields-garrison-blue'}) - } - }), - presets.upgrades.supply.oilPump:extend({ - name='oilfields-pump-blue-1', - products = { - presets.missions.supply.transfer:extend({name='oilfields-transfer-blue1'}) - } - }), - presets.upgrades.supply.oilPump:extend({ - name='oilfields-pump-blue-2', - products = { - presets.missions.supply.convoy:extend({name='oilfields-supply-blue-1'}) - } - }), - presets.upgrades.supply.oilPump:extend({ - name='oilfields-pump-blue-3', - products = { - presets.missions.supply.transfer:extend({name='oilfields-transfer-blue2'}) - } - }), - presets.upgrades.supply.oilPump:extend({ - name='oilfields-pump-blue-4', - products = { - presets.missions.supply.convoy:extend({name='oilfields-supply-blue-2'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/OilFields.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Echo.lua ]]----------------- - -zones.echo = ZoneCommand:new("Echo") -zones.echo.initialState = { side=2 } -zones.echo:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='echo-tent-red', - products = { - presets.special.red.infantry:extend({ name='echo-defense-red'}), - presets.defenses.red.infantry:extend({ name='echo-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='echo-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='echo-supply-red'}), - presets.missions.supply.transfer:extend({name='echo-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='echo-ammo-red', - products = { - presets.missions.attack.surface:extend({name='echo-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='echo-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='echo-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='echo-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='echo-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='echo-supply-blue'}), - presets.missions.supply.transfer:extend({name='echo-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='echo-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='echo-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Echo.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Kobuleti.lua ]]----------------- - -zones.kobuleti = ZoneCommand:new("Kobuleti") -zones.kobuleti.initialState = { side=2 } -zones.kobuleti.keepActive = true -zones.kobuleti.isHeloSpawn = true -zones.kobuleti.isPlaneSpawn = true -zones.kobuleti.maxResource = 50000 -zones.kobuleti:defineUpgrades({ - [1] = - { - presets.upgrades.basic.comPost:extend({ - name='kobuleti-compost-red', - products = { - presets.special.red.infantry:extend({ name='kobuleti-defense-red'}), - presets.defenses.red.infantry:extend({ name='kobuleti-garrison-red'}), - presets.missions.attack.surface:extend({ name='kobuleti-assault-red'}) - } - }), - presets.upgrades.supply.fuelTank:extend({ - name='kobuleti-fuel-red', - products = { - presets.missions.supply.helo:extend({name='kobuleti-supply-red-1'}), - presets.missions.supply.helo:extend({name='kobuleti-supply-red-2'}), - presets.missions.supply.transfer:extend({name='kobuleti-transfer-red'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='kobuleti-comcenter-red', - products = { - presets.defenses.red.shorad:extend({ name='kobuleti-airdef-red'}), - presets.missions.attack.sead:extend({name='kobuleti-sead-red', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.attack.cas:extend({name='kobuleti-cas-red', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.bai:extend({name='kobuleti-cas-red', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.strike:extend({name='kobuleti-strike-red', altitude=20000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.patrol.aircraft:extend({name='kobuleti-patrol-red', altitude=25000, range=25}) - } - }) - }, - [2] = - { - presets.upgrades.basic.comPost:extend({ - name='kobuleti-compost-blue', - products = { - presets.special.blue.infantry:extend({ name='kobuleti-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='kobuleti-garrison-blue'}), - presets.missions.attack.surface:extend({ name='kobuleti-assault-blue'}) - } - }), - presets.upgrades.supply.fuelTank:extend({ - name='kobuleti-fuel-blue', - products = { - presets.missions.supply.helo:extend({name='kobuleti-supply-blue-1'}), - presets.missions.supply.helo:extend({name='kobuleti-supply-blue-2'}), - presets.missions.supply.transfer:extend({name='kobuleti-transfer-blue'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='kobuleti-comcenter-blue', - products = { - presets.defenses.blue.shorad:extend({ name='kobuleti-airdef-blue'}), - presets.missions.attack.sead:extend({name='kobuleti-sead-blue', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.attack.cas:extend({name='kobuleti-cas-blue', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.bai:extend({name='kobuleti-cas-blue', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.strike:extend({name='kobuleti-strike-blue', altitude=20000, expend=AI.Task.WeaponExpend.TWO}), - presets.missions.patrol.aircraft:extend({name='kobuleti-patrol-blue', altitude=25000, range=25}), - presets.missions.support.awacs:extend({name='kobuleti-awacs-blue', altitude=30000, freq=258.5}), - presets.missions.support.tanker:extend({name='kobuleti-tanker-blue', altitude=23000, freq=258, tacan='38', variant='Boom'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Kobuleti.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Alpha.lua ]]----------------- - -zones.alpha = ZoneCommand:new('Alpha') -zones.alpha.initialState = { side=2 } -zones.alpha:defineUpgrades({ - [1] = --red side - { - presets.upgrades.basic.tent:extend({ - name = 'alpha-tent-red', - products = { - presets.special.red.infantry:extend({ name='alpha-defense-red'}), - presets.defenses.red.infantry:extend({ name='alpha-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name = 'alpha-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({ name='alpha-supply-red'}), - presets.missions.supply.transfer:extend({name='alpha-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name = 'alpha-ammo-red', - products = { - presets.missions.attack.surface:extend({ name='alpha-assault-red'}) - } - }) - }, - [2] = --blue side - { - presets.upgrades.basic.tent:extend({ - name = 'alpha-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='alpha-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='alpha-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name = 'alpha-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({ name='alpha-supply-blue'}), - presets.missions.supply.transfer:extend({name='alpha-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name = 'alpha-ammo-blue', - products = { - presets.missions.attack.surface:extend({ name='alpha-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Alpha.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Foxtrot.lua ]]----------------- - -zones.foxtrot = ZoneCommand:new("Foxtrot") -zones.foxtrot.initialState = { side=2 } -zones.foxtrot:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='foxtrot-tent-red', - products = { - presets.special.red.infantry:extend({ name='foxtrot-defense-red'}), - presets.defenses.red.infantry:extend({ name='foxtrot-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='foxtrot-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='foxtrot-supply-red'}), - presets.missions.supply.transfer:extend({name='foxtrot-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='foxtrot-ammo-red', - products = { - presets.missions.attack.surface:extend({name='foxtrot-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='foxtrot-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='foxtrot-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='foxtrot-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='foxtrot-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='foxtrot-supply-blue'}), - presets.missions.supply.transfer:extend({name='foxtrot-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='foxtrot-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='foxtrot-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Foxtrot.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Sierra.lua ]]----------------- - -zones.sierra = ZoneCommand:new("Sierra") -zones.sierra.initialState = { side=1 } -zones.sierra.isHeloSpawn = true -zones.sierra:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='sierra-tent-red', - products = { - presets.special.red.infantry:extend({ name='sierra-defense-red'}), - presets.defenses.red.infantry:extend({ name='sierra-garrison-red'}) - } - }), - presets.upgrades.supply.fuelTankFarp:extend({ - name='sierra-fuel-red', - products = { - presets.missions.supply.helo:extend({name='sierra-supply-red'}), - presets.missions.supply.helo:extend({name='sierra-supply-red-1'}), - presets.missions.supply.transfer:extend({name='sierra-transfer-red'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='sierra-comcenter-red', - products = { - presets.defenses.red.shorad:extend({name='sierra-sam-red'}), - presets.missions.attack.helo:extend({name='sierra-cas-red', altitude=200, expend=AI.Task.WeaponExpend.HALF }) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='sierra-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='sierra-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='sierra-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelTankFarp:extend({ - name='sierra-fuel-blue', - products = { - presets.missions.supply.helo:extend({name='sierra-supply-blue'}), - presets.missions.supply.helo:extend({name='sierra-supply-blue-1'}), - presets.missions.supply.transfer:extend({name='sierra-transfer-blue'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='sierra-comcenter-blue', - products = { - presets.defenses.blue.shorad:extend({name='sierra-sam-blue'}), - presets.missions.attack.helo:extend({name='sierra-cas-blue', altitude=200, expend=AI.Task.WeaponExpend.HALF }) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Sierra.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Oni.lua ]]----------------- - -zones.oni = ZoneCommand:new("Oni") -zones.oni.initialState = { side=1 } -zones.oni:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='oni-tent-red', - products = { - presets.special.red.infantry:extend({ name='oni-defense-red'}), - presets.defenses.red.infantry:extend({ name='oni-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='oni-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='oni-supply-red'}), - presets.missions.supply.helo:extend({name='oni-supply-red-2'}), - presets.missions.supply.transfer:extend({name='oni-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='oni-ammo-red', - products = { - presets.missions.attack.surface:extend({name='oni-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='oni-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='oni-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='oni-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='oni-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='oni-supply-blue'}), - presets.missions.supply.helo:extend({name='oni-supply-blue-2'}), - presets.missions.supply.transfer:extend({name='oni-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='oni-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='oni-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Oni.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Hotel.lua ]]----------------- - -zones.hotel = ZoneCommand:new("Hotel") -zones.hotel.initialState = nil -zones.hotel:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='hotel-tent-red', - products = { - presets.special.red.infantry:extend({ name='hotel-defense-red'}), - presets.defenses.red.infantry:extend({ name='hotel-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='hotel-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='hotel-supply-red'}), - presets.missions.supply.transfer:extend({name='hotel-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='hotel-ammo-red', - products = { - presets.missions.attack.surface:extend({name='hotel-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='hotel-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='hotel-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='hotel-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='hotel-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='hotel-supply-blue'}), - presets.missions.supply.transfer:extend({name='hotel-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='hotel-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='hotel-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Hotel.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Victor.lua ]]----------------- - -zones.victor = ZoneCommand:new("Victor") -zones.victor.initialState = { side=1 } -zones.victor:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='victor-tent-red', - products = { - presets.special.red.infantry:extend({ name='victor-defense-red'}), - presets.defenses.red.infantry:extend({ name='victor-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='victor-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='victor-supply-red'}), - presets.missions.supply.helo:extend({name='victor-supply-red-2'}), - presets.missions.supply.transfer:extend({name='victor-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='victor-ammo-red', - products = { - presets.missions.attack.surface:extend({name='victor-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='victor-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='victor-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='victor-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='victor-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='victor-supply-blue'}), - presets.missions.supply.helo:extend({name='victor-supply-blue-2'}), - presets.missions.supply.transfer:extend({name='victor-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='victor-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='victor-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Victor.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Tango.lua ]]----------------- - -zones.tango = ZoneCommand:new("Tango") -zones.tango.initialState = { side=1 } -zones.tango.isHeloSpawn = true -zones.tango:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='tango-tent-red', - products = { - presets.special.red.infantry:extend({ name='tango-defense-red'}), - presets.defenses.red.infantry:extend({ name='tango-garrison-red'}) - } - }), - presets.upgrades.supply.fuelTankFarp:extend({ - name='tango-fuel-red', - products = { - presets.missions.supply.helo:extend({name='tango-supply-red'}), - presets.missions.supply.helo:extend({name='tango-supply-red-1'}), - presets.missions.supply.transfer:extend({name='tango-transfer-red'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='tango-comcenter-red', - products = { - presets.defenses.red.shorad:extend({name='tango-sam-red'}), - presets.missions.attack.helo:extend({name='tango-cas-red', altitude=200, expend=AI.Task.WeaponExpend.HALF }) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='tango-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='tango-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='tango-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelTankFarp:extend({ - name='tango-fuel-blue', - products = { - presets.missions.supply.helo:extend({name='tango-supply-blue'}), - presets.missions.supply.helo:extend({name='tango-supply-blue-1'}), - presets.missions.supply.transfer:extend({name='tango-transfer-blue'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='tango-comcenter-blue', - products = { - presets.defenses.blue.shorad:extend({name='tango-sam-blue'}), - presets.missions.attack.helo:extend({name='tango-cas-blue', altitude=200, expend=AI.Task.WeaponExpend.HALF }) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Tango.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Unal.lua ]]----------------- - -zones.unal = ZoneCommand:new("Unal") -zones.unal.initialState = { side=1 } -zones.unal.isHeloSpawn = true -zones.unal:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='unal-tent-red', - products = { - presets.special.red.infantry:extend({ name='unal-defense-red'}), - presets.defenses.red.infantry:extend({ name='unal-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='unal-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='unal-supply-red'}), - presets.missions.supply.helo:extend({name='unal-supply-red-2'}), - presets.missions.supply.transfer:extend({name='unal-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='unal-ammo-red', - products = { - presets.missions.attack.surface:extend({name='unal-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='unal-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='unal-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='unal-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='unal-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='unal-supply-blue'}), - presets.missions.supply.helo:extend({name='unal-supply-blue-2'}), - presets.missions.supply.transfer:extend({name='unal-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='unal-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='unal-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Unal.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Beslan.lua ]]----------------- - -zones.beslan = ZoneCommand:new("Beslan") -zones.beslan.initialState = { side=1 } -zones.beslan.keepActive = true -zones.beslan.maxResource = 50000 -zones.beslan:defineUpgrades({ - [1] = - { - presets.upgrades.basic.comPost:extend({ - name='beslan-compost-red', - products = { - presets.special.red.infantry:extend({ name='beslan-defense-red'}), - presets.defenses.red.infantry:extend({ name='beslan-garrison-red'}) - } - }), - presets.upgrades.supply.fuelTank:extend({ - name='beslan-fuel-red', - products = { - presets.missions.supply.helo:extend({name='beslan-supply-red-1'}), - presets.missions.supply.helo:extend({name='beslan-supply-red-2'}), - presets.missions.supply.convoy_escorted:extend({name='beslan-supply-red-3'}), - presets.missions.supply.transfer:extend({name='beslan-transfer-red'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='beslan-comcenter-red', - products = { - presets.defenses.red.sa5:extend({ name='beslan-airdef-red'}), - presets.missions.attack.sead:extend({name='beslan-sead-red', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.attack.strike:extend({name='beslan-strike-red', altitude=30000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.attack.strike:extend({name='beslan-strike-red-1', altitude=30000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.patrol.aircraft:extend({name='beslan-patrol-red', altitude=25000, range=25}) - } - }) - }, - [2] = - { - presets.upgrades.basic.comPost:extend({ - name='beslan-compost-blue', - products = { - presets.special.blue.infantry:extend({ name='beslan-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='beslan-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelTank:extend({ - name='beslan-fuel-blue', - products = { - presets.missions.supply.helo:extend({name='beslan-supply-blue-1'}), - presets.missions.supply.helo:extend({name='beslan-supply-blue-2'}), - presets.missions.supply.convoy_escorted:extend({name='beslan-supply-blue-3'}), - presets.missions.supply.transfer:extend({name='beslan-transfer-blue'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='beslan-comcenter-blue', - products = { - presets.defenses.blue.patriot:extend({ name='beslan-airdef-blue'}), - presets.missions.attack.sead:extend({name='beslan-sead-blue', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.attack.strike:extend({name='beslan-strike-blue', altitude=30000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.attack.strike:extend({name='beslan-strike-blue-1', altitude=30000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.patrol.aircraft:extend({name='beslan-patrol-blue', altitude=25000, range=25}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Beslan.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Bravo.lua ]]----------------- - -zones.bravo = ZoneCommand:new("Bravo") -zones.bravo.initialState = { side=2 } -zones.bravo:defineUpgrades({ - [1] = - { - presets.upgrades.basic.comPost:extend({ - name='bravo-compost-red', - products = { - presets.special.red.infantry:extend({ name='bravo-defense-red'}), - presets.defenses.red.infantry:extend({ name='bravo-garrison-red'}) - } - }), - presets.upgrades.supply.fuelTankFarp:extend({ - name='bravo-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({ name='bravo-supply-red'}), - presets.missions.supply.transfer:extend({name='bravo-transfer-red'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='bravo-comcenter-red', - products = { - presets.defenses.red.shorad:extend({ name='bravo-airdef-red'}), - presets.missions.attack.helo:extend({name='bravo-attack-red', altitude=200, expend=AI.Task.WeaponExpend.HALF}) - } - }) - }, - [2] = - { - presets.upgrades.basic.comPost:extend({ - name='bravo-compost-blue', - products = { - presets.special.blue.infantry:extend({ name='bravo-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='bravo-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelTankFarp:extend({ - name='bravo-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({ name='bravo-supply-blue'}), - presets.missions.supply.transfer:extend({name='bravo-transfer-blue'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='bravo-comcenter-blue', - products = { - presets.defenses.blue.shorad:extend({ name='bravo-airdef-blue'}), - presets.missions.attack.helo:extend({name='bravo-attack-blue', altitude=200, expend=AI.Task.WeaponExpend.HALF }) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Bravo.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/WeaponDepot.lua ]]----------------- - -zones.weapondepot = ZoneCommand:new("Weapon Depot") -zones.weapondepot.initialState = { side=1 } -zones.weapondepot:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='weapons-tent-red', - products = { - presets.special.red.infantry:extend({ name='weapons-defense-red'}), - presets.defenses.red.infantry:extend({ name='weapons-garrison-red'}) - } - }), - presets.upgrades.supply.ammoDepot:extend({ - name='weapons-ammo-red-1', - products = { - presets.missions.supply.convoy:extend({name='weapons-supply-red-1'}), - presets.missions.supply.transfer:extend({name='weapons-transfer-red-1'}) - } - }), - presets.upgrades.supply.ammoDepot:extend({ - name='weapons-ammo-red-2', - products = { - presets.missions.supply.convoy:extend({name='weapons-supply-red-2'}), - presets.missions.supply.transfer:extend({name='weapons-transfer-red-2'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='weapons-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='weapons-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='weapons-garrison-blue'}) - } - }), - presets.upgrades.supply.ammoDepot:extend({ - name='weapons-ammo-blue-1', - products = { - presets.missions.supply.convoy:extend({name='weapons-supply-blue-1'}), - presets.missions.supply.transfer:extend({name='weapons-transfer-blue-1'}) - } - }), - presets.upgrades.supply.ammoDepot:extend({ - name='weapons-ammo-blue-2', - products = { - presets.missions.supply.convoy:extend({name='weapons-supply-blue-2'}), - presets.missions.supply.transfer:extend({name='weapons-transfer-blue-2'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/WeaponDepot.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Delta.lua ]]----------------- - -zones.delta = ZoneCommand:new("Delta") -zones.delta.initialState = { side=2 } -zones.delta:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='delta-tent-red', - products = { - presets.special.red.infantry:extend({ name='delta-defense-red'}), - presets.defenses.red.infantry:extend({ name='delta-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='delta-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='delta-supply-red'}), - presets.missions.supply.transfer:extend({name='delta-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='delta-ammo-red', - products = { - presets.missions.attack.surface:extend({name='delta-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='delta-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='delta-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='delta-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='delta-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='delta-supply-blue'}), - presets.missions.supply.transfer:extend({name='delta-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='delta-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='delta-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Delta.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Cherkessk.lua ]]----------------- - -zones.cherkessk = ZoneCommand:new("Cherkessk") -zones.cherkessk.initialState = { side=1 } -zones.cherkessk.isHeloSpawn = true -zones.cherkessk:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='cherkessk-tent-red', - products = { - presets.special.red.infantry:extend({ name='cherkessk-defense-red'}), - presets.defenses.red.infantry:extend({ name='cherkessk-garrison-red'}) - } - }), - presets.upgrades.supply.fuelTankFarp:extend({ - name='cherkessk-fuel-red', - products = { - presets.missions.supply.helo:extend({name='cherkessk-supply-red'}), - presets.missions.supply.helo:extend({name='cherkessk-supply-red-1'}), - presets.missions.supply.transfer:extend({name='cherkessk-transfer-red'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='cherkessk-comcenter-red', - products = { - presets.defenses.red.shorad:extend({name='cherkessk-sam-red'}), - presets.missions.attack.helo:extend({name='cherkessk-cas-red', altitude=200, expend=AI.Task.WeaponExpend.HALF }), - presets.missions.attack.helo:extend({name='cherkessk-cas-red-1', altitude=200, expend=AI.Task.WeaponExpend.HALF }), - presets.missions.attack.surface:extend({name='cherkessk-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='cherkessk-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='cherkessk-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='cherkessk-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelTankFarp:extend({ - name='cherkessk-fuel-blue', - products = { - presets.missions.supply.helo:extend({name='cherkessk-supply-blue'}), - presets.missions.supply.helo:extend({name='cherkessk-supply-blue-1'}), - presets.missions.supply.transfer:extend({name='cherkessk-transfer-blue'}), - presets.missions.attack.surface:extend({name='cherkessk-assault-blue'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='cherkessk-comcenter-blue', - products = { - presets.defenses.blue.shorad:extend({name='cherkessk-sam-blue'}), - presets.missions.attack.helo:extend({name='cherkessk-cas-blue', altitude=200, expend=AI.Task.WeaponExpend.HALF }), - presets.missions.attack.helo:extend({name='cherkessk-cas-blue-1', altitude=200, expend=AI.Task.WeaponExpend.HALF }) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Cherkessk.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Juliett.lua ]]----------------- - -zones.juliett = ZoneCommand:new("Juliett") -zones.initialState = nil -zones.juliett:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='juliett-tent-red', - products = { - presets.special.red.infantry:extend({ name='juliett-defense-red'}), - presets.defenses.red.infantry:extend({ name='juliett-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='juliett-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='juliett-supply-red'}), - presets.missions.supply.transfer:extend({name='juliett-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='juliett-ammo-red', - products = { - presets.missions.attack.surface:extend({name='juliett-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='juliett-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='juliett-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='juliett-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='juliett-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='juliett-supply-blue'}), - presets.missions.supply.transfer:extend({name='juliett-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='juliett-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='juliett-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Juliett.lua ]]----------------- - - - - cm = ConnectionManager:new() - cm:addConnection('Batumi', 'Alpha') - cm:addConnection('Alpha', 'Bravo') - cm:addConnection('Bravo', 'Kobuleti') - cm:addConnection('Bravo', 'Factory') - cm:addConnection('Kobuleti', 'Factory') - cm:addConnection('Kobuleti', 'Charlie') - cm:addConnection('Foxtrot', 'Charlie') - cm:addConnection('Foxtrot', 'Kobuleti') - cm:addConnection('Delta','Foxtrot') - cm:addConnection('Delta','Kobuleti') - cm:addConnection('Delta','Factory') - cm:addConnection('Echo','Charlie') - cm:addConnection('Golf','Echo') - cm:addConnection('Golf','Foxtrot') - cm:addConnection('India','Delta') - cm:addConnection('Hotel','Golf') - cm:addConnection('Hotel','Foxtrot') - cm:addConnection('Hotel','Delta') - cm:addConnection('Hotel','India') - cm:addConnection('Juliett','Echo') - cm:addConnection('Juliett','Golf') - cm:addConnection('Senaki','Juliett') - cm:addConnection('Senaki','Golf') - cm:addConnection('Senaki','Hotel') - cm:addConnection('Kutaisi','Hotel') - cm:addConnection('Kutaisi','India') - cm:addConnection('Kilo','Juliett') - cm:addConnection('Mike','Kutaisi') - cm:addConnection('Mike','Senaki') - cm:addConnection('Romeo','Mike') - cm:addConnection('Romeo','Kutaisi') - cm:addConnection('Weapon Depot','Juliett') - cm:addConnection('Weapon Depot','Senaki') - cm:addConnection('Weapon Depot','Kilo') - cm:addConnection('November','Weapon Depot') - cm:addConnection('November','Senaki') - cm:addConnection('November','Mike') - cm:addConnection('Oil Fields','Romeo') - cm:addConnection('Quebec','Kilo') - cm:addConnection('Zugdidi','Weapon Depot') - cm:addConnection('Zugdidi','Quebec') - cm:addConnection('Zugdidi','November') - cm:addConnection('Zugdidi','Kilo') - cm:addConnection('Distillery','November') - cm:addConnection('Distillery','Mike') - cm:addConnection('Zugdidi','Papa') - cm:addConnection('November','Papa') - cm:addConnection('Sierra','Papa') - cm:addConnection('Sierra','Zugdidi') - cm:addConnection('Sierra','Uniform') - cm:addConnection('Mine','Uniform') - cm:addConnection('Tango','Quebec') - cm:addConnection('Tango','Zugdidi') - cm:addConnection('Sierra','Tango') - cm:addConnection('Whiskey','Tango') - cm:addConnection('Ochamchira','Tango') - cm:addConnection('Ochamchira','Whiskey') - cm:addConnection('Ochamchira','Farm') - cm:addConnection('Ochamchira','Zulu') - cm:addConnection('Farm','Zulu') - cm:addConnection('Sukhumi','Zulu') - cm:addConnection('Lentehi','Distillery', true, 3000) - cm:addConnection('Lentehi','Babugent', true, 5000) - cm:addConnection('Nalchik','Babugent') - cm:addConnection('Victor','Distillery', true, 2000) - cm:addConnection('Victor','Romeo') - cm:addConnection('Victor','Lentehi') - cm:addConnection('Victor','Oil Fields', true, 2000) - cm:addConnection('Victor','Oni') - cm:addConnection('Unal','Oni', true, 4500) - cm:addConnection('Beslan','Unal') - cm:addConnection('Digora','Beslan') - cm:addConnection('Digora','Unal') - cm:addConnection('Digora','Babugent') - cm:addConnection('Terek','Digora') - cm:addConnection('Terek','Nalchik') - cm:addConnection('Terek','Beslan') - cm:addConnection('Prohladniy','Terek') - cm:addConnection('Prohladniy','Nalchik') - cm:addConnection('Malgobek','Terek') - cm:addConnection('Malgobek','Beslan') - cm:addConnection('Lima','Mine') - cm:addConnection('Lima','Lentehi', true, 4000) - cm:addConnection('Tyrnyauz','Lima', true, 4000) - cm:addConnection('Tyrnyauz','Nalchik') - cm:addConnection('XRay','Sukhumi') - cm:addConnection('Oscar','Sukhumi') - cm:addConnection('Oscar','XRay') - cm:addConnection('Mozdok','Malgobek') - cm:addConnection('Mozdok','Prohladniy') - cm:addConnection('Gudauta','Oscar') - cm:addConnection('Yankee','Gudauta') - cm:addConnection('Sochi','Yankee') - cm:addConnection('Refinery','XRay', true, 4000) - cm:addConnection('Refinery','Humara') - cm:addConnection('Intel Center','Tyrnyauz') - cm:addConnection('Intel Center','Nalchik') - cm:addConnection('Intel Center','Prohladniy') - cm:addConnection('Intel Center','Kislovodsk') - cm:addConnection('Mineralnye','Intel Center') - cm:addConnection('Kislovodsk','Mineralnye') - cm:addConnection('Tallyk','Mineralnye') - cm:addConnection('Tallyk','Kislovodsk') - cm:addConnection('Power Plant','Mineralnye') - cm:addConnection('Power Plant','Tallyk') - cm:addConnection('Cherkessk','Tallyk') - cm:addConnection('Cherkessk','Power Plant') - cm:addConnection('Cherkessk','Humara') -end - ZoneCommand.setNeighbours(cm) bm = BattlefieldManager:new() @@ -4527,144 +104,3 @@ local offmapZones = { -- zones.gudauta, -- zones.kobuleti, } - -supplyPointRegistry = { - blue = {}, - red = {} -} - -for i,v in ipairs(blueSupply) do - local g = Group.getByName(v) - if g then - supplyPointRegistry.blue[v] = g:getUnit(1):getPoint() - end -end - -for i,v in ipairs(redSupply) do - local g = Group.getByName(v) - if g then - supplyPointRegistry.red[v] = g:getUnit(1):getPoint() - end -end - -offmapSupplyRegistry = {} -timer.scheduleFunction(function(param, time) - local availableBlue = {} - for i,v in ipairs(param.blue) do - if offmapSupplyRegistry[v] == nil then - table.insert(availableBlue, v) - end - end - - local availableRed = {} - for i,v in ipairs(param.red) do - if offmapSupplyRegistry[v] == nil then - table.insert(availableRed, v) - end - end - - local redtargets = {} - local bluetargets = {} - for _, zn in ipairs(param.offmapZones) do - if zn:needsSupplies(3000) then - local isOnRoute = false - for _,data in pairs(offmapSupplyRegistry) do - if data.zone.name == zn.name then - isOnRoute = true - break - end - end - if not isOnRoute then - if zn.side == 1 then - table.insert(redtargets, zn) - elseif zn.side == 2 then - table.insert(bluetargets, zn) - end - end - end - end - - if #availableRed > 0 and #redtargets > 0 then - local zn = redtargets[math.random(1,#redtargets)] - - local red = nil - local minD = 999999999 - for i,v in ipairs(availableRed) do - local d = mist.utils.get2DDist(zn.zone.point, supplyPointRegistry.red[v]) - if d < minD then - red = v - minD = d - end - end - - if not red then red = availableRed[math.random(1,#availableRed)] end - - local gr = red - red = nil - mist.respawnGroup(gr, true) - offmapSupplyRegistry[gr] = {zone = zn, assigned = timer.getAbsTime()} - env.info(gr..' was deployed') - timer.scheduleFunction(function(param,time) - local g = Group.getByName(param.group) - TaskExtensions.landAtAirfield(g, param.target.zone.point) - env.info(param.group..' going to '..param.target.name) - end, {group=gr, target=zn}, timer.getTime()+2) - end - - if #availableBlue > 0 and #bluetargets>0 then - local zn = bluetargets[math.random(1,#bluetargets)] - - local blue = nil - local minD = 999999999 - for i,v in ipairs(availableBlue) do - local d = mist.utils.get2DDist(zn.zone.point, supplyPointRegistry.blue[v]) - if d < minD then - blue = v - minD = d - end - end - - if not blue then blue = availableBlue[math.random(1,#availableBlue)] end - - local gr = blue - blue = nil - mist.respawnGroup(gr, true) - offmapSupplyRegistry[gr] = {zone = zn, assigned = timer.getAbsTime()} - env.info(gr..' was deployed') - timer.scheduleFunction(function(param,time) - local g = Group.getByName(param.group) - TaskExtensions.landAtAirfield(g, param.target.zone.point) - env.info(param.group..' going to '..param.target.name) - end, {group=gr, target=zn}, timer.getTime()+2) - end - - return time+(60*5) -end, {blue = blueSupply, red = redSupply, offmapZones = offmapZones}, timer.getTime()+60) - - - -timer.scheduleFunction(function(param, time) - - for groupname,data in pairs(offmapSupplyRegistry) do - local gr = Group.getByName(groupname) - if not gr then - offmapSupplyRegistry[groupname] = nil - env.info(groupname..' was destroyed') - end - - if gr and ((timer.getAbsTime() - data.assigned) > (60*60)) then - gr:destroy() - offmapSupplyRegistry[groupname] = nil - env.info(groupname..' despawned due to being alive for too long') - end - - if gr and Utils.allGroupIsLanded(gr) and Utils.someOfGroupInZone(gr, data.zone.name) then - data.zone:addResource(15000) - gr:destroy() - offmapSupplyRegistry[groupname] = nil - env.info(groupname..' landed at '..data.zone.name..' and delivered 15000 resources') - end - end - - return time+180 -end, {}, timer.getTime()+180) \ No newline at end of file diff --git a/resources/plugins/pretense/init_footer.lua b/resources/plugins/pretense/init_footer.lua index 9df8452d..2559cb93 100644 --- a/resources/plugins/pretense/init_footer.lua +++ b/resources/plugins/pretense/init_footer.lua @@ -1,4533 +1,3 @@ - - -local savefile = 'pretense_1.1.json' -if lfs then - local dir = lfs.writedir()..'Missions/Saves/' - lfs.mkdir(dir) - savefile = dir..savefile - env.info('Pretense - Save file path: '..savefile) -end - - -do - TemplateDB.templates["infantry-red"] = { - units = { - "BTR_D", - "T-90", - "T-90", - "Infantry AK ver2", - "Infantry AK", - "Infantry AK", - "Paratrooper RPG-16", - "Infantry AK ver3", - "SA-18 Igla manpad" - }, - skill = "Excellent", - dataCategory= TemplateDB.type.group - } - - TemplateDB.templates["infantry-blue"] = { - units = { - "M1045 HMMWV TOW", - "Soldier stinger", - "Soldier M4 GRG", - "Soldier M4 GRG", - "Soldier M4 GRG", - "Soldier M4 GRG", - "Soldier M4 GRG", - "M1043 HMMWV Armament" - }, - skill = "Excellent", - dataCategory= TemplateDB.type.group - } - - TemplateDB.templates["defense-red"] = { - units = { - "Infantry AK ver2", - "Infantry AK", - "Infantry AK ver3", - "Paratrooper RPG-16", - "SA-18 Igla manpad" - }, - skill = "Excellent", - dataCategory= TemplateDB.type.group - } - - TemplateDB.templates["defense-blue"] = { - units = { - "Soldier M4 GRG", - "Soldier M4 GRG", - "Soldier M4 GRG", - "Soldier RPG", - "Soldier stinger", - }, - skill = "Excellent", - dataCategory= TemplateDB.type.group - } - - TemplateDB.templates["shorad-red"] = { - units = { - "Strela-10M3", - "Strela-10M3", - "Ural-4320T", - "2S6 Tunguska" - }, - maxDist = 300, - skill = "Excellent", - dataCategory= TemplateDB.type.group - } - - TemplateDB.templates["shorad-blue"] = { - units = { - "Roland ADS", - "M48 Chaparral", - "M 818", - "Gepard", - "Gepard" - }, - maxDist = 300, - skill = "Excellent", - dataCategory= TemplateDB.type.group - } - - TemplateDB.templates["sam-red"] = { - units = { - "p-19 s-125 sr", - "Ural-4320T", - "Ural-4320T", - "S_75M_Volhov", - "S_75M_Volhov", - "S_75M_Volhov", - "S_75M_Volhov", - "S_75M_Volhov", - "Tor 9A331", - "SNR_75V" - }, - maxDist = 300, - skill = "Excellent", - dataCategory= TemplateDB.type.group - } - - TemplateDB.templates["sam-blue"] = { - units = { - "Hawk pcp", - "Hawk cwar", - "Hawk ln", - "Hawk ln", - "Hawk ln", - "Hawk ln", - "Hawk ln", - "Hawk tr", - "M 818", - "Hawk sr" - }, - maxDist = 300, - skill = "Good", - dataCategory= TemplateDB.type.group - } - - TemplateDB.templates["patriot"] = { - units = { - "Patriot cp", - "Patriot str", - "M 818", - "M 818", - "Patriot ln", - "Patriot ln", - "Patriot ln", - "Patriot ln", - "Patriot str", - "Patriot str", - "Patriot str", - "Patriot EPP", - "Patriot ECS", - "Patriot AMG" - }, - maxDist = 300, - skill = "Good", - dataCategory= TemplateDB.type.group - } - - TemplateDB.templates["sa3"] = { - units = { - "p-19 s-125 sr", - "snr s-125 tr", - "5p73 s-125 ln", - "5p73 s-125 ln", - "Ural-4320T", - "5p73 s-125 ln", - "5p73 s-125 ln" - }, - maxDist = 300, - skill = "Excellent", - dataCategory= TemplateDB.type.group - } - - TemplateDB.templates["sa6"] = { - units = { - "Kub 1S91 str", - "Kub 2P25 ln", - "Kub 2P25 ln", - "Kub 2P25 ln", - "Kub 2P25 ln", - "2S6 Tunguska", - "Ural-4320T", - "2S6 Tunguska", - "Kub 2P25 ln" - }, - maxDist = 300, - skill = "Excellent", - dataCategory= TemplateDB.type.group - } - - TemplateDB.templates["sa10"] = { - units = { - "S-300PS 54K6 cp", - "S-300PS 5P85C ln", - "S-300PS 5P85C ln", - "S-300PS 5P85C ln", - "GAZ-66", - "GAZ-66", - "GAZ-66", - "S-300PS 5P85C ln", - "S-300PS 5P85C ln", - "S-300PS 5P85C ln", - "S-300PS 40B6MD sr", - "S-300PS 40B6M tr", - "S-300PS 64H6E sr" - }, - maxDist = 300, - skill = "Excellent", - dataCategory= TemplateDB.type.group - } - - TemplateDB.templates["sa5"] = { - units = { - "RLS_19J6", - "Ural-4320T", - "Ural-4320T", - "RPC_5N62V", - "S-200_Launcher", - "S-200_Launcher", - "S-200_Launcher", - "S-200_Launcher", - "S-200_Launcher", - "S-200_Launcher" - }, - maxDist = 300, - skill = "Excellent", - dataCategory= TemplateDB.type.group - } - - TemplateDB.templates["sa11"] = { - units = { - "SA-11 Buk SR 9S18M1", - "SA-11 Buk LN 9A310M1", - "SA-11 Buk LN 9A310M1", - "SA-11 Buk LN 9A310M1", - "SA-11 Buk LN 9A310M1", - "SA-11 Buk LN 9A310M1", - "2S6 Tunguska", - "SA-11 Buk SR 9S18M1", - "GAZ-66", - "GAZ-66", - "SA-11 Buk CC 9S470M1" - }, - maxDist = 300, - skill = "Excellent", - dataCategory= TemplateDB.type.group - } - - TemplateDB.templates["nasams"] = { - units = { - "NASAMS_Command_Post", - "NASAMS_Radar_MPQ64F1", - "Vulcan", - "M 818", - "M 818", - "Roland ADS", - "Roland ADS", - "NASAMS_LN_C", - "NASAMS_LN_C", - "NASAMS_LN_C", - "NASAMS_LN_C", - "NASAMS_Radar_MPQ64F1", - "NASAMS_Radar_MPQ64F1", - "NASAMS_Radar_MPQ64F1" - }, - maxDist = 300, - skill = "Excellent", - dataCategory= TemplateDB.type.group - } -end - -presets = { - upgrades = { - basic = { - tent = Preset:new({ - display = 'Tent', - cost = 1500, - type = 'upgrade', - template = "tent" - }), - comPost = Preset:new({ - display = 'Barracks', - cost = 1500, - type = 'upgrade', - template = "barracks" - }), - outpost = Preset:new({ - display = 'Outpost', - cost = 1500, - type = 'upgrade', - template = "outpost" - }) - }, - attack = { - ammoCache = Preset:new({ - display = 'Ammo Cache', - cost = 1500, - type = 'upgrade', - template = "ammo-cache" - }), - ammoDepot = Preset:new({ - display = 'Ammo Depot', - cost = 2000, - type = 'upgrade', - template = "ammo-depot" - }) - }, - supply = { - fuelCache = Preset:new({ - display = 'Fuel Cache', - cost = 1500, - type = 'upgrade', - template = "fuel-cache" - }), - fuelTank = Preset:new({ - display = 'Fuel Tank', - cost = 1500, - type = 'upgrade', - template = "fuel-tank-big" - }), - fuelTankFarp = Preset:new({ - display = 'Fuel Tank', - cost = 1500, - type = 'upgrade', - template = "fuel-tank-small" - }), - factory1 = Preset:new({ - display='Factory', - cost = 2000, - type ='upgrade', - income = 20, - template = "factory-1" - }), - factory2 = Preset:new({ - display='Factory', - cost = 2000, - type ='upgrade', - income = 20, - template = "factory-2" - }), - factoryTank = Preset:new({ - display='Storage Tank', - cost = 1500, - type ='upgrade', - income = 10, - template = "chem-tank" - }), - ammoDepot = Preset:new({ - display = 'Ammo Depot', - cost = 2000, - type = 'upgrade', - income = 40, - template = "ammo-depot" - }), - oilPump = Preset:new({ - display = 'Oil Pump', - cost = 1500, - type = 'upgrade', - income = 20, - template = "oil-pump" - }), - hangar = Preset:new({ - display = 'Hangar', - cost = 2000, - type = 'upgrade', - income = 30, - template = "hangar" - }), - excavator = Preset:new({ - display = 'Excavator', - cost = 2000, - type = 'upgrade', - income = 20, - template = "excavator" - }), - farm1 = Preset:new({ - display = 'Farm House', - cost = 2000, - type = 'upgrade', - income = 40, - template = "farm-house-1" - }), - farm2 = Preset:new({ - display = 'Farm House', - cost = 2000, - type = 'upgrade', - income = 40, - template = "farm-house-2" - }), - refinery1 = Preset:new({ - display='Refinery', - cost = 2000, - type ='upgrade', - income = 100, - template = "factory-1" - }), - powerplant1 = Preset:new({ - display='Power Plant', - cost = 1500, - type ='upgrade', - income = 25, - template = "factory-1" - }), - powerplant2 = Preset:new({ - display='Power Plant', - cost = 1500, - type ='upgrade', - income = 25, - template = "factory-2" - }), - antenna = Preset:new({ - display='Antenna', - cost = 1000, - type ='upgrade', - income = 10, - template = "antenna" - }), - hq = Preset:new({ - display='HQ Building', - cost = 2000, - type ='upgrade', - income = 50, - template = "tv-tower" - }) - }, - airdef = { - comCenter = Preset:new({ - display = 'Command Center', - cost = 2500, - type = 'upgrade', - template = "command-center" - }) - } - }, - defenses = { - red = { - infantry = Preset:new({ - display = 'Infantry', - cost=2000, - type='defense', - template='infantry-red', - }), - shorad = Preset:new({ - display = 'SAM', - cost=2500, - type='defense', - template='shorad-red', - }), - sam = Preset:new({ - display = 'SAM', - cost=3000, - type='defense', - template='sam-red', - }), - sa10 = Preset:new({ - display = 'SAM', - cost=3000, - type='defense', - template='sa10', - }), - sa5 = Preset:new({ - display = 'SAM', - cost=3000, - type='defense', - template='sa5', - }), - sa3 = Preset:new({ - display = 'SAM', - cost=3000, - type='defense', - template='sa3', - }), - sa6 = Preset:new({ - display = 'SAM', - cost=3000, - type='defense', - template='sa6', - }), - sa11 = Preset:new({ - display = 'SAM', - cost=3000, - type='defense', - template='sa11', - }) - }, - blue = { - infantry = Preset:new({ - display = 'Infantry', - cost=2000, - type='defense', - template='infantry-blue', - }), - shorad = Preset:new({ - display = 'SAM', - cost=2500, - type='defense', - template='shorad-blue', - }), - sam = Preset:new({ - display = 'SAM', - cost=3000, - type='defense', - template='sam-blue', - }), - patriot = Preset:new({ - display = 'SAM', - cost=3000, - type='defense', - template='patriot', - }), - nasams = Preset:new({ - display = 'SAM', - cost=3000, - type='defense', - template='nasams', - }) - } - }, - missions = { - supply = { - convoy = Preset:new({ - display = 'Supply convoy', - cost = 4000, - type = 'mission', - missionType = ZoneCommand.missionTypes.supply_convoy - }), - convoy_escorted = Preset:new({ - display = 'Supply convoy', - cost = 3000, - type = 'mission', - missionType = ZoneCommand.missionTypes.supply_convoy - }), - helo = Preset:new({ - display = 'Supply helicopter', - cost = 2500, - type='mission', - missionType = ZoneCommand.missionTypes.supply_air - }), - transfer = Preset:new({ - display = 'Supply transfer', - cost = 1000, - type='mission', - missionType = ZoneCommand.missionTypes.supply_transfer - }) - }, - attack = { - surface = Preset:new({ - display = 'Ground assault', - cost = 100, - type = 'mission', - missionType = ZoneCommand.missionTypes.assault, - }), - cas = Preset:new({ - display = 'CAS', - cost = 200, - type='mission', - missionType = ZoneCommand.missionTypes.cas - }), - bai = Preset:new({ - display = 'BAI', - cost = 200, - type='mission', - missionType = ZoneCommand.missionTypes.bai - }), - strike = Preset:new({ - display = 'Strike', - cost = 300, - type='mission', - missionType = ZoneCommand.missionTypes.strike - }), - sead = Preset:new({ - display = 'SEAD', - cost = 200, - type='mission', - missionType = ZoneCommand.missionTypes.sead - }), - helo = Preset:new({ - display = 'CAS', - cost = 100, - type='mission', - missionType = ZoneCommand.missionTypes.cas_helo - }) - }, - patrol={ - aircraft = Preset:new({ - display= "Patrol", - cost = 100, - type='mission', - missionType = ZoneCommand.missionTypes.patrol - }) - }, - support ={ - awacs = Preset:new({ - display= "AWACS", - cost = 300, - type='mission', - bias='5', - missionType = ZoneCommand.missionTypes.awacs - }), - tanker = Preset:new({ - display= "Tanker", - cost = 200, - type='mission', - bias='2', - missionType = ZoneCommand.missionTypes.tanker - }) - } - }, - special = { - red = { - infantry = Preset:new({ - display = 'Infantry', - cost=-1, - type='defense', - template='defense-red', - }), - }, - blue = { - infantry = Preset:new({ - display = 'Infantry', - cost=-1, - type='defense', - template='defense-blue', - }) - } - } -} - -zones = {} -do - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Batumi.lua ]]----------------- - -zones.batumi = ZoneCommand:new('Batumi') -zones.batumi.initialState = { side=2 } -zones.batumi.keepActive = true -zones.batumi.isHeloSpawn = true -zones.batumi.isPlaneSpawn = true -zones.batumi.maxResource = 50000 -zones.batumi:defineUpgrades({ - [1] = { --red side - presets.upgrades.basic.comPost:extend({ - name = 'batumi-com-red', - products = { - presets.special.red.infantry:extend({ name='batumi-defense-red'}), - presets.defenses.red.infantry:extend({ name='batumi-garrison-red' }) - } - }), - }, - [2] = --blue side - { - presets.upgrades.basic.comPost:extend({ - name = 'batumi-com-blue', - products = { - presets.special.blue.infantry:extend({ name='batumi-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='batumi-garrison-blue' }) - } - }), - presets.upgrades.supply.fuelTank:extend({ - name = 'batumi-fueltank-blue', - products = { - presets.missions.supply.convoy_escorted:extend({ name='batumi-supply-convoy-1'}), - presets.missions.supply.helo:extend({ name='batumi-supply-blue-1' }), - presets.missions.supply.transfer:extend({name='batumi-transfer-blue'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name = 'batumi-mission-command-blue', - products = { - presets.defenses.blue.shorad:extend({ name='batumi-sam-blue' }), - presets.missions.attack.sead:extend({name='batumi-sead-blue-1', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.attack.cas:extend({name='batumi-cas-blue-1', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.bai:extend({name='batumi-cas-blue-1', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.strike:extend({name='batumi-strike-blue-1', altitude=20000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.patrol.aircraft:extend({name='batumi-patrol-blue-1', altitude=25000, range=25}), - presets.missions.support.awacs:extend({name='batumi-awacs-blue', altitude=30000, freq=257.5}), - presets.missions.support.tanker:extend({name='batumi-tanker-blue', altitude=25000, freq=257, tacan='37', variant="Drogue"}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Batumi.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Mike.lua ]]----------------- - -zones.mike = ZoneCommand:new("Mike") -zones.mike.initialState = { side=1 } -zones.mike.keepActive = true -zones.mike:defineUpgrades({ - [1] = - { - presets.upgrades.basic.tent:extend({ - name='mike-tent-red', - products = { - presets.special.red.infantry:extend({ name='mike-defense-red'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='mike-comcenter-red', - products = { - presets.defenses.red.sam:extend({ name='mike-sam-red'}) - } - }) - }, - [2] = - { - presets.upgrades.basic.tent:extend({ - name='mike-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='mike-defense-blue'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='mike-comcenter-blue', - products = { - presets.defenses.blue.sam:extend({ name='mike-sam-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Mike.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Tyrnyauz.lua ]]----------------- - -zones.tyrnyauz = ZoneCommand:new("Tyrnyauz") -zones.tyrnyauz.initialState = { side=1 } -zones.tyrnyauz.isHeloSpawn = true -zones.tyrnyauz:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='tyrnyauz-tent-red', - products = { - presets.special.red.infantry:extend({ name='tyrnyauz-defense-red'}), - presets.defenses.red.infantry:extend({ name='tyrnyauz-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='tyrnyauz-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='tyrnyauz-supply-red'}), - presets.missions.supply.helo:extend({name='tyrnyauz-supply-red-2'}), - presets.missions.supply.transfer:extend({name='tyrnyauz-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='tyrnyauz-ammo-red', - products = { - presets.missions.attack.surface:extend({name='tyrnyauz-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='tyrnyauz-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='tyrnyauz-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='tyrnyauz-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='tyrnyauz-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='tyrnyauz-supply-blue'}), - presets.missions.supply.helo:extend({name='tyrnyauz-supply-blue-2'}), - presets.missions.supply.transfer:extend({name='tyrnyauz-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='tyrnyauz-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='tyrnyauz-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Tyrnyauz.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/India.lua ]]----------------- - -zones.india = ZoneCommand:new("India") -zones.india.initialState = nil -zones.india:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='india-tent-red', - products = { - presets.special.red.infantry:extend({ name='india-defense-red'}), - presets.defenses.red.infantry:extend({ name='india-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='india-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='india-supply-red'}), - presets.missions.supply.transfer:extend({name='india-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='india-ammo-red', - products = { - presets.missions.attack.surface:extend({name='india-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='india-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='india-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='india-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='india-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='india-supply-blue'}), - presets.missions.supply.transfer:extend({name='india-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='india-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='india-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/India.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/IntelCenter.lua ]]----------------- - -zones.intelcenter = ZoneCommand:new("Intel Center") -zones.intelcenter.initialState = { side=1 } -zones.intelcenter:defineUpgrades({ - [1] = - { - presets.upgrades.basic.tent:extend({ - name='intelcenter-tent-red', - products = { - presets.special.red.infantry:extend({ name='intelcenter-defense-red'}), - presets.defenses.red.infantry:extend({ name='intelcenter-garrison-red'}) - } - }), - presets.upgrades.supply.hq:extend({ - name='intelcenter-hq-red', - products = { - presets.missions.supply.convoy:extend({ name='intelcenter-supply-red'}), - presets.missions.supply.convoy:extend({ name='intelcenter-supply-red-1'}), - presets.missions.supply.transfer:extend({name='intelcenter-transfer-red'}) - } - }), - presets.upgrades.supply.antenna:extend({ - name='intelcenter-antenna-red', - products = { - } - }), - presets.upgrades.supply.antenna:extend({ - name='intelcenter-antenna-red-1', - products = { - } - }), - presets.upgrades.supply.antenna:extend({ - name='intelcenter-antenna-red-2', - products = { - } - }) - }, - [2] = - { - presets.upgrades.basic.tent:extend({ - name='intelcenter-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='intelcenter-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='intelcenter-garrison-blue'}) - } - }), - presets.upgrades.supply.hq:extend({ - name='intelcenter-hq-blue', - products = { - presets.missions.supply.convoy:extend({ name='intelcenter-supply-blue'}), - presets.missions.supply.convoy:extend({ name='intelcenter-supply-blue-1'}), - presets.missions.supply.transfer:extend({name='intelcenter-transfer-blue'}) - } - }), - presets.upgrades.supply.antenna:extend({ - name='intelcenter-antenna-blue', - products = { - } - }), - presets.upgrades.supply.antenna:extend({ - name='intelcenter-antenna-blue-1', - products = { - } - }), - presets.upgrades.supply.antenna:extend({ - name='intelcenter-antenna-blue-2', - products = { - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/IntelCenter.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Mineralnye.lua ]]----------------- - -zones.mineralnye = ZoneCommand:new("Mineralnye") -zones.mineralnye.initialState = { side=1 } -zones.mineralnye.keepActive = true -zones.mineralnye.isHeloSpawn = true -zones.mineralnye.isPlaneSpawn = true -zones.mineralnye:defineUpgrades({ - [1] = - { - presets.upgrades.basic.comPost:extend({ - name='mineralnye-compost-red', - products = { - presets.special.red.infantry:extend({ name='mineralnye-defense-red'}), - presets.defenses.red.infantry:extend({ name='mineralnye-garrison-red'}) - } - }), - presets.upgrades.supply.fuelTank:extend({ - name='mineralnye-fuel-red', - products = { - presets.missions.supply.helo:extend({name='mineralnye-supply-red'}), - presets.missions.supply.helo:extend({name='mineralnye-supply-red-1'}), - presets.missions.supply.transfer:extend({name='mineralnye-transfer-red'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='mineralnye-comcenter-red', - products = { - presets.defenses.red.sa11:extend({ name='mineralnye-airdef-red'}), - presets.missions.attack.cas:extend({name='mineralnye-cas-red', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.bai:extend({name='mineralnye-cas-red', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.strike:extend({name='mineralnye-strike-red', altitude=30000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.attack.strike:extend({name='mineralnye-strike-red-1', altitude=30000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.patrol.aircraft:extend({name='mineralnye-patrol-red', altitude=25000, range=25}) - } - }) - }, - [2] = - { - presets.upgrades.basic.comPost:extend({ - name='mineralnye-compost-blue', - products = { - presets.special.blue.infantry:extend({ name='mineralnye-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='mineralnye-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelTank:extend({ - name='mineralnye-fuel-blue', - products = { - presets.missions.supply.helo:extend({name='mineralnye-supply-blue'}), - presets.missions.supply.helo:extend({name='mineralnye-supply-blue-1'}), - presets.missions.supply.transfer:extend({name='mineralnye-transfer-blue'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='mineralnye-comcenter-blue', - products = { - presets.defenses.blue.nasams:extend({ name='mineralnye-airdef-blue'}), - presets.missions.attack.cas:extend({name='mineralnye-cas-blue', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.bai:extend({name='mineralnye-cas-blue', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.strike:extend({name='mineralnye-strike-blue', altitude=30000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.attack.strike:extend({name='mineralnye-strike-blue-1', altitude=30000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.patrol.aircraft:extend({name='mineralnye-patrol-blue', altitude=25000, range=25}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Mineralnye.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/PowerPlant.lua ]]----------------- - -zones.powerplant = ZoneCommand:new("Power Plant") -zones.powerplant.initialState = { side=1 } -zones.powerplant:defineUpgrades({ - [1] = - { - presets.upgrades.basic.tent:extend({ - name='powerplant-tent-red', - products = { - presets.special.red.infantry:extend({ name='powerplant-defense-red'}), - presets.defenses.red.infantry:extend({ name='powerplant-garrison-red'}) - } - }), - presets.upgrades.supply.powerplant1:extend({ - name='powerplant-building-red-1', - products = { - presets.missions.supply.convoy:extend({ name='powerplant-supply-red'}), - presets.missions.supply.transfer:extend({name='powerplant-transfer-red'}) - } - }), - presets.upgrades.supply.powerplant2:extend({ - name='powerplant-building-red-2', - products = { - presets.missions.supply.convoy:extend({ name='powerplant-supply-red-1'}), - presets.missions.supply.transfer:extend({name='powerplant-transfer-red'}) - } - }) - }, - [2] = - { - presets.upgrades.basic.tent:extend({ - name='powerplant-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='powerplant-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='powerplant-garrison-blue'}) - } - }), - presets.upgrades.supply.powerplant1:extend({ - name='powerplant-building-blue-1', - products = { - presets.missions.supply.convoy:extend({ name='powerplant-supply-blue'}), - presets.missions.supply.transfer:extend({name='powerplant-transfer-blue'}) - } - }), - presets.upgrades.supply.powerplant2:extend({ - name='powerplant-building-blue-2', - products = { - presets.missions.supply.convoy:extend({ name='powerplant-supply-blue-1'}), - presets.missions.supply.transfer:extend({name='powerplant-transfer-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/PowerPlant.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Zugdidi.lua ]]----------------- - -zones.zugdidi = ZoneCommand:new("Zugdidi") -zones.zugdidi.initialState = { side=1 } -zones.zugdidi:defineUpgrades({ - [1] = - { - presets.upgrades.basic.comPost:extend({ - name='zugdidi-compost-red', - products = { - presets.missions.supply.transfer:extend({name='zugdidi-transfer-red'}), - presets.special.red.infantry:extend({ name='zugdidi-defense-red'}), - presets.defenses.red.infantry:extend({ name='zugdidi-garrison-red'}), - presets.missions.attack.surface:extend({name='zugdidi-attack-red'}), - presets.missions.supply.convoy:extend({name='zugdidi-supply-red'}) - } - }), - presets.upgrades.supply.hangar:extend({ - name='zugdidi-hangar-red-1', - products = { - presets.missions.supply.helo:extend({name='zugdidi-supply-red-1'}) - } - }), - presets.upgrades.supply.hangar:extend({ - name='zugdidi-hangar-red-2', - products = { - presets.missions.supply.helo:extend({name='zugdidi-supply-red-2'}) - } - }), - presets.upgrades.supply.hangar:extend({ - name='zugdidi-hangar-red-3', - products = { - presets.missions.supply.helo:extend({name='zugdidi-supply-red-3'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='zugdidi-comcenter-red', - products = { - presets.defenses.red.sa6:extend({ name='zugdidi-sam-red'}) - } - }) - }, - [2] = - { - presets.upgrades.basic.tent:extend({ - name='zugdidi-compost-blue', - products = { - presets.missions.supply.transfer:extend({name='zugdidi-transfer-blue'}), - presets.special.blue.infantry:extend({ name='zugdidi-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='zugdidi-garrison-blue'}), - presets.missions.attack.surface:extend({name='zugdidi-attack-blue'}), - presets.missions.supply.convoy:extend({name='zugdidi-supply-blue'}) - } - }), - presets.upgrades.supply.hangar:extend({ - name='zugdidi-hangar-blue-1', - products = { - presets.missions.supply.helo:extend({name='zugdidi-supply-blue-1'}) - } - }), - presets.upgrades.supply.hangar:extend({ - name='zugdidi-hangar-blue-2', - products = { - presets.missions.supply.helo:extend({name='zugdidi-supply-blue-2'}) - } - }), - presets.upgrades.supply.hangar:extend({ - name='zugdidi-hangar-blue-3', - products = { - presets.missions.supply.helo:extend({name='zugdidi-supply-blue-3'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='zugdidi-comcenter-blue', - products = { - presets.defenses.blue.shorad:extend({ name='zugdidi-sam-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Zugdidi.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Babugent.lua ]]----------------- - -zones.babugent = ZoneCommand:new("Babugent") -zones.babugent.initialState = { side=1 } -zones.babugent:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='babugent-tent-red', - products = { - presets.special.red.infantry:extend({ name='babugent-defense-red'}), - presets.defenses.red.infantry:extend({ name='babugent-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='babugent-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='babugent-supply-red'}), - presets.missions.supply.helo:extend({name='babugent-supply-red-2'}), - presets.missions.supply.transfer:extend({name='babugent-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='babugent-ammo-red', - products = { - presets.missions.attack.surface:extend({name='babugent-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='babugent-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='babugent-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='babugent-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='babugent-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='babugent-supply-blue'}), - presets.missions.supply.helo:extend({name='babugent-supply-blue-2'}), - presets.missions.supply.transfer:extend({name='babugent-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='babugent-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='babugent-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Babugent.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Kislovodsk.lua ]]----------------- - -zones.kislovodsk = ZoneCommand:new("Kislovodsk") -zones.kislovodsk.initialState = { side=1 } -zones.kislovodsk:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='kislovodsk-tent-red', - products = { - presets.special.red.infantry:extend({ name='kislovodsk-defense-red'}), - presets.defenses.red.infantry:extend({ name='kislovodsk-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='kislovodsk-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='kislovodsk-supply-red'}), - presets.missions.supply.transfer:extend({name='kislovodsk-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='kislovodsk-ammo-red', - products = { - presets.missions.attack.surface:extend({name='kislovodsk-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='kislovodsk-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='kislovodsk-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='kislovodsk-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='kislovodsk-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='kislovodsk-supply-blue'}), - presets.missions.supply.transfer:extend({name='kislovodsk-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='kislovodsk-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='kislovodsk-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Kislovodsk.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Gudauta.lua ]]----------------- - -zones.gudauta = ZoneCommand:new("Gudauta") -zones.gudauta.initialState = { side=1 } -zones.gudauta.keepActive = true -zones.gudauta.maxResource = 50000 -zones.gudauta:defineUpgrades({ - [1] = - { - presets.upgrades.basic.comPost:extend({ - name='gudauta-compost-red', - products = { - presets.special.red.infantry:extend({ name='gudauta-defense-red'}), - presets.defenses.red.infantry:extend({ name='gudauta-garrison-red'}) - } - }), - presets.upgrades.supply.fuelTank:extend({ - name='gudauta-fuel-red', - products = { - presets.missions.supply.helo:extend({name='gudauta-supply-red'}), - presets.missions.supply.helo:extend({name='gudauta-supply-red-1'}), - presets.missions.supply.transfer:extend({name='gudauta-transfer-red'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='gudauta-comcenter-red', - products = { - presets.defenses.red.sam:extend({ name='gudauta-airdef-red'}), - presets.missions.attack.sead:extend({name='gudauta-sead-red', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.attack.sead:extend({name='gudauta-sead-red-1', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.attack.cas:extend({name='gudauta-cas-red', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.bai:extend({name='gudauta-cas-red', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.patrol.aircraft:extend({name='gudauta-patrol-red', altitude=25000, range=25}) - } - }) - }, - [2] = - { - presets.upgrades.basic.comPost:extend({ - name='gudauta-compost-blue', - products = { - presets.special.blue.infantry:extend({ name='gudauta-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='gudauta-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelTank:extend({ - name='gudauta-fuel-blue', - products = { - presets.missions.supply.helo:extend({name='gudauta-supply-blue'}), - presets.missions.supply.helo:extend({name='gudauta-supply-blue-1'}), - presets.missions.supply.transfer:extend({name='gudauta-transfer-blue'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='gudauta-comcenter-blue', - products = { - presets.defenses.blue.sam:extend({ name='gudauta-airdef-blue'}), - presets.missions.attack.sead:extend({name='gudauta-sead-blue', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.attack.sead:extend({name='gudauta-sead-blue-1', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.attack.cas:extend({name='gudauta-cas-blue', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.bai:extend({name='gudauta-cas-blue', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.patrol.aircraft:extend({name='gudauta-patrol-blue', altitude=25000, range=25}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Gudauta.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Distillery.lua ]]----------------- - -zones.distillery = ZoneCommand:new("Distillery") -zones.distillery.initialState = { side=1 } -zones.distillery:defineUpgrades({ - [1] = - { - presets.upgrades.basic.tent:extend({ - name='distillery-tent-red', - products = { - presets.special.red.infantry:extend({ name='distillery-defense-red'}), - presets.defenses.red.infantry:extend({ name='distillery-garrison-red'}) - } - }), - presets.upgrades.supply.factory1:extend({ - name='distillery-prod-red-1', - products = { - presets.missions.supply.convoy:extend({ name='distillery-supply-red-1'}), - presets.missions.supply.transfer:extend({name='distillery-transfer-red'}) - } - }), - presets.upgrades.supply.factory2:extend({ - name='distillery-prod-red-2', - products = { - presets.missions.supply.convoy:extend({ name='distillery-supply-red-2', cost=2000}), - presets.missions.supply.transfer:extend({name='distillery-transfer-red2'}) - } - }), - presets.upgrades.supply.factoryTank:extend({ - name='distillery-tank-red-1', - products = { - } - }), - presets.upgrades.supply.factoryTank:extend({ - name='distillery-tank-red-2', - products = { - } - }), - presets.upgrades.supply.factoryTank:extend({ - name='distillery-tank-red-3', - products = { - } - }) - }, - [2] = - { - presets.upgrades.basic.tent:extend({ - name='distillery-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='distillery-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='distillery-garrison-blue'}) - } - }), - presets.upgrades.supply.factory1:extend({ - name='distillery-prod-blue-1', - products = { - presets.missions.supply.convoy:extend({ name='distillery-supply-blue-1'}), - presets.missions.supply.transfer:extend({name='distillery-transfer-blue'}) - } - }), - presets.upgrades.supply.factory2:extend({ - name='distillery-prod-blue-2', - products = { - presets.missions.supply.convoy:extend({ name='distillery-supply-blue-2', cost=2000}), - presets.missions.supply.transfer:extend({name='distillery-transfer-blue2'}) - } - }), - presets.upgrades.supply.factoryTank:extend({ - name='distillery-tank-blue-1', - products = { - } - }), - presets.upgrades.supply.factoryTank:extend({ - name='distillery-tank-blue-2', - products = { - } - }), - presets.upgrades.supply.factoryTank:extend({ - name='distillery-tank-blue-3', - products = { - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Distillery.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Sochi.lua ]]----------------- - -zones.sochi = ZoneCommand:new("Sochi") -zones.sochi.initialState = { side=1 } -zones.sochi.keepActive = true -zones.sochi.maxResource = 50000 -zones.sochi:defineUpgrades({ - [1] = - { - presets.upgrades.basic.comPost:extend({ - name='sochi-compost-red', - products = { - presets.special.red.infantry:extend({ name='sochi-defense-red'}), - presets.defenses.red.infantry:extend({ name='sochi-garrison-red'}) - } - }), - presets.upgrades.supply.fuelTank:extend({ - name='sochi-fuel-red', - products = { - presets.missions.supply.helo:extend({name='sochi-supply-red-1'}), - presets.missions.supply.helo:extend({name='sochi-supply-red-2'}), - presets.missions.supply.convoy_escorted:extend({name='sochi-supply-red-3'}), - presets.missions.supply.transfer:extend({name='sochi-transfer-red'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='sochi-comcenter-red', - products = { - presets.defenses.red.sa10:extend({ name='sochi-airdef-red'}), - presets.missions.attack.sead:extend({name='sochi-sead-red', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.attack.strike:extend({name='sochi-strike-red', altitude=30000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.patrol.aircraft:extend({name='sochi-patrol-red', altitude=25000, range=25}), - presets.missions.patrol.aircraft:extend({name='sochi-patrol-red-1', altitude=25000, range=25}), - presets.missions.attack.cas:extend({name='sochi-cas-red', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.bai:extend({name='sochi-cas-red', altitude=10000, expend=AI.Task.WeaponExpend.ONE}) - } - }) - }, - [2] = - { - presets.upgrades.basic.comPost:extend({ - name='sochi-compost-blue', - products = { - presets.special.blue.infantry:extend({ name='sochi-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='sochi-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelTank:extend({ - name='sochi-fuel-blue', - products = { - presets.missions.supply.helo:extend({name='sochi-supply-blue-1'}), - presets.missions.supply.helo:extend({name='sochi-supply-blue-2'}), - presets.missions.supply.convoy_escorted:extend({name='sochi-supply-blue-3'}), - presets.missions.supply.transfer:extend({name='sochi-transfer-blue'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='sochi-comcenter-blue', - products = { - presets.defenses.blue.patriot:extend({ name='sochi-airdef-blue'}), - presets.missions.attack.sead:extend({name='sochi-sead-blue', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.attack.strike:extend({name='sochi-strike-blue', altitude=30000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.patrol.aircraft:extend({name='sochi-patrol-blue', altitude=25000, range=25}), - presets.missions.attack.cas:extend({name='sochi-cas-blue', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.bai:extend({name='sochi-cas-blue', altitude=10000, expend=AI.Task.WeaponExpend.ONE}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Sochi.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Golf.lua ]]----------------- - -zones.golf = ZoneCommand:new("Golf") -zones.golf.initialState = nil -zones.golf.isHeloSpawn = true -zones.golf:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='golf-tent-red', - products = { - presets.special.red.infantry:extend({ name='golf-defense-red'}), - presets.defenses.red.infantry:extend({ name='golf-garrison-red'}) - } - }), - presets.upgrades.supply.fuelTank:extend({ - name='golf-fuel-red', - products = { - presets.missions.supply.helo:extend({name='golf-supply-red'}), - presets.missions.supply.helo:extend({name='golf-supply-red-1'}), - presets.missions.supply.transfer:extend({name='golf-transfer-red'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='golf-comcenter-red', - products = { - presets.defenses.red.shorad:extend({name='golf-sam-red'}), - presets.missions.attack.helo:extend({name='golf-cas-red', altitude=200, expend=AI.Task.WeaponExpend.HALF }) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='golf-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='golf-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='golf-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelTankFarp:extend({ - name='golf-fuel-blue', - products = { - presets.missions.supply.helo:extend({name='golf-supply-blue'}), - presets.missions.supply.helo:extend({name='golf-supply-blue-1'}), - presets.missions.supply.transfer:extend({name='golf-transfer-blue'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='golf-comcenter-blue', - products = { - presets.defenses.blue.shorad:extend({name='golf-sam-blue'}), - presets.missions.attack.helo:extend({name='golf-cas-blue', altitude=200, expend=AI.Task.WeaponExpend.HALF }) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Golf.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Charlie.lua ]]----------------- - -zones.charlie = ZoneCommand:new("Charlie") -zones.charlie.initialState = { side=2 } -zones.charlie.keepActive = true -zones.charlie:defineUpgrades({ - [1] = - { - presets.upgrades.basic.tent:extend({ - name='charlie-tent-red', - products = { - presets.special.red.infantry:extend({ name='charlie-defense-red'}), - presets.defenses.red.infantry:extend({ name='charlie-garrison-red'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='charlie-comcenter-red', - products = { - presets.defenses.red.sam:extend({ name='charlie-sam-red'}) - } - }) - }, - [2] = - { - presets.upgrades.basic.tent:extend({ - name='charlie-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='charlie-defense-red'}), - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='charlie-comcenter-blue', - products = { - presets.defenses.blue.sam:extend({ name='charlie-sam-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Charlie.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Lentehi.lua ]]----------------- - -zones.lentehi = ZoneCommand:new("Lentehi") -zones.lentehi.initialState = { side=1 } -zones.lentehi:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='lentehi-tent-red', - products = { - presets.special.red.infantry:extend({ name='lentehi-defense-red'}), - presets.defenses.red.infantry:extend({ name='lentehi-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='lentehi-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='lentehi-supply-red'}), - presets.missions.supply.helo:extend({name='lentehi-supply-red-2'}), - presets.missions.supply.transfer:extend({name='lentehi-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='lentehi-ammo-red', - products = { - presets.missions.attack.surface:extend({name='lentehi-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='lentehi-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='lentehi-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='lentehi-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='lentehi-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='lentehi-supply-blue'}), - presets.missions.supply.helo:extend({name='lentehi-supply-blue-2'}), - presets.missions.supply.transfer:extend({name='lentehi-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='lentehi-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='lentehi-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Lentehi.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Refinery.lua ]]----------------- - -zones.refinery = ZoneCommand:new("Refinery") -zones.refinery.initialState = { side=1 } -zones.refinery:defineUpgrades({ - [1] = - { - presets.upgrades.basic.tent:extend({ - name='refinery-tent-red', - products = { - presets.special.red.infantry:extend({ name='refinery-defense-red'}), - presets.defenses.red.infantry:extend({ name='refinery-garrison-red'}) - } - }), - presets.upgrades.supply.refinery1:extend({ - name='refinery-building-red', - products = { - presets.missions.supply.convoy:extend({ name='refinery-supply-red'}), - presets.missions.supply.convoy:extend({ name='refinery-supply-red-1'}), - presets.missions.supply.helo:extend({ name='refinery-supply-red-2'}), - presets.missions.supply.transfer:extend({name='refinery-transfer-red'}) - } - }) - }, - [2] = - { - presets.upgrades.basic.tent:extend({ - name='refinery-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='refinery-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='refinery-garrison-blue'}) - } - }), - presets.upgrades.supply.refinery1:extend({ - name='refinery-building-blue', - products = { - presets.missions.supply.convoy:extend({ name='refinery-supply-blue'}), - presets.missions.supply.convoy:extend({ name='refinery-supply-blue-1'}), - presets.missions.supply.helo:extend({ name='refinery-supply-blue-2'}), - presets.missions.supply.transfer:extend({name='refinery-transfer-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Refinery.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Mozdok.lua ]]----------------- - -zones.mozdok = ZoneCommand:new("Mozdok") -zones.mozdok.initialState = { side=1 } -zones.mozdok.keepActive = true -zones.mozdok.maxResource = 50000 -zones.mozdok:defineUpgrades({ - [1] = - { - presets.upgrades.basic.comPost:extend({ - name='mozdok-compost-red', - products = { - presets.special.red.infantry:extend({ name='mozdok-defense-red'}), - presets.defenses.red.infantry:extend({ name='mozdok-garrison-red'}) - } - }), - presets.upgrades.supply.fuelTank:extend({ - name='mozdok-fuel-red', - products = { - presets.missions.supply.helo:extend({name='mozdok-supply-red-1'}), - presets.missions.supply.helo:extend({name='mozdok-supply-red-2'}), - presets.missions.supply.convoy_escorted:extend({name='mozdok-supply-red-3'}), - presets.missions.supply.transfer:extend({name='mozdok-transfer-red'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='mozdok-comcenter-red', - products = { - presets.defenses.red.sa10:extend({ name='mozdok-airdef-red'}), - presets.missions.patrol.aircraft:extend({name='mozdok-patrol-red', altitude=25000, range=25}), - presets.missions.attack.cas:extend({name='mozdok-cas-red', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.bai:extend({name='mozdok-cas-red', altitude=10000, expend=AI.Task.WeaponExpend.ONE}) - } - }) - }, - [2] = - { - presets.upgrades.basic.comPost:extend({ - name='mozdok-compost-blue', - products = { - presets.special.blue.infantry:extend({ name='mozdok-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='mozdok-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelTank:extend({ - name='mozdok-fuel-blue', - products = { - presets.missions.supply.helo:extend({name='mozdok-supply-blue-1'}), - presets.missions.supply.helo:extend({name='mozdok-supply-blue-2'}), - presets.missions.supply.convoy_escorted:extend({name='mozdok-supply-blue-3'}), - presets.missions.supply.transfer:extend({name='mozdok-transfer-blue'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='mozdok-comcenter-blue', - products = { - presets.defenses.blue.shorad:extend({ name='mozdok-airdef-blue'}), - presets.missions.patrol.aircraft:extend({name='mozdok-patrol-blue', altitude=25000, range=25}), - presets.missions.attack.cas:extend({name='mozdok-cas-blue', altitude=15000, cost=50, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.cas:extend({name='mozdok-cas-blue-1', altitude=15000, cost=50, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.bai:extend({name='mozdok-cas-blue', altitude=15000, cost=50, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.bai:extend({name='mozdok-cas-blue-1', altitude=15000, cost=50, expend=AI.Task.WeaponExpend.ONE}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Mozdok.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Lima.lua ]]----------------- - -zones.lima = ZoneCommand:new("Lima") -zones.lima.initialState = { side=1 } -zones.lima:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='lima-tent-red', - products = { - presets.special.red.infantry:extend({ name='lima-defense-red'}), - presets.defenses.red.infantry:extend({ name='lima-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='lima-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='lima-supply-red'}), - presets.missions.supply.helo:extend({name='lima-supply-red-1'}), - presets.missions.supply.transfer:extend({name='lima-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='lima-ammo-red', - products = { - presets.missions.attack.surface:extend({name='lima-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='lima-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='lima-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='lima-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='lima-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='lima-supply-blue'}), - presets.missions.supply.helo:extend({name='lima-supply-blue-1'}), - presets.missions.supply.transfer:extend({name='lima-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='lima-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='lima-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Lima.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Oscar.lua ]]----------------- - -zones.oscar = ZoneCommand:new("Oscar") -zones.oscar.initialState = { side=1 } -zones.oscar:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='oscar-tent-red', - products = { - presets.special.red.infantry:extend({ name='oscar-defense-red'}), - presets.defenses.red.infantry:extend({ name='oscar-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='oscar-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='oscar-supply-red'}), - presets.missions.supply.transfer:extend({name='oscar-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='oscar-ammo-red', - products = { - presets.missions.attack.surface:extend({name='oscar-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='oscar-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='oscar-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='oscar-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='oscar-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='oscar-supply-blue'}), - presets.missions.supply.transfer:extend({name='oscar-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='oscar-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='oscar-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Oscar.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Nalchik.lua ]]----------------- - -zones.nalchik = ZoneCommand:new("Nalchik") -zones.nalchik.initialState = { side=1 } -zones.nalchik.keepActive = true -zones.nalchik.isHeloSpawn = true -zones.nalchik.isPlaneSpawn = true -zones.nalchik:defineUpgrades({ - [1] = - { - presets.upgrades.basic.comPost:extend({ - name='nalchik-compost-red', - products = { - presets.special.red.infantry:extend({ name='nalchik-defense-red'}), - presets.defenses.red.infantry:extend({ name='nalchik-garrison-red'}) - } - }), - presets.upgrades.supply.fuelTank:extend({ - name='nalchik-fuel-red', - products = { - presets.missions.supply.helo:extend({name='nalchik-supply-red-1'}), - presets.missions.supply.helo:extend({name='nalchik-supply-red-2'}), - presets.missions.supply.transfer:extend({name='nalchik-transfer-red'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='nalchik-comcenter-red', - products = { - presets.defenses.red.sa3:extend({ name='nalchik-airdef-red'}), - presets.missions.attack.sead:extend({name='nalchik-sead-red', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.attack.cas:extend({name='nalchik-cas-red', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.bai:extend({name='nalchik-cas-red', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.strike:extend({name='nalchik-strike-red', altitude=30000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.patrol.aircraft:extend({name='nalchik-patrol-red', altitude=25000, range=25}), - presets.missions.patrol.aircraft:extend({name='nalchik-patrol-red-2', altitude=25000, range=25}), - presets.missions.support.awacs:extend({name='nalchik-awacs-red', altitude=30000, freq=251.2}), - presets.missions.support.tanker:extend({name='nalchik-tanker-red', altitude=30000, freq=252.2, tacan='40', variant='Drogue'}) - } - }) - }, - [2] = - { - presets.upgrades.basic.comPost:extend({ - name='nalchik-compost-blue', - products = { - presets.special.blue.infantry:extend({ name='nalchik-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='nalchik-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelTank:extend({ - name='nalchik-fuel-blue', - products = { - presets.missions.supply.helo:extend({name='nalchik-supply-blue-1'}), - presets.missions.supply.helo:extend({name='nalchik-supply-blue-2'}), - presets.missions.supply.transfer:extend({name='nalchik-transfer-blue'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='nalchik-comcenter-blue', - products = { - presets.defenses.blue.nasams:extend({ name='nalchik-airdef-blue'}), - presets.missions.support.awacs:extend({name='nalchik-awacs-blue', altitude=30000, freq=259.5}), - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Nalchik.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Digora.lua ]]----------------- - -zones.digora = ZoneCommand:new("Digora") -zones.digora.initialState = { side=1 } -zones.digora:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='digora-tent-red', - products = { - presets.special.red.infantry:extend({ name='digora-defense-red'}), - presets.defenses.red.infantry:extend({ name='digora-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='digora-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='digora-supply-red'}), - presets.missions.supply.transfer:extend({name='digora-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='digora-ammo-red', - products = { - presets.missions.attack.surface:extend({name='digora-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='digora-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='digora-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='digora-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='digora-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='digora-supply-blue'}), - presets.missions.supply.transfer:extend({name='digora-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='digora-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='digora-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Digora.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Uniform.lua ]]----------------- - -zones.uniform = ZoneCommand:new("Uniform") -zones.uniform.initialState = { side=1 } -zones.uniform:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='uniform-tent-red', - products = { - presets.special.red.infantry:extend({ name='uniform-defense-red'}), - presets.defenses.red.infantry:extend({ name='uniform-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='uniform-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='uniform-supply-red'}), - presets.missions.supply.transfer:extend({name='uniform-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='uniform-ammo-red', - products = { - presets.missions.attack.surface:extend({name='uniform-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='uniform-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='uniform-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='uniform-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='uniform-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='uniform-supply-blue'}), - presets.missions.supply.transfer:extend({name='uniform-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='uniform-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='uniform-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Uniform.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Factory.lua ]]----------------- - -zones.factory = ZoneCommand:new("Factory") -zones.factory.initialState = { side=2 } -zones.factory:defineUpgrades({ - [1] = - { - presets.upgrades.basic.tent:extend({ - name='factory-tent-red', - products = { - presets.special.red.infantry:extend({ name='factory-defense-red'}), - presets.defenses.red.infantry:extend({ name='factory-garrison-red'}) - } - }), - presets.upgrades.supply.factory1:extend({ - name='factory-prod-red-1', - products = { - presets.missions.supply.convoy:extend({ name='factory-supply-red-1'}), - presets.missions.supply.transfer:extend({name='factory-transfer-red'}) - } - }), - presets.upgrades.supply.factory2:extend({ - name='factory-prod-red-2', - products = { - presets.missions.supply.convoy:extend({ name='factory-supply-red-2', cost=2000}), - presets.missions.supply.transfer:extend({name='factory-transfer-red2'}) - } - }), - presets.upgrades.supply.factoryTank:extend({ - name='factory-tank-red-1', - products = { - } - }), - presets.upgrades.supply.factoryTank:extend({ - name='factory-tank-red-2', - products = { - } - }), - presets.upgrades.supply.factoryTank:extend({ - name='factory-tank-red-3', - products = { - } - }) - }, - [2] = - { - presets.upgrades.basic.tent:extend({ - name='factory-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='factory-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='factory-garrison-blue'}) - } - }), - presets.upgrades.supply.factory1:extend({ - name='factory-prod-blue-1', - products = { - presets.missions.supply.convoy:extend({ name='factory-supply-blue-1'}), - presets.missions.supply.transfer:extend({name='factory-transfer-blue'}) - } - }), - presets.upgrades.supply.factory2:extend({ - name='factory-prod-blue-2', - products = { - presets.missions.supply.convoy:extend({ name='factory-supply-blue-2', cost=2000}), - presets.missions.supply.transfer:extend({name='factory-transfer-blue2'}) - } - }), - presets.upgrades.supply.factoryTank:extend({ - name='factory-tank-blue-1', - products = { - } - }), - presets.upgrades.supply.factoryTank:extend({ - name='factory-tank-blue-2', - products = { - } - }), - presets.upgrades.supply.factoryTank:extend({ - name='factory-tank-blue-3', - products = { - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Factory.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Senaki.lua ]]----------------- - -zones.senaki = ZoneCommand:new("Senaki") -zones.senaki.initialState = { side=1 } -zones.senaki.keepActive = true -zones.senaki.isHeloSpawn = true -zones.senaki.isPlaneSpawn = true -zones.senaki.maxResource = 50000 -zones.senaki:defineUpgrades({ - [1] = - { - presets.upgrades.basic.comPost:extend({ - name='senaki-compost-red', - products = { - presets.special.red.infantry:extend({ name='senaki-defense-red'}), - presets.defenses.red.infantry:extend({ name='senaki-garrison-red'}) - } - }), - presets.upgrades.supply.fuelTank:extend({ - name='senaki-fuel-red', - products = { - presets.missions.supply.helo:extend({name='senaki-supply-red-1'}), - presets.missions.supply.helo:extend({name='senaki-supply-red-2'}), - presets.missions.supply.transfer:extend({name='senaki-transfer-red'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='senaki-comcenter-red', - products = { - presets.defenses.red.sa3:extend({ name='senaki-airdef-red'}), - presets.missions.attack.sead:extend({name='senaki-sead-red', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.attack.cas:extend({name='senaki-cas-red', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.bai:extend({name='senaki-cas-red', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.strike:extend({name='senaki-strike-red', altitude=20000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.patrol.aircraft:extend({name='senaki-patrol-red', altitude=25000, range=25}), - presets.missions.patrol.aircraft:extend({name='senaki-patrol-red-2', altitude=20000, range=25}) - } - }) - }, - [2] = - { - presets.upgrades.basic.comPost:extend({ - name='senaki-compost-blue', - products = { - presets.special.blue.infantry:extend({ name='senaki-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='senaki-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelTank:extend({ - name='senaki-fuel-blue', - products = { - presets.missions.supply.helo:extend({name='senaki-supply-blue-1'}), - presets.missions.supply.helo:extend({name='senaki-supply-blue-2'}), - presets.missions.supply.transfer:extend({name='senaki-transfer-blue'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='senaki-comcenter-blue', - products = { - presets.defenses.blue.shorad:extend({ name='senaki-airdef-blue'}), - presets.missions.attack.sead:extend({name='senaki-sead-blue', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.attack.cas:extend({name='senaki-cas-blue', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.bai:extend({name='senaki-cas-blue', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.strike:extend({name='senaki-strike-blue', altitude=20000, expend=AI.Task.WeaponExpend.TWO}), - presets.missions.patrol.aircraft:extend({name='senaki-patrol-blue', altitude=25000, range=25}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Senaki.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Kutaisi.lua ]]----------------- - -zones.kutaisi = ZoneCommand:new("Kutaisi") -zones.kutaisi.initialState = { side=1 } -zones.kutaisi.keepActive = true -zones.kutaisi.maxResource = 50000 -zones.kutaisi:defineUpgrades({ - [1] = - { - presets.upgrades.basic.comPost:extend({ - name='kutaisi-compost-red', - products = { - presets.special.red.infantry:extend({ name='kutaisi-defense-red'}), - presets.defenses.red.infantry:extend({ name='kutaisi-garrison-red'}) - } - }), - presets.upgrades.supply.fuelTank:extend({ - name='kutaisi-fuel-red', - products = { - presets.missions.supply.helo:extend({name='kutaisi-supply-red-1'}), - presets.missions.supply.helo:extend({name='kutaisi-supply-red-2'}), - presets.missions.supply.transfer:extend({name='kutaisi-transfer-red'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='kutaisi-comcenter-red', - products = { - presets.defenses.red.shorad:extend({ name='kutaisi-airdef-red'}), - presets.missions.attack.sead:extend({name='kutaisi-sead-red', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.attack.cas:extend({name='kutaisi-cas-red', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.bai:extend({name='kutaisi-cas-red', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.strike:extend({name='kutaisi-strike-red', altitude=20000, expend=AI.Task.WeaponExpend.HALF}), - presets.missions.patrol.aircraft:extend({name='kutaisi-patrol-red', altitude=25000, range=25}), - presets.missions.patrol.aircraft:extend({name='kutaisi-patrol-red-2', altitude=25000, range=25}) - } - }) - }, - [2] = - { - presets.upgrades.basic.comPost:extend({ - name='kutaisi-compost-blue', - products = { - presets.special.blue.infantry:extend({ name='kutaisi-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='kutaisi-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelTank:extend({ - name='kutaisi-fuel-blue', - products = { - presets.missions.supply.helo:extend({name='kutaisi-supply-blue-1'}), - presets.missions.supply.helo:extend({name='kutaisi-supply-blue-2'}), - presets.missions.supply.transfer:extend({name='kutaisi-transfer-blue'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='kutaisi-comcenter-blue', - products = { - presets.defenses.blue.shorad:extend({ name='kutaisi-airdef-blue'}), - presets.missions.attack.sead:extend({name='kutaisi-sead-blue', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.attack.cas:extend({name='kutaisi-cas-blue', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.bai:extend({name='kutaisi-cas-blue', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.strike:extend({name='kutaisi-strike-blue', altitude=20000, expend=AI.Task.WeaponExpend.TWO}), - presets.missions.patrol.aircraft:extend({name='kutaisi-patrol-blue', altitude=25000, range=25}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Kutaisi.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Prohladniy.lua ]]----------------- - -zones.prohladniy = ZoneCommand:new("Prohladniy") -zones.prohladniy.initialState = { side=1 } -zones.prohladniy:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='prohladniy-tent-red', - products = { - presets.special.red.infantry:extend({ name='prohladniy-defense-red'}), - presets.defenses.red.infantry:extend({ name='prohladniy-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='prohladniy-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='prohladniy-supply-red'}), - presets.missions.supply.transfer:extend({name='prohladniy-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='prohladniy-ammo-red', - products = { - presets.missions.attack.surface:extend({name='prohladniy-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='prohladniy-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='prohladniy-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='prohladniy-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='prohladniy-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='prohladniy-supply-blue'}), - presets.missions.supply.transfer:extend({name='prohladniy-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='prohladniy-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='prohladniy-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Prohladniy.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Tallyk.lua ]]----------------- - -zones.tallyk = ZoneCommand:new("Tallyk") -zones.tallyk.initialState = { side=1 } -zones.tallyk.keepActive = true -zones.tallyk:defineUpgrades({ - [1] = - { - presets.upgrades.basic.tent:extend({ - name='tallyk-tent-red', - products = { - presets.special.red.infantry:extend({ name='tallyk-defense-red'}), - presets.defenses.red.infantry:extend({ name='tallyk-garrison-red'}), - presets.missions.attack.surface:extend({name='tallyk-assault-red'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='tallyk-comcenter-red', - products = { - presets.defenses.red.sam:extend({ name='tallyk-sam-red'}) - } - }) - }, - [2] = - { - presets.upgrades.basic.tent:extend({ - name='tallyk-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='tallyk-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='tallyk-garrison-blue'}), - presets.missions.attack.surface:extend({name='tallyk-assault-blue'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='tallyk-comcenter-blue', - products = { - presets.defenses.blue.sam:extend({ name='tallyk-sam-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Tallyk.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Terek.lua ]]----------------- - -zones.terek = ZoneCommand:new("Terek") -zones.terek.initialState = { side=1 } -zones.terek:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='terek-tent-red', - products = { - presets.special.red.infantry:extend({ name='terek-defense-red'}), - presets.defenses.red.infantry:extend({ name='terek-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='terek-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='terek-supply-red'}), - presets.missions.supply.transfer:extend({name='terek-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='terek-ammo-red', - products = { - presets.missions.attack.surface:extend({name='terek-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='terek-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='terek-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='terek-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='terek-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='terek-supply-blue'}), - presets.missions.supply.transfer:extend({name='terek-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='terek-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='terek-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Terek.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Humara.lua ]]----------------- - -zones.humara = ZoneCommand:new("Humara") -zones.humara.initialState = { side=1 } -zones.humara:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='humara-tent-red', - products = { - presets.special.red.infantry:extend({ name='humara-defense-red'}), - presets.defenses.red.infantry:extend({ name='humara-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='humara-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='humara-supply-red'}), - presets.missions.supply.transfer:extend({name='humara-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='humara-ammo-red', - products = { - presets.missions.attack.surface:extend({name='humara-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='humara-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='humara-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='humara-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='humara-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='humara-supply-blue'}), - presets.missions.supply.transfer:extend({name='humara-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='humara-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='humara-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Humara.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Ochamchira.lua ]]----------------- - -zones.ochamchira = ZoneCommand:new("Ochamchira") -zones.ochamchira.initialState = { side=1 } -zones.ochamchira:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='ochamchira-tent-red', - products = { - presets.special.red.infantry:extend({ name='ochamchira-defense-red'}), - presets.defenses.red.infantry:extend({ name='ochamchira-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='ochamchira-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='ochamchira-supply-red'}), - presets.missions.supply.transfer:extend({name='ochamchira-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='ochamchira-ammo-red', - products = { - presets.missions.attack.surface:extend({name='ochamchira-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='ochamchira-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='ochamchira-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='ochamchira-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='ochamchira-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='ochamchira-supply-blue'}), - presets.missions.supply.transfer:extend({name='ochamchira-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='ochamchira-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='ochamchira-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Ochamchira.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/November.lua ]]----------------- - -zones.november = ZoneCommand:new("November") -zones.november.initialState = { side=1 } -zones.november.isHeloSpawn = true -zones.november:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='november-tent-red', - products = { - presets.special.red.infantry:extend({ name='november-defense-red'}), - presets.defenses.red.infantry:extend({ name='november-garrison-red'}) - } - }), - presets.upgrades.supply.fuelTankFarp:extend({ - name='november-fuel-red', - products = { - presets.missions.supply.helo:extend({name='november-supply-red'}), - presets.missions.supply.helo:extend({name='november-supply-red-1'}), - presets.missions.supply.transfer:extend({name='november-transfer-red'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='november-comcenter-red', - products = { - presets.defenses.red.shorad:extend({name='november-sam-red'}), - presets.missions.attack.helo:extend({name='november-cas-red', altitude=200, expend=AI.Task.WeaponExpend.HALF }) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='november-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='november-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='november-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelTankFarp:extend({ - name='november-fuel-blue', - products = { - presets.missions.supply.helo:extend({name='november-supply-blue'}), - presets.missions.supply.helo:extend({name='november-supply-blue-1'}), - presets.missions.supply.transfer:extend({name='november-transfer-blue'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='november-comcenter-blue', - products = { - presets.defenses.blue.shorad:extend({name='november-sam-blue'}), - presets.missions.attack.helo:extend({name='november-cas-blue', altitude=200, expend=AI.Task.WeaponExpend.HALF }) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/November.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/XRay.lua ]]----------------- - -zones.xray = ZoneCommand:new("XRay") -zones.xray.initialState = { side=1 } -zones.xray:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='xray-tent-red', - products = { - presets.special.red.infantry:extend({ name='xray-defense-red'}), - presets.defenses.red.infantry:extend({ name='xray-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='xray-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='xray-supply-red'}), - presets.missions.supply.helo:extend({name='xray-supply-red-2'}), - presets.missions.supply.transfer:extend({name='xray-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='xray-ammo-red', - products = { - presets.missions.attack.surface:extend({name='xray-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='xray-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='xray-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='xray-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='xray-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='xray-supply-blue'}), - presets.missions.supply.helo:extend({name='xray-supply-blue-2'}), - presets.missions.supply.transfer:extend({name='xray-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='xray-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='xray-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/XRay.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Whiskey.lua ]]----------------- - -zones.whiskey = ZoneCommand:new("Whiskey") -zones.whiskey.initialState = { side=1 } -zones.whiskey:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='whiskey-tent-red', - products = { - presets.special.red.infantry:extend({ name='whiskey-defense-red'}), - presets.defenses.red.infantry:extend({ name='whiskey-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='whiskey-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='whiskey-supply-red'}), - presets.missions.supply.transfer:extend({name='whiskey-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='whiskey-ammo-red', - products = { - presets.missions.attack.surface:extend({name='whiskey-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='whiskey-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='whiskey-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='whiskey-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='whiskey-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='whiskey-supply-blue'}), - presets.missions.supply.transfer:extend({name='whiskey-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='whiskey-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='whiskey-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Whiskey.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Mine.lua ]]----------------- - -zones.mine = ZoneCommand:new("Mine") -zones.mine.initialState = { side=1 } -zones.mine:defineUpgrades({ - [1] = - { - presets.upgrades.basic.tent:extend({ - name='mine-tent-red', - products = { - presets.special.red.infantry:extend({ name='mine-defense-red'}), - presets.defenses.red.infantry:extend({ name='mine-garrison-red'}) - } - }), - presets.upgrades.supply.excavator:extend({ - name='mine-excavator-red-1', - products = { - presets.missions.supply.convoy:extend({ name='mine-supply-red'}), - presets.missions.supply.transfer:extend({name='mine-transfer-red'}) - } - }), - presets.upgrades.supply.excavator:extend({ - name='mine-excavator-red-2', - products = { - presets.missions.supply.convoy:extend({ name='mine-supply-red'}), - presets.missions.supply.transfer:extend({name='mine-transfer-red'}) - } - }), - presets.upgrades.supply.excavator:extend({ - name='mine-excavator-red-3', - products = { - presets.missions.supply.convoy:extend({ name='mine-supply-red'}), - presets.missions.supply.transfer:extend({name='mine-transfer-red'}) - } - }) - }, - [2] = - { - presets.upgrades.basic.tent:extend({ - name='mine-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='mine-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='mine-garrison-blue'}) - } - }), - presets.upgrades.supply.excavator:extend({ - name='mine-excavator-blue-1', - products = { - presets.missions.supply.convoy:extend({ name='mine-supply-blue'}), - presets.missions.supply.transfer:extend({name='mine-transfer-blue'}) - } - }), - presets.upgrades.supply.excavator:extend({ - name='mine-excavator-blue-2', - products = { - presets.missions.supply.convoy:extend({ name='mine-supply-blue'}), - presets.missions.supply.transfer:extend({name='mine-transfer-blue'}) - } - }), - presets.upgrades.supply.excavator:extend({ - name='mine-excavator-blue-3', - products = { - presets.missions.supply.convoy:extend({ name='mine-supply-blue'}), - presets.missions.supply.transfer:extend({name='mine-transfer-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Mine.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Papa.lua ]]----------------- - -zones.papa = ZoneCommand:new("Papa") -zones.papa.initialState = { side=1 } -zones.papa.keepActive = true -zones.papa:defineUpgrades({ - [1] = - { - presets.upgrades.basic.tent:extend({ - name='papa-tent-red', - products = { - presets.special.red.infantry:extend({ name='papa-defense-red'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='papa-comcenter-red', - products = { - presets.defenses.red.sam:extend({ name='papa-sam-red'}) - } - }) - }, - [2] = - { - presets.upgrades.basic.tent:extend({ - name='papa-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='papa-defense-blue'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='papa-comcenter-blue', - products = { - presets.defenses.blue.sam:extend({ name='papa-sam-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Papa.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Sukhumi.lua ]]----------------- - -zones.sukhumi = ZoneCommand:new("Sukhumi") -zones.sukhumi.initialState = { side=1 } -zones.sukhumi.keepActive = true -zones.sukhumi.maxResource = 50000 -zones.sukhumi:defineUpgrades({ - [1] = - { - presets.upgrades.basic.comPost:extend({ - name='sukhumi-compost-red', - products = { - presets.special.red.infantry:extend({ name='sukhumi-defense-red'}), - presets.defenses.red.infantry:extend({ name='sukhumi-garrison-red'}) - } - }), - presets.upgrades.supply.fuelTank:extend({ - name='sukhumi-fuel-red', - products = { - presets.missions.supply.helo:extend({name='sukhumi-supply-red-1'}), - presets.missions.supply.helo:extend({name='sukhumi-supply-red-2'}), - presets.missions.supply.transfer:extend({name='sukhumi-transfer-red'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='sukhumi-comcenter-red', - products = { - presets.defenses.red.sa11:extend({ name='sukhumi-airdef-red'}), - presets.missions.attack.sead:extend({name='sukhumi-sead-red', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.attack.cas:extend({name='sukhumi-cas-red', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.bai:extend({name='sukhumi-cas-red', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.strike:extend({name='sukhumi-strike-red', altitude=20000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.patrol.aircraft:extend({name='sukhumi-patrol-red', altitude=25000, range=25}), - presets.missions.patrol.aircraft:extend({name='sukhumi-patrol-red-2', altitude=25000, range=25}) - } - }) - }, - [2] = - { - presets.upgrades.basic.comPost:extend({ - name='sukhumi-compost-blue', - products = { - presets.special.blue.infantry:extend({ name='sukhumi-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='sukhumi-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelTank:extend({ - name='sukhumi-fuel-blue', - products = { - presets.missions.supply.helo:extend({name='sukhumi-supply-blue-1'}), - presets.missions.supply.helo:extend({name='sukhumi-supply-blue-2'}), - presets.missions.supply.transfer:extend({name='sukhumi-transfer-blue'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='sukhumi-comcenter-blue', - products = { - presets.defenses.blue.nasams:extend({ name='sukhumi-airdef-blue'}), - presets.missions.attack.sead:extend({name='sukhumi-sead-blue', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.attack.cas:extend({name='sukhumi-cas-blue', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.bai:extend({name='sukhumi-cas-blue', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.strike:extend({name='sukhumi-strike-blue', altitude=20000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.patrol.aircraft:extend({name='sukhumi-patrol-blue', altitude=25000, range=25}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Sukhumi.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Farm.lua ]]----------------- - -zones.farm = ZoneCommand:new("Farm") -zones.farm.initialState = { side=1 } -zones.farm:defineUpgrades({ - [1] = - { - presets.upgrades.basic.tent:extend({ - name='farm-tent-red', - products = { - presets.special.red.infantry:extend({ name='farm-defense-red'}), - presets.defenses.red.infantry:extend({ name='farm-garrison-red'}) - } - }), - presets.upgrades.supply.farm1:extend({ - name='farm-prod-red-1', - products = { - presets.missions.supply.convoy:extend({ name='farm-supply-red'}), - presets.missions.supply.transfer:extend({name='farm-transfer-red'}) - } - }), - presets.upgrades.supply.farm2:extend({ - name='farm-prod-red-2', - products = { - presets.missions.supply.convoy:extend({ name='farm-supply-red'}), - presets.missions.supply.transfer:extend({name='farm-transfer-red'}) - } - }) - }, - [2] = - { - presets.upgrades.basic.tent:extend({ - name='farm-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='farm-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='farm-garrison-blue'}) - } - }), - presets.upgrades.supply.farm1:extend({ - name='farm-prod-blue-1', - products = { - presets.missions.supply.convoy:extend({ name='farm-supply-blue'}), - presets.missions.supply.transfer:extend({name='farm-transfer-blue'}) - } - }), - presets.upgrades.supply.farm2:extend({ - name='farm-prod-blue-2', - products = { - presets.missions.supply.convoy:extend({ name='farm-supply-blue'}), - presets.missions.supply.transfer:extend({name='farm-transfer-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Farm.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Romeo.lua ]]----------------- - -zones.romeo = ZoneCommand:new("Romeo") -zones.romeo.initialState = { side=1 } -zones.romeo:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='romeo-tent-red', - products = { - presets.special.red.infantry:extend({ name='romeo-defense-red'}), - presets.defenses.red.infantry:extend({ name='romeo-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='romeo-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='romeo-supply-red'}), - presets.missions.supply.transfer:extend({name='romeo-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='romeo-ammo-red', - products = { - presets.missions.attack.surface:extend({name='romeo-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='romeo-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='romeo-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='romeo-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='romeo-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='romeo-supply-blue'}), - presets.missions.supply.transfer:extend({name='romeo-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='romeo-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='romeo-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Romeo.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Zulu.lua ]]----------------- - -zones.zulu = ZoneCommand:new("Zulu") -zones.zulu.initialState = { side=1 } -zones.zulu:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='zulu-tent-red', - products = { - presets.special.red.infantry:extend({ name='zulu-defense-red'}), - presets.defenses.red.infantry:extend({ name='zulu-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='zulu-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='zulu-supply-red'}), - presets.missions.supply.transfer:extend({name='zulu-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='zulu-ammo-red', - products = { - presets.missions.attack.surface:extend({name='zulu-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='zulu-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='zulu-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='zulu-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='zulu-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='zulu-supply-blue'}), - presets.missions.supply.transfer:extend({name='zulu-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='zulu-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='zulu-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Zulu.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Yankee.lua ]]----------------- - -zones.yankee = ZoneCommand:new("Yankee") -zones.yankee.initialState = { side=1 } -zones.yankee:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='yankee-tent-red', - products = { - presets.special.red.infantry:extend({ name='yankee-defense-red'}), - presets.defenses.red.infantry:extend({ name='yankee-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='yankee-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='yankee-supply-red'}), - presets.missions.supply.transfer:extend({name='yankee-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='yankee-ammo-red', - products = { - presets.missions.attack.surface:extend({name='yankee-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='yankee-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='yankee-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='yankee-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='yankee-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='yankee-supply-blue'}), - presets.missions.supply.transfer:extend({name='yankee-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='yankee-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='yankee-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Yankee.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Malgobek.lua ]]----------------- - -zones.malgobek = ZoneCommand:new("Malgobek") -zones.malgobek.initialState = { side=1 } -zones.malgobek:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='malgobek-tent-red', - products = { - presets.special.red.infantry:extend({ name='malgobek-defense-red'}), - presets.defenses.red.infantry:extend({ name='malgobek-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='malgobek-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='malgobek-supply-red'}), - presets.missions.supply.transfer:extend({name='malgobek-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='malgobek-ammo-red', - products = { - presets.missions.attack.surface:extend({name='malgobek-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='malgobek-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='malgobek-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='malgobek-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='malgobek-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='malgobek-supply-blue'}), - presets.missions.supply.transfer:extend({name='malgobek-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='malgobek-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='malgobek-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Malgobek.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Kilo.lua ]]----------------- - -zones.kilo = ZoneCommand:new("Kilo") -zones.kilo.initialState = { side=1 } -zones.kilo:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='kilo-tent-red', - products = { - presets.special.red.infantry:extend({ name='kilo-defense-red'}), - presets.defenses.red.infantry:extend({ name='kilo-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='kilo-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='kilo-supply-red'}), - presets.missions.supply.transfer:extend({name='kilo-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='kilo-ammo-red', - products = { - presets.missions.attack.surface:extend({name='kilo-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='kilo-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='kilo-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='kilo-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='kilo-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='kilo-supply-blue'}), - presets.missions.supply.transfer:extend({name='kilo-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='kilo-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='kilo-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Kilo.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Quebec.lua ]]----------------- - -zones.quebec = ZoneCommand:new("Quebec") -zones.quebec.initialState = { side=1 } -zones.quebec.keepActive = true -zones.quebec:defineUpgrades({ - [1] = - { - presets.upgrades.basic.tent:extend({ - name='quebec-tent-red', - products = { - presets.special.red.infantry:extend({ name='quebec-defense-red'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='quebec-comcenter-red', - products = { - presets.defenses.red.sam:extend({ name='quebec-sam-red'}) - } - }) - }, - [2] = - { - presets.upgrades.basic.tent:extend({ - name='quebec-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='quebec-defense-blue'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='quebec-comcenter-blue', - products = { - presets.defenses.blue.sam:extend({ name='quebec-sam-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Quebec.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/OilFields.lua ]]----------------- - -zones.oilfields = ZoneCommand:new("Oil Fields") -zones.oilfields.initialState = { side=1 } -zones.oilfields:defineUpgrades({ - [1] = { - presets.upgrades.basic.outpost:extend({ - name='oilfields-outpost-red', - products = { - presets.special.red.infantry:extend({ name='oilfields-defense-red'}), - presets.defenses.red.infantry:extend({ name='oilfields-garrison-red'}) - } - }), - presets.upgrades.supply.oilPump:extend({ - name='oilfields-pump-red-1', - products = { - presets.missions.supply.transfer:extend({name='oilfields-transfer-red1'}) - } - }), - presets.upgrades.supply.oilPump:extend({ - name='oilfields-pump-red-2', - products = { - presets.missions.supply.convoy:extend({name='oilfields-supply-red-1'}) - } - }), - presets.upgrades.supply.oilPump:extend({ - name='oilfields-pump-red-3', - products = { - presets.missions.supply.transfer:extend({name='oilfields-transfer-red2'}) - } - }), - presets.upgrades.supply.oilPump:extend({ - name='oilfields-pump-red-4', - products = { - presets.missions.supply.convoy:extend({name='oilfields-supply-red-2'}) - } - }) - }, - [2] = { - presets.upgrades.basic.outpost:extend({ - name='oilfields-outpost-blue', - products = { - presets.special.blue.infantry:extend({ name='oilfields-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='oilfields-garrison-blue'}) - } - }), - presets.upgrades.supply.oilPump:extend({ - name='oilfields-pump-blue-1', - products = { - presets.missions.supply.transfer:extend({name='oilfields-transfer-blue1'}) - } - }), - presets.upgrades.supply.oilPump:extend({ - name='oilfields-pump-blue-2', - products = { - presets.missions.supply.convoy:extend({name='oilfields-supply-blue-1'}) - } - }), - presets.upgrades.supply.oilPump:extend({ - name='oilfields-pump-blue-3', - products = { - presets.missions.supply.transfer:extend({name='oilfields-transfer-blue2'}) - } - }), - presets.upgrades.supply.oilPump:extend({ - name='oilfields-pump-blue-4', - products = { - presets.missions.supply.convoy:extend({name='oilfields-supply-blue-2'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/OilFields.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Echo.lua ]]----------------- - -zones.echo = ZoneCommand:new("Echo") -zones.echo.initialState = { side=2 } -zones.echo:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='echo-tent-red', - products = { - presets.special.red.infantry:extend({ name='echo-defense-red'}), - presets.defenses.red.infantry:extend({ name='echo-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='echo-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='echo-supply-red'}), - presets.missions.supply.transfer:extend({name='echo-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='echo-ammo-red', - products = { - presets.missions.attack.surface:extend({name='echo-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='echo-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='echo-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='echo-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='echo-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='echo-supply-blue'}), - presets.missions.supply.transfer:extend({name='echo-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='echo-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='echo-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Echo.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Kobuleti.lua ]]----------------- - -zones.kobuleti = ZoneCommand:new("Kobuleti") -zones.kobuleti.initialState = { side=2 } -zones.kobuleti.keepActive = true -zones.kobuleti.isHeloSpawn = true -zones.kobuleti.isPlaneSpawn = true -zones.kobuleti.maxResource = 50000 -zones.kobuleti:defineUpgrades({ - [1] = - { - presets.upgrades.basic.comPost:extend({ - name='kobuleti-compost-red', - products = { - presets.special.red.infantry:extend({ name='kobuleti-defense-red'}), - presets.defenses.red.infantry:extend({ name='kobuleti-garrison-red'}), - presets.missions.attack.surface:extend({ name='kobuleti-assault-red'}) - } - }), - presets.upgrades.supply.fuelTank:extend({ - name='kobuleti-fuel-red', - products = { - presets.missions.supply.helo:extend({name='kobuleti-supply-red-1'}), - presets.missions.supply.helo:extend({name='kobuleti-supply-red-2'}), - presets.missions.supply.transfer:extend({name='kobuleti-transfer-red'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='kobuleti-comcenter-red', - products = { - presets.defenses.red.shorad:extend({ name='kobuleti-airdef-red'}), - presets.missions.attack.sead:extend({name='kobuleti-sead-red', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.attack.cas:extend({name='kobuleti-cas-red', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.bai:extend({name='kobuleti-cas-red', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.strike:extend({name='kobuleti-strike-red', altitude=20000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.patrol.aircraft:extend({name='kobuleti-patrol-red', altitude=25000, range=25}) - } - }) - }, - [2] = - { - presets.upgrades.basic.comPost:extend({ - name='kobuleti-compost-blue', - products = { - presets.special.blue.infantry:extend({ name='kobuleti-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='kobuleti-garrison-blue'}), - presets.missions.attack.surface:extend({ name='kobuleti-assault-blue'}) - } - }), - presets.upgrades.supply.fuelTank:extend({ - name='kobuleti-fuel-blue', - products = { - presets.missions.supply.helo:extend({name='kobuleti-supply-blue-1'}), - presets.missions.supply.helo:extend({name='kobuleti-supply-blue-2'}), - presets.missions.supply.transfer:extend({name='kobuleti-transfer-blue'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='kobuleti-comcenter-blue', - products = { - presets.defenses.blue.shorad:extend({ name='kobuleti-airdef-blue'}), - presets.missions.attack.sead:extend({name='kobuleti-sead-blue', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.attack.cas:extend({name='kobuleti-cas-blue', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.bai:extend({name='kobuleti-cas-blue', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.strike:extend({name='kobuleti-strike-blue', altitude=20000, expend=AI.Task.WeaponExpend.TWO}), - presets.missions.patrol.aircraft:extend({name='kobuleti-patrol-blue', altitude=25000, range=25}), - presets.missions.support.awacs:extend({name='kobuleti-awacs-blue', altitude=30000, freq=258.5}), - presets.missions.support.tanker:extend({name='kobuleti-tanker-blue', altitude=23000, freq=258, tacan='38', variant='Boom'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Kobuleti.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Alpha.lua ]]----------------- - -zones.alpha = ZoneCommand:new('Alpha') -zones.alpha.initialState = { side=2 } -zones.alpha:defineUpgrades({ - [1] = --red side - { - presets.upgrades.basic.tent:extend({ - name = 'alpha-tent-red', - products = { - presets.special.red.infantry:extend({ name='alpha-defense-red'}), - presets.defenses.red.infantry:extend({ name='alpha-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name = 'alpha-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({ name='alpha-supply-red'}), - presets.missions.supply.transfer:extend({name='alpha-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name = 'alpha-ammo-red', - products = { - presets.missions.attack.surface:extend({ name='alpha-assault-red'}) - } - }) - }, - [2] = --blue side - { - presets.upgrades.basic.tent:extend({ - name = 'alpha-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='alpha-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='alpha-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name = 'alpha-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({ name='alpha-supply-blue'}), - presets.missions.supply.transfer:extend({name='alpha-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name = 'alpha-ammo-blue', - products = { - presets.missions.attack.surface:extend({ name='alpha-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Alpha.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Foxtrot.lua ]]----------------- - -zones.foxtrot = ZoneCommand:new("Foxtrot") -zones.foxtrot.initialState = { side=2 } -zones.foxtrot:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='foxtrot-tent-red', - products = { - presets.special.red.infantry:extend({ name='foxtrot-defense-red'}), - presets.defenses.red.infantry:extend({ name='foxtrot-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='foxtrot-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='foxtrot-supply-red'}), - presets.missions.supply.transfer:extend({name='foxtrot-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='foxtrot-ammo-red', - products = { - presets.missions.attack.surface:extend({name='foxtrot-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='foxtrot-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='foxtrot-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='foxtrot-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='foxtrot-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='foxtrot-supply-blue'}), - presets.missions.supply.transfer:extend({name='foxtrot-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='foxtrot-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='foxtrot-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Foxtrot.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Sierra.lua ]]----------------- - -zones.sierra = ZoneCommand:new("Sierra") -zones.sierra.initialState = { side=1 } -zones.sierra.isHeloSpawn = true -zones.sierra:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='sierra-tent-red', - products = { - presets.special.red.infantry:extend({ name='sierra-defense-red'}), - presets.defenses.red.infantry:extend({ name='sierra-garrison-red'}) - } - }), - presets.upgrades.supply.fuelTankFarp:extend({ - name='sierra-fuel-red', - products = { - presets.missions.supply.helo:extend({name='sierra-supply-red'}), - presets.missions.supply.helo:extend({name='sierra-supply-red-1'}), - presets.missions.supply.transfer:extend({name='sierra-transfer-red'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='sierra-comcenter-red', - products = { - presets.defenses.red.shorad:extend({name='sierra-sam-red'}), - presets.missions.attack.helo:extend({name='sierra-cas-red', altitude=200, expend=AI.Task.WeaponExpend.HALF }) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='sierra-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='sierra-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='sierra-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelTankFarp:extend({ - name='sierra-fuel-blue', - products = { - presets.missions.supply.helo:extend({name='sierra-supply-blue'}), - presets.missions.supply.helo:extend({name='sierra-supply-blue-1'}), - presets.missions.supply.transfer:extend({name='sierra-transfer-blue'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='sierra-comcenter-blue', - products = { - presets.defenses.blue.shorad:extend({name='sierra-sam-blue'}), - presets.missions.attack.helo:extend({name='sierra-cas-blue', altitude=200, expend=AI.Task.WeaponExpend.HALF }) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Sierra.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Oni.lua ]]----------------- - -zones.oni = ZoneCommand:new("Oni") -zones.oni.initialState = { side=1 } -zones.oni:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='oni-tent-red', - products = { - presets.special.red.infantry:extend({ name='oni-defense-red'}), - presets.defenses.red.infantry:extend({ name='oni-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='oni-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='oni-supply-red'}), - presets.missions.supply.helo:extend({name='oni-supply-red-2'}), - presets.missions.supply.transfer:extend({name='oni-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='oni-ammo-red', - products = { - presets.missions.attack.surface:extend({name='oni-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='oni-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='oni-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='oni-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='oni-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='oni-supply-blue'}), - presets.missions.supply.helo:extend({name='oni-supply-blue-2'}), - presets.missions.supply.transfer:extend({name='oni-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='oni-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='oni-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Oni.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Hotel.lua ]]----------------- - -zones.hotel = ZoneCommand:new("Hotel") -zones.hotel.initialState = nil -zones.hotel:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='hotel-tent-red', - products = { - presets.special.red.infantry:extend({ name='hotel-defense-red'}), - presets.defenses.red.infantry:extend({ name='hotel-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='hotel-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='hotel-supply-red'}), - presets.missions.supply.transfer:extend({name='hotel-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='hotel-ammo-red', - products = { - presets.missions.attack.surface:extend({name='hotel-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='hotel-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='hotel-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='hotel-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='hotel-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='hotel-supply-blue'}), - presets.missions.supply.transfer:extend({name='hotel-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='hotel-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='hotel-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Hotel.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Victor.lua ]]----------------- - -zones.victor = ZoneCommand:new("Victor") -zones.victor.initialState = { side=1 } -zones.victor:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='victor-tent-red', - products = { - presets.special.red.infantry:extend({ name='victor-defense-red'}), - presets.defenses.red.infantry:extend({ name='victor-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='victor-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='victor-supply-red'}), - presets.missions.supply.helo:extend({name='victor-supply-red-2'}), - presets.missions.supply.transfer:extend({name='victor-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='victor-ammo-red', - products = { - presets.missions.attack.surface:extend({name='victor-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='victor-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='victor-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='victor-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='victor-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='victor-supply-blue'}), - presets.missions.supply.helo:extend({name='victor-supply-blue-2'}), - presets.missions.supply.transfer:extend({name='victor-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='victor-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='victor-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Victor.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Tango.lua ]]----------------- - -zones.tango = ZoneCommand:new("Tango") -zones.tango.initialState = { side=1 } -zones.tango.isHeloSpawn = true -zones.tango:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='tango-tent-red', - products = { - presets.special.red.infantry:extend({ name='tango-defense-red'}), - presets.defenses.red.infantry:extend({ name='tango-garrison-red'}) - } - }), - presets.upgrades.supply.fuelTankFarp:extend({ - name='tango-fuel-red', - products = { - presets.missions.supply.helo:extend({name='tango-supply-red'}), - presets.missions.supply.helo:extend({name='tango-supply-red-1'}), - presets.missions.supply.transfer:extend({name='tango-transfer-red'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='tango-comcenter-red', - products = { - presets.defenses.red.shorad:extend({name='tango-sam-red'}), - presets.missions.attack.helo:extend({name='tango-cas-red', altitude=200, expend=AI.Task.WeaponExpend.HALF }) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='tango-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='tango-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='tango-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelTankFarp:extend({ - name='tango-fuel-blue', - products = { - presets.missions.supply.helo:extend({name='tango-supply-blue'}), - presets.missions.supply.helo:extend({name='tango-supply-blue-1'}), - presets.missions.supply.transfer:extend({name='tango-transfer-blue'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='tango-comcenter-blue', - products = { - presets.defenses.blue.shorad:extend({name='tango-sam-blue'}), - presets.missions.attack.helo:extend({name='tango-cas-blue', altitude=200, expend=AI.Task.WeaponExpend.HALF }) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Tango.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Unal.lua ]]----------------- - -zones.unal = ZoneCommand:new("Unal") -zones.unal.initialState = { side=1 } -zones.unal.isHeloSpawn = true -zones.unal:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='unal-tent-red', - products = { - presets.special.red.infantry:extend({ name='unal-defense-red'}), - presets.defenses.red.infantry:extend({ name='unal-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='unal-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='unal-supply-red'}), - presets.missions.supply.helo:extend({name='unal-supply-red-2'}), - presets.missions.supply.transfer:extend({name='unal-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='unal-ammo-red', - products = { - presets.missions.attack.surface:extend({name='unal-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='unal-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='unal-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='unal-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='unal-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='unal-supply-blue'}), - presets.missions.supply.helo:extend({name='unal-supply-blue-2'}), - presets.missions.supply.transfer:extend({name='unal-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='unal-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='unal-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Unal.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Beslan.lua ]]----------------- - -zones.beslan = ZoneCommand:new("Beslan") -zones.beslan.initialState = { side=1 } -zones.beslan.keepActive = true -zones.beslan.maxResource = 50000 -zones.beslan:defineUpgrades({ - [1] = - { - presets.upgrades.basic.comPost:extend({ - name='beslan-compost-red', - products = { - presets.special.red.infantry:extend({ name='beslan-defense-red'}), - presets.defenses.red.infantry:extend({ name='beslan-garrison-red'}) - } - }), - presets.upgrades.supply.fuelTank:extend({ - name='beslan-fuel-red', - products = { - presets.missions.supply.helo:extend({name='beslan-supply-red-1'}), - presets.missions.supply.helo:extend({name='beslan-supply-red-2'}), - presets.missions.supply.convoy_escorted:extend({name='beslan-supply-red-3'}), - presets.missions.supply.transfer:extend({name='beslan-transfer-red'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='beslan-comcenter-red', - products = { - presets.defenses.red.sa5:extend({ name='beslan-airdef-red'}), - presets.missions.attack.sead:extend({name='beslan-sead-red', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.attack.strike:extend({name='beslan-strike-red', altitude=30000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.attack.strike:extend({name='beslan-strike-red-1', altitude=30000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.patrol.aircraft:extend({name='beslan-patrol-red', altitude=25000, range=25}) - } - }) - }, - [2] = - { - presets.upgrades.basic.comPost:extend({ - name='beslan-compost-blue', - products = { - presets.special.blue.infantry:extend({ name='beslan-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='beslan-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelTank:extend({ - name='beslan-fuel-blue', - products = { - presets.missions.supply.helo:extend({name='beslan-supply-blue-1'}), - presets.missions.supply.helo:extend({name='beslan-supply-blue-2'}), - presets.missions.supply.convoy_escorted:extend({name='beslan-supply-blue-3'}), - presets.missions.supply.transfer:extend({name='beslan-transfer-blue'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='beslan-comcenter-blue', - products = { - presets.defenses.blue.patriot:extend({ name='beslan-airdef-blue'}), - presets.missions.attack.sead:extend({name='beslan-sead-blue', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.attack.strike:extend({name='beslan-strike-blue', altitude=30000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.attack.strike:extend({name='beslan-strike-blue-1', altitude=30000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.patrol.aircraft:extend({name='beslan-patrol-blue', altitude=25000, range=25}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Beslan.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Bravo.lua ]]----------------- - -zones.bravo = ZoneCommand:new("Bravo") -zones.bravo.initialState = { side=2 } -zones.bravo:defineUpgrades({ - [1] = - { - presets.upgrades.basic.comPost:extend({ - name='bravo-compost-red', - products = { - presets.special.red.infantry:extend({ name='bravo-defense-red'}), - presets.defenses.red.infantry:extend({ name='bravo-garrison-red'}) - } - }), - presets.upgrades.supply.fuelTankFarp:extend({ - name='bravo-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({ name='bravo-supply-red'}), - presets.missions.supply.transfer:extend({name='bravo-transfer-red'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='bravo-comcenter-red', - products = { - presets.defenses.red.shorad:extend({ name='bravo-airdef-red'}), - presets.missions.attack.helo:extend({name='bravo-attack-red', altitude=200, expend=AI.Task.WeaponExpend.HALF}) - } - }) - }, - [2] = - { - presets.upgrades.basic.comPost:extend({ - name='bravo-compost-blue', - products = { - presets.special.blue.infantry:extend({ name='bravo-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='bravo-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelTankFarp:extend({ - name='bravo-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({ name='bravo-supply-blue'}), - presets.missions.supply.transfer:extend({name='bravo-transfer-blue'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='bravo-comcenter-blue', - products = { - presets.defenses.blue.shorad:extend({ name='bravo-airdef-blue'}), - presets.missions.attack.helo:extend({name='bravo-attack-blue', altitude=200, expend=AI.Task.WeaponExpend.HALF }) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Bravo.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/WeaponDepot.lua ]]----------------- - -zones.weapondepot = ZoneCommand:new("Weapon Depot") -zones.weapondepot.initialState = { side=1 } -zones.weapondepot:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='weapons-tent-red', - products = { - presets.special.red.infantry:extend({ name='weapons-defense-red'}), - presets.defenses.red.infantry:extend({ name='weapons-garrison-red'}) - } - }), - presets.upgrades.supply.ammoDepot:extend({ - name='weapons-ammo-red-1', - products = { - presets.missions.supply.convoy:extend({name='weapons-supply-red-1'}), - presets.missions.supply.transfer:extend({name='weapons-transfer-red-1'}) - } - }), - presets.upgrades.supply.ammoDepot:extend({ - name='weapons-ammo-red-2', - products = { - presets.missions.supply.convoy:extend({name='weapons-supply-red-2'}), - presets.missions.supply.transfer:extend({name='weapons-transfer-red-2'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='weapons-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='weapons-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='weapons-garrison-blue'}) - } - }), - presets.upgrades.supply.ammoDepot:extend({ - name='weapons-ammo-blue-1', - products = { - presets.missions.supply.convoy:extend({name='weapons-supply-blue-1'}), - presets.missions.supply.transfer:extend({name='weapons-transfer-blue-1'}) - } - }), - presets.upgrades.supply.ammoDepot:extend({ - name='weapons-ammo-blue-2', - products = { - presets.missions.supply.convoy:extend({name='weapons-supply-blue-2'}), - presets.missions.supply.transfer:extend({name='weapons-transfer-blue-2'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/WeaponDepot.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Delta.lua ]]----------------- - -zones.delta = ZoneCommand:new("Delta") -zones.delta.initialState = { side=2 } -zones.delta:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='delta-tent-red', - products = { - presets.special.red.infantry:extend({ name='delta-defense-red'}), - presets.defenses.red.infantry:extend({ name='delta-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='delta-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='delta-supply-red'}), - presets.missions.supply.transfer:extend({name='delta-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='delta-ammo-red', - products = { - presets.missions.attack.surface:extend({name='delta-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='delta-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='delta-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='delta-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='delta-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='delta-supply-blue'}), - presets.missions.supply.transfer:extend({name='delta-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='delta-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='delta-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Delta.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Cherkessk.lua ]]----------------- - -zones.cherkessk = ZoneCommand:new("Cherkessk") -zones.cherkessk.initialState = { side=1 } -zones.cherkessk.isHeloSpawn = true -zones.cherkessk:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='cherkessk-tent-red', - products = { - presets.special.red.infantry:extend({ name='cherkessk-defense-red'}), - presets.defenses.red.infantry:extend({ name='cherkessk-garrison-red'}) - } - }), - presets.upgrades.supply.fuelTankFarp:extend({ - name='cherkessk-fuel-red', - products = { - presets.missions.supply.helo:extend({name='cherkessk-supply-red'}), - presets.missions.supply.helo:extend({name='cherkessk-supply-red-1'}), - presets.missions.supply.transfer:extend({name='cherkessk-transfer-red'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='cherkessk-comcenter-red', - products = { - presets.defenses.red.shorad:extend({name='cherkessk-sam-red'}), - presets.missions.attack.helo:extend({name='cherkessk-cas-red', altitude=200, expend=AI.Task.WeaponExpend.HALF }), - presets.missions.attack.helo:extend({name='cherkessk-cas-red-1', altitude=200, expend=AI.Task.WeaponExpend.HALF }), - presets.missions.attack.surface:extend({name='cherkessk-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='cherkessk-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='cherkessk-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='cherkessk-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelTankFarp:extend({ - name='cherkessk-fuel-blue', - products = { - presets.missions.supply.helo:extend({name='cherkessk-supply-blue'}), - presets.missions.supply.helo:extend({name='cherkessk-supply-blue-1'}), - presets.missions.supply.transfer:extend({name='cherkessk-transfer-blue'}), - presets.missions.attack.surface:extend({name='cherkessk-assault-blue'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='cherkessk-comcenter-blue', - products = { - presets.defenses.blue.shorad:extend({name='cherkessk-sam-blue'}), - presets.missions.attack.helo:extend({name='cherkessk-cas-blue', altitude=200, expend=AI.Task.WeaponExpend.HALF }), - presets.missions.attack.helo:extend({name='cherkessk-cas-blue-1', altitude=200, expend=AI.Task.WeaponExpend.HALF }) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Cherkessk.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Juliett.lua ]]----------------- - -zones.juliett = ZoneCommand:new("Juliett") -zones.initialState = nil -zones.juliett:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='juliett-tent-red', - products = { - presets.special.red.infantry:extend({ name='juliett-defense-red'}), - presets.defenses.red.infantry:extend({ name='juliett-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='juliett-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='juliett-supply-red'}), - presets.missions.supply.transfer:extend({name='juliett-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='juliett-ammo-red', - products = { - presets.missions.attack.surface:extend({name='juliett-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='juliett-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='juliett-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='juliett-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='juliett-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='juliett-supply-blue'}), - presets.missions.supply.transfer:extend({name='juliett-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='juliett-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='juliett-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Juliett.lua ]]----------------- - - - - cm = ConnectionManager:new() - cm:addConnection('Batumi', 'Alpha') - cm:addConnection('Alpha', 'Bravo') - cm:addConnection('Bravo', 'Kobuleti') - cm:addConnection('Bravo', 'Factory') - cm:addConnection('Kobuleti', 'Factory') - cm:addConnection('Kobuleti', 'Charlie') - cm:addConnection('Foxtrot', 'Charlie') - cm:addConnection('Foxtrot', 'Kobuleti') - cm:addConnection('Delta','Foxtrot') - cm:addConnection('Delta','Kobuleti') - cm:addConnection('Delta','Factory') - cm:addConnection('Echo','Charlie') - cm:addConnection('Golf','Echo') - cm:addConnection('Golf','Foxtrot') - cm:addConnection('India','Delta') - cm:addConnection('Hotel','Golf') - cm:addConnection('Hotel','Foxtrot') - cm:addConnection('Hotel','Delta') - cm:addConnection('Hotel','India') - cm:addConnection('Juliett','Echo') - cm:addConnection('Juliett','Golf') - cm:addConnection('Senaki','Juliett') - cm:addConnection('Senaki','Golf') - cm:addConnection('Senaki','Hotel') - cm:addConnection('Kutaisi','Hotel') - cm:addConnection('Kutaisi','India') - cm:addConnection('Kilo','Juliett') - cm:addConnection('Mike','Kutaisi') - cm:addConnection('Mike','Senaki') - cm:addConnection('Romeo','Mike') - cm:addConnection('Romeo','Kutaisi') - cm:addConnection('Weapon Depot','Juliett') - cm:addConnection('Weapon Depot','Senaki') - cm:addConnection('Weapon Depot','Kilo') - cm:addConnection('November','Weapon Depot') - cm:addConnection('November','Senaki') - cm:addConnection('November','Mike') - cm:addConnection('Oil Fields','Romeo') - cm:addConnection('Quebec','Kilo') - cm:addConnection('Zugdidi','Weapon Depot') - cm:addConnection('Zugdidi','Quebec') - cm:addConnection('Zugdidi','November') - cm:addConnection('Zugdidi','Kilo') - cm:addConnection('Distillery','November') - cm:addConnection('Distillery','Mike') - cm:addConnection('Zugdidi','Papa') - cm:addConnection('November','Papa') - cm:addConnection('Sierra','Papa') - cm:addConnection('Sierra','Zugdidi') - cm:addConnection('Sierra','Uniform') - cm:addConnection('Mine','Uniform') - cm:addConnection('Tango','Quebec') - cm:addConnection('Tango','Zugdidi') - cm:addConnection('Sierra','Tango') - cm:addConnection('Whiskey','Tango') - cm:addConnection('Ochamchira','Tango') - cm:addConnection('Ochamchira','Whiskey') - cm:addConnection('Ochamchira','Farm') - cm:addConnection('Ochamchira','Zulu') - cm:addConnection('Farm','Zulu') - cm:addConnection('Sukhumi','Zulu') - cm:addConnection('Lentehi','Distillery', true, 3000) - cm:addConnection('Lentehi','Babugent', true, 5000) - cm:addConnection('Nalchik','Babugent') - cm:addConnection('Victor','Distillery', true, 2000) - cm:addConnection('Victor','Romeo') - cm:addConnection('Victor','Lentehi') - cm:addConnection('Victor','Oil Fields', true, 2000) - cm:addConnection('Victor','Oni') - cm:addConnection('Unal','Oni', true, 4500) - cm:addConnection('Beslan','Unal') - cm:addConnection('Digora','Beslan') - cm:addConnection('Digora','Unal') - cm:addConnection('Digora','Babugent') - cm:addConnection('Terek','Digora') - cm:addConnection('Terek','Nalchik') - cm:addConnection('Terek','Beslan') - cm:addConnection('Prohladniy','Terek') - cm:addConnection('Prohladniy','Nalchik') - cm:addConnection('Malgobek','Terek') - cm:addConnection('Malgobek','Beslan') - cm:addConnection('Lima','Mine') - cm:addConnection('Lima','Lentehi', true, 4000) - cm:addConnection('Tyrnyauz','Lima', true, 4000) - cm:addConnection('Tyrnyauz','Nalchik') - cm:addConnection('XRay','Sukhumi') - cm:addConnection('Oscar','Sukhumi') - cm:addConnection('Oscar','XRay') - cm:addConnection('Mozdok','Malgobek') - cm:addConnection('Mozdok','Prohladniy') - cm:addConnection('Gudauta','Oscar') - cm:addConnection('Yankee','Gudauta') - cm:addConnection('Sochi','Yankee') - cm:addConnection('Refinery','XRay', true, 4000) - cm:addConnection('Refinery','Humara') - cm:addConnection('Intel Center','Tyrnyauz') - cm:addConnection('Intel Center','Nalchik') - cm:addConnection('Intel Center','Prohladniy') - cm:addConnection('Intel Center','Kislovodsk') - cm:addConnection('Mineralnye','Intel Center') - cm:addConnection('Kislovodsk','Mineralnye') - cm:addConnection('Tallyk','Mineralnye') - cm:addConnection('Tallyk','Kislovodsk') - cm:addConnection('Power Plant','Mineralnye') - cm:addConnection('Power Plant','Tallyk') - cm:addConnection('Cherkessk','Tallyk') - cm:addConnection('Cherkessk','Power Plant') - cm:addConnection('Cherkessk','Humara') -end - -ZoneCommand.setNeighbours(cm) - -bm = BattlefieldManager:new() - -mc = MarkerCommands:new() - -pt = PlayerTracker:new(mc) - -mt = MissionTracker:new(pt, mc) - -st = SquadTracker:new() - -ct = CSARTracker:new() - -pl = PlayerLogistics:new(mt, pt, st, ct) - -gci = GCI:new(2) - -gm = GroupMonitor:new(cm) -ZoneCommand.groupMonitor = gm - --- PlayerLogistics:registerSquadGroup(squadType, groupname, weight,cost,jobtime,extracttime, squadSize) -pl:registerSquadGroup(PlayerLogistics.infantryTypes.capture, 'capture-squad', 700, 200, 60, 60*30, 4) -pl:registerSquadGroup(PlayerLogistics.infantryTypes.sabotage, 'sabotage-squad', 800, 500, 60*5, 60*30, 4) -pl:registerSquadGroup(PlayerLogistics.infantryTypes.ambush, 'ambush-squad', 900, 300, 60*20, 60*30, 5) -pl:registerSquadGroup(PlayerLogistics.infantryTypes.engineer, 'engineer-squad', 200, 1000,60, 60*30, 2) -pl:registerSquadGroup(PlayerLogistics.infantryTypes.manpads, 'manpads-squad', 900, 500, 60*20, 60*30, 5) -pl:registerSquadGroup(PlayerLogistics.infantryTypes.spy, 'spy-squad', 100, 300, 60*10, 60*30, 1) -pl:registerSquadGroup(PlayerLogistics.infantryTypes.rapier, 'rapier-squad', 1200,2000,60*60, 60*30, 8) - -Group.getByName('jtacDrone'):destroy() -CommandFunctions.jtac = JTAC:new({name = 'jtacDrone'}) - -pm = PersistenceManager:new(savefile, gm, st, ct, pl) -pm:load() - -if pm:canRestore() then - pm:restoreZones() - pm:restoreAIMissions() - pm:restoreBattlefield() - pm:restoreCsar() - pm:restoreSquads() -else - --initial states - Starter.start(zones) -end - -timer.scheduleFunction(function(param, time) - pm:save() - env.info("Mission state saved") - return time+60 -end, zones, timer.getTime()+60) - - ---make sure support units are present where needed -ensureSpawn = { - ['golf-farp-suport'] = zones.golf, - ['november-farp-suport'] = zones.november, - ['tango-farp-suport'] = zones.tango, - ['sierra-farp-suport'] = zones.sierra, - ['cherkessk-farp-suport'] = zones.cherkessk, - ['unal-farp-suport'] = zones.unal, - ['tyrnyauz-farp-suport'] = zones.tyrnyauz -} - -for grname, zn in pairs(ensureSpawn) do - local g = Group.getByName(grname) - if g then g:destroy() end -end - -timer.scheduleFunction(function(param, time) - - for grname, zn in pairs(ensureSpawn) do - local g = Group.getByName(grname) - if zn.side == 2 then - if not g then - local err, msg = pcall(mist.respawnGroup,grname,true) - if not err then - env.info("ERROR spawning "..grname) - env.info(msg) - end - end - else - if g then g:destroy() end - end - end - - return time+30 -end, {}, timer.getTime()+30) - - ---supply injection -local blueSupply = {'offmap-supply-blue-1','offmap-supply-blue-2','offmap-supply-blue-3','offmap-supply-blue-4','offmap-supply-blue-5'} -local redSupply = {'offmap-supply-red-1','offmap-supply-red-2','offmap-supply-red-3','offmap-supply-red-4','offmap-supply-red-5'} -local offmapZones = { - zones.batumi, - zones.sochi, - zones.nalchik, - zones.beslan, - zones.mozdok, - zones.mineralnye, --- zones.senaki, --- zones.sukhumi, --- zones.gudauta, --- zones.kobuleti, -} - supplyPointRegistry = { blue = {}, red = {} diff --git a/resources/plugins/pretense/init_header.lua b/resources/plugins/pretense/init_header.lua index 9df8452d..d569e9bb 100644 --- a/resources/plugins/pretense/init_header.lua +++ b/resources/plugins/pretense/init_header.lua @@ -619,4052 +619,4 @@ presets = { } zones = {} -do - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Batumi.lua ]]----------------- - -zones.batumi = ZoneCommand:new('Batumi') -zones.batumi.initialState = { side=2 } -zones.batumi.keepActive = true -zones.batumi.isHeloSpawn = true -zones.batumi.isPlaneSpawn = true -zones.batumi.maxResource = 50000 -zones.batumi:defineUpgrades({ - [1] = { --red side - presets.upgrades.basic.comPost:extend({ - name = 'batumi-com-red', - products = { - presets.special.red.infantry:extend({ name='batumi-defense-red'}), - presets.defenses.red.infantry:extend({ name='batumi-garrison-red' }) - } - }), - }, - [2] = --blue side - { - presets.upgrades.basic.comPost:extend({ - name = 'batumi-com-blue', - products = { - presets.special.blue.infantry:extend({ name='batumi-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='batumi-garrison-blue' }) - } - }), - presets.upgrades.supply.fuelTank:extend({ - name = 'batumi-fueltank-blue', - products = { - presets.missions.supply.convoy_escorted:extend({ name='batumi-supply-convoy-1'}), - presets.missions.supply.helo:extend({ name='batumi-supply-blue-1' }), - presets.missions.supply.transfer:extend({name='batumi-transfer-blue'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name = 'batumi-mission-command-blue', - products = { - presets.defenses.blue.shorad:extend({ name='batumi-sam-blue' }), - presets.missions.attack.sead:extend({name='batumi-sead-blue-1', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.attack.cas:extend({name='batumi-cas-blue-1', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.bai:extend({name='batumi-cas-blue-1', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.strike:extend({name='batumi-strike-blue-1', altitude=20000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.patrol.aircraft:extend({name='batumi-patrol-blue-1', altitude=25000, range=25}), - presets.missions.support.awacs:extend({name='batumi-awacs-blue', altitude=30000, freq=257.5}), - presets.missions.support.tanker:extend({name='batumi-tanker-blue', altitude=25000, freq=257, tacan='37', variant="Drogue"}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Batumi.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Mike.lua ]]----------------- - -zones.mike = ZoneCommand:new("Mike") -zones.mike.initialState = { side=1 } -zones.mike.keepActive = true -zones.mike:defineUpgrades({ - [1] = - { - presets.upgrades.basic.tent:extend({ - name='mike-tent-red', - products = { - presets.special.red.infantry:extend({ name='mike-defense-red'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='mike-comcenter-red', - products = { - presets.defenses.red.sam:extend({ name='mike-sam-red'}) - } - }) - }, - [2] = - { - presets.upgrades.basic.tent:extend({ - name='mike-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='mike-defense-blue'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='mike-comcenter-blue', - products = { - presets.defenses.blue.sam:extend({ name='mike-sam-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Mike.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Tyrnyauz.lua ]]----------------- - -zones.tyrnyauz = ZoneCommand:new("Tyrnyauz") -zones.tyrnyauz.initialState = { side=1 } -zones.tyrnyauz.isHeloSpawn = true -zones.tyrnyauz:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='tyrnyauz-tent-red', - products = { - presets.special.red.infantry:extend({ name='tyrnyauz-defense-red'}), - presets.defenses.red.infantry:extend({ name='tyrnyauz-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='tyrnyauz-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='tyrnyauz-supply-red'}), - presets.missions.supply.helo:extend({name='tyrnyauz-supply-red-2'}), - presets.missions.supply.transfer:extend({name='tyrnyauz-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='tyrnyauz-ammo-red', - products = { - presets.missions.attack.surface:extend({name='tyrnyauz-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='tyrnyauz-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='tyrnyauz-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='tyrnyauz-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='tyrnyauz-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='tyrnyauz-supply-blue'}), - presets.missions.supply.helo:extend({name='tyrnyauz-supply-blue-2'}), - presets.missions.supply.transfer:extend({name='tyrnyauz-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='tyrnyauz-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='tyrnyauz-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Tyrnyauz.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/India.lua ]]----------------- - -zones.india = ZoneCommand:new("India") -zones.india.initialState = nil -zones.india:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='india-tent-red', - products = { - presets.special.red.infantry:extend({ name='india-defense-red'}), - presets.defenses.red.infantry:extend({ name='india-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='india-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='india-supply-red'}), - presets.missions.supply.transfer:extend({name='india-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='india-ammo-red', - products = { - presets.missions.attack.surface:extend({name='india-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='india-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='india-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='india-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='india-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='india-supply-blue'}), - presets.missions.supply.transfer:extend({name='india-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='india-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='india-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/India.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/IntelCenter.lua ]]----------------- - -zones.intelcenter = ZoneCommand:new("Intel Center") -zones.intelcenter.initialState = { side=1 } -zones.intelcenter:defineUpgrades({ - [1] = - { - presets.upgrades.basic.tent:extend({ - name='intelcenter-tent-red', - products = { - presets.special.red.infantry:extend({ name='intelcenter-defense-red'}), - presets.defenses.red.infantry:extend({ name='intelcenter-garrison-red'}) - } - }), - presets.upgrades.supply.hq:extend({ - name='intelcenter-hq-red', - products = { - presets.missions.supply.convoy:extend({ name='intelcenter-supply-red'}), - presets.missions.supply.convoy:extend({ name='intelcenter-supply-red-1'}), - presets.missions.supply.transfer:extend({name='intelcenter-transfer-red'}) - } - }), - presets.upgrades.supply.antenna:extend({ - name='intelcenter-antenna-red', - products = { - } - }), - presets.upgrades.supply.antenna:extend({ - name='intelcenter-antenna-red-1', - products = { - } - }), - presets.upgrades.supply.antenna:extend({ - name='intelcenter-antenna-red-2', - products = { - } - }) - }, - [2] = - { - presets.upgrades.basic.tent:extend({ - name='intelcenter-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='intelcenter-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='intelcenter-garrison-blue'}) - } - }), - presets.upgrades.supply.hq:extend({ - name='intelcenter-hq-blue', - products = { - presets.missions.supply.convoy:extend({ name='intelcenter-supply-blue'}), - presets.missions.supply.convoy:extend({ name='intelcenter-supply-blue-1'}), - presets.missions.supply.transfer:extend({name='intelcenter-transfer-blue'}) - } - }), - presets.upgrades.supply.antenna:extend({ - name='intelcenter-antenna-blue', - products = { - } - }), - presets.upgrades.supply.antenna:extend({ - name='intelcenter-antenna-blue-1', - products = { - } - }), - presets.upgrades.supply.antenna:extend({ - name='intelcenter-antenna-blue-2', - products = { - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/IntelCenter.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Mineralnye.lua ]]----------------- - -zones.mineralnye = ZoneCommand:new("Mineralnye") -zones.mineralnye.initialState = { side=1 } -zones.mineralnye.keepActive = true -zones.mineralnye.isHeloSpawn = true -zones.mineralnye.isPlaneSpawn = true -zones.mineralnye:defineUpgrades({ - [1] = - { - presets.upgrades.basic.comPost:extend({ - name='mineralnye-compost-red', - products = { - presets.special.red.infantry:extend({ name='mineralnye-defense-red'}), - presets.defenses.red.infantry:extend({ name='mineralnye-garrison-red'}) - } - }), - presets.upgrades.supply.fuelTank:extend({ - name='mineralnye-fuel-red', - products = { - presets.missions.supply.helo:extend({name='mineralnye-supply-red'}), - presets.missions.supply.helo:extend({name='mineralnye-supply-red-1'}), - presets.missions.supply.transfer:extend({name='mineralnye-transfer-red'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='mineralnye-comcenter-red', - products = { - presets.defenses.red.sa11:extend({ name='mineralnye-airdef-red'}), - presets.missions.attack.cas:extend({name='mineralnye-cas-red', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.bai:extend({name='mineralnye-cas-red', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.strike:extend({name='mineralnye-strike-red', altitude=30000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.attack.strike:extend({name='mineralnye-strike-red-1', altitude=30000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.patrol.aircraft:extend({name='mineralnye-patrol-red', altitude=25000, range=25}) - } - }) - }, - [2] = - { - presets.upgrades.basic.comPost:extend({ - name='mineralnye-compost-blue', - products = { - presets.special.blue.infantry:extend({ name='mineralnye-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='mineralnye-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelTank:extend({ - name='mineralnye-fuel-blue', - products = { - presets.missions.supply.helo:extend({name='mineralnye-supply-blue'}), - presets.missions.supply.helo:extend({name='mineralnye-supply-blue-1'}), - presets.missions.supply.transfer:extend({name='mineralnye-transfer-blue'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='mineralnye-comcenter-blue', - products = { - presets.defenses.blue.nasams:extend({ name='mineralnye-airdef-blue'}), - presets.missions.attack.cas:extend({name='mineralnye-cas-blue', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.bai:extend({name='mineralnye-cas-blue', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.strike:extend({name='mineralnye-strike-blue', altitude=30000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.attack.strike:extend({name='mineralnye-strike-blue-1', altitude=30000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.patrol.aircraft:extend({name='mineralnye-patrol-blue', altitude=25000, range=25}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Mineralnye.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/PowerPlant.lua ]]----------------- - -zones.powerplant = ZoneCommand:new("Power Plant") -zones.powerplant.initialState = { side=1 } -zones.powerplant:defineUpgrades({ - [1] = - { - presets.upgrades.basic.tent:extend({ - name='powerplant-tent-red', - products = { - presets.special.red.infantry:extend({ name='powerplant-defense-red'}), - presets.defenses.red.infantry:extend({ name='powerplant-garrison-red'}) - } - }), - presets.upgrades.supply.powerplant1:extend({ - name='powerplant-building-red-1', - products = { - presets.missions.supply.convoy:extend({ name='powerplant-supply-red'}), - presets.missions.supply.transfer:extend({name='powerplant-transfer-red'}) - } - }), - presets.upgrades.supply.powerplant2:extend({ - name='powerplant-building-red-2', - products = { - presets.missions.supply.convoy:extend({ name='powerplant-supply-red-1'}), - presets.missions.supply.transfer:extend({name='powerplant-transfer-red'}) - } - }) - }, - [2] = - { - presets.upgrades.basic.tent:extend({ - name='powerplant-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='powerplant-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='powerplant-garrison-blue'}) - } - }), - presets.upgrades.supply.powerplant1:extend({ - name='powerplant-building-blue-1', - products = { - presets.missions.supply.convoy:extend({ name='powerplant-supply-blue'}), - presets.missions.supply.transfer:extend({name='powerplant-transfer-blue'}) - } - }), - presets.upgrades.supply.powerplant2:extend({ - name='powerplant-building-blue-2', - products = { - presets.missions.supply.convoy:extend({ name='powerplant-supply-blue-1'}), - presets.missions.supply.transfer:extend({name='powerplant-transfer-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/PowerPlant.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Zugdidi.lua ]]----------------- - -zones.zugdidi = ZoneCommand:new("Zugdidi") -zones.zugdidi.initialState = { side=1 } -zones.zugdidi:defineUpgrades({ - [1] = - { - presets.upgrades.basic.comPost:extend({ - name='zugdidi-compost-red', - products = { - presets.missions.supply.transfer:extend({name='zugdidi-transfer-red'}), - presets.special.red.infantry:extend({ name='zugdidi-defense-red'}), - presets.defenses.red.infantry:extend({ name='zugdidi-garrison-red'}), - presets.missions.attack.surface:extend({name='zugdidi-attack-red'}), - presets.missions.supply.convoy:extend({name='zugdidi-supply-red'}) - } - }), - presets.upgrades.supply.hangar:extend({ - name='zugdidi-hangar-red-1', - products = { - presets.missions.supply.helo:extend({name='zugdidi-supply-red-1'}) - } - }), - presets.upgrades.supply.hangar:extend({ - name='zugdidi-hangar-red-2', - products = { - presets.missions.supply.helo:extend({name='zugdidi-supply-red-2'}) - } - }), - presets.upgrades.supply.hangar:extend({ - name='zugdidi-hangar-red-3', - products = { - presets.missions.supply.helo:extend({name='zugdidi-supply-red-3'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='zugdidi-comcenter-red', - products = { - presets.defenses.red.sa6:extend({ name='zugdidi-sam-red'}) - } - }) - }, - [2] = - { - presets.upgrades.basic.tent:extend({ - name='zugdidi-compost-blue', - products = { - presets.missions.supply.transfer:extend({name='zugdidi-transfer-blue'}), - presets.special.blue.infantry:extend({ name='zugdidi-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='zugdidi-garrison-blue'}), - presets.missions.attack.surface:extend({name='zugdidi-attack-blue'}), - presets.missions.supply.convoy:extend({name='zugdidi-supply-blue'}) - } - }), - presets.upgrades.supply.hangar:extend({ - name='zugdidi-hangar-blue-1', - products = { - presets.missions.supply.helo:extend({name='zugdidi-supply-blue-1'}) - } - }), - presets.upgrades.supply.hangar:extend({ - name='zugdidi-hangar-blue-2', - products = { - presets.missions.supply.helo:extend({name='zugdidi-supply-blue-2'}) - } - }), - presets.upgrades.supply.hangar:extend({ - name='zugdidi-hangar-blue-3', - products = { - presets.missions.supply.helo:extend({name='zugdidi-supply-blue-3'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='zugdidi-comcenter-blue', - products = { - presets.defenses.blue.shorad:extend({ name='zugdidi-sam-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Zugdidi.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Babugent.lua ]]----------------- - -zones.babugent = ZoneCommand:new("Babugent") -zones.babugent.initialState = { side=1 } -zones.babugent:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='babugent-tent-red', - products = { - presets.special.red.infantry:extend({ name='babugent-defense-red'}), - presets.defenses.red.infantry:extend({ name='babugent-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='babugent-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='babugent-supply-red'}), - presets.missions.supply.helo:extend({name='babugent-supply-red-2'}), - presets.missions.supply.transfer:extend({name='babugent-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='babugent-ammo-red', - products = { - presets.missions.attack.surface:extend({name='babugent-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='babugent-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='babugent-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='babugent-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='babugent-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='babugent-supply-blue'}), - presets.missions.supply.helo:extend({name='babugent-supply-blue-2'}), - presets.missions.supply.transfer:extend({name='babugent-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='babugent-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='babugent-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Babugent.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Kislovodsk.lua ]]----------------- - -zones.kislovodsk = ZoneCommand:new("Kislovodsk") -zones.kislovodsk.initialState = { side=1 } -zones.kislovodsk:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='kislovodsk-tent-red', - products = { - presets.special.red.infantry:extend({ name='kislovodsk-defense-red'}), - presets.defenses.red.infantry:extend({ name='kislovodsk-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='kislovodsk-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='kislovodsk-supply-red'}), - presets.missions.supply.transfer:extend({name='kislovodsk-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='kislovodsk-ammo-red', - products = { - presets.missions.attack.surface:extend({name='kislovodsk-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='kislovodsk-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='kislovodsk-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='kislovodsk-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='kislovodsk-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='kislovodsk-supply-blue'}), - presets.missions.supply.transfer:extend({name='kislovodsk-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='kislovodsk-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='kislovodsk-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Kislovodsk.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Gudauta.lua ]]----------------- - -zones.gudauta = ZoneCommand:new("Gudauta") -zones.gudauta.initialState = { side=1 } -zones.gudauta.keepActive = true -zones.gudauta.maxResource = 50000 -zones.gudauta:defineUpgrades({ - [1] = - { - presets.upgrades.basic.comPost:extend({ - name='gudauta-compost-red', - products = { - presets.special.red.infantry:extend({ name='gudauta-defense-red'}), - presets.defenses.red.infantry:extend({ name='gudauta-garrison-red'}) - } - }), - presets.upgrades.supply.fuelTank:extend({ - name='gudauta-fuel-red', - products = { - presets.missions.supply.helo:extend({name='gudauta-supply-red'}), - presets.missions.supply.helo:extend({name='gudauta-supply-red-1'}), - presets.missions.supply.transfer:extend({name='gudauta-transfer-red'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='gudauta-comcenter-red', - products = { - presets.defenses.red.sam:extend({ name='gudauta-airdef-red'}), - presets.missions.attack.sead:extend({name='gudauta-sead-red', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.attack.sead:extend({name='gudauta-sead-red-1', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.attack.cas:extend({name='gudauta-cas-red', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.bai:extend({name='gudauta-cas-red', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.patrol.aircraft:extend({name='gudauta-patrol-red', altitude=25000, range=25}) - } - }) - }, - [2] = - { - presets.upgrades.basic.comPost:extend({ - name='gudauta-compost-blue', - products = { - presets.special.blue.infantry:extend({ name='gudauta-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='gudauta-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelTank:extend({ - name='gudauta-fuel-blue', - products = { - presets.missions.supply.helo:extend({name='gudauta-supply-blue'}), - presets.missions.supply.helo:extend({name='gudauta-supply-blue-1'}), - presets.missions.supply.transfer:extend({name='gudauta-transfer-blue'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='gudauta-comcenter-blue', - products = { - presets.defenses.blue.sam:extend({ name='gudauta-airdef-blue'}), - presets.missions.attack.sead:extend({name='gudauta-sead-blue', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.attack.sead:extend({name='gudauta-sead-blue-1', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.attack.cas:extend({name='gudauta-cas-blue', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.bai:extend({name='gudauta-cas-blue', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.patrol.aircraft:extend({name='gudauta-patrol-blue', altitude=25000, range=25}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Gudauta.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Distillery.lua ]]----------------- - -zones.distillery = ZoneCommand:new("Distillery") -zones.distillery.initialState = { side=1 } -zones.distillery:defineUpgrades({ - [1] = - { - presets.upgrades.basic.tent:extend({ - name='distillery-tent-red', - products = { - presets.special.red.infantry:extend({ name='distillery-defense-red'}), - presets.defenses.red.infantry:extend({ name='distillery-garrison-red'}) - } - }), - presets.upgrades.supply.factory1:extend({ - name='distillery-prod-red-1', - products = { - presets.missions.supply.convoy:extend({ name='distillery-supply-red-1'}), - presets.missions.supply.transfer:extend({name='distillery-transfer-red'}) - } - }), - presets.upgrades.supply.factory2:extend({ - name='distillery-prod-red-2', - products = { - presets.missions.supply.convoy:extend({ name='distillery-supply-red-2', cost=2000}), - presets.missions.supply.transfer:extend({name='distillery-transfer-red2'}) - } - }), - presets.upgrades.supply.factoryTank:extend({ - name='distillery-tank-red-1', - products = { - } - }), - presets.upgrades.supply.factoryTank:extend({ - name='distillery-tank-red-2', - products = { - } - }), - presets.upgrades.supply.factoryTank:extend({ - name='distillery-tank-red-3', - products = { - } - }) - }, - [2] = - { - presets.upgrades.basic.tent:extend({ - name='distillery-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='distillery-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='distillery-garrison-blue'}) - } - }), - presets.upgrades.supply.factory1:extend({ - name='distillery-prod-blue-1', - products = { - presets.missions.supply.convoy:extend({ name='distillery-supply-blue-1'}), - presets.missions.supply.transfer:extend({name='distillery-transfer-blue'}) - } - }), - presets.upgrades.supply.factory2:extend({ - name='distillery-prod-blue-2', - products = { - presets.missions.supply.convoy:extend({ name='distillery-supply-blue-2', cost=2000}), - presets.missions.supply.transfer:extend({name='distillery-transfer-blue2'}) - } - }), - presets.upgrades.supply.factoryTank:extend({ - name='distillery-tank-blue-1', - products = { - } - }), - presets.upgrades.supply.factoryTank:extend({ - name='distillery-tank-blue-2', - products = { - } - }), - presets.upgrades.supply.factoryTank:extend({ - name='distillery-tank-blue-3', - products = { - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Distillery.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Sochi.lua ]]----------------- - -zones.sochi = ZoneCommand:new("Sochi") -zones.sochi.initialState = { side=1 } -zones.sochi.keepActive = true -zones.sochi.maxResource = 50000 -zones.sochi:defineUpgrades({ - [1] = - { - presets.upgrades.basic.comPost:extend({ - name='sochi-compost-red', - products = { - presets.special.red.infantry:extend({ name='sochi-defense-red'}), - presets.defenses.red.infantry:extend({ name='sochi-garrison-red'}) - } - }), - presets.upgrades.supply.fuelTank:extend({ - name='sochi-fuel-red', - products = { - presets.missions.supply.helo:extend({name='sochi-supply-red-1'}), - presets.missions.supply.helo:extend({name='sochi-supply-red-2'}), - presets.missions.supply.convoy_escorted:extend({name='sochi-supply-red-3'}), - presets.missions.supply.transfer:extend({name='sochi-transfer-red'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='sochi-comcenter-red', - products = { - presets.defenses.red.sa10:extend({ name='sochi-airdef-red'}), - presets.missions.attack.sead:extend({name='sochi-sead-red', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.attack.strike:extend({name='sochi-strike-red', altitude=30000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.patrol.aircraft:extend({name='sochi-patrol-red', altitude=25000, range=25}), - presets.missions.patrol.aircraft:extend({name='sochi-patrol-red-1', altitude=25000, range=25}), - presets.missions.attack.cas:extend({name='sochi-cas-red', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.bai:extend({name='sochi-cas-red', altitude=10000, expend=AI.Task.WeaponExpend.ONE}) - } - }) - }, - [2] = - { - presets.upgrades.basic.comPost:extend({ - name='sochi-compost-blue', - products = { - presets.special.blue.infantry:extend({ name='sochi-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='sochi-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelTank:extend({ - name='sochi-fuel-blue', - products = { - presets.missions.supply.helo:extend({name='sochi-supply-blue-1'}), - presets.missions.supply.helo:extend({name='sochi-supply-blue-2'}), - presets.missions.supply.convoy_escorted:extend({name='sochi-supply-blue-3'}), - presets.missions.supply.transfer:extend({name='sochi-transfer-blue'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='sochi-comcenter-blue', - products = { - presets.defenses.blue.patriot:extend({ name='sochi-airdef-blue'}), - presets.missions.attack.sead:extend({name='sochi-sead-blue', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.attack.strike:extend({name='sochi-strike-blue', altitude=30000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.patrol.aircraft:extend({name='sochi-patrol-blue', altitude=25000, range=25}), - presets.missions.attack.cas:extend({name='sochi-cas-blue', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.bai:extend({name='sochi-cas-blue', altitude=10000, expend=AI.Task.WeaponExpend.ONE}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Sochi.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Golf.lua ]]----------------- - -zones.golf = ZoneCommand:new("Golf") -zones.golf.initialState = nil -zones.golf.isHeloSpawn = true -zones.golf:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='golf-tent-red', - products = { - presets.special.red.infantry:extend({ name='golf-defense-red'}), - presets.defenses.red.infantry:extend({ name='golf-garrison-red'}) - } - }), - presets.upgrades.supply.fuelTank:extend({ - name='golf-fuel-red', - products = { - presets.missions.supply.helo:extend({name='golf-supply-red'}), - presets.missions.supply.helo:extend({name='golf-supply-red-1'}), - presets.missions.supply.transfer:extend({name='golf-transfer-red'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='golf-comcenter-red', - products = { - presets.defenses.red.shorad:extend({name='golf-sam-red'}), - presets.missions.attack.helo:extend({name='golf-cas-red', altitude=200, expend=AI.Task.WeaponExpend.HALF }) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='golf-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='golf-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='golf-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelTankFarp:extend({ - name='golf-fuel-blue', - products = { - presets.missions.supply.helo:extend({name='golf-supply-blue'}), - presets.missions.supply.helo:extend({name='golf-supply-blue-1'}), - presets.missions.supply.transfer:extend({name='golf-transfer-blue'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='golf-comcenter-blue', - products = { - presets.defenses.blue.shorad:extend({name='golf-sam-blue'}), - presets.missions.attack.helo:extend({name='golf-cas-blue', altitude=200, expend=AI.Task.WeaponExpend.HALF }) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Golf.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Charlie.lua ]]----------------- - -zones.charlie = ZoneCommand:new("Charlie") -zones.charlie.initialState = { side=2 } -zones.charlie.keepActive = true -zones.charlie:defineUpgrades({ - [1] = - { - presets.upgrades.basic.tent:extend({ - name='charlie-tent-red', - products = { - presets.special.red.infantry:extend({ name='charlie-defense-red'}), - presets.defenses.red.infantry:extend({ name='charlie-garrison-red'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='charlie-comcenter-red', - products = { - presets.defenses.red.sam:extend({ name='charlie-sam-red'}) - } - }) - }, - [2] = - { - presets.upgrades.basic.tent:extend({ - name='charlie-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='charlie-defense-red'}), - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='charlie-comcenter-blue', - products = { - presets.defenses.blue.sam:extend({ name='charlie-sam-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Charlie.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Lentehi.lua ]]----------------- - -zones.lentehi = ZoneCommand:new("Lentehi") -zones.lentehi.initialState = { side=1 } -zones.lentehi:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='lentehi-tent-red', - products = { - presets.special.red.infantry:extend({ name='lentehi-defense-red'}), - presets.defenses.red.infantry:extend({ name='lentehi-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='lentehi-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='lentehi-supply-red'}), - presets.missions.supply.helo:extend({name='lentehi-supply-red-2'}), - presets.missions.supply.transfer:extend({name='lentehi-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='lentehi-ammo-red', - products = { - presets.missions.attack.surface:extend({name='lentehi-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='lentehi-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='lentehi-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='lentehi-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='lentehi-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='lentehi-supply-blue'}), - presets.missions.supply.helo:extend({name='lentehi-supply-blue-2'}), - presets.missions.supply.transfer:extend({name='lentehi-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='lentehi-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='lentehi-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Lentehi.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Refinery.lua ]]----------------- - -zones.refinery = ZoneCommand:new("Refinery") -zones.refinery.initialState = { side=1 } -zones.refinery:defineUpgrades({ - [1] = - { - presets.upgrades.basic.tent:extend({ - name='refinery-tent-red', - products = { - presets.special.red.infantry:extend({ name='refinery-defense-red'}), - presets.defenses.red.infantry:extend({ name='refinery-garrison-red'}) - } - }), - presets.upgrades.supply.refinery1:extend({ - name='refinery-building-red', - products = { - presets.missions.supply.convoy:extend({ name='refinery-supply-red'}), - presets.missions.supply.convoy:extend({ name='refinery-supply-red-1'}), - presets.missions.supply.helo:extend({ name='refinery-supply-red-2'}), - presets.missions.supply.transfer:extend({name='refinery-transfer-red'}) - } - }) - }, - [2] = - { - presets.upgrades.basic.tent:extend({ - name='refinery-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='refinery-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='refinery-garrison-blue'}) - } - }), - presets.upgrades.supply.refinery1:extend({ - name='refinery-building-blue', - products = { - presets.missions.supply.convoy:extend({ name='refinery-supply-blue'}), - presets.missions.supply.convoy:extend({ name='refinery-supply-blue-1'}), - presets.missions.supply.helo:extend({ name='refinery-supply-blue-2'}), - presets.missions.supply.transfer:extend({name='refinery-transfer-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Refinery.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Mozdok.lua ]]----------------- - -zones.mozdok = ZoneCommand:new("Mozdok") -zones.mozdok.initialState = { side=1 } -zones.mozdok.keepActive = true -zones.mozdok.maxResource = 50000 -zones.mozdok:defineUpgrades({ - [1] = - { - presets.upgrades.basic.comPost:extend({ - name='mozdok-compost-red', - products = { - presets.special.red.infantry:extend({ name='mozdok-defense-red'}), - presets.defenses.red.infantry:extend({ name='mozdok-garrison-red'}) - } - }), - presets.upgrades.supply.fuelTank:extend({ - name='mozdok-fuel-red', - products = { - presets.missions.supply.helo:extend({name='mozdok-supply-red-1'}), - presets.missions.supply.helo:extend({name='mozdok-supply-red-2'}), - presets.missions.supply.convoy_escorted:extend({name='mozdok-supply-red-3'}), - presets.missions.supply.transfer:extend({name='mozdok-transfer-red'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='mozdok-comcenter-red', - products = { - presets.defenses.red.sa10:extend({ name='mozdok-airdef-red'}), - presets.missions.patrol.aircraft:extend({name='mozdok-patrol-red', altitude=25000, range=25}), - presets.missions.attack.cas:extend({name='mozdok-cas-red', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.bai:extend({name='mozdok-cas-red', altitude=10000, expend=AI.Task.WeaponExpend.ONE}) - } - }) - }, - [2] = - { - presets.upgrades.basic.comPost:extend({ - name='mozdok-compost-blue', - products = { - presets.special.blue.infantry:extend({ name='mozdok-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='mozdok-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelTank:extend({ - name='mozdok-fuel-blue', - products = { - presets.missions.supply.helo:extend({name='mozdok-supply-blue-1'}), - presets.missions.supply.helo:extend({name='mozdok-supply-blue-2'}), - presets.missions.supply.convoy_escorted:extend({name='mozdok-supply-blue-3'}), - presets.missions.supply.transfer:extend({name='mozdok-transfer-blue'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='mozdok-comcenter-blue', - products = { - presets.defenses.blue.shorad:extend({ name='mozdok-airdef-blue'}), - presets.missions.patrol.aircraft:extend({name='mozdok-patrol-blue', altitude=25000, range=25}), - presets.missions.attack.cas:extend({name='mozdok-cas-blue', altitude=15000, cost=50, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.cas:extend({name='mozdok-cas-blue-1', altitude=15000, cost=50, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.bai:extend({name='mozdok-cas-blue', altitude=15000, cost=50, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.bai:extend({name='mozdok-cas-blue-1', altitude=15000, cost=50, expend=AI.Task.WeaponExpend.ONE}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Mozdok.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Lima.lua ]]----------------- - -zones.lima = ZoneCommand:new("Lima") -zones.lima.initialState = { side=1 } -zones.lima:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='lima-tent-red', - products = { - presets.special.red.infantry:extend({ name='lima-defense-red'}), - presets.defenses.red.infantry:extend({ name='lima-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='lima-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='lima-supply-red'}), - presets.missions.supply.helo:extend({name='lima-supply-red-1'}), - presets.missions.supply.transfer:extend({name='lima-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='lima-ammo-red', - products = { - presets.missions.attack.surface:extend({name='lima-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='lima-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='lima-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='lima-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='lima-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='lima-supply-blue'}), - presets.missions.supply.helo:extend({name='lima-supply-blue-1'}), - presets.missions.supply.transfer:extend({name='lima-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='lima-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='lima-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Lima.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Oscar.lua ]]----------------- - -zones.oscar = ZoneCommand:new("Oscar") -zones.oscar.initialState = { side=1 } -zones.oscar:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='oscar-tent-red', - products = { - presets.special.red.infantry:extend({ name='oscar-defense-red'}), - presets.defenses.red.infantry:extend({ name='oscar-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='oscar-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='oscar-supply-red'}), - presets.missions.supply.transfer:extend({name='oscar-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='oscar-ammo-red', - products = { - presets.missions.attack.surface:extend({name='oscar-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='oscar-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='oscar-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='oscar-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='oscar-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='oscar-supply-blue'}), - presets.missions.supply.transfer:extend({name='oscar-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='oscar-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='oscar-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Oscar.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Nalchik.lua ]]----------------- - -zones.nalchik = ZoneCommand:new("Nalchik") -zones.nalchik.initialState = { side=1 } -zones.nalchik.keepActive = true -zones.nalchik.isHeloSpawn = true -zones.nalchik.isPlaneSpawn = true -zones.nalchik:defineUpgrades({ - [1] = - { - presets.upgrades.basic.comPost:extend({ - name='nalchik-compost-red', - products = { - presets.special.red.infantry:extend({ name='nalchik-defense-red'}), - presets.defenses.red.infantry:extend({ name='nalchik-garrison-red'}) - } - }), - presets.upgrades.supply.fuelTank:extend({ - name='nalchik-fuel-red', - products = { - presets.missions.supply.helo:extend({name='nalchik-supply-red-1'}), - presets.missions.supply.helo:extend({name='nalchik-supply-red-2'}), - presets.missions.supply.transfer:extend({name='nalchik-transfer-red'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='nalchik-comcenter-red', - products = { - presets.defenses.red.sa3:extend({ name='nalchik-airdef-red'}), - presets.missions.attack.sead:extend({name='nalchik-sead-red', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.attack.cas:extend({name='nalchik-cas-red', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.bai:extend({name='nalchik-cas-red', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.strike:extend({name='nalchik-strike-red', altitude=30000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.patrol.aircraft:extend({name='nalchik-patrol-red', altitude=25000, range=25}), - presets.missions.patrol.aircraft:extend({name='nalchik-patrol-red-2', altitude=25000, range=25}), - presets.missions.support.awacs:extend({name='nalchik-awacs-red', altitude=30000, freq=251.2}), - presets.missions.support.tanker:extend({name='nalchik-tanker-red', altitude=30000, freq=252.2, tacan='40', variant='Drogue'}) - } - }) - }, - [2] = - { - presets.upgrades.basic.comPost:extend({ - name='nalchik-compost-blue', - products = { - presets.special.blue.infantry:extend({ name='nalchik-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='nalchik-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelTank:extend({ - name='nalchik-fuel-blue', - products = { - presets.missions.supply.helo:extend({name='nalchik-supply-blue-1'}), - presets.missions.supply.helo:extend({name='nalchik-supply-blue-2'}), - presets.missions.supply.transfer:extend({name='nalchik-transfer-blue'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='nalchik-comcenter-blue', - products = { - presets.defenses.blue.nasams:extend({ name='nalchik-airdef-blue'}), - presets.missions.support.awacs:extend({name='nalchik-awacs-blue', altitude=30000, freq=259.5}), - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Nalchik.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Digora.lua ]]----------------- - -zones.digora = ZoneCommand:new("Digora") -zones.digora.initialState = { side=1 } -zones.digora:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='digora-tent-red', - products = { - presets.special.red.infantry:extend({ name='digora-defense-red'}), - presets.defenses.red.infantry:extend({ name='digora-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='digora-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='digora-supply-red'}), - presets.missions.supply.transfer:extend({name='digora-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='digora-ammo-red', - products = { - presets.missions.attack.surface:extend({name='digora-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='digora-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='digora-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='digora-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='digora-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='digora-supply-blue'}), - presets.missions.supply.transfer:extend({name='digora-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='digora-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='digora-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Digora.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Uniform.lua ]]----------------- - -zones.uniform = ZoneCommand:new("Uniform") -zones.uniform.initialState = { side=1 } -zones.uniform:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='uniform-tent-red', - products = { - presets.special.red.infantry:extend({ name='uniform-defense-red'}), - presets.defenses.red.infantry:extend({ name='uniform-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='uniform-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='uniform-supply-red'}), - presets.missions.supply.transfer:extend({name='uniform-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='uniform-ammo-red', - products = { - presets.missions.attack.surface:extend({name='uniform-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='uniform-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='uniform-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='uniform-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='uniform-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='uniform-supply-blue'}), - presets.missions.supply.transfer:extend({name='uniform-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='uniform-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='uniform-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Uniform.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Factory.lua ]]----------------- - -zones.factory = ZoneCommand:new("Factory") -zones.factory.initialState = { side=2 } -zones.factory:defineUpgrades({ - [1] = - { - presets.upgrades.basic.tent:extend({ - name='factory-tent-red', - products = { - presets.special.red.infantry:extend({ name='factory-defense-red'}), - presets.defenses.red.infantry:extend({ name='factory-garrison-red'}) - } - }), - presets.upgrades.supply.factory1:extend({ - name='factory-prod-red-1', - products = { - presets.missions.supply.convoy:extend({ name='factory-supply-red-1'}), - presets.missions.supply.transfer:extend({name='factory-transfer-red'}) - } - }), - presets.upgrades.supply.factory2:extend({ - name='factory-prod-red-2', - products = { - presets.missions.supply.convoy:extend({ name='factory-supply-red-2', cost=2000}), - presets.missions.supply.transfer:extend({name='factory-transfer-red2'}) - } - }), - presets.upgrades.supply.factoryTank:extend({ - name='factory-tank-red-1', - products = { - } - }), - presets.upgrades.supply.factoryTank:extend({ - name='factory-tank-red-2', - products = { - } - }), - presets.upgrades.supply.factoryTank:extend({ - name='factory-tank-red-3', - products = { - } - }) - }, - [2] = - { - presets.upgrades.basic.tent:extend({ - name='factory-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='factory-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='factory-garrison-blue'}) - } - }), - presets.upgrades.supply.factory1:extend({ - name='factory-prod-blue-1', - products = { - presets.missions.supply.convoy:extend({ name='factory-supply-blue-1'}), - presets.missions.supply.transfer:extend({name='factory-transfer-blue'}) - } - }), - presets.upgrades.supply.factory2:extend({ - name='factory-prod-blue-2', - products = { - presets.missions.supply.convoy:extend({ name='factory-supply-blue-2', cost=2000}), - presets.missions.supply.transfer:extend({name='factory-transfer-blue2'}) - } - }), - presets.upgrades.supply.factoryTank:extend({ - name='factory-tank-blue-1', - products = { - } - }), - presets.upgrades.supply.factoryTank:extend({ - name='factory-tank-blue-2', - products = { - } - }), - presets.upgrades.supply.factoryTank:extend({ - name='factory-tank-blue-3', - products = { - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Factory.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Senaki.lua ]]----------------- - -zones.senaki = ZoneCommand:new("Senaki") -zones.senaki.initialState = { side=1 } -zones.senaki.keepActive = true -zones.senaki.isHeloSpawn = true -zones.senaki.isPlaneSpawn = true -zones.senaki.maxResource = 50000 -zones.senaki:defineUpgrades({ - [1] = - { - presets.upgrades.basic.comPost:extend({ - name='senaki-compost-red', - products = { - presets.special.red.infantry:extend({ name='senaki-defense-red'}), - presets.defenses.red.infantry:extend({ name='senaki-garrison-red'}) - } - }), - presets.upgrades.supply.fuelTank:extend({ - name='senaki-fuel-red', - products = { - presets.missions.supply.helo:extend({name='senaki-supply-red-1'}), - presets.missions.supply.helo:extend({name='senaki-supply-red-2'}), - presets.missions.supply.transfer:extend({name='senaki-transfer-red'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='senaki-comcenter-red', - products = { - presets.defenses.red.sa3:extend({ name='senaki-airdef-red'}), - presets.missions.attack.sead:extend({name='senaki-sead-red', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.attack.cas:extend({name='senaki-cas-red', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.bai:extend({name='senaki-cas-red', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.strike:extend({name='senaki-strike-red', altitude=20000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.patrol.aircraft:extend({name='senaki-patrol-red', altitude=25000, range=25}), - presets.missions.patrol.aircraft:extend({name='senaki-patrol-red-2', altitude=20000, range=25}) - } - }) - }, - [2] = - { - presets.upgrades.basic.comPost:extend({ - name='senaki-compost-blue', - products = { - presets.special.blue.infantry:extend({ name='senaki-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='senaki-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelTank:extend({ - name='senaki-fuel-blue', - products = { - presets.missions.supply.helo:extend({name='senaki-supply-blue-1'}), - presets.missions.supply.helo:extend({name='senaki-supply-blue-2'}), - presets.missions.supply.transfer:extend({name='senaki-transfer-blue'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='senaki-comcenter-blue', - products = { - presets.defenses.blue.shorad:extend({ name='senaki-airdef-blue'}), - presets.missions.attack.sead:extend({name='senaki-sead-blue', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.attack.cas:extend({name='senaki-cas-blue', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.bai:extend({name='senaki-cas-blue', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.strike:extend({name='senaki-strike-blue', altitude=20000, expend=AI.Task.WeaponExpend.TWO}), - presets.missions.patrol.aircraft:extend({name='senaki-patrol-blue', altitude=25000, range=25}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Senaki.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Kutaisi.lua ]]----------------- - -zones.kutaisi = ZoneCommand:new("Kutaisi") -zones.kutaisi.initialState = { side=1 } -zones.kutaisi.keepActive = true -zones.kutaisi.maxResource = 50000 -zones.kutaisi:defineUpgrades({ - [1] = - { - presets.upgrades.basic.comPost:extend({ - name='kutaisi-compost-red', - products = { - presets.special.red.infantry:extend({ name='kutaisi-defense-red'}), - presets.defenses.red.infantry:extend({ name='kutaisi-garrison-red'}) - } - }), - presets.upgrades.supply.fuelTank:extend({ - name='kutaisi-fuel-red', - products = { - presets.missions.supply.helo:extend({name='kutaisi-supply-red-1'}), - presets.missions.supply.helo:extend({name='kutaisi-supply-red-2'}), - presets.missions.supply.transfer:extend({name='kutaisi-transfer-red'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='kutaisi-comcenter-red', - products = { - presets.defenses.red.shorad:extend({ name='kutaisi-airdef-red'}), - presets.missions.attack.sead:extend({name='kutaisi-sead-red', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.attack.cas:extend({name='kutaisi-cas-red', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.bai:extend({name='kutaisi-cas-red', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.strike:extend({name='kutaisi-strike-red', altitude=20000, expend=AI.Task.WeaponExpend.HALF}), - presets.missions.patrol.aircraft:extend({name='kutaisi-patrol-red', altitude=25000, range=25}), - presets.missions.patrol.aircraft:extend({name='kutaisi-patrol-red-2', altitude=25000, range=25}) - } - }) - }, - [2] = - { - presets.upgrades.basic.comPost:extend({ - name='kutaisi-compost-blue', - products = { - presets.special.blue.infantry:extend({ name='kutaisi-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='kutaisi-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelTank:extend({ - name='kutaisi-fuel-blue', - products = { - presets.missions.supply.helo:extend({name='kutaisi-supply-blue-1'}), - presets.missions.supply.helo:extend({name='kutaisi-supply-blue-2'}), - presets.missions.supply.transfer:extend({name='kutaisi-transfer-blue'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='kutaisi-comcenter-blue', - products = { - presets.defenses.blue.shorad:extend({ name='kutaisi-airdef-blue'}), - presets.missions.attack.sead:extend({name='kutaisi-sead-blue', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.attack.cas:extend({name='kutaisi-cas-blue', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.bai:extend({name='kutaisi-cas-blue', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.strike:extend({name='kutaisi-strike-blue', altitude=20000, expend=AI.Task.WeaponExpend.TWO}), - presets.missions.patrol.aircraft:extend({name='kutaisi-patrol-blue', altitude=25000, range=25}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Kutaisi.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Prohladniy.lua ]]----------------- - -zones.prohladniy = ZoneCommand:new("Prohladniy") -zones.prohladniy.initialState = { side=1 } -zones.prohladniy:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='prohladniy-tent-red', - products = { - presets.special.red.infantry:extend({ name='prohladniy-defense-red'}), - presets.defenses.red.infantry:extend({ name='prohladniy-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='prohladniy-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='prohladniy-supply-red'}), - presets.missions.supply.transfer:extend({name='prohladniy-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='prohladniy-ammo-red', - products = { - presets.missions.attack.surface:extend({name='prohladniy-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='prohladniy-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='prohladniy-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='prohladniy-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='prohladniy-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='prohladniy-supply-blue'}), - presets.missions.supply.transfer:extend({name='prohladniy-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='prohladniy-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='prohladniy-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Prohladniy.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Tallyk.lua ]]----------------- - -zones.tallyk = ZoneCommand:new("Tallyk") -zones.tallyk.initialState = { side=1 } -zones.tallyk.keepActive = true -zones.tallyk:defineUpgrades({ - [1] = - { - presets.upgrades.basic.tent:extend({ - name='tallyk-tent-red', - products = { - presets.special.red.infantry:extend({ name='tallyk-defense-red'}), - presets.defenses.red.infantry:extend({ name='tallyk-garrison-red'}), - presets.missions.attack.surface:extend({name='tallyk-assault-red'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='tallyk-comcenter-red', - products = { - presets.defenses.red.sam:extend({ name='tallyk-sam-red'}) - } - }) - }, - [2] = - { - presets.upgrades.basic.tent:extend({ - name='tallyk-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='tallyk-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='tallyk-garrison-blue'}), - presets.missions.attack.surface:extend({name='tallyk-assault-blue'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='tallyk-comcenter-blue', - products = { - presets.defenses.blue.sam:extend({ name='tallyk-sam-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Tallyk.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Terek.lua ]]----------------- - -zones.terek = ZoneCommand:new("Terek") -zones.terek.initialState = { side=1 } -zones.terek:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='terek-tent-red', - products = { - presets.special.red.infantry:extend({ name='terek-defense-red'}), - presets.defenses.red.infantry:extend({ name='terek-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='terek-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='terek-supply-red'}), - presets.missions.supply.transfer:extend({name='terek-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='terek-ammo-red', - products = { - presets.missions.attack.surface:extend({name='terek-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='terek-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='terek-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='terek-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='terek-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='terek-supply-blue'}), - presets.missions.supply.transfer:extend({name='terek-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='terek-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='terek-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Terek.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Humara.lua ]]----------------- - -zones.humara = ZoneCommand:new("Humara") -zones.humara.initialState = { side=1 } -zones.humara:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='humara-tent-red', - products = { - presets.special.red.infantry:extend({ name='humara-defense-red'}), - presets.defenses.red.infantry:extend({ name='humara-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='humara-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='humara-supply-red'}), - presets.missions.supply.transfer:extend({name='humara-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='humara-ammo-red', - products = { - presets.missions.attack.surface:extend({name='humara-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='humara-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='humara-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='humara-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='humara-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='humara-supply-blue'}), - presets.missions.supply.transfer:extend({name='humara-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='humara-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='humara-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Humara.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Ochamchira.lua ]]----------------- - -zones.ochamchira = ZoneCommand:new("Ochamchira") -zones.ochamchira.initialState = { side=1 } -zones.ochamchira:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='ochamchira-tent-red', - products = { - presets.special.red.infantry:extend({ name='ochamchira-defense-red'}), - presets.defenses.red.infantry:extend({ name='ochamchira-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='ochamchira-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='ochamchira-supply-red'}), - presets.missions.supply.transfer:extend({name='ochamchira-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='ochamchira-ammo-red', - products = { - presets.missions.attack.surface:extend({name='ochamchira-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='ochamchira-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='ochamchira-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='ochamchira-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='ochamchira-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='ochamchira-supply-blue'}), - presets.missions.supply.transfer:extend({name='ochamchira-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='ochamchira-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='ochamchira-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Ochamchira.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/November.lua ]]----------------- - -zones.november = ZoneCommand:new("November") -zones.november.initialState = { side=1 } -zones.november.isHeloSpawn = true -zones.november:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='november-tent-red', - products = { - presets.special.red.infantry:extend({ name='november-defense-red'}), - presets.defenses.red.infantry:extend({ name='november-garrison-red'}) - } - }), - presets.upgrades.supply.fuelTankFarp:extend({ - name='november-fuel-red', - products = { - presets.missions.supply.helo:extend({name='november-supply-red'}), - presets.missions.supply.helo:extend({name='november-supply-red-1'}), - presets.missions.supply.transfer:extend({name='november-transfer-red'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='november-comcenter-red', - products = { - presets.defenses.red.shorad:extend({name='november-sam-red'}), - presets.missions.attack.helo:extend({name='november-cas-red', altitude=200, expend=AI.Task.WeaponExpend.HALF }) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='november-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='november-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='november-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelTankFarp:extend({ - name='november-fuel-blue', - products = { - presets.missions.supply.helo:extend({name='november-supply-blue'}), - presets.missions.supply.helo:extend({name='november-supply-blue-1'}), - presets.missions.supply.transfer:extend({name='november-transfer-blue'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='november-comcenter-blue', - products = { - presets.defenses.blue.shorad:extend({name='november-sam-blue'}), - presets.missions.attack.helo:extend({name='november-cas-blue', altitude=200, expend=AI.Task.WeaponExpend.HALF }) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/November.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/XRay.lua ]]----------------- - -zones.xray = ZoneCommand:new("XRay") -zones.xray.initialState = { side=1 } -zones.xray:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='xray-tent-red', - products = { - presets.special.red.infantry:extend({ name='xray-defense-red'}), - presets.defenses.red.infantry:extend({ name='xray-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='xray-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='xray-supply-red'}), - presets.missions.supply.helo:extend({name='xray-supply-red-2'}), - presets.missions.supply.transfer:extend({name='xray-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='xray-ammo-red', - products = { - presets.missions.attack.surface:extend({name='xray-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='xray-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='xray-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='xray-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='xray-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='xray-supply-blue'}), - presets.missions.supply.helo:extend({name='xray-supply-blue-2'}), - presets.missions.supply.transfer:extend({name='xray-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='xray-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='xray-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/XRay.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Whiskey.lua ]]----------------- - -zones.whiskey = ZoneCommand:new("Whiskey") -zones.whiskey.initialState = { side=1 } -zones.whiskey:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='whiskey-tent-red', - products = { - presets.special.red.infantry:extend({ name='whiskey-defense-red'}), - presets.defenses.red.infantry:extend({ name='whiskey-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='whiskey-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='whiskey-supply-red'}), - presets.missions.supply.transfer:extend({name='whiskey-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='whiskey-ammo-red', - products = { - presets.missions.attack.surface:extend({name='whiskey-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='whiskey-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='whiskey-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='whiskey-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='whiskey-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='whiskey-supply-blue'}), - presets.missions.supply.transfer:extend({name='whiskey-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='whiskey-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='whiskey-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Whiskey.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Mine.lua ]]----------------- - -zones.mine = ZoneCommand:new("Mine") -zones.mine.initialState = { side=1 } -zones.mine:defineUpgrades({ - [1] = - { - presets.upgrades.basic.tent:extend({ - name='mine-tent-red', - products = { - presets.special.red.infantry:extend({ name='mine-defense-red'}), - presets.defenses.red.infantry:extend({ name='mine-garrison-red'}) - } - }), - presets.upgrades.supply.excavator:extend({ - name='mine-excavator-red-1', - products = { - presets.missions.supply.convoy:extend({ name='mine-supply-red'}), - presets.missions.supply.transfer:extend({name='mine-transfer-red'}) - } - }), - presets.upgrades.supply.excavator:extend({ - name='mine-excavator-red-2', - products = { - presets.missions.supply.convoy:extend({ name='mine-supply-red'}), - presets.missions.supply.transfer:extend({name='mine-transfer-red'}) - } - }), - presets.upgrades.supply.excavator:extend({ - name='mine-excavator-red-3', - products = { - presets.missions.supply.convoy:extend({ name='mine-supply-red'}), - presets.missions.supply.transfer:extend({name='mine-transfer-red'}) - } - }) - }, - [2] = - { - presets.upgrades.basic.tent:extend({ - name='mine-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='mine-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='mine-garrison-blue'}) - } - }), - presets.upgrades.supply.excavator:extend({ - name='mine-excavator-blue-1', - products = { - presets.missions.supply.convoy:extend({ name='mine-supply-blue'}), - presets.missions.supply.transfer:extend({name='mine-transfer-blue'}) - } - }), - presets.upgrades.supply.excavator:extend({ - name='mine-excavator-blue-2', - products = { - presets.missions.supply.convoy:extend({ name='mine-supply-blue'}), - presets.missions.supply.transfer:extend({name='mine-transfer-blue'}) - } - }), - presets.upgrades.supply.excavator:extend({ - name='mine-excavator-blue-3', - products = { - presets.missions.supply.convoy:extend({ name='mine-supply-blue'}), - presets.missions.supply.transfer:extend({name='mine-transfer-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Mine.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Papa.lua ]]----------------- - -zones.papa = ZoneCommand:new("Papa") -zones.papa.initialState = { side=1 } -zones.papa.keepActive = true -zones.papa:defineUpgrades({ - [1] = - { - presets.upgrades.basic.tent:extend({ - name='papa-tent-red', - products = { - presets.special.red.infantry:extend({ name='papa-defense-red'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='papa-comcenter-red', - products = { - presets.defenses.red.sam:extend({ name='papa-sam-red'}) - } - }) - }, - [2] = - { - presets.upgrades.basic.tent:extend({ - name='papa-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='papa-defense-blue'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='papa-comcenter-blue', - products = { - presets.defenses.blue.sam:extend({ name='papa-sam-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Papa.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Sukhumi.lua ]]----------------- - -zones.sukhumi = ZoneCommand:new("Sukhumi") -zones.sukhumi.initialState = { side=1 } -zones.sukhumi.keepActive = true -zones.sukhumi.maxResource = 50000 -zones.sukhumi:defineUpgrades({ - [1] = - { - presets.upgrades.basic.comPost:extend({ - name='sukhumi-compost-red', - products = { - presets.special.red.infantry:extend({ name='sukhumi-defense-red'}), - presets.defenses.red.infantry:extend({ name='sukhumi-garrison-red'}) - } - }), - presets.upgrades.supply.fuelTank:extend({ - name='sukhumi-fuel-red', - products = { - presets.missions.supply.helo:extend({name='sukhumi-supply-red-1'}), - presets.missions.supply.helo:extend({name='sukhumi-supply-red-2'}), - presets.missions.supply.transfer:extend({name='sukhumi-transfer-red'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='sukhumi-comcenter-red', - products = { - presets.defenses.red.sa11:extend({ name='sukhumi-airdef-red'}), - presets.missions.attack.sead:extend({name='sukhumi-sead-red', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.attack.cas:extend({name='sukhumi-cas-red', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.bai:extend({name='sukhumi-cas-red', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.strike:extend({name='sukhumi-strike-red', altitude=20000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.patrol.aircraft:extend({name='sukhumi-patrol-red', altitude=25000, range=25}), - presets.missions.patrol.aircraft:extend({name='sukhumi-patrol-red-2', altitude=25000, range=25}) - } - }) - }, - [2] = - { - presets.upgrades.basic.comPost:extend({ - name='sukhumi-compost-blue', - products = { - presets.special.blue.infantry:extend({ name='sukhumi-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='sukhumi-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelTank:extend({ - name='sukhumi-fuel-blue', - products = { - presets.missions.supply.helo:extend({name='sukhumi-supply-blue-1'}), - presets.missions.supply.helo:extend({name='sukhumi-supply-blue-2'}), - presets.missions.supply.transfer:extend({name='sukhumi-transfer-blue'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='sukhumi-comcenter-blue', - products = { - presets.defenses.blue.nasams:extend({ name='sukhumi-airdef-blue'}), - presets.missions.attack.sead:extend({name='sukhumi-sead-blue', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.attack.cas:extend({name='sukhumi-cas-blue', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.bai:extend({name='sukhumi-cas-blue', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.strike:extend({name='sukhumi-strike-blue', altitude=20000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.patrol.aircraft:extend({name='sukhumi-patrol-blue', altitude=25000, range=25}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Sukhumi.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Farm.lua ]]----------------- - -zones.farm = ZoneCommand:new("Farm") -zones.farm.initialState = { side=1 } -zones.farm:defineUpgrades({ - [1] = - { - presets.upgrades.basic.tent:extend({ - name='farm-tent-red', - products = { - presets.special.red.infantry:extend({ name='farm-defense-red'}), - presets.defenses.red.infantry:extend({ name='farm-garrison-red'}) - } - }), - presets.upgrades.supply.farm1:extend({ - name='farm-prod-red-1', - products = { - presets.missions.supply.convoy:extend({ name='farm-supply-red'}), - presets.missions.supply.transfer:extend({name='farm-transfer-red'}) - } - }), - presets.upgrades.supply.farm2:extend({ - name='farm-prod-red-2', - products = { - presets.missions.supply.convoy:extend({ name='farm-supply-red'}), - presets.missions.supply.transfer:extend({name='farm-transfer-red'}) - } - }) - }, - [2] = - { - presets.upgrades.basic.tent:extend({ - name='farm-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='farm-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='farm-garrison-blue'}) - } - }), - presets.upgrades.supply.farm1:extend({ - name='farm-prod-blue-1', - products = { - presets.missions.supply.convoy:extend({ name='farm-supply-blue'}), - presets.missions.supply.transfer:extend({name='farm-transfer-blue'}) - } - }), - presets.upgrades.supply.farm2:extend({ - name='farm-prod-blue-2', - products = { - presets.missions.supply.convoy:extend({ name='farm-supply-blue'}), - presets.missions.supply.transfer:extend({name='farm-transfer-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Farm.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Romeo.lua ]]----------------- - -zones.romeo = ZoneCommand:new("Romeo") -zones.romeo.initialState = { side=1 } -zones.romeo:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='romeo-tent-red', - products = { - presets.special.red.infantry:extend({ name='romeo-defense-red'}), - presets.defenses.red.infantry:extend({ name='romeo-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='romeo-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='romeo-supply-red'}), - presets.missions.supply.transfer:extend({name='romeo-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='romeo-ammo-red', - products = { - presets.missions.attack.surface:extend({name='romeo-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='romeo-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='romeo-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='romeo-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='romeo-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='romeo-supply-blue'}), - presets.missions.supply.transfer:extend({name='romeo-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='romeo-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='romeo-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Romeo.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Zulu.lua ]]----------------- - -zones.zulu = ZoneCommand:new("Zulu") -zones.zulu.initialState = { side=1 } -zones.zulu:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='zulu-tent-red', - products = { - presets.special.red.infantry:extend({ name='zulu-defense-red'}), - presets.defenses.red.infantry:extend({ name='zulu-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='zulu-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='zulu-supply-red'}), - presets.missions.supply.transfer:extend({name='zulu-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='zulu-ammo-red', - products = { - presets.missions.attack.surface:extend({name='zulu-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='zulu-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='zulu-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='zulu-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='zulu-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='zulu-supply-blue'}), - presets.missions.supply.transfer:extend({name='zulu-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='zulu-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='zulu-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Zulu.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Yankee.lua ]]----------------- - -zones.yankee = ZoneCommand:new("Yankee") -zones.yankee.initialState = { side=1 } -zones.yankee:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='yankee-tent-red', - products = { - presets.special.red.infantry:extend({ name='yankee-defense-red'}), - presets.defenses.red.infantry:extend({ name='yankee-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='yankee-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='yankee-supply-red'}), - presets.missions.supply.transfer:extend({name='yankee-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='yankee-ammo-red', - products = { - presets.missions.attack.surface:extend({name='yankee-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='yankee-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='yankee-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='yankee-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='yankee-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='yankee-supply-blue'}), - presets.missions.supply.transfer:extend({name='yankee-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='yankee-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='yankee-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Yankee.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Malgobek.lua ]]----------------- - -zones.malgobek = ZoneCommand:new("Malgobek") -zones.malgobek.initialState = { side=1 } -zones.malgobek:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='malgobek-tent-red', - products = { - presets.special.red.infantry:extend({ name='malgobek-defense-red'}), - presets.defenses.red.infantry:extend({ name='malgobek-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='malgobek-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='malgobek-supply-red'}), - presets.missions.supply.transfer:extend({name='malgobek-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='malgobek-ammo-red', - products = { - presets.missions.attack.surface:extend({name='malgobek-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='malgobek-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='malgobek-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='malgobek-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='malgobek-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='malgobek-supply-blue'}), - presets.missions.supply.transfer:extend({name='malgobek-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='malgobek-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='malgobek-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Malgobek.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Kilo.lua ]]----------------- - -zones.kilo = ZoneCommand:new("Kilo") -zones.kilo.initialState = { side=1 } -zones.kilo:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='kilo-tent-red', - products = { - presets.special.red.infantry:extend({ name='kilo-defense-red'}), - presets.defenses.red.infantry:extend({ name='kilo-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='kilo-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='kilo-supply-red'}), - presets.missions.supply.transfer:extend({name='kilo-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='kilo-ammo-red', - products = { - presets.missions.attack.surface:extend({name='kilo-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='kilo-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='kilo-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='kilo-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='kilo-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='kilo-supply-blue'}), - presets.missions.supply.transfer:extend({name='kilo-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='kilo-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='kilo-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Kilo.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Quebec.lua ]]----------------- - -zones.quebec = ZoneCommand:new("Quebec") -zones.quebec.initialState = { side=1 } -zones.quebec.keepActive = true -zones.quebec:defineUpgrades({ - [1] = - { - presets.upgrades.basic.tent:extend({ - name='quebec-tent-red', - products = { - presets.special.red.infantry:extend({ name='quebec-defense-red'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='quebec-comcenter-red', - products = { - presets.defenses.red.sam:extend({ name='quebec-sam-red'}) - } - }) - }, - [2] = - { - presets.upgrades.basic.tent:extend({ - name='quebec-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='quebec-defense-blue'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='quebec-comcenter-blue', - products = { - presets.defenses.blue.sam:extend({ name='quebec-sam-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Quebec.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/OilFields.lua ]]----------------- - -zones.oilfields = ZoneCommand:new("Oil Fields") -zones.oilfields.initialState = { side=1 } -zones.oilfields:defineUpgrades({ - [1] = { - presets.upgrades.basic.outpost:extend({ - name='oilfields-outpost-red', - products = { - presets.special.red.infantry:extend({ name='oilfields-defense-red'}), - presets.defenses.red.infantry:extend({ name='oilfields-garrison-red'}) - } - }), - presets.upgrades.supply.oilPump:extend({ - name='oilfields-pump-red-1', - products = { - presets.missions.supply.transfer:extend({name='oilfields-transfer-red1'}) - } - }), - presets.upgrades.supply.oilPump:extend({ - name='oilfields-pump-red-2', - products = { - presets.missions.supply.convoy:extend({name='oilfields-supply-red-1'}) - } - }), - presets.upgrades.supply.oilPump:extend({ - name='oilfields-pump-red-3', - products = { - presets.missions.supply.transfer:extend({name='oilfields-transfer-red2'}) - } - }), - presets.upgrades.supply.oilPump:extend({ - name='oilfields-pump-red-4', - products = { - presets.missions.supply.convoy:extend({name='oilfields-supply-red-2'}) - } - }) - }, - [2] = { - presets.upgrades.basic.outpost:extend({ - name='oilfields-outpost-blue', - products = { - presets.special.blue.infantry:extend({ name='oilfields-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='oilfields-garrison-blue'}) - } - }), - presets.upgrades.supply.oilPump:extend({ - name='oilfields-pump-blue-1', - products = { - presets.missions.supply.transfer:extend({name='oilfields-transfer-blue1'}) - } - }), - presets.upgrades.supply.oilPump:extend({ - name='oilfields-pump-blue-2', - products = { - presets.missions.supply.convoy:extend({name='oilfields-supply-blue-1'}) - } - }), - presets.upgrades.supply.oilPump:extend({ - name='oilfields-pump-blue-3', - products = { - presets.missions.supply.transfer:extend({name='oilfields-transfer-blue2'}) - } - }), - presets.upgrades.supply.oilPump:extend({ - name='oilfields-pump-blue-4', - products = { - presets.missions.supply.convoy:extend({name='oilfields-supply-blue-2'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/OilFields.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Echo.lua ]]----------------- - -zones.echo = ZoneCommand:new("Echo") -zones.echo.initialState = { side=2 } -zones.echo:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='echo-tent-red', - products = { - presets.special.red.infantry:extend({ name='echo-defense-red'}), - presets.defenses.red.infantry:extend({ name='echo-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='echo-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='echo-supply-red'}), - presets.missions.supply.transfer:extend({name='echo-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='echo-ammo-red', - products = { - presets.missions.attack.surface:extend({name='echo-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='echo-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='echo-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='echo-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='echo-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='echo-supply-blue'}), - presets.missions.supply.transfer:extend({name='echo-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='echo-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='echo-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Echo.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Kobuleti.lua ]]----------------- - -zones.kobuleti = ZoneCommand:new("Kobuleti") -zones.kobuleti.initialState = { side=2 } -zones.kobuleti.keepActive = true -zones.kobuleti.isHeloSpawn = true -zones.kobuleti.isPlaneSpawn = true -zones.kobuleti.maxResource = 50000 -zones.kobuleti:defineUpgrades({ - [1] = - { - presets.upgrades.basic.comPost:extend({ - name='kobuleti-compost-red', - products = { - presets.special.red.infantry:extend({ name='kobuleti-defense-red'}), - presets.defenses.red.infantry:extend({ name='kobuleti-garrison-red'}), - presets.missions.attack.surface:extend({ name='kobuleti-assault-red'}) - } - }), - presets.upgrades.supply.fuelTank:extend({ - name='kobuleti-fuel-red', - products = { - presets.missions.supply.helo:extend({name='kobuleti-supply-red-1'}), - presets.missions.supply.helo:extend({name='kobuleti-supply-red-2'}), - presets.missions.supply.transfer:extend({name='kobuleti-transfer-red'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='kobuleti-comcenter-red', - products = { - presets.defenses.red.shorad:extend({ name='kobuleti-airdef-red'}), - presets.missions.attack.sead:extend({name='kobuleti-sead-red', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.attack.cas:extend({name='kobuleti-cas-red', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.bai:extend({name='kobuleti-cas-red', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.strike:extend({name='kobuleti-strike-red', altitude=20000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.patrol.aircraft:extend({name='kobuleti-patrol-red', altitude=25000, range=25}) - } - }) - }, - [2] = - { - presets.upgrades.basic.comPost:extend({ - name='kobuleti-compost-blue', - products = { - presets.special.blue.infantry:extend({ name='kobuleti-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='kobuleti-garrison-blue'}), - presets.missions.attack.surface:extend({ name='kobuleti-assault-blue'}) - } - }), - presets.upgrades.supply.fuelTank:extend({ - name='kobuleti-fuel-blue', - products = { - presets.missions.supply.helo:extend({name='kobuleti-supply-blue-1'}), - presets.missions.supply.helo:extend({name='kobuleti-supply-blue-2'}), - presets.missions.supply.transfer:extend({name='kobuleti-transfer-blue'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='kobuleti-comcenter-blue', - products = { - presets.defenses.blue.shorad:extend({ name='kobuleti-airdef-blue'}), - presets.missions.attack.sead:extend({name='kobuleti-sead-blue', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.attack.cas:extend({name='kobuleti-cas-blue', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.bai:extend({name='kobuleti-cas-blue', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.strike:extend({name='kobuleti-strike-blue', altitude=20000, expend=AI.Task.WeaponExpend.TWO}), - presets.missions.patrol.aircraft:extend({name='kobuleti-patrol-blue', altitude=25000, range=25}), - presets.missions.support.awacs:extend({name='kobuleti-awacs-blue', altitude=30000, freq=258.5}), - presets.missions.support.tanker:extend({name='kobuleti-tanker-blue', altitude=23000, freq=258, tacan='38', variant='Boom'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Kobuleti.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Alpha.lua ]]----------------- - -zones.alpha = ZoneCommand:new('Alpha') -zones.alpha.initialState = { side=2 } -zones.alpha:defineUpgrades({ - [1] = --red side - { - presets.upgrades.basic.tent:extend({ - name = 'alpha-tent-red', - products = { - presets.special.red.infantry:extend({ name='alpha-defense-red'}), - presets.defenses.red.infantry:extend({ name='alpha-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name = 'alpha-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({ name='alpha-supply-red'}), - presets.missions.supply.transfer:extend({name='alpha-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name = 'alpha-ammo-red', - products = { - presets.missions.attack.surface:extend({ name='alpha-assault-red'}) - } - }) - }, - [2] = --blue side - { - presets.upgrades.basic.tent:extend({ - name = 'alpha-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='alpha-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='alpha-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name = 'alpha-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({ name='alpha-supply-blue'}), - presets.missions.supply.transfer:extend({name='alpha-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name = 'alpha-ammo-blue', - products = { - presets.missions.attack.surface:extend({ name='alpha-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Alpha.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Foxtrot.lua ]]----------------- - -zones.foxtrot = ZoneCommand:new("Foxtrot") -zones.foxtrot.initialState = { side=2 } -zones.foxtrot:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='foxtrot-tent-red', - products = { - presets.special.red.infantry:extend({ name='foxtrot-defense-red'}), - presets.defenses.red.infantry:extend({ name='foxtrot-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='foxtrot-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='foxtrot-supply-red'}), - presets.missions.supply.transfer:extend({name='foxtrot-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='foxtrot-ammo-red', - products = { - presets.missions.attack.surface:extend({name='foxtrot-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='foxtrot-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='foxtrot-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='foxtrot-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='foxtrot-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='foxtrot-supply-blue'}), - presets.missions.supply.transfer:extend({name='foxtrot-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='foxtrot-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='foxtrot-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Foxtrot.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Sierra.lua ]]----------------- - -zones.sierra = ZoneCommand:new("Sierra") -zones.sierra.initialState = { side=1 } -zones.sierra.isHeloSpawn = true -zones.sierra:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='sierra-tent-red', - products = { - presets.special.red.infantry:extend({ name='sierra-defense-red'}), - presets.defenses.red.infantry:extend({ name='sierra-garrison-red'}) - } - }), - presets.upgrades.supply.fuelTankFarp:extend({ - name='sierra-fuel-red', - products = { - presets.missions.supply.helo:extend({name='sierra-supply-red'}), - presets.missions.supply.helo:extend({name='sierra-supply-red-1'}), - presets.missions.supply.transfer:extend({name='sierra-transfer-red'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='sierra-comcenter-red', - products = { - presets.defenses.red.shorad:extend({name='sierra-sam-red'}), - presets.missions.attack.helo:extend({name='sierra-cas-red', altitude=200, expend=AI.Task.WeaponExpend.HALF }) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='sierra-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='sierra-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='sierra-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelTankFarp:extend({ - name='sierra-fuel-blue', - products = { - presets.missions.supply.helo:extend({name='sierra-supply-blue'}), - presets.missions.supply.helo:extend({name='sierra-supply-blue-1'}), - presets.missions.supply.transfer:extend({name='sierra-transfer-blue'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='sierra-comcenter-blue', - products = { - presets.defenses.blue.shorad:extend({name='sierra-sam-blue'}), - presets.missions.attack.helo:extend({name='sierra-cas-blue', altitude=200, expend=AI.Task.WeaponExpend.HALF }) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Sierra.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Oni.lua ]]----------------- - -zones.oni = ZoneCommand:new("Oni") -zones.oni.initialState = { side=1 } -zones.oni:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='oni-tent-red', - products = { - presets.special.red.infantry:extend({ name='oni-defense-red'}), - presets.defenses.red.infantry:extend({ name='oni-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='oni-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='oni-supply-red'}), - presets.missions.supply.helo:extend({name='oni-supply-red-2'}), - presets.missions.supply.transfer:extend({name='oni-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='oni-ammo-red', - products = { - presets.missions.attack.surface:extend({name='oni-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='oni-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='oni-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='oni-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='oni-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='oni-supply-blue'}), - presets.missions.supply.helo:extend({name='oni-supply-blue-2'}), - presets.missions.supply.transfer:extend({name='oni-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='oni-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='oni-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Oni.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Hotel.lua ]]----------------- - -zones.hotel = ZoneCommand:new("Hotel") -zones.hotel.initialState = nil -zones.hotel:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='hotel-tent-red', - products = { - presets.special.red.infantry:extend({ name='hotel-defense-red'}), - presets.defenses.red.infantry:extend({ name='hotel-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='hotel-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='hotel-supply-red'}), - presets.missions.supply.transfer:extend({name='hotel-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='hotel-ammo-red', - products = { - presets.missions.attack.surface:extend({name='hotel-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='hotel-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='hotel-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='hotel-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='hotel-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='hotel-supply-blue'}), - presets.missions.supply.transfer:extend({name='hotel-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='hotel-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='hotel-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Hotel.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Victor.lua ]]----------------- - -zones.victor = ZoneCommand:new("Victor") -zones.victor.initialState = { side=1 } -zones.victor:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='victor-tent-red', - products = { - presets.special.red.infantry:extend({ name='victor-defense-red'}), - presets.defenses.red.infantry:extend({ name='victor-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='victor-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='victor-supply-red'}), - presets.missions.supply.helo:extend({name='victor-supply-red-2'}), - presets.missions.supply.transfer:extend({name='victor-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='victor-ammo-red', - products = { - presets.missions.attack.surface:extend({name='victor-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='victor-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='victor-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='victor-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='victor-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='victor-supply-blue'}), - presets.missions.supply.helo:extend({name='victor-supply-blue-2'}), - presets.missions.supply.transfer:extend({name='victor-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='victor-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='victor-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Victor.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Tango.lua ]]----------------- - -zones.tango = ZoneCommand:new("Tango") -zones.tango.initialState = { side=1 } -zones.tango.isHeloSpawn = true -zones.tango:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='tango-tent-red', - products = { - presets.special.red.infantry:extend({ name='tango-defense-red'}), - presets.defenses.red.infantry:extend({ name='tango-garrison-red'}) - } - }), - presets.upgrades.supply.fuelTankFarp:extend({ - name='tango-fuel-red', - products = { - presets.missions.supply.helo:extend({name='tango-supply-red'}), - presets.missions.supply.helo:extend({name='tango-supply-red-1'}), - presets.missions.supply.transfer:extend({name='tango-transfer-red'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='tango-comcenter-red', - products = { - presets.defenses.red.shorad:extend({name='tango-sam-red'}), - presets.missions.attack.helo:extend({name='tango-cas-red', altitude=200, expend=AI.Task.WeaponExpend.HALF }) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='tango-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='tango-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='tango-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelTankFarp:extend({ - name='tango-fuel-blue', - products = { - presets.missions.supply.helo:extend({name='tango-supply-blue'}), - presets.missions.supply.helo:extend({name='tango-supply-blue-1'}), - presets.missions.supply.transfer:extend({name='tango-transfer-blue'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='tango-comcenter-blue', - products = { - presets.defenses.blue.shorad:extend({name='tango-sam-blue'}), - presets.missions.attack.helo:extend({name='tango-cas-blue', altitude=200, expend=AI.Task.WeaponExpend.HALF }) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Tango.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Unal.lua ]]----------------- - -zones.unal = ZoneCommand:new("Unal") -zones.unal.initialState = { side=1 } -zones.unal.isHeloSpawn = true -zones.unal:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='unal-tent-red', - products = { - presets.special.red.infantry:extend({ name='unal-defense-red'}), - presets.defenses.red.infantry:extend({ name='unal-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='unal-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='unal-supply-red'}), - presets.missions.supply.helo:extend({name='unal-supply-red-2'}), - presets.missions.supply.transfer:extend({name='unal-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='unal-ammo-red', - products = { - presets.missions.attack.surface:extend({name='unal-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='unal-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='unal-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='unal-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='unal-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='unal-supply-blue'}), - presets.missions.supply.helo:extend({name='unal-supply-blue-2'}), - presets.missions.supply.transfer:extend({name='unal-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='unal-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='unal-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Unal.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Beslan.lua ]]----------------- - -zones.beslan = ZoneCommand:new("Beslan") -zones.beslan.initialState = { side=1 } -zones.beslan.keepActive = true -zones.beslan.maxResource = 50000 -zones.beslan:defineUpgrades({ - [1] = - { - presets.upgrades.basic.comPost:extend({ - name='beslan-compost-red', - products = { - presets.special.red.infantry:extend({ name='beslan-defense-red'}), - presets.defenses.red.infantry:extend({ name='beslan-garrison-red'}) - } - }), - presets.upgrades.supply.fuelTank:extend({ - name='beslan-fuel-red', - products = { - presets.missions.supply.helo:extend({name='beslan-supply-red-1'}), - presets.missions.supply.helo:extend({name='beslan-supply-red-2'}), - presets.missions.supply.convoy_escorted:extend({name='beslan-supply-red-3'}), - presets.missions.supply.transfer:extend({name='beslan-transfer-red'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='beslan-comcenter-red', - products = { - presets.defenses.red.sa5:extend({ name='beslan-airdef-red'}), - presets.missions.attack.sead:extend({name='beslan-sead-red', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.attack.strike:extend({name='beslan-strike-red', altitude=30000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.attack.strike:extend({name='beslan-strike-red-1', altitude=30000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.patrol.aircraft:extend({name='beslan-patrol-red', altitude=25000, range=25}) - } - }) - }, - [2] = - { - presets.upgrades.basic.comPost:extend({ - name='beslan-compost-blue', - products = { - presets.special.blue.infantry:extend({ name='beslan-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='beslan-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelTank:extend({ - name='beslan-fuel-blue', - products = { - presets.missions.supply.helo:extend({name='beslan-supply-blue-1'}), - presets.missions.supply.helo:extend({name='beslan-supply-blue-2'}), - presets.missions.supply.convoy_escorted:extend({name='beslan-supply-blue-3'}), - presets.missions.supply.transfer:extend({name='beslan-transfer-blue'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='beslan-comcenter-blue', - products = { - presets.defenses.blue.patriot:extend({ name='beslan-airdef-blue'}), - presets.missions.attack.sead:extend({name='beslan-sead-blue', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.attack.strike:extend({name='beslan-strike-blue', altitude=30000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.attack.strike:extend({name='beslan-strike-blue-1', altitude=30000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.patrol.aircraft:extend({name='beslan-patrol-blue', altitude=25000, range=25}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Beslan.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Bravo.lua ]]----------------- - -zones.bravo = ZoneCommand:new("Bravo") -zones.bravo.initialState = { side=2 } -zones.bravo:defineUpgrades({ - [1] = - { - presets.upgrades.basic.comPost:extend({ - name='bravo-compost-red', - products = { - presets.special.red.infantry:extend({ name='bravo-defense-red'}), - presets.defenses.red.infantry:extend({ name='bravo-garrison-red'}) - } - }), - presets.upgrades.supply.fuelTankFarp:extend({ - name='bravo-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({ name='bravo-supply-red'}), - presets.missions.supply.transfer:extend({name='bravo-transfer-red'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='bravo-comcenter-red', - products = { - presets.defenses.red.shorad:extend({ name='bravo-airdef-red'}), - presets.missions.attack.helo:extend({name='bravo-attack-red', altitude=200, expend=AI.Task.WeaponExpend.HALF}) - } - }) - }, - [2] = - { - presets.upgrades.basic.comPost:extend({ - name='bravo-compost-blue', - products = { - presets.special.blue.infantry:extend({ name='bravo-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='bravo-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelTankFarp:extend({ - name='bravo-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({ name='bravo-supply-blue'}), - presets.missions.supply.transfer:extend({name='bravo-transfer-blue'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='bravo-comcenter-blue', - products = { - presets.defenses.blue.shorad:extend({ name='bravo-airdef-blue'}), - presets.missions.attack.helo:extend({name='bravo-attack-blue', altitude=200, expend=AI.Task.WeaponExpend.HALF }) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Bravo.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/WeaponDepot.lua ]]----------------- - -zones.weapondepot = ZoneCommand:new("Weapon Depot") -zones.weapondepot.initialState = { side=1 } -zones.weapondepot:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='weapons-tent-red', - products = { - presets.special.red.infantry:extend({ name='weapons-defense-red'}), - presets.defenses.red.infantry:extend({ name='weapons-garrison-red'}) - } - }), - presets.upgrades.supply.ammoDepot:extend({ - name='weapons-ammo-red-1', - products = { - presets.missions.supply.convoy:extend({name='weapons-supply-red-1'}), - presets.missions.supply.transfer:extend({name='weapons-transfer-red-1'}) - } - }), - presets.upgrades.supply.ammoDepot:extend({ - name='weapons-ammo-red-2', - products = { - presets.missions.supply.convoy:extend({name='weapons-supply-red-2'}), - presets.missions.supply.transfer:extend({name='weapons-transfer-red-2'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='weapons-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='weapons-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='weapons-garrison-blue'}) - } - }), - presets.upgrades.supply.ammoDepot:extend({ - name='weapons-ammo-blue-1', - products = { - presets.missions.supply.convoy:extend({name='weapons-supply-blue-1'}), - presets.missions.supply.transfer:extend({name='weapons-transfer-blue-1'}) - } - }), - presets.upgrades.supply.ammoDepot:extend({ - name='weapons-ammo-blue-2', - products = { - presets.missions.supply.convoy:extend({name='weapons-supply-blue-2'}), - presets.missions.supply.transfer:extend({name='weapons-transfer-blue-2'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/WeaponDepot.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Delta.lua ]]----------------- - -zones.delta = ZoneCommand:new("Delta") -zones.delta.initialState = { side=2 } -zones.delta:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='delta-tent-red', - products = { - presets.special.red.infantry:extend({ name='delta-defense-red'}), - presets.defenses.red.infantry:extend({ name='delta-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='delta-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='delta-supply-red'}), - presets.missions.supply.transfer:extend({name='delta-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='delta-ammo-red', - products = { - presets.missions.attack.surface:extend({name='delta-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='delta-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='delta-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='delta-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='delta-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='delta-supply-blue'}), - presets.missions.supply.transfer:extend({name='delta-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='delta-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='delta-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Delta.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Cherkessk.lua ]]----------------- - -zones.cherkessk = ZoneCommand:new("Cherkessk") -zones.cherkessk.initialState = { side=1 } -zones.cherkessk.isHeloSpawn = true -zones.cherkessk:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='cherkessk-tent-red', - products = { - presets.special.red.infantry:extend({ name='cherkessk-defense-red'}), - presets.defenses.red.infantry:extend({ name='cherkessk-garrison-red'}) - } - }), - presets.upgrades.supply.fuelTankFarp:extend({ - name='cherkessk-fuel-red', - products = { - presets.missions.supply.helo:extend({name='cherkessk-supply-red'}), - presets.missions.supply.helo:extend({name='cherkessk-supply-red-1'}), - presets.missions.supply.transfer:extend({name='cherkessk-transfer-red'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='cherkessk-comcenter-red', - products = { - presets.defenses.red.shorad:extend({name='cherkessk-sam-red'}), - presets.missions.attack.helo:extend({name='cherkessk-cas-red', altitude=200, expend=AI.Task.WeaponExpend.HALF }), - presets.missions.attack.helo:extend({name='cherkessk-cas-red-1', altitude=200, expend=AI.Task.WeaponExpend.HALF }), - presets.missions.attack.surface:extend({name='cherkessk-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='cherkessk-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='cherkessk-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='cherkessk-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelTankFarp:extend({ - name='cherkessk-fuel-blue', - products = { - presets.missions.supply.helo:extend({name='cherkessk-supply-blue'}), - presets.missions.supply.helo:extend({name='cherkessk-supply-blue-1'}), - presets.missions.supply.transfer:extend({name='cherkessk-transfer-blue'}), - presets.missions.attack.surface:extend({name='cherkessk-assault-blue'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='cherkessk-comcenter-blue', - products = { - presets.defenses.blue.shorad:extend({name='cherkessk-sam-blue'}), - presets.missions.attack.helo:extend({name='cherkessk-cas-blue', altitude=200, expend=AI.Task.WeaponExpend.HALF }), - presets.missions.attack.helo:extend({name='cherkessk-cas-blue-1', altitude=200, expend=AI.Task.WeaponExpend.HALF }) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Cherkessk.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Juliett.lua ]]----------------- - -zones.juliett = ZoneCommand:new("Juliett") -zones.initialState = nil -zones.juliett:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='juliett-tent-red', - products = { - presets.special.red.infantry:extend({ name='juliett-defense-red'}), - presets.defenses.red.infantry:extend({ name='juliett-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='juliett-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='juliett-supply-red'}), - presets.missions.supply.transfer:extend({name='juliett-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='juliett-ammo-red', - products = { - presets.missions.attack.surface:extend({name='juliett-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='juliett-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='juliett-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='juliett-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='juliett-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='juliett-supply-blue'}), - presets.missions.supply.transfer:extend({name='juliett-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='juliett-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='juliett-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Juliett.lua ]]----------------- - - - - cm = ConnectionManager:new() - cm:addConnection('Batumi', 'Alpha') - cm:addConnection('Alpha', 'Bravo') - cm:addConnection('Bravo', 'Kobuleti') - cm:addConnection('Bravo', 'Factory') - cm:addConnection('Kobuleti', 'Factory') - cm:addConnection('Kobuleti', 'Charlie') - cm:addConnection('Foxtrot', 'Charlie') - cm:addConnection('Foxtrot', 'Kobuleti') - cm:addConnection('Delta','Foxtrot') - cm:addConnection('Delta','Kobuleti') - cm:addConnection('Delta','Factory') - cm:addConnection('Echo','Charlie') - cm:addConnection('Golf','Echo') - cm:addConnection('Golf','Foxtrot') - cm:addConnection('India','Delta') - cm:addConnection('Hotel','Golf') - cm:addConnection('Hotel','Foxtrot') - cm:addConnection('Hotel','Delta') - cm:addConnection('Hotel','India') - cm:addConnection('Juliett','Echo') - cm:addConnection('Juliett','Golf') - cm:addConnection('Senaki','Juliett') - cm:addConnection('Senaki','Golf') - cm:addConnection('Senaki','Hotel') - cm:addConnection('Kutaisi','Hotel') - cm:addConnection('Kutaisi','India') - cm:addConnection('Kilo','Juliett') - cm:addConnection('Mike','Kutaisi') - cm:addConnection('Mike','Senaki') - cm:addConnection('Romeo','Mike') - cm:addConnection('Romeo','Kutaisi') - cm:addConnection('Weapon Depot','Juliett') - cm:addConnection('Weapon Depot','Senaki') - cm:addConnection('Weapon Depot','Kilo') - cm:addConnection('November','Weapon Depot') - cm:addConnection('November','Senaki') - cm:addConnection('November','Mike') - cm:addConnection('Oil Fields','Romeo') - cm:addConnection('Quebec','Kilo') - cm:addConnection('Zugdidi','Weapon Depot') - cm:addConnection('Zugdidi','Quebec') - cm:addConnection('Zugdidi','November') - cm:addConnection('Zugdidi','Kilo') - cm:addConnection('Distillery','November') - cm:addConnection('Distillery','Mike') - cm:addConnection('Zugdidi','Papa') - cm:addConnection('November','Papa') - cm:addConnection('Sierra','Papa') - cm:addConnection('Sierra','Zugdidi') - cm:addConnection('Sierra','Uniform') - cm:addConnection('Mine','Uniform') - cm:addConnection('Tango','Quebec') - cm:addConnection('Tango','Zugdidi') - cm:addConnection('Sierra','Tango') - cm:addConnection('Whiskey','Tango') - cm:addConnection('Ochamchira','Tango') - cm:addConnection('Ochamchira','Whiskey') - cm:addConnection('Ochamchira','Farm') - cm:addConnection('Ochamchira','Zulu') - cm:addConnection('Farm','Zulu') - cm:addConnection('Sukhumi','Zulu') - cm:addConnection('Lentehi','Distillery', true, 3000) - cm:addConnection('Lentehi','Babugent', true, 5000) - cm:addConnection('Nalchik','Babugent') - cm:addConnection('Victor','Distillery', true, 2000) - cm:addConnection('Victor','Romeo') - cm:addConnection('Victor','Lentehi') - cm:addConnection('Victor','Oil Fields', true, 2000) - cm:addConnection('Victor','Oni') - cm:addConnection('Unal','Oni', true, 4500) - cm:addConnection('Beslan','Unal') - cm:addConnection('Digora','Beslan') - cm:addConnection('Digora','Unal') - cm:addConnection('Digora','Babugent') - cm:addConnection('Terek','Digora') - cm:addConnection('Terek','Nalchik') - cm:addConnection('Terek','Beslan') - cm:addConnection('Prohladniy','Terek') - cm:addConnection('Prohladniy','Nalchik') - cm:addConnection('Malgobek','Terek') - cm:addConnection('Malgobek','Beslan') - cm:addConnection('Lima','Mine') - cm:addConnection('Lima','Lentehi', true, 4000) - cm:addConnection('Tyrnyauz','Lima', true, 4000) - cm:addConnection('Tyrnyauz','Nalchik') - cm:addConnection('XRay','Sukhumi') - cm:addConnection('Oscar','Sukhumi') - cm:addConnection('Oscar','XRay') - cm:addConnection('Mozdok','Malgobek') - cm:addConnection('Mozdok','Prohladniy') - cm:addConnection('Gudauta','Oscar') - cm:addConnection('Yankee','Gudauta') - cm:addConnection('Sochi','Yankee') - cm:addConnection('Refinery','XRay', true, 4000) - cm:addConnection('Refinery','Humara') - cm:addConnection('Intel Center','Tyrnyauz') - cm:addConnection('Intel Center','Nalchik') - cm:addConnection('Intel Center','Prohladniy') - cm:addConnection('Intel Center','Kislovodsk') - cm:addConnection('Mineralnye','Intel Center') - cm:addConnection('Kislovodsk','Mineralnye') - cm:addConnection('Tallyk','Mineralnye') - cm:addConnection('Tallyk','Kislovodsk') - cm:addConnection('Power Plant','Mineralnye') - cm:addConnection('Power Plant','Tallyk') - cm:addConnection('Cherkessk','Tallyk') - cm:addConnection('Cherkessk','Power Plant') - cm:addConnection('Cherkessk','Humara') -end - -ZoneCommand.setNeighbours(cm) - -bm = BattlefieldManager:new() - -mc = MarkerCommands:new() - -pt = PlayerTracker:new(mc) - -mt = MissionTracker:new(pt, mc) - -st = SquadTracker:new() - -ct = CSARTracker:new() - -pl = PlayerLogistics:new(mt, pt, st, ct) - -gci = GCI:new(2) - -gm = GroupMonitor:new(cm) -ZoneCommand.groupMonitor = gm - --- PlayerLogistics:registerSquadGroup(squadType, groupname, weight,cost,jobtime,extracttime, squadSize) -pl:registerSquadGroup(PlayerLogistics.infantryTypes.capture, 'capture-squad', 700, 200, 60, 60*30, 4) -pl:registerSquadGroup(PlayerLogistics.infantryTypes.sabotage, 'sabotage-squad', 800, 500, 60*5, 60*30, 4) -pl:registerSquadGroup(PlayerLogistics.infantryTypes.ambush, 'ambush-squad', 900, 300, 60*20, 60*30, 5) -pl:registerSquadGroup(PlayerLogistics.infantryTypes.engineer, 'engineer-squad', 200, 1000,60, 60*30, 2) -pl:registerSquadGroup(PlayerLogistics.infantryTypes.manpads, 'manpads-squad', 900, 500, 60*20, 60*30, 5) -pl:registerSquadGroup(PlayerLogistics.infantryTypes.spy, 'spy-squad', 100, 300, 60*10, 60*30, 1) -pl:registerSquadGroup(PlayerLogistics.infantryTypes.rapier, 'rapier-squad', 1200,2000,60*60, 60*30, 8) - -Group.getByName('jtacDrone'):destroy() -CommandFunctions.jtac = JTAC:new({name = 'jtacDrone'}) - -pm = PersistenceManager:new(savefile, gm, st, ct, pl) -pm:load() - -if pm:canRestore() then - pm:restoreZones() - pm:restoreAIMissions() - pm:restoreBattlefield() - pm:restoreCsar() - pm:restoreSquads() -else - --initial states - Starter.start(zones) -end - -timer.scheduleFunction(function(param, time) - pm:save() - env.info("Mission state saved") - return time+60 -end, zones, timer.getTime()+60) - - ---make sure support units are present where needed -ensureSpawn = { - ['golf-farp-suport'] = zones.golf, - ['november-farp-suport'] = zones.november, - ['tango-farp-suport'] = zones.tango, - ['sierra-farp-suport'] = zones.sierra, - ['cherkessk-farp-suport'] = zones.cherkessk, - ['unal-farp-suport'] = zones.unal, - ['tyrnyauz-farp-suport'] = zones.tyrnyauz -} - -for grname, zn in pairs(ensureSpawn) do - local g = Group.getByName(grname) - if g then g:destroy() end -end - -timer.scheduleFunction(function(param, time) - - for grname, zn in pairs(ensureSpawn) do - local g = Group.getByName(grname) - if zn.side == 2 then - if not g then - local err, msg = pcall(mist.respawnGroup,grname,true) - if not err then - env.info("ERROR spawning "..grname) - env.info(msg) - end - end - else - if g then g:destroy() end - end - end - - return time+30 -end, {}, timer.getTime()+30) - - ---supply injection -local blueSupply = {'offmap-supply-blue-1','offmap-supply-blue-2','offmap-supply-blue-3','offmap-supply-blue-4','offmap-supply-blue-5'} -local redSupply = {'offmap-supply-red-1','offmap-supply-red-2','offmap-supply-red-3','offmap-supply-red-4','offmap-supply-red-5'} -local offmapZones = { - zones.batumi, - zones.sochi, - zones.nalchik, - zones.beslan, - zones.mozdok, - zones.mineralnye, --- zones.senaki, --- zones.sukhumi, --- zones.gudauta, --- zones.kobuleti, -} - -supplyPointRegistry = { - blue = {}, - red = {} -} - -for i,v in ipairs(blueSupply) do - local g = Group.getByName(v) - if g then - supplyPointRegistry.blue[v] = g:getUnit(1):getPoint() - end -end - -for i,v in ipairs(redSupply) do - local g = Group.getByName(v) - if g then - supplyPointRegistry.red[v] = g:getUnit(1):getPoint() - end -end - -offmapSupplyRegistry = {} -timer.scheduleFunction(function(param, time) - local availableBlue = {} - for i,v in ipairs(param.blue) do - if offmapSupplyRegistry[v] == nil then - table.insert(availableBlue, v) - end - end - - local availableRed = {} - for i,v in ipairs(param.red) do - if offmapSupplyRegistry[v] == nil then - table.insert(availableRed, v) - end - end - - local redtargets = {} - local bluetargets = {} - for _, zn in ipairs(param.offmapZones) do - if zn:needsSupplies(3000) then - local isOnRoute = false - for _,data in pairs(offmapSupplyRegistry) do - if data.zone.name == zn.name then - isOnRoute = true - break - end - end - if not isOnRoute then - if zn.side == 1 then - table.insert(redtargets, zn) - elseif zn.side == 2 then - table.insert(bluetargets, zn) - end - end - end - end - - if #availableRed > 0 and #redtargets > 0 then - local zn = redtargets[math.random(1,#redtargets)] - - local red = nil - local minD = 999999999 - for i,v in ipairs(availableRed) do - local d = mist.utils.get2DDist(zn.zone.point, supplyPointRegistry.red[v]) - if d < minD then - red = v - minD = d - end - end - - if not red then red = availableRed[math.random(1,#availableRed)] end - - local gr = red - red = nil - mist.respawnGroup(gr, true) - offmapSupplyRegistry[gr] = {zone = zn, assigned = timer.getAbsTime()} - env.info(gr..' was deployed') - timer.scheduleFunction(function(param,time) - local g = Group.getByName(param.group) - TaskExtensions.landAtAirfield(g, param.target.zone.point) - env.info(param.group..' going to '..param.target.name) - end, {group=gr, target=zn}, timer.getTime()+2) - end - - if #availableBlue > 0 and #bluetargets>0 then - local zn = bluetargets[math.random(1,#bluetargets)] - - local blue = nil - local minD = 999999999 - for i,v in ipairs(availableBlue) do - local d = mist.utils.get2DDist(zn.zone.point, supplyPointRegistry.blue[v]) - if d < minD then - blue = v - minD = d - end - end - - if not blue then blue = availableBlue[math.random(1,#availableBlue)] end - - local gr = blue - blue = nil - mist.respawnGroup(gr, true) - offmapSupplyRegistry[gr] = {zone = zn, assigned = timer.getAbsTime()} - env.info(gr..' was deployed') - timer.scheduleFunction(function(param,time) - local g = Group.getByName(param.group) - TaskExtensions.landAtAirfield(g, param.target.zone.point) - env.info(param.group..' going to '..param.target.name) - end, {group=gr, target=zn}, timer.getTime()+2) - end - - return time+(60*5) -end, {blue = blueSupply, red = redSupply, offmapZones = offmapZones}, timer.getTime()+60) - - - -timer.scheduleFunction(function(param, time) - - for groupname,data in pairs(offmapSupplyRegistry) do - local gr = Group.getByName(groupname) - if not gr then - offmapSupplyRegistry[groupname] = nil - env.info(groupname..' was destroyed') - end - - if gr and ((timer.getAbsTime() - data.assigned) > (60*60)) then - gr:destroy() - offmapSupplyRegistry[groupname] = nil - env.info(groupname..' despawned due to being alive for too long') - end - - if gr and Utils.allGroupIsLanded(gr) and Utils.someOfGroupInZone(gr, data.zone.name) then - data.zone:addResource(15000) - gr:destroy() - offmapSupplyRegistry[groupname] = nil - env.info(groupname..' landed at '..data.zone.name..' and delivered 15000 resources') - end - end - - return time+180 -end, {}, timer.getTime()+180) \ No newline at end of file +do \ No newline at end of file From c126d1dff79d9f9ec4314b44bcf3a2200488e813 Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Mon, 11 Sep 2023 21:42:02 +0300 Subject: [PATCH 013/243] Added newlines to Pretense init scripts. --- resources/plugins/pretense/init_body.lua | 2 ++ resources/plugins/pretense/init_footer.lua | 1 + resources/plugins/pretense/init_header.lua | 3 ++- 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/resources/plugins/pretense/init_body.lua b/resources/plugins/pretense/init_body.lua index a4452456..33a7f627 100644 --- a/resources/plugins/pretense/init_body.lua +++ b/resources/plugins/pretense/init_body.lua @@ -1,3 +1,4 @@ + ZoneCommand.setNeighbours(cm) bm = BattlefieldManager:new() @@ -104,3 +105,4 @@ local offmapZones = { -- zones.gudauta, -- zones.kobuleti, } + diff --git a/resources/plugins/pretense/init_footer.lua b/resources/plugins/pretense/init_footer.lua index 2559cb93..455520b2 100644 --- a/resources/plugins/pretense/init_footer.lua +++ b/resources/plugins/pretense/init_footer.lua @@ -1,3 +1,4 @@ + supplyPointRegistry = { blue = {}, red = {} diff --git a/resources/plugins/pretense/init_header.lua b/resources/plugins/pretense/init_header.lua index d569e9bb..b5d49d0e 100644 --- a/resources/plugins/pretense/init_header.lua +++ b/resources/plugins/pretense/init_header.lua @@ -619,4 +619,5 @@ presets = { } zones = {} -do \ No newline at end of file +do + From 84c20e8d81cc57e9eedaf9ed5dbd6f8853c7b5d1 Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Mon, 11 Sep 2023 21:42:42 +0300 Subject: [PATCH 014/243] ntentionally don't spawn anything at OffMapSpawns in Pretense --- game/pretense/pretenseaircraftgenerator.py | 5 +++++ game/pretense/pretenseflightgroupspawner.py | 6 ++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/game/pretense/pretenseaircraftgenerator.py b/game/pretense/pretenseaircraftgenerator.py index 5abec403..2437bab3 100644 --- a/game/pretense/pretenseaircraftgenerator.py +++ b/game/pretense/pretenseaircraftgenerator.py @@ -32,6 +32,7 @@ from game.theater.controlpoint import ( Airfield, ControlPoint, Fob, + OffMapSpawn, ) from game.unitmap import UnitMap from game.missiongenerator.aircraft.aircraftpainter import AircraftPainter @@ -121,6 +122,10 @@ class PretenseAircraftGenerator: num_of_strike = 0 num_of_cap = 0 for squadron in cp.squadrons: + # Intentionally don't spawn anything at OffMapSpawns in Pretense + if isinstance(squadron.location, OffMapSpawn): + continue + squadron.owned_aircraft += 1 squadron.untasked_aircraft += 1 package = Package(cp, squadron.flight_db, auto_asap=False) diff --git a/game/pretense/pretenseflightgroupspawner.py b/game/pretense/pretenseflightgroupspawner.py index 908edcc0..880ad88d 100644 --- a/game/pretense/pretenseflightgroupspawner.py +++ b/game/pretense/pretenseflightgroupspawner.py @@ -17,7 +17,7 @@ from game.ato.starttype import StartType from game.missiongenerator.aircraft.flightgroupspawner import FlightGroupSpawner from game.missiongenerator.missiondata import MissionData from game.naming import NameGenerator -from game.theater import Airfield, ControlPoint, Fob, NavalControlPoint +from game.theater import Airfield, ControlPoint, Fob, NavalControlPoint, OffMapSpawn from game.utils import feet, meters @@ -67,11 +67,13 @@ class PretenseFlightGroupSpawner(FlightGroupSpawner): cp = self.flight.departure name = namegen.next_pretense_aircraft_name(cp, self.flight) - print(name) try: if self.start_type is StartType.IN_FLIGHT: group = self._generate_over_departure(name, cp) return group + elif isinstance(cp, OffMapSpawn): + # Intentionally don't spawn anything at OffMapSpawns in Pretense + logging.info(f"Skipping flight generation for off-map spawn {cp}.") elif isinstance(cp, NavalControlPoint): group_name = cp.get_carrier_group_name() carrier_group = self.mission.find_group(group_name) From cc713f089a9d60900ae65ddd5f160ab667ce3b5b Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Mon, 11 Sep 2023 21:43:29 +0300 Subject: [PATCH 015/243] First version of PretenseLuaGenerator, inherited from LuaGenerator --- game/pretense/pretenseluagenerator.py | 104 ++++++++++++++++++++-- game/pretense/pretensemissiongenerator.py | 4 +- 2 files changed, 101 insertions(+), 7 deletions(-) diff --git a/game/pretense/pretenseluagenerator.py b/game/pretense/pretenseluagenerator.py index f8698ec7..d860abd3 100644 --- a/game/pretense/pretenseluagenerator.py +++ b/game/pretense/pretenseluagenerator.py @@ -13,23 +13,30 @@ from dcs.triggers import TriggerStart from game.ato import FlightType from game.dcs.aircrafttype import AircraftType +from game.missiongenerator.luagenerator import LuaGenerator from game.plugins import LuaPluginManager -from game.theater import TheaterGroundObject +from game.theater import TheaterGroundObject, Airfield from game.theater.iadsnetwork.iadsrole import IadsRole from game.utils import escape_string_for_lua -from .missiondata import MissionData +from game.missiongenerator.missiondata import MissionData if TYPE_CHECKING: from game import Game -class LuaGenerator: +class PretenseLuaGenerator(LuaGenerator): def __init__( self, game: Game, mission: Mission, mission_data: MissionData, ) -> None: + super().__init__( + game, + mission, + mission_data, + ) + self.game = game self.mission = mission self.mission_data = mission_data @@ -207,8 +214,95 @@ class LuaGenerator: 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()))) + trigger = TriggerStart(comment="Pretense init") + + init_header_file = open("./resources/plugins/pretense/init_header.lua", "r") + init_header = init_header_file.read() + + lua_string = "" + lua_data = LuaData("products") + + for cp in self.game.theater.controlpoints: + cp_name_trimmed = "".join([i for i in cp.name.lower() if i.isalnum()]) + cp_side = 2 if cp.captured else 1 + + lua_string += f"zones.{cp_name_trimmed} = ZoneCommand:new('{cp.name}')\n" + lua_string += ( + f"zones.{cp_name_trimmed}.initialState = " + + "{ side=" + + str(cp_side) + + " }\n" + ) + lua_string += f"zones.{cp_name_trimmed}.keepActive = true\n" + if cp.has_helipads: + lua_string += f"zones.{cp_name_trimmed}.isHeloSpawn = true\n" + if isinstance(cp, Airfield) or cp.has_ground_spawns: + lua_string += f"zones.{cp_name_trimmed}.isPlaneSpawn = true\n" + lua_string += f"zones.{cp_name_trimmed}.maxResource = 50000\n" + lua_string += f"zones.{cp_name_trimmed}:defineUpgrades(" + "{\n" + lua_string += " [1] = { --red side\n" + lua_string += " presets.upgrades.basic.tent:extend({\n" + lua_string += f" name='{cp_name_trimmed}-tent-red',\n" + lua_string += " products = {\n" + lua_string += " presets.special.red.infantry:extend({ name='mike-defense-red'})\n" + lua_string += " }\n" + lua_string += " }),\n" + lua_string += " presets.upgrades.basic.comPost:extend({\n" + lua_string += " name = 'batumi-com-red',\n" + lua_string += " products = {" + lua_string += " presets.special.red.infantry:extend({ name='batumi-defense-red'}),\n" + lua_string += " presets.defenses.red.infantry:extend({ name='batumi-garrison-red' })\n" + lua_string += " }\n" + lua_string += " }),\n" + lua_string += " },\n" + lua_string += " [2] = --blue side\n" + lua_string += " {\n" + lua_string += " presets.upgrades.basic.tent:extend({\n" + lua_string += " name='mike-tent-blue',\n" + lua_string += " products = {\n" + lua_string += " presets.special.blue.infantry:extend({ name='mike-defense-blue'})\n" + lua_string += " }\n" + lua_string += " }),\n" + lua_string += " presets.upgrades.basic.comPost:extend({\n" + lua_string += " name = 'batumi-com-blue',\n" + lua_string += " products = {" + lua_string += " presets.special.blue.infantry:extend({ name='batumi-defense-blue'}),\n" + lua_string += " presets.defenses.blue.infantry:extend({ name='batumi-garrison-blue' })\n" + lua_string += " }\n" + lua_string += " }),\n" + lua_string += " presets.upgrades.supply.fuelTank:extend({\n" + lua_string += " name = 'batumi-fueltank-blue',\n" + lua_string += " products = {\n" + lua_string += " presets.missions.supply.convoy_escorted:extend({ name='batumi-supply-convoy-1'}),\n" + lua_string += " presets.missions.supply.helo:extend({ name='batumi-supply-blue-1' }),\n" + lua_string += " presets.missions.supply.transfer:extend({name='batumi-transfer-blue'})\n" + lua_string += " }\n" + lua_string += " }),\n" + lua_string += " presets.upgrades.airdef.comCenter:extend({\n" + lua_string += " name = 'batumi-mission-command-blue',\n" + lua_string += " products = {\n" + lua_string += " presets.defenses.blue.shorad:extend({ name='batumi-sam-blue' }),\n" + lua_string += " presets.missions.attack.sead:extend({name='batumi-sead-blue-1', altitude=25000, expend=AI.Task.WeaponExpend.ALL}),\n" + lua_string += " presets.missions.attack.cas:extend({name='batumi-cas-blue-1', altitude=15000, expend=AI.Task.WeaponExpend.ONE}),\n" + lua_string += " presets.missions.attack.bai:extend({name='batumi-cas-blue-1', altitude=10000, expend=AI.Task.WeaponExpend.ONE}),\n" + lua_string += " presets.missions.attack.strike:extend({name='batumi-strike-blue-1', altitude=20000, expend=AI.Task.WeaponExpend.ALL}),\n" + lua_string += " presets.missions.patrol.aircraft:extend({name='batumi-patrol-blue-1', altitude=25000, range=25}),\n" + lua_string += " presets.missions.support.awacs:extend({name='batumi-awacs-blue', altitude=30000, freq=257.5}),\n" + lua_string += " presets.missions.support.tanker:extend({name='batumi-tanker-blue', altitude=25000, freq=257, tacan='37', variant=\"Drogue\"})\n" + lua_string += " }\n" + lua_string += " })\n" + lua_string += " }\n" + lua_string += "})\n" + + init_body_file = open("./resources/plugins/pretense/init_body.lua", "r") + init_body = init_body_file.read() + + init_footer_file = open("./resources/plugins/pretense/init_footer.lua", "r") + init_footer = init_footer_file.read() + + lua_string = init_header + lua_string + init_body + init_footer + + trigger.add_action(DoScript(String(lua_string))) self.mission.triggerrules.triggers.append(trigger) def inject_lua_trigger(self, contents: str, comment: str) -> None: diff --git a/game/pretense/pretensemissiongenerator.py b/game/pretense/pretensemissiongenerator.py index d39b338c..ed220130 100644 --- a/game/pretense/pretensemissiongenerator.py +++ b/game/pretense/pretensemissiongenerator.py @@ -35,9 +35,9 @@ from game.missiongenerator.frontlineconflictdescription import ( ) from game.missiongenerator.kneeboard import KneeboardGenerator from game.missiongenerator.lasercoderegistry import LaserCodeRegistry -from game.missiongenerator.luagenerator import LuaGenerator from game.missiongenerator.missiondata import MissionData from game.missiongenerator.tgogenerator import TgoGenerator +from .pretenseluagenerator import PretenseLuaGenerator from .pretensetgogenerator import PretenseTgoGenerator from .pretensetriggergenerator import PretenseTriggerGenerator from game.missiongenerator.visualsgenerator import VisualsGenerator @@ -107,7 +107,7 @@ class PretenseMissionGenerator: PretenseTriggerGenerator(self.mission, self.game).generate() ForcedOptionsGenerator(self.mission, self.game).generate() VisualsGenerator(self.mission, self.game).generate() - LuaGenerator(self.game, self.mission, self.mission_data).generate() + PretenseLuaGenerator(self.game, self.mission, self.mission_data).generate() self.setup_combined_arms() From 17533399b350c2885a52616ba510aa4f53efcffc Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Mon, 11 Sep 2023 21:43:53 +0300 Subject: [PATCH 016/243] Removed the reference to (nonexistent) NewPretenseWizard. --- qt_ui/windows/QLiberationWindow.py | 1 - 1 file changed, 1 deletion(-) diff --git a/qt_ui/windows/QLiberationWindow.py b/qt_ui/windows/QLiberationWindow.py index e6e090ef..f4f8c38f 100644 --- a/qt_ui/windows/QLiberationWindow.py +++ b/qt_ui/windows/QLiberationWindow.py @@ -42,7 +42,6 @@ from qt_ui.windows.groundobject.QGroundObjectMenu import QGroundObjectMenu from qt_ui.windows.infos.QInfoPanel import QInfoPanel from qt_ui.windows.logs.QLogsWindow import QLogsWindow from qt_ui.windows.newgame.QNewGameWizard import NewGameWizard -from qt_ui.windows.newgame.QNewPretenseWizard import NewPretenseWizard from qt_ui.windows.notes.QNotesWindow import QNotesWindow from qt_ui.windows.preferences.QLiberationPreferencesWindow import ( QLiberationPreferencesWindow, From 33a2c27f5e57008c181600830520332f0fac4e86 Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Wed, 13 Sep 2023 21:01:49 +0300 Subject: [PATCH 017/243] Copied flightgroupconfigurator.py as a template/inheritance for generating Pretense campaigns from Retribution campaigns. --- .../pretenseflightgroupconfigurator.py | 288 ++++++++++++++++++ 1 file changed, 288 insertions(+) create mode 100644 game/pretense/pretenseflightgroupconfigurator.py diff --git a/game/pretense/pretenseflightgroupconfigurator.py b/game/pretense/pretenseflightgroupconfigurator.py new file mode 100644 index 00000000..ec20f532 --- /dev/null +++ b/game/pretense/pretenseflightgroupconfigurator.py @@ -0,0 +1,288 @@ +from __future__ import annotations + +import logging +from datetime import datetime +from typing import Any, Optional, TYPE_CHECKING + +from dcs import Mission +from dcs.action import DoScript +from dcs.flyingunit import FlyingUnit +from dcs.task import OptReactOnThreat +from dcs.translation import String +from dcs.triggers import TriggerStart +from dcs.unit import Skill +from dcs.unitgroup import FlyingGroup + +from game.ato import Flight, FlightType +from game.callsigns import callsign_for_support_unit +from game.data.weapons import Pylon, WeaponType as WeaponTypeEnum +from game.missiongenerator.lasercoderegistry import LaserCodeRegistry +from game.missiongenerator.logisticsgenerator import LogisticsGenerator +from game.missiongenerator.missiondata import MissionData, AwacsInfo, TankerInfo +from game.radio.radios import RadioFrequency, RadioRegistry +from game.radio.tacan import TacanBand, TacanRegistry, TacanUsage +from game.runways import RunwayData +from game.squadrons import Pilot +from .aircraftbehavior import AircraftBehavior +from .aircraftpainter import AircraftPainter +from .flightdata import FlightData +from .waypoints import WaypointGenerator +from ...ato.flightplans.aewc import AewcFlightPlan +from ...ato.flightplans.packagerefueling import PackageRefuelingFlightPlan +from ...ato.flightplans.theaterrefueling import TheaterRefuelingFlightPlan +from ...theater import Fob + +if TYPE_CHECKING: + from game import Game + + +class FlightGroupConfigurator: + def __init__( + self, + flight: Flight, + group: FlyingGroup[Any], + game: Game, + mission: Mission, + time: datetime, + radio_registry: RadioRegistry, + tacan_registry: TacanRegistry, + laser_code_registry: LaserCodeRegistry, + mission_data: MissionData, + dynamic_runways: dict[str, RunwayData], + use_client: bool, + ) -> None: + self.flight = flight + self.group = group + self.game = game + self.mission = mission + self.time = time + self.radio_registry = radio_registry + self.tacan_registry = tacan_registry + self.laser_code_registry = laser_code_registry + self.mission_data = mission_data + self.dynamic_runways = dynamic_runways + self.use_client = use_client + + def configure(self) -> FlightData: + AircraftBehavior(self.flight.flight_type).apply_to(self.flight, self.group) + AircraftPainter(self.flight, self.group).apply_livery() + self.setup_props() + self.setup_payload() + self.setup_fuel() + flight_channel = self.setup_radios() + + laser_codes: list[Optional[int]] = [] + for unit, pilot in zip(self.group.units, self.flight.roster.pilots): + self.configure_flight_member(unit, pilot, laser_codes) + + divert = None + if self.flight.divert is not None: + divert = self.flight.divert.active_runway( + self.game.theater, self.game.conditions, self.dynamic_runways + ) + + if self.flight.flight_type in [ + FlightType.TRANSPORT, + FlightType.AIR_ASSAULT, + ] and self.game.settings.plugin_option("ctld"): + transfer = None + if self.flight.flight_type == FlightType.TRANSPORT: + coalition = self.game.coalition_for(player=self.flight.blue) + transfer = coalition.transfers.transfer_for_flight(self.flight) + self.mission_data.logistics.append( + LogisticsGenerator( + self.flight, self.group, self.mission, self.game.settings, transfer + ).generate_logistics() + ) + + mission_start_time, waypoints = WaypointGenerator( + self.flight, + self.group, + self.mission, + self.game.conditions.start_time, + self.time, + self.game.settings, + self.mission_data, + ).create_waypoints() + + # Special handling for landing waypoints when: + # 1. It's an AI-only flight + # 2. Aircraft are not helicopters/VTOL + # 3. Landing waypoint does not point to an airfield + if ( + self.flight.client_count < 1 + and not self.flight.unit_type.helicopter + and not self.flight.unit_type.lha_capable + and isinstance(self.flight.squadron.location, Fob) + ): + # Need to set uncontrolled to false, otherwise the AI will skip the mission and just land + self.group.uncontrolled = False + + return FlightData( + package=self.flight.package, + aircraft_type=self.flight.unit_type, + squadron=self.flight.squadron, + flight_type=self.flight.flight_type, + units=self.group.units, + size=len(self.group.units), + friendly=self.flight.from_cp.captured, + departure_delay=mission_start_time, + departure=self.flight.departure.active_runway( + self.game.theater, self.game.conditions, self.dynamic_runways + ), + arrival=self.flight.arrival.active_runway( + self.game.theater, self.game.conditions, self.dynamic_runways + ), + divert=divert, + waypoints=waypoints, + intra_flight_channel=flight_channel, + bingo_fuel=self.flight.flight_plan.bingo_fuel, + joker_fuel=self.flight.flight_plan.joker_fuel, + custom_name=self.flight.custom_name, + laser_codes=laser_codes, + ) + + def configure_flight_member( + self, unit: FlyingUnit, pilot: Optional[Pilot], laser_codes: list[Optional[int]] + ) -> None: + player = pilot is not None and pilot.player + self.set_skill(unit, pilot) + if self.flight.loadout.has_weapon_of_type(WeaponTypeEnum.TGP) and player: + laser_codes.append(self.laser_code_registry.get_next_laser_code()) + else: + laser_codes.append(None) + settings = self.flight.coalition.game.settings + if not player or not settings.plugins.get("ewrj"): + return + jammer_required = settings.plugin_option("ewrj.ecm_required") + if jammer_required: + ecm = WeaponTypeEnum.JAMMER + if not self.flight.loadout.has_weapon_of_type(ecm): + return + ewrj_menu_trigger = TriggerStart(comment=f"EWRJ-{unit.name}") + ewrj_menu_trigger.add_action(DoScript(String(f'EWJamming("{unit.name}")'))) + self.mission.triggerrules.triggers.append(ewrj_menu_trigger) + self.group.points[0].tasks[0] = OptReactOnThreat( + OptReactOnThreat.Values.PassiveDefense + ) + + def setup_radios(self) -> RadioFrequency: + freq = self.flight.frequency + if freq is None and (freq := self.flight.package.frequency) is None: + freq = self.radio_registry.alloc_uhf() + self.flight.package.frequency = freq + if freq not in self.radio_registry.allocated_channels: + self.radio_registry.reserve(freq) + + if self.flight.flight_type in {FlightType.AEWC, FlightType.REFUELING}: + self.register_air_support(freq) + elif self.flight.frequency is None and self.flight.client_count: + freq = self.flight.unit_type.alloc_flight_radio(self.radio_registry) + + self.group.set_frequency(freq.mhz) + return freq + + def register_air_support(self, channel: RadioFrequency) -> None: + callsign = callsign_for_support_unit(self.group) + if isinstance(self.flight.flight_plan, AewcFlightPlan): + self.mission_data.awacs.append( + AwacsInfo( + group_name=str(self.group.name), + callsign=callsign, + freq=channel, + depature_location=self.flight.departure.name, + start_time=self.flight.flight_plan.patrol_start_time, + end_time=self.flight.flight_plan.patrol_end_time, + blue=self.flight.departure.captured, + ) + ) + elif isinstance( + self.flight.flight_plan, TheaterRefuelingFlightPlan + ) or isinstance(self.flight.flight_plan, PackageRefuelingFlightPlan): + if self.flight.tacan is None: + tacan = self.tacan_registry.alloc_for_band( + TacanBand.Y, TacanUsage.AirToAir + ) + else: + tacan = self.flight.tacan + self.mission_data.tankers.append( + TankerInfo( + group_name=str(self.group.name), + callsign=callsign, + variant=self.flight.unit_type.name, + freq=channel, + tacan=tacan, + start_time=self.flight.flight_plan.patrol_start_time, + end_time=self.flight.flight_plan.patrol_end_time, + blue=self.flight.departure.captured, + ) + ) + + def set_skill(self, unit: FlyingUnit, pilot: Optional[Pilot]) -> None: + if pilot is None or not pilot.player: + unit.skill = self.skill_level_for(unit, pilot) + return + + if self.use_client or "Pilot #1" not in unit.name: + unit.set_client() + else: + unit.set_player() + + def skill_level_for(self, unit: FlyingUnit, pilot: Optional[Pilot]) -> Skill: + if self.flight.squadron.player: + base_skill = Skill(self.game.settings.player_skill) + else: + base_skill = Skill(self.game.settings.enemy_skill) + + if pilot is None: + logging.error(f"Cannot determine skill level: {unit.name} has not pilot") + return base_skill + + levels = [ + Skill.Average, + Skill.Good, + Skill.High, + Skill.Excellent, + ] + current_level = levels.index(base_skill) + missions_for_skill_increase = 4 + increase = pilot.record.missions_flown // missions_for_skill_increase + capped_increase = min(current_level + increase, len(levels) - 1) + + if self.game.settings.ai_pilot_levelling: + new_level = capped_increase + else: + new_level = current_level + + return levels[new_level] + + def setup_props(self) -> None: + for prop_id, value in self.flight.props.items(): + for unit in self.group.units: + unit.set_property(prop_id, value) + + def setup_payload(self) -> None: + for p in self.group.units: + p.pylons.clear() + + loadout = self.flight.loadout + if self.game.settings.restrict_weapons_by_date: + loadout = loadout.degrade_for_date(self.flight.unit_type, self.game.date) + + for pylon_number, weapon in loadout.pylons.items(): + if weapon is None: + continue + pylon = Pylon.for_aircraft(self.flight.unit_type, pylon_number) + pylon.equip(self.group, weapon) + + def setup_fuel(self) -> None: + fuel = self.flight.state.estimate_fuel() + if fuel < 0: + logging.warning( + f"Flight {self.flight} is estimated to have no fuel at mission start. " + "This estimate does not account for external fuel tanks. Setting " + "starting fuel to 100kg." + ) + fuel = 100 + for unit, pilot in zip(self.group.units, self.flight.roster.pilots): + unit.fuel = fuel From 73f7d94bda4fc297c10646ceacd580a00f3c0dfb Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Wed, 13 Sep 2023 21:55:21 +0300 Subject: [PATCH 018/243] Dynamically generate Pretense air groups (pretense/pretenseluagenerator.py) --- game/game.py | 5 + game/pretense/pretenseaircraftgenerator.py | 37 +++--- game/pretense/pretenseflightgroupspawner.py | 24 ++++ game/pretense/pretenseluagenerator.py | 122 ++++++++++++++++++-- game/pretense/pretensemissiongenerator.py | 6 +- 5 files changed, 165 insertions(+), 29 deletions(-) diff --git a/game/game.py b/game/game.py index ebe47cfb..4fb68761 100644 --- a/game/game.py +++ b/game/game.py @@ -148,6 +148,11 @@ class Game: self.blue.configure_default_air_wing(air_wing_config) self.red.configure_default_air_wing(air_wing_config) + # Side, control point, mission type + self.pretense_ground_supply: dict[int, dict[str, List[str]]] = {1: {}, 2: {}} + self.pretense_ground_assault: dict[int, dict[str, List[str]]] = {1: {}, 2: {}} + self.pretense_air: dict[int, dict[str, dict[str, List[str]]]] = {1: {}, 2: {}} + self.on_load(game_still_initializing=True) def __setstate__(self, state: dict[str, Any]) -> None: diff --git a/game/pretense/pretenseaircraftgenerator.py b/game/pretense/pretenseaircraftgenerator.py index 2437bab3..248f95d8 100644 --- a/game/pretense/pretenseaircraftgenerator.py +++ b/game/pretense/pretenseaircraftgenerator.py @@ -121,6 +121,7 @@ class PretenseAircraftGenerator: num_of_cas = 0 num_of_strike = 0 num_of_cap = 0 + for squadron in cp.squadrons: # Intentionally don't spawn anything at OffMapSpawns in Pretense if isinstance(squadron.location, OffMapSpawn): @@ -134,7 +135,7 @@ class PretenseAircraftGenerator: FlightType.TRANSPORT in mission_types or FlightType.AIR_ASSAULT in mission_types ): - flight_type = FlightType.TRANSPORT + flight_type = FlightType.AIR_ASSAULT elif ( FlightType.SEAD in mission_types or FlightType.SEAD_SWEEP in mission_types @@ -210,21 +211,25 @@ class PretenseAircraftGenerator: self.ground_spawns, self.mission_data, ).create_flight_group() - # self.flights.append( - # FlightGroupConfigurator( - # flight, - # group, - # self.game, - # self.mission, - # self.time, - # self.radio_registry, - # self.tacan_registy, - # self.laser_code_registry, - # self.mission_data, - # dynamic_runways, - # self.use_client, - # ).configure() - # ) + if flight.flight_type in [ + FlightType.REFUELING, + FlightType.AEWC, + ]: + self.flights.append( + FlightGroupConfigurator( + flight, + group, + self.game, + self.mission, + self.time, + self.radio_registry, + self.tacan_registy, + self.laser_code_registry, + self.mission_data, + dynamic_runways, + self.use_client, + ).configure() + ) if self.ewrj: self._track_ewrj_flight(flight, group) diff --git a/game/pretense/pretenseflightgroupspawner.py b/game/pretense/pretenseflightgroupspawner.py index 880ad88d..4e671c2d 100644 --- a/game/pretense/pretenseflightgroupspawner.py +++ b/game/pretense/pretenseflightgroupspawner.py @@ -66,9 +66,24 @@ class PretenseFlightGroupSpawner(FlightGroupSpawner): def generate_flight_at_departure(self) -> FlyingGroup[Any]: cp = self.flight.departure name = namegen.next_pretense_aircraft_name(cp, self.flight) + cp_side = 2 if cp.captured else 1 + cp_name_trimmed = "".join([i for i in cp.name.lower() if i.isalnum()]) + flight_type = self.flight.flight_type.name + if cp_name_trimmed not in self.flight.coalition.game.pretense_air[cp_side]: + self.flight.coalition.game.pretense_air[cp_side][cp_name_trimmed] = {} + if ( + flight_type + not in self.flight.coalition.game.pretense_air[cp_side][cp_name_trimmed] + ): + self.flight.coalition.game.pretense_air[cp_side][cp_name_trimmed][ + flight_type + ] = list() try: if self.start_type is StartType.IN_FLIGHT: + self.flight.coalition.game.pretense_air[cp_side][cp_name_trimmed][ + self.flight.flight_type.name + ].append(name) group = self._generate_over_departure(name, cp) return group elif isinstance(cp, OffMapSpawn): @@ -82,6 +97,9 @@ class PretenseFlightGroupSpawner(FlightGroupSpawner): f"Carrier group {carrier_group} is a " f"{carrier_group.__class__.__name__}, expected a ShipGroup" ) + self.flight.coalition.game.pretense_air[cp_side][cp_name_trimmed][ + self.flight.flight_type.name + ].append(name) return self._generate_at_group(name, carrier_group) elif isinstance(cp, Fob): is_heli = self.flight.squadron.aircraft.helicopter @@ -109,6 +127,9 @@ class PretenseFlightGroupSpawner(FlightGroupSpawner): pad_group = self._generate_at_cp_ground_spawn(name, cp) if pad_group is not None: return pad_group + self.flight.coalition.game.pretense_air[cp_side][cp_name_trimmed][ + self.flight.flight_type.name + ].append(name) return self._generate_over_departure(name, cp) elif isinstance(cp, Airfield): is_heli = self.flight.squadron.aircraft.helicopter @@ -126,6 +147,9 @@ class PretenseFlightGroupSpawner(FlightGroupSpawner): pad_group = self._generate_at_cp_ground_spawn(name, cp) if pad_group is not None: return pad_group + self.flight.coalition.game.pretense_air[cp_side][cp_name_trimmed][ + self.flight.flight_type.name + ].append(name) return self._generate_at_airfield(name, cp) else: raise NotImplementedError( diff --git a/game/pretense/pretenseluagenerator.py b/game/pretense/pretenseluagenerator.py index d860abd3..9961c1af 100644 --- a/game/pretense/pretenseluagenerator.py +++ b/game/pretense/pretenseluagenerator.py @@ -225,6 +225,10 @@ class PretenseLuaGenerator(LuaGenerator): for cp in self.game.theater.controlpoints: cp_name_trimmed = "".join([i for i in cp.name.lower() if i.isalnum()]) cp_side = 2 if cp.captured else 1 + if cp_name_trimmed not in self.game.pretense_air[cp_side]: + self.game.pretense_air[cp_side][cp_name_trimmed] = {} + # if flight_type not in self.flight.coalition.game.pretense_air[cp_side][cp_name_trimmed]: + # self.flight.coalition.game.pretense_air[cp_side][cp_name_trimmed][flight_type] = list() lua_string += f"zones.{cp_name_trimmed} = ZoneCommand:new('{cp.name}')\n" lua_string += ( @@ -248,7 +252,7 @@ class PretenseLuaGenerator(LuaGenerator): lua_string += " }\n" lua_string += " }),\n" lua_string += " presets.upgrades.basic.comPost:extend({\n" - lua_string += " name = 'batumi-com-red',\n" + lua_string += f" name = '{cp_name_trimmed}-com-red',\n" lua_string += " products = {" lua_string += " presets.special.red.infantry:extend({ name='batumi-defense-red'}),\n" lua_string += " presets.defenses.red.infantry:extend({ name='batumi-garrison-red' })\n" @@ -258,13 +262,13 @@ class PretenseLuaGenerator(LuaGenerator): lua_string += " [2] = --blue side\n" lua_string += " {\n" lua_string += " presets.upgrades.basic.tent:extend({\n" - lua_string += " name='mike-tent-blue',\n" + lua_string += f" name='{cp_name_trimmed}-tent-blue',\n" lua_string += " products = {\n" lua_string += " presets.special.blue.infantry:extend({ name='mike-defense-blue'})\n" lua_string += " }\n" lua_string += " }),\n" lua_string += " presets.upgrades.basic.comPost:extend({\n" - lua_string += " name = 'batumi-com-blue',\n" + lua_string += f" name = '{cp_name_trimmed}-com-blue',\n" lua_string += " products = {" lua_string += " presets.special.blue.infantry:extend({ name='batumi-defense-blue'}),\n" lua_string += " presets.defenses.blue.infantry:extend({ name='batumi-garrison-blue' })\n" @@ -279,16 +283,110 @@ class PretenseLuaGenerator(LuaGenerator): lua_string += " }\n" lua_string += " }),\n" lua_string += " presets.upgrades.airdef.comCenter:extend({\n" - lua_string += " name = 'batumi-mission-command-blue',\n" + lua_string += ( + f" name = '{cp_name_trimmed}-mission-command-blue',\n" + ) lua_string += " products = {\n" - lua_string += " presets.defenses.blue.shorad:extend({ name='batumi-sam-blue' }),\n" - lua_string += " presets.missions.attack.sead:extend({name='batumi-sead-blue-1', altitude=25000, expend=AI.Task.WeaponExpend.ALL}),\n" - lua_string += " presets.missions.attack.cas:extend({name='batumi-cas-blue-1', altitude=15000, expend=AI.Task.WeaponExpend.ONE}),\n" - lua_string += " presets.missions.attack.bai:extend({name='batumi-cas-blue-1', altitude=10000, expend=AI.Task.WeaponExpend.ONE}),\n" - lua_string += " presets.missions.attack.strike:extend({name='batumi-strike-blue-1', altitude=20000, expend=AI.Task.WeaponExpend.ALL}),\n" - lua_string += " presets.missions.patrol.aircraft:extend({name='batumi-patrol-blue-1', altitude=25000, range=25}),\n" - lua_string += " presets.missions.support.awacs:extend({name='batumi-awacs-blue', altitude=30000, freq=257.5}),\n" - lua_string += " presets.missions.support.tanker:extend({name='batumi-tanker-blue', altitude=25000, freq=257, tacan='37', variant=\"Drogue\"})\n" + lua_string += ( + " presets.defenses.blue.shorad:extend({ name='" + + cp_name_trimmed + + "-sam-blue' }),\n" + ) + for mission_type in self.game.pretense_air[cp_side][cp_name_trimmed]: + if mission_type == FlightType.SEAD.name: + mission_name = "attack.sead" + for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ + mission_type + ]: + lua_string += ( + f" presets.missions.{mission_name}:extend" + + "({name='" + + air_group + + "', altitude=25000, expend=AI.Task.WeaponExpend.ALL}),\n" + ) + elif mission_type == FlightType.CAS.name: + mission_name = "attack.cas" + for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ + mission_type + ]: + lua_string += ( + f" presets.missions.{mission_name}:extend" + + "({name='" + + air_group + + "', altitude=15000, expend=AI.Task.WeaponExpend.ONE}),\n" + ) + elif mission_type == FlightType.BAI.name: + mission_name = "attack.bai" + for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ + mission_type + ]: + lua_string += ( + f" presets.missions.{mission_name}:extend" + + "({name='" + + air_group + + "', altitude=10000, expend=AI.Task.WeaponExpend.ONE}),\n" + ) + elif mission_type == FlightType.STRIKE.name: + mission_name = "attack.strike" + for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ + mission_type + ]: + lua_string += ( + f" presets.missions.{mission_name}:extend" + + "({name='" + + air_group + + "', altitude=20000, expend=AI.Task.WeaponExpend.ALL}),\n" + ) + elif mission_type == FlightType.BARCAP.name: + mission_name = "patrol.aircraft" + for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ + mission_type + ]: + lua_string += ( + f" presets.missions.{mission_name}:extend" + + "({name='" + + air_group + + "', altitude=25000, range=25}),\n" + ) + elif mission_type == FlightType.AIR_ASSAULT.name: + mission_name = "supply.helo" + for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ + mission_type + ]: + lua_string += ( + f" presets.missions.{mission_name}:extend" + + "({name='" + + air_group + + "'}),\n" + ) + elif mission_type == FlightType.REFUELING.name: + mission_name = "support.tanker" + for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ + mission_type + ]: + lua_string += ( + f" presets.missions.{mission_name}:extend" + + "({name='" + + air_group + + "', freq=257, tacan='37', variant=\"Drogue\"}),\n" + ) + for tanker in self.mission_data.tankers: + if tanker.group_name == air_group: + print(tanker) + elif mission_type == FlightType.AEWC.name: + mission_name = "support.awacs" + for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ + mission_type + ]: + lua_string += ( + f" presets.missions.{mission_name}:extend" + + "({name='" + + air_group + + "', freq=257.5}),\n" + ) + for awacs in self.mission_data.awacs: + if awacs.group_name == air_group: + print(awacs) lua_string += " }\n" lua_string += " })\n" lua_string += " }\n" diff --git a/game/pretense/pretensemissiongenerator.py b/game/pretense/pretensemissiongenerator.py index ed220130..9daaab9b 100644 --- a/game/pretense/pretensemissiongenerator.py +++ b/game/pretense/pretensemissiongenerator.py @@ -3,7 +3,7 @@ from __future__ import annotations import logging from datetime import datetime from pathlib import Path -from typing import TYPE_CHECKING, cast +from typing import TYPE_CHECKING, cast, List import dcs.lua from dataclasses import field @@ -81,6 +81,10 @@ class PretenseMissionGenerator: ) self.generation_started = True + self.game.pretense_ground_supply = {1: {}, 2: {}} + self.game.pretense_ground_assault = {1: {}, 2: {}} + self.game.pretense_air = {1: {}, 2: {}} + self.setup_mission_coalitions() self.add_airfields_to_unit_map() self.initialize_registries() From f7e618bd0f907eff1c9a9a1b5a5dcacd9111055f Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Wed, 13 Sep 2023 22:09:08 +0300 Subject: [PATCH 019/243] Tanker and AWACS frequency, TACAN and variant handling. --- game/pretense/pretenseaircraftgenerator.py | 3 ++ game/pretense/pretenseluagenerator.py | 32 ++++++++++++++++------ 2 files changed, 27 insertions(+), 8 deletions(-) diff --git a/game/pretense/pretenseaircraftgenerator.py b/game/pretense/pretenseaircraftgenerator.py index 248f95d8..82135e57 100644 --- a/game/pretense/pretenseaircraftgenerator.py +++ b/game/pretense/pretenseaircraftgenerator.py @@ -22,6 +22,9 @@ from game.ato.flightstate import Completed, WaitingForStart from game.ato.flighttype import FlightType from game.ato.package import Package from game.ato.starttype import StartType +from game.missiongenerator.aircraft.flightgroupconfigurator import ( + FlightGroupConfigurator, +) from game.missiongenerator.lasercoderegistry import LaserCodeRegistry from game.missiongenerator.missiondata import MissionData from game.radio.radios import RadioRegistry diff --git a/game/pretense/pretenseluagenerator.py b/game/pretense/pretenseluagenerator.py index 9961c1af..9ac9beea 100644 --- a/game/pretense/pretenseluagenerator.py +++ b/game/pretense/pretenseluagenerator.py @@ -364,29 +364,45 @@ class PretenseLuaGenerator(LuaGenerator): for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ mission_type ]: + tanker_freq = 257 + tanker_tacan = 37 + for tanker in self.mission_data.tankers: + if tanker.group_name == air_group: + tanker_freq = tanker.freq.hertz / 1000000 + tanker_tacan = tanker.tacan.number + if tanker.variant == "KC-135 Stratotanker": + tanker_variant = "Boom" + else: + tanker_variant = "Drogue" lua_string += ( f" presets.missions.{mission_name}:extend" + "({name='" + air_group - + "', freq=257, tacan='37', variant=\"Drogue\"}),\n" + + "', freq='" + + str(tanker_freq) + + "', tacan='" + + str(tanker_tacan) + + "', variant='" + + tanker_variant + + "'}),\n" ) - for tanker in self.mission_data.tankers: - if tanker.group_name == air_group: - print(tanker) elif mission_type == FlightType.AEWC.name: mission_name = "support.awacs" for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ mission_type ]: + awacs_freq = 257.5 + for awacs in self.mission_data.awacs: + if awacs.group_name == air_group: + awacs_freq = awacs.freq.hertz / 1000000 lua_string += ( f" presets.missions.{mission_name}:extend" + "({name='" + air_group - + "', freq=257.5}),\n" + + "', freq=" + + str(awacs_freq) + + "}),\n" ) - for awacs in self.mission_data.awacs: - if awacs.group_name == air_group: - print(awacs) lua_string += " }\n" lua_string += " })\n" lua_string += " }\n" From ab76d18621412f0b62c34558581ff35e08b80b22 Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Wed, 13 Sep 2023 22:15:23 +0300 Subject: [PATCH 020/243] Deleted pretense/pretenseflightgroupconfigurator.py since it looks like it's not needed (at least for the time being). --- .../pretenseflightgroupconfigurator.py | 288 ------------------ 1 file changed, 288 deletions(-) delete mode 100644 game/pretense/pretenseflightgroupconfigurator.py diff --git a/game/pretense/pretenseflightgroupconfigurator.py b/game/pretense/pretenseflightgroupconfigurator.py deleted file mode 100644 index ec20f532..00000000 --- a/game/pretense/pretenseflightgroupconfigurator.py +++ /dev/null @@ -1,288 +0,0 @@ -from __future__ import annotations - -import logging -from datetime import datetime -from typing import Any, Optional, TYPE_CHECKING - -from dcs import Mission -from dcs.action import DoScript -from dcs.flyingunit import FlyingUnit -from dcs.task import OptReactOnThreat -from dcs.translation import String -from dcs.triggers import TriggerStart -from dcs.unit import Skill -from dcs.unitgroup import FlyingGroup - -from game.ato import Flight, FlightType -from game.callsigns import callsign_for_support_unit -from game.data.weapons import Pylon, WeaponType as WeaponTypeEnum -from game.missiongenerator.lasercoderegistry import LaserCodeRegistry -from game.missiongenerator.logisticsgenerator import LogisticsGenerator -from game.missiongenerator.missiondata import MissionData, AwacsInfo, TankerInfo -from game.radio.radios import RadioFrequency, RadioRegistry -from game.radio.tacan import TacanBand, TacanRegistry, TacanUsage -from game.runways import RunwayData -from game.squadrons import Pilot -from .aircraftbehavior import AircraftBehavior -from .aircraftpainter import AircraftPainter -from .flightdata import FlightData -from .waypoints import WaypointGenerator -from ...ato.flightplans.aewc import AewcFlightPlan -from ...ato.flightplans.packagerefueling import PackageRefuelingFlightPlan -from ...ato.flightplans.theaterrefueling import TheaterRefuelingFlightPlan -from ...theater import Fob - -if TYPE_CHECKING: - from game import Game - - -class FlightGroupConfigurator: - def __init__( - self, - flight: Flight, - group: FlyingGroup[Any], - game: Game, - mission: Mission, - time: datetime, - radio_registry: RadioRegistry, - tacan_registry: TacanRegistry, - laser_code_registry: LaserCodeRegistry, - mission_data: MissionData, - dynamic_runways: dict[str, RunwayData], - use_client: bool, - ) -> None: - self.flight = flight - self.group = group - self.game = game - self.mission = mission - self.time = time - self.radio_registry = radio_registry - self.tacan_registry = tacan_registry - self.laser_code_registry = laser_code_registry - self.mission_data = mission_data - self.dynamic_runways = dynamic_runways - self.use_client = use_client - - def configure(self) -> FlightData: - AircraftBehavior(self.flight.flight_type).apply_to(self.flight, self.group) - AircraftPainter(self.flight, self.group).apply_livery() - self.setup_props() - self.setup_payload() - self.setup_fuel() - flight_channel = self.setup_radios() - - laser_codes: list[Optional[int]] = [] - for unit, pilot in zip(self.group.units, self.flight.roster.pilots): - self.configure_flight_member(unit, pilot, laser_codes) - - divert = None - if self.flight.divert is not None: - divert = self.flight.divert.active_runway( - self.game.theater, self.game.conditions, self.dynamic_runways - ) - - if self.flight.flight_type in [ - FlightType.TRANSPORT, - FlightType.AIR_ASSAULT, - ] and self.game.settings.plugin_option("ctld"): - transfer = None - if self.flight.flight_type == FlightType.TRANSPORT: - coalition = self.game.coalition_for(player=self.flight.blue) - transfer = coalition.transfers.transfer_for_flight(self.flight) - self.mission_data.logistics.append( - LogisticsGenerator( - self.flight, self.group, self.mission, self.game.settings, transfer - ).generate_logistics() - ) - - mission_start_time, waypoints = WaypointGenerator( - self.flight, - self.group, - self.mission, - self.game.conditions.start_time, - self.time, - self.game.settings, - self.mission_data, - ).create_waypoints() - - # Special handling for landing waypoints when: - # 1. It's an AI-only flight - # 2. Aircraft are not helicopters/VTOL - # 3. Landing waypoint does not point to an airfield - if ( - self.flight.client_count < 1 - and not self.flight.unit_type.helicopter - and not self.flight.unit_type.lha_capable - and isinstance(self.flight.squadron.location, Fob) - ): - # Need to set uncontrolled to false, otherwise the AI will skip the mission and just land - self.group.uncontrolled = False - - return FlightData( - package=self.flight.package, - aircraft_type=self.flight.unit_type, - squadron=self.flight.squadron, - flight_type=self.flight.flight_type, - units=self.group.units, - size=len(self.group.units), - friendly=self.flight.from_cp.captured, - departure_delay=mission_start_time, - departure=self.flight.departure.active_runway( - self.game.theater, self.game.conditions, self.dynamic_runways - ), - arrival=self.flight.arrival.active_runway( - self.game.theater, self.game.conditions, self.dynamic_runways - ), - divert=divert, - waypoints=waypoints, - intra_flight_channel=flight_channel, - bingo_fuel=self.flight.flight_plan.bingo_fuel, - joker_fuel=self.flight.flight_plan.joker_fuel, - custom_name=self.flight.custom_name, - laser_codes=laser_codes, - ) - - def configure_flight_member( - self, unit: FlyingUnit, pilot: Optional[Pilot], laser_codes: list[Optional[int]] - ) -> None: - player = pilot is not None and pilot.player - self.set_skill(unit, pilot) - if self.flight.loadout.has_weapon_of_type(WeaponTypeEnum.TGP) and player: - laser_codes.append(self.laser_code_registry.get_next_laser_code()) - else: - laser_codes.append(None) - settings = self.flight.coalition.game.settings - if not player or not settings.plugins.get("ewrj"): - return - jammer_required = settings.plugin_option("ewrj.ecm_required") - if jammer_required: - ecm = WeaponTypeEnum.JAMMER - if not self.flight.loadout.has_weapon_of_type(ecm): - return - ewrj_menu_trigger = TriggerStart(comment=f"EWRJ-{unit.name}") - ewrj_menu_trigger.add_action(DoScript(String(f'EWJamming("{unit.name}")'))) - self.mission.triggerrules.triggers.append(ewrj_menu_trigger) - self.group.points[0].tasks[0] = OptReactOnThreat( - OptReactOnThreat.Values.PassiveDefense - ) - - def setup_radios(self) -> RadioFrequency: - freq = self.flight.frequency - if freq is None and (freq := self.flight.package.frequency) is None: - freq = self.radio_registry.alloc_uhf() - self.flight.package.frequency = freq - if freq not in self.radio_registry.allocated_channels: - self.radio_registry.reserve(freq) - - if self.flight.flight_type in {FlightType.AEWC, FlightType.REFUELING}: - self.register_air_support(freq) - elif self.flight.frequency is None and self.flight.client_count: - freq = self.flight.unit_type.alloc_flight_radio(self.radio_registry) - - self.group.set_frequency(freq.mhz) - return freq - - def register_air_support(self, channel: RadioFrequency) -> None: - callsign = callsign_for_support_unit(self.group) - if isinstance(self.flight.flight_plan, AewcFlightPlan): - self.mission_data.awacs.append( - AwacsInfo( - group_name=str(self.group.name), - callsign=callsign, - freq=channel, - depature_location=self.flight.departure.name, - start_time=self.flight.flight_plan.patrol_start_time, - end_time=self.flight.flight_plan.patrol_end_time, - blue=self.flight.departure.captured, - ) - ) - elif isinstance( - self.flight.flight_plan, TheaterRefuelingFlightPlan - ) or isinstance(self.flight.flight_plan, PackageRefuelingFlightPlan): - if self.flight.tacan is None: - tacan = self.tacan_registry.alloc_for_band( - TacanBand.Y, TacanUsage.AirToAir - ) - else: - tacan = self.flight.tacan - self.mission_data.tankers.append( - TankerInfo( - group_name=str(self.group.name), - callsign=callsign, - variant=self.flight.unit_type.name, - freq=channel, - tacan=tacan, - start_time=self.flight.flight_plan.patrol_start_time, - end_time=self.flight.flight_plan.patrol_end_time, - blue=self.flight.departure.captured, - ) - ) - - def set_skill(self, unit: FlyingUnit, pilot: Optional[Pilot]) -> None: - if pilot is None or not pilot.player: - unit.skill = self.skill_level_for(unit, pilot) - return - - if self.use_client or "Pilot #1" not in unit.name: - unit.set_client() - else: - unit.set_player() - - def skill_level_for(self, unit: FlyingUnit, pilot: Optional[Pilot]) -> Skill: - if self.flight.squadron.player: - base_skill = Skill(self.game.settings.player_skill) - else: - base_skill = Skill(self.game.settings.enemy_skill) - - if pilot is None: - logging.error(f"Cannot determine skill level: {unit.name} has not pilot") - return base_skill - - levels = [ - Skill.Average, - Skill.Good, - Skill.High, - Skill.Excellent, - ] - current_level = levels.index(base_skill) - missions_for_skill_increase = 4 - increase = pilot.record.missions_flown // missions_for_skill_increase - capped_increase = min(current_level + increase, len(levels) - 1) - - if self.game.settings.ai_pilot_levelling: - new_level = capped_increase - else: - new_level = current_level - - return levels[new_level] - - def setup_props(self) -> None: - for prop_id, value in self.flight.props.items(): - for unit in self.group.units: - unit.set_property(prop_id, value) - - def setup_payload(self) -> None: - for p in self.group.units: - p.pylons.clear() - - loadout = self.flight.loadout - if self.game.settings.restrict_weapons_by_date: - loadout = loadout.degrade_for_date(self.flight.unit_type, self.game.date) - - for pylon_number, weapon in loadout.pylons.items(): - if weapon is None: - continue - pylon = Pylon.for_aircraft(self.flight.unit_type, pylon_number) - pylon.equip(self.group, weapon) - - def setup_fuel(self) -> None: - fuel = self.flight.state.estimate_fuel() - if fuel < 0: - logging.warning( - f"Flight {self.flight} is estimated to have no fuel at mission start. " - "This estimate does not account for external fuel tanks. Setting " - "starting fuel to 100kg." - ) - fuel = 100 - for unit, pilot in zip(self.group.units, self.flight.roster.pilots): - unit.fuel = fuel From c4effe88ee99aa99bec0660120599e6b6a9c67a5 Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Thu, 14 Sep 2023 08:11:38 +0300 Subject: [PATCH 021/243] Implemented adding ground unit groups to pretense data containers. --- game/pretense/pretenseluagenerator.py | 47 ++++++++++++++++++------ game/pretense/pretensetgogenerator.py | 51 ++++++++++++++++++--------- 2 files changed, 71 insertions(+), 27 deletions(-) diff --git a/game/pretense/pretenseluagenerator.py b/game/pretense/pretenseluagenerator.py index 9ac9beea..823c97cc 100644 --- a/game/pretense/pretenseluagenerator.py +++ b/game/pretense/pretenseluagenerator.py @@ -225,8 +225,9 @@ class PretenseLuaGenerator(LuaGenerator): for cp in self.game.theater.controlpoints: cp_name_trimmed = "".join([i for i in cp.name.lower() if i.isalnum()]) cp_side = 2 if cp.captured else 1 - if cp_name_trimmed not in self.game.pretense_air[cp_side]: - self.game.pretense_air[cp_side][cp_name_trimmed] = {} + for side in range(1, 3): + if cp_name_trimmed not in self.game.pretense_air[cp_side]: + self.game.pretense_air[side][cp_name_trimmed] = {} # if flight_type not in self.flight.coalition.game.pretense_air[cp_side][cp_name_trimmed]: # self.flight.coalition.game.pretense_air[cp_side][cp_name_trimmed][flight_type] = list() @@ -248,14 +249,26 @@ class PretenseLuaGenerator(LuaGenerator): lua_string += " presets.upgrades.basic.tent:extend({\n" lua_string += f" name='{cp_name_trimmed}-tent-red',\n" lua_string += " products = {\n" - lua_string += " presets.special.red.infantry:extend({ name='mike-defense-red'})\n" + lua_string += ( + " presets.special.red.infantry:extend({ name='" + + cp_name_trimmed + + "-defense-red'})\n" + ) lua_string += " }\n" lua_string += " }),\n" lua_string += " presets.upgrades.basic.comPost:extend({\n" lua_string += f" name = '{cp_name_trimmed}-com-red',\n" lua_string += " products = {" - lua_string += " presets.special.red.infantry:extend({ name='batumi-defense-red'}),\n" - lua_string += " presets.defenses.red.infantry:extend({ name='batumi-garrison-red' })\n" + lua_string += ( + " presets.special.red.infantry:extend({ name='" + + cp_name_trimmed + + "-defense-red'}),\n" + ) + lua_string += ( + " presets.defenses.red.infantry:extend({ name='" + + cp_name_trimmed + + "-garrison-red' })\n" + ) lua_string += " }\n" lua_string += " }),\n" lua_string += " },\n" @@ -264,18 +277,32 @@ class PretenseLuaGenerator(LuaGenerator): lua_string += " presets.upgrades.basic.tent:extend({\n" lua_string += f" name='{cp_name_trimmed}-tent-blue',\n" lua_string += " products = {\n" - lua_string += " presets.special.blue.infantry:extend({ name='mike-defense-blue'})\n" + lua_string += ( + " presets.special.blue.infantry:extend({ name='" + + cp_name_trimmed + + "-defense-blue'})\n" + ) lua_string += " }\n" lua_string += " }),\n" lua_string += " presets.upgrades.basic.comPost:extend({\n" lua_string += f" name = '{cp_name_trimmed}-com-blue',\n" - lua_string += " products = {" - lua_string += " presets.special.blue.infantry:extend({ name='batumi-defense-blue'}),\n" - lua_string += " presets.defenses.blue.infantry:extend({ name='batumi-garrison-blue' })\n" + lua_string += " products = {\n" + lua_string += ( + " presets.special.blue.infantry:extend({ name='" + + cp_name_trimmed + + "-defense-blue'}),\n" + ) + lua_string += ( + " presets.defenses.blue.infantry:extend({ name='" + + cp_name_trimmed + + "-garrison-blue' })\n" + ) lua_string += " }\n" lua_string += " }),\n" lua_string += " presets.upgrades.supply.fuelTank:extend({\n" - lua_string += " name = 'batumi-fueltank-blue',\n" + lua_string += ( + " name = '" + cp_name_trimmed + "-fueltank-blue',\n" + ) lua_string += " products = {\n" lua_string += " presets.missions.supply.convoy_escorted:extend({ name='batumi-supply-convoy-1'}),\n" lua_string += " presets.missions.supply.helo:extend({ name='batumi-supply-blue-1' }),\n" diff --git a/game/pretense/pretensetgogenerator.py b/game/pretense/pretensetgogenerator.py index 56d84f54..00e0477b 100644 --- a/game/pretense/pretensetgogenerator.py +++ b/game/pretense/pretensetgogenerator.py @@ -168,37 +168,55 @@ class PretenseGroundObjectGenerator(GroundObjectGenerator): def generate(self) -> None: if self.culled: return + cp_name_trimmed = "".join( + [i for i in self.ground_object.control_point.name.lower() if i.isalnum()] + ) + cp_side = 2 if self.ground_object.control_point.captured else 1 + for side in range(1, 3): + if cp_name_trimmed not in self.game.pretense_ground_supply[cp_side]: + self.game.pretense_ground_supply[side][cp_name_trimmed] = list() + if cp_name_trimmed not in self.game.pretense_ground_assault[cp_side]: + self.game.pretense_ground_assault[side][cp_name_trimmed] = list() + print(self.game.pretense_ground_supply[cp_side][cp_name_trimmed]) for group in self.ground_object.groups: vehicle_units: list[TheaterUnit] = [] ship_units: list[TheaterUnit] = [] # Split the different unit types to be compliant to dcs limitation for unit in group.units: - cp_name_trimmed = "".join( - [ - i - for i in self.ground_object.control_point.name.lower() - if i.isalnum() - ] - ) - if unit.is_static: # Add supply convoy + group_id = self.game.next_group_id() + group_role = "supply" + group_name = f"{cp_name_trimmed}-{group_role}-{group_id}" + group.name = group_name + self.game.pretense_ground_supply[cp_side][cp_name_trimmed].append( + group_name + ) + self.generate_ground_unit_of_class( UnitClass.LOGISTICS, group, vehicle_units, cp_name_trimmed, - "supply", + group_role, PRETENSE_GROUND_UNIT_GROUP_SIZE, ) elif unit.is_vehicle and unit.alive: # Add armor group + group_id = self.game.next_group_id() + group_role = "assault" + group_name = f"{cp_name_trimmed}-{group_role}-{group_id}" + group.name = group_name + self.game.pretense_ground_supply[cp_side][cp_name_trimmed].append( + group_name + ) + self.generate_ground_unit_of_class( UnitClass.TANK, group, vehicle_units, cp_name_trimmed, - "assault", + group_role, PRETENSE_GROUND_UNIT_GROUP_SIZE - 3, ) self.generate_ground_unit_of_class( @@ -206,7 +224,7 @@ class PretenseGroundObjectGenerator(GroundObjectGenerator): group, vehicle_units, cp_name_trimmed, - "assault", + group_role, PRETENSE_GROUND_UNIT_GROUP_SIZE - 2, ) self.generate_ground_unit_of_class( @@ -214,7 +232,7 @@ class PretenseGroundObjectGenerator(GroundObjectGenerator): group, vehicle_units, cp_name_trimmed, - "assault", + group_role, PRETENSE_GROUND_UNIT_GROUP_SIZE - 1, ) self.generate_ground_unit_of_class( @@ -222,7 +240,7 @@ class PretenseGroundObjectGenerator(GroundObjectGenerator): group, vehicle_units, cp_name_trimmed, - "assault", + group_role, PRETENSE_GROUND_UNIT_GROUP_SIZE, ) self.generate_ground_unit_of_class( @@ -230,7 +248,7 @@ class PretenseGroundObjectGenerator(GroundObjectGenerator): group, vehicle_units, cp_name_trimmed, - "assault", + group_role, PRETENSE_GROUND_UNIT_GROUP_SIZE, ) self.generate_ground_unit_of_class( @@ -238,7 +256,7 @@ class PretenseGroundObjectGenerator(GroundObjectGenerator): group, vehicle_units, cp_name_trimmed, - "assault", + group_role, PRETENSE_GROUND_UNIT_GROUP_SIZE, ) if random.randrange(0, 100) > 75: @@ -247,14 +265,13 @@ class PretenseGroundObjectGenerator(GroundObjectGenerator): group, vehicle_units, cp_name_trimmed, - "assault", + group_role, PRETENSE_GROUND_UNIT_GROUP_SIZE, ) elif unit.is_ship and unit.alive: # All alive Ships ship_units.append(unit) if vehicle_units: - print(f"Generating vehicle group {vehicle_units}") self.create_vehicle_group(group.group_name, vehicle_units) if ship_units: self.create_ship_group(group.group_name, ship_units) From 5687c15c218f5da3adfa92a24df396a3d4e68280 Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Thu, 14 Sep 2023 08:43:43 +0300 Subject: [PATCH 022/243] Fixed missing return statement with isinstance(cp, OffMapSpawn). --- game/pretense/pretenseflightgroupspawner.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/game/pretense/pretenseflightgroupspawner.py b/game/pretense/pretenseflightgroupspawner.py index 4e671c2d..839c2b19 100644 --- a/game/pretense/pretenseflightgroupspawner.py +++ b/game/pretense/pretenseflightgroupspawner.py @@ -86,9 +86,6 @@ class PretenseFlightGroupSpawner(FlightGroupSpawner): ].append(name) group = self._generate_over_departure(name, cp) return group - elif isinstance(cp, OffMapSpawn): - # Intentionally don't spawn anything at OffMapSpawns in Pretense - logging.info(f"Skipping flight generation for off-map spawn {cp}.") elif isinstance(cp, NavalControlPoint): group_name = cp.get_carrier_group_name() carrier_group = self.mission.find_group(group_name) From d517b1cdeb334c7cb378c6550a8dce873791a319 Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Thu, 14 Sep 2023 08:44:23 +0300 Subject: [PATCH 023/243] Now clears Retribution triggers when generating a Pretense campaign. --- game/pretense/pretenseluagenerator.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/game/pretense/pretenseluagenerator.py b/game/pretense/pretenseluagenerator.py index 823c97cc..cb1e34c7 100644 --- a/game/pretense/pretenseluagenerator.py +++ b/game/pretense/pretenseluagenerator.py @@ -53,6 +53,8 @@ class PretenseLuaGenerator(LuaGenerator): self.mission.triggerrules.triggers.append(t) def generate_plugin_data(self) -> None: + self.mission.triggerrules.triggers.clear() + lua_data = LuaData("dcsRetribution") install_path = lua_data.add_item("installPath") @@ -391,8 +393,8 @@ class PretenseLuaGenerator(LuaGenerator): for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ mission_type ]: - tanker_freq = 257 - tanker_tacan = 37 + tanker_freq = 257.0 + tanker_tacan = 37.0 for tanker in self.mission_data.tankers: if tanker.group_name == air_group: tanker_freq = tanker.freq.hertz / 1000000 From 93265040f66a7d191c035df00ffbb3b5b55a093e Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Thu, 14 Sep 2023 21:10:50 +0300 Subject: [PATCH 024/243] First version of the generated Pretense campaign running in-game. --- game/pretense/pretenseluagenerator.py | 356 ++++++++-------------- game/pretense/pretensetriggergenerator.py | 5 +- resources/plugins/pretense/init_body.lua | 2 + 3 files changed, 123 insertions(+), 240 deletions(-) diff --git a/game/pretense/pretenseluagenerator.py b/game/pretense/pretenseluagenerator.py index cb1e34c7..2d0e2dd3 100644 --- a/game/pretense/pretenseluagenerator.py +++ b/game/pretense/pretenseluagenerator.py @@ -55,174 +55,17 @@ class PretenseLuaGenerator(LuaGenerator): def generate_plugin_data(self) -> None: self.mission.triggerrules.triggers.clear() - 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)) - 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) + self.inject_plugin_script("base", "mist_4_5_107.lua", "mist_4_5_107") + self.inject_plugin_script( + "pretense", "pretense_compiled.lua", "pretense_compiled" + ) trigger = TriggerStart(comment="Pretense init") init_header_file = open("./resources/plugins/pretense/init_header.lua", "r") init_header = init_header_file.read() - lua_string = "" - lua_data = LuaData("products") + lua_string_zones = "" for cp in self.game.theater.controlpoints: cp_name_trimmed = "".join([i for i in cp.name.lower() if i.isalnum()]) @@ -230,93 +73,129 @@ class PretenseLuaGenerator(LuaGenerator): for side in range(1, 3): if cp_name_trimmed not in self.game.pretense_air[cp_side]: self.game.pretense_air[side][cp_name_trimmed] = {} - # if flight_type not in self.flight.coalition.game.pretense_air[cp_side][cp_name_trimmed]: - # self.flight.coalition.game.pretense_air[cp_side][cp_name_trimmed][flight_type] = list() - - lua_string += f"zones.{cp_name_trimmed} = ZoneCommand:new('{cp.name}')\n" - lua_string += ( + if cp_name_trimmed not in self.game.pretense_ground_supply[cp_side]: + self.game.pretense_ground_supply[side][cp_name_trimmed] = list() + if cp_name_trimmed not in self.game.pretense_ground_assault[cp_side]: + self.game.pretense_ground_assault[side][cp_name_trimmed] = list() + lua_string_zones += ( + f"zones.{cp_name_trimmed} = ZoneCommand:new('{cp.name}')\n" + ) + lua_string_zones += ( f"zones.{cp_name_trimmed}.initialState = " + "{ side=" + str(cp_side) + " }\n" ) - lua_string += f"zones.{cp_name_trimmed}.keepActive = true\n" + lua_string_zones += f"zones.{cp_name_trimmed}.keepActive = true\n" + max_resource = 20000 if cp.has_helipads: - lua_string += f"zones.{cp_name_trimmed}.isHeloSpawn = true\n" + lua_string_zones += f"zones.{cp_name_trimmed}.isHeloSpawn = true\n" + max_resource = 30000 if isinstance(cp, Airfield) or cp.has_ground_spawns: - lua_string += f"zones.{cp_name_trimmed}.isPlaneSpawn = true\n" - lua_string += f"zones.{cp_name_trimmed}.maxResource = 50000\n" - lua_string += f"zones.{cp_name_trimmed}:defineUpgrades(" + "{\n" - lua_string += " [1] = { --red side\n" - lua_string += " presets.upgrades.basic.tent:extend({\n" - lua_string += f" name='{cp_name_trimmed}-tent-red',\n" - lua_string += " products = {\n" - lua_string += ( + lua_string_zones += f"zones.{cp_name_trimmed}.isPlaneSpawn = true\n" + if cp.has_ground_spawns: + max_resource = 40000 + if isinstance(cp, Airfield): + max_resource = 50000 + lua_string_zones += ( + f"zones.{cp_name_trimmed}.maxResource = {max_resource}\n" + ) + lua_string_zones += f"zones.{cp_name_trimmed}:defineUpgrades(" + "{\n" + lua_string_zones += " [1] = { --red side\n" + lua_string_zones += " presets.upgrades.basic.tent:extend({\n" + lua_string_zones += f" name='{cp_name_trimmed}-tent-red',\n" + lua_string_zones += " products = {\n" + lua_string_zones += ( " presets.special.red.infantry:extend({ name='" + cp_name_trimmed + "-defense-red'})\n" ) - lua_string += " }\n" - lua_string += " }),\n" - lua_string += " presets.upgrades.basic.comPost:extend({\n" - lua_string += f" name = '{cp_name_trimmed}-com-red',\n" - lua_string += " products = {" - lua_string += ( + lua_string_zones += " }\n" + lua_string_zones += " }),\n" + lua_string_zones += " presets.upgrades.basic.comPost:extend({\n" + lua_string_zones += f" name = '{cp_name_trimmed}-com-red',\n" + lua_string_zones += " products = {" + lua_string_zones += ( " presets.special.red.infantry:extend({ name='" + cp_name_trimmed + "-defense-red'}),\n" ) - lua_string += ( + lua_string_zones += ( " presets.defenses.red.infantry:extend({ name='" + cp_name_trimmed + "-garrison-red' })\n" ) - lua_string += " }\n" - lua_string += " }),\n" - lua_string += " },\n" - lua_string += " [2] = --blue side\n" - lua_string += " {\n" - lua_string += " presets.upgrades.basic.tent:extend({\n" - lua_string += f" name='{cp_name_trimmed}-tent-blue',\n" - lua_string += " products = {\n" - lua_string += ( + lua_string_zones += " }\n" + lua_string_zones += " }),\n" + lua_string_zones += " },\n" + lua_string_zones += " [2] = --blue side\n" + lua_string_zones += " {\n" + lua_string_zones += " presets.upgrades.basic.tent:extend({\n" + lua_string_zones += f" name='{cp_name_trimmed}-tent-blue',\n" + lua_string_zones += " products = {\n" + lua_string_zones += ( " presets.special.blue.infantry:extend({ name='" + cp_name_trimmed + "-defense-blue'})\n" ) - lua_string += " }\n" - lua_string += " }),\n" - lua_string += " presets.upgrades.basic.comPost:extend({\n" - lua_string += f" name = '{cp_name_trimmed}-com-blue',\n" - lua_string += " products = {\n" - lua_string += ( + lua_string_zones += " }\n" + lua_string_zones += " }),\n" + lua_string_zones += " presets.upgrades.basic.comPost:extend({\n" + lua_string_zones += f" name = '{cp_name_trimmed}-com-blue',\n" + lua_string_zones += " products = {\n" + lua_string_zones += ( " presets.special.blue.infantry:extend({ name='" + cp_name_trimmed + "-defense-blue'}),\n" ) - lua_string += ( + lua_string_zones += ( " presets.defenses.blue.infantry:extend({ name='" + cp_name_trimmed + "-garrison-blue' })\n" ) - lua_string += " }\n" - lua_string += " }),\n" - lua_string += " presets.upgrades.supply.fuelTank:extend({\n" - lua_string += ( + lua_string_zones += " }\n" + lua_string_zones += " }),\n" + lua_string_zones += " presets.upgrades.supply.fuelTank:extend({\n" + lua_string_zones += ( " name = '" + cp_name_trimmed + "-fueltank-blue',\n" ) - lua_string += " products = {\n" - lua_string += " presets.missions.supply.convoy_escorted:extend({ name='batumi-supply-convoy-1'}),\n" - lua_string += " presets.missions.supply.helo:extend({ name='batumi-supply-blue-1' }),\n" - lua_string += " presets.missions.supply.transfer:extend({name='batumi-transfer-blue'})\n" - lua_string += " }\n" - lua_string += " }),\n" - lua_string += " presets.upgrades.airdef.comCenter:extend({\n" - lua_string += ( + lua_string_zones += " products = {\n" + for ground_group in self.game.pretense_ground_supply[cp_side][ + cp_name_trimmed + ]: + lua_string_zones += ( + " presets.missions.supply.convoy:extend({ name='" + + ground_group + + "'}),\n" + ) + for ground_group in self.game.pretense_ground_assault[cp_side][ + cp_name_trimmed + ]: + lua_string_zones += ( + " presets.missions.attack.surface:extend({ name='" + + ground_group + + "'}),\n" + ) + for mission_type in self.game.pretense_air[cp_side][cp_name_trimmed]: + if mission_type == FlightType.AIR_ASSAULT.name: + mission_name = "supply.helo" + for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ + mission_type + ]: + lua_string_zones += ( + f" presets.missions.{mission_name}:extend" + + "({name='" + + air_group + + "'}),\n" + ) + lua_string_zones += " }\n" + lua_string_zones += " }),\n" + lua_string_zones += " presets.upgrades.airdef.comCenter:extend({\n" + lua_string_zones += ( f" name = '{cp_name_trimmed}-mission-command-blue',\n" ) - lua_string += " products = {\n" - lua_string += ( + lua_string_zones += " products = {\n" + lua_string_zones += ( " presets.defenses.blue.shorad:extend({ name='" + cp_name_trimmed + "-sam-blue' }),\n" @@ -327,7 +206,7 @@ class PretenseLuaGenerator(LuaGenerator): for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ mission_type ]: - lua_string += ( + lua_string_zones += ( f" presets.missions.{mission_name}:extend" + "({name='" + air_group @@ -338,7 +217,7 @@ class PretenseLuaGenerator(LuaGenerator): for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ mission_type ]: - lua_string += ( + lua_string_zones += ( f" presets.missions.{mission_name}:extend" + "({name='" + air_group @@ -349,7 +228,7 @@ class PretenseLuaGenerator(LuaGenerator): for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ mission_type ]: - lua_string += ( + lua_string_zones += ( f" presets.missions.{mission_name}:extend" + "({name='" + air_group @@ -360,7 +239,7 @@ class PretenseLuaGenerator(LuaGenerator): for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ mission_type ]: - lua_string += ( + lua_string_zones += ( f" presets.missions.{mission_name}:extend" + "({name='" + air_group @@ -371,23 +250,12 @@ class PretenseLuaGenerator(LuaGenerator): for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ mission_type ]: - lua_string += ( + lua_string_zones += ( f" presets.missions.{mission_name}:extend" + "({name='" + air_group + "', altitude=25000, range=25}),\n" ) - elif mission_type == FlightType.AIR_ASSAULT.name: - mission_name = "supply.helo" - for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ - mission_type - ]: - lua_string += ( - f" presets.missions.{mission_name}:extend" - + "({name='" - + air_group - + "'}),\n" - ) elif mission_type == FlightType.REFUELING.name: mission_name = "support.tanker" for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ @@ -403,7 +271,7 @@ class PretenseLuaGenerator(LuaGenerator): tanker_variant = "Boom" else: tanker_variant = "Drogue" - lua_string += ( + lua_string_zones += ( f" presets.missions.{mission_name}:extend" + "({name='" + air_group @@ -424,7 +292,7 @@ class PretenseLuaGenerator(LuaGenerator): for awacs in self.mission_data.awacs: if awacs.group_name == air_group: awacs_freq = awacs.freq.hertz / 1000000 - lua_string += ( + lua_string_zones += ( f" presets.missions.{mission_name}:extend" + "({name='" + air_group @@ -432,18 +300,32 @@ class PretenseLuaGenerator(LuaGenerator): + str(awacs_freq) + "}),\n" ) - lua_string += " }\n" - lua_string += " })\n" - lua_string += " }\n" - lua_string += "})\n" + lua_string_zones += " }\n" + lua_string_zones += " })\n" + lua_string_zones += " }\n" + lua_string_zones += "})\n" init_body_file = open("./resources/plugins/pretense/init_body.lua", "r") init_body = init_body_file.read() + lua_string_connman = " cm = ConnectionManager:new()" + + for cp in self.game.theater.controlpoints: + for other_cp in cp.connected_points: + lua_string_connman += ( + f" cm: addConnection('{cp.name}', '{other_cp.name}')" + ) + init_footer_file = open("./resources/plugins/pretense/init_footer.lua", "r") init_footer = init_footer_file.read() - lua_string = init_header + lua_string + init_body + init_footer + lua_string = ( + init_header + + lua_string_zones + + lua_string_connman + + init_body + + init_footer + ) trigger.add_action(DoScript(String(lua_string))) self.mission.triggerrules.triggers.append(trigger) diff --git a/game/pretense/pretensetriggergenerator.py b/game/pretense/pretensetriggergenerator.py index 43fbb1de..352654b9 100644 --- a/game/pretense/pretensetriggergenerator.py +++ b/game/pretense/pretensetriggergenerator.py @@ -29,7 +29,7 @@ from dcs.triggers import Event, TriggerCondition, TriggerOnce from dcs.unit import Skill from game.theater import Airfield -from game.theater.controlpoint import Fob, TRIGGER_RADIUS_CAPTURE +from game.theater.controlpoint import Fob, TRIGGER_RADIUS_CAPTURE, OffMapSpawn if TYPE_CHECKING: from game.game import Game @@ -157,7 +157,7 @@ class PretenseTriggerGenerator: 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_fleet: + 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( @@ -215,7 +215,6 @@ class PretenseTriggerGenerator: self._set_skill(player_coalition, enemy_coalition) self._set_allegiances(player_coalition, enemy_coalition) - self._gen_markers() self._generate_pretense_zone_triggers(player_coalition, enemy_coalition) @classmethod diff --git a/resources/plugins/pretense/init_body.lua b/resources/plugins/pretense/init_body.lua index 33a7f627..429b8c01 100644 --- a/resources/plugins/pretense/init_body.lua +++ b/resources/plugins/pretense/init_body.lua @@ -1,4 +1,6 @@ +end + ZoneCommand.setNeighbours(cm) bm = BattlefieldManager:new() From 04993ddf1bfbf253159c035153734dbbb5f3dcb8 Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Fri, 15 Sep 2023 07:45:04 +0300 Subject: [PATCH 025/243] Split the init_body.lua script in two parts. --- .../{init_body.lua => init_body_1.lua} | 0 resources/plugins/pretense/init_body_2.lua | 110 ++++++++++++++++++ 2 files changed, 110 insertions(+) rename resources/plugins/pretense/{init_body.lua => init_body_1.lua} (100%) create mode 100644 resources/plugins/pretense/init_body_2.lua diff --git a/resources/plugins/pretense/init_body.lua b/resources/plugins/pretense/init_body_1.lua similarity index 100% rename from resources/plugins/pretense/init_body.lua rename to resources/plugins/pretense/init_body_1.lua diff --git a/resources/plugins/pretense/init_body_2.lua b/resources/plugins/pretense/init_body_2.lua new file mode 100644 index 00000000..429b8c01 --- /dev/null +++ b/resources/plugins/pretense/init_body_2.lua @@ -0,0 +1,110 @@ + +end + +ZoneCommand.setNeighbours(cm) + +bm = BattlefieldManager:new() + +mc = MarkerCommands:new() + +pt = PlayerTracker:new(mc) + +mt = MissionTracker:new(pt, mc) + +st = SquadTracker:new() + +ct = CSARTracker:new() + +pl = PlayerLogistics:new(mt, pt, st, ct) + +gci = GCI:new(2) + +gm = GroupMonitor:new(cm) +ZoneCommand.groupMonitor = gm + +-- PlayerLogistics:registerSquadGroup(squadType, groupname, weight,cost,jobtime,extracttime, squadSize) +pl:registerSquadGroup(PlayerLogistics.infantryTypes.capture, 'capture-squad', 700, 200, 60, 60*30, 4) +pl:registerSquadGroup(PlayerLogistics.infantryTypes.sabotage, 'sabotage-squad', 800, 500, 60*5, 60*30, 4) +pl:registerSquadGroup(PlayerLogistics.infantryTypes.ambush, 'ambush-squad', 900, 300, 60*20, 60*30, 5) +pl:registerSquadGroup(PlayerLogistics.infantryTypes.engineer, 'engineer-squad', 200, 1000,60, 60*30, 2) +pl:registerSquadGroup(PlayerLogistics.infantryTypes.manpads, 'manpads-squad', 900, 500, 60*20, 60*30, 5) +pl:registerSquadGroup(PlayerLogistics.infantryTypes.spy, 'spy-squad', 100, 300, 60*10, 60*30, 1) +pl:registerSquadGroup(PlayerLogistics.infantryTypes.rapier, 'rapier-squad', 1200,2000,60*60, 60*30, 8) + +Group.getByName('jtacDrone'):destroy() +CommandFunctions.jtac = JTAC:new({name = 'jtacDrone'}) + +pm = PersistenceManager:new(savefile, gm, st, ct, pl) +pm:load() + +if pm:canRestore() then + pm:restoreZones() + pm:restoreAIMissions() + pm:restoreBattlefield() + pm:restoreCsar() + pm:restoreSquads() +else + --initial states + Starter.start(zones) +end + +timer.scheduleFunction(function(param, time) + pm:save() + env.info("Mission state saved") + return time+60 +end, zones, timer.getTime()+60) + + +--make sure support units are present where needed +ensureSpawn = { + ['golf-farp-suport'] = zones.golf, + ['november-farp-suport'] = zones.november, + ['tango-farp-suport'] = zones.tango, + ['sierra-farp-suport'] = zones.sierra, + ['cherkessk-farp-suport'] = zones.cherkessk, + ['unal-farp-suport'] = zones.unal, + ['tyrnyauz-farp-suport'] = zones.tyrnyauz +} + +for grname, zn in pairs(ensureSpawn) do + local g = Group.getByName(grname) + if g then g:destroy() end +end + +timer.scheduleFunction(function(param, time) + + for grname, zn in pairs(ensureSpawn) do + local g = Group.getByName(grname) + if zn.side == 2 then + if not g then + local err, msg = pcall(mist.respawnGroup,grname,true) + if not err then + env.info("ERROR spawning "..grname) + env.info(msg) + end + end + else + if g then g:destroy() end + end + end + + return time+30 +end, {}, timer.getTime()+30) + + +--supply injection +local blueSupply = {'offmap-supply-blue-1','offmap-supply-blue-2','offmap-supply-blue-3','offmap-supply-blue-4','offmap-supply-blue-5'} +local redSupply = {'offmap-supply-red-1','offmap-supply-red-2','offmap-supply-red-3','offmap-supply-red-4','offmap-supply-red-5'} +local offmapZones = { + zones.batumi, + zones.sochi, + zones.nalchik, + zones.beslan, + zones.mozdok, + zones.mineralnye, +-- zones.senaki, +-- zones.sukhumi, +-- zones.gudauta, +-- zones.kobuleti, +} + From acf035697fc993954169650ec6dfa9d27dd8bedd Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Fri, 15 Sep 2023 21:02:56 +0300 Subject: [PATCH 026/243] Implemented dynamic outputting of JTAC units to the Pretense init script. --- game/pretense/pretenseluagenerator.py | 20 +++++++-- game/pretense/pretensemissiongenerator.py | 54 +++-------------------- 2 files changed, 23 insertions(+), 51 deletions(-) diff --git a/game/pretense/pretenseluagenerator.py b/game/pretense/pretenseluagenerator.py index 2d0e2dd3..414b0a74 100644 --- a/game/pretense/pretenseluagenerator.py +++ b/game/pretense/pretenseluagenerator.py @@ -305,9 +305,6 @@ class PretenseLuaGenerator(LuaGenerator): lua_string_zones += " }\n" lua_string_zones += "})\n" - init_body_file = open("./resources/plugins/pretense/init_body.lua", "r") - init_body = init_body_file.read() - lua_string_connman = " cm = ConnectionManager:new()" for cp in self.game.theater.controlpoints: @@ -316,6 +313,19 @@ class PretenseLuaGenerator(LuaGenerator): f" cm: addConnection('{cp.name}', '{other_cp.name}')" ) + init_body_1_file = open("./resources/plugins/pretense/init_body_1.lua", "r") + init_body_1 = init_body_1_file.read() + + lua_string_jtac = "" + for jtac in self.mission_data.jtacs: + lua_string_jtac = f"Group.getByName('{jtac.group_name}'): destroy()" + lua_string_jtac += ( + "CommandFunctions.jtac = JTAC:new({name = '" + jtac.group_name + "'})" + ) + + init_body_2_file = open("./resources/plugins/pretense/init_body_2.lua", "r") + init_body_2 = init_body_2_file.read() + init_footer_file = open("./resources/plugins/pretense/init_footer.lua", "r") init_footer = init_footer_file.read() @@ -323,7 +333,9 @@ class PretenseLuaGenerator(LuaGenerator): init_header + lua_string_zones + lua_string_connman - + init_body + + init_body_1 + + lua_string_jtac + + init_body_2 + init_footer ) diff --git a/game/pretense/pretensemissiongenerator.py b/game/pretense/pretensemissiongenerator.py index 9daaab9b..033faca4 100644 --- a/game/pretense/pretensemissiongenerator.py +++ b/game/pretense/pretensemissiongenerator.py @@ -42,14 +42,20 @@ from .pretensetgogenerator import PretenseTgoGenerator from .pretensetriggergenerator import PretenseTriggerGenerator from game.missiongenerator.visualsgenerator import VisualsGenerator from ..ato import Flight +from ..missiongenerator import MissionGenerator from ..radio.TacanContainer import TacanContainer if TYPE_CHECKING: from game import Game -class PretenseMissionGenerator: +class PretenseMissionGenerator(MissionGenerator): def __init__(self, game: Game, time: datetime) -> None: + super().__init__( + game, + time, + ) + self.game = game self.time = time self.mission = Mission(game.theater.terrain) @@ -143,51 +149,6 @@ class PretenseMissionGenerator: c = country_dict[country_id]() self.mission.coalition["neutrals"].add_country(c) - def add_airfields_to_unit_map(self) -> None: - for control_point in self.game.theater.controlpoints: - if isinstance(control_point, Airfield): - self.unit_map.add_airfield(control_point) - - def initialize_registries(self) -> None: - unique_map_frequencies: set[RadioFrequency] = set() - self.initialize_tacan_registry(unique_map_frequencies) - self.initialize_radio_registry(unique_map_frequencies) - # Allocate UHF/VHF Guard Freq first! - unique_map_frequencies.add(MHz(243)) - unique_map_frequencies.add(MHz(121, 500)) - for frequency in unique_map_frequencies: - self.radio_registry.reserve(frequency) - - def initialize_tacan_registry( - self, unique_map_frequencies: set[RadioFrequency] - ) -> None: - """ - Dedup beacon/radio frequencies, since some maps have some frequencies - used multiple times. - """ - for beacon in Beacons.iter_theater(self.game.theater): - unique_map_frequencies.add(beacon.frequency) - if beacon.is_tacan: - if beacon.channel is None: - logging.warning(f"TACAN beacon has no channel: {beacon.callsign}") - else: - self.tacan_registry.mark_unavailable(beacon.tacan_channel) - for cp in self.game.theater.controlpoints: - if isinstance(cp, TacanContainer) and cp.tacan is not None: - self.tacan_registry.mark_unavailable(cp.tacan) - - def initialize_radio_registry( - self, unique_map_frequencies: set[RadioFrequency] - ) -> None: - for airport in self.game.theater.terrain.airport_list(): - if (atc := AtcData.from_pydcs(airport)) is not None: - unique_map_frequencies.add(atc.hf) - unique_map_frequencies.add(atc.vhf_fm) - unique_map_frequencies.add(atc.vhf_am) - unique_map_frequencies.add(atc.uhf) - # No need to reserve ILS or TACAN because those are in the - # beacon list. - def generate_ground_conflicts(self) -> None: """Generate FLOTs and JTACs for each active front line.""" for front_line in self.game.theater.conflicts(): @@ -247,7 +208,6 @@ class PretenseMissionGenerator: else: ato = self.game.red.ato cp_country = self.e_country - print(f"Generating flights for {cp_country.name} at {cp}") aircraft_generator.generate_flights( cp_country, cp, From 4a859e00ae18dd464fa6e5b33c69cec2b77c7bee Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Fri, 15 Sep 2023 22:24:17 +0300 Subject: [PATCH 027/243] Add CJTF factions to the coalitions in Pretense, if they're not being used in the campaign. --- game/pretense/pretensemissiongenerator.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/game/pretense/pretensemissiongenerator.py b/game/pretense/pretensemissiongenerator.py index 033faca4..79f18fd4 100644 --- a/game/pretense/pretensemissiongenerator.py +++ b/game/pretense/pretensemissiongenerator.py @@ -9,7 +9,11 @@ import dcs.lua from dataclasses import field from dcs import Mission, Point from dcs.coalition import Coalition -from dcs.countries import country_dict +from dcs.countries import ( + country_dict, + CombinedJointTaskForcesBlue, + CombinedJointTaskForcesRed, +) from dcs.task import OptReactOnThreat from game.atcdata import AtcData @@ -143,6 +147,12 @@ class PretenseMissionGenerator(MissionGenerator): self.mission.coalition["blue"].add_country(self.p_country) self.mission.coalition["red"].add_country(self.e_country) + # Add CJTF factions to the coalitions, if they're not being used in the campaign + # if CombinedJointTaskForcesBlue not in {self.p_country, self.e_country}: + # self.mission.coalition["blue"].add_country(CombinedJointTaskForcesBlue()) + # if CombinedJointTaskForcesRed not in {self.p_country, self.e_country}: + # self.mission.coalition["red"].add_country(CombinedJointTaskForcesRed()) + belligerents = {self.p_country.id, self.e_country.id} for country_id in country_dict.keys(): if country_id not in belligerents: From b73a18b7b90ceb1392d0c09bc6918b62a1f093cb Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Sat, 16 Sep 2023 14:36:59 +0300 Subject: [PATCH 028/243] Implemented spawning of Pretense cargo aircraft. To support that, implemented a separate flight plan called PretenseCargoFlightPlan. Also, will now automatically generate transport squadrons for factions which don't have pre-defined squadrons for it, but have access to transport aircraft. --- .../ato/flightplans/flightplanbuildertypes.py | 2 + game/ato/flightplans/pretensecargo.py | 99 +++++++++++++ game/ato/flightstate/navigating.py | 7 +- game/ato/flighttype.py | 4 + game/pretense/pretenseaircraftgenerator.py | 135 +++++++++++++++--- 5 files changed, 225 insertions(+), 22 deletions(-) create mode 100644 game/ato/flightplans/pretensecargo.py diff --git a/game/ato/flightplans/flightplanbuildertypes.py b/game/ato/flightplans/flightplanbuildertypes.py index 5047e45c..4561eeec 100644 --- a/game/ato/flightplans/flightplanbuildertypes.py +++ b/game/ato/flightplans/flightplanbuildertypes.py @@ -18,6 +18,7 @@ from .ocaaircraft import OcaAircraftFlightPlan from .ocarunway import OcaRunwayFlightPlan from .packagerefueling import PackageRefuelingFlightPlan from .planningerror import PlanningError +from .pretensecargo import PretenseCargoFlightPlan from .sead import SeadFlightPlan from .seadsweep import SeadSweepFlightPlan from .strike import StrikeFlightPlan @@ -60,6 +61,7 @@ class FlightPlanBuilderTypes: FlightType.TRANSPORT: AirliftFlightPlan.builder_type(), FlightType.FERRY: FerryFlightPlan.builder_type(), FlightType.AIR_ASSAULT: AirAssaultFlightPlan.builder_type(), + FlightType.PRETENSE_CARGO: PretenseCargoFlightPlan.builder_type(), } try: return builder_dict[flight.flight_type] diff --git a/game/ato/flightplans/pretensecargo.py b/game/ato/flightplans/pretensecargo.py new file mode 100644 index 00000000..f980b5f7 --- /dev/null +++ b/game/ato/flightplans/pretensecargo.py @@ -0,0 +1,99 @@ +from __future__ import annotations + +from collections.abc import Iterator +from dataclasses import dataclass +from datetime import timedelta +from typing import TYPE_CHECKING, Type + +from game.utils import feet +from .ferry import FerryLayout +from .ibuilder import IBuilder +from .planningerror import PlanningError +from .standard import StandardFlightPlan, StandardLayout +from .waypointbuilder import WaypointBuilder + +if TYPE_CHECKING: + from ..flightwaypoint import FlightWaypoint + + +PRETENSE_CARGO_FLIGHT_DISTANCE = 50000 + + +class PretenseCargoFlightPlan(StandardFlightPlan[FerryLayout]): + @staticmethod + def builder_type() -> Type[Builder]: + return Builder + + @property + def tot_waypoint(self) -> FlightWaypoint: + return self.layout.arrival + + def tot_for_waypoint(self, waypoint: FlightWaypoint) -> timedelta | None: + # TOT planning isn't really useful for ferries. They're behind the front + # lines so no need to wait for escorts or for other missions to complete. + return None + + def depart_time_for_waypoint(self, waypoint: FlightWaypoint) -> timedelta | None: + return None + + @property + def mission_departure_time(self) -> timedelta: + return self.package.time_over_target + + +class Builder(IBuilder[PretenseCargoFlightPlan, FerryLayout]): + def layout(self) -> FerryLayout: + # Find the spawn location for off-map transport planes + distance_to_flot = 0 + heading_from_flot = 0.0 + offmap_transport_cp_id = self.flight.departure.id + for front_line_cp in self.coalition.game.theater.controlpoints: + for front_line in self.coalition.game.theater.conflicts(): + if front_line_cp.captured == self.flight.coalition.player: + if ( + front_line_cp.position.distance_to_point(front_line.position) + > distance_to_flot + ): + distance_to_flot = front_line_cp.position.distance_to_point( + front_line.position + ) + heading_from_flot = front_line.position.heading_between_point( + front_line_cp.position + ) + offmap_transport_cp_id = front_line_cp.id + offmap_transport_cp = self.coalition.game.theater.find_control_point_by_id( + offmap_transport_cp_id + ) + offmap_transport_spawn = offmap_transport_cp.position.point_from_heading( + heading_from_flot, PRETENSE_CARGO_FLIGHT_DISTANCE + ) + + altitude_is_agl = self.flight.unit_type.dcs_unit_type.helicopter + altitude = ( + feet(1500) + if altitude_is_agl + else self.flight.unit_type.preferred_patrol_altitude + ) + + builder = WaypointBuilder(self.flight, self.coalition) + ferry_layout = FerryLayout( + departure=builder.join(offmap_transport_spawn), + nav_to=builder.nav_path( + offmap_transport_spawn, + self.flight.arrival.position, + altitude, + altitude_is_agl, + ), + arrival=builder.land(self.flight.arrival), + divert=builder.divert(self.flight.divert), + bullseye=builder.bullseye(), + nav_from=[], + ) + ferry_layout.departure = builder.join(offmap_transport_spawn) + ferry_layout.nav_to.append(builder.join(offmap_transport_spawn)) + ferry_layout.nav_from.append(builder.join(offmap_transport_spawn)) + print(ferry_layout) + return ferry_layout + + def build(self) -> PretenseCargoFlightPlan: + return PretenseCargoFlightPlan(self.flight, self.layout()) diff --git a/game/ato/flightstate/navigating.py b/game/ato/flightstate/navigating.py index 3932dbf3..39acfb06 100644 --- a/game/ato/flightstate/navigating.py +++ b/game/ato/flightstate/navigating.py @@ -29,11 +29,8 @@ class Navigating(InFlight): events.update_flight_position(self.flight, self.estimate_position()) def progress(self) -> float: - # if next waypoint is very close, assume we reach it immediately to avoid divide - # by zero error - if self.total_time_to_next_waypoint.total_seconds() < 1: - return 1.0 - + if self.total_time_to_next_waypoint.total_seconds() == 0.0: + return 99.9 return ( self.elapsed_time.total_seconds() / self.total_time_to_next_waypoint.total_seconds() diff --git a/game/ato/flighttype.py b/game/ato/flighttype.py index 615aaa5b..b8eeb1c9 100644 --- a/game/ato/flighttype.py +++ b/game/ato/flighttype.py @@ -58,6 +58,9 @@ class FlightType(Enum): FERRY = "Ferry" AIR_ASSAULT = "Air Assault" SEAD_SWEEP = "SEAD Sweep" # Reintroduce legacy "engage-whatever-you-can-find" SEAD + PRETENSE_CARGO = ( + "Cargo Transport" # Flight type for Pretense campaign AI cargo planes + ) def __str__(self) -> str: return self.value @@ -121,5 +124,6 @@ class FlightType(Enum): FlightType.SWEEP: AirEntity.FIGHTER, FlightType.TARCAP: AirEntity.FIGHTER, FlightType.TRANSPORT: AirEntity.UTILITY, + FlightType.PRETENSE_CARGO: AirEntity.UTILITY, FlightType.AIR_ASSAULT: AirEntity.ROTARY_WING, }.get(self, AirEntity.UNSPECIFIED) diff --git a/game/pretense/pretenseaircraftgenerator.py b/game/pretense/pretenseaircraftgenerator.py index 82135e57..60dc0c3a 100644 --- a/game/pretense/pretenseaircraftgenerator.py +++ b/game/pretense/pretenseaircraftgenerator.py @@ -5,20 +5,16 @@ import random from datetime import datetime from functools import cached_property from typing import Any, Dict, List, TYPE_CHECKING, Tuple +from uuid import UUID from dcs import Point -from dcs.action import AITaskPush -from dcs.condition import FlagIsTrue, GroupDead, Or, FlagIsFalse from dcs.country import Country from dcs.mission import Mission -from dcs.terrain.terrain import NoParkingSlotError -from dcs.triggers import TriggerOnce, Event -from dcs.unit import Skill from dcs.unitgroup import FlyingGroup, StaticGroup from game.ato.airtaaskingorder import AirTaskingOrder from game.ato.flight import Flight -from game.ato.flightstate import Completed, WaitingForStart +from game.ato.flightstate import Completed, WaitingForStart, Navigating from game.ato.flighttype import FlightType from game.ato.package import Package from game.ato.starttype import StartType @@ -32,10 +28,9 @@ from game.radio.tacan import TacanRegistry from game.runways import RunwayData from game.settings import Settings from game.theater.controlpoint import ( - Airfield, ControlPoint, - Fob, OffMapSpawn, + ParkingType, ) from game.unitmap import UnitMap from game.missiongenerator.aircraft.aircraftpainter import AircraftPainter @@ -44,7 +39,9 @@ from game.data.weapons import WeaponType if TYPE_CHECKING: from game import Game - from game.squadrons import Squadron + + +PRETENSE_SQUADRON_DEF_RETRIES = 100 class PretenseAircraftGenerator: @@ -108,6 +105,8 @@ class PretenseAircraftGenerator: ato: AirTaskingOrder, dynamic_runways: Dict[str, RunwayData], ) -> None: + from game.squadrons import Squadron + """Adds aircraft to the mission for every flight in the ATO. Aircraft generation is done by walking the ATO and spawning each flight in turn. @@ -125,6 +124,89 @@ class PretenseAircraftGenerator: num_of_strike = 0 num_of_cap = 0 + # Find locations for off-map transport planes + distance_to_flot = 0 + offmap_transport_cp_id = cp.id + parking_type = ParkingType( + fixed_wing=True, fixed_wing_stol=True, rotary_wing=False + ) + for front_line_cp in self.game.theater.controlpoints: + for front_line in self.game.theater.conflicts(): + if front_line_cp.captured == cp.captured: + if ( + front_line_cp.total_aircraft_parking(parking_type) > 0 + and front_line_cp.position.distance_to_point( + front_line.position + ) + > distance_to_flot + ): + distance_to_flot = front_line_cp.position.distance_to_point( + front_line.position + ) + offmap_transport_cp_id = front_line_cp.id + offmap_transport_cp = self.game.theater.find_control_point_by_id( + offmap_transport_cp_id + ) + + # Ensure that the faction has at least one transport helicopter and one cargo plane squadron + autogenerate_transport_helicopter_squadron = True + autogenerate_cargo_plane_squadron = True + for aircraft_type in cp.coalition.air_wing.squadrons: + for squadron in cp.coalition.air_wing.squadrons[aircraft_type]: + mission_types = squadron.auto_assignable_mission_types + if squadron.aircraft.helicopter and ( + FlightType.TRANSPORT in mission_types + or FlightType.AIR_ASSAULT in mission_types + ): + autogenerate_transport_helicopter_squadron = False + elif not squadron.aircraft.helicopter and ( + FlightType.TRANSPORT in mission_types + or FlightType.AIR_ASSAULT in mission_types + ): + autogenerate_cargo_plane_squadron = False + + if autogenerate_transport_helicopter_squadron: + flight_type = FlightType.AIR_ASSAULT + squadron_def = ( + cp.coalition.air_wing.squadron_def_generator.generate_for_task( + flight_type, offmap_transport_cp + ) + ) + squadron = Squadron.create_from( + squadron_def, + flight_type, + 2, + offmap_transport_cp, + cp.coalition, + self.game, + ) + cp.coalition.air_wing.squadrons[squadron.aircraft] = list() + cp.coalition.air_wing.add_squadron(squadron) + if autogenerate_cargo_plane_squadron: + flight_type = FlightType.TRANSPORT + squadron_def = ( + cp.coalition.air_wing.squadron_def_generator.generate_for_task( + flight_type, offmap_transport_cp + ) + ) + for retries in range(PRETENSE_SQUADRON_DEF_RETRIES): + if squadron_def.aircraft.helicopter: + squadron_def = ( + cp.coalition.air_wing.squadron_def_generator.generate_for_task( + flight_type, offmap_transport_cp + ) + ) + squadron = Squadron.create_from( + squadron_def, + flight_type, + 2, + offmap_transport_cp, + cp.coalition, + self.game, + ) + cp.coalition.air_wing.squadrons[squadron.aircraft] = list() + cp.coalition.air_wing.add_squadron(squadron) + for squadron in cp.squadrons: # Intentionally don't spawn anything at OffMapSpawns in Pretense if isinstance(squadron.location, OffMapSpawn): @@ -134,11 +216,16 @@ class PretenseAircraftGenerator: squadron.untasked_aircraft += 1 package = Package(cp, squadron.flight_db, auto_asap=False) mission_types = squadron.auto_assignable_mission_types - if ( + if squadron.aircraft.helicopter and ( FlightType.TRANSPORT in mission_types or FlightType.AIR_ASSAULT in mission_types ): flight_type = FlightType.AIR_ASSAULT + elif not squadron.aircraft.helicopter and ( + FlightType.TRANSPORT in mission_types + or FlightType.AIR_ASSAULT in mission_types + ): + flight_type = FlightType.TRANSPORT elif ( FlightType.SEAD in mission_types or FlightType.SEAD_SWEEP in mission_types @@ -170,13 +257,27 @@ class PretenseAircraftGenerator: num_of_cap += 1 else: flight_type = random.choice(list(mission_types)) - flight = Flight( - package, squadron, 1, flight_type, StartType.COLD, divert=cp - ) - flight.state = WaitingForStart( - flight, self.game.settings, self.game.conditions.start_time - ) - package.add_flight(flight) + + if flight_type == FlightType.TRANSPORT: + flight = Flight( + package, + squadron, + 1, + FlightType.PRETENSE_CARGO, + StartType.IN_FLIGHT, + divert=cp, + ) + package.add_flight(flight) + flight.state = Navigating(flight, self.game.settings, waypoint_index=1) + else: + flight = Flight( + package, squadron, 1, flight_type, StartType.COLD, divert=cp + ) + package.add_flight(flight) + flight.state = WaitingForStart( + flight, self.game.settings, self.game.conditions.start_time + ) + ato.add_package(package) self._reserve_frequencies_and_tacan(ato) From 46e7f89da92a4f6fa09e4aa07bdf5ec32ff41742 Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Sat, 16 Sep 2023 15:02:49 +0300 Subject: [PATCH 029/243] PretenseFlightGroupSpawner method for spawning mid-mission flights and setting the appropriate Pretense names to them. --- game/pretense/pretenseflightgroupspawner.py | 49 ++++++++++++++++++++- 1 file changed, 48 insertions(+), 1 deletion(-) diff --git a/game/pretense/pretenseflightgroupspawner.py b/game/pretense/pretenseflightgroupspawner.py index 839c2b19..ea990924 100644 --- a/game/pretense/pretenseflightgroupspawner.py +++ b/game/pretense/pretenseflightgroupspawner.py @@ -1,4 +1,5 @@ import logging +import random import re from typing import Any, Tuple @@ -13,8 +14,14 @@ from dcs.unitgroup import ( ) from game.ato import Flight +from game.ato.flightstate import InFlight from game.ato.starttype import StartType -from game.missiongenerator.aircraft.flightgroupspawner import FlightGroupSpawner +from game.missiongenerator.aircraft.flightgroupspawner import ( + FlightGroupSpawner, + MINIMUM_MID_MISSION_SPAWN_ALTITUDE_AGL, + MINIMUM_MID_MISSION_SPAWN_ALTITUDE_MSL, + STACK_SEPARATION, +) from game.missiongenerator.missiondata import MissionData from game.naming import NameGenerator from game.theater import Airfield, ControlPoint, Fob, NavalControlPoint, OffMapSpawn @@ -160,3 +167,43 @@ class PretenseFlightGroupSpawner(FlightGroupSpawner): self.flight.start_type = StartType.IN_FLIGHT group = self._generate_over_departure(name, cp) return group + + def generate_mid_mission(self) -> FlyingGroup[Any]: + assert isinstance(self.flight.state, InFlight) + cp = self.flight.departure + name = namegen.next_pretense_aircraft_name(cp, self.flight) + speed = self.flight.state.estimate_speed() + pos = self.flight.state.estimate_position() + pos += Vector2(random.randint(100, 1000), random.randint(100, 1000)) + alt, alt_type = self.flight.state.estimate_altitude() + cp = self.flight.squadron.location.id + + if cp not in self.mission_data.cp_stack: + self.mission_data.cp_stack[cp] = MINIMUM_MID_MISSION_SPAWN_ALTITUDE_AGL + + # We don't know where the ground is, so just make sure that any aircraft + # spawning at an MSL altitude is spawned at some minimum altitude. + # https://github.com/dcs-liberation/dcs_liberation/issues/1941 + if alt_type == "BARO" and alt < MINIMUM_MID_MISSION_SPAWN_ALTITUDE_MSL: + alt = MINIMUM_MID_MISSION_SPAWN_ALTITUDE_MSL + + # Set a minimum AGL value for 'alt' if needed, + # otherwise planes might crash in trees and stuff. + if alt_type == "RADIO" and alt < self.mission_data.cp_stack[cp]: + alt = self.mission_data.cp_stack[cp] + self.mission_data.cp_stack[cp] += STACK_SEPARATION + + group = self.mission.flight_group( + country=self.country, + name=name, + aircraft_type=self.flight.unit_type.dcs_unit_type, + airport=None, + position=pos, + altitude=alt.meters, + speed=speed.kph, + maintask=None, + group_size=self.flight.count, + ) + + group.points[0].alt_type = alt_type + return group From f4a239aaad56f472ab7c68aa20399f65041bfc1e Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Sat, 16 Sep 2023 15:04:03 +0300 Subject: [PATCH 030/243] Split the init_body.lua script in two parts to facilitate dynamic JTAC spawning. --- resources/plugins/pretense/init_body_1.lua | 78 ---------------------- resources/plugins/pretense/init_body_2.lua | 35 ---------- 2 files changed, 113 deletions(-) diff --git a/resources/plugins/pretense/init_body_1.lua b/resources/plugins/pretense/init_body_1.lua index 429b8c01..3f5068a8 100644 --- a/resources/plugins/pretense/init_body_1.lua +++ b/resources/plugins/pretense/init_body_1.lua @@ -30,81 +30,3 @@ pl:registerSquadGroup(PlayerLogistics.infantryTypes.engineer, 'engineer-squad', pl:registerSquadGroup(PlayerLogistics.infantryTypes.manpads, 'manpads-squad', 900, 500, 60*20, 60*30, 5) pl:registerSquadGroup(PlayerLogistics.infantryTypes.spy, 'spy-squad', 100, 300, 60*10, 60*30, 1) pl:registerSquadGroup(PlayerLogistics.infantryTypes.rapier, 'rapier-squad', 1200,2000,60*60, 60*30, 8) - -Group.getByName('jtacDrone'):destroy() -CommandFunctions.jtac = JTAC:new({name = 'jtacDrone'}) - -pm = PersistenceManager:new(savefile, gm, st, ct, pl) -pm:load() - -if pm:canRestore() then - pm:restoreZones() - pm:restoreAIMissions() - pm:restoreBattlefield() - pm:restoreCsar() - pm:restoreSquads() -else - --initial states - Starter.start(zones) -end - -timer.scheduleFunction(function(param, time) - pm:save() - env.info("Mission state saved") - return time+60 -end, zones, timer.getTime()+60) - - ---make sure support units are present where needed -ensureSpawn = { - ['golf-farp-suport'] = zones.golf, - ['november-farp-suport'] = zones.november, - ['tango-farp-suport'] = zones.tango, - ['sierra-farp-suport'] = zones.sierra, - ['cherkessk-farp-suport'] = zones.cherkessk, - ['unal-farp-suport'] = zones.unal, - ['tyrnyauz-farp-suport'] = zones.tyrnyauz -} - -for grname, zn in pairs(ensureSpawn) do - local g = Group.getByName(grname) - if g then g:destroy() end -end - -timer.scheduleFunction(function(param, time) - - for grname, zn in pairs(ensureSpawn) do - local g = Group.getByName(grname) - if zn.side == 2 then - if not g then - local err, msg = pcall(mist.respawnGroup,grname,true) - if not err then - env.info("ERROR spawning "..grname) - env.info(msg) - end - end - else - if g then g:destroy() end - end - end - - return time+30 -end, {}, timer.getTime()+30) - - ---supply injection -local blueSupply = {'offmap-supply-blue-1','offmap-supply-blue-2','offmap-supply-blue-3','offmap-supply-blue-4','offmap-supply-blue-5'} -local redSupply = {'offmap-supply-red-1','offmap-supply-red-2','offmap-supply-red-3','offmap-supply-red-4','offmap-supply-red-5'} -local offmapZones = { - zones.batumi, - zones.sochi, - zones.nalchik, - zones.beslan, - zones.mozdok, - zones.mineralnye, --- zones.senaki, --- zones.sukhumi, --- zones.gudauta, --- zones.kobuleti, -} - diff --git a/resources/plugins/pretense/init_body_2.lua b/resources/plugins/pretense/init_body_2.lua index 429b8c01..9d2798d8 100644 --- a/resources/plugins/pretense/init_body_2.lua +++ b/resources/plugins/pretense/init_body_2.lua @@ -1,39 +1,4 @@ -end - -ZoneCommand.setNeighbours(cm) - -bm = BattlefieldManager:new() - -mc = MarkerCommands:new() - -pt = PlayerTracker:new(mc) - -mt = MissionTracker:new(pt, mc) - -st = SquadTracker:new() - -ct = CSARTracker:new() - -pl = PlayerLogistics:new(mt, pt, st, ct) - -gci = GCI:new(2) - -gm = GroupMonitor:new(cm) -ZoneCommand.groupMonitor = gm - --- PlayerLogistics:registerSquadGroup(squadType, groupname, weight,cost,jobtime,extracttime, squadSize) -pl:registerSquadGroup(PlayerLogistics.infantryTypes.capture, 'capture-squad', 700, 200, 60, 60*30, 4) -pl:registerSquadGroup(PlayerLogistics.infantryTypes.sabotage, 'sabotage-squad', 800, 500, 60*5, 60*30, 4) -pl:registerSquadGroup(PlayerLogistics.infantryTypes.ambush, 'ambush-squad', 900, 300, 60*20, 60*30, 5) -pl:registerSquadGroup(PlayerLogistics.infantryTypes.engineer, 'engineer-squad', 200, 1000,60, 60*30, 2) -pl:registerSquadGroup(PlayerLogistics.infantryTypes.manpads, 'manpads-squad', 900, 500, 60*20, 60*30, 5) -pl:registerSquadGroup(PlayerLogistics.infantryTypes.spy, 'spy-squad', 100, 300, 60*10, 60*30, 1) -pl:registerSquadGroup(PlayerLogistics.infantryTypes.rapier, 'rapier-squad', 1200,2000,60*60, 60*30, 8) - -Group.getByName('jtacDrone'):destroy() -CommandFunctions.jtac = JTAC:new({name = 'jtacDrone'}) - pm = PersistenceManager:new(savefile, gm, st, ct, pl) pm:load() From 5621649f107a3b8733b9c9ab60a37edb97cad281 Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Sat, 16 Sep 2023 15:06:14 +0300 Subject: [PATCH 031/243] Also connect carrier and LHA control points to adjacent friendly points in Pretense. Enlarged the carrier trigger zones. --- game/pretense/pretenseluagenerator.py | 17 ++++++++++---- game/pretense/pretensetriggergenerator.py | 7 +++++- game/theater/conflicttheater.py | 28 +++++++++++++++++++++++ 3 files changed, 47 insertions(+), 5 deletions(-) diff --git a/game/pretense/pretenseluagenerator.py b/game/pretense/pretenseluagenerator.py index 414b0a74..99500892 100644 --- a/game/pretense/pretenseluagenerator.py +++ b/game/pretense/pretenseluagenerator.py @@ -93,9 +93,9 @@ class PretenseLuaGenerator(LuaGenerator): max_resource = 30000 if isinstance(cp, Airfield) or cp.has_ground_spawns: lua_string_zones += f"zones.{cp_name_trimmed}.isPlaneSpawn = true\n" - if cp.has_ground_spawns: + if cp.has_ground_spawns or cp.is_lha: max_resource = 40000 - if isinstance(cp, Airfield): + if isinstance(cp, Airfield) or cp.is_carrier: max_resource = 50000 lua_string_zones += ( f"zones.{cp_name_trimmed}.maxResource = {max_resource}\n" @@ -114,7 +114,7 @@ class PretenseLuaGenerator(LuaGenerator): lua_string_zones += " }),\n" lua_string_zones += " presets.upgrades.basic.comPost:extend({\n" lua_string_zones += f" name = '{cp_name_trimmed}-com-red',\n" - lua_string_zones += " products = {" + lua_string_zones += " products = {\n" lua_string_zones += ( " presets.special.red.infantry:extend({ name='" + cp_name_trimmed @@ -307,11 +307,20 @@ class PretenseLuaGenerator(LuaGenerator): lua_string_connman = " cm = ConnectionManager:new()" + # Generate ConnectionManager connections for cp in self.game.theater.controlpoints: for other_cp in cp.connected_points: lua_string_connman += ( - f" cm: addConnection('{cp.name}', '{other_cp.name}')" + f" cm: addConnection('{cp.name}', '{other_cp.name}')\n" ) + # Also connect carrier and LHA control points to adjacent friendly points + if cp.is_fleet and len(cp.connected_points) == 0: + for other_cp in self.game.theater.closest_friendly_control_points_to( + cp + ): + lua_string_connman += ( + f" cm: addConnection('{cp.name}', '{other_cp.name}')\n" + ) init_body_1_file = open("./resources/plugins/pretense/init_body_1.lua", "r") init_body_1 = init_body_1_file.read() diff --git a/game/pretense/pretensetriggergenerator.py b/game/pretense/pretensetriggergenerator.py index 352654b9..604abdfc 100644 --- a/game/pretense/pretensetriggergenerator.py +++ b/game/pretense/pretensetriggergenerator.py @@ -53,6 +53,7 @@ TRIGGER_RADIUS_CLEAR_SCENERY = 1000 TRIGGER_RADIUS_PRETENSE_TGO = 500 TRIGGER_RADIUS_PRETENSE_SUPPLY = 500 TRIGGER_RADIUS_PRETENSE_HELI = 1000 +TRIGGER_RADIUS_PRETENSE_CARRIER = 50000 class Silence(Option): @@ -157,12 +158,16 @@ class PretenseTriggerGenerator: Directly appends to the global `base_capture_events` var declared by `dcs_libaration.lua` """ for cp in self.game.theater.controlpoints: + if cp.is_fleet: + trigger_radius = TRIGGER_RADIUS_PRETENSE_CARRIER + else: + 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( cp.position, - radius=TRIGGER_RADIUS_CAPTURE, + radius=trigger_radius, hidden=False, name=cp.name, color=zone_color, diff --git a/game/theater/conflicttheater.py b/game/theater/conflicttheater.py index 7575daed..2ac97da3 100644 --- a/game/theater/conflicttheater.py +++ b/game/theater/conflicttheater.py @@ -195,6 +195,34 @@ class ConflictTheater: assert closest_red is not None return closest_blue, closest_red + def closest_friendly_control_points_to( + self, cp: ControlPoint + ) -> Tuple[ControlPoint, ControlPoint]: + """ + Returns a tuple of the two nearest friendly ControlPoints in theater to ControlPoint cp. + (closest_cp, second_closest_cp) + """ + seen = set() + min_distance = math.inf + closest_cp = None + second_closest_cp = None + if cp.captured: + control_points = self.player_points() + else: + control_points = self.enemy_points() + for other_cp in control_points: + if cp == other_cp: + continue + dist = other_cp.position.distance_to_point(cp.position) + if dist < min_distance: + second_closest_cp = closest_cp + closest_cp = other_cp + min_distance = dist + + assert closest_cp is not None + assert second_closest_cp is not None + return closest_cp, second_closest_cp + def find_control_point_by_id(self, cp_id: UUID) -> ControlPoint: for i in self.controlpoints: if i.id == cp_id: From 34fae511895e637a821eedb048eacb6c64a59b3a Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Sat, 16 Sep 2023 16:30:49 +0300 Subject: [PATCH 032/243] Fixed Pretense ground unit group id/name handling. --- game/pretense/pretensetgogenerator.py | 31 ++++++++++++++------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/game/pretense/pretensetgogenerator.py b/game/pretense/pretensetgogenerator.py index 00e0477b..639e74e5 100644 --- a/game/pretense/pretensetgogenerator.py +++ b/game/pretense/pretensetgogenerator.py @@ -145,8 +145,8 @@ class PretenseGroundObjectGenerator(GroundObjectGenerator): if self.ground_object.coalition.faction.has_access_to_unit_class(unit_class): unit_type = self.ground_unit_of_class(unit_class) if unit_type is not None and len(vehicle_units) < max_num: - group_id = self.game.next_group_id() - group_name = f"{cp_name}-{group_role}-{group_id}" + unit_id = self.game.next_unit_id() + unit_name = f"{cp_name}-{group_role}-{unit_id}" spread_out_heading = random.randrange(1, 360) spread_out_position = group.position.point_from_heading( @@ -157,8 +157,8 @@ class PretenseGroundObjectGenerator(GroundObjectGenerator): ) theater_unit = TheaterUnit( - group_id, - group_name, + unit_id, + unit_name, unit_type.dcs_unit_type, ground_unit_pos, group.ground_object, @@ -177,7 +177,6 @@ class PretenseGroundObjectGenerator(GroundObjectGenerator): self.game.pretense_ground_supply[side][cp_name_trimmed] = list() if cp_name_trimmed not in self.game.pretense_ground_assault[cp_side]: self.game.pretense_ground_assault[side][cp_name_trimmed] = list() - print(self.game.pretense_ground_supply[cp_side][cp_name_trimmed]) for group in self.ground_object.groups: vehicle_units: list[TheaterUnit] = [] ship_units: list[TheaterUnit] = [] @@ -185,13 +184,9 @@ class PretenseGroundObjectGenerator(GroundObjectGenerator): for unit in group.units: if unit.is_static: # Add supply convoy - group_id = self.game.next_group_id() group_role = "supply" - group_name = f"{cp_name_trimmed}-{group_role}-{group_id}" + group_name = f"{cp_name_trimmed}-{group_role}-{group.id}" group.name = group_name - self.game.pretense_ground_supply[cp_side][cp_name_trimmed].append( - group_name - ) self.generate_ground_unit_of_class( UnitClass.LOGISTICS, @@ -203,13 +198,9 @@ class PretenseGroundObjectGenerator(GroundObjectGenerator): ) elif unit.is_vehicle and unit.alive: # Add armor group - group_id = self.game.next_group_id() group_role = "assault" - group_name = f"{cp_name_trimmed}-{group_role}-{group_id}" + group_name = f"{cp_name_trimmed}-{group_role}-{group.id}" group.name = group_name - self.game.pretense_ground_supply[cp_side][cp_name_trimmed].append( - group_name - ) self.generate_ground_unit_of_class( UnitClass.TANK, @@ -280,6 +271,12 @@ class PretenseGroundObjectGenerator(GroundObjectGenerator): self, group_name: str, units: list[TheaterUnit] ) -> VehicleGroup: vehicle_group: Optional[VehicleGroup] = None + + cp_name_trimmed = "".join( + [i for i in self.ground_object.control_point.name.lower() if i.isalnum()] + ) + cp_side = 2 if self.ground_object.control_point.captured else 1 + for unit in units: assert issubclass(unit.type, VehicleType) faction = unit.ground_object.control_point.coalition.faction @@ -296,6 +293,10 @@ class PretenseGroundObjectGenerator(GroundObjectGenerator): vehicle_group.units[0].name = unit.unit_name self.set_alarm_state(vehicle_group) GroundForcePainter(faction, vehicle_group.units[0]).apply_livery() + + self.game.pretense_ground_supply[cp_side][cp_name_trimmed].append( + f"{vehicle_group.name}" + ) else: vehicle_unit = self.m.vehicle(unit.unit_name, unit.type) vehicle_unit.player_can_drive = True From ddc5709a174b60ccd75c2e7fdaddcdcf9c9263a8 Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Sat, 16 Sep 2023 17:20:38 +0300 Subject: [PATCH 033/243] Add CJTF factions to the coalitions in Pretense, if they're not being used in the Retribution campaign. --- game/pretense/pretensemissiongenerator.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/game/pretense/pretensemissiongenerator.py b/game/pretense/pretensemissiongenerator.py index 79f18fd4..ee626082 100644 --- a/game/pretense/pretensemissiongenerator.py +++ b/game/pretense/pretensemissiongenerator.py @@ -148,10 +148,10 @@ class PretenseMissionGenerator(MissionGenerator): self.mission.coalition["red"].add_country(self.e_country) # Add CJTF factions to the coalitions, if they're not being used in the campaign - # if CombinedJointTaskForcesBlue not in {self.p_country, self.e_country}: - # self.mission.coalition["blue"].add_country(CombinedJointTaskForcesBlue()) - # if CombinedJointTaskForcesRed not in {self.p_country, self.e_country}: - # self.mission.coalition["red"].add_country(CombinedJointTaskForcesRed()) + if CombinedJointTaskForcesBlue not in {self.p_country, self.e_country}: + self.mission.coalition["blue"].add_country(CombinedJointTaskForcesBlue()) + if CombinedJointTaskForcesRed not in {self.p_country, self.e_country}: + self.mission.coalition["red"].add_country(CombinedJointTaskForcesRed()) belligerents = {self.p_country.id, self.e_country.id} for country_id in country_dict.keys(): From a975e2c2dccec83fc773f59731823f46e5ffcad6 Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Sat, 16 Sep 2023 17:21:28 +0300 Subject: [PATCH 034/243] Now connects more isolated zones in Pretense. --- game/pretense/pretenseluagenerator.py | 32 +++++++++++++++++++++------ game/theater/conflicttheater.py | 19 +++++++++++----- 2 files changed, 39 insertions(+), 12 deletions(-) diff --git a/game/pretense/pretenseluagenerator.py b/game/pretense/pretenseluagenerator.py index 99500892..fa71e08b 100644 --- a/game/pretense/pretenseluagenerator.py +++ b/game/pretense/pretenseluagenerator.py @@ -305,7 +305,7 @@ class PretenseLuaGenerator(LuaGenerator): lua_string_zones += " }\n" lua_string_zones += "})\n" - lua_string_connman = " cm = ConnectionManager:new()" + lua_string_connman = " cm = ConnectionManager:new()\n" # Generate ConnectionManager connections for cp in self.game.theater.controlpoints: @@ -313,14 +313,32 @@ class PretenseLuaGenerator(LuaGenerator): lua_string_connman += ( f" cm: addConnection('{cp.name}', '{other_cp.name}')\n" ) - # Also connect carrier and LHA control points to adjacent friendly points - if cp.is_fleet and len(cp.connected_points) == 0: - for other_cp in self.game.theater.closest_friendly_control_points_to( - cp - ): + for sea_connection in cp.shipping_lanes: + if sea_connection.is_friendly_to(cp): lua_string_connman += ( - f" cm: addConnection('{cp.name}', '{other_cp.name}')\n" + f" cm: addConnection('{cp.name}', '{sea_connection.name}')\n" ) + if len(cp.connected_points) == 0 and len(cp.shipping_lanes) == 0: + # Also connect carrier and LHA control points to adjacent friendly points + if cp.is_fleet: + for ( + other_cp + ) in self.game.theater.closest_friendly_control_points_to(cp): + lua_string_connman += ( + f" cm: addConnection('{cp.name}', '{other_cp.name}')\n" + ) + else: + # Finally, connect remaining non-connected points + ( + closest_cp, + second_closest_cp, + ) = self.game.theater.closest_friendly_control_points_to(cp) + lua_string_connman += ( + f" cm: addConnection('{cp.name}', '{closest_cp.name}')\n" + ) + lua_string_connman += ( + f" cm: addConnection('{cp.name}', '{second_closest_cp.name}')\n" + ) init_body_1_file = open("./resources/plugins/pretense/init_body_1.lua", "r") init_body_1 = init_body_1_file.read() diff --git a/game/theater/conflicttheater.py b/game/theater/conflicttheater.py index 2ac97da3..03ae071f 100644 --- a/game/theater/conflicttheater.py +++ b/game/theater/conflicttheater.py @@ -202,10 +202,9 @@ class ConflictTheater: Returns a tuple of the two nearest friendly ControlPoints in theater to ControlPoint cp. (closest_cp, second_closest_cp) """ - seen = set() - min_distance = math.inf closest_cp = None second_closest_cp = None + distances_to_cp = dict() if cp.captured: control_points = self.player_points() else: @@ -213,11 +212,21 @@ class ConflictTheater: for other_cp in control_points: if cp == other_cp: continue + print(f"{cp}: {other_cp} being evaluated...") + dist = other_cp.position.distance_to_point(cp.position) - if dist < min_distance: - second_closest_cp = closest_cp + print(f" {other_cp} is at {dist} meters") + distances_to_cp[dist] = other_cp + for i in sorted(distances_to_cp.keys()): + other_cp = distances_to_cp[i] + print(f" {other_cp} is at {i} meters") + if closest_cp is None: closest_cp = other_cp - min_distance = dist + continue + elif second_closest_cp is None: + second_closest_cp = other_cp + break + break assert closest_cp is not None assert second_closest_cp is not None From a787cd4f70ac68846aada9d2bbef9fb5029e4f33 Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Sat, 16 Sep 2023 20:12:00 +0300 Subject: [PATCH 035/243] Disabled the base Retribution plugin in pretenseluagenerator.py and disabled adding zones for OffMapSpawns. --- game/pretense/pretenseluagenerator.py | 7 +++++-- game/theater/conflicttheater.py | 3 --- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/game/pretense/pretenseluagenerator.py b/game/pretense/pretenseluagenerator.py index fa71e08b..776d2b79 100644 --- a/game/pretense/pretenseluagenerator.py +++ b/game/pretense/pretenseluagenerator.py @@ -15,7 +15,7 @@ from game.ato import FlightType from game.dcs.aircrafttype import AircraftType from game.missiongenerator.luagenerator import LuaGenerator from game.plugins import LuaPluginManager -from game.theater import TheaterGroundObject, Airfield +from game.theater import TheaterGroundObject, Airfield, OffMapSpawn from game.theater.iadsnetwork.iadsrole import IadsRole from game.utils import escape_string_for_lua from game.missiongenerator.missiondata import MissionData @@ -68,6 +68,9 @@ class PretenseLuaGenerator(LuaGenerator): lua_string_zones = "" for cp in self.game.theater.controlpoints: + if isinstance(cp, OffMapSpawn): + continue + cp_name_trimmed = "".join([i for i in cp.name.lower() if i.isalnum()]) cp_side = 2 if cp.captured else 1 for side in range(1, 3): @@ -401,7 +404,7 @@ class PretenseLuaGenerator(LuaGenerator): def inject_plugins(self) -> None: for plugin in LuaPluginManager.plugins(): - if plugin.enabled: + if plugin.enabled and plugin.identifier not in ("base"): plugin.inject_scripts(self) plugin.inject_configuration(self) diff --git a/game/theater/conflicttheater.py b/game/theater/conflicttheater.py index 03ae071f..84c89b14 100644 --- a/game/theater/conflicttheater.py +++ b/game/theater/conflicttheater.py @@ -212,14 +212,11 @@ class ConflictTheater: for other_cp in control_points: if cp == other_cp: continue - print(f"{cp}: {other_cp} being evaluated...") dist = other_cp.position.distance_to_point(cp.position) - print(f" {other_cp} is at {dist} meters") distances_to_cp[dist] = other_cp for i in sorted(distances_to_cp.keys()): other_cp = distances_to_cp[i] - print(f" {other_cp} is at {i} meters") if closest_cp is None: closest_cp = other_cp continue From 2e889325e1bcbdcc050658a21b21e140cead3122 Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Sat, 16 Sep 2023 20:14:50 +0300 Subject: [PATCH 036/243] Calling the constructor of MissionGenerator in PretenseMissionGenerator in unnecessary, so leaving it out. --- game/pretense/pretensemissiongenerator.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/game/pretense/pretensemissiongenerator.py b/game/pretense/pretensemissiongenerator.py index ee626082..52e7349c 100644 --- a/game/pretense/pretensemissiongenerator.py +++ b/game/pretense/pretensemissiongenerator.py @@ -55,11 +55,6 @@ if TYPE_CHECKING: class PretenseMissionGenerator(MissionGenerator): def __init__(self, game: Game, time: datetime) -> None: - super().__init__( - game, - time, - ) - self.game = game self.time = time self.mission = Mission(game.theater.terrain) From f4c5c96fba377d7a7ab143f8cf6d2362c44c9fc1 Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Sat, 16 Sep 2023 20:57:11 +0300 Subject: [PATCH 037/243] Fixed a type assignment in pretensecargo.py --- game/ato/flightplans/pretensecargo.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/game/ato/flightplans/pretensecargo.py b/game/ato/flightplans/pretensecargo.py index f980b5f7..f5d86170 100644 --- a/game/ato/flightplans/pretensecargo.py +++ b/game/ato/flightplans/pretensecargo.py @@ -44,7 +44,7 @@ class PretenseCargoFlightPlan(StandardFlightPlan[FerryLayout]): class Builder(IBuilder[PretenseCargoFlightPlan, FerryLayout]): def layout(self) -> FerryLayout: # Find the spawn location for off-map transport planes - distance_to_flot = 0 + distance_to_flot = 0.0 heading_from_flot = 0.0 offmap_transport_cp_id = self.flight.departure.id for front_line_cp in self.coalition.game.theater.controlpoints: From 19984636e3a4997353f43aca7a502c8122eea684 Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Sat, 16 Sep 2023 21:08:06 +0300 Subject: [PATCH 038/243] Converted closest_friendly_control_points_to from returning a tuple of the two closest control points to returning a list of all in sorted order. --- game/pretense/pretenseluagenerator.py | 19 +++++++++++++------ game/theater/conflicttheater.py | 21 +++++---------------- 2 files changed, 18 insertions(+), 22 deletions(-) diff --git a/game/pretense/pretenseluagenerator.py b/game/pretense/pretenseluagenerator.py index 776d2b79..cb80b397 100644 --- a/game/pretense/pretenseluagenerator.py +++ b/game/pretense/pretenseluagenerator.py @@ -23,6 +23,8 @@ from game.missiongenerator.missiondata import MissionData if TYPE_CHECKING: from game import Game +PRETENSE_NUMBER_OF_ZONES_TO_CONNECT_CARRIERS_TO = 2 + class PretenseLuaGenerator(LuaGenerator): def __init__( @@ -324,23 +326,28 @@ class PretenseLuaGenerator(LuaGenerator): if len(cp.connected_points) == 0 and len(cp.shipping_lanes) == 0: # Also connect carrier and LHA control points to adjacent friendly points if cp.is_fleet: + num_of_carrier_connections = 0 for ( other_cp ) in self.game.theater.closest_friendly_control_points_to(cp): + num_of_carrier_connections += 1 + if ( + num_of_carrier_connections + > PRETENSE_NUMBER_OF_ZONES_TO_CONNECT_CARRIERS_TO + ): + break + lua_string_connman += ( f" cm: addConnection('{cp.name}', '{other_cp.name}')\n" ) else: # Finally, connect remaining non-connected points - ( - closest_cp, - second_closest_cp, - ) = self.game.theater.closest_friendly_control_points_to(cp) + closest_cps = self.game.theater.closest_friendly_control_points_to(cp) lua_string_connman += ( - f" cm: addConnection('{cp.name}', '{closest_cp.name}')\n" + f" cm: addConnection('{cp.name}', '{closest_cps[0].name}')\n" ) lua_string_connman += ( - f" cm: addConnection('{cp.name}', '{second_closest_cp.name}')\n" + f" cm: addConnection('{cp.name}', '{closest_cps[1].name}')\n" ) init_body_1_file = open("./resources/plugins/pretense/init_body_1.lua", "r") diff --git a/game/theater/conflicttheater.py b/game/theater/conflicttheater.py index 84c89b14..1649b6ea 100644 --- a/game/theater/conflicttheater.py +++ b/game/theater/conflicttheater.py @@ -197,13 +197,11 @@ class ConflictTheater: def closest_friendly_control_points_to( self, cp: ControlPoint - ) -> Tuple[ControlPoint, ControlPoint]: + ) -> List[ControlPoint]: """ - Returns a tuple of the two nearest friendly ControlPoints in theater to ControlPoint cp. - (closest_cp, second_closest_cp) + Returns a list of the friendly ControlPoints in theater to ControlPoint cp, sorted closest to farthest. """ - closest_cp = None - second_closest_cp = None + closest_cps = list() distances_to_cp = dict() if cp.captured: control_points = self.player_points() @@ -216,18 +214,9 @@ class ConflictTheater: dist = other_cp.position.distance_to_point(cp.position) distances_to_cp[dist] = other_cp for i in sorted(distances_to_cp.keys()): - other_cp = distances_to_cp[i] - if closest_cp is None: - closest_cp = other_cp - continue - elif second_closest_cp is None: - second_closest_cp = other_cp - break - break + closest_cps.append(distances_to_cp[i]) - assert closest_cp is not None - assert second_closest_cp is not None - return closest_cp, second_closest_cp + return closest_cps def find_control_point_by_id(self, cp_id: UUID) -> ControlPoint: for i in self.controlpoints: From 3809e28a6aa08013690628a0f3289c776f7f8c45 Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Sat, 16 Sep 2023 21:11:43 +0300 Subject: [PATCH 039/243] Fixed conflicting types for cp in generate_mid_mission(). --- game/pretense/pretenseflightgroupspawner.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/game/pretense/pretenseflightgroupspawner.py b/game/pretense/pretenseflightgroupspawner.py index ea990924..d3b20b7e 100644 --- a/game/pretense/pretenseflightgroupspawner.py +++ b/game/pretense/pretenseflightgroupspawner.py @@ -170,8 +170,7 @@ class PretenseFlightGroupSpawner(FlightGroupSpawner): def generate_mid_mission(self) -> FlyingGroup[Any]: assert isinstance(self.flight.state, InFlight) - cp = self.flight.departure - name = namegen.next_pretense_aircraft_name(cp, self.flight) + name = namegen.next_pretense_aircraft_name(self.flight.departure, self.flight) speed = self.flight.state.estimate_speed() pos = self.flight.state.estimate_position() pos += Vector2(random.randint(100, 1000), random.randint(100, 1000)) From 8aea905640fda070256707ea54c7db9a27711a25 Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Sat, 16 Sep 2023 21:11:55 +0300 Subject: [PATCH 040/243] Cleaned up pretense/pretenseaircraftgenerator.py Moved a lot of logic into proper methods and added comments for all of those methods. --- game/pretense/pretenseaircraftgenerator.py | 200 +++++++++++++-------- 1 file changed, 129 insertions(+), 71 deletions(-) diff --git a/game/pretense/pretenseaircraftgenerator.py b/game/pretense/pretenseaircraftgenerator.py index 60dc0c3a..1d208128 100644 --- a/game/pretense/pretenseaircraftgenerator.py +++ b/game/pretense/pretenseaircraftgenerator.py @@ -98,34 +98,11 @@ class PretenseAircraftGenerator: for parking_slot in cp.parking_slots: parking_slot.unit_id = None - def generate_flights( - self, - country: Country, - cp: ControlPoint, - ato: AirTaskingOrder, - dynamic_runways: Dict[str, RunwayData], - ) -> None: - from game.squadrons import Squadron - - """Adds aircraft to the mission for every flight in the ATO. - - Aircraft generation is done by walking the ATO and spawning each flight in turn. - After the flight is generated the group is added to the UnitMap so aircraft - deaths can be tracked. - - Args: - country: The country from the mission to use for this ATO. - ato: The ATO to spawn aircraft for. - dynamic_runways: Runway data for carriers and FARPs. + def find_pretense_cargo_plane_cp(self, cp: ControlPoint) -> ControlPoint: """ - - num_of_sead = 0 - num_of_cas = 0 - num_of_strike = 0 - num_of_cap = 0 - - # Find locations for off-map transport planes - distance_to_flot = 0 + Finds the location (ControlPoint) for Pretense off-map transport planes. + """ + distance_to_flot = 0.0 offmap_transport_cp_id = cp.id parking_type = ParkingType( fixed_wing=True, fixed_wing_stol=True, rotary_wing=False @@ -144,11 +121,19 @@ class PretenseAircraftGenerator: front_line.position ) offmap_transport_cp_id = front_line_cp.id - offmap_transport_cp = self.game.theater.find_control_point_by_id( - offmap_transport_cp_id - ) + return self.game.theater.find_control_point_by_id(offmap_transport_cp_id) - # Ensure that the faction has at least one transport helicopter and one cargo plane squadron + def should_generate_pretense_transports( + self, cp: ControlPoint + ) -> tuple[bool, bool]: + """ + Returns a tuple of booleans, telling whether transport helicopter and aircraft + squadrons should be generated from the faction squadron definitions. + + This helps to ensure that the faction has at least one transport helicopter and one cargo plane squadron. + + (autogenerate_transport_helicopter_squadron, autogenerate_cargo_plane_squadron) + """ autogenerate_transport_helicopter_squadron = True autogenerate_cargo_plane_squadron = True for aircraft_type in cp.coalition.air_wing.squadrons: @@ -164,48 +149,80 @@ class PretenseAircraftGenerator: or FlightType.AIR_ASSAULT in mission_types ): autogenerate_cargo_plane_squadron = False + return ( + autogenerate_transport_helicopter_squadron, + autogenerate_cargo_plane_squadron, + ) - if autogenerate_transport_helicopter_squadron: - flight_type = FlightType.AIR_ASSAULT - squadron_def = ( - cp.coalition.air_wing.squadron_def_generator.generate_for_task( - flight_type, offmap_transport_cp - ) - ) - squadron = Squadron.create_from( - squadron_def, - flight_type, - 2, - offmap_transport_cp, - cp.coalition, - self.game, - ) - cp.coalition.air_wing.squadrons[squadron.aircraft] = list() - cp.coalition.air_wing.add_squadron(squadron) - if autogenerate_cargo_plane_squadron: - flight_type = FlightType.TRANSPORT - squadron_def = ( - cp.coalition.air_wing.squadron_def_generator.generate_for_task( - flight_type, offmap_transport_cp - ) - ) - for retries in range(PRETENSE_SQUADRON_DEF_RETRIES): - if squadron_def.aircraft.helicopter: - squadron_def = ( - cp.coalition.air_wing.squadron_def_generator.generate_for_task( - flight_type, offmap_transport_cp - ) + def generate_pretense_transport_squadron( + self, + cp: ControlPoint, + flight_type: FlightType, + fixed_wing: bool, + num_retries: int, + ) -> None: + from game.squadrons import Squadron + + """ + Generates a Pretense transport squadron from the faction squadron definitions. Use FlightType AIR_ASSAULT + for Pretense supply helicopters and TRANSPORT for off-map cargo plane squadrons. + + Retribution does not differentiate between fixed wing and rotary wing transport squadron definitions, which + is why there is a retry mechanism in case the wrong type is returned. Use fixed_wing False + for Pretense supply helicopters and fixed_wing True for off-map cargo plane squadrons. + + TODO: Find out if Pretense can handle rotary wing "cargo planes". + """ + + squadron_def = cp.coalition.air_wing.squadron_def_generator.generate_for_task( + flight_type, cp + ) + print( + f"Generating a squadron definition for fixed-wing {fixed_wing} squadron at {cp}" + ) + for retries in range(num_retries): + if squadron_def is None or fixed_wing != squadron_def.aircraft.helicopter: + squadron_def = ( + cp.coalition.air_wing.squadron_def_generator.generate_for_task( + flight_type, cp ) - squadron = Squadron.create_from( - squadron_def, - flight_type, - 2, - offmap_transport_cp, - cp.coalition, - self.game, - ) - cp.coalition.air_wing.squadrons[squadron.aircraft] = list() - cp.coalition.air_wing.add_squadron(squadron) + ) + + # Failed, stop here + if squadron_def is None: + return + + squadron = Squadron.create_from( + squadron_def, + flight_type, + 2, + cp, + cp.coalition, + self.game, + ) + cp.coalition.air_wing.squadrons[squadron.aircraft] = list() + cp.coalition.air_wing.add_squadron(squadron) + return + + def generate_pretense_aircraft( + self, cp: ControlPoint, ato: AirTaskingOrder + ) -> None: + """ + Plans and generates AI aircraft groups/packages for Pretense. + + Aircraft generation is done by walking the control points which will be made into + Pretense "zones" and spawning flights for different missions. + After the flight is generated the package is added to the ATO so the flights + can be configured. + + Args: + cp: Control point to generate aircraft for. + ato: The ATO to generate aircraft for. + """ + num_of_sead = 0 + num_of_cas = 0 + num_of_strike = 0 + num_of_cap = 0 for squadron in cp.squadrons: # Intentionally don't spawn anything at OffMapSpawns in Pretense @@ -279,6 +296,47 @@ class PretenseAircraftGenerator: ) ato.add_package(package) + return + + def generate_flights( + self, + country: Country, + cp: ControlPoint, + ato: AirTaskingOrder, + dynamic_runways: Dict[str, RunwayData], + ) -> None: + """Adds aircraft to the mission for every flight in the ATO. + + Args: + country: The country from the mission to use for this ATO. + cp: Control point to generate aircraft for. + ato: The ATO to generate aircraft for. + dynamic_runways: Runway data for carriers and FARPs. + """ + + offmap_transport_cp = self.find_pretense_cargo_plane_cp(cp) + + ( + autogenerate_transport_helicopter_squadron, + autogenerate_cargo_plane_squadron, + ) = self.should_generate_pretense_transports(cp) + + if autogenerate_transport_helicopter_squadron: + self.generate_pretense_transport_squadron( + offmap_transport_cp, + FlightType.AIR_ASSAULT, + False, + PRETENSE_SQUADRON_DEF_RETRIES, + ) + if autogenerate_cargo_plane_squadron: + self.generate_pretense_transport_squadron( + offmap_transport_cp, + FlightType.TRANSPORT, + True, + PRETENSE_SQUADRON_DEF_RETRIES, + ) + + self.generate_pretense_aircraft(cp, ato) self._reserve_frequencies_and_tacan(ato) From 730b98224781c9f9bb6d6a1daa986c72dd5fa595 Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Sat, 16 Sep 2023 23:34:05 +0300 Subject: [PATCH 041/243] Will now dynamically generate offmapZones for supply cargo aircraft. --- game/pretense/pretenseluagenerator.py | 27 ++++++++++++++++++++++ resources/plugins/pretense/init_body_2.lua | 15 +----------- 2 files changed, 28 insertions(+), 14 deletions(-) diff --git a/game/pretense/pretenseluagenerator.py b/game/pretense/pretenseluagenerator.py index cb80b397..d4005d08 100644 --- a/game/pretense/pretenseluagenerator.py +++ b/game/pretense/pretenseluagenerator.py @@ -350,6 +350,32 @@ class PretenseLuaGenerator(LuaGenerator): f" cm: addConnection('{cp.name}', '{closest_cps[1].name}')\n" ) + lua_string_supply = "local redSupply = {\n" + # Generate ConnectionManager connections + for cp_side in range(1, 3): + for cp in self.game.theater.controlpoints: + if isinstance(cp, OffMapSpawn): + continue + cp_side_captured = cp_side == 2 + if cp_side_captured != cp.captured: + continue + cp_name_trimmed = "".join([i for i in cp.name.lower() if i.isalnum()]) + for mission_type in self.game.pretense_air[cp_side][cp_name_trimmed]: + if mission_type == FlightType.TRANSPORT.name: + for air_group in self.game.pretense_air[cp_side][ + cp_name_trimmed + ][mission_type]: + lua_string_supply += f"'{air_group}'," + lua_string_supply += "}\n" + if cp_side < 2: + lua_string_supply += "local blueSupply = {\n" + lua_string_supply += "local offmapZones = {\n" + for cp in self.game.theater.controlpoints: + if isinstance(cp, Airfield): + cp_name_trimmed = "".join([i for i in cp.name.lower() if i.isalnum()]) + lua_string_supply += f" zones.{cp_name_trimmed},\n" + lua_string_supply += "}\n" + init_body_1_file = open("./resources/plugins/pretense/init_body_1.lua", "r") init_body_1 = init_body_1_file.read() @@ -373,6 +399,7 @@ class PretenseLuaGenerator(LuaGenerator): + init_body_1 + lua_string_jtac + init_body_2 + + lua_string_supply + init_footer ) diff --git a/resources/plugins/pretense/init_body_2.lua b/resources/plugins/pretense/init_body_2.lua index 9d2798d8..9994dce9 100644 --- a/resources/plugins/pretense/init_body_2.lua +++ b/resources/plugins/pretense/init_body_2.lua @@ -58,18 +58,5 @@ end, {}, timer.getTime()+30) --supply injection -local blueSupply = {'offmap-supply-blue-1','offmap-supply-blue-2','offmap-supply-blue-3','offmap-supply-blue-4','offmap-supply-blue-5'} -local redSupply = {'offmap-supply-red-1','offmap-supply-red-2','offmap-supply-red-3','offmap-supply-red-4','offmap-supply-red-5'} -local offmapZones = { - zones.batumi, - zones.sochi, - zones.nalchik, - zones.beslan, - zones.mozdok, - zones.mineralnye, --- zones.senaki, --- zones.sukhumi, --- zones.gudauta, --- zones.kobuleti, -} + From 1d6c8a24537ad60ffb96c53268d94575a86feaa7 Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Sun, 17 Sep 2023 11:16:18 +0300 Subject: [PATCH 042/243] Now splits the generated Pretense group role properly in create_vehicle_group(). --- game/pretense/pretensetgogenerator.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/game/pretense/pretensetgogenerator.py b/game/pretense/pretensetgogenerator.py index 639e74e5..ed86f157 100644 --- a/game/pretense/pretensetgogenerator.py +++ b/game/pretense/pretensetgogenerator.py @@ -294,9 +294,15 @@ class PretenseGroundObjectGenerator(GroundObjectGenerator): self.set_alarm_state(vehicle_group) GroundForcePainter(faction, vehicle_group.units[0]).apply_livery() - self.game.pretense_ground_supply[cp_side][cp_name_trimmed].append( - f"{vehicle_group.name}" - ) + group_role = group_name.split("-")[1] + if group_role == "supply": + self.game.pretense_ground_supply[cp_side][cp_name_trimmed].append( + f"{vehicle_group.name}" + ) + elif group_role == "assault": + self.game.pretense_ground_assault[cp_side][cp_name_trimmed].append( + f"{vehicle_group.name}" + ) else: vehicle_unit = self.m.vehicle(unit.unit_name, unit.type) vehicle_unit.player_can_drive = True From c7ec3283bdcb55392f4141931bbbc313925fb4f2 Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Sun, 17 Sep 2023 11:21:43 +0300 Subject: [PATCH 043/243] Added configuration constants for flights generated for Pretense. Fixed a bug which caused only one squadron per CP to be generated. Will now not generate Pretense cargo flights from Retribution off-map spawns, but instead will generate own air spawn points for them. Added a helper function initialize_pretense_data_structures(). --- game/ato/flightplans/pretensecargo.py | 3 + game/pretense/pretenseaircraftgenerator.py | 102 ++++++++++++++++---- game/pretense/pretenseflightgroupspawner.py | 17 ++-- game/pretense/pretenseluagenerator.py | 2 +- 4 files changed, 92 insertions(+), 32 deletions(-) diff --git a/game/ato/flightplans/pretensecargo.py b/game/ato/flightplans/pretensecargo.py index f5d86170..9aa3472f 100644 --- a/game/ato/flightplans/pretensecargo.py +++ b/game/ato/flightplans/pretensecargo.py @@ -11,6 +11,7 @@ from .ibuilder import IBuilder from .planningerror import PlanningError from .standard import StandardFlightPlan, StandardLayout from .waypointbuilder import WaypointBuilder +from ...theater import OffMapSpawn if TYPE_CHECKING: from ..flightwaypoint import FlightWaypoint @@ -48,6 +49,8 @@ class Builder(IBuilder[PretenseCargoFlightPlan, FerryLayout]): heading_from_flot = 0.0 offmap_transport_cp_id = self.flight.departure.id for front_line_cp in self.coalition.game.theater.controlpoints: + if isinstance(front_line_cp, OffMapSpawn): + continue for front_line in self.coalition.game.theater.conflicts(): if front_line_cp.captured == self.flight.coalition.player: if ( diff --git a/game/pretense/pretenseaircraftgenerator.py b/game/pretense/pretenseaircraftgenerator.py index 1d208128..35900a06 100644 --- a/game/pretense/pretenseaircraftgenerator.py +++ b/game/pretense/pretenseaircraftgenerator.py @@ -27,6 +27,7 @@ from game.radio.radios import RadioRegistry from game.radio.tacan import TacanRegistry from game.runways import RunwayData from game.settings import Settings +from game.squadrons import AirWing from game.theater.controlpoint import ( ControlPoint, OffMapSpawn, @@ -42,6 +43,13 @@ if TYPE_CHECKING: PRETENSE_SQUADRON_DEF_RETRIES = 100 +PRETENSE_SEAD_FLIGHTS_PER_CP = 1 +PRETENSE_CAS_FLIGHTS_PER_CP = 1 +PRETENSE_STRIKE_FLIGHTS_PER_CP = 1 +PRETENSE_BARCAP_FLIGHTS_PER_CP = 1 +PRETENSE_AI_AIRCRAFT_PER_FLIGHT = 2 +PRETENSE_AI_AWACS_PER_FLIGHT = 1 +PRETENSE_AI_TANKERS_PER_FLIGHT = 1 class PretenseAircraftGenerator: @@ -108,6 +116,8 @@ class PretenseAircraftGenerator: fixed_wing=True, fixed_wing_stol=True, rotary_wing=False ) for front_line_cp in self.game.theater.controlpoints: + if isinstance(front_line_cp, OffMapSpawn): + continue for front_line in self.game.theater.conflicts(): if front_line_cp.captured == cp.captured: if ( @@ -124,7 +134,7 @@ class PretenseAircraftGenerator: return self.game.theater.find_control_point_by_id(offmap_transport_cp_id) def should_generate_pretense_transports( - self, cp: ControlPoint + self, air_wing: AirWing ) -> tuple[bool, bool]: """ Returns a tuple of booleans, telling whether transport helicopter and aircraft @@ -136,17 +146,16 @@ class PretenseAircraftGenerator: """ autogenerate_transport_helicopter_squadron = True autogenerate_cargo_plane_squadron = True - for aircraft_type in cp.coalition.air_wing.squadrons: - for squadron in cp.coalition.air_wing.squadrons[aircraft_type]: - mission_types = squadron.auto_assignable_mission_types + for aircraft_type in air_wing.squadrons: + for squadron in air_wing.squadrons[aircraft_type]: if squadron.aircraft.helicopter and ( - FlightType.TRANSPORT in mission_types - or FlightType.AIR_ASSAULT in mission_types + squadron.aircraft.capable_of(FlightType.TRANSPORT) + or squadron.aircraft.capable_of(FlightType.AIR_ASSAULT) ): autogenerate_transport_helicopter_squadron = False elif not squadron.aircraft.helicopter and ( - FlightType.TRANSPORT in mission_types - or FlightType.AIR_ASSAULT in mission_types + squadron.aircraft.capable_of(FlightType.TRANSPORT) + or squadron.aircraft.capable_of(FlightType.AIR_ASSAULT) ): autogenerate_cargo_plane_squadron = False return ( @@ -181,7 +190,7 @@ class PretenseAircraftGenerator: f"Generating a squadron definition for fixed-wing {fixed_wing} squadron at {cp}" ) for retries in range(num_retries): - if squadron_def is None or fixed_wing != squadron_def.aircraft.helicopter: + if squadron_def is None or fixed_wing == squadron_def.aircraft.helicopter: squadron_def = ( cp.coalition.air_wing.squadron_def_generator.generate_for_task( flight_type, cp @@ -200,7 +209,8 @@ class PretenseAircraftGenerator: cp.coalition, self.game, ) - cp.coalition.air_wing.squadrons[squadron.aircraft] = list() + if squadron.aircraft not in cp.coalition.air_wing.squadrons: + cp.coalition.air_wing.squadrons[squadron.aircraft] = list() cp.coalition.air_wing.add_squadron(squadron) return @@ -229,10 +239,11 @@ class PretenseAircraftGenerator: if isinstance(squadron.location, OffMapSpawn): continue - squadron.owned_aircraft += 1 - squadron.untasked_aircraft += 1 + squadron.owned_aircraft += PRETENSE_AI_AIRCRAFT_PER_FLIGHT + squadron.untasked_aircraft += PRETENSE_AI_AIRCRAFT_PER_FLIGHT package = Package(cp, squadron.flight_db, auto_asap=False) mission_types = squadron.auto_assignable_mission_types + aircraft_per_flight = 1 if squadron.aircraft.helicopter and ( FlightType.TRANSPORT in mission_types or FlightType.AIR_ASSAULT in mission_types @@ -247,39 +258,54 @@ class PretenseAircraftGenerator: FlightType.SEAD in mission_types or FlightType.SEAD_SWEEP in mission_types or FlightType.SEAD_ESCORT in mission_types - ) and num_of_sead < 2: + ) and num_of_sead < PRETENSE_SEAD_FLIGHTS_PER_CP: flight_type = FlightType.SEAD num_of_sead += 1 - elif FlightType.DEAD in mission_types and num_of_sead < 2: + aircraft_per_flight = PRETENSE_AI_AIRCRAFT_PER_FLIGHT + elif ( + FlightType.DEAD in mission_types + and num_of_sead < PRETENSE_SEAD_FLIGHTS_PER_CP + ): flight_type = FlightType.DEAD num_of_sead += 1 + aircraft_per_flight = PRETENSE_AI_AIRCRAFT_PER_FLIGHT elif ( FlightType.CAS in mission_types or FlightType.BAI in mission_types - ) and num_of_cas < 2: + ) and num_of_cas < PRETENSE_CAS_FLIGHTS_PER_CP: flight_type = FlightType.CAS num_of_cas += 1 + aircraft_per_flight = PRETENSE_AI_AIRCRAFT_PER_FLIGHT elif ( FlightType.STRIKE in mission_types or FlightType.OCA_RUNWAY in mission_types or FlightType.OCA_AIRCRAFT in mission_types - ) and num_of_strike < 2: + ) and num_of_strike < PRETENSE_STRIKE_FLIGHTS_PER_CP: flight_type = FlightType.STRIKE num_of_strike += 1 + aircraft_per_flight = PRETENSE_AI_AIRCRAFT_PER_FLIGHT elif ( FlightType.BARCAP in mission_types or FlightType.TARCAP in mission_types or FlightType.ESCORT in mission_types - ) and num_of_cap < 2: + ) and num_of_cap < PRETENSE_BARCAP_FLIGHTS_PER_CP: flight_type = FlightType.BARCAP num_of_cap += 1 + aircraft_per_flight = PRETENSE_AI_AIRCRAFT_PER_FLIGHT + elif FlightType.AEWC in mission_types: + aircraft_per_flight = PRETENSE_AI_AWACS_PER_FLIGHT + elif FlightType.REFUELING in mission_types: + aircraft_per_flight = PRETENSE_AI_TANKERS_PER_FLIGHT else: flight_type = random.choice(list(mission_types)) + print( + f"Generating flight of {aircraft_per_flight} for {flight_type}: {squadron.aircraft}" + ) if flight_type == FlightType.TRANSPORT: flight = Flight( package, squadron, - 1, + aircraft_per_flight, FlightType.PRETENSE_CARGO, StartType.IN_FLIGHT, divert=cp, @@ -288,7 +314,12 @@ class PretenseAircraftGenerator: flight.state = Navigating(flight, self.game.settings, waypoint_index=1) else: flight = Flight( - package, squadron, 1, flight_type, StartType.COLD, divert=cp + package, + squadron, + aircraft_per_flight, + flight_type, + StartType.COLD, + divert=cp, ) package.add_flight(flight) flight.state = WaitingForStart( @@ -296,7 +327,34 @@ class PretenseAircraftGenerator: ) ato.add_package(package) - return + return + + def initialize_pretense_data_structures( + self, cp: ControlPoint, flight: Flight + ) -> None: + """ + Ensures that the data structures used to pass flight group information + to the Pretense init script lua are initialized for use in + PretenseFlightGroupSpawner and PretenseLuaGenerator. + + Args: + cp: Control point to generate aircraft for. + flight: The current flight being generated. + """ + flight_type = flight.flight_type.name + cp_side = 2 if cp.captured else 1 + cp_name_trimmed = "".join([i for i in cp.name.lower() if i.isalnum()]) + + if cp_name_trimmed not in flight.coalition.game.pretense_air[cp_side]: + flight.coalition.game.pretense_air[cp_side][cp_name_trimmed] = {} + if ( + flight_type + not in flight.coalition.game.pretense_air[cp_side][cp_name_trimmed] + ): + flight.coalition.game.pretense_air[cp_side][cp_name_trimmed][ + flight_type + ] = list() + return def generate_flights( self, @@ -319,7 +377,7 @@ class PretenseAircraftGenerator: ( autogenerate_transport_helicopter_squadron, autogenerate_cargo_plane_squadron, - ) = self.should_generate_pretense_transports(cp) + ) = self.should_generate_pretense_transports(cp.coalition.air_wing) if autogenerate_transport_helicopter_squadron: self.generate_pretense_transport_squadron( @@ -345,6 +403,8 @@ class PretenseAircraftGenerator: if not package.flights: continue for flight in package.flights: + self.initialize_pretense_data_structures(cp, flight) + if flight.alive: if not flight.squadron.location.runway_is_operational(): logging.warning( diff --git a/game/pretense/pretenseflightgroupspawner.py b/game/pretense/pretenseflightgroupspawner.py index d3b20b7e..6f785ceb 100644 --- a/game/pretense/pretenseflightgroupspawner.py +++ b/game/pretense/pretenseflightgroupspawner.py @@ -75,16 +75,6 @@ class PretenseFlightGroupSpawner(FlightGroupSpawner): name = namegen.next_pretense_aircraft_name(cp, self.flight) cp_side = 2 if cp.captured else 1 cp_name_trimmed = "".join([i for i in cp.name.lower() if i.isalnum()]) - flight_type = self.flight.flight_type.name - if cp_name_trimmed not in self.flight.coalition.game.pretense_air[cp_side]: - self.flight.coalition.game.pretense_air[cp_side][cp_name_trimmed] = {} - if ( - flight_type - not in self.flight.coalition.game.pretense_air[cp_side][cp_name_trimmed] - ): - self.flight.coalition.game.pretense_air[cp_side][cp_name_trimmed][ - flight_type - ] = list() try: if self.start_type is StartType.IN_FLIGHT: @@ -171,6 +161,10 @@ class PretenseFlightGroupSpawner(FlightGroupSpawner): def generate_mid_mission(self) -> FlyingGroup[Any]: assert isinstance(self.flight.state, InFlight) name = namegen.next_pretense_aircraft_name(self.flight.departure, self.flight) + cp_side = 2 if self.flight.departure.captured else 1 + cp_name_trimmed = "".join( + [i for i in self.flight.departure.name.lower() if i.isalnum()] + ) speed = self.flight.state.estimate_speed() pos = self.flight.state.estimate_position() pos += Vector2(random.randint(100, 1000), random.randint(100, 1000)) @@ -192,6 +186,9 @@ class PretenseFlightGroupSpawner(FlightGroupSpawner): alt = self.mission_data.cp_stack[cp] self.mission_data.cp_stack[cp] += STACK_SEPARATION + self.flight.coalition.game.pretense_air[cp_side][cp_name_trimmed][ + self.flight.flight_type.name + ].append(name) group = self.mission.flight_group( country=self.country, name=name, diff --git a/game/pretense/pretenseluagenerator.py b/game/pretense/pretenseluagenerator.py index d4005d08..a5e6b86e 100644 --- a/game/pretense/pretenseluagenerator.py +++ b/game/pretense/pretenseluagenerator.py @@ -361,7 +361,7 @@ class PretenseLuaGenerator(LuaGenerator): continue cp_name_trimmed = "".join([i for i in cp.name.lower() if i.isalnum()]) for mission_type in self.game.pretense_air[cp_side][cp_name_trimmed]: - if mission_type == FlightType.TRANSPORT.name: + if mission_type == FlightType.PRETENSE_CARGO.name: for air_group in self.game.pretense_air[cp_side][ cp_name_trimmed ][mission_type]: From f7c6f14220e7decef28b0b3a27f3af5f66297ee7 Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Sun, 17 Sep 2023 12:29:21 +0300 Subject: [PATCH 044/243] Fixed FlightType.AEWC and FlightType.REFUELING handling in generate_pretense_aircraft(). --- game/pretense/pretenseaircraftgenerator.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/game/pretense/pretenseaircraftgenerator.py b/game/pretense/pretenseaircraftgenerator.py index 35900a06..aebf990b 100644 --- a/game/pretense/pretenseaircraftgenerator.py +++ b/game/pretense/pretenseaircraftgenerator.py @@ -292,15 +292,16 @@ class PretenseAircraftGenerator: num_of_cap += 1 aircraft_per_flight = PRETENSE_AI_AIRCRAFT_PER_FLIGHT elif FlightType.AEWC in mission_types: + flight_type = FlightType.AEWC aircraft_per_flight = PRETENSE_AI_AWACS_PER_FLIGHT elif FlightType.REFUELING in mission_types: + flight_type = FlightType.REFUELING aircraft_per_flight = PRETENSE_AI_TANKERS_PER_FLIGHT else: + if len(list(mission_types)) == 0: + continue flight_type = random.choice(list(mission_types)) - print( - f"Generating flight of {aircraft_per_flight} for {flight_type}: {squadron.aircraft}" - ) if flight_type == FlightType.TRANSPORT: flight = Flight( package, From 4be122e11a9d86a39cc05cf3f08f44106b72062e Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Sun, 17 Sep 2023 13:09:02 +0300 Subject: [PATCH 045/243] Split the Pretense zone definition generation into separate methods: generate_pretense_zone_land() for land control points and generate_pretense_zone_sea() for carriers/LHAs. --- game/pretense/pretenseluagenerator.py | 622 +++++++++++++++++--------- 1 file changed, 418 insertions(+), 204 deletions(-) diff --git a/game/pretense/pretenseluagenerator.py b/game/pretense/pretenseluagenerator.py index a5e6b86e..966724ef 100644 --- a/game/pretense/pretenseluagenerator.py +++ b/game/pretense/pretenseluagenerator.py @@ -54,6 +54,420 @@ class PretenseLuaGenerator(LuaGenerator): self.mission.triggerrules.triggers.remove(t) self.mission.triggerrules.triggers.append(t) + def generate_pretense_zone_land(self, cp_name: str, cp_side: int) -> str: + lua_string_zones = "" + cp_name_trimmed = "".join([i for i in cp_name.lower() if i.isalnum()]) + + lua_string_zones += f"zones.{cp_name_trimmed}:defineUpgrades(" + "{\n" + lua_string_zones += " [1] = { --red side\n" + lua_string_zones += " presets.upgrades.basic.tent:extend({\n" + lua_string_zones += f" name='{cp_name_trimmed}-tent-red',\n" + lua_string_zones += " products = {\n" + lua_string_zones += ( + " presets.special.red.infantry:extend({ name='" + + cp_name_trimmed + + "-defense-red'})\n" + ) + lua_string_zones += " }\n" + lua_string_zones += " }),\n" + lua_string_zones += " presets.upgrades.basic.comPost:extend({\n" + lua_string_zones += f" name = '{cp_name_trimmed}-com-red',\n" + lua_string_zones += " products = {\n" + lua_string_zones += ( + " presets.special.red.infantry:extend({ name='" + + cp_name_trimmed + + "-defense-red'}),\n" + ) + lua_string_zones += ( + " presets.defenses.red.infantry:extend({ name='" + + cp_name_trimmed + + "-garrison-red' })\n" + ) + lua_string_zones += " }\n" + lua_string_zones += " }),\n" + lua_string_zones += " },\n" + lua_string_zones += " [2] = --blue side\n" + lua_string_zones += " {\n" + lua_string_zones += " presets.upgrades.basic.tent:extend({\n" + lua_string_zones += f" name='{cp_name_trimmed}-tent-blue',\n" + lua_string_zones += " products = {\n" + lua_string_zones += ( + " presets.special.blue.infantry:extend({ name='" + + cp_name_trimmed + + "-defense-blue'})\n" + ) + lua_string_zones += " }\n" + lua_string_zones += " }),\n" + lua_string_zones += " presets.upgrades.basic.comPost:extend({\n" + lua_string_zones += f" name = '{cp_name_trimmed}-com-blue',\n" + lua_string_zones += " products = {\n" + lua_string_zones += ( + " presets.special.blue.infantry:extend({ name='" + + cp_name_trimmed + + "-defense-blue'}),\n" + ) + lua_string_zones += ( + " presets.defenses.blue.infantry:extend({ name='" + + cp_name_trimmed + + "-garrison-blue' })\n" + ) + lua_string_zones += " }\n" + lua_string_zones += " }),\n" + lua_string_zones += " presets.upgrades.supply.fuelTank:extend({\n" + lua_string_zones += ( + " name = '" + cp_name_trimmed + "-fueltank-blue',\n" + ) + lua_string_zones += " products = {\n" + for ground_group in self.game.pretense_ground_supply[cp_side][cp_name_trimmed]: + lua_string_zones += ( + " presets.missions.supply.convoy:extend({ name='" + + ground_group + + "'}),\n" + ) + for ground_group in self.game.pretense_ground_assault[cp_side][cp_name_trimmed]: + lua_string_zones += ( + " presets.missions.attack.surface:extend({ name='" + + ground_group + + "'}),\n" + ) + for mission_type in self.game.pretense_air[cp_side][cp_name_trimmed]: + if mission_type == FlightType.AIR_ASSAULT.name: + mission_name = "supply.helo" + for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ + mission_type + ]: + lua_string_zones += ( + f" presets.missions.{mission_name}:extend" + + "({name='" + + air_group + + "'}),\n" + ) + lua_string_zones += " }\n" + lua_string_zones += " }),\n" + lua_string_zones += " presets.upgrades.airdef.comCenter:extend({\n" + lua_string_zones += ( + f" name = '{cp_name_trimmed}-mission-command-blue',\n" + ) + lua_string_zones += " products = {\n" + lua_string_zones += ( + " presets.defenses.blue.shorad:extend({ name='" + + cp_name_trimmed + + "-sam-blue' }),\n" + ) + for mission_type in self.game.pretense_air[cp_side][cp_name_trimmed]: + if mission_type == FlightType.SEAD.name: + mission_name = "attack.sead" + for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ + mission_type + ]: + lua_string_zones += ( + f" presets.missions.{mission_name}:extend" + + "({name='" + + air_group + + "', altitude=25000, expend=AI.Task.WeaponExpend.ALL}),\n" + ) + elif mission_type == FlightType.CAS.name: + mission_name = "attack.cas" + for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ + mission_type + ]: + lua_string_zones += ( + f" presets.missions.{mission_name}:extend" + + "({name='" + + air_group + + "', altitude=15000, expend=AI.Task.WeaponExpend.ONE}),\n" + ) + elif mission_type == FlightType.BAI.name: + mission_name = "attack.bai" + for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ + mission_type + ]: + lua_string_zones += ( + f" presets.missions.{mission_name}:extend" + + "({name='" + + air_group + + "', altitude=10000, expend=AI.Task.WeaponExpend.ONE}),\n" + ) + elif mission_type == FlightType.STRIKE.name: + mission_name = "attack.strike" + for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ + mission_type + ]: + lua_string_zones += ( + f" presets.missions.{mission_name}:extend" + + "({name='" + + air_group + + "', altitude=20000, expend=AI.Task.WeaponExpend.ALL}),\n" + ) + elif mission_type == FlightType.BARCAP.name: + mission_name = "patrol.aircraft" + for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ + mission_type + ]: + lua_string_zones += ( + f" presets.missions.{mission_name}:extend" + + "({name='" + + air_group + + "', altitude=25000, range=25}),\n" + ) + elif mission_type == FlightType.REFUELING.name: + mission_name = "support.tanker" + for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ + mission_type + ]: + tanker_freq = 257.0 + tanker_tacan = 37.0 + for tanker in self.mission_data.tankers: + if tanker.group_name == air_group: + tanker_freq = tanker.freq.hertz / 1000000 + tanker_tacan = tanker.tacan.number + if tanker.variant == "KC-135 Stratotanker": + tanker_variant = "Boom" + else: + tanker_variant = "Drogue" + lua_string_zones += ( + f" presets.missions.{mission_name}:extend" + + "({name='" + + air_group + + "', freq='" + + str(tanker_freq) + + "', tacan='" + + str(tanker_tacan) + + "', variant='" + + tanker_variant + + "'}),\n" + ) + elif mission_type == FlightType.AEWC.name: + mission_name = "support.awacs" + for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ + mission_type + ]: + awacs_freq = 257.5 + for awacs in self.mission_data.awacs: + if awacs.group_name == air_group: + awacs_freq = awacs.freq.hertz / 1000000 + lua_string_zones += ( + f" presets.missions.{mission_name}:extend" + + "({name='" + + air_group + + "', freq=" + + str(awacs_freq) + + "}),\n" + ) + lua_string_zones += " }\n" + lua_string_zones += " })\n" + lua_string_zones += " }\n" + lua_string_zones += "})\n" + + return lua_string_zones + + def generate_pretense_zone_sea(self, cp_name: str, cp_side: int) -> str: + lua_string_zones = "" + cp_name_trimmed = "".join([i for i in cp_name.lower() if i.isalnum()]) + + lua_string_zones += f"zones.{cp_name_trimmed}:defineUpgrades(" + "{\n" + lua_string_zones += " [1] = { --red side\n" + lua_string_zones += " presets.upgrades.basic.tent:extend({\n" + lua_string_zones += f" name='{cp_name_trimmed}-tent-red',\n" + lua_string_zones += " products = {\n" + lua_string_zones += ( + " presets.special.red.infantry:extend({ name='" + + cp_name_trimmed + + "-defense-red'})\n" + ) + lua_string_zones += " }\n" + lua_string_zones += " }),\n" + lua_string_zones += " presets.upgrades.basic.comPost:extend({\n" + lua_string_zones += f" name = '{cp_name_trimmed}-com-red',\n" + lua_string_zones += " products = {\n" + lua_string_zones += ( + " presets.special.red.infantry:extend({ name='" + + cp_name_trimmed + + "-defense-red'}),\n" + ) + lua_string_zones += ( + " presets.defenses.red.infantry:extend({ name='" + + cp_name_trimmed + + "-garrison-red' })\n" + ) + lua_string_zones += " }\n" + lua_string_zones += " }),\n" + lua_string_zones += " },\n" + lua_string_zones += " [2] = --blue side\n" + lua_string_zones += " {\n" + lua_string_zones += " presets.upgrades.basic.tent:extend({\n" + lua_string_zones += f" name='{cp_name_trimmed}-tent-blue',\n" + lua_string_zones += " products = {\n" + lua_string_zones += ( + " presets.special.blue.infantry:extend({ name='" + + cp_name_trimmed + + "-defense-blue'})\n" + ) + lua_string_zones += " }\n" + lua_string_zones += " }),\n" + lua_string_zones += " presets.upgrades.basic.comPost:extend({\n" + lua_string_zones += f" name = '{cp_name_trimmed}-com-blue',\n" + lua_string_zones += " products = {\n" + lua_string_zones += ( + " presets.special.blue.infantry:extend({ name='" + + cp_name_trimmed + + "-defense-blue'}),\n" + ) + lua_string_zones += ( + " presets.defenses.blue.infantry:extend({ name='" + + cp_name_trimmed + + "-garrison-blue' })\n" + ) + lua_string_zones += " }\n" + lua_string_zones += " }),\n" + lua_string_zones += " presets.upgrades.supply.fuelTank:extend({\n" + lua_string_zones += ( + " name = '" + cp_name_trimmed + "-fueltank-blue',\n" + ) + lua_string_zones += " products = {\n" + for ground_group in self.game.pretense_ground_supply[cp_side][cp_name_trimmed]: + lua_string_zones += ( + " presets.missions.supply.convoy:extend({ name='" + + ground_group + + "'}),\n" + ) + for ground_group in self.game.pretense_ground_assault[cp_side][cp_name_trimmed]: + lua_string_zones += ( + " presets.missions.attack.surface:extend({ name='" + + ground_group + + "'}),\n" + ) + for mission_type in self.game.pretense_air[cp_side][cp_name_trimmed]: + if mission_type == FlightType.AIR_ASSAULT.name: + mission_name = "supply.helo" + for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ + mission_type + ]: + lua_string_zones += ( + f" presets.missions.{mission_name}:extend" + + "({name='" + + air_group + + "'}),\n" + ) + lua_string_zones += " }\n" + lua_string_zones += " }),\n" + lua_string_zones += " presets.upgrades.airdef.comCenter:extend({\n" + lua_string_zones += ( + f" name = '{cp_name_trimmed}-mission-command-blue',\n" + ) + lua_string_zones += " products = {\n" + lua_string_zones += ( + " presets.defenses.blue.shorad:extend({ name='" + + cp_name_trimmed + + "-sam-blue' }),\n" + ) + for mission_type in self.game.pretense_air[cp_side][cp_name_trimmed]: + if mission_type == FlightType.SEAD.name: + mission_name = "attack.sead" + for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ + mission_type + ]: + lua_string_zones += ( + f" presets.missions.{mission_name}:extend" + + "({name='" + + air_group + + "', altitude=25000, expend=AI.Task.WeaponExpend.ALL}),\n" + ) + elif mission_type == FlightType.CAS.name: + mission_name = "attack.cas" + for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ + mission_type + ]: + lua_string_zones += ( + f" presets.missions.{mission_name}:extend" + + "({name='" + + air_group + + "', altitude=15000, expend=AI.Task.WeaponExpend.ONE}),\n" + ) + elif mission_type == FlightType.BAI.name: + mission_name = "attack.bai" + for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ + mission_type + ]: + lua_string_zones += ( + f" presets.missions.{mission_name}:extend" + + "({name='" + + air_group + + "', altitude=10000, expend=AI.Task.WeaponExpend.ONE}),\n" + ) + elif mission_type == FlightType.STRIKE.name: + mission_name = "attack.strike" + for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ + mission_type + ]: + lua_string_zones += ( + f" presets.missions.{mission_name}:extend" + + "({name='" + + air_group + + "', altitude=20000, expend=AI.Task.WeaponExpend.ALL}),\n" + ) + elif mission_type == FlightType.BARCAP.name: + mission_name = "patrol.aircraft" + for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ + mission_type + ]: + lua_string_zones += ( + f" presets.missions.{mission_name}:extend" + + "({name='" + + air_group + + "', altitude=25000, range=25}),\n" + ) + elif mission_type == FlightType.REFUELING.name: + mission_name = "support.tanker" + for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ + mission_type + ]: + tanker_freq = 257.0 + tanker_tacan = 37.0 + for tanker in self.mission_data.tankers: + if tanker.group_name == air_group: + tanker_freq = tanker.freq.hertz / 1000000 + tanker_tacan = tanker.tacan.number + if tanker.variant == "KC-135 Stratotanker": + tanker_variant = "Boom" + else: + tanker_variant = "Drogue" + lua_string_zones += ( + f" presets.missions.{mission_name}:extend" + + "({name='" + + air_group + + "', freq='" + + str(tanker_freq) + + "', tacan='" + + str(tanker_tacan) + + "', variant='" + + tanker_variant + + "'}),\n" + ) + elif mission_type == FlightType.AEWC.name: + mission_name = "support.awacs" + for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ + mission_type + ]: + awacs_freq = 257.5 + for awacs in self.mission_data.awacs: + if awacs.group_name == air_group: + awacs_freq = awacs.freq.hertz / 1000000 + lua_string_zones += ( + f" presets.missions.{mission_name}:extend" + + "({name='" + + air_group + + "', freq=" + + str(awacs_freq) + + "}),\n" + ) + lua_string_zones += " }\n" + lua_string_zones += " })\n" + lua_string_zones += " }\n" + lua_string_zones += "})\n" + + return lua_string_zones + def generate_plugin_data(self) -> None: self.mission.triggerrules.triggers.clear() @@ -105,210 +519,10 @@ class PretenseLuaGenerator(LuaGenerator): lua_string_zones += ( f"zones.{cp_name_trimmed}.maxResource = {max_resource}\n" ) - lua_string_zones += f"zones.{cp_name_trimmed}:defineUpgrades(" + "{\n" - lua_string_zones += " [1] = { --red side\n" - lua_string_zones += " presets.upgrades.basic.tent:extend({\n" - lua_string_zones += f" name='{cp_name_trimmed}-tent-red',\n" - lua_string_zones += " products = {\n" - lua_string_zones += ( - " presets.special.red.infantry:extend({ name='" - + cp_name_trimmed - + "-defense-red'})\n" - ) - lua_string_zones += " }\n" - lua_string_zones += " }),\n" - lua_string_zones += " presets.upgrades.basic.comPost:extend({\n" - lua_string_zones += f" name = '{cp_name_trimmed}-com-red',\n" - lua_string_zones += " products = {\n" - lua_string_zones += ( - " presets.special.red.infantry:extend({ name='" - + cp_name_trimmed - + "-defense-red'}),\n" - ) - lua_string_zones += ( - " presets.defenses.red.infantry:extend({ name='" - + cp_name_trimmed - + "-garrison-red' })\n" - ) - lua_string_zones += " }\n" - lua_string_zones += " }),\n" - lua_string_zones += " },\n" - lua_string_zones += " [2] = --blue side\n" - lua_string_zones += " {\n" - lua_string_zones += " presets.upgrades.basic.tent:extend({\n" - lua_string_zones += f" name='{cp_name_trimmed}-tent-blue',\n" - lua_string_zones += " products = {\n" - lua_string_zones += ( - " presets.special.blue.infantry:extend({ name='" - + cp_name_trimmed - + "-defense-blue'})\n" - ) - lua_string_zones += " }\n" - lua_string_zones += " }),\n" - lua_string_zones += " presets.upgrades.basic.comPost:extend({\n" - lua_string_zones += f" name = '{cp_name_trimmed}-com-blue',\n" - lua_string_zones += " products = {\n" - lua_string_zones += ( - " presets.special.blue.infantry:extend({ name='" - + cp_name_trimmed - + "-defense-blue'}),\n" - ) - lua_string_zones += ( - " presets.defenses.blue.infantry:extend({ name='" - + cp_name_trimmed - + "-garrison-blue' })\n" - ) - lua_string_zones += " }\n" - lua_string_zones += " }),\n" - lua_string_zones += " presets.upgrades.supply.fuelTank:extend({\n" - lua_string_zones += ( - " name = '" + cp_name_trimmed + "-fueltank-blue',\n" - ) - lua_string_zones += " products = {\n" - for ground_group in self.game.pretense_ground_supply[cp_side][ - cp_name_trimmed - ]: - lua_string_zones += ( - " presets.missions.supply.convoy:extend({ name='" - + ground_group - + "'}),\n" - ) - for ground_group in self.game.pretense_ground_assault[cp_side][ - cp_name_trimmed - ]: - lua_string_zones += ( - " presets.missions.attack.surface:extend({ name='" - + ground_group - + "'}),\n" - ) - for mission_type in self.game.pretense_air[cp_side][cp_name_trimmed]: - if mission_type == FlightType.AIR_ASSAULT.name: - mission_name = "supply.helo" - for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ - mission_type - ]: - lua_string_zones += ( - f" presets.missions.{mission_name}:extend" - + "({name='" - + air_group - + "'}),\n" - ) - lua_string_zones += " }\n" - lua_string_zones += " }),\n" - lua_string_zones += " presets.upgrades.airdef.comCenter:extend({\n" - lua_string_zones += ( - f" name = '{cp_name_trimmed}-mission-command-blue',\n" - ) - lua_string_zones += " products = {\n" - lua_string_zones += ( - " presets.defenses.blue.shorad:extend({ name='" - + cp_name_trimmed - + "-sam-blue' }),\n" - ) - for mission_type in self.game.pretense_air[cp_side][cp_name_trimmed]: - if mission_type == FlightType.SEAD.name: - mission_name = "attack.sead" - for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ - mission_type - ]: - lua_string_zones += ( - f" presets.missions.{mission_name}:extend" - + "({name='" - + air_group - + "', altitude=25000, expend=AI.Task.WeaponExpend.ALL}),\n" - ) - elif mission_type == FlightType.CAS.name: - mission_name = "attack.cas" - for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ - mission_type - ]: - lua_string_zones += ( - f" presets.missions.{mission_name}:extend" - + "({name='" - + air_group - + "', altitude=15000, expend=AI.Task.WeaponExpend.ONE}),\n" - ) - elif mission_type == FlightType.BAI.name: - mission_name = "attack.bai" - for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ - mission_type - ]: - lua_string_zones += ( - f" presets.missions.{mission_name}:extend" - + "({name='" - + air_group - + "', altitude=10000, expend=AI.Task.WeaponExpend.ONE}),\n" - ) - elif mission_type == FlightType.STRIKE.name: - mission_name = "attack.strike" - for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ - mission_type - ]: - lua_string_zones += ( - f" presets.missions.{mission_name}:extend" - + "({name='" - + air_group - + "', altitude=20000, expend=AI.Task.WeaponExpend.ALL}),\n" - ) - elif mission_type == FlightType.BARCAP.name: - mission_name = "patrol.aircraft" - for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ - mission_type - ]: - lua_string_zones += ( - f" presets.missions.{mission_name}:extend" - + "({name='" - + air_group - + "', altitude=25000, range=25}),\n" - ) - elif mission_type == FlightType.REFUELING.name: - mission_name = "support.tanker" - for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ - mission_type - ]: - tanker_freq = 257.0 - tanker_tacan = 37.0 - for tanker in self.mission_data.tankers: - if tanker.group_name == air_group: - tanker_freq = tanker.freq.hertz / 1000000 - tanker_tacan = tanker.tacan.number - if tanker.variant == "KC-135 Stratotanker": - tanker_variant = "Boom" - else: - tanker_variant = "Drogue" - lua_string_zones += ( - f" presets.missions.{mission_name}:extend" - + "({name='" - + air_group - + "', freq='" - + str(tanker_freq) - + "', tacan='" - + str(tanker_tacan) - + "', variant='" - + tanker_variant - + "'}),\n" - ) - elif mission_type == FlightType.AEWC.name: - mission_name = "support.awacs" - for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ - mission_type - ]: - awacs_freq = 257.5 - for awacs in self.mission_data.awacs: - if awacs.group_name == air_group: - awacs_freq = awacs.freq.hertz / 1000000 - lua_string_zones += ( - f" presets.missions.{mission_name}:extend" - + "({name='" - + air_group - + "', freq=" - + str(awacs_freq) - + "}),\n" - ) - lua_string_zones += " }\n" - lua_string_zones += " })\n" - lua_string_zones += " }\n" - lua_string_zones += "})\n" + if cp.is_fleet: + lua_string_zones += self.generate_pretense_zone_sea(cp.name, cp_side) + else: + lua_string_zones += self.generate_pretense_zone_land(cp.name, cp_side) lua_string_connman = " cm = ConnectionManager:new()\n" From 94c14552494804b42bc2696378553b97189c6173 Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Sun, 17 Sep 2023 15:58:06 +0300 Subject: [PATCH 046/243] Now generates air units for both sides at airfields. --- game/pretense/pretenseaircraftgenerator.py | 228 ++++++++++++++++---- game/pretense/pretenseflightgroupspawner.py | 16 +- game/pretense/pretenseluagenerator.py | 152 +++++++------ game/pretense/pretensemissiongenerator.py | 25 +-- game/pretense/pretensetgogenerator.py | 74 ++++++- 5 files changed, 373 insertions(+), 122 deletions(-) diff --git a/game/pretense/pretenseaircraftgenerator.py b/game/pretense/pretenseaircraftgenerator.py index aebf990b..3a956879 100644 --- a/game/pretense/pretenseaircraftgenerator.py +++ b/game/pretense/pretenseaircraftgenerator.py @@ -4,7 +4,7 @@ import logging import random from datetime import datetime from functools import cached_property -from typing import Any, Dict, List, TYPE_CHECKING, Tuple +from typing import Any, Dict, List, TYPE_CHECKING, Tuple, Optional from uuid import UUID from dcs import Point @@ -18,6 +18,7 @@ from game.ato.flightstate import Completed, WaitingForStart, Navigating from game.ato.flighttype import FlightType from game.ato.package import Package from game.ato.starttype import StartType +from game.coalition import Coalition from game.missiongenerator.aircraft.flightgroupconfigurator import ( FlightGroupConfigurator, ) @@ -32,11 +33,13 @@ from game.theater.controlpoint import ( ControlPoint, OffMapSpawn, ParkingType, + Airfield, ) from game.unitmap import UnitMap from game.missiongenerator.aircraft.aircraftpainter import AircraftPainter from game.missiongenerator.aircraft.flightdata import FlightData from game.data.weapons import WeaponType +from game.squadrons import Squadron if TYPE_CHECKING: from game import Game @@ -163,27 +166,26 @@ class PretenseAircraftGenerator: autogenerate_cargo_plane_squadron, ) - def generate_pretense_transport_squadron( + def generate_pretense_squadron( self, cp: ControlPoint, + coalition: Coalition, flight_type: FlightType, fixed_wing: bool, num_retries: int, - ) -> None: - from game.squadrons import Squadron - + ) -> Optional[Squadron]: """ - Generates a Pretense transport squadron from the faction squadron definitions. Use FlightType AIR_ASSAULT + Generates a Pretense squadron from the faction squadron definitions. Use FlightType AIR_ASSAULT for Pretense supply helicopters and TRANSPORT for off-map cargo plane squadrons. - + Retribution does not differentiate between fixed wing and rotary wing transport squadron definitions, which is why there is a retry mechanism in case the wrong type is returned. Use fixed_wing False for Pretense supply helicopters and fixed_wing True for off-map cargo plane squadrons. - - TODO: Find out if Pretense can handle rotary wing "cargo planes". + + TODO: Find out if Pretense can handle rotary wing "cargo planes". """ - squadron_def = cp.coalition.air_wing.squadron_def_generator.generate_for_task( + squadron_def = coalition.air_wing.squadron_def_generator.generate_for_task( flight_type, cp ) print( @@ -192,27 +194,27 @@ class PretenseAircraftGenerator: for retries in range(num_retries): if squadron_def is None or fixed_wing == squadron_def.aircraft.helicopter: squadron_def = ( - cp.coalition.air_wing.squadron_def_generator.generate_for_task( + coalition.air_wing.squadron_def_generator.generate_for_task( flight_type, cp ) ) # Failed, stop here if squadron_def is None: - return + return None squadron = Squadron.create_from( squadron_def, flight_type, 2, cp, - cp.coalition, + coalition, self.game, ) - if squadron.aircraft not in cp.coalition.air_wing.squadrons: - cp.coalition.air_wing.squadrons[squadron.aircraft] = list() - cp.coalition.air_wing.add_squadron(squadron) - return + if squadron.aircraft not in coalition.air_wing.squadrons: + coalition.air_wing.squadrons[squadron.aircraft] = list() + coalition.air_wing.add_squadron(squadron) + return squadron def generate_pretense_aircraft( self, cp: ControlPoint, ato: AirTaskingOrder @@ -287,6 +289,7 @@ class PretenseAircraftGenerator: FlightType.BARCAP in mission_types or FlightType.TARCAP in mission_types or FlightType.ESCORT in mission_types + or FlightType.INTERCEPTION in mission_types ) and num_of_cap < PRETENSE_BARCAP_FLIGHTS_PER_CP: flight_type = FlightType.BARCAP num_of_cap += 1 @@ -330,6 +333,137 @@ class PretenseAircraftGenerator: ato.add_package(package) return + def generate_pretense_aircraft_for_other_side( + self, cp: ControlPoint, coalition: Coalition, ato: AirTaskingOrder + ) -> None: + """ + Plans and generates AI aircraft groups/packages for Pretense + for the other side, which doesn't initially hold this control point. + + Aircraft generation is done by walking the control points which will be made into + Pretense "zones" and spawning flights for different missions. + After the flight is generated the package is added to the ATO so the flights + can be configured. + + Args: + cp: Control point to generate aircraft for. + coalition: Coalition to generate aircraft for. + ato: The ATO to generate aircraft for. + """ + + aircraft_per_flight = 1 + if cp.has_helipads and not cp.is_fleet: + flight_type = FlightType.AIR_ASSAULT + squadron = self.generate_pretense_squadron( + cp, + coalition, + flight_type, + False, + PRETENSE_SQUADRON_DEF_RETRIES, + ) + if squadron is not None: + squadron.owned_aircraft += PRETENSE_AI_AIRCRAFT_PER_FLIGHT + squadron.untasked_aircraft += PRETENSE_AI_AIRCRAFT_PER_FLIGHT + package = Package(cp, squadron.flight_db, auto_asap=False) + flight = Flight( + package, + squadron, + aircraft_per_flight, + flight_type, + StartType.COLD, + divert=cp, + ) + print( + f"Generated flight for {flight_type} flying {squadron.aircraft.name} at {squadron.location.name}" + ) + + package.add_flight(flight) + flight.state = WaitingForStart( + flight, self.game.settings, self.game.conditions.start_time + ) + ato.add_package(package) + if isinstance(cp, Airfield): + # Generate SEAD flight + flight_type = FlightType.SEAD + aircraft_per_flight = PRETENSE_AI_AIRCRAFT_PER_FLIGHT + squadron = self.generate_pretense_squadron( + cp, + coalition, + flight_type, + True, + PRETENSE_SQUADRON_DEF_RETRIES, + ) + if squadron is None: + squadron = self.generate_pretense_squadron( + cp, + coalition, + FlightType.DEAD, + True, + PRETENSE_SQUADRON_DEF_RETRIES, + ) + if squadron is not None: + squadron.owned_aircraft += PRETENSE_AI_AIRCRAFT_PER_FLIGHT + squadron.untasked_aircraft += PRETENSE_AI_AIRCRAFT_PER_FLIGHT + package = Package(cp, squadron.flight_db, auto_asap=False) + flight = Flight( + package, + squadron, + aircraft_per_flight, + flight_type, + StartType.COLD, + divert=cp, + ) + print( + f"Generated flight for {flight_type} flying {squadron.aircraft.name} at {squadron.location.name}" + ) + + package.add_flight(flight) + flight.state = WaitingForStart( + flight, self.game.settings, self.game.conditions.start_time + ) + ato.add_package(package) + + # Generate CAS flight + flight_type = FlightType.CAS + aircraft_per_flight = PRETENSE_AI_AIRCRAFT_PER_FLIGHT + squadron = self.generate_pretense_squadron( + cp, + coalition, + flight_type, + True, + PRETENSE_SQUADRON_DEF_RETRIES, + ) + if squadron is None: + squadron = self.generate_pretense_squadron( + cp, + coalition, + FlightType.BAI, + True, + PRETENSE_SQUADRON_DEF_RETRIES, + ) + if squadron is not None: + squadron.owned_aircraft += PRETENSE_AI_AIRCRAFT_PER_FLIGHT + squadron.untasked_aircraft += PRETENSE_AI_AIRCRAFT_PER_FLIGHT + package = Package(cp, squadron.flight_db, auto_asap=False) + flight = Flight( + package, + squadron, + aircraft_per_flight, + flight_type, + StartType.COLD, + divert=cp, + ) + print( + f"Generated flight for {flight_type} flying {squadron.aircraft.name} at {squadron.location.name}" + ) + + package.add_flight(flight) + flight.state = WaitingForStart( + flight, self.game.settings, self.game.conditions.start_time + ) + ato.add_package(package) + return + def initialize_pretense_data_structures( self, cp: ControlPoint, flight: Flight ) -> None: @@ -343,11 +477,15 @@ class PretenseAircraftGenerator: flight: The current flight being generated. """ flight_type = flight.flight_type.name - cp_side = 2 if cp.captured else 1 + is_player = True + cp_side = 2 if flight.coalition == self.game.coalition_for(is_player) else 1 cp_name_trimmed = "".join([i for i in cp.name.lower() if i.isalnum()]) if cp_name_trimmed not in flight.coalition.game.pretense_air[cp_side]: flight.coalition.game.pretense_air[cp_side][cp_name_trimmed] = {} + print( + f"Populated flight.coalition.game.pretense_air[{cp_side}][{cp_name_trimmed}]" + ) if ( flight_type not in flight.coalition.game.pretense_air[cp_side][cp_name_trimmed] @@ -355,6 +493,9 @@ class PretenseAircraftGenerator: flight.coalition.game.pretense_air[cp_side][cp_name_trimmed][ flight_type ] = list() + print( + f"Populated flight.coalition.game.pretense_air[{cp_side}][{cp_name_trimmed}][{flight_type}]" + ) return def generate_flights( @@ -373,29 +514,40 @@ class PretenseAircraftGenerator: dynamic_runways: Runway data for carriers and FARPs. """ - offmap_transport_cp = self.find_pretense_cargo_plane_cp(cp) + if country == cp.coalition.faction.country: + offmap_transport_cp = self.find_pretense_cargo_plane_cp(cp) - ( - autogenerate_transport_helicopter_squadron, - autogenerate_cargo_plane_squadron, - ) = self.should_generate_pretense_transports(cp.coalition.air_wing) + ( + autogenerate_transport_helicopter_squadron, + autogenerate_cargo_plane_squadron, + ) = self.should_generate_pretense_transports(cp.coalition.air_wing) - if autogenerate_transport_helicopter_squadron: - self.generate_pretense_transport_squadron( - offmap_transport_cp, - FlightType.AIR_ASSAULT, - False, - PRETENSE_SQUADRON_DEF_RETRIES, + if autogenerate_transport_helicopter_squadron: + self.generate_pretense_squadron( + offmap_transport_cp, + offmap_transport_cp.coalition, + FlightType.AIR_ASSAULT, + False, + PRETENSE_SQUADRON_DEF_RETRIES, + ) + if autogenerate_cargo_plane_squadron: + self.generate_pretense_squadron( + offmap_transport_cp, + offmap_transport_cp.coalition, + FlightType.TRANSPORT, + True, + PRETENSE_SQUADRON_DEF_RETRIES, + ) + + self.generate_pretense_aircraft(cp, ato) + else: + is_player = True + coalition = ( + self.game.coalition_for(is_player) + if country == self.game.coalition_for(is_player).faction.country + else self.game.coalition_for(False) ) - if autogenerate_cargo_plane_squadron: - self.generate_pretense_transport_squadron( - offmap_transport_cp, - FlightType.TRANSPORT, - True, - PRETENSE_SQUADRON_DEF_RETRIES, - ) - - self.generate_pretense_aircraft(cp, ato) + self.generate_pretense_aircraft_for_other_side(cp, coalition, ato) self._reserve_frequencies_and_tacan(ato) diff --git a/game/pretense/pretenseflightgroupspawner.py b/game/pretense/pretenseflightgroupspawner.py index 6f785ceb..51732d57 100644 --- a/game/pretense/pretenseflightgroupspawner.py +++ b/game/pretense/pretenseflightgroupspawner.py @@ -73,7 +73,13 @@ class PretenseFlightGroupSpawner(FlightGroupSpawner): def generate_flight_at_departure(self) -> FlyingGroup[Any]: cp = self.flight.departure name = namegen.next_pretense_aircraft_name(cp, self.flight) - cp_side = 2 if cp.captured else 1 + is_player = True + cp_side = ( + 2 + if self.flight.coalition + == self.flight.coalition.game.coalition_for(is_player) + else 1 + ) cp_name_trimmed = "".join([i for i in cp.name.lower() if i.isalnum()]) try: @@ -161,7 +167,13 @@ class PretenseFlightGroupSpawner(FlightGroupSpawner): def generate_mid_mission(self) -> FlyingGroup[Any]: assert isinstance(self.flight.state, InFlight) name = namegen.next_pretense_aircraft_name(self.flight.departure, self.flight) - cp_side = 2 if self.flight.departure.captured else 1 + is_player = True + cp_side = ( + 2 + if self.flight.coalition + == self.flight.coalition.game.coalition_for(is_player) + else 1 + ) cp_name_trimmed = "".join( [i for i in self.flight.departure.name.lower() if i.isalnum()] ) diff --git a/game/pretense/pretenseluagenerator.py b/game/pretense/pretenseluagenerator.py index 966724ef..7c5dc964 100644 --- a/game/pretense/pretenseluagenerator.py +++ b/game/pretense/pretenseluagenerator.py @@ -23,6 +23,8 @@ from game.missiongenerator.missiondata import MissionData if TYPE_CHECKING: from game import Game +PRETENSE_RED_SIDE = 1 +PRETENSE_BLUE_SIDE = 2 PRETENSE_NUMBER_OF_ZONES_TO_CONNECT_CARRIERS_TO = 2 @@ -54,68 +56,18 @@ class PretenseLuaGenerator(LuaGenerator): self.mission.triggerrules.triggers.remove(t) self.mission.triggerrules.triggers.append(t) - def generate_pretense_zone_land(self, cp_name: str, cp_side: int) -> str: + def generate_pretense_land_upgrade_supply(self, cp_name: str, cp_side: int): lua_string_zones = "" cp_name_trimmed = "".join([i for i in cp_name.lower() if i.isalnum()]) + cp_side_str = "blue" if cp_side == PRETENSE_BLUE_SIDE else "red" - lua_string_zones += f"zones.{cp_name_trimmed}:defineUpgrades(" + "{\n" - lua_string_zones += " [1] = { --red side\n" - lua_string_zones += " presets.upgrades.basic.tent:extend({\n" - lua_string_zones += f" name='{cp_name_trimmed}-tent-red',\n" - lua_string_zones += " products = {\n" - lua_string_zones += ( - " presets.special.red.infantry:extend({ name='" - + cp_name_trimmed - + "-defense-red'})\n" - ) - lua_string_zones += " }\n" - lua_string_zones += " }),\n" - lua_string_zones += " presets.upgrades.basic.comPost:extend({\n" - lua_string_zones += f" name = '{cp_name_trimmed}-com-red',\n" - lua_string_zones += " products = {\n" - lua_string_zones += ( - " presets.special.red.infantry:extend({ name='" - + cp_name_trimmed - + "-defense-red'}),\n" - ) - lua_string_zones += ( - " presets.defenses.red.infantry:extend({ name='" - + cp_name_trimmed - + "-garrison-red' })\n" - ) - lua_string_zones += " }\n" - lua_string_zones += " }),\n" - lua_string_zones += " },\n" - lua_string_zones += " [2] = --blue side\n" - lua_string_zones += " {\n" - lua_string_zones += " presets.upgrades.basic.tent:extend({\n" - lua_string_zones += f" name='{cp_name_trimmed}-tent-blue',\n" - lua_string_zones += " products = {\n" - lua_string_zones += ( - " presets.special.blue.infantry:extend({ name='" - + cp_name_trimmed - + "-defense-blue'})\n" - ) - lua_string_zones += " }\n" - lua_string_zones += " }),\n" - lua_string_zones += " presets.upgrades.basic.comPost:extend({\n" - lua_string_zones += f" name = '{cp_name_trimmed}-com-blue',\n" - lua_string_zones += " products = {\n" - lua_string_zones += ( - " presets.special.blue.infantry:extend({ name='" - + cp_name_trimmed - + "-defense-blue'}),\n" - ) - lua_string_zones += ( - " presets.defenses.blue.infantry:extend({ name='" - + cp_name_trimmed - + "-garrison-blue' })\n" - ) - lua_string_zones += " }\n" - lua_string_zones += " }),\n" lua_string_zones += " presets.upgrades.supply.fuelTank:extend({\n" lua_string_zones += ( - " name = '" + cp_name_trimmed + "-fueltank-blue',\n" + " name = '" + + cp_name_trimmed + + "-fueltank-" + + cp_side_str + + "',\n" ) lua_string_zones += " products = {\n" for ground_group in self.game.pretense_ground_supply[cp_side][cp_name_trimmed]: @@ -146,13 +98,19 @@ class PretenseLuaGenerator(LuaGenerator): lua_string_zones += " }),\n" lua_string_zones += " presets.upgrades.airdef.comCenter:extend({\n" lua_string_zones += ( - f" name = '{cp_name_trimmed}-mission-command-blue',\n" + f" name = '{cp_name_trimmed}-mission-command-" + + cp_side_str + + "',\n" ) lua_string_zones += " products = {\n" lua_string_zones += ( - " presets.defenses.blue.shorad:extend({ name='" + " presets.defenses." + + cp_side_str + + ".shorad:extend({ name='" + cp_name_trimmed - + "-sam-blue' }),\n" + + "-sam-" + + cp_side_str + + "' }),\n" ) for mission_type in self.game.pretense_air[cp_side][cp_name_trimmed]: if mission_type == FlightType.SEAD.name: @@ -256,6 +214,78 @@ class PretenseLuaGenerator(LuaGenerator): ) lua_string_zones += " }\n" lua_string_zones += " })\n" + + return lua_string_zones + + def generate_pretense_zone_land(self, cp_name: str) -> str: + lua_string_zones = "" + cp_name_trimmed = "".join([i for i in cp_name.lower() if i.isalnum()]) + + lua_string_zones += f"zones.{cp_name_trimmed}:defineUpgrades(" + "{\n" + lua_string_zones += " [1] = { --red side\n" + lua_string_zones += " presets.upgrades.basic.tent:extend({\n" + lua_string_zones += f" name='{cp_name_trimmed}-tent-red',\n" + lua_string_zones += " products = {\n" + lua_string_zones += ( + " presets.special.red.infantry:extend({ name='" + + cp_name_trimmed + + "-defense-red'})\n" + ) + lua_string_zones += " }\n" + lua_string_zones += " }),\n" + lua_string_zones += " presets.upgrades.basic.comPost:extend({\n" + lua_string_zones += f" name = '{cp_name_trimmed}-com-red',\n" + lua_string_zones += " products = {\n" + lua_string_zones += ( + " presets.special.red.infantry:extend({ name='" + + cp_name_trimmed + + "-defense-red'}),\n" + ) + lua_string_zones += ( + " presets.defenses.red.infantry:extend({ name='" + + cp_name_trimmed + + "-garrison-red' })\n" + ) + lua_string_zones += " }\n" + lua_string_zones += " }),\n" + + lua_string_zones += self.generate_pretense_land_upgrade_supply( + cp_name, PRETENSE_RED_SIDE + ) + + lua_string_zones += " },\n" + lua_string_zones += " [2] = --blue side\n" + lua_string_zones += " {\n" + lua_string_zones += " presets.upgrades.basic.tent:extend({\n" + lua_string_zones += f" name='{cp_name_trimmed}-tent-blue',\n" + lua_string_zones += " products = {\n" + lua_string_zones += ( + " presets.special.blue.infantry:extend({ name='" + + cp_name_trimmed + + "-defense-blue'})\n" + ) + lua_string_zones += " }\n" + lua_string_zones += " }),\n" + lua_string_zones += " presets.upgrades.basic.comPost:extend({\n" + lua_string_zones += f" name = '{cp_name_trimmed}-com-blue',\n" + lua_string_zones += " products = {\n" + lua_string_zones += ( + " presets.special.blue.infantry:extend({ name='" + + cp_name_trimmed + + "-defense-blue'}),\n" + ) + lua_string_zones += ( + " presets.defenses.blue.infantry:extend({ name='" + + cp_name_trimmed + + "-garrison-blue' })\n" + ) + lua_string_zones += " }\n" + lua_string_zones += " }),\n" + + lua_string_zones += self.generate_pretense_land_upgrade_supply( + cp_name, PRETENSE_BLUE_SIDE + ) + lua_string_zones += " }\n" lua_string_zones += "})\n" @@ -522,7 +552,7 @@ class PretenseLuaGenerator(LuaGenerator): if cp.is_fleet: lua_string_zones += self.generate_pretense_zone_sea(cp.name, cp_side) else: - lua_string_zones += self.generate_pretense_zone_land(cp.name, cp_side) + lua_string_zones += self.generate_pretense_zone_land(cp.name) lua_string_connman = " cm = ConnectionManager:new()\n" diff --git a/game/pretense/pretensemissiongenerator.py b/game/pretense/pretensemissiongenerator.py index 52e7349c..5d64622b 100644 --- a/game/pretense/pretensemissiongenerator.py +++ b/game/pretense/pretensemissiongenerator.py @@ -207,18 +207,19 @@ class PretenseMissionGenerator(MissionGenerator): self.game.red.ato.clear() for cp in self.game.theater.controlpoints: - if cp.captured: - ato = self.game.blue.ato - cp_country = self.p_country - else: - ato = self.game.red.ato - cp_country = self.e_country - aircraft_generator.generate_flights( - cp_country, - cp, - ato, - tgo_generator.runways, - ) + for country in (self.p_country, self.e_country): + + if country == self.p_country: + ato = self.game.blue.ato + else: + ato = self.game.red.ato + print(f"Running generate_flights for {country.name} at {cp.name}") + aircraft_generator.generate_flights( + country, + cp, + ato, + tgo_generator.runways, + ) self.mission_data.flights = aircraft_generator.flights diff --git a/game/pretense/pretensetgogenerator.py b/game/pretense/pretensetgogenerator.py index ed86f157..bcb9a0c2 100644 --- a/game/pretense/pretensetgogenerator.py +++ b/game/pretense/pretensetgogenerator.py @@ -72,12 +72,15 @@ from game.theater import ( TheaterGroundObject, TheaterUnit, NavalControlPoint, + PresetLocation, ) from game.theater.theatergroundobject import ( CarrierGroundObject, GenericCarrierGroundObject, LhaGroundObject, MissileSiteGroundObject, + BuildingGroundObject, + VehicleGroupGroundObject, ) from game.theater.theatergroup import SceneryUnit, IadsGroundGroup, TheaterGroup from game.unitmap import UnitMap @@ -171,12 +174,7 @@ class PretenseGroundObjectGenerator(GroundObjectGenerator): cp_name_trimmed = "".join( [i for i in self.ground_object.control_point.name.lower() if i.isalnum()] ) - cp_side = 2 if self.ground_object.control_point.captured else 1 - for side in range(1, 3): - if cp_name_trimmed not in self.game.pretense_ground_supply[cp_side]: - self.game.pretense_ground_supply[side][cp_name_trimmed] = list() - if cp_name_trimmed not in self.game.pretense_ground_assault[cp_side]: - self.game.pretense_ground_assault[side][cp_name_trimmed] = list() + for group in self.ground_object.groups: vehicle_units: list[TheaterUnit] = [] ship_units: list[TheaterUnit] = [] @@ -275,7 +273,12 @@ class PretenseGroundObjectGenerator(GroundObjectGenerator): cp_name_trimmed = "".join( [i for i in self.ground_object.control_point.name.lower() if i.isalnum()] ) - cp_side = 2 if self.ground_object.control_point.captured else 1 + is_player = True + side = ( + 2 + if self.country == self.game.coalition_for(is_player).faction.country + else 1 + ) for unit in units: assert issubclass(unit.type, VehicleType) @@ -296,11 +299,11 @@ class PretenseGroundObjectGenerator(GroundObjectGenerator): group_role = group_name.split("-")[1] if group_role == "supply": - self.game.pretense_ground_supply[cp_side][cp_name_trimmed].append( + self.game.pretense_ground_supply[side][cp_name_trimmed].append( f"{vehicle_group.name}" ) elif group_role == "assault": - self.game.pretense_ground_assault[cp_side][cp_name_trimmed].append( + self.game.pretense_ground_assault[side][cp_name_trimmed].append( f"{vehicle_group.name}" ) else: @@ -361,6 +364,14 @@ class PretenseTgoGenerator(TgoGenerator): def generate(self) -> None: for cp in self.game.theater.controlpoints: + cp_name_trimmed = "".join([i for i in cp.name.lower() if i.isalnum()]) + for side in range(1, 3): + if cp_name_trimmed not in self.game.pretense_ground_supply[side]: + self.game.pretense_ground_supply[side][cp_name_trimmed] = list() + if cp_name_trimmed not in self.game.pretense_ground_assault[side]: + self.game.pretense_ground_assault[side][cp_name_trimmed] = list() + + # First generate units for the coalition, which initially holds this CP country = self.m.country(cp.coalition.faction.country.name) # Generate helipads @@ -431,4 +442,49 @@ class PretenseTgoGenerator(TgoGenerator): ground_object, country, self.game, self.m, self.unit_map ) generator.generate() + # Then generate ground supply and assault groups for the other coalition + other_coalition = cp.coalition + for coalition in cp.coalition.game.coalitions: + if coalition == cp.coalition: + continue + else: + other_coalition = coalition + country = self.m.country(other_coalition.faction.country.name) + for ground_object in cp.ground_objects: + generator: GroundObjectGenerator + if isinstance(ground_object, BuildingGroundObject): + new_ground_object = BuildingGroundObject( + name=ground_object.name, + category=ground_object.category, + location=PresetLocation( + f"{ground_object.name} {ground_object.id}", + ground_object.position, + ground_object.heading, + ), + control_point=ground_object.control_point, + is_fob_structure=ground_object.is_fob_structure, + task=ground_object.task, + ) + generator = PretenseGroundObjectGenerator( + new_ground_object, country, self.game, self.m, self.unit_map + ) + elif isinstance(ground_object, VehicleGroupGroundObject): + new_ground_object = VehicleGroupGroundObject( + name=ground_object.name, + location=PresetLocation( + f"{ground_object.name} {ground_object.id}", + ground_object.position, + ground_object.heading, + ), + control_point=ground_object.control_point, + task=ground_object.task, + ) + generator = PretenseGroundObjectGenerator( + new_ground_object, country, self.game, self.m, self.unit_map + ) + else: + continue + + generator.generate() + self.mission_data.runways = list(self.runways.values()) From bc27dc4621a86c204430b81d86a50c1a9402fd78 Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Sun, 17 Sep 2023 16:06:17 +0300 Subject: [PATCH 047/243] Cleaned up some code. --- game/pretense/pretenseluagenerator.py | 2 +- game/pretense/pretensetgogenerator.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/game/pretense/pretenseluagenerator.py b/game/pretense/pretenseluagenerator.py index 7c5dc964..b10f6ab6 100644 --- a/game/pretense/pretenseluagenerator.py +++ b/game/pretense/pretenseluagenerator.py @@ -56,7 +56,7 @@ class PretenseLuaGenerator(LuaGenerator): self.mission.triggerrules.triggers.remove(t) self.mission.triggerrules.triggers.append(t) - def generate_pretense_land_upgrade_supply(self, cp_name: str, cp_side: int): + def generate_pretense_land_upgrade_supply(self, cp_name: str, cp_side: int) -> str: lua_string_zones = "" cp_name_trimmed = "".join([i for i in cp_name.lower() if i.isalnum()]) cp_side_str = "blue" if cp_side == PRETENSE_BLUE_SIDE else "red" diff --git a/game/pretense/pretensetgogenerator.py b/game/pretense/pretensetgogenerator.py index bcb9a0c2..74f99a6e 100644 --- a/game/pretense/pretensetgogenerator.py +++ b/game/pretense/pretensetgogenerator.py @@ -450,8 +450,8 @@ class PretenseTgoGenerator(TgoGenerator): else: other_coalition = coalition country = self.m.country(other_coalition.faction.country.name) + new_ground_object: TheaterGroundObject for ground_object in cp.ground_objects: - generator: GroundObjectGenerator if isinstance(ground_object, BuildingGroundObject): new_ground_object = BuildingGroundObject( name=ground_object.name, From bc92a2ade3d49278505963d26e84c7ea0b25d32b Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Sun, 17 Sep 2023 18:55:52 +0300 Subject: [PATCH 048/243] Don't spawn flights for the other squadron in generate_pretense_aircraft(). --- game/pretense/pretenseaircraftgenerator.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/game/pretense/pretenseaircraftgenerator.py b/game/pretense/pretenseaircraftgenerator.py index 3a956879..25408106 100644 --- a/game/pretense/pretenseaircraftgenerator.py +++ b/game/pretense/pretenseaircraftgenerator.py @@ -240,6 +240,8 @@ class PretenseAircraftGenerator: # Intentionally don't spawn anything at OffMapSpawns in Pretense if isinstance(squadron.location, OffMapSpawn): continue + if cp.coalition != squadron.coalition: + continue squadron.owned_aircraft += PRETENSE_AI_AIRCRAFT_PER_FLIGHT squadron.untasked_aircraft += PRETENSE_AI_AIRCRAFT_PER_FLIGHT From 087a38d105d3de468384402048ead6c7d760b26a Mon Sep 17 00:00:00 2001 From: Raffson Date: Sun, 17 Sep 2023 15:24:13 +0200 Subject: [PATCH 049/243] imports --- game/pretense/pretenseaircraftgenerator.py | 8 +++----- game/pretense/pretenseflightgroupspawner.py | 4 +--- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/game/pretense/pretenseaircraftgenerator.py b/game/pretense/pretenseaircraftgenerator.py index 25408106..9a7c36f4 100644 --- a/game/pretense/pretenseaircraftgenerator.py +++ b/game/pretense/pretenseaircraftgenerator.py @@ -5,7 +5,6 @@ import random from datetime import datetime from functools import cached_property from typing import Any, Dict, List, TYPE_CHECKING, Tuple, Optional -from uuid import UUID from dcs import Point from dcs.country import Country @@ -14,11 +13,13 @@ from dcs.unitgroup import FlyingGroup, StaticGroup from game.ato.airtaaskingorder import AirTaskingOrder from game.ato.flight import Flight -from game.ato.flightstate import Completed, WaitingForStart, Navigating +from game.ato.flightstate import WaitingForStart, Navigating from game.ato.flighttype import FlightType from game.ato.package import Package from game.ato.starttype import StartType from game.coalition import Coalition +from game.data.weapons import WeaponType +from game.missiongenerator.aircraft.flightdata import FlightData from game.missiongenerator.aircraft.flightgroupconfigurator import ( FlightGroupConfigurator, ) @@ -36,9 +37,6 @@ from game.theater.controlpoint import ( Airfield, ) from game.unitmap import UnitMap -from game.missiongenerator.aircraft.aircraftpainter import AircraftPainter -from game.missiongenerator.aircraft.flightdata import FlightData -from game.data.weapons import WeaponType from game.squadrons import Squadron if TYPE_CHECKING: diff --git a/game/pretense/pretenseflightgroupspawner.py b/game/pretense/pretenseflightgroupspawner.py index 51732d57..18deecbe 100644 --- a/game/pretense/pretenseflightgroupspawner.py +++ b/game/pretense/pretenseflightgroupspawner.py @@ -1,6 +1,5 @@ import logging import random -import re from typing import Any, Tuple from dcs import Mission @@ -24,8 +23,7 @@ from game.missiongenerator.aircraft.flightgroupspawner import ( ) from game.missiongenerator.missiondata import MissionData from game.naming import NameGenerator -from game.theater import Airfield, ControlPoint, Fob, NavalControlPoint, OffMapSpawn -from game.utils import feet, meters +from game.theater import Airfield, ControlPoint, Fob, NavalControlPoint class PretenseNameGenerator(NameGenerator): From a30fb0f368fd68d4ca4f5f3ca1d06b775b414620 Mon Sep 17 00:00:00 2001 From: Raffson Date: Sun, 17 Sep 2023 16:34:08 +0200 Subject: [PATCH 050/243] Blast through errors --- .../aircraft/flightgroupconfigurator.py | 17 +++++++++++++---- game/pretense/pretenseaircraftgenerator.py | 2 ++ game/pretense/pretenseluagenerator.py | 14 +++++--------- 3 files changed, 20 insertions(+), 13 deletions(-) diff --git a/game/missiongenerator/aircraft/flightgroupconfigurator.py b/game/missiongenerator/aircraft/flightgroupconfigurator.py index ec42092c..4e815d28 100644 --- a/game/missiongenerator/aircraft/flightgroupconfigurator.py +++ b/game/missiongenerator/aircraft/flightgroupconfigurator.py @@ -1,6 +1,7 @@ from __future__ import annotations import logging +import random from datetime import datetime from typing import Any, Optional, TYPE_CHECKING @@ -19,7 +20,12 @@ from game.data.weapons import Pylon, WeaponType from game.missiongenerator.logisticsgenerator import LogisticsGenerator from game.missiongenerator.missiondata import MissionData, AwacsInfo, TankerInfo from game.radio.radios import RadioFrequency, RadioRegistry -from game.radio.tacan import TacanBand, TacanRegistry, TacanUsage +from game.radio.tacan import ( + TacanBand, + TacanRegistry, + TacanUsage, + OutOfTacanChannelsError, +) from game.runways import RunwayData from game.squadrons import Pilot from .aircraftbehavior import AircraftBehavior @@ -210,9 +216,12 @@ class FlightGroupConfigurator: ) or isinstance(self.flight.flight_plan, PackageRefuelingFlightPlan): tacan = self.flight.tacan if tacan is None and self.flight.squadron.aircraft.dcs_unit_type.tacan: - tacan = self.tacan_registry.alloc_for_band( - TacanBand.Y, TacanUsage.AirToAir - ) + try: + tacan = self.tacan_registry.alloc_for_band( + TacanBand.Y, TacanUsage.AirToAir + ) + except OutOfTacanChannelsError: + tacan = random.choice(list(self.tacan_registry.allocated_channels)) else: tacan = self.flight.tacan self.mission_data.tankers.append( diff --git a/game/pretense/pretenseaircraftgenerator.py b/game/pretense/pretenseaircraftgenerator.py index 9a7c36f4..59f1340a 100644 --- a/game/pretense/pretenseaircraftgenerator.py +++ b/game/pretense/pretenseaircraftgenerator.py @@ -577,6 +577,8 @@ class PretenseAircraftGenerator: from game.pretense.pretenseflightgroupspawner import PretenseFlightGroupSpawner """Creates and configures the flight group in the mission.""" + if not country.unused_onboard_numbers: + country.reset_onboard_numbers() group = PretenseFlightGroupSpawner( flight, country, diff --git a/game/pretense/pretenseluagenerator.py b/game/pretense/pretenseluagenerator.py index b10f6ab6..acc2798c 100644 --- a/game/pretense/pretenseluagenerator.py +++ b/game/pretense/pretenseluagenerator.py @@ -1,7 +1,6 @@ from __future__ import annotations import logging -import os from abc import ABC, abstractmethod from pathlib import Path from typing import TYPE_CHECKING, Optional @@ -12,13 +11,11 @@ from dcs.translation import String from dcs.triggers import TriggerStart from game.ato import FlightType -from game.dcs.aircrafttype import AircraftType from game.missiongenerator.luagenerator import LuaGenerator -from game.plugins import LuaPluginManager -from game.theater import TheaterGroundObject, Airfield, OffMapSpawn -from game.theater.iadsnetwork.iadsrole import IadsRole -from game.utils import escape_string_for_lua from game.missiongenerator.missiondata import MissionData +from game.plugins import LuaPluginManager +from game.theater import Airfield, OffMapSpawn +from game.utils import escape_string_for_lua if TYPE_CHECKING: from game import Game @@ -175,14 +172,13 @@ class PretenseLuaGenerator(LuaGenerator): ]: tanker_freq = 257.0 tanker_tacan = 37.0 + tanker_variant = "Drogue" for tanker in self.mission_data.tankers: if tanker.group_name == air_group: tanker_freq = tanker.freq.hertz / 1000000 - tanker_tacan = tanker.tacan.number + tanker_tacan = tanker.tacan if tanker.tacan else "N/A" if tanker.variant == "KC-135 Stratotanker": tanker_variant = "Boom" - else: - tanker_variant = "Drogue" lua_string_zones += ( f" presets.missions.{mission_name}:extend" + "({name='" From 00bcd547d3f6fefec57abffbe8880c607390d445 Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Sun, 17 Sep 2023 20:31:57 +0300 Subject: [PATCH 051/243] Implemented a separate generate_packages() method in PretenseAircraftGenerator to prevent generating the same ATO multiple times over. --- game/pretense/pretenseaircraftgenerator.py | 69 +++++++++++++++------- game/pretense/pretensemissiongenerator.py | 56 ++++++------------ 2 files changed, 64 insertions(+), 61 deletions(-) diff --git a/game/pretense/pretenseaircraftgenerator.py b/game/pretense/pretenseaircraftgenerator.py index 59f1340a..0a1f704e 100644 --- a/game/pretense/pretenseaircraftgenerator.py +++ b/game/pretense/pretenseaircraftgenerator.py @@ -301,9 +301,7 @@ class PretenseAircraftGenerator: flight_type = FlightType.REFUELING aircraft_per_flight = PRETENSE_AI_TANKERS_PER_FLIGHT else: - if len(list(mission_types)) == 0: - continue - flight_type = random.choice(list(mission_types)) + continue if flight_type == FlightType.TRANSPORT: flight = Flight( @@ -464,7 +462,27 @@ class PretenseAircraftGenerator: ato.add_package(package) return - def initialize_pretense_data_structures( + def initialize_pretense_data_structures(self, cp: ControlPoint) -> None: + """ + Ensures that the data structures used to pass flight group information + to the Pretense init script lua are initialized for use in + PretenseFlightGroupSpawner and PretenseLuaGenerator. + + Args: + cp: Control point to generate aircraft for. + flight: The current flight being generated. + """ + cp_name_trimmed = "".join([i for i in cp.name.lower() if i.isalnum()]) + + for side in range(1, 3): + if cp_name_trimmed not in cp.coalition.game.pretense_air[side]: + cp.coalition.game.pretense_air[side][cp_name_trimmed] = {} + print( + f"Populated flight.coalition.game.pretense_air[{side}][{cp_name_trimmed}]" + ) + return + + def initialize_pretense_data_structures_for_flight( self, cp: ControlPoint, flight: Flight ) -> None: """ @@ -477,25 +495,24 @@ class PretenseAircraftGenerator: flight: The current flight being generated. """ flight_type = flight.flight_type.name - is_player = True - cp_side = 2 if flight.coalition == self.game.coalition_for(is_player) else 1 cp_name_trimmed = "".join([i for i in cp.name.lower() if i.isalnum()]) - if cp_name_trimmed not in flight.coalition.game.pretense_air[cp_side]: - flight.coalition.game.pretense_air[cp_side][cp_name_trimmed] = {} - print( - f"Populated flight.coalition.game.pretense_air[{cp_side}][{cp_name_trimmed}]" - ) - if ( - flight_type - not in flight.coalition.game.pretense_air[cp_side][cp_name_trimmed] - ): - flight.coalition.game.pretense_air[cp_side][cp_name_trimmed][ + for side in range(1, 3): + if cp_name_trimmed not in flight.coalition.game.pretense_air[side]: + flight.coalition.game.pretense_air[side][cp_name_trimmed] = {} + print( + f"Populated flight.coalition.game.pretense_air[{side}][{cp_name_trimmed}]" + ) + if ( flight_type - ] = list() - print( - f"Populated flight.coalition.game.pretense_air[{cp_side}][{cp_name_trimmed}][{flight_type}]" - ) + not in flight.coalition.game.pretense_air[side][cp_name_trimmed] + ): + flight.coalition.game.pretense_air[side][cp_name_trimmed][ + flight_type + ] = list() + print( + f"Populated flight.coalition.game.pretense_air[{side}][{cp_name_trimmed}][{flight_type}]" + ) return def generate_flights( @@ -503,7 +520,6 @@ class PretenseAircraftGenerator: country: Country, cp: ControlPoint, ato: AirTaskingOrder, - dynamic_runways: Dict[str, RunwayData], ) -> None: """Adds aircraft to the mission for every flight in the ATO. @@ -513,6 +529,7 @@ class PretenseAircraftGenerator: ato: The ATO to generate aircraft for. dynamic_runways: Runway data for carriers and FARPs. """ + self.initialize_pretense_data_structures(cp) if country == cp.coalition.faction.country: offmap_transport_cp = self.find_pretense_cargo_plane_cp(cp) @@ -551,12 +568,20 @@ class PretenseAircraftGenerator: self._reserve_frequencies_and_tacan(ato) + def generate_packages( + self, + country: Country, + ato: AirTaskingOrder, + dynamic_runways: Dict[str, RunwayData], + ) -> None: for package in reversed(sorted(ato.packages, key=lambda x: x.time_over_target)): logging.info(f"Generating package for target: {package.target.name}") if not package.flights: continue for flight in package.flights: - self.initialize_pretense_data_structures(cp, flight) + self.initialize_pretense_data_structures_for_flight( + flight.departure, flight + ) if flight.alive: if not flight.squadron.location.runway_is_operational(): diff --git a/game/pretense/pretensemissiongenerator.py b/game/pretense/pretensemissiongenerator.py index 5d64622b..8c8368bd 100644 --- a/game/pretense/pretensemissiongenerator.py +++ b/game/pretense/pretensemissiongenerator.py @@ -55,6 +55,7 @@ if TYPE_CHECKING: class PretenseMissionGenerator(MissionGenerator): def __init__(self, game: Game, time: datetime) -> None: + super().__init__(game, time) self.game = game self.time = time self.mission = Mission(game.theater.terrain) @@ -218,9 +219,22 @@ class PretenseMissionGenerator(MissionGenerator): country, cp, ato, - tgo_generator.runways, ) + for cp in self.game.theater.controlpoints: + if cp.captured: + ato = self.game.blue.ato + country = self.p_country + else: + ato = self.game.red.ato + country = self.e_country + + aircraft_generator.generate_packages( + country, + ato, + tgo_generator.runways, + ) + self.mission_data.flights = aircraft_generator.flights for flight in aircraft_generator.flights: @@ -228,41 +242,5 @@ class PretenseMissionGenerator(MissionGenerator): continue flight.aircraft_type.assign_channels_for_flight(flight, self.mission_data) - def notify_info_generators( - self, - ) -> None: - """Generates subscribed MissionInfoGenerator objects.""" - mission_data = self.mission_data - gens: list[MissionInfoGenerator] = [ - KneeboardGenerator(self.mission, self.game), - BriefingGenerator(self.mission, self.game), - ] - for gen in gens: - for dynamic_runway in mission_data.runways: - gen.add_dynamic_runway(dynamic_runway) - - for tanker in mission_data.tankers: - if tanker.blue: - gen.add_tanker(tanker) - - for aewc in mission_data.awacs: - if aewc.blue: - gen.add_awacs(aewc) - - for jtac in mission_data.jtacs: - if jtac.blue: - gen.add_jtac(jtac) - - for flight in mission_data.flights: - gen.add_flight(flight) - gen.generate() - - def setup_combined_arms(self) -> None: - settings = self.game.settings - commanders = settings.tactical_commander_count - self.mission.groundControl.pilot_can_control_vehicles = commanders > 0 - - self.mission.groundControl.blue_game_masters = settings.game_masters_count - self.mission.groundControl.blue_tactical_commander = commanders - self.mission.groundControl.blue_jtac = settings.jtac_count - self.mission.groundControl.blue_observer = settings.observer_count + if self.game.settings.plugins.get("ewrj"): + self._configure_ewrj(aircraft_generator) From 1e3f6baceb8a4638f8597e470dcc9e38f04a5330 Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Sun, 17 Sep 2023 20:37:29 +0300 Subject: [PATCH 052/243] Changed tanker_tacan from "N/A" to 0.0 when the tanker is not compatible with TACAN, in order to avoid mission script errors. --- game/pretense/pretenseluagenerator.py | 4 ++-- game/pretense/pretensemissiongenerator.py | 3 --- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/game/pretense/pretenseluagenerator.py b/game/pretense/pretenseluagenerator.py index acc2798c..ec4aff92 100644 --- a/game/pretense/pretenseluagenerator.py +++ b/game/pretense/pretenseluagenerator.py @@ -176,7 +176,7 @@ class PretenseLuaGenerator(LuaGenerator): for tanker in self.mission_data.tankers: if tanker.group_name == air_group: tanker_freq = tanker.freq.hertz / 1000000 - tanker_tacan = tanker.tacan if tanker.tacan else "N/A" + tanker_tacan = tanker.tacan.number if tanker.tacan else 0.0 if tanker.variant == "KC-135 Stratotanker": tanker_variant = "Boom" lua_string_zones += ( @@ -453,7 +453,7 @@ class PretenseLuaGenerator(LuaGenerator): for tanker in self.mission_data.tankers: if tanker.group_name == air_group: tanker_freq = tanker.freq.hertz / 1000000 - tanker_tacan = tanker.tacan.number + tanker_tacan = tanker.tacan.number if tanker.tacan else 0.0 if tanker.variant == "KC-135 Stratotanker": tanker_variant = "Boom" else: diff --git a/game/pretense/pretensemissiongenerator.py b/game/pretense/pretensemissiongenerator.py index 8c8368bd..c724c471 100644 --- a/game/pretense/pretensemissiongenerator.py +++ b/game/pretense/pretensemissiongenerator.py @@ -241,6 +241,3 @@ class PretenseMissionGenerator(MissionGenerator): if not flight.client_units: continue flight.aircraft_type.assign_channels_for_flight(flight, self.mission_data) - - if self.game.settings.plugins.get("ewrj"): - self._configure_ewrj(aircraft_generator) From 85207643085d3c2d7279bef67f4b701d33cf9821 Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Mon, 18 Sep 2023 18:23:03 +0300 Subject: [PATCH 053/243] Run aircraft_generator.generate_packages() only once per ATO, instead of multiple times. Fixed the duplicated flights issue. --- game/pretense/pretensemissiongenerator.py | 26 ++++++++++++----------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/game/pretense/pretensemissiongenerator.py b/game/pretense/pretensemissiongenerator.py index c724c471..2f8942fe 100644 --- a/game/pretense/pretensemissiongenerator.py +++ b/game/pretense/pretensemissiongenerator.py @@ -221,19 +221,21 @@ class PretenseMissionGenerator(MissionGenerator): ato, ) - for cp in self.game.theater.controlpoints: - if cp.captured: - ato = self.game.blue.ato - country = self.p_country - else: - ato = self.game.red.ato - country = self.e_country + ato = self.game.blue.ato + country = self.p_country + aircraft_generator.generate_packages( + country, + ato, + tgo_generator.runways, + ) - aircraft_generator.generate_packages( - country, - ato, - tgo_generator.runways, - ) + ato = self.game.red.ato + country = self.e_country + aircraft_generator.generate_packages( + country, + ato, + tgo_generator.runways, + ) self.mission_data.flights = aircraft_generator.flights From d890ef0808b69812b88c8fca901375c057942551 Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Tue, 19 Sep 2023 01:05:52 +0300 Subject: [PATCH 054/243] Implemented generating player slots in the Pretense campaign. --- game/pretense/pretenseaircraftgenerator.py | 188 +++++++++++++++++++- game/pretense/pretenseflightgroupspawner.py | 47 +++-- 2 files changed, 213 insertions(+), 22 deletions(-) diff --git a/game/pretense/pretenseaircraftgenerator.py b/game/pretense/pretenseaircraftgenerator.py index 0a1f704e..6e78054d 100644 --- a/game/pretense/pretenseaircraftgenerator.py +++ b/game/pretense/pretenseaircraftgenerator.py @@ -9,6 +9,7 @@ from typing import Any, Dict, List, TYPE_CHECKING, Tuple, Optional from dcs import Point from dcs.country import Country from dcs.mission import Mission +from dcs.terrain import NoParkingSlotError from dcs.unitgroup import FlyingGroup, StaticGroup from game.ato.airtaaskingorder import AirTaskingOrder @@ -19,6 +20,7 @@ from game.ato.package import Package from game.ato.starttype import StartType from game.coalition import Coalition from game.data.weapons import WeaponType +from game.dcs.aircrafttype import AircraftType from game.missiongenerator.aircraft.flightdata import FlightData from game.missiongenerator.aircraft.flightgroupconfigurator import ( FlightGroupConfigurator, @@ -36,6 +38,7 @@ from game.theater.controlpoint import ( ParkingType, Airfield, ) +from game.theater.theatergroundobject import EwrGroundObject, SamGroundObject from game.unitmap import UnitMap from game.squadrons import Squadron @@ -51,6 +54,7 @@ PRETENSE_BARCAP_FLIGHTS_PER_CP = 1 PRETENSE_AI_AIRCRAFT_PER_FLIGHT = 2 PRETENSE_AI_AWACS_PER_FLIGHT = 1 PRETENSE_AI_TANKERS_PER_FLIGHT = 1 +PRETENSE_PLAYER_AIRCRAFT_PER_FLIGHT = 2 class PretenseAircraftGenerator: @@ -214,6 +218,49 @@ class PretenseAircraftGenerator: coalition.air_wing.add_squadron(squadron) return squadron + def generate_pretense_squadron_for( + self, + aircraft_type: AircraftType, + cp: ControlPoint, + coalition: Coalition, + ) -> Optional[Squadron]: + """ + Generates a Pretense squadron from the faction squadron definitions for the designated + AircraftType. Use FlightType AIR_ASSAULT + for Pretense supply helicopters and TRANSPORT for off-map cargo plane squadrons. + """ + + squadron_def = coalition.air_wing.squadron_def_generator.generate_for_aircraft( + aircraft_type + ) + flight_type = random.choice(list(squadron_def.auto_assignable_mission_types)) + if flight_type == FlightType.ESCORT and aircraft_type.helicopter: + flight_type = FlightType.CAS + if flight_type in ( + FlightType.INTERCEPTION, + FlightType.ESCORT, + FlightType.SWEEP, + ): + flight_type = FlightType.BARCAP + if flight_type in (FlightType.SEAD_ESCORT, FlightType.SEAD_SWEEP): + flight_type = FlightType.SEAD + if flight_type == FlightType.ANTISHIP: + flight_type = FlightType.STRIKE + if flight_type == FlightType.TRANSPORT: + flight_type = FlightType.AIR_ASSAULT + squadron = Squadron.create_from( + squadron_def, + flight_type, + 2, + cp, + coalition, + self.game, + ) + if squadron.aircraft not in coalition.air_wing.squadrons: + coalition.air_wing.squadrons[squadron.aircraft] = list() + coalition.air_wing.add_squadron(squadron) + return squadron + def generate_pretense_aircraft( self, cp: ControlPoint, ato: AirTaskingOrder ) -> None: @@ -462,6 +509,68 @@ class PretenseAircraftGenerator: ato.add_package(package) return + def generate_pretense_aircraft_for_players( + self, cp: ControlPoint, coalition: Coalition, ato: AirTaskingOrder + ) -> None: + """ + Plans and generates player piloted aircraft groups/packages for Pretense. + + Aircraft generation is done by walking the control points which will be made into + Pretense "zones" and spawning flights for different missions. + After the flight is generated the package is added to the ATO so the flights + can be configured. + + Args: + cp: Control point to generate aircraft for. + coalition: Coalition to generate aircraft for. + ato: The ATO to generate aircraft for. + """ + + aircraft_per_flight = PRETENSE_PLAYER_AIRCRAFT_PER_FLIGHT + random_aircraft_list = list(coalition.faction.aircraft) + random.shuffle(random_aircraft_list) + for aircraft_type in random_aircraft_list: + # Don't generate any player flights for non-flyable types (obviously) + if not aircraft_type.flyable: + continue + if not cp.can_operate(aircraft_type): + continue + + squadron = self.generate_pretense_squadron_for( + aircraft_type, + cp, + coalition, + ) + if squadron is not None: + squadron.owned_aircraft += PRETENSE_PLAYER_AIRCRAFT_PER_FLIGHT + squadron.untasked_aircraft += PRETENSE_PLAYER_AIRCRAFT_PER_FLIGHT + squadron.populate_for_turn_0(False) + for pilot in squadron.pilot_pool: + pilot.player = True + package = Package(cp, squadron.flight_db, auto_asap=False) + flight = Flight( + package, + squadron, + aircraft_per_flight, + squadron.primary_task, + StartType.COLD, + divert=cp, + ) + for pilot in flight.roster.pilots: + if pilot is not None: + pilot.player = True + print( + f"Generated flight for {squadron.primary_task} flying {squadron.aircraft.name} at {squadron.location.name}. Pilot client count: {flight.client_count}" + ) + + package.add_flight(flight) + flight.state = WaitingForStart( + flight, self.game.settings, self.game.conditions.start_time + ) + ato.add_package(package) + + return + def initialize_pretense_data_structures(self, cp: ControlPoint) -> None: """ Ensures that the data structures used to pass flight group information @@ -531,6 +640,7 @@ class PretenseAircraftGenerator: """ self.initialize_pretense_data_structures(cp) + is_player = True if country == cp.coalition.faction.country: offmap_transport_cp = self.find_pretense_cargo_plane_cp(cp) @@ -558,7 +668,6 @@ class PretenseAircraftGenerator: self.generate_pretense_aircraft(cp, ato) else: - is_player = True coalition = ( self.game.coalition_for(is_player) if country == self.game.coalition_for(is_player).faction.country @@ -566,6 +675,11 @@ class PretenseAircraftGenerator: ) self.generate_pretense_aircraft_for_other_side(cp, coalition, ato) + if country == self.game.coalition_for(is_player).faction.country: + if not isinstance(cp, OffMapSpawn): + coalition = self.game.coalition_for(is_player) + self.generate_pretense_aircraft_for_players(cp, coalition, ato) + self._reserve_frequencies_and_tacan(ato) def generate_packages( @@ -590,10 +704,15 @@ class PretenseAircraftGenerator: ) flight.return_pilots_and_aircraft() continue - logging.info(f"Generating flight: {flight.unit_type}") - group = self.create_and_configure_flight( - flight, country, dynamic_runways + logging.info( + f"Generating flight: {flight.unit_type} for {flight.flight_type.name}" ) + try: + group = self.create_and_configure_flight( + flight, country, dynamic_runways + ) + except NoParkingSlotError: + return self.unit_map.add_aircraft(group, flight) def create_and_configure_flight( @@ -632,6 +751,67 @@ class PretenseAircraftGenerator: self.use_client, ).configure() ) + else: + if flight.client_count > 0: + if flight.flight_type == FlightType.CAS: + for conflict in self.game.theater.conflicts(): + flight.package.target = conflict + break + elif ( + flight.flight_type == FlightType.STRIKE + or flight.flight_type == FlightType.BAI + ): + for cp in self.game.theater.closest_opposing_control_points(): + if cp.coalition == flight.coalition: + continue + for mission_target in cp.ground_objects: + flight.package.target = mission_target + elif ( + flight.flight_type == FlightType.OCA_RUNWAY + or flight.flight_type == FlightType.OCA_AIRCRAFT + ): + for cp in self.game.theater.controlpoints: + if cp.coalition == flight.coalition or not isinstance( + cp, Airfield + ): + continue + flight.package.target = cp + elif flight.flight_type == FlightType.DEAD: + for cp in self.game.theater.controlpoints: + if cp.coalition == flight.coalition: + continue + for ground_object in cp.ground_objects: + is_ewr = isinstance(ground_object, EwrGroundObject) + is_sam = isinstance(ground_object, SamGroundObject) + + if is_ewr or is_sam: + flight.package.target = ground_object + elif flight.flight_type == FlightType.AIR_ASSAULT: + for cp in self.game.theater.closest_opposing_control_points(): + if cp.coalition == flight.coalition: + continue + if flight.is_hercules: + if cp.coalition == flight.coalition or not isinstance( + cp, Airfield + ): + continue + flight.package.target = cp + + FlightGroupConfigurator( + flight, + group, + self.game, + self.mission, + self.time, + self.radio_registry, + self.tacan_registy, + self.laser_code_registry, + self.mission_data, + dynamic_runways, + self.use_client, + ).configure() + # for unit in group.units: + # unit.set_client() if self.ewrj: self._track_ewrj_flight(flight, group) diff --git a/game/pretense/pretenseflightgroupspawner.py b/game/pretense/pretenseflightgroupspawner.py index 18deecbe..5622905b 100644 --- a/game/pretense/pretenseflightgroupspawner.py +++ b/game/pretense/pretenseflightgroupspawner.py @@ -82,9 +82,10 @@ class PretenseFlightGroupSpawner(FlightGroupSpawner): try: if self.start_type is StartType.IN_FLIGHT: - self.flight.coalition.game.pretense_air[cp_side][cp_name_trimmed][ - self.flight.flight_type.name - ].append(name) + if self.flight.client_count == 0: + self.flight.coalition.game.pretense_air[cp_side][cp_name_trimmed][ + self.flight.flight_type.name + ].append(name) group = self._generate_over_departure(name, cp) return group elif isinstance(cp, NavalControlPoint): @@ -95,9 +96,10 @@ class PretenseFlightGroupSpawner(FlightGroupSpawner): f"Carrier group {carrier_group} is a " f"{carrier_group.__class__.__name__}, expected a ShipGroup" ) - self.flight.coalition.game.pretense_air[cp_side][cp_name_trimmed][ - self.flight.flight_type.name - ].append(name) + if self.flight.client_count == 0: + self.flight.coalition.game.pretense_air[cp_side][cp_name_trimmed][ + self.flight.flight_type.name + ].append(name) return self._generate_at_group(name, carrier_group) elif isinstance(cp, Fob): is_heli = self.flight.squadron.aircraft.helicopter @@ -125,9 +127,10 @@ class PretenseFlightGroupSpawner(FlightGroupSpawner): pad_group = self._generate_at_cp_ground_spawn(name, cp) if pad_group is not None: return pad_group - self.flight.coalition.game.pretense_air[cp_side][cp_name_trimmed][ - self.flight.flight_type.name - ].append(name) + if self.flight.client_count == 0: + self.flight.coalition.game.pretense_air[cp_side][cp_name_trimmed][ + self.flight.flight_type.name + ].append(name) return self._generate_over_departure(name, cp) elif isinstance(cp, Airfield): is_heli = self.flight.squadron.aircraft.helicopter @@ -145,9 +148,10 @@ class PretenseFlightGroupSpawner(FlightGroupSpawner): pad_group = self._generate_at_cp_ground_spawn(name, cp) if pad_group is not None: return pad_group - self.flight.coalition.game.pretense_air[cp_side][cp_name_trimmed][ - self.flight.flight_type.name - ].append(name) + if self.flight.client_count == 0: + self.flight.coalition.game.pretense_air[cp_side][cp_name_trimmed][ + self.flight.flight_type.name + ].append(name) return self._generate_at_airfield(name, cp) else: raise NotImplementedError( @@ -155,12 +159,19 @@ class PretenseFlightGroupSpawner(FlightGroupSpawner): ) except NoParkingSlotError: # Generated when there is no place on Runway or on Parking Slots - logging.warning( - "No room on runway or parking slots. Starting from the air." - ) - self.flight.start_type = StartType.IN_FLIGHT - group = self._generate_over_departure(name, cp) - return group + if self.flight.client_count > 0: + # Don't generate player airstarts + logging.warning( + "No room on runway or parking slots. Not generating a player air-start." + ) + raise NoParkingSlotError + else: + logging.warning( + "No room on runway or parking slots. Starting from the air." + ) + self.flight.start_type = StartType.IN_FLIGHT + group = self._generate_over_departure(name, cp) + return group def generate_mid_mission(self) -> FlyingGroup[Any]: assert isinstance(self.flight.state, InFlight) From bd0deed16a45c6e835fd52d0272a7bf3542672af Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Tue, 19 Sep 2023 11:11:37 +0300 Subject: [PATCH 055/243] Raise NoParkingSlotError instead of RuntimeError when running out of ground spawns. --- game/missiongenerator/aircraft/flightgroupspawner.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/game/missiongenerator/aircraft/flightgroupspawner.py b/game/missiongenerator/aircraft/flightgroupspawner.py index 96348fff..181a58b7 100644 --- a/game/missiongenerator/aircraft/flightgroupspawner.py +++ b/game/missiongenerator/aircraft/flightgroupspawner.py @@ -434,7 +434,9 @@ class FlightGroupSpawner: ) group.units[1 + i].heading = ground_spawn[0].units[0].heading except IndexError as ex: - raise RuntimeError(f"Not enough STOL slots available at {cp}") from ex + raise NoParkingSlotError( + f"Not enough STOL slots available at {cp}" + ) from ex return group def dcs_start_type(self) -> DcsStartType: From ecaa68f32a1114563b08efaa59dfe57dbec642ca Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Tue, 19 Sep 2023 11:12:39 +0300 Subject: [PATCH 056/243] Added naval units (supply/cargo/landing ships) to Pretense scripts. --- resources/plugins/pretense/init_header.lua | 52 ++++++++++++++++++- .../plugins/pretense/pretense_compiled.lua | 18 +++++++ 2 files changed, 68 insertions(+), 2 deletions(-) diff --git a/resources/plugins/pretense/init_header.lua b/resources/plugins/pretense/init_header.lua index b5d49d0e..850bfbc7 100644 --- a/resources/plugins/pretense/init_header.lua +++ b/resources/plugins/pretense/init_header.lua @@ -295,7 +295,31 @@ presets = { cost = 2000, type = 'upgrade', template = "ammo-depot" - }) + }), + shipTankerSeawisegiant = Preset:new({ + display = 'Tanker Seawise Giant', + cost = 1500, + type = 'upgrade', + template = "ship-tanker-seawisegiant" + }), + shipLandingShipSamuelChase = Preset:new({ + display = 'LST USS Samuel Chase', + cost = 1500, + type = 'upgrade', + template = "ship-landingship-samuelchase" + }), + shipLandingShipRopucha = Preset:new({ + display = 'LS Ropucha', + cost = 1500, + type = 'upgrade', + template = "ship-landingship-ropucha" + }), + shipTankerElnya = Preset:new({ + display = 'Tanker Elnya', + cost = 1500, + type = 'upgrade', + template = "ship-tanker-elnya" + }) }, supply = { fuelCache = Preset:new({ @@ -413,7 +437,31 @@ presets = { type ='upgrade', income = 50, template = "tv-tower" - }) + }), + shipSupplyTilde = Preset:new({ + display = 'Ship_Tilde_Supply', + cost = 1500, + type = 'upgrade', + template = "ship-supply-tilde" + }), + shipLandingShipLstMk2 = Preset:new({ + display = 'LST Mk.II', + cost = 1500, + type = 'upgrade', + template = "ship-landingship-lstmk2" + }), + shipBulkerYakushev = Preset:new({ + display = 'Bulker Yakushev', + cost = 1500, + type = 'upgrade', + template = "ship-bulker-yakushev" + }), + shipCargoIvanov = Preset:new({ + display = 'Cargo Ivanov', + cost = 1500, + type = 'upgrade', + template = "ship-cargo-ivanov" + }) }, airdef = { comCenter = Preset:new({ diff --git a/resources/plugins/pretense/pretense_compiled.lua b/resources/plugins/pretense/pretense_compiled.lua index 4d3a04f2..0a8d7be1 100644 --- a/resources/plugins/pretense/pretense_compiled.lua +++ b/resources/plugins/pretense/pretense_compiled.lua @@ -7527,6 +7527,24 @@ do TemplateDB.templates["command-center"] = { type=".Command Center", category="Fortifications", shape="ComCenter", dataCategory=TemplateDB.type.static } TemplateDB.templates["military-staff"] = { type="Military staff", category="Fortifications", shape="aviashtab", dataCategory=TemplateDB.type.static } + + TemplateDB.templates["ship-tanker-seawisegiant"] = { type="Seawise_Giant", category="Ships", dataCategory=TemplateDB.type.static } + + TemplateDB.templates["ship-supply-tilde"] = { type="Ship_Tilde_Supply", category="Ships", dataCategory=TemplateDB.type.static } + + TemplateDB.templates["ship-landingship-samuelchase"] = { type="USS_Samuel_Chase", category="Ships", dataCategory=TemplateDB.type.static } + + TemplateDB.templates["ship-landingship-ropucha"] = { type="BDK-775", category="Ships", dataCategory=TemplateDB.type.static } + + TemplateDB.templates["ship-tanker-elnya"] = { type="ELNYA", category="Ships", dataCategory=TemplateDB.type.static } + + TemplateDB.templates["ship-landingship-lstmk2"] = { type="LST_Mk2", category="Ships", dataCategory=TemplateDB.type.static } + + TemplateDB.templates["ship-bulker-yakushev"] = { type="Dry-cargo ship-1", category="Ships", dataCategory=TemplateDB.type.static } + + TemplateDB.templates["ship-cargo-ivanov"] = { type="Dry-cargo ship-2", category="Ships", dataCategory=TemplateDB.type.static } + + end -----------------[[ END OF TemplateDB.lua ]]----------------- From 7653866ddb2a566553a5f33591232bd051263c1b Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Tue, 19 Sep 2023 18:20:55 +0300 Subject: [PATCH 057/243] Improved the typing of game.pretense_air --- game/game.py | 5 ++- game/pretense/pretenseaircraftgenerator.py | 2 +- game/pretense/pretenseflightgroupspawner.py | 14 ++++++--- game/pretense/pretenseluagenerator.py | 34 ++++++++++----------- 4 files changed, 31 insertions(+), 24 deletions(-) diff --git a/game/game.py b/game/game.py index 4fb68761..47f8cfc4 100644 --- a/game/game.py +++ b/game/game.py @@ -151,7 +151,10 @@ class Game: # Side, control point, mission type self.pretense_ground_supply: dict[int, dict[str, List[str]]] = {1: {}, 2: {}} self.pretense_ground_assault: dict[int, dict[str, List[str]]] = {1: {}, 2: {}} - self.pretense_air: dict[int, dict[str, dict[str, List[str]]]] = {1: {}, 2: {}} + self.pretense_air: dict[int, dict[str, dict[FlightType, List[str]]]] = { + 1: {}, + 2: {}, + } self.on_load(game_still_initializing=True) diff --git a/game/pretense/pretenseaircraftgenerator.py b/game/pretense/pretenseaircraftgenerator.py index 6e78054d..2d4e98ce 100644 --- a/game/pretense/pretenseaircraftgenerator.py +++ b/game/pretense/pretenseaircraftgenerator.py @@ -603,7 +603,7 @@ class PretenseAircraftGenerator: cp: Control point to generate aircraft for. flight: The current flight being generated. """ - flight_type = flight.flight_type.name + flight_type = flight.flight_type cp_name_trimmed = "".join([i for i in cp.name.lower() if i.isalnum()]) for side in range(1, 3): diff --git a/game/pretense/pretenseflightgroupspawner.py b/game/pretense/pretenseflightgroupspawner.py index 5622905b..35e010f8 100644 --- a/game/pretense/pretenseflightgroupspawner.py +++ b/game/pretense/pretenseflightgroupspawner.py @@ -84,7 +84,7 @@ class PretenseFlightGroupSpawner(FlightGroupSpawner): if self.start_type is StartType.IN_FLIGHT: if self.flight.client_count == 0: self.flight.coalition.game.pretense_air[cp_side][cp_name_trimmed][ - self.flight.flight_type.name + self.flight.flight_type ].append(name) group = self._generate_over_departure(name, cp) return group @@ -98,7 +98,7 @@ class PretenseFlightGroupSpawner(FlightGroupSpawner): ) if self.flight.client_count == 0: self.flight.coalition.game.pretense_air[cp_side][cp_name_trimmed][ - self.flight.flight_type.name + self.flight.flight_type ].append(name) return self._generate_at_group(name, carrier_group) elif isinstance(cp, Fob): @@ -120,6 +120,10 @@ class PretenseFlightGroupSpawner(FlightGroupSpawner): f' spawns" setting is currently disabled.' ) if cp.has_helipads and (is_heli or is_vtol): + if self.flight.client_count == 0: + self.flight.coalition.game.pretense_air[cp_side][ + cp_name_trimmed + ][self.flight.flight_type].append(name) pad_group = self._generate_at_cp_helipad(name, cp) if pad_group is not None: return pad_group @@ -129,7 +133,7 @@ class PretenseFlightGroupSpawner(FlightGroupSpawner): return pad_group if self.flight.client_count == 0: self.flight.coalition.game.pretense_air[cp_side][cp_name_trimmed][ - self.flight.flight_type.name + self.flight.flight_type ].append(name) return self._generate_over_departure(name, cp) elif isinstance(cp, Airfield): @@ -150,7 +154,7 @@ class PretenseFlightGroupSpawner(FlightGroupSpawner): return pad_group if self.flight.client_count == 0: self.flight.coalition.game.pretense_air[cp_side][cp_name_trimmed][ - self.flight.flight_type.name + self.flight.flight_type ].append(name) return self._generate_at_airfield(name, cp) else: @@ -208,7 +212,7 @@ class PretenseFlightGroupSpawner(FlightGroupSpawner): self.mission_data.cp_stack[cp] += STACK_SEPARATION self.flight.coalition.game.pretense_air[cp_side][cp_name_trimmed][ - self.flight.flight_type.name + self.flight.flight_type ].append(name) group = self.mission.flight_group( country=self.country, diff --git a/game/pretense/pretenseluagenerator.py b/game/pretense/pretenseluagenerator.py index ec4aff92..336ec5b1 100644 --- a/game/pretense/pretenseluagenerator.py +++ b/game/pretense/pretenseluagenerator.py @@ -80,7 +80,7 @@ class PretenseLuaGenerator(LuaGenerator): + "'}),\n" ) for mission_type in self.game.pretense_air[cp_side][cp_name_trimmed]: - if mission_type == FlightType.AIR_ASSAULT.name: + if mission_type == FlightType.AIR_ASSAULT: mission_name = "supply.helo" for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ mission_type @@ -110,7 +110,7 @@ class PretenseLuaGenerator(LuaGenerator): + "' }),\n" ) for mission_type in self.game.pretense_air[cp_side][cp_name_trimmed]: - if mission_type == FlightType.SEAD.name: + if mission_type == FlightType.SEAD: mission_name = "attack.sead" for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ mission_type @@ -121,7 +121,7 @@ class PretenseLuaGenerator(LuaGenerator): + air_group + "', altitude=25000, expend=AI.Task.WeaponExpend.ALL}),\n" ) - elif mission_type == FlightType.CAS.name: + elif mission_type == FlightType.CAS: mission_name = "attack.cas" for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ mission_type @@ -132,7 +132,7 @@ class PretenseLuaGenerator(LuaGenerator): + air_group + "', altitude=15000, expend=AI.Task.WeaponExpend.ONE}),\n" ) - elif mission_type == FlightType.BAI.name: + elif mission_type == FlightType.BAI: mission_name = "attack.bai" for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ mission_type @@ -143,7 +143,7 @@ class PretenseLuaGenerator(LuaGenerator): + air_group + "', altitude=10000, expend=AI.Task.WeaponExpend.ONE}),\n" ) - elif mission_type == FlightType.STRIKE.name: + elif mission_type == FlightType.STRIKE: mission_name = "attack.strike" for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ mission_type @@ -154,7 +154,7 @@ class PretenseLuaGenerator(LuaGenerator): + air_group + "', altitude=20000, expend=AI.Task.WeaponExpend.ALL}),\n" ) - elif mission_type == FlightType.BARCAP.name: + elif mission_type == FlightType.BARCAP: mission_name = "patrol.aircraft" for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ mission_type @@ -165,7 +165,7 @@ class PretenseLuaGenerator(LuaGenerator): + air_group + "', altitude=25000, range=25}),\n" ) - elif mission_type == FlightType.REFUELING.name: + elif mission_type == FlightType.REFUELING: mission_name = "support.tanker" for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ mission_type @@ -191,7 +191,7 @@ class PretenseLuaGenerator(LuaGenerator): + tanker_variant + "'}),\n" ) - elif mission_type == FlightType.AEWC.name: + elif mission_type == FlightType.AEWC: mission_name = "support.awacs" for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ mission_type @@ -364,7 +364,7 @@ class PretenseLuaGenerator(LuaGenerator): + "'}),\n" ) for mission_type in self.game.pretense_air[cp_side][cp_name_trimmed]: - if mission_type == FlightType.AIR_ASSAULT.name: + if mission_type == FlightType.AIR_ASSAULT: mission_name = "supply.helo" for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ mission_type @@ -388,7 +388,7 @@ class PretenseLuaGenerator(LuaGenerator): + "-sam-blue' }),\n" ) for mission_type in self.game.pretense_air[cp_side][cp_name_trimmed]: - if mission_type == FlightType.SEAD.name: + if mission_type == FlightType.SEAD: mission_name = "attack.sead" for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ mission_type @@ -399,7 +399,7 @@ class PretenseLuaGenerator(LuaGenerator): + air_group + "', altitude=25000, expend=AI.Task.WeaponExpend.ALL}),\n" ) - elif mission_type == FlightType.CAS.name: + elif mission_type == FlightType.CAS: mission_name = "attack.cas" for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ mission_type @@ -410,7 +410,7 @@ class PretenseLuaGenerator(LuaGenerator): + air_group + "', altitude=15000, expend=AI.Task.WeaponExpend.ONE}),\n" ) - elif mission_type == FlightType.BAI.name: + elif mission_type == FlightType.BAI: mission_name = "attack.bai" for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ mission_type @@ -421,7 +421,7 @@ class PretenseLuaGenerator(LuaGenerator): + air_group + "', altitude=10000, expend=AI.Task.WeaponExpend.ONE}),\n" ) - elif mission_type == FlightType.STRIKE.name: + elif mission_type == FlightType.STRIKE: mission_name = "attack.strike" for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ mission_type @@ -432,7 +432,7 @@ class PretenseLuaGenerator(LuaGenerator): + air_group + "', altitude=20000, expend=AI.Task.WeaponExpend.ALL}),\n" ) - elif mission_type == FlightType.BARCAP.name: + elif mission_type == FlightType.BARCAP: mission_name = "patrol.aircraft" for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ mission_type @@ -443,7 +443,7 @@ class PretenseLuaGenerator(LuaGenerator): + air_group + "', altitude=25000, range=25}),\n" ) - elif mission_type == FlightType.REFUELING.name: + elif mission_type == FlightType.REFUELING: mission_name = "support.tanker" for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ mission_type @@ -470,7 +470,7 @@ class PretenseLuaGenerator(LuaGenerator): + tanker_variant + "'}),\n" ) - elif mission_type == FlightType.AEWC.name: + elif mission_type == FlightType.AEWC: mission_name = "support.awacs" for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ mission_type @@ -601,7 +601,7 @@ class PretenseLuaGenerator(LuaGenerator): continue cp_name_trimmed = "".join([i for i in cp.name.lower() if i.isalnum()]) for mission_type in self.game.pretense_air[cp_side][cp_name_trimmed]: - if mission_type == FlightType.PRETENSE_CARGO.name: + if mission_type == FlightType.PRETENSE_CARGO: for air_group in self.game.pretense_air[cp_side][ cp_name_trimmed ][mission_type]: From 1bcecd5b3a33aa18910b1d5d30c28c8d8c9812bd Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Tue, 19 Sep 2023 19:10:36 +0300 Subject: [PATCH 058/243] Fixed not all generated flights getting inserted into Pretense data structures. --- game/pretense/pretenseaircraftgenerator.py | 6 +++--- game/pretense/pretenseflightgroupspawner.py | 12 ++++++++++++ 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/game/pretense/pretenseaircraftgenerator.py b/game/pretense/pretenseaircraftgenerator.py index 2d4e98ce..133c5501 100644 --- a/game/pretense/pretenseaircraftgenerator.py +++ b/game/pretense/pretenseaircraftgenerator.py @@ -556,9 +556,9 @@ class PretenseAircraftGenerator: StartType.COLD, divert=cp, ) - for pilot in flight.roster.pilots: - if pilot is not None: - pilot.player = True + for roster_pilot in flight.roster.pilots: + if roster_pilot is not None: + roster_pilot.player = True print( f"Generated flight for {squadron.primary_task} flying {squadron.aircraft.name} at {squadron.location.name}. Pilot client count: {flight.client_count}" ) diff --git a/game/pretense/pretenseflightgroupspawner.py b/game/pretense/pretenseflightgroupspawner.py index 35e010f8..9ee1a6fb 100644 --- a/game/pretense/pretenseflightgroupspawner.py +++ b/game/pretense/pretenseflightgroupspawner.py @@ -128,6 +128,10 @@ class PretenseFlightGroupSpawner(FlightGroupSpawner): if pad_group is not None: return pad_group if cp.has_ground_spawns and (self.flight.client_count > 0 or is_heli): + if self.flight.client_count == 0: + self.flight.coalition.game.pretense_air[cp_side][ + cp_name_trimmed + ][self.flight.flight_type].append(name) pad_group = self._generate_at_cp_ground_spawn(name, cp) if pad_group is not None: return pad_group @@ -139,6 +143,10 @@ class PretenseFlightGroupSpawner(FlightGroupSpawner): elif isinstance(cp, Airfield): is_heli = self.flight.squadron.aircraft.helicopter if cp.has_helipads and is_heli: + if self.flight.client_count == 0: + self.flight.coalition.game.pretense_air[cp_side][ + cp_name_trimmed + ][self.flight.flight_type].append(name) pad_group = self._generate_at_cp_helipad(name, cp) if pad_group is not None: return pad_group @@ -149,6 +157,10 @@ class PretenseFlightGroupSpawner(FlightGroupSpawner): >= self.flight.count and (self.flight.client_count > 0 or is_heli) ): + if self.flight.client_count == 0: + self.flight.coalition.game.pretense_air[cp_side][ + cp_name_trimmed + ][self.flight.flight_type].append(name) pad_group = self._generate_at_cp_ground_spawn(name, cp) if pad_group is not None: return pad_group From 6f4a12658d7285516afee9460bf622d6e701aeb2 Mon Sep 17 00:00:00 2001 From: Raffson Date: Sat, 23 Sep 2023 19:44:01 +0200 Subject: [PATCH 059/243] Formatting --- game/pretense/pretensemissiongenerator.py | 1 - game/pretense/pretensetriggergenerator.py | 1 - 2 files changed, 2 deletions(-) diff --git a/game/pretense/pretensemissiongenerator.py b/game/pretense/pretensemissiongenerator.py index 2f8942fe..4bb5fe16 100644 --- a/game/pretense/pretensemissiongenerator.py +++ b/game/pretense/pretensemissiongenerator.py @@ -209,7 +209,6 @@ class PretenseMissionGenerator(MissionGenerator): for cp in self.game.theater.controlpoints: for country in (self.p_country, self.e_country): - if country == self.p_country: ato = self.game.blue.ato else: diff --git a/game/pretense/pretensetriggergenerator.py b/game/pretense/pretensetriggergenerator.py index 604abdfc..d01fdf2a 100644 --- a/game/pretense/pretensetriggergenerator.py +++ b/game/pretense/pretensetriggergenerator.py @@ -163,7 +163,6 @@ class PretenseTriggerGenerator: else: 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( cp.position, From fbbc2536a1ea51b704d78f3c02c0413e3397e2f0 Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Sat, 23 Sep 2023 12:59:50 +0300 Subject: [PATCH 060/243] Now generates one transport helicopter squadron for each control point which can operate one. Also implemented generating several Pretense cargo plane squadrons, defined by PRETENSE_AI_CARGO_PLANES_PER_SIDE. Cleaned up PretenseMissionGenerator.generate_air_units() a bit. --- game/ato/flightplans/pretensecargo.py | 10 +- game/pretense/pretenseaircraftgenerator.py | 105 ++++++++++++--------- game/pretense/pretensemissiongenerator.py | 28 ++---- 3 files changed, 75 insertions(+), 68 deletions(-) diff --git a/game/ato/flightplans/pretensecargo.py b/game/ato/flightplans/pretensecargo.py index 9aa3472f..44c1ba02 100644 --- a/game/ato/flightplans/pretensecargo.py +++ b/game/ato/flightplans/pretensecargo.py @@ -1,5 +1,6 @@ from __future__ import annotations +import random from collections.abc import Iterator from dataclasses import dataclass from datetime import timedelta @@ -17,7 +18,8 @@ if TYPE_CHECKING: from ..flightwaypoint import FlightWaypoint -PRETENSE_CARGO_FLIGHT_DISTANCE = 50000 +PRETENSE_CARGO_FLIGHT_DISTANCE = 100000 +PRETENSE_CARGO_FLIGHT_HEADING_RANGE = 20 class PretenseCargoFlightPlan(StandardFlightPlan[FerryLayout]): @@ -67,8 +69,12 @@ class Builder(IBuilder[PretenseCargoFlightPlan, FerryLayout]): offmap_transport_cp = self.coalition.game.theater.find_control_point_by_id( offmap_transport_cp_id ) + offmap_heading = random.randrange( + int(heading_from_flot - PRETENSE_CARGO_FLIGHT_HEADING_RANGE), + int(heading_from_flot + PRETENSE_CARGO_FLIGHT_HEADING_RANGE), + ) offmap_transport_spawn = offmap_transport_cp.position.point_from_heading( - heading_from_flot, PRETENSE_CARGO_FLIGHT_DISTANCE + offmap_heading, PRETENSE_CARGO_FLIGHT_DISTANCE ) altitude_is_agl = self.flight.unit_type.dcs_unit_type.helicopter diff --git a/game/pretense/pretenseaircraftgenerator.py b/game/pretense/pretenseaircraftgenerator.py index 133c5501..89a078f2 100644 --- a/game/pretense/pretenseaircraftgenerator.py +++ b/game/pretense/pretenseaircraftgenerator.py @@ -37,6 +37,8 @@ from game.theater.controlpoint import ( OffMapSpawn, ParkingType, Airfield, + Carrier, + Lha, ) from game.theater.theatergroundobject import EwrGroundObject, SamGroundObject from game.unitmap import UnitMap @@ -47,13 +49,14 @@ if TYPE_CHECKING: PRETENSE_SQUADRON_DEF_RETRIES = 100 -PRETENSE_SEAD_FLIGHTS_PER_CP = 1 -PRETENSE_CAS_FLIGHTS_PER_CP = 1 -PRETENSE_STRIKE_FLIGHTS_PER_CP = 1 -PRETENSE_BARCAP_FLIGHTS_PER_CP = 1 +PRETENSE_SEAD_FLIGHTS_PER_CP = 2 +PRETENSE_CAS_FLIGHTS_PER_CP = 2 +PRETENSE_STRIKE_FLIGHTS_PER_CP = 2 +PRETENSE_BARCAP_FLIGHTS_PER_CP = 2 PRETENSE_AI_AIRCRAFT_PER_FLIGHT = 2 PRETENSE_AI_AWACS_PER_FLIGHT = 1 PRETENSE_AI_TANKERS_PER_FLIGHT = 1 +PRETENSE_AI_CARGO_PLANES_PER_SIDE = 8 PRETENSE_PLAYER_AIRCRAFT_PER_FLIGHT = 2 @@ -138,35 +141,41 @@ class PretenseAircraftGenerator: offmap_transport_cp_id = front_line_cp.id return self.game.theater.find_control_point_by_id(offmap_transport_cp_id) - def should_generate_pretense_transports( - self, air_wing: AirWing - ) -> tuple[bool, bool]: + def should_generate_pretense_transports_at( + self, control_point: ControlPoint + ) -> bool: """ - Returns a tuple of booleans, telling whether transport helicopter and aircraft - squadrons should be generated from the faction squadron definitions. + Returns a boolean, telling whether a transport helicopter squadron + should be generated at control point from the faction squadron definitions. - This helps to ensure that the faction has at least one transport helicopter and one cargo plane squadron. - - (autogenerate_transport_helicopter_squadron, autogenerate_cargo_plane_squadron) + This helps to ensure that the faction has at least one transport helicopter at each control point. """ autogenerate_transport_helicopter_squadron = True - autogenerate_cargo_plane_squadron = True + for squadron in control_point.squadrons: + if squadron.aircraft.helicopter and ( + squadron.aircraft.capable_of(FlightType.TRANSPORT) + or squadron.aircraft.capable_of(FlightType.AIR_ASSAULT) + ): + autogenerate_transport_helicopter_squadron = False + return autogenerate_transport_helicopter_squadron + + def number_of_pretense_cargo_plane_sq_for(self, air_wing: AirWing) -> int: + """ + Returns how many Pretense cargo plane squadrons a specific coalition has. + This is used to define how many such squadrons should be generated from + the faction squadron definitions. + + This helps to ensure that the faction has enough cargo plane squadrons. + """ + number_of_pretense_cargo_plane_squadrons = 0 for aircraft_type in air_wing.squadrons: for squadron in air_wing.squadrons[aircraft_type]: - if squadron.aircraft.helicopter and ( + if not squadron.aircraft.helicopter and ( squadron.aircraft.capable_of(FlightType.TRANSPORT) or squadron.aircraft.capable_of(FlightType.AIR_ASSAULT) ): - autogenerate_transport_helicopter_squadron = False - elif not squadron.aircraft.helicopter and ( - squadron.aircraft.capable_of(FlightType.TRANSPORT) - or squadron.aircraft.capable_of(FlightType.AIR_ASSAULT) - ): - autogenerate_cargo_plane_squadron = False - return ( - autogenerate_transport_helicopter_squadron, - autogenerate_cargo_plane_squadron, - ) + number_of_pretense_cargo_plane_squadrons += 1 + return number_of_pretense_cargo_plane_squadrons def generate_pretense_squadron( self, @@ -375,6 +384,9 @@ class PretenseAircraftGenerator: flight, self.game.settings, self.game.conditions.start_time ) + print( + f"Generated flight for {flight_type} flying {squadron.aircraft.name} at {squadron.location.name}" + ) ato.add_package(package) return @@ -397,7 +409,7 @@ class PretenseAircraftGenerator: """ aircraft_per_flight = 1 - if cp.has_helipads and not cp.is_fleet: + if (cp.has_helipads or isinstance(cp, Airfield)) and not cp.is_fleet: flight_type = FlightType.AIR_ASSAULT squadron = self.generate_pretense_squadron( cp, @@ -586,9 +598,6 @@ class PretenseAircraftGenerator: for side in range(1, 3): if cp_name_trimmed not in cp.coalition.game.pretense_air[side]: cp.coalition.game.pretense_air[side][cp_name_trimmed] = {} - print( - f"Populated flight.coalition.game.pretense_air[{side}][{cp_name_trimmed}]" - ) return def initialize_pretense_data_structures_for_flight( @@ -609,9 +618,6 @@ class PretenseAircraftGenerator: for side in range(1, 3): if cp_name_trimmed not in flight.coalition.game.pretense_air[side]: flight.coalition.game.pretense_air[side][cp_name_trimmed] = {} - print( - f"Populated flight.coalition.game.pretense_air[{side}][{cp_name_trimmed}]" - ) if ( flight_type not in flight.coalition.game.pretense_air[side][cp_name_trimmed] @@ -619,9 +625,6 @@ class PretenseAircraftGenerator: flight.coalition.game.pretense_air[side][cp_name_trimmed][ flight_type ] = list() - print( - f"Populated flight.coalition.game.pretense_air[{side}][{cp_name_trimmed}][{flight_type}]" - ) return def generate_flights( @@ -644,20 +647,24 @@ class PretenseAircraftGenerator: if country == cp.coalition.faction.country: offmap_transport_cp = self.find_pretense_cargo_plane_cp(cp) - ( - autogenerate_transport_helicopter_squadron, - autogenerate_cargo_plane_squadron, - ) = self.should_generate_pretense_transports(cp.coalition.air_wing) - - if autogenerate_transport_helicopter_squadron: + if ( + cp.has_helipads + or isinstance(cp, Airfield) + or isinstance(cp, Carrier) + or isinstance(cp, Lha) + ) and self.should_generate_pretense_transports_at(cp): self.generate_pretense_squadron( - offmap_transport_cp, - offmap_transport_cp.coalition, + cp, + cp.coalition, FlightType.AIR_ASSAULT, False, PRETENSE_SQUADRON_DEF_RETRIES, ) - if autogenerate_cargo_plane_squadron: + num_of_cargo_sq_to_generate = ( + PRETENSE_AI_CARGO_PLANES_PER_SIDE + - self.number_of_pretense_cargo_plane_sq_for(cp.coalition.air_wing) + ) + for i in range(num_of_cargo_sq_to_generate): self.generate_pretense_squadron( offmap_transport_cp, offmap_transport_cp.coalition, @@ -688,14 +695,19 @@ class PretenseAircraftGenerator: ato: AirTaskingOrder, dynamic_runways: Dict[str, RunwayData], ) -> None: - for package in reversed(sorted(ato.packages, key=lambda x: x.time_over_target)): - logging.info(f"Generating package for target: {package.target.name}") + for package in ato.packages: + logging.info( + f"Generating package for target: {package.target.name}, has_players: {package.has_players}" + ) if not package.flights: continue for flight in package.flights: self.initialize_pretense_data_structures_for_flight( flight.departure, flight ) + logging.info( + f"Generating flight in {flight.coalition.faction.name} package {flight.squadron.aircraft} {flight.flight_type} for target: {package.target.name}, departure: {flight.from_cp.name}" + ) if flight.alive: if not flight.squadron.location.runway_is_operational(): @@ -712,6 +724,9 @@ class PretenseAircraftGenerator: flight, country, dynamic_runways ) except NoParkingSlotError: + logging.warning( + f"No room on runway or parking slots for {flight.squadron.aircraft} {flight.flight_type} for target: {package.target.name}. Not generating flight." + ) return self.unit_map.add_aircraft(group, flight) diff --git a/game/pretense/pretensemissiongenerator.py b/game/pretense/pretensemissiongenerator.py index 4bb5fe16..8894aaf3 100644 --- a/game/pretense/pretensemissiongenerator.py +++ b/game/pretense/pretensemissiongenerator.py @@ -46,6 +46,7 @@ from .pretensetgogenerator import PretenseTgoGenerator from .pretensetriggergenerator import PretenseTriggerGenerator from game.missiongenerator.visualsgenerator import VisualsGenerator from ..ato import Flight +from ..ato.airtaaskingorder import AirTaskingOrder from ..missiongenerator import MissionGenerator from ..radio.TacanContainer import TacanContainer @@ -209,32 +210,17 @@ class PretenseMissionGenerator(MissionGenerator): for cp in self.game.theater.controlpoints: for country in (self.p_country, self.e_country): - if country == self.p_country: - ato = self.game.blue.ato - else: - ato = self.game.red.ato - print(f"Running generate_flights for {country.name} at {cp.name}") + ato = AirTaskingOrder() aircraft_generator.generate_flights( country, cp, ato, ) - - ato = self.game.blue.ato - country = self.p_country - aircraft_generator.generate_packages( - country, - ato, - tgo_generator.runways, - ) - - ato = self.game.red.ato - country = self.e_country - aircraft_generator.generate_packages( - country, - ato, - tgo_generator.runways, - ) + aircraft_generator.generate_packages( + country, + ato, + tgo_generator.runways, + ) self.mission_data.flights = aircraft_generator.flights From 8f883d51d7e3b9d122666decebc78533b93dbcf9 Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Sat, 23 Sep 2023 13:00:44 +0300 Subject: [PATCH 061/243] Aircraft squadrons are now products of the Hangar, instead of the air defence Command Center. --- game/pretense/pretenseluagenerator.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/game/pretense/pretenseluagenerator.py b/game/pretense/pretenseluagenerator.py index 336ec5b1..6732da43 100644 --- a/game/pretense/pretenseluagenerator.py +++ b/game/pretense/pretenseluagenerator.py @@ -109,6 +109,15 @@ class PretenseLuaGenerator(LuaGenerator): + cp_side_str + "' }),\n" ) + lua_string_zones += " }\n" + lua_string_zones += " }),\n" + lua_string_zones += " presets.upgrades.supply.hangar:extend({\n" + lua_string_zones += ( + f" name = '{cp_name_trimmed}-aircraft-command-" + + cp_side_str + + "',\n" + ) + lua_string_zones += " products = {\n" for mission_type in self.game.pretense_air[cp_side][cp_name_trimmed]: if mission_type == FlightType.SEAD: mission_name = "attack.sead" From bd4e2d7189698b94cdf40bcc301789822e6436e8 Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Sat, 23 Sep 2023 14:28:48 +0300 Subject: [PATCH 062/243] Copied flightgroupconfigurator.py as a template/inheritance for generating Pretense campaigns from Retribution campaigns. --- .../pretenseflightgroupconfigurator.py | 300 ++++++++++++++++++ 1 file changed, 300 insertions(+) create mode 100644 game/pretense/pretenseflightgroupconfigurator.py diff --git a/game/pretense/pretenseflightgroupconfigurator.py b/game/pretense/pretenseflightgroupconfigurator.py new file mode 100644 index 00000000..aa781cc8 --- /dev/null +++ b/game/pretense/pretenseflightgroupconfigurator.py @@ -0,0 +1,300 @@ +from __future__ import annotations + +import logging +import random +from datetime import datetime +from typing import Any, Optional, TYPE_CHECKING + +from dcs import Mission +from dcs.action import DoScript +from dcs.flyingunit import FlyingUnit +from dcs.task import OptReactOnThreat +from dcs.translation import String +from dcs.triggers import TriggerStart +from dcs.unit import Skill +from dcs.unitgroup import FlyingGroup + +from game.ato import Flight, FlightType +from game.callsigns import callsign_for_support_unit +from game.data.weapons import Pylon, WeaponType as WeaponTypeEnum +from game.missiongenerator.lasercoderegistry import LaserCodeRegistry +from game.missiongenerator.logisticsgenerator import LogisticsGenerator +from game.missiongenerator.missiondata import MissionData, AwacsInfo, TankerInfo +from game.radio.radios import RadioFrequency, RadioRegistry +from game.radio.tacan import ( + TacanBand, + TacanRegistry, + TacanUsage, + OutOfTacanChannelsError, +) +from game.runways import RunwayData +from game.squadrons import Pilot +from .aircraftbehavior import AircraftBehavior +from .aircraftpainter import AircraftPainter +from .flightdata import FlightData +from .waypoints import WaypointGenerator +from ...ato.flightplans.aewc import AewcFlightPlan +from ...ato.flightplans.packagerefueling import PackageRefuelingFlightPlan +from ...ato.flightplans.theaterrefueling import TheaterRefuelingFlightPlan +from ...theater import Fob + +if TYPE_CHECKING: + from game import Game + + +class FlightGroupConfigurator: + def __init__( + self, + flight: Flight, + group: FlyingGroup[Any], + game: Game, + mission: Mission, + time: datetime, + radio_registry: RadioRegistry, + tacan_registry: TacanRegistry, + laser_code_registry: LaserCodeRegistry, + mission_data: MissionData, + dynamic_runways: dict[str, RunwayData], + use_client: bool, + ) -> None: + self.flight = flight + self.group = group + self.game = game + self.mission = mission + self.time = time + self.radio_registry = radio_registry + self.tacan_registry = tacan_registry + self.laser_code_registry = laser_code_registry + self.mission_data = mission_data + self.dynamic_runways = dynamic_runways + self.use_client = use_client + + def configure(self) -> FlightData: + AircraftBehavior(self.flight.flight_type).apply_to(self.flight, self.group) + AircraftPainter(self.flight, self.group).apply_livery() + self.setup_props() + self.setup_payload() + self.setup_fuel() + flight_channel = self.setup_radios() + + laser_codes: list[Optional[int]] = [] + for unit, pilot in zip(self.group.units, self.flight.roster.pilots): + self.configure_flight_member(unit, pilot, laser_codes) + + divert = None + if self.flight.divert is not None: + divert = self.flight.divert.active_runway( + self.game.theater, self.game.conditions, self.dynamic_runways + ) + + if self.flight.flight_type in [ + FlightType.TRANSPORT, + FlightType.AIR_ASSAULT, + ] and self.game.settings.plugin_option("ctld"): + transfer = None + if self.flight.flight_type == FlightType.TRANSPORT: + coalition = self.game.coalition_for(player=self.flight.blue) + transfer = coalition.transfers.transfer_for_flight(self.flight) + self.mission_data.logistics.append( + LogisticsGenerator( + self.flight, self.group, self.mission, self.game.settings, transfer + ).generate_logistics() + ) + + mission_start_time, waypoints = WaypointGenerator( + self.flight, + self.group, + self.mission, + self.game.conditions.start_time, + self.time, + self.game.settings, + self.mission_data, + ).create_waypoints() + + # Special handling for landing waypoints when: + # 1. It's an AI-only flight + # 2. Aircraft are not helicopters/VTOL + # 3. Landing waypoint does not point to an airfield + if ( + self.flight.client_count < 1 + and not self.flight.unit_type.helicopter + and not self.flight.unit_type.lha_capable + and isinstance(self.flight.squadron.location, Fob) + ): + # Need to set uncontrolled to false, otherwise the AI will skip the mission and just land + self.group.uncontrolled = False + + return FlightData( + package=self.flight.package, + aircraft_type=self.flight.unit_type, + squadron=self.flight.squadron, + flight_type=self.flight.flight_type, + units=self.group.units, + size=len(self.group.units), + friendly=self.flight.from_cp.captured, + departure_delay=mission_start_time, + departure=self.flight.departure.active_runway( + self.game.theater, self.game.conditions, self.dynamic_runways + ), + arrival=self.flight.arrival.active_runway( + self.game.theater, self.game.conditions, self.dynamic_runways + ), + divert=divert, + waypoints=waypoints, + intra_flight_channel=flight_channel, + bingo_fuel=self.flight.flight_plan.bingo_fuel, + joker_fuel=self.flight.flight_plan.joker_fuel, + custom_name=self.flight.custom_name, + laser_codes=laser_codes, + ) + + def configure_flight_member( + self, unit: FlyingUnit, pilot: Optional[Pilot], laser_codes: list[Optional[int]] + ) -> None: + player = pilot is not None and pilot.player + self.set_skill(unit, pilot) + if self.flight.loadout.has_weapon_of_type(WeaponTypeEnum.TGP) and player: + laser_codes.append(self.laser_code_registry.get_next_laser_code()) + else: + laser_codes.append(None) + settings = self.flight.coalition.game.settings + if not player or not settings.plugins.get("ewrj"): + return + jammer_required = settings.plugin_option("ewrj.ecm_required") + if jammer_required: + ecm = WeaponTypeEnum.JAMMER + if not self.flight.loadout.has_weapon_of_type(ecm): + return + ewrj_menu_trigger = TriggerStart(comment=f"EWRJ-{unit.name}") + ewrj_menu_trigger.add_action(DoScript(String(f'EWJamming("{unit.name}")'))) + self.mission.triggerrules.triggers.append(ewrj_menu_trigger) + self.group.points[0].tasks[0] = OptReactOnThreat( + OptReactOnThreat.Values.PassiveDefense + ) + + def setup_radios(self) -> RadioFrequency: + freq = self.flight.frequency + if freq is None and (freq := self.flight.package.frequency) is None: + freq = self.radio_registry.alloc_uhf() + self.flight.package.frequency = freq + if freq not in self.radio_registry.allocated_channels: + self.radio_registry.reserve(freq) + + if self.flight.flight_type in {FlightType.AEWC, FlightType.REFUELING}: + self.register_air_support(freq) + elif self.flight.client_count and self.flight.squadron.radio_presets: + freq = self.flight.squadron.radio_presets["intra_flight"][0] + elif self.flight.frequency is None and self.flight.client_count: + freq = self.flight.unit_type.alloc_flight_radio(self.radio_registry) + + self.group.set_frequency(freq.mhz) + return freq + + def register_air_support(self, channel: RadioFrequency) -> None: + callsign = callsign_for_support_unit(self.group) + if isinstance(self.flight.flight_plan, AewcFlightPlan): + self.mission_data.awacs.append( + AwacsInfo( + group_name=str(self.group.name), + callsign=callsign, + freq=channel, + depature_location=self.flight.departure.name, + start_time=self.flight.flight_plan.patrol_start_time, + end_time=self.flight.flight_plan.patrol_end_time, + blue=self.flight.departure.captured, + ) + ) + elif isinstance( + self.flight.flight_plan, TheaterRefuelingFlightPlan + ) or isinstance(self.flight.flight_plan, PackageRefuelingFlightPlan): + tacan = self.flight.tacan + if tacan is None and self.flight.squadron.aircraft.dcs_unit_type.tacan: + try: + tacan = self.tacan_registry.alloc_for_band( + TacanBand.Y, TacanUsage.AirToAir + ) + except OutOfTacanChannelsError: + tacan = random.choice(list(self.tacan_registry.allocated_channels)) + else: + tacan = self.flight.tacan + self.mission_data.tankers.append( + TankerInfo( + group_name=str(self.group.name), + callsign=callsign, + variant=self.flight.unit_type.name, + freq=channel, + tacan=tacan, + start_time=self.flight.flight_plan.patrol_start_time, + end_time=self.flight.flight_plan.patrol_end_time, + blue=self.flight.departure.captured, + ) + ) + + def set_skill(self, unit: FlyingUnit, pilot: Optional[Pilot]) -> None: + if pilot is None or not pilot.player: + unit.skill = self.skill_level_for(unit, pilot) + return + + if self.use_client or "Pilot #1" not in unit.name: + unit.set_client() + else: + unit.set_player() + + def skill_level_for(self, unit: FlyingUnit, pilot: Optional[Pilot]) -> Skill: + if self.flight.squadron.player: + base_skill = Skill(self.game.settings.player_skill) + else: + base_skill = Skill(self.game.settings.enemy_skill) + + if pilot is None: + logging.error(f"Cannot determine skill level: {unit.name} has not pilot") + return base_skill + + levels = [ + Skill.Average, + Skill.Good, + Skill.High, + Skill.Excellent, + ] + current_level = levels.index(base_skill) + missions_for_skill_increase = 4 + increase = pilot.record.missions_flown // missions_for_skill_increase + capped_increase = min(current_level + increase, len(levels) - 1) + + if self.game.settings.ai_pilot_levelling: + new_level = capped_increase + else: + new_level = current_level + + return levels[new_level] + + def setup_props(self) -> None: + for prop_id, value in self.flight.props.items(): + for unit in self.group.units: + unit.set_property(prop_id, value) + + def setup_payload(self) -> None: + for p in self.group.units: + p.pylons.clear() + + loadout = self.flight.loadout + if self.game.settings.restrict_weapons_by_date: + loadout = loadout.degrade_for_date(self.flight.unit_type, self.game.date) + + for pylon_number, weapon in loadout.pylons.items(): + if weapon is None: + continue + pylon = Pylon.for_aircraft(self.flight.unit_type, pylon_number) + pylon.equip(self.group, weapon) + + def setup_fuel(self) -> None: + fuel = self.flight.state.estimate_fuel() + if fuel < 0: + logging.warning( + f"Flight {self.flight} is estimated to have no fuel at mission start. " + "This estimate does not account for external fuel tanks. Setting " + "starting fuel to 100kg." + ) + fuel = 100 + for unit, pilot in zip(self.group.units, self.flight.roster.pilots): + unit.fuel = fuel From 9d9740cdb3faec33edc8c7d46ef3e1da81a8e3f0 Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Sat, 23 Sep 2023 16:17:36 +0300 Subject: [PATCH 063/243] Added missing generation of AI BAI flights. Player flight groups are now generated as single-ship groups to support the Pretense missions for all players. Moved repetitive code in PretenseFlightGroupSpawner to insert_into_pretense() method. Disabled spawning of air assault statics by PretenseFlightGroupConfigurator since they are unnecessary in Pretense. --- game/pretense/pretenseaircraftgenerator.py | 216 +++++++++--------- .../pretenseflightgroupconfigurator.py | 214 +++-------------- game/pretense/pretenseflightgroupspawner.py | 72 +++--- 3 files changed, 156 insertions(+), 346 deletions(-) diff --git a/game/pretense/pretenseaircraftgenerator.py b/game/pretense/pretenseaircraftgenerator.py index 89a078f2..5157e3c6 100644 --- a/game/pretense/pretenseaircraftgenerator.py +++ b/game/pretense/pretenseaircraftgenerator.py @@ -22,11 +22,11 @@ from game.coalition import Coalition from game.data.weapons import WeaponType from game.dcs.aircrafttype import AircraftType from game.missiongenerator.aircraft.flightdata import FlightData -from game.missiongenerator.aircraft.flightgroupconfigurator import ( - FlightGroupConfigurator, -) from game.missiongenerator.lasercoderegistry import LaserCodeRegistry from game.missiongenerator.missiondata import MissionData +from game.pretense.pretenseflightgroupconfigurator import ( + PretenseFlightGroupConfigurator, +) from game.radio.radios import RadioRegistry from game.radio.tacan import TacanRegistry from game.runways import RunwayData @@ -51,13 +51,15 @@ if TYPE_CHECKING: PRETENSE_SQUADRON_DEF_RETRIES = 100 PRETENSE_SEAD_FLIGHTS_PER_CP = 2 PRETENSE_CAS_FLIGHTS_PER_CP = 2 +PRETENSE_BAI_FLIGHTS_PER_CP = 2 PRETENSE_STRIKE_FLIGHTS_PER_CP = 2 PRETENSE_BARCAP_FLIGHTS_PER_CP = 2 PRETENSE_AI_AIRCRAFT_PER_FLIGHT = 2 PRETENSE_AI_AWACS_PER_FLIGHT = 1 PRETENSE_AI_TANKERS_PER_FLIGHT = 1 PRETENSE_AI_CARGO_PLANES_PER_SIDE = 8 -PRETENSE_PLAYER_AIRCRAFT_PER_FLIGHT = 2 +PRETENSE_PLAYER_AIRCRAFT_PER_FLIGHT = 1 +PRETENSE_PLAYER_FLIGHTS_PER_TYPE = 2 class PretenseAircraftGenerator: @@ -287,6 +289,7 @@ class PretenseAircraftGenerator: """ num_of_sead = 0 num_of_cas = 0 + num_of_bai = 0 num_of_strike = 0 num_of_cap = 0 @@ -299,6 +302,7 @@ class PretenseAircraftGenerator: squadron.owned_aircraft += PRETENSE_AI_AIRCRAFT_PER_FLIGHT squadron.untasked_aircraft += PRETENSE_AI_AIRCRAFT_PER_FLIGHT + squadron.populate_for_turn_0(False) package = Package(cp, squadron.flight_db, auto_asap=False) mission_types = squadron.auto_assignable_mission_types aircraft_per_flight = 1 @@ -328,11 +332,17 @@ class PretenseAircraftGenerator: num_of_sead += 1 aircraft_per_flight = PRETENSE_AI_AIRCRAFT_PER_FLIGHT elif ( - FlightType.CAS in mission_types or FlightType.BAI in mission_types + FlightType.CAS in mission_types ) and num_of_cas < PRETENSE_CAS_FLIGHTS_PER_CP: flight_type = FlightType.CAS num_of_cas += 1 aircraft_per_flight = PRETENSE_AI_AIRCRAFT_PER_FLIGHT + elif ( + FlightType.BAI in mission_types + ) and num_of_bai < PRETENSE_BAI_FLIGHTS_PER_CP: + flight_type = FlightType.BAI + num_of_bai += 1 + aircraft_per_flight = PRETENSE_AI_AIRCRAFT_PER_FLIGHT elif ( FlightType.STRIKE in mission_types or FlightType.OCA_RUNWAY in mission_types @@ -421,6 +431,7 @@ class PretenseAircraftGenerator: if squadron is not None: squadron.owned_aircraft += PRETENSE_AI_AIRCRAFT_PER_FLIGHT squadron.untasked_aircraft += PRETENSE_AI_AIRCRAFT_PER_FLIGHT + squadron.populate_for_turn_0(False) package = Package(cp, squadron.flight_db, auto_asap=False) flight = Flight( package, @@ -461,6 +472,7 @@ class PretenseAircraftGenerator: if squadron is not None: squadron.owned_aircraft += PRETENSE_AI_AIRCRAFT_PER_FLIGHT squadron.untasked_aircraft += PRETENSE_AI_AIRCRAFT_PER_FLIGHT + squadron.populate_for_turn_0(False) package = Package(cp, squadron.flight_db, auto_asap=False) flight = Flight( package, @@ -501,6 +513,7 @@ class PretenseAircraftGenerator: if squadron is not None: squadron.owned_aircraft += PRETENSE_AI_AIRCRAFT_PER_FLIGHT squadron.untasked_aircraft += PRETENSE_AI_AIRCRAFT_PER_FLIGHT + squadron.populate_for_turn_0(False) package = Package(cp, squadron.flight_db, auto_asap=False) flight = Flight( package, @@ -548,38 +561,39 @@ class PretenseAircraftGenerator: if not cp.can_operate(aircraft_type): continue - squadron = self.generate_pretense_squadron_for( - aircraft_type, - cp, - coalition, - ) - if squadron is not None: - squadron.owned_aircraft += PRETENSE_PLAYER_AIRCRAFT_PER_FLIGHT - squadron.untasked_aircraft += PRETENSE_PLAYER_AIRCRAFT_PER_FLIGHT - squadron.populate_for_turn_0(False) - for pilot in squadron.pilot_pool: - pilot.player = True - package = Package(cp, squadron.flight_db, auto_asap=False) - flight = Flight( - package, - squadron, - aircraft_per_flight, - squadron.primary_task, - StartType.COLD, - divert=cp, - ) - for roster_pilot in flight.roster.pilots: - if roster_pilot is not None: - roster_pilot.player = True - print( - f"Generated flight for {squadron.primary_task} flying {squadron.aircraft.name} at {squadron.location.name}. Pilot client count: {flight.client_count}" + for i in range(PRETENSE_PLAYER_FLIGHTS_PER_TYPE): + squadron = self.generate_pretense_squadron_for( + aircraft_type, + cp, + coalition, ) + if squadron is not None: + squadron.owned_aircraft += PRETENSE_PLAYER_AIRCRAFT_PER_FLIGHT + squadron.untasked_aircraft += PRETENSE_PLAYER_AIRCRAFT_PER_FLIGHT + squadron.populate_for_turn_0(False) + for pilot in squadron.pilot_pool: + pilot.player = True + package = Package(cp, squadron.flight_db, auto_asap=False) + flight = Flight( + package, + squadron, + aircraft_per_flight, + squadron.primary_task, + StartType.COLD, + divert=cp, + ) + for roster_pilot in flight.roster.pilots: + if roster_pilot is not None: + roster_pilot.player = True + print( + f"Generated flight for {squadron.primary_task} flying {squadron.aircraft.name} at {squadron.location.name}. Pilot client count: {flight.client_count}" + ) - package.add_flight(flight) - flight.state = WaitingForStart( - flight, self.game.settings, self.game.conditions.start_time - ) - ato.add_package(package) + package.add_flight(flight) + flight.state = WaitingForStart( + flight, self.game.settings, self.game.conditions.start_time + ) + ato.add_package(package) return @@ -747,86 +761,62 @@ class PretenseAircraftGenerator: self.ground_spawns, self.mission_data, ).create_flight_group() - if flight.flight_type in [ - FlightType.REFUELING, - FlightType.AEWC, - ]: - self.flights.append( - FlightGroupConfigurator( - flight, - group, - self.game, - self.mission, - self.time, - self.radio_registry, - self.tacan_registy, - self.laser_code_registry, - self.mission_data, - dynamic_runways, - self.use_client, - ).configure() - ) - else: - if flight.client_count > 0: - if flight.flight_type == FlightType.CAS: - for conflict in self.game.theater.conflicts(): - flight.package.target = conflict - break - elif ( - flight.flight_type == FlightType.STRIKE - or flight.flight_type == FlightType.BAI - ): - for cp in self.game.theater.closest_opposing_control_points(): - if cp.coalition == flight.coalition: - continue - for mission_target in cp.ground_objects: - flight.package.target = mission_target - elif ( - flight.flight_type == FlightType.OCA_RUNWAY - or flight.flight_type == FlightType.OCA_AIRCRAFT - ): - for cp in self.game.theater.controlpoints: - if cp.coalition == flight.coalition or not isinstance( - cp, Airfield - ): - continue - flight.package.target = cp - elif flight.flight_type == FlightType.DEAD: - for cp in self.game.theater.controlpoints: - if cp.coalition == flight.coalition: - continue - for ground_object in cp.ground_objects: - is_ewr = isinstance(ground_object, EwrGroundObject) - is_sam = isinstance(ground_object, SamGroundObject) + if flight.flight_type == FlightType.CAS: + for conflict in self.game.theater.conflicts(): + flight.package.target = conflict + break + elif ( + flight.flight_type == FlightType.STRIKE + or flight.flight_type == FlightType.BAI + ): + for cp in self.game.theater.closest_opposing_control_points(): + if cp.coalition == flight.coalition: + continue + for mission_target in cp.ground_objects: + flight.package.target = mission_target + elif ( + flight.flight_type == FlightType.OCA_RUNWAY + or flight.flight_type == FlightType.OCA_AIRCRAFT + ): + for cp in self.game.theater.controlpoints: + if cp.coalition == flight.coalition or not isinstance(cp, Airfield): + continue + flight.package.target = cp + elif flight.flight_type == FlightType.DEAD: + for cp in self.game.theater.controlpoints: + if cp.coalition == flight.coalition: + continue + for ground_object in cp.ground_objects: + is_ewr = isinstance(ground_object, EwrGroundObject) + is_sam = isinstance(ground_object, SamGroundObject) - if is_ewr or is_sam: - flight.package.target = ground_object - elif flight.flight_type == FlightType.AIR_ASSAULT: - for cp in self.game.theater.closest_opposing_control_points(): - if cp.coalition == flight.coalition: - continue - if flight.is_hercules: - if cp.coalition == flight.coalition or not isinstance( - cp, Airfield - ): - continue - flight.package.target = cp + if is_ewr or is_sam: + flight.package.target = ground_object + elif flight.flight_type == FlightType.AIR_ASSAULT: + for cp in self.game.theater.closest_opposing_control_points(): + if cp.coalition == flight.coalition: + continue + if flight.is_hercules: + if cp.coalition == flight.coalition or not isinstance(cp, Airfield): + continue + flight.package.target = cp - FlightGroupConfigurator( - flight, - group, - self.game, - self.mission, - self.time, - self.radio_registry, - self.tacan_registy, - self.laser_code_registry, - self.mission_data, - dynamic_runways, - self.use_client, - ).configure() - # for unit in group.units: - # unit.set_client() + logging.info( + f"Configuring flight {group.name} {flight.squadron.aircraft} {flight.flight_type}, number of players: {flight.client_count}" + ) + PretenseFlightGroupConfigurator( + flight, + group, + self.game, + self.mission, + self.time, + self.radio_registry, + self.tacan_registy, + self.laser_code_registry, + self.mission_data, + dynamic_runways, + self.use_client, + ).configure() if self.ewrj: self._track_ewrj_flight(flight, group) diff --git a/game/pretense/pretenseflightgroupconfigurator.py b/game/pretense/pretenseflightgroupconfigurator.py index aa781cc8..a0f4616c 100644 --- a/game/pretense/pretenseflightgroupconfigurator.py +++ b/game/pretense/pretenseflightgroupconfigurator.py @@ -1,48 +1,37 @@ from __future__ import annotations import logging -import random from datetime import datetime from typing import Any, Optional, TYPE_CHECKING from dcs import Mission -from dcs.action import DoScript from dcs.flyingunit import FlyingUnit -from dcs.task import OptReactOnThreat -from dcs.translation import String -from dcs.triggers import TriggerStart from dcs.unit import Skill from dcs.unitgroup import FlyingGroup -from game.ato import Flight, FlightType -from game.callsigns import callsign_for_support_unit -from game.data.weapons import Pylon, WeaponType as WeaponTypeEnum +from game.ato import Flight +from game.missiongenerator.aircraft.flightgroupconfigurator import ( + FlightGroupConfigurator, +) from game.missiongenerator.lasercoderegistry import LaserCodeRegistry -from game.missiongenerator.logisticsgenerator import LogisticsGenerator -from game.missiongenerator.missiondata import MissionData, AwacsInfo, TankerInfo -from game.radio.radios import RadioFrequency, RadioRegistry +from game.missiongenerator.missiondata import MissionData +from game.radio.radios import RadioRegistry from game.radio.tacan import ( - TacanBand, TacanRegistry, - TacanUsage, - OutOfTacanChannelsError, ) from game.runways import RunwayData from game.squadrons import Pilot -from .aircraftbehavior import AircraftBehavior -from .aircraftpainter import AircraftPainter -from .flightdata import FlightData -from .waypoints import WaypointGenerator -from ...ato.flightplans.aewc import AewcFlightPlan -from ...ato.flightplans.packagerefueling import PackageRefuelingFlightPlan -from ...ato.flightplans.theaterrefueling import TheaterRefuelingFlightPlan -from ...theater import Fob +from game.missiongenerator.aircraft.aircraftbehavior import AircraftBehavior +from game.missiongenerator.aircraft.aircraftpainter import AircraftPainter +from game.missiongenerator.aircraft.flightdata import FlightData +from game.missiongenerator.aircraft.waypoints import WaypointGenerator +from game.theater import Fob if TYPE_CHECKING: from game import Game -class FlightGroupConfigurator: +class PretenseFlightGroupConfigurator(FlightGroupConfigurator): def __init__( self, flight: Flight, @@ -57,6 +46,20 @@ class FlightGroupConfigurator: dynamic_runways: dict[str, RunwayData], use_client: bool, ) -> None: + super().__init__( + flight, + group, + game, + mission, + time, + radio_registry, + tacan_registry, + laser_code_registry, + mission_data, + dynamic_runways, + use_client, + ) + self.flight = flight self.group = group self.game = game @@ -87,20 +90,6 @@ class FlightGroupConfigurator: self.game.theater, self.game.conditions, self.dynamic_runways ) - if self.flight.flight_type in [ - FlightType.TRANSPORT, - FlightType.AIR_ASSAULT, - ] and self.game.settings.plugin_option("ctld"): - transfer = None - if self.flight.flight_type == FlightType.TRANSPORT: - coalition = self.game.coalition_for(player=self.flight.blue) - transfer = coalition.transfers.transfer_for_flight(self.flight) - self.mission_data.logistics.append( - LogisticsGenerator( - self.flight, self.group, self.mission, self.game.settings, transfer - ).generate_logistics() - ) - mission_start_time, waypoints = WaypointGenerator( self.flight, self.group, @@ -147,154 +136,3 @@ class FlightGroupConfigurator: custom_name=self.flight.custom_name, laser_codes=laser_codes, ) - - def configure_flight_member( - self, unit: FlyingUnit, pilot: Optional[Pilot], laser_codes: list[Optional[int]] - ) -> None: - player = pilot is not None and pilot.player - self.set_skill(unit, pilot) - if self.flight.loadout.has_weapon_of_type(WeaponTypeEnum.TGP) and player: - laser_codes.append(self.laser_code_registry.get_next_laser_code()) - else: - laser_codes.append(None) - settings = self.flight.coalition.game.settings - if not player or not settings.plugins.get("ewrj"): - return - jammer_required = settings.plugin_option("ewrj.ecm_required") - if jammer_required: - ecm = WeaponTypeEnum.JAMMER - if not self.flight.loadout.has_weapon_of_type(ecm): - return - ewrj_menu_trigger = TriggerStart(comment=f"EWRJ-{unit.name}") - ewrj_menu_trigger.add_action(DoScript(String(f'EWJamming("{unit.name}")'))) - self.mission.triggerrules.triggers.append(ewrj_menu_trigger) - self.group.points[0].tasks[0] = OptReactOnThreat( - OptReactOnThreat.Values.PassiveDefense - ) - - def setup_radios(self) -> RadioFrequency: - freq = self.flight.frequency - if freq is None and (freq := self.flight.package.frequency) is None: - freq = self.radio_registry.alloc_uhf() - self.flight.package.frequency = freq - if freq not in self.radio_registry.allocated_channels: - self.radio_registry.reserve(freq) - - if self.flight.flight_type in {FlightType.AEWC, FlightType.REFUELING}: - self.register_air_support(freq) - elif self.flight.client_count and self.flight.squadron.radio_presets: - freq = self.flight.squadron.radio_presets["intra_flight"][0] - elif self.flight.frequency is None and self.flight.client_count: - freq = self.flight.unit_type.alloc_flight_radio(self.radio_registry) - - self.group.set_frequency(freq.mhz) - return freq - - def register_air_support(self, channel: RadioFrequency) -> None: - callsign = callsign_for_support_unit(self.group) - if isinstance(self.flight.flight_plan, AewcFlightPlan): - self.mission_data.awacs.append( - AwacsInfo( - group_name=str(self.group.name), - callsign=callsign, - freq=channel, - depature_location=self.flight.departure.name, - start_time=self.flight.flight_plan.patrol_start_time, - end_time=self.flight.flight_plan.patrol_end_time, - blue=self.flight.departure.captured, - ) - ) - elif isinstance( - self.flight.flight_plan, TheaterRefuelingFlightPlan - ) or isinstance(self.flight.flight_plan, PackageRefuelingFlightPlan): - tacan = self.flight.tacan - if tacan is None and self.flight.squadron.aircraft.dcs_unit_type.tacan: - try: - tacan = self.tacan_registry.alloc_for_band( - TacanBand.Y, TacanUsage.AirToAir - ) - except OutOfTacanChannelsError: - tacan = random.choice(list(self.tacan_registry.allocated_channels)) - else: - tacan = self.flight.tacan - self.mission_data.tankers.append( - TankerInfo( - group_name=str(self.group.name), - callsign=callsign, - variant=self.flight.unit_type.name, - freq=channel, - tacan=tacan, - start_time=self.flight.flight_plan.patrol_start_time, - end_time=self.flight.flight_plan.patrol_end_time, - blue=self.flight.departure.captured, - ) - ) - - def set_skill(self, unit: FlyingUnit, pilot: Optional[Pilot]) -> None: - if pilot is None or not pilot.player: - unit.skill = self.skill_level_for(unit, pilot) - return - - if self.use_client or "Pilot #1" not in unit.name: - unit.set_client() - else: - unit.set_player() - - def skill_level_for(self, unit: FlyingUnit, pilot: Optional[Pilot]) -> Skill: - if self.flight.squadron.player: - base_skill = Skill(self.game.settings.player_skill) - else: - base_skill = Skill(self.game.settings.enemy_skill) - - if pilot is None: - logging.error(f"Cannot determine skill level: {unit.name} has not pilot") - return base_skill - - levels = [ - Skill.Average, - Skill.Good, - Skill.High, - Skill.Excellent, - ] - current_level = levels.index(base_skill) - missions_for_skill_increase = 4 - increase = pilot.record.missions_flown // missions_for_skill_increase - capped_increase = min(current_level + increase, len(levels) - 1) - - if self.game.settings.ai_pilot_levelling: - new_level = capped_increase - else: - new_level = current_level - - return levels[new_level] - - def setup_props(self) -> None: - for prop_id, value in self.flight.props.items(): - for unit in self.group.units: - unit.set_property(prop_id, value) - - def setup_payload(self) -> None: - for p in self.group.units: - p.pylons.clear() - - loadout = self.flight.loadout - if self.game.settings.restrict_weapons_by_date: - loadout = loadout.degrade_for_date(self.flight.unit_type, self.game.date) - - for pylon_number, weapon in loadout.pylons.items(): - if weapon is None: - continue - pylon = Pylon.for_aircraft(self.flight.unit_type, pylon_number) - pylon.equip(self.group, weapon) - - def setup_fuel(self) -> None: - fuel = self.flight.state.estimate_fuel() - if fuel < 0: - logging.warning( - f"Flight {self.flight} is estimated to have no fuel at mission start. " - "This estimate does not account for external fuel tanks. Setting " - "starting fuel to 100kg." - ) - fuel = 100 - for unit, pilot in zip(self.group.units, self.flight.roster.pilots): - unit.fuel = fuel diff --git a/game/pretense/pretenseflightgroupspawner.py b/game/pretense/pretenseflightgroupspawner.py index 9ee1a6fb..4ddbb6e8 100644 --- a/game/pretense/pretenseflightgroupspawner.py +++ b/game/pretense/pretenseflightgroupspawner.py @@ -68,6 +68,22 @@ class PretenseFlightGroupSpawner(FlightGroupSpawner): self.ground_spawns = ground_spawns self.mission_data = mission_data + def insert_into_pretense(self, name: str) -> None: + cp = self.flight.departure + is_player = True + cp_side = ( + 2 + if self.flight.coalition + == self.flight.coalition.game.coalition_for(is_player) + else 1 + ) + cp_name_trimmed = "".join([i for i in cp.name.lower() if i.isalnum()]) + + if self.flight.client_count == 0: + self.flight.coalition.game.pretense_air[cp_side][cp_name_trimmed][ + self.flight.flight_type + ].append(name) + def generate_flight_at_departure(self) -> FlyingGroup[Any]: cp = self.flight.departure name = namegen.next_pretense_aircraft_name(cp, self.flight) @@ -82,10 +98,7 @@ class PretenseFlightGroupSpawner(FlightGroupSpawner): try: if self.start_type is StartType.IN_FLIGHT: - if self.flight.client_count == 0: - self.flight.coalition.game.pretense_air[cp_side][cp_name_trimmed][ - self.flight.flight_type - ].append(name) + self.insert_into_pretense(name) group = self._generate_over_departure(name, cp) return group elif isinstance(cp, NavalControlPoint): @@ -96,10 +109,7 @@ class PretenseFlightGroupSpawner(FlightGroupSpawner): f"Carrier group {carrier_group} is a " f"{carrier_group.__class__.__name__}, expected a ShipGroup" ) - if self.flight.client_count == 0: - self.flight.coalition.game.pretense_air[cp_side][cp_name_trimmed][ - self.flight.flight_type - ].append(name) + self.insert_into_pretense(name) return self._generate_at_group(name, carrier_group) elif isinstance(cp, Fob): is_heli = self.flight.squadron.aircraft.helicopter @@ -120,33 +130,21 @@ class PretenseFlightGroupSpawner(FlightGroupSpawner): f' spawns" setting is currently disabled.' ) if cp.has_helipads and (is_heli or is_vtol): - if self.flight.client_count == 0: - self.flight.coalition.game.pretense_air[cp_side][ - cp_name_trimmed - ][self.flight.flight_type].append(name) + self.insert_into_pretense(name) pad_group = self._generate_at_cp_helipad(name, cp) if pad_group is not None: return pad_group if cp.has_ground_spawns and (self.flight.client_count > 0 or is_heli): - if self.flight.client_count == 0: - self.flight.coalition.game.pretense_air[cp_side][ - cp_name_trimmed - ][self.flight.flight_type].append(name) + self.insert_into_pretense(name) pad_group = self._generate_at_cp_ground_spawn(name, cp) if pad_group is not None: return pad_group - if self.flight.client_count == 0: - self.flight.coalition.game.pretense_air[cp_side][cp_name_trimmed][ - self.flight.flight_type - ].append(name) + self.insert_into_pretense(name) return self._generate_over_departure(name, cp) elif isinstance(cp, Airfield): is_heli = self.flight.squadron.aircraft.helicopter if cp.has_helipads and is_heli: - if self.flight.client_count == 0: - self.flight.coalition.game.pretense_air[cp_side][ - cp_name_trimmed - ][self.flight.flight_type].append(name) + self.insert_into_pretense(name) pad_group = self._generate_at_cp_helipad(name, cp) if pad_group is not None: return pad_group @@ -157,17 +155,11 @@ class PretenseFlightGroupSpawner(FlightGroupSpawner): >= self.flight.count and (self.flight.client_count > 0 or is_heli) ): - if self.flight.client_count == 0: - self.flight.coalition.game.pretense_air[cp_side][ - cp_name_trimmed - ][self.flight.flight_type].append(name) + self.insert_into_pretense(name) pad_group = self._generate_at_cp_ground_spawn(name, cp) if pad_group is not None: return pad_group - if self.flight.client_count == 0: - self.flight.coalition.game.pretense_air[cp_side][cp_name_trimmed][ - self.flight.flight_type - ].append(name) + self.insert_into_pretense(name) return self._generate_at_airfield(name, cp) else: raise NotImplementedError( @@ -186,22 +178,14 @@ class PretenseFlightGroupSpawner(FlightGroupSpawner): "No room on runway or parking slots. Starting from the air." ) self.flight.start_type = StartType.IN_FLIGHT + self.insert_into_pretense(name) group = self._generate_over_departure(name, cp) return group def generate_mid_mission(self) -> FlyingGroup[Any]: assert isinstance(self.flight.state, InFlight) name = namegen.next_pretense_aircraft_name(self.flight.departure, self.flight) - is_player = True - cp_side = ( - 2 - if self.flight.coalition - == self.flight.coalition.game.coalition_for(is_player) - else 1 - ) - cp_name_trimmed = "".join( - [i for i in self.flight.departure.name.lower() if i.isalnum()] - ) + speed = self.flight.state.estimate_speed() pos = self.flight.state.estimate_position() pos += Vector2(random.randint(100, 1000), random.randint(100, 1000)) @@ -223,9 +207,7 @@ class PretenseFlightGroupSpawner(FlightGroupSpawner): alt = self.mission_data.cp_stack[cp] self.mission_data.cp_stack[cp] += STACK_SEPARATION - self.flight.coalition.game.pretense_air[cp_side][cp_name_trimmed][ - self.flight.flight_type - ].append(name) + self.insert_into_pretense(name) group = self.mission.flight_group( country=self.country, name=name, From d1b8e20fdecc56399ba21872639fe2cf6b8d22c3 Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Sat, 23 Sep 2023 16:20:49 +0300 Subject: [PATCH 064/243] Replaced the air defence Command Center with a bunker for SHORADs. Will use the Command Center in the future for medium/long range SAMs. --- game/pretense/pretenseluagenerator.py | 2 +- resources/plugins/pretense/init_header.lua | 6 ++++++ resources/plugins/pretense/pretense_compiled.lua | 2 ++ 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/game/pretense/pretenseluagenerator.py b/game/pretense/pretenseluagenerator.py index 6732da43..3e616357 100644 --- a/game/pretense/pretenseluagenerator.py +++ b/game/pretense/pretenseluagenerator.py @@ -93,7 +93,7 @@ class PretenseLuaGenerator(LuaGenerator): ) lua_string_zones += " }\n" lua_string_zones += " }),\n" - lua_string_zones += " presets.upgrades.airdef.comCenter:extend({\n" + lua_string_zones += " presets.upgrades.airdef.bunker:extend({\n" lua_string_zones += ( f" name = '{cp_name_trimmed}-mission-command-" + cp_side_str diff --git a/resources/plugins/pretense/init_header.lua b/resources/plugins/pretense/init_header.lua index 850bfbc7..12f84597 100644 --- a/resources/plugins/pretense/init_header.lua +++ b/resources/plugins/pretense/init_header.lua @@ -464,6 +464,12 @@ presets = { }) }, airdef = { + bunker = Preset:new({ + display = 'Bunker', + cost = 1500, + type = 'upgrade', + template = "bunker-1" + }), comCenter = Preset:new({ display = 'Command Center', cost = 2500, diff --git a/resources/plugins/pretense/pretense_compiled.lua b/resources/plugins/pretense/pretense_compiled.lua index 0a8d7be1..11a3a8b1 100644 --- a/resources/plugins/pretense/pretense_compiled.lua +++ b/resources/plugins/pretense/pretense_compiled.lua @@ -7524,6 +7524,8 @@ do TemplateDB.templates["tv-tower"] = { type="TV tower", category="Fortifications", shape="tele_bash", dataCategory=TemplateDB.type.static } + TemplateDB.templates["bunker-1"] = { type="Sandbox", category="Fortifications", dataCategory=TemplateDB.type.static } + TemplateDB.templates["command-center"] = { type=".Command Center", category="Fortifications", shape="ComCenter", dataCategory=TemplateDB.type.static } TemplateDB.templates["military-staff"] = { type="Military staff", category="Fortifications", shape="aviashtab", dataCategory=TemplateDB.type.static } From a60d1dce19ac188a0d5d724d5f96ffd137807c0e Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Sat, 23 Sep 2023 18:15:11 +0300 Subject: [PATCH 065/243] Pretense SEAD flights will use the SEAD Sweep loadout, when available. --- .../pretenseflightgroupconfigurator.py | 34 ++++++++++++------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/game/pretense/pretenseflightgroupconfigurator.py b/game/pretense/pretenseflightgroupconfigurator.py index a0f4616c..b6a6c7a6 100644 --- a/game/pretense/pretenseflightgroupconfigurator.py +++ b/game/pretense/pretenseflightgroupconfigurator.py @@ -9,7 +9,8 @@ from dcs.flyingunit import FlyingUnit from dcs.unit import Skill from dcs.unitgroup import FlyingGroup -from game.ato import Flight +from game.ato import Flight, FlightType +from game.data.weapons import Pylon from game.missiongenerator.aircraft.flightgroupconfigurator import ( FlightGroupConfigurator, ) @@ -100,18 +101,7 @@ class PretenseFlightGroupConfigurator(FlightGroupConfigurator): self.mission_data, ).create_waypoints() - # Special handling for landing waypoints when: - # 1. It's an AI-only flight - # 2. Aircraft are not helicopters/VTOL - # 3. Landing waypoint does not point to an airfield - if ( - self.flight.client_count < 1 - and not self.flight.unit_type.helicopter - and not self.flight.unit_type.lha_capable - and isinstance(self.flight.squadron.location, Fob) - ): - # Need to set uncontrolled to false, otherwise the AI will skip the mission and just land - self.group.uncontrolled = False + self.group.uncontrolled = False return FlightData( package=self.flight.package, @@ -136,3 +126,21 @@ class PretenseFlightGroupConfigurator(FlightGroupConfigurator): custom_name=self.flight.custom_name, laser_codes=laser_codes, ) + + def setup_payload(self) -> None: + for p in self.group.units: + p.pylons.clear() + + if self.flight.flight_type == FlightType.SEAD: + self.flight.loadout = self.flight.loadout.default_for_task_and_aircraft( + FlightType.SEAD_SWEEP, self.flight.unit_type.dcs_unit_type + ) + loadout = self.flight.loadout + if self.game.settings.restrict_weapons_by_date: + loadout = loadout.degrade_for_date(self.flight.unit_type, self.game.date) + + for pylon_number, weapon in loadout.pylons.items(): + if weapon is None: + continue + pylon = Pylon.for_aircraft(self.flight.unit_type, pylon_number) + pylon.equip(self.group, weapon) From 4d8031ef96de7ff78a004a0454b56a6765d97dac Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Sun, 24 Sep 2023 02:05:18 +0300 Subject: [PATCH 066/243] =?UTF-8?q?Pretense=20SEAD=20missions=20will=20now?= =?UTF-8?q?=20also=20target=20AAA.=20Also=20incorporates=20the=20fix=20to?= =?UTF-8?q?=20fullBuild():=20Dzsekeb=20=E2=80=94=2003/09/2023=2011:50=20ad?= =?UTF-8?q?d=20the=20highlighted=20line=20to=20the=20fullbuild=20function?= =?UTF-8?q?=20https://discord.com/channels/959044877470027848/103145972131?= =?UTF-8?q?3517578/1147815809075392604?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- resources/plugins/pretense/pretense_compiled.lua | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/resources/plugins/pretense/pretense_compiled.lua b/resources/plugins/pretense/pretense_compiled.lua index 11a3a8b1..a3aaf78c 100644 --- a/resources/plugins/pretense/pretense_compiled.lua +++ b/resources/plugins/pretense/pretense_compiled.lua @@ -1221,7 +1221,7 @@ do if v.type == 'defense' and v.side ~= group:getCoalition() then local gr = Group.getByName(v.name) for _,unit in ipairs(gr:getUnits()) do - if unit:hasAttribute('SAM SR') or unit:hasAttribute('SAM TR') then + if unit:hasAttribute('SAM SR') or unit:hasAttribute('SAM TR') or unit:hasAttribute('AAA') or unit:hasAttribute('IR Guided SAM') or unit:hasAttribute('SAM LL') then table.insert(viable, unit:getName()) end end @@ -1235,7 +1235,7 @@ do { id = 'EngageTargets', params = { - targetTypes = {'SAM SR', 'SAM TR'} + targetTypes = {'SAM SR', 'SAM TR', 'AAA', 'IR Guided SAM', 'SAM LL'} } } } @@ -4064,6 +4064,8 @@ do end function ZoneCommand:fullBuild(useCost) + if self.side ~= 1 and self.side ~= 2 then return end + for i,v in ipairs(self.upgrades[self.side]) do if useCost then local cost = v.cost * useCost From 687ae88685676208582c4bf1ed9fa6dc3419c54f Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Mon, 2 Oct 2023 10:32:17 +0300 Subject: [PATCH 067/243] Implemented a static method for creating the Pretense zone connections and to avoid duplicate connections. --- game/pretense/pretenseluagenerator.py | 54 +++++++++++++++++++++------ 1 file changed, 43 insertions(+), 11 deletions(-) diff --git a/game/pretense/pretenseluagenerator.py b/game/pretense/pretenseluagenerator.py index 3e616357..6c44d547 100644 --- a/game/pretense/pretenseluagenerator.py +++ b/game/pretense/pretenseluagenerator.py @@ -503,6 +503,34 @@ class PretenseLuaGenerator(LuaGenerator): return lua_string_zones + @staticmethod + def generate_pretense_zone_connection( + lua_string_connman: str, + connected_points: dict[str, list[str]], + cp_name: str, + other_cp_name: str, + ) -> str: + try: + connected_points[cp_name] + except KeyError: + connected_points[cp_name] = list() + try: + connected_points[other_cp_name] + except KeyError: + connected_points[other_cp_name] = list() + + if ( + other_cp_name not in connected_points[cp_name] + and cp_name not in connected_points[other_cp_name] + ): + lua_string_connman += ( + f" cm: addConnection('{cp_name}', '{other_cp_name}')\n" + ) + connected_points[cp_name].append(other_cp_name) + connected_points[other_cp_name].append(cp_name) + + return lua_string_connman + def generate_plugin_data(self) -> None: self.mission.triggerrules.triggers.clear() @@ -562,15 +590,19 @@ class PretenseLuaGenerator(LuaGenerator): lua_string_connman = " cm = ConnectionManager:new()\n" # Generate ConnectionManager connections + connected_points: dict[str, list[str]] = {} for cp in self.game.theater.controlpoints: for other_cp in cp.connected_points: - lua_string_connman += ( - f" cm: addConnection('{cp.name}', '{other_cp.name}')\n" + self.generate_pretense_zone_connection( + lua_string_connman, connected_points, cp.name, other_cp.name ) for sea_connection in cp.shipping_lanes: if sea_connection.is_friendly_to(cp): - lua_string_connman += ( - f" cm: addConnection('{cp.name}', '{sea_connection.name}')\n" + self.generate_pretense_zone_connection( + lua_string_connman, + connected_points, + cp.name, + sea_connection.name, ) if len(cp.connected_points) == 0 and len(cp.shipping_lanes) == 0: # Also connect carrier and LHA control points to adjacent friendly points @@ -586,21 +618,21 @@ class PretenseLuaGenerator(LuaGenerator): ): break - lua_string_connman += ( - f" cm: addConnection('{cp.name}', '{other_cp.name}')\n" + self.generate_pretense_zone_connection( + lua_string_connman, connected_points, cp.name, other_cp.name ) else: # Finally, connect remaining non-connected points closest_cps = self.game.theater.closest_friendly_control_points_to(cp) - lua_string_connman += ( - f" cm: addConnection('{cp.name}', '{closest_cps[0].name}')\n" + self.generate_pretense_zone_connection( + lua_string_connman, connected_points, cp.name, closest_cps[0].name ) - lua_string_connman += ( - f" cm: addConnection('{cp.name}', '{closest_cps[1].name}')\n" + self.generate_pretense_zone_connection( + lua_string_connman, connected_points, cp.name, closest_cps[1].name ) lua_string_supply = "local redSupply = {\n" - # Generate ConnectionManager connections + # Generate supply for cp_side in range(1, 3): for cp in self.game.theater.controlpoints: if isinstance(cp, OffMapSpawn): From ffac0362661acf9d25a7f31c169bff79e9fb517f Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Tue, 3 Oct 2023 13:55:20 +0300 Subject: [PATCH 068/243] Implemented spawning of ship statics/units at naval control points instead of land structures or SHORAD sites. --- game/pretense/pretenseluagenerator.py | 419 +++++++++++---------- resources/plugins/pretense/init_header.lua | 36 +- 2 files changed, 259 insertions(+), 196 deletions(-) diff --git a/game/pretense/pretenseluagenerator.py b/game/pretense/pretenseluagenerator.py index 6c44d547..597e1681 100644 --- a/game/pretense/pretenseluagenerator.py +++ b/game/pretense/pretenseluagenerator.py @@ -1,6 +1,7 @@ from __future__ import annotations import logging +import random from abc import ABC, abstractmethod from pathlib import Path from typing import TYPE_CHECKING, Optional @@ -222,6 +223,200 @@ class PretenseLuaGenerator(LuaGenerator): return lua_string_zones + def generate_pretense_sea_upgrade_supply(self, cp_name: str, cp_side: int) -> str: + lua_string_zones = "" + cp_name_trimmed = "".join([i for i in cp_name.lower() if i.isalnum()]) + cp_side_str = "blue" if cp_side == PRETENSE_BLUE_SIDE else "red" + + if cp_side == PRETENSE_BLUE_SIDE: + if random.randint(0, 1): + supply_ship = "shipSupplyTilde" + else: + supply_ship = "shipLandingShipLstMk2" + tanker_ship = "shipTankerSeawisegiant" + command_ship = "shipLandingShipSamuelChase" + ship_group = "blueShipGroup" + else: + if random.randint(0, 1): + supply_ship = "shipBulkerYakushev" + else: + supply_ship = "shipCargoIvanov" + tanker_ship = "shipTankerElnya" + command_ship = "shipLandingShipRopucha" + ship_group = "redShipGroup" + + lua_string_zones += ( + " presets.upgrades.supply." + supply_ship + ":extend({\n" + ) + lua_string_zones += ( + " name = '" + + cp_name_trimmed + + f"-{supply_ship}-" + + cp_side_str + + "',\n" + ) + lua_string_zones += " products = {\n" + for ground_group in self.game.pretense_ground_supply[cp_side][cp_name_trimmed]: + lua_string_zones += ( + " presets.missions.supply.convoy:extend({ name='" + + ground_group + + "'}),\n" + ) + for ground_group in self.game.pretense_ground_assault[cp_side][cp_name_trimmed]: + lua_string_zones += ( + " presets.missions.attack.surface:extend({ name='" + + ground_group + + "'}),\n" + ) + for mission_type in self.game.pretense_air[cp_side][cp_name_trimmed]: + if mission_type == FlightType.AIR_ASSAULT: + mission_name = "supply.helo" + for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ + mission_type + ]: + lua_string_zones += ( + f" presets.missions.{mission_name}:extend" + + "({name='" + + air_group + + "'}),\n" + ) + lua_string_zones += " }\n" + lua_string_zones += " }),\n" + lua_string_zones += ( + " presets.upgrades.attack." + command_ship + ":extend({\n" + ) + lua_string_zones += ( + f" name = '{cp_name_trimmed}-mission-command-" + + cp_side_str + + "',\n" + ) + lua_string_zones += " products = {\n" + lua_string_zones += ( + " presets.defenses." + + cp_side_str + + "." + + ship_group + + ":extend({ name='" + + cp_name_trimmed + + "-sam-" + + cp_side_str + + "' }),\n" + ) + lua_string_zones += " }\n" + lua_string_zones += " }),\n" + lua_string_zones += ( + " presets.upgrades.attack." + tanker_ship + ":extend({\n" + ) + lua_string_zones += ( + f" name = '{cp_name_trimmed}-aircraft-command-" + + cp_side_str + + "',\n" + ) + lua_string_zones += " products = {\n" + for mission_type in self.game.pretense_air[cp_side][cp_name_trimmed]: + if mission_type == FlightType.SEAD: + mission_name = "attack.sead" + for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ + mission_type + ]: + lua_string_zones += ( + f" presets.missions.{mission_name}:extend" + + "({name='" + + air_group + + "', altitude=25000, expend=AI.Task.WeaponExpend.ALL}),\n" + ) + elif mission_type == FlightType.CAS: + mission_name = "attack.cas" + for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ + mission_type + ]: + lua_string_zones += ( + f" presets.missions.{mission_name}:extend" + + "({name='" + + air_group + + "', altitude=15000, expend=AI.Task.WeaponExpend.ONE}),\n" + ) + elif mission_type == FlightType.BAI: + mission_name = "attack.bai" + for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ + mission_type + ]: + lua_string_zones += ( + f" presets.missions.{mission_name}:extend" + + "({name='" + + air_group + + "', altitude=10000, expend=AI.Task.WeaponExpend.ONE}),\n" + ) + elif mission_type == FlightType.STRIKE: + mission_name = "attack.strike" + for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ + mission_type + ]: + lua_string_zones += ( + f" presets.missions.{mission_name}:extend" + + "({name='" + + air_group + + "', altitude=20000, expend=AI.Task.WeaponExpend.ALL}),\n" + ) + elif mission_type == FlightType.BARCAP: + mission_name = "patrol.aircraft" + for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ + mission_type + ]: + lua_string_zones += ( + f" presets.missions.{mission_name}:extend" + + "({name='" + + air_group + + "', altitude=25000, range=25}),\n" + ) + elif mission_type == FlightType.REFUELING: + mission_name = "support.tanker" + for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ + mission_type + ]: + tanker_freq = 257.0 + tanker_tacan = 37.0 + tanker_variant = "Drogue" + for tanker in self.mission_data.tankers: + if tanker.group_name == air_group: + tanker_freq = tanker.freq.hertz / 1000000 + tanker_tacan = tanker.tacan.number if tanker.tacan else 0.0 + if tanker.variant == "KC-135 Stratotanker": + tanker_variant = "Boom" + lua_string_zones += ( + f" presets.missions.{mission_name}:extend" + + "({name='" + + air_group + + "', freq='" + + str(tanker_freq) + + "', tacan='" + + str(tanker_tacan) + + "', variant='" + + tanker_variant + + "'}),\n" + ) + elif mission_type == FlightType.AEWC: + mission_name = "support.awacs" + for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ + mission_type + ]: + awacs_freq = 257.5 + for awacs in self.mission_data.awacs: + if awacs.group_name == air_group: + awacs_freq = awacs.freq.hertz / 1000000 + lua_string_zones += ( + f" presets.missions.{mission_name}:extend" + + "({name='" + + air_group + + "', freq=" + + str(awacs_freq) + + "}),\n" + ) + lua_string_zones += " }\n" + lua_string_zones += " })\n" + + return lua_string_zones + def generate_pretense_zone_land(self, cp_name: str) -> str: lua_string_zones = "" cp_name_trimmed = "".join([i for i in cp_name.lower() if i.isalnum()]) @@ -296,208 +491,25 @@ class PretenseLuaGenerator(LuaGenerator): return lua_string_zones - def generate_pretense_zone_sea(self, cp_name: str, cp_side: int) -> str: + def generate_pretense_zone_sea(self, cp_name: str) -> str: lua_string_zones = "" cp_name_trimmed = "".join([i for i in cp_name.lower() if i.isalnum()]) lua_string_zones += f"zones.{cp_name_trimmed}:defineUpgrades(" + "{\n" lua_string_zones += " [1] = { --red side\n" - lua_string_zones += " presets.upgrades.basic.tent:extend({\n" - lua_string_zones += f" name='{cp_name_trimmed}-tent-red',\n" - lua_string_zones += " products = {\n" - lua_string_zones += ( - " presets.special.red.infantry:extend({ name='" - + cp_name_trimmed - + "-defense-red'})\n" + + lua_string_zones += self.generate_pretense_sea_upgrade_supply( + cp_name, PRETENSE_RED_SIDE ) - lua_string_zones += " }\n" - lua_string_zones += " }),\n" - lua_string_zones += " presets.upgrades.basic.comPost:extend({\n" - lua_string_zones += f" name = '{cp_name_trimmed}-com-red',\n" - lua_string_zones += " products = {\n" - lua_string_zones += ( - " presets.special.red.infantry:extend({ name='" - + cp_name_trimmed - + "-defense-red'}),\n" - ) - lua_string_zones += ( - " presets.defenses.red.infantry:extend({ name='" - + cp_name_trimmed - + "-garrison-red' })\n" - ) - lua_string_zones += " }\n" - lua_string_zones += " }),\n" + lua_string_zones += " },\n" lua_string_zones += " [2] = --blue side\n" lua_string_zones += " {\n" - lua_string_zones += " presets.upgrades.basic.tent:extend({\n" - lua_string_zones += f" name='{cp_name_trimmed}-tent-blue',\n" - lua_string_zones += " products = {\n" - lua_string_zones += ( - " presets.special.blue.infantry:extend({ name='" - + cp_name_trimmed - + "-defense-blue'})\n" + + lua_string_zones += self.generate_pretense_sea_upgrade_supply( + cp_name, PRETENSE_BLUE_SIDE ) - lua_string_zones += " }\n" - lua_string_zones += " }),\n" - lua_string_zones += " presets.upgrades.basic.comPost:extend({\n" - lua_string_zones += f" name = '{cp_name_trimmed}-com-blue',\n" - lua_string_zones += " products = {\n" - lua_string_zones += ( - " presets.special.blue.infantry:extend({ name='" - + cp_name_trimmed - + "-defense-blue'}),\n" - ) - lua_string_zones += ( - " presets.defenses.blue.infantry:extend({ name='" - + cp_name_trimmed - + "-garrison-blue' })\n" - ) - lua_string_zones += " }\n" - lua_string_zones += " }),\n" - lua_string_zones += " presets.upgrades.supply.fuelTank:extend({\n" - lua_string_zones += ( - " name = '" + cp_name_trimmed + "-fueltank-blue',\n" - ) - lua_string_zones += " products = {\n" - for ground_group in self.game.pretense_ground_supply[cp_side][cp_name_trimmed]: - lua_string_zones += ( - " presets.missions.supply.convoy:extend({ name='" - + ground_group - + "'}),\n" - ) - for ground_group in self.game.pretense_ground_assault[cp_side][cp_name_trimmed]: - lua_string_zones += ( - " presets.missions.attack.surface:extend({ name='" - + ground_group - + "'}),\n" - ) - for mission_type in self.game.pretense_air[cp_side][cp_name_trimmed]: - if mission_type == FlightType.AIR_ASSAULT: - mission_name = "supply.helo" - for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ - mission_type - ]: - lua_string_zones += ( - f" presets.missions.{mission_name}:extend" - + "({name='" - + air_group - + "'}),\n" - ) - lua_string_zones += " }\n" - lua_string_zones += " }),\n" - lua_string_zones += " presets.upgrades.airdef.comCenter:extend({\n" - lua_string_zones += ( - f" name = '{cp_name_trimmed}-mission-command-blue',\n" - ) - lua_string_zones += " products = {\n" - lua_string_zones += ( - " presets.defenses.blue.shorad:extend({ name='" - + cp_name_trimmed - + "-sam-blue' }),\n" - ) - for mission_type in self.game.pretense_air[cp_side][cp_name_trimmed]: - if mission_type == FlightType.SEAD: - mission_name = "attack.sead" - for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ - mission_type - ]: - lua_string_zones += ( - f" presets.missions.{mission_name}:extend" - + "({name='" - + air_group - + "', altitude=25000, expend=AI.Task.WeaponExpend.ALL}),\n" - ) - elif mission_type == FlightType.CAS: - mission_name = "attack.cas" - for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ - mission_type - ]: - lua_string_zones += ( - f" presets.missions.{mission_name}:extend" - + "({name='" - + air_group - + "', altitude=15000, expend=AI.Task.WeaponExpend.ONE}),\n" - ) - elif mission_type == FlightType.BAI: - mission_name = "attack.bai" - for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ - mission_type - ]: - lua_string_zones += ( - f" presets.missions.{mission_name}:extend" - + "({name='" - + air_group - + "', altitude=10000, expend=AI.Task.WeaponExpend.ONE}),\n" - ) - elif mission_type == FlightType.STRIKE: - mission_name = "attack.strike" - for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ - mission_type - ]: - lua_string_zones += ( - f" presets.missions.{mission_name}:extend" - + "({name='" - + air_group - + "', altitude=20000, expend=AI.Task.WeaponExpend.ALL}),\n" - ) - elif mission_type == FlightType.BARCAP: - mission_name = "patrol.aircraft" - for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ - mission_type - ]: - lua_string_zones += ( - f" presets.missions.{mission_name}:extend" - + "({name='" - + air_group - + "', altitude=25000, range=25}),\n" - ) - elif mission_type == FlightType.REFUELING: - mission_name = "support.tanker" - for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ - mission_type - ]: - tanker_freq = 257.0 - tanker_tacan = 37.0 - for tanker in self.mission_data.tankers: - if tanker.group_name == air_group: - tanker_freq = tanker.freq.hertz / 1000000 - tanker_tacan = tanker.tacan.number if tanker.tacan else 0.0 - if tanker.variant == "KC-135 Stratotanker": - tanker_variant = "Boom" - else: - tanker_variant = "Drogue" - lua_string_zones += ( - f" presets.missions.{mission_name}:extend" - + "({name='" - + air_group - + "', freq='" - + str(tanker_freq) - + "', tacan='" - + str(tanker_tacan) - + "', variant='" - + tanker_variant - + "'}),\n" - ) - elif mission_type == FlightType.AEWC: - mission_name = "support.awacs" - for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ - mission_type - ]: - awacs_freq = 257.5 - for awacs in self.mission_data.awacs: - if awacs.group_name == air_group: - awacs_freq = awacs.freq.hertz / 1000000 - lua_string_zones += ( - f" presets.missions.{mission_name}:extend" - + "({name='" - + air_group - + "', freq=" - + str(awacs_freq) - + "}),\n" - ) - lua_string_zones += " }\n" - lua_string_zones += " })\n" + lua_string_zones += " }\n" lua_string_zones += "})\n" @@ -570,20 +582,33 @@ class PretenseLuaGenerator(LuaGenerator): ) lua_string_zones += f"zones.{cp_name_trimmed}.keepActive = true\n" max_resource = 20000 + is_helo_spawn = "false" + is_plane_spawn = "false" if cp.has_helipads: - lua_string_zones += f"zones.{cp_name_trimmed}.isHeloSpawn = true\n" + is_helo_spawn = "true" max_resource = 30000 if isinstance(cp, Airfield) or cp.has_ground_spawns: - lua_string_zones += f"zones.{cp_name_trimmed}.isPlaneSpawn = true\n" + is_helo_spawn = "true" + is_plane_spawn = "true" if cp.has_ground_spawns or cp.is_lha: + is_helo_spawn = "true" + is_plane_spawn = "true" max_resource = 40000 if isinstance(cp, Airfield) or cp.is_carrier: + is_helo_spawn = "true" + is_plane_spawn = "true" max_resource = 50000 lua_string_zones += ( f"zones.{cp_name_trimmed}.maxResource = {max_resource}\n" ) + lua_string_zones += ( + f"zones.{cp_name_trimmed}.isHeloSpawn = " + is_helo_spawn + "\n" + ) + lua_string_zones += ( + f"zones.{cp_name_trimmed}.isPlaneSpawn = " + is_plane_spawn + "\n" + ) if cp.is_fleet: - lua_string_zones += self.generate_pretense_zone_sea(cp.name, cp_side) + lua_string_zones += self.generate_pretense_zone_sea(cp.name) else: lua_string_zones += self.generate_pretense_zone_land(cp.name) @@ -687,6 +712,10 @@ class PretenseLuaGenerator(LuaGenerator): trigger.add_action(DoScript(String(lua_string))) self.mission.triggerrules.triggers.append(trigger) + file1 = open(Path("./resources/plugins/pretense", "pretense_output.lua"), "w") + file1.write(lua_string) + file1.close() + def inject_lua_trigger(self, contents: str, comment: str) -> None: trigger = TriggerStart(comment=comment) trigger.add_action(DoScript(String(contents))) diff --git a/resources/plugins/pretense/init_header.lua b/resources/plugins/pretense/init_header.lua index 12f84597..dddb6087 100644 --- a/resources/plugins/pretense/init_header.lua +++ b/resources/plugins/pretense/init_header.lua @@ -259,6 +259,28 @@ do skill = "Excellent", dataCategory= TemplateDB.type.group } + + TemplateDB.templates["blueShipGroup"] = { + units = { + "PERRY", + "USS_Arleigh_Burke_IIa", + "PERRY" + }, + maxDist = 300, + skill = "Excellent", + dataCategory= TemplateDB.type.group + } + + TemplateDB.templates["redShipGroup"] = { + units = { + "ALBATROS", + "NEUSTRASH", + "ALBATROS" + }, + maxDist = 300, + skill = "Excellent", + dataCategory= TemplateDB.type.group + } end presets = { @@ -523,10 +545,16 @@ presets = { template='sa6', }), sa11 = Preset:new({ + display = 'SAM', + cost=3000, + type='defense', + template='sa11', + }), + redShipGroup = Preset:new({ display = 'SAM', cost=3000, type='defense', - template='sa11', + template='redShipGroup', }) }, blue = { @@ -559,6 +587,12 @@ presets = { cost=3000, type='defense', template='nasams', + }), + blueShipGroup = Preset:new({ + display = 'SAM', + cost=3000, + type='defense', + template='blueShipGroup', }) } }, From 9edb4ecc45a752dd4ad4777ea2255757e3e3cad7 Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Tue, 3 Oct 2023 13:56:43 +0300 Subject: [PATCH 069/243] Implemented a list of units which will be removed from Pretense ground assault groups due to pathfinding problems. The units will just remain still instead of advancing. Also added one tank to each group and increased the maximum size of the groups to 5. Removed artillery units from the groups, similarly due to pathfinding problems. --- game/pretense/pretensetgogenerator.py | 28 ++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/game/pretense/pretensetgogenerator.py b/game/pretense/pretensetgogenerator.py index 74f99a6e..aa1ee203 100644 --- a/game/pretense/pretensetgogenerator.py +++ b/game/pretense/pretensetgogenerator.py @@ -91,7 +91,11 @@ if TYPE_CHECKING: FARP_FRONTLINE_DISTANCE = 10000 AA_CP_MIN_DISTANCE = 40000 -PRETENSE_GROUND_UNIT_GROUP_SIZE = 4 +PRETENSE_GROUND_UNIT_GROUP_SIZE = 5 +PRETENSE_GROUND_UNITS_TO_REMOVE_FROM_ASSAULT = [ + vehicles.Armor.Stug_III, + vehicles.Artillery.Grad_URAL, +] class PretenseGroundObjectGenerator(GroundObjectGenerator): @@ -131,6 +135,12 @@ class PretenseGroundObjectGenerator(GroundObjectGenerator): ) of_class = list({u for u in faction_units if u.unit_class is unit_class}) + # Remove units from list with known pathfinding issues in Pretense missions + for unit_to_remove in PRETENSE_GROUND_UNITS_TO_REMOVE_FROM_ASSAULT: + for groundunittype_to_remove in GroundUnitType.for_dcs_type(unit_to_remove): + if groundunittype_to_remove in of_class: + of_class.remove(groundunittype_to_remove) + if len(of_class) > 0: return random.choice(of_class) else: @@ -200,6 +210,14 @@ class PretenseGroundObjectGenerator(GroundObjectGenerator): group_name = f"{cp_name_trimmed}-{group_role}-{group.id}" group.name = group_name + self.generate_ground_unit_of_class( + UnitClass.TANK, + group, + vehicle_units, + cp_name_trimmed, + group_role, + PRETENSE_GROUND_UNIT_GROUP_SIZE - 4, + ) self.generate_ground_unit_of_class( UnitClass.TANK, group, @@ -232,14 +250,6 @@ class PretenseGroundObjectGenerator(GroundObjectGenerator): group_role, PRETENSE_GROUND_UNIT_GROUP_SIZE, ) - self.generate_ground_unit_of_class( - UnitClass.ARTILLERY, - group, - vehicle_units, - cp_name_trimmed, - group_role, - PRETENSE_GROUND_UNIT_GROUP_SIZE, - ) self.generate_ground_unit_of_class( UnitClass.RECON, group, From 9be06d96010f7ff5124900da47e1e4fdc240f8cd Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Tue, 3 Oct 2023 14:34:26 +0300 Subject: [PATCH 070/243] Fixed a bug in generate_pretense_zone_connection(). --- game/pretense/pretenseluagenerator.py | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/game/pretense/pretenseluagenerator.py b/game/pretense/pretenseluagenerator.py index 597e1681..f77088da 100644 --- a/game/pretense/pretenseluagenerator.py +++ b/game/pretense/pretenseluagenerator.py @@ -517,11 +517,11 @@ class PretenseLuaGenerator(LuaGenerator): @staticmethod def generate_pretense_zone_connection( - lua_string_connman: str, connected_points: dict[str, list[str]], cp_name: str, other_cp_name: str, ) -> str: + lua_string_connman = "" try: connected_points[cp_name] except KeyError: @@ -535,7 +535,7 @@ class PretenseLuaGenerator(LuaGenerator): other_cp_name not in connected_points[cp_name] and cp_name not in connected_points[other_cp_name] ): - lua_string_connman += ( + lua_string_connman = ( f" cm: addConnection('{cp_name}', '{other_cp_name}')\n" ) connected_points[cp_name].append(other_cp_name) @@ -618,13 +618,12 @@ class PretenseLuaGenerator(LuaGenerator): connected_points: dict[str, list[str]] = {} for cp in self.game.theater.controlpoints: for other_cp in cp.connected_points: - self.generate_pretense_zone_connection( - lua_string_connman, connected_points, cp.name, other_cp.name + lua_string_connman += self.generate_pretense_zone_connection( + connected_points, cp.name, other_cp.name ) for sea_connection in cp.shipping_lanes: if sea_connection.is_friendly_to(cp): - self.generate_pretense_zone_connection( - lua_string_connman, + lua_string_connman += self.generate_pretense_zone_connection( connected_points, cp.name, sea_connection.name, @@ -643,17 +642,17 @@ class PretenseLuaGenerator(LuaGenerator): ): break - self.generate_pretense_zone_connection( - lua_string_connman, connected_points, cp.name, other_cp.name + lua_string_connman += self.generate_pretense_zone_connection( + connected_points, cp.name, other_cp.name ) else: # Finally, connect remaining non-connected points closest_cps = self.game.theater.closest_friendly_control_points_to(cp) - self.generate_pretense_zone_connection( - lua_string_connman, connected_points, cp.name, closest_cps[0].name + lua_string_connman += self.generate_pretense_zone_connection( + connected_points, cp.name, closest_cps[0].name ) - self.generate_pretense_zone_connection( - lua_string_connman, connected_points, cp.name, closest_cps[1].name + lua_string_connman += self.generate_pretense_zone_connection( + connected_points, cp.name, closest_cps[1].name ) lua_string_supply = "local redSupply = {\n" From 473e70c44b4e375106688eb9174f0fb19c5bb520 Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Tue, 3 Oct 2023 17:17:12 +0300 Subject: [PATCH 071/243] Implemented SAM sites as products of a Command Center at a control point / zone, if the Retribution campaign has the corresponding SAM site there. The SAM site presets are still static, I might make them dynamic in the future. --- game/pretense/pretenseluagenerator.py | 119 +++++++++++++++++++-- resources/plugins/pretense/init_header.lua | 4 +- 2 files changed, 112 insertions(+), 11 deletions(-) diff --git a/game/pretense/pretenseluagenerator.py b/game/pretense/pretenseluagenerator.py index f77088da..32577ef9 100644 --- a/game/pretense/pretenseluagenerator.py +++ b/game/pretense/pretenseluagenerator.py @@ -3,6 +3,7 @@ from __future__ import annotations import logging import random from abc import ABC, abstractmethod +from dataclasses import dataclass from pathlib import Path from typing import TYPE_CHECKING, Optional @@ -10,6 +11,7 @@ from dcs import Mission from dcs.action import DoScript, DoScriptFile from dcs.translation import String from dcs.triggers import TriggerStart +from dcs.vehicles import AirDefence from game.ato import FlightType from game.missiongenerator.luagenerator import LuaGenerator @@ -26,6 +28,19 @@ PRETENSE_BLUE_SIDE = 2 PRETENSE_NUMBER_OF_ZONES_TO_CONNECT_CARRIERS_TO = 2 +@dataclass +class PretenseSam: + name: str + enabled: bool + + def __init__( + self, + name: str, + ) -> None: + self.name = name + self.enabled = False + + class PretenseLuaGenerator(LuaGenerator): def __init__( self, @@ -54,10 +69,44 @@ class PretenseLuaGenerator(LuaGenerator): self.mission.triggerrules.triggers.remove(t) self.mission.triggerrules.triggers.append(t) + @staticmethod + def generate_sam_from_preset( + preset: str, cp_side_str: str, cp_name_trimmed: str + ) -> str: + lua_string_zones = ( + " presets.defenses." + + cp_side_str + + "." + + preset + + ":extend({ name='" + + cp_name_trimmed + + f"-{preset}-" + + cp_side_str + + "' }),\n" + ) + return lua_string_zones + def generate_pretense_land_upgrade_supply(self, cp_name: str, cp_side: int) -> str: lua_string_zones = "" cp_name_trimmed = "".join([i for i in cp_name.lower() if i.isalnum()]) cp_side_str = "blue" if cp_side == PRETENSE_BLUE_SIDE else "red" + cp = self.game.theater.controlpoints[0] + for loop_cp in self.game.theater.controlpoints: + if loop_cp.name == cp_name: + cp = loop_cp + sam_presets: dict[str, PretenseSam] = {} + for sam_name in [ + "sa2", + "sa3", + "sa5", + "sa6", + "sa10", + "sa11", + "hawk", + "patriot", + "nasams", + ]: + sam_presets[sam_name] = PretenseSam(sam_name) lua_string_zones += " presets.upgrades.supply.fuelTank:extend({\n" lua_string_zones += ( @@ -96,22 +145,74 @@ class PretenseLuaGenerator(LuaGenerator): lua_string_zones += " }),\n" lua_string_zones += " presets.upgrades.airdef.bunker:extend({\n" lua_string_zones += ( - f" name = '{cp_name_trimmed}-mission-command-" + f" name = '{cp_name_trimmed}-shorad-command-" + cp_side_str + "',\n" ) lua_string_zones += " products = {\n" - lua_string_zones += ( - " presets.defenses." - + cp_side_str - + ".shorad:extend({ name='" - + cp_name_trimmed - + "-sam-" - + cp_side_str - + "' }),\n" + lua_string_zones += self.generate_sam_from_preset( + "shorad", cp_side_str, cp_name_trimmed ) lua_string_zones += " }\n" lua_string_zones += " }),\n" + + for ground_object in cp.ground_objects: + for ground_unit in ground_object.units: + if ground_unit.unit_type is not None: + if ground_unit.unit_type.dcs_unit_type == AirDefence.S_75M_Volhov: + sam_presets["sa2"].enabled = True + if ( + ground_unit.unit_type.dcs_unit_type + == AirDefence.X_5p73_s_125_ln + ): + sam_presets["sa3"].enabled = True + if ground_unit.unit_type.dcs_unit_type == AirDefence.S_200_Launcher: + sam_presets["sa5"].enabled = True + if ground_unit.unit_type.dcs_unit_type == AirDefence.Kub_2P25_ln: + sam_presets["sa6"].enabled = True + if ( + ground_unit.unit_type.dcs_unit_type + == AirDefence.S_300PS_5P85C_ln + or ground_unit.unit_type.dcs_unit_type + == AirDefence.S_300PS_5P85D_ln + ): + sam_presets["sa10"].enabled = True + if ( + ground_unit.unit_type.dcs_unit_type + == AirDefence.SA_11_Buk_LN_9A310M1 + ): + sam_presets["sa11"].enabled = True + if ground_unit.unit_type.dcs_unit_type == AirDefence.Hawk_ln: + sam_presets["hawk"].enabled = True + if ground_unit.unit_type.dcs_unit_type == AirDefence.Patriot_ln: + sam_presets["patriot"].enabled = True + if ( + ground_unit.unit_type.dcs_unit_type == AirDefence.NASAMS_LN_B + or ground_unit.unit_type.dcs_unit_type == AirDefence.NASAMS_LN_C + ): + sam_presets["nasams"].enabled = True + + cp_has_sams = False + for sam_name in sam_presets: + if sam_presets[sam_name].enabled: + cp_has_sams = True + break + if cp_has_sams: + lua_string_zones += " presets.upgrades.airdef.comCenter:extend({\n" + lua_string_zones += ( + f" name = '{cp_name_trimmed}-sam-command-" + + cp_side_str + + "',\n" + ) + lua_string_zones += " products = {\n" + for sam_name in sam_presets: + if sam_presets[sam_name].enabled: + lua_string_zones += self.generate_sam_from_preset( + sam_name, cp_side_str, cp_name_trimmed + ) + lua_string_zones += " }\n" + lua_string_zones += " }),\n" + lua_string_zones += " presets.upgrades.supply.hangar:extend({\n" lua_string_zones += ( f" name = '{cp_name_trimmed}-aircraft-command-" diff --git a/resources/plugins/pretense/init_header.lua b/resources/plugins/pretense/init_header.lua index dddb6087..a8585391 100644 --- a/resources/plugins/pretense/init_header.lua +++ b/resources/plugins/pretense/init_header.lua @@ -90,7 +90,7 @@ do dataCategory= TemplateDB.type.group } - TemplateDB.templates["sam-red"] = { + TemplateDB.templates["sa2"] = { units = { "p-19 s-125 sr", "Ural-4320T", @@ -108,7 +108,7 @@ do dataCategory= TemplateDB.type.group } - TemplateDB.templates["sam-blue"] = { + TemplateDB.templates["hawk"] = { units = { "Hawk pcp", "Hawk cwar", From 359a579e652c0451f55c3e05be58107346836031 Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Tue, 3 Oct 2023 17:56:21 +0300 Subject: [PATCH 072/243] Fixed a bug in SHORAD and SAM generation. --- game/pretense/pretenseluagenerator.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/game/pretense/pretenseluagenerator.py b/game/pretense/pretenseluagenerator.py index 32577ef9..af97601b 100644 --- a/game/pretense/pretenseluagenerator.py +++ b/game/pretense/pretenseluagenerator.py @@ -74,9 +74,7 @@ class PretenseLuaGenerator(LuaGenerator): preset: str, cp_side_str: str, cp_name_trimmed: str ) -> str: lua_string_zones = ( - " presets.defenses." - + cp_side_str - + "." + " presets.defenses.sam." + preset + ":extend({ name='" + cp_name_trimmed @@ -150,8 +148,14 @@ class PretenseLuaGenerator(LuaGenerator): + "',\n" ) lua_string_zones += " products = {\n" - lua_string_zones += self.generate_sam_from_preset( - "shorad", cp_side_str, cp_name_trimmed + lua_string_zones += ( + " presets.defenses." + + cp_side_str + + ".shorad:extend({ name='" + + cp_name_trimmed + + "-shorad-" + + cp_side_str + + "' }),\n" ) lua_string_zones += " }\n" lua_string_zones += " }),\n" From dc504005a1b97c132bd5f2bdc9c79860e961acd4 Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Tue, 3 Oct 2023 22:45:34 +0300 Subject: [PATCH 073/243] Added new icons and a separate button which will point to the Pretense/Foothold Discord. --- qt_ui/uiconstants.py | 3 ++- qt_ui/windows/QLiberationWindow.py | 15 +++++++++++++-- resources/ui/misc/pretense_discord.png | Bin 0 -> 36022 bytes resources/ui/misc/pretense_generate.png | Bin 0 -> 14287 bytes 4 files changed, 15 insertions(+), 3 deletions(-) create mode 100644 resources/ui/misc/pretense_discord.png create mode 100644 resources/ui/misc/pretense_generate.png diff --git a/qt_ui/uiconstants.py b/qt_ui/uiconstants.py index 8edc69a0..d431fc29 100644 --- a/qt_ui/uiconstants.py +++ b/qt_ui/uiconstants.py @@ -32,7 +32,8 @@ def load_icons(): "./resources/ui/misc/" + get_theme_icons() + "/github.png" ) ICONS["Ukraine"] = QPixmap("./resources/ui/misc/ukraine.png") - ICONS["Pretense"] = QPixmap("./resources/ui/misc/pretense.png") + ICONS["Pretense"] = QPixmap("./resources/ui/misc/pretense_discord.png") + ICONS["Pretense_generate"] = QPixmap("./resources/ui/misc/pretense_generate.png") ICONS["Control Points"] = QPixmap( "./resources/ui/misc/" + get_theme_icons() + "/circle.png" diff --git a/qt_ui/windows/QLiberationWindow.py b/qt_ui/windows/QLiberationWindow.py index f4f8c38f..13d0988a 100644 --- a/qt_ui/windows/QLiberationWindow.py +++ b/qt_ui/windows/QLiberationWindow.py @@ -194,8 +194,18 @@ class QLiberationWindow(QMainWindow): lambda: webbrowser.open_new_tab("https://shdwp.github.io/ukraine/") ) - self.newPretenseAction = QAction("&New Pretense Campaign", self) - self.newPretenseAction.setIcon(QIcon(CONST.ICONS["Pretense"])) + self.pretenseLinkAction = QAction("&DCS: Pretense", self) + self.pretenseLinkAction.setIcon(QIcon(CONST.ICONS["Pretense"])) + self.pretenseLinkAction.triggered.connect( + lambda: webbrowser.open_new_tab( + "https://" + "discord.gg" + "/" + "PtPsb9Mpk6" + ) + ) + + self.newPretenseAction = QAction( + "&Generate a Pretense Campaign from the running campaign", self + ) + self.newPretenseAction.setIcon(QIcon(CONST.ICONS["Pretense_generate"])) self.newPretenseAction.triggered.connect(self.newPretenseCampaign) self.openLogsAction = QAction("Show &logs", self) @@ -239,6 +249,7 @@ class QLiberationWindow(QMainWindow): self.links_bar.addAction(self.openDiscordAction) self.links_bar.addAction(self.openGithubAction) self.links_bar.addAction(self.ukraineAction) + self.links_bar.addAction(self.pretenseLinkAction) self.links_bar.addAction(self.newPretenseAction) self.actions_bar = self.addToolBar("Actions") diff --git a/resources/ui/misc/pretense_discord.png b/resources/ui/misc/pretense_discord.png new file mode 100644 index 0000000000000000000000000000000000000000..c9d7f544df58f12787ecff2a9b58696631b846f0 GIT binary patch literal 36022 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 literal 0 HcmV?d00001 diff --git a/resources/ui/misc/pretense_generate.png b/resources/ui/misc/pretense_generate.png new file mode 100644 index 0000000000000000000000000000000000000000..5fcc00b6a546b72be48354f183f0e1b4b71c6cf0 GIT binary patch literal 14287 zcmV;=H!#SFP)002t}1^@s6I8J)%0004nX+uL$Nkc;* zaB^>EX>4Tx04R}tkv&MmKpe$iQ>9WWf>sc5$WWauh>D1lRIvyaN?V~-2a`)bgeDD1 zii@M*T5#}VvFhOBtgC~oAP9bdxVktgx=4xtOA0MwJUH&hyL*qjcYshYGu7-E160j2 zQt_CW&8>)mR|MflFZwYfF;h>Z7c%f1U-$6w^)AY@ywCkPx|O`i0G~)a%XGscUMHT~ zv~yF6ykH@QG+f>{K$3L;xMsTY-72NS; zby?xO#aS&^S@WL!g`vE*oaQ>sA;htW1d+gyEO|F<8D$Y26VpI_Qx;~*aaFj+x|Yb?ZyelN54hX`2A*`ukQ~WRQz#UG_cQvY9ME?Q1XtbOn)^6?05a6o(hYEM z2#gddd(GqBUG2U7d#2gn52R0WyKTvNP5=M^24YJ`L;(K){{a7>y{D4^000SaNLh0L z04^f{04^f|c%?sf00007bV*G`2j>a{5;rQ#jwmSr000?uMObu0Z*6U5Zgc=ca%Ew3 zWn>_CX>@2HM@dakSAh-}001BWNkl z0(yl(BB;fhv1)*l0`EM|IgIsO+(@Y=ifW+nLZGBzrJFM-EK^mD5`xS?EmqXx03`%S zD2yS^3I@4lwU=>fwa?eiw2@u|l8aqOGxNkDXs=mr7xZ&WEMcsoiIqSKM=exT13?ma zVx>@0AiYNkP|_m=n=4XK4JD04QHvExAgKg`Ado~_5otl71X2iu@CYdoLLl&){{mR& zan7UHdKratbW%@ynBwpVB)aRFO+x}5kQWvZ7-z|>B(x46Dy(x@=a{T2#+)QC98aut zSRYvSPR2~uBBT^JXBniH6RSOvQ1kTV4#!t=nwiBqLsr1XKrpZZEd|aO^iqL!o?Hrs z)({9!q9nb*Q4J+=0Ff$%BmpHs3y=aLC8$J_Pyqs*2je`}dkPCud5m>{#Ced?BZWga zff5oaKuCv_AP^`eL0I&ckFQY;741Pm;U%FGT<8=GbHjKXPzeP(@FamC3?ws&rdJE; z=P}+{oOA>##2Q0kJ?-9r@0{!M?B#$s&pb2=VLYaAqzEz%iE$K05(W@Tg+L&LBM^c}L!<>Vl6dd&2%HBy5|*)!0*N#h z@BQeQKnhU8p%kUCBuG(eoRkm*0-Qj9^06j)?&%d!3j|6DEFO?(l@ltD3Lpw#JaSB~ zKF0Le1Pgoi69tO3te`b?%v3{^kn}UdAUB+EXLte5deY338t_QGa})-omPqMK^%v#e z6kcI1IOmZ79-Ol{CjgJdbMM{?g_S&TZitiy=|M|D5=fjc%_+A~*pW6_D2zi2iALbP z!+L>M4k;W`c(g3PLraj#qJ=88R0_0G2prynUhZ0~0fZnk4lN^_R z!!ch~)C1^bW2~HimJ25yXKsCi?fVb&WKqMV4y8N-z=3s;8B0HdG>3ulc;``45QU(m z#CcEQ1QHnF7z+jSHA$LV0tx3^mZA4ZDcDwlci+8*>3TpIN_H()Xw)>_)X~okUwW!V zH@5`J6Dm((3|X$w2$Ylr!jU8KP9UwPPy#9ZX!9VD5-lZ4OG0JQQXr*ADS^yALP$jT zlb`g)@q#4)t;D9|3=~u&7^?`Xp(JfB^UMSPm3nOqBSNmd{f#V}2;m(lIr0S%Sj~>f zm^14cC)zn$3516*0IeiSK;Z?66jWo0^3WX^HZsdAwpH0S8L)4V~rMNI4X=KP67s*#}tNdo><`vM_a^NQV9i7APJQuP-OrKB!pUx9C)d{Qb8yM zS_`x)wM+?t5&}PpuqwRaP4@`|2!V5DsFboiNeT$GCy;{NKyT<-?K;+qguo4#F{M;e~8PgehdPC?A1v@7bUb?+TJ(QeW&l#7RiWGq419$G={cpIM8yoQGr2&O8 zDC4;_v^ejXt4RLMO$*$xXPSMB6>iuwgAj0bEoG1uoLTA9NHorYkOFHRl{g>{HFJ#w z?LDX0Q-tt1FEF-r@ZNzCrB(?9&U>79cpO4V0K6ZWKjk%9VMz;zR!Ec(7{CdE7M?Kh z1k$6WCki}DDV*~d1I7uY75K%&^v^#|oUO1isL}3xnwhKb;B>#iSfpuG6pLd4^;j`g z3z)BI7AIl?A$Z&E+j!}|Nt89b>J@vqILJBGv}~IUSZimT-bm5D^n1@-YBJwQ&`O|` zV!9D>ac#)OcER=;N31+y5aFDsFb-!uNvODH`xpyei zpFOiRLJ8f}5C@WaMbM}y#;StJs$je#s6~*3zBD-;&N-|FYkdh}MjEJvphUnxC$E!{MsA#Fh8!y3qvx@ShtE)DF&TB2M#RpnJ+#`6bSY&)Y-o@iISyDC?lk&pBb{u zuxB=+GcZ)+5_JknQ%XXGL}R_D+0TfyC3h*+ieo8oR0Ba2cq#z|0Yp-ih+l{jmnpZ2 z&73QPo)o2^%GiGJU*9JwRWOhq=b>h>9&_%gW}IY}?k zfnv~WQ?$=9Q4Pq{47r2DixGF;wnROaoL_Ha&Rrr2HK%ex&nvW2OxHq^ie`HwVCVc8 zN=dYmC3F#GwjhK=0&^1y^&~(E$5=fg3MFBn2@ph)CJaiTnwgYlw^xErAdnD;s&w{J zl$)VOe#(Kzm*y_Z;3g3xC-3mWqsDJ|<9$Qputtt@AVFHd_KITHWX$RyM+%84JVg$L z0U@C{@nzB{zXbg?{9qN6cJWdGnrt{^xN?kI(dXtP2f6X^9Cekk))_E6H_g(Hd6J27 zR&#@Jo|^JhLdCwt3BL2KhZvunq8f)Itr8`;bPgp2wMsx3252p)CLvlWoO5NSP}tIm zd#HpV?aqLeP9KjaP=YvA1ggvlwU%WXbH$;1kWvzd0a}%Ub8h6nghwI}zw_~j{NH@_ zB0v4AtqgOEwVuyDvCiUDgcFV%caQVeU%A91s|DakDk`WP`)}-;p5Z%>oj_`V?G0eK zfpZH~kDd_w1&TR9LKNlCZ=yLMh@<5lMwso+ys!_j`QipT5Gi z2d^b9JjS_lmj%Reh@?!)5v3rD!h(0?*3lmpM1d}$j~1M{yiPyM>7^;&J3_4pl|n0t zDD4j+%IGM(*xVfJ9L_q7bEAzaQ!(+xvC}@wJxM4*lp(l1$ceSYBhd=hJ2_9EX>z%p z^QT|G$iPTM^B7z0Gt81O6mUKdXsvY^wA;8eM{KO%Y>QyWZVujY7e@|X$6O<1ZhDGc z^EGx&jWbmZuxSd;A`ryYDk@a0w1=EOzry8aPHT`6g#r8Kt0hhoptUO50Bh+Fa)!C3 zo2I0>qZS1S0oGgAJ6$TFq@U&tvJ5E$La9&^lqv-ym3%G_96<-`9M+a3f%9$zFF+i9 z`iz%SVvM7(79k+g8h?eR5M`bSj(%!5cD}>keCa%W=h;8erG4>fn$Mo)gbi?B(Mv6t z&RoJZd!=fR)v4{-$;8|iCTAw8CjmRg6!#q2&)oC`Q#*J-o_OlC7ee417-KuGKD5h4bq5MF}?uM*VcB=sFj{PbNnvoH~}wzAIp zMvIB@ajFwF&f19WTc+3&uP`%vkd4(9S{q#s?^y)zsKqg+Fl4sO=W0nnyVv8%Gpn3k zYva8~Nx8{IX)OtK>8Ao&`l&#b!Afg|RFX|tHHwPLgJlGhd?|GA!B~UNjV*zc^GKxxj`z!-M`PEXDgjg8L(lmXU40}Ardg3_bnnQ=^w^w=i zD_^8&ui$MDZ#)!$ixGri4Y&bOWQn4H3#%=TpIRr)4D+)S+;rqR7RE!Ke)J(~#nYTW zx5mMPS5st)Cmw&2kN?&EoIddk`wm>q)L4Z|7?jvYDkf_c_H3DE&z5N#m4INxT)2_x zDJe>ADzmyPBHE0Eev~bE=kdaixUkW2=dspetVKNZ?Gs)IfslUFhQ$b^U2%fedNfd2 ziroC_5@ip5{p!3PY$BjxtXaJcq0wfBbQN{KtL@?}wC7E+{ogL~MV_&0O=E zHxSn=w7YBg^fKf2`Xfw4niFlEXLO#5>;i>+HJo_3XZCH`iaaoBgkT9o5+-vh_>!R?p%3D`f2s zy?#NS_qgST-Q4w>ThX?|mptWYQmw%7g(=tVHnSoQ=tf^dFYdW!LT;Tf!&MD zO^@LmSnsig!TVAxtaGG!jy3)6M{_&9A9AUZ1qHpPVzK_szqjAO@^L3tJ?B6e+CKq<{?YlxHz?;X8i%38aJ z_l7tqt2A0E!T_W%QygI-4h6Y&o4mgpL5)y>z#&zcABsnxJnfxxBWLFD){*88sT5J5 zN8C<{nya-CEuhmH@Eh;{-}%BneHVdZ+ho88e(GL!-g__6>OOz|Qo%L)L( zKoAA0bTLkhC>=p7bC|*dqoi60>JfxWQCNrdARheAagSq@6m!n8)*jN&3`ra?-H1_2 zU<#HXjAbY(I zJCm4e<}28upiv2!otYv4(yZW8v&*^5Eyk-FXB?GEjoFz=oVPeDs8=9=%wx}- zWwqI+xpt00t4C*$(;pU$B@wT`>sGG5<{<6C5NiyX$>?Q$yenam6cS@yX}YeYttla8 zi5!GLhk|-6$TP>V5GXGYfA>$1`MmJ7dMVpxYFIC5_j5YKoL+9((ny#ak4j&qJb&@A zPw-cN@j2f1ORwWKcizQxH6aWG%3vUR&euA9{?{%d5QWjd!zUejeeV zQVD4Fa`L=D2MTW-nY9E`(9bN@I6w)>YA>hV&zP(y^wNTAs9Bs!Fy6A!DOhQzAPs>X zqDa|TUne(~G|O=g_U+qEq(i*YoVmP)!~K&{T$;xQkJPs7)rE;SOyFuix8H)u*^&}n5fOMIKPCH5C#Fg!H~618x=$h zvlL@2g>#$H4N(e9%29dFmsFD-eJqv0IEPRYiN{%wdfglU?R|}EM0-$hxjp3kTE_0# zDhIZYvvaP>L@lI0EYW+tULm0%^}5WLtm8lb=Rd(@8~mr=|6SHEouS^Cr8>FD%&x=i zIP@xBanm6>YnOtqGy`9Nu~g%TC{!ah0kR^e*=^J84e1VY26=&XuB2W@8s+_!l+NKr zS$NHzLsI_V8jDb6O`ym<;NW{iHG^Z0j<@{LEn&6mIS9TpGV#Fl+G^6G0QnXH8@w=yOxishAM zo;tsQl$xE}cQ9EEs8ux9cnW8U0}b92g#pcOiW9KfPHFZnH}0Kaq81Sbl5VfZ(X;0f zg+-~5>G3+XN`f(#tjI~ToHXsz%!d?(BrD7n_&&lynD z*RySDE60vM1tMhkwgtv&AwmdNdIcR5GCes(-^TPZL$jMynw2+!PFplxDZjMz=?nr$ox*yusioj4hu_xDsax zPbK!mQJJB5>!<`H-o0?N`<6U)WVRsCWw}jnoea5VdxL7IFh;P}NttdW#DOHs3#={C zZV>2A1~D%T9!PUb7$`c!tYi{}N8>qk?LlU@Z0GO4y}~dz^s@qE9jmR3L2kHeu|cTH z%ziAf*K#}Jegb`VuGaTe7p|NPJ&Dm&osD>KjAvFps za)!RFO-cu|^^mNvWx^~xl|T|GM<_IDVQ|io6(yqY4jdB2MsBDj3O6V)7W7mtpc)2L zBTe5I-~@v-!}}2LAS)~$OEnJ3O^JX9#$Y|P`x#m(*1H*@7K~Rzs!@Qojz%Tq2d`rBo{cm8!(r9A$EPSw>!zX<8+gq}c$FB#EXT z$29vrj8!-zh?FNybEGUO<0z2yb4!-?vCh&O8X^tW7nrPIm=?q_I0<1_lY(9K5#(6xv%< ztiefy%1Mo7XgoR!0|uEPEefQ7W+%g2R{}O$CYRQhjTXb)AiQVmRLo>GL`#9OhRz`8 z%vzsw>nVltY@d#3#0hCyHiT^%kMKw?ZS*OOM_}>RFuio8z{ga{?+rX_9C zYV%h=Lla$)A_N8PHO263yk&jGP7Alxv)OOBAKWw za%-t7LnSK6Y@b%MV9+heyxbIM6*JvPX!g7GI$hEMlbaH*9sNDHRwF~&MBw}&VJnJGJKtS|eZ;z**rAW({K z=IIVCgS1Z^DJr4n-1?A}Zq9f$K>LEZB&Qa6oF9D2I;gCX66b0QuX>Iz<`UZhg%#MvAiD-8R#8Jc`^Q^RUx~VBa zVxXu+iq+1Li>)DZV=Cm&*${346hHikgjyRAMN}|_W zYvhIC!nun)8#uI-gh7B18lyrg)fyY^h-#$pMTgc#n~iSD1trl+Q%f{y;c3+CI8|kN zwM(9LusE_om0@8B$cb%>R;xow%v2&+S?Qp)qSNd0ot1)anv%qtESCsh!6-*Y%Ak|c z8yK7wB@?Rxe2{R#hQv|8L|x*G3Ts7=LBCI6EwWx=daQ<&0-vRjWi0nwNGB0E>J3FD z*7OR=M59WcN+bnqgC3o(VZ5eT7lJeg9fHsSs@gzbI)9AIC(p2Q_9VriGs@ExNgOji zF~zQ{4zhj6E{?BU;^=q2Nw?jiFa~2wPD6^a6=-5|hJ`IlTwYzFwZ2SJtFU$VRa6^w zE-att^sy(&(v14}B-RvkS{s<6c&=zLDyUd5iIO@ZPz*XPiaalK#VbpN1eFGpbBk0L zwo|wSDjCl_b%Ha;kD`QUYGE6DuX!o;@d>2j$+{M_~825kbeh%zE zz@9yO_wf4(KY#r3$GNcDV$XFqGPksgPIHYXzwt$0annl(!;sH@ z@oQXtC>k@;2(bYVeh^7pZw$}eNwOa zU03h-m3rMj`Q(${d++P@y8oj;`Xm1@tq*_r!@g9w7hjhzU-rNHtH0`}w(a+afAA;# z&Kq9kh19;^?|bjPzvrHN{I0{d`I~@4c5{ zw@arxATy9<+2-|E=<_STXK9MHcJpt&*!^6&PG7mHviI!!u2mbLZ0XRr~mj{ zo1Z)9xc&CqdChBH!vhaI@O=fg{ooDEE$u)^LA5bSr=QYUU*YJNKDBvHeR>OHGxK-^ zLaOJqu1Fbl*XcJ`*}s2(32O74y?gi4TVH0-StF{AQIU(BfA$3GdHz;K5i?8MDYA^- z`ugT^F9gOGnB34Th%2DFwh5W=I6KFZ<4hk5_|-~WAuH9bAe8}57+ z-g|!c_kW+;e)tVklPTKnxoM>&NqEbT+_hOvn4g>5{H)(x% zOa<(_{vc7cMy$dY3d`gl z$E&2xwax3}KroRAQlUnTBd~4THh$~3O6$1q10UdT|KZb&RYS&VRlJK|D6Fi#!Lf(G zLEhfr1NYsB9`!VQ;uD|P6qdD?-bl;({eC%hrA8fi&l_KBHk*9rGoRsyfB1*_m0$Ul z7ZuiUI3&;W&DvxrVSKDYGb#s|t^|QrtK9jqu`!Y);mcqCGC%+GKR?1~j#s|&l>n@* zt+BGQvgx<-eixq)$cn~huhnyb%8?gESt}i(JY&pepvtloAZ94cPH{C>##OOe=d+%P3pE}QcHpRkN@p-uAW^d=@4Z4jei}tx_YYR=9NOGFg^k3iE>VzWd$p@~KaKiZ{Rc z%`Xt}^ViicznP>lg?AZ5V_e?oFdTI7-hI!xpZ)A-x$CaGxbemtUl=5|Z^sxzv)L>I zLcPxH>@0WP`C1-*`Wdvhgi3AJ1fiS)ivmrSrY|si>pT=0g^`>%af;~tIZij~EN$QS zoUp3ZDj)jLhhE@(Uu*?Iz z*yX?e^{;#Hz5n0`Kj?u?bpFwge$?wQ_CZ|n?|a|-Ht&7sRoD1e|M*Y(SHAv7{p_}F zzSrydOP4PBExY&lA9~AA`|Iz#%TI1u`2R)lQ;Tz6%ecYY-~M*q^PcxmtJPkxqeqV( zh4_c;Er1 z7q+5?_sN~5mm83!Cj3kHv%a=gYTM!s zxBInIN69Wv0@p40F3Uf`F65FpBNU8Mt*QWSXxLQtJvU~GODdD>;= z#1nXvm*c`Jz!upEP$z6Zc$my&G|!$Q?XDw)U}}CVTdvxT%`z^XJb^39%CxN3*?#o_ zL>O`UvG3sW9O27x6v1=Nr()C%S(}+b%-!*CeZRSiX|H0kA>NmhQA%laQX`sNAgqLB z%@tlYAFzAp4xDp5{`lkUyJ{~FKJ+bKe$x?(!f@Mdui~MHzQJ&ql3UO3e(1OO^k*L6 z=u=O#v^d8D4}6Y$@41Ilr%tnc=@Mtpon>ihiLtRU*4Nj0>cklkk{`L}HC(lSAK(7= zqx{+5{0~0z;oo6mYLXv&$G_wK|I1JCjfcL;*w`3{58uGY{_M}W`QW@<^DBP$$1b(%Zx zx|wUP-p{#n=Xu%7Ue40eR(|0f@8qecj!~;sx%=)r*|lpotE+1ah8aPiIe6dzYa4Ce z{kmGN6!7rF-{uXk|55(c>)*t`|Mg$x&;Q5g$cloW{n?-8);sRu zBY*UJJn-dja^p=mp_Ia=8TbC+z1(`sD>ec0)5o6SwXc0G7cN}jnuGf}c;En&lasvj ztv|>0*Iz?A%-Fkk4==mr2l(ip{1(vvgqJM0b6R~E6pCi1Sxp^r6fsq;aP_tY4lZpajw>`84QjO-)mn|O zJ@_pS95}!~Jn%)rD5BkJbLh|^!Z76O1N(U7k#DnW_fAZaQWQCN+;IoP;gB#0sn;7M z)e7Si6HLr+;SWFhmqcOAF!uyefQzd9?AXDcJ-bONF=O=_8yii8 z5S%@GcI4{H$##GreAO*{O^SaPxXw7=*VdM*SV5 zHpx)~ZHe$*fs#vy@AJ+hq(tC}(Cpqa!|`~Z)1h6^K(cc_`84j zC)Qd+_HCc%3*UN@#iv-hR$9V9mb98gd)|%kPBS(1n;fL|w^Wp#d|K*WKzRk?^B<*&auYUDw zoIQIM=RDu|)*~cI#6LXz6gM8&!O^Fm=2!oZ-{BQUUc%8Qo@9A-mF4BjTzB2IeEq=( zdFI&@I6UXipXb7b3p{e-8ImX{t2}u8sBcyxaNZ$D6BgwF#-X=*X+2w}>s;t|*}Y|g zg~!q+4AUW&{}O`<^39t_zzKgHgq8Rn-Z${|Qs=4ah>Kw%^gK7E>RJ$0JZ^$naU%9mpV5_Cv4 z4AI_Dm>fYl+3zXPDj*IOl}d~dhA@=GK|tgULvLyI^HDp#r>Z5B)tGv$x!CKGc}H9w zCyGL}@HooI=ddFIiOs2uawNKIAPTs+(t;3{*ZS<7pQRc{^wW&xR)>B+<#Ka~L{XWX zVQ#F!+{`%EKv5L*vYezS7|>#+Wm%{rvf?TjE+CmDJ_|wx3v4v%N+}2A+wE;)^Nza=?1M~K`S>%;mTPq??yA!W#1`+(ZE+Z*{Wx^ z%(1ez0Zt+Mj+uoDvokdUCCQ9onC669vw!ts!1W z_SF?}q{udUlna-ek_0hgVwDyI+GD(9x~7?|M-+vn zaF*P9(xUWDaVXg6S=NUZoTpY1#F3^}QA{>MQd=-cGpey-sn%d?qrtB6F$$B@O3fxX z7eZhq2=CD{cI?};z_Ak-5lE&Q6$)cH*Xm=fWoEoa7zE{Hg|!?%zdBr^>`y{bs9W2&KW zhe-vQ_Y|tUm+ezyRASAUW|vNCM-+l6TS6ym3D@jcpr$pCowqY|N%dfuR;!26J4mN6U}y4{R4%USF6F$Q!XY4=)L2_=R>RBbG`ra+npw@TY04Jx ziWVeK(8QxAU9A*KDUv8= zm=0LpXwmBSS=s1NuO>`15{9`U33b_)Im~$IiIdbTF>{kus+EA1R%-+#%O((8IJB1K z?2vbxC3rD<$qSF`+j5A>dRDDNctNNn<2EFYHHC998WMNHB84Ln5!N}h z6cn!f+l8}4TG8xfT<%!vp+p~l;sSFsQykp8$dP^9S(vG@Fx|i-sYIH!){yIWFVby4 zL$@s`*TY_Qtu5d@)Ye_y(>((-j6g`-@cu8r1HdI0h(ThuUaBgo%Hr7Z!bMsm zUct8f`y8L|V@?5|6kg0oA$Yf4QHx(po7J$7{bl@i93R7J@{)A0Uo&-*uf zny#nPOCtXJ`H{z_q3s+o2fo|ZD8D4}7$eCpmwBxhzh8cFT$L*F=C=BF%B&4&A?Vt` z&2`By`&T?W%R@5|QslZ6>^CK+G0=y^-)+xNa)AXzE|?OiB2i}8s|pPq?STH}*LQ5U zE51Bm`2N)uD^t>sGf$@;>pU?hQWDff$xm@SCF6Wx4vELp zg@@yX=eFnZbmq%biyxLpr7RU8MxGB1|9;tEj9Nqp^GxqXT$~Vcc}J~k!D>@6#+gz| z%1UC4pemr&8bxNK6}gP)V%gZT)P%L7wVs=m;(k{#M8~Z$Jhc`j6|eU-7v~UJV3fko ziBDZmWh!pUnRyoUqh#`#C+Rr*!2L?0-+p(?WIccU`I)*d`To_8ws(B^cwn5C)VcJQ z-O5m}G}pTdH+%m1+HmM5wx;5Czeae^Q`6B86JHM(9!?z($1{(|27j^O!{WJe0-@j% zXGS-1vooxXW~Buxc{T-@LP1X0G!sK+y%sM?wsd8+lIRy{89I^$*Z^84LK0*d*{&tR zX9jPVzrPfDcfTbiM-q~sw|f*4Cl+F3NRD<2oQA|S#}`0V(IY5Ia0K+KQdG+D+aDjW z*7M{0J0|aWK3tgN0-2AaW7s;jwj{(vH%xr^^vuQ>Zm-t7eZ4~$l6JD3TFbc`xpbDc zABg?*ZJ~t7i(WzsM2^Iq@IF$B0v{zlI97$k%Y@bnqd}FDO{LKy6J^3k!CDLMixso< zOfpbP#kMXuOqQ4eG7ExynX_6v1O%P9$&yhPY*a~|#o}~$I$bz65T=Na1%1m{U{sryI?F$J(QIVZ%vf}4sgBNEUELiYF!vJmJ3c4dLjfm*{=RbUKc zotTn9C5cf5XW=k1Qy@tqGt3KP6LO}`3P(aH0Wp!}5(uQsb!9kv%cLX)g4SA^c7&KH6%?jGfBf2TJon^bg58=^H$l$aUe)Zj1}!svH!++X)&)lE z2u%mWM2?Ahm^hiG&SH+xPcZraso?nb2_hxr#}Sb{>mRPDi%h8(PsB{3l7d}TQK?9s z6}n1@GU9}w5*ecerj#g^7R!+-h$#~C3$-Ocq!)~CUP_{sD!8xKjKPD<99>VsGiOZ_ x8IhOM+6TWl9HiuWCE1s2nxIffLR?gS{{cC6K)ve_(slp<002ovPDHLkV1jcGMScJP literal 0 HcmV?d00001 From 6c2e14c6d9571a5094cfb2aa213a4a9bacd21c7c Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Wed, 4 Oct 2023 18:38:41 +0300 Subject: [PATCH 074/243] Implemented generating amphibious units for Pretense unit groups. Amphibious units are selected out of a pre-defined list. Units which the faction has access to are preferred, but certain default unit types are selected as a fall-back to ensure that all the generated units can swim. --- game/pretense/pretensetgogenerator.py | 363 ++++++++++++++++++++--- resources/units/ground_units/LARC-V.yaml | 4 + 2 files changed, 321 insertions(+), 46 deletions(-) create mode 100644 resources/units/ground_units/LARC-V.yaml diff --git a/game/pretense/pretensetgogenerator.py b/game/pretense/pretensetgogenerator.py index aa1ee203..d9363a29 100644 --- a/game/pretense/pretensetgogenerator.py +++ b/game/pretense/pretensetgogenerator.py @@ -7,51 +7,22 @@ create the pydcs groups and statics for those areas and add them to the mission. """ from __future__ import annotations -import logging import random from collections import defaultdict -from typing import Any, Dict, Iterator, List, Optional, TYPE_CHECKING, Type, Tuple +from typing import Dict, Optional, TYPE_CHECKING, Tuple, Type -import dcs.vehicles -from dcs import Mission, Point, unitgroup -from dcs.action import DoScript, SceneryDestructionZone -from dcs.condition import MapObjectIsDead +from dcs import Mission, Point from dcs.countries import * from dcs.country import Country -from dcs.point import StaticPoint, PointAction -from dcs.ships import ( - CVN_71, - CVN_72, - CVN_73, - CVN_75, - Stennis, - Forrestal, - LHA_Tarawa, -) -from dcs.statics import Fortification -from dcs.task import ( - ActivateBeaconCommand, - ActivateICLSCommand, - ActivateLink4Command, - ActivateACLSCommand, - EPLRS, - FireAtPoint, - OptAlarmState, -) -from dcs.translation import String -from dcs.triggers import Event, TriggerOnce, TriggerStart, TriggerZone -from dcs.unit import Unit, InvisibleFARP, BaseFARP, SingleHeliPad, FARP -from dcs.unitgroup import MovingGroup, ShipGroup, StaticGroup, VehicleGroup -from dcs.unittype import ShipType, VehicleType -from dcs.vehicles import vehicle_map, Unarmed +from dcs.unitgroup import StaticGroup, VehicleGroup +from dcs.unittype import VehicleType from game.data.units import UnitClass from game.dcs.groundunittype import GroundUnitType from game.missiongenerator.groundforcepainter import ( - NavalForcePainter, GroundForcePainter, ) -from game.missiongenerator.missiondata import CarrierInfo, MissionData +from game.missiongenerator.missiondata import MissionData from game.missiongenerator.tgogenerator import ( TgoGenerator, HelipadGenerator, @@ -63,9 +34,8 @@ from game.missiongenerator.tgogenerator import ( MissileSiteGenerator, ) from game.point_with_heading import PointWithHeading -from game.radio.RadioFrequencyContainer import RadioFrequencyContainer -from game.radio.radios import RadioFrequency, RadioRegistry -from game.radio.tacan import TacanBand, TacanChannel, TacanRegistry, TacanUsage +from game.radio.radios import RadioRegistry +from game.radio.tacan import TacanRegistry from game.runways import RunwayData from game.theater import ( ControlPoint, @@ -76,15 +46,21 @@ from game.theater import ( ) from game.theater.theatergroundobject import ( CarrierGroundObject, - GenericCarrierGroundObject, LhaGroundObject, MissileSiteGroundObject, BuildingGroundObject, VehicleGroupGroundObject, ) -from game.theater.theatergroup import SceneryUnit, IadsGroundGroup, TheaterGroup +from game.theater.theatergroup import TheaterGroup from game.unitmap import UnitMap -from game.utils import Heading, feet, knots, mps +from pydcs_extensions import ( + Char_M551_Sheridan, + BV410_RBS70, + BV410_RBS90, + BV410, + VAB__50, + VAB_T20_13, +) if TYPE_CHECKING: from game import Game @@ -96,6 +72,30 @@ PRETENSE_GROUND_UNITS_TO_REMOVE_FROM_ASSAULT = [ vehicles.Armor.Stug_III, vehicles.Artillery.Grad_URAL, ] +PRETENSE_AMPHIBIOUS_UNITS = [ + vehicles.Unarmed.LARC_V, + vehicles.Armor.AAV7, + vehicles.Armor.LAV_25, + vehicles.Armor.TPZ, + vehicles.Armor.PT_76, + vehicles.Armor.BMD_1, + vehicles.Armor.BMP_1, + vehicles.Armor.BMP_2, + vehicles.Armor.BMP_3, + vehicles.Armor.BTR_80, + vehicles.Armor.BTR_82A, + vehicles.Armor.BRDM_2, + vehicles.Armor.BTR_D, + vehicles.Armor.MTLB, + vehicles.Armor.ZBD04A, + vehicles.Armor.VAB_Mephisto, + VAB__50, + VAB_T20_13, + Char_M551_Sheridan, + BV410_RBS70, + BV410_RBS90, + BV410, +] class PretenseGroundObjectGenerator(GroundObjectGenerator): @@ -128,6 +128,16 @@ class PretenseGroundObjectGenerator(GroundObjectGenerator): return self.game.iads_considerate_culling(self.ground_object) def ground_unit_of_class(self, unit_class: UnitClass) -> Optional[GroundUnitType]: + """ + Returns a GroundUnitType of the specified class that belongs to the + TheaterGroundObject faction. + + Units, which are known to have pathfinding issues in Pretense missions + are removed based on a pre-defined list. + + Args: + unit_class: Class of unit to return. + """ faction_units = ( set(self.ground_object.coalition.faction.frontline_units) | set(self.ground_object.coalition.faction.artillery_units) @@ -155,6 +165,25 @@ class PretenseGroundObjectGenerator(GroundObjectGenerator): group_role: str, max_num: int, ) -> None: + """ + Generates a single land based TheaterUnit for a Pretense unit group + for a specific TheaterGroup, provided that the group still has room + (defined by the max_num argument). Land based groups don't have + restrictions on the unit types, other than that they must be + accessible by the faction and must be of the specified class. + + Generated units are placed 30 meters from the TheaterGroup + position in a random direction. + + Args: + unit_class: Class of unit to generate. + group: The TheaterGroup to generate the unit/group for. + vehicle_units: List of TheaterUnits. The new unit will be appended to this list. + cp_name: Name of the Control Point. + group_role: Pretense group role, "support" or "assault". + max_num: Maximum number of units to generate per group. + """ + if self.ground_object.coalition.faction.has_access_to_unit_class(unit_class): unit_type = self.ground_unit_of_class(unit_class) if unit_type is not None and len(vehicle_units) < max_num: @@ -178,6 +207,140 @@ class PretenseGroundObjectGenerator(GroundObjectGenerator): ) vehicle_units.append(theater_unit) + def generate_amphibious_unit_of_class( + self, + unit_class: UnitClass, + group: TheaterGroup, + vehicle_units: list[TheaterUnit], + cp_name: str, + group_role: str, + max_num: int, + ) -> None: + """ + Generates a single amphibious TheaterUnit for a Pretense unit group + for a specific TheaterGroup, provided that the group still has room + (defined by the max_num argument). Amphibious units are selected + out of a pre-defined list. Units which the faction has access to + are preferred, but certain default unit types are selected as + a fall-back to ensure that all the generated units can swim. + + Generated units are placed 30 meters from the TheaterGroup + position in a random direction. + + Args: + unit_class: Class of unit to generate. + group: The TheaterGroup to generate the unit/group for. + vehicle_units: List of TheaterUnits. The new unit will be appended to this list. + cp_name: Name of the Control Point. + group_role: Pretense group role, "support" or "assault". + max_num: Maximum number of units to generate per group. + """ + unit_type = None + faction = self.ground_object.coalition.faction + is_player = True + side = ( + 2 + if self.country == self.game.coalition_for(is_player).faction.country + else 1 + ) + default_amphibious_unit = unit_type + default_logistics_unit = unit_type + default_tank_unit_blue = unit_type + default_apc_unit_blue = unit_type + default_ifv_unit_blue = unit_type + default_recon_unit_blue = unit_type + default_atgm_unit_blue = unit_type + default_tank_unit_red = unit_type + default_apc_unit_red = unit_type + default_ifv_unit_red = unit_type + default_recon_unit_red = unit_type + default_atgm_unit_red = unit_type + default_ifv_unit_chinese = unit_type + pretense_amphibious_units = PRETENSE_AMPHIBIOUS_UNITS + random.shuffle(pretense_amphibious_units) + for unit in pretense_amphibious_units: + for groundunittype in GroundUnitType.for_dcs_type(unit): + if unit == vehicles.Unarmed.LARC_V: + default_logistics_unit = groundunittype + elif unit == Char_M551_Sheridan: + default_tank_unit_blue = groundunittype + elif unit == vehicles.Armor.AAV7: + default_apc_unit_blue = groundunittype + elif unit == vehicles.Armor.LAV_25: + default_ifv_unit_blue = groundunittype + elif unit == vehicles.Armor.TPZ: + default_recon_unit_blue = groundunittype + elif unit == vehicles.Armor.VAB_Mephisto: + default_atgm_unit_blue = groundunittype + elif unit == vehicles.Armor.PT_76: + default_tank_unit_red = groundunittype + elif unit == vehicles.Armor.BTR_80: + default_apc_unit_red = groundunittype + elif unit == vehicles.Armor.BMD_1: + default_ifv_unit_red = groundunittype + elif unit == vehicles.Armor.BRDM_2: + default_recon_unit_red = groundunittype + elif unit == vehicles.Armor.BTR_D: + default_atgm_unit_red = groundunittype + elif unit == vehicles.Armor.ZBD04A: + default_ifv_unit_chinese = groundunittype + elif unit == vehicles.Armor.MTLB: + default_amphibious_unit = groundunittype + if self.ground_object.coalition.faction.has_access_to_dcs_type(unit): + if groundunittype.unit_class == unit_class: + unit_type = groundunittype + break + if unit_type is None: + if unit_class == UnitClass.LOGISTICS: + unit_type = default_logistics_unit + elif faction.country.id == China.id: + unit_type = default_ifv_unit_chinese + elif side == 2 and unit_class == UnitClass.TANK: + if faction.mod_settings is not None and faction.mod_settings.frenchpack: + unit_type = default_tank_unit_blue + else: + unit_type = default_apc_unit_blue + elif side == 2 and unit_class == UnitClass.IFV: + unit_type = default_ifv_unit_blue + elif side == 2 and unit_class == UnitClass.APC: + unit_type = default_apc_unit_blue + elif side == 2 and unit_class == UnitClass.ATGM: + unit_type = default_atgm_unit_blue + elif side == 2 and unit_class == UnitClass.RECON: + unit_type = default_recon_unit_blue + elif side == 1 and unit_class == UnitClass.TANK: + unit_type = default_tank_unit_red + elif side == 1 and unit_class == UnitClass.IFV: + unit_type = default_ifv_unit_red + elif side == 1 and unit_class == UnitClass.APC: + unit_type = default_apc_unit_red + elif side == 1 and unit_class == UnitClass.ATGM: + unit_type = default_atgm_unit_red + elif side == 1 and unit_class == UnitClass.RECON: + unit_type = default_recon_unit_red + else: + unit_type = default_amphibious_unit + if unit_type is not None and len(vehicle_units) < max_num: + unit_id = self.game.next_unit_id() + unit_name = f"{cp_name}-{group_role}-{unit_id}" + + spread_out_heading = random.randrange(1, 360) + spread_out_position = group.position.point_from_heading( + spread_out_heading, 30 + ) + ground_unit_pos = PointWithHeading.from_point( + spread_out_position, group.position.heading + ) + + theater_unit = TheaterUnit( + unit_id, + unit_name, + unit_type.dcs_unit_type, + ground_unit_pos, + group.ground_object, + ) + vehicle_units.append(theater_unit) + def generate(self) -> None: if self.culled: return @@ -187,7 +350,6 @@ class PretenseGroundObjectGenerator(GroundObjectGenerator): for group in self.ground_object.groups: vehicle_units: list[TheaterUnit] = [] - ship_units: list[TheaterUnit] = [] # Split the different unit types to be compliant to dcs limitation for unit in group.units: if unit.is_static: @@ -268,20 +430,129 @@ class PretenseGroundObjectGenerator(GroundObjectGenerator): PRETENSE_GROUND_UNIT_GROUP_SIZE, ) elif unit.is_ship and unit.alive: - # All alive Ships - ship_units.append(unit) + print(f"Generating amphibious group at {unit.unit_name}") + # Attach this group to the closest naval group, if available + control_point = self.ground_object.control_point + for ( + other_cp + ) in self.game.theater.closest_friendly_control_points_to( + self.ground_object.control_point + ): + if other_cp.is_fleet: + control_point = other_cp + break + + cp_name_trimmed = "".join( + [i for i in control_point.name.lower() if i.isalnum()] + ) + is_player = True + side = ( + 2 + if self.country + == self.game.coalition_for(is_player).faction.country + else 1 + ) + + try: + number_of_supply_groups = len( + self.game.pretense_ground_supply[side][cp_name_trimmed] + ) + except KeyError: + number_of_supply_groups = 0 + self.game.pretense_ground_supply[side][cp_name_trimmed] = list() + self.game.pretense_ground_assault[side][ + cp_name_trimmed + ] = list() + + if number_of_supply_groups == 0: + # Add supply convoy + group_role = "supply" + group_name = f"{cp_name_trimmed}-{group_role}-{group.id}" + group.name = group_name + + self.generate_amphibious_unit_of_class( + UnitClass.LOGISTICS, + group, + vehicle_units, + cp_name_trimmed, + group_role, + PRETENSE_GROUND_UNIT_GROUP_SIZE, + ) + else: + # Add armor group + group_role = "assault" + group_name = f"{cp_name_trimmed}-{group_role}-{group.id}" + group.name = group_name + + self.generate_amphibious_unit_of_class( + UnitClass.TANK, + group, + vehicle_units, + cp_name_trimmed, + group_role, + PRETENSE_GROUND_UNIT_GROUP_SIZE - 4, + ) + self.generate_amphibious_unit_of_class( + UnitClass.TANK, + group, + vehicle_units, + cp_name_trimmed, + group_role, + PRETENSE_GROUND_UNIT_GROUP_SIZE - 3, + ) + self.generate_amphibious_unit_of_class( + UnitClass.ATGM, + group, + vehicle_units, + cp_name_trimmed, + group_role, + PRETENSE_GROUND_UNIT_GROUP_SIZE - 2, + ) + self.generate_amphibious_unit_of_class( + UnitClass.APC, + group, + vehicle_units, + cp_name_trimmed, + group_role, + PRETENSE_GROUND_UNIT_GROUP_SIZE - 1, + ) + self.generate_amphibious_unit_of_class( + UnitClass.IFV, + group, + vehicle_units, + cp_name_trimmed, + group_role, + PRETENSE_GROUND_UNIT_GROUP_SIZE, + ) + self.generate_amphibious_unit_of_class( + UnitClass.RECON, + group, + vehicle_units, + cp_name_trimmed, + group_role, + PRETENSE_GROUND_UNIT_GROUP_SIZE, + ) if vehicle_units: self.create_vehicle_group(group.group_name, vehicle_units) - if ship_units: - self.create_ship_group(group.group_name, ship_units) def create_vehicle_group( self, group_name: str, units: list[TheaterUnit] ) -> VehicleGroup: vehicle_group: Optional[VehicleGroup] = None + control_point = self.ground_object.control_point + for unit in self.ground_object.units: + if unit.is_ship: + # Unit is naval/amphibious. Attach this group to the closest naval group, if available. + for other_cp in self.game.theater.closest_friendly_control_points_to( + self.ground_object.control_point + ): + if other_cp.is_fleet: + control_point = other_cp + break + cp_name_trimmed = "".join( - [i for i in self.ground_object.control_point.name.lower() if i.isalnum()] + [i for i in control_point.name.lower() if i.isalnum()] ) is_player = True side = ( diff --git a/resources/units/ground_units/LARC-V.yaml b/resources/units/ground_units/LARC-V.yaml new file mode 100644 index 00000000..2da32ca6 --- /dev/null +++ b/resources/units/ground_units/LARC-V.yaml @@ -0,0 +1,4 @@ +class: Logistics +price: 3 +variants: + LARC-V Amphibious Cargo Vehicle: null From e7b84e18d19a3427d6de6dac151e351b54a6a62d Mon Sep 17 00:00:00 2001 From: Raffson Date: Wed, 4 Oct 2023 19:49:34 +0200 Subject: [PATCH 075/243] Fix errors due to LaserCodeRegistry move --- game/pretense/pretenseaircraftgenerator.py | 2 +- .../pretenseflightgroupconfigurator.py | 15 +++------ game/pretense/pretensemissiongenerator.py | 32 ++++++------------- 3 files changed, 15 insertions(+), 34 deletions(-) diff --git a/game/pretense/pretenseaircraftgenerator.py b/game/pretense/pretenseaircraftgenerator.py index 5157e3c6..85a29793 100644 --- a/game/pretense/pretenseaircraftgenerator.py +++ b/game/pretense/pretenseaircraftgenerator.py @@ -21,8 +21,8 @@ from game.ato.starttype import StartType from game.coalition import Coalition from game.data.weapons import WeaponType from game.dcs.aircrafttype import AircraftType +from game.lasercodes.lasercoderegistry import LaserCodeRegistry from game.missiongenerator.aircraft.flightdata import FlightData -from game.missiongenerator.lasercoderegistry import LaserCodeRegistry from game.missiongenerator.missiondata import MissionData from game.pretense.pretenseflightgroupconfigurator import ( PretenseFlightGroupConfigurator, diff --git a/game/pretense/pretenseflightgroupconfigurator.py b/game/pretense/pretenseflightgroupconfigurator.py index b6a6c7a6..18a75981 100644 --- a/game/pretense/pretenseflightgroupconfigurator.py +++ b/game/pretense/pretenseflightgroupconfigurator.py @@ -1,32 +1,27 @@ from __future__ import annotations -import logging from datetime import datetime from typing import Any, Optional, TYPE_CHECKING from dcs import Mission -from dcs.flyingunit import FlyingUnit -from dcs.unit import Skill from dcs.unitgroup import FlyingGroup from game.ato import Flight, FlightType from game.data.weapons import Pylon +from game.lasercodes.lasercoderegistry import LaserCodeRegistry +from game.missiongenerator.aircraft.aircraftbehavior import AircraftBehavior +from game.missiongenerator.aircraft.aircraftpainter import AircraftPainter +from game.missiongenerator.aircraft.flightdata import FlightData from game.missiongenerator.aircraft.flightgroupconfigurator import ( FlightGroupConfigurator, ) -from game.missiongenerator.lasercoderegistry import LaserCodeRegistry +from game.missiongenerator.aircraft.waypoints import WaypointGenerator from game.missiongenerator.missiondata import MissionData from game.radio.radios import RadioRegistry from game.radio.tacan import ( TacanRegistry, ) from game.runways import RunwayData -from game.squadrons import Pilot -from game.missiongenerator.aircraft.aircraftbehavior import AircraftBehavior -from game.missiongenerator.aircraft.aircraftpainter import AircraftPainter -from game.missiongenerator.aircraft.flightdata import FlightData -from game.missiongenerator.aircraft.waypoints import WaypointGenerator -from game.theater import Fob if TYPE_CHECKING: from game import Game diff --git a/game/pretense/pretensemissiongenerator.py b/game/pretense/pretensemissiongenerator.py index 8894aaf3..79bf3eec 100644 --- a/game/pretense/pretensemissiongenerator.py +++ b/game/pretense/pretensemissiongenerator.py @@ -1,12 +1,10 @@ from __future__ import annotations -import logging from datetime import datetime from pathlib import Path -from typing import TYPE_CHECKING, cast, List +from typing import TYPE_CHECKING import dcs.lua -from dataclasses import field from dcs import Mission, Point from dcs.coalition import Coalition from dcs.countries import ( @@ -14,22 +12,8 @@ from dcs.countries import ( CombinedJointTaskForcesBlue, CombinedJointTaskForcesRed, ) -from dcs.task import OptReactOnThreat -from game.atcdata import AtcData -from game.dcs.beacons import Beacons - -from game.naming import namegen -from game.radio.radios import RadioFrequency, RadioRegistry, MHz -from game.radio.tacan import TacanRegistry -from game.theater import Airfield -from game.theater.bullseye import Bullseye -from game.unitmap import UnitMap -from game.pretense.pretenseaircraftgenerator import PretenseAircraftGenerator -from game.missiongenerator.briefinggenerator import ( - BriefingGenerator, - MissionInfoGenerator, -) +from game.lasercodes.lasercoderegistry import LaserCodeRegistry from game.missiongenerator.convoygenerator import ConvoyGenerator from game.missiongenerator.environmentgenerator import EnvironmentGenerator from game.missiongenerator.flotgenerator import FlotGenerator @@ -37,18 +21,20 @@ from game.missiongenerator.forcedoptionsgenerator import ForcedOptionsGenerator from game.missiongenerator.frontlineconflictdescription import ( FrontLineConflictDescription, ) -from game.missiongenerator.kneeboard import KneeboardGenerator -from game.missiongenerator.lasercoderegistry import LaserCodeRegistry from game.missiongenerator.missiondata import MissionData from game.missiongenerator.tgogenerator import TgoGenerator +from game.missiongenerator.visualsgenerator import VisualsGenerator +from game.naming import namegen +from game.pretense.pretenseaircraftgenerator import PretenseAircraftGenerator +from game.radio.radios import RadioRegistry +from game.radio.tacan import TacanRegistry +from game.theater.bullseye import Bullseye +from game.unitmap import UnitMap from .pretenseluagenerator import PretenseLuaGenerator from .pretensetgogenerator import PretenseTgoGenerator from .pretensetriggergenerator import PretenseTriggerGenerator -from game.missiongenerator.visualsgenerator import VisualsGenerator -from ..ato import Flight from ..ato.airtaaskingorder import AirTaskingOrder from ..missiongenerator import MissionGenerator -from ..radio.TacanContainer import TacanContainer if TYPE_CHECKING: from game import Game From 4f742b51f7f1796ba9e1f22d4aa3e75e85099858 Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Mon, 9 Oct 2023 10:43:25 +0300 Subject: [PATCH 076/243] Moved SAM site presets from presets.defenses.sam to presets.defenses.red/blue --- game/pretense/pretenseluagenerator.py | 4 +- resources/plugins/pretense/init_header.lua | 128 +++++++++++++++------ 2 files changed, 94 insertions(+), 38 deletions(-) diff --git a/game/pretense/pretenseluagenerator.py b/game/pretense/pretenseluagenerator.py index af97601b..d29880f0 100644 --- a/game/pretense/pretenseluagenerator.py +++ b/game/pretense/pretenseluagenerator.py @@ -74,7 +74,9 @@ class PretenseLuaGenerator(LuaGenerator): preset: str, cp_side_str: str, cp_name_trimmed: str ) -> str: lua_string_zones = ( - " presets.defenses.sam." + " presets.defenses." + + cp_side_str + + "." + preset + ":extend({ name='" + cp_name_trimmed diff --git a/resources/plugins/pretense/init_header.lua b/resources/plugins/pretense/init_header.lua index a8585391..c73f5444 100644 --- a/resources/plugins/pretense/init_header.lua +++ b/resources/plugins/pretense/init_header.lua @@ -514,34 +514,34 @@ presets = { type='defense', template='shorad-red', }), - sam = Preset:new({ - display = 'SAM', - cost=3000, - type='defense', - template='sam-red', + sa2 = Preset:new({ + display = 'SAM', + cost=3000, + type='defense', + template='sa2', }), sa10 = Preset:new({ - display = 'SAM', - cost=3000, - type='defense', + display = 'SAM', + cost=3000, + type='defense', template='sa10', }), sa5 = Preset:new({ - display = 'SAM', - cost=3000, - type='defense', + display = 'SAM', + cost=3000, + type='defense', template='sa5', }), sa3 = Preset:new({ - display = 'SAM', - cost=3000, - type='defense', + display = 'SAM', + cost=3000, + type='defense', template='sa3', }), sa6 = Preset:new({ - display = 'SAM', - cost=3000, - type='defense', + display = 'SAM', + cost=3000, + type='defense', template='sa6', }), sa11 = Preset:new({ @@ -550,42 +550,96 @@ presets = { type='defense', template='sa11', }), + hawk = Preset:new({ + display = 'SAM', + cost=3000, + type='defense', + template='hawk', + }), + patriot = Preset:new({ + display = 'SAM', + cost=3000, + type='defense', + template='patriot', + }), + nasams = Preset:new({ + display = 'SAM', + cost=3000, + type='defense', + template='nasams', + }), redShipGroup = Preset:new({ - display = 'SAM', - cost=3000, - type='defense', + display = 'SAM', + cost=3000, + type='defense', template='redShipGroup', }) }, blue = { infantry = Preset:new({ - display = 'Infantry', - cost=2000, - type='defense', + display = 'Infantry', + cost=2000, + type='defense', template='infantry-blue', }), shorad = Preset:new({ - display = 'SAM', - cost=2500, - type='defense', + display = 'SAM', + cost=2500, + type='defense', template='shorad-blue', }), - sam = Preset:new({ - display = 'SAM', - cost=3000, - type='defense', - template='sam-blue', + sa2 = Preset:new({ + display = 'SAM', + cost=3000, + type='defense', + template='sa2', + }), + sa10 = Preset:new({ + display = 'SAM', + cost=3000, + type='defense', + template='sa10', + }), + sa5 = Preset:new({ + display = 'SAM', + cost=3000, + type='defense', + template='sa5', + }), + sa3 = Preset:new({ + display = 'SAM', + cost=3000, + type='defense', + template='sa3', + }), + sa6 = Preset:new({ + display = 'SAM', + cost=3000, + type='defense', + template='sa6', + }), + sa11 = Preset:new({ + display = 'SAM', + cost=3000, + type='defense', + template='sa11', + }), + hawk = Preset:new({ + display = 'SAM', + cost=3000, + type='defense', + template='hawk', }), patriot = Preset:new({ - display = 'SAM', - cost=3000, - type='defense', + display = 'SAM', + cost=3000, + type='defense', template='patriot', }), nasams = Preset:new({ - display = 'SAM', - cost=3000, - type='defense', + display = 'SAM', + cost=3000, + type='defense', template='nasams', }), blueShipGroup = Preset:new({ From 04c32073aa927fdc5cb562a4c35cacda00bcb861 Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Mon, 9 Oct 2023 10:51:13 +0300 Subject: [PATCH 077/243] In the event of too many channel users, fail gracefully by reusing a random channel instead of always the previous one. --- game/radio/radios.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/game/radio/radios.py b/game/radio/radios.py index cf02f040..85d67d0f 100644 --- a/game/radio/radios.py +++ b/game/radio/radios.py @@ -405,15 +405,19 @@ class RadioRegistry: already allocated. """ try: + while_count = 0 while (channel := random_frequency(radio)) in self.allocated_channels: + while_count += 1 + if while_count > 1000: + raise StopIteration pass self.reserve(channel) return channel except StopIteration: # In the event of too many channel users, fail gracefully by reusing - # the last channel. + # a channel. # https://github.com/dcs-liberation/dcs_liberation/issues/598 - channel = radio.last_channel + channel = random_frequency(radio) logging.warning( f"No more free channels for {radio.name}. Reusing {channel}." ) From a3575995bdaf79dd166eb7d828ed4b241568e7ad Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Mon, 9 Oct 2023 20:17:31 +0300 Subject: [PATCH 078/243] Restored compatibility of the Pretense generator after the timedelta -> datetime change and other changes caused by the Liberation merge/sync. --- game/ato/flightplans/pretensecargo.py | 21 ++--- game/pretense/pretenseaircraftgenerator.py | 79 ++++++++++++++----- .../pretenseflightgroupconfigurator.py | 46 +++++++---- game/pretense/pretenseflightgroupspawner.py | 2 +- game/pretense/pretensemissiongenerator.py | 3 +- 5 files changed, 102 insertions(+), 49 deletions(-) diff --git a/game/ato/flightplans/pretensecargo.py b/game/ato/flightplans/pretensecargo.py index 44c1ba02..4022139e 100644 --- a/game/ato/flightplans/pretensecargo.py +++ b/game/ato/flightplans/pretensecargo.py @@ -3,7 +3,7 @@ from __future__ import annotations import random from collections.abc import Iterator from dataclasses import dataclass -from datetime import timedelta +from datetime import datetime from typing import TYPE_CHECKING, Type from game.utils import feet @@ -31,16 +31,20 @@ class PretenseCargoFlightPlan(StandardFlightPlan[FerryLayout]): def tot_waypoint(self) -> FlightWaypoint: return self.layout.arrival - def tot_for_waypoint(self, waypoint: FlightWaypoint) -> timedelta | None: + def tot_for_waypoint(self, waypoint: FlightWaypoint) -> datetime | None: # TOT planning isn't really useful for ferries. They're behind the front # lines so no need to wait for escorts or for other missions to complete. return None - def depart_time_for_waypoint(self, waypoint: FlightWaypoint) -> timedelta | None: + def depart_time_for_waypoint(self, waypoint: FlightWaypoint) -> datetime | None: return None @property - def mission_departure_time(self) -> timedelta: + def mission_begin_on_station_time(self) -> datetime | None: + return None + + @property + def mission_departure_time(self) -> datetime: return self.package.time_over_target @@ -77,14 +81,14 @@ class Builder(IBuilder[PretenseCargoFlightPlan, FerryLayout]): offmap_heading, PRETENSE_CARGO_FLIGHT_DISTANCE ) - altitude_is_agl = self.flight.unit_type.dcs_unit_type.helicopter + altitude_is_agl = self.flight.is_helo altitude = ( - feet(1500) + feet(self.coalition.game.settings.heli_cruise_alt_agl) if altitude_is_agl else self.flight.unit_type.preferred_patrol_altitude ) - builder = WaypointBuilder(self.flight, self.coalition) + builder = WaypointBuilder(self.flight) ferry_layout = FerryLayout( departure=builder.join(offmap_transport_spawn), nav_to=builder.nav_path( @@ -101,8 +105,7 @@ class Builder(IBuilder[PretenseCargoFlightPlan, FerryLayout]): ferry_layout.departure = builder.join(offmap_transport_spawn) ferry_layout.nav_to.append(builder.join(offmap_transport_spawn)) ferry_layout.nav_from.append(builder.join(offmap_transport_spawn)) - print(ferry_layout) return ferry_layout - def build(self) -> PretenseCargoFlightPlan: + def build(self, dump_debug_info: bool = False) -> PretenseCargoFlightPlan: return PretenseCargoFlightPlan(self.flight, self.layout()) diff --git a/game/pretense/pretenseaircraftgenerator.py b/game/pretense/pretenseaircraftgenerator.py index 85a29793..67efe309 100644 --- a/game/pretense/pretenseaircraftgenerator.py +++ b/game/pretense/pretenseaircraftgenerator.py @@ -395,7 +395,7 @@ class PretenseAircraftGenerator: ) print( - f"Generated flight for {flight_type} flying {squadron.aircraft.name} at {squadron.location.name}" + f"Generated flight for {flight_type} flying {squadron.aircraft.display_name} at {squadron.location.name}" ) ato.add_package(package) return @@ -442,7 +442,7 @@ class PretenseAircraftGenerator: divert=cp, ) print( - f"Generated flight for {flight_type} flying {squadron.aircraft.name} at {squadron.location.name}" + f"Generated flight for {flight_type} flying {squadron.aircraft.display_name} at {squadron.location.name}" ) package.add_flight(flight) @@ -483,7 +483,7 @@ class PretenseAircraftGenerator: divert=cp, ) print( - f"Generated flight for {flight_type} flying {squadron.aircraft.name} at {squadron.location.name}" + f"Generated flight for {flight_type} flying {squadron.aircraft.display_name} at {squadron.location.name}" ) package.add_flight(flight) @@ -524,7 +524,7 @@ class PretenseAircraftGenerator: divert=cp, ) print( - f"Generated flight for {flight_type} flying {squadron.aircraft.name} at {squadron.location.name}" + f"Generated flight for {flight_type} flying {squadron.aircraft.display_name} at {squadron.location.name}" ) package.add_flight(flight) @@ -582,11 +582,11 @@ class PretenseAircraftGenerator: StartType.COLD, divert=cp, ) - for roster_pilot in flight.roster.pilots: - if roster_pilot is not None: - roster_pilot.player = True + for roster_pilot in flight.roster.members: + if roster_pilot.pilot is not None: + roster_pilot.pilot.player = True print( - f"Generated flight for {squadron.primary_task} flying {squadron.aircraft.name} at {squadron.location.name}. Pilot client count: {flight.client_count}" + f"Generated flight for {squadron.primary_task} flying {squadron.aircraft.display_name} at {squadron.location.name}. Pilot client count: {flight.client_count}" ) package.add_flight(flight) @@ -720,7 +720,9 @@ class PretenseAircraftGenerator: flight.departure, flight ) logging.info( - f"Generating flight in {flight.coalition.faction.name} package {flight.squadron.aircraft} {flight.flight_type} for target: {package.target.name}, departure: {flight.from_cp.name}" + f"Generating flight in {flight.coalition.faction.name} package" + f" {flight.squadron.aircraft} {flight.flight_type} for target: {package.target.name}," + f" departure: {flight.departure.name}" ) if flight.alive: @@ -761,45 +763,81 @@ class PretenseAircraftGenerator: self.ground_spawns, self.mission_data, ).create_flight_group() - if flight.flight_type == FlightType.CAS: + + control_points_to_scan = ( + list(self.game.theater.closest_opposing_control_points()) + + self.game.theater.controlpoints + ) + + if ( + flight.flight_type == FlightType.CAS + or flight.flight_type == FlightType.TARCAP + ): for conflict in self.game.theater.conflicts(): flight.package.target = conflict break + elif flight.flight_type == FlightType.BARCAP: + for cp in control_points_to_scan: + if cp.coalition != flight.coalition or cp == flight.departure: + continue + if flight.package.target != flight.departure: + break + for mission_target in cp.ground_objects: + flight.package.target = mission_target + break elif ( flight.flight_type == FlightType.STRIKE or flight.flight_type == FlightType.BAI ): - for cp in self.game.theater.closest_opposing_control_points(): - if cp.coalition == flight.coalition: + for cp in control_points_to_scan: + if cp.coalition == flight.coalition or cp == flight.departure: continue + if flight.package.target != flight.departure: + break for mission_target in cp.ground_objects: flight.package.target = mission_target + break elif ( flight.flight_type == FlightType.OCA_RUNWAY or flight.flight_type == FlightType.OCA_AIRCRAFT ): - for cp in self.game.theater.controlpoints: - if cp.coalition == flight.coalition or not isinstance(cp, Airfield): + for cp in control_points_to_scan: + if ( + cp.coalition == flight.coalition + or not isinstance(cp, Airfield) + or cp == flight.departure + ): continue flight.package.target = cp - elif flight.flight_type == FlightType.DEAD: - for cp in self.game.theater.controlpoints: - if cp.coalition == flight.coalition: + break + elif ( + flight.flight_type == FlightType.DEAD + or flight.flight_type == FlightType.SEAD + ): + for cp in control_points_to_scan: + if cp.coalition == flight.coalition or cp == flight.departure: continue + if flight.package.target != flight.departure: + break for ground_object in cp.ground_objects: is_ewr = isinstance(ground_object, EwrGroundObject) is_sam = isinstance(ground_object, SamGroundObject) if is_ewr or is_sam: flight.package.target = ground_object + break elif flight.flight_type == FlightType.AIR_ASSAULT: - for cp in self.game.theater.closest_opposing_control_points(): - if cp.coalition == flight.coalition: + for cp in control_points_to_scan: + if cp.coalition == flight.coalition or cp == flight.departure: continue if flight.is_hercules: if cp.coalition == flight.coalition or not isinstance(cp, Airfield): continue flight.package.target = cp + break + + now = self.game.conditions.start_time + flight.package.set_tot_asap(now) logging.info( f"Configuring flight {group.name} {flight.squadron.aircraft} {flight.flight_type}, number of players: {flight.client_count}" @@ -812,7 +850,6 @@ class PretenseAircraftGenerator: self.time, self.radio_registry, self.tacan_registy, - self.laser_code_registry, self.mission_data, dynamic_runways, self.use_client, @@ -832,7 +869,7 @@ class PretenseAircraftGenerator: or flight.client_count and ( not self.need_ecm - or flight.loadout.has_weapon_of_type(WeaponType.JAMMER) + or flight.any_member_has_weapon_of_type(WeaponType.JAMMER) ) ): self.ewrj_package_dict[id(flight.package)].append(group) diff --git a/game/pretense/pretenseflightgroupconfigurator.py b/game/pretense/pretenseflightgroupconfigurator.py index 18a75981..10bc45a4 100644 --- a/game/pretense/pretenseflightgroupconfigurator.py +++ b/game/pretense/pretenseflightgroupconfigurator.py @@ -3,14 +3,17 @@ from __future__ import annotations from datetime import datetime from typing import Any, Optional, TYPE_CHECKING -from dcs import Mission +from dcs import Mission, Point +from dcs.flyingunit import FlyingUnit from dcs.unitgroup import FlyingGroup from game.ato import Flight, FlightType +from game.ato.flightmember import FlightMember from game.data.weapons import Pylon from game.lasercodes.lasercoderegistry import LaserCodeRegistry from game.missiongenerator.aircraft.aircraftbehavior import AircraftBehavior from game.missiongenerator.aircraft.aircraftpainter import AircraftPainter +from game.missiongenerator.aircraft.bingoestimator import BingoEstimator from game.missiongenerator.aircraft.flightdata import FlightData from game.missiongenerator.aircraft.flightgroupconfigurator import ( FlightGroupConfigurator, @@ -37,7 +40,6 @@ class PretenseFlightGroupConfigurator(FlightGroupConfigurator): time: datetime, radio_registry: RadioRegistry, tacan_registry: TacanRegistry, - laser_code_registry: LaserCodeRegistry, mission_data: MissionData, dynamic_runways: dict[str, RunwayData], use_client: bool, @@ -50,7 +52,6 @@ class PretenseFlightGroupConfigurator(FlightGroupConfigurator): time, radio_registry, tacan_registry, - laser_code_registry, mission_data, dynamic_runways, use_client, @@ -63,7 +64,6 @@ class PretenseFlightGroupConfigurator(FlightGroupConfigurator): self.time = time self.radio_registry = radio_registry self.tacan_registry = tacan_registry - self.laser_code_registry = laser_code_registry self.mission_data = mission_data self.dynamic_runways = dynamic_runways self.use_client = use_client @@ -72,12 +72,12 @@ class PretenseFlightGroupConfigurator(FlightGroupConfigurator): AircraftBehavior(self.flight.flight_type).apply_to(self.flight, self.group) AircraftPainter(self.flight, self.group).apply_livery() self.setup_props() - self.setup_payload() + self.setup_payloads() self.setup_fuel() flight_channel = self.setup_radios() laser_codes: list[Optional[int]] = [] - for unit, pilot in zip(self.group.units, self.flight.roster.pilots): + for unit, pilot in zip(self.group.units, self.flight.roster.members): self.configure_flight_member(unit, pilot, laser_codes) divert = None @@ -90,12 +90,21 @@ class PretenseFlightGroupConfigurator(FlightGroupConfigurator): self.flight, self.group, self.mission, - self.game.conditions.start_time, self.time, self.game.settings, self.mission_data, ).create_waypoints() + divert_position: Point | None = None + if self.flight.divert is not None: + divert_position = self.flight.divert.position + bingo_estimator = BingoEstimator( + self.flight.unit_type.fuel_consumption, + self.flight.arrival.position, + divert_position, + self.flight.flight_plan.waypoints, + ) + self.group.uncontrolled = False return FlightData( @@ -105,7 +114,7 @@ class PretenseFlightGroupConfigurator(FlightGroupConfigurator): flight_type=self.flight.flight_type, units=self.group.units, size=len(self.group.units), - friendly=self.flight.from_cp.captured, + friendly=self.flight.departure.captured, departure_delay=mission_start_time, departure=self.flight.departure.active_runway( self.game.theater, self.game.conditions, self.dynamic_runways @@ -116,21 +125,26 @@ class PretenseFlightGroupConfigurator(FlightGroupConfigurator): divert=divert, waypoints=waypoints, intra_flight_channel=flight_channel, - bingo_fuel=self.flight.flight_plan.bingo_fuel, - joker_fuel=self.flight.flight_plan.joker_fuel, + bingo_fuel=bingo_estimator.estimate_bingo(), + joker_fuel=bingo_estimator.estimate_joker(), custom_name=self.flight.custom_name, laser_codes=laser_codes, ) - def setup_payload(self) -> None: - for p in self.group.units: - p.pylons.clear() + def setup_payloads(self) -> None: + for unit, member in zip(self.group.units, self.flight.iter_members()): + self.setup_payload(unit, member) + + def setup_payload(self, unit: FlyingUnit, member: FlightMember) -> None: + unit.pylons.clear() + + loadout = member.loadout if self.flight.flight_type == FlightType.SEAD: - self.flight.loadout = self.flight.loadout.default_for_task_and_aircraft( + loadout = member.loadout.default_for_task_and_aircraft( FlightType.SEAD_SWEEP, self.flight.unit_type.dcs_unit_type ) - loadout = self.flight.loadout + if self.game.settings.restrict_weapons_by_date: loadout = loadout.degrade_for_date(self.flight.unit_type, self.game.date) @@ -138,4 +152,4 @@ class PretenseFlightGroupConfigurator(FlightGroupConfigurator): if weapon is None: continue pylon = Pylon.for_aircraft(self.flight.unit_type, pylon_number) - pylon.equip(self.group, weapon) + pylon.equip(unit, weapon) diff --git a/game/pretense/pretenseflightgroupspawner.py b/game/pretense/pretenseflightgroupspawner.py index 4ddbb6e8..cdb10a23 100644 --- a/game/pretense/pretenseflightgroupspawner.py +++ b/game/pretense/pretenseflightgroupspawner.py @@ -118,7 +118,7 @@ class PretenseFlightGroupSpawner(FlightGroupSpawner): raise RuntimeError( f"Cannot spawn fixed-wing aircraft at {cp} because of insufficient ground spawn slots." ) - pilot_count = len(self.flight.roster.pilots) + pilot_count = len(self.flight.roster.members) if ( not is_heli and self.flight.roster.player_count != pilot_count diff --git a/game/pretense/pretensemissiongenerator.py b/game/pretense/pretensemissiongenerator.py index 79bf3eec..ae08b766 100644 --- a/game/pretense/pretensemissiongenerator.py +++ b/game/pretense/pretensemissiongenerator.py @@ -148,7 +148,7 @@ class PretenseMissionGenerator(MissionGenerator): player_cp = front_line.blue_cp enemy_cp = front_line.red_cp conflict = FrontLineConflictDescription.frontline_cas_conflict( - front_line, self.game.theater, self.game.settings + front_line, self.game.theater ) # Generate frontline ops player_gp = self.game.ground_planners[player_cp.id].units_per_cp[ @@ -166,7 +166,6 @@ class PretenseMissionGenerator(MissionGenerator): self.unit_map, self.radio_registry, self.mission_data, - self.laser_code_registry, ) ground_conflict_gen.generate() From 2b5c415159370525c6ee652d35c9962bff797829 Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Sun, 15 Oct 2023 18:16:58 +0300 Subject: [PATCH 079/243] Implemented generating runway zones at airports in Pretense. --- game/pretense/pretensetriggergenerator.py | 44 +++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/game/pretense/pretensetriggergenerator.py b/game/pretense/pretensetriggergenerator.py index d01fdf2a..4eeae332 100644 --- a/game/pretense/pretensetriggergenerator.py +++ b/game/pretense/pretensetriggergenerator.py @@ -54,6 +54,8 @@ TRIGGER_RADIUS_PRETENSE_TGO = 500 TRIGGER_RADIUS_PRETENSE_SUPPLY = 500 TRIGGER_RADIUS_PRETENSE_HELI = 1000 TRIGGER_RADIUS_PRETENSE_CARRIER = 50000 +TRIGGER_RUNWAY_LENGTH_PRETENSE = 2500 +TRIGGER_RUNWAY_WIDTH_PRETENSE = 400 class Silence(Option): @@ -212,6 +214,48 @@ class PretenseTriggerGenerator: color=zone_color, ) break + airfields = [ + cp for cp in self.game.theater.controlpoints if isinstance(cp, Airfield) + ] + for airfield in airfields: + cp_airport = self.mission.terrain.airport_by_id(airfield.airport.id) + cp_name_trimmed = "".join( + [i for i in cp_airport.name.lower() if i.isalnum()] + ) + zone_color = {1: 0.0, 2: 1.0, 3: 0.5, 4: 0.15} + if cp_airport is None: + raise RuntimeError( + f"Could not find {airfield.airport.name} in the mission" + ) + for runway in cp_airport.runways: + runway_end_1 = cp_airport.position.point_from_heading( + runway.heading, TRIGGER_RUNWAY_LENGTH_PRETENSE / 2 + ) + runway_end_2 = cp_airport.position.point_from_heading( + runway.heading + 180, TRIGGER_RUNWAY_LENGTH_PRETENSE / 2 + ) + runway_verticies = [ + runway_end_1.point_from_heading( + runway.heading - 90, TRIGGER_RUNWAY_WIDTH_PRETENSE / 2 + ), + runway_end_1.point_from_heading( + runway.heading + 90, TRIGGER_RUNWAY_WIDTH_PRETENSE / 2 + ), + runway_end_2.point_from_heading( + runway.heading + 90, TRIGGER_RUNWAY_WIDTH_PRETENSE / 2 + ), + runway_end_2.point_from_heading( + runway.heading - 90, TRIGGER_RUNWAY_WIDTH_PRETENSE / 2 + ), + ] + trigger_zone = self.mission.triggers.add_triggerzone_quad( + cp_airport.position, + runway_verticies, + hidden=False, + name=f"{cp_name_trimmed}-runway-{runway.id}", + color=zone_color, + ) + break def generate(self) -> None: player_coalition = "blue" From 18a421f78eca96d65850e692eeebbb3bfe006296 Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Sun, 22 Oct 2023 21:45:36 +0300 Subject: [PATCH 080/243] Fixed the bug of not generating opposing force ground unit groups at Pretense zones. Added the country name to ground vehicle group names to avoid duplicates. --- game/pretense/pretensetgogenerator.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/game/pretense/pretensetgogenerator.py b/game/pretense/pretensetgogenerator.py index d9363a29..bab1f089 100644 --- a/game/pretense/pretensetgogenerator.py +++ b/game/pretense/pretensetgogenerator.py @@ -347,6 +347,9 @@ class PretenseGroundObjectGenerator(GroundObjectGenerator): cp_name_trimmed = "".join( [i for i in self.ground_object.control_point.name.lower() if i.isalnum()] ) + country_name_trimmed = "".join( + [i for i in self.country.shortname.lower() if i.isalnum()] + ) for group in self.ground_object.groups: vehicle_units: list[TheaterUnit] = [] @@ -355,7 +358,7 @@ class PretenseGroundObjectGenerator(GroundObjectGenerator): if unit.is_static: # Add supply convoy group_role = "supply" - group_name = f"{cp_name_trimmed}-{group_role}-{group.id}" + group_name = f"{cp_name_trimmed}-{country_name_trimmed}-{group_role}-{group.id}" group.name = group_name self.generate_ground_unit_of_class( @@ -369,7 +372,7 @@ class PretenseGroundObjectGenerator(GroundObjectGenerator): elif unit.is_vehicle and unit.alive: # Add armor group group_role = "assault" - group_name = f"{cp_name_trimmed}-{group_role}-{group.id}" + group_name = f"{cp_name_trimmed}-{country_name_trimmed}-{group_role}-{group.id}" group.name = group_name self.generate_ground_unit_of_class( @@ -430,7 +433,6 @@ class PretenseGroundObjectGenerator(GroundObjectGenerator): PRETENSE_GROUND_UNIT_GROUP_SIZE, ) elif unit.is_ship and unit.alive: - print(f"Generating amphibious group at {unit.unit_name}") # Attach this group to the closest naval group, if available control_point = self.ground_object.control_point for ( @@ -467,7 +469,7 @@ class PretenseGroundObjectGenerator(GroundObjectGenerator): if number_of_supply_groups == 0: # Add supply convoy group_role = "supply" - group_name = f"{cp_name_trimmed}-{group_role}-{group.id}" + group_name = f"{cp_name_trimmed}-{country_name_trimmed}-{group_role}-{group.id}" group.name = group_name self.generate_amphibious_unit_of_class( @@ -481,7 +483,7 @@ class PretenseGroundObjectGenerator(GroundObjectGenerator): else: # Add armor group group_role = "assault" - group_name = f"{cp_name_trimmed}-{group_role}-{group.id}" + group_name = f"{cp_name_trimmed}-{country_name_trimmed}-{group_role}-{group.id}" group.name = group_name self.generate_amphibious_unit_of_class( @@ -578,7 +580,7 @@ class PretenseGroundObjectGenerator(GroundObjectGenerator): self.set_alarm_state(vehicle_group) GroundForcePainter(faction, vehicle_group.units[0]).apply_livery() - group_role = group_name.split("-")[1] + group_role = group_name.split("-")[2] if group_role == "supply": self.game.pretense_ground_supply[side][cp_name_trimmed].append( f"{vehicle_group.name}" @@ -746,6 +748,7 @@ class PretenseTgoGenerator(TgoGenerator): is_fob_structure=ground_object.is_fob_structure, task=ground_object.task, ) + new_ground_object.groups = ground_object.groups generator = PretenseGroundObjectGenerator( new_ground_object, country, self.game, self.m, self.unit_map ) @@ -760,6 +763,7 @@ class PretenseTgoGenerator(TgoGenerator): control_point=ground_object.control_point, task=ground_object.task, ) + new_ground_object.groups = ground_object.groups generator = PretenseGroundObjectGenerator( new_ground_object, country, self.game, self.m, self.unit_map ) From b508ef9aec70e880b9a30240d19063d33e47a63e Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Sun, 22 Oct 2023 21:46:30 +0300 Subject: [PATCH 081/243] Reduced the number of cargo planes to 2 per side. --- game/pretense/pretenseaircraftgenerator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/game/pretense/pretenseaircraftgenerator.py b/game/pretense/pretenseaircraftgenerator.py index 67efe309..1bfe7b8b 100644 --- a/game/pretense/pretenseaircraftgenerator.py +++ b/game/pretense/pretenseaircraftgenerator.py @@ -57,7 +57,7 @@ PRETENSE_BARCAP_FLIGHTS_PER_CP = 2 PRETENSE_AI_AIRCRAFT_PER_FLIGHT = 2 PRETENSE_AI_AWACS_PER_FLIGHT = 1 PRETENSE_AI_TANKERS_PER_FLIGHT = 1 -PRETENSE_AI_CARGO_PLANES_PER_SIDE = 8 +PRETENSE_AI_CARGO_PLANES_PER_SIDE = 2 PRETENSE_PLAYER_AIRCRAFT_PER_FLIGHT = 1 PRETENSE_PLAYER_FLIGHTS_PER_TYPE = 2 From 87975b9aadf6263e670d6c052e5ac44c8f12bb7d Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Sun, 22 Oct 2023 21:59:21 +0300 Subject: [PATCH 082/243] Implemented a Pretense settings page. --- game/settings/settings.py | 24 ++++++++++++++++++++++++ qt_ui/uiconstants.py | 3 ++- qt_ui/windows/QLiberationWindow.py | 2 +- 3 files changed, 27 insertions(+), 2 deletions(-) diff --git a/game/settings/settings.py b/game/settings/settings.py index bdc42f78..3acc74c8 100644 --- a/game/settings/settings.py +++ b/game/settings/settings.py @@ -49,6 +49,8 @@ FLIGHT_PLANNER_AUTOMATION = "Flight Planner Automation" CAMPAIGN_DOCTRINE_PAGE = "Campaign Doctrine" DOCTRINE_DISTANCES_SECTION = "Doctrine distances" +PRETENSE_PAGE = "Pretense" + MISSION_GENERATOR_PAGE = "Mission Generator" GAMEPLAY_SECTION = "Gameplay" @@ -971,6 +973,28 @@ class Settings: "if the start-up type was manually changed to 'In-Flight'." ), ) + pretense_maxdistfromfront_distance: int = bounded_int_option( + "Max distance from front (km)", + page=PRETENSE_PAGE, + section=GENERAL_SECTION, + default=130, + min=10, + max=10000, + ) + pretense_closeoverride_distance: int = bounded_int_option( + "Close override distance (km)", + page=PRETENSE_PAGE, + section=GENERAL_SECTION, + default=28, + min=5, + max=10000, + ) + pretense_do_not_generate_sead_missions: bool = boolean_option( + "Do not generate player SEAD missions", + page=PRETENSE_PAGE, + section=PERFORMANCE_SECTION, + default=False, + ) # Cheating. Not using auto settings because the same page also has buttons which do # not alter settings. diff --git a/qt_ui/uiconstants.py b/qt_ui/uiconstants.py index d431fc29..99d84386 100644 --- a/qt_ui/uiconstants.py +++ b/qt_ui/uiconstants.py @@ -32,7 +32,8 @@ def load_icons(): "./resources/ui/misc/" + get_theme_icons() + "/github.png" ) ICONS["Ukraine"] = QPixmap("./resources/ui/misc/ukraine.png") - ICONS["Pretense"] = QPixmap("./resources/ui/misc/pretense_discord.png") + ICONS["Pretense"] = QPixmap("./resources/ui/misc/pretense.png") + ICONS["Pretense_discord"] = QPixmap("./resources/ui/misc/pretense_discord.png") ICONS["Pretense_generate"] = QPixmap("./resources/ui/misc/pretense_generate.png") ICONS["Control Points"] = QPixmap( diff --git a/qt_ui/windows/QLiberationWindow.py b/qt_ui/windows/QLiberationWindow.py index 13d0988a..f6a03968 100644 --- a/qt_ui/windows/QLiberationWindow.py +++ b/qt_ui/windows/QLiberationWindow.py @@ -195,7 +195,7 @@ class QLiberationWindow(QMainWindow): ) self.pretenseLinkAction = QAction("&DCS: Pretense", self) - self.pretenseLinkAction.setIcon(QIcon(CONST.ICONS["Pretense"])) + self.pretenseLinkAction.setIcon(QIcon(CONST.ICONS["Pretense_discord"])) self.pretenseLinkAction.triggered.connect( lambda: webbrowser.open_new_tab( "https://" + "discord.gg" + "/" + "PtPsb9Mpk6" From 595c468ab2d50fa5d0c35e33b9e159a5afe1692b Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Sun, 22 Oct 2023 22:01:00 +0300 Subject: [PATCH 083/243] Restored Retribution scripts and triggers to Pretense campaigns. Pretense progress can now be translated back to Retribution. Moved the trigger clearing from pretenseluagenerator.py to pretensemissiongenerator.py keepActive is now only enabled for airbases and carriers/LHAs, for performance reasons. --- game/pretense/pretenseluagenerator.py | 207 +++++++++++++++++++++- game/pretense/pretensemissiongenerator.py | 1 + game/pretense/pretensetriggergenerator.py | 78 +++++++- 3 files changed, 273 insertions(+), 13 deletions(-) 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: From 79088584338b236b9e31af66be136efa9d3ed4c9 Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Sun, 22 Oct 2023 22:03:29 +0300 Subject: [PATCH 084/243] Updated pretense_compiled.lua to version 1.3.6. Implemented Pretense settings in pretense_compiled.lua Added new function moveOffRoadToPointAndAssault() to enable assault groups to drive off-road and thus avoid some of the bridges where they might get stuck. --- .../plugins/pretense/pretense_compiled.lua | 139 +++++++++++++++--- 1 file changed, 121 insertions(+), 18 deletions(-) diff --git a/resources/plugins/pretense/pretense_compiled.lua b/resources/plugins/pretense/pretense_compiled.lua index a3aaf78c..cf8bb881 100644 --- a/resources/plugins/pretense/pretense_compiled.lua +++ b/resources/plugins/pretense/pretense_compiled.lua @@ -32,6 +32,8 @@ Config.buildSpeed = Config.buildSpeed or 10 -- structure and defense build speed Config.supplyBuildSpeed = Config.supplyBuildSpeed or 85 -- supply helicopters and convoys build speed Config.missionBuildSpeedReduction = Config.missionBuildSpeedReduction or 0.12 -- reduction of build speed in case of ai missions Config.maxDistFromFront = Config.maxDistFromFront or 129640 -- max distance in meters from front after which zone is forced into low activity state (export mode) +Config.closeOverride = Config.closeOverride or 27780 -- close override distance in meters from front within which zone is never forced into low activity state +Config.disablePlayerSead = Config.disablePlayerSead or false Config.missions = Config.missions or {} @@ -503,6 +505,8 @@ end GroupMonitor = {} do GroupMonitor.blockedDespawnTime = 10*60 --used to despawn aircraft that are stuck taxiing for some reason + GroupMonitor.blockedDespawnTimeGround = 30*60 --used to despawn ground units that are stuck en route for some reason + GroupMonitor.blockedDespawnTimeGroundAssault = 90*60 --used to despawn assault units that are stuck en route for some reason GroupMonitor.landedDespawnTime = 10 GroupMonitor.atDestinationDespawnTime = 2*60 GroupMonitor.recoveryReduction = 0.8 -- reduce recovered resource from landed missions by this amount to account for maintenance @@ -638,7 +642,13 @@ do group.state = 'enroute' group.lastStateTime = timer.getAbsTime() MissionTargetRegistry.addBaiTarget(group) - elseif timer.getAbsTime() - group.lastStateTime > GroupMonitor.blockedDespawnTime then + elseif group.product.missionType == 'assault' and timer.getAbsTime() - group.lastStateTime > GroupMonitor.blockedDespawnTimeGroundAssault then + env.info('GroupMonitor: processSurface ['..group.name..'] despawned due to blockage') + gr:destroy() + local todeliver = math.floor(group.product.cost) + z:addResource(todeliver) + return true + elseif timer.getAbsTime() - group.lastStateTime > GroupMonitor.blockedDespawnTimeGround then env.info('GroupMonitor: processSurface ['..group.name..'] despawned due to blockage') gr:destroy() local todeliver = math.floor(group.product.cost) @@ -734,7 +744,7 @@ do y = group.target.zone.point.z } - TaskExtensions.moveOnRoadToPointAndAssault(gr, tp, group.target.built) + TaskExtensions.moveOffRoadToPointAndAssault(gr, tp, group.target.built) group.isstopped = false end end @@ -2135,7 +2145,68 @@ do } group:getController():setTask(mis) end - + + function TaskExtensions.moveOffRoadToPointAndAssault(group, point, targets) + if not group or not point then return end + if not group:isExist() or group:getSize()==0 then return end + local startPos = group:getUnit(1):getPoint() + + local srx, sry = land.getClosestPointOnRoads('roads', startPos.x, startPos.z) + local erx, ery = land.getClosestPointOnRoads('roads', point.x, point.y) + + local mis = { + id='Mission', + params = { + route = { + points = { + [1] = { + type= AI.Task.WaypointType.TURNING_POINT, + x = srx, + y = sry, + speed = 1000, + action = AI.Task.VehicleFormation.DIAMOND + }, + [2] = { + type= AI.Task.WaypointType.TURNING_POINT, + x = erx, + y = ery, + speed = 1000, + action = AI.Task.VehicleFormation.DIAMOND + }, + [3] = { + type= AI.Task.WaypointType.TURNING_POINT, + x = point.x, + y = point.y, + speed = 1000, + action = AI.Task.VehicleFormation.DIAMOND + } + } + } + } + } + + for i,v in pairs(targets) do + if v.type == 'defense' then + local group = Group.getByName(v.name) + if group then + for i,v in ipairs(group:getUnits()) do + local unpos = v:getPoint() + local pnt = {x=unpos.x, y = unpos.z} + + table.insert(mis.params.route.points, { + type= AI.Task.WaypointType.TURNING_POINT, + x = pnt.x, + y = pnt.y, + speed = 10, + action = AI.Task.VehicleFormation.DIAMOND + }) + end + end + end + end + group:getController():setTask(mis) + end + function TaskExtensions.landAtPointFromAir(group, point, alt) if not group or not point then return end if not group:isExist() or group:getSize()==0 then return end @@ -2542,7 +2613,7 @@ do unit = event.initiator }) - env.info('PlayerLogistics - Hercules - '..unitName..'deployed crate with '..amount..' supplies') + env.info('PlayerLogistics - Hercules - '..unitName..' deployed crate with '..amount..' supplies') self.context:processHercCargos(unitName) self.context.hercPreparedDrops[groupId] = nil trigger.action.outTextForUnit(event.initiator:getID(), 'Crate with '..amount..' supplies deployed', 10) @@ -2577,7 +2648,7 @@ do unit = event.initiator }) - env.info('PlayerLogistics - Hercules - '..unitName..'deployed crate with '..toDrop.type) + env.info('PlayerLogistics - Hercules - '..unitName..' deployed crate with '..toDrop.type) self.context:processHercCargos(unitName) self.context.hercPreparedDrops[groupId] = nil @@ -2607,6 +2678,7 @@ do local reschedule = params.context:checkHercCargo(params.unitName, time) if not reschedule then params.context.hercTracker.cargoCheckFunctions[params.unitName] = nil + env.info('PlayerLogistics - Hercules - stopped tracking cargos of '..unitName) end return reschedule @@ -2627,9 +2699,14 @@ do table.insert(remaining, cargo) end else - env.info('PlayerLogistics - Hercules - cargo crashed') + env.info('PlayerLogistics - Hercules - cargo crashed '..tostring(cargo.supply)..' '..tostring(cargo.squad)) + if cargo.squad then + env.info('PlayerLogistics - Hercules - squad crashed '..tostring(cargo.squad.type)) + end + if cargo.unit and cargo.unit:isExist() then - trigger.action.outTextForUnit(cargo.unit:getID(), 'Cargo drop of '..cargo.unit:getPlayerName()..' crashed', 10) + local squadName = PlayerLogistics.getInfantryName(cargo.squad.type) + trigger.action.outTextForUnit(cargo.unit:getID(), 'Cargo drop of '..cargo.unit:getPlayerName()..' with '..squadName..' crashed', 10) end end end @@ -2647,13 +2724,14 @@ do local zone = ZoneCommand.getZoneOfWeapon(cargo.object) if zone then zone:addResource(cargo.supply) - cargo.object:destroy() env.info('PlayerLogistics - Hercules - '..cargo.supply..' delivered to '..zone.name) self:awardSupplyXP(cargo.lastLoaded, zone, cargo.unit, cargo.supply) end elseif cargo.squad then local pos = Utils.getPointOnSurface(cargo.object:getPoint()) + pos.y = pos.z + pos.z = nil local surface = land.getSurfaceType(pos) if surface == land.SurfaceType.LAND or surface == land.SurfaceType.ROAD or surface == land.SurfaceType.RUNWAY then local zn = ZoneCommand.getZoneOfPoint(pos) @@ -2672,13 +2750,12 @@ do else local error = self.squadTracker:spawnInfantry(self.registeredSquadGroups[cargo.squad.type], pos) if not error then - cargo.object:destroy() env.info('PlayerLogistics - Hercules - '..cargo.squad.type..' deployed') local squadName = PlayerLogistics.getInfantryName(cargo.squad.type) - trigger.action.outTextForUnit(cargo.unit:getID(), squadName..' deployed', 10) if cargo.unit and cargo.unit:isExist() and cargo.unit.getPlayerName then + trigger.action.outTextForUnit(cargo.unit:getID(), squadName..' deployed', 10) local player = cargo.unit:getPlayerName() local xp = RewardDefinitions.actions.squadDeploy @@ -2693,8 +2770,17 @@ do end end end + else + env.info('PlayerLogistics - Hercules - '..cargo.squad.type..' dropped on invalid surface '..tostring(surface)) + local cpos = cargo.object:getPoint() + env.info('PlayerLogistics - Hercules - cargo spot X:'..cpos.x..' Y:'..cpos.y..' Z:'..cpos.z) + env.info('PlayerLogistics - Hercules - surface spot X:'..pos.x..' Y:'..pos.y..' Z:'..pos.z) + local squadName = PlayerLogistics.getInfantryName(cargo.squad.type) + trigger.action.outTextForUnit(cargo.unit:getID(), 'Cargo drop of '..cargo.unit:getPlayerName()..' with '..squadName..' crashed', 10) end end + + cargo.object:destroy() end end @@ -3989,6 +4075,22 @@ do end self:refreshText() + + if self.airbaseName then + local ab = Airbase.getByName(self.airbaseName) + if ab then + if ab:autoCaptureIsOn() then ab:autoCapture(false) end + ab:setCoalition(self.side) + else + for i=1,10,1 do + local ab = Airbase.getByName(self.airbaseName..'-'..i) + if ab then + if ab:autoCaptureIsOn() then ab:autoCapture(false) end + ab:setCoalition(self.side) + end + end + end + end end function ZoneCommand:addResource(amount) @@ -4780,7 +4882,7 @@ do product.lastMission = {zoneName = zone.name} timer.scheduleFunction(function(param) local gr = Group.getByName(param.name) - TaskExtensions.moveOnRoadToPointAndAssault(gr, param.point, param.targets) + TaskExtensions.moveOffRoadToPointAndAssault(gr, param.point, param.targets) end, {name=product.name, point={ x=tgtPoint.point.x, y = tgtPoint.point.z}, targets=zone.built}, timer.getTime()+1) end end @@ -5311,7 +5413,7 @@ do product.lastMission = {zoneName = v.name} timer.scheduleFunction(function(param) local gr = Group.getByName(param.name) - TaskExtensions.moveOnRoadToPointAndAssault(gr, param.point, param.targets) + TaskExtensions.moveOffRoadToPointAndAssault(gr, param.point, param.targets) end, {name=product.name, point={ x=tgtPoint.point.x, y = tgtPoint.point.z}, targets=v.built}, timer.getTime()+1) env.info("ZoneCommand - "..product.name.." targeting "..v.name) @@ -5873,7 +5975,7 @@ end BattlefieldManager = {} do - BattlefieldManager.closeOverride = 27780 -- 15nm + BattlefieldManager.closeOverride = Config.closeOverride -- default 15nm BattlefieldManager.farOverride = Config.maxDistFromFront -- default 100nm BattlefieldManager.boostScale = {[0] = 1.0, [1]=1.0, [2]=1.0} BattlefieldManager.noRedZones = false @@ -10452,6 +10554,7 @@ do end end end + return false end function SEAD:getMissionName() @@ -12105,7 +12208,7 @@ do if toGen > 0 then local validMissions = {} for _,v in pairs(Mission.types) do - if self:canCreateMission(v) then + if timer.getAbsTime() - timer.getTime0() > 120 and self:canCreateMission(v) then table.insert(validMissions,v) end end @@ -12565,7 +12668,7 @@ do return CAS_Easy.canCreate() elseif misType == Mission.types.cas_medium then return CAS_Medium.canCreate() - elseif misType == Mission.types.sead then + elseif Config.disablePlayerSead == false and misType == Mission.types.sead then return SEAD.canCreate() elseif misType == Mission.types.dead then return DEAD.canCreate() @@ -12659,7 +12762,7 @@ do function SquadTracker:restoreInfantry(save) - Spawner.createObject(save.name, save.data.name, save.position, 2, 10, 20,{ + Spawner.createObject(save.name, save.data.name, save.position, 2, 20, 30,{ [land.SurfaceType.LAND] = true, [land.SurfaceType.ROAD] = true, [land.SurfaceType.RUNWAY] = true, @@ -12683,7 +12786,7 @@ do function SquadTracker:spawnInfantry(infantryData, position) local callsign = self:generateCallsign() if callsign then - Spawner.createObject(callsign, infantryData.name, position, 2, 10, 20,{ + Spawner.createObject(callsign, infantryData.name, position, 2, 20, 30,{ [land.SurfaceType.LAND] = true, [land.SurfaceType.ROAD] = true, [land.SurfaceType.RUNWAY] = true, @@ -12851,7 +12954,7 @@ do local p = Utils.getPointOnSurface(unPos) p.x = p.x + math.random(-5,5) p.z = p.z + math.random(-5,5) - trigger.action.smoke(p, trigger.smokeColor.Green) + trigger.action.smoke(p, trigger.smokeColor.Blue) squad.lastMarkerDeployedTime = timer.getAbsTime() end end From de0802a3c2293b79da6c8fceb5d57e9975c11db9 Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Wed, 22 Nov 2023 19:46:44 +0200 Subject: [PATCH 085/243] Implemented new options in settings: - Pretense: Extra friendly zone connections - Add connections from each zone to this many closest friendly zones, which don't have an existing supply route defined in the campaign. - Number of cargo planes per side - Number of AI SEAD flights per control point / zone - Number of AI CAS flights per control point / zone - Number of AI BAI flights per control point / zone - Number of AI Strike flights per control point / zone - Number of AI BARCAP flights per control point / zone - Number of AI aircraft per flight - Number of player flights per aircraft type at each base - Number of AI cargo planes per side Implemented CAS helo mission handling for Pretense. Implemented separate pretense_air_groups container for storing/referencing Flight objects. Tweaked the supply costs of SAM sites and Command centers. Will no longer generate player air starts at roadbases either. Restored the missing DEAD flights to Pretense. Removed spawning of frontline units and moved the JTAC spawning to pretensemissiongenerator.py --- game/game.py | 2 + game/missiongenerator/tgogenerator.py | 315 ++++++++++-------- game/pretense/pretenseaircraftgenerator.py | 112 ++++--- game/pretense/pretenseflightgroupspawner.py | 14 +- game/pretense/pretenseluagenerator.py | 64 ++-- game/pretense/pretensemissiongenerator.py | 81 +++-- game/settings/settings.py | 93 +++++- resources/plugins/pretense/init_header.lua | 30 +- .../plugins/pretense/pretense_compiled.lua | 89 +---- 9 files changed, 461 insertions(+), 339 deletions(-) diff --git a/game/game.py b/game/game.py index 47f8cfc4..3f71939d 100644 --- a/game/game.py +++ b/game/game.py @@ -22,6 +22,7 @@ from game.models.game_stats import GameStats from game.plugins import LuaPluginManager from game.utils import Distance from . import naming, persistency +from .ato import Flight from .ato.flighttype import FlightType from .campaignloader import CampaignAirWingConfig from .coalition import Coalition @@ -155,6 +156,7 @@ class Game: 1: {}, 2: {}, } + self.pretense_air_groups: dict[str, Flight] = {} self.on_load(game_still_initializing=True) diff --git a/game/missiongenerator/tgogenerator.py b/game/missiongenerator/tgogenerator.py index a01f33e1..ed83823e 100644 --- a/game/missiongenerator/tgogenerator.py +++ b/game/missiongenerator/tgogenerator.py @@ -290,11 +290,9 @@ class GroundObjectGenerator: # All alive Ships ship_units.append(unit) if vehicle_units: - vg = self.create_vehicle_group(group.group_name, vehicle_units) - vg.hidden_on_mfd = self.ground_object.hide_on_mfd + self.create_vehicle_group(group.group_name, vehicle_units) if ship_units: - sg = self.create_ship_group(group.group_name, ship_units) - sg.hidden_on_mfd = self.ground_object.hide_on_mfd + self.create_ship_group(group.group_name, ship_units) def create_vehicle_group( self, group_name: str, units: list[TheaterUnit] @@ -827,30 +825,45 @@ class HelipadGenerator: else: self.helipads.append(sg) - # Generate a FARP Ammo and Fuel stack for each pad - self.m.static_group( - country=country, - name=(name + "_fuel"), - _type=Fortification.FARP_Fuel_Depot, - position=pad.position.point_from_heading(helipad.heading.degrees, 35), - heading=pad.heading + 180, - ) - self.m.static_group( - country=country, - name=(name + "_ammo"), - _type=Fortification.FARP_Ammo_Dump_Coating, - position=pad.position.point_from_heading( - helipad.heading.degrees, 35 - ).point_from_heading(helipad.heading.degrees + 90, 10), - heading=pad.heading + 90, - ) - self.m.static_group( - country=country, - name=(name + "_ws"), - _type=Fortification.Windsock, - position=helipad.point_from_heading(helipad.heading.degrees + 45, 35), - heading=pad.heading, - ) + if self.game.position_culled(helipad): + cull_farp_statics = True + if self.cp.coalition.player: + for package in self.cp.coalition.ato.packages: + for flight in package.flights: + if flight.squadron.location == self.cp: + cull_farp_statics = False + break + elif flight.divert and flight.divert == self.cp: + cull_farp_statics = False + break + else: + cull_farp_statics = False + + if not cull_farp_statics: + # Generate a FARP Ammo and Fuel stack for each pad + self.m.static_group( + country=country, + name=(name + "_fuel"), + _type=Fortification.FARP_Fuel_Depot, + position=pad.position.point_from_heading(helipad.heading.degrees, 35), + heading=pad.heading + 180, + ) + self.m.static_group( + country=country, + name=(name + "_ammo"), + _type=Fortification.FARP_Ammo_Dump_Coating, + position=pad.position.point_from_heading( + helipad.heading.degrees, 35 + ).point_from_heading(helipad.heading.degrees + 90, 10), + heading=pad.heading + 90, + ) + self.m.static_group( + country=country, + name=(name + "_ws"), + _type=Fortification.Windsock, + position=helipad.point_from_heading(helipad.heading.degrees + 45, 35), + heading=pad.heading, + ) def append_helipad( self, @@ -927,61 +940,76 @@ class GroundSpawnRoadbaseGenerator: country.id ) - # Generate ammo truck/farp and fuel truck/stack for each pad - if self.game.settings.ground_start_trucks_roadbase: - self.m.vehicle_group( - country=country, - name=(name + "_fuel"), - _type=tanker_type, - position=pad.position.point_from_heading( - ground_spawn[0].heading.degrees + 90, 35 - ), - group_size=1, - heading=pad.heading + 315, - move_formation=PointAction.OffRoad, - ) - self.m.vehicle_group( - country=country, - name=(name + "_ammo"), - _type=ammo_truck_type, - position=pad.position.point_from_heading( - ground_spawn[0].heading.degrees + 90, 35 - ).point_from_heading(ground_spawn[0].heading.degrees + 180, 10), - group_size=1, - heading=pad.heading + 315, - move_formation=PointAction.OffRoad, - ) + if self.game.position_culled(ground_spawn[0]): + cull_farp_statics = True + if self.cp.coalition.player: + for package in self.cp.coalition.ato.packages: + for flight in package.flights: + if flight.squadron.location == self.cp: + cull_farp_statics = False + break + elif flight.divert and flight.divert == self.cp: + cull_farp_statics = False + break else: - self.m.static_group( - country=country, - name=(name + "_fuel"), - _type=Fortification.FARP_Fuel_Depot, - position=pad.position.point_from_heading( - ground_spawn[0].heading.degrees + 90, 35 - ), - heading=pad.heading + 270, - ) - self.m.static_group( - country=country, - name=(name + "_ammo"), - _type=Fortification.FARP_Ammo_Dump_Coating, - position=pad.position.point_from_heading( - ground_spawn[0].heading.degrees + 90, 35 - ).point_from_heading(ground_spawn[0].heading.degrees + 180, 10), - heading=pad.heading + 180, - ) - if self.game.settings.ground_start_ground_power_trucks_roadbase: - self.m.vehicle_group( - country=country, - name=(name + "_power"), - _type=power_truck_type, - position=pad.position.point_from_heading( - ground_spawn[0].heading.degrees + 90, 35 - ).point_from_heading(ground_spawn[0].heading.degrees + 180, 20), - group_size=1, - heading=pad.heading + 315, - move_formation=PointAction.OffRoad, - ) + cull_farp_statics = False + + if not cull_farp_statics: + # Generate ammo truck/farp and fuel truck/stack for each pad + if self.game.settings.ground_start_trucks_roadbase: + self.m.vehicle_group( + country=country, + name=(name + "_fuel"), + _type=tanker_type, + position=pad.position.point_from_heading( + ground_spawn[0].heading.degrees + 90, 35 + ), + group_size=1, + heading=pad.heading + 315, + move_formation=PointAction.OffRoad, + ) + self.m.vehicle_group( + country=country, + name=(name + "_ammo"), + _type=ammo_truck_type, + position=pad.position.point_from_heading( + ground_spawn[0].heading.degrees + 90, 35 + ).point_from_heading(ground_spawn[0].heading.degrees + 180, 10), + group_size=1, + heading=pad.heading + 315, + move_formation=PointAction.OffRoad, + ) + else: + self.m.static_group( + country=country, + name=(name + "_fuel"), + _type=Fortification.FARP_Fuel_Depot, + position=pad.position.point_from_heading( + ground_spawn[0].heading.degrees + 90, 35 + ), + heading=pad.heading + 270, + ) + self.m.static_group( + country=country, + name=(name + "_ammo"), + _type=Fortification.FARP_Ammo_Dump_Coating, + position=pad.position.point_from_heading( + ground_spawn[0].heading.degrees + 90, 35 + ).point_from_heading(ground_spawn[0].heading.degrees + 180, 10), + heading=pad.heading + 180, + ) + if self.game.settings.ground_start_ground_power_trucks_roadbase: + self.m.vehicle_group( + country=country, + name=(name + "_power"), + _type=power_truck_type, + position=pad.position.point_from_heading( + ground_spawn[0].heading.degrees + 90, 35 + ).point_from_heading(ground_spawn[0].heading.degrees + 180, 20), + group_size=1, + heading=pad.heading + 315, + move_formation=PointAction.OffRoad, + ) def generate(self) -> None: try: @@ -1044,61 +1072,76 @@ class GroundSpawnGenerator: country.id ) - # Generate a FARP Ammo and Fuel stack for each pad - if self.game.settings.ground_start_trucks: - self.m.vehicle_group( - country=country, - name=(name + "_fuel"), - _type=tanker_type, - position=pad.position.point_from_heading( - vtol_pad[0].heading.degrees - 175, 35 - ), - group_size=1, - heading=pad.heading + 45, - move_formation=PointAction.OffRoad, - ) - self.m.vehicle_group( - country=country, - name=(name + "_ammo"), - _type=ammo_truck_type, - position=pad.position.point_from_heading( - vtol_pad[0].heading.degrees - 185, 35 - ), - group_size=1, - heading=pad.heading + 45, - move_formation=PointAction.OffRoad, - ) + if self.game.position_culled(vtol_pad[0]): + cull_farp_statics = True + if self.cp.coalition.player: + for package in self.cp.coalition.ato.packages: + for flight in package.flights: + if flight.squadron.location == self.cp: + cull_farp_statics = False + break + elif flight.divert and flight.divert == self.cp: + cull_farp_statics = False + break else: - self.m.static_group( - country=country, - name=(name + "_fuel"), - _type=Fortification.FARP_Fuel_Depot, - position=pad.position.point_from_heading( - vtol_pad[0].heading.degrees - 180, 45 - ), - heading=pad.heading, - ) - self.m.static_group( - country=country, - name=(name + "_ammo"), - _type=Fortification.FARP_Ammo_Dump_Coating, - position=pad.position.point_from_heading( - vtol_pad[0].heading.degrees - 180, 35 - ), - heading=pad.heading + 270, - ) - if self.game.settings.ground_start_ground_power_trucks: - self.m.vehicle_group( - country=country, - name=(name + "_power"), - _type=power_truck_type, - position=pad.position.point_from_heading( - vtol_pad[0].heading.degrees - 185, 35 - ), - group_size=1, - heading=pad.heading + 45, - move_formation=PointAction.OffRoad, - ) + cull_farp_statics = False + + if not cull_farp_statics: + # Generate a FARP Ammo and Fuel stack for each pad + if self.game.settings.ground_start_trucks: + self.m.vehicle_group( + country=country, + name=(name + "_fuel"), + _type=tanker_type, + position=pad.position.point_from_heading( + vtol_pad[0].heading.degrees - 175, 35 + ), + group_size=1, + heading=pad.heading + 45, + move_formation=PointAction.OffRoad, + ) + self.m.vehicle_group( + country=country, + name=(name + "_ammo"), + _type=ammo_truck_type, + position=pad.position.point_from_heading( + vtol_pad[0].heading.degrees - 185, 35 + ), + group_size=1, + heading=pad.heading + 45, + move_formation=PointAction.OffRoad, + ) + else: + self.m.static_group( + country=country, + name=(name + "_fuel"), + _type=Fortification.FARP_Fuel_Depot, + position=pad.position.point_from_heading( + vtol_pad[0].heading.degrees - 180, 45 + ), + heading=pad.heading, + ) + self.m.static_group( + country=country, + name=(name + "_ammo"), + _type=Fortification.FARP_Ammo_Dump_Coating, + position=pad.position.point_from_heading( + vtol_pad[0].heading.degrees - 180, 35 + ), + heading=pad.heading + 270, + ) + if self.game.settings.ground_start_ground_power_trucks: + self.m.vehicle_group( + country=country, + name=(name + "_power"), + _type=power_truck_type, + position=pad.position.point_from_heading( + vtol_pad[0].heading.degrees - 185, 35 + ), + group_size=1, + heading=pad.heading + 45, + move_formation=PointAction.OffRoad, + ) def generate(self) -> None: try: diff --git a/game/pretense/pretenseaircraftgenerator.py b/game/pretense/pretenseaircraftgenerator.py index 1bfe7b8b..6b0a316c 100644 --- a/game/pretense/pretenseaircraftgenerator.py +++ b/game/pretense/pretenseaircraftgenerator.py @@ -49,17 +49,9 @@ if TYPE_CHECKING: PRETENSE_SQUADRON_DEF_RETRIES = 100 -PRETENSE_SEAD_FLIGHTS_PER_CP = 2 -PRETENSE_CAS_FLIGHTS_PER_CP = 2 -PRETENSE_BAI_FLIGHTS_PER_CP = 2 -PRETENSE_STRIKE_FLIGHTS_PER_CP = 2 -PRETENSE_BARCAP_FLIGHTS_PER_CP = 2 -PRETENSE_AI_AIRCRAFT_PER_FLIGHT = 2 PRETENSE_AI_AWACS_PER_FLIGHT = 1 PRETENSE_AI_TANKERS_PER_FLIGHT = 1 -PRETENSE_AI_CARGO_PLANES_PER_SIDE = 2 PRETENSE_PLAYER_AIRCRAFT_PER_FLIGHT = 1 -PRETENSE_PLAYER_FLIGHTS_PER_TYPE = 2 class PretenseAircraftGenerator: @@ -300,8 +292,12 @@ class PretenseAircraftGenerator: if cp.coalition != squadron.coalition: continue - squadron.owned_aircraft += PRETENSE_AI_AIRCRAFT_PER_FLIGHT - squadron.untasked_aircraft += PRETENSE_AI_AIRCRAFT_PER_FLIGHT + squadron.owned_aircraft += ( + self.game.settings.pretense_ai_aircraft_per_flight + ) + squadron.untasked_aircraft += ( + self.game.settings.pretense_ai_aircraft_per_flight + ) squadron.populate_for_turn_0(False) package = Package(cp, squadron.flight_db, auto_asap=False) mission_types = squadron.auto_assignable_mission_types @@ -320,46 +316,46 @@ class PretenseAircraftGenerator: FlightType.SEAD in mission_types or FlightType.SEAD_SWEEP in mission_types or FlightType.SEAD_ESCORT in mission_types - ) and num_of_sead < PRETENSE_SEAD_FLIGHTS_PER_CP: + ) and num_of_sead < self.game.settings.pretense_sead_flights_per_cp: flight_type = FlightType.SEAD num_of_sead += 1 - aircraft_per_flight = PRETENSE_AI_AIRCRAFT_PER_FLIGHT + aircraft_per_flight = self.game.settings.pretense_ai_aircraft_per_flight elif ( FlightType.DEAD in mission_types - and num_of_sead < PRETENSE_SEAD_FLIGHTS_PER_CP + and num_of_sead < self.game.settings.pretense_sead_flights_per_cp ): flight_type = FlightType.DEAD num_of_sead += 1 - aircraft_per_flight = PRETENSE_AI_AIRCRAFT_PER_FLIGHT + aircraft_per_flight = self.game.settings.pretense_ai_aircraft_per_flight elif ( FlightType.CAS in mission_types - ) and num_of_cas < PRETENSE_CAS_FLIGHTS_PER_CP: + ) and num_of_cas < self.game.settings.pretense_cas_flights_per_cp: flight_type = FlightType.CAS num_of_cas += 1 - aircraft_per_flight = PRETENSE_AI_AIRCRAFT_PER_FLIGHT + aircraft_per_flight = self.game.settings.pretense_ai_aircraft_per_flight elif ( FlightType.BAI in mission_types - ) and num_of_bai < PRETENSE_BAI_FLIGHTS_PER_CP: + ) and num_of_bai < self.game.settings.pretense_bai_flights_per_cp: flight_type = FlightType.BAI num_of_bai += 1 - aircraft_per_flight = PRETENSE_AI_AIRCRAFT_PER_FLIGHT + aircraft_per_flight = self.game.settings.pretense_ai_aircraft_per_flight elif ( FlightType.STRIKE in mission_types or FlightType.OCA_RUNWAY in mission_types or FlightType.OCA_AIRCRAFT in mission_types - ) and num_of_strike < PRETENSE_STRIKE_FLIGHTS_PER_CP: + ) and num_of_strike < self.game.settings.pretense_strike_flights_per_cp: flight_type = FlightType.STRIKE num_of_strike += 1 - aircraft_per_flight = PRETENSE_AI_AIRCRAFT_PER_FLIGHT + aircraft_per_flight = self.game.settings.pretense_ai_aircraft_per_flight elif ( FlightType.BARCAP in mission_types or FlightType.TARCAP in mission_types or FlightType.ESCORT in mission_types or FlightType.INTERCEPTION in mission_types - ) and num_of_cap < PRETENSE_BARCAP_FLIGHTS_PER_CP: + ) and num_of_cap < self.game.settings.pretense_barcap_flights_per_cp: flight_type = FlightType.BARCAP num_of_cap += 1 - aircraft_per_flight = PRETENSE_AI_AIRCRAFT_PER_FLIGHT + aircraft_per_flight = self.game.settings.pretense_ai_aircraft_per_flight elif FlightType.AEWC in mission_types: flight_type = FlightType.AEWC aircraft_per_flight = PRETENSE_AI_AWACS_PER_FLIGHT @@ -429,8 +425,12 @@ class PretenseAircraftGenerator: PRETENSE_SQUADRON_DEF_RETRIES, ) if squadron is not None: - squadron.owned_aircraft += PRETENSE_AI_AIRCRAFT_PER_FLIGHT - squadron.untasked_aircraft += PRETENSE_AI_AIRCRAFT_PER_FLIGHT + squadron.owned_aircraft += ( + self.game.settings.pretense_ai_aircraft_per_flight + ) + squadron.untasked_aircraft += ( + self.game.settings.pretense_ai_aircraft_per_flight + ) squadron.populate_for_turn_0(False) package = Package(cp, squadron.flight_db, auto_asap=False) flight = Flight( @@ -453,7 +453,7 @@ class PretenseAircraftGenerator: if isinstance(cp, Airfield): # Generate SEAD flight flight_type = FlightType.SEAD - aircraft_per_flight = PRETENSE_AI_AIRCRAFT_PER_FLIGHT + aircraft_per_flight = self.game.settings.pretense_ai_aircraft_per_flight squadron = self.generate_pretense_squadron( cp, coalition, @@ -470,8 +470,12 @@ class PretenseAircraftGenerator: PRETENSE_SQUADRON_DEF_RETRIES, ) if squadron is not None: - squadron.owned_aircraft += PRETENSE_AI_AIRCRAFT_PER_FLIGHT - squadron.untasked_aircraft += PRETENSE_AI_AIRCRAFT_PER_FLIGHT + squadron.owned_aircraft += ( + self.game.settings.pretense_ai_aircraft_per_flight + ) + squadron.untasked_aircraft += ( + self.game.settings.pretense_ai_aircraft_per_flight + ) squadron.populate_for_turn_0(False) package = Package(cp, squadron.flight_db, auto_asap=False) flight = Flight( @@ -494,7 +498,7 @@ class PretenseAircraftGenerator: # Generate CAS flight flight_type = FlightType.CAS - aircraft_per_flight = PRETENSE_AI_AIRCRAFT_PER_FLIGHT + aircraft_per_flight = self.game.settings.pretense_ai_aircraft_per_flight squadron = self.generate_pretense_squadron( cp, coalition, @@ -511,8 +515,12 @@ class PretenseAircraftGenerator: PRETENSE_SQUADRON_DEF_RETRIES, ) if squadron is not None: - squadron.owned_aircraft += PRETENSE_AI_AIRCRAFT_PER_FLIGHT - squadron.untasked_aircraft += PRETENSE_AI_AIRCRAFT_PER_FLIGHT + squadron.owned_aircraft += ( + self.game.settings.pretense_ai_aircraft_per_flight + ) + squadron.untasked_aircraft += ( + self.game.settings.pretense_ai_aircraft_per_flight + ) squadron.populate_for_turn_0(False) package = Package(cp, squadron.flight_db, auto_asap=False) flight = Flight( @@ -561,7 +569,7 @@ class PretenseAircraftGenerator: if not cp.can_operate(aircraft_type): continue - for i in range(PRETENSE_PLAYER_FLIGHTS_PER_TYPE): + for i in range(self.game.settings.pretense_player_flights_per_type): squadron = self.generate_pretense_squadron_for( aircraft_type, cp, @@ -607,7 +615,7 @@ class PretenseAircraftGenerator: cp: Control point to generate aircraft for. flight: The current flight being generated. """ - cp_name_trimmed = "".join([i for i in cp.name.lower() if i.isalnum()]) + cp_name_trimmed = "".join([i for i in cp.name.lower() if i.isalpha()]) for side in range(1, 3): if cp_name_trimmed not in cp.coalition.game.pretense_air[side]: @@ -627,7 +635,7 @@ class PretenseAircraftGenerator: flight: The current flight being generated. """ flight_type = flight.flight_type - cp_name_trimmed = "".join([i for i in cp.name.lower() if i.isalnum()]) + cp_name_trimmed = "".join([i for i in cp.name.lower() if i.isalpha()]) for side in range(1, 3): if cp_name_trimmed not in flight.coalition.game.pretense_air[side]: @@ -675,7 +683,7 @@ class PretenseAircraftGenerator: PRETENSE_SQUADRON_DEF_RETRIES, ) num_of_cargo_sq_to_generate = ( - PRETENSE_AI_CARGO_PLANES_PER_SIDE + self.game.settings.pretense_ai_cargo_planes_per_side - self.number_of_pretense_cargo_plane_sq_for(cp.coalition.air_wing) ) for i in range(num_of_cargo_sq_to_generate): @@ -795,7 +803,8 @@ class PretenseAircraftGenerator: if flight.package.target != flight.departure: break for mission_target in cp.ground_objects: - flight.package.target = mission_target + if mission_target.alive_unit_count > 0: + flight.package.target = mission_target break elif ( flight.flight_type == FlightType.OCA_RUNWAY @@ -837,23 +846,30 @@ class PretenseAircraftGenerator: break now = self.game.conditions.start_time - flight.package.set_tot_asap(now) + try: + flight.package.set_tot_asap(now) + except: + raise RuntimeError( + f"Pretense flight group {group.name} {flight.squadron.aircraft} {flight.flight_type} for target {flight.package.target} configuration failed. Please check if your Retribution campaign is compatible with Pretense." + ) logging.info( f"Configuring flight {group.name} {flight.squadron.aircraft} {flight.flight_type}, number of players: {flight.client_count}" ) - PretenseFlightGroupConfigurator( - flight, - group, - self.game, - self.mission, - self.time, - self.radio_registry, - self.tacan_registy, - self.mission_data, - dynamic_runways, - self.use_client, - ).configure() + self.mission_data.flights.append( + PretenseFlightGroupConfigurator( + flight, + group, + self.game, + self.mission, + self.time, + self.radio_registry, + self.tacan_registy, + self.mission_data, + dynamic_runways, + self.use_client, + ).configure() + ) if self.ewrj: self._track_ewrj_flight(flight, group) diff --git a/game/pretense/pretenseflightgroupspawner.py b/game/pretense/pretenseflightgroupspawner.py index cdb10a23..dad732c8 100644 --- a/game/pretense/pretenseflightgroupspawner.py +++ b/game/pretense/pretenseflightgroupspawner.py @@ -30,7 +30,7 @@ class PretenseNameGenerator(NameGenerator): @classmethod def next_pretense_aircraft_name(cls, cp: ControlPoint, flight: Flight) -> str: cls.aircraft_number += 1 - cp_name_trimmed = "".join([i for i in cp.name.lower() if i.isalnum()]) + cp_name_trimmed = "".join([i for i in cp.name.lower() if i.isalpha()]) return "{}-{}-{}".format( cp_name_trimmed, str(flight.flight_type).lower(), cls.aircraft_number ) @@ -77,12 +77,17 @@ class PretenseFlightGroupSpawner(FlightGroupSpawner): == self.flight.coalition.game.coalition_for(is_player) else 1 ) - cp_name_trimmed = "".join([i for i in cp.name.lower() if i.isalnum()]) + cp_name_trimmed = "".join([i for i in cp.name.lower() if i.isalpha()]) if self.flight.client_count == 0: self.flight.coalition.game.pretense_air[cp_side][cp_name_trimmed][ self.flight.flight_type ].append(name) + try: + self.flight.coalition.game.pretense_air_groups[name] = self.flight + except AttributeError: + self.flight.coalition.game.pretense_air_groups = {} + self.flight.coalition.game.pretense_air_groups[name] = self.flight def generate_flight_at_departure(self) -> FlyingGroup[Any]: cp = self.flight.departure @@ -94,7 +99,7 @@ class PretenseFlightGroupSpawner(FlightGroupSpawner): == self.flight.coalition.game.coalition_for(is_player) else 1 ) - cp_name_trimmed = "".join([i for i in cp.name.lower() if i.isalnum()]) + cp_name_trimmed = "".join([i for i in cp.name.lower() if i.isalpha()]) try: if self.start_type is StartType.IN_FLIGHT: @@ -139,8 +144,7 @@ class PretenseFlightGroupSpawner(FlightGroupSpawner): pad_group = self._generate_at_cp_ground_spawn(name, cp) if pad_group is not None: return pad_group - self.insert_into_pretense(name) - return self._generate_over_departure(name, cp) + raise NoParkingSlotError elif isinstance(cp, Airfield): is_heli = self.flight.squadron.aircraft.helicopter if cp.has_helipads and is_heli: diff --git a/game/pretense/pretenseluagenerator.py b/game/pretense/pretenseluagenerator.py index 605e257f..2a56ba8c 100644 --- a/game/pretense/pretenseluagenerator.py +++ b/game/pretense/pretenseluagenerator.py @@ -259,7 +259,7 @@ class PretenseLuaGenerator(LuaGenerator): def generate_pretense_land_upgrade_supply(self, cp_name: str, cp_side: int) -> str: lua_string_zones = "" - cp_name_trimmed = "".join([i for i in cp_name.lower() if i.isalnum()]) + cp_name_trimmed = "".join([i for i in cp_name.lower() if i.isalpha()]) cp_side_str = "blue" if cp_side == PRETENSE_BLUE_SIDE else "red" cp = self.game.theater.controlpoints[0] for loop_cp in self.game.theater.controlpoints: @@ -398,7 +398,7 @@ class PretenseLuaGenerator(LuaGenerator): ) lua_string_zones += " products = {\n" for mission_type in self.game.pretense_air[cp_side][cp_name_trimmed]: - if mission_type == FlightType.SEAD: + if mission_type in (FlightType.SEAD, FlightType.DEAD): mission_name = "attack.sead" for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ mission_type @@ -414,6 +414,9 @@ class PretenseLuaGenerator(LuaGenerator): for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ mission_type ]: + flight = self.game.pretense_air_groups[air_group] + if flight.is_helo: + mission_name = "attack.helo" lua_string_zones += ( f" presets.missions.{mission_name}:extend" + "({name='" @@ -503,7 +506,7 @@ class PretenseLuaGenerator(LuaGenerator): def generate_pretense_sea_upgrade_supply(self, cp_name: str, cp_side: int) -> str: lua_string_zones = "" - cp_name_trimmed = "".join([i for i in cp_name.lower() if i.isalnum()]) + cp_name_trimmed = "".join([i for i in cp_name.lower() if i.isalpha()]) cp_side_str = "blue" if cp_side == PRETENSE_BLUE_SIDE else "red" if cp_side == PRETENSE_BLUE_SIDE: @@ -608,6 +611,9 @@ class PretenseLuaGenerator(LuaGenerator): for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ mission_type ]: + flight = self.game.pretense_air_groups[air_group] + if flight.is_helo: + mission_name = "attack.helo" lua_string_zones += ( f" presets.missions.{mission_name}:extend" + "({name='" @@ -697,7 +703,7 @@ class PretenseLuaGenerator(LuaGenerator): def generate_pretense_zone_land(self, cp_name: str) -> str: lua_string_zones = "" - cp_name_trimmed = "".join([i for i in cp_name.lower() if i.isalnum()]) + cp_name_trimmed = "".join([i for i in cp_name.lower() if i.isalpha()]) lua_string_zones += f"zones.{cp_name_trimmed}:defineUpgrades(" + "{\n" lua_string_zones += " [1] = { --red side\n" @@ -771,7 +777,7 @@ class PretenseLuaGenerator(LuaGenerator): def generate_pretense_zone_sea(self, cp_name: str) -> str: lua_string_zones = "" - cp_name_trimmed = "".join([i for i in cp_name.lower() if i.isalnum()]) + cp_name_trimmed = "".join([i for i in cp_name.lower() if i.isalpha()]) lua_string_zones += f"zones.{cp_name_trimmed}:defineUpgrades(" + "{\n" lua_string_zones += " [1] = { --red side\n" @@ -823,29 +829,29 @@ class PretenseLuaGenerator(LuaGenerator): 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" - ) - trigger = TriggerStart(comment="Pretense init") - - lua_string_config = "" + lua_string_config = "Config = Config or {}\n" 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" + trigger = TriggerStart(comment="Pretense config") + trigger.add_action(DoScript(String(lua_string_config))) + self.mission.triggerrules.triggers.append(trigger) + + self.inject_plugin_script( + "pretense", "pretense_compiled.lua", "pretense_compiled" + ) + + trigger = TriggerStart(comment="Pretense init") + init_header_file = open("./resources/plugins/pretense/init_header.lua", "r") init_header = init_header_file.read() @@ -855,7 +861,7 @@ class PretenseLuaGenerator(LuaGenerator): if isinstance(cp, OffMapSpawn): continue - cp_name_trimmed = "".join([i for i in cp.name.lower() if i.isalnum()]) + cp_name_trimmed = "".join([i for i in cp.name.lower() if i.isalpha()]) cp_side = 2 if cp.captured else 1 for side in range(1, 3): if cp_name_trimmed not in self.game.pretense_air[cp_side]: @@ -947,12 +953,17 @@ class PretenseLuaGenerator(LuaGenerator): else: # Finally, connect remaining non-connected points closest_cps = self.game.theater.closest_friendly_control_points_to(cp) - lua_string_connman += self.generate_pretense_zone_connection( - connected_points, cp.name, closest_cps[0].name - ) - lua_string_connman += self.generate_pretense_zone_connection( - connected_points, cp.name, closest_cps[1].name - ) + for extra_connection in range( + self.game.settings.pretense_extra_zone_connections + ): + if len(closest_cps) > extra_connection: + lua_string_connman += self.generate_pretense_zone_connection( + connected_points, + cp.name, + closest_cps[extra_connection].name, + ) + else: + break lua_string_supply = "local redSupply = {\n" # Generate supply @@ -963,7 +974,7 @@ class PretenseLuaGenerator(LuaGenerator): cp_side_captured = cp_side == 2 if cp_side_captured != cp.captured: continue - cp_name_trimmed = "".join([i for i in cp.name.lower() if i.isalnum()]) + cp_name_trimmed = "".join([i for i in cp.name.lower() if i.isalpha()]) for mission_type in self.game.pretense_air[cp_side][cp_name_trimmed]: if mission_type == FlightType.PRETENSE_CARGO: for air_group in self.game.pretense_air[cp_side][ @@ -976,7 +987,7 @@ class PretenseLuaGenerator(LuaGenerator): lua_string_supply += "local offmapZones = {\n" for cp in self.game.theater.controlpoints: if isinstance(cp, Airfield): - cp_name_trimmed = "".join([i for i in cp.name.lower() if i.isalnum()]) + cp_name_trimmed = "".join([i for i in cp.name.lower() if i.isalpha()]) lua_string_supply += f" zones.{cp_name_trimmed},\n" lua_string_supply += "}\n" @@ -997,8 +1008,7 @@ class PretenseLuaGenerator(LuaGenerator): init_footer = init_footer_file.read() lua_string = ( - lua_string_config - + init_header + init_header + lua_string_zones + lua_string_connman + init_body_1 diff --git a/game/pretense/pretensemissiongenerator.py b/game/pretense/pretensemissiongenerator.py index e67ece34..a6dc8013 100644 --- a/game/pretense/pretensemissiongenerator.py +++ b/game/pretense/pretensemissiongenerator.py @@ -12,6 +12,7 @@ from dcs.countries import ( CombinedJointTaskForcesBlue, CombinedJointTaskForcesRed, ) +from dcs.task import AFAC, FAC, SetInvisibleCommand, SetImmortalCommand, OrbitAction from game.lasercodes.lasercoderegistry import LaserCodeRegistry from game.missiongenerator.convoygenerator import ConvoyGenerator @@ -21,7 +22,7 @@ from game.missiongenerator.forcedoptionsgenerator import ForcedOptionsGenerator from game.missiongenerator.frontlineconflictdescription import ( FrontLineConflictDescription, ) -from game.missiongenerator.missiondata import MissionData +from game.missiongenerator.missiondata import MissionData, JtacInfo from game.missiongenerator.tgogenerator import TgoGenerator from game.missiongenerator.visualsgenerator import VisualsGenerator from game.naming import namegen @@ -34,6 +35,8 @@ from .pretenseluagenerator import PretenseLuaGenerator from .pretensetgogenerator import PretenseTgoGenerator from .pretensetriggergenerator import PretenseTriggerGenerator from ..ato.airtaaskingorder import AirTaskingOrder +from ..callsigns import callsign_for_support_unit +from ..dcs.aircrafttype import AircraftType from ..missiongenerator import MissionGenerator if TYPE_CHECKING: @@ -148,27 +151,61 @@ class PretenseMissionGenerator(MissionGenerator): for front_line in self.game.theater.conflicts(): player_cp = front_line.blue_cp enemy_cp = front_line.red_cp - conflict = FrontLineConflictDescription.frontline_cas_conflict( - front_line, self.game.theater - ) - # Generate frontline ops - player_gp = self.game.ground_planners[player_cp.id].units_per_cp[ - enemy_cp.id - ] - enemy_gp = self.game.ground_planners[enemy_cp.id].units_per_cp[player_cp.id] - ground_conflict_gen = FlotGenerator( - self.mission, - conflict, - self.game, - player_gp, - enemy_gp, - player_cp.stances[enemy_cp.id], - enemy_cp.stances[player_cp.id], - self.unit_map, - self.radio_registry, - self.mission_data, - ) - ground_conflict_gen.generate() + + # Add JTAC + if self.game.blue.faction.has_jtac: + freq = self.radio_registry.alloc_uhf() + # If the option fc3LaserCode is enabled, force all JTAC + # laser codes to 1113 to allow lasing for Su-25 Frogfoots and A-10A Warthogs. + # Otherwise use 1688 for the first JTAC, 1687 for the second etc. + if self.game.settings.plugins.get("ctld.fc3LaserCode"): + code = self.game.laser_code_registry.fc3_code + else: + code = front_line.laser_code + + utype = self.game.blue.faction.jtac_unit + if utype is None: + utype = AircraftType.named("MQ-9 Reaper") + + country = self.mission.country(self.game.blue.faction.country.name) + position = FrontLineConflictDescription.frontline_position( + front_line, self.game.theater, self.game.settings + ) + jtac = self.mission.flight_group( + country=country, + name=namegen.next_jtac_name(), + aircraft_type=utype.dcs_unit_type, + position=position[0], + airport=None, + altitude=5000, + maintask=AFAC, + ) + jtac.points[0].tasks.append( + FAC( + callsign=len(self.mission_data.jtacs) + 1, + frequency=int(freq.mhz), + modulation=freq.modulation, + ) + ) + jtac.points[0].tasks.append(SetInvisibleCommand(True)) + jtac.points[0].tasks.append(SetImmortalCommand(True)) + jtac.points[0].tasks.append( + OrbitAction(5000, 300, OrbitAction.OrbitPattern.Circle) + ) + frontline = f"Frontline {player_cp.name}/{enemy_cp.name}" + # Note: Will need to change if we ever add ground based JTAC. + callsign = callsign_for_support_unit(jtac) + self.mission_data.jtacs.append( + JtacInfo( + group_name=jtac.name, + unit_name=jtac.units[0].name, + callsign=callsign, + region=frontline, + code=str(code), + blue=True, + freq=freq, + ) + ) def generate_air_units(self, tgo_generator: TgoGenerator) -> None: """Generate the air units for the Operation""" diff --git a/game/settings/settings.py b/game/settings/settings.py index 3acc74c8..d7c5348b 100644 --- a/game/settings/settings.py +++ b/game/settings/settings.py @@ -980,21 +980,102 @@ class Settings: default=130, min=10, max=10000, + detail=( + "Zones farther away than this from the front line are switched " + "into low activity state, but will still be there as functional " + "parts of the economy. Use this to adjust performance." + ), ) - pretense_closeoverride_distance: int = bounded_int_option( - "Close override distance (km)", + pretense_extra_zone_connections: int = bounded_int_option( + "Extra friendly zone connections", page=PRETENSE_PAGE, section=GENERAL_SECTION, - default=28, - min=5, - max=10000, + default=2, + min=0, + max=10, + detail=( + "Add connections from each zone to this many closest friendly zones," + "which don't have an existing supply route defined in the campaign." + ), ) pretense_do_not_generate_sead_missions: bool = boolean_option( "Do not generate player SEAD missions", page=PRETENSE_PAGE, - section=PERFORMANCE_SECTION, + section=GENERAL_SECTION, default=False, ) + pretense_num_of_cargo_planes: int = bounded_int_option( + "Number of cargo planes per side", + page=PRETENSE_PAGE, + section=GENERAL_SECTION, + default=2, + min=1, + max=100, + ) + pretense_sead_flights_per_cp: int = bounded_int_option( + "Number of AI SEAD flights per control point / zone", + page=PRETENSE_PAGE, + section=GENERAL_SECTION, + default=2, + min=1, + max=10, + ) + pretense_cas_flights_per_cp: int = bounded_int_option( + "Number of AI CAS flights per control point / zone", + page=PRETENSE_PAGE, + section=GENERAL_SECTION, + default=2, + min=1, + max=10, + ) + pretense_bai_flights_per_cp: int = bounded_int_option( + "Number of AI BAI flights per control point / zone", + page=PRETENSE_PAGE, + section=GENERAL_SECTION, + default=2, + min=1, + max=10, + ) + pretense_strike_flights_per_cp: int = bounded_int_option( + "Number of AI Strike flights per control point / zone", + page=PRETENSE_PAGE, + section=GENERAL_SECTION, + default=2, + min=1, + max=10, + ) + pretense_barcap_flights_per_cp: int = bounded_int_option( + "Number of AI BARCAP flights per control point / zone", + page=PRETENSE_PAGE, + section=GENERAL_SECTION, + default=2, + min=1, + max=10, + ) + pretense_ai_aircraft_per_flight: int = bounded_int_option( + "Number of AI aircraft per flight", + page=PRETENSE_PAGE, + section=GENERAL_SECTION, + default=2, + min=1, + max=4, + ) + pretense_player_flights_per_type: int = bounded_int_option( + "Number of player flights per aircraft type at each base", + page=PRETENSE_PAGE, + section=GENERAL_SECTION, + default=2, + min=1, + max=10, + ) + pretense_ai_cargo_planes_per_side: int = bounded_int_option( + "Number of AI cargo planes per side", + page=PRETENSE_PAGE, + section=GENERAL_SECTION, + default=2, + min=1, + max=20, + ) # Cheating. Not using auto settings because the same page also has buttons which do # not alter settings. diff --git a/resources/plugins/pretense/init_header.lua b/resources/plugins/pretense/init_header.lua index c73f5444..d7481f31 100644 --- a/resources/plugins/pretense/init_header.lua +++ b/resources/plugins/pretense/init_header.lua @@ -494,7 +494,7 @@ presets = { }), comCenter = Preset:new({ display = 'Command Center', - cost = 2500, + cost = 12500, type = 'upgrade', template = "command-center" }) @@ -522,43 +522,43 @@ presets = { }), sa10 = Preset:new({ display = 'SAM', - cost=3000, + cost=30000, type='defense', template='sa10', }), sa5 = Preset:new({ display = 'SAM', - cost=3000, + cost=20000, type='defense', template='sa5', }), sa3 = Preset:new({ display = 'SAM', - cost=3000, + cost=4000, type='defense', template='sa3', }), sa6 = Preset:new({ display = 'SAM', - cost=3000, + cost=6000, type='defense', template='sa6', }), sa11 = Preset:new({ display = 'SAM', - cost=3000, + cost=10000, type='defense', template='sa11', }), hawk = Preset:new({ display = 'SAM', - cost=3000, + cost=6000, type='defense', template='hawk', }), patriot = Preset:new({ display = 'SAM', - cost=3000, + cost=30000, type='defense', template='patriot', }), @@ -596,43 +596,43 @@ presets = { }), sa10 = Preset:new({ display = 'SAM', - cost=3000, + cost=30000, type='defense', template='sa10', }), sa5 = Preset:new({ display = 'SAM', - cost=3000, + cost=20000, type='defense', template='sa5', }), sa3 = Preset:new({ display = 'SAM', - cost=3000, + cost=4000, type='defense', template='sa3', }), sa6 = Preset:new({ display = 'SAM', - cost=3000, + cost=6000, type='defense', template='sa6', }), sa11 = Preset:new({ display = 'SAM', - cost=3000, + cost=10000, type='defense', template='sa11', }), hawk = Preset:new({ display = 'SAM', - cost=3000, + cost=6000, type='defense', template='hawk', }), patriot = Preset:new({ display = 'SAM', - cost=3000, + cost=30000, type='defense', template='patriot', }), diff --git a/resources/plugins/pretense/pretense_compiled.lua b/resources/plugins/pretense/pretense_compiled.lua index cf8bb881..5dbece1a 100644 --- a/resources/plugins/pretense/pretense_compiled.lua +++ b/resources/plugins/pretense/pretense_compiled.lua @@ -32,7 +32,6 @@ Config.buildSpeed = Config.buildSpeed or 10 -- structure and defense build speed Config.supplyBuildSpeed = Config.supplyBuildSpeed or 85 -- supply helicopters and convoys build speed Config.missionBuildSpeedReduction = Config.missionBuildSpeedReduction or 0.12 -- reduction of build speed in case of ai missions Config.maxDistFromFront = Config.maxDistFromFront or 129640 -- max distance in meters from front after which zone is forced into low activity state (export mode) -Config.closeOverride = Config.closeOverride or 27780 -- close override distance in meters from front within which zone is never forced into low activity state Config.disablePlayerSead = Config.disablePlayerSead or false Config.missions = Config.missions or {} @@ -505,8 +504,6 @@ end GroupMonitor = {} do GroupMonitor.blockedDespawnTime = 10*60 --used to despawn aircraft that are stuck taxiing for some reason - GroupMonitor.blockedDespawnTimeGround = 30*60 --used to despawn ground units that are stuck en route for some reason - GroupMonitor.blockedDespawnTimeGroundAssault = 90*60 --used to despawn assault units that are stuck en route for some reason GroupMonitor.landedDespawnTime = 10 GroupMonitor.atDestinationDespawnTime = 2*60 GroupMonitor.recoveryReduction = 0.8 -- reduce recovered resource from landed missions by this amount to account for maintenance @@ -642,13 +639,7 @@ do group.state = 'enroute' group.lastStateTime = timer.getAbsTime() MissionTargetRegistry.addBaiTarget(group) - elseif group.product.missionType == 'assault' and timer.getAbsTime() - group.lastStateTime > GroupMonitor.blockedDespawnTimeGroundAssault then - env.info('GroupMonitor: processSurface ['..group.name..'] despawned due to blockage') - gr:destroy() - local todeliver = math.floor(group.product.cost) - z:addResource(todeliver) - return true - elseif timer.getAbsTime() - group.lastStateTime > GroupMonitor.blockedDespawnTimeGround then + elseif timer.getAbsTime() - group.lastStateTime > GroupMonitor.blockedDespawnTime then env.info('GroupMonitor: processSurface ['..group.name..'] despawned due to blockage') gr:destroy() local todeliver = math.floor(group.product.cost) @@ -744,7 +735,7 @@ do y = group.target.zone.point.z } - TaskExtensions.moveOffRoadToPointAndAssault(gr, tp, group.target.built) + TaskExtensions.moveOnRoadToPointAndAssault(gr, tp, group.target.built) group.isstopped = false end end @@ -1231,7 +1222,7 @@ do if v.type == 'defense' and v.side ~= group:getCoalition() then local gr = Group.getByName(v.name) for _,unit in ipairs(gr:getUnits()) do - if unit:hasAttribute('SAM SR') or unit:hasAttribute('SAM TR') or unit:hasAttribute('AAA') or unit:hasAttribute('IR Guided SAM') or unit:hasAttribute('SAM LL') then + if unit:hasAttribute('SAM SR') or unit:hasAttribute('SAM TR') then table.insert(viable, unit:getName()) end end @@ -1245,7 +1236,7 @@ do { id = 'EngageTargets', params = { - targetTypes = {'SAM SR', 'SAM TR', 'AAA', 'IR Guided SAM', 'SAM LL'} + targetTypes = {'SAM SR', 'SAM TR'} } } } @@ -2145,68 +2136,7 @@ do } group:getController():setTask(mis) end - - function TaskExtensions.moveOffRoadToPointAndAssault(group, point, targets) - if not group or not point then return end - if not group:isExist() or group:getSize()==0 then return end - local startPos = group:getUnit(1):getPoint() - - local srx, sry = land.getClosestPointOnRoads('roads', startPos.x, startPos.z) - local erx, ery = land.getClosestPointOnRoads('roads', point.x, point.y) - - local mis = { - id='Mission', - params = { - route = { - points = { - [1] = { - type= AI.Task.WaypointType.TURNING_POINT, - x = srx, - y = sry, - speed = 1000, - action = AI.Task.VehicleFormation.DIAMOND - }, - [2] = { - type= AI.Task.WaypointType.TURNING_POINT, - x = erx, - y = ery, - speed = 1000, - action = AI.Task.VehicleFormation.DIAMOND - }, - [3] = { - type= AI.Task.WaypointType.TURNING_POINT, - x = point.x, - y = point.y, - speed = 1000, - action = AI.Task.VehicleFormation.DIAMOND - } - } - } - } - } - - for i,v in pairs(targets) do - if v.type == 'defense' then - local group = Group.getByName(v.name) - if group then - for i,v in ipairs(group:getUnits()) do - local unpos = v:getPoint() - local pnt = {x=unpos.x, y = unpos.z} - - table.insert(mis.params.route.points, { - type= AI.Task.WaypointType.TURNING_POINT, - x = pnt.x, - y = pnt.y, - speed = 10, - action = AI.Task.VehicleFormation.DIAMOND - }) - end - end - end - end - group:getController():setTask(mis) - end - + function TaskExtensions.landAtPointFromAir(group, point, alt) if not group or not point then return end if not group:isExist() or group:getSize()==0 then return end @@ -4882,7 +4812,7 @@ do product.lastMission = {zoneName = zone.name} timer.scheduleFunction(function(param) local gr = Group.getByName(param.name) - TaskExtensions.moveOffRoadToPointAndAssault(gr, param.point, param.targets) + TaskExtensions.moveOnRoadToPointAndAssault(gr, param.point, param.targets) end, {name=product.name, point={ x=tgtPoint.point.x, y = tgtPoint.point.z}, targets=zone.built}, timer.getTime()+1) end end @@ -5413,7 +5343,7 @@ do product.lastMission = {zoneName = v.name} timer.scheduleFunction(function(param) local gr = Group.getByName(param.name) - TaskExtensions.moveOffRoadToPointAndAssault(gr, param.point, param.targets) + TaskExtensions.moveOnRoadToPointAndAssault(gr, param.point, param.targets) end, {name=product.name, point={ x=tgtPoint.point.x, y = tgtPoint.point.z}, targets=v.built}, timer.getTime()+1) env.info("ZoneCommand - "..product.name.." targeting "..v.name) @@ -5975,7 +5905,7 @@ end BattlefieldManager = {} do - BattlefieldManager.closeOverride = Config.closeOverride -- default 15nm + BattlefieldManager.closeOverride = 27780 -- 15nm BattlefieldManager.farOverride = Config.maxDistFromFront -- default 100nm BattlefieldManager.boostScale = {[0] = 1.0, [1]=1.0, [2]=1.0} BattlefieldManager.noRedZones = false @@ -10554,7 +10484,6 @@ do end end end - return false end function SEAD:getMissionName() @@ -12208,7 +12137,7 @@ do if toGen > 0 then local validMissions = {} for _,v in pairs(Mission.types) do - if timer.getAbsTime() - timer.getTime0() > 120 and self:canCreateMission(v) then + if self:canCreateMission(v) then table.insert(validMissions,v) end end From 4c201f25ba6ebc0f5d4da018404d545774c8949a Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Wed, 22 Nov 2023 20:13:57 +0200 Subject: [PATCH 086/243] Will now correctly generate Pretense campaigns with CJTF factions. --- game/pretense/pretensemissiongenerator.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/game/pretense/pretensemissiongenerator.py b/game/pretense/pretensemissiongenerator.py index a6dc8013..7b18b35b 100644 --- a/game/pretense/pretensemissiongenerator.py +++ b/game/pretense/pretensemissiongenerator.py @@ -135,9 +135,9 @@ class PretenseMissionGenerator(MissionGenerator): self.mission.coalition["red"].add_country(self.e_country) # Add CJTF factions to the coalitions, if they're not being used in the campaign - if CombinedJointTaskForcesBlue not in {self.p_country, self.e_country}: + if CombinedJointTaskForcesBlue.id not in {self.p_country.id, self.e_country.id}: self.mission.coalition["blue"].add_country(CombinedJointTaskForcesBlue()) - if CombinedJointTaskForcesRed not in {self.p_country, self.e_country}: + if CombinedJointTaskForcesRed.id not in {self.p_country.id, self.e_country.id}: self.mission.coalition["red"].add_country(CombinedJointTaskForcesRed()) belligerents = {self.p_country.id, self.e_country.id} From 85cec06098a819e6b11747ffbe9a6bf57a1c9c9b Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Wed, 6 Dec 2023 16:38:25 +0200 Subject: [PATCH 087/243] Updated the Pretense script to version 1.3.8 - 3 Dec 2023 and MIST to version 4.5.122. --- game/pretense/pretenseluagenerator.py | 7 +- .../{mist_4_5_107.lua => mist_4_5_122.lua} | 1120 ++++++++++++----- resources/plugins/base/plugin.json | 2 +- resources/plugins/pretense/init_header.lua | 4 +- .../plugins/pretense/pretense_compiled.lua | 36 +- 5 files changed, 784 insertions(+), 385 deletions(-) rename resources/plugins/base/{mist_4_5_107.lua => mist_4_5_122.lua} (91%) diff --git a/game/pretense/pretenseluagenerator.py b/game/pretense/pretenseluagenerator.py index 2a56ba8c..17ac01a4 100644 --- a/game/pretense/pretenseluagenerator.py +++ b/game/pretense/pretenseluagenerator.py @@ -828,7 +828,7 @@ class PretenseLuaGenerator(LuaGenerator): return lua_string_connman 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("base", "mist_4_5_122.lua", "mist_4_5_122") lua_string_config = "Config = Config or {}\n" @@ -912,10 +912,7 @@ class PretenseLuaGenerator(LuaGenerator): 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: - lua_string_zones += self.generate_pretense_zone_land(cp.name) + lua_string_zones += self.generate_pretense_zone_land(cp.name) lua_string_connman = " cm = ConnectionManager:new()\n" diff --git a/resources/plugins/base/mist_4_5_107.lua b/resources/plugins/base/mist_4_5_122.lua similarity index 91% rename from resources/plugins/base/mist_4_5_107.lua rename to resources/plugins/base/mist_4_5_122.lua index 431848d8..97b656fb 100644 --- a/resources/plugins/base/mist_4_5_107.lua +++ b/resources/plugins/base/mist_4_5_122.lua @@ -35,7 +35,7 @@ mist = {} -- don't change these mist.majorVersion = 4 mist.minorVersion = 5 -mist.build = 107 +mist.build = 122 -- forward declaration of log shorthand local log @@ -64,8 +64,8 @@ do -- the main scope local updateAliveUnitsCounter = 0 local updateTenthSecond = 0 - local mistGpId = 7000 - local mistUnitId = 7000 + local mistGpId = 70000 + local mistUnitId = 70000 local mistDynAddIndex = {[' air '] = 0, [' hel '] = 0, [' gnd '] = 0, [' bld '] = 0, [' static '] = 0, [' shp '] = 0} local scheduledTasks = {} @@ -75,7 +75,7 @@ do -- the main scope mist.nextGroupId = 1 mist.nextUnitId = 1 - + local function initDBs() -- mist.DBs scope mist.DBs = {} @@ -92,48 +92,11 @@ do -- the main scope mist.DBs.missionData.files[#mist.DBs.missionData.files + 1] = mist.utils.deepCopy(fIndex) end end - -- if we add more coalition specific data then bullsye should be categorized by coaliton. For now its just the bullseye table + -- if we add more coalition specific data then bullseye should be categorized by coaliton. For now its just the bullseye table mist.DBs.missionData.bullseye = {} + mist.DBs.missionData.countries = {} end - mist.DBs.zonesByName = {} - mist.DBs.zonesByNum = {} - - - if env.mission.triggers and env.mission.triggers.zones then - for zone_ind, zone_data in pairs(env.mission.triggers.zones) do - if type(zone_data) == 'table' then - local zone = mist.utils.deepCopy(zone_data) - zone.point = {} -- point is used by SSE - zone.point.x = zone_data.x - zone.point.y = 0 - zone.point.z = zone_data.y - zone.properties = {} - if zone_data.properties then - for propInd, prop in pairs(zone_data.properties) do - if prop.value and type(prop.value) == 'string' and prop.value ~= "" then - zone.properties[prop.key] = prop.value - end - end - end - if zone.verticies then -- trust but verify - local r = 0 - for i = 1, #zone.verticies do - local dist = mist.utils.get2DDist(zone.point, zone.verticies[i]) - if dist > r then - r = mist.utils.deepCopy(dist) - end - end - zone.radius = r - - end - - mist.DBs.zonesByName[zone_data.name] = zone - mist.DBs.zonesByNum[#mist.DBs.zonesByNum + 1] = mist.utils.deepCopy(zone) --[[deepcopy so that the zone in zones_by_name and the zone in - zones_by_num se are different objects.. don't want them linked.]] - end - end - end mist.DBs.drawingByName = {} mist.DBs.drawingIndexed = {} @@ -213,6 +176,16 @@ do -- the main scope end + local abRef = {units = {}, airbase = {}} + for ind, val in pairs(world.getAirbases()) do + local cat = "airbase" + if Airbase.getDesc(val).category > 0 then + cat = "units" + end + abRef[cat][tonumber(val:getID())] = {name = val:getName()} + + end + mist.DBs.navPoints = {} mist.DBs.units = {} @@ -222,6 +195,7 @@ do -- the main scope if string.lower(coa_name_miz) == 'neutrals' then coa_name = 'neutral' end + local coaEnum = coalition.side[string.upper(coa_name)] if type(coa_data) == 'table' then mist.DBs.units[coa_name] = {} @@ -254,6 +228,7 @@ do -- the main scope if cntry_data.id and country.names[cntry_data.id] then countryName = string.lower(country.names[cntry_data.id]) end + mist.DBs.missionData.countries[countryName] = coa_name mist.DBs.units[coa_name][countryName] = {} mist.DBs.units[coa_name][countryName].countryId = cntry_data.id @@ -270,7 +245,18 @@ do -- the main scope mist.DBs.units[coa_name][countryName][category] = {} for group_num, group_data in pairs(obj_cat_data.group) do - + local helipadId + local airdromeId + + if group_data.route and group_data.route.points and group_data.route.points[1] then + if group_data.route.points[1].airdromeId then + airdromeId = group_data.route.points[1].airdromeId + --table.insert(abRef.airbase[group_data.route.points[1].airdromeId], group_data.groupId) + elseif group_data.route.points[1].helipadId then + helipadId = group_data.route.points[1].helipadId + --table.insert(abRef.units[group_data.route.points[1].helipadId], group_data.groupId) + end + end if group_data and group_data.units and type(group_data.units) == 'table' then --making sure again- this is a valid group mist.DBs.units[coa_name][countryName][category][group_num] = {} @@ -282,6 +268,7 @@ do -- the main scope mist.DBs.units[coa_name][countryName][category][group_num].groupId = group_data.groupId mist.DBs.units[coa_name][countryName][category][group_num].category = category mist.DBs.units[coa_name][countryName][category][group_num].coalition = coa_name + mist.DBs.units[coa_name][countryName][category][group_num].coalitionId = coaEnum mist.DBs.units[coa_name][countryName][category][group_num].country = countryName mist.DBs.units[coa_name][countryName][category][group_num].countryId = cntry_data.id mist.DBs.units[coa_name][countryName][category][group_num].startTime = group_data.start_time @@ -309,6 +296,8 @@ do -- the main scope units_tbl[unit_num].unitId = unit_data.unitId units_tbl[unit_num].category = category units_tbl[unit_num].coalition = coa_name + units_tbl[unit_num].coalitionId = coaEnum + units_tbl[unit_num].country = countryName units_tbl[unit_num].countryId = cntry_data.id units_tbl[unit_num].heading = unit_data.heading @@ -331,11 +320,17 @@ do -- the main scope units_tbl[unit_num].onboard_num = unit_data.onboard_num units_tbl[unit_num].hardpoint_racks = unit_data.hardpoint_racks units_tbl[unit_num].psi = unit_data.psi - + + if helipadId then + units_tbl[unit_num].helipadId = mist.utils.deepCopy(helipadId) + end + if airdromeId then + units_tbl[unit_num].airdromeId = mist.utils.deepCopy(airdromeId) + end units_tbl[unit_num].groupName = groupName units_tbl[unit_num].groupId = group_data.groupId - + units_tbl[unit_num].linkUnit = unit_data.linkUnit if unit_data.AddPropAircraft then units_tbl[unit_num].AddPropAircraft = unit_data.AddPropAircraft end @@ -343,7 +338,13 @@ do -- the main scope if category == 'static' then units_tbl[unit_num].categoryStatic = unit_data.category units_tbl[unit_num].shape_name = unit_data.shape_name - units_tbl[unit_num].linkUnit = unit_data.linkUnit + if group_data.linkOffset then + if group_data.route and group_data.route.points and group_data.route.points[1] and group_data.route.points[1].linkUnit then + units_tbl[unit_num].linkUnit = group_data.route.points[1].linkUnit + end + units_tbl[unit_num].offset = unit_data.offsets + end + if unit_data.mass then units_tbl[unit_num].mass = unit_data.mass end @@ -351,6 +352,13 @@ do -- the main scope if unit_data.canCargo then units_tbl[unit_num].canCargo = unit_data.canCargo end + + if unit_data.category == "Heliports" then + if not abRef.units[unit_data.unitId] then + abRef.units[unit_data.unitId] = {name = unit_data.name} + end + + end end end --for unit_num, unit_data in pairs(group_data.units) do @@ -390,6 +398,36 @@ do -- the main scope mist.DBs.removedAliveUnits = {} -- will be filled in by the "updateAliveUnits" coroutine in mist.main. mist.DBs.const = {} + + mist.DBs.const.nato = { + a = "alpha", + b = "bravo", + c = "charlie", + d = "delta", + e = "echo", + f = "foxtrot", + g = "golf", + h = "hotel", + i = "india", + j = "juliett", + k = "kilo", + l = "lima", + m = "mike", + n = "november", + o = "oscar", + p = "papa", + q = "quebec", + r = "romeo", + s = "sierra", + t = "tango", + u = "uniform", + v = "victor", + w = "whiskey", + x = "xray", + y = "yankee", + z = "zulu", + + } -- not accessible by SSE, must use static list :-/ mist.DBs.const.callsigns = { @@ -697,6 +735,10 @@ do -- the main scope -- end --Build DBs + + --dbLog:echo(abRef) + mist.DBs.spawnsByBase = {} + for coa_name, coa_data in pairs(mist.DBs.units) do for cntry_name, cntry_data in pairs(coa_data) do for category_name, category_data in pairs(cntry_data) do @@ -706,22 +748,41 @@ do -- the main scope mist.DBs.groupsByName[group_data.groupName] = mist.utils.deepCopy(group_data) mist.DBs.groupsById[group_data.groupId] = mist.utils.deepCopy(group_data) for unit_ind, unit_data in pairs(group_data.units) do - mist.DBs.unitsByName[unit_data.unitName] = mist.utils.deepCopy(unit_data) - mist.DBs.unitsById[unit_data.unitId] = mist.utils.deepCopy(unit_data) + local copy = mist.utils.deepCopy(unit_data) + local num = #mist.DBs.unitsByNum + 1 + copy.dbNum = num + + mist.DBs.unitsByName[unit_data.unitName] = mist.utils.deepCopy(copy) + mist.DBs.unitsById[unit_data.unitId] = mist.utils.deepCopy(copy) mist.DBs.unitsByCat[unit_data.category] = mist.DBs.unitsByCat[unit_data.category] or {} -- future-proofing against new categories... - table.insert(mist.DBs.unitsByCat[unit_data.category], mist.utils.deepCopy(unit_data)) + table.insert(mist.DBs.unitsByCat[unit_data.category], mist.utils.deepCopy(copy)) --dbLog:info('inserting $1', unit_data.unitName) - table.insert(mist.DBs.unitsByNum, mist.utils.deepCopy(unit_data)) + table.insert(mist.DBs.unitsByNum, mist.utils.deepCopy(copy)) if unit_data.skill and (unit_data.skill == "Client" or unit_data.skill == "Player") then - mist.DBs.humansByName[unit_data.unitName] = mist.utils.deepCopy(unit_data) - mist.DBs.humansById[unit_data.unitId] = mist.utils.deepCopy(unit_data) + mist.DBs.humansByName[unit_data.unitName] = mist.utils.deepCopy(copy) + mist.DBs.humansById[unit_data.unitId] = mist.utils.deepCopy(copy) --if Unit.getByName(unit_data.unitName) then -- mist.DBs.activeHumans[unit_data.unitName] = mist.utils.deepCopy(unit_data) -- mist.DBs.activeHumans[unit_data.unitName].playerName = Unit.getByName(unit_data.unitName):getPlayerName() --end end + if unit_data.airdromeId then + --log:echo(unit_data.airdromeId) + --log:echo(abRef.airbase[unit_data.airdromeId]) + if not mist.DBs.spawnsByBase[abRef.airbase[unit_data.airdromeId].name] then + mist.DBs.spawnsByBase[abRef.airbase[unit_data.airdromeId].name] = {} + end + table.insert(mist.DBs.spawnsByBase[abRef.airbase[unit_data.airdromeId].name], unit_data.unitName) + end + if unit_data.helipadId and abRef.units[unit_data.helipadId] and abRef.units[unit_data.helipadId].name then + if not mist.DBs.spawnsByBase[abRef.units[unit_data.helipadId].name] then + mist.DBs.spawnsByBase[abRef.units[unit_data.helipadId].name] = {} + end + table.insert(mist.DBs.spawnsByBase[abRef.units[unit_data.helipadId].name], unit_data.unitName) + end + end end end @@ -729,7 +790,60 @@ do -- the main scope end end end + + mist.DBs.zonesByName = {} + mist.DBs.zonesByNum = {} + if env.mission.triggers and env.mission.triggers.zones then + for zone_ind, zone_data in pairs(env.mission.triggers.zones) do + if type(zone_data) == 'table' then + local zone = mist.utils.deepCopy(zone_data) + --log:warn(zone) + zone.point = {} -- point is used by SSE + zone.point.x = zone_data.x + zone.point.y = land.getHeight({x = zone_data.x, y = zone_data.y}) + zone.point.z = zone_data.y + zone.properties = {} + if zone_data.properties then + for propInd, prop in pairs(zone_data.properties) do + if prop.value and tostring(prop.value) ~= "" then + zone.properties[prop.key] = prop.value + end + end + end + if zone.verticies then -- trust but verify + local r = 0 + for i = 1, #zone.verticies do + local dist = mist.utils.get2DDist(zone.point, zone.verticies[i]) + if dist > r then + r = mist.utils.deepCopy(dist) + end + end + zone.radius = r + + end + if zone.linkUnit then + local uRef = mist.DBs.unitsByName[zone.linkUnit] + if uRef then + if zone.verticies then + local offset = {} + for i = 1, #zone.verticies do + table.insert(offset, {dist = mist.utils.get2DDist(uRef.point, zone.verticies[i]), heading = mist.getHeadingPoints(uRef.point, zone.verticies[i]) + uRef.heading}) + end + zone.offset = offset + else + zone.offset = {dist = mist.utils.get2DDist(uRef.point, zone.point), heading = mist.getHeadingPoints(uRef.point, zone.point) + uRef.heading} + end + end + end + + mist.DBs.zonesByName[zone_data.name] = zone + mist.DBs.zonesByNum[#mist.DBs.zonesByNum + 1] = mist.utils.deepCopy(zone) --[[deepcopy so that the zone in zones_by_name and the zone in + zones_by_num se are different objects.. don't want them linked.]] + end + end + end + --DynDBs mist.DBs.MEunits = mist.utils.deepCopy(mist.DBs.units) mist.DBs.MEunitsByName = mist.utils.deepCopy(mist.DBs.unitsByName) @@ -789,6 +903,7 @@ do -- the main scope if not static_found then val.objectPos = pos.p val.objectType = 'building' + val.typeName = Object.getTypeName(val.object) end else val.objectType = 'unknown' @@ -803,10 +918,10 @@ do -- the main scope do -- mist unitID funcs for id, idData in pairs(mist.DBs.unitsById) do if idData.unitId > mist.nextUnitId then - mist.nextUnitId = mist.utils.deepCopy(idData.unitId) + mist.nextUnitId = mist.utils.deepCopy(idData.unitId) end if idData.groupId > mist.nextGroupId then - mist.nextGroupId = mist.utils.deepCopy(idData.groupId) + mist.nextGroupId = mist.utils.deepCopy(idData.groupId) end end end @@ -815,6 +930,7 @@ do -- the main scope end local function updateAliveUnits() -- coroutine function + --log:warn("updateALiveUnits") local lalive_units = mist.DBs.aliveUnits -- local references for faster execution local lunits = mist.DBs.unitsByNum local ldeepcopy = mist.utils.deepCopy @@ -831,7 +947,7 @@ do -- the main scope for i = 1, #lunits do if lunits[i].category ~= 'static' then -- can't get statics with Unit.getByName :( local unit = lUnit.getByName(lunits[i].unitName) - if unit then + if unit and unit:isExist() == true then ----dbLog:info("unit named $1 alive!", lunits[i].unitName) -- spammy local pos = unit:getPosition() local newtbl = ldeepcopy(lunits[i]) @@ -845,6 +961,7 @@ do -- the main scope end end if i%units_per_run == 0 then + --log:warn("yield: $1", i) coroutine.yield() end end @@ -858,9 +975,10 @@ do -- the main scope end end - local function dbUpdate(event, objType) - --dbLog:info('dbUpdate') + local function dbUpdate(event, oType, origGroupName) + --dbLog:info('dbUpdate: $1', event) local newTable = {} + local objType = oType newTable.startTime = 0 if type(event) == 'string' then -- if name of an object. local newObject @@ -868,15 +986,16 @@ do -- the main scope newObject = Group.getByName(event) elseif StaticObject.getByName(event) then newObject = StaticObject.getByName(event) + objType = "static" -- log:info('its static') else log:warn('$1 is not a Group or Static Object. This should not be possible. Sent category is: $2', event, objType) return false end - - newTable.name = newObject:getName() + local objName = newObject:getName() + newTable.name = origGroupName or objName newTable.groupId = tonumber(newObject:getID()) - newTable.groupName = newObject:getName() + newTable.groupName = origGroupName or objName local unitOneRef if objType == 'static' then unitOneRef = newObject @@ -888,7 +1007,7 @@ do -- the main scope if #unitOneRef > 0 and unitOneRef[1] and type(unitOneRef[1]) == 'table' then newTable.countryId = tonumber(unitOneRef[1]:getCountry()) newTable.coalitionId = tonumber(unitOneRef[1]:getCoalition()) - newTable.category = tonumber(newObject:getCategory()) + newTable.category = tonumber(Object.getCategory(newObject)) else log:warn('getUnits failed to return on $1 ; Built Data: $2.', event, newTable) return false @@ -939,15 +1058,16 @@ do -- the main scope newTable.units = {} if objType == 'group' then for unitId, unitData in pairs(unitOneRef) do + local point = unitData:getPoint() newTable.units[unitId] = {} newTable.units[unitId].unitName = unitData:getName() - newTable.units[unitId].x = mist.utils.round(unitData:getPosition().p.x) - newTable.units[unitId].y = mist.utils.round(unitData:getPosition().p.z) + newTable.units[unitId].x = mist.utils.round(point.x) + newTable.units[unitId].y = mist.utils.round(point.z) newTable.units[unitId].point = {} newTable.units[unitId].point.x = newTable.units[unitId].x newTable.units[unitId].point.y = newTable.units[unitId].y - newTable.units[unitId].alt = mist.utils.round(unitData:getPosition().p.y) + newTable.units[unitId].alt = mist.utils.round(point.y) newTable.units[unitId].speed = mist.vec.mag(unitData:getVelocity()) newTable.units[unitId].heading = mist.getHeading(unitData, true) @@ -985,15 +1105,16 @@ do -- the main scope end else -- its a static newTable.category = 'static' + local point = newObject:getPoint() newTable.units[1] = {} newTable.units[1].unitName = newObject:getName() newTable.units[1].category = 'static' - newTable.units[1].x = mist.utils.round(newObject:getPosition().p.x) - newTable.units[1].y = mist.utils.round(newObject:getPosition().p.z) + newTable.units[1].x = mist.utils.round(point.x) + newTable.units[1].y = mist.utils.round(point.z) newTable.units[1].point = {} newTable.units[1].point.x = newTable.units[1].x newTable.units[1].point.y = newTable.units[1].y - newTable.units[1].alt = mist.utils.round(newObject:getPosition().p.y) + newTable.units[1].alt = mist.utils.round(point.y) newTable.units[1].heading = mist.getHeading(newObject, true) newTable.units[1].type = newObject:getTypeName() newTable.units[1].unitId = tonumber(newObject:getID()) @@ -1003,7 +1124,7 @@ do -- the main scope newTable.units[1].country = newTable.country newTable.units[1].coalitionId = newTable.coalitionId newTable.units[1].coalition = newTable.coalition - if newObject:getCategory() == 6 and newObject:getCargoDisplayName() then + if Object.getCategory(newObject) == 6 and newObject:getCargoDisplayName() then local mass = newObject:getCargoDisplayName() mass = string.gsub(mass, ' ', '') mass = string.gsub(mass, 'kg', '') @@ -1061,9 +1182,10 @@ do -- the main scope --dbLog:info('iterate') for name, gData in pairs(tempSpawnedGroups) do --env.info(name) - --dbLog:info(gData) + --dbLog:warn(gData) local updated = false local stillExists = false + local staticGroupName if not gData.checked then tempSpawnedGroups[name].checked = true -- so if there was an error it will get cleared. local _g = gData.gp or Group.getByName(name) @@ -1073,7 +1195,7 @@ do -- the main scope local dbTable = mist.DBs.groupsByName[name] --dbLog:info(dbTable) if gData.type ~= 'static' then - -- dbLog:info('Not static') + --dbLog:info('Not static') if _g and _g:isExist() == true then stillExists = true @@ -1091,22 +1213,32 @@ do -- the main scope end end --dbLog:info('Updated: $1', updated) - if updated == false and gData.type ~= 'static' then -- time to check units - --dbLog:info('No Group Mismatch, Check Units') - if _g and _g:isExist() == true then - stillExists = true - for index, uObject in pairs(_g:getUnits()) do - --dbLog:info(index) - if mist.DBs.unitsByName[uObject:getName()] then - --dbLog:info('UnitByName table exists') - local uTable = mist.DBs.unitsByName[uObject:getName()] - if tonumber(uObject:getID()) ~= uTable.unitId or uObject:getTypeName() ~= uTable.type then - --dbLog:info('Unit Data mismatch') - updated = true - break + if updated == false then + if gData.type ~= 'static' then -- time to check units + -- dbLog:info('No Group Mismatch, Check Units') + if _g and _g:isExist() == true then + stillExists = true + for index, uObject in pairs(_g:getUnits()) do + -- dbLog:info(index) + if mist.DBs.unitsByName[uObject:getName()] then + --dbLog:info('UnitByName table exists') + local uTable = mist.DBs.unitsByName[uObject:getName()] + if tonumber(uObject:getID()) ~= uTable.unitId or uObject:getTypeName() ~= uTable.type then + --dbLog:info('Unit Data mismatch') + updated = true + break + end end end end + else -- it is a static object + local ref = mist.DBs.unitsByName[name] + if ref then + staticGroupName = ref.groupName + else + stillExists = true + end + end else stillExists = true @@ -1114,7 +1246,7 @@ do -- the main scope if stillExists == true and (updated == true or not mist.DBs.groupsByName[name]) then --dbLog:info('Get Table') - local dbData = dbUpdate(name, gData.type) + local dbData = dbUpdate(name, gData.type, staticGroupName) if dbData and type(dbData) == 'table' then writeGroups[#writeGroups+1] = {data = dbData, isUpdated = updated} end @@ -1127,6 +1259,151 @@ do -- the main scope end end + local updateChecker = {} + + + local function writeDBTables(newEntry) + local ldeepCopy = mist.utils.deepCopy + local newTable = newEntry.data + --dbLog:info(newTable) + + local state = 0 + if updateChecker[newTable.name] then + dbLog:warn("Failed to add to database: $1. Stopped at state: $2", newTable.name, updateChecker[newTable.name]) + return false + else + --dbLog:info('define default state') + updateChecker[newTable.name] = 0 + --dbLog:info('define default state1') + state = updateChecker[newTable.name] + --dbLog:info('define default state2') + end + + local updated = newEntry.isUpdated + local mistCategory + --dbLog:info('define categoryy') + if type(newTable.category) == 'string' then + mistCategory = string.lower(newTable.category) + end + + if string.upper(newTable.category) == 'GROUND_UNIT' then + mistCategory = 'vehicle' + newTable.category = mistCategory + elseif string.upper(newTable.category) == 'AIRPLANE' then + mistCategory = 'plane' + newTable.category = mistCategory + elseif string.upper(newTable.category) == 'HELICOPTER' then + mistCategory = 'helicopter' + newTable.category = mistCategory + elseif string.upper(newTable.category) == 'SHIP' then + mistCategory = 'ship' + newTable.category = mistCategory + end + --dbLog:info('Update unitsBy') + state = 1 + for newId, newUnitData in pairs(newTable.units) do + --dbLog:info(newId) + newUnitData.category = mistCategory + + --dbLog:info(updated) + if mist.DBs.unitsByName[newUnitData.unitName] and updated == true then --if unit existed before and something was updated, write over the entry for a given unit name just in case. + state = 1.1 + --dbLog:info('Updating Unit Tables') + local refNum = mist.DBs.unitsByName[newUnitData.unitName].dbNum + for i = 1, #mist.DBs.unitsByCat[mistCategory] do + if mist.DBs.unitsByCat[mistCategory][i].unitName == newUnitData.unitName then + --dbLog:info('Entry Found, Rewriting for unitsByCat') + mist.DBs.unitsByCat[mistCategory][i] = ldeepCopy(newUnitData) + break + end + end + state = 1.2 + --dbLog:info('updateByNum') + if refNum then -- easy way + --dbLog:info('refNum exists, Rewriting for unitsByCat') + mist.DBs.unitsByNum[refNum] = ldeepCopy(newUnitData) + else --- the hard way + --dbLog:info('iterate unitsByNum') + for i = 1, #mist.DBs.unitsByNum do + if mist.DBs.unitsByNum[i].unitName == newUnitData.unitName then + --dbLog:info('Entry Found, Rewriting for unitsByNum') + mist.DBs.unitsByNum[i] = ldeepCopy(newUnitData) + break + end + end + end + else + state = 1.3 + --dbLog:info('Unitname not in use, add as normal') + newUnitData.dbNum = #mist.DBs.unitsByNum + 1 + mist.DBs.unitsByCat[mistCategory][#mist.DBs.unitsByCat[mistCategory] + 1] = ldeepCopy(newUnitData) + mist.DBs.unitsByNum[#mist.DBs.unitsByNum + 1] = ldeepCopy(newUnitData) + end + if newUnitData.unitId then + --dbLog:info('byId') + mist.DBs.unitsById[tonumber(newUnitData.unitId)] = ldeepCopy(newUnitData) + end + mist.DBs.unitsByName[newUnitData.unitName] = ldeepCopy(newUnitData) + end + -- this is a really annoying DB to populate. Gotta create new tables in case its missing + --dbLog:info('write mist.DBs.units') + state = 2 + if not mist.DBs.units[newTable.coalition] then + mist.DBs.units[newTable.coalition] = {} + end + state = 3 + if not mist.DBs.units[newTable.coalition][newTable.country] then + mist.DBs.units[newTable.coalition][(newTable.country)] = {} + mist.DBs.units[newTable.coalition][(newTable.country)].countryId = newTable.countryId + end + state = 4 + if not mist.DBs.units[newTable.coalition][newTable.country][mistCategory] then + mist.DBs.units[newTable.coalition][(newTable.country)][mistCategory] = {} + end + state = 5 + if updated == true then + --dbLog:info('Updating DBsUnits') + for i = 1, #mist.DBs.units[newTable.coalition][(newTable.country)][mistCategory] do + if mist.DBs.units[newTable.coalition][(newTable.country)][mistCategory][i].groupName == newTable.groupName then + --dbLog:info('Entry Found, Rewriting') + mist.DBs.units[newTable.coalition][(newTable.country)][mistCategory][i] = ldeepCopy(newTable) + break + end + end + else + --dbLog:info('adding to DBs Units') + mist.DBs.units[newTable.coalition][(newTable.country)][mistCategory][#mist.DBs.units[newTable.coalition][(newTable.country)][mistCategory] + 1] = ldeepCopy(newTable) + end + state = 6 + + if newTable.groupId then + --dbLog:info('Make groupsById') + mist.DBs.groupsById[newTable.groupId] = ldeepCopy(newTable) + end + --dbLog:info('make groupsByName') + mist.DBs.groupsByName[newTable.name] = ldeepCopy(newTable) + --dbLog:info('add to dynGroups') + mist.DBs.dynGroupsAdded[#mist.DBs.dynGroupsAdded + 1] = ldeepCopy(newTable) + --dbLog:info('clear entry') + updateChecker[newTable.name] = nil + --dbLog:info('return') + return true + end + + function mist.forceAddToDB(object) + -- object is static object or group. + -- call dbUpdate to get the table + + local tbl = dbUpdate(object) + if tbl then + local res = writeDBTables(tbl) + if not res then + log:warn("Failed to force add to DBs: $1", object) + end + end + -- call writeDBTables with that table. + end + local function updateDBTables() local i = #writeGroups @@ -1135,108 +1412,24 @@ do -- the main scope savesPerRun = 5 end if i > 0 then - --dbLog:info('updateDBTables') - local ldeepCopy = mist.utils.deepCopy - for x = 1, i do - --dbLog:info(writeGroups[x]) - local newTable = writeGroups[x].data - local updated = writeGroups[x].isUpdated - local mistCategory - if type(newTable.category) == 'string' then - mistCategory = string.lower(newTable.category) - end - - if string.upper(newTable.category) == 'GROUND_UNIT' then - mistCategory = 'vehicle' - newTable.category = mistCategory - elseif string.upper(newTable.category) == 'AIRPLANE' then - mistCategory = 'plane' - newTable.category = mistCategory - elseif string.upper(newTable.category) == 'HELICOPTER' then - mistCategory = 'helicopter' - newTable.category = mistCategory - elseif string.upper(newTable.category) == 'SHIP' then - mistCategory = 'ship' - newTable.category = mistCategory - end - --dbLog:info('Update unitsBy') - for newId, newUnitData in pairs(newTable.units) do - --dbLog:info(newId) - newUnitData.category = mistCategory - if newUnitData.unitId then - --dbLog:info('byId') - mist.DBs.unitsById[tonumber(newUnitData.unitId)] = ldeepCopy(newUnitData) - end - --dbLog:info(updated) - if mist.DBs.unitsByName[newUnitData.unitName] and updated == true then--if unit existed before and something was updated, write over the entry for a given unit name just in case. - --dbLog:info('Updating Unit Tables') - for i = 1, #mist.DBs.unitsByCat[mistCategory] do - if mist.DBs.unitsByCat[mistCategory][i].unitName == newUnitData.unitName then - --dbLog:info('Entry Found, Rewriting for unitsByCat') - mist.DBs.unitsByCat[mistCategory][i] = ldeepCopy(newUnitData) - break - end - end - for i = 1, #mist.DBs.unitsByNum do - if mist.DBs.unitsByNum[i].unitName == newUnitData.unitName then - --dbLog:info('Entry Found, Rewriting for unitsByNum') - mist.DBs.unitsByNum[i] = ldeepCopy(newUnitData) - break - end - end - - else - --dbLog:info('Unitname not in use, add as normal') - mist.DBs.unitsByCat[mistCategory][#mist.DBs.unitsByCat[mistCategory] + 1] = ldeepCopy(newUnitData) - mist.DBs.unitsByNum[#mist.DBs.unitsByNum + 1] = ldeepCopy(newUnitData) - end - mist.DBs.unitsByName[newUnitData.unitName] = ldeepCopy(newUnitData) - - - end - -- this is a really annoying DB to populate. Gotta create new tables in case its missing - --dbLog:info('write mist.DBs.units') - if not mist.DBs.units[newTable.coalition] then - mist.DBs.units[newTable.coalition] = {} - end - - if not mist.DBs.units[newTable.coalition][newTable.country] then - mist.DBs.units[newTable.coalition][(newTable.country)] = {} - mist.DBs.units[newTable.coalition][(newTable.country)].countryId = newTable.countryId - end - if not mist.DBs.units[newTable.coalition][newTable.country][mistCategory] then - mist.DBs.units[newTable.coalition][(newTable.country)][mistCategory] = {} - end - - if updated == true then - --dbLog:info('Updating DBsUnits') - for i = 1, #mist.DBs.units[newTable.coalition][(newTable.country)][mistCategory] do - if mist.DBs.units[newTable.coalition][(newTable.country)][mistCategory][i].groupName == newTable.groupName then - --dbLog:info('Entry Found, Rewriting') - mist.DBs.units[newTable.coalition][(newTable.country)][mistCategory][i] = ldeepCopy(newTable) - break - end - end + --dbLog:info('updateDBTables: $1', #writeGroups) + + for x = 1, i do + local res = writeDBTables(writeGroups[x]) + if res and res == true then + --dbLog:info('result: complete') + writeGroups[x] = nil else - mist.DBs.units[newTable.coalition][(newTable.country)][mistCategory][#mist.DBs.units[newTable.coalition][(newTable.country)][mistCategory] + 1] = ldeepCopy(newTable) - end - - - if newTable.groupId then - mist.DBs.groupsById[newTable.groupId] = ldeepCopy(newTable) - end - - mist.DBs.groupsByName[newTable.name] = ldeepCopy(newTable) - mist.DBs.dynGroupsAdded[#mist.DBs.dynGroupsAdded + 1] = ldeepCopy(newTable) - - writeGroups[x] = nil - if x%savesPerRun == 0 then - coroutine.yield() + writeGroups[x] = nil end end + if x%savesPerRun == 0 then + coroutine.yield() + end if timer.getTime() > lastUpdateTime then lastUpdateTime = timer.getTime() end + --dbLog:info('endUpdateTables') end end @@ -1244,13 +1437,7 @@ do -- the main scope local function groupSpawned(event) -- dont need to add units spawned in at the start of the mission if mist is loaded in init line if event.id == world.event.S_EVENT_BIRTH and timer.getTime0() < timer.getAbsTime() then - --log:info('unitSpawnEvent') - --log:info(event) - --log:info(event.initiator:getTypeName()) - --table.insert(tempSpawnedUnits,(event.initiator)) - ------- - -- New functionality below. - ------- + if Object.getCategory(event.initiator) == 1 and not Unit.getPlayerName(event.initiator) then -- simple player check, will need to later check to see if unit was spawned with a player in a flight --log:info('Object is a Unit') if Unit.getGroup(event.initiator) then @@ -1265,7 +1452,15 @@ do -- the main scope log:error('Group not accessible by unit in event handler. This is a DCS bug') end elseif Object.getCategory(event.initiator) == 3 or Object.getCategory(event.initiator) == 6 then - --log:info('Object is Static') + --log:info('staticSpawnEvent') + --log:info(event) + --log:info(event.initiator:getTypeName()) + --table.insert(tempSpawnedUnits,(event.initiator)) + ------- + -- New functionality below. + ------- + --log:info(event.initiator:getName()) + --log:info('Object is Static') tempSpawnedGroups[StaticObject.getName(event.initiator)] = {type = 'static'} tempSpawnGroupsCounter = tempSpawnGroupsCounter + 1 end @@ -1277,8 +1472,9 @@ do -- the main scope local function doScheduledFunctions() local i = 1 while i <= #scheduledTasks do + local refTime = timer.getTime() if not scheduledTasks[i].rep then -- not a repeated process - if scheduledTasks[i].t <= timer.getTime() then + if scheduledTasks[i].t <= refTime then local task = scheduledTasks[i] -- local reference table.remove(scheduledTasks, i) local err, errmsg = pcall(task.f, unpack(task.vars, 1, table.maxn(task.vars))) @@ -1290,9 +1486,9 @@ do -- the main scope i = i + 1 end else - if scheduledTasks[i].st and scheduledTasks[i].st <= timer.getTime() then --if a stoptime was specified, and the stop time exceeded + if scheduledTasks[i].st and scheduledTasks[i].st <= refTime then --if a stoptime was specified, and the stop time exceeded table.remove(scheduledTasks, i) -- stop time exceeded, do not execute, do not increment i - elseif scheduledTasks[i].t <= timer.getTime() then + elseif scheduledTasks[i].t <= refTime then local task = scheduledTasks[i] -- local reference task.t = timer.getTime() + task.rep --schedule next run local err, errmsg = pcall(task.f, unpack(task.vars, 1, table.maxn(task.vars))) @@ -1363,6 +1559,7 @@ do -- the main scope if not static_found then val.objectPos = pos.p val.objectType = 'building' + val.typeName = Object.getTypeName(val.object) end else val.objectType = 'unknown' @@ -1440,7 +1637,7 @@ do -- the main scope -- create logger mist.log = mist.Logger:new("MIST", mistSettings.logLevel) - dbLog = mist.Logger:new('MISTDB', 'warn') + dbLog = mist.Logger:new('MISTDB', mistSettings.dbLog) log = mist.log -- log shorthand -- set warning log level, showing only @@ -1502,7 +1699,7 @@ do -- the main scope coroutines.updateAliveUnits = nil end end - + doScheduledFunctions() end -- end of mist.main @@ -1536,8 +1733,9 @@ do -- the main scope -- @todo write good docs -- @tparam table staticObj table containing data needed for the object creation function mist.dynAddStatic(n) - --log:info(newObj) + local newObj = mist.utils.deepCopy(n) + log:warn(newObj) if newObj.units and newObj.units[1] then -- if its mist format for entry, val in pairs(newObj.units[1]) do if newObj[entry] and newObj[entry] ~= val or not newObj[entry] then @@ -1595,7 +1793,7 @@ do -- the main scope end if not newObj.heading then - newObj.heading = math.random(360) + newObj.heading = math.rad(math.random(360)) end if newObj.categoryStatic then @@ -1805,8 +2003,9 @@ do -- the main scope -- update and verify any self tasks if newGroup.route and newGroup.route.points then + --log:warn(newGroup.route.points) for i, pData in pairs(newGroup.route.points) do - if pData.task and pData.task.params and pData.task.params.tasks and #pData.task.params.tasks > 0 then + if pData.task and pData.task.params and pData.task.params.tasks and #pData.task.params.tasks > 0 then for tIndex, tData in pairs(pData.task.params.tasks) do if tData.params and tData.params.action then if tData.params.action.id == "EPLRS" then @@ -2033,7 +2232,12 @@ do if metric then s = s .. ' at ' .. mist.utils.round(alt, 0) else - s = s .. ' at ' .. mist.utils.round(mist.utils.metersToFeet(alt), 0) + s = s .. ' at ' + local rounded = mist.utils.round(mist.utils.metersToFeet(alt/1000), 0) + s = s .. rounded + if rounded > 0 then + s = s .. "000" + end end end return s @@ -2827,11 +3031,11 @@ end function mist.getUnitsByAttribute(att, rnum, id) local cEntry = {} - cEntry.typeName = att.type or att.typeName or att.typename + cEntry.type = att.type or att.typeName or att.typename cEntry.country = att.country cEntry.coalition = att.coalition cEntry.skill = att.skill - cEntry.categry = att.category + cEntry.category = att.category local num = rnum or 1 @@ -2852,6 +3056,7 @@ function mist.getUnitsByAttribute(att, rnum, id) end end else + if uData[cName] and uData[cName] == cVal then matched = matched + 1 end @@ -2877,11 +3082,11 @@ end function mist.getGroupsByAttribute(att, rnum, id) local cEntry = {} - cEntry.typeName = att.type or att.typeName or att.typename + cEntry.type = att.type or att.typeName or att.typename cEntry.country = att.country cEntry.coalition = att.coalition cEntry.skill = att.skill - cEntry.categry = att.category + cEntry.category = att.category local num = rnum or 1 @@ -2894,7 +3099,7 @@ function mist.getGroupsByAttribute(att, rnum, id) for cName, cVal in pairs(cEntry) do if type(cVal) == 'table' then for sName, sVal in pairs(cVal) do - if cName == 'skill' or cName == 'typeName' then + if cName == 'skill' or cName == 'type' then local lMatch = 0 for uId, uData in pairs(gData.units) do if (uData[cName] and uData[cName] == sVal) or (gData[cName] and gData[cName] == sName) then @@ -2912,7 +3117,7 @@ function mist.getGroupsByAttribute(att, rnum, id) end end else - if cName == 'skill' or cName == 'typeName' then + if cName == 'skill' or cName == 'type' then local lMatch = 0 for uId, uData in pairs(gData.units) do if (uData[cName] and uData[cName] == sVal) then @@ -2945,7 +3150,33 @@ function mist.getGroupsByAttribute(att, rnum, id) end -function mist.getDeadMapObjsInZones(zone_names) +function mist.getDeadMapObjectsFromPoint(p, radius, filters) + local map_objs = {} + local fCheck = filters or {} + local filter = {} + local r = radius or p.radius or 100 + local point = mist.utils.makeVec3(p) + local filterSize = 0 + for fInd, fVal in pairs(fCheck) do + filterSize = filterSize + 1 + filter[string.lower(fInd)] = true + filter[string.lower(fVal)] = true + + end + for obj_id, obj in pairs(mist.DBs.deadObjects) do + log:warn(obj) + if obj.objectType and obj.objectType == 'building' then --dead map object + if ((point.x - obj.objectPos.x)^2 + (point.z - obj.objectPos.z)^2)^0.5 <= r then + if filterSize == 0 or (obj.typeName and filter[string.lower(obj.typeName)])then + map_objs[#map_objs + 1] = mist.utils.deepCopy(obj) + end + end + end + end + return map_objs +end + +function mist.getDeadMapObjsInZones(zone_names, filters) -- zone_names: table of zone names -- returns: table of dead map objects (indexed numerically) local map_objs = {} @@ -2955,25 +3186,32 @@ function mist.getDeadMapObjsInZones(zone_names) zones[#zones + 1] = mist.DBs.zonesByName[zone_names[i]] end end - for obj_id, obj in pairs(mist.DBs.deadObjects) do - if obj.objectType and obj.objectType == 'building' then --dead map object - for i = 1, #zones do - if ((zones[i].point.x - obj.objectPos.x)^2 + (zones[i].point.z - obj.objectPos.z)^2)^0.5 <= zones[i].radius then - map_objs[#map_objs + 1] = mist.utils.deepCopy(obj) - end - end - end - end + for i = 1, #zones do + local rtn = mist.getDeadMapObjectsFromPoint(zones[i], nil, filters) + for j = 1, #rtn do + map_objs[#map_objs + 1] = rtn[j] + end + end + return map_objs end -function mist.getDeadMapObjsInPolygonZone(zone) +function mist.getDeadMapObjsInPolygonZone(zone, filters) -- zone_names: table of zone names -- returns: table of dead map objects (indexed numerically) + local filter = {} + local fCheck = filters or {} + local filterSize = 0 + for fInd, fVal in pairs(fCheck) do + filterSize = filterSize + 1 + filter[string.lower(fInd)] = true + filter[string.lower(fVal)] = true + + end local map_objs = {} for obj_id, obj in pairs(mist.DBs.deadObjects) do if obj.objectType and obj.objectType == 'building' then --dead map object - if mist.pointInPolygon(obj.objectPos, zone) then + if mist.pointInPolygon(obj.objectPos, zone) and (filterSize == 0 or filter[string.lower(obj.objectData.type)]) then map_objs[#map_objs + 1] = mist.utils.deepCopy(obj) end end @@ -3096,7 +3334,7 @@ function mist.shape.getPointOnSegment(point, seg, isSeg) local cx, cy = p.x - s1.x, p.y - s1.y - local dx, dy = s2.x - s1.x, s2.x - s1.y + local dx, dy = s2.x - s1.x, s2.y - s1.y local d = (dx*dx + dy*dy) if d == 0 then @@ -3114,11 +3352,17 @@ function mist.shape.getPointOnSegment(point, seg, isSeg) end -function mist.shape.segmentIntersect(segA, segB) - local dx1, dy1 = segA[2].x - segA[1].x, segA[2] - segA[1].y - local dx2, dy2 = segB[2].x - segB[1].x, segB[2] - segB[1].y + +function mist.shape.segmentIntersect(seg1, seg2) + local segA = {mist.utils.makeVec2(seg1[1]), mist.utils.makeVec2(seg1[2])} + local segB = {mist.utils.makeVec2(seg2[1]), mist.utils.makeVec2(seg2[2])} + + local dx1, dy1 = segA[2].x - segA[1].x, segA[2].y - segA[1].y + local dx2, dy2 = segB[2].x - segB[1].x, segB[2].y - segB[1].y local dx3, dy3 = segA[1].x - segB[1].x, segA[1].y - segB[1].y + local d = dx1*dy2 - dy1*dx2 + if d == 0 then return false end @@ -3131,7 +3375,7 @@ function mist.shape.segmentIntersect(segA, segB) return false end -- point of intersection - return true, segA[1].x + t1*dx1, segA[1].y + t1*dy1 + return true, {x = segA[1].x + t1*dx1, y = segA[1].y + t1*dy1} end @@ -3187,8 +3431,8 @@ function mist.getUnitsInPolygon(unit_names, polyZone, max_alt) local inZoneUnits = {} for i =1, #units do local lUnit = units[i] - local lCat = lUnit:getCategory() - if ((lCat == 1 and lUnit:isActive()) or lCat ~= 1) and mist.pointInPolygon(lUnit:getPosition().p, polyZone, max_alt) then + local lCat = Object.getCategory(lUnit) + if lUnit:isExist() == true and ((lCat == 1 and lUnit:isActive()) or lCat ~= 1) and mist.pointInPolygon(lUnit:getPosition().p, polyZone, max_alt) then inZoneUnits[#inZoneUnits + 1] = lUnit end end @@ -3216,7 +3460,7 @@ function mist.getUnitsInZones(unit_names, zone_names, zone_type) for k = 1, #unit_names do local unit = Unit.getByName(unit_names[k]) or StaticObject.getByName(unit_names[k]) - if unit then + if unit and unit:isExist() == true then units[#units + 1] = unit end end @@ -3233,7 +3477,7 @@ function mist.getUnitsInZones(unit_names, zone_names, zone_type) for units_ind = 1, #units do local lUnit = units[units_ind] local unit_pos = lUnit:getPosition().p - local lCat = lUnit:getCategory() + local lCat = Object.getCategory(lUnit) for zones_ind = 1, #zones do if zone_type == 'sphere' then --add land height value for sphere zone type local alt = land.getHeight({x = zones[zones_ind].x, y = zones[zones_ind].z}) @@ -3280,14 +3524,14 @@ function mist.getUnitsInMovingZones(unit_names, zone_unit_names, radius, zone_ty for k = 1, #unit_names do local unit = Unit.getByName(unit_names[k]) or StaticObject.getByName(unit_names[k]) - if unit then + if unit and unit:isExist() == true then units[#units + 1] = unit end end for k = 1, #zone_unit_names do local unit = Unit.getByName(zone_unit_names[k]) or StaticObject.getByName(zone_unit_names[k]) - if unit then + if unit and unit:isExist() == true then zone_units[#zone_units + 1] = unit end end @@ -3296,7 +3540,7 @@ function mist.getUnitsInMovingZones(unit_names, zone_unit_names, radius, zone_ty for units_ind = 1, #units do local lUnit = units[units_ind] - local lCat = lUnit:getCategory() + local lCat = Object.getCategory(lUnit) local unit_pos = lUnit:getPosition().p for zone_units_ind = 1, #zone_units do @@ -3324,8 +3568,8 @@ function mist.getUnitsLOS(unitset1, altoffset1, unitset2, altoffset2, radius) -- get the positions all in one step, saves execution time. for unitset1_ind = 1, #unitset1 do local unit1 = Unit.getByName(unitset1[unitset1_ind]) - local lCat = unit1:getCategory() - if unit1 and ((lCat == 1 and unit1:isActive()) or lCat ~= 1) then + local lCat = Object.getCategory(unit1) + if unit1 and ((lCat == 1 and unit1:isActive()) or lCat ~= 1) and unit:isExist() == true then unit_info1[#unit_info1 + 1] = {} unit_info1[#unit_info1].unit = unit1 unit_info1[#unit_info1].pos = unit1:getPosition().p @@ -3334,8 +3578,8 @@ function mist.getUnitsLOS(unitset1, altoffset1, unitset2, altoffset2, radius) for unitset2_ind = 1, #unitset2 do local unit2 = Unit.getByName(unitset2[unitset2_ind]) - local lCat = unit2:getCategory() - if unit2 and ((lCat == 1 and unit2:isActive()) or lCat ~= 1) then + local lCat = Object.getCategory(unit2) + if unit2 and ((lCat == 1 and unit2:isActive()) or lCat ~= 1) and unit:isExist() == true then unit_info2[#unit_info2 + 1] = {} unit_info2[#unit_info2].unit = unit2 unit_info2[#unit_info2].pos = unit2:getPosition().p @@ -3395,7 +3639,7 @@ function mist.getAvgPos(unitNames) elseif StaticObject.getByName(unitNames[i]) then unit = StaticObject.getByName(unitNames[i]) end - if unit then + if unit and unit:isExist() == true then local pos = unit:getPosition().p if pos then -- you never know O.o avgX = avgX + pos.x @@ -3764,7 +4008,7 @@ do -- group functions scope --- Returns group data table of give group. function mist.getCurrentGroupData(gpName) - local dbData = mist.getGroupData(gpName) + local dbData = mist.getGroupData(gpName) or {} if Group.getByName(gpName) and Group.getByName(gpName):isExist() == true then local newGroup = Group.getByName(gpName) @@ -3800,23 +4044,24 @@ do -- group functions scope newData.units[unitNum].callsign = unitData:getCallsign() newData.units[unitNum].unitName = uName end - - newData.units[unitNum].x = unitData:getPosition().p.x - newData.units[unitNum].y = unitData:getPosition().p.z + local pos = unitData:getPosition() + newData.units[unitNum].x = pos.p.x + newData.units[unitNum].y = pos.p.z newData.units[unitNum].point = {x = newData.units[unitNum].x, y = newData.units[unitNum].y} - newData.units[unitNum].heading = mist.getHeading(unitData, true) -- added to DBs - newData.units[unitNum].alt = unitData:getPosition().p.y + newData.units[unitNum].heading = math.atan2(pos.x.z, pos.x.x) + newData.units[unitNum].alt = pos.p.y newData.units[unitNum].speed = mist.vec.mag(unitData:getVelocity()) end return newData - elseif StaticObject.getByName(gpName) and StaticObject.getByName(gpName):isExist() == true then + elseif StaticObject.getByName(gpName) and StaticObject.getByName(gpName):isExist() == true and dbData.units then local staticObj = StaticObject.getByName(gpName) - dbData.units[1].x = staticObj:getPosition().p.x - dbData.units[1].y = staticObj:getPosition().p.z - dbData.units[1].alt = staticObj:getPosition().p.y - dbData.units[1].heading = mist.getHeading(staticObj, true) + local pos =staticObj:getPosition() + dbData.units[1].x = pos.p.x + dbData.units[1].y = pos.p.z + dbData.units[1].alt = pos.p.y + dbData.units[1].heading = math.atan2(pos.x.z, pos.x.x) return dbData end @@ -3903,40 +4148,48 @@ do -- group functions scope unitId = mist.DBs.MEunitsByName[unitIdent].unitId else log:error("Unit not found in mist.DBs.MEunitsByName: $1", unitIdent) + return {} end - end - local gpId = mist.DBs.MEunitsById[unitId].groupId + elseif type(unitIdent) == "number" and not mist.DBs.MEunitsById[unitIdent] then + log:error("Unit not found in mist.DBs.MEunitsBId: $1", unitIdent) + return {} + end + local ref = mist.DBs.MEunitsById[unitId] + + if ref then + local gpId = mist.DBs.MEunitsById[unitId].groupId - if gpId and unitId then - for coa_name, coa_data in pairs(env.mission.coalition) do - if (coa_name == 'red' or coa_name == 'blue') and type(coa_data) == 'table' then - if coa_data.country then --there is a country table - for cntry_id, cntry_data in pairs(coa_data.country) do - for obj_cat_name, obj_cat_data in pairs(cntry_data) do - if obj_cat_name == "helicopter" or obj_cat_name == "ship" or obj_cat_name == "plane" or obj_cat_name == "vehicle" then -- only these types have points - if ((type(obj_cat_data) == 'table') and obj_cat_data.group and (type(obj_cat_data.group) == 'table') and (#obj_cat_data.group > 0)) then --there's a group! - for group_num, group_data in pairs(obj_cat_data.group) do - if group_data and group_data.groupId == gpId then - for unitIndex, unitData in pairs(group_data.units) do --group index - if unitData.unitId == unitId then - return unitData.payload - end - end - end - end - end - end - end - end - end - end - end + if gpId and unitId then + for coa_name, coa_data in pairs(env.mission.coalition) do + if (coa_name == 'red' or coa_name == 'blue') and type(coa_data) == 'table' then + if coa_data.country then --there is a country table + for cntry_id, cntry_data in pairs(coa_data.country) do + for obj_cat_name, obj_cat_data in pairs(cntry_data) do + if obj_cat_name == "helicopter" or obj_cat_name == "ship" or obj_cat_name == "plane" or obj_cat_name == "vehicle" then -- only these types have points + if ((type(obj_cat_data) == 'table') and obj_cat_data.group and (type(obj_cat_data.group) == 'table') and (#obj_cat_data.group > 0)) then --there's a group! + for group_num, group_data in pairs(obj_cat_data.group) do + if group_data and group_data.groupId == gpId then + for unitIndex, unitData in pairs(group_data.units) do --group index + if unitData.unitId == unitId then + return unitData.payload + end + end + end + end + end + end + end + end + end + end + end + end else log:error('Need string or number. Got: $1', type(unitIdent)) - return false + return {} end log:warn("Couldn't find payload for unit: $1", unitIdent) - return + return {} end function mist.getGroupPayload(groupIdent) @@ -3946,6 +4199,7 @@ do -- group functions scope gpId = mist.DBs.MEgroupsByName[groupIdent].groupId else log:error('$1 not found in mist.DBs.MEgroupsByName', groupIdent) + return {} end end @@ -3975,10 +4229,10 @@ do -- group functions scope end else log:error('Need string or number. Got: $1', type(groupIdent)) - return false + return {} end log:warn("Couldn't find payload for group: $1", groupIdent) - return + return {} end function mist.getGroupTable(groupIdent) @@ -4001,7 +4255,10 @@ do -- group functions scope if ((type(obj_cat_data) == 'table') and obj_cat_data.group and (type(obj_cat_data.group) == 'table') and (#obj_cat_data.group > 0)) then --there's a group! for group_num, group_data in pairs(obj_cat_data.group) do if group_data and group_data.groupId == gpId then - return group_data + local gp = mist.utils.deepCopy(group_data) + gp.category = obj_cat_name + gp.country = cntry_data.id + return gp end end end @@ -4033,7 +4290,7 @@ do -- group functions scope elseif vars.groupName then gpName = vars.groupName else - log:error('Missing field groupName or gpName in variable table') + log:error('Missing field groupName or gpName in variable table. Table: $1', vars) end --[[New vars to add, mostly for when called via inZone functions @@ -5167,21 +5424,21 @@ do -- mist.util scope -- borrowed from slmod -- @param var variable to serialize -- @treturn string variable serialized to string - function mist.utils.basicSerialize(var) - if var == nil then - return "\"\"" - else - if ((type(var) == 'number') or - (type(var) == 'boolean') or - (type(var) == 'function') or - (type(var) == 'table') or - (type(var) == 'userdata') ) then - return tostring(var) - elseif type(var) == 'string' then - var = string.format('%q', var) - return var - end - end +function mist.utils.basicSerialize(var) + if var == nil then + return "\"\"" + else + if ((type(var) == 'number') or + (type(var) == 'boolean') or + (type(var) == 'function') or + (type(var) == 'table') or + (type(var) == 'userdata') ) then + return tostring(var) + elseif type(var) == 'string' then + var = string.format('%q', var) + return var + end + end end --- Serialize value @@ -5332,6 +5589,123 @@ function mist.utils.oneLineSerialize(tbl) end end +function mist.utils.tableShowSorted(tbls, v) + local vars = v or {} + local loc = vars.loc or "" + local indent = vars.indent or "" + local tableshow_tbls = vars.tableshow_tbls or {} + local tbl = tbls or {} + + if type(tbl) == 'table' then --function only works for tables! + tableshow_tbls[tbl] = loc + + local tbl_str = {} + + tbl_str[#tbl_str + 1] = indent .. '{\n' + + local sorted = {} + local function byteCompare(str1, str2) + local shorter = string.len(str1) + if shorter > string.len(str2) then + shorter = string.len(str2) + end + for i = 1, shorter do + local b1 = string.byte(str1, i) + local b2 = string.byte(str2, i) + + if b1 < b2 then + return true + elseif b1 > b2 then + return false + end + + end + return false + end + for ind, val in pairs(tbl) do -- serialize its fields + local indS = tostring(ind) + local ins = {ind = indS, val = val} + local index + if #sorted > 0 then + local found = false + for i = 1, #sorted do + if byteCompare(indS, tostring(sorted[i].ind)) == true then + index = i + break + end + + end + end + if index then + table.insert(sorted, index, ins) + else + table.insert(sorted, ins) + end + + end + --log:warn(sorted) + for i = 1, #sorted do + local ind = sorted[i].ind + local val = sorted[i].val + + if type(ind) == "number" then + tbl_str[#tbl_str + 1] = indent + tbl_str[#tbl_str + 1] = loc .. '[' + tbl_str[#tbl_str + 1] = tostring(ind) + tbl_str[#tbl_str + 1] = '] = ' + else + tbl_str[#tbl_str + 1] = indent + tbl_str[#tbl_str + 1] = loc .. '[' + tbl_str[#tbl_str + 1] = mist.utils.basicSerialize(ind) + tbl_str[#tbl_str + 1] = '] = ' + end + + if ((type(val) == 'number') or (type(val) == 'boolean')) then + tbl_str[#tbl_str + 1] = tostring(val) + tbl_str[#tbl_str + 1] = ',\n' + elseif type(val) == 'string' then + tbl_str[#tbl_str + 1] = mist.utils.basicSerialize(val) + tbl_str[#tbl_str + 1] = ',\n' + elseif type(val) == 'nil' then -- won't ever happen, right? + tbl_str[#tbl_str + 1] = 'nil,\n' + elseif type(val) == 'table' then + if tableshow_tbls[val] then + tbl_str[#tbl_str + 1] = ' already defined: ' .. tableshow_tbls[val] .. ',\n' + else + tableshow_tbls[val] = loc .. '["' .. ind .. '"]' + --tbl_str[#tbl_str + 1] = tostring(val) .. ' ' + tbl_str[#tbl_str + 1] = mist.utils.tableShowSorted(val, {loc = loc .. '["' .. ind .. '"]', indent = indent .. ' ', tableshow_tbls = tableshow_tbls}) + tbl_str[#tbl_str + 1] = ',\n' + end + elseif type(val) == 'function' then + if debug and debug.getinfo then + local fcnname = tostring(val) + local info = debug.getinfo(val, "S") + if info.what == "C" then + tbl_str[#tbl_str + 1] = ', C function\n' + else + if (string.sub(info.source, 1, 2) == [[./]]) then + tbl_str[#tbl_str + 1] = string.format('%q', 'function, defined in (' .. '-' .. info.lastlinedefined .. ')' .. info.source) ..',\n' + else + tbl_str[#tbl_str + 1] = string.format('%q', 'function, defined in (' .. '-' .. info.lastlinedefined .. ')') ..',\n' + end + end + + else + tbl_str[#tbl_str + 1] = 'a function,\n' + end + else + tbl_str[#tbl_str + 1] = 'unable to serialize value type ' .. mist.utils.basicSerialize(type(val)) .. ' at index ' .. tostring(ind) + end + end + + tbl_str[#tbl_str + 1] = indent .. '}' + return table.concat(tbl_str) + end + + +end + --- Returns table in a easy readable string representation. -- this function is not meant for serialization because it uses -- newlines for better readability. @@ -5351,7 +5725,7 @@ function mist.utils.tableShow(tbl, loc, indent, tableshow_tbls) --based on seria tbl_str[#tbl_str + 1] = indent .. '{\n' - for ind,val in pairs(tbl) do -- serialize its fields + for ind, val in pairs(tbl) do if type(ind) == "number" then tbl_str[#tbl_str + 1] = indent tbl_str[#tbl_str + 1] = loc .. '[' @@ -5451,10 +5825,10 @@ do -- mist.debug scope g.country.by_idx = nil g.country.by_country = nil - f:write(mist.utils.tableShow(g)) + f:write(mist.utils.tableShowSorted(g)) else - f:write(mist.utils.tableShow(_G)) + f:write(mist.utils.tableShowSorted(_G)) end f:close() log:info('Wrote debug data to $1', fdir) @@ -5906,7 +6280,7 @@ unitTableDef = table or nil local num_in_zone = 0 for i = 1, #units do local unit = Unit.getByName(units[i]) or StaticObject.getByName(units[i]) - if unit then + if unit and unit:isExist() == true then local pos = unit:getPosition().p if mist.pointInPolygon(pos, zone, maxalt) then num_in_zone = num_in_zone + 1 @@ -6368,7 +6742,8 @@ do -- mist.msg scope local caSlots = false local caMSGtoGroup = false local anyUpdate = false - local lastMessageTime = nil + local anySound = false + local lastMessageTime = math.huge if env.mission.groundControl then -- just to be sure? for index, value in pairs(env.mission.groundControl) do @@ -6390,12 +6765,12 @@ do -- mist.msg scope end local function mistdisplayV5() - --log:warn("mistdisplayV5: $1", timer.getTime()) + log:warn("mistdisplayV5: $1", timer.getTime()) local clearView = true if #messageList > 0 then - --log:warn('Updates: $1', anyUpdate) - if anyUpdate == true then + log:warn('Updates: $1', anyUpdate) + if anyUpdate == true or anySound == true then local activeClients = {} for clientId, clientData in pairs(mist.DBs.humansById) do @@ -6403,7 +6778,7 @@ do -- mist.msg scope activeClients[clientData.groupId] = clientData.groupName end end - anyUpdate = false + if displayActive == false then displayActive = true end @@ -6412,21 +6787,24 @@ do -- mist.msg scope local msgTableSound = {} local curTime = timer.getTime() for mInd, messageData in pairs(messageList) do - --log:warn(messageData) + log:warn(messageData) if messageData.displayTill < curTime then + log:warn('remove') messageData:remove() -- now using the remove/destroy function. else if messageData.displayedFor then messageData.displayedFor = curTime - messageData.addedAt end - local nextSound = 1000 + local soundIndex = 0 - + local refSound = 100000 if messageData.multSound and #messageData.multSound > 0 then + anySound = true for index, sData in pairs(messageData.multSound) do - if sData.time <= messageData.displayedFor and sData.played == false and sData.time < nextSound then -- find index of the next sound to be played - nextSound = sData.time + if sData.time <= messageData.displayedFor and sData.played == false and sData.time < refSound then -- find index of the next sound to be played + refSound = sData.time soundIndex = index + end end if soundIndex ~= 0 then @@ -6471,20 +6849,21 @@ do -- mist.msg scope end ------- new display + if anyUpdate == true then + if caSlots == true and caMSGtoGroup == false then + if msgTableText.RED then + trigger.action.outTextForCoalition(coalition.side.RED, table.concat(msgTableText.RED.text), msgTableText.RED.displayTime, clearView) - if caSlots == true and caMSGtoGroup == false then - if msgTableText.RED then - trigger.action.outTextForCoalition(coalition.side.RED, table.concat(msgTableText.RED.text), msgTableText.RED.displayTime, clearView) - + end + if msgTableText.BLUE then + trigger.action.outTextForCoalition(coalition.side.BLUE, table.concat(msgTableText.BLUE.text), msgTableText.BLUE.displayTime, clearView) + end end - if msgTableText.BLUE then - trigger.action.outTextForCoalition(coalition.side.BLUE, table.concat(msgTableText.BLUE.text), msgTableText.BLUE.displayTime, clearView) - end - end - for index, msgData in pairs(msgTableText) do - if type(index) == 'number' then -- its a groupNumber - trigger.action.outTextForGroup(index, table.concat(msgData.text), msgData.displayTime, clearView) + for index, msgData in pairs(msgTableText) do + if type(index) == 'number' then -- its a groupNumber + trigger.action.outTextForGroup(index, table.concat(msgData.text), msgData.displayTime, clearView) + end end end --- new audio @@ -6502,7 +6881,10 @@ do -- mist.msg scope end end - end + end + + anyUpdate = false + anySound = false else mist.removeFunction(displayFuncId) @@ -6676,6 +7058,7 @@ end]] new.displayTill = timer.getTime() + vars.displayTime new.name = vars.name -- ID to overwrite the older message (if it exists) Basically it replaces a message that is displayed with new text. new.addedAt = timer.getTime() + new.clearView = vars.clearView or true --log:warn('New Message: $1', new.text) if vars.multSound and vars.multSound[1] then @@ -6764,7 +7147,6 @@ end]] messageList[i].displayTill = timer.getTime() + messageList[i].displayTime messageList[i].displayedFor = 0 messageList[i].addedAt = timer.getTime() - messageList[i].sound = new.sound messageList[i].text = new.text messageList[i].msgFor = new.msgFor messageList[i].multSound = new.multSound @@ -6789,7 +7171,7 @@ end]] if displayActive == false then displayActive = true - displayFuncId = mist.scheduleFunction(mistdisplayV5, {}, timer.getTime() + messageDisplayRate, messageDisplayRate) + displayFuncId = mist.scheduleFunction(mistdisplayV4, {}, timer.getTime() + messageDisplayRate, messageDisplayRate) end return messageID @@ -7268,7 +7650,7 @@ do local altNames = {['poly'] = 7, ['lines'] = 1, ['polygon'] = 7 } local function draw(s) - --log:warn(s) + --log:warn(s) if type(s) == 'table' then local mType = s.markType if mType == 'panel' then @@ -7373,7 +7755,7 @@ do --log:info('create maker DB: $1', e.idx) mist.DBs.markList[e.idx] = {time = e.time, pos = e.pos, groupId = e.groupId, mType = 'panel', text = e.text, markId = e.idx, coalition = e.coalition} if e.unit then - mist.DBs.markList[e.idx].unit = e.initiaor:getName() + mist.DBs.markList[e.idx].unit = e.intiator:getName() end --log:info(mist.marker.list[e.idx]) end @@ -7472,7 +7854,7 @@ do local coa = -1 local usedId = 0 - + pos = mist.utils.deepCopy(pos) if id then if type(id) ~= 'number' then @@ -8594,7 +8976,7 @@ do -- group tasks scope break end if j == 100 then - newCoord = mist.getRandPointInCircle(avg, 50000) + newCoord = mist.getRandPointInCircle(avg, radius) log:warn("Failed to find point in poly; Giving random point from center of the poly") end end @@ -8602,13 +8984,13 @@ do -- group tasks scope end function mist.getWindBearingAndVel(p) - local point = mist.utils.makeVec3(o) + local point = mist.utils.makeVec3(p) local gLevel = land.getHeight({x = point.x, y = point.z}) if point.y <= gLevel then point.y = gLevel + 10 end local t = atmosphere.getWind(point) - local bearing = math.tan(t.z/t.x) + local bearing = math.atan2(t.z, t.x) local vel = math.sqrt(t.x^2 + t.z^2) return bearing, vel @@ -8788,36 +9170,54 @@ do -- group tasks scope end function mist.getLeadPos(group) - if type(group) == 'string' then -- group name - group = Group.getByName(group) - end + local gObj + if type(group) == 'string' then -- group name + gObj = Group.getByName(group) + elseif type(group) == "table" then + gObj = group + end - local units = group:getUnits() + if gObj then + local units = gObj:getUnits() - local leader = units[1] - if Unit.getLife(leader) == 0 or not Unit.isExist(leader) then -- SHOULD be good, but if there is a bug, this code future-proofs it then. - local lowestInd = math.huge - for ind, unit in pairs(units) do - if Unit.isExist(unit) and ind < lowestInd then - lowestInd = ind - return unit:getPosition().p - end - end - end - if leader and Unit.isExist(leader) then -- maybe a little too paranoid now... - return leader:getPosition().p - end + local leader = units[1] + if leader then + if Unit.isExist(leader) then + return leader:getPoint() + elseif #units > 1 then + for i = 2, #units do + if Unit.isExist(units[i]) then + return units[i]:getPoint() + end + end + + end + end + end + log:error("Group passed to mist.getLeadPos might be dead: $1", group) end function mist.groupIsDead(groupName) -- copy more or less from on station - if Group.getByName(groupName) then - local gp = Group.getByName(groupName) + local gp = Group.getByName(groupName) + if gp then if #gp:getUnits() > 0 or gp:isExist() == true then return false end end return true end + + function mist.pointInZone(point, zone) + local ref = mist.utils.deepCopy(zone) + if type(zone) == 'string' then + ref = mist.DBs.zonesByName[zone] + end + if ref.verticies then + return mist.pointInPolygon(point, ref.verticies) + else + return mist.utils.get2DDist(point, ref.point) < ref.radius + end + end end @@ -8937,10 +9337,10 @@ do -- mist.Logger scope -- @usage -- log everything --myLogger:setLevel(3) function mist.Logger:setLevel(level) - if not level then - self.level = 2 - else + self.level = 2 + if level then if type(level) == 'string' then + level = string.lower(level) if level == 'none' or level == 'off' then self.level = 0 elseif level == 'error' then @@ -8952,8 +9352,6 @@ do -- mist.Logger scope end elseif type(level) == 'number' then self.level = level - else - self.level = 2 end end end @@ -9025,6 +9423,30 @@ do -- mist.Logger scope end end end + --- Logs a message, disregarding the log level and displays a message out text box. + -- @tparam string text the text with keywords to substitute. + -- @param ... variables to be used for substitution. + -- @usage myLogger:msg("Always logged!") + + function mist.Logger:echo(text, ...) + text = formatText(text, unpack(arg)) + if text:len() > 4000 then + local texts = splitText(text) + for i = 1, #texts do + if i == 1 then + env.info(self.tag .. '|' .. texts[i]) + else + env.info(texts[i]) + end + end + else + env.info(self.tag .. '|' .. text) + end + trigger.action.outText(text, 30) + end + + + --- Logs a warning. -- logs a message prefixed with this loggers tag to dcs.log as diff --git a/resources/plugins/base/plugin.json b/resources/plugins/base/plugin.json index eac5ca6f..ebb93361 100644 --- a/resources/plugins/base/plugin.json +++ b/resources/plugins/base/plugin.json @@ -5,7 +5,7 @@ "specificOptions": [], "scriptsWorkOrders": [ { - "file": "mist_4_5_107.lua", + "file": "mist_4_5_122.lua", "mnemonic": "mist" }, { diff --git a/resources/plugins/pretense/init_header.lua b/resources/plugins/pretense/init_header.lua index d7481f31..fce64047 100644 --- a/resources/plugins/pretense/init_header.lua +++ b/resources/plugins/pretense/init_header.lua @@ -487,10 +487,10 @@ presets = { }, airdef = { bunker = Preset:new({ - display = 'Bunker', + display = 'Excavator', cost = 1500, type = 'upgrade', - template = "bunker-1" + template = "excavator" }), comCenter = Preset:new({ display = 'Command Center', diff --git a/resources/plugins/pretense/pretense_compiled.lua b/resources/plugins/pretense/pretense_compiled.lua index 5dbece1a..ac48acce 100644 --- a/resources/plugins/pretense/pretense_compiled.lua +++ b/resources/plugins/pretense/pretense_compiled.lua @@ -709,8 +709,8 @@ do if #targets > 0 then for _,tgt in ipairs(targets) do if tgt.visible and tgt.object then - if tgt.object.getCoalition and tgt.object:getCoalition()~=frUnit:getCoalition() and - tgt.object.getCategory and tgt.object:getCategory() == 1 then + if tgt.object.isExist and tgt.object:isExist() and tgt.object.getCoalition and tgt.object:getCoalition()~=frUnit:getCoalition() and + Object.getCategory(tgt.object) == 1 then local dist = mist.utils.get3DDist(frUnit:getPoint(), tgt.object:getPoint()) if dist < 1000 then if not group.isstopped then @@ -886,9 +886,9 @@ do if #targets > 0 then for _,tgt in ipairs(targets) do if tgt.visible and tgt.object and tgt.object.isExist and tgt.object:isExist() then - if tgt.object.getCategory and tgt.object:getCategory() == Object.Category.UNIT and + if Object.getCategory(tgt.object) == Object.Category.UNIT and tgt.object.getCoalition and tgt.object:getCoalition()~=frUnit:getCoalition() and - tgt.object:getDesc().category == Unit.Category.GROUND_UNIT then + Unit.getCategory(tgt.object) == Unit.Category.GROUND_UNIT then local dist = mist.utils.get3DDist(frUnit:getPoint(), tgt.object:getPoint()) if dist < 2000 then @@ -6274,8 +6274,8 @@ do if not player then return end if event.id==world.event.S_EVENT_PLAYER_ENTER_UNIT then - if event.initiator and event.initiator:getCategory() == Object.Category.UNIT and - (event.initiator:getDesc().category == Unit.Category.AIRPLANE or event.initiator:getDesc().category == Unit.Category.HELICOPTER) then + if event.initiator and Object.getCategory(event.initiator) == Object.Category.UNIT and + (Unit.getCategory(event.initiator) == Unit.Category.AIRPLANE or Unit.getCategory(event.initiator) == Unit.Category.HELICOPTER) then local pname = event.initiator:getPlayerName() if pname then @@ -7558,29 +7558,9 @@ do TemplateDB.templates["tv-tower"] = { type="TV tower", category="Fortifications", shape="tele_bash", dataCategory=TemplateDB.type.static } - TemplateDB.templates["bunker-1"] = { type="Sandbox", category="Fortifications", dataCategory=TemplateDB.type.static } - TemplateDB.templates["command-center"] = { type=".Command Center", category="Fortifications", shape="ComCenter", dataCategory=TemplateDB.type.static } TemplateDB.templates["military-staff"] = { type="Military staff", category="Fortifications", shape="aviashtab", dataCategory=TemplateDB.type.static } - - TemplateDB.templates["ship-tanker-seawisegiant"] = { type="Seawise_Giant", category="Ships", dataCategory=TemplateDB.type.static } - - TemplateDB.templates["ship-supply-tilde"] = { type="Ship_Tilde_Supply", category="Ships", dataCategory=TemplateDB.type.static } - - TemplateDB.templates["ship-landingship-samuelchase"] = { type="USS_Samuel_Chase", category="Ships", dataCategory=TemplateDB.type.static } - - TemplateDB.templates["ship-landingship-ropucha"] = { type="BDK-775", category="Ships", dataCategory=TemplateDB.type.static } - - TemplateDB.templates["ship-tanker-elnya"] = { type="ELNYA", category="Ships", dataCategory=TemplateDB.type.static } - - TemplateDB.templates["ship-landingship-lstmk2"] = { type="LST_Mk2", category="Ships", dataCategory=TemplateDB.type.static } - - TemplateDB.templates["ship-bulker-yakushev"] = { type="Dry-cargo ship-1", category="Ships", dataCategory=TemplateDB.type.static } - - TemplateDB.templates["ship-cargo-ivanov"] = { type="Dry-cargo ship-2", category="Ships", dataCategory=TemplateDB.type.static } - - end -----------------[[ END OF TemplateDB.lua ]]----------------- @@ -12357,7 +12337,7 @@ do for _,m in pairs(self.activeMissions) do if m.players[player] then if m.state == Mission.states.active then - if weapon:getDesc().category == Weapon.Category.BOMB then + if Weapon.getCategory(weapon) == Weapon.Category.BOMB then timer.scheduleFunction(function (params, time) if not params.weapon:isExist() then return nil -- weapon despawned @@ -13209,7 +13189,7 @@ do local detected = u:getController():getDetectedTargets(Controller.Detection.RADAR) for _,d in ipairs(detected) do if d and d.object and d.object.isExist and d.object:isExist() and - d.object:getCategory() == Object.Category.UNIT and + Object.getCategory(d.object) == Object.Category.UNIT and d.object.getCoalition and d.object:getCoalition() == self.tgtSide then From 09362cade43232f77d37a8cd84e8a0a9e157d963 Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Sat, 9 Dec 2023 11:24:47 +0200 Subject: [PATCH 088/243] Removed a non-functional option from the settings. --- game/pretense/pretenseluagenerator.py | 4 ---- game/settings/settings.py | 6 ------ 2 files changed, 10 deletions(-) diff --git a/game/pretense/pretenseluagenerator.py b/game/pretense/pretenseluagenerator.py index 17ac01a4..e30f53d2 100644 --- a/game/pretense/pretenseluagenerator.py +++ b/game/pretense/pretenseluagenerator.py @@ -837,10 +837,6 @@ class PretenseLuaGenerator(LuaGenerator): + str(self.game.settings.pretense_maxdistfromfront_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" trigger = TriggerStart(comment="Pretense config") trigger.add_action(DoScript(String(lua_string_config))) diff --git a/game/settings/settings.py b/game/settings/settings.py index d7c5348b..8266d60d 100644 --- a/game/settings/settings.py +++ b/game/settings/settings.py @@ -998,12 +998,6 @@ class Settings: "which don't have an existing supply route defined in the campaign." ), ) - pretense_do_not_generate_sead_missions: bool = boolean_option( - "Do not generate player SEAD missions", - page=PRETENSE_PAGE, - section=GENERAL_SECTION, - default=False, - ) pretense_num_of_cargo_planes: int = bounded_int_option( "Number of cargo planes per side", page=PRETENSE_PAGE, From 59f6a3dcba12a109cf5df436016cc76038abfe8f Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Sat, 9 Dec 2023 12:24:06 +0200 Subject: [PATCH 089/243] Will now append the date and time in the Pretense savefile, mitigating risks of missions script errors when trying to load a savefile from a previously generated mission. --- game/pretense/pretenseluagenerator.py | 10 +++++++++- resources/plugins/pretense/init_header.lua | 3 +-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/game/pretense/pretenseluagenerator.py b/game/pretense/pretenseluagenerator.py index e30f53d2..63cd22dd 100644 --- a/game/pretense/pretenseluagenerator.py +++ b/game/pretense/pretenseluagenerator.py @@ -5,6 +5,7 @@ import os import random from abc import ABC, abstractmethod from dataclasses import dataclass +from datetime import datetime from pathlib import Path from typing import TYPE_CHECKING, Optional @@ -848,6 +849,12 @@ class PretenseLuaGenerator(LuaGenerator): trigger = TriggerStart(comment="Pretense init") + now = datetime.now() + date_time = now.strftime("%Y-%d-%mT%H_%M_%S") + lua_string_savefile = ( + f"local savefile = 'pretense_retribution_{date_time}.json'" + ) + init_header_file = open("./resources/plugins/pretense/init_header.lua", "r") init_header = init_header_file.read() @@ -1001,7 +1008,8 @@ class PretenseLuaGenerator(LuaGenerator): init_footer = init_footer_file.read() lua_string = ( - init_header + lua_string_savefile + + init_header + lua_string_zones + lua_string_connman + init_body_1 diff --git a/resources/plugins/pretense/init_header.lua b/resources/plugins/pretense/init_header.lua index fce64047..5854907d 100644 --- a/resources/plugins/pretense/init_header.lua +++ b/resources/plugins/pretense/init_header.lua @@ -1,7 +1,6 @@ -local savefile = 'pretense_1.1.json' -if lfs then +if lfs then local dir = lfs.writedir()..'Missions/Saves/' lfs.mkdir(dir) savefile = dir..savefile From c830a6e7c0c9371f9e79803424ead4a9b19f6b4e Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Mon, 22 Jan 2024 18:28:13 +0200 Subject: [PATCH 090/243] Rename resources/plugins/pretense/init_body_3.lua (from resources/plugins/pretense/init_body_2.lua) --- resources/plugins/pretense/{init_body_2.lua => init_body_3.lua} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename resources/plugins/pretense/{init_body_2.lua => init_body_3.lua} (100%) diff --git a/resources/plugins/pretense/init_body_2.lua b/resources/plugins/pretense/init_body_3.lua similarity index 100% rename from resources/plugins/pretense/init_body_2.lua rename to resources/plugins/pretense/init_body_3.lua From 2b63f453928a5ae0fe6c91e10f771c93590b54c0 Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Mon, 22 Jan 2024 18:28:31 +0200 Subject: [PATCH 091/243] Rename resources/plugins/pretense/init_body_2.lua (from resources/plugins/pretense/init_body_1.lua) --- resources/plugins/pretense/{init_body_1.lua => init_body_2.lua} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename resources/plugins/pretense/{init_body_1.lua => init_body_2.lua} (100%) diff --git a/resources/plugins/pretense/init_body_1.lua b/resources/plugins/pretense/init_body_2.lua similarity index 100% rename from resources/plugins/pretense/init_body_1.lua rename to resources/plugins/pretense/init_body_2.lua From 17b9d9c3d56285ace6c3c48f99636c9a949abbe3 Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Mon, 22 Jan 2024 18:29:18 +0200 Subject: [PATCH 092/243] Copied init_header.lua to init_body_1.lua --- resources/plugins/pretense/init_body_1.lua | 764 +++++++++++++++++++++ 1 file changed, 764 insertions(+) create mode 100644 resources/plugins/pretense/init_body_1.lua diff --git a/resources/plugins/pretense/init_body_1.lua b/resources/plugins/pretense/init_body_1.lua new file mode 100644 index 00000000..5854907d --- /dev/null +++ b/resources/plugins/pretense/init_body_1.lua @@ -0,0 +1,764 @@ + + +if lfs then + local dir = lfs.writedir()..'Missions/Saves/' + lfs.mkdir(dir) + savefile = dir..savefile + env.info('Pretense - Save file path: '..savefile) +end + + +do + TemplateDB.templates["infantry-red"] = { + units = { + "BTR_D", + "T-90", + "T-90", + "Infantry AK ver2", + "Infantry AK", + "Infantry AK", + "Paratrooper RPG-16", + "Infantry AK ver3", + "SA-18 Igla manpad" + }, + skill = "Excellent", + dataCategory= TemplateDB.type.group + } + + TemplateDB.templates["infantry-blue"] = { + units = { + "M1045 HMMWV TOW", + "Soldier stinger", + "Soldier M4 GRG", + "Soldier M4 GRG", + "Soldier M4 GRG", + "Soldier M4 GRG", + "Soldier M4 GRG", + "M1043 HMMWV Armament" + }, + skill = "Excellent", + dataCategory= TemplateDB.type.group + } + + TemplateDB.templates["defense-red"] = { + units = { + "Infantry AK ver2", + "Infantry AK", + "Infantry AK ver3", + "Paratrooper RPG-16", + "SA-18 Igla manpad" + }, + skill = "Excellent", + dataCategory= TemplateDB.type.group + } + + TemplateDB.templates["defense-blue"] = { + units = { + "Soldier M4 GRG", + "Soldier M4 GRG", + "Soldier M4 GRG", + "Soldier RPG", + "Soldier stinger", + }, + skill = "Excellent", + dataCategory= TemplateDB.type.group + } + + TemplateDB.templates["shorad-red"] = { + units = { + "Strela-10M3", + "Strela-10M3", + "Ural-4320T", + "2S6 Tunguska" + }, + maxDist = 300, + skill = "Excellent", + dataCategory= TemplateDB.type.group + } + + TemplateDB.templates["shorad-blue"] = { + units = { + "Roland ADS", + "M48 Chaparral", + "M 818", + "Gepard", + "Gepard" + }, + maxDist = 300, + skill = "Excellent", + dataCategory= TemplateDB.type.group + } + + TemplateDB.templates["sa2"] = { + units = { + "p-19 s-125 sr", + "Ural-4320T", + "Ural-4320T", + "S_75M_Volhov", + "S_75M_Volhov", + "S_75M_Volhov", + "S_75M_Volhov", + "S_75M_Volhov", + "Tor 9A331", + "SNR_75V" + }, + maxDist = 300, + skill = "Excellent", + dataCategory= TemplateDB.type.group + } + + TemplateDB.templates["hawk"] = { + units = { + "Hawk pcp", + "Hawk cwar", + "Hawk ln", + "Hawk ln", + "Hawk ln", + "Hawk ln", + "Hawk ln", + "Hawk tr", + "M 818", + "Hawk sr" + }, + maxDist = 300, + skill = "Good", + dataCategory= TemplateDB.type.group + } + + TemplateDB.templates["patriot"] = { + units = { + "Patriot cp", + "Patriot str", + "M 818", + "M 818", + "Patriot ln", + "Patriot ln", + "Patriot ln", + "Patriot ln", + "Patriot str", + "Patriot str", + "Patriot str", + "Patriot EPP", + "Patriot ECS", + "Patriot AMG" + }, + maxDist = 300, + skill = "Good", + dataCategory= TemplateDB.type.group + } + + TemplateDB.templates["sa3"] = { + units = { + "p-19 s-125 sr", + "snr s-125 tr", + "5p73 s-125 ln", + "5p73 s-125 ln", + "Ural-4320T", + "5p73 s-125 ln", + "5p73 s-125 ln" + }, + maxDist = 300, + skill = "Excellent", + dataCategory= TemplateDB.type.group + } + + TemplateDB.templates["sa6"] = { + units = { + "Kub 1S91 str", + "Kub 2P25 ln", + "Kub 2P25 ln", + "Kub 2P25 ln", + "Kub 2P25 ln", + "2S6 Tunguska", + "Ural-4320T", + "2S6 Tunguska", + "Kub 2P25 ln" + }, + maxDist = 300, + skill = "Excellent", + dataCategory= TemplateDB.type.group + } + + TemplateDB.templates["sa10"] = { + units = { + "S-300PS 54K6 cp", + "S-300PS 5P85C ln", + "S-300PS 5P85C ln", + "S-300PS 5P85C ln", + "GAZ-66", + "GAZ-66", + "GAZ-66", + "S-300PS 5P85C ln", + "S-300PS 5P85C ln", + "S-300PS 5P85C ln", + "S-300PS 40B6MD sr", + "S-300PS 40B6M tr", + "S-300PS 64H6E sr" + }, + maxDist = 300, + skill = "Excellent", + dataCategory= TemplateDB.type.group + } + + TemplateDB.templates["sa5"] = { + units = { + "RLS_19J6", + "Ural-4320T", + "Ural-4320T", + "RPC_5N62V", + "S-200_Launcher", + "S-200_Launcher", + "S-200_Launcher", + "S-200_Launcher", + "S-200_Launcher", + "S-200_Launcher" + }, + maxDist = 300, + skill = "Excellent", + dataCategory= TemplateDB.type.group + } + + TemplateDB.templates["sa11"] = { + units = { + "SA-11 Buk SR 9S18M1", + "SA-11 Buk LN 9A310M1", + "SA-11 Buk LN 9A310M1", + "SA-11 Buk LN 9A310M1", + "SA-11 Buk LN 9A310M1", + "SA-11 Buk LN 9A310M1", + "2S6 Tunguska", + "SA-11 Buk SR 9S18M1", + "GAZ-66", + "GAZ-66", + "SA-11 Buk CC 9S470M1" + }, + maxDist = 300, + skill = "Excellent", + dataCategory= TemplateDB.type.group + } + + TemplateDB.templates["nasams"] = { + units = { + "NASAMS_Command_Post", + "NASAMS_Radar_MPQ64F1", + "Vulcan", + "M 818", + "M 818", + "Roland ADS", + "Roland ADS", + "NASAMS_LN_C", + "NASAMS_LN_C", + "NASAMS_LN_C", + "NASAMS_LN_C", + "NASAMS_Radar_MPQ64F1", + "NASAMS_Radar_MPQ64F1", + "NASAMS_Radar_MPQ64F1" + }, + maxDist = 300, + skill = "Excellent", + dataCategory= TemplateDB.type.group + } + + TemplateDB.templates["blueShipGroup"] = { + units = { + "PERRY", + "USS_Arleigh_Burke_IIa", + "PERRY" + }, + maxDist = 300, + skill = "Excellent", + dataCategory= TemplateDB.type.group + } + + TemplateDB.templates["redShipGroup"] = { + units = { + "ALBATROS", + "NEUSTRASH", + "ALBATROS" + }, + maxDist = 300, + skill = "Excellent", + dataCategory= TemplateDB.type.group + } +end + +presets = { + upgrades = { + basic = { + tent = Preset:new({ + display = 'Tent', + cost = 1500, + type = 'upgrade', + template = "tent" + }), + comPost = Preset:new({ + display = 'Barracks', + cost = 1500, + type = 'upgrade', + template = "barracks" + }), + outpost = Preset:new({ + display = 'Outpost', + cost = 1500, + type = 'upgrade', + template = "outpost" + }) + }, + attack = { + ammoCache = Preset:new({ + display = 'Ammo Cache', + cost = 1500, + type = 'upgrade', + template = "ammo-cache" + }), + ammoDepot = Preset:new({ + display = 'Ammo Depot', + cost = 2000, + type = 'upgrade', + template = "ammo-depot" + }), + shipTankerSeawisegiant = Preset:new({ + display = 'Tanker Seawise Giant', + cost = 1500, + type = 'upgrade', + template = "ship-tanker-seawisegiant" + }), + shipLandingShipSamuelChase = Preset:new({ + display = 'LST USS Samuel Chase', + cost = 1500, + type = 'upgrade', + template = "ship-landingship-samuelchase" + }), + shipLandingShipRopucha = Preset:new({ + display = 'LS Ropucha', + cost = 1500, + type = 'upgrade', + template = "ship-landingship-ropucha" + }), + shipTankerElnya = Preset:new({ + display = 'Tanker Elnya', + cost = 1500, + type = 'upgrade', + template = "ship-tanker-elnya" + }) + }, + supply = { + fuelCache = Preset:new({ + display = 'Fuel Cache', + cost = 1500, + type = 'upgrade', + template = "fuel-cache" + }), + fuelTank = Preset:new({ + display = 'Fuel Tank', + cost = 1500, + type = 'upgrade', + template = "fuel-tank-big" + }), + fuelTankFarp = Preset:new({ + display = 'Fuel Tank', + cost = 1500, + type = 'upgrade', + template = "fuel-tank-small" + }), + factory1 = Preset:new({ + display='Factory', + cost = 2000, + type ='upgrade', + income = 20, + template = "factory-1" + }), + factory2 = Preset:new({ + display='Factory', + cost = 2000, + type ='upgrade', + income = 20, + template = "factory-2" + }), + factoryTank = Preset:new({ + display='Storage Tank', + cost = 1500, + type ='upgrade', + income = 10, + template = "chem-tank" + }), + ammoDepot = Preset:new({ + display = 'Ammo Depot', + cost = 2000, + type = 'upgrade', + income = 40, + template = "ammo-depot" + }), + oilPump = Preset:new({ + display = 'Oil Pump', + cost = 1500, + type = 'upgrade', + income = 20, + template = "oil-pump" + }), + hangar = Preset:new({ + display = 'Hangar', + cost = 2000, + type = 'upgrade', + income = 30, + template = "hangar" + }), + excavator = Preset:new({ + display = 'Excavator', + cost = 2000, + type = 'upgrade', + income = 20, + template = "excavator" + }), + farm1 = Preset:new({ + display = 'Farm House', + cost = 2000, + type = 'upgrade', + income = 40, + template = "farm-house-1" + }), + farm2 = Preset:new({ + display = 'Farm House', + cost = 2000, + type = 'upgrade', + income = 40, + template = "farm-house-2" + }), + refinery1 = Preset:new({ + display='Refinery', + cost = 2000, + type ='upgrade', + income = 100, + template = "factory-1" + }), + powerplant1 = Preset:new({ + display='Power Plant', + cost = 1500, + type ='upgrade', + income = 25, + template = "factory-1" + }), + powerplant2 = Preset:new({ + display='Power Plant', + cost = 1500, + type ='upgrade', + income = 25, + template = "factory-2" + }), + antenna = Preset:new({ + display='Antenna', + cost = 1000, + type ='upgrade', + income = 10, + template = "antenna" + }), + hq = Preset:new({ + display='HQ Building', + cost = 2000, + type ='upgrade', + income = 50, + template = "tv-tower" + }), + shipSupplyTilde = Preset:new({ + display = 'Ship_Tilde_Supply', + cost = 1500, + type = 'upgrade', + template = "ship-supply-tilde" + }), + shipLandingShipLstMk2 = Preset:new({ + display = 'LST Mk.II', + cost = 1500, + type = 'upgrade', + template = "ship-landingship-lstmk2" + }), + shipBulkerYakushev = Preset:new({ + display = 'Bulker Yakushev', + cost = 1500, + type = 'upgrade', + template = "ship-bulker-yakushev" + }), + shipCargoIvanov = Preset:new({ + display = 'Cargo Ivanov', + cost = 1500, + type = 'upgrade', + template = "ship-cargo-ivanov" + }) + }, + airdef = { + bunker = Preset:new({ + display = 'Excavator', + cost = 1500, + type = 'upgrade', + template = "excavator" + }), + comCenter = Preset:new({ + display = 'Command Center', + cost = 12500, + type = 'upgrade', + template = "command-center" + }) + } + }, + defenses = { + red = { + infantry = Preset:new({ + display = 'Infantry', + cost=2000, + type='defense', + template='infantry-red', + }), + shorad = Preset:new({ + display = 'SAM', + cost=2500, + type='defense', + template='shorad-red', + }), + sa2 = Preset:new({ + display = 'SAM', + cost=3000, + type='defense', + template='sa2', + }), + sa10 = Preset:new({ + display = 'SAM', + cost=30000, + type='defense', + template='sa10', + }), + sa5 = Preset:new({ + display = 'SAM', + cost=20000, + type='defense', + template='sa5', + }), + sa3 = Preset:new({ + display = 'SAM', + cost=4000, + type='defense', + template='sa3', + }), + sa6 = Preset:new({ + display = 'SAM', + cost=6000, + type='defense', + template='sa6', + }), + sa11 = Preset:new({ + display = 'SAM', + cost=10000, + type='defense', + template='sa11', + }), + hawk = Preset:new({ + display = 'SAM', + cost=6000, + type='defense', + template='hawk', + }), + patriot = Preset:new({ + display = 'SAM', + cost=30000, + type='defense', + template='patriot', + }), + nasams = Preset:new({ + display = 'SAM', + cost=3000, + type='defense', + template='nasams', + }), + redShipGroup = Preset:new({ + display = 'SAM', + cost=3000, + type='defense', + template='redShipGroup', + }) + }, + blue = { + infantry = Preset:new({ + display = 'Infantry', + cost=2000, + type='defense', + template='infantry-blue', + }), + shorad = Preset:new({ + display = 'SAM', + cost=2500, + type='defense', + template='shorad-blue', + }), + sa2 = Preset:new({ + display = 'SAM', + cost=3000, + type='defense', + template='sa2', + }), + sa10 = Preset:new({ + display = 'SAM', + cost=30000, + type='defense', + template='sa10', + }), + sa5 = Preset:new({ + display = 'SAM', + cost=20000, + type='defense', + template='sa5', + }), + sa3 = Preset:new({ + display = 'SAM', + cost=4000, + type='defense', + template='sa3', + }), + sa6 = Preset:new({ + display = 'SAM', + cost=6000, + type='defense', + template='sa6', + }), + sa11 = Preset:new({ + display = 'SAM', + cost=10000, + type='defense', + template='sa11', + }), + hawk = Preset:new({ + display = 'SAM', + cost=6000, + type='defense', + template='hawk', + }), + patriot = Preset:new({ + display = 'SAM', + cost=30000, + type='defense', + template='patriot', + }), + nasams = Preset:new({ + display = 'SAM', + cost=3000, + type='defense', + template='nasams', + }), + blueShipGroup = Preset:new({ + display = 'SAM', + cost=3000, + type='defense', + template='blueShipGroup', + }) + } + }, + missions = { + supply = { + convoy = Preset:new({ + display = 'Supply convoy', + cost = 4000, + type = 'mission', + missionType = ZoneCommand.missionTypes.supply_convoy + }), + convoy_escorted = Preset:new({ + display = 'Supply convoy', + cost = 3000, + type = 'mission', + missionType = ZoneCommand.missionTypes.supply_convoy + }), + helo = Preset:new({ + display = 'Supply helicopter', + cost = 2500, + type='mission', + missionType = ZoneCommand.missionTypes.supply_air + }), + transfer = Preset:new({ + display = 'Supply transfer', + cost = 1000, + type='mission', + missionType = ZoneCommand.missionTypes.supply_transfer + }) + }, + attack = { + surface = Preset:new({ + display = 'Ground assault', + cost = 100, + type = 'mission', + missionType = ZoneCommand.missionTypes.assault, + }), + cas = Preset:new({ + display = 'CAS', + cost = 200, + type='mission', + missionType = ZoneCommand.missionTypes.cas + }), + bai = Preset:new({ + display = 'BAI', + cost = 200, + type='mission', + missionType = ZoneCommand.missionTypes.bai + }), + strike = Preset:new({ + display = 'Strike', + cost = 300, + type='mission', + missionType = ZoneCommand.missionTypes.strike + }), + sead = Preset:new({ + display = 'SEAD', + cost = 200, + type='mission', + missionType = ZoneCommand.missionTypes.sead + }), + helo = Preset:new({ + display = 'CAS', + cost = 100, + type='mission', + missionType = ZoneCommand.missionTypes.cas_helo + }) + }, + patrol={ + aircraft = Preset:new({ + display= "Patrol", + cost = 100, + type='mission', + missionType = ZoneCommand.missionTypes.patrol + }) + }, + support ={ + awacs = Preset:new({ + display= "AWACS", + cost = 300, + type='mission', + bias='5', + missionType = ZoneCommand.missionTypes.awacs + }), + tanker = Preset:new({ + display= "Tanker", + cost = 200, + type='mission', + bias='2', + missionType = ZoneCommand.missionTypes.tanker + }) + } + }, + special = { + red = { + infantry = Preset:new({ + display = 'Infantry', + cost=-1, + type='defense', + template='defense-red', + }), + }, + blue = { + infantry = Preset:new({ + display = 'Infantry', + cost=-1, + type='defense', + template='defense-blue', + }) + } + } +} + +zones = {} +do + From 6c9115f517d66204c25c7ad350017eab15c9ec1d Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Mon, 22 Jan 2024 18:30:09 +0200 Subject: [PATCH 093/243] Separated init_header.lua into init_body_1.lua and init_header.lua. Deleted the ground unit groups since they will eventually be generated dynamically. --- resources/plugins/pretense/init_body_1.lua | 281 -------- resources/plugins/pretense/init_header.lua | 752 --------------------- 2 files changed, 1033 deletions(-) diff --git a/resources/plugins/pretense/init_body_1.lua b/resources/plugins/pretense/init_body_1.lua index 5854907d..0d528ddf 100644 --- a/resources/plugins/pretense/init_body_1.lua +++ b/resources/plugins/pretense/init_body_1.lua @@ -1,285 +1,4 @@ - -if lfs then - local dir = lfs.writedir()..'Missions/Saves/' - lfs.mkdir(dir) - savefile = dir..savefile - env.info('Pretense - Save file path: '..savefile) -end - - -do - TemplateDB.templates["infantry-red"] = { - units = { - "BTR_D", - "T-90", - "T-90", - "Infantry AK ver2", - "Infantry AK", - "Infantry AK", - "Paratrooper RPG-16", - "Infantry AK ver3", - "SA-18 Igla manpad" - }, - skill = "Excellent", - dataCategory= TemplateDB.type.group - } - - TemplateDB.templates["infantry-blue"] = { - units = { - "M1045 HMMWV TOW", - "Soldier stinger", - "Soldier M4 GRG", - "Soldier M4 GRG", - "Soldier M4 GRG", - "Soldier M4 GRG", - "Soldier M4 GRG", - "M1043 HMMWV Armament" - }, - skill = "Excellent", - dataCategory= TemplateDB.type.group - } - - TemplateDB.templates["defense-red"] = { - units = { - "Infantry AK ver2", - "Infantry AK", - "Infantry AK ver3", - "Paratrooper RPG-16", - "SA-18 Igla manpad" - }, - skill = "Excellent", - dataCategory= TemplateDB.type.group - } - - TemplateDB.templates["defense-blue"] = { - units = { - "Soldier M4 GRG", - "Soldier M4 GRG", - "Soldier M4 GRG", - "Soldier RPG", - "Soldier stinger", - }, - skill = "Excellent", - dataCategory= TemplateDB.type.group - } - - TemplateDB.templates["shorad-red"] = { - units = { - "Strela-10M3", - "Strela-10M3", - "Ural-4320T", - "2S6 Tunguska" - }, - maxDist = 300, - skill = "Excellent", - dataCategory= TemplateDB.type.group - } - - TemplateDB.templates["shorad-blue"] = { - units = { - "Roland ADS", - "M48 Chaparral", - "M 818", - "Gepard", - "Gepard" - }, - maxDist = 300, - skill = "Excellent", - dataCategory= TemplateDB.type.group - } - - TemplateDB.templates["sa2"] = { - units = { - "p-19 s-125 sr", - "Ural-4320T", - "Ural-4320T", - "S_75M_Volhov", - "S_75M_Volhov", - "S_75M_Volhov", - "S_75M_Volhov", - "S_75M_Volhov", - "Tor 9A331", - "SNR_75V" - }, - maxDist = 300, - skill = "Excellent", - dataCategory= TemplateDB.type.group - } - - TemplateDB.templates["hawk"] = { - units = { - "Hawk pcp", - "Hawk cwar", - "Hawk ln", - "Hawk ln", - "Hawk ln", - "Hawk ln", - "Hawk ln", - "Hawk tr", - "M 818", - "Hawk sr" - }, - maxDist = 300, - skill = "Good", - dataCategory= TemplateDB.type.group - } - - TemplateDB.templates["patriot"] = { - units = { - "Patriot cp", - "Patriot str", - "M 818", - "M 818", - "Patriot ln", - "Patriot ln", - "Patriot ln", - "Patriot ln", - "Patriot str", - "Patriot str", - "Patriot str", - "Patriot EPP", - "Patriot ECS", - "Patriot AMG" - }, - maxDist = 300, - skill = "Good", - dataCategory= TemplateDB.type.group - } - - TemplateDB.templates["sa3"] = { - units = { - "p-19 s-125 sr", - "snr s-125 tr", - "5p73 s-125 ln", - "5p73 s-125 ln", - "Ural-4320T", - "5p73 s-125 ln", - "5p73 s-125 ln" - }, - maxDist = 300, - skill = "Excellent", - dataCategory= TemplateDB.type.group - } - - TemplateDB.templates["sa6"] = { - units = { - "Kub 1S91 str", - "Kub 2P25 ln", - "Kub 2P25 ln", - "Kub 2P25 ln", - "Kub 2P25 ln", - "2S6 Tunguska", - "Ural-4320T", - "2S6 Tunguska", - "Kub 2P25 ln" - }, - maxDist = 300, - skill = "Excellent", - dataCategory= TemplateDB.type.group - } - - TemplateDB.templates["sa10"] = { - units = { - "S-300PS 54K6 cp", - "S-300PS 5P85C ln", - "S-300PS 5P85C ln", - "S-300PS 5P85C ln", - "GAZ-66", - "GAZ-66", - "GAZ-66", - "S-300PS 5P85C ln", - "S-300PS 5P85C ln", - "S-300PS 5P85C ln", - "S-300PS 40B6MD sr", - "S-300PS 40B6M tr", - "S-300PS 64H6E sr" - }, - maxDist = 300, - skill = "Excellent", - dataCategory= TemplateDB.type.group - } - - TemplateDB.templates["sa5"] = { - units = { - "RLS_19J6", - "Ural-4320T", - "Ural-4320T", - "RPC_5N62V", - "S-200_Launcher", - "S-200_Launcher", - "S-200_Launcher", - "S-200_Launcher", - "S-200_Launcher", - "S-200_Launcher" - }, - maxDist = 300, - skill = "Excellent", - dataCategory= TemplateDB.type.group - } - - TemplateDB.templates["sa11"] = { - units = { - "SA-11 Buk SR 9S18M1", - "SA-11 Buk LN 9A310M1", - "SA-11 Buk LN 9A310M1", - "SA-11 Buk LN 9A310M1", - "SA-11 Buk LN 9A310M1", - "SA-11 Buk LN 9A310M1", - "2S6 Tunguska", - "SA-11 Buk SR 9S18M1", - "GAZ-66", - "GAZ-66", - "SA-11 Buk CC 9S470M1" - }, - maxDist = 300, - skill = "Excellent", - dataCategory= TemplateDB.type.group - } - - TemplateDB.templates["nasams"] = { - units = { - "NASAMS_Command_Post", - "NASAMS_Radar_MPQ64F1", - "Vulcan", - "M 818", - "M 818", - "Roland ADS", - "Roland ADS", - "NASAMS_LN_C", - "NASAMS_LN_C", - "NASAMS_LN_C", - "NASAMS_LN_C", - "NASAMS_Radar_MPQ64F1", - "NASAMS_Radar_MPQ64F1", - "NASAMS_Radar_MPQ64F1" - }, - maxDist = 300, - skill = "Excellent", - dataCategory= TemplateDB.type.group - } - - TemplateDB.templates["blueShipGroup"] = { - units = { - "PERRY", - "USS_Arleigh_Burke_IIa", - "PERRY" - }, - maxDist = 300, - skill = "Excellent", - dataCategory= TemplateDB.type.group - } - - TemplateDB.templates["redShipGroup"] = { - units = { - "ALBATROS", - "NEUSTRASH", - "ALBATROS" - }, - maxDist = 300, - skill = "Excellent", - dataCategory= TemplateDB.type.group - } end presets = { diff --git a/resources/plugins/pretense/init_header.lua b/resources/plugins/pretense/init_header.lua index 5854907d..3b9cbbf5 100644 --- a/resources/plugins/pretense/init_header.lua +++ b/resources/plugins/pretense/init_header.lua @@ -8,757 +8,5 @@ if lfs then end -do - TemplateDB.templates["infantry-red"] = { - units = { - "BTR_D", - "T-90", - "T-90", - "Infantry AK ver2", - "Infantry AK", - "Infantry AK", - "Paratrooper RPG-16", - "Infantry AK ver3", - "SA-18 Igla manpad" - }, - skill = "Excellent", - dataCategory= TemplateDB.type.group - } - - TemplateDB.templates["infantry-blue"] = { - units = { - "M1045 HMMWV TOW", - "Soldier stinger", - "Soldier M4 GRG", - "Soldier M4 GRG", - "Soldier M4 GRG", - "Soldier M4 GRG", - "Soldier M4 GRG", - "M1043 HMMWV Armament" - }, - skill = "Excellent", - dataCategory= TemplateDB.type.group - } - - TemplateDB.templates["defense-red"] = { - units = { - "Infantry AK ver2", - "Infantry AK", - "Infantry AK ver3", - "Paratrooper RPG-16", - "SA-18 Igla manpad" - }, - skill = "Excellent", - dataCategory= TemplateDB.type.group - } - - TemplateDB.templates["defense-blue"] = { - units = { - "Soldier M4 GRG", - "Soldier M4 GRG", - "Soldier M4 GRG", - "Soldier RPG", - "Soldier stinger", - }, - skill = "Excellent", - dataCategory= TemplateDB.type.group - } - - TemplateDB.templates["shorad-red"] = { - units = { - "Strela-10M3", - "Strela-10M3", - "Ural-4320T", - "2S6 Tunguska" - }, - maxDist = 300, - skill = "Excellent", - dataCategory= TemplateDB.type.group - } - - TemplateDB.templates["shorad-blue"] = { - units = { - "Roland ADS", - "M48 Chaparral", - "M 818", - "Gepard", - "Gepard" - }, - maxDist = 300, - skill = "Excellent", - dataCategory= TemplateDB.type.group - } - - TemplateDB.templates["sa2"] = { - units = { - "p-19 s-125 sr", - "Ural-4320T", - "Ural-4320T", - "S_75M_Volhov", - "S_75M_Volhov", - "S_75M_Volhov", - "S_75M_Volhov", - "S_75M_Volhov", - "Tor 9A331", - "SNR_75V" - }, - maxDist = 300, - skill = "Excellent", - dataCategory= TemplateDB.type.group - } - - TemplateDB.templates["hawk"] = { - units = { - "Hawk pcp", - "Hawk cwar", - "Hawk ln", - "Hawk ln", - "Hawk ln", - "Hawk ln", - "Hawk ln", - "Hawk tr", - "M 818", - "Hawk sr" - }, - maxDist = 300, - skill = "Good", - dataCategory= TemplateDB.type.group - } - - TemplateDB.templates["patriot"] = { - units = { - "Patriot cp", - "Patriot str", - "M 818", - "M 818", - "Patriot ln", - "Patriot ln", - "Patriot ln", - "Patriot ln", - "Patriot str", - "Patriot str", - "Patriot str", - "Patriot EPP", - "Patriot ECS", - "Patriot AMG" - }, - maxDist = 300, - skill = "Good", - dataCategory= TemplateDB.type.group - } - - TemplateDB.templates["sa3"] = { - units = { - "p-19 s-125 sr", - "snr s-125 tr", - "5p73 s-125 ln", - "5p73 s-125 ln", - "Ural-4320T", - "5p73 s-125 ln", - "5p73 s-125 ln" - }, - maxDist = 300, - skill = "Excellent", - dataCategory= TemplateDB.type.group - } - - TemplateDB.templates["sa6"] = { - units = { - "Kub 1S91 str", - "Kub 2P25 ln", - "Kub 2P25 ln", - "Kub 2P25 ln", - "Kub 2P25 ln", - "2S6 Tunguska", - "Ural-4320T", - "2S6 Tunguska", - "Kub 2P25 ln" - }, - maxDist = 300, - skill = "Excellent", - dataCategory= TemplateDB.type.group - } - - TemplateDB.templates["sa10"] = { - units = { - "S-300PS 54K6 cp", - "S-300PS 5P85C ln", - "S-300PS 5P85C ln", - "S-300PS 5P85C ln", - "GAZ-66", - "GAZ-66", - "GAZ-66", - "S-300PS 5P85C ln", - "S-300PS 5P85C ln", - "S-300PS 5P85C ln", - "S-300PS 40B6MD sr", - "S-300PS 40B6M tr", - "S-300PS 64H6E sr" - }, - maxDist = 300, - skill = "Excellent", - dataCategory= TemplateDB.type.group - } - - TemplateDB.templates["sa5"] = { - units = { - "RLS_19J6", - "Ural-4320T", - "Ural-4320T", - "RPC_5N62V", - "S-200_Launcher", - "S-200_Launcher", - "S-200_Launcher", - "S-200_Launcher", - "S-200_Launcher", - "S-200_Launcher" - }, - maxDist = 300, - skill = "Excellent", - dataCategory= TemplateDB.type.group - } - - TemplateDB.templates["sa11"] = { - units = { - "SA-11 Buk SR 9S18M1", - "SA-11 Buk LN 9A310M1", - "SA-11 Buk LN 9A310M1", - "SA-11 Buk LN 9A310M1", - "SA-11 Buk LN 9A310M1", - "SA-11 Buk LN 9A310M1", - "2S6 Tunguska", - "SA-11 Buk SR 9S18M1", - "GAZ-66", - "GAZ-66", - "SA-11 Buk CC 9S470M1" - }, - maxDist = 300, - skill = "Excellent", - dataCategory= TemplateDB.type.group - } - - TemplateDB.templates["nasams"] = { - units = { - "NASAMS_Command_Post", - "NASAMS_Radar_MPQ64F1", - "Vulcan", - "M 818", - "M 818", - "Roland ADS", - "Roland ADS", - "NASAMS_LN_C", - "NASAMS_LN_C", - "NASAMS_LN_C", - "NASAMS_LN_C", - "NASAMS_Radar_MPQ64F1", - "NASAMS_Radar_MPQ64F1", - "NASAMS_Radar_MPQ64F1" - }, - maxDist = 300, - skill = "Excellent", - dataCategory= TemplateDB.type.group - } - - TemplateDB.templates["blueShipGroup"] = { - units = { - "PERRY", - "USS_Arleigh_Burke_IIa", - "PERRY" - }, - maxDist = 300, - skill = "Excellent", - dataCategory= TemplateDB.type.group - } - - TemplateDB.templates["redShipGroup"] = { - units = { - "ALBATROS", - "NEUSTRASH", - "ALBATROS" - }, - maxDist = 300, - skill = "Excellent", - dataCategory= TemplateDB.type.group - } -end - -presets = { - upgrades = { - basic = { - tent = Preset:new({ - display = 'Tent', - cost = 1500, - type = 'upgrade', - template = "tent" - }), - comPost = Preset:new({ - display = 'Barracks', - cost = 1500, - type = 'upgrade', - template = "barracks" - }), - outpost = Preset:new({ - display = 'Outpost', - cost = 1500, - type = 'upgrade', - template = "outpost" - }) - }, - attack = { - ammoCache = Preset:new({ - display = 'Ammo Cache', - cost = 1500, - type = 'upgrade', - template = "ammo-cache" - }), - ammoDepot = Preset:new({ - display = 'Ammo Depot', - cost = 2000, - type = 'upgrade', - template = "ammo-depot" - }), - shipTankerSeawisegiant = Preset:new({ - display = 'Tanker Seawise Giant', - cost = 1500, - type = 'upgrade', - template = "ship-tanker-seawisegiant" - }), - shipLandingShipSamuelChase = Preset:new({ - display = 'LST USS Samuel Chase', - cost = 1500, - type = 'upgrade', - template = "ship-landingship-samuelchase" - }), - shipLandingShipRopucha = Preset:new({ - display = 'LS Ropucha', - cost = 1500, - type = 'upgrade', - template = "ship-landingship-ropucha" - }), - shipTankerElnya = Preset:new({ - display = 'Tanker Elnya', - cost = 1500, - type = 'upgrade', - template = "ship-tanker-elnya" - }) - }, - supply = { - fuelCache = Preset:new({ - display = 'Fuel Cache', - cost = 1500, - type = 'upgrade', - template = "fuel-cache" - }), - fuelTank = Preset:new({ - display = 'Fuel Tank', - cost = 1500, - type = 'upgrade', - template = "fuel-tank-big" - }), - fuelTankFarp = Preset:new({ - display = 'Fuel Tank', - cost = 1500, - type = 'upgrade', - template = "fuel-tank-small" - }), - factory1 = Preset:new({ - display='Factory', - cost = 2000, - type ='upgrade', - income = 20, - template = "factory-1" - }), - factory2 = Preset:new({ - display='Factory', - cost = 2000, - type ='upgrade', - income = 20, - template = "factory-2" - }), - factoryTank = Preset:new({ - display='Storage Tank', - cost = 1500, - type ='upgrade', - income = 10, - template = "chem-tank" - }), - ammoDepot = Preset:new({ - display = 'Ammo Depot', - cost = 2000, - type = 'upgrade', - income = 40, - template = "ammo-depot" - }), - oilPump = Preset:new({ - display = 'Oil Pump', - cost = 1500, - type = 'upgrade', - income = 20, - template = "oil-pump" - }), - hangar = Preset:new({ - display = 'Hangar', - cost = 2000, - type = 'upgrade', - income = 30, - template = "hangar" - }), - excavator = Preset:new({ - display = 'Excavator', - cost = 2000, - type = 'upgrade', - income = 20, - template = "excavator" - }), - farm1 = Preset:new({ - display = 'Farm House', - cost = 2000, - type = 'upgrade', - income = 40, - template = "farm-house-1" - }), - farm2 = Preset:new({ - display = 'Farm House', - cost = 2000, - type = 'upgrade', - income = 40, - template = "farm-house-2" - }), - refinery1 = Preset:new({ - display='Refinery', - cost = 2000, - type ='upgrade', - income = 100, - template = "factory-1" - }), - powerplant1 = Preset:new({ - display='Power Plant', - cost = 1500, - type ='upgrade', - income = 25, - template = "factory-1" - }), - powerplant2 = Preset:new({ - display='Power Plant', - cost = 1500, - type ='upgrade', - income = 25, - template = "factory-2" - }), - antenna = Preset:new({ - display='Antenna', - cost = 1000, - type ='upgrade', - income = 10, - template = "antenna" - }), - hq = Preset:new({ - display='HQ Building', - cost = 2000, - type ='upgrade', - income = 50, - template = "tv-tower" - }), - shipSupplyTilde = Preset:new({ - display = 'Ship_Tilde_Supply', - cost = 1500, - type = 'upgrade', - template = "ship-supply-tilde" - }), - shipLandingShipLstMk2 = Preset:new({ - display = 'LST Mk.II', - cost = 1500, - type = 'upgrade', - template = "ship-landingship-lstmk2" - }), - shipBulkerYakushev = Preset:new({ - display = 'Bulker Yakushev', - cost = 1500, - type = 'upgrade', - template = "ship-bulker-yakushev" - }), - shipCargoIvanov = Preset:new({ - display = 'Cargo Ivanov', - cost = 1500, - type = 'upgrade', - template = "ship-cargo-ivanov" - }) - }, - airdef = { - bunker = Preset:new({ - display = 'Excavator', - cost = 1500, - type = 'upgrade', - template = "excavator" - }), - comCenter = Preset:new({ - display = 'Command Center', - cost = 12500, - type = 'upgrade', - template = "command-center" - }) - } - }, - defenses = { - red = { - infantry = Preset:new({ - display = 'Infantry', - cost=2000, - type='defense', - template='infantry-red', - }), - shorad = Preset:new({ - display = 'SAM', - cost=2500, - type='defense', - template='shorad-red', - }), - sa2 = Preset:new({ - display = 'SAM', - cost=3000, - type='defense', - template='sa2', - }), - sa10 = Preset:new({ - display = 'SAM', - cost=30000, - type='defense', - template='sa10', - }), - sa5 = Preset:new({ - display = 'SAM', - cost=20000, - type='defense', - template='sa5', - }), - sa3 = Preset:new({ - display = 'SAM', - cost=4000, - type='defense', - template='sa3', - }), - sa6 = Preset:new({ - display = 'SAM', - cost=6000, - type='defense', - template='sa6', - }), - sa11 = Preset:new({ - display = 'SAM', - cost=10000, - type='defense', - template='sa11', - }), - hawk = Preset:new({ - display = 'SAM', - cost=6000, - type='defense', - template='hawk', - }), - patriot = Preset:new({ - display = 'SAM', - cost=30000, - type='defense', - template='patriot', - }), - nasams = Preset:new({ - display = 'SAM', - cost=3000, - type='defense', - template='nasams', - }), - redShipGroup = Preset:new({ - display = 'SAM', - cost=3000, - type='defense', - template='redShipGroup', - }) - }, - blue = { - infantry = Preset:new({ - display = 'Infantry', - cost=2000, - type='defense', - template='infantry-blue', - }), - shorad = Preset:new({ - display = 'SAM', - cost=2500, - type='defense', - template='shorad-blue', - }), - sa2 = Preset:new({ - display = 'SAM', - cost=3000, - type='defense', - template='sa2', - }), - sa10 = Preset:new({ - display = 'SAM', - cost=30000, - type='defense', - template='sa10', - }), - sa5 = Preset:new({ - display = 'SAM', - cost=20000, - type='defense', - template='sa5', - }), - sa3 = Preset:new({ - display = 'SAM', - cost=4000, - type='defense', - template='sa3', - }), - sa6 = Preset:new({ - display = 'SAM', - cost=6000, - type='defense', - template='sa6', - }), - sa11 = Preset:new({ - display = 'SAM', - cost=10000, - type='defense', - template='sa11', - }), - hawk = Preset:new({ - display = 'SAM', - cost=6000, - type='defense', - template='hawk', - }), - patriot = Preset:new({ - display = 'SAM', - cost=30000, - type='defense', - template='patriot', - }), - nasams = Preset:new({ - display = 'SAM', - cost=3000, - type='defense', - template='nasams', - }), - blueShipGroup = Preset:new({ - display = 'SAM', - cost=3000, - type='defense', - template='blueShipGroup', - }) - } - }, - missions = { - supply = { - convoy = Preset:new({ - display = 'Supply convoy', - cost = 4000, - type = 'mission', - missionType = ZoneCommand.missionTypes.supply_convoy - }), - convoy_escorted = Preset:new({ - display = 'Supply convoy', - cost = 3000, - type = 'mission', - missionType = ZoneCommand.missionTypes.supply_convoy - }), - helo = Preset:new({ - display = 'Supply helicopter', - cost = 2500, - type='mission', - missionType = ZoneCommand.missionTypes.supply_air - }), - transfer = Preset:new({ - display = 'Supply transfer', - cost = 1000, - type='mission', - missionType = ZoneCommand.missionTypes.supply_transfer - }) - }, - attack = { - surface = Preset:new({ - display = 'Ground assault', - cost = 100, - type = 'mission', - missionType = ZoneCommand.missionTypes.assault, - }), - cas = Preset:new({ - display = 'CAS', - cost = 200, - type='mission', - missionType = ZoneCommand.missionTypes.cas - }), - bai = Preset:new({ - display = 'BAI', - cost = 200, - type='mission', - missionType = ZoneCommand.missionTypes.bai - }), - strike = Preset:new({ - display = 'Strike', - cost = 300, - type='mission', - missionType = ZoneCommand.missionTypes.strike - }), - sead = Preset:new({ - display = 'SEAD', - cost = 200, - type='mission', - missionType = ZoneCommand.missionTypes.sead - }), - helo = Preset:new({ - display = 'CAS', - cost = 100, - type='mission', - missionType = ZoneCommand.missionTypes.cas_helo - }) - }, - patrol={ - aircraft = Preset:new({ - display= "Patrol", - cost = 100, - type='mission', - missionType = ZoneCommand.missionTypes.patrol - }) - }, - support ={ - awacs = Preset:new({ - display= "AWACS", - cost = 300, - type='mission', - bias='5', - missionType = ZoneCommand.missionTypes.awacs - }), - tanker = Preset:new({ - display= "Tanker", - cost = 200, - type='mission', - bias='2', - missionType = ZoneCommand.missionTypes.tanker - }) - } - }, - special = { - red = { - infantry = Preset:new({ - display = 'Infantry', - cost=-1, - type='defense', - template='defense-red', - }), - }, - blue = { - infantry = Preset:new({ - display = 'Infantry', - cost=-1, - type='defense', - template='defense-blue', - }) - } - } -} - -zones = {} do From 2011a5f55508228b05e552bb0669cc5f6329134a Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Mon, 22 Jan 2024 18:31:54 +0200 Subject: [PATCH 094/243] Helicopter escort squadrons no longer cause an error on Pretense campaign generation. --- game/pretense/pretenseaircraftgenerator.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/game/pretense/pretenseaircraftgenerator.py b/game/pretense/pretenseaircraftgenerator.py index 6b0a316c..e9b4484f 100644 --- a/game/pretense/pretenseaircraftgenerator.py +++ b/game/pretense/pretenseaircraftgenerator.py @@ -193,9 +193,6 @@ class PretenseAircraftGenerator: squadron_def = coalition.air_wing.squadron_def_generator.generate_for_task( flight_type, cp ) - print( - f"Generating a squadron definition for fixed-wing {fixed_wing} squadron at {cp}" - ) for retries in range(num_retries): if squadron_def is None or fixed_wing == squadron_def.aircraft.helicopter: squadron_def = ( @@ -328,8 +325,10 @@ class PretenseAircraftGenerator: num_of_sead += 1 aircraft_per_flight = self.game.settings.pretense_ai_aircraft_per_flight elif ( - FlightType.CAS in mission_types - ) and num_of_cas < self.game.settings.pretense_cas_flights_per_cp: + (squadron.aircraft.helicopter and (FlightType.ESCORT in mission_types)) + or (FlightType.CAS in mission_types) + and num_of_cas < self.game.settings.pretense_cas_flights_per_cp + ): flight_type = FlightType.CAS num_of_cas += 1 aircraft_per_flight = self.game.settings.pretense_ai_aircraft_per_flight From db620a9056ffa4723487f716d406dad83079aae5 Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Mon, 22 Jan 2024 18:32:32 +0200 Subject: [PATCH 095/243] Now randomly shuffles the Pretense squadrons when generating a Pretense campaign. --- game/pretense/pretenseaircraftgenerator.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/game/pretense/pretenseaircraftgenerator.py b/game/pretense/pretenseaircraftgenerator.py index e9b4484f..a8c45dde 100644 --- a/game/pretense/pretenseaircraftgenerator.py +++ b/game/pretense/pretenseaircraftgenerator.py @@ -282,7 +282,9 @@ class PretenseAircraftGenerator: num_of_strike = 0 num_of_cap = 0 - for squadron in cp.squadrons: + random_squadron_list = list(cp.squadrons) + random.shuffle(random_squadron_list) + for squadron in random_squadron_list: # Intentionally don't spawn anything at OffMapSpawns in Pretense if isinstance(squadron.location, OffMapSpawn): continue From 226efe1151ef44c1ee9baa229431ccf855cd45b2 Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Mon, 22 Jan 2024 21:14:13 +0200 Subject: [PATCH 096/243] Pretense zone radius (radii) for FOBs with FARPs will now be dynamically adjusted. Increased the size of Pretense zones at Damascus, Khalkhalah and Krasnodar-Pashkovsky (which are quite spread out) so the zone would encompass the entire airfield. --- game/pretense/pretensetriggergenerator.py | 36 +++++++++++++++++++---- 1 file changed, 31 insertions(+), 5 deletions(-) diff --git a/game/pretense/pretensetriggergenerator.py b/game/pretense/pretensetriggergenerator.py index 740c17c6..0f33f68a 100644 --- a/game/pretense/pretensetriggergenerator.py +++ b/game/pretense/pretensetriggergenerator.py @@ -24,6 +24,8 @@ from dcs.condition import ( ) from dcs.mission import Mission from dcs.task import Option +from dcs.terrain.caucasus.airports import Krasnodar_Pashkovsky +from dcs.terrain.syria.airports import Damascus, Khalkhalah from dcs.translation import String from dcs.triggers import Event, TriggerCondition, TriggerOnce from dcs.unit import Skill @@ -53,6 +55,7 @@ TRIGGER_RADIUS_CLEAR_SCENERY = 1000 TRIGGER_RADIUS_PRETENSE_TGO = 500 TRIGGER_RADIUS_PRETENSE_SUPPLY = 500 TRIGGER_RADIUS_PRETENSE_HELI = 1000 +TRIGGER_RADIUS_PRETENSE_HELI_BUFFER = 500 TRIGGER_RADIUS_PRETENSE_CARRIER = 50000 TRIGGER_RUNWAY_LENGTH_PRETENSE = 2500 TRIGGER_RUNWAY_WIDTH_PRETENSE = 400 @@ -224,19 +227,42 @@ class PretenseTriggerGenerator: """ for cp in self.game.theater.controlpoints: if cp.is_fleet: - trigger_radius = TRIGGER_RADIUS_PRETENSE_CARRIER + trigger_radius = float(TRIGGER_RADIUS_PRETENSE_CARRIER) + elif isinstance(cp, Fob) and cp.has_helipads: + trigger_radius = TRIGGER_RADIUS_PRETENSE_HELI + for helipad in list( + cp.helipads + cp.helipads_quad + cp.helipads_invisible + ): + if cp.position.distance_to_point(helipad) > trigger_radius: + trigger_radius = cp.position.distance_to_point(helipad) + for ground_spawn, ground_spawn_wp in list( + cp.ground_spawns + cp.ground_spawns_roadbase + ): + if cp.position.distance_to_point(ground_spawn) > trigger_radius: + trigger_radius = cp.position.distance_to_point(ground_spawn) + trigger_radius += TRIGGER_RADIUS_PRETENSE_HELI_BUFFER else: - trigger_radius = TRIGGER_RADIUS_CAPTURE + if cp.dcs_airport is not None and ( + isinstance(cp.dcs_airport, Damascus) + or isinstance(cp.dcs_airport, Khalkhalah) + or isinstance(cp.dcs_airport, Krasnodar_Pashkovsky) + ): + trigger_radius = int(TRIGGER_RADIUS_CAPTURE * 1.8) + else: + trigger_radius = TRIGGER_RADIUS_CAPTURE + cp_name = "".join( + [i for i in cp.name if i.isalnum() or i.isspace() or i == "-"] + ) if not isinstance(cp, OffMapSpawn): zone_color = {1: 0.0, 2: 0.0, 3: 0.0, 4: 0.15} self.mission.triggers.add_triggerzone( cp.position, radius=trigger_radius, hidden=False, - name=cp.name, + name=cp_name, color=zone_color, ) - cp_name_trimmed = "".join([i for i in cp.name.lower() if i.isalnum()]) + cp_name_trimmed = "".join([i for i in cp.name.lower() if i.isalpha()]) tgo_num = 0 for tgo in cp.ground_objects: if cp.is_fleet or tgo.sea_object: @@ -285,7 +311,7 @@ class PretenseTriggerGenerator: if cp_airport is None: continue cp_name_trimmed = "".join( - [i for i in cp_airport.name.lower() if i.isalnum()] + [i for i in cp_airport.name.lower() if i.isalpha()] ) zone_color = {1: 0.0, 2: 1.0, 3: 0.5, 4: 0.15} if cp_airport is None: From 3831f4f309298a920fd8fa97b7aed41483d70da2 Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Mon, 22 Jan 2024 21:15:44 +0200 Subject: [PATCH 097/243] Added Iron Dome, David's Slinh, NASAMS-B and Rapier sites to Pretense. Fixed the timestamp in the save file name. --- game/pretense/pretenseluagenerator.py | 415 ++++++++++++++++++++- resources/plugins/pretense/init_body_1.lua | 92 +++-- 2 files changed, 476 insertions(+), 31 deletions(-) diff --git a/game/pretense/pretenseluagenerator.py b/game/pretense/pretenseluagenerator.py index 63cd22dd..971588a7 100644 --- a/game/pretense/pretenseluagenerator.py +++ b/game/pretense/pretenseluagenerator.py @@ -16,13 +16,17 @@ from dcs.triggers import TriggerStart from dcs.vehicles import AirDefence from game.ato import FlightType +from game.coalition import Coalition +from game.data.units import UnitClass 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.pretense.pretensetgogenerator import PretenseGroundObjectGenerator from game.theater import Airfield, OffMapSpawn, TheaterGroundObject from game.theater.iadsnetwork.iadsrole import IadsRole from game.utils import escape_string_for_lua +from pydcs_extensions import IRON_DOME_LN, DAVID_SLING_LN if TYPE_CHECKING: from game import Game @@ -276,7 +280,11 @@ class PretenseLuaGenerator(LuaGenerator): "sa11", "hawk", "patriot", - "nasams", + "nasamsb", + "nasamsc", + "rapier", + "irondome", + "davidsling", ]: sam_presets[sam_name] = PretenseSam(sam_name) @@ -364,11 +372,19 @@ class PretenseLuaGenerator(LuaGenerator): sam_presets["hawk"].enabled = True if ground_unit.unit_type.dcs_unit_type == AirDefence.Patriot_ln: sam_presets["patriot"].enabled = True + if ground_unit.unit_type.dcs_unit_type == AirDefence.NASAMS_LN_B: + sam_presets["nasamsb"].enabled = True + if ground_unit.unit_type.dcs_unit_type == AirDefence.NASAMS_LN_C: + sam_presets["nasamsc"].enabled = True if ( - ground_unit.unit_type.dcs_unit_type == AirDefence.NASAMS_LN_B - or ground_unit.unit_type.dcs_unit_type == AirDefence.NASAMS_LN_C + ground_unit.unit_type.dcs_unit_type + == AirDefence.Rapier_fsa_launcher ): - sam_presets["nasams"].enabled = True + sam_presets["rapier"].enabled = True + if ground_unit.unit_type.dcs_unit_type == IRON_DOME_LN: + sam_presets["irondome"].enabled = True + if ground_unit.unit_type.dcs_unit_type == DAVID_SLING_LN: + sam_presets["davidsling"].enabled = True cp_has_sams = False for sam_name in sam_presets: @@ -800,6 +816,365 @@ class PretenseLuaGenerator(LuaGenerator): return lua_string_zones + def get_ground_unit( + self, coalition: Coalition, side: int, desired_unit_classes: list[UnitClass] + ) -> str: + for unit_class in desired_unit_classes: + if coalition.faction.has_access_to_unit_class(unit_class): + dcs_unit_type = PretenseGroundObjectGenerator.ground_unit_of_class( + coalition=coalition, unit_class=unit_class + ) + if dcs_unit_type is not None: + return dcs_unit_type.dcs_id + + # Faction did not contain any of the desired unit classes. + # Fall back to defaults. + if desired_unit_classes[0] == UnitClass.TANK: + if side == PRETENSE_BLUE_SIDE: + return "M-1 Abrams" + else: + return "T-90" + elif desired_unit_classes[0] == UnitClass.ATGM: + if side == PRETENSE_BLUE_SIDE: + return "M1134 Stryker ATGM" + else: + return "BTR_D" + elif desired_unit_classes[0] == UnitClass.IFV: + if side == PRETENSE_BLUE_SIDE: + return "M1128 Stryker MGS" + else: + return "BMP-3" + elif desired_unit_classes[0] == UnitClass.APC: + if side == PRETENSE_BLUE_SIDE: + return "LAV-25" + else: + return "BTR-80" + elif desired_unit_classes[0] == UnitClass.RECON: + if side == PRETENSE_BLUE_SIDE: + return "M1043 HMMWV Armament" + else: + return "BRDM-2" + elif desired_unit_classes[0] == UnitClass.SHORAD: + if side == PRETENSE_BLUE_SIDE: + return "Roland ADS" + else: + return "2S6 Tunguska" + elif desired_unit_classes[0] == UnitClass.AAA: + if side == PRETENSE_BLUE_SIDE: + return "bofors40" + else: + return "KS-19" + elif desired_unit_classes[0] == UnitClass.MANPAD: + if side == PRETENSE_BLUE_SIDE: + return "Soldier stinger" + else: + return "SA-18 Igla manpad" + elif desired_unit_classes[0] == UnitClass.LOGISTICS: + if side == PRETENSE_BLUE_SIDE: + return "M 818" + else: + return "Ural-4320T" + else: + if side == PRETENSE_BLUE_SIDE: + return "Soldier M4" + else: + return "Infantry AK" + + def generate_pretense_ground_groups(self, side: int) -> str: + if side == PRETENSE_BLUE_SIDE: + side_str = "blue" + skill_str = self.game.settings.player_skill + coalition = self.game.blue + else: + side_str = "red" + skill_str = self.game.settings.enemy_vehicle_skill + coalition = self.game.red + + lua_string_ground_groups = "" + + lua_string_ground_groups += ( + 'TemplateDB.templates["infantry-' + side_str + '"] = {\n' + ) + lua_string_ground_groups += " units = {\n" + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.IFV, UnitClass.APC, UnitClass.RECON])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.TANK, UnitClass.ATGM, UnitClass.IFV, UnitClass.APC, UnitClass.RECON])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.TANK, UnitClass.ATGM, UnitClass.IFV, UnitClass.APC, UnitClass.RECON])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.INFANTRY])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.INFANTRY])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.INFANTRY])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.INFANTRY])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.INFANTRY])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.MANPAD, UnitClass.INFANTRY])}"\n' + lua_string_ground_groups += " },\n" + lua_string_ground_groups += f' skill = "{skill_str}",\n' + lua_string_ground_groups += " dataCategory = TemplateDB.type.group\n" + lua_string_ground_groups += "}\n" + + lua_string_ground_groups += ( + 'TemplateDB.templates["defense-' + side_str + '"] = {\n' + ) + lua_string_ground_groups += " units = {\n" + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.INFANTRY])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.INFANTRY])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.INFANTRY])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.INFANTRY])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.MANPAD, UnitClass.INFANTRY])}"\n' + lua_string_ground_groups += " },\n" + lua_string_ground_groups += f' skill = "{skill_str}",\n' + lua_string_ground_groups += " dataCategory = TemplateDB.type.group\n" + lua_string_ground_groups += "}\n" + + lua_string_ground_groups += ( + 'TemplateDB.templates["shorad-' + side_str + '"] = {\n' + ) + lua_string_ground_groups += " units = {\n" + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.SHORAD, UnitClass.AAA, UnitClass.MANPAD])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.SHORAD, UnitClass.AAA, UnitClass.MANPAD])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.LOGISTICS])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.AAA, UnitClass.SHORAD, UnitClass.MANPAD])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.AAA, UnitClass.SHORAD, UnitClass.MANPAD])}"\n' + lua_string_ground_groups += " },\n" + lua_string_ground_groups += " maxDist = 300,\n" + lua_string_ground_groups += f' skill = "{skill_str}",\n' + lua_string_ground_groups += " dataCategory = TemplateDB.type.group\n" + lua_string_ground_groups += "}\n" + + lua_string_ground_groups += 'TemplateDB.templates["sa2-' + side_str + '"] = {\n' + lua_string_ground_groups += " units = {\n" + lua_string_ground_groups += ' "p-19 s-125 sr",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.LOGISTICS])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.LOGISTICS])}",\n' + lua_string_ground_groups += ' "S_75M_Volhov",\n' + lua_string_ground_groups += ' "S_75M_Volhov",\n' + lua_string_ground_groups += ' "S_75M_Volhov",\n' + lua_string_ground_groups += ' "S_75M_Volhov",\n' + lua_string_ground_groups += ' "S_75M_Volhov",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.SHORAD, UnitClass.AAA, UnitClass.MANPAD])}",\n' + lua_string_ground_groups += ' "SNR_75V"\n' + lua_string_ground_groups += " },\n" + lua_string_ground_groups += " maxDist = 300,\n" + lua_string_ground_groups += f' skill = "{skill_str}",\n' + lua_string_ground_groups += " dataCategory = TemplateDB.type.group\n" + lua_string_ground_groups += "}\n" + + lua_string_ground_groups += ( + 'TemplateDB.templates["hawk-' + side_str + '"] = {\n' + ) + lua_string_ground_groups += " units = {\n" + lua_string_ground_groups += ' "Hawk pcp",\n' + lua_string_ground_groups += ' "Hawk cwar",\n' + lua_string_ground_groups += ' "Hawk ln",\n' + lua_string_ground_groups += ' "Hawk ln",\n' + lua_string_ground_groups += ' "Hawk ln",\n' + lua_string_ground_groups += ' "Hawk ln",\n' + lua_string_ground_groups += ' "Hawk ln",\n' + lua_string_ground_groups += ' "Hawk tr",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.LOGISTICS])}",\n' + lua_string_ground_groups += ' "Hawk sr"\n' + lua_string_ground_groups += " },\n" + lua_string_ground_groups += " maxDist = 300,\n" + lua_string_ground_groups += f' skill = "{skill_str}",\n' + lua_string_ground_groups += " dataCategory = TemplateDB.type.group\n" + lua_string_ground_groups += "}\n" + + lua_string_ground_groups += ( + 'TemplateDB.templates["patriot-' + side_str + '"] = {\n' + ) + lua_string_ground_groups += " units = {\n" + lua_string_ground_groups += ' "Patriot cp",\n' + lua_string_ground_groups += ' "Patriot str",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.LOGISTICS])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.LOGISTICS])}",\n' + lua_string_ground_groups += ' "Patriot ln",\n' + lua_string_ground_groups += ' "Patriot ln",\n' + lua_string_ground_groups += ' "Patriot ln",\n' + lua_string_ground_groups += ' "Patriot ln",\n' + lua_string_ground_groups += ' "Patriot str",\n' + lua_string_ground_groups += ' "Patriot str",\n' + lua_string_ground_groups += ' "Patriot str",\n' + lua_string_ground_groups += ' "Patriot EPP",\n' + lua_string_ground_groups += ' "Patriot ECS",\n' + lua_string_ground_groups += ' "Patriot AMG"\n' + lua_string_ground_groups += " },\n" + lua_string_ground_groups += " maxDist = 300,\n" + lua_string_ground_groups += f' skill = "{skill_str}",\n' + lua_string_ground_groups += " dataCategory = TemplateDB.type.group\n" + lua_string_ground_groups += "}\n" + + lua_string_ground_groups += 'TemplateDB.templates["sa3-' + side_str + '"] = {\n' + lua_string_ground_groups += " units = {\n" + lua_string_ground_groups += ' "p-19 s-125 sr",\n' + lua_string_ground_groups += ' "snr s-125 tr",\n' + lua_string_ground_groups += ' "5p73 s-125 ln",\n' + lua_string_ground_groups += ' "5p73 s-125 ln",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.LOGISTICS])}",\n' + lua_string_ground_groups += ' "5p73 s-125 ln",\n' + lua_string_ground_groups += ' "5p73 s-125 ln"\n' + lua_string_ground_groups += " },\n" + lua_string_ground_groups += " maxDist = 300,\n" + lua_string_ground_groups += f' skill = "{skill_str}",\n' + lua_string_ground_groups += " dataCategory = TemplateDB.type.group\n" + lua_string_ground_groups += "}\n" + + lua_string_ground_groups += 'TemplateDB.templates["sa6-' + side_str + '"] = {\n' + lua_string_ground_groups += " units = {\n" + lua_string_ground_groups += ' "Kub 1S91 str",\n' + lua_string_ground_groups += ' "Kub 2P25 ln",\n' + lua_string_ground_groups += ' "Kub 2P25 ln",\n' + lua_string_ground_groups += ' "Kub 2P25 ln",\n' + lua_string_ground_groups += ' "Kub 2P25 ln",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.SHORAD, UnitClass.AAA, UnitClass.MANPAD])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.LOGISTICS])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.SHORAD, UnitClass.AAA, UnitClass.MANPAD])}",\n' + lua_string_ground_groups += ' "Kub 2P25 ln"\n' + lua_string_ground_groups += " },\n" + lua_string_ground_groups += " maxDist = 300,\n" + lua_string_ground_groups += f' skill = "{skill_str}",\n' + lua_string_ground_groups += " dataCategory = TemplateDB.type.group\n" + lua_string_ground_groups += "}" + + lua_string_ground_groups += ( + 'TemplateDB.templates["sa10-' + side_str + '"] = {\n' + ) + lua_string_ground_groups += " units = {\n" + lua_string_ground_groups += ' "S-300PS 54K6 cp",\n' + lua_string_ground_groups += ' "S-300PS 5P85C ln",\n' + lua_string_ground_groups += ' "S-300PS 5P85C ln",\n' + lua_string_ground_groups += ' "S-300PS 5P85C ln",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.LOGISTICS])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.LOGISTICS])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.LOGISTICS])}",\n' + lua_string_ground_groups += ' "S-300PS 5P85C ln",\n' + lua_string_ground_groups += ' "S-300PS 5P85C ln",\n' + lua_string_ground_groups += ' "S-300PS 5P85C ln",\n' + lua_string_ground_groups += ' "S-300PS 40B6MD sr",\n' + lua_string_ground_groups += ' "S-300PS 40B6M tr",\n' + lua_string_ground_groups += ' "S-300PS 64H6E sr"\n' + lua_string_ground_groups += " },\n" + lua_string_ground_groups += " maxDist = 300,\n" + lua_string_ground_groups += f' skill = "{skill_str}",\n' + lua_string_ground_groups += " dataCategory = TemplateDB.type.group\n" + lua_string_ground_groups += "}\n" + + lua_string_ground_groups += 'TemplateDB.templates["sa5-' + side_str + '"] = {\n' + lua_string_ground_groups += " units = {\n" + lua_string_ground_groups += ' "RLS_19J6",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.LOGISTICS])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.LOGISTICS])}",\n' + lua_string_ground_groups += ' "RPC_5N62V",\n' + lua_string_ground_groups += ' "S-200_Launcher",\n' + lua_string_ground_groups += ' "S-200_Launcher",\n' + lua_string_ground_groups += ' "S-200_Launcher",\n' + lua_string_ground_groups += ' "S-200_Launcher",\n' + lua_string_ground_groups += ' "S-200_Launcher",\n' + lua_string_ground_groups += ' "S-200_Launcher"\n' + lua_string_ground_groups += " },\n" + lua_string_ground_groups += " maxDist = 300,\n" + lua_string_ground_groups += f' skill = "{skill_str}",\n' + lua_string_ground_groups += " dataCategory = TemplateDB.type.group\n" + lua_string_ground_groups += "}\n" + + lua_string_ground_groups += ( + 'TemplateDB.templates["sa11-' + side_str + '"] = {\n' + ) + lua_string_ground_groups += " units = {\n" + lua_string_ground_groups += ' "SA-11 Buk SR 9S18M1",\n' + lua_string_ground_groups += ' "SA-11 Buk LN 9A310M1",\n' + lua_string_ground_groups += ' "SA-11 Buk LN 9A310M1",\n' + lua_string_ground_groups += ' "SA-11 Buk LN 9A310M1",\n' + lua_string_ground_groups += ' "SA-11 Buk LN 9A310M1",\n' + lua_string_ground_groups += ' "SA-11 Buk LN 9A310M1",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.SHORAD, UnitClass.AAA, UnitClass.MANPAD])}",\n' + lua_string_ground_groups += ' "SA-11 Buk SR 9S18M1",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.LOGISTICS])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.LOGISTICS])}",\n' + lua_string_ground_groups += ' "SA-11 Buk CC 9S470M1"\n' + lua_string_ground_groups += " },\n" + lua_string_ground_groups += " maxDist = 300,\n" + lua_string_ground_groups += f' skill = "{skill_str}",\n' + lua_string_ground_groups += " dataCategory = TemplateDB.type.group\n" + lua_string_ground_groups += "}\n" + + lua_string_ground_groups += ( + 'TemplateDB.templates["nasamsb-' + side_str + '"] = {\n' + ) + lua_string_ground_groups += " units = {\n" + lua_string_ground_groups += ' "NASAMS_Command_Post",\n' + lua_string_ground_groups += ' "NASAMS_Radar_MPQ64F1",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.AAA, UnitClass.SHORAD, UnitClass.MANPAD])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.LOGISTICS])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.LOGISTICS])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.SHORAD, UnitClass.AAA, UnitClass.MANPAD])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.SHORAD, UnitClass.AAA, UnitClass.MANPAD])}",\n' + lua_string_ground_groups += ' "NASAMS_LN_B",\n' + lua_string_ground_groups += ' "NASAMS_LN_B",\n' + lua_string_ground_groups += ' "NASAMS_LN_B",\n' + lua_string_ground_groups += ' "NASAMS_LN_B",\n' + lua_string_ground_groups += ' "NASAMS_Radar_MPQ64F1",\n' + lua_string_ground_groups += ' "NASAMS_Radar_MPQ64F1",\n' + lua_string_ground_groups += ' "NASAMS_Radar_MPQ64F1"\n' + lua_string_ground_groups += " },\n" + lua_string_ground_groups += " maxDist = 300,\n" + lua_string_ground_groups += f' skill = "{skill_str}",\n' + lua_string_ground_groups += " dataCategory = TemplateDB.type.group\n" + lua_string_ground_groups += "}\n" + + lua_string_ground_groups += ( + 'TemplateDB.templates["nasamsc-' + side_str + '"] = {\n' + ) + lua_string_ground_groups += " units = {\n" + lua_string_ground_groups += ' "NASAMS_Command_Post",\n' + lua_string_ground_groups += ' "NASAMS_Radar_MPQ64F1",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.AAA, UnitClass.SHORAD, UnitClass.MANPAD])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.LOGISTICS])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.LOGISTICS])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.SHORAD, UnitClass.AAA, UnitClass.MANPAD])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.SHORAD, UnitClass.AAA, UnitClass.MANPAD])}",\n' + lua_string_ground_groups += ' "NASAMS_LN_C",\n' + lua_string_ground_groups += ' "NASAMS_LN_C",\n' + lua_string_ground_groups += ' "NASAMS_LN_C",\n' + lua_string_ground_groups += ' "NASAMS_LN_C",\n' + lua_string_ground_groups += ' "NASAMS_Radar_MPQ64F1",\n' + lua_string_ground_groups += ' "NASAMS_Radar_MPQ64F1",\n' + lua_string_ground_groups += ' "NASAMS_Radar_MPQ64F1"\n' + lua_string_ground_groups += " },\n" + lua_string_ground_groups += " maxDist = 300,\n" + lua_string_ground_groups += f' skill = "{skill_str}",\n' + lua_string_ground_groups += " dataCategory = TemplateDB.type.group\n" + lua_string_ground_groups += "}\n" + + lua_string_ground_groups += ( + 'TemplateDB.templates["rapier-' + side_str + '"] = {\n' + ) + lua_string_ground_groups += " units = {\n" + lua_string_ground_groups += ' "rapier_fsa_blindfire_radar",\n' + lua_string_ground_groups += ' "rapier_fsa_blindfire_radar",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.AAA, UnitClass.SHORAD, UnitClass.MANPAD])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.LOGISTICS])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.LOGISTICS])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.SHORAD, UnitClass.AAA, UnitClass.MANPAD])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.SHORAD, UnitClass.AAA, UnitClass.MANPAD])}",\n' + lua_string_ground_groups += ' "rapier_fsa_launcher",\n' + lua_string_ground_groups += ' "rapier_fsa_launcher",\n' + lua_string_ground_groups += ' "rapier_fsa_launcher",\n' + lua_string_ground_groups += ' "rapier_fsa_launcher",\n' + lua_string_ground_groups += ( + ' "rapier_fsa_optical_tracker_unit",\n' + ) + lua_string_ground_groups += ( + ' "rapier_fsa_optical_tracker_unit",\n' + ) + lua_string_ground_groups += ( + ' "rapier_fsa_optical_tracker_unit"\n' + ) + lua_string_ground_groups += " },\n" + lua_string_ground_groups += " maxDist = 300,\n" + lua_string_ground_groups += f' skill = "{skill_str}",\n' + lua_string_ground_groups += " dataCategory = TemplateDB.type.group\n" + lua_string_ground_groups += "}\n" + + return lua_string_ground_groups + @staticmethod def generate_pretense_zone_connection( connected_points: dict[str, list[str]], @@ -820,8 +1195,14 @@ class PretenseLuaGenerator(LuaGenerator): other_cp_name not in connected_points[cp_name] and cp_name not in connected_points[other_cp_name] ): + cp_name_conn = "".join( + [i for i in cp_name if i.isalnum() or i.isspace() or i == "-"] + ) + cp_name_conn_other = "".join( + [i for i in other_cp_name if i.isalnum() or i.isspace() or i == "-"] + ) lua_string_connman = ( - f" cm: addConnection('{cp_name}', '{other_cp_name}')\n" + f" cm: addConnection('{cp_name_conn}', '{cp_name_conn_other}')\n" ) connected_points[cp_name].append(other_cp_name) connected_points[other_cp_name].append(cp_name) @@ -850,7 +1231,7 @@ class PretenseLuaGenerator(LuaGenerator): trigger = TriggerStart(comment="Pretense init") now = datetime.now() - date_time = now.strftime("%Y-%d-%mT%H_%M_%S") + date_time = now.strftime("%Y-%m-%dT%H_%M_%S") lua_string_savefile = ( f"local savefile = 'pretense_retribution_{date_time}.json'" ) @@ -858,6 +1239,13 @@ class PretenseLuaGenerator(LuaGenerator): init_header_file = open("./resources/plugins/pretense/init_header.lua", "r") init_header = init_header_file.read() + lua_string_ground_groups_blue = self.generate_pretense_ground_groups( + PRETENSE_BLUE_SIDE + ) + lua_string_ground_groups_red = self.generate_pretense_ground_groups( + PRETENSE_RED_SIDE + ) + lua_string_zones = "" for cp in self.game.theater.controlpoints: @@ -865,6 +1253,9 @@ class PretenseLuaGenerator(LuaGenerator): continue cp_name_trimmed = "".join([i for i in cp.name.lower() if i.isalpha()]) + cp_name = "".join( + [i for i in cp.name if i.isalnum() or i.isspace() or i == "-"] + ) cp_side = 2 if cp.captured else 1 for side in range(1, 3): if cp_name_trimmed not in self.game.pretense_air[cp_side]: @@ -874,7 +1265,7 @@ class PretenseLuaGenerator(LuaGenerator): if cp_name_trimmed not in self.game.pretense_ground_assault[cp_side]: self.game.pretense_ground_assault[side][cp_name_trimmed] = list() lua_string_zones += ( - f"zones.{cp_name_trimmed} = ZoneCommand:new('{cp.name}')\n" + f"zones.{cp_name_trimmed} = ZoneCommand:new('{cp_name}')\n" ) lua_string_zones += ( f"zones.{cp_name_trimmed}.initialState = " @@ -1004,17 +1395,23 @@ class PretenseLuaGenerator(LuaGenerator): init_body_2_file = open("./resources/plugins/pretense/init_body_2.lua", "r") init_body_2 = init_body_2_file.read() + init_body_3_file = open("./resources/plugins/pretense/init_body_3.lua", "r") + init_body_3 = init_body_3_file.read() + init_footer_file = open("./resources/plugins/pretense/init_footer.lua", "r") init_footer = init_footer_file.read() lua_string = ( lua_string_savefile + init_header + + lua_string_ground_groups_blue + + lua_string_ground_groups_red + + init_body_1 + lua_string_zones + lua_string_connman - + init_body_1 - + lua_string_jtac + init_body_2 + + lua_string_jtac + + init_body_3 + lua_string_supply + init_footer ) diff --git a/resources/plugins/pretense/init_body_1.lua b/resources/plugins/pretense/init_body_1.lua index 0d528ddf..ad7ae685 100644 --- a/resources/plugins/pretense/init_body_1.lua +++ b/resources/plugins/pretense/init_body_1.lua @@ -227,7 +227,7 @@ presets = { template='infantry-red', }), shorad = Preset:new({ - display = 'SAM', + display = 'SHORAD', cost=2500, type='defense', template='shorad-red', @@ -236,55 +236,79 @@ presets = { display = 'SAM', cost=3000, type='defense', - template='sa2', + template='sa2-red', }), sa10 = Preset:new({ display = 'SAM', cost=30000, type='defense', - template='sa10', + template='sa10-red', }), sa5 = Preset:new({ display = 'SAM', cost=20000, type='defense', - template='sa5', + template='sa5-red', }), sa3 = Preset:new({ display = 'SAM', cost=4000, type='defense', - template='sa3', + template='sa3-red', }), sa6 = Preset:new({ display = 'SAM', cost=6000, type='defense', - template='sa6', + template='sa6-red', }), sa11 = Preset:new({ display = 'SAM', cost=10000, type='defense', - template='sa11', + template='sa11-red', }), hawk = Preset:new({ display = 'SAM', cost=6000, type='defense', - template='hawk', + template='hawk-red', }), patriot = Preset:new({ display = 'SAM', cost=30000, type='defense', - template='patriot', + template='patriot-red', }), - nasams = Preset:new({ + nasamsb = Preset:new({ display = 'SAM', cost=3000, type='defense', - template='nasams', + template='nasamsb-red', + }), + nasamsc = Preset:new({ + display = 'SAM', + cost=3000, + type='defense', + template='nasamsc-red', + }), + rapier = Preset:new({ + display = 'SAM', + cost=3000, + type='defense', + template='rapier-red', + }), + irondome = Preset:new({ + display = 'SAM', + cost=20000, + type='defense', + template='irondome-red', + }), + davidsling = Preset:new({ + display = 'SAM', + cost=30000, + type='defense', + template='davidsling-red', }), redShipGroup = Preset:new({ display = 'SAM', @@ -301,7 +325,7 @@ presets = { template='infantry-blue', }), shorad = Preset:new({ - display = 'SAM', + display = 'SHORAD', cost=2500, type='defense', template='shorad-blue', @@ -310,55 +334,79 @@ presets = { display = 'SAM', cost=3000, type='defense', - template='sa2', + template='sa2-blue', }), sa10 = Preset:new({ display = 'SAM', cost=30000, type='defense', - template='sa10', + template='sa10-blue', }), sa5 = Preset:new({ display = 'SAM', cost=20000, type='defense', - template='sa5', + template='sa5-blue', }), sa3 = Preset:new({ display = 'SAM', cost=4000, type='defense', - template='sa3', + template='sa3-blue', }), sa6 = Preset:new({ display = 'SAM', cost=6000, type='defense', - template='sa6', + template='sa6-blue', }), sa11 = Preset:new({ display = 'SAM', cost=10000, type='defense', - template='sa11', + template='sa11-blue', }), hawk = Preset:new({ display = 'SAM', cost=6000, type='defense', - template='hawk', + template='hawk-blue', }), patriot = Preset:new({ display = 'SAM', cost=30000, type='defense', - template='patriot', + template='patriot-blue', }), - nasams = Preset:new({ + nasamsb = Preset:new({ display = 'SAM', cost=3000, type='defense', - template='nasams', + template='nasamsb-blue', + }), + nasamsc = Preset:new({ + display = 'SAM', + cost=3000, + type='defense', + template='nasamsc-blue', + }), + rapier = Preset:new({ + display = 'SAM', + cost=3000, + type='defense', + template='rapier-blue', + }), + irondome = Preset:new({ + display = 'SAM', + cost=20000, + type='defense', + template='irondome-blue', + }), + davidsling = Preset:new({ + display = 'SAM', + cost=30000, + type='defense', + template='davidsling-blue', }), blueShipGroup = Preset:new({ display = 'SAM', From 1835743af0f222e5bbb24d11020f5e110088810c Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Tue, 26 Dec 2023 14:36:33 +0200 Subject: [PATCH 098/243] Other coalition TGO spawns are now generated with the correct faction. --- game/pretense/pretensetgogenerator.py | 28 ++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/game/pretense/pretensetgogenerator.py b/game/pretense/pretensetgogenerator.py index bab1f089..061957f8 100644 --- a/game/pretense/pretensetgogenerator.py +++ b/game/pretense/pretensetgogenerator.py @@ -122,6 +122,7 @@ class PretenseGroundObjectGenerator(GroundObjectGenerator): self.game = game self.m = mission self.unit_map = unit_map + self.coalition = ground_object.coalition @property def culled(self) -> bool: @@ -139,9 +140,9 @@ class PretenseGroundObjectGenerator(GroundObjectGenerator): unit_class: Class of unit to return. """ faction_units = ( - set(self.ground_object.coalition.faction.frontline_units) - | set(self.ground_object.coalition.faction.artillery_units) - | set(self.ground_object.coalition.faction.logistics_units) + set(self.coalition.faction.frontline_units) + | set(self.coalition.faction.artillery_units) + | set(self.coalition.faction.logistics_units) ) of_class = list({u for u in faction_units if u.unit_class is unit_class}) @@ -184,7 +185,7 @@ class PretenseGroundObjectGenerator(GroundObjectGenerator): max_num: Maximum number of units to generate per group. """ - if self.ground_object.coalition.faction.has_access_to_unit_class(unit_class): + if self.coalition.faction.has_access_to_unit_class(unit_class): unit_type = self.ground_unit_of_class(unit_class) if unit_type is not None and len(vehicle_units) < max_num: unit_id = self.game.next_unit_id() @@ -236,7 +237,7 @@ class PretenseGroundObjectGenerator(GroundObjectGenerator): max_num: Maximum number of units to generate per group. """ unit_type = None - faction = self.ground_object.coalition.faction + faction = self.coalition.faction is_player = True side = ( 2 @@ -286,7 +287,7 @@ class PretenseGroundObjectGenerator(GroundObjectGenerator): default_ifv_unit_chinese = groundunittype elif unit == vehicles.Armor.MTLB: default_amphibious_unit = groundunittype - if self.ground_object.coalition.faction.has_access_to_dcs_type(unit): + if self.coalition.faction.has_access_to_dcs_type(unit): if groundunittype.unit_class == unit_class: unit_type = groundunittype break @@ -345,15 +346,15 @@ class PretenseGroundObjectGenerator(GroundObjectGenerator): if self.culled: return cp_name_trimmed = "".join( - [i for i in self.ground_object.control_point.name.lower() if i.isalnum()] + [i for i in self.ground_object.control_point.name.lower() if i.isalpha()] ) country_name_trimmed = "".join( - [i for i in self.country.shortname.lower() if i.isalnum()] + [i for i in self.country.shortname.lower() if i.isalpha()] ) for group in self.ground_object.groups: vehicle_units: list[TheaterUnit] = [] - # Split the different unit types to be compliant to dcs limitation + for unit in group.units: if unit.is_static: # Add supply convoy @@ -445,7 +446,7 @@ class PretenseGroundObjectGenerator(GroundObjectGenerator): break cp_name_trimmed = "".join( - [i for i in control_point.name.lower() if i.isalnum()] + [i for i in control_point.name.lower() if i.isalpha()] ) is_player = True side = ( @@ -554,7 +555,7 @@ class PretenseGroundObjectGenerator(GroundObjectGenerator): break cp_name_trimmed = "".join( - [i for i in control_point.name.lower() if i.isalnum()] + [i for i in control_point.name.lower() if i.isalpha()] ) is_player = True side = ( @@ -565,7 +566,7 @@ class PretenseGroundObjectGenerator(GroundObjectGenerator): for unit in units: assert issubclass(unit.type, VehicleType) - faction = unit.ground_object.control_point.coalition.faction + faction = self.coalition.faction if vehicle_group is None: vehicle_group = self.m.vehicle_group( self.country, @@ -647,7 +648,7 @@ class PretenseTgoGenerator(TgoGenerator): def generate(self) -> None: for cp in self.game.theater.controlpoints: - cp_name_trimmed = "".join([i for i in cp.name.lower() if i.isalnum()]) + cp_name_trimmed = "".join([i for i in cp.name.lower() if i.isalpha()]) for side in range(1, 3): if cp_name_trimmed not in self.game.pretense_ground_supply[side]: self.game.pretense_ground_supply[side][cp_name_trimmed] = list() @@ -770,6 +771,7 @@ class PretenseTgoGenerator(TgoGenerator): else: continue + generator.coalition = other_coalition generator.generate() self.mission_data.runways = list(self.runways.values()) From 6e7844982d23b444ec2c639ca25d66fa28f693fc Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Tue, 26 Dec 2023 17:23:15 +0200 Subject: [PATCH 099/243] Ground unit presets are now generated from the coalition/faction definitions. --- game/pretense/pretensetgogenerator.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/game/pretense/pretensetgogenerator.py b/game/pretense/pretensetgogenerator.py index 061957f8..58a1cbed 100644 --- a/game/pretense/pretensetgogenerator.py +++ b/game/pretense/pretensetgogenerator.py @@ -17,6 +17,7 @@ from dcs.country import Country from dcs.unitgroup import StaticGroup, VehicleGroup from dcs.unittype import VehicleType +from game.coalition import Coalition from game.data.units import UnitClass from game.dcs.groundunittype import GroundUnitType from game.missiongenerator.groundforcepainter import ( @@ -128,7 +129,10 @@ class PretenseGroundObjectGenerator(GroundObjectGenerator): def culled(self) -> bool: return self.game.iads_considerate_culling(self.ground_object) - def ground_unit_of_class(self, unit_class: UnitClass) -> Optional[GroundUnitType]: + @staticmethod + def ground_unit_of_class( + coalition: Coalition, unit_class: UnitClass + ) -> Optional[GroundUnitType]: """ Returns a GroundUnitType of the specified class that belongs to the TheaterGroundObject faction. @@ -137,12 +141,13 @@ class PretenseGroundObjectGenerator(GroundObjectGenerator): are removed based on a pre-defined list. Args: + coalition: Coalition to return the unit for. unit_class: Class of unit to return. """ faction_units = ( - set(self.coalition.faction.frontline_units) - | set(self.coalition.faction.artillery_units) - | set(self.coalition.faction.logistics_units) + set(coalition.faction.frontline_units) + | set(coalition.faction.artillery_units) + | set(coalition.faction.logistics_units) ) of_class = list({u for u in faction_units if u.unit_class is unit_class}) @@ -186,7 +191,7 @@ class PretenseGroundObjectGenerator(GroundObjectGenerator): """ if self.coalition.faction.has_access_to_unit_class(unit_class): - unit_type = self.ground_unit_of_class(unit_class) + unit_type = self.ground_unit_of_class(self.coalition, unit_class) if unit_type is not None and len(vehicle_units) < max_num: unit_id = self.game.next_unit_id() unit_name = f"{cp_name}-{group_role}-{unit_id}" From 3d8eed7334380ddc71702684a54a8da7afbf4727 Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Tue, 26 Dec 2023 17:57:26 +0200 Subject: [PATCH 100/243] Updated the Pretense script to version 1.4.5 --- .../plugins/pretense/pretense_compiled.lua | 991 ++++++++++++------ 1 file changed, 683 insertions(+), 308 deletions(-) diff --git a/resources/plugins/pretense/pretense_compiled.lua b/resources/plugins/pretense/pretense_compiled.lua index ac48acce..a6b58409 100644 --- a/resources/plugins/pretense/pretense_compiled.lua +++ b/resources/plugins/pretense/pretense_compiled.lua @@ -23,6 +23,27 @@ Makes use of Mission scripting tools (Mist): = 5 then + env.info('GroupMonitor: processSurface ['..group.name..'] is stuck, trying to get unstuck by teleport') + group.unstuck_attempts = 0 + local frUnit = gr:getUnit(1) + local pos = frUnit:getPoint() + + mist.teleportToPoint({ + groupName = group.name, + action = 'teleport', + initTasks = false, + point = {x=pos.x+math.random(-25,25), y=pos.y, z = pos.z+math.random(-25,25)} + }) + + timer.scheduleFunction(function(params, time) + local group = params.group + local gr = Group.getByName(group.name) + local supplyPoint = trigger.misc.getZone(group.target.name..'-sp') + if not supplyPoint then + supplyPoint = trigger.misc.getZone(group.target.name) + end + + TaskExtensions.moveOnRoadToPoint(gr, {x=supplyPoint.point.x, y=supplyPoint.point.z}, true) + end, {gr = group}, timer.getTime()+2) + end + elseif group.unstuck_attempts and group.unstuck_attempts > 0 then + group.unstuck_attempts = 0 end - elseif group.product.missionType == 'assault' then + elseif group.product.missionType == 'assault' then local frUnit = gr:getUnit(1) if frUnit then - local controller = frUnit:getController() - local targets = controller:getDetectedTargets() + local skipDetection = false + if group.lastStarted and (timer.getAbsTime() - group.lastStarted) < (30) then + skipDetection = true + else + group.lastStarted = nil + end local shouldstop = false - if #targets > 0 then - for _,tgt in ipairs(targets) do - if tgt.visible and tgt.object then - if tgt.object.isExist and tgt.object:isExist() and tgt.object.getCoalition and tgt.object:getCoalition()~=frUnit:getCoalition() and - Object.getCategory(tgt.object) == 1 then - local dist = mist.utils.get3DDist(frUnit:getPoint(), tgt.object:getPoint()) - if dist < 1000 then - if not group.isstopped then - env.info('GroupMonitor: processSurface ['..group.name..'] stopping to engage targets') - --gr:getController():setCommand({id = 'StopRoute', params = { value = true}}) - TaskExtensions.stopAndDisperse(gr) - group.isstopped = true + if not skipDetection then + local controller = frUnit:getController() + local targets = controller:getDetectedTargets() + + if #targets > 0 then + for _,tgt in ipairs(targets) do + if tgt.visible and tgt.object then + if tgt.object.isExist and tgt.object:isExist() and tgt.object.getCoalition and tgt.object:getCoalition()~=frUnit:getCoalition() and + Object.getCategory(tgt.object) == 1 then + local dist = mist.utils.get3DDist(frUnit:getPoint(), tgt.object:getPoint()) + if dist < 700 then + if not group.isstopped then + env.info('GroupMonitor: processSurface ['..group.name..'] stopping to engage targets') + TaskExtensions.stopAndDisperse(gr) + group.isstopped = true + group.lastStopped = timer.getAbsTime() + end + shouldstop = true + break end - shouldstop = true - break end end end end end + if group.lastStopped then + if (timer.getAbsTime() - group.lastStopped) > (3*60) then + env.info('GroupMonitor: processSurface ['..group.name..'] override stop, waited too long') + shouldstop = false + group.lastStarted = timer.getAbsTime() + end + end + if not shouldstop and group.isstopped then env.info('GroupMonitor: processSurface ['..group.name..'] resuming mission') - --gr:getController():setCommand({id = 'StopRoute', params = { value = false}}) local tp = { x = group.target.zone.point.x, y = group.target.zone.point.z @@ -737,6 +814,48 @@ do TaskExtensions.moveOnRoadToPointAndAssault(gr, tp, group.target.built) group.isstopped = false + group.lastStopped = nil + end + + if not shouldstop and not group.isstopped then + if GroupMonitor.isStuck(group) then + env.info('GroupMonitor: processSurface ['..group.name..'] is stuck, trying to get unstuck') + local tp = { + x = group.target.zone.point.x, + y = group.target.zone.point.z + } + + TaskExtensions.moveOnRoadToPointAndAssault(gr, tp, group.target.built, true) + + group.unstuck_attempts = group.unstuck_attempts or 0 + group.unstuck_attempts = group.unstuck_attempts + 1 + + if group.unstuck_attempts >= 5 then + env.info('GroupMonitor: processSurface ['..group.name..'] is stuck, trying to get unstuck by teleport') + group.unstuck_attempts = 0 + local pos = frUnit:getPoint() + + mist.teleportToPoint({ + groupName = group.name, + action = 'teleport', + initTasks = false, + point = {x=pos.x+math.random(-25,25), y=pos.y, z = pos.z+math.random(-25,25)} + }) + + timer.scheduleFunction(function(params, time) + local group = params.group + local gr = Group.getByName(gr) + local tp = { + x = group.target.zone.point.x, + y = group.target.zone.point.z + } + + TaskExtensions.moveOnRoadToPointAndAssault(gr, tp, group.target.built, true) + end, {gr = group}, timer.getTime()+2) + end + elseif group.unstuck_attempts and group.unstuck_attempts > 0 then + group.unstuck_attempts = 0 + end end end end @@ -799,6 +918,31 @@ do end end end + + function GroupMonitor.isStuck(group) + local gr = Group.getByName(group.name) + if not gr then return false end + if gr:getSize() == 0 then return false end + + local un = gr:getUnit(1) + if un and un:isExist() and mist.vec.mag(un:getVelocity()) >= 0.01 and group.stuck_marker > 0 then + group.stuck_marker = 0 + env.info('GroupMonitor: isStuck ['..group.name..'] is moving, reseting stuck marker velocity='..mist.vec.mag(un:getVelocity())) + end + + if un and un:isExist() and mist.vec.mag(un:getVelocity()) < 0.01 then + group.stuck_marker = group.stuck_marker + 1 + env.info('GroupMonitor: isStuck ['..group.name..'] is not moving, increasing stuck marker to '..group.stuck_marker..' velocity='..mist.vec.mag(un:getVelocity())) + + if group.stuck_marker >= 3 then + group.stuck_marker = 0 + env.info('GroupMonitor: isStuck ['..group.name..'] is stuck') + return true + end + end + + return false + end function GroupMonitor:processAir(group)-- states: [takeoff, inair, landed] local gr = Group.getByName(group.name) @@ -888,7 +1032,7 @@ do if tgt.visible and tgt.object and tgt.object.isExist and tgt.object:isExist() then if Object.getCategory(tgt.object) == Object.Category.UNIT and tgt.object.getCoalition and tgt.object:getCoalition()~=frUnit:getCoalition() and - Unit.getCategory(tgt.object) == Unit.Category.GROUND_UNIT then + Unit.getCategoryEx(tgt.object) == Unit.Category.GROUND_UNIT then local dist = mist.utils.get3DDist(frUnit:getPoint(), tgt.object:getPoint()) if dist < 2000 then @@ -925,7 +1069,7 @@ do if supplyPoint then group.returning = true - local alt = self.connectionManager:getHeliAlt(group.target.name, group.home.name) + local alt = DependencyManager.get("ConnectionManager"):getHeliAlt(group.target.name, group.home.name) TaskExtensions.landAtPointFromAir(gr, {x=supplyPoint.point.x, y=supplyPoint.point.z}, alt) env.info('GroupMonitor: processAir ['..group.name..'] returning home') end @@ -961,7 +1105,8 @@ do obj.blockedRoads = {} setmetatable(obj, self) self.__index = self - + + DependencyManager.register("ConnectionManager", obj) return obj end @@ -1770,9 +1915,31 @@ do } } + local awacs = { - id = 'AWACS', - params = { + id = 'ComboTask', + params = { + tasks = { + { + id = "WrappedAction", + params = + { + action = + { + id = "EPLRS", + params = { + value = true, + groupId = group:getID(), + } + } + } + }, + { + id = 'AWACS', + params = { + } + } + } } } @@ -2035,7 +2202,7 @@ do }) end - function TaskExtensions.moveOnRoadToPointAndAssault(group, point, targets) + function TaskExtensions.moveOnRoadToPointAndAssault(group, point, targets, detour) if not group or not point then return end if not group:isExist() or group:getSize()==0 then return end local startPos = group:getUnit(1):getPoint() @@ -2047,33 +2214,72 @@ do id='Mission', params = { route = { - points = { - [1] = { - type= AI.Task.WaypointType.TURNING_POINT, - x = srx, - y = sry, - speed = 1000, - action = AI.Task.VehicleFormation.ON_ROAD - }, - [2] = { - type= AI.Task.WaypointType.TURNING_POINT, - x = erx, - y = ery, - speed = 1000, - action = AI.Task.VehicleFormation.ON_ROAD - }, - [3] = { - type= AI.Task.WaypointType.TURNING_POINT, - x = point.x, - y = point.y, - speed = 1000, - action = AI.Task.VehicleFormation.DIAMOND - } - } + points = {} } } } + if detour then + local detourPoint = {x = startPos.x, y = startPos.z} + + local direction = { + x = erx - startPos.x, + y = ery - startPos.y + } + + local magnitude = (direction.x^2 + direction.y^2) ^ 0.5 + if magnitude > 0.0 then + direction.x = direction.x / magnitude + direction.y = direction.y / magnitude + + local scale = math.random(250,500) + direction.x = direction.x * scale + direction.y = direction.y * scale + + detourPoint.x = detourPoint.x + direction.x + detourPoint.y = detourPoint.y + direction.y + else + detourPoint.x = detourPoint.x + math.random(-500,500) + detourPoint.y = detourPoint.y + math.random(-500,500) + end + + table.insert(mis.params.route.points, { + type= AI.Task.WaypointType.TURNING_POINT, + x = detourPoint.x, + y = detourPoint.y, + speed = 1000, + action = AI.Task.VehicleFormation.OFF_ROAD + }) + + srx, sry = land.getClosestPointOnRoads('roads', detourPoint.x, detourPoint.y) + end + + + table.insert(mis.params.route.points, { + type= AI.Task.WaypointType.TURNING_POINT, + x = srx, + y = sry, + speed = 1000, + action = AI.Task.VehicleFormation.ON_ROAD + }) + + table.insert(mis.params.route.points, { + type= AI.Task.WaypointType.TURNING_POINT, + x = erx, + y = ery, + speed = 1000, + action = AI.Task.VehicleFormation.ON_ROAD + }) + + table.insert(mis.params.route.points, { + type= AI.Task.WaypointType.TURNING_POINT, + x = point.x, + y = point.y, + speed = 1000, + action = AI.Task.VehicleFormation.DIAMOND + }) + + for i,v in pairs(targets) do if v.type == 'defense' then local group = Group.getByName(v.name) @@ -2093,10 +2299,11 @@ do end end end + group:getController():setTask(mis) end - function TaskExtensions.moveOnRoadToPoint(group, point) -- point = {x,y} + function TaskExtensions.moveOnRoadToPoint(group, point, detour) -- point = {x,y} if not group or not point then return end if not group:isExist() or group:getSize()==0 then return end local startPos = group:getUnit(1):getPoint() @@ -2109,31 +2316,70 @@ do params = { route = { points = { - [1] = { - type= AI.Task.WaypointType.TURNING_POINT, - x = srx, - y = sry, - speed = 1000, - action = AI.Task.VehicleFormation.ON_ROAD - }, - [2] = { - type= AI.Task.WaypointType.TURNING_POINT, - x = erx, - y = ery, - speed = 1000, - action = AI.Task.VehicleFormation.ON_ROAD - }, - [3] = { - type= AI.Task.WaypointType.TURNING_POINT, - x = point.x, - y = point.y, - speed = 1000, - action = AI.Task.VehicleFormation.OFF_ROAD - } } } } } + + if detour then + local detourPoint = {x = startPos.x, y = startPos.z} + + local direction = { + x = erx - startPos.x, + y = ery - startPos.y + } + + local magnitude = (direction.x^2 + direction.y^2) ^ 0.5 + if magnitude > 0.0 then + direction.x = direction.x / magnitude + direction.y = direction.y / magnitude + + local scale = math.random(250,1000) + direction.x = direction.x * scale + direction.y = direction.y * scale + + detourPoint.x = detourPoint.x + direction.x + detourPoint.y = detourPoint.y + direction.y + else + detourPoint.x = detourPoint.x + math.random(-500,500) + detourPoint.y = detourPoint.y + math.random(-500,500) + end + + table.insert(mis.params.route.points, { + type= AI.Task.WaypointType.TURNING_POINT, + x = detourPoint.x, + y = detourPoint.y, + speed = 1000, + action = AI.Task.VehicleFormation.OFF_ROAD + }) + + srx, sry = land.getClosestPointOnRoads('roads', detourPoint.x, detourPoint.y) + end + + table.insert(mis.params.route.points, { + type= AI.Task.WaypointType.TURNING_POINT, + x = srx, + y = sry, + speed = 1000, + action = AI.Task.VehicleFormation.ON_ROAD + }) + + table.insert(mis.params.route.points, { + type= AI.Task.WaypointType.TURNING_POINT, + x = erx, + y = ery, + speed = 1000, + action = AI.Task.VehicleFormation.ON_ROAD + }) + + table.insert(mis.params.route.points, { + type= AI.Task.WaypointType.TURNING_POINT, + x = point.x, + y = point.y, + speed = 1000, + action = AI.Task.VehicleFormation.OFF_ROAD + }) + group:getController():setTask(mis) end @@ -2332,7 +2578,7 @@ do return "INVALID SQUAD" end - function PlayerLogistics:new(misTracker, plyTracker, squadTracker, csarTracker) + function PlayerLogistics:new() local obj = {} obj.groupMenus = {} -- groupid = path obj.carriedCargo = {} -- groupid = source @@ -2340,10 +2586,6 @@ do obj.carriedPilots = {} --groupid = source obj.registeredSquadGroups = {} obj.lastLoaded = {} -- groupid = zonename - obj.missionTracker = misTracker - obj.playerTracker = plyTracker - obj.squadTracker = squadTracker - obj.csarTracker = csarTracker obj.hercTracker = { cargos = {}, @@ -2357,6 +2599,7 @@ do obj:start() + DependencyManager.register("PlayerLogistics", obj) return obj end @@ -2635,8 +2878,12 @@ do end if cargo.unit and cargo.unit:isExist() then - local squadName = PlayerLogistics.getInfantryName(cargo.squad.type) - trigger.action.outTextForUnit(cargo.unit:getID(), 'Cargo drop of '..cargo.unit:getPlayerName()..' with '..squadName..' crashed', 10) + if cargo.squad then + local squadName = PlayerLogistics.getInfantryName(cargo.squad.type) + trigger.action.outTextForUnit(cargo.unit:getID(), 'Cargo drop of '..cargo.unit:getPlayerName()..' with '..squadName..' crashed', 10) + elseif cargo.supply then + trigger.action.outTextForUnit(cargo.unit:getID(), 'Cargo drop of '..cargo.unit:getPlayerName()..' with '..cargo.supply..' supplies crashed', 10) + end end end end @@ -2678,7 +2925,7 @@ do end end else - local error = self.squadTracker:spawnInfantry(self.registeredSquadGroups[cargo.squad.type], pos) + local error = DependencyManager.get("SquadTracker"):spawnInfantry(self.registeredSquadGroups[cargo.squad.type], pos) if not error then env.info('PlayerLogistics - Hercules - '..cargo.squad.type..' deployed') @@ -2687,14 +2934,14 @@ do if cargo.unit and cargo.unit:isExist() and cargo.unit.getPlayerName then trigger.action.outTextForUnit(cargo.unit:getID(), squadName..' deployed', 10) local player = cargo.unit:getPlayerName() - local xp = RewardDefinitions.actions.squadDeploy + local xp = RewardDefinitions.actions.squadDeploy * DependencyManager.get("PlayerTracker"):getPlayerMultiplier(player) - self.playerTracker:addStat(player, math.floor(xp), PlayerTracker.statTypes.xp) + DependencyManager.get("PlayerTracker"):addStat(player, math.floor(xp), PlayerTracker.statTypes.xp) if zn then - self.missionTracker:tallyUnloadSquad(player, zn.name, cargo.squad.type) + DependencyManager.get("MissionTracker"):tallyUnloadSquad(player, zn.name, cargo.squad.type) else - self.missionTracker:tallyUnloadSquad(player, '', cargo.squad.type) + DependencyManager.get("MissionTracker"):tallyUnloadSquad(player, '', cargo.squad.type) end trigger.action.outTextForUnit(cargo.unit:getID(), '+'..math.floor(xp)..' XP', 10) end @@ -2793,8 +3040,10 @@ do xp = xp * 0.25 end - self.playerTracker:addStat(player, math.floor(xp), PlayerTracker.statTypes.xp) - self.missionTracker:tallySupplies(player, amount, zone.name) + xp = xp * DependencyManager.get("PlayerTracker"):getPlayerMultiplier(player) + + DependencyManager.get("PlayerTracker"):addStat(player, math.floor(xp), PlayerTracker.statTypes.xp) + DependencyManager.get("MissionTracker"):tallySupplies(player, amount, zone.name) trigger.action.outTextForUnit(unit:getID(), '+'..math.floor(xp)..' XP', 10) end end @@ -2908,7 +3157,7 @@ do if gr then local un = gr:getUnit(1) if un then - local data = self.csarTracker:getClosestPilot(un:getPoint()) + local data = DependencyManager.get("CSARTracker"):getClosestPilot(un:getPoint()) if not data then trigger.action.outTextForUnit(un:getID(), 'No pilots in need of extraction', 10) @@ -2944,14 +3193,14 @@ do if gr then local un = gr:getUnit(1) if un then - local data = self.csarTracker:getClosestPilot(un:getPoint()) + local data = DependencyManager.get("CSARTracker"):getClosestPilot(un:getPoint()) if not data or data.dist >= 5000 then trigger.action.outTextForUnit(un:getID(), 'No pilots nearby', 10) return end - self.csarTracker:markPilot(data) + DependencyManager.get("CSARTracker"):markPilot(data) trigger.action.outTextForUnit(un:getID(), 'Location of '..data.name..' marked with green smoke.', 10) end end @@ -2962,14 +3211,14 @@ do if gr then local un = gr:getUnit(1) if un then - local data = self.csarTracker:getClosestPilot(un:getPoint()) + local data = DependencyManager.get("CSARTracker"):getClosestPilot(un:getPoint()) if not data or data.dist >= 5000 then trigger.action.outTextForUnit(un:getID(), 'No pilots nearby', 10) return end - self.csarTracker:flarePilot(data) + DependencyManager.get("CSARTracker"):flarePilot(data) trigger.action.outTextForUnit(un:getID(), data.name..' has deployed a green flare', 10) end end @@ -3015,8 +3264,10 @@ do local xp = #pilots*RewardDefinitions.actions.pilotExtract - self.playerTracker:addStat(player, math.floor(xp), PlayerTracker.statTypes.xp) - self.missionTracker:tallyUnloadPilot(player, zn.name) + xp = xp * DependencyManager.get("PlayerTracker"):getPlayerMultiplier(player) + + DependencyManager.get("PlayerTracker"):addStat(player, math.floor(xp), PlayerTracker.statTypes.xp) + DependencyManager.get("MissionTracker"):tallyUnloadPilot(player, zn.name) trigger.action.outTextForUnit(un:getID(), '+'..math.floor(xp)..' XP', 10) end @@ -3044,7 +3295,7 @@ do if not un:isExist() then return end local gr = un:getGroup() - local data = self.csarTracker:getClosestPilot(un:getPoint()) + local data = DependencyManager.get("CSARTracker"):getClosestPilot(un:getPoint()) if not data or data.dist > 500 then trigger.action.outTextForUnit(un:getID(), 'There is no pilot nearby that needs extraction', 10) @@ -3063,8 +3314,8 @@ do if not self.carriedPilots[gr:getID()] then self.carriedPilots[gr:getID()] = {} end table.insert(self.carriedPilots[gr:getID()], data.name) local player = un:getPlayerName() - self.missionTracker:tallyLoadPilot(player, data) - self.csarTracker:removePilot(data.name) + DependencyManager.get("MissionTracker"):tallyLoadPilot(player, data) + DependencyManager.get("CSARTracker"):removePilot(data.name) local weight = self:getCarriedPersonWeight(gr:getName()) trigger.action.setUnitInternalCargo(un:getName(), weight) trigger.action.outTextForUnit(un:getID(), data.name..' onboard. ('..weight..' kg)', 10) @@ -3098,7 +3349,7 @@ do return end - local squad, distance = self.squadTracker:getClosestExtractableSquad(un:getPoint()) + local squad, distance = DependencyManager.get("SquadTracker"):getClosestExtractableSquad(un:getPoint()) if squad and distance < 50 then local squadgr = Group.getByName(squad.name) if squadgr and squadgr:isExist() then @@ -3119,8 +3370,8 @@ do trigger.action.outTextForUnit(un:getID(), loadedInfName..' onboard. ('..weight..' kg)', 10) local player = un:getPlayerName() - self.missionTracker:tallyLoadSquad(player, squad) - self.squadTracker:removeSquad(squad.name) + DependencyManager.get("MissionTracker"):tallyLoadSquad(player, squad) + DependencyManager.get("SquadTracker"):removeSquad(squad.name) squadgr:destroy() end @@ -3280,31 +3531,31 @@ do if un.getPlayerName then local player = un:getPlayerName() - local xp = RewardDefinitions.actions.squadExtract + local xp = RewardDefinitions.actions.squadExtract * DependencyManager.get("PlayerTracker"):getPlayerMultiplier(player) - self.playerTracker:addStat(player, math.floor(xp), PlayerTracker.statTypes.xp) - self.missionTracker:tallyUnloadSquad(player, zn.name, sq.type) + DependencyManager.get("PlayerTracker"):addStat(player, math.floor(xp), PlayerTracker.statTypes.xp) + DependencyManager.get("MissionTracker"):tallyUnloadSquad(player, zn.name, sq.type) trigger.action.outTextForUnit(un:getID(), '+'..math.floor(xp)..' XP', 10) end end elseif self.registeredSquadGroups[sq.type] then local pos = Utils.getPointOnSurface(un:getPoint()) - local error = self.squadTracker:spawnInfantry(self.registeredSquadGroups[sq.type], pos) + local error = DependencyManager.get("SquadTracker"):spawnInfantry(self.registeredSquadGroups[sq.type], pos) if not error then trigger.action.outTextForUnit(un:getID(), squadName..' deployed', 10) if un.getPlayerName then local player = un:getPlayerName() - local xp = RewardDefinitions.actions.squadDeploy + local xp = RewardDefinitions.actions.squadDeploy * DependencyManager.get("PlayerTracker"):getPlayerMultiplier(player) - self.playerTracker:addStat(player, math.floor(xp), PlayerTracker.statTypes.xp) + DependencyManager.get("PlayerTracker"):addStat(player, math.floor(xp), PlayerTracker.statTypes.xp) if zn then - self.missionTracker:tallyUnloadSquad(player, zn.name, sq.type) + DependencyManager.get("MissionTracker"):tallyUnloadSquad(player, zn.name, sq.type) else - self.missionTracker:tallyUnloadSquad(player, '', sq.type) + DependencyManager.get("MissionTracker"):tallyUnloadSquad(player, '', sq.type) end trigger.action.outTextForUnit(un:getID(), '+'..math.floor(xp)..' XP', 10) end @@ -3500,9 +3751,11 @@ do if un:getDesc().typeName == "Hercules" then local loadedInCrates = 0 local ammo = un:getAmmo() - for _,load in ipairs(ammo) do - if load.desc.typeName == 'weapons.bombs.Generic Crate [20000lb]' then - loadedInCrates = 9000 * load.count + if ammo then + for _,load in ipairs(ammo) do + if load.desc.typeName == 'weapons.bombs.Generic Crate [20000lb]' then + loadedInCrates = 9000 * load.count + end end end @@ -3616,6 +3869,7 @@ do obj:start() + DependencyManager.register("MarkerCommands", obj) return obj end @@ -3711,8 +3965,6 @@ do obj.reservedMissions = {} obj.isHeloSpawn = false obj.isPlaneSpawn = false - - obj.connectionManager = nil obj.zone = CustomZone:getByName(zonename) obj.products = {} @@ -3806,9 +4058,9 @@ do end end - function ZoneCommand.setNeighbours(conManager) + function ZoneCommand.setNeighbours() + local conManager = DependencyManager.get("ConnectionManager") for name,zone in pairs(ZoneCommand.allZones) do - zone.connectionManager = conManager local neighbours = conManager:getConnectionsOfZone(name) zone.neighbours = {} for _,zname in ipairs(neighbours) do @@ -4637,16 +4889,14 @@ do end end elseif product.missionType == ZoneCommand.missionTypes.bai then - if not ZoneCommand.groupMonitor then return false end if self.mode ~= ZoneCommand.modes.normal and not self.keepActive then return false end - for _,tgt in pairs(ZoneCommand.groupMonitor.groups) do + for _,tgt in pairs(DependencyManager.get("GroupMonitor").groups) do if self:isBaiMissionValid(product, tgt) then return true end end elseif product.missionType == ZoneCommand.missionTypes.awacs then - if not ZoneCommand.groupMonitor then return false end if self.mode ~= ZoneCommand.modes.normal and not self.keepActive then return false end for _,tgt in pairs(ZoneCommand.getAllZones()) do if self:isAwacsMissionValid(product, tgt) then @@ -4654,7 +4904,6 @@ do end end elseif product.missionType == ZoneCommand.missionTypes.tanker then - if not ZoneCommand.groupMonitor then return false end if self.mode ~= ZoneCommand.modes.normal and not self.keepActive then return false end if not self.distToFront or self.distToFront == 0 then return false end for _,tgt in pairs(ZoneCommand.getAllZones()) do @@ -4781,9 +5030,7 @@ do end if supplyPoint then mist.teleportToPoint(getDefaultPos(savedData, false)) - if ZoneCommand.groupMonitor then - ZoneCommand.groupMonitor:registerGroup(product, zone, self, savedData) - end + DependencyManager.get("GroupMonitor"):registerGroup(product, zone, self, savedData) product.lastMission = {zoneName = zone.name} timer.scheduleFunction(function(param) @@ -4802,9 +5049,7 @@ do end if supplyPoint then mist.teleportToPoint(getDefaultPos(savedData, false)) - if ZoneCommand.groupMonitor then - ZoneCommand.groupMonitor:registerGroup(product, zone, self, savedData) - end + DependencyManager.get("GroupMonitor"):registerGroup(product, zone, self, savedData) local tgtPoint = trigger.misc.getZone(zone.name) @@ -4822,9 +5067,7 @@ do local zone = ZoneCommand.getZoneByName(savedData.lastMission.zoneName) mist.teleportToPoint(getDefaultPos(savedData, true)) - if ZoneCommand.groupMonitor then - ZoneCommand.groupMonitor:registerGroup(product, zone, self, savedData) - end + DependencyManager.get("GroupMonitor"):registerGroup(product, zone, self, savedData) local supplyPoint = trigger.misc.getZone(zone.name..'-hsp') if not supplyPoint then @@ -4833,7 +5076,7 @@ do if supplyPoint then product.lastMission = {zoneName = zone.name} - local alt = self.connectionManager:getHeliAlt(self.name, zone.name) + local alt = DependencyManager.get("ConnectionManager"):getHeliAlt(self.name, zone.name) timer.scheduleFunction(function(param) local gr = Group.getByName(param.name) TaskExtensions.landAtPoint(gr, param.point, param.alt) @@ -4849,9 +5092,7 @@ do local zone = ZoneCommand.getZoneByName(savedData.lastMission.zoneName) mist.teleportToPoint(getDefaultPos(savedData, true)) - if ZoneCommand.groupMonitor then - ZoneCommand.groupMonitor:registerGroup(product, zone, self, savedData) - end + DependencyManager.get("GroupMonitor"):registerGroup(product, zone, self, savedData) local homePos = trigger.misc.getZone(savedData.homeName).point @@ -4873,9 +5114,7 @@ do mist.teleportToPoint(getDefaultPos(savedData, true)) - if ZoneCommand.groupMonitor then - ZoneCommand.groupMonitor:registerGroup(product, zone, self, savedData) - end + DependencyManager.get("GroupMonitor"):registerGroup(product, zone, self, savedData) local homePos = trigger.misc.getZone(savedData.homeName).point @@ -4892,9 +5131,7 @@ do local zone = ZoneCommand.getZoneByName(savedData.lastMission.zoneName) mist.teleportToPoint(getDefaultPos(savedData, true)) - if ZoneCommand.groupMonitor then - ZoneCommand.groupMonitor:registerGroup(product, zone, self, savedData) - end + DependencyManager.get("GroupMonitor"):registerGroup(product, zone, self, savedData) local homePos = trigger.misc.getZone(savedData.homeName).point @@ -4912,9 +5149,7 @@ do local zn1 = ZoneCommand.getZoneByName(savedData.lastMission.zone1name) mist.teleportToPoint(getDefaultPos(savedData, true)) - if ZoneCommand.groupMonitor then - ZoneCommand.groupMonitor:registerGroup(product, zn1, self, savedData) - end + DependencyManager.get("GroupMonitor"):registerGroup(product, zn1, self, savedData) local homePos = trigger.misc.getZone(savedData.homeName).point @@ -4933,7 +5168,7 @@ do function ZoneCommand:reActivateBaiMission(product, savedData) local targets = {} local hasTarget = false - for _,tgt in pairs(ZoneCommand.groupMonitor.groups) do + for _,tgt in pairs(DependencyManager.get("GroupMonitor").groups) do if self:isBaiMissionValid(product, tgt) then targets[tgt.product.name] = tgt.product hasTarget = true @@ -4944,9 +5179,7 @@ do if hasTarget then mist.teleportToPoint(getDefaultPos(savedData, true)) - if ZoneCommand.groupMonitor then - ZoneCommand.groupMonitor:registerGroup(product, nil, self, savedData) - end + DependencyManager.get("GroupMonitor"):registerGroup(product, nil, self, savedData) product.lastMission = { active = true } timer.scheduleFunction(function(param) @@ -4962,9 +5195,7 @@ do local homePos = trigger.misc.getZone(savedData.homeName).point mist.teleportToPoint(getDefaultPos(savedData, true)) - if ZoneCommand.groupMonitor then - ZoneCommand.groupMonitor:registerGroup(product, nil, self, savedData) - end + DependencyManager.get("GroupMonitor"):registerGroup(product, nil, self, savedData) timer.scheduleFunction(function(param) local gr = Group.getByName(param.prod.name) if gr then @@ -4987,9 +5218,7 @@ do local homePos = trigger.misc.getZone(savedData.homeName).point mist.teleportToPoint(getDefaultPos(savedData, true)) - if ZoneCommand.groupMonitor then - ZoneCommand.groupMonitor:registerGroup(product, zone, self, savedData) - end + DependencyManager.get("GroupMonitor"):registerGroup(product, zone, self, savedData) timer.scheduleFunction(function(param) local gr = Group.getByName(param.prod.name) @@ -5018,7 +5247,7 @@ do --{name = product.name, lastStateTime = timer.getAbsTime(), product = product, target = target} local targets = {} local hasTarget = false - for _,tgt in pairs(ZoneCommand.groupMonitor.groups) do + for _,tgt in pairs(DependencyManager.get("GroupMonitor").groups) do if self:isBaiMissionValid(product, tgt) then targets[tgt.product.name] = tgt.product hasTarget = true @@ -5035,9 +5264,7 @@ do env.info("ZoneCommand - activateBaiMission fallback to respawnGroup") end - if ZoneCommand.groupMonitor then - ZoneCommand.groupMonitor:registerGroup(product, nil, self) - end + DependencyManager.get("GroupMonitor"):registerGroup(product, nil, self) product.lastMission = { active = true } timer.scheduleFunction(function(param) @@ -5136,9 +5363,7 @@ do env.info("ZoneCommand - activateAirSupplyMission fallback to respawnGroup") end - if ZoneCommand.groupMonitor then - ZoneCommand.groupMonitor:registerGroup(product, v, self) - end + DependencyManager.get("GroupMonitor"):registerGroup(product, v, self) local supplyPoint = trigger.misc.getZone(v.name..'-hsp') if not supplyPoint then @@ -5148,7 +5373,7 @@ do if supplyPoint then product.lastMission = {zoneName = v.name} - local alt = self.connectionManager:getHeliAlt(self.name, v.name) + local alt = DependencyManager.get("ConnectionManager"):getHeliAlt(self.name, v.name) timer.scheduleFunction(function(param) local gr = Group.getByName(param.name) TaskExtensions.landAtPoint(gr, param.point, param.alt ) @@ -5189,12 +5414,8 @@ do end function ZoneCommand:isSupplyMissionValid(product, target) - if not self.connectionManager then - env.info("ZoneCommand - ERROR missing connection manager") - end - if product.missionType == ZoneCommand.missionTypes.supply_convoy then - if self.connectionManager:isRoadBlocked(self.name, target.name) then + if DependencyManager.get("ConnectionManager"):isRoadBlocked(self.name, target.name) then return false end end @@ -5266,9 +5487,7 @@ do env.info("ZoneCommand - activateSupplyConvoyMission fallback to respawnGroup") end - if ZoneCommand.groupMonitor then - ZoneCommand.groupMonitor:registerGroup(product, v, self) - end + DependencyManager.get("GroupMonitor"):registerGroup(product, v, self) product.lastMission = {zoneName = v.name} timer.scheduleFunction(function(param) @@ -5285,12 +5504,9 @@ do end function ZoneCommand:isAssaultMissionValid(product, target) - if not self.connectionManager then - env.info("ZoneCommand - ERROR missing connection manager") - end if product.missionType == ZoneCommand.missionTypes.assault then - if self.connectionManager:isRoadBlocked(self.name, target.name) then + if DependencyManager.get("ConnectionManager"):isRoadBlocked(self.name, target.name) then return false end end @@ -5333,9 +5549,7 @@ do env.info("ZoneCommand - activateAssaultMission fallback to respawnGroup") end - if ZoneCommand.groupMonitor then - ZoneCommand.groupMonitor:registerGroup(product, v, self) - end + DependencyManager.get("GroupMonitor"):registerGroup(product, v, self) local tgtPoint = trigger.misc.getZone(v.name) @@ -5382,9 +5596,8 @@ do env.info("ZoneCommand - activateAwacsMission fallback to respawnGroup") end - if ZoneCommand.groupMonitor then - ZoneCommand.groupMonitor:registerGroup(product, zn, self) - end + DependencyManager.get("GroupMonitor"):registerGroup(product, zn, self) + timer.scheduleFunction(function(param) local gr = Group.getByName(param.prod.name) if gr then @@ -5434,9 +5647,7 @@ do env.info("ZoneCommand - activateTankerMission fallback to respawnGroup") end - if ZoneCommand.groupMonitor then - ZoneCommand.groupMonitor:registerGroup(product, zn, self) - end + DependencyManager.get("GroupMonitor"):registerGroup(product, zn, self) timer.scheduleFunction(function(param) local gr = Group.getByName(param.prod.name) @@ -5494,9 +5705,7 @@ do env.info("ZoneCommand - activatePatrolMission fallback to respawnGroup") end - if ZoneCommand.groupMonitor then - ZoneCommand.groupMonitor:registerGroup(product, zn1, self) - end + DependencyManager.get("GroupMonitor"):registerGroup(product, zn1, self) if zn1 then product.lastMission = {zone1name = zn1.name} @@ -5601,7 +5810,7 @@ do for i,v in pairs(self.built) do if v.type == ZoneCommand.productTypes.upgrade and v.side == side then local st = StaticObject.getByName(v.name) - if st then + if st and st:isExist() then for _,a in ipairs(attributes) do if a == "Buildings" and ZoneCommand.staticRegistry[v.name] then -- dcs does not consider all statics buildings so we compensate if amount==nil then @@ -5617,13 +5826,15 @@ do local gr = Group.getByName(v.name) if gr then for _,unit in ipairs(gr:getUnits()) do - for _,a in ipairs(attributes) do - if unit:hasAttribute(a) then - if amount==nil then - return true - else - count = count + 1 - if count >= amount then return true end + if unit:isExist() then + for _,a in ipairs(attributes) do + if unit:hasAttribute(a) then + if amount==nil then + return true + else + count = count + 1 + if count >= amount then return true end + end end end end @@ -5692,9 +5903,7 @@ do env.info("ZoneCommand - activateSeadMission fallback to respawnGroup") end - if ZoneCommand.groupMonitor then - ZoneCommand.groupMonitor:registerGroup(product, target, self) - end + DependencyManager.get("GroupMonitor"):registerGroup(product, target, self) if target then product.lastMission = {zoneName = target.name} @@ -5750,9 +5959,7 @@ do env.info("ZoneCommand - activateStrikeMission fallback to respawnGroup") end - if ZoneCommand.groupMonitor then - ZoneCommand.groupMonitor:registerGroup(product, target, self) - end + DependencyManager.get("GroupMonitor"):registerGroup(product, target, self) if target then product.lastMission = {zoneName = target.name} @@ -5814,9 +6021,7 @@ do env.info("ZoneCommand - activateCasMission fallback to respawnGroup") end - if ZoneCommand.groupMonitor then - ZoneCommand.groupMonitor:registerGroup(product, target, self) - end + DependencyManager.get("GroupMonitor"):registerGroup(product, target, self) if target then product.lastMission = {zoneName = target.name} @@ -6230,16 +6435,17 @@ do [PlayerTracker.cmdShopTypes.bribe2] = 3, } - function PlayerTracker:new(markerCommands) + function PlayerTracker:new() local obj = {} - obj.markerCommands = markerCommands obj.stats = {} + obj.config = {} obj.tempStats = {} obj.groupMenus = {} obj.groupShopMenus = {} + obj.groupConfigMenus = {} obj.groupTgtMenus = {} obj.playerAircraft = {} - obj.playerWeaponStock = {} + obj.playerEarningMultiplier = {} if lfs then local dir = lfs.writedir()..'Missions/Saves/' @@ -6251,7 +6457,7 @@ do local save = Utils.loadTable(PlayerTracker.savefile) if save then obj.stats = save.stats or {} - obj.playerWeaponStock = save.playerWeaponStock or {} + obj.config = save.config or {} end setmetatable(obj, self) @@ -6259,6 +6465,7 @@ do obj:init() + DependencyManager.register("PlayerTracker", obj) return obj end @@ -6275,7 +6482,7 @@ do if event.id==world.event.S_EVENT_PLAYER_ENTER_UNIT then if event.initiator and Object.getCategory(event.initiator) == Object.Category.UNIT and - (Unit.getCategory(event.initiator) == Unit.Category.AIRPLANE or Unit.getCategory(event.initiator) == Unit.Category.HELICOPTER) then + (Unit.getCategoryEx(event.initiator) == Unit.Category.AIRPLANE or Unit.getCategoryEx(event.initiator) == Unit.Category.HELICOPTER) then local pname = event.initiator:getPlayerName() if pname then @@ -6298,6 +6505,14 @@ do self.context.tempStats[player] = nil -- reset playeraircraft self.context.playerAircraft[player] = nil + + self.context.playerEarningMultiplier[player] = { spawnTime = timer.getAbsTime(), unit = event.initiator, multiplier = 1.0, minutes = 0 } + + local config = self.context:getPlayerConfig(player) + if config.gci_warning_radius then + local gci = DependencyManager.get("GCI") + gci:registerPlayer(player, event.initiator, config.gci_warning_radius, config.gci_metric) + end end if event.id == world.event.S_EVENT_KILL then @@ -6311,6 +6526,8 @@ do local xpkey = PlayerTracker.statTypes.xp local award = PlayerTracker.getXP(target) + award = math.floor(award * self.context:getPlayerMultiplier(player)) + local instantxp = math.floor(award*0.25) local tempxp = award - instantxp @@ -6333,7 +6550,7 @@ do local key = PlayerTracker.statTypes.xp local xp = self.context.tempStats[player][key] if xp then - local isFree = event.initiator:getGroup():getName():find("(FREE)") + xp = xp * self.context:getPlayerMultiplier(player) trigger.action.outTextForUnit(un:getID(), 'Ejection. 30\% XP claimed', 5) self.context:addStat(player, math.floor(xp*0.3), PlayerTracker.statTypes.xp) trigger.action.outTextForUnit(un:getID(), '[XP] '..self.context.stats[player][key]..' (+'..math.floor(xp*0.3)..')', 5) @@ -6364,53 +6581,79 @@ do end if event.id==world.event.S_EVENT_LAND then - local un = event.initiator - local zn = ZoneCommand.getZoneOfUnit(event.initiator:getName()) - local aircraft = self.context.playerAircraft[player] - env.info('PlayerTracker - '..player..' landed in '..tostring(un:getID())..' '..un:getName()) - if aircraft and un and zn and zn.side == un:getCoalition() then - trigger.action.outTextForUnit(event.initiator:getID(), "Wait 10 seconds to validate landing...", 10) - timer.scheduleFunction(function(param, time) - local un = param.unit - if not un or not un:isExist() then return end - - local player = param.player - local isLanded = Utils.isLanded(un, true) - local zn = ZoneCommand.getZoneOfUnit(un:getName()) - - env.info('PlayerTracker - '..player..' checking if landed: '..tostring(isLanded)) - - if isLanded then - if self.context.tempStats[player] then - if zn and zn.side == un:getCoalition() then - self.context.stats[player] = self.context.stats[player] or {} - - trigger.action.outTextForUnit(un:getID(), 'Rewards claimed', 5) - for _,key in pairs(PlayerTracker.statTypes) do - local value = self.context.tempStats[player][key] - env.info("PlayerTracker.landing - "..player..' redeeming '..tostring(value)..' '..key) - if value then - self.context:commitTempStat(player, key) - trigger.action.outTextForUnit(un:getID(), key..' +'..value..'', 5) - end - end - end - end - - local aircraft = param.context.playerAircraft[player] - if aircraft and aircraft.unitID == un:getID() then - param.context.playerAircraft[player] = nil - end - end - end, {player = player, unit = event.initiator, context = self.context}, timer.getTime()+10) - end - + self.context:validateLanding(event.initiator, player) end end world.addEventHandler(ev) self:periodicSave() self:menuSetup() + + timer.scheduleFunction(function(params, time) + local players = params.context.playerEarningMultiplier + for i,v in pairs(players) do + local aircraft = params.context.playerAircraft[i] + if aircraft and v.unit.isExist and v.unit:isExist() and aircraft.unitID == v.unit:getID() then + if v.multiplier < 5.0 and v.unit and v.unit:isExist() and Utils.isInAir(v.unit) then + v.minutes = v.minutes + 1 + + local multi = 1.0 + if v.minutes > 10 and v.minutes <= 60 then + multi = 1.0 + ((v.minutes-10)*0.05) + elseif v.minutes > 60 then + multi = 1.0 + (50*0.05) + ((v.minutes - 60)*0.025) + end + + v.multiplier = math.min(multi, 5.0) + end + end + end + + return time+60 + end, {context = self}, timer.getTime()+60) + end + + function PlayerTracker:validateLanding(unit, player) + local un = unit + local zn = ZoneCommand.getZoneOfUnit(unit:getName()) + local aircraft = self.playerAircraft[player] + env.info('PlayerTracker - '..player..' landed in '..tostring(un:getID())..' '..un:getName()) + if aircraft and un and zn and zn.side == un:getCoalition() then + trigger.action.outTextForUnit(unit:getID(), "Wait 10 seconds to validate landing...", 10) + timer.scheduleFunction(function(param, time) + local un = param.unit + if not un or not un:isExist() then return end + + local player = param.player + local isLanded = Utils.isLanded(un, true) + local zn = ZoneCommand.getZoneOfUnit(un:getName()) + + env.info('PlayerTracker - '..player..' checking if landed: '..tostring(isLanded)) + + if isLanded then + if param.context.tempStats[player] then + if zn and zn.side == un:getCoalition() then + param.context.stats[player] = param.context.stats[player] or {} + + trigger.action.outTextForUnit(un:getID(), 'Rewards claimed', 5) + for _,key in pairs(PlayerTracker.statTypes) do + local value = param.context.tempStats[player][key] + env.info("PlayerTracker.landing - "..player..' redeeming '..tostring(value)..' '..key) + if value then + param.context:commitTempStat(player, key) + trigger.action.outTextForUnit(un:getID(), key..' +'..value..'', 5) + end + end + end + end + + local aircraft = param.context.playerAircraft[player] + if aircraft and aircraft.unitID == un:getID() then + param.context.playerAircraft[player] = nil + end + end + end, {player = player, unit = unit, context = self}, timer.getTime()+10) + end end function PlayerTracker:addTempStat(player, amount, stattype) @@ -6512,6 +6755,7 @@ do local menu = missionCommands.addSubMenuForGroup(groupid, 'Information') missionCommands.addCommandForGroup(groupid, 'Player', menu, Utils.log(context.showGroupStats), context, groupname) missionCommands.addCommandForGroup(groupid, 'Frequencies', menu, Utils.log(context.showFrequencies), context, groupname) + missionCommands.addCommandForGroup(groupid, 'Validate Landing', menu, Utils.log(context.validateLandingMenu), context, groupname) context.groupMenus[groupid] = menu end @@ -6564,6 +6808,21 @@ do context.groupShopMenus[groupid] = menu end + + if context.groupConfigMenus[groupid] then + missionCommands.removeItemForGroup(groupid, context.groupConfigMenus[groupid]) + context.groupConfigMenus[groupid] = nil + end + + if not context.groupConfigMenus[groupid] then + + local menu = missionCommands.addSubMenuForGroup(groupid, 'Config') + local missionWarningMenu = missionCommands.addSubMenuForGroup(groupid, 'No mission warning', menu) + missionCommands.addCommandForGroup(groupid, 'Activate', missionWarningMenu, Utils.log(context.setNoMissionWarning), context, groupname, true) + missionCommands.addCommandForGroup(groupid, 'Deactivate', missionWarningMenu, Utils.log(context.setNoMissionWarning), context, groupname, false) + + context.groupConfigMenus[groupid] = menu + end end end elseif (event.id == world.event.S_EVENT_PLAYER_LEAVE_UNIT or event.id == world.event.S_EVENT_DEAD) and event.initiator and event.initiator.getPlayerName then @@ -6584,7 +6843,7 @@ do end end, self) - self.markerCommands:addCommand('stats',function(event, _, state) + DependencyManager.get("MarkerCommands"):addCommand('stats',function(event, _, state) local unit = nil if event.initiator then unit = event.initiator @@ -6598,7 +6857,7 @@ do return true end, false, self) - self.markerCommands:addCommand('freqs',function(event, _, state) + DependencyManager.get("MarkerCommands"):addCommand('freqs',function(event, _, state) local unit = nil if event.initiator then unit = event.initiator @@ -6613,6 +6872,19 @@ do end, false, self) end + function PlayerTracker:setNoMissionWarning(groupname, active) + local gr = Group.getByName(groupname) + if gr and gr:getSize()>0 then + local un = gr:getUnit(1) + if un then + local player = un:getPlayerName() + if player then + self:setPlayerConfig(player, "noMissionWarning", active) + end + end + end + end + function PlayerTracker:buyCommand(groupname, itemType) local gr = Group.getByName(groupname) if gr and gr:getSize()>0 then @@ -6717,6 +6989,17 @@ do end end + function PlayerTracker:validateLandingMenu(groupname) + local gr = Group.getByName(groupname) + if gr then + for i,v in pairs(gr:getUnits()) do + if v.getPlayerName and v:getPlayerName() then + self:validateLanding(v, v:getPlayerName()) + end + end + end + end + function PlayerTracker:showGroupStats(groupname) local gr = Group.getByName(groupname) if gr then @@ -6742,6 +7025,11 @@ do end end + local multiplier = self:getPlayerMultiplier(player) + if multiplier then + message = message..'\nSurvival XP multiplier: '..string.format("%.2f", multiplier)..'x' + end + local cmd = stats[PlayerTracker.statTypes.cmd] if cmd then message = message ..'\n\nCMD: '..cmd @@ -6763,11 +7051,28 @@ do end end + function PlayerTracker:setPlayerConfig(player, setting, value) + local cfg = self:getPlayerConfig(player) + cfg[setting] = value + end + + function PlayerTracker:getPlayerConfig(player) + if not self.config[player] then + self.config[player] = { + noMissionWarning = false, + gci_warning_radius = nil, + gci_metric = nil + } + end + + return self.config[player] + end + function PlayerTracker:periodicSave() timer.scheduleFunction(function(param, time) local tosave = {} tosave.stats = param.stats - tosave.playerWeaponStock = param.playerWeaponStock + tosave.config = param.config --temp mission stat tracking tosave.zones = {} @@ -6829,6 +7134,14 @@ do end end + function PlayerTracker:getPlayerMultiplier(playername) + if self.playerEarningMultiplier[playername] then + return self.playerEarningMultiplier[playername].multiplier + end + + return 1.0 + end + function PlayerTracker:getRank(xp) local rank = nil local nextRank = nil @@ -7102,13 +7415,9 @@ PersistenceManager = {} do - function PersistenceManager:new(path, groupManager, squadTracker, csarTracker, playerLogistics) + function PersistenceManager:new(path) local obj = { path = path, - groupManager = groupManager, - squadTracker = squadTracker, - csarTracker = csarTracker, - playerLogistics = playerLogistics, data = nil } @@ -7252,7 +7561,7 @@ do local save = self.data if save.csarTracker then for i,v in pairs(save.csarTracker) do - self.csarTracker:restorePilot(v) + DependencyManager.get("CSARTracker"):restorePilot(v) end end end @@ -7261,17 +7570,30 @@ do local save = self.data if save.squadTracker then for i,v in pairs(save.squadTracker) do - local sdata = self.playerLogistics.registeredSquadGroups[v.type] + local sdata = DependencyManager.get("PlayerLogistics").registeredSquadGroups[v.type] if sdata then v.data = sdata - self.squadTracker:restoreInfantry(v) + DependencyManager.get("SquadTracker"):restoreInfantry(v) end end end end function PersistenceManager:canRestore() - return self.data ~= nil + if self.data == nil then return false end + + local redExist = false + local blueExist = false + for _,z in pairs(self.data.zones) do + if z.side == 1 and not redExist then redExist = true end + if z.side == 2 and not blueExist then blueExist = true end + + if redExist and blueExist then break end + end + + if not redExist or not blueExist then return false end + + return true end function PersistenceManager:load() @@ -7330,7 +7652,7 @@ do end tosave.activeGroups = {} - for i,v in pairs(self.groupManager.groups) do + for i,v in pairs(DependencyManager.get("GroupMonitor").groups) do tosave.activeGroups[i] = { productName = v.product.name, type = v.product.missionType @@ -7393,7 +7715,7 @@ do tosave.csarTracker = {} - for i,v in pairs(self.csarTracker.activePilots) do + for i,v in pairs(DependencyManager.get("CSARTracker").activePilots) do if v.pilot:isExist() and v.pilot:getSize()>0 and v.remainingTime>60 then tosave.csarTracker[i] = { name = v.name, @@ -7405,7 +7727,7 @@ do tosave.squadTracker = {} - for i,v in pairs(self.squadTracker.activeInfantrySquads) do + for i,v in pairs(DependencyManager.get("SquadTracker").activeInfantrySquads) do tosave.squadTracker[i] = { state = v.state, remainingStateTime = v.remainingStateTime, @@ -9039,9 +9361,9 @@ do end if self.param.mis.state == Mission.states.completed then - if self.state == Mission.states.new or - self.state == Mission.states.preping or - self.state == Mission.states.comencing then + if self.mission.state == Mission.states.new or + self.mission.state == Mission.states.preping or + self.mission.state == Mission.states.comencing then self.isFailed = true end @@ -9774,6 +10096,19 @@ do end end + function Mission:pushMessageToPlayer(player, msg, duration) + if not duration then + duration = 10 + end + + for name,un in pairs(self.players) do + if name == player and un and un:isExist() then + trigger.action.outTextForUnit(un:getID(), msg, duration) + break + end + end + end + function Mission:pushMessageToPlayers(msg, duration) if not duration then duration = 10 @@ -10111,7 +10446,7 @@ do function Mission:getDetailedDescription() local msg = '['..self.name..']' - if self.state == Mission.states.comencing or self.state == Mission.states.preping then + if self.state == Mission.states.comencing or self.state == Mission.states.preping or (not Config.restrictMissionAcceptance) then msg = msg..'\nJoin code ['..self.missionID..']' end @@ -10988,7 +11323,7 @@ Escort = Mission:new() do function Escort.canCreate() local currentTime = timer.getAbsTime() - for _,gr in pairs(ZoneCommand.groupMonitor.groups) do + for _,gr in pairs(DependencyManager.get("GroupMonitor").groups) do if gr.product.side == 2 and gr.product.type == 'mission' and (gr.product.missionType == 'supply_convoy' or gr.product.missionType == 'assault') then local z = gr.target if z.distToFront == 0 and z.side~= 2 then @@ -11014,7 +11349,7 @@ do local currentTime = timer.getAbsTime() local viableConvoys = {} - for _,gr in pairs(ZoneCommand.groupMonitor.groups) do + for _,gr in pairs(DependencyManager.get("GroupMonitor").groups) do if gr.product.side == 2 and gr.product.type == 'mission' and (gr.product.missionType == 'supply_convoy' or gr.product.missionType == 'assault') then local z = gr.target if z.distToFront == 0 and z.side ~= 2 then @@ -11796,7 +12131,7 @@ do [Mission.types.cas_easy] = 1, [Mission.types.cas_medium] = 1, [Mission.types.cas_hard] = 1, - [Mission.types.sead] = 1, + [Mission.types.sead] = 3, [Mission.types.supply_easy] = 1, [Mission.types.supply_hard] = 1, [Mission.types.strike_veryeasy] = 1, @@ -11814,7 +12149,7 @@ do [Mission.types.anti_runway] = 1, [Mission.types.csar] = 1, [Mission.types.extraction] = 1, - [Mission.types.deploy_squad] = 1, + [Mission.types.deploy_squad] = 3, } if Config.missions then @@ -11827,10 +12162,8 @@ do MissionTracker.missionBoardSize = 10 - function MissionTracker:new(playerTracker, markerCommands) + function MissionTracker:new() local obj = {} - obj.playerTracker = playerTracker - obj.markerCommands = markerCommands obj.groupMenus = {} obj.missionIDPool = {} obj.missionBoard = {} @@ -11839,7 +12172,7 @@ do setmetatable(obj, self) self.__index = self - obj.markerCommands:addCommand('list', function(event, _, state) + DependencyManager.get("MarkerCommands"):addCommand('list', function(event, _, state) if event.initiator then state:printMissionBoard(event.initiator:getID(), nil, event.initiator:getGroup():getName()) elseif world.getPlayer() then @@ -11849,7 +12182,7 @@ do return true end, nil, obj) - obj.markerCommands:addCommand('help', function(event, _, state) + DependencyManager.get("MarkerCommands"):addCommand('help', function(event, _, state) if event.initiator then state:printHelp(event.initiator:getID()) elseif world.getPlayer() then @@ -11859,7 +12192,7 @@ do return true end, nil, obj) - obj.markerCommands:addCommand('active', function(event, _, state) + DependencyManager.get("MarkerCommands"):addCommand('active', function(event, _, state) if event.initiator then state:printActiveMission(event.initiator:getID(), nil, event.initiator:getPlayerName()) elseif world.getPlayer() then @@ -11868,7 +12201,7 @@ do return true end, nil, obj) - obj.markerCommands:addCommand('accept',function(event, code, state) + DependencyManager.get("MarkerCommands"):addCommand('accept',function(event, code, state) local numcode = tonumber(code) if not numcode or numcode<1000 or numcode > 9999 then return false end @@ -11885,7 +12218,7 @@ do return state:activateMission(numcode, player, unit) end, true, obj) - obj.markerCommands:addCommand('join',function(event, code, state) + DependencyManager.get("MarkerCommands"):addCommand('join',function(event, code, state) local numcode = tonumber(code) if not numcode or numcode<1000 or numcode > 9999 then return false end @@ -11902,7 +12235,7 @@ do return state:joinMission(numcode, player, unit) end, true, obj) - obj.markerCommands:addCommand('leave',function(event, _, state) + DependencyManager.get("MarkerCommands"):addCommand('leave',function(event, _, state) local player = '' if event.initiator then player = event.initiator:getPlayerName() @@ -11915,6 +12248,8 @@ do obj:menuSetup() obj:start() + + DependencyManager.register("MissionTracker", obj) return obj end @@ -12096,6 +12431,30 @@ do end function MissionTracker:start() + timer.scheduleFunction(function(params, time) + for i,v in ipairs(coalition.getPlayers(2)) do + if v and v:isExist() and not Utils.isInAir(v) and v.getPlayerName and v:getPlayerName() then + local player = v:getPlayerName() + local cfg = DependencyManager.get("PlayerTracker"):getPlayerConfig(player) + if cfg.noMissionWarning == true then + local hasMis = false + for _,mis in pairs(params.context.activeMissions) do + if mis.players[player] then + hasMis = true + break + end + end + + if not hasMis then + trigger.action.outTextForUnit(v:getID(), "No mission selected", 9) + end + end + end + end + + return time+10 + end, {context = self}, timer.getTime()+10) + timer.scheduleFunction(function(param, time) for code,mis in pairs(param.missionBoard) do if timer.getAbsTime() - mis.lastStateTime > mis.expireTime then @@ -12207,20 +12566,22 @@ do for _,reward in ipairs(mis.rewards) do for p,_ in pairs(mis.players) do - if isInstant then - param.playerTracker:addStat(p, reward.amount, reward.type) - else - param.playerTracker:addTempStat(p, reward.amount, reward.type) + local finalAmount = reward.amount + if reward.type == PlayerTracker.statTypes.xp then + finalAmount = math.floor(finalAmount * DependencyManager.get("PlayerTracker"):getPlayerMultiplier(p)) end - end - if isInstant then - mis:pushMessageToPlayers('+'..reward.amount..' '..reward.type) + if isInstant then + DependencyManager.get("PlayerTracker"):addStat(p, finalAmount, reward.type) + mis:pushMessageToPlayer(p, '+'..reward.amount..' '..reward.type) + else + DependencyManager.get("PlayerTracker"):addTempStat(p, finalAmount, reward.type) + end end end for p,u in pairs(mis.players) do - param.playerTracker:addRankRewards(p,u, not isInstant) + DependencyManager.get("PlayerTracker"):addRankRewards(p,u, not isInstant) end mis:pushSoundToPlayers("success.ogg") @@ -12446,15 +12807,17 @@ do end function MissionTracker:activateMission(code, player, unit) - if not unit or not unit:isExist() or not Utils.isLanded(unit, true) then - if unit and unit:isExist() then trigger.action.outTextForUnit(unit:getID(), 'Can only accept mission while landed', 5) end - return false - end + if Config.restrictMissionAcceptance then + if not unit or not unit:isExist() or not Utils.isLanded(unit, true) then + if unit and unit:isExist() then trigger.action.outTextForUnit(unit:getID(), 'Can only accept mission while landed', 5) end + return false + end - local zn = ZoneCommand.getZoneOfUnit(unit:getName()) - if not zn or zn.side ~= unit:getCoalition() then - trigger.action.outTextForUnit(unit:getID(), 'Can only accept mission while inside friendly zone', 5) - return false + local zn = ZoneCommand.getZoneOfUnit(unit:getName()) + if not zn or zn.side ~= unit:getCoalition() then + trigger.action.outTextForUnit(unit:getID(), 'Can only accept mission while inside friendly zone', 5) + return false + end end for c,m in pairs(self.activeMissions) do @@ -12496,15 +12859,17 @@ do end function MissionTracker:joinMission(code, player, unit) - if not unit or not unit:isExist() or not Utils.isLanded(unit, true) then - if unit and unit:isExist() then trigger.action.outTextForUnit(unit:getID(), 'Can only join mission while landed', 5) end - return false - end + if Config.restrictMissionAcceptance then + if not unit or not unit:isExist() or not Utils.isLanded(unit, true) then + if unit and unit:isExist() then trigger.action.outTextForUnit(unit:getID(), 'Can only join mission while landed', 5) end + return false + end - local zn = ZoneCommand.getZoneOfUnit(unit:getName()) - if not zn or zn.side ~= unit:getCoalition() then - trigger.action.outTextForUnit(unit:getID(), 'Can only join mission while inside friendly zone', 5) - return false + local zn = ZoneCommand.getZoneOfUnit(unit:getName()) + if not zn or zn.side ~= unit:getCoalition() then + trigger.action.outTextForUnit(unit:getID(), 'Can only join mission while inside friendly zone', 5) + return false + end end for c,m in pairs(self.activeMissions) do @@ -12577,7 +12942,7 @@ do return CAS_Easy.canCreate() elseif misType == Mission.types.cas_medium then return CAS_Medium.canCreate() - elseif Config.disablePlayerSead == false and misType == Mission.types.sead then + elseif misType == Mission.types.sead then return SEAD.canCreate() elseif misType == Mission.types.dead then return DEAD.canCreate() @@ -12640,6 +13005,8 @@ do self.__index = self obj:start() + + DependencyManager.register("SquadTracker", obj) return obj end @@ -12886,6 +13253,8 @@ do self.__index = self obj:start() + + DependencyManager.register("CSARTracker", obj) return obj end @@ -13065,6 +13434,7 @@ do o:start() + DependencyManager.register("GCI", o) return o end @@ -13077,22 +13447,27 @@ do msg=msg.."nm" end + local wRadius = 0 if metric then - warningRadius = warningRadius * 1000 + wRadius = warningRadius * 1000 else - warningRadius = warningRadius * 1852 + wRadius = warningRadius * 1852 end self.players[name] = { unit = unit, - warningRadius = warningRadius, + warningRadius = wRadius, metric = metric } trigger.action.outTextForUnit(unit:getID(), msg, 10) + DependencyManager.get("PlayerTracker"):setPlayerConfig(name, "gci_warning_radius", warningRadius) + DependencyManager.get("PlayerTracker"):setPlayerConfig(name, "gci_metric", metric) else self.players[name] = nil trigger.action.outTextForUnit(unit:getID(), "GCI Reports disabled", 10) + DependencyManager.get("PlayerTracker"):setPlayerConfig(name, "gci_warning_radius", nil) + DependencyManager.get("PlayerTracker"):setPlayerConfig(name, "gci_metric", nil) end end From a24925bc453c61028a648c80d94a8e3f8a97f86d Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Mon, 5 Feb 2024 20:00:43 +0200 Subject: [PATCH 101/243] Updated pretense_compiled.lua to version 1.6.5: Pretense v1.6.5 - 04 Feb 2024 Fixed error on restoring strike targets Block slots from within the mission, removing the need for slotblock.lua --- .../plugins/pretense/pretense_compiled.lua | 2292 ++++++++++++++--- 1 file changed, 1951 insertions(+), 341 deletions(-) diff --git a/resources/plugins/pretense/pretense_compiled.lua b/resources/plugins/pretense/pretense_compiled.lua index a6b58409..4a458bbd 100644 --- a/resources/plugins/pretense/pretense_compiled.lua +++ b/resources/plugins/pretense/pretense_compiled.lua @@ -57,7 +57,10 @@ Config.maxDistFromFront = Config.maxDistFromFront or 129640 -- max distance in m if Config.restrictMissionAcceptance == nil then Config.restrictMissionAcceptance = true end -- if set to true, missions can only be accepted while landed inside friendly zones Config.missions = Config.missions or {} +Config.missionBoardSize = Config.missionBoardSize or 15 +Config.carrierSpawnCost = Config.carrierSpawnCost or 500 -- resource cost for carrier when players take off, set to 0 to disable restriction +Config.zoneSpawnCost = Config.zoneSpawnCost or 500 -- resource cost for zones when players take off, set to 0 to disable restriction -----------------[[ END OF Config.lua ]]----------------- @@ -79,6 +82,14 @@ do return cnt end + function Utils.isInArray(value, array) + for _,v in ipairs(array) do + if value == v then + return true + end + end + end + Utils.cache = {} Utils.cache.groups = {} function Utils.getOriginalGroup(groupName) @@ -181,6 +192,11 @@ do return false end + + function Utils.isInCircle(point, center, radius) + local dist = mist.utils.get2DDist(point, center) + return dist 0 then + local count = 0 + for i,v in ipairs(targets) do + count = count + 1 + local oname = v.zone.name..'('..v.data.display..')' + if count<10 then + missionCommands.addCommandForGroup(groupid, oname, menu, executeAction, action, {target = v, menu=menu, groupid=groupid, data=data}) + elseif count==10 then + sub1 = missionCommands.addSubMenuForGroup(groupid, "More", menu) + missionCommands.addCommandForGroup(groupid, oname, sub1, executeAction, action, {target = v, menu=menu, groupid=groupid, data=data}) + elseif count%9==1 then + sub1 = missionCommands.addSubMenuForGroup(groupid, "More", sub1) + missionCommands.addCommandForGroup(groupid, oname, sub1, executeAction, action, {target = v, menu=menu, groupid=groupid, data=data}) + else + missionCommands.addCommandForGroup(groupid, oname, sub1, executeAction, action, {target = v, menu=menu, groupid=groupid, data=data}) + end + end + else + return false + end + + return menu + end + + function MenuRegistry.showTargetZoneMenu(groupid, name, action, targetside, minDistToFront, data, includeCarriers) + local executeAction = function(act, params) + local err = act(params) + if not err then + missionCommands.removeItemForGroup(params.groupid, params.menu) + end + end + + local menu = missionCommands.addSubMenuForGroup(groupid, name) + local sub1 = nil + local zones = ZoneCommand.getAllZones() + + if targetside and type(targetside) == 'number' then + targetside = { targetside } + end + local zns = {} for i,v in pairs(zones) do - if not targetside or v.side == targetside then + if not targetside or Utils.isInArray(v.side,targetside) then if not minDistToFront or v.distToFront <= minDistToFront then table.insert(zns, v) end end end + if includeCarriers then + for i,v in pairs(CarrierCommand.getAllCarriers()) do + if not targetside or Utils.isInArray(v.side,targetside) then + table.insert(zns, v) + end + end + end + table.sort(zns, function(a,b) return a.name < b.name end) local count = 0 for i,v in ipairs(zns) do count = count + 1 if count<10 then - missionCommands.addCommandForGroup(groupid, v.name, menu, executeAction, action, {zone = v, menu=menu, groupid=groupid}) + missionCommands.addCommandForGroup(groupid, v.name, menu, executeAction, action, {zone = v, menu=menu, groupid=groupid, data=data}) elseif count==10 then sub1 = missionCommands.addSubMenuForGroup(groupid, "More", menu) - missionCommands.addCommandForGroup(groupid, v.name, sub1, executeAction, action, {zone = v, menu=menu, groupid=groupid}) + missionCommands.addCommandForGroup(groupid, v.name, sub1, executeAction, action, {zone = v, menu=menu, groupid=groupid, data=data}) elseif count%9==1 then sub1 = missionCommands.addSubMenuForGroup(groupid, "More", sub1) - missionCommands.addCommandForGroup(groupid, v.name, sub1, executeAction, action, {zone = v, menu=menu, groupid=groupid}) + missionCommands.addCommandForGroup(groupid, v.name, sub1, executeAction, action, {zone = v, menu=menu, groupid=groupid, data=data}) else - missionCommands.addCommandForGroup(groupid, v.name, sub1, executeAction, action, {zone = v, menu=menu, groupid=groupid}) + missionCommands.addCommandForGroup(groupid, v.name, sub1, executeAction, action, {zone = v, menu=menu, groupid=groupid, data=data}) end end @@ -649,7 +723,7 @@ do if not group.state then group.state = 'started' - lastStateTime = timer.getAbsTime() + group.lastStateTime = timer.getAbsTime() env.info('GroupMonitor: processSurface ['..group.name..'] starting') end @@ -657,6 +731,9 @@ do if gr then local firstUnit = gr:getUnit(1):getName() local z = ZoneCommand.getZoneOfUnit(firstUnit) + if not z then + z = CarrierCommand.getCarrierOfUnit(firstUnit) + end if not z then env.info('GroupMonitor: processSurface ['..group.name..'] is enroute') @@ -675,6 +752,9 @@ do if gr then local firstUnit = gr:getUnit(1):getName() local z = ZoneCommand.getZoneOfUnit(firstUnit) + if not z then + z = CarrierCommand.getCarrierOfUnit(firstUnit) + end if z and (z.name==group.target.name or z.name==group.home.name) then MissionTargetRegistry.removeBaiTarget(group) @@ -724,9 +804,15 @@ do end elseif GroupMonitor.isStuck(group) then env.info('GroupMonitor: processSurface ['..group.name..'] is stuck, trying to get unstuck') - local supplyPoint = trigger.misc.getZone(group.target.name..'-sp') + + local tgtname = group.target.name + if group.returning then + tgtname = group.home.name + end + + local supplyPoint = trigger.misc.getZone(tgtname..'-sp') if not supplyPoint then - supplyPoint = trigger.misc.getZone(group.target.name) + supplyPoint = trigger.misc.getZone(tgtname) end TaskExtensions.moveOnRoadToPoint(gr, {x=supplyPoint.point.x, y=supplyPoint.point.z}, true) @@ -747,18 +833,20 @@ do }) timer.scheduleFunction(function(params, time) - local group = params.group + local group = params.gr + local tgtname = group.target.name + if group.returning then + tgtname = group.home.name + end local gr = Group.getByName(group.name) - local supplyPoint = trigger.misc.getZone(group.target.name..'-sp') + local supplyPoint = trigger.misc.getZone(tgtname..'-sp') if not supplyPoint then - supplyPoint = trigger.misc.getZone(group.target.name) + supplyPoint = trigger.misc.getZone(tgtname) end TaskExtensions.moveOnRoadToPoint(gr, {x=supplyPoint.point.x, y=supplyPoint.point.z}, true) end, {gr = group}, timer.getTime()+2) end - elseif group.unstuck_attempts and group.unstuck_attempts > 0 then - group.unstuck_attempts = 0 end elseif group.product.missionType == 'assault' then local frUnit = gr:getUnit(1) @@ -867,6 +955,9 @@ do if gr then local firstUnit = gr:getUnit(1):getName() local z = ZoneCommand.getZoneOfUnit(firstUnit) + if not z then + z = CarrierCommand.getCarrierOfUnit(firstUnit) + end if z and z.side == 0 then env.info('GroupMonitor: processSurface ['..group.name..'] is at neutral zone') z:capture(gr:getCoalition()) @@ -927,6 +1018,7 @@ do local un = gr:getUnit(1) if un and un:isExist() and mist.vec.mag(un:getVelocity()) >= 0.01 and group.stuck_marker > 0 then group.stuck_marker = 0 + group.unstuck_attempts = 0 env.info('GroupMonitor: isStuck ['..group.name..'] is moving, reseting stuck marker velocity='..mist.vec.mag(un:getVelocity())) end @@ -947,7 +1039,7 @@ do function GroupMonitor:processAir(group)-- states: [takeoff, inair, landed] local gr = Group.getByName(group.name) if not gr then return true end - if gr:getSize()==0 then + if not gr:isExist() or gr:getSize()==0 then gr:destroy() return true end @@ -968,20 +1060,27 @@ do if group.state =='takeoff' then if timer.getAbsTime() - group.lastStateTime > GroupMonitor.blockedDespawnTime then - if gr and Utils.allGroupIsLanded(gr) then - env.info('GroupMonitor: processAir ['..group.name..'] is blocked, despawning') + if gr and gr:getSize()>0 and gr:getUnit(1):isExist() then local frUnit = gr:getUnit(1) - if frUnit then - local firstUnit = frUnit:getName() - local z = ZoneCommand.getZoneOfUnit(firstUnit) - if z then - z:addResource(group.product.cost) - env.info('GroupMonitor: processAir ['..z.name..'] has recovered ['..group.product.cost..'] from ['..group.name..']') + local cz = CarrierCommand.getCarrierOfUnit(frUnit:getName()) + if Utils.allGroupIsLanded(gr, cz ~= nil) then + env.info('GroupMonitor: processAir ['..group.name..'] is blocked, despawning') + local frUnit = gr:getUnit(1) + if frUnit then + local firstUnit = frUnit:getName() + local z = ZoneCommand.getZoneOfUnit(firstUnit) + if not z then + z = CarrierCommand.getCarrierOfUnit(firstUnit) + end + if z then + z:addResource(group.product.cost) + env.info('GroupMonitor: processAir ['..z.name..'] has recovered ['..group.product.cost..'] from ['..group.name..']') + end end - end - gr:destroy() - return true + gr:destroy() + return true + end end elseif gr and Utils.someOfGroupInAir(gr) then env.info('GroupMonitor: processAir ['..group.name..'] is in the air') @@ -989,89 +1088,97 @@ do group.lastStateTime = timer.getAbsTime() end elseif group.state =='inair' then - if gr and Utils.allGroupIsLanded(gr) then - env.info('GroupMonitor: processAir ['..group.name..'] has landed') - group.state = 'landed' - group.lastStateTime = timer.getAbsTime() - + if gr then local unit = gr:getUnit(1) - if unit then - local firstUnit = unit:getName() - local z = ZoneCommand.getZoneOfUnit(firstUnit) - - if group.product.missionType == 'supply_air' then - if z then - z:capture(gr:getCoalition()) - z:addResource(group.product.cost) - env.info('GroupMonitor: processAir ['..group.name..'] has supplied ['..z.name..'] with ['..group.product.cost..']') + if not unit or not unit:isExist() then return end + + local cz = CarrierCommand.getCarrierOfUnit(unit:getName()) + if Utils.allGroupIsLanded(gr, cz ~= nil) then + env.info('GroupMonitor: processAir ['..group.name..'] has landed') + group.state = 'landed' + group.lastStateTime = timer.getAbsTime() + + if unit then + local firstUnit = unit:getName() + local z = ZoneCommand.getZoneOfUnit(firstUnit) + if not z then + z = CarrierCommand.getCarrierOfUnit(firstUnit) + end + + if group.product.missionType == 'supply_air' then + if z then + z:capture(gr:getCoalition()) + z:addResource(group.product.cost) + env.info('GroupMonitor: processAir ['..group.name..'] has supplied ['..z.name..'] with ['..group.product.cost..']') + end + else + if z and z.side == gr:getCoalition() then + local percentSurvived = gr:getSize()/gr:getInitialSize() + local torecover = math.floor(group.product.cost * percentSurvived * GroupMonitor.recoveryReduction) + z:addResource(torecover) + env.info('GroupMonitor: processAir ['..z.name..'] has recovered ['..torecover..'] from ['..group.name..']') + end end else - if z and z.side == gr:getCoalition() then - local percentSurvived = gr:getSize()/gr:getInitialSize() - local torecover = math.floor(group.product.cost * percentSurvived * GroupMonitor.recoveryReduction) - z:addResource(torecover) - env.info('GroupMonitor: processAir ['..z.name..'] has recovered ['..torecover..'] from ['..group.name..']') - end + env.info('GroupMonitor: processAir ['..group.name..'] size ['..gr:getSize()..'] has no unit 1') end else - env.info('GroupMonitor: processAir ['..group.name..'] size ['..gr:getSize()..'] has no unit 1') - end - elseif gr then - if GroupMonitor.isAirAttack(group.product.missionType) and not group.returning then - if not GroupMonitor.hasWeapons(gr) then - env.info('GroupMonitor: processAir ['..group.name..'] size ['..gr:getSize()..'] has no weapons outside of shells') - self:sendHome(group) - elseif group.product.missionType == ZoneCommand.missionTypes.cas_helo then - local frUnit = gr:getUnit(1) - local controller = frUnit:getController() - local targets = controller:getDetectedTargets() + if GroupMonitor.isAirAttack(group.product.missionType) and not group.returning then + if not GroupMonitor.hasWeapons(gr) then + env.info('GroupMonitor: processAir ['..group.name..'] size ['..gr:getSize()..'] has no weapons outside of shells') + self:sendHome(group) + elseif group.product.missionType == ZoneCommand.missionTypes.cas_helo then + local frUnit = gr:getUnit(1) + local controller = frUnit:getController() + local targets = controller:getDetectedTargets() - local tgtToEngage = {} - if #targets > 0 then - for _,tgt in ipairs(targets) do - if tgt.visible and tgt.object and tgt.object.isExist and tgt.object:isExist() then - if Object.getCategory(tgt.object) == Object.Category.UNIT and - tgt.object.getCoalition and tgt.object:getCoalition()~=frUnit:getCoalition() and - Unit.getCategoryEx(tgt.object) == Unit.Category.GROUND_UNIT then + local tgtToEngage = {} + if #targets > 0 then + for _,tgt in ipairs(targets) do + if tgt.visible and tgt.object and tgt.object.isExist and tgt.object:isExist() then + if Object.getCategory(tgt.object) == Object.Category.UNIT and + tgt.object.getCoalition and tgt.object:getCoalition()~=frUnit:getCoalition() and + Unit.getCategoryEx(tgt.object) == Unit.Category.GROUND_UNIT then - local dist = mist.utils.get3DDist(frUnit:getPoint(), tgt.object:getPoint()) - if dist < 2000 then - table.insert(tgtToEngage, tgt.object) + local dist = mist.utils.get3DDist(frUnit:getPoint(), tgt.object:getPoint()) + if dist < 2000 then + table.insert(tgtToEngage, tgt.object) + end end end end end - end - if not group.isengaging and #tgtToEngage > 0 then - env.info('GroupMonitor: processAir ['..group.name..'] engaging targets') - TaskExtensions.heloEngageTargets(gr, tgtToEngage, group.product.expend) - group.isengaging = true - group.startedEngaging = timer.getAbsTime() - elseif group.isengaging and #tgtToEngage == 0 and group.startedEngaging and (timer.getAbsTime() - group.startedEngaging) > 60*5 then - env.info('GroupMonitor: processAir ['..group.name..'] resuming mission') - if group.returning then - group.returning = nil - self:sendHome(group) - else - local homePos = group.home.zone.point - TaskExtensions.executeHeloCasMission(gr, group.target.built, group.product.expend, group.product.altitude, {homePos = homePos}) + if not group.isengaging and #tgtToEngage > 0 then + env.info('GroupMonitor: processAir ['..group.name..'] engaging targets') + TaskExtensions.heloEngageTargets(gr, tgtToEngage, group.product.expend) + group.isengaging = true + group.startedEngaging = timer.getAbsTime() + elseif group.isengaging and #tgtToEngage == 0 and group.startedEngaging and (timer.getAbsTime() - group.startedEngaging) > 60*5 then + env.info('GroupMonitor: processAir ['..group.name..'] resuming mission') + if group.returning then + group.returning = nil + self:sendHome(group) + else + local homePos = group.home.zone.point + TaskExtensions.executeHeloCasMission(gr, group.target.built, group.product.expend, group.product.altitude, {homePos = homePos}) + end + group.isengaging = false end - group.isengaging = false - end - end - elseif group.product.missionType == 'supply_air' then - if not group.returning and group.target and group.target.side ~= group.product.side and group.target.side ~= 0 then - local supplyPoint = trigger.misc.getZone(group.home.name..'-hsp') - if not supplyPoint then - supplyPoint = trigger.misc.getZone(group.home.name) end + elseif group.product.missionType == 'supply_air' then + if not group.returning and group.target and group.target.side ~= group.product.side and group.target.side ~= 0 then + local supplyPoint = trigger.misc.getZone(group.home.name..'-hsp') + if not supplyPoint then + supplyPoint = trigger.misc.getZone(group.home.name) + end - if supplyPoint then - group.returning = true - local alt = DependencyManager.get("ConnectionManager"):getHeliAlt(group.target.name, group.home.name) - TaskExtensions.landAtPointFromAir(gr, {x=supplyPoint.point.x, y=supplyPoint.point.z}, alt) - env.info('GroupMonitor: processAir ['..group.name..'] returning home') + if supplyPoint then + group.returning = true + local alt = DependencyManager.get("ConnectionManager"):getHeliAlt(group.target.name, group.home.name) + TaskExtensions.landAtPointFromAir(gr, {x=supplyPoint.point.x, y=supplyPoint.point.z}, alt) + env.info('GroupMonitor: processAir ['..group.name..'] returning home') + end end end end @@ -1271,7 +1378,15 @@ do end end - function TaskExtensions.getDefaultWaypoints(startPos, task, tgpos, reactivated) + function TaskExtensions.getTargetPos(targetName) + local tgt = StaticObject.getByName(targetName) + if not tgt then tgt = Unit.getByName(targetName) end + if tgt then + return tgt:getPoint() + end + end + + function TaskExtensions.getDefaultWaypoints(startPos, task, tgpos, reactivated, landUnitID) local defwp = { id='Mission', params = { @@ -1329,15 +1444,29 @@ do }) end - table.insert(defwp.params.route.points, { - type= AI.Task.WaypointType.LAND, - x = startPos.x, - y = startPos.z, - speed = 257, - action = AI.Task.TurnMethod.FIN_POINT, - alt = 0, - alt_type = AI.Task.AltitudeType.RADIO - }) + if landUnitID then + table.insert(defwp.params.route.points, { + type= AI.Task.WaypointType.LAND, + linkUnit = landUnitID, + helipadId = landUnitID, + x = startPos.x, + y = startPos.z, + speed = 257, + action = AI.Task.TurnMethod.FIN_POINT, + alt = 0, + alt_type = AI.Task.AltitudeType.RADIO + }) + else + table.insert(defwp.params.route.points, { + type= AI.Task.WaypointType.LAND, + x = startPos.x, + y = startPos.z, + speed = 257, + action = AI.Task.TurnMethod.FIN_POINT, + alt = 0, + alt_type = AI.Task.AltitudeType.RADIO + }) + end return defwp end @@ -1405,7 +1534,7 @@ do TaskExtensions.setDefaultAG(group) end - function TaskExtensions.executeStrikeMission(group,targets, expend, altitude, reactivated) + function TaskExtensions.executeStrikeMission(group, targets, expend, altitude, reactivated, landUnitID) if not group then return end if not group:isExist() or group:getSize()==0 then return end local startPos = group:getUnit(1):getPoint() @@ -1440,7 +1569,59 @@ do end end - local mis = TaskExtensions.getDefaultWaypoints(startPos, attack, nil, reactivated) + local mis = TaskExtensions.getDefaultWaypoints(startPos, attack, nil, reactivated, landUnitID) + + group:getController():setTask(mis) + TaskExtensions.setDefaultAG(group) + end + + function TaskExtensions.executePinpointStrikeMission(group, targetPos, expend, altitude, reactivated, landUnitID) + if not group then return end + if not group:isExist() or group:getSize()==0 then return end + local startPos = group:getUnit(1):getPoint() + + if reactivated then + reactivated.currentPos = startPos + startPos = reactivated.homePos + end + + local expCount = AI.Task.WeaponExpend.ALL + if expend then + expCount = expend + end + + local alt = 4572 + if altitude then + alt = altitude/3.281 + end + + local attack = { + id = 'Bombing', + params = { + point = { + x = targetPos.x, + y = targetPos.z + }, + attackQty = 1, + weaponType = Weapon.flag.AnyBomb, + expend = expCount, + groupAttack = true, + altitude = alt, + altitudeEnabled = (altitude ~= nil), + } + } + + local diff = { + x = targetPos.x - startPos.x, + z = targetPos.z - startPos.z + } + + local tp = { + x = targetPos.x - diff.x*0.5, + z = targetPos.z - diff.z*0.5 + } + + local mis = TaskExtensions.getDefaultWaypoints(startPos, attack, tp, reactivated, landUnitID) group:getController():setTask(mis) TaskExtensions.setDefaultAG(group) @@ -1679,7 +1860,7 @@ do TaskExtensions.setDefaultAG(group) end - function TaskExtensions.executeTankerMission(group, point, altitude, frequency, tacan, reactivated) + function TaskExtensions.executeTankerMission(group, point, altitude, frequency, tacan, reactivated, landUnitID) if not group then return end if not group:isExist() or group:getSize()==0 then return end local startPos = group:getUnit(1):getPoint() @@ -1713,7 +1894,11 @@ do system = 4, -- Tanker TACAN name = 'tacan task', callsign = group:getUnit(1):getCallsign():sub(1,3), - frequency = tacan + frequency = tacan, + AA = true, + channel = tacan, + bearing = true, + modeChannel = "X" } } @@ -1833,23 +2018,37 @@ do } }) - table.insert(task.params.route.points, { - type= AI.Task.WaypointType.LAND, - x = startPos.x, - y = startPos.z, - speed = 450, - action = AI.Task.TurnMethod.FIN_POINT, - alt = 0, - alt_type = AI.Task.AltitudeType.RADIO - }) - + if landUnitID then + table.insert(task.params.route.points, { + type= AI.Task.WaypointType.LAND, + linkUnit = landUnitID, + helipadId = landUnitID, + x = startPos.x, + y = startPos.z, + speed = 450, + action = AI.Task.TurnMethod.FIN_POINT, + alt = 0, + alt_type = AI.Task.AltitudeType.RADIO + }) + else + table.insert(task.params.route.points, { + type= AI.Task.WaypointType.LAND, + x = startPos.x, + y = startPos.z, + speed = 450, + action = AI.Task.TurnMethod.FIN_POINT, + alt = 0, + alt_type = AI.Task.AltitudeType.RADIO + }) + end + group:getController():setTask(task) group:getController():setOption(AI.Option.Air.id.REACTION_ON_THREAT, AI.Option.Air.val.REACTION_ON_THREAT.PASSIVE_DEFENCE) group:getController():setCommand(setfreq) group:getController():setCommand(setbeacon) end - function TaskExtensions.executeAwacsMission(group, point, altitude, frequency, reactivated) + function TaskExtensions.executeAwacsMission(group, point, altitude, frequency, reactivated, landUnitID) if not group then return end if not group:isExist() or group:getSize()==0 then return end local startPos = group:getUnit(1):getPoint() @@ -2013,22 +2212,36 @@ do } }) - table.insert(task.params.route.points, { - type= AI.Task.WaypointType.LAND, - x = startPos.x, - y = startPos.z, - speed = 257, - action = AI.Task.TurnMethod.FIN_POINT, - alt = 0, - alt_type = AI.Task.AltitudeType.RADIO - }) + if landUnitID then + table.insert(task.params.route.points, { + type= AI.Task.WaypointType.LAND, + linkUnit = landUnitID, + helipadId = landUnitID, + x = startPos.x, + y = startPos.z, + speed = 257, + action = AI.Task.TurnMethod.FIN_POINT, + alt = 0, + alt_type = AI.Task.AltitudeType.RADIO + }) + else + table.insert(task.params.route.points, { + type= AI.Task.WaypointType.LAND, + x = startPos.x, + y = startPos.z, + speed = 257, + action = AI.Task.TurnMethod.FIN_POINT, + alt = 0, + alt_type = AI.Task.AltitudeType.RADIO + }) + end group:getController():setTask(task) group:getController():setOption(AI.Option.Air.id.REACTION_ON_THREAT, AI.Option.Air.val.REACTION_ON_THREAT.PASSIVE_DEFENCE) group:getController():setCommand(setfreq) end - function TaskExtensions.executePatrolMission(group, point, altitude, range, reactivated) + function TaskExtensions.executePatrolMission(group, point, altitude, range, reactivated, landUnitID) if not group then return end if not group:isExist() or group:getSize()==0 then return end local startPos = group:getUnit(1):getPoint() @@ -2138,15 +2351,29 @@ do task = orbit }) - table.insert(task.params.route.points, { - type= AI.Task.WaypointType.LAND, - x = startPos.x, - y = startPos.z, - speed = 257, - action = AI.Task.TurnMethod.FIN_POINT, - alt = 0, - alt_type = AI.Task.AltitudeType.RADIO - }) + if landUnitID then + table.insert(task.params.route.points, { + type= AI.Task.WaypointType.LAND, + linkUnit = landUnitID, + helipadId = landUnitID, + x = startPos.x, + y = startPos.z, + speed = 257, + action = AI.Task.TurnMethod.FIN_POINT, + alt = 0, + alt_type = AI.Task.AltitudeType.RADIO + }) + else + table.insert(task.params.route.points, { + type= AI.Task.WaypointType.LAND, + x = startPos.x, + y = startPos.z, + speed = 257, + action = AI.Task.TurnMethod.FIN_POINT, + alt = 0, + alt_type = AI.Task.AltitudeType.RADIO + }) + end group:getController():setTask(task) TaskExtensions.setDefaultAA(group) @@ -2435,7 +2662,7 @@ do group:getController():setTask(mis) end - function TaskExtensions.landAtPoint(group, point, alt) -- point = {x,y} + function TaskExtensions.landAtPoint(group, point, alt, skiptakeoff) -- point = {x,y} if not group or not point then return end if not group:isExist() or group:getSize()==0 then return end local startPos = group:getUnit(1):getPoint() @@ -2459,30 +2686,33 @@ do params = { route = { airborne = true, - points = { - [1] = { - type = AI.Task.WaypointType.TAKEOFF, - x = startPos.x, - y = startPos.z, - speed = 0, - action = AI.Task.TurnMethod.FIN_POINT, - alt = alt, - alt_type = atype - }, - [2] = { - type = AI.Task.WaypointType.TURNING_POINT, - x = point.x, - y = point.y, - speed = 257, - action = AI.Task.TurnMethod.FIN_POINT, - alt = alt, - alt_type = atype, - task = land - } - } + points = {} } } } + + if not skiptakeoff then + table.insert(mis.params.route.points,{ + type = AI.Task.WaypointType.TAKEOFF, + x = startPos.x, + y = startPos.z, + speed = 0, + action = AI.Task.TurnMethod.FIN_POINT, + alt = alt, + alt_type = atype + }) + end + + table.insert(mis.params.route.points,{ + type = AI.Task.WaypointType.TURNING_POINT, + x = point.x, + y = point.y, + speed = 257, + action = AI.Task.TurnMethod.FIN_POINT, + alt = alt, + alt_type = atype, + task = land + }) group:getController():setTask(mis) end @@ -2522,6 +2752,127 @@ do group:getController():setTask(mis) end + + function TaskExtensions.carrierGoToPos(group, point) + if not group or not point then return end + if not group:isExist() or group:getSize()==0 then return end + + local mis = { + id='Mission', + params = { + route = { + airborne = true, + points = { + [1] = { + type= AI.Task.WaypointType.TURNING_POINT, + x = point.x, + y = point.z, + speed = 50, + action = AI.Task.TurnMethod.FIN_POINT + } + } + } + } + } + + group:getController():setTask(mis) + end + + function TaskExtensions.stopCarrier(group) + if not group then return end + if not group:isExist() or group:getSize()==0 then return end + local point = group:getUnit(1):getPoint() + + group:getController():setTask({ + id='Mission', + params = { + route = { + airborne = false, + points = { + [1] = { + type= AI.Task.WaypointType.TURNING_POINT, + x = point.x, + y = point.z, + speed = 0, + action = AI.Task.TurnMethod.FIN_POINT + } + } + } + } + }) + end + + function TaskExtensions.setupCarrier(unit, icls, acls, tacan, link4, radio) + if not unit then return end + if not unit:isExist() then return end + + local commands = {} + if icls then + table.insert(commands, { + id = 'ActivateICLS', + params = { + type = 131584, + channel = icls, + unitId = unit:getID(), + name = "ICLS "..icls, + } + }) + end + + if acls then + table.insert(commands, { + id = 'ActivateACLS', + params = { + unitId = unit:getID(), + name = "ACLS", + } + }) + end + + if tacan then + table.insert(commands, { + id = 'ActivateBeacon', + params = { + type = 4, + system = 4, + name = "TACAN "..tacan.channel, + callsign = tacan.callsign, + frequency = tacan.channel, + channel = tacan.channel, + bearing = true, + modeChannel = "X" + } + }) + end + + if link4 then + table.insert(commands, { + id = 'ActivateLink4', + params = { + unitId = unit:getID(), + frequency = link4, + name = "Link4 "..link4, + } + }) + end + + if radio then + table.insert(commands, { + id = "SetFrequency", + params = { + power = 100, + modulation = 0, + frequency = radio, + } + }) + end + + for i,v in ipairs(commands) do + unit:getController():setCommand(v) + end + + unit:getGroup():getController():setOption(AI.Option.Ground.id.AC_ENGAGEMENT_RANGE_RESTRICTION, 30) + end end -----------------[[ END OF TaskExtensions.lua ]]----------------- @@ -2951,7 +3302,7 @@ do env.info('PlayerLogistics - Hercules - '..cargo.squad.type..' dropped on invalid surface '..tostring(surface)) local cpos = cargo.object:getPoint() env.info('PlayerLogistics - Hercules - cargo spot X:'..cpos.x..' Y:'..cpos.y..' Z:'..cpos.z) - env.info('PlayerLogistics - Hercules - surface spot X:'..pos.x..' Y:'..pos.y..' Z:'..pos.z) + env.info('PlayerLogistics - Hercules - surface spot X:'..pos.x..' Y:'..pos.y) local squadName = PlayerLogistics.getInfantryName(cargo.squad.type) trigger.action.outTextForUnit(cargo.unit:getID(), 'Cargo drop of '..cargo.unit:getPlayerName()..' with '..squadName..' crashed', 10) end @@ -3000,7 +3351,7 @@ do end function PlayerLogistics:awardSupplyXP(lastLoad, zone, unit, amount) - if lastLoad and zone.name~=lastLoad.name then + if lastLoad and zone.name~=lastLoad.name and not zone.isCarrier and not lastLoad.isCarrier then if unit and unit.isExist and unit:isExist() and unit.getPlayerName then local player = unit:getPlayerName() local xp = amount*RewardDefinitions.actions.supplyRatio @@ -3246,6 +3597,10 @@ do end local zn = ZoneCommand.getZoneOfUnit(un:getName()) + if not zn then + zn = CarrierCommand.getCarrierOfUnit(un:getName()) + end + if not zn then trigger.action.outTextForUnit(un:getID(), 'Can only unload extracted pilots while within a friendly zone', 10) return @@ -3407,6 +3762,10 @@ do end local zn = ZoneCommand.getZoneOfUnit(un:getName()) + if not zn then + zn = CarrierCommand.getCarrierOfUnit(un:getName()) + end + if not zn then trigger.action.outTextForUnit(un:getID(), 'Can only load infantry while within a friendly zone', 10) return @@ -3505,6 +3864,9 @@ do end local zn = ZoneCommand.getZoneOfUnit(un:getName()) + if not zn then + zn = CarrierCommand.getCarrierOfUnit(un:getName()) + end for _, sq in ipairs(toUnload) do local squadName = PlayerLogistics.getInfantryName(sq.type) @@ -3711,6 +4073,10 @@ do end local zn = ZoneCommand.getZoneOfUnit(un:getName()) + if not zn then + zn = CarrierCommand.getCarrierOfUnit(un:getName()) + end + if not zn then trigger.action.outTextForUnit(un:getID(), 'Can only load supplies while within a friendly zone', 10) return @@ -3791,6 +4157,10 @@ do end local zn = ZoneCommand.getZoneOfUnit(un:getName()) + if not zn then + zn = CarrierCommand.getCarrierOfUnit(un:getName()) + end + if not zn then trigger.action.outTextForUnit(un:getID(), 'Can only unload supplies while within a friendly zone', 10) return @@ -4054,7 +4424,10 @@ do function ZoneCommand:refreshSpawnBlocking() for _,v in ipairs(self.spawns) do - trigger.action.setUserFlag(v.name, v.side ~= self.side) + local isDifferentSide = v.side ~= self.side + local noResources = self.resource < Config.zoneSpawnCost + + trigger.action.setUserFlag(v.name, isDifferentSide or noResources) end end @@ -4278,11 +4651,13 @@ do function ZoneCommand:addResource(amount) self.resource = self.resource+amount self.resource = math.floor(math.min(self.resource, self.maxResource)) + self:refreshSpawnBlocking() end function ZoneCommand:removeResource(amount) self.resource = self.resource-amount self.resource = math.floor(math.max(self.resource, 0)) + self:refreshSpawnBlocking() end function ZoneCommand:reveal() @@ -6416,7 +6791,8 @@ do PlayerTracker.savefile = 'player_stats.json' PlayerTracker.statTypes = { xp = 'XP', - cmd = "CMD" + cmd = "CMD", + survivalBonus = "SB" } PlayerTracker.cmdShopTypes = { @@ -6425,14 +6801,18 @@ do jtac = 'jtac', bribe1 = 'bribe1', bribe2 = 'bribe2', + artillery = 'artillery', + sabotage1 = 'sabotage1', } PlayerTracker.cmdShopPrices = { [PlayerTracker.cmdShopTypes.smoke] = 1, - [PlayerTracker.cmdShopTypes.prio] = 2, - [PlayerTracker.cmdShopTypes.jtac] = 3, - [PlayerTracker.cmdShopTypes.bribe1] = 1, - [PlayerTracker.cmdShopTypes.bribe2] = 3, + [PlayerTracker.cmdShopTypes.prio] = 10, + [PlayerTracker.cmdShopTypes.jtac] = 5, + [PlayerTracker.cmdShopTypes.bribe1] = 5, + [PlayerTracker.cmdShopTypes.bribe2] = 10, + [PlayerTracker.cmdShopTypes.artillery] = 15, + [PlayerTracker.cmdShopTypes.sabotage1] = 20, } function PlayerTracker:new() @@ -6444,7 +6824,6 @@ do obj.groupShopMenus = {} obj.groupConfigMenus = {} obj.groupTgtMenus = {} - obj.playerAircraft = {} obj.playerEarningMultiplier = {} if lfs then @@ -6480,7 +6859,8 @@ do local player = event.initiator:getPlayerName() if not player then return end - if event.id==world.event.S_EVENT_PLAYER_ENTER_UNIT then + local blocked = false + if event.id==world.event.S_EVENT_BIRTH then if event.initiator and Object.getCategory(event.initiator) == Object.Category.UNIT and (Unit.getCategoryEx(event.initiator) == Unit.Category.AIRPLANE or Unit.getCategoryEx(event.initiator) == Unit.Category.HELICOPTER) then @@ -6488,14 +6868,23 @@ do if pname then local gr = event.initiator:getGroup() if trigger.misc.getUserFlag(gr:getName())==1 then - trigger.action.outTextForGroup(gr:getID(), 'Can not spawn as '..gr:getName()..' in enemy/neutral zone',5) + blocked = true + trigger.action.outTextForGroup(gr:getID(), 'Can not spawn as '..gr:getName()..' in enemy/neutral zone or zone without enough resources',5) event.initiator:destroy() + + for i,v in pairs(net.get_player_list()) do + if net.get_name(v) == pname then + net.send_chat_to('Can not spawn as '..gr:getName()..' in enemy/neutral zone or zone without enough resources' , v) + net.force_player_slot(v, 0, '') + break + end + end end end end end - if event.id == world.event.S_EVENT_BIRTH then + if event.id == world.event.S_EVENT_BIRTH and not blocked then -- init stats for player if not exist if not self.context.stats[player] then self.context.stats[player] = {} @@ -6503,10 +6892,15 @@ do -- reset temp track for player self.context.tempStats[player] = nil - -- reset playeraircraft - self.context.playerAircraft[player] = nil - self.context.playerEarningMultiplier[player] = { spawnTime = timer.getAbsTime(), unit = event.initiator, multiplier = 1.0, minutes = 0 } + local minutes = 0 + local multiplier = 1.0 + if self.context.stats[player][PlayerTracker.statTypes.survivalBonus] ~= nil then + minutes = self.context.stats[player][PlayerTracker.statTypes.survivalBonus] + multiplier = PlayerTracker.minutesToMultiplier(minutes) + end + + self.context.playerEarningMultiplier[player] = { spawnTime = timer.getAbsTime(), unit = event.initiator, multiplier = multiplier, minutes = minutes } local config = self.context:getPlayerConfig(player) if config.gci_warning_radius then @@ -6562,21 +6956,35 @@ do if event.id==world.event.S_EVENT_TAKEOFF then local un = event.initiator - local zn = ZoneCommand.getZoneOfUnit(event.initiator:getName()) env.info('PlayerTracker - '..player..' took off in '..tostring(un:getID())..' '..un:getName()) - if un and zn and zn.side == un:getCoalition() then - timer.scheduleFunction(function(param, time) - local un = param.unit - if not un or not un:isExist() then return end - local player = param.player - local inAir = Utils.isInAir(un) - env.info('PlayerTracker - '..player..' checking if in air: '..tostring(inAir)) - if inAir and param.context.playerAircraft[player] == nil then - if param.context.playerAircraft[player] == nil then - param.context.playerAircraft[player] = { unitID = un:getID() } - end - end - end, {player = player, unit = event.initiator, context = self.context}, timer.getTime()+10) + if self.context.stats[player][PlayerTracker.statTypes.survivalBonus] ~= nil then + self.context.stats[player][PlayerTracker.statTypes.survivalBonus] = nil + trigger.action.outTextForUnit(un:getID(), 'Taken off, survival bonus no longer secure.', 10) + end + + local zn = CarrierCommand.getCarrierOfUnit(un:getName()) + if zn then + zn:removeResource(Config.carrierSpawnCost) + else + zn = ZoneCommand.getZoneOfUnit(un:getName()) + if zn then + zn:removeResource(Config.zoneSpawnCost) + end + end + end + + if event.id==world.event.S_EVENT_ENGINE_SHUTDOWN then + local un = event.initiator + local zn = ZoneCommand.getZoneOfUnit(un:getName()) + if not zn then + zn = CarrierCommand.getCarrierOfUnit(un:getName()) + end + if un and un:isExist() and zn and zn.side == un:getCoalition() then + env.info('PlayerTracker - '..player..' has shut down engine of '..tostring(un:getID())..' '..un:getName()..' at '..zn.name) + self.context.stats[player][PlayerTracker.statTypes.survivalBonus] = self.context:getPlayerMinutes(player) + self.context:save() + trigger.action.outTextForUnit(un:getID(), 'Engines shut down. Survival bonus secured.', 10) + env.info('PlayerTracker - '..player..' secured survival bonus of '..self.context.stats[player][PlayerTracker.statTypes.survivalBonus]..' minutes') end end @@ -6592,19 +7000,10 @@ do timer.scheduleFunction(function(params, time) local players = params.context.playerEarningMultiplier for i,v in pairs(players) do - local aircraft = params.context.playerAircraft[i] - if aircraft and v.unit.isExist and v.unit:isExist() and aircraft.unitID == v.unit:getID() then + if v.unit.isExist and v.unit:isExist() then if v.multiplier < 5.0 and v.unit and v.unit:isExist() and Utils.isInAir(v.unit) then v.minutes = v.minutes + 1 - - local multi = 1.0 - if v.minutes > 10 and v.minutes <= 60 then - multi = 1.0 + ((v.minutes-10)*0.05) - elseif v.minutes > 60 then - multi = 1.0 + (50*0.05) + ((v.minutes - 60)*0.025) - end - - v.multiplier = math.min(multi, 5.0) + v.multiplier = PlayerTracker.minutesToMultiplier(v.minutes) end end end @@ -6616,9 +7015,12 @@ do function PlayerTracker:validateLanding(unit, player) local un = unit local zn = ZoneCommand.getZoneOfUnit(unit:getName()) - local aircraft = self.playerAircraft[player] + if not zn then + zn = CarrierCommand.getCarrierOfUnit(unit:getName()) + end + env.info('PlayerTracker - '..player..' landed in '..tostring(un:getID())..' '..un:getName()) - if aircraft and un and zn and zn.side == un:getCoalition() then + if un and zn and zn.side == un:getCoalition() then trigger.action.outTextForUnit(unit:getID(), "Wait 10 seconds to validate landing...", 10) timer.scheduleFunction(function(param, time) local un = param.unit @@ -6627,10 +7029,19 @@ do local player = param.player local isLanded = Utils.isLanded(un, true) local zn = ZoneCommand.getZoneOfUnit(un:getName()) + if not zn then + zn = CarrierCommand.getCarrierOfUnit(un:getName()) + end env.info('PlayerTracker - '..player..' checking if landed: '..tostring(isLanded)) if isLanded then + if zn.isCarrier then + zn:addResource(Config.carrierSpawnCost) + else + zn:addResource(Config.zoneSpawnCost) + end + if param.context.tempStats[player] then if zn and zn.side == un:getCoalition() then param.context.stats[player] = param.context.stats[player] or {} @@ -6644,12 +7055,9 @@ do trigger.action.outTextForUnit(un:getID(), key..' +'..value..'', 5) end end - end - end - local aircraft = param.context.playerAircraft[player] - if aircraft and aircraft.unitID == un:getID() then - param.context.playerAircraft[player] = nil + param.context:save() + end end end end, {player = player, unit = unit, context = self}, timer.getTime()+10) @@ -6699,23 +7107,31 @@ do local cmdChance = rank.cmdChance if cmdChance > 0 then - local die = math.random() - if die <= cmdChance then + + local tkns = 0 + for i=1,rank.cmdTrys,1 do + local die = math.random() + if die <= cmdChance then + tkns = tkns + 1 + end + end + + if tkns > 0 then if isTemp then - self:addTempStat(player, 1, PlayerTracker.statTypes.cmd) + self:addTempStat(player, tkns, PlayerTracker.statTypes.cmd) else - self:addStat(player, 1, PlayerTracker.statTypes.cmd) + self:addStat(player, tkns, PlayerTracker.statTypes.cmd) end local msg = "" if isTemp then - msg = '+1 CMD (unclaimed)' + msg = '+'..tkns..' CMD (unclaimed)' else - msg = '[CMD] '..self.stats[player][PlayerTracker.statTypes.cmd]..' (+1)' + msg = '[CMD] '..self.stats[player][PlayerTracker.statTypes.cmd]..' (+'..tkns..')' end trigger.action.outTextForUnit(unit:getID(), msg, 5) - env.info("PlayerTracker.addRankRewards - Awarded "..player.." a CMD token with chance "..cmdChance.." die roll "..die) + env.info("PlayerTracker.addRankRewards - Awarded "..player.." "..tkns.." CMD tokens with chance "..cmdChance) end end end @@ -6773,56 +7189,56 @@ do end end, self) - MenuRegistry:register(4, function(event, context) + MenuRegistry:register(5, function(event, context) if event.id == world.event.S_EVENT_BIRTH and event.initiator and event.initiator.getPlayerName then local player = event.initiator:getPlayerName() if player then local rank = context:getPlayerRank(player) if not rank then return end - if rank.cmdChance > 0 then - local groupid = event.initiator:getGroup():getID() - local groupname = event.initiator:getGroup():getName() + local groupid = event.initiator:getGroup():getID() + local groupname = event.initiator:getGroup():getName() + + if context.groupConfigMenus[groupid] then + missionCommands.removeItemForGroup(groupid, context.groupConfigMenus[groupid]) + context.groupConfigMenus[groupid] = nil + end + + if not context.groupConfigMenus[groupid] then - if context.groupShopMenus[groupid] then - missionCommands.removeItemForGroup(groupid, context.groupShopMenus[groupid]) - context.groupShopMenus[groupid] = nil - end + local menu = missionCommands.addSubMenuForGroup(groupid, 'Config') + local missionWarningMenu = missionCommands.addSubMenuForGroup(groupid, 'No mission warning', menu) + missionCommands.addCommandForGroup(groupid, 'Activate', missionWarningMenu, Utils.log(context.setNoMissionWarning), context, groupname, true) + missionCommands.addCommandForGroup(groupid, 'Deactivate', missionWarningMenu, Utils.log(context.setNoMissionWarning), context, groupname, false) + + context.groupConfigMenus[groupid] = menu + end + + if context.groupShopMenus[groupid] then + missionCommands.removeItemForGroup(groupid, context.groupShopMenus[groupid]) + context.groupShopMenus[groupid] = nil + end + + if context.groupTgtMenus[groupid] then + missionCommands.removeItemForGroup(groupid, context.groupTgtMenus[groupid]) + context.groupTgtMenus[groupid] = nil + end + + if not context.groupShopMenus[groupid] then - if context.groupTgtMenus[groupid] then - missionCommands.removeItemForGroup(groupid, context.groupTgtMenus[groupid]) - context.groupTgtMenus[groupid] = nil + local menu = missionCommands.addSubMenuForGroup(groupid, 'Command & Control') + missionCommands.addCommandForGroup(groupid, 'Deploy Smoke ['..PlayerTracker.cmdShopPrices[PlayerTracker.cmdShopTypes.smoke]..' CMD]', menu, Utils.log(context.buyCommand), context, groupname, PlayerTracker.cmdShopTypes.smoke) + missionCommands.addCommandForGroup(groupid, 'Hack enemy comms ['..PlayerTracker.cmdShopPrices[PlayerTracker.cmdShopTypes.bribe1]..' CMD]', menu, Utils.log(context.buyCommand), context, groupname, PlayerTracker.cmdShopTypes.bribe1) + missionCommands.addCommandForGroup(groupid, 'Prioritize zone ['..PlayerTracker.cmdShopPrices[PlayerTracker.cmdShopTypes.prio]..' CMD]', menu, Utils.log(context.buyCommand), context, groupname, PlayerTracker.cmdShopTypes.prio) + missionCommands.addCommandForGroup(groupid, 'Bribe enemy officer ['..PlayerTracker.cmdShopPrices[PlayerTracker.cmdShopTypes.bribe2]..' CMD]', menu, Utils.log(context.buyCommand), context, groupname, PlayerTracker.cmdShopTypes.bribe2) + missionCommands.addCommandForGroup(groupid, 'Shell zone with artillery ['..PlayerTracker.cmdShopPrices[PlayerTracker.cmdShopTypes.artillery]..' CMD]', menu, Utils.log(context.buyCommand), context, groupname, PlayerTracker.cmdShopTypes.artillery) + missionCommands.addCommandForGroup(groupid, 'Sabotage enemy zone ['..PlayerTracker.cmdShopPrices[PlayerTracker.cmdShopTypes.sabotage1]..' CMD]', menu, Utils.log(context.buyCommand), context, groupname, PlayerTracker.cmdShopTypes.sabotage1) + + if CommandFunctions.jtac then + missionCommands.addCommandForGroup(groupid, 'Deploy JTAC ['..PlayerTracker.cmdShopPrices[PlayerTracker.cmdShopTypes.jtac]..' CMD]', menu, Utils.log(context.buyCommand), context, groupname, PlayerTracker.cmdShopTypes.jtac) end - if not context.groupShopMenus[groupid] then - - local menu = missionCommands.addSubMenuForGroup(groupid, 'Command & Control') - missionCommands.addCommandForGroup(groupid, 'Deploy Smoke ['..PlayerTracker.cmdShopPrices[PlayerTracker.cmdShopTypes.smoke]..' CMD]', menu, Utils.log(context.buyCommand), context, groupname, PlayerTracker.cmdShopTypes.smoke) - missionCommands.addCommandForGroup(groupid, 'Hack enemy comms ['..PlayerTracker.cmdShopPrices[PlayerTracker.cmdShopTypes.bribe1]..' CMD]', menu, Utils.log(context.buyCommand), context, groupname, PlayerTracker.cmdShopTypes.bribe1) - missionCommands.addCommandForGroup(groupid, 'Prioritize zone ['..PlayerTracker.cmdShopPrices[PlayerTracker.cmdShopTypes.prio]..' CMD]', menu, Utils.log(context.buyCommand), context, groupname, PlayerTracker.cmdShopTypes.prio) - missionCommands.addCommandForGroup(groupid, 'Bribe enemy officer ['..PlayerTracker.cmdShopPrices[PlayerTracker.cmdShopTypes.bribe2]..' CMD]', menu, Utils.log(context.buyCommand), context, groupname, PlayerTracker.cmdShopTypes.bribe2) - - if CommandFunctions.jtac then - missionCommands.addCommandForGroup(groupid, 'Deploy JTAC ['..PlayerTracker.cmdShopPrices[PlayerTracker.cmdShopTypes.jtac]..' CMD]', menu, Utils.log(context.buyCommand), context, groupname, PlayerTracker.cmdShopTypes.jtac) - end - - context.groupShopMenus[groupid] = menu - end - - if context.groupConfigMenus[groupid] then - missionCommands.removeItemForGroup(groupid, context.groupConfigMenus[groupid]) - context.groupConfigMenus[groupid] = nil - end - - if not context.groupConfigMenus[groupid] then - - local menu = missionCommands.addSubMenuForGroup(groupid, 'Config') - local missionWarningMenu = missionCommands.addSubMenuForGroup(groupid, 'No mission warning', menu) - missionCommands.addCommandForGroup(groupid, 'Activate', missionWarningMenu, Utils.log(context.setNoMissionWarning), context, groupname, true) - missionCommands.addCommandForGroup(groupid, 'Deactivate', missionWarningMenu, Utils.log(context.setNoMissionWarning), context, groupname, false) - - context.groupConfigMenus[groupid] = menu - end + context.groupShopMenus[groupid] = menu end end elseif (event.id == world.event.S_EVENT_PLAYER_LEAVE_UNIT or event.id == world.event.S_EVENT_DEAD) and event.initiator and event.initiator.getPlayerName then @@ -6921,8 +7337,8 @@ do elseif itemType== PlayerTracker.cmdShopTypes.prio then self.groupTgtMenus[gr:getID()] = MenuRegistry.showTargetZoneMenu(gr:getID(), "Priority zone", function(params) - BattlefieldManager.overridePriority(2, params.zone, 2) - trigger.action.outTextForGroup(params.groupid, "Blue is concentrating efforts on "..params.zone.name.." for the next hour", 5) + BattlefieldManager.overridePriority(2, params.zone, 4) + trigger.action.outTextForGroup(params.groupid, "Blue is concentrating efforts on "..params.zone.name.." for the next two hours", 5) end, nil, 1) trigger.action.outTextForGroup(gr:getID(), "Select target from radio menu",10) @@ -6967,6 +7383,18 @@ do end, {groupid=gr:getID()}, timer.getTime()+(60*5)) trigger.action.outTextForGroup(gr:getID(), "Bribe has been transfered to enemy officer. Waiting for contact...",20) + elseif itemType == PlayerTracker.cmdShopTypes.artillery then + self.groupTgtMenus[gr:getID()] = MenuRegistry.showTargetZoneMenu(gr:getID(), "Artillery target", function(params) + CommandFunctions.shellZone(params.zone, 50) + end, 1, 1) + + trigger.action.outTextForGroup(gr:getID(), "Select target zone from radio menu",10) + elseif itemType == PlayerTracker.cmdShopTypes.sabotage1 then + self.groupTgtMenus[gr:getID()] = MenuRegistry.showTargetZoneMenu(gr:getID(), "Sabotage target", function(params) + CommandFunctions.sabotageZone(params.zone) + end, 1, 1) + + trigger.action.outTextForGroup(gr:getID(), "Select target zone from radio menu",10) end self.stats[player][PlayerTracker.statTypes.cmd] = self.stats[player][PlayerTracker.statTypes.cmd] - cost @@ -7028,6 +7456,10 @@ do local multiplier = self:getPlayerMultiplier(player) if multiplier then message = message..'\nSurvival XP multiplier: '..string.format("%.2f", multiplier)..'x' + + if stats[PlayerTracker.statTypes.survivalBonus] ~= nil then + message = message..' [SECURED]' + end end local cmd = stats[PlayerTracker.statTypes.cmd] @@ -7043,6 +7475,11 @@ do if tempxp and tempxp > 0 then message = message .. '\nUnclaimed XP: '..tempxp end + + local tempcmd = tstats[PlayerTracker.statTypes.cmd] + if tempcmd and tempcmd > 0 then + message = message .. '\nUnclaimed CMD: '..tempcmd + end end trigger.action.outTextForUnit(v:getID(), message, 10) @@ -7070,60 +7507,61 @@ do function PlayerTracker:periodicSave() timer.scheduleFunction(function(param, time) - local tosave = {} - tosave.stats = param.stats - tosave.config = param.config - - --temp mission stat tracking - tosave.zones = {} - tosave.zones.red = {} - tosave.zones.blue = {} - tosave.zones.neutral = {} - for i,v in pairs(ZoneCommand.getAllZones()) do - if v.side == 1 then - table.insert(tosave.zones.red,v.name) - elseif v.side == 2 then - table.insert(tosave.zones.blue,v.name) - elseif v.side == 0 then - table.insert(tosave.zones.neutral,v.name) - end - end - - tosave.players = {} - for i,v in ipairs(coalition.getPlayers(2)) do - if v and v:isExist() and v.getPlayerName then - table.insert(tosave.players, {name=v:getPlayerName(), unit=v:getDesc().typeName}) - end - end - - --end mission stat tracking - - Utils.saveTable(PlayerTracker.savefile, tosave) - env.info("PlayerTracker - state saved") + param:save() return time+60 end, self, timer.getTime()+60) end + function PlayerTracker:save() + local tosave = {} + tosave.stats = self.stats + tosave.config = self.config + + tosave.zones = {} + tosave.zones.red = {} + tosave.zones.blue = {} + tosave.zones.neutral = {} + for i,v in pairs(ZoneCommand.getAllZones()) do + if v.side == 1 then + table.insert(tosave.zones.red,v.name) + elseif v.side == 2 then + table.insert(tosave.zones.blue,v.name) + elseif v.side == 0 then + table.insert(tosave.zones.neutral,v.name) + end + end + + tosave.players = {} + for i,v in ipairs(coalition.getPlayers(2)) do + if v and v:isExist() and v.getPlayerName then + table.insert(tosave.players, {name=v:getPlayerName(), unit=v:getDesc().typeName}) + end + end + + Utils.saveTable(PlayerTracker.savefile, tosave) + env.info("PlayerTracker - state saved") + end + PlayerTracker.ranks = {} - PlayerTracker.ranks[1] = { rank=1, name='E-1 Airman basic', requiredXP = 0, cmdChance = 0, cmdAward=0} - PlayerTracker.ranks[2] = { rank=2, name='E-2 Airman', requiredXP = 2000, cmdChance = 0, cmdAward=0} - PlayerTracker.ranks[3] = { rank=3, name='E-3 Airman first class', requiredXP = 4500, cmdChance = 0, cmdAward=0} - PlayerTracker.ranks[4] = { rank=4, name='E-4 Senior airman', requiredXP = 7700, cmdChance = 0, cmdAward=0} - PlayerTracker.ranks[5] = { rank=5, name='E-5 Staff sergeant', requiredXP = 11800, cmdChance = 0, cmdAward=0} - PlayerTracker.ranks[6] = { rank=6, name='E-6 Technical sergeant', requiredXP = 17000, cmdChance = 0.01, cmdAward=1} - PlayerTracker.ranks[7] = { rank=7, name='E-7 Master sergeant', requiredXP = 23500, cmdChance = 0.02, cmdAward=1} - PlayerTracker.ranks[8] = { rank=8, name='E-8 Senior master sergeant', requiredXP = 31500, cmdChance = 0.03, cmdAward=1} - PlayerTracker.ranks[9] = { rank=9, name='E-9 Chief master sergeant', requiredXP = 42000, cmdChance = 0.05, cmdAward=1} - PlayerTracker.ranks[10] = { rank=10, name='O-1 Second lieutenant', requiredXP = 52800, cmdChance = 0.08, cmdAward=2} - PlayerTracker.ranks[11] = { rank=11, name='O-2 First lieutenant', requiredXP = 66500, cmdChance = 0.10, cmdAward=2} - PlayerTracker.ranks[12] = { rank=12, name='O-3 Captain', requiredXP = 82500, cmdChance = 0.14, cmdAward=2} - PlayerTracker.ranks[13] = { rank=13, name='O-4 Major', requiredXP = 101000, cmdChance = 0.17, cmdAward=2} - PlayerTracker.ranks[14] = { rank=14, name='O-5 Lieutenant colonel', requiredXP = 122200, cmdChance = 0.22, cmdAward=3} - PlayerTracker.ranks[15] = { rank=15, name='O-6 Colonel', requiredXP = 146300, cmdChance = 0.26, cmdAward=3} - PlayerTracker.ranks[16] = { rank=16, name='O-7 Brigadier general', requiredXP = 173500, cmdChance = 0.32, cmdAward=3} - PlayerTracker.ranks[17] = { rank=17, name='O-8 Major general', requiredXP = 204000, cmdChance = 0.37, cmdAward=4} - PlayerTracker.ranks[18] = { rank=18, name='O-9 Lieutenant general', requiredXP = 238000, cmdChance = 0.43, cmdAward=4} - PlayerTracker.ranks[19] = { rank=19, name='O-10 General', requiredXP = 275700, cmdChance = 0.50, cmdAward=5} + PlayerTracker.ranks[1] = { rank=1, name='E-1 Airman basic', requiredXP = 0, cmdChance = 0, cmdAward=0, cmdTrys=0} + PlayerTracker.ranks[2] = { rank=2, name='E-2 Airman', requiredXP = 2000, cmdChance = 0, cmdAward=0, cmdTrys=0} + PlayerTracker.ranks[3] = { rank=3, name='E-3 Airman first class', requiredXP = 4500, cmdChance = 0, cmdAward=0, cmdTrys=0} + PlayerTracker.ranks[4] = { rank=4, name='E-4 Senior airman', requiredXP = 7700, cmdChance = 0, cmdAward=0, cmdTrys=0} + PlayerTracker.ranks[5] = { rank=5, name='E-5 Staff sergeant', requiredXP = 11800, cmdChance = 0.01, cmdAward=1, cmdTrys=1} + PlayerTracker.ranks[6] = { rank=6, name='E-6 Technical sergeant', requiredXP = 17000, cmdChance = 0.01, cmdAward=5, cmdTrys=10} + PlayerTracker.ranks[7] = { rank=7, name='E-7 Master sergeant', requiredXP = 23500, cmdChance = 0.03, cmdAward=5, cmdTrys=10} + PlayerTracker.ranks[8] = { rank=8, name='E-8 Senior master sergeant', requiredXP = 31500, cmdChance = 0.06, cmdAward=10, cmdTrys=10} + PlayerTracker.ranks[9] = { rank=9, name='E-9 Chief master sergeant', requiredXP = 42000, cmdChance = 0.10, cmdAward=10, cmdTrys=10} + PlayerTracker.ranks[10] = { rank=10, name='O-1 Second lieutenant', requiredXP = 52800, cmdChance = 0.14, cmdAward=20, cmdTrys=15} + PlayerTracker.ranks[11] = { rank=11, name='O-2 First lieutenant', requiredXP = 66500, cmdChance = 0.20, cmdAward=20, cmdTrys=15} + PlayerTracker.ranks[12] = { rank=12, name='O-3 Captain', requiredXP = 82500, cmdChance = 0.27, cmdAward=25, cmdTrys=15, allowCarrierSupport=true} + PlayerTracker.ranks[13] = { rank=13, name='O-4 Major', requiredXP = 101000, cmdChance = 0.34, cmdAward=25, cmdTrys=20, allowCarrierSupport=true} + PlayerTracker.ranks[14] = { rank=14, name='O-5 Lieutenant colonel', requiredXP = 122200, cmdChance = 0.43, cmdAward=25, cmdTrys=20, allowCarrierSupport=true} + PlayerTracker.ranks[15] = { rank=15, name='O-6 Colonel', requiredXP = 146300, cmdChance = 0.52, cmdAward=30, cmdTrys=20, allowCarrierSupport=true} + PlayerTracker.ranks[16] = { rank=16, name='O-7 Brigadier general', requiredXP = 173500, cmdChance = 0.63, cmdAward=35, cmdTrys=25, allowCarrierSupport=true, allowCarrierCommand=true} + PlayerTracker.ranks[17] = { rank=17, name='O-8 Major general', requiredXP = 204000, cmdChance = 0.74, cmdAward=40, cmdTrys=25, allowCarrierSupport=true, allowCarrierCommand=true} + PlayerTracker.ranks[18] = { rank=18, name='O-9 Lieutenant general', requiredXP = 238000, cmdChance = 0.87, cmdAward=45, cmdTrys=25, allowCarrierSupport=true, allowCarrierCommand=true} + PlayerTracker.ranks[19] = { rank=19, name='O-10 General', requiredXP = 275700, cmdChance = 0.95, cmdAward=50, cmdTrys=30, allowCarrierSupport=true, allowCarrierCommand=true} function PlayerTracker:getPlayerRank(playername) if self.stats[playername] then @@ -7142,6 +7580,25 @@ do return 1.0 end + function PlayerTracker:getPlayerMinutes(playername) + if self.playerEarningMultiplier[playername] then + return self.playerEarningMultiplier[playername].minutes + end + + return 0 + end + + function PlayerTracker.minutesToMultiplier(minutes) + local multi = 1.0 + if minutes > 10 and minutes <= 60 then + multi = 1.0 + ((minutes-10)*0.05) + elseif minutes > 60 then + multi = 1.0 + (50*0.05) + ((minutes - 60)*0.025) + end + + return math.min(multi, 5.0) + end + function PlayerTracker:getRank(xp) local rank = nil local nextRank = nil @@ -7234,7 +7691,7 @@ do env.info('MissionTargetRegistry - bai target removed '..target.name) end - MissionTargetRegistry.strikeTargetExpireTime = 30*60 + MissionTargetRegistry.strikeTargetExpireTime = 60*60 MissionTargetRegistry.strikeTargets = {} function MissionTargetRegistry.addStrikeTarget(target, zone, isDeep) @@ -7285,6 +7742,26 @@ do return targets[dice] end + function MissionTargetRegistry.getAllStrikeTargets(coalition) + local targets = {} + for i,v in pairs(MissionTargetRegistry.strikeTargets) do + if v.data.side == coalition then + local tgt = StaticObject.getByName(v.data.name) + if not tgt then tgt = Group.getByName(v.data.name) end + + if not tgt or not tgt:isExist() then + MissionTargetRegistry.removeStrikeTarget(v) + elseif timer.getAbsTime() - v.addedTime > MissionTargetRegistry.strikeTargetExpireTime then + MissionTargetRegistry.removeStrikeTarget(v) + elseif v.isDeep == isDeep then + table.insert(targets, v) + end + end + end + + return targets + end + function MissionTargetRegistry.removeStrikeTarget(target) MissionTargetRegistry.strikeTargets[target.data.name] = nil env.info('MissionTargetRegistry - strike target removed '..target.data.name) @@ -7426,6 +7903,19 @@ do return obj end + function PersistenceManager:restore() + self:restoreZones() + self:restoreAIMissions() + self:restoreBattlefield() + self:restoreCsar() + self:restoreSquads() + self:restoreCarriers() + + timer.scheduleFunction(function(param) + param:restoreStrikeTargets() + end, self, timer.getTime()+5) + end + function PersistenceManager:restoreZones() local save = self.data for i,v in pairs(save.zones) do @@ -7484,6 +7974,7 @@ do end z:refreshText() + z:refreshSpawnBlocking() end end @@ -7579,6 +8070,90 @@ do end end + function PersistenceManager:restoreStrikeTargets() + local save = self.data + if save.strikeTargets then + for i,v in pairs(save.strikeTargets) do + local zone = ZoneCommand.getZoneByName(v.zname) + local product = zone:getProductByName(v.pname) + + MissionTargetRegistry.strikeTargets[i] = { + data = product, + zone = zone, + addedTime = timer.getAbsTime() - v.elapsedTime, + isDeep = isDeep + } + end + end + end + + function PersistenceManager:restoreCarriers() + local save = self.data + if save.carriers then + for i,v in pairs(save.carriers) do + local carrier = CarrierCommand.getCarrierByName(v.name) + if carrier then + carrier.resource = math.min(v.resource, carrier.maxResource) + carrier:refreshSpawnBlocking() + + local unit = Unit.getByName(v.name) + if unit then + if not v.isAlive then + unit:destroy() + else + local vars = { + groupName = unit:getGroup():getName(), + point = v.position.p, + action = 'teleport', + heading = math.atan2(v.position.x.z, v.position.x.x), + initTasks = false, + route = {} + } + + mist.teleportToPoint(vars) + + timer.scheduleFunction(function(param, time) + param:setupRadios() + end, carrier, timer.getTime()+3) + + carrier.navigation.waypoints = v.navigation.waypoints + carrier.navigation.currentWaypoint = nil + carrier.navigation.nextWaypoint = v.navigation.currentWaypoint + carrier.navigation.loop = v.navigation.loop + + if v.supportFlightStates then + for sfsName, sfsData in pairs(v.supportFlightStates) do + local sflight = carrier.supportFlights[sfsName] + if sflight then + if sfsData.state == CarrierCommand.supportStates.inair and sfsData.targetName and sfsData.position then + local zn = ZoneCommand.getZoneByName(sfsData.targetName) + if not zn then + zn = CarrierCommand.getCarrierByName(sfsData.targetName) + end + + if zn then + CarrierCommand.spawnSupport(sflight, zn, sfsData) + end + elseif sfsData.state == CarrierCommand.supportStates.takeoff and sfsData.targetName then + local zn = ZoneCommand.getZoneByName(sfsData.targetName) + if not zn then + zn = CarrierCommand.getCarrierByName(sfsData.targetName) + end + + if zn then + CarrierCommand.spawnSupport(sflight, zn) + end + end + end + end + end + end + end + end + end + end + end + function PersistenceManager:canRestore() if self.data == nil then return false end @@ -7737,6 +8312,49 @@ do } end + tosave.carriers = {} + for cname,cdata in pairs(CarrierCommand.getAllCarriers()) do + local unit = Unit.getByName(cdata.name) + + tosave.carriers[cname] = { + name = cdata.name, + resource = cdata.resource, + isAlive = unit ~= nil, + position = unit:getPosition(), + navigation = cdata.navigation, + supportFlightStates = {} + } + + for spname, spdata in pairs(cdata.supportFlights) do + tosave.carriers[cname].supportFlightStates[spname] = { + name = spdata.name, + state = spdata.state, + lastStateDuration = timer.getAbsTime() - spdata.lastStateTime, + returning = spdata.returning + } + + if spdata.target then + tosave.carriers[cname].supportFlightStates[spname].targetName = spdata.target.name + end + + if spdata.state == CarrierCommand.supportStates.inair then + local spgr = Group.getByName(spname) + if spgr and spgr:isExist() and spgr:getSize()>0 then + local spun = spgr:getUnit(1) + if spun and spun:isExist() then + tosave.carriers[cname].supportFlightStates[spname].position = spun:getPoint() + tosave.carriers[cname].supportFlightStates[spname].heading = math.atan2(spun:getPosition().x.z, spun:getPosition().x.x) + end + end + end + end + end + + tosave.strikeTargets = {} + for i,v in pairs(MissionTargetRegistry.strikeTargets) do + tosave.strikeTargets[i] = { pname = v.data.name, zname = v.zone.name, elapsedTime = timer.getAbsTime() - v.addedTime, isDeep = v.isDeep } + end + Utils.saveTable(self.path, tosave) end end @@ -8130,13 +8748,15 @@ do local units = {} for i,v in pairs(zone.built) do local g = Group.getByName(v.name) - if g then + if g and g:isExist() then for i2,v2 in ipairs(g:getUnits()) do - table.insert(units, v2) + if v2:isExist() then + table.insert(units, v2) + end end else local s = StaticObject.getByName(v.name) - if s then + if s and s:isExist() then table.insert(units, s) end end @@ -8156,6 +8776,100 @@ do trigger.action.smoke(pos, 1) end end + + function CommandFunctions.sabotageZone(zone) + trigger.action.outText("Saboteurs have been dispatched to "..zone.name, 10) + local delay = math.random(5*60, 7*60) + timer.scheduleFunction(function(param, time) + if math.random() < 0.1 then + trigger.action.outText("Saboteurs have been caught by the enemy before they could complete their mission", 10) + return + end + + local zone = param.zone + local units = {} + for i,v in pairs(zone.built) do + if v.type == 'upgrade' then + local s = StaticObject.getByName(v.name) + if s and s:isExist() then + table.insert(units, s) + end + end + end + + if #units > 0 then + local selected = units[math.random(1,#units)] + + timer.scheduleFunction(function(p2, t2) + if p2.count > 0 then + p2.count = p2.count - 1 + local offsetPos = { + x = p2.pos.x + math.random(-25,25), + y = p2.pos.y, + z = p2.pos.z + math.random(-25,25) + } + + offsetPos.y = land.getHeight({x = offsetPos.x, y = offsetPos.z}) + trigger.action.explosion(offsetPos, 30) + return t2 + 0.05 + (math.random()) + else + trigger.action.explosion(p2.pos, 2000) + end + end, {count = 3, pos = selected:getPoint()}, timer.getTime()+0.5) + + trigger.action.outText("Saboteurs have succesfully triggered explosions at "..zone.name, 10) + end + end, { zone = zone }, timer.getTime()+delay) + end + + function CommandFunctions.shellZone(zone, count) + local minutes = math.random(3,7) + local seconds = math.random(-30,30) + local delay = (minutes*60)+seconds + trigger.action.outText("Artillery preparing to fire on "..zone.name.." ETA: "..minutes.." minutes", 10) + + local positions = {} + for i,v in pairs(zone.built) do + local g = Group.getByName(v.name) + if g and g:isExist() then + for i2,v2 in ipairs(g:getUnits()) do + if v2:isExist() then + table.insert(positions, v2:getPoint()) + end + end + else + local s = StaticObject.getByName(v.name) + if s and s:isExist() then + table.insert(positions, s:getPoint()) + end + end + end + + timer.scheduleFunction(function(param, time) + trigger.action.outText("Artillery firing on "..param.zone.name.." ETA: 30 seconds", 10) + end, {zone = zone}, timer.getTime()+delay-30) + + timer.scheduleFunction(function(param, time) + param.count = param.count - 1 + + local selected = param.positions[math.random(1,#param.positions)] + local offsetPos = { + x = selected.x + math.random(-50,50), + y = selected.y, + z = selected.z + math.random(-50,50) + } + + offsetPos.y = land.getHeight({x = offsetPos.x, y = offsetPos.z}) + + trigger.action.explosion(offsetPos, 20) + + if param.count > 0 then + return time+0.05+(math.random()*2) + else + trigger.action.outText("Artillery finished firing on "..param.zone.name, 10) + end + end, { positions = positions, count = count, zone = zone}, timer.getTime()+delay) + end end -----------------[[ END OF CommandFunctions.lua ]]----------------- @@ -8362,7 +9076,7 @@ do isStructure = true end - if tgtunit then + if tgtunit and tgtunit:isExist() then local pnt = tgtunit:getPoint() local tgttype = "Unidentified" if isStructure then @@ -8413,7 +9127,7 @@ do end if self.timerReference then - mist.removeFunction(self.timerReference) + timer.removeFunction(self.timerReference) self.timerReference = nil end @@ -8427,20 +9141,20 @@ do function JTAC:searchTarget() local gr = Group.getByName(self.name) - if gr then + if gr and gr:isExist() then if self.tgtzone and self.tgtzone.side~=0 and self.tgtzone.side~=gr:getCoalition() then local viabletgts = {} for i,v in pairs(self.tgtzone.built) do local tgtgr = Group.getByName(v.name) - if tgtgr and tgtgr:getSize()>0 then + if tgtgr and tgtgr:isExist() and tgtgr:getSize()>0 then for i2,v2 in ipairs(tgtgr:getUnits()) do - if v2:getLife()>=1 then + if v2:isExist() and v2:getLife()>=1 then table.insert(viabletgts, v2) end end else tgtgr = StaticObject.getByName(v.name) - if tgtgr then + if tgtgr and tgtgr:isExist() then table.insert(viabletgts, tgtgr) end end @@ -8483,11 +9197,11 @@ do function JTAC:searchIfNoTarget() if Group.getByName(self.name) then - if not self.target or (not Unit.getByName(self.target) and not StaticObject.getByName(self.target)) then + if not self.target then self:searchTarget() - elseif self.target then + else local un = Unit.getByName(self.target) - if un then + if un and un:isExist() then if un:getLife()>=1 then self:setTarget(un) else @@ -8495,9 +9209,11 @@ do end else local st = StaticObject.getByName(self.target) - if st then + if st and st:isExist() then self:setTarget(st) - end + else + self:searchTarget() + end end end else @@ -8515,10 +9231,15 @@ do vars.point = {x=p.x, y=5000, z = p.z} mist.teleportToPoint(vars) - mist.scheduleFunction(self.setOrbit, {self, self.tgtzone.zone, p}, timer.getTime()+1) + timer.scheduleFunction(function(param,time) + param.context:setOrbit(param.target, param.point) + end, {context = self, target = self.tgtzone.zone, point = p}, timer.getTime()+1) if not self.timerReference then - self.timerReference = mist.scheduleFunction(self.searchIfNoTarget, {self}, timer.getTime()+5, 5) + self.timerReference = timer.scheduleFunction(function(param, time) + param:searchIfNoTarget() + return time+5 + end, self, timer.getTime()+5) end end @@ -8553,6 +9274,880 @@ end +-----------------[[ CarrierMap.lua ]]----------------- + +CarrierMap = {} +do + CarrierMap.currentIndex = 15000 + function CarrierMap:new(zoneList) + + local obj = {} + obj.zones = {} + + for i,v in ipairs(zoneList) do + local zn = CustomZone:getByName(v) + + local id = CarrierMap.currentIndex + CarrierMap.currentIndex = CarrierMap.currentIndex + 1 + + zn:draw(id, {1,1,1,0.2}, {1,1,1,0.2}) + obj.zones[v] = {zone = zn, waypoints = {}} + + for subi=1,1000,1 do + local subname = v..'-'..subi + if CustomZone:getByName(subname) then + table.insert(obj.zones[v].waypoints, subname) + else + break + end + end + + id = CarrierMap.currentIndex + CarrierMap.currentIndex = CarrierMap.currentIndex + 1 + + trigger.action.textToAll(-1, id , zn.point, {0,0,0,0.8}, {1,1,1,0}, 15, true, v) + for i,wps in ipairs(obj.zones[v].waypoints) do + id = CarrierMap.currentIndex + CarrierMap.currentIndex = CarrierMap.currentIndex + 1 + local point = CustomZone:getByName(wps).point + trigger.action.textToAll(-1, id, point, {0,0,0,0.8}, {1,1,1,0}, 10, true, wps) + end + end + + setmetatable(obj, self) + self.__index = self + + return obj + end + + function CarrierMap:getNavMap() + local map = {} + for nm, zn in pairs(self.zones) do + table.insert(map, {name = zn.zone.name, waypoints = zn.waypoints}) + end + + table.sort(map, function(a,b) return a.name < b.name end) + return map + end +end + +-----------------[[ END OF CarrierMap.lua ]]----------------- + + + +-----------------[[ CarrierCommand.lua ]]----------------- + +CarrierCommand = {} +do + CarrierCommand.allCarriers = {} + CarrierCommand.currentIndex = 6000 + CarrierCommand.isCarrier = true + + CarrierCommand.supportTypes = { + strike = 'Strike', + cap = 'CAP', + awacs = 'AWACS', + tanker = 'Tanker', + transport = 'Transport' + } + + CarrierCommand.supportStates = { + takeoff = 'takeoff', + inair = 'inair', + landed = 'landed', + none = 'none' + } + + CarrierCommand.blockedDespawnTime = 10*60 + CarrierCommand.recoveryReduction = 0.8 + CarrierCommand.landedDespawnTime = 10 + + function CarrierCommand:new(name, range, navmap, radioConfig, maxResource) + local unit = Unit.getByName(name) + if not unit then return end + + local obj = {} + obj.name = name + obj.range = range + obj.side = unit:getCoalition() + obj.resource = maxResource or 30000 + obj.maxResource = maxResource or 30000 + obj.spendTreshold = 500 + obj.revealTime = 0 + obj.isHeloSpawn = true + obj.isPlaneSpawn = true + obj.supportFlights = {} + + obj.navigation = { + currentWaypoint = nil, + waypoints = {}, + loop = true + } + + obj.navmap = navmap + + obj.tacan = radioConfig.tacan + obj.icls = radioConfig.icls + obj.acls = radioConfig.acls + obj.link4 = radioConfig.link4 + obj.radio = radioConfig.radio + + obj.spawns = {} + for i,v in pairs(mist.DBs.groupsByName) do + if v.units[1].skill == 'Client' then + local pos3d = { + x = v.units[1].point.x, + y = 0, + z = v.units[1].point.y + } + + if Utils.isInCircle(pos3d, unit:getPoint(), obj.range)then + table.insert(obj.spawns, {name=i}) + end + end + end + + obj.index = CarrierCommand.currentIndex + CarrierCommand.currentIndex = CarrierCommand.currentIndex + 1 + + local point = unit:getPoint() + + local color = {0.7,0.7,0.7,0.3} + if obj.side == 1 then + color = {1,0,0,0.3} + elseif obj.side == 2 then + color = {0,0,1,0.3} + end + + trigger.action.circleToAll(-1,3000+obj.index,point, obj.range, color, color, 1) + + point.z = point.z + obj.range + trigger.action.textToAll(-1,2000+obj.index, point, {0,0,0,0.8}, {1,1,1,0.5}, 15, true, '') + + setmetatable(obj, self) + self.__index = self + + obj:start() + obj:refreshText() + obj:refreshSpawnBlocking() + CarrierCommand.allCarriers[obj.name] = obj + return obj + end + + function CarrierCommand:setupRadios() + local unit = Unit.getByName(self.name) + TaskExtensions.setupCarrier(unit, self.icls, self.acls, self.tacan, self.link4, self.radio) + end + + function CarrierCommand:start() + self:setupRadios() + + timer.scheduleFunction(function(param, time) + local self = param.context + local unit = Unit.getByName(self.name) + if not unit then + self:clearDrawings() + return + end + + self:updateNavigation() + self:updateSupports() + self:refreshText() + return time+10 + end, {context = self}, timer.getTime()+1) + end + + function CarrierCommand:clearDrawings() + if not self.cleared then + trigger.action.removeMark(2000+self.index) + trigger.action.removeMark(3000+self.index) + self.cleared = true + end + end + + function CarrierCommand:updateSupports() + for _, data in pairs(self.supportFlights) do + self:processAir(data) + end + end + + local function setState(group, state) + group.state = state + group.lastStateTime = timer.getAbsTime() + end + + local function isAttack(group) + if group.type == CarrierCommand.supportTypes.cap then return true end + if group.type == CarrierCommand.supportTypes.strike then return true end + end + + local function hasWeapons(group) + for _,un in ipairs(group:getUnits()) do + local wps = un:getAmmo() + if wps then + for _,w in ipairs(wps) do + if w.desc.category ~= 0 and w.count > 0 then + return true + end + end + end + end + end + + function CarrierCommand:processAir(group) + local carrier = Unit.getByName(self.name) + if not carrier or not carrier:isExist() then return end + + local gr = Group.getByName(group.name) + if not gr or not gr:isExist() then + if group.state ~= CarrierCommand.supportStates.none then + setState(group, CarrierCommand.supportStates.none) + group.returning = false + env.info('CarrierCommand: processAir ['..group.name..'] does not exist state=none') + end + return + end + + if gr:getSize() == 0 then + gr:destroy() + setState(group, CarrierCommand.supportStates.none) + group.returning = false + env.info('CarrierCommand: processAir ['..group.name..'] has no members state=none') + return + end + + if group.state == CarrierCommand.supportStates.none then + setState(group, CarrierCommand.supportStates.takeoff) + env.info('CarrierCommand: processAir ['..group.name..'] started existing state=takeoff') + elseif group.state == CarrierCommand.supportStates.takeoff then + if timer.getAbsTime() - group.lastStateTime > CarrierCommand.blockedDespawnTime then + if gr and gr:getSize()>0 and gr:getUnit(1):isExist() then + local frUnit = gr:getUnit(1) + local cz = CarrierCommand.getCarrierOfUnit(frUnit:getName()) + if Utils.allGroupIsLanded(gr, cz ~= nil) then + env.info('CarrierCommand: processAir ['..group.name..'] is blocked, despawning') + local frUnit = gr:getUnit(1) + if frUnit then + local firstUnit = frUnit:getName() + local z = ZoneCommand.getZoneOfUnit(firstUnit) + if not z then + z = CarrierCommand.getCarrierOfUnit(firstUnit) + end + if z then + z:addResource(group.cost) + env.info('CarrierCommand: processAir ['..z.name..'] has recovered ['..group.cost..'] from ['..group.name..']') + end + end + + gr:destroy() + setState(group, CarrierCommand.supportStates.none) + group.returning = false + env.info('CarrierCommand: processAir ['..group.name..'] has been removed due to being blocked state=none') + return + end + end + elseif gr and Utils.someOfGroupInAir(gr) then + env.info('CarrierCommand: processAir ['..group.name..'] is in the air state=inair') + setState(group, CarrierCommand.supportStates.inair) + end + elseif group.state == CarrierCommand.supportStates.inair then + if gr and gr:getSize()>0 and gr:getUnit(1) and gr:getUnit(1):isExist() then + local frUnit = gr:getUnit(1) + local cz = CarrierCommand.getCarrierOfUnit(frUnit:getName()) + if Utils.allGroupIsLanded(gr, cz ~= nil) then + env.info('CarrierCommand: processAir ['..group.name..'] has landed state=landed') + setState(group, CarrierCommand.supportStates.landed) + + local unit = gr:getUnit(1) + if unit then + local firstUnit = unit:getName() + local z = ZoneCommand.getZoneOfUnit(firstUnit) + if not z then + z = CarrierCommand.getCarrierOfUnit(firstUnit) + end + + if group.type == CarrierCommand.supportTypes.transport then + if z then + z:capture(gr:getCoalition()) + z:addResource(group.cost) + env.info('CarrierCommand: processAir ['..group.name..'] has supplied ['..z.name..'] with ['..group.cost..']') + end + else + if z and z.side == gr:getCoalition() then + local percentSurvived = gr:getSize()/gr:getInitialSize() + local torecover = math.floor(group.cost * percentSurvived * CarrierCommand.recoveryReduction) + z:addResource(torecover) + env.info('CarrierCommand: processAir ['..z.name..'] has recovered ['..torecover..'] from ['..group.name..']') + end + end + else + env.info('CarrierCommand: processAir ['..group.name..'] size ['..gr:getSize()..'] has no unit 1') + end + else + if isAttack(group) and not group.returning then + if not hasWeapons(gr) then + env.info('CarrierCommand: processAir ['..group.name..'] size ['..gr:getSize()..'] has no weapons outside of shells') + group.returning = true + + local point = carrier:getPoint() + TaskExtensions.landAtAirfield(gr, {x=point.x, y=point.z}) + local cnt = gr:getController() + cnt:setOption(0,4) -- force ai hold fire + cnt:setOption(1, 4) -- force reaction on threat to allow abort + end + elseif group.type == CarrierCommand.supportTypes.transport then + if not group.returning and group.target and group.target.side ~= self.side and group.target.side ~= 0 then + group.returning = true + local point = carrier:getPoint() + TaskExtensions.landAtPointFromAir(gr, {x=point.x, y=point.z}, group.altitude) + env.info('CarrierCommand: processAir ['..group.name..'] returning home due to invalid target') + end + end + end + end + elseif group.state == CarrierCommand.supportStates.landed then + if timer.getAbsTime() - group.lastStateTime > CarrierCommand.landedDespawnTime then + if gr then + gr:destroy() + setState(group, CarrierCommand.supportStates.none) + group.returning = false + env.info('CarrierCommand: processAir ['..group.name..'] despawned after landing state=none') + return true + end + end + end + end + + function CarrierCommand:setWaypoints(wplist) + self.navigation.waypoints = wplist + self.navigation.currentWaypoint = nil + self.navigation.nextWaypoint = 1 + self.navigation.loop = #wplist > 1 + end + + function CarrierCommand:updateNavigation() + local unit = Unit.getByName(self.name) + + if self.navigation.nextWaypoint then + local dist = 0 + if self.navigation.currentWaypoint then + local tgzn = self.navigation.waypoints[self.navigation.currentWaypoint] + local point = CustomZone:getByName(tgzn).point + dist = mist.utils.get2DDist(unit:getPoint(), point) + end + + if dist<2000 then + self.navigation.currentWaypoint = self.navigation.nextWaypoint + + local tgzn = self.navigation.waypoints[self.navigation.currentWaypoint] + local point = CustomZone:getByName(tgzn).point + env.info("CarrierCommand - sending "..self.name.." to "..tgzn.." x"..point.x.." z"..point.z) + TaskExtensions.carrierGoToPos(unit:getGroup(), point) + + if self.navigation.loop then + self.navigation.nextWaypoint = self.navigation.nextWaypoint + 1 + if self.navigation.nextWaypoint > #self.navigation.waypoints then + self.navigation.nextWaypoint = 1 + end + else + self.navigation.nextWaypoint = nil + end + end + else + local dist = 9999999 + if self.navigation.currentWaypoint then + local tgzn = self.navigation.waypoints[self.navigation.currentWaypoint] + local point = CustomZone:getByName(tgzn).point + dist = mist.utils.get2DDist(unit:getPoint(), point) + end + + if dist<2000 then + env.info("CarrierCommand - "..self.name.." stopping after reached waypoint") + TaskExtensions.stopCarrier(unit:getGroup()) + self.navigation.currentWaypoint = nil + end + end + end + + function CarrierCommand:addSupportFlight(name, cost, type, data) + self.supportFlights[name] = { + name = name, + cost = cost, + type = type, + target = nil, + state = CarrierCommand.supportStates.none, + lastStateTime = timer.getAbsTime(), + carrier = self + } + + for i,v in pairs(data) do + self.supportFlights[name][i] = v + end + + local gr = Group.getByName(name) + if gr then gr:destroy() end + end + + function CarrierCommand:callSupport(data, groupname) + local playerGroup = Group.getByName(groupname) + if not playerGroup then return end + + if Group.getByName(data.name) and (timer.getAbsTime() - data.lastStateTime < 60*60) then + trigger.action.outTextForGroup(playerGroup:getID(), data.name..' tasking is not available at this time.', 10) + return + end + + if self.resource <= data.cost then + trigger.action.outTextForGroup(playerGroup:getID(), self.name..' does not have enough resources to deploy '..data.name, 10) + return + end + + local targetCoalition = nil + local minDistToFront = nil + local includeCarriers = nil + + if data.type == CarrierCommand.supportTypes.strike then + targetCoalition = 1 + minDistToFront = 1 + elseif data.type == CarrierCommand.supportTypes.cap then + minDistToFront = 1 + includeCarriers = true + elseif data.type == CarrierCommand.supportTypes.awacs then + targetCoalition = 2 + includeCarriers = true + elseif data.type == CarrierCommand.supportTypes.tanker then + targetCoalition = 2 + includeCarriers = true + elseif data.type == CarrierCommand.supportTypes.transport then + targetCoalition = {0,2} + end + + MenuRegistry.showTargetZoneMenu(playerGroup:getID(), "Select "..data.name..'('..data.type..") target", function(params) + CarrierCommand.spawnSupport(params.data, params.zone) + trigger.action.outTextForGroup(params.groupid, params.data.name..'('..params.data.type..') heading to '..params.zone.name, 10) + end, targetCoalition, minDistToFront, data, includeCarriers) + + self:removeResource(data.cost) + trigger.action.outTextForGroup(playerGroup:getID(), 'Select target for '..data.name..' ('..data.type..') from radio menu.', 20) + end + + local function getDefaultPos(savedData) + local action = 'Turning Point' + local speed = 250 + + local vars = { + groupName = savedData.name, + point = savedData.position, + action = 'respawn', + heading = savedData.heading, + initTasks = false, + route = { + [1] = { + alt = savedData.position.y, + type = 'Turning Point', + action = action, + alt_type = 'BARO', + x = savedData.position.x, + y = savedData.position.z, + speed = speed + } + } + } + + return vars + end + + function CarrierCommand.spawnSupport(data, target, saveData) + data.target = target + + if saveData then + mist.teleportToPoint(getDefaultPos(saveData)) + data.state = saveData.state + data.lastStateTime = timer.getAbsTime() - saveData.lastStateDuration + data.returning = saveData.returning + else + mist.respawnGroup(data.name, true) + end + + if data.type == CarrierCommand.supportTypes.strike then + CarrierCommand.dispatchStrike(data, saveData~=nil) + elseif data.type == CarrierCommand.supportTypes.cap then + CarrierCommand.dispatchCap(data, saveData~=nil) + elseif data.type == CarrierCommand.supportTypes.awacs then + CarrierCommand.dispatchAwacs(data, saveData~=nil) + elseif data.type == CarrierCommand.supportTypes.tanker then + CarrierCommand.dispatchTanker(data, saveData~=nil) + elseif data.type == CarrierCommand.supportTypes.transport then + CarrierCommand.dispatchTransport(data, saveData~=nil) + end + end + + function CarrierCommand.dispatchStrike(data, isReactivated) + timer.scheduleFunction(function(param) + local gr = Group.getByName(param.data.name) + local homePos = nil + local carrier = Unit.getByName(param.data.carrier.name) + if carrier and isReactivated then + homePos = { homePos = carrier:getPoint() } + end + env.info('CarrierCommand - sending '..param.data.name..' to '..param.data.target.name) + + local targets = {} + for i,v in pairs(param.data.target.built) do + if v.type == 'upgrade' and v.side ~= gr:getCoalition() then + local tg = TaskExtensions.getTargetPos(v.name) + table.insert(targets, tg) + end + end + + if #targets == 0 then + gr:destroy() + return + end + + local choice = targets[math.random(1, #targets)] + TaskExtensions.executePinpointStrikeMission(gr, choice, AI.Task.WeaponExpend.ALL, param.data.altitude, homePos, carrier:getID()) + end, {data = data}, timer.getTime()+1) + end + + function CarrierCommand.dispatchCap(data, isReactivated) + timer.scheduleFunction(function(param) + local gr = Group.getByName(param.data.name) + + local homePos = nil + local carrier = Unit.getByName(param.data.carrier.name) + if carrier and isReactivated then + homePos = { homePos = carrier:getPoint() } + end + + local point = nil + if param.data.target.isCarrier then + point = Unit.getByName(param.data.target.name):getPoint() + else + point = trigger.misc.getZone(param.data.target.name).point + end + + TaskExtensions.executePatrolMission(gr, point, param.data.altitude, param.data.range, homePos, carrier:getID()) + end, {data = data}, timer.getTime()+1) + end + + function CarrierCommand.dispatchAwacs(data, isReactivated) + timer.scheduleFunction(function(param) + local gr = Group.getByName(param.data.name) + + local homePos = nil + local carrier = Unit.getByName(param.data.carrier.name) + if carrier and isReactivated then + homePos = { homePos = carrier:getPoint() } + end + + local un = gr:getUnit(1) + if un then + local callsign = un:getCallsign() + RadioFrequencyTracker.registerRadio(param.data.name, '[AWACS] '..callsign, param.data.freq..' AM') + end + + local point = nil + if param.data.target.isCarrier then + point = Unit.getByName(param.data.target.name):getPoint() + else + point = trigger.misc.getZone(param.data.target.name).point + end + + TaskExtensions.executeAwacsMission(gr, point, param.data.altitude, param.data.freq, homePos, carrier:getID()) + end, {data = data}, timer.getTime()+1) + end + + function CarrierCommand.dispatchTanker(data, isReactivated) + timer.scheduleFunction(function(param) + local gr = Group.getByName(param.data.name) + + local homePos = nil + local carrier = Unit.getByName(param.data.carrier.name) + if carrier and isReactivated then + homePos = { homePos = carrier:getPoint() } + end + + local un = gr:getUnit(1) + if un then + local callsign = un:getCallsign() + RadioFrequencyTracker.registerRadio(param.data.name, '[Tanker(Drogue)] '..callsign, param.data.freq..' AM | TCN '..param.data.tacan..'X') + end + + local point = nil + if param.data.target.isCarrier then + point = Unit.getByName(param.data.target.name):getPoint() + else + point = trigger.misc.getZone(param.data.target.name).point + end + + TaskExtensions.executeTankerMission(gr, point, param.data.altitude, param.data.freq, param.data.tacan, homePos, carrier:getID()) + end, {data = data}, timer.getTime()+1) + end + + function CarrierCommand.dispatchTransport(data, isReactivated) + timer.scheduleFunction(function(param) + local gr = Group.getByName(param.data.name) + + local supplyPoint = trigger.misc.getZone(param.data.target.name..'-hsp') + if not supplyPoint then + supplyPoint = trigger.misc.getZone(param.data.target.name) + end + + local point = { x=supplyPoint.point.x, y = supplyPoint.point.z} + TaskExtensions.landAtPoint(gr, point, param.data.altitude, true) + end, {data = data}, timer.getTime()+1) + end + + function CarrierCommand:showInformation(groupname) + local gr = Group.getByName(groupname) + if gr then + local msg = '['..self.name..']' + if self.radio then msg = msg..'\n Radio: '..string.format('%.3f',self.radio/1000000)..' AM' end + if self.tacan then msg = msg..'\n TACAN: '..self.tacan.channel..'X ('..self.tacan.callsign..')' end + if self.link4 then msg = msg..'\n Link4: '..string.format('%.3f',self.link4/1000000) end + if self.icls then msg = msg..'\n ICLS: '..self.icls end + + if Utils.getTableSize(self.supportFlights) > 0 then + local flights = {} + for _, data in pairs(self.supportFlights) do + if (data.state == CarrierCommand.supportStates.none or (timer.getAbsTime()-data.lastStateTime >= 60*60)) and data.cost <= self.resource then + table.insert(flights, data) + end + end + + table.sort(flights, function(a,b) return a.name 0 then + msg = msg..'\n\n Available for tasking:' + for _,data in ipairs(flights) do + msg = msg..'\n '..data.name..' ('..data.type..') ['..data.cost..']' + end + end + end + + trigger.action.outTextForGroup(gr:getID(), msg, 20) + end + end + + function CarrierCommand:addResource(amount) + self.resource = self.resource+amount + self.resource = math.floor(math.min(self.resource, self.maxResource)) + self:refreshSpawnBlocking() + self:refreshText() + end + + function CarrierCommand:removeResource(amount) + self.resource = self.resource-amount + self.resource = math.floor(math.max(self.resource, 0)) + self:refreshSpawnBlocking() + self:refreshText() + end + + function CarrierCommand:refreshSpawnBlocking() + for _,v in ipairs(self.spawns) do + trigger.action.setUserFlag(v.name, self.resource < Config.carrierSpawnCost) + end + end + + function CarrierCommand:refreshText() + local build = '' + local mBuild = '' + + local status='' + if self:criticalOnSupplies() then + status = '(!)' + end + + local color = {0.3,0.3,0.3,1} + if self.side == 1 then + color = {0.7,0,0,1} + elseif self.side == 2 then + color = {0,0,0.7,1} + end + + trigger.action.setMarkupColor(2000+self.index, color) + + local label = '['..self.resource..'/'..self.maxResource..']'..status..build..mBuild + + if self.side == 1 then + if self.revealTime > 0 then + trigger.action.setMarkupText(2000+self.index, self.name..label) + else + trigger.action.setMarkupText(2000+self.index, self.name) + end + elseif self.side == 2 then + trigger.action.setMarkupText(2000+self.index, self.name..label) + elseif self.side == 0 then + trigger.action.setMarkupText(2000+self.index, ' '..self.name..' ') + end + + if self.side == 2 and (self.isHeloSpawn or self.isPlaneSpawn) then + trigger.action.setMarkupTypeLine(3000+self.index, 2) + trigger.action.setMarkupColor(3000+self.index, {0,1,0,1}) + end + + local unit = Unit.getByName(self.name) + local point = unit:getPoint() + trigger.action.setMarkupPositionStart(3000+self.index, point) + + point.z = point.z + self.range + trigger.action.setMarkupPositionStart(2000+self.index, point) + end + + function CarrierCommand:capture(side) + end + + function CarrierCommand:criticalOnSupplies() + return self.resource<=self.spendTreshold + end + + function CarrierCommand.getCarrierByName(name) + if not name then return nil end + return CarrierCommand.allCarriers[name] + end + + function CarrierCommand.getAllCarriers() + return CarrierCommand.allCarriers + end + + function CarrierCommand.getCarrierOfUnit(unitname) + local un = Unit.getByName(unitname) + + if not un then + return nil + end + + for i,v in pairs(CarrierCommand.allCarriers) do + local carrier = Unit.getByName(v.name) + if carrier then + if Utils.isInCircle(un:getPoint(), carrier:getPoint(), v.range) then + return v + end + end + end + + return nil + end + + function CarrierCommand.getClosestCarrierToPoint(point) + local minDist = 9999999 + local closest = nil + for i,v in pairs(CarrierCommand.allCarriers) do + local carrier = Unit.getByName(v.name) + if carrier then + local d = mist.utils.get2DDist(carrier:getPoint(), point) + if d < minDist then + minDist = d + closest = v + end + end + end + + return closest, minDist + end + + function CarrierCommand.getCarrierOfPoint(point) + for i,v in pairs(CarrierCommand.allCarriers) do + local carrier = Unit.getByName(v.name) + if carrier then + if Utils.isInCircle(point, carrier:getPoint(), v.range) then + return v + end + end + end + + return nil + end + + CarrierCommand.groupMenus = {} + MenuRegistry:register(6, function(event, context) + if event.id == world.event.S_EVENT_BIRTH and event.initiator and event.initiator.getPlayerName then + local player = event.initiator:getPlayerName() + if player then + local groupid = event.initiator:getGroup():getID() + local groupname = event.initiator:getGroup():getName() + + if CarrierCommand.groupMenus[groupid] then + missionCommands.removeItemForGroup(groupid, CarrierCommand.groupMenus[groupid]) + CarrierCommand.groupMenus[groupid] = nil + end + + if not CarrierCommand.groupMenus[groupid] then + + local menu = missionCommands.addSubMenuForGroup(groupid, 'Naval Command') + + local sorted = {} + for cname, carrier in pairs(CarrierCommand.getAllCarriers()) do + local cr = Unit.getByName(carrier.name) + if cr then + table.insert(sorted, carrier) + end + end + + table.sort(sorted, function(a,b) return a.name < b.name end) + + for _,carrier in ipairs(sorted) do + local crunit = Unit.getByName(carrier.name) + if crunit and crunit:isExist() then + local subm = missionCommands.addSubMenuForGroup(groupid, carrier.name, menu) + missionCommands.addCommandForGroup(groupid, 'Information', subm, Utils.log(carrier.showInformation), carrier, groupname) + + local rank = DependencyManager.get("PlayerTracker"):getPlayerRank(player) + + if rank and rank.allowCarrierSupport and Utils.getTableSize(carrier.supportFlights) > 0 then + local supm = missionCommands.addSubMenuForGroup(groupid, "Support", subm) + local flights = {} + for _, data in pairs(carrier.supportFlights) do + table.insert(flights, data) + end + + table.sort(flights, function(a,b) return a.name 1 then + missionCommands.addCommandForGroup(groupid, 'Patrol Area', wpm, Utils.log(carrier.setWaypoints), carrier, wp.waypoints, groupname) + end + + missionCommands.addCommandForGroup(groupid, 'Go to '..wp.name, wpm, Utils.log(carrier.setWaypoints), carrier, {wp.name}, groupname) + for _,subwp in ipairs(wp.waypoints) do + missionCommands.addCommandForGroup(groupid, 'Go to '..subwp, wpm, Utils.log(carrier.setWaypoints), carrier, {subwp}, groupname) + end + end + end + end + end + + CarrierCommand.groupMenus[groupid] = menu + end + end + elseif (event.id == world.event.S_EVENT_PLAYER_LEAVE_UNIT or event.id == world.event.S_EVENT_DEAD) and event.initiator and event.initiator.getPlayerName then + local player = event.initiator:getPlayerName() + if player then + local groupid = event.initiator:getGroup():getID() + + if CarrierCommand.groupMenus[groupid] then + missionCommands.removeItemForGroup(groupid, CarrierCommand.groupMenus[groupid]) + CarrierCommand.groupMenus[groupid] = nil + end + end + end + end, nil) +end + +-----------------[[ END OF CarrierCommand.lua ]]----------------- + + + -----------------[[ Objectives/Objective.lua ]]----------------- Objective = {} @@ -9708,7 +11303,10 @@ do if not self.param.loadedBy then - if self.param.target.pilot:isExist() then + if self.param.target.pilot:isExist() and + self.param.target.pilot:getSize() > 0 and + self.param.target.pilot:getUnit(1):isExist() then + local point = self.param.target.pilot:getUnit(1):getPoint() local lat,lon,alt = coord.LOtoLL(point) @@ -11187,7 +12785,6 @@ do targetzone = zn } - MissionTargetRegistry.removeStrikeTarget(tgt) end self.description = self.description..description end @@ -11307,7 +12904,6 @@ do targetzone = zn } - MissionTargetRegistry.removeStrikeTarget(tgt) end self.description = self.description..description end @@ -11554,7 +13150,7 @@ do if firstWP and firstWP.zone:hasUnitWithAttributeOnSide({'Buildings'}, 1) then local tgt = firstWP.zone:getRandomUnitWithAttributeOnSide({'Buildings'}, 1) if tgt then - MissionTargetRegistry.addStrikeTarget(tgt, firstWP.zone, true) + MissionTargetRegistry.addStrikeTarget(tgt, firstWP.zone, false) self:pushMessageToPlayers(tgt.display..' discovered at '..firstWP.zone.name) firstWP.zone:reveal() end @@ -12126,27 +13722,27 @@ end MissionTracker = {} do MissionTracker.maxMissionCount = { - [Mission.types.cap_easy] = 1, + [Mission.types.cap_easy] = 2, [Mission.types.cap_medium] = 1, - [Mission.types.cas_easy] = 1, + [Mission.types.cas_easy] = 2, [Mission.types.cas_medium] = 1, [Mission.types.cas_hard] = 1, [Mission.types.sead] = 3, - [Mission.types.supply_easy] = 1, + [Mission.types.supply_easy] = 3, [Mission.types.supply_hard] = 1, - [Mission.types.strike_veryeasy] = 1, + [Mission.types.strike_veryeasy] = 2, [Mission.types.strike_easy] = 1, [Mission.types.strike_medium] = 3, [Mission.types.strike_hard] = 1, [Mission.types.dead] = 1, - [Mission.types.escort] = 1, + [Mission.types.escort] = 2, [Mission.types.tarcap] = 1, - [Mission.types.recon_plane] = 1, - [Mission.types.recon_plane_deep] = 1, + [Mission.types.recon_plane] = 3, + [Mission.types.recon_plane_deep] = 3, [Mission.types.deep_strike] = 3, - [Mission.types.scout_helo] = 1, + [Mission.types.scout_helo] = 3, [Mission.types.bai] = 1, - [Mission.types.anti_runway] = 1, + [Mission.types.anti_runway] = 2, [Mission.types.csar] = 1, [Mission.types.extraction] = 1, [Mission.types.deploy_squad] = 3, @@ -12160,7 +13756,7 @@ do end end - MissionTracker.missionBoardSize = 10 + MissionTracker.missionBoardSize = Config.missionBoardSize or 15 function MissionTracker:new() local obj = {} @@ -12698,7 +14294,7 @@ do for _,m in pairs(self.activeMissions) do if m.players[player] then if m.state == Mission.states.active then - if Weapon.getCategory(weapon) == Weapon.Category.BOMB then + if Weapon.getCategoryEx(weapon) == Weapon.Category.BOMB then timer.scheduleFunction(function (params, time) if not params.weapon:isExist() then return nil -- weapon despawned @@ -12814,6 +14410,10 @@ do end local zn = ZoneCommand.getZoneOfUnit(unit:getName()) + if not zn then + zn = CarrierCommand.getCarrierOfUnit(unit:getName()) + end + if not zn or zn.side ~= unit:getCoalition() then trigger.action.outTextForUnit(unit:getID(), 'Can only accept mission while inside friendly zone', 5) return false @@ -12866,6 +14466,10 @@ do end local zn = ZoneCommand.getZoneOfUnit(unit:getName()) + if not zn then + zn = CarrierCommand.getCarrierOfUnit(unit:getName()) + end + if not zn or zn.side ~= unit:getCoalition() then trigger.action.outTextForUnit(unit:getID(), 'Can only join mission while inside friendly zone', 5) return false @@ -13196,6 +14800,12 @@ do for _,v in pairs(zn.neighbours) do if v.side ~= gr:getCoalition() and v.side ~= 0 then v:reveal() + if v:hasUnitWithAttributeOnSide({'Buildings'}, v.side) then + local tgt = v:getRandomUnitWithAttributeOnSide({'Buildings'}, v.side) + if tgt then + MissionTargetRegistry.addStrikeTarget(tgt, v, v.distToFront >= 2) + end + end end end end @@ -13357,7 +14967,7 @@ do local name = nil for i,v in pairs(self.activePilots) do - if v.pilot:isExist() and v.remainingTime > 0 then + if v.pilot:isExist() and v.pilot:getSize()>0 and v.pilot:getUnit(1):isExist() and v.remainingTime > 0 then local dist = mist.utils.get2DDist(toPosition, v.pilot:getUnit(1):getPoint()) if dist Date: Sat, 9 Sep 2023 15:15:42 +0300 Subject: [PATCH 102/243] Copied three files as templates/inheritance for generating Pretense campaigns from Retribution campaigns: - missiongenerator.py - aircraftgenerator.py - triggergenerator.py --- game/pretense/pretenseaircraftgenerator.py | 296 ++++++++++++++++++ game/pretense/pretensemissiongenerator.py | 343 +++++++++++++++++++++ game/pretense/pretensetriggergenerator.py | 264 ++++++++++++++++ 3 files changed, 903 insertions(+) create mode 100644 game/pretense/pretenseaircraftgenerator.py create mode 100644 game/pretense/pretensemissiongenerator.py create mode 100644 game/pretense/pretensetriggergenerator.py diff --git a/game/pretense/pretenseaircraftgenerator.py b/game/pretense/pretenseaircraftgenerator.py new file mode 100644 index 00000000..7caa91c3 --- /dev/null +++ b/game/pretense/pretenseaircraftgenerator.py @@ -0,0 +1,296 @@ +from __future__ import annotations + +import logging +from datetime import datetime +from functools import cached_property +from typing import Any, Dict, List, TYPE_CHECKING, Tuple + +from dcs import Point +from dcs.action import AITaskPush +from dcs.condition import FlagIsTrue, GroupDead, Or, FlagIsFalse +from dcs.country import Country +from dcs.mission import Mission +from dcs.terrain.terrain import NoParkingSlotError +from dcs.triggers import TriggerOnce, Event +from dcs.unit import Skill +from dcs.unitgroup import FlyingGroup, StaticGroup + +from game.ato.airtaaskingorder import AirTaskingOrder +from game.ato.flight import Flight +from game.ato.flightstate import Completed, WaitingForStart +from game.ato.flighttype import FlightType +from game.ato.package import Package +from game.ato.starttype import StartType +from game.missiongenerator.lasercoderegistry import LaserCodeRegistry +from game.missiongenerator.missiondata import MissionData +from game.radio.radios import RadioRegistry +from game.radio.tacan import TacanRegistry +from game.runways import RunwayData +from game.settings import Settings +from game.theater.controlpoint import ( + Airfield, + ControlPoint, + Fob, +) +from game.unitmap import UnitMap +from .aircraftpainter import AircraftPainter +from .flightdata import FlightData +from .flightgroupconfigurator import FlightGroupConfigurator +from .flightgroupspawner import FlightGroupSpawner +from ...data.weapons import WeaponType + +if TYPE_CHECKING: + from game import Game + from game.squadrons import Squadron + + +class AircraftGenerator: + def __init__( + self, + mission: Mission, + settings: Settings, + game: Game, + time: datetime, + radio_registry: RadioRegistry, + tacan_registry: TacanRegistry, + laser_code_registry: LaserCodeRegistry, + unit_map: UnitMap, + mission_data: MissionData, + helipads: dict[ControlPoint, list[StaticGroup]], + ground_spawns_roadbase: dict[ControlPoint, list[Tuple[StaticGroup, Point]]], + ground_spawns: dict[ControlPoint, list[Tuple[StaticGroup, Point]]], + ) -> None: + self.mission = mission + self.settings = settings + self.game = game + self.time = time + self.radio_registry = radio_registry + self.tacan_registy = tacan_registry + self.laser_code_registry = laser_code_registry + self.unit_map = unit_map + self.flights: List[FlightData] = [] + self.mission_data = mission_data + self.helipads = helipads + self.ground_spawns_roadbase = ground_spawns_roadbase + self.ground_spawns = ground_spawns + + self.ewrj_package_dict: Dict[int, List[FlyingGroup[Any]]] = {} + self.ewrj = settings.plugins.get("ewrj") + self.need_ecm = settings.plugin_option("ewrj.ecm_required") + + @cached_property + def use_client(self) -> bool: + """True if Client should be used instead of Player.""" + blue_clients = self.client_slots_in_ato(self.game.blue.ato) + red_clients = self.client_slots_in_ato(self.game.red.ato) + return blue_clients + red_clients > 1 + + @staticmethod + def client_slots_in_ato(ato: AirTaskingOrder) -> int: + total = 0 + for package in ato.packages: + for flight in package.flights: + total += flight.client_count + return total + + def clear_parking_slots(self) -> None: + for cp in self.game.theater.controlpoints: + for parking_slot in cp.parking_slots: + parking_slot.unit_id = None + + def generate_flights( + self, + country: Country, + ato: AirTaskingOrder, + dynamic_runways: Dict[str, RunwayData], + ) -> None: + """Adds aircraft to the mission for every flight in the ATO. + + Aircraft generation is done by walking the ATO and spawning each flight in turn. + After the flight is generated the group is added to the UnitMap so aircraft + deaths can be tracked. + + Args: + country: The country from the mission to use for this ATO. + ato: The ATO to spawn aircraft for. + dynamic_runways: Runway data for carriers and FARPs. + """ + self._reserve_frequencies_and_tacan(ato) + + for package in reversed(sorted(ato.packages, key=lambda x: x.time_over_target)): + logging.info(f"Generating package for target: {package.target.name}") + if not package.flights: + continue + for flight in package.flights: + if flight.alive: + if not flight.squadron.location.runway_is_operational(): + logging.warning( + f"Runway not operational, skipping flight: {flight.flight_type}" + ) + flight.return_pilots_and_aircraft() + continue + logging.info(f"Generating flight: {flight.unit_type}") + group = self.create_and_configure_flight( + flight, country, dynamic_runways + ) + self.unit_map.add_aircraft(group, flight) + if ( + package.primary_flight is not None + and package.primary_flight.flight_plan.is_formation( + package.primary_flight.flight_plan + ) + ): + splittrigger = TriggerOnce(Event.NoEvent, f"Split-{id(package)}") + splittrigger.add_condition(FlagIsTrue(flag=f"split-{id(package)}")) + splittrigger.add_condition(Or()) + splittrigger.add_condition(FlagIsFalse(flag=f"split-{id(package)}")) + splittrigger.add_condition(GroupDead(package.primary_flight.group_id)) + for flight in package.flights: + if flight.flight_type in [ + FlightType.ESCORT, + FlightType.SEAD_ESCORT, + ]: + splittrigger.add_action(AITaskPush(flight.group_id, 1)) + if len(splittrigger.actions) > 0: + self.mission.triggerrules.triggers.append(splittrigger) + + def spawn_unused_aircraft( + self, player_country: Country, enemy_country: Country + ) -> None: + for control_point in self.game.theater.controlpoints: + if not ( + isinstance(control_point, Airfield) or isinstance(control_point, Fob) + ): + continue + + if control_point.captured: + country = player_country + else: + country = enemy_country + + for squadron in control_point.squadrons: + try: + self._spawn_unused_for(squadron, country) + except NoParkingSlotError: + # If we run out of parking, stop spawning aircraft at this base. + break + + def _spawn_unused_for(self, squadron: Squadron, country: Country) -> None: + assert isinstance(squadron.location, Airfield) or isinstance( + squadron.location, Fob + ) + if ( + squadron.coalition.player + and self.game.settings.perf_disable_untasked_blufor_aircraft + ): + return + elif ( + not squadron.coalition.player + and self.game.settings.perf_disable_untasked_opfor_aircraft + ): + return + + for _ in range(squadron.untasked_aircraft): + # Creating a flight even those this isn't a fragged mission lets us + # reuse the existing debriefing code. + # TODO: Special flight type? + flight = Flight( + Package(squadron.location, self.game.db.flights), + squadron, + 1, + FlightType.BARCAP, + StartType.COLD, + divert=None, + claim_inv=False, + ) + flight.state = Completed(flight, self.game.settings) + + group = FlightGroupSpawner( + flight, + country, + self.mission, + self.helipads, + self.ground_spawns_roadbase, + self.ground_spawns, + self.mission_data, + ).create_idle_aircraft() + if group: + if ( + not squadron.coalition.player + and squadron.aircraft.flyable + and ( + self.game.settings.enable_squadron_pilot_limits + or squadron.number_of_available_pilots > 0 + ) + and self.game.settings.untasked_opfor_client_slots + ): + flight.state = WaitingForStart( + flight, self.game.settings, self.game.conditions.start_time + ) + group.uncontrolled = False + group.units[0].skill = Skill.Client + AircraftPainter(flight, group).apply_livery() + self.unit_map.add_aircraft(group, flight) + + def create_and_configure_flight( + self, flight: Flight, country: Country, dynamic_runways: Dict[str, RunwayData] + ) -> FlyingGroup[Any]: + """Creates and configures the flight group in the mission.""" + group = FlightGroupSpawner( + flight, + country, + self.mission, + self.helipads, + self.ground_spawns_roadbase, + self.ground_spawns, + self.mission_data, + ).create_flight_group() + self.flights.append( + FlightGroupConfigurator( + flight, + group, + self.game, + self.mission, + self.time, + self.radio_registry, + self.tacan_registy, + self.laser_code_registry, + self.mission_data, + dynamic_runways, + self.use_client, + ).configure() + ) + + if self.ewrj: + self._track_ewrj_flight(flight, group) + + return group + + def _track_ewrj_flight(self, flight: Flight, group: FlyingGroup[Any]) -> None: + if not self.ewrj_package_dict.get(id(flight.package)): + self.ewrj_package_dict[id(flight.package)] = [] + if ( + flight.package.primary_flight + and flight is flight.package.primary_flight + or flight.client_count + and ( + not self.need_ecm + or flight.loadout.has_weapon_of_type(WeaponType.JAMMER) + ) + ): + self.ewrj_package_dict[id(flight.package)].append(group) + + def _reserve_frequencies_and_tacan(self, ato: AirTaskingOrder) -> None: + for package in ato.packages: + if package.frequency is None: + continue + if package.frequency not in self.radio_registry.allocated_channels: + self.radio_registry.reserve(package.frequency) + for f in package.flights: + if ( + f.frequency + and f.frequency not in self.radio_registry.allocated_channels + ): + self.radio_registry.reserve(f.frequency) + if f.tacan and f.tacan not in self.tacan_registy.allocated_channels: + self.tacan_registy.mark_unavailable(f.tacan) diff --git a/game/pretense/pretensemissiongenerator.py b/game/pretense/pretensemissiongenerator.py new file mode 100644 index 00000000..85c5e5c9 --- /dev/null +++ b/game/pretense/pretensemissiongenerator.py @@ -0,0 +1,343 @@ +from __future__ import annotations + +import logging +from datetime import datetime +from pathlib import Path +from typing import TYPE_CHECKING, cast + +import dcs.lua +from dcs import Mission, Point +from dcs.coalition import Coalition +from dcs.countries import country_dict +from dcs.task import OptReactOnThreat + +from game.atcdata import AtcData +from game.dcs.beacons import Beacons +from game.dcs.helpers import unit_type_from_name +from game.missiongenerator.aircraft.aircraftgenerator import ( + AircraftGenerator, +) +from game.naming import namegen +from game.radio.radios import RadioFrequency, RadioRegistry, MHz +from game.radio.tacan import TacanRegistry +from game.theater import Airfield +from game.theater.bullseye import Bullseye +from game.unitmap import UnitMap +from .briefinggenerator import BriefingGenerator, MissionInfoGenerator +from .cargoshipgenerator import CargoShipGenerator +from .convoygenerator import ConvoyGenerator +from .drawingsgenerator import DrawingsGenerator +from .environmentgenerator import EnvironmentGenerator +from .flotgenerator import FlotGenerator +from .forcedoptionsgenerator import ForcedOptionsGenerator +from .frontlineconflictdescription import FrontLineConflictDescription +from .kneeboard import KneeboardGenerator +from .lasercoderegistry import LaserCodeRegistry +from .luagenerator import LuaGenerator +from .missiondata import MissionData +from .tgogenerator import TgoGenerator +from .triggergenerator import TriggerGenerator +from .visualsgenerator import VisualsGenerator +from ..radio.TacanContainer import TacanContainer + +if TYPE_CHECKING: + from game import Game + + +class MissionGenerator: + def __init__(self, game: Game, time: datetime) -> None: + self.game = game + self.time = time + self.mission = Mission(game.theater.terrain) + self.unit_map = UnitMap() + + self.mission_data = MissionData() + + self.laser_code_registry = LaserCodeRegistry() + self.radio_registry = RadioRegistry() + self.tacan_registry = TacanRegistry() + + self.generation_started = False + + self.p_country = country_dict[self.game.blue.faction.country.id]() + self.e_country = country_dict[self.game.red.faction.country.id]() + + with open("resources/default_options.lua", "r", encoding="utf-8") as f: + options = dcs.lua.loads(f.read())["options"] + ext_view = game.settings.external_views_allowed + options["miscellaneous"]["f11_free_camera"] = ext_view + options["difficulty"]["spectatorExternalViews"] = ext_view + self.mission.options.load_from_dict(options) + + def generate_miz(self, output: Path) -> UnitMap: + if self.generation_started: + raise RuntimeError( + "Mission has already begun generating. To reset, create a new " + "MissionSimulation." + ) + self.generation_started = True + + self.setup_mission_coalitions() + self.add_airfields_to_unit_map() + self.initialize_registries() + + EnvironmentGenerator(self.mission, self.game.conditions, self.time).generate() + + tgo_generator = TgoGenerator( + self.mission, + self.game, + self.radio_registry, + self.tacan_registry, + self.unit_map, + self.mission_data, + ) + tgo_generator.generate() + + ConvoyGenerator(self.mission, self.game, self.unit_map).generate() + CargoShipGenerator(self.mission, self.game, self.unit_map).generate() + + self.generate_destroyed_units() + + # Generate ground conflicts first so the JTACs get the first laser code (1688) + # rather than the first player flight with a TGP. + self.generate_ground_conflicts() + self.generate_air_units(tgo_generator) + + TriggerGenerator(self.mission, self.game).generate() + ForcedOptionsGenerator(self.mission, self.game).generate() + VisualsGenerator(self.mission, self.game).generate() + LuaGenerator(self.game, self.mission, self.mission_data).generate() + DrawingsGenerator(self.mission, self.game).generate() + + self.setup_combined_arms() + + self.notify_info_generators() + + # TODO: Shouldn't this be first? + namegen.reset_numbers() + self.mission.save(output) + + return self.unit_map + + @staticmethod + def _configure_ewrj(gen: AircraftGenerator) -> None: + for groups in gen.ewrj_package_dict.values(): + optrot = groups[0].points[0].tasks[0] + assert isinstance(optrot, OptReactOnThreat) + if ( + len(groups) == 1 + and optrot.value != OptReactOnThreat.Values.PassiveDefense + ): + # primary flight with no EWR-Jamming capability + continue + for group in groups: + group.points[0].tasks[0] = OptReactOnThreat( + OptReactOnThreat.Values.PassiveDefense + ) + + def setup_mission_coalitions(self) -> None: + self.mission.coalition["blue"] = Coalition( + "blue", bullseye=self.game.blue.bullseye.to_pydcs() + ) + self.mission.coalition["red"] = Coalition( + "red", bullseye=self.game.red.bullseye.to_pydcs() + ) + self.mission.coalition["neutrals"] = Coalition( + "neutrals", bullseye=Bullseye(Point(0, 0, self.mission.terrain)).to_pydcs() + ) + + self.mission.coalition["blue"].add_country(self.p_country) + self.mission.coalition["red"].add_country(self.e_country) + + belligerents = {self.p_country.id, self.e_country.id} + for country_id in country_dict.keys(): + if country_id not in belligerents: + c = country_dict[country_id]() + self.mission.coalition["neutrals"].add_country(c) + + def add_airfields_to_unit_map(self) -> None: + for control_point in self.game.theater.controlpoints: + if isinstance(control_point, Airfield): + self.unit_map.add_airfield(control_point) + + def initialize_registries(self) -> None: + unique_map_frequencies: set[RadioFrequency] = set() + self.initialize_tacan_registry(unique_map_frequencies) + self.initialize_radio_registry(unique_map_frequencies) + # Allocate UHF/VHF Guard Freq first! + unique_map_frequencies.add(MHz(243)) + unique_map_frequencies.add(MHz(121, 500)) + for frequency in unique_map_frequencies: + self.radio_registry.reserve(frequency) + + def initialize_tacan_registry( + self, unique_map_frequencies: set[RadioFrequency] + ) -> None: + """ + Dedup beacon/radio frequencies, since some maps have some frequencies + used multiple times. + """ + for beacon in Beacons.iter_theater(self.game.theater): + unique_map_frequencies.add(beacon.frequency) + if beacon.is_tacan: + if beacon.channel is None: + logging.warning(f"TACAN beacon has no channel: {beacon.callsign}") + else: + self.tacan_registry.mark_unavailable(beacon.tacan_channel) + for cp in self.game.theater.controlpoints: + if isinstance(cp, TacanContainer) and cp.tacan is not None: + self.tacan_registry.mark_unavailable(cp.tacan) + + def initialize_radio_registry( + self, unique_map_frequencies: set[RadioFrequency] + ) -> None: + for airport in self.game.theater.terrain.airport_list(): + if (atc := AtcData.from_pydcs(airport)) is not None: + unique_map_frequencies.add(atc.hf) + unique_map_frequencies.add(atc.vhf_fm) + unique_map_frequencies.add(atc.vhf_am) + unique_map_frequencies.add(atc.uhf) + # No need to reserve ILS or TACAN because those are in the + # beacon list. + + def generate_ground_conflicts(self) -> None: + """Generate FLOTs and JTACs for each active front line.""" + for front_line in self.game.theater.conflicts(): + player_cp = front_line.blue_cp + enemy_cp = front_line.red_cp + conflict = FrontLineConflictDescription.frontline_cas_conflict( + front_line, self.game.theater, self.game.settings + ) + # Generate frontline ops + player_gp = self.game.ground_planners[player_cp.id].units_per_cp[ + enemy_cp.id + ] + enemy_gp = self.game.ground_planners[enemy_cp.id].units_per_cp[player_cp.id] + ground_conflict_gen = FlotGenerator( + self.mission, + conflict, + self.game, + player_gp, + enemy_gp, + player_cp.stances[enemy_cp.id], + enemy_cp.stances[player_cp.id], + self.unit_map, + self.radio_registry, + self.mission_data, + self.laser_code_registry, + ) + ground_conflict_gen.generate() + + def generate_air_units(self, tgo_generator: TgoGenerator) -> None: + """Generate the air units for the Operation""" + + # Generate Aircraft Activity on the map + aircraft_generator = AircraftGenerator( + self.mission, + self.game.settings, + self.game, + self.time, + self.radio_registry, + self.tacan_registry, + self.laser_code_registry, + self.unit_map, + mission_data=self.mission_data, + helipads=tgo_generator.helipads, + ground_spawns_roadbase=tgo_generator.ground_spawns_roadbase, + ground_spawns=tgo_generator.ground_spawns, + ) + + aircraft_generator.clear_parking_slots() + + aircraft_generator.generate_flights( + self.p_country, + self.game.blue.ato, + tgo_generator.runways, + ) + aircraft_generator.generate_flights( + self.e_country, + self.game.red.ato, + tgo_generator.runways, + ) + aircraft_generator.spawn_unused_aircraft( + self.p_country, + self.e_country, + ) + + self.mission_data.flights = aircraft_generator.flights + + for flight in aircraft_generator.flights: + if not flight.client_units: + continue + flight.aircraft_type.assign_channels_for_flight(flight, self.mission_data) + + if self.game.settings.plugins.get("ewrj"): + self._configure_ewrj(aircraft_generator) + + def generate_destroyed_units(self) -> None: + """Add destroyed units to the Mission""" + if not self.game.settings.perf_destroyed_units: + return + + for d in self.game.get_destroyed_units(): + try: + type_name = d["type"] + if not isinstance(type_name, str): + raise TypeError( + "Expected the type of the destroyed static to be a string" + ) + utype = unit_type_from_name(type_name) + except KeyError: + logging.warning(f"Destroyed unit has no type: {d}") + continue + + pos = Point(cast(float, d["x"]), cast(float, d["z"]), self.mission.terrain) + if utype is not None and not self.game.position_culled(pos): + self.mission.static_group( + country=self.p_country, + name="", + _type=utype, + hidden=True, + position=pos, + heading=d["orientation"], + dead=True, + ) + + def notify_info_generators( + self, + ) -> None: + """Generates subscribed MissionInfoGenerator objects.""" + mission_data = self.mission_data + gens: list[MissionInfoGenerator] = [ + KneeboardGenerator(self.mission, self.game), + BriefingGenerator(self.mission, self.game), + ] + for gen in gens: + for dynamic_runway in mission_data.runways: + gen.add_dynamic_runway(dynamic_runway) + + for tanker in mission_data.tankers: + if tanker.blue: + gen.add_tanker(tanker) + + for aewc in mission_data.awacs: + if aewc.blue: + gen.add_awacs(aewc) + + for jtac in mission_data.jtacs: + if jtac.blue: + gen.add_jtac(jtac) + + for flight in mission_data.flights: + gen.add_flight(flight) + gen.generate() + + def setup_combined_arms(self) -> None: + settings = self.game.settings + commanders = settings.tactical_commander_count + self.mission.groundControl.pilot_can_control_vehicles = commanders > 0 + + self.mission.groundControl.blue_game_masters = settings.game_masters_count + self.mission.groundControl.blue_tactical_commander = commanders + self.mission.groundControl.blue_jtac = settings.jtac_count + self.mission.groundControl.blue_observer = settings.observer_count diff --git a/game/pretense/pretensetriggergenerator.py b/game/pretense/pretensetriggergenerator.py new file mode 100644 index 00000000..cc31b4e5 --- /dev/null +++ b/game/pretense/pretensetriggergenerator.py @@ -0,0 +1,264 @@ +from __future__ import annotations + +import logging +from typing import TYPE_CHECKING, List + +from dcs import Point +from dcs.action import ( + ClearFlag, + DoScript, + MarkToAll, + SetFlag, + RemoveSceneObjects, + RemoveSceneObjectsMask, + SceneryDestructionZone, + Smoke, +) +from dcs.condition import ( + AllOfCoalitionOutsideZone, + FlagIsFalse, + FlagIsTrue, + PartOfCoalitionInZone, + TimeAfter, + TimeSinceFlag, +) +from dcs.mission import Mission +from dcs.task import Option +from dcs.translation import String +from dcs.triggers import Event, TriggerCondition, TriggerOnce +from dcs.unit import Skill + +from game.theater import Airfield +from game.theater.controlpoint import Fob, TRIGGER_RADIUS_CAPTURE + +if TYPE_CHECKING: + from game.game import Game + +PUSH_TRIGGER_SIZE = 3000 +PUSH_TRIGGER_ACTIVATION_AGL = 25 + +REGROUP_ZONE_DISTANCE = 12000 +REGROUP_ALT = 5000 + +TRIGGER_WAYPOINT_OFFSET = 2 +TRIGGER_MIN_DISTANCE_FROM_START = 10000 +# modified since we now have advanced SAM units +TRIGGER_RADIUS_MINIMUM = 3000000 + +TRIGGER_RADIUS_SMALL = 50000 +TRIGGER_RADIUS_MEDIUM = 100000 +TRIGGER_RADIUS_LARGE = 150000 +TRIGGER_RADIUS_ALL_MAP = 3000000 +TRIGGER_RADIUS_CLEAR_SCENERY = 1000 + + +class Silence(Option): + Key = 7 + + +class TriggerGenerator: + capture_zone_types = (Fob, Airfield) + capture_zone_flag = 600 + + def __init__(self, mission: Mission, game: Game) -> None: + self.mission = mission + self.game = game + + def _set_allegiances(self, player_coalition: str, enemy_coalition: str) -> None: + """ + Set airbase initial coalition + """ + + # Empty neutrals airports + airfields = [ + cp for cp in self.game.theater.controlpoints if isinstance(cp, Airfield) + ] + airport_ids = {cp.airport.id for cp in airfields} + for airport in self.mission.terrain.airport_list(): + if airport.id not in airport_ids: + airport.unlimited_fuel = False + airport.unlimited_munitions = False + airport.unlimited_aircrafts = False + airport.gasoline_init = 0 + airport.methanol_mixture_init = 0 + airport.diesel_init = 0 + airport.jet_init = 0 + airport.operating_level_air = 0 + airport.operating_level_equipment = 0 + airport.operating_level_fuel = 0 + + for airport in self.mission.terrain.airport_list(): + if airport.id not in airport_ids: + airport.unlimited_fuel = True + airport.unlimited_munitions = True + airport.unlimited_aircrafts = True + + for airfield in airfields: + cp_airport = self.mission.terrain.airport_by_id(airfield.airport.id) + if cp_airport is None: + raise RuntimeError( + f"Could not find {airfield.airport.name} in the mission" + ) + cp_airport.set_coalition( + airfield.captured and player_coalition or enemy_coalition + ) + + def _set_skill(self, player_coalition: str, enemy_coalition: str) -> None: + """ + Set skill level for all aircraft in the mission + """ + for coalition_name, coalition in self.mission.coalition.items(): + if coalition_name == player_coalition: + skill_level = Skill(self.game.settings.player_skill) + elif coalition_name == enemy_coalition: + skill_level = Skill(self.game.settings.enemy_vehicle_skill) + else: + continue + + for country in coalition.countries.values(): + for vehicle_group in country.vehicle_group: + vehicle_group.set_skill(skill_level) + + def _gen_markers(self) -> None: + """ + Generate markers on F10 map for each existing objective + """ + if self.game.settings.generate_marks: + mark_trigger = TriggerOnce(Event.NoEvent, "Marks generator") + mark_trigger.add_condition(TimeAfter(1)) + v = 10 + for cp in self.game.theater.controlpoints: + seen = set() + for ground_object in cp.ground_objects: + if ground_object.obj_name in seen: + continue + + seen.add(ground_object.obj_name) + for location in ground_object.mark_locations: + zone = self.mission.triggers.add_triggerzone( + location, radius=10, hidden=True, name="MARK" + ) + if cp.captured: + name = ground_object.obj_name + " [ALLY]" + else: + name = ground_object.obj_name + " [ENEMY]" + mark_trigger.add_action(MarkToAll(v, zone.id, String(name))) + v += 1 + self.mission.triggerrules.triggers.append(mark_trigger) + + def _generate_clear_statics_trigger(self, scenery_clear_zones: List[Point]) -> None: + for zone_center in scenery_clear_zones: + trigger_zone = self.mission.triggers.add_triggerzone( + zone_center, + radius=TRIGGER_RADIUS_CLEAR_SCENERY, + hidden=False, + name="CLEAR", + ) + clear_trigger = TriggerCondition(Event.NoEvent, "Clear Trigger") + clear_flag = self.get_capture_zone_flag() + clear_trigger.add_condition(TimeSinceFlag(clear_flag, 30)) + clear_trigger.add_action(ClearFlag(clear_flag)) + clear_trigger.add_action(SetFlag(clear_flag)) + clear_trigger.add_action( + RemoveSceneObjects( + objects_mask=RemoveSceneObjectsMask.OBJECTS_ONLY, + zone=trigger_zone.id, + ) + ) + clear_trigger.add_action( + SceneryDestructionZone(destruction_level=100, zone=trigger_zone.id) + ) + self.mission.triggerrules.triggers.append(clear_trigger) + + enable_clear_trigger = TriggerOnce(Event.NoEvent, "Enable Clear Trigger") + enable_clear_trigger.add_condition(TimeAfter(30)) + enable_clear_trigger.add_action(ClearFlag(clear_flag)) + enable_clear_trigger.add_action(SetFlag(clear_flag)) + # clear_trigger.add_action(MessageToAll(text=String("Enable clear trigger"),)) + self.mission.triggerrules.triggers.append(enable_clear_trigger) + + 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(self) -> None: + player_coalition = "blue" + enemy_coalition = "red" + + self._set_skill(player_coalition, enemy_coalition) + self._set_allegiances(player_coalition, enemy_coalition) + self._gen_markers() + self._generate_capture_triggers(player_coalition, enemy_coalition) + if self.game.settings.ground_start_scenery_remove_triggers: + try: + self._generate_clear_statics_trigger(self.game.scenery_clear_zones) + self.game.scenery_clear_zones.clear() + except AttributeError: + logging.info(f"Unable to create Clear Statics triggers") + + @classmethod + def get_capture_zone_flag(cls) -> int: + flag = cls.capture_zone_flag + cls.capture_zone_flag += 1 + return flag From 31fb340ac86d0242d52e47fca895698ba9a0bb07 Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Sat, 9 Sep 2023 16:04:44 +0300 Subject: [PATCH 103/243] Will now generate control point trigger zones and AI aircraft for the Pretense campaign. --- game/pretense/pretenseaircraftgenerator.py | 195 ++++++++------------- game/pretense/pretensemissiongenerator.py | 127 +++++--------- game/pretense/pretensetriggergenerator.py | 97 +--------- qt_ui/uiconstants.py | 1 + qt_ui/windows/QLiberationWindow.py | 16 ++ 5 files changed, 143 insertions(+), 293 deletions(-) diff --git a/game/pretense/pretenseaircraftgenerator.py b/game/pretense/pretenseaircraftgenerator.py index 7caa91c3..f6c79c8b 100644 --- a/game/pretense/pretenseaircraftgenerator.py +++ b/game/pretense/pretenseaircraftgenerator.py @@ -1,6 +1,7 @@ from __future__ import annotations import logging +import random from datetime import datetime from functools import cached_property from typing import Any, Dict, List, TYPE_CHECKING, Tuple @@ -33,18 +34,17 @@ from game.theater.controlpoint import ( Fob, ) from game.unitmap import UnitMap -from .aircraftpainter import AircraftPainter -from .flightdata import FlightData -from .flightgroupconfigurator import FlightGroupConfigurator -from .flightgroupspawner import FlightGroupSpawner -from ...data.weapons import WeaponType +from game.missiongenerator.aircraft.aircraftpainter import AircraftPainter +from game.missiongenerator.aircraft.flightdata import FlightData +from game.missiongenerator.aircraft.flightgroupspawner import FlightGroupSpawner +from game.data.weapons import WeaponType if TYPE_CHECKING: from game import Game from game.squadrons import Squadron -class AircraftGenerator: +class PretenseAircraftGenerator: def __init__( self, mission: Mission, @@ -101,6 +101,7 @@ class AircraftGenerator: def generate_flights( self, country: Country, + cp: ControlPoint, ato: AirTaskingOrder, dynamic_runways: Dict[str, RunwayData], ) -> None: @@ -115,6 +116,61 @@ class AircraftGenerator: ato: The ATO to spawn aircraft for. dynamic_runways: Runway data for carriers and FARPs. """ + + num_of_sead = 0 + num_of_cas = 0 + num_of_strike = 0 + num_of_cap = 0 + for squadron in cp.squadrons: + squadron.owned_aircraft += 1 + squadron.untasked_aircraft += 1 + package = Package(cp, squadron.flight_db, auto_asap=False) + mission_types = squadron.auto_assignable_mission_types + if ( + FlightType.TRANSPORT in mission_types + or FlightType.AIR_ASSAULT in mission_types + ): + flight_type = FlightType.TRANSPORT + elif ( + FlightType.SEAD in mission_types + or FlightType.SEAD_SWEEP + or FlightType.SEAD_ESCORT in mission_types + ) and num_of_sead < 2: + flight_type = FlightType.SEAD + num_of_sead += 1 + elif FlightType.DEAD in mission_types and num_of_sead < 2: + flight_type = FlightType.DEAD + num_of_sead += 1 + elif ( + FlightType.CAS in mission_types or FlightType.BAI in mission_types + ) and num_of_cas < 2: + flight_type = FlightType.CAS + num_of_cas += 1 + elif ( + FlightType.STRIKE in mission_types + or FlightType.OCA_RUNWAY in mission_types + or FlightType.OCA_AIRCRAFT in mission_types + ) and num_of_strike < 2: + flight_type = FlightType.STRIKE + num_of_strike += 1 + elif ( + FlightType.BARCAP in mission_types + or FlightType.TARCAP in mission_types + or FlightType.ESCORT in mission_types + ) and num_of_cap < 2: + flight_type = FlightType.BARCAP + num_of_cap += 1 + else: + flight_type = random.choice(list(mission_types)) + flight = Flight( + package, squadron, 1, flight_type, StartType.COLD, divert=cp + ) + flight.state = WaitingForStart( + flight, self.game.settings, self.game.conditions.start_time + ) + package.add_flight(flight) + ato.add_package(package) + self._reserve_frequencies_and_tacan(ato) for package in reversed(sorted(ato.packages, key=lambda x: x.time_over_target)): @@ -134,103 +190,6 @@ class AircraftGenerator: flight, country, dynamic_runways ) self.unit_map.add_aircraft(group, flight) - if ( - package.primary_flight is not None - and package.primary_flight.flight_plan.is_formation( - package.primary_flight.flight_plan - ) - ): - splittrigger = TriggerOnce(Event.NoEvent, f"Split-{id(package)}") - splittrigger.add_condition(FlagIsTrue(flag=f"split-{id(package)}")) - splittrigger.add_condition(Or()) - splittrigger.add_condition(FlagIsFalse(flag=f"split-{id(package)}")) - splittrigger.add_condition(GroupDead(package.primary_flight.group_id)) - for flight in package.flights: - if flight.flight_type in [ - FlightType.ESCORT, - FlightType.SEAD_ESCORT, - ]: - splittrigger.add_action(AITaskPush(flight.group_id, 1)) - if len(splittrigger.actions) > 0: - self.mission.triggerrules.triggers.append(splittrigger) - - def spawn_unused_aircraft( - self, player_country: Country, enemy_country: Country - ) -> None: - for control_point in self.game.theater.controlpoints: - if not ( - isinstance(control_point, Airfield) or isinstance(control_point, Fob) - ): - continue - - if control_point.captured: - country = player_country - else: - country = enemy_country - - for squadron in control_point.squadrons: - try: - self._spawn_unused_for(squadron, country) - except NoParkingSlotError: - # If we run out of parking, stop spawning aircraft at this base. - break - - def _spawn_unused_for(self, squadron: Squadron, country: Country) -> None: - assert isinstance(squadron.location, Airfield) or isinstance( - squadron.location, Fob - ) - if ( - squadron.coalition.player - and self.game.settings.perf_disable_untasked_blufor_aircraft - ): - return - elif ( - not squadron.coalition.player - and self.game.settings.perf_disable_untasked_opfor_aircraft - ): - return - - for _ in range(squadron.untasked_aircraft): - # Creating a flight even those this isn't a fragged mission lets us - # reuse the existing debriefing code. - # TODO: Special flight type? - flight = Flight( - Package(squadron.location, self.game.db.flights), - squadron, - 1, - FlightType.BARCAP, - StartType.COLD, - divert=None, - claim_inv=False, - ) - flight.state = Completed(flight, self.game.settings) - - group = FlightGroupSpawner( - flight, - country, - self.mission, - self.helipads, - self.ground_spawns_roadbase, - self.ground_spawns, - self.mission_data, - ).create_idle_aircraft() - if group: - if ( - not squadron.coalition.player - and squadron.aircraft.flyable - and ( - self.game.settings.enable_squadron_pilot_limits - or squadron.number_of_available_pilots > 0 - ) - and self.game.settings.untasked_opfor_client_slots - ): - flight.state = WaitingForStart( - flight, self.game.settings, self.game.conditions.start_time - ) - group.uncontrolled = False - group.units[0].skill = Skill.Client - AircraftPainter(flight, group).apply_livery() - self.unit_map.add_aircraft(group, flight) def create_and_configure_flight( self, flight: Flight, country: Country, dynamic_runways: Dict[str, RunwayData] @@ -245,21 +204,21 @@ class AircraftGenerator: self.ground_spawns, self.mission_data, ).create_flight_group() - self.flights.append( - FlightGroupConfigurator( - flight, - group, - self.game, - self.mission, - self.time, - self.radio_registry, - self.tacan_registy, - self.laser_code_registry, - self.mission_data, - dynamic_runways, - self.use_client, - ).configure() - ) + # self.flights.append( + # FlightGroupConfigurator( + # flight, + # group, + # self.game, + # self.mission, + # self.time, + # self.radio_registry, + # self.tacan_registy, + # self.laser_code_registry, + # self.mission_data, + # dynamic_runways, + # self.use_client, + # ).configure() + # ) if self.ewrj: self._track_ewrj_flight(flight, group) diff --git a/game/pretense/pretensemissiongenerator.py b/game/pretense/pretensemissiongenerator.py index 85c5e5c9..16478f27 100644 --- a/game/pretense/pretensemissiongenerator.py +++ b/game/pretense/pretensemissiongenerator.py @@ -6,6 +6,7 @@ from pathlib import Path from typing import TYPE_CHECKING, cast import dcs.lua +from dataclasses import field from dcs import Mission, Point from dcs.coalition import Coalition from dcs.countries import country_dict @@ -13,38 +14,40 @@ from dcs.task import OptReactOnThreat from game.atcdata import AtcData from game.dcs.beacons import Beacons -from game.dcs.helpers import unit_type_from_name -from game.missiongenerator.aircraft.aircraftgenerator import ( - AircraftGenerator, -) + from game.naming import namegen from game.radio.radios import RadioFrequency, RadioRegistry, MHz from game.radio.tacan import TacanRegistry from game.theater import Airfield from game.theater.bullseye import Bullseye from game.unitmap import UnitMap -from .briefinggenerator import BriefingGenerator, MissionInfoGenerator -from .cargoshipgenerator import CargoShipGenerator -from .convoygenerator import ConvoyGenerator -from .drawingsgenerator import DrawingsGenerator -from .environmentgenerator import EnvironmentGenerator -from .flotgenerator import FlotGenerator -from .forcedoptionsgenerator import ForcedOptionsGenerator -from .frontlineconflictdescription import FrontLineConflictDescription -from .kneeboard import KneeboardGenerator -from .lasercoderegistry import LaserCodeRegistry -from .luagenerator import LuaGenerator -from .missiondata import MissionData -from .tgogenerator import TgoGenerator -from .triggergenerator import TriggerGenerator -from .visualsgenerator import VisualsGenerator +from game.pretense.pretenseaircraftgenerator import PretenseAircraftGenerator +from game.missiongenerator.briefinggenerator import ( + BriefingGenerator, + MissionInfoGenerator, +) +from game.missiongenerator.convoygenerator import ConvoyGenerator +from game.missiongenerator.environmentgenerator import EnvironmentGenerator +from game.missiongenerator.flotgenerator import FlotGenerator +from game.missiongenerator.forcedoptionsgenerator import ForcedOptionsGenerator +from game.missiongenerator.frontlineconflictdescription import ( + FrontLineConflictDescription, +) +from game.missiongenerator.kneeboard import KneeboardGenerator +from game.missiongenerator.lasercoderegistry import LaserCodeRegistry +from game.missiongenerator.luagenerator import LuaGenerator +from game.missiongenerator.missiondata import MissionData +from game.missiongenerator.tgogenerator import TgoGenerator +from .pretensetriggergenerator import PretenseTriggerGenerator +from game.missiongenerator.visualsgenerator import VisualsGenerator +from ..ato import Flight from ..radio.TacanContainer import TacanContainer if TYPE_CHECKING: from game import Game -class MissionGenerator: +class PretenseMissionGenerator: def __init__(self, game: Game, time: datetime) -> None: self.game = game self.time = time @@ -94,20 +97,16 @@ class MissionGenerator: tgo_generator.generate() ConvoyGenerator(self.mission, self.game, self.unit_map).generate() - CargoShipGenerator(self.mission, self.game, self.unit_map).generate() - - self.generate_destroyed_units() # Generate ground conflicts first so the JTACs get the first laser code (1688) # rather than the first player flight with a TGP. self.generate_ground_conflicts() self.generate_air_units(tgo_generator) - TriggerGenerator(self.mission, self.game).generate() + PretenseTriggerGenerator(self.mission, self.game).generate() ForcedOptionsGenerator(self.mission, self.game).generate() VisualsGenerator(self.mission, self.game).generate() LuaGenerator(self.game, self.mission, self.mission_data).generate() - DrawingsGenerator(self.mission, self.game).generate() self.setup_combined_arms() @@ -119,22 +118,6 @@ class MissionGenerator: return self.unit_map - @staticmethod - def _configure_ewrj(gen: AircraftGenerator) -> None: - for groups in gen.ewrj_package_dict.values(): - optrot = groups[0].points[0].tasks[0] - assert isinstance(optrot, OptReactOnThreat) - if ( - len(groups) == 1 - and optrot.value != OptReactOnThreat.Values.PassiveDefense - ): - # primary flight with no EWR-Jamming capability - continue - for group in groups: - group.points[0].tasks[0] = OptReactOnThreat( - OptReactOnThreat.Values.PassiveDefense - ) - def setup_mission_coalitions(self) -> None: self.mission.coalition["blue"] = Coalition( "blue", bullseye=self.game.blue.bullseye.to_pydcs() @@ -232,7 +215,7 @@ class MissionGenerator: """Generate the air units for the Operation""" # Generate Aircraft Activity on the map - aircraft_generator = AircraftGenerator( + aircraft_generator = PretenseAircraftGenerator( self.mission, self.game.settings, self.game, @@ -247,22 +230,25 @@ class MissionGenerator: ground_spawns=tgo_generator.ground_spawns, ) + # Clear parking slots and ATOs aircraft_generator.clear_parking_slots() + self.game.blue.ato.clear() + self.game.red.ato.clear() - aircraft_generator.generate_flights( - self.p_country, - self.game.blue.ato, - tgo_generator.runways, - ) - aircraft_generator.generate_flights( - self.e_country, - self.game.red.ato, - tgo_generator.runways, - ) - aircraft_generator.spawn_unused_aircraft( - self.p_country, - self.e_country, - ) + for cp in self.game.theater.controlpoints: + if cp.captured: + ato = self.game.blue.ato + cp_country = self.p_country + else: + ato = self.game.red.ato + cp_country = self.e_country + print(f"Generating flights for {cp_country.name} at {cp}") + aircraft_generator.generate_flights( + cp_country, + cp, + ato, + tgo_generator.runways, + ) self.mission_data.flights = aircraft_generator.flights @@ -274,35 +260,6 @@ class MissionGenerator: if self.game.settings.plugins.get("ewrj"): self._configure_ewrj(aircraft_generator) - def generate_destroyed_units(self) -> None: - """Add destroyed units to the Mission""" - if not self.game.settings.perf_destroyed_units: - return - - for d in self.game.get_destroyed_units(): - try: - type_name = d["type"] - if not isinstance(type_name, str): - raise TypeError( - "Expected the type of the destroyed static to be a string" - ) - utype = unit_type_from_name(type_name) - except KeyError: - logging.warning(f"Destroyed unit has no type: {d}") - continue - - pos = Point(cast(float, d["x"]), cast(float, d["z"]), self.mission.terrain) - if utype is not None and not self.game.position_culled(pos): - self.mission.static_group( - country=self.p_country, - name="", - _type=utype, - hidden=True, - position=pos, - heading=d["orientation"], - dead=True, - ) - def notify_info_generators( self, ) -> None: diff --git a/game/pretense/pretensetriggergenerator.py b/game/pretense/pretensetriggergenerator.py index cc31b4e5..11932f87 100644 --- a/game/pretense/pretensetriggergenerator.py +++ b/game/pretense/pretensetriggergenerator.py @@ -56,7 +56,7 @@ class Silence(Option): Key = 7 -class TriggerGenerator: +class PretenseTriggerGenerator: capture_zone_types = (Fob, Airfield) capture_zone_flag = 600 @@ -146,38 +146,7 @@ class TriggerGenerator: v += 1 self.mission.triggerrules.triggers.append(mark_trigger) - def _generate_clear_statics_trigger(self, scenery_clear_zones: List[Point]) -> None: - for zone_center in scenery_clear_zones: - trigger_zone = self.mission.triggers.add_triggerzone( - zone_center, - radius=TRIGGER_RADIUS_CLEAR_SCENERY, - hidden=False, - name="CLEAR", - ) - clear_trigger = TriggerCondition(Event.NoEvent, "Clear Trigger") - clear_flag = self.get_capture_zone_flag() - clear_trigger.add_condition(TimeSinceFlag(clear_flag, 30)) - clear_trigger.add_action(ClearFlag(clear_flag)) - clear_trigger.add_action(SetFlag(clear_flag)) - clear_trigger.add_action( - RemoveSceneObjects( - objects_mask=RemoveSceneObjectsMask.OBJECTS_ONLY, - zone=trigger_zone.id, - ) - ) - clear_trigger.add_action( - SceneryDestructionZone(destruction_level=100, zone=trigger_zone.id) - ) - self.mission.triggerrules.triggers.append(clear_trigger) - - enable_clear_trigger = TriggerOnce(Event.NoEvent, "Enable Clear Trigger") - enable_clear_trigger.add_condition(TimeAfter(30)) - enable_clear_trigger.add_action(ClearFlag(clear_flag)) - enable_clear_trigger.add_action(SetFlag(clear_flag)) - # clear_trigger.add_action(MessageToAll(text=String("Enable clear trigger"),)) - self.mission.triggerrules.triggers.append(enable_clear_trigger) - - def _generate_capture_triggers( + def _generate_pretense_zone_triggers( self, player_coalition: str, enemy_coalition: str ) -> None: """Creates a pair of triggers for each control point of `cls.capture_zone_types`. @@ -185,62 +154,16 @@ class TriggerGenerator: 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 + if isinstance(cp, self.capture_zone_types) and not cp.is_fleet: + zone_color = {1: 0.0, 2: 0.0, 3: 0.0, 4: 0.149} trigger_zone = self.mission.triggers.add_triggerzone( cp.position, radius=TRIGGER_RADIUS_CAPTURE, hidden=False, - name="CAPTURE", + name=cp.name, + color=zone_color, ) - 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(self) -> None: player_coalition = "blue" @@ -249,13 +172,7 @@ class TriggerGenerator: self._set_skill(player_coalition, enemy_coalition) self._set_allegiances(player_coalition, enemy_coalition) self._gen_markers() - self._generate_capture_triggers(player_coalition, enemy_coalition) - if self.game.settings.ground_start_scenery_remove_triggers: - try: - self._generate_clear_statics_trigger(self.game.scenery_clear_zones) - self.game.scenery_clear_zones.clear() - except AttributeError: - logging.info(f"Unable to create Clear Statics triggers") + self._generate_pretense_zone_triggers(player_coalition, enemy_coalition) @classmethod def get_capture_zone_flag(cls) -> int: diff --git a/qt_ui/uiconstants.py b/qt_ui/uiconstants.py index dcb95b1a..8edc69a0 100644 --- a/qt_ui/uiconstants.py +++ b/qt_ui/uiconstants.py @@ -32,6 +32,7 @@ def load_icons(): "./resources/ui/misc/" + get_theme_icons() + "/github.png" ) ICONS["Ukraine"] = QPixmap("./resources/ui/misc/ukraine.png") + ICONS["Pretense"] = QPixmap("./resources/ui/misc/pretense.png") ICONS["Control Points"] = QPixmap( "./resources/ui/misc/" + get_theme_icons() + "/circle.png" diff --git a/qt_ui/windows/QLiberationWindow.py b/qt_ui/windows/QLiberationWindow.py index bfe61851..c6c32c48 100644 --- a/qt_ui/windows/QLiberationWindow.py +++ b/qt_ui/windows/QLiberationWindow.py @@ -21,6 +21,7 @@ from game import Game, VERSION, persistency, Migrator from game.debriefing import Debriefing from game.game import TurnState from game.layout import LAYOUTS +from game.pretense.pretensemissiongenerator import PretenseMissionGenerator from game.server import EventStream, GameContext from game.server.dependencies import QtCallbacks, QtContext from game.theater import ControlPoint, MissionTarget, TheaterGroundObject @@ -41,6 +42,7 @@ from qt_ui.windows.groundobject.QGroundObjectMenu import QGroundObjectMenu from qt_ui.windows.infos.QInfoPanel import QInfoPanel from qt_ui.windows.logs.QLogsWindow import QLogsWindow from qt_ui.windows.newgame.QNewGameWizard import NewGameWizard +from qt_ui.windows.newgame.QNewPretenseWizard import NewPretenseWizard from qt_ui.windows.notes.QNotesWindow import QNotesWindow from qt_ui.windows.preferences.QLiberationPreferencesWindow import ( QLiberationPreferencesWindow, @@ -193,6 +195,10 @@ class QLiberationWindow(QMainWindow): lambda: webbrowser.open_new_tab("https://shdwp.github.io/ukraine/") ) + self.newPretenseAction = QAction("&New Pretense Campaign", self) + self.newPretenseAction.setIcon(QIcon(CONST.ICONS["Pretense"])) + self.newPretenseAction.triggered.connect(self.newPretenseCampaign) + self.openLogsAction = QAction("Show &logs", self) self.openLogsAction.triggered.connect(self.showLogsDialog) @@ -234,6 +240,7 @@ class QLiberationWindow(QMainWindow): self.links_bar.addAction(self.openDiscordAction) self.links_bar.addAction(self.openGithubAction) self.links_bar.addAction(self.ukraineAction) + self.links_bar.addAction(self.newPretenseAction) self.actions_bar = self.addToolBar("Actions") self.actions_bar.addAction(self.openSettingsAction) @@ -303,6 +310,15 @@ class QLiberationWindow(QMainWindow): wizard.show() wizard.accepted.connect(lambda: self.onGameGenerated(wizard.generatedGame)) + def newPretenseCampaign(self): + output = persistency.mission_path_for("pretense_campaign.miz") + PretenseMissionGenerator( + self.game, self.game.conditions.start_time + ).generate_miz(output) + title = "Pretense campaign generated" + msg = f"A Pretense campaign mission has been successfully generated in {output}" + QMessageBox.information(QApplication.focusWidget(), title, msg, QMessageBox.Ok) + def openFile(self): if self.game is not None and self.game.savepath: save_dir = self.game.savepath From af4cf03335e30b362f20ea89c4423240cbdbd263 Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Sat, 9 Sep 2023 16:12:18 +0300 Subject: [PATCH 104/243] Copied flightgroupspawner.py as a template/inheritance for generating Pretense campaigns from Retribution campaigns. --- game/pretense/pretenseflightgroupspawner.py | 433 ++++++++++++++++++++ 1 file changed, 433 insertions(+) create mode 100644 game/pretense/pretenseflightgroupspawner.py diff --git a/game/pretense/pretenseflightgroupspawner.py b/game/pretense/pretenseflightgroupspawner.py new file mode 100644 index 00000000..f3b19e87 --- /dev/null +++ b/game/pretense/pretenseflightgroupspawner.py @@ -0,0 +1,433 @@ +import logging +import random +from typing import Any, Union, Tuple, Optional + +from dcs import Mission +from dcs.country import Country +from dcs.mapping import Vector2, Point +from dcs.mission import StartType as DcsStartType +from dcs.planes import F_14A, Su_33 +from dcs.point import PointAction +from dcs.ships import KUZNECOW +from dcs.terrain import NoParkingSlotError +from dcs.unitgroup import ( + FlyingGroup, + ShipGroup, + StaticGroup, + HelicopterGroup, + PlaneGroup, +) + +from game.ato import Flight +from game.ato.flightstate import InFlight +from game.ato.starttype import StartType +from game.ato.traveltime import GroundSpeed +from game.missiongenerator.missiondata import MissionData +from game.naming import namegen +from game.theater import Airfield, ControlPoint, Fob, NavalControlPoint, OffMapSpawn +from game.utils import feet, meters + +WARM_START_HELI_ALT = meters(500) +WARM_START_ALTITUDE = meters(3000) + +# In-flight spawns are MSL for the first waypoint (this can maybe be changed to AGL, but +# AGL waypoints have different piloting behavior, so we need to check whether that's +# safe to do first), so spawn them high enough that they're unlikely to be near (or +# under) the ground, or any nearby obstacles. The highest airfield in DCS is Kerman in +# PG at 5700ft. This could still be too low if there are tall obstacles near the +# airfield, but the lowest we can push this the better to avoid spawning helicopters +# well above the altitude for WP1. +MINIMUM_MID_MISSION_SPAWN_ALTITUDE_MSL = feet(6000) +MINIMUM_MID_MISSION_SPAWN_ALTITUDE_AGL = feet(500) + +STACK_SEPARATION = feet(200) + +RTB_ALTITUDE = meters(800) +RTB_DISTANCE = 5000 +HELI_ALT = 500 + + +class FlightGroupSpawner: + def __init__( + self, + flight: Flight, + country: Country, + mission: Mission, + helipads: dict[ControlPoint, list[StaticGroup]], + ground_spawns_roadbase: dict[ControlPoint, list[Tuple[StaticGroup, Point]]], + ground_spawns: dict[ControlPoint, list[Tuple[StaticGroup, Point]]], + mission_data: MissionData, + ) -> None: + self.flight = flight + self.country = country + self.mission = mission + self.helipads = helipads + self.ground_spawns_roadbase = ground_spawns_roadbase + self.ground_spawns = ground_spawns + self.mission_data = mission_data + + def create_flight_group(self) -> FlyingGroup[Any]: + """Creates the group for the flight and adds it to the mission. + + Each flight is spawned according to its FlightState at the time of mission + generation. Aircraft that are WaitingForStart will be set up based on their + StartType with a delay. Note that delays are actually created during waypoint + generation. + + Aircraft that are *not* WaitingForStart will be spawned in their current state. + We cannot spawn aircraft mid-taxi, so when the simulated state is near the end + of a long taxi period the aircraft will be spawned in their parking spot. This + could lead to problems but that's what loiter points are for. The other pre- + flight states have the same problem but are much shorter and more easily covered + by the loiter time. Player flights that are spawned near the end of their cold + start have the biggest problem but players are able to cut corners to make up + for lost time. + + Aircraft that are already in the air will be spawned at their estimated + location, speed, and altitude based on their flight plan. + """ + if ( + self.flight.state.is_waiting_for_start + or self.flight.state.spawn_type is not StartType.IN_FLIGHT + ): + grp = self.generate_flight_at_departure() + self.flight.group_id = grp.id + return grp + grp = self.generate_mid_mission() + self.flight.group_id = grp.id + return grp + + def create_idle_aircraft(self) -> Optional[FlyingGroup[Any]]: + group = None + if ( + self.flight.is_helo + or self.flight.is_lha + and isinstance(self.flight.squadron.location, Fob) + ): + group = self._generate_at_cp_helipad( + name=namegen.next_aircraft_name(self.country, self.flight), + cp=self.flight.squadron.location, + ) + elif isinstance(self.flight.squadron.location, Fob): + group = self._generate_at_cp_ground_spawn( + name=namegen.next_aircraft_name(self.country, self.flight), + cp=self.flight.squadron.location, + ) + elif isinstance(self.flight.squadron.location, Airfield): + group = self._generate_at_airfield( + name=namegen.next_aircraft_name(self.country, self.flight), + airfield=self.flight.squadron.location, + ) + if group: + group.uncontrolled = True + return group + + @property + def start_type(self) -> StartType: + return self.flight.state.spawn_type + + def generate_flight_at_departure(self) -> FlyingGroup[Any]: + name = namegen.next_aircraft_name(self.country, self.flight) + cp = self.flight.departure + try: + if self.start_type is StartType.IN_FLIGHT: + group = self._generate_over_departure(name, cp) + return group + elif isinstance(cp, NavalControlPoint): + group_name = cp.get_carrier_group_name() + carrier_group = self.mission.find_group(group_name) + if not isinstance(carrier_group, ShipGroup): + raise RuntimeError( + f"Carrier group {carrier_group} is a " + f"{carrier_group.__class__.__name__}, expected a ShipGroup" + ) + return self._generate_at_group(name, carrier_group) + elif isinstance(cp, Fob): + is_heli = self.flight.squadron.aircraft.helicopter + is_vtol = not is_heli and self.flight.squadron.aircraft.lha_capable + if not is_heli and not is_vtol and not cp.has_ground_spawns: + raise RuntimeError( + f"Cannot spawn fixed-wing aircraft at {cp} because of insufficient ground spawn slots." + ) + pilot_count = len(self.flight.roster.pilots) + if ( + not is_heli + and self.flight.roster.player_count != pilot_count + and not self.flight.coalition.game.settings.ground_start_ai_planes + ): + raise RuntimeError( + f"Fixed-wing aircraft at {cp} must be piloted by humans exclusively because" + f' the "AI fixed-wing aircraft can use roadbases / bases with only ground' + f' spawns" setting is currently disabled.' + ) + if cp.has_helipads and (is_heli or is_vtol): + pad_group = self._generate_at_cp_helipad(name, cp) + if pad_group is not None: + return pad_group + if cp.has_ground_spawns and (self.flight.client_count > 0 or is_heli): + pad_group = self._generate_at_cp_ground_spawn(name, cp) + if pad_group is not None: + return pad_group + return self._generate_over_departure(name, cp) + elif isinstance(cp, Airfield): + is_heli = self.flight.squadron.aircraft.helicopter + if cp.has_helipads and is_heli: + pad_group = self._generate_at_cp_helipad(name, cp) + if pad_group is not None: + return pad_group + if ( + cp.has_ground_spawns + and len(self.ground_spawns[cp]) + + len(self.ground_spawns_roadbase[cp]) + >= self.flight.count + and (self.flight.client_count > 0 or is_heli) + ): + pad_group = self._generate_at_cp_ground_spawn(name, cp) + if pad_group is not None: + return pad_group + return self._generate_at_airfield(name, cp) + else: + raise NotImplementedError( + f"Aircraft spawn behavior not implemented for {cp} ({cp.__class__})" + ) + except NoParkingSlotError: + # Generated when there is no place on Runway or on Parking Slots + logging.warning( + "No room on runway or parking slots. Starting from the air." + ) + self.flight.start_type = StartType.IN_FLIGHT + group = self._generate_over_departure(name, cp) + return group + + def generate_mid_mission(self) -> FlyingGroup[Any]: + assert isinstance(self.flight.state, InFlight) + name = namegen.next_aircraft_name(self.country, self.flight) + speed = self.flight.state.estimate_speed() + pos = self.flight.state.estimate_position() + pos += Vector2(random.randint(100, 1000), random.randint(100, 1000)) + alt, alt_type = self.flight.state.estimate_altitude() + cp = self.flight.squadron.location.id + + if cp not in self.mission_data.cp_stack: + self.mission_data.cp_stack[cp] = MINIMUM_MID_MISSION_SPAWN_ALTITUDE_AGL + + # We don't know where the ground is, so just make sure that any aircraft + # spawning at an MSL altitude is spawned at some minimum altitude. + # https://github.com/dcs-liberation/dcs_liberation/issues/1941 + if alt_type == "BARO" and alt < MINIMUM_MID_MISSION_SPAWN_ALTITUDE_MSL: + alt = MINIMUM_MID_MISSION_SPAWN_ALTITUDE_MSL + + # Set a minimum AGL value for 'alt' if needed, + # otherwise planes might crash in trees and stuff. + if alt_type == "RADIO" and alt < self.mission_data.cp_stack[cp]: + alt = self.mission_data.cp_stack[cp] + self.mission_data.cp_stack[cp] += STACK_SEPARATION + + group = self.mission.flight_group( + country=self.country, + name=name, + aircraft_type=self.flight.unit_type.dcs_unit_type, + airport=None, + position=pos, + altitude=alt.meters, + speed=speed.kph, + maintask=None, + group_size=self.flight.count, + ) + + group.points[0].alt_type = alt_type + return group + + def _generate_at_airfield(self, name: str, airfield: Airfield) -> FlyingGroup[Any]: + # TODO: Delayed runway starts should be converted to air starts for multiplayer. + # Runway starts do not work with late activated aircraft in multiplayer. Instead + # of spawning on the runway the aircraft will spawn on the taxiway, potentially + # somewhere that they don't fit anyway. We should either upgrade these to air + # starts or (less likely) downgrade to warm starts to avoid the issue when the + # player is generating the mission for multiplayer (which would need a new + # option). + self.flight.unit_type.dcs_unit_type.load_payloads() + return self.mission.flight_group_from_airport( + country=self.country, + name=name, + aircraft_type=self.flight.unit_type.dcs_unit_type, + airport=airfield.airport, + maintask=None, + start_type=self._start_type_at_airfield(airfield), + group_size=self.flight.count, + parking_slots=None, + ) + + def _generate_over_departure( + self, name: str, origin: ControlPoint + ) -> FlyingGroup[Any]: + at = origin.position + + alt_type = "RADIO" + if isinstance(origin, OffMapSpawn): + alt = self.flight.flight_plan.waypoints[0].alt + alt_type = self.flight.flight_plan.waypoints[0].alt_type + elif self.flight.unit_type.helicopter: + alt = WARM_START_HELI_ALT + else: + if origin.id not in self.mission_data.cp_stack: + min_alt = MINIMUM_MID_MISSION_SPAWN_ALTITUDE_AGL + self.mission_data.cp_stack[origin.id] = min_alt + alt = self.mission_data.cp_stack[origin.id] + self.mission_data.cp_stack[origin.id] += STACK_SEPARATION + + speed = GroundSpeed.for_flight(self.flight, alt) + pos = at + Vector2(random.randint(100, 1000), random.randint(100, 1000)) + + group = self.mission.flight_group( + country=self.country, + name=name, + aircraft_type=self.flight.unit_type.dcs_unit_type, + airport=None, + position=pos, + altitude=alt.meters, + speed=speed.kph, + maintask=None, + group_size=self.flight.count, + ) + + group.points[0].alt_type = alt_type + return group + + def _generate_at_group( + self, name: str, at: Union[ShipGroup, StaticGroup] + ) -> FlyingGroup[Any]: + return self.mission.flight_group_from_unit( + country=self.country, + name=name, + aircraft_type=self.flight.unit_type.dcs_unit_type, + pad_group=at, + maintask=None, + start_type=self._start_type_at_group(at), + group_size=self.flight.count, + ) + + def _generate_at_cp_helipad( + self, name: str, cp: ControlPoint + ) -> Optional[FlyingGroup[Any]]: + try: + helipad = self.helipads[cp].pop() + except IndexError as ex: + logging.warning("Not enough helipads available at " + str(ex)) + if isinstance(cp, Airfield): + return self._generate_at_airfield(name, cp) + else: + return None + # raise RuntimeError(f"Not enough helipads available at {cp}") from ex + + group = self._generate_at_group(name, helipad) + + # Note : A bit dirty, need better support in pydcs + group.points[0].action = PointAction.FromGroundArea + group.points[0].type = "TakeOffGround" + group.units[0].heading = helipad.units[0].heading + if self.start_type is not StartType.COLD: + group.points[0].action = PointAction.FromGroundAreaHot + group.points[0].type = "TakeOffGroundHot" + + wpt = group.waypoint("LANDING") + if wpt: + hpad = self.helipads[self.flight.arrival].pop(0) + wpt.helipad_id = hpad.units[0].id + wpt.link_unit = hpad.units[0].id + self.helipads[self.flight.arrival].append(hpad) + + for i in range(self.flight.count - 1): + try: + helipad = self.helipads[cp].pop() + terrain = cp.coalition.game.theater.terrain + group.units[1 + i].position = Point( + helipad.x, helipad.y, terrain=terrain + ) + group.units[1 + i].heading = helipad.units[0].heading + except IndexError as ex: + logging.warning("Not enough helipads available at " + str(ex)) + if isinstance(cp, Airfield): + return self._generate_at_airfield(name, cp) + else: + if isinstance(group, HelicopterGroup): + self.country.helicopter_group.remove(group) + elif isinstance(group, PlaneGroup): + self.country.plane_group.remove(group) + return None + return group + + def _generate_at_cp_ground_spawn( + self, name: str, cp: ControlPoint + ) -> Optional[FlyingGroup[Any]]: + try: + if len(self.ground_spawns_roadbase[cp]) > 0: + ground_spawn = self.ground_spawns_roadbase[cp].pop() + else: + ground_spawn = self.ground_spawns[cp].pop() + except IndexError as ex: + logging.warning("Not enough STOL slots available at " + str(ex)) + return None + # raise RuntimeError(f"Not enough STOL slots available at {cp}") from ex + + group = self._generate_at_group(name, ground_spawn[0]) + + # Note : A bit dirty, need better support in pydcs + group.points[0].action = PointAction.FromGroundArea + group.points[0].type = "TakeOffGround" + group.units[0].heading = ground_spawn[0].units[0].heading + + try: + cp.coalition.game.scenery_clear_zones + except AttributeError: + cp.coalition.game.scenery_clear_zones = [] + cp.coalition.game.scenery_clear_zones.append(ground_spawn[1]) + + for i in range(self.flight.count - 1): + try: + terrain = cp.coalition.game.theater.terrain + if len(self.ground_spawns_roadbase[cp]) > 0: + ground_spawn = self.ground_spawns_roadbase[cp].pop() + else: + ground_spawn = self.ground_spawns[cp].pop() + group.units[1 + i].position = Point( + ground_spawn[0].x, ground_spawn[0].y, terrain=terrain + ) + group.units[1 + i].heading = ground_spawn[0].units[0].heading + except IndexError as ex: + raise RuntimeError(f"Not enough STOL slots available at {cp}") from ex + return group + + def dcs_start_type(self) -> DcsStartType: + if self.start_type is StartType.RUNWAY: + return DcsStartType.Runway + elif self.start_type is StartType.COLD: + return DcsStartType.Cold + elif self.start_type is StartType.WARM: + return DcsStartType.Warm + raise ValueError(f"There is no pydcs StartType matching {self.start_type}") + + def _start_type_at_airfield( + self, + airfield: Airfield, + ) -> DcsStartType: + return self.dcs_start_type() + + def _start_type_at_group( + self, + at: Union[ShipGroup, StaticGroup], + ) -> DcsStartType: + group_units = at.units + # Setting Su-33s starting from the non-supercarrier Kuznetsov to take off from + # runway to work around a DCS AI issue preventing Su-33s from taking off when + # set to "Takeoff from ramp" (#1352) + # Also setting the F-14A AI variant to start from cats since they are reported + # to have severe pathfinding problems when doing ramp starts (#1927) + if self.flight.unit_type.dcs_unit_type == F_14A or ( + self.flight.unit_type.dcs_unit_type == Su_33 + and group_units[0] is not None + and group_units[0].type == KUZNECOW.id + ): + return DcsStartType.Runway + else: + return self.dcs_start_type() From 9b54730191a5a1f644ae7566675453c89325c5cb Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Sat, 9 Sep 2023 19:52:16 +0300 Subject: [PATCH 105/243] Implemented generating trigger zones for supply routes, theater ground objects and helicopter supply points. Implemented name generator for Pretense air units. --- game/pretense/pretenseaircraftgenerator.py | 5 +- game/pretense/pretenseflightgroupspawner.py | 354 ++------------------ game/pretense/pretensetriggergenerator.py | 44 ++- 3 files changed, 76 insertions(+), 327 deletions(-) diff --git a/game/pretense/pretenseaircraftgenerator.py b/game/pretense/pretenseaircraftgenerator.py index f6c79c8b..334a8871 100644 --- a/game/pretense/pretenseaircraftgenerator.py +++ b/game/pretense/pretenseaircraftgenerator.py @@ -36,7 +36,6 @@ from game.theater.controlpoint import ( from game.unitmap import UnitMap from game.missiongenerator.aircraft.aircraftpainter import AircraftPainter from game.missiongenerator.aircraft.flightdata import FlightData -from game.missiongenerator.aircraft.flightgroupspawner import FlightGroupSpawner from game.data.weapons import WeaponType if TYPE_CHECKING: @@ -194,8 +193,10 @@ class PretenseAircraftGenerator: def create_and_configure_flight( self, flight: Flight, country: Country, dynamic_runways: Dict[str, RunwayData] ) -> FlyingGroup[Any]: + from game.pretense.pretenseflightgroupspawner import PretenseFlightGroupSpawner + """Creates and configures the flight group in the mission.""" - group = FlightGroupSpawner( + group = PretenseFlightGroupSpawner( flight, country, self.mission, diff --git a/game/pretense/pretenseflightgroupspawner.py b/game/pretense/pretenseflightgroupspawner.py index f3b19e87..908edcc0 100644 --- a/game/pretense/pretenseflightgroupspawner.py +++ b/game/pretense/pretenseflightgroupspawner.py @@ -1,53 +1,40 @@ import logging -import random -from typing import Any, Union, Tuple, Optional +import re +from typing import Any, Tuple from dcs import Mission from dcs.country import Country from dcs.mapping import Vector2, Point -from dcs.mission import StartType as DcsStartType -from dcs.planes import F_14A, Su_33 -from dcs.point import PointAction -from dcs.ships import KUZNECOW from dcs.terrain import NoParkingSlotError from dcs.unitgroup import ( FlyingGroup, ShipGroup, StaticGroup, - HelicopterGroup, - PlaneGroup, ) from game.ato import Flight -from game.ato.flightstate import InFlight from game.ato.starttype import StartType -from game.ato.traveltime import GroundSpeed +from game.missiongenerator.aircraft.flightgroupspawner import FlightGroupSpawner from game.missiongenerator.missiondata import MissionData -from game.naming import namegen -from game.theater import Airfield, ControlPoint, Fob, NavalControlPoint, OffMapSpawn +from game.naming import NameGenerator +from game.theater import Airfield, ControlPoint, Fob, NavalControlPoint from game.utils import feet, meters -WARM_START_HELI_ALT = meters(500) -WARM_START_ALTITUDE = meters(3000) -# In-flight spawns are MSL for the first waypoint (this can maybe be changed to AGL, but -# AGL waypoints have different piloting behavior, so we need to check whether that's -# safe to do first), so spawn them high enough that they're unlikely to be near (or -# under) the ground, or any nearby obstacles. The highest airfield in DCS is Kerman in -# PG at 5700ft. This could still be too low if there are tall obstacles near the -# airfield, but the lowest we can push this the better to avoid spawning helicopters -# well above the altitude for WP1. -MINIMUM_MID_MISSION_SPAWN_ALTITUDE_MSL = feet(6000) -MINIMUM_MID_MISSION_SPAWN_ALTITUDE_AGL = feet(500) - -STACK_SEPARATION = feet(200) - -RTB_ALTITUDE = meters(800) -RTB_DISTANCE = 5000 -HELI_ALT = 500 +class PretenseNameGenerator(NameGenerator): + @classmethod + def next_pretense_aircraft_name(cls, cp: ControlPoint, flight: Flight) -> str: + cls.aircraft_number += 1 + cp_name_trimmed = "".join([i for i in cp.name.lower() if i.isalnum()]) + return "{}-{}-{}".format( + cp_name_trimmed, str(flight.flight_type).lower(), cls.aircraft_number + ) -class FlightGroupSpawner: +namegen = PretenseNameGenerator + + +class PretenseFlightGroupSpawner(FlightGroupSpawner): def __init__( self, flight: Flight, @@ -58,6 +45,16 @@ class FlightGroupSpawner: ground_spawns: dict[ControlPoint, list[Tuple[StaticGroup, Point]]], mission_data: MissionData, ) -> None: + super().__init__( + flight, + country, + mission, + helipads, + ground_spawns_roadbase, + ground_spawns, + mission_data, + ) + self.flight = flight self.country = country self.mission = mission @@ -66,69 +63,11 @@ class FlightGroupSpawner: self.ground_spawns = ground_spawns self.mission_data = mission_data - def create_flight_group(self) -> FlyingGroup[Any]: - """Creates the group for the flight and adds it to the mission. - - Each flight is spawned according to its FlightState at the time of mission - generation. Aircraft that are WaitingForStart will be set up based on their - StartType with a delay. Note that delays are actually created during waypoint - generation. - - Aircraft that are *not* WaitingForStart will be spawned in their current state. - We cannot spawn aircraft mid-taxi, so when the simulated state is near the end - of a long taxi period the aircraft will be spawned in their parking spot. This - could lead to problems but that's what loiter points are for. The other pre- - flight states have the same problem but are much shorter and more easily covered - by the loiter time. Player flights that are spawned near the end of their cold - start have the biggest problem but players are able to cut corners to make up - for lost time. - - Aircraft that are already in the air will be spawned at their estimated - location, speed, and altitude based on their flight plan. - """ - if ( - self.flight.state.is_waiting_for_start - or self.flight.state.spawn_type is not StartType.IN_FLIGHT - ): - grp = self.generate_flight_at_departure() - self.flight.group_id = grp.id - return grp - grp = self.generate_mid_mission() - self.flight.group_id = grp.id - return grp - - def create_idle_aircraft(self) -> Optional[FlyingGroup[Any]]: - group = None - if ( - self.flight.is_helo - or self.flight.is_lha - and isinstance(self.flight.squadron.location, Fob) - ): - group = self._generate_at_cp_helipad( - name=namegen.next_aircraft_name(self.country, self.flight), - cp=self.flight.squadron.location, - ) - elif isinstance(self.flight.squadron.location, Fob): - group = self._generate_at_cp_ground_spawn( - name=namegen.next_aircraft_name(self.country, self.flight), - cp=self.flight.squadron.location, - ) - elif isinstance(self.flight.squadron.location, Airfield): - group = self._generate_at_airfield( - name=namegen.next_aircraft_name(self.country, self.flight), - airfield=self.flight.squadron.location, - ) - if group: - group.uncontrolled = True - return group - - @property - def start_type(self) -> StartType: - return self.flight.state.spawn_type - def generate_flight_at_departure(self) -> FlyingGroup[Any]: - name = namegen.next_aircraft_name(self.country, self.flight) cp = self.flight.departure + name = namegen.next_pretense_aircraft_name(cp, self.flight) + + print(name) try: if self.start_type is StartType.IN_FLIGHT: group = self._generate_over_departure(name, cp) @@ -198,236 +137,3 @@ class FlightGroupSpawner: self.flight.start_type = StartType.IN_FLIGHT group = self._generate_over_departure(name, cp) return group - - def generate_mid_mission(self) -> FlyingGroup[Any]: - assert isinstance(self.flight.state, InFlight) - name = namegen.next_aircraft_name(self.country, self.flight) - speed = self.flight.state.estimate_speed() - pos = self.flight.state.estimate_position() - pos += Vector2(random.randint(100, 1000), random.randint(100, 1000)) - alt, alt_type = self.flight.state.estimate_altitude() - cp = self.flight.squadron.location.id - - if cp not in self.mission_data.cp_stack: - self.mission_data.cp_stack[cp] = MINIMUM_MID_MISSION_SPAWN_ALTITUDE_AGL - - # We don't know where the ground is, so just make sure that any aircraft - # spawning at an MSL altitude is spawned at some minimum altitude. - # https://github.com/dcs-liberation/dcs_liberation/issues/1941 - if alt_type == "BARO" and alt < MINIMUM_MID_MISSION_SPAWN_ALTITUDE_MSL: - alt = MINIMUM_MID_MISSION_SPAWN_ALTITUDE_MSL - - # Set a minimum AGL value for 'alt' if needed, - # otherwise planes might crash in trees and stuff. - if alt_type == "RADIO" and alt < self.mission_data.cp_stack[cp]: - alt = self.mission_data.cp_stack[cp] - self.mission_data.cp_stack[cp] += STACK_SEPARATION - - group = self.mission.flight_group( - country=self.country, - name=name, - aircraft_type=self.flight.unit_type.dcs_unit_type, - airport=None, - position=pos, - altitude=alt.meters, - speed=speed.kph, - maintask=None, - group_size=self.flight.count, - ) - - group.points[0].alt_type = alt_type - return group - - def _generate_at_airfield(self, name: str, airfield: Airfield) -> FlyingGroup[Any]: - # TODO: Delayed runway starts should be converted to air starts for multiplayer. - # Runway starts do not work with late activated aircraft in multiplayer. Instead - # of spawning on the runway the aircraft will spawn on the taxiway, potentially - # somewhere that they don't fit anyway. We should either upgrade these to air - # starts or (less likely) downgrade to warm starts to avoid the issue when the - # player is generating the mission for multiplayer (which would need a new - # option). - self.flight.unit_type.dcs_unit_type.load_payloads() - return self.mission.flight_group_from_airport( - country=self.country, - name=name, - aircraft_type=self.flight.unit_type.dcs_unit_type, - airport=airfield.airport, - maintask=None, - start_type=self._start_type_at_airfield(airfield), - group_size=self.flight.count, - parking_slots=None, - ) - - def _generate_over_departure( - self, name: str, origin: ControlPoint - ) -> FlyingGroup[Any]: - at = origin.position - - alt_type = "RADIO" - if isinstance(origin, OffMapSpawn): - alt = self.flight.flight_plan.waypoints[0].alt - alt_type = self.flight.flight_plan.waypoints[0].alt_type - elif self.flight.unit_type.helicopter: - alt = WARM_START_HELI_ALT - else: - if origin.id not in self.mission_data.cp_stack: - min_alt = MINIMUM_MID_MISSION_SPAWN_ALTITUDE_AGL - self.mission_data.cp_stack[origin.id] = min_alt - alt = self.mission_data.cp_stack[origin.id] - self.mission_data.cp_stack[origin.id] += STACK_SEPARATION - - speed = GroundSpeed.for_flight(self.flight, alt) - pos = at + Vector2(random.randint(100, 1000), random.randint(100, 1000)) - - group = self.mission.flight_group( - country=self.country, - name=name, - aircraft_type=self.flight.unit_type.dcs_unit_type, - airport=None, - position=pos, - altitude=alt.meters, - speed=speed.kph, - maintask=None, - group_size=self.flight.count, - ) - - group.points[0].alt_type = alt_type - return group - - def _generate_at_group( - self, name: str, at: Union[ShipGroup, StaticGroup] - ) -> FlyingGroup[Any]: - return self.mission.flight_group_from_unit( - country=self.country, - name=name, - aircraft_type=self.flight.unit_type.dcs_unit_type, - pad_group=at, - maintask=None, - start_type=self._start_type_at_group(at), - group_size=self.flight.count, - ) - - def _generate_at_cp_helipad( - self, name: str, cp: ControlPoint - ) -> Optional[FlyingGroup[Any]]: - try: - helipad = self.helipads[cp].pop() - except IndexError as ex: - logging.warning("Not enough helipads available at " + str(ex)) - if isinstance(cp, Airfield): - return self._generate_at_airfield(name, cp) - else: - return None - # raise RuntimeError(f"Not enough helipads available at {cp}") from ex - - group = self._generate_at_group(name, helipad) - - # Note : A bit dirty, need better support in pydcs - group.points[0].action = PointAction.FromGroundArea - group.points[0].type = "TakeOffGround" - group.units[0].heading = helipad.units[0].heading - if self.start_type is not StartType.COLD: - group.points[0].action = PointAction.FromGroundAreaHot - group.points[0].type = "TakeOffGroundHot" - - wpt = group.waypoint("LANDING") - if wpt: - hpad = self.helipads[self.flight.arrival].pop(0) - wpt.helipad_id = hpad.units[0].id - wpt.link_unit = hpad.units[0].id - self.helipads[self.flight.arrival].append(hpad) - - for i in range(self.flight.count - 1): - try: - helipad = self.helipads[cp].pop() - terrain = cp.coalition.game.theater.terrain - group.units[1 + i].position = Point( - helipad.x, helipad.y, terrain=terrain - ) - group.units[1 + i].heading = helipad.units[0].heading - except IndexError as ex: - logging.warning("Not enough helipads available at " + str(ex)) - if isinstance(cp, Airfield): - return self._generate_at_airfield(name, cp) - else: - if isinstance(group, HelicopterGroup): - self.country.helicopter_group.remove(group) - elif isinstance(group, PlaneGroup): - self.country.plane_group.remove(group) - return None - return group - - def _generate_at_cp_ground_spawn( - self, name: str, cp: ControlPoint - ) -> Optional[FlyingGroup[Any]]: - try: - if len(self.ground_spawns_roadbase[cp]) > 0: - ground_spawn = self.ground_spawns_roadbase[cp].pop() - else: - ground_spawn = self.ground_spawns[cp].pop() - except IndexError as ex: - logging.warning("Not enough STOL slots available at " + str(ex)) - return None - # raise RuntimeError(f"Not enough STOL slots available at {cp}") from ex - - group = self._generate_at_group(name, ground_spawn[0]) - - # Note : A bit dirty, need better support in pydcs - group.points[0].action = PointAction.FromGroundArea - group.points[0].type = "TakeOffGround" - group.units[0].heading = ground_spawn[0].units[0].heading - - try: - cp.coalition.game.scenery_clear_zones - except AttributeError: - cp.coalition.game.scenery_clear_zones = [] - cp.coalition.game.scenery_clear_zones.append(ground_spawn[1]) - - for i in range(self.flight.count - 1): - try: - terrain = cp.coalition.game.theater.terrain - if len(self.ground_spawns_roadbase[cp]) > 0: - ground_spawn = self.ground_spawns_roadbase[cp].pop() - else: - ground_spawn = self.ground_spawns[cp].pop() - group.units[1 + i].position = Point( - ground_spawn[0].x, ground_spawn[0].y, terrain=terrain - ) - group.units[1 + i].heading = ground_spawn[0].units[0].heading - except IndexError as ex: - raise RuntimeError(f"Not enough STOL slots available at {cp}") from ex - return group - - def dcs_start_type(self) -> DcsStartType: - if self.start_type is StartType.RUNWAY: - return DcsStartType.Runway - elif self.start_type is StartType.COLD: - return DcsStartType.Cold - elif self.start_type is StartType.WARM: - return DcsStartType.Warm - raise ValueError(f"There is no pydcs StartType matching {self.start_type}") - - def _start_type_at_airfield( - self, - airfield: Airfield, - ) -> DcsStartType: - return self.dcs_start_type() - - def _start_type_at_group( - self, - at: Union[ShipGroup, StaticGroup], - ) -> DcsStartType: - group_units = at.units - # Setting Su-33s starting from the non-supercarrier Kuznetsov to take off from - # runway to work around a DCS AI issue preventing Su-33s from taking off when - # set to "Takeoff from ramp" (#1352) - # Also setting the F-14A AI variant to start from cats since they are reported - # to have severe pathfinding problems when doing ramp starts (#1927) - if self.flight.unit_type.dcs_unit_type == F_14A or ( - self.flight.unit_type.dcs_unit_type == Su_33 - and group_units[0] is not None - and group_units[0].type == KUZNECOW.id - ): - return DcsStartType.Runway - else: - return self.dcs_start_type() diff --git a/game/pretense/pretensetriggergenerator.py b/game/pretense/pretensetriggergenerator.py index 11932f87..e5ea05e9 100644 --- a/game/pretense/pretensetriggergenerator.py +++ b/game/pretense/pretensetriggergenerator.py @@ -50,6 +50,9 @@ TRIGGER_RADIUS_MEDIUM = 100000 TRIGGER_RADIUS_LARGE = 150000 TRIGGER_RADIUS_ALL_MAP = 3000000 TRIGGER_RADIUS_CLEAR_SCENERY = 1000 +TRIGGER_RADIUS_PRETENSE_TGO = 500 +TRIGGER_RADIUS_PRETENSE_SUPPLY = 500 +TRIGGER_RADIUS_PRETENSE_HELI = 1000 class Silence(Option): @@ -156,7 +159,7 @@ class PretenseTriggerGenerator: for cp in self.game.theater.controlpoints: if isinstance(cp, self.capture_zone_types) and not cp.is_fleet: - zone_color = {1: 0.0, 2: 0.0, 3: 0.0, 4: 0.149} + zone_color = {1: 0.0, 2: 0.0, 3: 0.0, 4: 0.15} trigger_zone = self.mission.triggers.add_triggerzone( cp.position, radius=TRIGGER_RADIUS_CAPTURE, @@ -164,6 +167,45 @@ class PretenseTriggerGenerator: name=cp.name, color=zone_color, ) + cp_name_trimmed = "".join([i for i in cp.name.lower() if i.isalnum()]) + tgo_num = 0 + for tgo in cp.ground_objects: + tgo_num += 1 + zone_color = {1: 1.0, 2: 1.0, 3: 1.0, 4: 0.15} + trigger_zone = self.mission.triggers.add_triggerzone( + tgo.position, + radius=TRIGGER_RADIUS_PRETENSE_TGO, + hidden=False, + name=f"{cp_name_trimmed}-{tgo_num}", + color=zone_color, + ) + 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( + position=helipad, + radius=TRIGGER_RADIUS_PRETENSE_HELI, + hidden=False, + name=f"{cp_name_trimmed}-hsp", + color=zone_color, + ) + break + for supply_route in cp.convoy_routes.values(): + tgo_num += 1 + zone_color = {1: 1.0, 2: 1.0, 3: 1.0, 4: 0.15} + origin_position = supply_route[0] + next_position = supply_route[1] + convoy_heading = origin_position.heading_between_point(next_position) + supply_position = origin_position.point_from_heading( + convoy_heading, 300 + ) + trigger_zone = self.mission.triggers.add_triggerzone( + supply_position, + radius=TRIGGER_RADIUS_PRETENSE_TGO, + hidden=False, + name=f"{cp_name_trimmed}-sp", + color=zone_color, + ) + break def generate(self) -> None: player_coalition = "blue" From 3d781d89def062628d3ae591b0724ac64058d51a Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Sat, 9 Sep 2023 23:02:30 +0300 Subject: [PATCH 106/243] Copied tgogenerator.py as a template/inheritance for generating Pretense campaigns from Retribution campaigns. --- game/pretense/pretensetgogenerator.py | 1182 +++++++++++++++++++++++++ 1 file changed, 1182 insertions(+) create mode 100644 game/pretense/pretensetgogenerator.py diff --git a/game/pretense/pretensetgogenerator.py b/game/pretense/pretensetgogenerator.py new file mode 100644 index 00000000..326a1ded --- /dev/null +++ b/game/pretense/pretensetgogenerator.py @@ -0,0 +1,1182 @@ +"""Generators for creating the groups for ground objectives. + +The classes in this file are responsible for creating the vehicle groups, ship +groups, statics, missile sites, and AA sites for the mission. Each of these +objectives is defined in the Theater by a TheaterGroundObject. These classes +create the pydcs groups and statics for those areas and add them to the mission. +""" +from __future__ import annotations + +import logging +import random +from collections import defaultdict +from typing import Any, Dict, Iterator, List, Optional, TYPE_CHECKING, Type, Tuple + +import dcs.vehicles +from dcs import Mission, Point, unitgroup +from dcs.action import DoScript, SceneryDestructionZone +from dcs.condition import MapObjectIsDead +from dcs.countries import * +from dcs.country import Country +from dcs.point import StaticPoint, PointAction +from dcs.ships import ( + CVN_71, + CVN_72, + CVN_73, + CVN_75, + Stennis, + Forrestal, + LHA_Tarawa, +) +from dcs.statics import Fortification +from dcs.task import ( + ActivateBeaconCommand, + ActivateICLSCommand, + ActivateLink4Command, + ActivateACLSCommand, + EPLRS, + FireAtPoint, + OptAlarmState, +) +from dcs.translation import String +from dcs.triggers import Event, TriggerOnce, TriggerStart, TriggerZone +from dcs.unit import Unit, InvisibleFARP, BaseFARP, SingleHeliPad, FARP +from dcs.unitgroup import MovingGroup, ShipGroup, StaticGroup, VehicleGroup +from dcs.unittype import ShipType, VehicleType +from dcs.vehicles import vehicle_map, Unarmed + +from game.missiongenerator.groundforcepainter import ( + NavalForcePainter, + GroundForcePainter, +) +from game.missiongenerator.missiondata import CarrierInfo, MissionData +from game.point_with_heading import PointWithHeading +from game.radio.RadioFrequencyContainer import RadioFrequencyContainer +from game.radio.radios import RadioFrequency, RadioRegistry +from game.radio.tacan import TacanBand, TacanChannel, TacanRegistry, TacanUsage +from game.runways import RunwayData +from game.theater import ( + ControlPoint, + TheaterGroundObject, + TheaterUnit, + NavalControlPoint, +) +from game.theater.theatergroundobject import ( + CarrierGroundObject, + GenericCarrierGroundObject, + LhaGroundObject, + MissileSiteGroundObject, +) +from game.theater.theatergroup import SceneryUnit, IadsGroundGroup +from game.unitmap import UnitMap +from game.utils import Heading, feet, knots, mps + +if TYPE_CHECKING: + from game import Game + +FARP_FRONTLINE_DISTANCE = 10000 +AA_CP_MIN_DISTANCE = 40000 + + +def farp_truck_types_for_country( + country_id: int, +) -> Tuple[Type[VehicleType], Type[VehicleType]]: + soviet_tankers: List[Type[VehicleType]] = [ + Unarmed.ATMZ_5, + Unarmed.ATZ_10, + Unarmed.ATZ_5, + Unarmed.ATZ_60_Maz, + Unarmed.TZ_22_KrAZ, + ] + soviet_trucks: List[Type[VehicleType]] = [ + Unarmed.S_75_ZIL, + Unarmed.GAZ_3308, + Unarmed.GAZ_66, + Unarmed.KAMAZ_Truck, + Unarmed.KrAZ6322, + Unarmed.Ural_375, + Unarmed.Ural_375_PBU, + Unarmed.Ural_4320_31, + Unarmed.Ural_4320T, + Unarmed.ZIL_135, + ] + + axis_trucks: List[Type[VehicleType]] = [Unarmed.Blitz_36_6700A] + + us_tankers: List[Type[VehicleType]] = [Unarmed.M978_HEMTT_Tanker] + us_trucks: List[Type[VehicleType]] = [Unarmed.M_818] + uk_trucks: List[Type[VehicleType]] = [Unarmed.Bedford_MWD] + + if country_id in [ + Abkhazia.id, + Algeria.id, + Bahrain.id, + Belarus.id, + Belgium.id, + Bulgaria.id, + China.id, + Croatia.id, + Cuba.id, + Cyprus.id, + CzechRepublic.id, + Egypt.id, + Ethiopia.id, + Finland.id, + GDR.id, + Georgia.id, + Ghana.id, + Greece.id, + Hungary.id, + India.id, + Insurgents.id, + Iraq.id, + Jordan.id, + Kazakhstan.id, + Lebanon.id, + Libya.id, + Morocco.id, + Nigeria.id, + NorthKorea.id, + Poland.id, + Romania.id, + Russia.id, + Serbia.id, + Slovakia.id, + Slovenia.id, + SouthAfrica.id, + SouthOssetia.id, + Sudan.id, + Syria.id, + Tunisia.id, + USSR.id, + Ukraine.id, + Venezuela.id, + Vietnam.id, + Yemen.id, + Yugoslavia.id, + ]: + tanker_type = random.choice(soviet_tankers) + ammo_truck_type = random.choice(soviet_trucks) + elif country_id in [ItalianSocialRepublic.id, ThirdReich.id]: + tanker_type = random.choice(soviet_tankers) + ammo_truck_type = random.choice(axis_trucks) + elif country_id in [ + Argentina.id, + Australia.id, + Austria.id, + Bolivia.id, + Brazil.id, + Canada.id, + Chile.id, + Denmark.id, + Ecuador.id, + France.id, + Germany.id, + Honduras.id, + Indonesia.id, + Iran.id, + Israel.id, + Italy.id, + Japan.id, + Kuwait.id, + Malaysia.id, + Mexico.id, + Norway.id, + Oman.id, + Pakistan.id, + Peru.id, + Philippines.id, + Portugal.id, + Qatar.id, + SaudiArabia.id, + SouthKorea.id, + Spain.id, + Sweden.id, + Switzerland.id, + Thailand.id, + TheNetherlands.id, + Turkey.id, + USA.id, + USAFAggressors.id, + UnitedArabEmirates.id, + ]: + tanker_type = random.choice(us_tankers) + ammo_truck_type = random.choice(us_trucks) + elif country_id in [UK.id]: + tanker_type = random.choice(us_tankers) + ammo_truck_type = random.choice(uk_trucks) + elif country_id in [CombinedJointTaskForcesBlue.id]: + tanker_types = us_tankers + truck_types = us_trucks + uk_trucks + + tanker_type = random.choice(tanker_types) + ammo_truck_type = random.choice(truck_types) + elif country_id in [CombinedJointTaskForcesRed.id]: + tanker_types = us_tankers + truck_types = us_trucks + uk_trucks + + tanker_type = random.choice(tanker_types) + ammo_truck_type = random.choice(truck_types) + elif country_id in [UnitedNationsPeacekeepers.id]: + tanker_types = soviet_tankers + us_tankers + truck_types = soviet_trucks + us_trucks + uk_trucks + + tanker_type = random.choice(tanker_types) + ammo_truck_type = random.choice(truck_types) + else: + tanker_types = soviet_tankers + us_tankers + truck_types = soviet_trucks + us_trucks + uk_trucks + axis_trucks + + tanker_type = random.choice(tanker_types) + ammo_truck_type = random.choice(truck_types) + + return tanker_type, ammo_truck_type + + +class GroundObjectGenerator: + """generates the DCS groups and units from the TheaterGroundObject""" + + def __init__( + self, + ground_object: TheaterGroundObject, + country: Country, + game: Game, + mission: Mission, + unit_map: UnitMap, + ) -> None: + self.ground_object = ground_object + self.country = country + self.game = game + self.m = mission + self.unit_map = unit_map + + @property + def culled(self) -> bool: + return self.game.iads_considerate_culling(self.ground_object) + + def generate(self) -> None: + if self.culled: + return + for group in self.ground_object.groups: + vehicle_units = [] + ship_units = [] + # Split the different unit types to be compliant to dcs limitation + for unit in group.units: + if unit.is_static: + if isinstance(unit, SceneryUnit): + # Special handling for scenery objects + self.add_trigger_zone_for_scenery(unit) + if ( + self.game.settings.plugin_option("skynetiads") + and isinstance(group, IadsGroundGroup) + and group.iads_role.participate + ): + # Generate a unit which can be controlled by skynet + self.generate_iads_command_unit(unit) + else: + # Create a static group for each static unit + self.create_static_group(unit) + elif unit.is_vehicle and unit.alive: + # All alive Vehicles + vehicle_units.append(unit) + elif unit.is_ship and unit.alive: + # All alive Ships + ship_units.append(unit) + if vehicle_units: + self.create_vehicle_group(group.group_name, vehicle_units) + if ship_units: + self.create_ship_group(group.group_name, ship_units) + + def create_vehicle_group( + self, group_name: str, units: list[TheaterUnit] + ) -> VehicleGroup: + vehicle_group: Optional[VehicleGroup] = None + for unit in units: + assert issubclass(unit.type, VehicleType) + faction = unit.ground_object.control_point.coalition.faction + if vehicle_group is None: + vehicle_group = self.m.vehicle_group( + self.country, + group_name, + unit.type, + position=unit.position, + heading=unit.position.heading.degrees, + ) + vehicle_group.units[0].player_can_drive = True + self.enable_eplrs(vehicle_group, unit.type) + vehicle_group.units[0].name = unit.unit_name + self.set_alarm_state(vehicle_group) + GroundForcePainter(faction, vehicle_group.units[0]).apply_livery() + else: + vehicle_unit = self.m.vehicle(unit.unit_name, unit.type) + vehicle_unit.player_can_drive = True + vehicle_unit.position = unit.position + vehicle_unit.heading = unit.position.heading.degrees + GroundForcePainter(faction, vehicle_unit).apply_livery() + vehicle_group.add_unit(vehicle_unit) + self._register_theater_unit(unit, vehicle_group.units[-1]) + if vehicle_group is None: + raise RuntimeError(f"Error creating VehicleGroup for {group_name}") + return vehicle_group + + def create_ship_group( + self, + group_name: str, + units: list[TheaterUnit], + frequency: Optional[RadioFrequency] = None, + ) -> ShipGroup: + ship_group: Optional[ShipGroup] = None + for unit in units: + assert issubclass(unit.type, ShipType) + faction = unit.ground_object.control_point.coalition.faction + if ship_group is None: + ship_group = self.m.ship_group( + self.country, + group_name, + unit.type, + position=unit.position, + heading=unit.position.heading.degrees, + ) + if frequency: + ship_group.set_frequency(frequency.hertz) + ship_group.units[0].name = unit.unit_name + self.set_alarm_state(ship_group) + NavalForcePainter(faction, ship_group.units[0]).apply_livery() + else: + ship_unit = self.m.ship(unit.unit_name, unit.type) + if frequency: + ship_unit.set_frequency(frequency.hertz) + ship_unit.position = unit.position + ship_unit.heading = unit.position.heading.degrees + NavalForcePainter(faction, ship_unit).apply_livery() + ship_group.add_unit(ship_unit) + self._register_theater_unit(unit, ship_group.units[-1]) + if ship_group is None: + raise RuntimeError(f"Error creating ShipGroup for {group_name}") + return ship_group + + def create_static_group(self, unit: TheaterUnit) -> None: + static_group = self.m.static_group( + country=self.country, + name=unit.unit_name, + _type=unit.type, + position=unit.position, + heading=unit.position.heading.degrees, + dead=not unit.alive, + ) + self._register_theater_unit(unit, static_group.units[0]) + + @staticmethod + def enable_eplrs(group: VehicleGroup, unit_type: Type[VehicleType]) -> None: + if unit_type.eplrs: + group.points[0].tasks.append(EPLRS(group.id)) + + def set_alarm_state(self, group: MovingGroup[Any]) -> None: + if self.game.settings.perf_red_alert_state: + group.points[0].tasks.append(OptAlarmState(2)) + else: + group.points[0].tasks.append(OptAlarmState(1)) + + def _register_theater_unit( + self, + theater_unit: TheaterUnit, + dcs_unit: Unit, + ) -> None: + self.unit_map.add_theater_unit_mapping(theater_unit, dcs_unit) + + def add_trigger_zone_for_scenery(self, scenery: SceneryUnit) -> None: + # Align the trigger zones to the faction color on the DCS briefing/F10 map. + color = ( + {1: 0.2, 2: 0.7, 3: 1, 4: 0.15} + if scenery.ground_object.is_friendly(to_player=True) + else {1: 1, 2: 0.2, 3: 0.2, 4: 0.15} + ) + + # Create the smallest valid size trigger zone (16 feet) so that risk of overlap + # is minimized. As long as the triggerzone is over the scenery object, we're ok. + smallest_valid_radius = feet(16).meters + + trigger_zone = self.m.triggers.add_triggerzone( + scenery.zone.position, + smallest_valid_radius, + scenery.zone.hidden, + scenery.zone.name, + color, + scenery.zone.properties, + ) + # DCS only visually shows a scenery object is dead when + # this trigger rule is applied. Otherwise you can kill a + # structure twice. + if not scenery.alive: + self.generate_destruction_trigger_rule(trigger_zone) + else: + self.generate_on_dead_trigger_rule(trigger_zone) + + self.unit_map.add_scenery(scenery, trigger_zone) + + def generate_destruction_trigger_rule(self, trigger_zone: TriggerZone) -> None: + # Add destruction zone trigger + t = TriggerStart(comment="Destruction") + t.actions.append( + SceneryDestructionZone(destruction_level=100, zone=trigger_zone.id) + ) + 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'dead_events[#dead_events + 1] = "{trigger_zone.name}"') + t.actions.append(DoScript(script_string)) + self.m.triggerrules.triggers.append(t) + + def generate_iads_command_unit(self, unit: SceneryUnit) -> None: + # Creates a static Infantry Unit next to a scenery object. This is needed + # because skynet can not use map objects as Comms, Power or Command and needs a + # "real" unit to function correctly + self.m.static_group( + country=self.country, + name=unit.unit_name, + _type=dcs.vehicles.Infantry.Soldier_M4, + position=unit.position, + heading=unit.position.heading.degrees, + dead=not unit.alive, # Also spawn as dead! + ) + + +class MissileSiteGenerator(GroundObjectGenerator): + @property + def culled(self) -> bool: + # Don't cull missile sites - their range is long enough to make them easily + # culled despite being a threat. + return False + + def generate(self) -> None: + super(MissileSiteGenerator, self).generate() + + if not self.game.settings.generate_fire_tasks_for_missile_sites: + return + + # Note : Only the SCUD missiles group can fire (V1 site cannot fire in game right now) + # TODO : Should be pre-planned ? + # TODO : Add delay to task to spread fire task over mission duration ? + for group in self.ground_object.groups: + vg = self.m.find_group(group.group_name) + if vg is not None: + targets = self.possible_missile_targets() + if targets: + target = random.choice(targets) + real_target = target.point_from_heading( + Heading.random().degrees, random.randint(0, 2500) + ) + vg.points[0].add_task(FireAtPoint(real_target)) + logging.info("Set up fire task for missile group.") + else: + logging.info( + "Couldn't setup missile site to fire, no valid target in range." + ) + else: + logging.info( + "Couldn't setup missile site to fire, group was not generated." + ) + + def possible_missile_targets(self) -> List[Point]: + """ + Find enemy control points in range + :return: List of possible missile targets + """ + targets: List[Point] = [] + for cp in self.game.theater.controlpoints: + if cp.captured != self.ground_object.control_point.captured: + distance = cp.position.distance_to_point(self.ground_object.position) + if distance < self.missile_site_range: + targets.append(cp.position) + return targets + + @property + def missile_site_range(self) -> int: + """ + Get the missile site range + :return: Missile site range + """ + site_range = 0 + for group in self.ground_object.groups: + vg = self.m.find_group(group.group_name) + if vg is not None: + for u in vg.units: + if u.type in vehicle_map: + if vehicle_map[u.type].threat_range > site_range: + site_range = vehicle_map[u.type].threat_range + return site_range + + +class GenericCarrierGenerator(GroundObjectGenerator): + """Base type for carrier group generation. + + Used by both CV(N) groups and LHA groups. + """ + + def __init__( + self, + ground_object: GenericCarrierGroundObject, + control_point: NavalControlPoint, + country: Country, + game: Game, + mission: Mission, + radio_registry: RadioRegistry, + tacan_registry: TacanRegistry, + icls_alloc: Iterator[int], + runways: Dict[str, RunwayData], + unit_map: UnitMap, + mission_data: MissionData, + ) -> None: + super().__init__(ground_object, country, game, mission, unit_map) + self.ground_object = ground_object + self.control_point = control_point + self.radio_registry = radio_registry + self.tacan_registry = tacan_registry + self.icls_alloc = icls_alloc + self.runways = runways + self.mission_data = mission_data + + def generate(self) -> None: + if self.control_point.frequency is not None: + atc = self.control_point.frequency + if atc not in self.radio_registry.allocated_channels: + self.radio_registry.reserve(atc) + else: + atc = self.radio_registry.alloc_uhf() + + for g_id, group in enumerate(self.ground_object.groups): + if not group.units: + logging.warning(f"Found empty carrier group in {self.control_point}") + continue + + ship_units = [] + for unit in group.units: + if unit.alive: + # All alive Ships + ship_units.append(unit) + + if not ship_units: + # Empty array (no alive units), skip this group + continue + + ship_group = self.create_ship_group(group.group_name, ship_units, atc) + + # Always steam into the wind, even if the carrier is being moved. + # There are multiple unsimulated hours between turns, so we can + # count those as the time the carrier uses to move and the mission + # time as the recovery window. + brc = self.steam_into_wind(ship_group) + + # Set Carrier Specific Options + if g_id == 0 and self.control_point.runway_is_operational(): + # Get Correct unit type for the carrier. + # This will upgrade to super carrier if option is enabled + carrier_type = self.carrier_type + if carrier_type is None: + raise RuntimeError( + f"Error generating carrier group for {self.control_point.name}" + ) + ship_group.units[0].type = carrier_type.id + if self.control_point.tacan is None: + tacan = self.tacan_registry.alloc_for_band( + TacanBand.X, TacanUsage.TransmitReceive + ) + else: + tacan = self.control_point.tacan + if self.control_point.tcn_name is None: + tacan_callsign = self.tacan_callsign() + else: + tacan_callsign = self.control_point.tcn_name + link4 = None + link4carriers = [Stennis, CVN_71, CVN_72, CVN_73, CVN_75, Forrestal] + if carrier_type in link4carriers: + if self.control_point.link4 is None: + link4 = self.radio_registry.alloc_uhf() + else: + link4 = self.control_point.link4 + icls = None + icls_name = self.control_point.icls_name + if carrier_type in link4carriers or carrier_type == LHA_Tarawa: + if self.control_point.icls_channel is None: + icls = next(self.icls_alloc) + else: + icls = self.control_point.icls_channel + self.activate_beacons( + ship_group, tacan, tacan_callsign, icls, icls_name, link4 + ) + self.add_runway_data( + brc or Heading.from_degrees(0), atc, tacan, tacan_callsign, icls + ) + self.mission_data.carriers.append( + CarrierInfo( + group_name=ship_group.name, + unit_name=ship_group.units[0].name, + callsign=tacan_callsign, + freq=atc, + tacan=tacan, + blue=self.control_point.captured, + ) + ) + + @property + def carrier_type(self) -> Optional[Type[ShipType]]: + return self.control_point.get_carrier_group_type() + + def steam_into_wind(self, group: ShipGroup) -> Optional[Heading]: + wind = self.game.conditions.weather.wind.at_0m + brc = Heading.from_degrees(wind.direction).opposite + # Aim for 25kts over the deck. + carrier_speed = knots(25) - mps(wind.speed) + for attempt in range(5): + point = group.points[0].position.point_from_heading( + brc.degrees, 100000 - attempt * 20000 + ) + if self.game.theater.is_in_sea(point): + group.points[0].speed = carrier_speed.meters_per_second + group.add_waypoint(point, carrier_speed.kph) + # Rotate the whole ground object to the new course + self.ground_object.rotate(brc) + return brc + return None + + def tacan_callsign(self) -> str: + raise NotImplementedError + + @staticmethod + def activate_beacons( + group: ShipGroup, + tacan: TacanChannel, + callsign: str, + icls: Optional[int] = None, + icls_name: Optional[str] = None, + link4: Optional[RadioFrequency] = None, + ) -> None: + group.points[0].tasks.append( + ActivateBeaconCommand( + channel=tacan.number, + modechannel=tacan.band.value, + callsign=callsign, + unit_id=group.units[0].id, + aa=False, + ) + ) + if icls is not None: + icls_name = "" if icls_name is None else icls_name + group.points[0].tasks.append( + ActivateICLSCommand(icls, group.units[0].id, icls_name) + ) + if link4 is not None: + group.points[0].tasks.append( + ActivateLink4Command(link4.hertz, group.units[0].id) + ) + group.points[0].tasks.append(ActivateACLSCommand(unit_id=group.units[0].id)) + + def add_runway_data( + self, + brc: Heading, + atc: RadioFrequency, + tacan: TacanChannel, + callsign: str, + icls: Optional[int], + ) -> None: + # This relies on one control point mapping exactly + # to one LHA, carrier, or other usable "runway". + # This isn't wholly true, since the DD escorts of + # the carrier group are valid for helicopters, but + # they aren't exposed as such to the game. Should + # clean this up so that's possible. We can't use the + # unit name since it's an arbitrary ID. + self.runways[self.control_point.full_name] = RunwayData( + self.control_point.name, + brc, + f"{brc.degrees:03}", + atc=atc, + tacan=tacan, + tacan_callsign=callsign, + icls=icls, + ) + + +class CarrierGenerator(GenericCarrierGenerator): + """Generator for CV(N) groups.""" + + def tacan_callsign(self) -> str: + # TODO: Assign these properly. + return random.choice( + [ + "STE", + "CVN", + "CVH", + "CCV", + "ACC", + "ARC", + "GER", + "ABR", + "LIN", + "TRU", + ] + ) + + +class LhaGenerator(GenericCarrierGenerator): + """Generator for LHA groups.""" + + def tacan_callsign(self) -> str: + # TODO: Assign these properly. + return random.choice( + [ + "LHD", + "LHA", + "LHB", + "LHC", + "LHD", + "LDS", + ] + ) + + +class HelipadGenerator: + """ + Generates helipads for given control point + """ + + def __init__( + self, + mission: Mission, + cp: ControlPoint, + game: Game, + radio_registry: RadioRegistry, + tacan_registry: TacanRegistry, + ): + self.m = mission + self.cp = cp + self.game = game + self.radio_registry = radio_registry + self.tacan_registry = tacan_registry + self.helipads: list[StaticGroup] = [] + + def create_helipad( + self, i: int, helipad: PointWithHeading, helipad_type: str + ) -> None: + # Note: Helipad are generated as neutral object in order not to interfere with + # capture triggers + pad: BaseFARP + neutral_country = self.m.country(self.game.neutral_country.name) + country = self.m.country( + self.game.coalition_for(self.cp.captured).faction.country.name + ) + + name = f"{self.cp.name} {helipad_type} {i}" + logging.info("Generating helipad static : " + name) + terrain = self.m.terrain + if helipad_type == "SINGLE_HELIPAD": + pad = SingleHeliPad( + unit_id=self.m.next_unit_id(), name=name, terrain=terrain + ) + number_of_pads = 1 + elif helipad_type == "FARP": + pad = FARP(unit_id=self.m.next_unit_id(), name=name, terrain=terrain) + number_of_pads = 4 + else: + pad = InvisibleFARP( + unit_id=self.m.next_unit_id(), name=name, terrain=terrain + ) + number_of_pads = 1 + pad.position = Point(helipad.x, helipad.y, terrain=terrain) + pad.heading = helipad.heading.degrees + + # Set FREQ + if isinstance(self.cp, RadioFrequencyContainer) and self.cp.frequency: + if isinstance(pad, BaseFARP): + pad.heliport_frequency = self.cp.frequency.mhz + + sg = unitgroup.StaticGroup(self.m.next_group_id(), name) + sg.add_unit(pad) + sp = StaticPoint(pad.position) + sg.add_point(sp) + neutral_country.add_static_group(sg) + + if number_of_pads > 1: + self.append_helipad(pad, name, helipad.heading.degrees, 60, 0, 0) + self.append_helipad(pad, name, helipad.heading.degrees + 180, 20, 0, 0) + self.append_helipad( + pad, name, helipad.heading.degrees + 90, 60, helipad.heading.degrees, 20 + ) + self.append_helipad( + pad, + name, + helipad.heading.degrees + 90, + 60, + helipad.heading.degrees + 180, + 60, + ) + else: + self.helipads.append(sg) + + # Generate a FARP Ammo and Fuel stack for each pad + self.m.static_group( + country=country, + name=(name + "_fuel"), + _type=Fortification.FARP_Fuel_Depot, + position=pad.position.point_from_heading(helipad.heading.degrees, 35), + heading=pad.heading + 180, + ) + self.m.static_group( + country=country, + name=(name + "_ammo"), + _type=Fortification.FARP_Ammo_Dump_Coating, + position=pad.position.point_from_heading( + helipad.heading.degrees, 35 + ).point_from_heading(helipad.heading.degrees + 90, 10), + heading=pad.heading + 90, + ) + self.m.static_group( + country=country, + name=(name + "_ws"), + _type=Fortification.Windsock, + position=helipad.point_from_heading(helipad.heading.degrees + 45, 35), + heading=pad.heading, + ) + + def append_helipad( + self, + pad: BaseFARP, + name: str, + heading_1: int, + distance_1: int, + heading_2: int, + distance_2: int, + ) -> None: + new_pad = InvisibleFARP(pad._terrain) + new_pad.position = pad.position.point_from_heading(heading_1, distance_1) + new_pad.position = new_pad.position.point_from_heading(heading_2, distance_2) + sg = unitgroup.StaticGroup(self.m.next_group_id(), name) + sg.add_unit(new_pad) + self.helipads.append(sg) + + def generate(self) -> None: + for i, helipad in enumerate(self.cp.helipads): + self.create_helipad(i, helipad, "SINGLE_HELIPAD") + for i, helipad in enumerate(self.cp.helipads_quad): + self.create_helipad(i, helipad, "FARP") + for i, helipad in enumerate(self.cp.helipads_invisible): + self.create_helipad(i, helipad, "Invisible FARP") + + +class GroundSpawnRoadbaseGenerator: + """ + Generates Highway strip starting positions for given control point + """ + + def __init__( + self, + mission: Mission, + cp: ControlPoint, + game: Game, + radio_registry: RadioRegistry, + tacan_registry: TacanRegistry, + ): + self.m = mission + self.cp = cp + self.game = game + self.radio_registry = radio_registry + self.tacan_registry = tacan_registry + self.ground_spawns_roadbase: list[Tuple[StaticGroup, Point]] = [] + + def create_ground_spawn_roadbase( + self, i: int, ground_spawn: Tuple[PointWithHeading, Point] + ) -> None: + # Note: FARPs are generated as neutral object in order not to interfere with + # capture triggers + neutral_country = self.m.country(self.game.neutral_country.name) + country = self.m.country( + self.game.coalition_for(self.cp.captured).faction.country.name + ) + terrain = self.cp.coalition.game.theater.terrain + + name = f"{self.cp.name} roadbase spawn {i}" + logging.info("Generating Roadbase Spawn static : " + name) + + pad = InvisibleFARP(unit_id=self.m.next_unit_id(), name=name, terrain=terrain) + + pad.position = Point(ground_spawn[0].x, ground_spawn[0].y, terrain=terrain) + pad.heading = ground_spawn[0].heading.degrees + sg = unitgroup.StaticGroup(self.m.next_group_id(), name) + sg.add_unit(pad) + sp = StaticPoint(pad.position) + sg.add_point(sp) + neutral_country.add_static_group(sg) + + self.ground_spawns_roadbase.append((sg, ground_spawn[1])) + + # tanker_type: Type[VehicleType] + # ammo_truck_type: Type[VehicleType] + + tanker_type, ammo_truck_type = farp_truck_types_for_country(country.id) + + # Generate ammo truck/farp and fuel truck/stack for each pad + if self.game.settings.ground_start_trucks_roadbase: + self.m.vehicle_group( + country=country, + name=(name + "_fuel"), + _type=tanker_type, + position=pad.position.point_from_heading( + ground_spawn[0].heading.degrees + 90, 35 + ), + group_size=1, + heading=pad.heading + 315, + move_formation=PointAction.OffRoad, + ) + self.m.vehicle_group( + country=country, + name=(name + "_ammo"), + _type=ammo_truck_type, + position=pad.position.point_from_heading( + ground_spawn[0].heading.degrees + 90, 35 + ).point_from_heading(ground_spawn[0].heading.degrees + 180, 10), + group_size=1, + heading=pad.heading + 315, + move_formation=PointAction.OffRoad, + ) + else: + self.m.static_group( + country=country, + name=(name + "_fuel"), + _type=Fortification.FARP_Fuel_Depot, + position=pad.position.point_from_heading( + ground_spawn[0].heading.degrees + 90, 35 + ), + heading=pad.heading + 270, + ) + self.m.static_group( + country=country, + name=(name + "_ammo"), + _type=Fortification.FARP_Ammo_Dump_Coating, + position=pad.position.point_from_heading( + ground_spawn[0].heading.degrees + 90, 35 + ).point_from_heading(ground_spawn[0].heading.degrees + 180, 10), + heading=pad.heading + 180, + ) + + def generate(self) -> None: + try: + for i, ground_spawn in enumerate(self.cp.ground_spawns_roadbase): + self.create_ground_spawn_roadbase(i, ground_spawn) + except AttributeError: + self.ground_spawns_roadbase = [] + + +class GroundSpawnGenerator: + """ + Generates STOL aircraft starting positions for given control point + """ + + def __init__( + self, + mission: Mission, + cp: ControlPoint, + game: Game, + radio_registry: RadioRegistry, + tacan_registry: TacanRegistry, + ): + self.m = mission + self.cp = cp + self.game = game + self.radio_registry = radio_registry + self.tacan_registry = tacan_registry + self.ground_spawns: list[Tuple[StaticGroup, Point]] = [] + + def create_ground_spawn( + self, i: int, vtol_pad: Tuple[PointWithHeading, Point] + ) -> None: + # Note: FARPs are generated as neutral object in order not to interfere with + # capture triggers + neutral_country = self.m.country(self.game.neutral_country.name) + country = self.m.country( + self.game.coalition_for(self.cp.captured).faction.country.name + ) + terrain = self.cp.coalition.game.theater.terrain + + name = f"{self.cp.name} ground spawn {i}" + logging.info("Generating Ground Spawn static : " + name) + + pad = InvisibleFARP(unit_id=self.m.next_unit_id(), name=name, terrain=terrain) + + pad.position = Point(vtol_pad[0].x, vtol_pad[0].y, terrain=terrain) + pad.heading = vtol_pad[0].heading.degrees + sg = unitgroup.StaticGroup(self.m.next_group_id(), name) + sg.add_unit(pad) + sp = StaticPoint(pad.position) + sg.add_point(sp) + neutral_country.add_static_group(sg) + + self.ground_spawns.append((sg, vtol_pad[1])) + + # tanker_type: Type[VehicleType] + # ammo_truck_type: Type[VehicleType] + + tanker_type, ammo_truck_type = farp_truck_types_for_country(country.id) + + # Generate a FARP Ammo and Fuel stack for each pad + if self.game.settings.ground_start_trucks: + self.m.vehicle_group( + country=country, + name=(name + "_fuel"), + _type=tanker_type, + position=pad.position.point_from_heading( + vtol_pad[0].heading.degrees - 175, 35 + ), + group_size=1, + heading=pad.heading + 45, + move_formation=PointAction.OffRoad, + ) + self.m.vehicle_group( + country=country, + name=(name + "_ammo"), + _type=ammo_truck_type, + position=pad.position.point_from_heading( + vtol_pad[0].heading.degrees - 185, 35 + ), + group_size=1, + heading=pad.heading + 45, + move_formation=PointAction.OffRoad, + ) + else: + self.m.static_group( + country=country, + name=(name + "_fuel"), + _type=Fortification.FARP_Fuel_Depot, + position=pad.position.point_from_heading( + vtol_pad[0].heading.degrees - 180, 45 + ), + heading=pad.heading, + ) + self.m.static_group( + country=country, + name=(name + "_ammo"), + _type=Fortification.FARP_Ammo_Dump_Coating, + position=pad.position.point_from_heading( + vtol_pad[0].heading.degrees - 180, 35 + ), + heading=pad.heading + 270, + ) + + def generate(self) -> None: + try: + for i, vtol_pad in enumerate(self.cp.ground_spawns): + self.create_ground_spawn(i, vtol_pad) + except AttributeError: + self.ground_spawns = [] + + +class TgoGenerator: + """Creates DCS groups and statics for the theater during mission generation. + + Most of the work of group/static generation is delegated to the other + generator classes. This class is responsible for finding each of the + locations for spawning ground objects, determining their types, and creating + the appropriate generators. + """ + + def __init__( + self, + mission: Mission, + game: Game, + radio_registry: RadioRegistry, + tacan_registry: TacanRegistry, + unit_map: UnitMap, + mission_data: MissionData, + ) -> None: + self.m = mission + self.game = game + self.radio_registry = radio_registry + self.tacan_registry = tacan_registry + self.unit_map = unit_map + self.icls_alloc = iter(range(1, 21)) + self.runways: Dict[str, RunwayData] = {} + self.helipads: dict[ControlPoint, list[StaticGroup]] = defaultdict(list) + self.ground_spawns_roadbase: dict[ + ControlPoint, list[Tuple[StaticGroup, Point]] + ] = defaultdict(list) + self.ground_spawns: dict[ + ControlPoint, list[Tuple[StaticGroup, Point]] + ] = defaultdict(list) + self.mission_data = mission_data + + def generate(self) -> None: + for cp in self.game.theater.controlpoints: + country = self.m.country(cp.coalition.faction.country.name) + + # Generate helipads + helipad_gen = HelipadGenerator( + self.m, cp, self.game, self.radio_registry, self.tacan_registry + ) + helipad_gen.generate() + self.helipads[cp] = helipad_gen.helipads + + # Generate Highway Strip slots + ground_spawn_roadbase_gen = GroundSpawnRoadbaseGenerator( + self.m, cp, self.game, self.radio_registry, self.tacan_registry + ) + ground_spawn_roadbase_gen.generate() + self.ground_spawns_roadbase[ + cp + ] = ground_spawn_roadbase_gen.ground_spawns_roadbase + random.shuffle(self.ground_spawns_roadbase[cp]) + + # Generate STOL pads + ground_spawn_gen = GroundSpawnGenerator( + self.m, cp, self.game, self.radio_registry, self.tacan_registry + ) + ground_spawn_gen.generate() + self.ground_spawns[cp] = ground_spawn_gen.ground_spawns + random.shuffle(self.ground_spawns[cp]) + + for ground_object in cp.ground_objects: + generator: GroundObjectGenerator + if isinstance(ground_object, CarrierGroundObject) and isinstance( + cp, NavalControlPoint + ): + generator = CarrierGenerator( + ground_object, + cp, + country, + self.game, + self.m, + self.radio_registry, + self.tacan_registry, + self.icls_alloc, + self.runways, + self.unit_map, + self.mission_data, + ) + elif isinstance(ground_object, LhaGroundObject) and isinstance( + cp, NavalControlPoint + ): + generator = LhaGenerator( + ground_object, + cp, + country, + self.game, + self.m, + self.radio_registry, + self.tacan_registry, + self.icls_alloc, + self.runways, + self.unit_map, + self.mission_data, + ) + elif isinstance(ground_object, MissileSiteGroundObject): + generator = MissileSiteGenerator( + ground_object, country, self.game, self.m, self.unit_map + ) + else: + generator = GroundObjectGenerator( + ground_object, country, self.game, self.m, self.unit_map + ) + generator.generate() + self.mission_data.runways = list(self.runways.values()) From dc02c6f857a8c71ae3cd28b5a18d7ea3b94fdecd Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Sun, 10 Sep 2023 14:05:23 +0300 Subject: [PATCH 107/243] Will now generate ground units for the Pretense campaign. --- game/pretense/pretensemissiongenerator.py | 3 +- game/pretense/pretensetgogenerator.py | 1082 +++------------------ game/pretense/pretensetriggergenerator.py | 2 + 3 files changed, 159 insertions(+), 928 deletions(-) diff --git a/game/pretense/pretensemissiongenerator.py b/game/pretense/pretensemissiongenerator.py index 16478f27..b0b61ce2 100644 --- a/game/pretense/pretensemissiongenerator.py +++ b/game/pretense/pretensemissiongenerator.py @@ -38,6 +38,7 @@ from game.missiongenerator.lasercoderegistry import LaserCodeRegistry from game.missiongenerator.luagenerator import LuaGenerator from game.missiongenerator.missiondata import MissionData from game.missiongenerator.tgogenerator import TgoGenerator +from .pretensetgogenerator import PretenseTgoGenerator from .pretensetriggergenerator import PretenseTriggerGenerator from game.missiongenerator.visualsgenerator import VisualsGenerator from ..ato import Flight @@ -86,7 +87,7 @@ class PretenseMissionGenerator: EnvironmentGenerator(self.mission, self.game.conditions, self.time).generate() - tgo_generator = TgoGenerator( + tgo_generator = PretenseTgoGenerator( self.mission, self.game, self.radio_registry, diff --git a/game/pretense/pretensetgogenerator.py b/game/pretense/pretensetgogenerator.py index 326a1ded..5c4ef4e2 100644 --- a/game/pretense/pretensetgogenerator.py +++ b/game/pretense/pretensetgogenerator.py @@ -45,11 +45,23 @@ from dcs.unitgroup import MovingGroup, ShipGroup, StaticGroup, VehicleGroup from dcs.unittype import ShipType, VehicleType from dcs.vehicles import vehicle_map, Unarmed +from game.data.units import UnitClass +from game.dcs.groundunittype import GroundUnitType from game.missiongenerator.groundforcepainter import ( NavalForcePainter, GroundForcePainter, ) from game.missiongenerator.missiondata import CarrierInfo, MissionData +from game.missiongenerator.tgogenerator import ( + TgoGenerator, + HelipadGenerator, + GroundSpawnRoadbaseGenerator, + GroundSpawnGenerator, + GroundObjectGenerator, + CarrierGenerator, + LhaGenerator, + MissileSiteGenerator, +) from game.point_with_heading import PointWithHeading from game.radio.RadioFrequencyContainer import RadioFrequencyContainer from game.radio.radios import RadioFrequency, RadioRegistry @@ -67,7 +79,7 @@ from game.theater.theatergroundobject import ( LhaGroundObject, MissileSiteGroundObject, ) -from game.theater.theatergroup import SceneryUnit, IadsGroundGroup +from game.theater.theatergroup import SceneryUnit, IadsGroundGroup, TheaterGroup from game.unitmap import UnitMap from game.utils import Heading, feet, knots, mps @@ -76,164 +88,10 @@ if TYPE_CHECKING: FARP_FRONTLINE_DISTANCE = 10000 AA_CP_MIN_DISTANCE = 40000 +PRETENSE_GROUND_UNIT_GROUP_SIZE = 4 -def farp_truck_types_for_country( - country_id: int, -) -> Tuple[Type[VehicleType], Type[VehicleType]]: - soviet_tankers: List[Type[VehicleType]] = [ - Unarmed.ATMZ_5, - Unarmed.ATZ_10, - Unarmed.ATZ_5, - Unarmed.ATZ_60_Maz, - Unarmed.TZ_22_KrAZ, - ] - soviet_trucks: List[Type[VehicleType]] = [ - Unarmed.S_75_ZIL, - Unarmed.GAZ_3308, - Unarmed.GAZ_66, - Unarmed.KAMAZ_Truck, - Unarmed.KrAZ6322, - Unarmed.Ural_375, - Unarmed.Ural_375_PBU, - Unarmed.Ural_4320_31, - Unarmed.Ural_4320T, - Unarmed.ZIL_135, - ] - - axis_trucks: List[Type[VehicleType]] = [Unarmed.Blitz_36_6700A] - - us_tankers: List[Type[VehicleType]] = [Unarmed.M978_HEMTT_Tanker] - us_trucks: List[Type[VehicleType]] = [Unarmed.M_818] - uk_trucks: List[Type[VehicleType]] = [Unarmed.Bedford_MWD] - - if country_id in [ - Abkhazia.id, - Algeria.id, - Bahrain.id, - Belarus.id, - Belgium.id, - Bulgaria.id, - China.id, - Croatia.id, - Cuba.id, - Cyprus.id, - CzechRepublic.id, - Egypt.id, - Ethiopia.id, - Finland.id, - GDR.id, - Georgia.id, - Ghana.id, - Greece.id, - Hungary.id, - India.id, - Insurgents.id, - Iraq.id, - Jordan.id, - Kazakhstan.id, - Lebanon.id, - Libya.id, - Morocco.id, - Nigeria.id, - NorthKorea.id, - Poland.id, - Romania.id, - Russia.id, - Serbia.id, - Slovakia.id, - Slovenia.id, - SouthAfrica.id, - SouthOssetia.id, - Sudan.id, - Syria.id, - Tunisia.id, - USSR.id, - Ukraine.id, - Venezuela.id, - Vietnam.id, - Yemen.id, - Yugoslavia.id, - ]: - tanker_type = random.choice(soviet_tankers) - ammo_truck_type = random.choice(soviet_trucks) - elif country_id in [ItalianSocialRepublic.id, ThirdReich.id]: - tanker_type = random.choice(soviet_tankers) - ammo_truck_type = random.choice(axis_trucks) - elif country_id in [ - Argentina.id, - Australia.id, - Austria.id, - Bolivia.id, - Brazil.id, - Canada.id, - Chile.id, - Denmark.id, - Ecuador.id, - France.id, - Germany.id, - Honduras.id, - Indonesia.id, - Iran.id, - Israel.id, - Italy.id, - Japan.id, - Kuwait.id, - Malaysia.id, - Mexico.id, - Norway.id, - Oman.id, - Pakistan.id, - Peru.id, - Philippines.id, - Portugal.id, - Qatar.id, - SaudiArabia.id, - SouthKorea.id, - Spain.id, - Sweden.id, - Switzerland.id, - Thailand.id, - TheNetherlands.id, - Turkey.id, - USA.id, - USAFAggressors.id, - UnitedArabEmirates.id, - ]: - tanker_type = random.choice(us_tankers) - ammo_truck_type = random.choice(us_trucks) - elif country_id in [UK.id]: - tanker_type = random.choice(us_tankers) - ammo_truck_type = random.choice(uk_trucks) - elif country_id in [CombinedJointTaskForcesBlue.id]: - tanker_types = us_tankers - truck_types = us_trucks + uk_trucks - - tanker_type = random.choice(tanker_types) - ammo_truck_type = random.choice(truck_types) - elif country_id in [CombinedJointTaskForcesRed.id]: - tanker_types = us_tankers - truck_types = us_trucks + uk_trucks - - tanker_type = random.choice(tanker_types) - ammo_truck_type = random.choice(truck_types) - elif country_id in [UnitedNationsPeacekeepers.id]: - tanker_types = soviet_tankers + us_tankers - truck_types = soviet_trucks + us_trucks + uk_trucks - - tanker_type = random.choice(tanker_types) - ammo_truck_type = random.choice(truck_types) - else: - tanker_types = soviet_tankers + us_tankers - truck_types = soviet_trucks + us_trucks + uk_trucks + axis_trucks - - tanker_type = random.choice(tanker_types) - ammo_truck_type = random.choice(truck_types) - - return tanker_type, ammo_truck_type - - -class GroundObjectGenerator: +class PretenseGroundObjectGenerator(GroundObjectGenerator): """generates the DCS groups and units from the TheaterGroundObject""" def __init__( @@ -244,6 +102,14 @@ class GroundObjectGenerator: mission: Mission, unit_map: UnitMap, ) -> None: + super().__init__( + ground_object, + country, + game, + mission, + unit_map, + ) + self.ground_object = ground_object self.country = country self.game = game @@ -254,6 +120,51 @@ class GroundObjectGenerator: def culled(self) -> bool: return self.game.iads_considerate_culling(self.ground_object) + def ground_unit_of_class(self, unit_class: UnitClass) -> Optional[GroundUnitType]: + faction_units = ( + set(self.ground_object.coalition.faction.frontline_units) + | set(self.ground_object.coalition.faction.artillery_units) + | set(self.ground_object.coalition.faction.logistics_units) + ) + of_class = list({u for u in faction_units if u.unit_class is unit_class}) + + if len(of_class) > 0: + return random.choice(of_class) + else: + return None + + def generate_ground_unit_of_class( + self, + unit_class: UnitClass, + group: TheaterGroup, + vehicle_units: list[TheaterUnit], + cp_name: str, + group_role: str, + max_num: int, + ): + if self.ground_object.coalition.faction.has_access_to_unit_class(unit_class): + unit_type = self.ground_unit_of_class(unit_class) + if unit_type is not None and len(vehicle_units) < max_num: + group_id = self.game.next_group_id() + group_name = f"{cp_name}-{group_role}-{group_id}" + + spread_out_heading = random.randrange(1, 360) + spread_out_position = group.position.point_from_heading( + spread_out_heading, 30 + ) + ground_unit_pos = PointWithHeading.from_point( + spread_out_position, group.position.heading + ) + + theater_unit = TheaterUnit( + group_id, + group_name, + unit_type.dcs_unit_type, + ground_unit_pos, + group.ground_object, + ) + vehicle_units.append(theater_unit) + def generate(self) -> None: if self.culled: return @@ -262,27 +173,88 @@ class GroundObjectGenerator: ship_units = [] # Split the different unit types to be compliant to dcs limitation for unit in group.units: + cp_name_trimmed = "".join( + [ + i + for i in self.ground_object.control_point.name.lower() + if i.isalnum() + ] + ) + if unit.is_static: - if isinstance(unit, SceneryUnit): - # Special handling for scenery objects - self.add_trigger_zone_for_scenery(unit) - if ( - self.game.settings.plugin_option("skynetiads") - and isinstance(group, IadsGroundGroup) - and group.iads_role.participate - ): - # Generate a unit which can be controlled by skynet - self.generate_iads_command_unit(unit) - else: - # Create a static group for each static unit - self.create_static_group(unit) + # Add supply convoy + self.generate_ground_unit_of_class( + UnitClass.LOGISTICS, + group, + vehicle_units, + cp_name_trimmed, + "supply", + PRETENSE_GROUND_UNIT_GROUP_SIZE, + ) elif unit.is_vehicle and unit.alive: - # All alive Vehicles - vehicle_units.append(unit) + # Add armor group + self.generate_ground_unit_of_class( + UnitClass.TANK, + group, + vehicle_units, + cp_name_trimmed, + "assault", + PRETENSE_GROUND_UNIT_GROUP_SIZE - 3, + ) + self.generate_ground_unit_of_class( + UnitClass.ATGM, + group, + vehicle_units, + cp_name_trimmed, + "assault", + PRETENSE_GROUND_UNIT_GROUP_SIZE - 2, + ) + self.generate_ground_unit_of_class( + UnitClass.APC, + group, + vehicle_units, + cp_name_trimmed, + "assault", + PRETENSE_GROUND_UNIT_GROUP_SIZE - 1, + ) + self.generate_ground_unit_of_class( + UnitClass.IFV, + group, + vehicle_units, + cp_name_trimmed, + "assault", + PRETENSE_GROUND_UNIT_GROUP_SIZE, + ) + self.generate_ground_unit_of_class( + UnitClass.ARTILLERY, + group, + vehicle_units, + cp_name_trimmed, + "assault", + PRETENSE_GROUND_UNIT_GROUP_SIZE, + ) + self.generate_ground_unit_of_class( + UnitClass.RECON, + group, + vehicle_units, + cp_name_trimmed, + "assault", + PRETENSE_GROUND_UNIT_GROUP_SIZE, + ) + if random.randrange(0, 100) > 75: + self.generate_ground_unit_of_class( + UnitClass.SHORAD, + group, + vehicle_units, + cp_name_trimmed, + "assault", + PRETENSE_GROUND_UNIT_GROUP_SIZE, + ) elif unit.is_ship and unit.alive: # All alive Ships ship_units.append(unit) if vehicle_units: + print(f"Generating vehicle group {vehicle_units}") self.create_vehicle_group(group.group_name, vehicle_units) if ship_units: self.create_ship_group(group.group_name, ship_units) @@ -319,761 +291,8 @@ class GroundObjectGenerator: raise RuntimeError(f"Error creating VehicleGroup for {group_name}") return vehicle_group - def create_ship_group( - self, - group_name: str, - units: list[TheaterUnit], - frequency: Optional[RadioFrequency] = None, - ) -> ShipGroup: - ship_group: Optional[ShipGroup] = None - for unit in units: - assert issubclass(unit.type, ShipType) - faction = unit.ground_object.control_point.coalition.faction - if ship_group is None: - ship_group = self.m.ship_group( - self.country, - group_name, - unit.type, - position=unit.position, - heading=unit.position.heading.degrees, - ) - if frequency: - ship_group.set_frequency(frequency.hertz) - ship_group.units[0].name = unit.unit_name - self.set_alarm_state(ship_group) - NavalForcePainter(faction, ship_group.units[0]).apply_livery() - else: - ship_unit = self.m.ship(unit.unit_name, unit.type) - if frequency: - ship_unit.set_frequency(frequency.hertz) - ship_unit.position = unit.position - ship_unit.heading = unit.position.heading.degrees - NavalForcePainter(faction, ship_unit).apply_livery() - ship_group.add_unit(ship_unit) - self._register_theater_unit(unit, ship_group.units[-1]) - if ship_group is None: - raise RuntimeError(f"Error creating ShipGroup for {group_name}") - return ship_group - def create_static_group(self, unit: TheaterUnit) -> None: - static_group = self.m.static_group( - country=self.country, - name=unit.unit_name, - _type=unit.type, - position=unit.position, - heading=unit.position.heading.degrees, - dead=not unit.alive, - ) - self._register_theater_unit(unit, static_group.units[0]) - - @staticmethod - def enable_eplrs(group: VehicleGroup, unit_type: Type[VehicleType]) -> None: - if unit_type.eplrs: - group.points[0].tasks.append(EPLRS(group.id)) - - def set_alarm_state(self, group: MovingGroup[Any]) -> None: - if self.game.settings.perf_red_alert_state: - group.points[0].tasks.append(OptAlarmState(2)) - else: - group.points[0].tasks.append(OptAlarmState(1)) - - def _register_theater_unit( - self, - theater_unit: TheaterUnit, - dcs_unit: Unit, - ) -> None: - self.unit_map.add_theater_unit_mapping(theater_unit, dcs_unit) - - def add_trigger_zone_for_scenery(self, scenery: SceneryUnit) -> None: - # Align the trigger zones to the faction color on the DCS briefing/F10 map. - color = ( - {1: 0.2, 2: 0.7, 3: 1, 4: 0.15} - if scenery.ground_object.is_friendly(to_player=True) - else {1: 1, 2: 0.2, 3: 0.2, 4: 0.15} - ) - - # Create the smallest valid size trigger zone (16 feet) so that risk of overlap - # is minimized. As long as the triggerzone is over the scenery object, we're ok. - smallest_valid_radius = feet(16).meters - - trigger_zone = self.m.triggers.add_triggerzone( - scenery.zone.position, - smallest_valid_radius, - scenery.zone.hidden, - scenery.zone.name, - color, - scenery.zone.properties, - ) - # DCS only visually shows a scenery object is dead when - # this trigger rule is applied. Otherwise you can kill a - # structure twice. - if not scenery.alive: - self.generate_destruction_trigger_rule(trigger_zone) - else: - self.generate_on_dead_trigger_rule(trigger_zone) - - self.unit_map.add_scenery(scenery, trigger_zone) - - def generate_destruction_trigger_rule(self, trigger_zone: TriggerZone) -> None: - # Add destruction zone trigger - t = TriggerStart(comment="Destruction") - t.actions.append( - SceneryDestructionZone(destruction_level=100, zone=trigger_zone.id) - ) - 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'dead_events[#dead_events + 1] = "{trigger_zone.name}"') - t.actions.append(DoScript(script_string)) - self.m.triggerrules.triggers.append(t) - - def generate_iads_command_unit(self, unit: SceneryUnit) -> None: - # Creates a static Infantry Unit next to a scenery object. This is needed - # because skynet can not use map objects as Comms, Power or Command and needs a - # "real" unit to function correctly - self.m.static_group( - country=self.country, - name=unit.unit_name, - _type=dcs.vehicles.Infantry.Soldier_M4, - position=unit.position, - heading=unit.position.heading.degrees, - dead=not unit.alive, # Also spawn as dead! - ) - - -class MissileSiteGenerator(GroundObjectGenerator): - @property - def culled(self) -> bool: - # Don't cull missile sites - their range is long enough to make them easily - # culled despite being a threat. - return False - - def generate(self) -> None: - super(MissileSiteGenerator, self).generate() - - if not self.game.settings.generate_fire_tasks_for_missile_sites: - return - - # Note : Only the SCUD missiles group can fire (V1 site cannot fire in game right now) - # TODO : Should be pre-planned ? - # TODO : Add delay to task to spread fire task over mission duration ? - for group in self.ground_object.groups: - vg = self.m.find_group(group.group_name) - if vg is not None: - targets = self.possible_missile_targets() - if targets: - target = random.choice(targets) - real_target = target.point_from_heading( - Heading.random().degrees, random.randint(0, 2500) - ) - vg.points[0].add_task(FireAtPoint(real_target)) - logging.info("Set up fire task for missile group.") - else: - logging.info( - "Couldn't setup missile site to fire, no valid target in range." - ) - else: - logging.info( - "Couldn't setup missile site to fire, group was not generated." - ) - - def possible_missile_targets(self) -> List[Point]: - """ - Find enemy control points in range - :return: List of possible missile targets - """ - targets: List[Point] = [] - for cp in self.game.theater.controlpoints: - if cp.captured != self.ground_object.control_point.captured: - distance = cp.position.distance_to_point(self.ground_object.position) - if distance < self.missile_site_range: - targets.append(cp.position) - return targets - - @property - def missile_site_range(self) -> int: - """ - Get the missile site range - :return: Missile site range - """ - site_range = 0 - for group in self.ground_object.groups: - vg = self.m.find_group(group.group_name) - if vg is not None: - for u in vg.units: - if u.type in vehicle_map: - if vehicle_map[u.type].threat_range > site_range: - site_range = vehicle_map[u.type].threat_range - return site_range - - -class GenericCarrierGenerator(GroundObjectGenerator): - """Base type for carrier group generation. - - Used by both CV(N) groups and LHA groups. - """ - - def __init__( - self, - ground_object: GenericCarrierGroundObject, - control_point: NavalControlPoint, - country: Country, - game: Game, - mission: Mission, - radio_registry: RadioRegistry, - tacan_registry: TacanRegistry, - icls_alloc: Iterator[int], - runways: Dict[str, RunwayData], - unit_map: UnitMap, - mission_data: MissionData, - ) -> None: - super().__init__(ground_object, country, game, mission, unit_map) - self.ground_object = ground_object - self.control_point = control_point - self.radio_registry = radio_registry - self.tacan_registry = tacan_registry - self.icls_alloc = icls_alloc - self.runways = runways - self.mission_data = mission_data - - def generate(self) -> None: - if self.control_point.frequency is not None: - atc = self.control_point.frequency - if atc not in self.radio_registry.allocated_channels: - self.radio_registry.reserve(atc) - else: - atc = self.radio_registry.alloc_uhf() - - for g_id, group in enumerate(self.ground_object.groups): - if not group.units: - logging.warning(f"Found empty carrier group in {self.control_point}") - continue - - ship_units = [] - for unit in group.units: - if unit.alive: - # All alive Ships - ship_units.append(unit) - - if not ship_units: - # Empty array (no alive units), skip this group - continue - - ship_group = self.create_ship_group(group.group_name, ship_units, atc) - - # Always steam into the wind, even if the carrier is being moved. - # There are multiple unsimulated hours between turns, so we can - # count those as the time the carrier uses to move and the mission - # time as the recovery window. - brc = self.steam_into_wind(ship_group) - - # Set Carrier Specific Options - if g_id == 0 and self.control_point.runway_is_operational(): - # Get Correct unit type for the carrier. - # This will upgrade to super carrier if option is enabled - carrier_type = self.carrier_type - if carrier_type is None: - raise RuntimeError( - f"Error generating carrier group for {self.control_point.name}" - ) - ship_group.units[0].type = carrier_type.id - if self.control_point.tacan is None: - tacan = self.tacan_registry.alloc_for_band( - TacanBand.X, TacanUsage.TransmitReceive - ) - else: - tacan = self.control_point.tacan - if self.control_point.tcn_name is None: - tacan_callsign = self.tacan_callsign() - else: - tacan_callsign = self.control_point.tcn_name - link4 = None - link4carriers = [Stennis, CVN_71, CVN_72, CVN_73, CVN_75, Forrestal] - if carrier_type in link4carriers: - if self.control_point.link4 is None: - link4 = self.radio_registry.alloc_uhf() - else: - link4 = self.control_point.link4 - icls = None - icls_name = self.control_point.icls_name - if carrier_type in link4carriers or carrier_type == LHA_Tarawa: - if self.control_point.icls_channel is None: - icls = next(self.icls_alloc) - else: - icls = self.control_point.icls_channel - self.activate_beacons( - ship_group, tacan, tacan_callsign, icls, icls_name, link4 - ) - self.add_runway_data( - brc or Heading.from_degrees(0), atc, tacan, tacan_callsign, icls - ) - self.mission_data.carriers.append( - CarrierInfo( - group_name=ship_group.name, - unit_name=ship_group.units[0].name, - callsign=tacan_callsign, - freq=atc, - tacan=tacan, - blue=self.control_point.captured, - ) - ) - - @property - def carrier_type(self) -> Optional[Type[ShipType]]: - return self.control_point.get_carrier_group_type() - - def steam_into_wind(self, group: ShipGroup) -> Optional[Heading]: - wind = self.game.conditions.weather.wind.at_0m - brc = Heading.from_degrees(wind.direction).opposite - # Aim for 25kts over the deck. - carrier_speed = knots(25) - mps(wind.speed) - for attempt in range(5): - point = group.points[0].position.point_from_heading( - brc.degrees, 100000 - attempt * 20000 - ) - if self.game.theater.is_in_sea(point): - group.points[0].speed = carrier_speed.meters_per_second - group.add_waypoint(point, carrier_speed.kph) - # Rotate the whole ground object to the new course - self.ground_object.rotate(brc) - return brc - return None - - def tacan_callsign(self) -> str: - raise NotImplementedError - - @staticmethod - def activate_beacons( - group: ShipGroup, - tacan: TacanChannel, - callsign: str, - icls: Optional[int] = None, - icls_name: Optional[str] = None, - link4: Optional[RadioFrequency] = None, - ) -> None: - group.points[0].tasks.append( - ActivateBeaconCommand( - channel=tacan.number, - modechannel=tacan.band.value, - callsign=callsign, - unit_id=group.units[0].id, - aa=False, - ) - ) - if icls is not None: - icls_name = "" if icls_name is None else icls_name - group.points[0].tasks.append( - ActivateICLSCommand(icls, group.units[0].id, icls_name) - ) - if link4 is not None: - group.points[0].tasks.append( - ActivateLink4Command(link4.hertz, group.units[0].id) - ) - group.points[0].tasks.append(ActivateACLSCommand(unit_id=group.units[0].id)) - - def add_runway_data( - self, - brc: Heading, - atc: RadioFrequency, - tacan: TacanChannel, - callsign: str, - icls: Optional[int], - ) -> None: - # This relies on one control point mapping exactly - # to one LHA, carrier, or other usable "runway". - # This isn't wholly true, since the DD escorts of - # the carrier group are valid for helicopters, but - # they aren't exposed as such to the game. Should - # clean this up so that's possible. We can't use the - # unit name since it's an arbitrary ID. - self.runways[self.control_point.full_name] = RunwayData( - self.control_point.name, - brc, - f"{brc.degrees:03}", - atc=atc, - tacan=tacan, - tacan_callsign=callsign, - icls=icls, - ) - - -class CarrierGenerator(GenericCarrierGenerator): - """Generator for CV(N) groups.""" - - def tacan_callsign(self) -> str: - # TODO: Assign these properly. - return random.choice( - [ - "STE", - "CVN", - "CVH", - "CCV", - "ACC", - "ARC", - "GER", - "ABR", - "LIN", - "TRU", - ] - ) - - -class LhaGenerator(GenericCarrierGenerator): - """Generator for LHA groups.""" - - def tacan_callsign(self) -> str: - # TODO: Assign these properly. - return random.choice( - [ - "LHD", - "LHA", - "LHB", - "LHC", - "LHD", - "LDS", - ] - ) - - -class HelipadGenerator: - """ - Generates helipads for given control point - """ - - def __init__( - self, - mission: Mission, - cp: ControlPoint, - game: Game, - radio_registry: RadioRegistry, - tacan_registry: TacanRegistry, - ): - self.m = mission - self.cp = cp - self.game = game - self.radio_registry = radio_registry - self.tacan_registry = tacan_registry - self.helipads: list[StaticGroup] = [] - - def create_helipad( - self, i: int, helipad: PointWithHeading, helipad_type: str - ) -> None: - # Note: Helipad are generated as neutral object in order not to interfere with - # capture triggers - pad: BaseFARP - neutral_country = self.m.country(self.game.neutral_country.name) - country = self.m.country( - self.game.coalition_for(self.cp.captured).faction.country.name - ) - - name = f"{self.cp.name} {helipad_type} {i}" - logging.info("Generating helipad static : " + name) - terrain = self.m.terrain - if helipad_type == "SINGLE_HELIPAD": - pad = SingleHeliPad( - unit_id=self.m.next_unit_id(), name=name, terrain=terrain - ) - number_of_pads = 1 - elif helipad_type == "FARP": - pad = FARP(unit_id=self.m.next_unit_id(), name=name, terrain=terrain) - number_of_pads = 4 - else: - pad = InvisibleFARP( - unit_id=self.m.next_unit_id(), name=name, terrain=terrain - ) - number_of_pads = 1 - pad.position = Point(helipad.x, helipad.y, terrain=terrain) - pad.heading = helipad.heading.degrees - - # Set FREQ - if isinstance(self.cp, RadioFrequencyContainer) and self.cp.frequency: - if isinstance(pad, BaseFARP): - pad.heliport_frequency = self.cp.frequency.mhz - - sg = unitgroup.StaticGroup(self.m.next_group_id(), name) - sg.add_unit(pad) - sp = StaticPoint(pad.position) - sg.add_point(sp) - neutral_country.add_static_group(sg) - - if number_of_pads > 1: - self.append_helipad(pad, name, helipad.heading.degrees, 60, 0, 0) - self.append_helipad(pad, name, helipad.heading.degrees + 180, 20, 0, 0) - self.append_helipad( - pad, name, helipad.heading.degrees + 90, 60, helipad.heading.degrees, 20 - ) - self.append_helipad( - pad, - name, - helipad.heading.degrees + 90, - 60, - helipad.heading.degrees + 180, - 60, - ) - else: - self.helipads.append(sg) - - # Generate a FARP Ammo and Fuel stack for each pad - self.m.static_group( - country=country, - name=(name + "_fuel"), - _type=Fortification.FARP_Fuel_Depot, - position=pad.position.point_from_heading(helipad.heading.degrees, 35), - heading=pad.heading + 180, - ) - self.m.static_group( - country=country, - name=(name + "_ammo"), - _type=Fortification.FARP_Ammo_Dump_Coating, - position=pad.position.point_from_heading( - helipad.heading.degrees, 35 - ).point_from_heading(helipad.heading.degrees + 90, 10), - heading=pad.heading + 90, - ) - self.m.static_group( - country=country, - name=(name + "_ws"), - _type=Fortification.Windsock, - position=helipad.point_from_heading(helipad.heading.degrees + 45, 35), - heading=pad.heading, - ) - - def append_helipad( - self, - pad: BaseFARP, - name: str, - heading_1: int, - distance_1: int, - heading_2: int, - distance_2: int, - ) -> None: - new_pad = InvisibleFARP(pad._terrain) - new_pad.position = pad.position.point_from_heading(heading_1, distance_1) - new_pad.position = new_pad.position.point_from_heading(heading_2, distance_2) - sg = unitgroup.StaticGroup(self.m.next_group_id(), name) - sg.add_unit(new_pad) - self.helipads.append(sg) - - def generate(self) -> None: - for i, helipad in enumerate(self.cp.helipads): - self.create_helipad(i, helipad, "SINGLE_HELIPAD") - for i, helipad in enumerate(self.cp.helipads_quad): - self.create_helipad(i, helipad, "FARP") - for i, helipad in enumerate(self.cp.helipads_invisible): - self.create_helipad(i, helipad, "Invisible FARP") - - -class GroundSpawnRoadbaseGenerator: - """ - Generates Highway strip starting positions for given control point - """ - - def __init__( - self, - mission: Mission, - cp: ControlPoint, - game: Game, - radio_registry: RadioRegistry, - tacan_registry: TacanRegistry, - ): - self.m = mission - self.cp = cp - self.game = game - self.radio_registry = radio_registry - self.tacan_registry = tacan_registry - self.ground_spawns_roadbase: list[Tuple[StaticGroup, Point]] = [] - - def create_ground_spawn_roadbase( - self, i: int, ground_spawn: Tuple[PointWithHeading, Point] - ) -> None: - # Note: FARPs are generated as neutral object in order not to interfere with - # capture triggers - neutral_country = self.m.country(self.game.neutral_country.name) - country = self.m.country( - self.game.coalition_for(self.cp.captured).faction.country.name - ) - terrain = self.cp.coalition.game.theater.terrain - - name = f"{self.cp.name} roadbase spawn {i}" - logging.info("Generating Roadbase Spawn static : " + name) - - pad = InvisibleFARP(unit_id=self.m.next_unit_id(), name=name, terrain=terrain) - - pad.position = Point(ground_spawn[0].x, ground_spawn[0].y, terrain=terrain) - pad.heading = ground_spawn[0].heading.degrees - sg = unitgroup.StaticGroup(self.m.next_group_id(), name) - sg.add_unit(pad) - sp = StaticPoint(pad.position) - sg.add_point(sp) - neutral_country.add_static_group(sg) - - self.ground_spawns_roadbase.append((sg, ground_spawn[1])) - - # tanker_type: Type[VehicleType] - # ammo_truck_type: Type[VehicleType] - - tanker_type, ammo_truck_type = farp_truck_types_for_country(country.id) - - # Generate ammo truck/farp and fuel truck/stack for each pad - if self.game.settings.ground_start_trucks_roadbase: - self.m.vehicle_group( - country=country, - name=(name + "_fuel"), - _type=tanker_type, - position=pad.position.point_from_heading( - ground_spawn[0].heading.degrees + 90, 35 - ), - group_size=1, - heading=pad.heading + 315, - move_formation=PointAction.OffRoad, - ) - self.m.vehicle_group( - country=country, - name=(name + "_ammo"), - _type=ammo_truck_type, - position=pad.position.point_from_heading( - ground_spawn[0].heading.degrees + 90, 35 - ).point_from_heading(ground_spawn[0].heading.degrees + 180, 10), - group_size=1, - heading=pad.heading + 315, - move_formation=PointAction.OffRoad, - ) - else: - self.m.static_group( - country=country, - name=(name + "_fuel"), - _type=Fortification.FARP_Fuel_Depot, - position=pad.position.point_from_heading( - ground_spawn[0].heading.degrees + 90, 35 - ), - heading=pad.heading + 270, - ) - self.m.static_group( - country=country, - name=(name + "_ammo"), - _type=Fortification.FARP_Ammo_Dump_Coating, - position=pad.position.point_from_heading( - ground_spawn[0].heading.degrees + 90, 35 - ).point_from_heading(ground_spawn[0].heading.degrees + 180, 10), - heading=pad.heading + 180, - ) - - def generate(self) -> None: - try: - for i, ground_spawn in enumerate(self.cp.ground_spawns_roadbase): - self.create_ground_spawn_roadbase(i, ground_spawn) - except AttributeError: - self.ground_spawns_roadbase = [] - - -class GroundSpawnGenerator: - """ - Generates STOL aircraft starting positions for given control point - """ - - def __init__( - self, - mission: Mission, - cp: ControlPoint, - game: Game, - radio_registry: RadioRegistry, - tacan_registry: TacanRegistry, - ): - self.m = mission - self.cp = cp - self.game = game - self.radio_registry = radio_registry - self.tacan_registry = tacan_registry - self.ground_spawns: list[Tuple[StaticGroup, Point]] = [] - - def create_ground_spawn( - self, i: int, vtol_pad: Tuple[PointWithHeading, Point] - ) -> None: - # Note: FARPs are generated as neutral object in order not to interfere with - # capture triggers - neutral_country = self.m.country(self.game.neutral_country.name) - country = self.m.country( - self.game.coalition_for(self.cp.captured).faction.country.name - ) - terrain = self.cp.coalition.game.theater.terrain - - name = f"{self.cp.name} ground spawn {i}" - logging.info("Generating Ground Spawn static : " + name) - - pad = InvisibleFARP(unit_id=self.m.next_unit_id(), name=name, terrain=terrain) - - pad.position = Point(vtol_pad[0].x, vtol_pad[0].y, terrain=terrain) - pad.heading = vtol_pad[0].heading.degrees - sg = unitgroup.StaticGroup(self.m.next_group_id(), name) - sg.add_unit(pad) - sp = StaticPoint(pad.position) - sg.add_point(sp) - neutral_country.add_static_group(sg) - - self.ground_spawns.append((sg, vtol_pad[1])) - - # tanker_type: Type[VehicleType] - # ammo_truck_type: Type[VehicleType] - - tanker_type, ammo_truck_type = farp_truck_types_for_country(country.id) - - # Generate a FARP Ammo and Fuel stack for each pad - if self.game.settings.ground_start_trucks: - self.m.vehicle_group( - country=country, - name=(name + "_fuel"), - _type=tanker_type, - position=pad.position.point_from_heading( - vtol_pad[0].heading.degrees - 175, 35 - ), - group_size=1, - heading=pad.heading + 45, - move_formation=PointAction.OffRoad, - ) - self.m.vehicle_group( - country=country, - name=(name + "_ammo"), - _type=ammo_truck_type, - position=pad.position.point_from_heading( - vtol_pad[0].heading.degrees - 185, 35 - ), - group_size=1, - heading=pad.heading + 45, - move_formation=PointAction.OffRoad, - ) - else: - self.m.static_group( - country=country, - name=(name + "_fuel"), - _type=Fortification.FARP_Fuel_Depot, - position=pad.position.point_from_heading( - vtol_pad[0].heading.degrees - 180, 45 - ), - heading=pad.heading, - ) - self.m.static_group( - country=country, - name=(name + "_ammo"), - _type=Fortification.FARP_Ammo_Dump_Coating, - position=pad.position.point_from_heading( - vtol_pad[0].heading.degrees - 180, 35 - ), - heading=pad.heading + 270, - ) - - def generate(self) -> None: - try: - for i, vtol_pad in enumerate(self.cp.ground_spawns): - self.create_ground_spawn(i, vtol_pad) - except AttributeError: - self.ground_spawns = [] - - -class TgoGenerator: +class PretenseTgoGenerator(TgoGenerator): """Creates DCS groups and statics for the theater during mission generation. Most of the work of group/static generation is delegated to the other @@ -1091,6 +310,15 @@ class TgoGenerator: unit_map: UnitMap, mission_data: MissionData, ) -> None: + super().__init__( + mission, + game, + radio_registry, + tacan_registry, + unit_map, + mission_data, + ) + self.m = mission self.game = game self.radio_registry = radio_registry @@ -1175,7 +403,7 @@ class TgoGenerator: ground_object, country, self.game, self.m, self.unit_map ) else: - generator = GroundObjectGenerator( + generator = PretenseGroundObjectGenerator( ground_object, country, self.game, self.m, self.unit_map ) generator.generate() diff --git a/game/pretense/pretensetriggergenerator.py b/game/pretense/pretensetriggergenerator.py index e5ea05e9..43fbb1de 100644 --- a/game/pretense/pretensetriggergenerator.py +++ b/game/pretense/pretensetriggergenerator.py @@ -170,6 +170,8 @@ class PretenseTriggerGenerator: cp_name_trimmed = "".join([i for i in cp.name.lower() if i.isalnum()]) tgo_num = 0 for tgo in cp.ground_objects: + if cp.is_fleet or tgo.sea_object: + 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( From 1322c3ca54cf050c762d195b4363132a083d53e9 Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Sun, 10 Sep 2023 14:09:24 +0300 Subject: [PATCH 108/243] Cleaned up some of my recent Pretense code. --- game/pretense/pretenseaircraftgenerator.py | 2 +- game/pretense/pretensemissiongenerator.py | 3 --- game/pretense/pretensetgogenerator.py | 6 +++--- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/game/pretense/pretenseaircraftgenerator.py b/game/pretense/pretenseaircraftgenerator.py index 334a8871..5abec403 100644 --- a/game/pretense/pretenseaircraftgenerator.py +++ b/game/pretense/pretenseaircraftgenerator.py @@ -132,7 +132,7 @@ class PretenseAircraftGenerator: flight_type = FlightType.TRANSPORT elif ( FlightType.SEAD in mission_types - or FlightType.SEAD_SWEEP + or FlightType.SEAD_SWEEP in mission_types or FlightType.SEAD_ESCORT in mission_types ) and num_of_sead < 2: flight_type = FlightType.SEAD diff --git a/game/pretense/pretensemissiongenerator.py b/game/pretense/pretensemissiongenerator.py index b0b61ce2..d39b338c 100644 --- a/game/pretense/pretensemissiongenerator.py +++ b/game/pretense/pretensemissiongenerator.py @@ -258,9 +258,6 @@ class PretenseMissionGenerator: continue flight.aircraft_type.assign_channels_for_flight(flight, self.mission_data) - if self.game.settings.plugins.get("ewrj"): - self._configure_ewrj(aircraft_generator) - def notify_info_generators( self, ) -> None: diff --git a/game/pretense/pretensetgogenerator.py b/game/pretense/pretensetgogenerator.py index 5c4ef4e2..56d84f54 100644 --- a/game/pretense/pretensetgogenerator.py +++ b/game/pretense/pretensetgogenerator.py @@ -141,7 +141,7 @@ class PretenseGroundObjectGenerator(GroundObjectGenerator): cp_name: str, group_role: str, max_num: int, - ): + ) -> None: if self.ground_object.coalition.faction.has_access_to_unit_class(unit_class): unit_type = self.ground_unit_of_class(unit_class) if unit_type is not None and len(vehicle_units) < max_num: @@ -169,8 +169,8 @@ class PretenseGroundObjectGenerator(GroundObjectGenerator): if self.culled: return for group in self.ground_object.groups: - vehicle_units = [] - ship_units = [] + vehicle_units: list[TheaterUnit] = [] + ship_units: list[TheaterUnit] = [] # Split the different unit types to be compliant to dcs limitation for unit in group.units: cp_name_trimmed = "".join( From f752d422df03b0345d219fe462e5078dcf726905 Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Sun, 10 Sep 2023 14:10:54 +0300 Subject: [PATCH 109/243] Added Pretense icon, credit: Dzsekeb, original author of DCS Pretense. Used with permission. --- resources/ui/misc/pretense.png | Bin 0 -> 29600 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 resources/ui/misc/pretense.png diff --git a/resources/ui/misc/pretense.png b/resources/ui/misc/pretense.png new file mode 100644 index 0000000000000000000000000000000000000000..0daefbc3ae016cb08ebe5884382ac6aec382520a GIT binary patch literal 29600 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 literal 0 HcmV?d00001 From d58f06be6d72ea5f54fd00390982c71013a64713 Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Sun, 10 Sep 2023 14:39:26 +0300 Subject: [PATCH 110/243] Copied luagenerator.py as a template/inheritance for generating Pretense campaigns from Retribution campaigns. --- game/pretense/pretenseluagenerator.py | 392 ++++++++++++++++++++++++++ 1 file changed, 392 insertions(+) create mode 100644 game/pretense/pretenseluagenerator.py diff --git a/game/pretense/pretenseluagenerator.py b/game/pretense/pretenseluagenerator.py new file mode 100644 index 00000000..f8698ec7 --- /dev/null +++ b/game/pretense/pretenseluagenerator.py @@ -0,0 +1,392 @@ +from __future__ import annotations + +import logging +import os +from abc import ABC, abstractmethod +from pathlib import Path +from typing import TYPE_CHECKING, Optional + +from dcs import Mission +from dcs.action import DoScript, DoScriptFile +from dcs.translation import String +from dcs.triggers import TriggerStart + +from game.ato import FlightType +from game.dcs.aircrafttype import AircraftType +from game.plugins import LuaPluginManager +from game.theater import TheaterGroundObject +from game.theater.iadsnetwork.iadsrole import IadsRole +from game.utils import escape_string_for_lua +from .missiondata import MissionData + +if TYPE_CHECKING: + from game import Game + + +class LuaGenerator: + def __init__( + self, + game: Game, + mission: Mission, + mission_data: MissionData, + ) -> None: + self.game = game + self.mission = mission + self.mission_data = mission_data + self.plugin_scripts: list[str] = [] + + def generate(self) -> None: + ewrj_triggers = [ + x for x in self.mission.triggerrules.triggers if isinstance(x, TriggerStart) + ] + 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)) + 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) + + def inject_lua_trigger(self, contents: str, comment: str) -> None: + trigger = TriggerStart(comment=comment) + trigger.add_action(DoScript(String(contents))) + self.mission.triggerrules.triggers.append(trigger) + + def bypass_plugin_script(self, mnemonic: str) -> None: + self.plugin_scripts.append(mnemonic) + + def inject_plugin_script( + self, plugin_mnemonic: str, script: str, script_mnemonic: str + ) -> None: + if script_mnemonic in self.plugin_scripts: + logging.debug(f"Skipping already loaded {script} for {plugin_mnemonic}") + return + + self.plugin_scripts.append(script_mnemonic) + + plugin_path = Path("./resources/plugins", plugin_mnemonic) + + script_path = Path(plugin_path, script) + if not script_path.exists(): + logging.error(f"Cannot find {script_path} for plugin {plugin_mnemonic}") + return + + trigger = TriggerStart(comment=f"Load {script_mnemonic}") + filename = script_path.resolve() + fileref = self.mission.map_resource.add_resource_file(filename) + trigger.add_action(DoScriptFile(fileref)) + self.mission.triggerrules.triggers.append(trigger) + + def inject_plugins(self) -> None: + for plugin in LuaPluginManager.plugins(): + if plugin.enabled: + plugin.inject_scripts(self) + plugin.inject_configuration(self) + + +class LuaValue: + key: Optional[str] + value: str | list[str] + + def __init__(self, key: Optional[str], value: str | list[str]): + self.key = key + self.value = value + + def serialize(self) -> str: + serialized_value = self.key + " = " if self.key else "" + if isinstance(self.value, str): + serialized_value += f'"{escape_string_for_lua(self.value)}"' + else: + escaped_values = [f'"{escape_string_for_lua(v)}"' for v in self.value] + serialized_value += "{" + ", ".join(escaped_values) + "}" + return serialized_value + + +class LuaItem(ABC): + value: LuaValue | list[LuaValue] + name: Optional[str] + + def __init__(self, name: Optional[str]): + self.value = [] + self.name = name + + def set_value(self, value: str) -> None: + self.value = LuaValue(None, value) + + def set_data_array(self, values: list[str]) -> None: + self.value = LuaValue(None, values) + + def add_data_array(self, key: str, values: list[str]) -> None: + self._add_value(LuaValue(key, values)) + + def add_key_value(self, key: str, value: str) -> None: + self._add_value(LuaValue(key, value)) + + def _add_value(self, value: LuaValue) -> None: + if isinstance(self.value, list): + self.value.append(value) + else: + self.value = value + + @abstractmethod + def add_item(self, item_name: Optional[str] = None) -> LuaItem: + """adds a new item to the LuaArray without checking the existence""" + raise NotImplementedError + + @abstractmethod + def get_item(self, item_name: str) -> Optional[LuaItem]: + """gets item from LuaArray. Returns None if it does not exist""" + raise NotImplementedError + + @abstractmethod + def get_or_create_item(self, item_name: Optional[str] = None) -> LuaItem: + """gets item from the LuaArray or creates one if it does not exist already""" + raise NotImplementedError + + @abstractmethod + def serialize(self) -> str: + if isinstance(self.value, LuaValue): + return self.value.serialize() + else: + serialized_data = [d.serialize() for d in self.value] + return "{" + ", ".join(serialized_data) + "}" + + +class LuaData(LuaItem): + objects: list[LuaData] + base_name: Optional[str] + + def __init__(self, name: Optional[str], is_base_name: bool = True): + self.objects = [] + self.base_name = name if is_base_name else None + super().__init__(name) + + def add_item(self, item_name: Optional[str] = None) -> LuaItem: + item = LuaData(item_name, False) + self.objects.append(item) + return item + + def get_item(self, item_name: str) -> Optional[LuaItem]: + for lua_object in self.objects: + if lua_object.name == item_name: + return lua_object + return None + + def get_or_create_item(self, item_name: Optional[str] = None) -> LuaItem: + if item_name: + item = self.get_item(item_name) + if item: + return item + return self.add_item(item_name) + + def serialize(self, level: int = 0) -> str: + """serialize the LuaData to a string""" + serialized_data: list[str] = [] + serialized_name = "" + linebreak = "\n" + tab = "\t" + tab_end = "" + for _ in range(level): + tab += "\t" + tab_end += "\t" + if self.base_name: + # Only used for initialization of the object in lua + serialized_name += self.base_name + " = " + if self.objects: + # nested objects + serialized_objects = [o.serialize(level + 1) for o in self.objects] + if self.name: + if self.name is not self.base_name: + serialized_name += self.name + " = " + serialized_data.append( + serialized_name + + "{" + + linebreak + + tab + + ("," + linebreak + tab).join(serialized_objects) + + linebreak + + tab_end + + "}" + ) + else: + # key with value + if self.name: + serialized_data.append(self.name + " = " + super().serialize()) + # only value + else: + serialized_data.append(super().serialize()) + + return "\n".join(serialized_data) + + def create_operations_lua(self) -> str: + """crates the liberation lua script for the dcs mission""" + lua_prefix = """ +-- setting configuration table +env.info("DCSRetribution|: setting configuration table") +""" + + return lua_prefix + self.serialize() From c95e746687ce689ee5dea3945e1a04751933e110 Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Sun, 10 Sep 2023 14:57:06 +0300 Subject: [PATCH 111/243] Lua scripts from Pretense Caucasus 1.3.5, credit: Dzsekeb, original author of DCS Pretense. Used with permission. --- resources/plugins/pretense/init.lua | 4670 ++++++ .../plugins/pretense/pretense_compiled.lua | 13350 ++++++++++++++++ 2 files changed, 18020 insertions(+) create mode 100644 resources/plugins/pretense/init.lua create mode 100644 resources/plugins/pretense/pretense_compiled.lua diff --git a/resources/plugins/pretense/init.lua b/resources/plugins/pretense/init.lua new file mode 100644 index 00000000..9df8452d --- /dev/null +++ b/resources/plugins/pretense/init.lua @@ -0,0 +1,4670 @@ + + +local savefile = 'pretense_1.1.json' +if lfs then + local dir = lfs.writedir()..'Missions/Saves/' + lfs.mkdir(dir) + savefile = dir..savefile + env.info('Pretense - Save file path: '..savefile) +end + + +do + TemplateDB.templates["infantry-red"] = { + units = { + "BTR_D", + "T-90", + "T-90", + "Infantry AK ver2", + "Infantry AK", + "Infantry AK", + "Paratrooper RPG-16", + "Infantry AK ver3", + "SA-18 Igla manpad" + }, + skill = "Excellent", + dataCategory= TemplateDB.type.group + } + + TemplateDB.templates["infantry-blue"] = { + units = { + "M1045 HMMWV TOW", + "Soldier stinger", + "Soldier M4 GRG", + "Soldier M4 GRG", + "Soldier M4 GRG", + "Soldier M4 GRG", + "Soldier M4 GRG", + "M1043 HMMWV Armament" + }, + skill = "Excellent", + dataCategory= TemplateDB.type.group + } + + TemplateDB.templates["defense-red"] = { + units = { + "Infantry AK ver2", + "Infantry AK", + "Infantry AK ver3", + "Paratrooper RPG-16", + "SA-18 Igla manpad" + }, + skill = "Excellent", + dataCategory= TemplateDB.type.group + } + + TemplateDB.templates["defense-blue"] = { + units = { + "Soldier M4 GRG", + "Soldier M4 GRG", + "Soldier M4 GRG", + "Soldier RPG", + "Soldier stinger", + }, + skill = "Excellent", + dataCategory= TemplateDB.type.group + } + + TemplateDB.templates["shorad-red"] = { + units = { + "Strela-10M3", + "Strela-10M3", + "Ural-4320T", + "2S6 Tunguska" + }, + maxDist = 300, + skill = "Excellent", + dataCategory= TemplateDB.type.group + } + + TemplateDB.templates["shorad-blue"] = { + units = { + "Roland ADS", + "M48 Chaparral", + "M 818", + "Gepard", + "Gepard" + }, + maxDist = 300, + skill = "Excellent", + dataCategory= TemplateDB.type.group + } + + TemplateDB.templates["sam-red"] = { + units = { + "p-19 s-125 sr", + "Ural-4320T", + "Ural-4320T", + "S_75M_Volhov", + "S_75M_Volhov", + "S_75M_Volhov", + "S_75M_Volhov", + "S_75M_Volhov", + "Tor 9A331", + "SNR_75V" + }, + maxDist = 300, + skill = "Excellent", + dataCategory= TemplateDB.type.group + } + + TemplateDB.templates["sam-blue"] = { + units = { + "Hawk pcp", + "Hawk cwar", + "Hawk ln", + "Hawk ln", + "Hawk ln", + "Hawk ln", + "Hawk ln", + "Hawk tr", + "M 818", + "Hawk sr" + }, + maxDist = 300, + skill = "Good", + dataCategory= TemplateDB.type.group + } + + TemplateDB.templates["patriot"] = { + units = { + "Patriot cp", + "Patriot str", + "M 818", + "M 818", + "Patriot ln", + "Patriot ln", + "Patriot ln", + "Patriot ln", + "Patriot str", + "Patriot str", + "Patriot str", + "Patriot EPP", + "Patriot ECS", + "Patriot AMG" + }, + maxDist = 300, + skill = "Good", + dataCategory= TemplateDB.type.group + } + + TemplateDB.templates["sa3"] = { + units = { + "p-19 s-125 sr", + "snr s-125 tr", + "5p73 s-125 ln", + "5p73 s-125 ln", + "Ural-4320T", + "5p73 s-125 ln", + "5p73 s-125 ln" + }, + maxDist = 300, + skill = "Excellent", + dataCategory= TemplateDB.type.group + } + + TemplateDB.templates["sa6"] = { + units = { + "Kub 1S91 str", + "Kub 2P25 ln", + "Kub 2P25 ln", + "Kub 2P25 ln", + "Kub 2P25 ln", + "2S6 Tunguska", + "Ural-4320T", + "2S6 Tunguska", + "Kub 2P25 ln" + }, + maxDist = 300, + skill = "Excellent", + dataCategory= TemplateDB.type.group + } + + TemplateDB.templates["sa10"] = { + units = { + "S-300PS 54K6 cp", + "S-300PS 5P85C ln", + "S-300PS 5P85C ln", + "S-300PS 5P85C ln", + "GAZ-66", + "GAZ-66", + "GAZ-66", + "S-300PS 5P85C ln", + "S-300PS 5P85C ln", + "S-300PS 5P85C ln", + "S-300PS 40B6MD sr", + "S-300PS 40B6M tr", + "S-300PS 64H6E sr" + }, + maxDist = 300, + skill = "Excellent", + dataCategory= TemplateDB.type.group + } + + TemplateDB.templates["sa5"] = { + units = { + "RLS_19J6", + "Ural-4320T", + "Ural-4320T", + "RPC_5N62V", + "S-200_Launcher", + "S-200_Launcher", + "S-200_Launcher", + "S-200_Launcher", + "S-200_Launcher", + "S-200_Launcher" + }, + maxDist = 300, + skill = "Excellent", + dataCategory= TemplateDB.type.group + } + + TemplateDB.templates["sa11"] = { + units = { + "SA-11 Buk SR 9S18M1", + "SA-11 Buk LN 9A310M1", + "SA-11 Buk LN 9A310M1", + "SA-11 Buk LN 9A310M1", + "SA-11 Buk LN 9A310M1", + "SA-11 Buk LN 9A310M1", + "2S6 Tunguska", + "SA-11 Buk SR 9S18M1", + "GAZ-66", + "GAZ-66", + "SA-11 Buk CC 9S470M1" + }, + maxDist = 300, + skill = "Excellent", + dataCategory= TemplateDB.type.group + } + + TemplateDB.templates["nasams"] = { + units = { + "NASAMS_Command_Post", + "NASAMS_Radar_MPQ64F1", + "Vulcan", + "M 818", + "M 818", + "Roland ADS", + "Roland ADS", + "NASAMS_LN_C", + "NASAMS_LN_C", + "NASAMS_LN_C", + "NASAMS_LN_C", + "NASAMS_Radar_MPQ64F1", + "NASAMS_Radar_MPQ64F1", + "NASAMS_Radar_MPQ64F1" + }, + maxDist = 300, + skill = "Excellent", + dataCategory= TemplateDB.type.group + } +end + +presets = { + upgrades = { + basic = { + tent = Preset:new({ + display = 'Tent', + cost = 1500, + type = 'upgrade', + template = "tent" + }), + comPost = Preset:new({ + display = 'Barracks', + cost = 1500, + type = 'upgrade', + template = "barracks" + }), + outpost = Preset:new({ + display = 'Outpost', + cost = 1500, + type = 'upgrade', + template = "outpost" + }) + }, + attack = { + ammoCache = Preset:new({ + display = 'Ammo Cache', + cost = 1500, + type = 'upgrade', + template = "ammo-cache" + }), + ammoDepot = Preset:new({ + display = 'Ammo Depot', + cost = 2000, + type = 'upgrade', + template = "ammo-depot" + }) + }, + supply = { + fuelCache = Preset:new({ + display = 'Fuel Cache', + cost = 1500, + type = 'upgrade', + template = "fuel-cache" + }), + fuelTank = Preset:new({ + display = 'Fuel Tank', + cost = 1500, + type = 'upgrade', + template = "fuel-tank-big" + }), + fuelTankFarp = Preset:new({ + display = 'Fuel Tank', + cost = 1500, + type = 'upgrade', + template = "fuel-tank-small" + }), + factory1 = Preset:new({ + display='Factory', + cost = 2000, + type ='upgrade', + income = 20, + template = "factory-1" + }), + factory2 = Preset:new({ + display='Factory', + cost = 2000, + type ='upgrade', + income = 20, + template = "factory-2" + }), + factoryTank = Preset:new({ + display='Storage Tank', + cost = 1500, + type ='upgrade', + income = 10, + template = "chem-tank" + }), + ammoDepot = Preset:new({ + display = 'Ammo Depot', + cost = 2000, + type = 'upgrade', + income = 40, + template = "ammo-depot" + }), + oilPump = Preset:new({ + display = 'Oil Pump', + cost = 1500, + type = 'upgrade', + income = 20, + template = "oil-pump" + }), + hangar = Preset:new({ + display = 'Hangar', + cost = 2000, + type = 'upgrade', + income = 30, + template = "hangar" + }), + excavator = Preset:new({ + display = 'Excavator', + cost = 2000, + type = 'upgrade', + income = 20, + template = "excavator" + }), + farm1 = Preset:new({ + display = 'Farm House', + cost = 2000, + type = 'upgrade', + income = 40, + template = "farm-house-1" + }), + farm2 = Preset:new({ + display = 'Farm House', + cost = 2000, + type = 'upgrade', + income = 40, + template = "farm-house-2" + }), + refinery1 = Preset:new({ + display='Refinery', + cost = 2000, + type ='upgrade', + income = 100, + template = "factory-1" + }), + powerplant1 = Preset:new({ + display='Power Plant', + cost = 1500, + type ='upgrade', + income = 25, + template = "factory-1" + }), + powerplant2 = Preset:new({ + display='Power Plant', + cost = 1500, + type ='upgrade', + income = 25, + template = "factory-2" + }), + antenna = Preset:new({ + display='Antenna', + cost = 1000, + type ='upgrade', + income = 10, + template = "antenna" + }), + hq = Preset:new({ + display='HQ Building', + cost = 2000, + type ='upgrade', + income = 50, + template = "tv-tower" + }) + }, + airdef = { + comCenter = Preset:new({ + display = 'Command Center', + cost = 2500, + type = 'upgrade', + template = "command-center" + }) + } + }, + defenses = { + red = { + infantry = Preset:new({ + display = 'Infantry', + cost=2000, + type='defense', + template='infantry-red', + }), + shorad = Preset:new({ + display = 'SAM', + cost=2500, + type='defense', + template='shorad-red', + }), + sam = Preset:new({ + display = 'SAM', + cost=3000, + type='defense', + template='sam-red', + }), + sa10 = Preset:new({ + display = 'SAM', + cost=3000, + type='defense', + template='sa10', + }), + sa5 = Preset:new({ + display = 'SAM', + cost=3000, + type='defense', + template='sa5', + }), + sa3 = Preset:new({ + display = 'SAM', + cost=3000, + type='defense', + template='sa3', + }), + sa6 = Preset:new({ + display = 'SAM', + cost=3000, + type='defense', + template='sa6', + }), + sa11 = Preset:new({ + display = 'SAM', + cost=3000, + type='defense', + template='sa11', + }) + }, + blue = { + infantry = Preset:new({ + display = 'Infantry', + cost=2000, + type='defense', + template='infantry-blue', + }), + shorad = Preset:new({ + display = 'SAM', + cost=2500, + type='defense', + template='shorad-blue', + }), + sam = Preset:new({ + display = 'SAM', + cost=3000, + type='defense', + template='sam-blue', + }), + patriot = Preset:new({ + display = 'SAM', + cost=3000, + type='defense', + template='patriot', + }), + nasams = Preset:new({ + display = 'SAM', + cost=3000, + type='defense', + template='nasams', + }) + } + }, + missions = { + supply = { + convoy = Preset:new({ + display = 'Supply convoy', + cost = 4000, + type = 'mission', + missionType = ZoneCommand.missionTypes.supply_convoy + }), + convoy_escorted = Preset:new({ + display = 'Supply convoy', + cost = 3000, + type = 'mission', + missionType = ZoneCommand.missionTypes.supply_convoy + }), + helo = Preset:new({ + display = 'Supply helicopter', + cost = 2500, + type='mission', + missionType = ZoneCommand.missionTypes.supply_air + }), + transfer = Preset:new({ + display = 'Supply transfer', + cost = 1000, + type='mission', + missionType = ZoneCommand.missionTypes.supply_transfer + }) + }, + attack = { + surface = Preset:new({ + display = 'Ground assault', + cost = 100, + type = 'mission', + missionType = ZoneCommand.missionTypes.assault, + }), + cas = Preset:new({ + display = 'CAS', + cost = 200, + type='mission', + missionType = ZoneCommand.missionTypes.cas + }), + bai = Preset:new({ + display = 'BAI', + cost = 200, + type='mission', + missionType = ZoneCommand.missionTypes.bai + }), + strike = Preset:new({ + display = 'Strike', + cost = 300, + type='mission', + missionType = ZoneCommand.missionTypes.strike + }), + sead = Preset:new({ + display = 'SEAD', + cost = 200, + type='mission', + missionType = ZoneCommand.missionTypes.sead + }), + helo = Preset:new({ + display = 'CAS', + cost = 100, + type='mission', + missionType = ZoneCommand.missionTypes.cas_helo + }) + }, + patrol={ + aircraft = Preset:new({ + display= "Patrol", + cost = 100, + type='mission', + missionType = ZoneCommand.missionTypes.patrol + }) + }, + support ={ + awacs = Preset:new({ + display= "AWACS", + cost = 300, + type='mission', + bias='5', + missionType = ZoneCommand.missionTypes.awacs + }), + tanker = Preset:new({ + display= "Tanker", + cost = 200, + type='mission', + bias='2', + missionType = ZoneCommand.missionTypes.tanker + }) + } + }, + special = { + red = { + infantry = Preset:new({ + display = 'Infantry', + cost=-1, + type='defense', + template='defense-red', + }), + }, + blue = { + infantry = Preset:new({ + display = 'Infantry', + cost=-1, + type='defense', + template='defense-blue', + }) + } + } +} + +zones = {} +do + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Batumi.lua ]]----------------- + +zones.batumi = ZoneCommand:new('Batumi') +zones.batumi.initialState = { side=2 } +zones.batumi.keepActive = true +zones.batumi.isHeloSpawn = true +zones.batumi.isPlaneSpawn = true +zones.batumi.maxResource = 50000 +zones.batumi:defineUpgrades({ + [1] = { --red side + presets.upgrades.basic.comPost:extend({ + name = 'batumi-com-red', + products = { + presets.special.red.infantry:extend({ name='batumi-defense-red'}), + presets.defenses.red.infantry:extend({ name='batumi-garrison-red' }) + } + }), + }, + [2] = --blue side + { + presets.upgrades.basic.comPost:extend({ + name = 'batumi-com-blue', + products = { + presets.special.blue.infantry:extend({ name='batumi-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='batumi-garrison-blue' }) + } + }), + presets.upgrades.supply.fuelTank:extend({ + name = 'batumi-fueltank-blue', + products = { + presets.missions.supply.convoy_escorted:extend({ name='batumi-supply-convoy-1'}), + presets.missions.supply.helo:extend({ name='batumi-supply-blue-1' }), + presets.missions.supply.transfer:extend({name='batumi-transfer-blue'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name = 'batumi-mission-command-blue', + products = { + presets.defenses.blue.shorad:extend({ name='batumi-sam-blue' }), + presets.missions.attack.sead:extend({name='batumi-sead-blue-1', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.attack.cas:extend({name='batumi-cas-blue-1', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.bai:extend({name='batumi-cas-blue-1', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.strike:extend({name='batumi-strike-blue-1', altitude=20000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.patrol.aircraft:extend({name='batumi-patrol-blue-1', altitude=25000, range=25}), + presets.missions.support.awacs:extend({name='batumi-awacs-blue', altitude=30000, freq=257.5}), + presets.missions.support.tanker:extend({name='batumi-tanker-blue', altitude=25000, freq=257, tacan='37', variant="Drogue"}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Batumi.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Mike.lua ]]----------------- + +zones.mike = ZoneCommand:new("Mike") +zones.mike.initialState = { side=1 } +zones.mike.keepActive = true +zones.mike:defineUpgrades({ + [1] = + { + presets.upgrades.basic.tent:extend({ + name='mike-tent-red', + products = { + presets.special.red.infantry:extend({ name='mike-defense-red'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='mike-comcenter-red', + products = { + presets.defenses.red.sam:extend({ name='mike-sam-red'}) + } + }) + }, + [2] = + { + presets.upgrades.basic.tent:extend({ + name='mike-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='mike-defense-blue'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='mike-comcenter-blue', + products = { + presets.defenses.blue.sam:extend({ name='mike-sam-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Mike.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Tyrnyauz.lua ]]----------------- + +zones.tyrnyauz = ZoneCommand:new("Tyrnyauz") +zones.tyrnyauz.initialState = { side=1 } +zones.tyrnyauz.isHeloSpawn = true +zones.tyrnyauz:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='tyrnyauz-tent-red', + products = { + presets.special.red.infantry:extend({ name='tyrnyauz-defense-red'}), + presets.defenses.red.infantry:extend({ name='tyrnyauz-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='tyrnyauz-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='tyrnyauz-supply-red'}), + presets.missions.supply.helo:extend({name='tyrnyauz-supply-red-2'}), + presets.missions.supply.transfer:extend({name='tyrnyauz-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='tyrnyauz-ammo-red', + products = { + presets.missions.attack.surface:extend({name='tyrnyauz-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='tyrnyauz-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='tyrnyauz-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='tyrnyauz-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='tyrnyauz-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='tyrnyauz-supply-blue'}), + presets.missions.supply.helo:extend({name='tyrnyauz-supply-blue-2'}), + presets.missions.supply.transfer:extend({name='tyrnyauz-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='tyrnyauz-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='tyrnyauz-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Tyrnyauz.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/India.lua ]]----------------- + +zones.india = ZoneCommand:new("India") +zones.india.initialState = nil +zones.india:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='india-tent-red', + products = { + presets.special.red.infantry:extend({ name='india-defense-red'}), + presets.defenses.red.infantry:extend({ name='india-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='india-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='india-supply-red'}), + presets.missions.supply.transfer:extend({name='india-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='india-ammo-red', + products = { + presets.missions.attack.surface:extend({name='india-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='india-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='india-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='india-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='india-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='india-supply-blue'}), + presets.missions.supply.transfer:extend({name='india-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='india-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='india-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/India.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/IntelCenter.lua ]]----------------- + +zones.intelcenter = ZoneCommand:new("Intel Center") +zones.intelcenter.initialState = { side=1 } +zones.intelcenter:defineUpgrades({ + [1] = + { + presets.upgrades.basic.tent:extend({ + name='intelcenter-tent-red', + products = { + presets.special.red.infantry:extend({ name='intelcenter-defense-red'}), + presets.defenses.red.infantry:extend({ name='intelcenter-garrison-red'}) + } + }), + presets.upgrades.supply.hq:extend({ + name='intelcenter-hq-red', + products = { + presets.missions.supply.convoy:extend({ name='intelcenter-supply-red'}), + presets.missions.supply.convoy:extend({ name='intelcenter-supply-red-1'}), + presets.missions.supply.transfer:extend({name='intelcenter-transfer-red'}) + } + }), + presets.upgrades.supply.antenna:extend({ + name='intelcenter-antenna-red', + products = { + } + }), + presets.upgrades.supply.antenna:extend({ + name='intelcenter-antenna-red-1', + products = { + } + }), + presets.upgrades.supply.antenna:extend({ + name='intelcenter-antenna-red-2', + products = { + } + }) + }, + [2] = + { + presets.upgrades.basic.tent:extend({ + name='intelcenter-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='intelcenter-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='intelcenter-garrison-blue'}) + } + }), + presets.upgrades.supply.hq:extend({ + name='intelcenter-hq-blue', + products = { + presets.missions.supply.convoy:extend({ name='intelcenter-supply-blue'}), + presets.missions.supply.convoy:extend({ name='intelcenter-supply-blue-1'}), + presets.missions.supply.transfer:extend({name='intelcenter-transfer-blue'}) + } + }), + presets.upgrades.supply.antenna:extend({ + name='intelcenter-antenna-blue', + products = { + } + }), + presets.upgrades.supply.antenna:extend({ + name='intelcenter-antenna-blue-1', + products = { + } + }), + presets.upgrades.supply.antenna:extend({ + name='intelcenter-antenna-blue-2', + products = { + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/IntelCenter.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Mineralnye.lua ]]----------------- + +zones.mineralnye = ZoneCommand:new("Mineralnye") +zones.mineralnye.initialState = { side=1 } +zones.mineralnye.keepActive = true +zones.mineralnye.isHeloSpawn = true +zones.mineralnye.isPlaneSpawn = true +zones.mineralnye:defineUpgrades({ + [1] = + { + presets.upgrades.basic.comPost:extend({ + name='mineralnye-compost-red', + products = { + presets.special.red.infantry:extend({ name='mineralnye-defense-red'}), + presets.defenses.red.infantry:extend({ name='mineralnye-garrison-red'}) + } + }), + presets.upgrades.supply.fuelTank:extend({ + name='mineralnye-fuel-red', + products = { + presets.missions.supply.helo:extend({name='mineralnye-supply-red'}), + presets.missions.supply.helo:extend({name='mineralnye-supply-red-1'}), + presets.missions.supply.transfer:extend({name='mineralnye-transfer-red'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='mineralnye-comcenter-red', + products = { + presets.defenses.red.sa11:extend({ name='mineralnye-airdef-red'}), + presets.missions.attack.cas:extend({name='mineralnye-cas-red', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.bai:extend({name='mineralnye-cas-red', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.strike:extend({name='mineralnye-strike-red', altitude=30000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.attack.strike:extend({name='mineralnye-strike-red-1', altitude=30000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.patrol.aircraft:extend({name='mineralnye-patrol-red', altitude=25000, range=25}) + } + }) + }, + [2] = + { + presets.upgrades.basic.comPost:extend({ + name='mineralnye-compost-blue', + products = { + presets.special.blue.infantry:extend({ name='mineralnye-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='mineralnye-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelTank:extend({ + name='mineralnye-fuel-blue', + products = { + presets.missions.supply.helo:extend({name='mineralnye-supply-blue'}), + presets.missions.supply.helo:extend({name='mineralnye-supply-blue-1'}), + presets.missions.supply.transfer:extend({name='mineralnye-transfer-blue'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='mineralnye-comcenter-blue', + products = { + presets.defenses.blue.nasams:extend({ name='mineralnye-airdef-blue'}), + presets.missions.attack.cas:extend({name='mineralnye-cas-blue', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.bai:extend({name='mineralnye-cas-blue', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.strike:extend({name='mineralnye-strike-blue', altitude=30000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.attack.strike:extend({name='mineralnye-strike-blue-1', altitude=30000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.patrol.aircraft:extend({name='mineralnye-patrol-blue', altitude=25000, range=25}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Mineralnye.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/PowerPlant.lua ]]----------------- + +zones.powerplant = ZoneCommand:new("Power Plant") +zones.powerplant.initialState = { side=1 } +zones.powerplant:defineUpgrades({ + [1] = + { + presets.upgrades.basic.tent:extend({ + name='powerplant-tent-red', + products = { + presets.special.red.infantry:extend({ name='powerplant-defense-red'}), + presets.defenses.red.infantry:extend({ name='powerplant-garrison-red'}) + } + }), + presets.upgrades.supply.powerplant1:extend({ + name='powerplant-building-red-1', + products = { + presets.missions.supply.convoy:extend({ name='powerplant-supply-red'}), + presets.missions.supply.transfer:extend({name='powerplant-transfer-red'}) + } + }), + presets.upgrades.supply.powerplant2:extend({ + name='powerplant-building-red-2', + products = { + presets.missions.supply.convoy:extend({ name='powerplant-supply-red-1'}), + presets.missions.supply.transfer:extend({name='powerplant-transfer-red'}) + } + }) + }, + [2] = + { + presets.upgrades.basic.tent:extend({ + name='powerplant-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='powerplant-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='powerplant-garrison-blue'}) + } + }), + presets.upgrades.supply.powerplant1:extend({ + name='powerplant-building-blue-1', + products = { + presets.missions.supply.convoy:extend({ name='powerplant-supply-blue'}), + presets.missions.supply.transfer:extend({name='powerplant-transfer-blue'}) + } + }), + presets.upgrades.supply.powerplant2:extend({ + name='powerplant-building-blue-2', + products = { + presets.missions.supply.convoy:extend({ name='powerplant-supply-blue-1'}), + presets.missions.supply.transfer:extend({name='powerplant-transfer-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/PowerPlant.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Zugdidi.lua ]]----------------- + +zones.zugdidi = ZoneCommand:new("Zugdidi") +zones.zugdidi.initialState = { side=1 } +zones.zugdidi:defineUpgrades({ + [1] = + { + presets.upgrades.basic.comPost:extend({ + name='zugdidi-compost-red', + products = { + presets.missions.supply.transfer:extend({name='zugdidi-transfer-red'}), + presets.special.red.infantry:extend({ name='zugdidi-defense-red'}), + presets.defenses.red.infantry:extend({ name='zugdidi-garrison-red'}), + presets.missions.attack.surface:extend({name='zugdidi-attack-red'}), + presets.missions.supply.convoy:extend({name='zugdidi-supply-red'}) + } + }), + presets.upgrades.supply.hangar:extend({ + name='zugdidi-hangar-red-1', + products = { + presets.missions.supply.helo:extend({name='zugdidi-supply-red-1'}) + } + }), + presets.upgrades.supply.hangar:extend({ + name='zugdidi-hangar-red-2', + products = { + presets.missions.supply.helo:extend({name='zugdidi-supply-red-2'}) + } + }), + presets.upgrades.supply.hangar:extend({ + name='zugdidi-hangar-red-3', + products = { + presets.missions.supply.helo:extend({name='zugdidi-supply-red-3'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='zugdidi-comcenter-red', + products = { + presets.defenses.red.sa6:extend({ name='zugdidi-sam-red'}) + } + }) + }, + [2] = + { + presets.upgrades.basic.tent:extend({ + name='zugdidi-compost-blue', + products = { + presets.missions.supply.transfer:extend({name='zugdidi-transfer-blue'}), + presets.special.blue.infantry:extend({ name='zugdidi-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='zugdidi-garrison-blue'}), + presets.missions.attack.surface:extend({name='zugdidi-attack-blue'}), + presets.missions.supply.convoy:extend({name='zugdidi-supply-blue'}) + } + }), + presets.upgrades.supply.hangar:extend({ + name='zugdidi-hangar-blue-1', + products = { + presets.missions.supply.helo:extend({name='zugdidi-supply-blue-1'}) + } + }), + presets.upgrades.supply.hangar:extend({ + name='zugdidi-hangar-blue-2', + products = { + presets.missions.supply.helo:extend({name='zugdidi-supply-blue-2'}) + } + }), + presets.upgrades.supply.hangar:extend({ + name='zugdidi-hangar-blue-3', + products = { + presets.missions.supply.helo:extend({name='zugdidi-supply-blue-3'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='zugdidi-comcenter-blue', + products = { + presets.defenses.blue.shorad:extend({ name='zugdidi-sam-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Zugdidi.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Babugent.lua ]]----------------- + +zones.babugent = ZoneCommand:new("Babugent") +zones.babugent.initialState = { side=1 } +zones.babugent:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='babugent-tent-red', + products = { + presets.special.red.infantry:extend({ name='babugent-defense-red'}), + presets.defenses.red.infantry:extend({ name='babugent-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='babugent-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='babugent-supply-red'}), + presets.missions.supply.helo:extend({name='babugent-supply-red-2'}), + presets.missions.supply.transfer:extend({name='babugent-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='babugent-ammo-red', + products = { + presets.missions.attack.surface:extend({name='babugent-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='babugent-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='babugent-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='babugent-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='babugent-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='babugent-supply-blue'}), + presets.missions.supply.helo:extend({name='babugent-supply-blue-2'}), + presets.missions.supply.transfer:extend({name='babugent-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='babugent-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='babugent-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Babugent.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Kislovodsk.lua ]]----------------- + +zones.kislovodsk = ZoneCommand:new("Kislovodsk") +zones.kislovodsk.initialState = { side=1 } +zones.kislovodsk:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='kislovodsk-tent-red', + products = { + presets.special.red.infantry:extend({ name='kislovodsk-defense-red'}), + presets.defenses.red.infantry:extend({ name='kislovodsk-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='kislovodsk-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='kislovodsk-supply-red'}), + presets.missions.supply.transfer:extend({name='kislovodsk-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='kislovodsk-ammo-red', + products = { + presets.missions.attack.surface:extend({name='kislovodsk-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='kislovodsk-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='kislovodsk-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='kislovodsk-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='kislovodsk-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='kislovodsk-supply-blue'}), + presets.missions.supply.transfer:extend({name='kislovodsk-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='kislovodsk-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='kislovodsk-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Kislovodsk.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Gudauta.lua ]]----------------- + +zones.gudauta = ZoneCommand:new("Gudauta") +zones.gudauta.initialState = { side=1 } +zones.gudauta.keepActive = true +zones.gudauta.maxResource = 50000 +zones.gudauta:defineUpgrades({ + [1] = + { + presets.upgrades.basic.comPost:extend({ + name='gudauta-compost-red', + products = { + presets.special.red.infantry:extend({ name='gudauta-defense-red'}), + presets.defenses.red.infantry:extend({ name='gudauta-garrison-red'}) + } + }), + presets.upgrades.supply.fuelTank:extend({ + name='gudauta-fuel-red', + products = { + presets.missions.supply.helo:extend({name='gudauta-supply-red'}), + presets.missions.supply.helo:extend({name='gudauta-supply-red-1'}), + presets.missions.supply.transfer:extend({name='gudauta-transfer-red'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='gudauta-comcenter-red', + products = { + presets.defenses.red.sam:extend({ name='gudauta-airdef-red'}), + presets.missions.attack.sead:extend({name='gudauta-sead-red', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.attack.sead:extend({name='gudauta-sead-red-1', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.attack.cas:extend({name='gudauta-cas-red', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.bai:extend({name='gudauta-cas-red', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.patrol.aircraft:extend({name='gudauta-patrol-red', altitude=25000, range=25}) + } + }) + }, + [2] = + { + presets.upgrades.basic.comPost:extend({ + name='gudauta-compost-blue', + products = { + presets.special.blue.infantry:extend({ name='gudauta-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='gudauta-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelTank:extend({ + name='gudauta-fuel-blue', + products = { + presets.missions.supply.helo:extend({name='gudauta-supply-blue'}), + presets.missions.supply.helo:extend({name='gudauta-supply-blue-1'}), + presets.missions.supply.transfer:extend({name='gudauta-transfer-blue'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='gudauta-comcenter-blue', + products = { + presets.defenses.blue.sam:extend({ name='gudauta-airdef-blue'}), + presets.missions.attack.sead:extend({name='gudauta-sead-blue', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.attack.sead:extend({name='gudauta-sead-blue-1', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.attack.cas:extend({name='gudauta-cas-blue', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.bai:extend({name='gudauta-cas-blue', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.patrol.aircraft:extend({name='gudauta-patrol-blue', altitude=25000, range=25}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Gudauta.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Distillery.lua ]]----------------- + +zones.distillery = ZoneCommand:new("Distillery") +zones.distillery.initialState = { side=1 } +zones.distillery:defineUpgrades({ + [1] = + { + presets.upgrades.basic.tent:extend({ + name='distillery-tent-red', + products = { + presets.special.red.infantry:extend({ name='distillery-defense-red'}), + presets.defenses.red.infantry:extend({ name='distillery-garrison-red'}) + } + }), + presets.upgrades.supply.factory1:extend({ + name='distillery-prod-red-1', + products = { + presets.missions.supply.convoy:extend({ name='distillery-supply-red-1'}), + presets.missions.supply.transfer:extend({name='distillery-transfer-red'}) + } + }), + presets.upgrades.supply.factory2:extend({ + name='distillery-prod-red-2', + products = { + presets.missions.supply.convoy:extend({ name='distillery-supply-red-2', cost=2000}), + presets.missions.supply.transfer:extend({name='distillery-transfer-red2'}) + } + }), + presets.upgrades.supply.factoryTank:extend({ + name='distillery-tank-red-1', + products = { + } + }), + presets.upgrades.supply.factoryTank:extend({ + name='distillery-tank-red-2', + products = { + } + }), + presets.upgrades.supply.factoryTank:extend({ + name='distillery-tank-red-3', + products = { + } + }) + }, + [2] = + { + presets.upgrades.basic.tent:extend({ + name='distillery-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='distillery-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='distillery-garrison-blue'}) + } + }), + presets.upgrades.supply.factory1:extend({ + name='distillery-prod-blue-1', + products = { + presets.missions.supply.convoy:extend({ name='distillery-supply-blue-1'}), + presets.missions.supply.transfer:extend({name='distillery-transfer-blue'}) + } + }), + presets.upgrades.supply.factory2:extend({ + name='distillery-prod-blue-2', + products = { + presets.missions.supply.convoy:extend({ name='distillery-supply-blue-2', cost=2000}), + presets.missions.supply.transfer:extend({name='distillery-transfer-blue2'}) + } + }), + presets.upgrades.supply.factoryTank:extend({ + name='distillery-tank-blue-1', + products = { + } + }), + presets.upgrades.supply.factoryTank:extend({ + name='distillery-tank-blue-2', + products = { + } + }), + presets.upgrades.supply.factoryTank:extend({ + name='distillery-tank-blue-3', + products = { + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Distillery.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Sochi.lua ]]----------------- + +zones.sochi = ZoneCommand:new("Sochi") +zones.sochi.initialState = { side=1 } +zones.sochi.keepActive = true +zones.sochi.maxResource = 50000 +zones.sochi:defineUpgrades({ + [1] = + { + presets.upgrades.basic.comPost:extend({ + name='sochi-compost-red', + products = { + presets.special.red.infantry:extend({ name='sochi-defense-red'}), + presets.defenses.red.infantry:extend({ name='sochi-garrison-red'}) + } + }), + presets.upgrades.supply.fuelTank:extend({ + name='sochi-fuel-red', + products = { + presets.missions.supply.helo:extend({name='sochi-supply-red-1'}), + presets.missions.supply.helo:extend({name='sochi-supply-red-2'}), + presets.missions.supply.convoy_escorted:extend({name='sochi-supply-red-3'}), + presets.missions.supply.transfer:extend({name='sochi-transfer-red'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='sochi-comcenter-red', + products = { + presets.defenses.red.sa10:extend({ name='sochi-airdef-red'}), + presets.missions.attack.sead:extend({name='sochi-sead-red', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.attack.strike:extend({name='sochi-strike-red', altitude=30000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.patrol.aircraft:extend({name='sochi-patrol-red', altitude=25000, range=25}), + presets.missions.patrol.aircraft:extend({name='sochi-patrol-red-1', altitude=25000, range=25}), + presets.missions.attack.cas:extend({name='sochi-cas-red', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.bai:extend({name='sochi-cas-red', altitude=10000, expend=AI.Task.WeaponExpend.ONE}) + } + }) + }, + [2] = + { + presets.upgrades.basic.comPost:extend({ + name='sochi-compost-blue', + products = { + presets.special.blue.infantry:extend({ name='sochi-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='sochi-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelTank:extend({ + name='sochi-fuel-blue', + products = { + presets.missions.supply.helo:extend({name='sochi-supply-blue-1'}), + presets.missions.supply.helo:extend({name='sochi-supply-blue-2'}), + presets.missions.supply.convoy_escorted:extend({name='sochi-supply-blue-3'}), + presets.missions.supply.transfer:extend({name='sochi-transfer-blue'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='sochi-comcenter-blue', + products = { + presets.defenses.blue.patriot:extend({ name='sochi-airdef-blue'}), + presets.missions.attack.sead:extend({name='sochi-sead-blue', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.attack.strike:extend({name='sochi-strike-blue', altitude=30000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.patrol.aircraft:extend({name='sochi-patrol-blue', altitude=25000, range=25}), + presets.missions.attack.cas:extend({name='sochi-cas-blue', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.bai:extend({name='sochi-cas-blue', altitude=10000, expend=AI.Task.WeaponExpend.ONE}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Sochi.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Golf.lua ]]----------------- + +zones.golf = ZoneCommand:new("Golf") +zones.golf.initialState = nil +zones.golf.isHeloSpawn = true +zones.golf:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='golf-tent-red', + products = { + presets.special.red.infantry:extend({ name='golf-defense-red'}), + presets.defenses.red.infantry:extend({ name='golf-garrison-red'}) + } + }), + presets.upgrades.supply.fuelTank:extend({ + name='golf-fuel-red', + products = { + presets.missions.supply.helo:extend({name='golf-supply-red'}), + presets.missions.supply.helo:extend({name='golf-supply-red-1'}), + presets.missions.supply.transfer:extend({name='golf-transfer-red'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='golf-comcenter-red', + products = { + presets.defenses.red.shorad:extend({name='golf-sam-red'}), + presets.missions.attack.helo:extend({name='golf-cas-red', altitude=200, expend=AI.Task.WeaponExpend.HALF }) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='golf-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='golf-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='golf-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelTankFarp:extend({ + name='golf-fuel-blue', + products = { + presets.missions.supply.helo:extend({name='golf-supply-blue'}), + presets.missions.supply.helo:extend({name='golf-supply-blue-1'}), + presets.missions.supply.transfer:extend({name='golf-transfer-blue'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='golf-comcenter-blue', + products = { + presets.defenses.blue.shorad:extend({name='golf-sam-blue'}), + presets.missions.attack.helo:extend({name='golf-cas-blue', altitude=200, expend=AI.Task.WeaponExpend.HALF }) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Golf.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Charlie.lua ]]----------------- + +zones.charlie = ZoneCommand:new("Charlie") +zones.charlie.initialState = { side=2 } +zones.charlie.keepActive = true +zones.charlie:defineUpgrades({ + [1] = + { + presets.upgrades.basic.tent:extend({ + name='charlie-tent-red', + products = { + presets.special.red.infantry:extend({ name='charlie-defense-red'}), + presets.defenses.red.infantry:extend({ name='charlie-garrison-red'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='charlie-comcenter-red', + products = { + presets.defenses.red.sam:extend({ name='charlie-sam-red'}) + } + }) + }, + [2] = + { + presets.upgrades.basic.tent:extend({ + name='charlie-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='charlie-defense-red'}), + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='charlie-comcenter-blue', + products = { + presets.defenses.blue.sam:extend({ name='charlie-sam-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Charlie.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Lentehi.lua ]]----------------- + +zones.lentehi = ZoneCommand:new("Lentehi") +zones.lentehi.initialState = { side=1 } +zones.lentehi:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='lentehi-tent-red', + products = { + presets.special.red.infantry:extend({ name='lentehi-defense-red'}), + presets.defenses.red.infantry:extend({ name='lentehi-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='lentehi-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='lentehi-supply-red'}), + presets.missions.supply.helo:extend({name='lentehi-supply-red-2'}), + presets.missions.supply.transfer:extend({name='lentehi-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='lentehi-ammo-red', + products = { + presets.missions.attack.surface:extend({name='lentehi-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='lentehi-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='lentehi-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='lentehi-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='lentehi-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='lentehi-supply-blue'}), + presets.missions.supply.helo:extend({name='lentehi-supply-blue-2'}), + presets.missions.supply.transfer:extend({name='lentehi-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='lentehi-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='lentehi-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Lentehi.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Refinery.lua ]]----------------- + +zones.refinery = ZoneCommand:new("Refinery") +zones.refinery.initialState = { side=1 } +zones.refinery:defineUpgrades({ + [1] = + { + presets.upgrades.basic.tent:extend({ + name='refinery-tent-red', + products = { + presets.special.red.infantry:extend({ name='refinery-defense-red'}), + presets.defenses.red.infantry:extend({ name='refinery-garrison-red'}) + } + }), + presets.upgrades.supply.refinery1:extend({ + name='refinery-building-red', + products = { + presets.missions.supply.convoy:extend({ name='refinery-supply-red'}), + presets.missions.supply.convoy:extend({ name='refinery-supply-red-1'}), + presets.missions.supply.helo:extend({ name='refinery-supply-red-2'}), + presets.missions.supply.transfer:extend({name='refinery-transfer-red'}) + } + }) + }, + [2] = + { + presets.upgrades.basic.tent:extend({ + name='refinery-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='refinery-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='refinery-garrison-blue'}) + } + }), + presets.upgrades.supply.refinery1:extend({ + name='refinery-building-blue', + products = { + presets.missions.supply.convoy:extend({ name='refinery-supply-blue'}), + presets.missions.supply.convoy:extend({ name='refinery-supply-blue-1'}), + presets.missions.supply.helo:extend({ name='refinery-supply-blue-2'}), + presets.missions.supply.transfer:extend({name='refinery-transfer-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Refinery.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Mozdok.lua ]]----------------- + +zones.mozdok = ZoneCommand:new("Mozdok") +zones.mozdok.initialState = { side=1 } +zones.mozdok.keepActive = true +zones.mozdok.maxResource = 50000 +zones.mozdok:defineUpgrades({ + [1] = + { + presets.upgrades.basic.comPost:extend({ + name='mozdok-compost-red', + products = { + presets.special.red.infantry:extend({ name='mozdok-defense-red'}), + presets.defenses.red.infantry:extend({ name='mozdok-garrison-red'}) + } + }), + presets.upgrades.supply.fuelTank:extend({ + name='mozdok-fuel-red', + products = { + presets.missions.supply.helo:extend({name='mozdok-supply-red-1'}), + presets.missions.supply.helo:extend({name='mozdok-supply-red-2'}), + presets.missions.supply.convoy_escorted:extend({name='mozdok-supply-red-3'}), + presets.missions.supply.transfer:extend({name='mozdok-transfer-red'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='mozdok-comcenter-red', + products = { + presets.defenses.red.sa10:extend({ name='mozdok-airdef-red'}), + presets.missions.patrol.aircraft:extend({name='mozdok-patrol-red', altitude=25000, range=25}), + presets.missions.attack.cas:extend({name='mozdok-cas-red', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.bai:extend({name='mozdok-cas-red', altitude=10000, expend=AI.Task.WeaponExpend.ONE}) + } + }) + }, + [2] = + { + presets.upgrades.basic.comPost:extend({ + name='mozdok-compost-blue', + products = { + presets.special.blue.infantry:extend({ name='mozdok-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='mozdok-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelTank:extend({ + name='mozdok-fuel-blue', + products = { + presets.missions.supply.helo:extend({name='mozdok-supply-blue-1'}), + presets.missions.supply.helo:extend({name='mozdok-supply-blue-2'}), + presets.missions.supply.convoy_escorted:extend({name='mozdok-supply-blue-3'}), + presets.missions.supply.transfer:extend({name='mozdok-transfer-blue'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='mozdok-comcenter-blue', + products = { + presets.defenses.blue.shorad:extend({ name='mozdok-airdef-blue'}), + presets.missions.patrol.aircraft:extend({name='mozdok-patrol-blue', altitude=25000, range=25}), + presets.missions.attack.cas:extend({name='mozdok-cas-blue', altitude=15000, cost=50, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.cas:extend({name='mozdok-cas-blue-1', altitude=15000, cost=50, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.bai:extend({name='mozdok-cas-blue', altitude=15000, cost=50, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.bai:extend({name='mozdok-cas-blue-1', altitude=15000, cost=50, expend=AI.Task.WeaponExpend.ONE}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Mozdok.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Lima.lua ]]----------------- + +zones.lima = ZoneCommand:new("Lima") +zones.lima.initialState = { side=1 } +zones.lima:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='lima-tent-red', + products = { + presets.special.red.infantry:extend({ name='lima-defense-red'}), + presets.defenses.red.infantry:extend({ name='lima-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='lima-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='lima-supply-red'}), + presets.missions.supply.helo:extend({name='lima-supply-red-1'}), + presets.missions.supply.transfer:extend({name='lima-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='lima-ammo-red', + products = { + presets.missions.attack.surface:extend({name='lima-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='lima-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='lima-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='lima-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='lima-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='lima-supply-blue'}), + presets.missions.supply.helo:extend({name='lima-supply-blue-1'}), + presets.missions.supply.transfer:extend({name='lima-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='lima-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='lima-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Lima.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Oscar.lua ]]----------------- + +zones.oscar = ZoneCommand:new("Oscar") +zones.oscar.initialState = { side=1 } +zones.oscar:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='oscar-tent-red', + products = { + presets.special.red.infantry:extend({ name='oscar-defense-red'}), + presets.defenses.red.infantry:extend({ name='oscar-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='oscar-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='oscar-supply-red'}), + presets.missions.supply.transfer:extend({name='oscar-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='oscar-ammo-red', + products = { + presets.missions.attack.surface:extend({name='oscar-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='oscar-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='oscar-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='oscar-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='oscar-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='oscar-supply-blue'}), + presets.missions.supply.transfer:extend({name='oscar-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='oscar-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='oscar-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Oscar.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Nalchik.lua ]]----------------- + +zones.nalchik = ZoneCommand:new("Nalchik") +zones.nalchik.initialState = { side=1 } +zones.nalchik.keepActive = true +zones.nalchik.isHeloSpawn = true +zones.nalchik.isPlaneSpawn = true +zones.nalchik:defineUpgrades({ + [1] = + { + presets.upgrades.basic.comPost:extend({ + name='nalchik-compost-red', + products = { + presets.special.red.infantry:extend({ name='nalchik-defense-red'}), + presets.defenses.red.infantry:extend({ name='nalchik-garrison-red'}) + } + }), + presets.upgrades.supply.fuelTank:extend({ + name='nalchik-fuel-red', + products = { + presets.missions.supply.helo:extend({name='nalchik-supply-red-1'}), + presets.missions.supply.helo:extend({name='nalchik-supply-red-2'}), + presets.missions.supply.transfer:extend({name='nalchik-transfer-red'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='nalchik-comcenter-red', + products = { + presets.defenses.red.sa3:extend({ name='nalchik-airdef-red'}), + presets.missions.attack.sead:extend({name='nalchik-sead-red', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.attack.cas:extend({name='nalchik-cas-red', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.bai:extend({name='nalchik-cas-red', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.strike:extend({name='nalchik-strike-red', altitude=30000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.patrol.aircraft:extend({name='nalchik-patrol-red', altitude=25000, range=25}), + presets.missions.patrol.aircraft:extend({name='nalchik-patrol-red-2', altitude=25000, range=25}), + presets.missions.support.awacs:extend({name='nalchik-awacs-red', altitude=30000, freq=251.2}), + presets.missions.support.tanker:extend({name='nalchik-tanker-red', altitude=30000, freq=252.2, tacan='40', variant='Drogue'}) + } + }) + }, + [2] = + { + presets.upgrades.basic.comPost:extend({ + name='nalchik-compost-blue', + products = { + presets.special.blue.infantry:extend({ name='nalchik-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='nalchik-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelTank:extend({ + name='nalchik-fuel-blue', + products = { + presets.missions.supply.helo:extend({name='nalchik-supply-blue-1'}), + presets.missions.supply.helo:extend({name='nalchik-supply-blue-2'}), + presets.missions.supply.transfer:extend({name='nalchik-transfer-blue'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='nalchik-comcenter-blue', + products = { + presets.defenses.blue.nasams:extend({ name='nalchik-airdef-blue'}), + presets.missions.support.awacs:extend({name='nalchik-awacs-blue', altitude=30000, freq=259.5}), + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Nalchik.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Digora.lua ]]----------------- + +zones.digora = ZoneCommand:new("Digora") +zones.digora.initialState = { side=1 } +zones.digora:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='digora-tent-red', + products = { + presets.special.red.infantry:extend({ name='digora-defense-red'}), + presets.defenses.red.infantry:extend({ name='digora-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='digora-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='digora-supply-red'}), + presets.missions.supply.transfer:extend({name='digora-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='digora-ammo-red', + products = { + presets.missions.attack.surface:extend({name='digora-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='digora-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='digora-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='digora-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='digora-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='digora-supply-blue'}), + presets.missions.supply.transfer:extend({name='digora-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='digora-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='digora-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Digora.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Uniform.lua ]]----------------- + +zones.uniform = ZoneCommand:new("Uniform") +zones.uniform.initialState = { side=1 } +zones.uniform:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='uniform-tent-red', + products = { + presets.special.red.infantry:extend({ name='uniform-defense-red'}), + presets.defenses.red.infantry:extend({ name='uniform-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='uniform-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='uniform-supply-red'}), + presets.missions.supply.transfer:extend({name='uniform-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='uniform-ammo-red', + products = { + presets.missions.attack.surface:extend({name='uniform-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='uniform-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='uniform-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='uniform-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='uniform-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='uniform-supply-blue'}), + presets.missions.supply.transfer:extend({name='uniform-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='uniform-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='uniform-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Uniform.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Factory.lua ]]----------------- + +zones.factory = ZoneCommand:new("Factory") +zones.factory.initialState = { side=2 } +zones.factory:defineUpgrades({ + [1] = + { + presets.upgrades.basic.tent:extend({ + name='factory-tent-red', + products = { + presets.special.red.infantry:extend({ name='factory-defense-red'}), + presets.defenses.red.infantry:extend({ name='factory-garrison-red'}) + } + }), + presets.upgrades.supply.factory1:extend({ + name='factory-prod-red-1', + products = { + presets.missions.supply.convoy:extend({ name='factory-supply-red-1'}), + presets.missions.supply.transfer:extend({name='factory-transfer-red'}) + } + }), + presets.upgrades.supply.factory2:extend({ + name='factory-prod-red-2', + products = { + presets.missions.supply.convoy:extend({ name='factory-supply-red-2', cost=2000}), + presets.missions.supply.transfer:extend({name='factory-transfer-red2'}) + } + }), + presets.upgrades.supply.factoryTank:extend({ + name='factory-tank-red-1', + products = { + } + }), + presets.upgrades.supply.factoryTank:extend({ + name='factory-tank-red-2', + products = { + } + }), + presets.upgrades.supply.factoryTank:extend({ + name='factory-tank-red-3', + products = { + } + }) + }, + [2] = + { + presets.upgrades.basic.tent:extend({ + name='factory-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='factory-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='factory-garrison-blue'}) + } + }), + presets.upgrades.supply.factory1:extend({ + name='factory-prod-blue-1', + products = { + presets.missions.supply.convoy:extend({ name='factory-supply-blue-1'}), + presets.missions.supply.transfer:extend({name='factory-transfer-blue'}) + } + }), + presets.upgrades.supply.factory2:extend({ + name='factory-prod-blue-2', + products = { + presets.missions.supply.convoy:extend({ name='factory-supply-blue-2', cost=2000}), + presets.missions.supply.transfer:extend({name='factory-transfer-blue2'}) + } + }), + presets.upgrades.supply.factoryTank:extend({ + name='factory-tank-blue-1', + products = { + } + }), + presets.upgrades.supply.factoryTank:extend({ + name='factory-tank-blue-2', + products = { + } + }), + presets.upgrades.supply.factoryTank:extend({ + name='factory-tank-blue-3', + products = { + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Factory.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Senaki.lua ]]----------------- + +zones.senaki = ZoneCommand:new("Senaki") +zones.senaki.initialState = { side=1 } +zones.senaki.keepActive = true +zones.senaki.isHeloSpawn = true +zones.senaki.isPlaneSpawn = true +zones.senaki.maxResource = 50000 +zones.senaki:defineUpgrades({ + [1] = + { + presets.upgrades.basic.comPost:extend({ + name='senaki-compost-red', + products = { + presets.special.red.infantry:extend({ name='senaki-defense-red'}), + presets.defenses.red.infantry:extend({ name='senaki-garrison-red'}) + } + }), + presets.upgrades.supply.fuelTank:extend({ + name='senaki-fuel-red', + products = { + presets.missions.supply.helo:extend({name='senaki-supply-red-1'}), + presets.missions.supply.helo:extend({name='senaki-supply-red-2'}), + presets.missions.supply.transfer:extend({name='senaki-transfer-red'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='senaki-comcenter-red', + products = { + presets.defenses.red.sa3:extend({ name='senaki-airdef-red'}), + presets.missions.attack.sead:extend({name='senaki-sead-red', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.attack.cas:extend({name='senaki-cas-red', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.bai:extend({name='senaki-cas-red', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.strike:extend({name='senaki-strike-red', altitude=20000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.patrol.aircraft:extend({name='senaki-patrol-red', altitude=25000, range=25}), + presets.missions.patrol.aircraft:extend({name='senaki-patrol-red-2', altitude=20000, range=25}) + } + }) + }, + [2] = + { + presets.upgrades.basic.comPost:extend({ + name='senaki-compost-blue', + products = { + presets.special.blue.infantry:extend({ name='senaki-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='senaki-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelTank:extend({ + name='senaki-fuel-blue', + products = { + presets.missions.supply.helo:extend({name='senaki-supply-blue-1'}), + presets.missions.supply.helo:extend({name='senaki-supply-blue-2'}), + presets.missions.supply.transfer:extend({name='senaki-transfer-blue'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='senaki-comcenter-blue', + products = { + presets.defenses.blue.shorad:extend({ name='senaki-airdef-blue'}), + presets.missions.attack.sead:extend({name='senaki-sead-blue', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.attack.cas:extend({name='senaki-cas-blue', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.bai:extend({name='senaki-cas-blue', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.strike:extend({name='senaki-strike-blue', altitude=20000, expend=AI.Task.WeaponExpend.TWO}), + presets.missions.patrol.aircraft:extend({name='senaki-patrol-blue', altitude=25000, range=25}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Senaki.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Kutaisi.lua ]]----------------- + +zones.kutaisi = ZoneCommand:new("Kutaisi") +zones.kutaisi.initialState = { side=1 } +zones.kutaisi.keepActive = true +zones.kutaisi.maxResource = 50000 +zones.kutaisi:defineUpgrades({ + [1] = + { + presets.upgrades.basic.comPost:extend({ + name='kutaisi-compost-red', + products = { + presets.special.red.infantry:extend({ name='kutaisi-defense-red'}), + presets.defenses.red.infantry:extend({ name='kutaisi-garrison-red'}) + } + }), + presets.upgrades.supply.fuelTank:extend({ + name='kutaisi-fuel-red', + products = { + presets.missions.supply.helo:extend({name='kutaisi-supply-red-1'}), + presets.missions.supply.helo:extend({name='kutaisi-supply-red-2'}), + presets.missions.supply.transfer:extend({name='kutaisi-transfer-red'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='kutaisi-comcenter-red', + products = { + presets.defenses.red.shorad:extend({ name='kutaisi-airdef-red'}), + presets.missions.attack.sead:extend({name='kutaisi-sead-red', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.attack.cas:extend({name='kutaisi-cas-red', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.bai:extend({name='kutaisi-cas-red', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.strike:extend({name='kutaisi-strike-red', altitude=20000, expend=AI.Task.WeaponExpend.HALF}), + presets.missions.patrol.aircraft:extend({name='kutaisi-patrol-red', altitude=25000, range=25}), + presets.missions.patrol.aircraft:extend({name='kutaisi-patrol-red-2', altitude=25000, range=25}) + } + }) + }, + [2] = + { + presets.upgrades.basic.comPost:extend({ + name='kutaisi-compost-blue', + products = { + presets.special.blue.infantry:extend({ name='kutaisi-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='kutaisi-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelTank:extend({ + name='kutaisi-fuel-blue', + products = { + presets.missions.supply.helo:extend({name='kutaisi-supply-blue-1'}), + presets.missions.supply.helo:extend({name='kutaisi-supply-blue-2'}), + presets.missions.supply.transfer:extend({name='kutaisi-transfer-blue'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='kutaisi-comcenter-blue', + products = { + presets.defenses.blue.shorad:extend({ name='kutaisi-airdef-blue'}), + presets.missions.attack.sead:extend({name='kutaisi-sead-blue', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.attack.cas:extend({name='kutaisi-cas-blue', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.bai:extend({name='kutaisi-cas-blue', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.strike:extend({name='kutaisi-strike-blue', altitude=20000, expend=AI.Task.WeaponExpend.TWO}), + presets.missions.patrol.aircraft:extend({name='kutaisi-patrol-blue', altitude=25000, range=25}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Kutaisi.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Prohladniy.lua ]]----------------- + +zones.prohladniy = ZoneCommand:new("Prohladniy") +zones.prohladniy.initialState = { side=1 } +zones.prohladniy:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='prohladniy-tent-red', + products = { + presets.special.red.infantry:extend({ name='prohladniy-defense-red'}), + presets.defenses.red.infantry:extend({ name='prohladniy-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='prohladniy-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='prohladniy-supply-red'}), + presets.missions.supply.transfer:extend({name='prohladniy-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='prohladniy-ammo-red', + products = { + presets.missions.attack.surface:extend({name='prohladniy-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='prohladniy-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='prohladniy-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='prohladniy-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='prohladniy-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='prohladniy-supply-blue'}), + presets.missions.supply.transfer:extend({name='prohladniy-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='prohladniy-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='prohladniy-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Prohladniy.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Tallyk.lua ]]----------------- + +zones.tallyk = ZoneCommand:new("Tallyk") +zones.tallyk.initialState = { side=1 } +zones.tallyk.keepActive = true +zones.tallyk:defineUpgrades({ + [1] = + { + presets.upgrades.basic.tent:extend({ + name='tallyk-tent-red', + products = { + presets.special.red.infantry:extend({ name='tallyk-defense-red'}), + presets.defenses.red.infantry:extend({ name='tallyk-garrison-red'}), + presets.missions.attack.surface:extend({name='tallyk-assault-red'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='tallyk-comcenter-red', + products = { + presets.defenses.red.sam:extend({ name='tallyk-sam-red'}) + } + }) + }, + [2] = + { + presets.upgrades.basic.tent:extend({ + name='tallyk-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='tallyk-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='tallyk-garrison-blue'}), + presets.missions.attack.surface:extend({name='tallyk-assault-blue'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='tallyk-comcenter-blue', + products = { + presets.defenses.blue.sam:extend({ name='tallyk-sam-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Tallyk.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Terek.lua ]]----------------- + +zones.terek = ZoneCommand:new("Terek") +zones.terek.initialState = { side=1 } +zones.terek:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='terek-tent-red', + products = { + presets.special.red.infantry:extend({ name='terek-defense-red'}), + presets.defenses.red.infantry:extend({ name='terek-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='terek-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='terek-supply-red'}), + presets.missions.supply.transfer:extend({name='terek-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='terek-ammo-red', + products = { + presets.missions.attack.surface:extend({name='terek-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='terek-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='terek-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='terek-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='terek-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='terek-supply-blue'}), + presets.missions.supply.transfer:extend({name='terek-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='terek-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='terek-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Terek.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Humara.lua ]]----------------- + +zones.humara = ZoneCommand:new("Humara") +zones.humara.initialState = { side=1 } +zones.humara:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='humara-tent-red', + products = { + presets.special.red.infantry:extend({ name='humara-defense-red'}), + presets.defenses.red.infantry:extend({ name='humara-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='humara-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='humara-supply-red'}), + presets.missions.supply.transfer:extend({name='humara-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='humara-ammo-red', + products = { + presets.missions.attack.surface:extend({name='humara-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='humara-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='humara-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='humara-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='humara-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='humara-supply-blue'}), + presets.missions.supply.transfer:extend({name='humara-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='humara-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='humara-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Humara.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Ochamchira.lua ]]----------------- + +zones.ochamchira = ZoneCommand:new("Ochamchira") +zones.ochamchira.initialState = { side=1 } +zones.ochamchira:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='ochamchira-tent-red', + products = { + presets.special.red.infantry:extend({ name='ochamchira-defense-red'}), + presets.defenses.red.infantry:extend({ name='ochamchira-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='ochamchira-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='ochamchira-supply-red'}), + presets.missions.supply.transfer:extend({name='ochamchira-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='ochamchira-ammo-red', + products = { + presets.missions.attack.surface:extend({name='ochamchira-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='ochamchira-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='ochamchira-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='ochamchira-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='ochamchira-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='ochamchira-supply-blue'}), + presets.missions.supply.transfer:extend({name='ochamchira-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='ochamchira-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='ochamchira-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Ochamchira.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/November.lua ]]----------------- + +zones.november = ZoneCommand:new("November") +zones.november.initialState = { side=1 } +zones.november.isHeloSpawn = true +zones.november:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='november-tent-red', + products = { + presets.special.red.infantry:extend({ name='november-defense-red'}), + presets.defenses.red.infantry:extend({ name='november-garrison-red'}) + } + }), + presets.upgrades.supply.fuelTankFarp:extend({ + name='november-fuel-red', + products = { + presets.missions.supply.helo:extend({name='november-supply-red'}), + presets.missions.supply.helo:extend({name='november-supply-red-1'}), + presets.missions.supply.transfer:extend({name='november-transfer-red'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='november-comcenter-red', + products = { + presets.defenses.red.shorad:extend({name='november-sam-red'}), + presets.missions.attack.helo:extend({name='november-cas-red', altitude=200, expend=AI.Task.WeaponExpend.HALF }) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='november-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='november-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='november-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelTankFarp:extend({ + name='november-fuel-blue', + products = { + presets.missions.supply.helo:extend({name='november-supply-blue'}), + presets.missions.supply.helo:extend({name='november-supply-blue-1'}), + presets.missions.supply.transfer:extend({name='november-transfer-blue'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='november-comcenter-blue', + products = { + presets.defenses.blue.shorad:extend({name='november-sam-blue'}), + presets.missions.attack.helo:extend({name='november-cas-blue', altitude=200, expend=AI.Task.WeaponExpend.HALF }) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/November.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/XRay.lua ]]----------------- + +zones.xray = ZoneCommand:new("XRay") +zones.xray.initialState = { side=1 } +zones.xray:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='xray-tent-red', + products = { + presets.special.red.infantry:extend({ name='xray-defense-red'}), + presets.defenses.red.infantry:extend({ name='xray-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='xray-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='xray-supply-red'}), + presets.missions.supply.helo:extend({name='xray-supply-red-2'}), + presets.missions.supply.transfer:extend({name='xray-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='xray-ammo-red', + products = { + presets.missions.attack.surface:extend({name='xray-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='xray-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='xray-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='xray-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='xray-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='xray-supply-blue'}), + presets.missions.supply.helo:extend({name='xray-supply-blue-2'}), + presets.missions.supply.transfer:extend({name='xray-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='xray-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='xray-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/XRay.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Whiskey.lua ]]----------------- + +zones.whiskey = ZoneCommand:new("Whiskey") +zones.whiskey.initialState = { side=1 } +zones.whiskey:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='whiskey-tent-red', + products = { + presets.special.red.infantry:extend({ name='whiskey-defense-red'}), + presets.defenses.red.infantry:extend({ name='whiskey-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='whiskey-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='whiskey-supply-red'}), + presets.missions.supply.transfer:extend({name='whiskey-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='whiskey-ammo-red', + products = { + presets.missions.attack.surface:extend({name='whiskey-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='whiskey-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='whiskey-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='whiskey-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='whiskey-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='whiskey-supply-blue'}), + presets.missions.supply.transfer:extend({name='whiskey-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='whiskey-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='whiskey-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Whiskey.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Mine.lua ]]----------------- + +zones.mine = ZoneCommand:new("Mine") +zones.mine.initialState = { side=1 } +zones.mine:defineUpgrades({ + [1] = + { + presets.upgrades.basic.tent:extend({ + name='mine-tent-red', + products = { + presets.special.red.infantry:extend({ name='mine-defense-red'}), + presets.defenses.red.infantry:extend({ name='mine-garrison-red'}) + } + }), + presets.upgrades.supply.excavator:extend({ + name='mine-excavator-red-1', + products = { + presets.missions.supply.convoy:extend({ name='mine-supply-red'}), + presets.missions.supply.transfer:extend({name='mine-transfer-red'}) + } + }), + presets.upgrades.supply.excavator:extend({ + name='mine-excavator-red-2', + products = { + presets.missions.supply.convoy:extend({ name='mine-supply-red'}), + presets.missions.supply.transfer:extend({name='mine-transfer-red'}) + } + }), + presets.upgrades.supply.excavator:extend({ + name='mine-excavator-red-3', + products = { + presets.missions.supply.convoy:extend({ name='mine-supply-red'}), + presets.missions.supply.transfer:extend({name='mine-transfer-red'}) + } + }) + }, + [2] = + { + presets.upgrades.basic.tent:extend({ + name='mine-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='mine-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='mine-garrison-blue'}) + } + }), + presets.upgrades.supply.excavator:extend({ + name='mine-excavator-blue-1', + products = { + presets.missions.supply.convoy:extend({ name='mine-supply-blue'}), + presets.missions.supply.transfer:extend({name='mine-transfer-blue'}) + } + }), + presets.upgrades.supply.excavator:extend({ + name='mine-excavator-blue-2', + products = { + presets.missions.supply.convoy:extend({ name='mine-supply-blue'}), + presets.missions.supply.transfer:extend({name='mine-transfer-blue'}) + } + }), + presets.upgrades.supply.excavator:extend({ + name='mine-excavator-blue-3', + products = { + presets.missions.supply.convoy:extend({ name='mine-supply-blue'}), + presets.missions.supply.transfer:extend({name='mine-transfer-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Mine.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Papa.lua ]]----------------- + +zones.papa = ZoneCommand:new("Papa") +zones.papa.initialState = { side=1 } +zones.papa.keepActive = true +zones.papa:defineUpgrades({ + [1] = + { + presets.upgrades.basic.tent:extend({ + name='papa-tent-red', + products = { + presets.special.red.infantry:extend({ name='papa-defense-red'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='papa-comcenter-red', + products = { + presets.defenses.red.sam:extend({ name='papa-sam-red'}) + } + }) + }, + [2] = + { + presets.upgrades.basic.tent:extend({ + name='papa-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='papa-defense-blue'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='papa-comcenter-blue', + products = { + presets.defenses.blue.sam:extend({ name='papa-sam-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Papa.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Sukhumi.lua ]]----------------- + +zones.sukhumi = ZoneCommand:new("Sukhumi") +zones.sukhumi.initialState = { side=1 } +zones.sukhumi.keepActive = true +zones.sukhumi.maxResource = 50000 +zones.sukhumi:defineUpgrades({ + [1] = + { + presets.upgrades.basic.comPost:extend({ + name='sukhumi-compost-red', + products = { + presets.special.red.infantry:extend({ name='sukhumi-defense-red'}), + presets.defenses.red.infantry:extend({ name='sukhumi-garrison-red'}) + } + }), + presets.upgrades.supply.fuelTank:extend({ + name='sukhumi-fuel-red', + products = { + presets.missions.supply.helo:extend({name='sukhumi-supply-red-1'}), + presets.missions.supply.helo:extend({name='sukhumi-supply-red-2'}), + presets.missions.supply.transfer:extend({name='sukhumi-transfer-red'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='sukhumi-comcenter-red', + products = { + presets.defenses.red.sa11:extend({ name='sukhumi-airdef-red'}), + presets.missions.attack.sead:extend({name='sukhumi-sead-red', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.attack.cas:extend({name='sukhumi-cas-red', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.bai:extend({name='sukhumi-cas-red', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.strike:extend({name='sukhumi-strike-red', altitude=20000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.patrol.aircraft:extend({name='sukhumi-patrol-red', altitude=25000, range=25}), + presets.missions.patrol.aircraft:extend({name='sukhumi-patrol-red-2', altitude=25000, range=25}) + } + }) + }, + [2] = + { + presets.upgrades.basic.comPost:extend({ + name='sukhumi-compost-blue', + products = { + presets.special.blue.infantry:extend({ name='sukhumi-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='sukhumi-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelTank:extend({ + name='sukhumi-fuel-blue', + products = { + presets.missions.supply.helo:extend({name='sukhumi-supply-blue-1'}), + presets.missions.supply.helo:extend({name='sukhumi-supply-blue-2'}), + presets.missions.supply.transfer:extend({name='sukhumi-transfer-blue'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='sukhumi-comcenter-blue', + products = { + presets.defenses.blue.nasams:extend({ name='sukhumi-airdef-blue'}), + presets.missions.attack.sead:extend({name='sukhumi-sead-blue', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.attack.cas:extend({name='sukhumi-cas-blue', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.bai:extend({name='sukhumi-cas-blue', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.strike:extend({name='sukhumi-strike-blue', altitude=20000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.patrol.aircraft:extend({name='sukhumi-patrol-blue', altitude=25000, range=25}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Sukhumi.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Farm.lua ]]----------------- + +zones.farm = ZoneCommand:new("Farm") +zones.farm.initialState = { side=1 } +zones.farm:defineUpgrades({ + [1] = + { + presets.upgrades.basic.tent:extend({ + name='farm-tent-red', + products = { + presets.special.red.infantry:extend({ name='farm-defense-red'}), + presets.defenses.red.infantry:extend({ name='farm-garrison-red'}) + } + }), + presets.upgrades.supply.farm1:extend({ + name='farm-prod-red-1', + products = { + presets.missions.supply.convoy:extend({ name='farm-supply-red'}), + presets.missions.supply.transfer:extend({name='farm-transfer-red'}) + } + }), + presets.upgrades.supply.farm2:extend({ + name='farm-prod-red-2', + products = { + presets.missions.supply.convoy:extend({ name='farm-supply-red'}), + presets.missions.supply.transfer:extend({name='farm-transfer-red'}) + } + }) + }, + [2] = + { + presets.upgrades.basic.tent:extend({ + name='farm-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='farm-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='farm-garrison-blue'}) + } + }), + presets.upgrades.supply.farm1:extend({ + name='farm-prod-blue-1', + products = { + presets.missions.supply.convoy:extend({ name='farm-supply-blue'}), + presets.missions.supply.transfer:extend({name='farm-transfer-blue'}) + } + }), + presets.upgrades.supply.farm2:extend({ + name='farm-prod-blue-2', + products = { + presets.missions.supply.convoy:extend({ name='farm-supply-blue'}), + presets.missions.supply.transfer:extend({name='farm-transfer-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Farm.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Romeo.lua ]]----------------- + +zones.romeo = ZoneCommand:new("Romeo") +zones.romeo.initialState = { side=1 } +zones.romeo:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='romeo-tent-red', + products = { + presets.special.red.infantry:extend({ name='romeo-defense-red'}), + presets.defenses.red.infantry:extend({ name='romeo-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='romeo-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='romeo-supply-red'}), + presets.missions.supply.transfer:extend({name='romeo-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='romeo-ammo-red', + products = { + presets.missions.attack.surface:extend({name='romeo-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='romeo-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='romeo-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='romeo-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='romeo-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='romeo-supply-blue'}), + presets.missions.supply.transfer:extend({name='romeo-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='romeo-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='romeo-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Romeo.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Zulu.lua ]]----------------- + +zones.zulu = ZoneCommand:new("Zulu") +zones.zulu.initialState = { side=1 } +zones.zulu:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='zulu-tent-red', + products = { + presets.special.red.infantry:extend({ name='zulu-defense-red'}), + presets.defenses.red.infantry:extend({ name='zulu-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='zulu-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='zulu-supply-red'}), + presets.missions.supply.transfer:extend({name='zulu-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='zulu-ammo-red', + products = { + presets.missions.attack.surface:extend({name='zulu-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='zulu-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='zulu-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='zulu-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='zulu-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='zulu-supply-blue'}), + presets.missions.supply.transfer:extend({name='zulu-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='zulu-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='zulu-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Zulu.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Yankee.lua ]]----------------- + +zones.yankee = ZoneCommand:new("Yankee") +zones.yankee.initialState = { side=1 } +zones.yankee:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='yankee-tent-red', + products = { + presets.special.red.infantry:extend({ name='yankee-defense-red'}), + presets.defenses.red.infantry:extend({ name='yankee-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='yankee-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='yankee-supply-red'}), + presets.missions.supply.transfer:extend({name='yankee-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='yankee-ammo-red', + products = { + presets.missions.attack.surface:extend({name='yankee-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='yankee-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='yankee-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='yankee-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='yankee-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='yankee-supply-blue'}), + presets.missions.supply.transfer:extend({name='yankee-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='yankee-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='yankee-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Yankee.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Malgobek.lua ]]----------------- + +zones.malgobek = ZoneCommand:new("Malgobek") +zones.malgobek.initialState = { side=1 } +zones.malgobek:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='malgobek-tent-red', + products = { + presets.special.red.infantry:extend({ name='malgobek-defense-red'}), + presets.defenses.red.infantry:extend({ name='malgobek-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='malgobek-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='malgobek-supply-red'}), + presets.missions.supply.transfer:extend({name='malgobek-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='malgobek-ammo-red', + products = { + presets.missions.attack.surface:extend({name='malgobek-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='malgobek-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='malgobek-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='malgobek-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='malgobek-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='malgobek-supply-blue'}), + presets.missions.supply.transfer:extend({name='malgobek-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='malgobek-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='malgobek-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Malgobek.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Kilo.lua ]]----------------- + +zones.kilo = ZoneCommand:new("Kilo") +zones.kilo.initialState = { side=1 } +zones.kilo:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='kilo-tent-red', + products = { + presets.special.red.infantry:extend({ name='kilo-defense-red'}), + presets.defenses.red.infantry:extend({ name='kilo-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='kilo-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='kilo-supply-red'}), + presets.missions.supply.transfer:extend({name='kilo-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='kilo-ammo-red', + products = { + presets.missions.attack.surface:extend({name='kilo-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='kilo-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='kilo-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='kilo-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='kilo-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='kilo-supply-blue'}), + presets.missions.supply.transfer:extend({name='kilo-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='kilo-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='kilo-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Kilo.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Quebec.lua ]]----------------- + +zones.quebec = ZoneCommand:new("Quebec") +zones.quebec.initialState = { side=1 } +zones.quebec.keepActive = true +zones.quebec:defineUpgrades({ + [1] = + { + presets.upgrades.basic.tent:extend({ + name='quebec-tent-red', + products = { + presets.special.red.infantry:extend({ name='quebec-defense-red'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='quebec-comcenter-red', + products = { + presets.defenses.red.sam:extend({ name='quebec-sam-red'}) + } + }) + }, + [2] = + { + presets.upgrades.basic.tent:extend({ + name='quebec-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='quebec-defense-blue'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='quebec-comcenter-blue', + products = { + presets.defenses.blue.sam:extend({ name='quebec-sam-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Quebec.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/OilFields.lua ]]----------------- + +zones.oilfields = ZoneCommand:new("Oil Fields") +zones.oilfields.initialState = { side=1 } +zones.oilfields:defineUpgrades({ + [1] = { + presets.upgrades.basic.outpost:extend({ + name='oilfields-outpost-red', + products = { + presets.special.red.infantry:extend({ name='oilfields-defense-red'}), + presets.defenses.red.infantry:extend({ name='oilfields-garrison-red'}) + } + }), + presets.upgrades.supply.oilPump:extend({ + name='oilfields-pump-red-1', + products = { + presets.missions.supply.transfer:extend({name='oilfields-transfer-red1'}) + } + }), + presets.upgrades.supply.oilPump:extend({ + name='oilfields-pump-red-2', + products = { + presets.missions.supply.convoy:extend({name='oilfields-supply-red-1'}) + } + }), + presets.upgrades.supply.oilPump:extend({ + name='oilfields-pump-red-3', + products = { + presets.missions.supply.transfer:extend({name='oilfields-transfer-red2'}) + } + }), + presets.upgrades.supply.oilPump:extend({ + name='oilfields-pump-red-4', + products = { + presets.missions.supply.convoy:extend({name='oilfields-supply-red-2'}) + } + }) + }, + [2] = { + presets.upgrades.basic.outpost:extend({ + name='oilfields-outpost-blue', + products = { + presets.special.blue.infantry:extend({ name='oilfields-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='oilfields-garrison-blue'}) + } + }), + presets.upgrades.supply.oilPump:extend({ + name='oilfields-pump-blue-1', + products = { + presets.missions.supply.transfer:extend({name='oilfields-transfer-blue1'}) + } + }), + presets.upgrades.supply.oilPump:extend({ + name='oilfields-pump-blue-2', + products = { + presets.missions.supply.convoy:extend({name='oilfields-supply-blue-1'}) + } + }), + presets.upgrades.supply.oilPump:extend({ + name='oilfields-pump-blue-3', + products = { + presets.missions.supply.transfer:extend({name='oilfields-transfer-blue2'}) + } + }), + presets.upgrades.supply.oilPump:extend({ + name='oilfields-pump-blue-4', + products = { + presets.missions.supply.convoy:extend({name='oilfields-supply-blue-2'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/OilFields.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Echo.lua ]]----------------- + +zones.echo = ZoneCommand:new("Echo") +zones.echo.initialState = { side=2 } +zones.echo:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='echo-tent-red', + products = { + presets.special.red.infantry:extend({ name='echo-defense-red'}), + presets.defenses.red.infantry:extend({ name='echo-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='echo-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='echo-supply-red'}), + presets.missions.supply.transfer:extend({name='echo-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='echo-ammo-red', + products = { + presets.missions.attack.surface:extend({name='echo-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='echo-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='echo-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='echo-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='echo-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='echo-supply-blue'}), + presets.missions.supply.transfer:extend({name='echo-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='echo-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='echo-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Echo.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Kobuleti.lua ]]----------------- + +zones.kobuleti = ZoneCommand:new("Kobuleti") +zones.kobuleti.initialState = { side=2 } +zones.kobuleti.keepActive = true +zones.kobuleti.isHeloSpawn = true +zones.kobuleti.isPlaneSpawn = true +zones.kobuleti.maxResource = 50000 +zones.kobuleti:defineUpgrades({ + [1] = + { + presets.upgrades.basic.comPost:extend({ + name='kobuleti-compost-red', + products = { + presets.special.red.infantry:extend({ name='kobuleti-defense-red'}), + presets.defenses.red.infantry:extend({ name='kobuleti-garrison-red'}), + presets.missions.attack.surface:extend({ name='kobuleti-assault-red'}) + } + }), + presets.upgrades.supply.fuelTank:extend({ + name='kobuleti-fuel-red', + products = { + presets.missions.supply.helo:extend({name='kobuleti-supply-red-1'}), + presets.missions.supply.helo:extend({name='kobuleti-supply-red-2'}), + presets.missions.supply.transfer:extend({name='kobuleti-transfer-red'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='kobuleti-comcenter-red', + products = { + presets.defenses.red.shorad:extend({ name='kobuleti-airdef-red'}), + presets.missions.attack.sead:extend({name='kobuleti-sead-red', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.attack.cas:extend({name='kobuleti-cas-red', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.bai:extend({name='kobuleti-cas-red', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.strike:extend({name='kobuleti-strike-red', altitude=20000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.patrol.aircraft:extend({name='kobuleti-patrol-red', altitude=25000, range=25}) + } + }) + }, + [2] = + { + presets.upgrades.basic.comPost:extend({ + name='kobuleti-compost-blue', + products = { + presets.special.blue.infantry:extend({ name='kobuleti-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='kobuleti-garrison-blue'}), + presets.missions.attack.surface:extend({ name='kobuleti-assault-blue'}) + } + }), + presets.upgrades.supply.fuelTank:extend({ + name='kobuleti-fuel-blue', + products = { + presets.missions.supply.helo:extend({name='kobuleti-supply-blue-1'}), + presets.missions.supply.helo:extend({name='kobuleti-supply-blue-2'}), + presets.missions.supply.transfer:extend({name='kobuleti-transfer-blue'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='kobuleti-comcenter-blue', + products = { + presets.defenses.blue.shorad:extend({ name='kobuleti-airdef-blue'}), + presets.missions.attack.sead:extend({name='kobuleti-sead-blue', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.attack.cas:extend({name='kobuleti-cas-blue', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.bai:extend({name='kobuleti-cas-blue', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.strike:extend({name='kobuleti-strike-blue', altitude=20000, expend=AI.Task.WeaponExpend.TWO}), + presets.missions.patrol.aircraft:extend({name='kobuleti-patrol-blue', altitude=25000, range=25}), + presets.missions.support.awacs:extend({name='kobuleti-awacs-blue', altitude=30000, freq=258.5}), + presets.missions.support.tanker:extend({name='kobuleti-tanker-blue', altitude=23000, freq=258, tacan='38', variant='Boom'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Kobuleti.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Alpha.lua ]]----------------- + +zones.alpha = ZoneCommand:new('Alpha') +zones.alpha.initialState = { side=2 } +zones.alpha:defineUpgrades({ + [1] = --red side + { + presets.upgrades.basic.tent:extend({ + name = 'alpha-tent-red', + products = { + presets.special.red.infantry:extend({ name='alpha-defense-red'}), + presets.defenses.red.infantry:extend({ name='alpha-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name = 'alpha-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({ name='alpha-supply-red'}), + presets.missions.supply.transfer:extend({name='alpha-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name = 'alpha-ammo-red', + products = { + presets.missions.attack.surface:extend({ name='alpha-assault-red'}) + } + }) + }, + [2] = --blue side + { + presets.upgrades.basic.tent:extend({ + name = 'alpha-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='alpha-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='alpha-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name = 'alpha-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({ name='alpha-supply-blue'}), + presets.missions.supply.transfer:extend({name='alpha-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name = 'alpha-ammo-blue', + products = { + presets.missions.attack.surface:extend({ name='alpha-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Alpha.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Foxtrot.lua ]]----------------- + +zones.foxtrot = ZoneCommand:new("Foxtrot") +zones.foxtrot.initialState = { side=2 } +zones.foxtrot:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='foxtrot-tent-red', + products = { + presets.special.red.infantry:extend({ name='foxtrot-defense-red'}), + presets.defenses.red.infantry:extend({ name='foxtrot-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='foxtrot-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='foxtrot-supply-red'}), + presets.missions.supply.transfer:extend({name='foxtrot-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='foxtrot-ammo-red', + products = { + presets.missions.attack.surface:extend({name='foxtrot-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='foxtrot-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='foxtrot-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='foxtrot-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='foxtrot-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='foxtrot-supply-blue'}), + presets.missions.supply.transfer:extend({name='foxtrot-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='foxtrot-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='foxtrot-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Foxtrot.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Sierra.lua ]]----------------- + +zones.sierra = ZoneCommand:new("Sierra") +zones.sierra.initialState = { side=1 } +zones.sierra.isHeloSpawn = true +zones.sierra:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='sierra-tent-red', + products = { + presets.special.red.infantry:extend({ name='sierra-defense-red'}), + presets.defenses.red.infantry:extend({ name='sierra-garrison-red'}) + } + }), + presets.upgrades.supply.fuelTankFarp:extend({ + name='sierra-fuel-red', + products = { + presets.missions.supply.helo:extend({name='sierra-supply-red'}), + presets.missions.supply.helo:extend({name='sierra-supply-red-1'}), + presets.missions.supply.transfer:extend({name='sierra-transfer-red'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='sierra-comcenter-red', + products = { + presets.defenses.red.shorad:extend({name='sierra-sam-red'}), + presets.missions.attack.helo:extend({name='sierra-cas-red', altitude=200, expend=AI.Task.WeaponExpend.HALF }) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='sierra-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='sierra-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='sierra-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelTankFarp:extend({ + name='sierra-fuel-blue', + products = { + presets.missions.supply.helo:extend({name='sierra-supply-blue'}), + presets.missions.supply.helo:extend({name='sierra-supply-blue-1'}), + presets.missions.supply.transfer:extend({name='sierra-transfer-blue'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='sierra-comcenter-blue', + products = { + presets.defenses.blue.shorad:extend({name='sierra-sam-blue'}), + presets.missions.attack.helo:extend({name='sierra-cas-blue', altitude=200, expend=AI.Task.WeaponExpend.HALF }) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Sierra.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Oni.lua ]]----------------- + +zones.oni = ZoneCommand:new("Oni") +zones.oni.initialState = { side=1 } +zones.oni:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='oni-tent-red', + products = { + presets.special.red.infantry:extend({ name='oni-defense-red'}), + presets.defenses.red.infantry:extend({ name='oni-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='oni-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='oni-supply-red'}), + presets.missions.supply.helo:extend({name='oni-supply-red-2'}), + presets.missions.supply.transfer:extend({name='oni-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='oni-ammo-red', + products = { + presets.missions.attack.surface:extend({name='oni-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='oni-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='oni-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='oni-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='oni-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='oni-supply-blue'}), + presets.missions.supply.helo:extend({name='oni-supply-blue-2'}), + presets.missions.supply.transfer:extend({name='oni-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='oni-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='oni-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Oni.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Hotel.lua ]]----------------- + +zones.hotel = ZoneCommand:new("Hotel") +zones.hotel.initialState = nil +zones.hotel:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='hotel-tent-red', + products = { + presets.special.red.infantry:extend({ name='hotel-defense-red'}), + presets.defenses.red.infantry:extend({ name='hotel-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='hotel-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='hotel-supply-red'}), + presets.missions.supply.transfer:extend({name='hotel-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='hotel-ammo-red', + products = { + presets.missions.attack.surface:extend({name='hotel-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='hotel-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='hotel-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='hotel-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='hotel-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='hotel-supply-blue'}), + presets.missions.supply.transfer:extend({name='hotel-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='hotel-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='hotel-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Hotel.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Victor.lua ]]----------------- + +zones.victor = ZoneCommand:new("Victor") +zones.victor.initialState = { side=1 } +zones.victor:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='victor-tent-red', + products = { + presets.special.red.infantry:extend({ name='victor-defense-red'}), + presets.defenses.red.infantry:extend({ name='victor-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='victor-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='victor-supply-red'}), + presets.missions.supply.helo:extend({name='victor-supply-red-2'}), + presets.missions.supply.transfer:extend({name='victor-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='victor-ammo-red', + products = { + presets.missions.attack.surface:extend({name='victor-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='victor-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='victor-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='victor-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='victor-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='victor-supply-blue'}), + presets.missions.supply.helo:extend({name='victor-supply-blue-2'}), + presets.missions.supply.transfer:extend({name='victor-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='victor-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='victor-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Victor.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Tango.lua ]]----------------- + +zones.tango = ZoneCommand:new("Tango") +zones.tango.initialState = { side=1 } +zones.tango.isHeloSpawn = true +zones.tango:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='tango-tent-red', + products = { + presets.special.red.infantry:extend({ name='tango-defense-red'}), + presets.defenses.red.infantry:extend({ name='tango-garrison-red'}) + } + }), + presets.upgrades.supply.fuelTankFarp:extend({ + name='tango-fuel-red', + products = { + presets.missions.supply.helo:extend({name='tango-supply-red'}), + presets.missions.supply.helo:extend({name='tango-supply-red-1'}), + presets.missions.supply.transfer:extend({name='tango-transfer-red'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='tango-comcenter-red', + products = { + presets.defenses.red.shorad:extend({name='tango-sam-red'}), + presets.missions.attack.helo:extend({name='tango-cas-red', altitude=200, expend=AI.Task.WeaponExpend.HALF }) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='tango-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='tango-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='tango-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelTankFarp:extend({ + name='tango-fuel-blue', + products = { + presets.missions.supply.helo:extend({name='tango-supply-blue'}), + presets.missions.supply.helo:extend({name='tango-supply-blue-1'}), + presets.missions.supply.transfer:extend({name='tango-transfer-blue'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='tango-comcenter-blue', + products = { + presets.defenses.blue.shorad:extend({name='tango-sam-blue'}), + presets.missions.attack.helo:extend({name='tango-cas-blue', altitude=200, expend=AI.Task.WeaponExpend.HALF }) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Tango.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Unal.lua ]]----------------- + +zones.unal = ZoneCommand:new("Unal") +zones.unal.initialState = { side=1 } +zones.unal.isHeloSpawn = true +zones.unal:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='unal-tent-red', + products = { + presets.special.red.infantry:extend({ name='unal-defense-red'}), + presets.defenses.red.infantry:extend({ name='unal-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='unal-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='unal-supply-red'}), + presets.missions.supply.helo:extend({name='unal-supply-red-2'}), + presets.missions.supply.transfer:extend({name='unal-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='unal-ammo-red', + products = { + presets.missions.attack.surface:extend({name='unal-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='unal-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='unal-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='unal-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='unal-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='unal-supply-blue'}), + presets.missions.supply.helo:extend({name='unal-supply-blue-2'}), + presets.missions.supply.transfer:extend({name='unal-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='unal-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='unal-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Unal.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Beslan.lua ]]----------------- + +zones.beslan = ZoneCommand:new("Beslan") +zones.beslan.initialState = { side=1 } +zones.beslan.keepActive = true +zones.beslan.maxResource = 50000 +zones.beslan:defineUpgrades({ + [1] = + { + presets.upgrades.basic.comPost:extend({ + name='beslan-compost-red', + products = { + presets.special.red.infantry:extend({ name='beslan-defense-red'}), + presets.defenses.red.infantry:extend({ name='beslan-garrison-red'}) + } + }), + presets.upgrades.supply.fuelTank:extend({ + name='beslan-fuel-red', + products = { + presets.missions.supply.helo:extend({name='beslan-supply-red-1'}), + presets.missions.supply.helo:extend({name='beslan-supply-red-2'}), + presets.missions.supply.convoy_escorted:extend({name='beslan-supply-red-3'}), + presets.missions.supply.transfer:extend({name='beslan-transfer-red'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='beslan-comcenter-red', + products = { + presets.defenses.red.sa5:extend({ name='beslan-airdef-red'}), + presets.missions.attack.sead:extend({name='beslan-sead-red', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.attack.strike:extend({name='beslan-strike-red', altitude=30000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.attack.strike:extend({name='beslan-strike-red-1', altitude=30000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.patrol.aircraft:extend({name='beslan-patrol-red', altitude=25000, range=25}) + } + }) + }, + [2] = + { + presets.upgrades.basic.comPost:extend({ + name='beslan-compost-blue', + products = { + presets.special.blue.infantry:extend({ name='beslan-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='beslan-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelTank:extend({ + name='beslan-fuel-blue', + products = { + presets.missions.supply.helo:extend({name='beslan-supply-blue-1'}), + presets.missions.supply.helo:extend({name='beslan-supply-blue-2'}), + presets.missions.supply.convoy_escorted:extend({name='beslan-supply-blue-3'}), + presets.missions.supply.transfer:extend({name='beslan-transfer-blue'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='beslan-comcenter-blue', + products = { + presets.defenses.blue.patriot:extend({ name='beslan-airdef-blue'}), + presets.missions.attack.sead:extend({name='beslan-sead-blue', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.attack.strike:extend({name='beslan-strike-blue', altitude=30000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.attack.strike:extend({name='beslan-strike-blue-1', altitude=30000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.patrol.aircraft:extend({name='beslan-patrol-blue', altitude=25000, range=25}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Beslan.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Bravo.lua ]]----------------- + +zones.bravo = ZoneCommand:new("Bravo") +zones.bravo.initialState = { side=2 } +zones.bravo:defineUpgrades({ + [1] = + { + presets.upgrades.basic.comPost:extend({ + name='bravo-compost-red', + products = { + presets.special.red.infantry:extend({ name='bravo-defense-red'}), + presets.defenses.red.infantry:extend({ name='bravo-garrison-red'}) + } + }), + presets.upgrades.supply.fuelTankFarp:extend({ + name='bravo-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({ name='bravo-supply-red'}), + presets.missions.supply.transfer:extend({name='bravo-transfer-red'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='bravo-comcenter-red', + products = { + presets.defenses.red.shorad:extend({ name='bravo-airdef-red'}), + presets.missions.attack.helo:extend({name='bravo-attack-red', altitude=200, expend=AI.Task.WeaponExpend.HALF}) + } + }) + }, + [2] = + { + presets.upgrades.basic.comPost:extend({ + name='bravo-compost-blue', + products = { + presets.special.blue.infantry:extend({ name='bravo-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='bravo-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelTankFarp:extend({ + name='bravo-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({ name='bravo-supply-blue'}), + presets.missions.supply.transfer:extend({name='bravo-transfer-blue'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='bravo-comcenter-blue', + products = { + presets.defenses.blue.shorad:extend({ name='bravo-airdef-blue'}), + presets.missions.attack.helo:extend({name='bravo-attack-blue', altitude=200, expend=AI.Task.WeaponExpend.HALF }) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Bravo.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/WeaponDepot.lua ]]----------------- + +zones.weapondepot = ZoneCommand:new("Weapon Depot") +zones.weapondepot.initialState = { side=1 } +zones.weapondepot:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='weapons-tent-red', + products = { + presets.special.red.infantry:extend({ name='weapons-defense-red'}), + presets.defenses.red.infantry:extend({ name='weapons-garrison-red'}) + } + }), + presets.upgrades.supply.ammoDepot:extend({ + name='weapons-ammo-red-1', + products = { + presets.missions.supply.convoy:extend({name='weapons-supply-red-1'}), + presets.missions.supply.transfer:extend({name='weapons-transfer-red-1'}) + } + }), + presets.upgrades.supply.ammoDepot:extend({ + name='weapons-ammo-red-2', + products = { + presets.missions.supply.convoy:extend({name='weapons-supply-red-2'}), + presets.missions.supply.transfer:extend({name='weapons-transfer-red-2'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='weapons-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='weapons-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='weapons-garrison-blue'}) + } + }), + presets.upgrades.supply.ammoDepot:extend({ + name='weapons-ammo-blue-1', + products = { + presets.missions.supply.convoy:extend({name='weapons-supply-blue-1'}), + presets.missions.supply.transfer:extend({name='weapons-transfer-blue-1'}) + } + }), + presets.upgrades.supply.ammoDepot:extend({ + name='weapons-ammo-blue-2', + products = { + presets.missions.supply.convoy:extend({name='weapons-supply-blue-2'}), + presets.missions.supply.transfer:extend({name='weapons-transfer-blue-2'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/WeaponDepot.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Delta.lua ]]----------------- + +zones.delta = ZoneCommand:new("Delta") +zones.delta.initialState = { side=2 } +zones.delta:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='delta-tent-red', + products = { + presets.special.red.infantry:extend({ name='delta-defense-red'}), + presets.defenses.red.infantry:extend({ name='delta-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='delta-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='delta-supply-red'}), + presets.missions.supply.transfer:extend({name='delta-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='delta-ammo-red', + products = { + presets.missions.attack.surface:extend({name='delta-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='delta-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='delta-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='delta-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='delta-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='delta-supply-blue'}), + presets.missions.supply.transfer:extend({name='delta-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='delta-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='delta-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Delta.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Cherkessk.lua ]]----------------- + +zones.cherkessk = ZoneCommand:new("Cherkessk") +zones.cherkessk.initialState = { side=1 } +zones.cherkessk.isHeloSpawn = true +zones.cherkessk:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='cherkessk-tent-red', + products = { + presets.special.red.infantry:extend({ name='cherkessk-defense-red'}), + presets.defenses.red.infantry:extend({ name='cherkessk-garrison-red'}) + } + }), + presets.upgrades.supply.fuelTankFarp:extend({ + name='cherkessk-fuel-red', + products = { + presets.missions.supply.helo:extend({name='cherkessk-supply-red'}), + presets.missions.supply.helo:extend({name='cherkessk-supply-red-1'}), + presets.missions.supply.transfer:extend({name='cherkessk-transfer-red'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='cherkessk-comcenter-red', + products = { + presets.defenses.red.shorad:extend({name='cherkessk-sam-red'}), + presets.missions.attack.helo:extend({name='cherkessk-cas-red', altitude=200, expend=AI.Task.WeaponExpend.HALF }), + presets.missions.attack.helo:extend({name='cherkessk-cas-red-1', altitude=200, expend=AI.Task.WeaponExpend.HALF }), + presets.missions.attack.surface:extend({name='cherkessk-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='cherkessk-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='cherkessk-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='cherkessk-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelTankFarp:extend({ + name='cherkessk-fuel-blue', + products = { + presets.missions.supply.helo:extend({name='cherkessk-supply-blue'}), + presets.missions.supply.helo:extend({name='cherkessk-supply-blue-1'}), + presets.missions.supply.transfer:extend({name='cherkessk-transfer-blue'}), + presets.missions.attack.surface:extend({name='cherkessk-assault-blue'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='cherkessk-comcenter-blue', + products = { + presets.defenses.blue.shorad:extend({name='cherkessk-sam-blue'}), + presets.missions.attack.helo:extend({name='cherkessk-cas-blue', altitude=200, expend=AI.Task.WeaponExpend.HALF }), + presets.missions.attack.helo:extend({name='cherkessk-cas-blue-1', altitude=200, expend=AI.Task.WeaponExpend.HALF }) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Cherkessk.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Juliett.lua ]]----------------- + +zones.juliett = ZoneCommand:new("Juliett") +zones.initialState = nil +zones.juliett:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='juliett-tent-red', + products = { + presets.special.red.infantry:extend({ name='juliett-defense-red'}), + presets.defenses.red.infantry:extend({ name='juliett-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='juliett-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='juliett-supply-red'}), + presets.missions.supply.transfer:extend({name='juliett-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='juliett-ammo-red', + products = { + presets.missions.attack.surface:extend({name='juliett-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='juliett-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='juliett-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='juliett-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='juliett-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='juliett-supply-blue'}), + presets.missions.supply.transfer:extend({name='juliett-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='juliett-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='juliett-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Juliett.lua ]]----------------- + + + + cm = ConnectionManager:new() + cm:addConnection('Batumi', 'Alpha') + cm:addConnection('Alpha', 'Bravo') + cm:addConnection('Bravo', 'Kobuleti') + cm:addConnection('Bravo', 'Factory') + cm:addConnection('Kobuleti', 'Factory') + cm:addConnection('Kobuleti', 'Charlie') + cm:addConnection('Foxtrot', 'Charlie') + cm:addConnection('Foxtrot', 'Kobuleti') + cm:addConnection('Delta','Foxtrot') + cm:addConnection('Delta','Kobuleti') + cm:addConnection('Delta','Factory') + cm:addConnection('Echo','Charlie') + cm:addConnection('Golf','Echo') + cm:addConnection('Golf','Foxtrot') + cm:addConnection('India','Delta') + cm:addConnection('Hotel','Golf') + cm:addConnection('Hotel','Foxtrot') + cm:addConnection('Hotel','Delta') + cm:addConnection('Hotel','India') + cm:addConnection('Juliett','Echo') + cm:addConnection('Juliett','Golf') + cm:addConnection('Senaki','Juliett') + cm:addConnection('Senaki','Golf') + cm:addConnection('Senaki','Hotel') + cm:addConnection('Kutaisi','Hotel') + cm:addConnection('Kutaisi','India') + cm:addConnection('Kilo','Juliett') + cm:addConnection('Mike','Kutaisi') + cm:addConnection('Mike','Senaki') + cm:addConnection('Romeo','Mike') + cm:addConnection('Romeo','Kutaisi') + cm:addConnection('Weapon Depot','Juliett') + cm:addConnection('Weapon Depot','Senaki') + cm:addConnection('Weapon Depot','Kilo') + cm:addConnection('November','Weapon Depot') + cm:addConnection('November','Senaki') + cm:addConnection('November','Mike') + cm:addConnection('Oil Fields','Romeo') + cm:addConnection('Quebec','Kilo') + cm:addConnection('Zugdidi','Weapon Depot') + cm:addConnection('Zugdidi','Quebec') + cm:addConnection('Zugdidi','November') + cm:addConnection('Zugdidi','Kilo') + cm:addConnection('Distillery','November') + cm:addConnection('Distillery','Mike') + cm:addConnection('Zugdidi','Papa') + cm:addConnection('November','Papa') + cm:addConnection('Sierra','Papa') + cm:addConnection('Sierra','Zugdidi') + cm:addConnection('Sierra','Uniform') + cm:addConnection('Mine','Uniform') + cm:addConnection('Tango','Quebec') + cm:addConnection('Tango','Zugdidi') + cm:addConnection('Sierra','Tango') + cm:addConnection('Whiskey','Tango') + cm:addConnection('Ochamchira','Tango') + cm:addConnection('Ochamchira','Whiskey') + cm:addConnection('Ochamchira','Farm') + cm:addConnection('Ochamchira','Zulu') + cm:addConnection('Farm','Zulu') + cm:addConnection('Sukhumi','Zulu') + cm:addConnection('Lentehi','Distillery', true, 3000) + cm:addConnection('Lentehi','Babugent', true, 5000) + cm:addConnection('Nalchik','Babugent') + cm:addConnection('Victor','Distillery', true, 2000) + cm:addConnection('Victor','Romeo') + cm:addConnection('Victor','Lentehi') + cm:addConnection('Victor','Oil Fields', true, 2000) + cm:addConnection('Victor','Oni') + cm:addConnection('Unal','Oni', true, 4500) + cm:addConnection('Beslan','Unal') + cm:addConnection('Digora','Beslan') + cm:addConnection('Digora','Unal') + cm:addConnection('Digora','Babugent') + cm:addConnection('Terek','Digora') + cm:addConnection('Terek','Nalchik') + cm:addConnection('Terek','Beslan') + cm:addConnection('Prohladniy','Terek') + cm:addConnection('Prohladniy','Nalchik') + cm:addConnection('Malgobek','Terek') + cm:addConnection('Malgobek','Beslan') + cm:addConnection('Lima','Mine') + cm:addConnection('Lima','Lentehi', true, 4000) + cm:addConnection('Tyrnyauz','Lima', true, 4000) + cm:addConnection('Tyrnyauz','Nalchik') + cm:addConnection('XRay','Sukhumi') + cm:addConnection('Oscar','Sukhumi') + cm:addConnection('Oscar','XRay') + cm:addConnection('Mozdok','Malgobek') + cm:addConnection('Mozdok','Prohladniy') + cm:addConnection('Gudauta','Oscar') + cm:addConnection('Yankee','Gudauta') + cm:addConnection('Sochi','Yankee') + cm:addConnection('Refinery','XRay', true, 4000) + cm:addConnection('Refinery','Humara') + cm:addConnection('Intel Center','Tyrnyauz') + cm:addConnection('Intel Center','Nalchik') + cm:addConnection('Intel Center','Prohladniy') + cm:addConnection('Intel Center','Kislovodsk') + cm:addConnection('Mineralnye','Intel Center') + cm:addConnection('Kislovodsk','Mineralnye') + cm:addConnection('Tallyk','Mineralnye') + cm:addConnection('Tallyk','Kislovodsk') + cm:addConnection('Power Plant','Mineralnye') + cm:addConnection('Power Plant','Tallyk') + cm:addConnection('Cherkessk','Tallyk') + cm:addConnection('Cherkessk','Power Plant') + cm:addConnection('Cherkessk','Humara') +end + +ZoneCommand.setNeighbours(cm) + +bm = BattlefieldManager:new() + +mc = MarkerCommands:new() + +pt = PlayerTracker:new(mc) + +mt = MissionTracker:new(pt, mc) + +st = SquadTracker:new() + +ct = CSARTracker:new() + +pl = PlayerLogistics:new(mt, pt, st, ct) + +gci = GCI:new(2) + +gm = GroupMonitor:new(cm) +ZoneCommand.groupMonitor = gm + +-- PlayerLogistics:registerSquadGroup(squadType, groupname, weight,cost,jobtime,extracttime, squadSize) +pl:registerSquadGroup(PlayerLogistics.infantryTypes.capture, 'capture-squad', 700, 200, 60, 60*30, 4) +pl:registerSquadGroup(PlayerLogistics.infantryTypes.sabotage, 'sabotage-squad', 800, 500, 60*5, 60*30, 4) +pl:registerSquadGroup(PlayerLogistics.infantryTypes.ambush, 'ambush-squad', 900, 300, 60*20, 60*30, 5) +pl:registerSquadGroup(PlayerLogistics.infantryTypes.engineer, 'engineer-squad', 200, 1000,60, 60*30, 2) +pl:registerSquadGroup(PlayerLogistics.infantryTypes.manpads, 'manpads-squad', 900, 500, 60*20, 60*30, 5) +pl:registerSquadGroup(PlayerLogistics.infantryTypes.spy, 'spy-squad', 100, 300, 60*10, 60*30, 1) +pl:registerSquadGroup(PlayerLogistics.infantryTypes.rapier, 'rapier-squad', 1200,2000,60*60, 60*30, 8) + +Group.getByName('jtacDrone'):destroy() +CommandFunctions.jtac = JTAC:new({name = 'jtacDrone'}) + +pm = PersistenceManager:new(savefile, gm, st, ct, pl) +pm:load() + +if pm:canRestore() then + pm:restoreZones() + pm:restoreAIMissions() + pm:restoreBattlefield() + pm:restoreCsar() + pm:restoreSquads() +else + --initial states + Starter.start(zones) +end + +timer.scheduleFunction(function(param, time) + pm:save() + env.info("Mission state saved") + return time+60 +end, zones, timer.getTime()+60) + + +--make sure support units are present where needed +ensureSpawn = { + ['golf-farp-suport'] = zones.golf, + ['november-farp-suport'] = zones.november, + ['tango-farp-suport'] = zones.tango, + ['sierra-farp-suport'] = zones.sierra, + ['cherkessk-farp-suport'] = zones.cherkessk, + ['unal-farp-suport'] = zones.unal, + ['tyrnyauz-farp-suport'] = zones.tyrnyauz +} + +for grname, zn in pairs(ensureSpawn) do + local g = Group.getByName(grname) + if g then g:destroy() end +end + +timer.scheduleFunction(function(param, time) + + for grname, zn in pairs(ensureSpawn) do + local g = Group.getByName(grname) + if zn.side == 2 then + if not g then + local err, msg = pcall(mist.respawnGroup,grname,true) + if not err then + env.info("ERROR spawning "..grname) + env.info(msg) + end + end + else + if g then g:destroy() end + end + end + + return time+30 +end, {}, timer.getTime()+30) + + +--supply injection +local blueSupply = {'offmap-supply-blue-1','offmap-supply-blue-2','offmap-supply-blue-3','offmap-supply-blue-4','offmap-supply-blue-5'} +local redSupply = {'offmap-supply-red-1','offmap-supply-red-2','offmap-supply-red-3','offmap-supply-red-4','offmap-supply-red-5'} +local offmapZones = { + zones.batumi, + zones.sochi, + zones.nalchik, + zones.beslan, + zones.mozdok, + zones.mineralnye, +-- zones.senaki, +-- zones.sukhumi, +-- zones.gudauta, +-- zones.kobuleti, +} + +supplyPointRegistry = { + blue = {}, + red = {} +} + +for i,v in ipairs(blueSupply) do + local g = Group.getByName(v) + if g then + supplyPointRegistry.blue[v] = g:getUnit(1):getPoint() + end +end + +for i,v in ipairs(redSupply) do + local g = Group.getByName(v) + if g then + supplyPointRegistry.red[v] = g:getUnit(1):getPoint() + end +end + +offmapSupplyRegistry = {} +timer.scheduleFunction(function(param, time) + local availableBlue = {} + for i,v in ipairs(param.blue) do + if offmapSupplyRegistry[v] == nil then + table.insert(availableBlue, v) + end + end + + local availableRed = {} + for i,v in ipairs(param.red) do + if offmapSupplyRegistry[v] == nil then + table.insert(availableRed, v) + end + end + + local redtargets = {} + local bluetargets = {} + for _, zn in ipairs(param.offmapZones) do + if zn:needsSupplies(3000) then + local isOnRoute = false + for _,data in pairs(offmapSupplyRegistry) do + if data.zone.name == zn.name then + isOnRoute = true + break + end + end + if not isOnRoute then + if zn.side == 1 then + table.insert(redtargets, zn) + elseif zn.side == 2 then + table.insert(bluetargets, zn) + end + end + end + end + + if #availableRed > 0 and #redtargets > 0 then + local zn = redtargets[math.random(1,#redtargets)] + + local red = nil + local minD = 999999999 + for i,v in ipairs(availableRed) do + local d = mist.utils.get2DDist(zn.zone.point, supplyPointRegistry.red[v]) + if d < minD then + red = v + minD = d + end + end + + if not red then red = availableRed[math.random(1,#availableRed)] end + + local gr = red + red = nil + mist.respawnGroup(gr, true) + offmapSupplyRegistry[gr] = {zone = zn, assigned = timer.getAbsTime()} + env.info(gr..' was deployed') + timer.scheduleFunction(function(param,time) + local g = Group.getByName(param.group) + TaskExtensions.landAtAirfield(g, param.target.zone.point) + env.info(param.group..' going to '..param.target.name) + end, {group=gr, target=zn}, timer.getTime()+2) + end + + if #availableBlue > 0 and #bluetargets>0 then + local zn = bluetargets[math.random(1,#bluetargets)] + + local blue = nil + local minD = 999999999 + for i,v in ipairs(availableBlue) do + local d = mist.utils.get2DDist(zn.zone.point, supplyPointRegistry.blue[v]) + if d < minD then + blue = v + minD = d + end + end + + if not blue then blue = availableBlue[math.random(1,#availableBlue)] end + + local gr = blue + blue = nil + mist.respawnGroup(gr, true) + offmapSupplyRegistry[gr] = {zone = zn, assigned = timer.getAbsTime()} + env.info(gr..' was deployed') + timer.scheduleFunction(function(param,time) + local g = Group.getByName(param.group) + TaskExtensions.landAtAirfield(g, param.target.zone.point) + env.info(param.group..' going to '..param.target.name) + end, {group=gr, target=zn}, timer.getTime()+2) + end + + return time+(60*5) +end, {blue = blueSupply, red = redSupply, offmapZones = offmapZones}, timer.getTime()+60) + + + +timer.scheduleFunction(function(param, time) + + for groupname,data in pairs(offmapSupplyRegistry) do + local gr = Group.getByName(groupname) + if not gr then + offmapSupplyRegistry[groupname] = nil + env.info(groupname..' was destroyed') + end + + if gr and ((timer.getAbsTime() - data.assigned) > (60*60)) then + gr:destroy() + offmapSupplyRegistry[groupname] = nil + env.info(groupname..' despawned due to being alive for too long') + end + + if gr and Utils.allGroupIsLanded(gr) and Utils.someOfGroupInZone(gr, data.zone.name) then + data.zone:addResource(15000) + gr:destroy() + offmapSupplyRegistry[groupname] = nil + env.info(groupname..' landed at '..data.zone.name..' and delivered 15000 resources') + end + end + + return time+180 +end, {}, timer.getTime()+180) \ No newline at end of file diff --git a/resources/plugins/pretense/pretense_compiled.lua b/resources/plugins/pretense/pretense_compiled.lua new file mode 100644 index 00000000..4d3a04f2 --- /dev/null +++ b/resources/plugins/pretense/pretense_compiled.lua @@ -0,0 +1,13350 @@ +--[[ +Pretense Dynamic Mission Engine +## Description: + +Pretense Dynamic Mission Engine (PDME) is a the heart and soul of the Pretense missions. +You are allowed to use and modify this script for personal or private use. +Please do not share modified versions of this script. +Please do not reupload missions that use this script. +Please do not charge money for access to missions using this script. + +## Links: + +ED Forums Post: + +Pretense Manual: + +If you'd like to buy me a beer: + +Makes use of Mission scripting tools (Mist): + +@script PDME +@author Dzsekeb + +]]-- + +-----------------[[ Config.lua ]]----------------- + +Config = Config or {} +Config.lossCompensation = Config.lossCompensation or 1.1 -- gives advantage to the side with less zones. Set to 0 to disable +Config.randomBoost = Config.randomBoost or 0.0004 -- adds a random factor to build speeds that changes every 30 minutes, set to 0 to disable +Config.buildSpeed = Config.buildSpeed or 10 -- structure and defense build speed +Config.supplyBuildSpeed = Config.supplyBuildSpeed or 85 -- supply helicopters and convoys build speed +Config.missionBuildSpeedReduction = Config.missionBuildSpeedReduction or 0.12 -- reduction of build speed in case of ai missions +Config.maxDistFromFront = Config.maxDistFromFront or 129640 -- max distance in meters from front after which zone is forced into low activity state (export mode) + +Config.missions = Config.missions or {} + +-----------------[[ END OF Config.lua ]]----------------- + + + +-----------------[[ Utils.lua ]]----------------- + +Utils = {} +do + local JSON = (loadfile('Scripts/JSON.lua'))() + + function Utils.getPointOnSurface(point) + return {x = point.x, y = land.getHeight({x = point.x, y = point.z}), z= point.z} + end + + function Utils.getTableSize(tbl) + local cnt = 0 + for i,v in pairs(tbl) do cnt=cnt+1 end + return cnt + end + + Utils.cache = {} + Utils.cache.groups = {} + function Utils.getOriginalGroup(groupName) + if Utils.cache.groups[groupName] then + return Utils.cache.groups[groupName] + end + + for _,coalition in pairs(env.mission.coalition) do + for _,country in pairs(coalition.country) do + local tocheck = {} + table.insert(tocheck, country.plane) + table.insert(tocheck, country.helicopter) + table.insert(tocheck, country.ship) + table.insert(tocheck, country.vehicle) + table.insert(tocheck, country.static) + + for _, checkGroup in ipairs(tocheck) do + for _,item in pairs(checkGroup.group) do + Utils.cache.groups[item.name] = item + if item.name == groupName then + return item + end + end + end + end + end + end + + function Utils.getBearing(fromvec, tovec) + local fx = fromvec.x + local fy = fromvec.z + + local tx = tovec.x + local ty = tovec.z + + local brg = math.atan2(ty - fy, tx - fx) + + + if brg < 0 then + brg = brg + 2 * math.pi + end + + brg = brg * 180 / math.pi + + + return brg + end + + function Utils.getHeadingDiff(heading1, heading2) -- heading1 + result == heading2 + local diff = heading1 - heading2 + local absDiff = math.abs(diff) + local complementaryAngle = 360 - absDiff + + if absDiff <= 180 then + return -diff + elseif heading1 > heading2 then + return complementaryAngle + else + return -complementaryAngle + end + end + + function Utils.getAGL(object) + local pt = object:getPoint() + return pt.y - land.getHeight({ x = pt.x, y = pt.z }) + end + + function Utils.round(number) + return math.floor(number+0.5) + end + + function Utils.isLanded(unit, ignorespeed) + --return (Utils.getAGL(unit)<5 and mist.vec.mag(unit:getVelocity())<0.10) + + if ignorespeed then + return not unit:inAir() + else + return (not unit:inAir() and mist.vec.mag(unit:getVelocity())<1) + end + end + + function Utils.isGroupActive(group) + if group and group:getSize()>0 and group:getController():hasTask() then + return not Utils.allGroupIsLanded(group, true) + else + return false + end + end + + function Utils.isInAir(unit) + --return Utils.getAGL(unit)>5 + return unit:inAir() + end + + function Utils.isInZone(unit, zonename) + local zn = CustomZone:getByName(zonename) + if zn then + return zn:isInside(unit:getPosition().p) + end + + return false + end + + function Utils.isCrateSettledInZone(crate, zonename) + local zn = CustomZone:getByName(zonename) + if zn and crate then + return (zn:isInside(crate:getPosition().p) and Utils.getAGL(crate)<1) + end + + return false + end + + function Utils.someOfGroupInZone(group, zonename) + for i,v in pairs(group:getUnits()) do + if Utils.isInZone(v, zonename) then + return true + end + end + + return false + end + + function Utils.allGroupIsLanded(group, ignorespeed) + for i,v in pairs(group:getUnits()) do + if not Utils.isLanded(v, ignorespeed) then + return false + end + end + + return true + end + + function Utils.someOfGroupInAir(group) + for i,v in pairs(group:getUnits()) do + if Utils.isInAir(v) then + return true + end + end + + return false + end + + Utils.canAccessFS = true + function Utils.saveTable(filename, data) + if not Utils.canAccessFS then + return + end + + if not io then + Utils.canAccessFS = false + trigger.action.outText('Persistance disabled', 30) + return + end + + local str = JSON:encode(data) + -- local str = 'return (function() local tbl = {}' + -- for i,v in pairs(data) do + -- str = str..'\ntbl[\''..i..'\'] = '..Utils.serializeValue(v) + -- end + + -- str = str..'\nreturn tbl end)()' + + local File = io.open(filename, "w") + File:write(str) + File:close() + end + + function Utils.serializeValue(value) + local res = '' + if type(value)=='number' or type(value)=='boolean' then + res = res..tostring(value) + elseif type(value)=='string' then + res = res..'\''..value..'\'' + elseif type(value)=='table' then + res = res..'{ ' + for i,v in pairs(value) do + if type(i)=='number' then + res = res..'['..i..']='..Utils.serializeValue(v)..',' + else + res = res..'[\''..i..'\']='..Utils.serializeValue(v)..',' + end + end + res = res:sub(1,-2) + res = res..' }' + end + return res + end + + function Utils.loadTable(filename) + if not Utils.canAccessFS then + return + end + + if not lfs then + Utils.canAccessFS = false + trigger.action.outText('Persistance disabled', 30) + return + end + + if lfs.attributes(filename) then + local File = io.open(filename, "r") + local str = File:read('*all') + File:close() + + return JSON:decode(str) + end + end + + function Utils.merge(table1, table2) + local result = {} + for i,v in pairs(table1) do + result[i] = v + end + + for i,v in pairs(table2) do + result[i] = v + end + + return result + end + + function Utils.log(func) + return function(p1,p2,p3,p4,p5,p6,p7,p8,p9,p10) + local err, msg = pcall(func,p1,p2,p3,p4,p5,p6,p7,p8,p9,p10) + if not err then + env.info("ERROR - callFunc\n"..msg) + env.info('Traceback\n'..debug.traceback()) + end + end + end +end + + + +-----------------[[ END OF Utils.lua ]]----------------- + + + +-----------------[[ MenuRegistry.lua ]]----------------- + +MenuRegistry = {} + +do + MenuRegistry.menus = {} + function MenuRegistry:register(order, registerfunction, context) + for i=1,order,1 do + if not MenuRegistry.menus[i] then MenuRegistry.menus[i] = {func = function() end, context = {}} end + end + + MenuRegistry.menus[order] = {func = registerfunction, context = context} + end + + local ev = {} + function ev:onEvent(event) + if event.id == world.event.S_EVENT_BIRTH and event.initiator and event.initiator.getPlayerName then + local player = event.initiator:getPlayerName() + if player then + env.info('MenuRegistry - creating menus for player: '..player) + for i,v in ipairs(MenuRegistry.menus) do + local err, msg = pcall(v.func, event, v.context) + if not err then + env.info("MenuRegistry - ERROR :\n"..msg) + env.info('Traceback\n'..debug.traceback()) + end + end + end + end + end + + world.addEventHandler(ev) + + + function MenuRegistry.showTargetZoneMenu(groupid, name, action, targetside, minDistToFront) + local executeAction = function(act, params) + local err = act(params) + if not err then + missionCommands.removeItemForGroup(params.groupid, params.menu) + end + end + + local menu = missionCommands.addSubMenuForGroup(groupid, name) + local sub1 = nil + local zones = ZoneCommand.getAllZones() + + local zns = {} + for i,v in pairs(zones) do + if not targetside or v.side == targetside then + if not minDistToFront or v.distToFront <= minDistToFront then + table.insert(zns, v) + end + end + end + + table.sort(zns, function(a,b) return a.name < b.name end) + + local count = 0 + for i,v in ipairs(zns) do + count = count + 1 + if count<10 then + missionCommands.addCommandForGroup(groupid, v.name, menu, executeAction, action, {zone = v, menu=menu, groupid=groupid}) + elseif count==10 then + sub1 = missionCommands.addSubMenuForGroup(groupid, "More", menu) + missionCommands.addCommandForGroup(groupid, v.name, sub1, executeAction, action, {zone = v, menu=menu, groupid=groupid}) + elseif count%9==1 then + sub1 = missionCommands.addSubMenuForGroup(groupid, "More", sub1) + missionCommands.addCommandForGroup(groupid, v.name, sub1, executeAction, action, {zone = v, menu=menu, groupid=groupid}) + else + missionCommands.addCommandForGroup(groupid, v.name, sub1, executeAction, action, {zone = v, menu=menu, groupid=groupid}) + end + end + + return menu + end +end + +-----------------[[ END OF MenuRegistry.lua ]]----------------- + + + +-----------------[[ CustomZone.lua ]]----------------- + + +CustomZone = {} +do + function CustomZone:getByName(name) + local obj = {} + obj.name = name + + local zd = nil + for _,v in ipairs(env.mission.triggers.zones) do + if v.name == name then + zd = v + break + end + end + + if not zd then + return nil + end + + obj.type = zd.type -- 2 == quad, 0 == circle + if obj.type == 2 then + obj.vertices = {} + for _,v in ipairs(zd.verticies) do + local vertex = { + x = v.x, + y = 0, + z = v.y + } + table.insert(obj.vertices, vertex) + end + end + + obj.radius = zd.radius + obj.point = { + x = zd.x, + y = 0, + z = zd.y + } + + setmetatable(obj, self) + self.__index = self + return obj + end + + function CustomZone:isQuad() + return self.type==2 + end + + function CustomZone:isCircle() + return self.type==0 + end + + function CustomZone:isInside(point) + if self:isCircle() then + local dist = mist.utils.get2DDist(point, self.point) + return dist 0 then + return true + end + end + end + end + end + + function GroupMonitor:sendHome(trackedGroup) + if trackedGroup.home == nil then + env.info("GroupMonitor - sendHome "..trackedGroup.name..' does not have home set') + return + end + + if trackedGroup.returning then return end + + + local gr = Group.getByName(trackedGroup.name) + if gr then + if trackedGroup.product.missionType == ZoneCommand.missionTypes.cas_helo then + local hsp = trigger.misc.getZone(trackedGroup.home.name..'-hsp') + if not hsp then + hsp = trigger.misc.getZone(trackedGroup.home.name) + end + + local alt = self.connectionManager:getHeliAlt(trackedGroup.target.name, trackedGroup.home.name) + TaskExtensions.landAtPointFromAir(gr, {x=hsp.point.x, y=hsp.point.z}, alt) + else + local homeZn = trigger.misc.getZone(trackedGroup.home.name) + TaskExtensions.landAtAirfield(gr, {x=homeZn.point.x, y=homeZn.point.z}) + end + + local cnt = gr:getController() + cnt:setOption(0,4) -- force ai hold fire + cnt:setOption(1, 4) -- force reaction on threat to allow abort + + trackedGroup.returning = true + env.info('GroupMonitor - sendHome ['..trackedGroup.name..'] returning home') + end + end + + function GroupMonitor:registerGroup(product, target, home, savedData) + self.groups[product.name] = {name = product.name, lastStateTime = timer.getAbsTime(), product = product, target = target, home = home} + + if savedData and savedData.state ~= 'uninitialized' then + env.info('GroupMonitor - registerGroup ['..product.name..'] restored state '..savedData.state..' dur:'..savedData.lastStateDuration) + self.groups[product.name].state = savedData.state + self.groups[product.name].lastStateTime = timer.getAbsTime() - savedData.lastStateDuration + end + end + + function GroupMonitor:start() + timer.scheduleFunction(function(param, time) + local self = param.context + + for i,v in pairs(self.groups) do + local isDead = false + if v.product.missionType == 'supply_convoy' or v.product.missionType == 'assault' then + isDead = self:processSurface(v) + if isDead then + MissionTargetRegistry.removeBaiTarget(v) --safety measure in case group is dead + end + else + isDead = self:processAir(v) + end + + if isDead then + self.groups[i] = nil + end + end + + return time+10 + end, {context = self}, timer.getTime()+1) + end + + function GroupMonitor:getGroup(name) + return self.groups[name] + end + + function GroupMonitor:processSurface(group) -- states: [started, enroute, atdestination, siege] + local gr = Group.getByName(group.name) + if not gr then return true end + if gr:getSize()==0 then + gr:destroy() + return true + end + + if not group.state then + group.state = 'started' + lastStateTime = timer.getAbsTime() + env.info('GroupMonitor: processSurface ['..group.name..'] starting') + end + + if group.state =='started' then + if gr then + local firstUnit = gr:getUnit(1):getName() + local z = ZoneCommand.getZoneOfUnit(firstUnit) + + if not z then + env.info('GroupMonitor: processSurface ['..group.name..'] is enroute') + group.state = 'enroute' + group.lastStateTime = timer.getAbsTime() + MissionTargetRegistry.addBaiTarget(group) + elseif timer.getAbsTime() - group.lastStateTime > GroupMonitor.blockedDespawnTime then + env.info('GroupMonitor: processSurface ['..group.name..'] despawned due to blockage') + gr:destroy() + local todeliver = math.floor(group.product.cost) + z:addResource(todeliver) + return true + end + end + elseif group.state =='enroute' then + if gr then + local firstUnit = gr:getUnit(1):getName() + local z = ZoneCommand.getZoneOfUnit(firstUnit) + + if z and (z.name==group.target.name or z.name==group.home.name) then + MissionTargetRegistry.removeBaiTarget(group) + + if group.product.missionType == 'supply_convoy' then + env.info('GroupMonitor: processSurface ['..group.name..'] has arrived at destination') + group.state = 'atdestination' + group.lastStateTime = timer.getAbsTime() + z:capture(gr:getCoalition()) + local percentSurvived = gr:getSize()/gr:getInitialSize() + local todeliver = math.floor(group.product.cost * percentSurvived) + z:addResource(todeliver) + env.info('GroupMonitor: processSurface ['..group.name..'] has supplied ['..z.name..'] with ['..todeliver..']') + elseif group.product.missionType == 'assault' then + if z.side == gr:getCoalition() then + env.info('GroupMonitor: processSurface ['..group.name..'] has arrived at destination') + group.state = 'atdestination' + group.lastStateTime = timer.getAbsTime() + local percentSurvived = gr:getSize()/gr:getInitialSize() + local torecover = math.floor(group.product.cost * percentSurvived * GroupMonitor.recoveryReduction) + z:addResource(torecover) + env.info('GroupMonitor: processSurface ['..z.name..'] has recovered ['..torecover..'] from ['..group.name..']') + elseif z.side == 0 then + env.info('GroupMonitor: processSurface ['..group.name..'] has arrived at destination') + group.state = 'atdestination' + group.lastStateTime = timer.getAbsTime() + z:capture(gr:getCoalition()) + env.info('GroupMonitor: processSurface ['..group.name..'] has captured ['..z.name..']') + elseif z.side ~= gr:getCoalition() and z.side ~= 0 then + env.info('GroupMonitor: processSurface ['..group.name..'] starting siege') + group.state = 'siege' + group.lastStateTime = timer.getAbsTime() + end + end + else + if group.product.missionType == 'supply_convoy' then + if not group.returning and group.target and group.target.side ~= group.product.side and group.target.side ~= 0 then + local supplyPoint = trigger.misc.getZone(group.home.name..'-sp') + if not supplyPoint then + supplyPoint = trigger.misc.getZone(group.home.name) + end + + if supplyPoint then + group.returning = true + env.info('GroupMonitor: processSurface ['..group.name..'] returning home') + TaskExtensions.moveOnRoadToPoint(gr, {x=supplyPoint.point.x, y=supplyPoint.point.z}) + end + end + elseif group.product.missionType == 'assault' then + local frUnit = gr:getUnit(1) + if frUnit then + local controller = frUnit:getController() + local targets = controller:getDetectedTargets() + + local shouldstop = false + if #targets > 0 then + for _,tgt in ipairs(targets) do + if tgt.visible and tgt.object then + if tgt.object.getCoalition and tgt.object:getCoalition()~=frUnit:getCoalition() and + tgt.object.getCategory and tgt.object:getCategory() == 1 then + local dist = mist.utils.get3DDist(frUnit:getPoint(), tgt.object:getPoint()) + if dist < 1000 then + if not group.isstopped then + env.info('GroupMonitor: processSurface ['..group.name..'] stopping to engage targets') + --gr:getController():setCommand({id = 'StopRoute', params = { value = true}}) + TaskExtensions.stopAndDisperse(gr) + group.isstopped = true + end + shouldstop = true + break + end + end + end + end + end + + if not shouldstop and group.isstopped then + env.info('GroupMonitor: processSurface ['..group.name..'] resuming mission') + --gr:getController():setCommand({id = 'StopRoute', params = { value = false}}) + local tp = { + x = group.target.zone.point.x, + y = group.target.zone.point.z + } + + TaskExtensions.moveOnRoadToPointAndAssault(gr, tp, group.target.built) + group.isstopped = false + end + end + end + end + end + elseif group.state == 'atdestination' then + if timer.getAbsTime() - group.lastStateTime > GroupMonitor.atDestinationDespawnTime then + + if gr then + local firstUnit = gr:getUnit(1):getName() + local z = ZoneCommand.getZoneOfUnit(firstUnit) + if z and z.side == 0 then + env.info('GroupMonitor: processSurface ['..group.name..'] is at neutral zone') + z:capture(gr:getCoalition()) + env.info('GroupMonitor: processSurface ['..group.name..'] has captured ['..z.name..']') + else + env.info('GroupMonitor: processSurface ['..group.name..'] starting siege') + group.state = 'siege' + group.lastStateTime = timer.getAbsTime() + end + + env.info('GroupMonitor: processSurface ['..group.name..'] despawned after arriving at destination') + gr:destroy() + return true + end + end + elseif group.state == 'siege' then + if group.product.missionType ~= 'assault' then + group.state = 'atdestination' + group.lastStateTime = timer.getAbsTime() + else + if timer.getAbsTime() - group.lastStateTime > GroupMonitor.siegeExplosiveTime then + if gr then + local firstUnit = gr:getUnit(1):getName() + local z = ZoneCommand.getZoneOfUnit(firstUnit) + local success = false + + if z then + for i,v in pairs(z.built) do + if v.type == 'upgrade' and v.side ~= gr:getCoalition() then + local st = StaticObject.getByName(v.name) + if not st then st = Group.getByName(v.name) end + local pos = st:getPoint() + trigger.action.explosion(pos, GroupMonitor.siegeExplosiveStrength) + group.lastStateTime = timer.getAbsTime() + success = true + env.info('GroupMonitor: processSurface ['..group.name..'] detonating structure at '..z.name) + break + end + end + end + + if not success then + env.info('GroupMonitor: processSurface ['..group.name..'] no targets to detonate, switching to atdestination') + group.state = 'atdestination' + group.lastStateTime = timer.getAbsTime() + end + end + end + end + end + end + + function GroupMonitor:processAir(group)-- states: [takeoff, inair, landed] + local gr = Group.getByName(group.name) + if not gr then return true end + if gr:getSize()==0 then + gr:destroy() + return true + end + --[[ + if group.product.missionType == 'cas' or group.product.missionType == 'cas_helo' or group.product.missionType == 'strike' or group.product.missionType == 'sead' then + if MissionTargetRegistry.isZoneTargeted(group.target) and group.product.side == 2 and not group.returning then + env.info('GroupMonitor - mission ['..group.name..'] to ['..group.target..'] canceled due to player mission') + + GroupMonitor.sendHome(group) + end + end + ]]-- + + if not group.state then + group.state = 'takeoff' + env.info('GroupMonitor: processAir ['..group.name..'] taking off') + end + + if group.state =='takeoff' then + if timer.getAbsTime() - group.lastStateTime > GroupMonitor.blockedDespawnTime then + if gr and Utils.allGroupIsLanded(gr) then + env.info('GroupMonitor: processAir ['..group.name..'] is blocked, despawning') + local frUnit = gr:getUnit(1) + if frUnit then + local firstUnit = frUnit:getName() + local z = ZoneCommand.getZoneOfUnit(firstUnit) + if z then + z:addResource(group.product.cost) + env.info('GroupMonitor: processAir ['..z.name..'] has recovered ['..group.product.cost..'] from ['..group.name..']') + end + end + + gr:destroy() + return true + end + elseif gr and Utils.someOfGroupInAir(gr) then + env.info('GroupMonitor: processAir ['..group.name..'] is in the air') + group.state = 'inair' + group.lastStateTime = timer.getAbsTime() + end + elseif group.state =='inair' then + if gr and Utils.allGroupIsLanded(gr) then + env.info('GroupMonitor: processAir ['..group.name..'] has landed') + group.state = 'landed' + group.lastStateTime = timer.getAbsTime() + + local unit = gr:getUnit(1) + if unit then + local firstUnit = unit:getName() + local z = ZoneCommand.getZoneOfUnit(firstUnit) + + if group.product.missionType == 'supply_air' then + if z then + z:capture(gr:getCoalition()) + z:addResource(group.product.cost) + env.info('GroupMonitor: processAir ['..group.name..'] has supplied ['..z.name..'] with ['..group.product.cost..']') + end + else + if z and z.side == gr:getCoalition() then + local percentSurvived = gr:getSize()/gr:getInitialSize() + local torecover = math.floor(group.product.cost * percentSurvived * GroupMonitor.recoveryReduction) + z:addResource(torecover) + env.info('GroupMonitor: processAir ['..z.name..'] has recovered ['..torecover..'] from ['..group.name..']') + end + end + else + env.info('GroupMonitor: processAir ['..group.name..'] size ['..gr:getSize()..'] has no unit 1') + end + elseif gr then + if GroupMonitor.isAirAttack(group.product.missionType) and not group.returning then + if not GroupMonitor.hasWeapons(gr) then + env.info('GroupMonitor: processAir ['..group.name..'] size ['..gr:getSize()..'] has no weapons outside of shells') + self:sendHome(group) + elseif group.product.missionType == ZoneCommand.missionTypes.cas_helo then + local frUnit = gr:getUnit(1) + local controller = frUnit:getController() + local targets = controller:getDetectedTargets() + + local tgtToEngage = {} + if #targets > 0 then + for _,tgt in ipairs(targets) do + if tgt.visible and tgt.object and tgt.object.isExist and tgt.object:isExist() then + if tgt.object.getCategory and tgt.object:getCategory() == Object.Category.UNIT and + tgt.object.getCoalition and tgt.object:getCoalition()~=frUnit:getCoalition() and + tgt.object:getDesc().category == Unit.Category.GROUND_UNIT then + + local dist = mist.utils.get3DDist(frUnit:getPoint(), tgt.object:getPoint()) + if dist < 2000 then + table.insert(tgtToEngage, tgt.object) + end + end + end + end + end + + if not group.isengaging and #tgtToEngage > 0 then + env.info('GroupMonitor: processAir ['..group.name..'] engaging targets') + TaskExtensions.heloEngageTargets(gr, tgtToEngage, group.product.expend) + group.isengaging = true + group.startedEngaging = timer.getAbsTime() + elseif group.isengaging and #tgtToEngage == 0 and group.startedEngaging and (timer.getAbsTime() - group.startedEngaging) > 60*5 then + env.info('GroupMonitor: processAir ['..group.name..'] resuming mission') + if group.returning then + group.returning = nil + self:sendHome(group) + else + local homePos = group.home.zone.point + TaskExtensions.executeHeloCasMission(gr, group.target.built, group.product.expend, group.product.altitude, {homePos = homePos}) + end + group.isengaging = false + end + end + elseif group.product.missionType == 'supply_air' then + if not group.returning and group.target and group.target.side ~= group.product.side and group.target.side ~= 0 then + local supplyPoint = trigger.misc.getZone(group.home.name..'-hsp') + if not supplyPoint then + supplyPoint = trigger.misc.getZone(group.home.name) + end + + if supplyPoint then + group.returning = true + local alt = self.connectionManager:getHeliAlt(group.target.name, group.home.name) + TaskExtensions.landAtPointFromAir(gr, {x=supplyPoint.point.x, y=supplyPoint.point.z}, alt) + env.info('GroupMonitor: processAir ['..group.name..'] returning home') + end + end + end + end + elseif group.state =='landed' then + if timer.getAbsTime() - group.lastStateTime > GroupMonitor.landedDespawnTime then + if gr then + env.info('GroupMonitor: processAir ['..group.name..'] despawned after landing') + gr:destroy() + return true + end + end + end + end +end + +-----------------[[ END OF GroupMonitor.lua ]]----------------- + + + +-----------------[[ ConnectionManager.lua ]]----------------- + +ConnectionManager = {} +do + ConnectionManager.currentLineIndex = 5000 + function ConnectionManager:new() + local obj = {} + obj.connections = {} + obj.zoneConnections = {} + obj.heliAlts = {} + obj.blockedRoads = {} + setmetatable(obj, self) + self.__index = self + + return obj + end + + function ConnectionManager:addConnection(f, t, blockedRoad, heliAlt) + local i = ConnectionManager.currentLineIndex + ConnectionManager.currentLineIndex = ConnectionManager.currentLineIndex + 1 + + table.insert(self.connections, {from=f, to=t, index=i}) + self.zoneConnections[f] = self.zoneConnections[f] or {} + self.zoneConnections[t] = self.zoneConnections[t] or {} + self.zoneConnections[f][t] = true + self.zoneConnections[t][f] = true + + if heliAlt then + self.heliAlts[f] = self.heliAlts[f] or {} + self.heliAlts[t] = self.heliAlts[t] or {} + self.heliAlts[f][t] = heliAlt + self.heliAlts[t][f] = heliAlt + end + + if blockedRoad then + self.blockedRoads[f] = self.blockedRoads[f] or {} + self.blockedRoads[t] = self.blockedRoads[t] or {} + self.blockedRoads[f][t] = true + self.blockedRoads[t][f] = true + end + + local from = CustomZone:getByName(f) + local to = CustomZone:getByName(t) + + if not from then env.info("ConnectionManager - addConnection: missing zone "..f) end + if not to then env.info("ConnectionManager - addConnection: missing zone "..t) end + + if blockedRoad then + trigger.action.lineToAll(-1, i, from.point, to.point, {1,1,1,0.5}, 3) + else + trigger.action.lineToAll(-1, i, from.point, to.point, {1,1,1,0.5}, 2) + end + end + + function ConnectionManager:getConnectionsOfZone(zonename) + if not self.zoneConnections[zonename] then return {} end + + local connections = {} + for i,v in pairs(self.zoneConnections[zonename]) do + table.insert(connections, i) + end + + return connections + end + + function ConnectionManager:isRoadBlocked(f,t) + if self.blockedRoads[f] then + return self.blockedRoads[f][t] + end + + if self.blockedRoads[t] then + return self.blockedRoads[t][f] + end + end + + function ConnectionManager:getHeliAltSimple(f,t) + if self.heliAlts[f] then + if self.heliAlts[f][t] then + return self.heliAlts[f][t] + end + end + + if self.heliAlts[t] then + if self.heliAlts[t][f] then + return self.heliAlts[t][f] + end + end + end + + function ConnectionManager:getHeliAlt(f,t) + local alt = self:getHeliAltSimple(f,t) + if alt then return alt end + + if self.heliAlts[f] then + local max = -1 + for zn,_ in pairs(self.heliAlts[f]) do + local alt = self:getHeliAltSimple(f, zn) + if alt then + if alt > max then + max = alt + end + end + + alt = self:getHeliAltSimple(zn, t) + if alt then + if alt > max then + max = alt + end + end + end + + if max > 0 then return max end + end + + if self.heliAlts[t] then + local max = -1 + for zn,_ in pairs(self.heliAlts[t]) do + local alt = self:getHeliAltSimple(t, zn) + if alt then + if alt > max then + max = alt + end + end + + alt = self:getHeliAltSimple(zn, f) + if alt then + if alt > max then + max = alt + end + end + end + + if max > 0 then return max end + end + end +end + +-----------------[[ END OF ConnectionManager.lua ]]----------------- + + + +-----------------[[ TaskExtensions.lua ]]----------------- + +TaskExtensions = {} +do + function TaskExtensions.getAttackTask(targetName, expend, altitude) + local tgt = Group.getByName(targetName) + if tgt then + return { + id = 'AttackGroup', + params = { + groupId = tgt:getID(), + expend = expend, + weaponType = Weapon.flag.AnyWeapon, + groupAttack = true, + altitudeEnabled = (altitude ~= nil), + altitude = altitude + } + } + else + tgt = StaticObject.getByName(targetName) + if not tgt then tgt = Unit.getByName(targetName) end + if tgt then + return { + id = 'AttackUnit', + params = { + unitId = tgt:getID(), + expend = expend, + weaponType = Weapon.flag.AnyWeapon, + groupAttack = true, + altitudeEnabled = (altitude ~= nil), + altitude = altitude + } + } + end + end + end + + function TaskExtensions.getDefaultWaypoints(startPos, task, tgpos, reactivated) + local defwp = { + id='Mission', + params = { + route = { + airborne = true, + points = {} + } + } + } + + if reactivated then + table.insert(defwp.params.route.points, { + type= AI.Task.WaypointType.TURNING_POINT, + x = reactivated.currentPos.x, + y = reactivated.currentPos.z, + speed = 257, + action = AI.Task.TurnMethod.FLY_OVER_POINT, + alt = 4572, + alt_type = AI.Task.AltitudeType.BARO, + task = task + }) + else + table.insert(defwp.params.route.points, { + type= AI.Task.WaypointType.TAKEOFF, + x = startPos.x, + y = startPos.z, + speed = 0, + action = AI.Task.TurnMethod.FIN_POINT, + alt = 0, + alt_type = AI.Task.AltitudeType.RADIO + }) + + table.insert(defwp.params.route.points, { + type= AI.Task.WaypointType.TURNING_POINT, + x = startPos.x, + y = startPos.z, + speed = 257, + action = AI.Task.TurnMethod.FLY_OVER_POINT, + alt = 4572, + alt_type = AI.Task.AltitudeType.BARO, + task = task + }) + end + + if tgpos then + table.insert(defwp.params.route.points, { + type= AI.Task.WaypointType.TURNING_POINT, + x = tgpos.x, + y = tgpos.z, + speed = 257, + action = AI.Task.TurnMethod.FLY_OVER_POINT, + alt = 4572, + alt_type = AI.Task.AltitudeType.BARO, + task = task + }) + end + + table.insert(defwp.params.route.points, { + type= AI.Task.WaypointType.LAND, + x = startPos.x, + y = startPos.z, + speed = 257, + action = AI.Task.TurnMethod.FIN_POINT, + alt = 0, + alt_type = AI.Task.AltitudeType.RADIO + }) + + return defwp + end + + function TaskExtensions.executeSeadMission(group,targets, expend, altitude, reactivated) + if not group then return end + if not group:isExist() or group:getSize()==0 then return end + local startPos = group:getUnit(1):getPoint() + + if reactivated then + reactivated.currentPos = startPos + startPos = reactivated.homePos + end + + local expCount = AI.Task.WeaponExpend.ALL + if expend then + expCount = expend + end + + local alt = 4572 + if altitude then + alt = altitude/3.281 + end + + local viable = {} + for i,v in pairs(targets) do + if v.type == 'defense' and v.side ~= group:getCoalition() then + local gr = Group.getByName(v.name) + for _,unit in ipairs(gr:getUnits()) do + if unit:hasAttribute('SAM SR') or unit:hasAttribute('SAM TR') then + table.insert(viable, unit:getName()) + end + end + end + end + + local attack = { + id = 'ComboTask', + params = { + tasks = { + { + id = 'EngageTargets', + params = { + targetTypes = {'SAM SR', 'SAM TR'} + } + } + } + } + } + + for i,v in ipairs(viable) do + local task = TaskExtensions.getAttackTask(v, expCount, alt) + table.insert(attack.params.tasks, task) + end + + local firstunitpos = nil + local tgt = viable[1] + if tgt then + firstunitpos = Unit.getByName(tgt):getPoint() + end + + local mis = TaskExtensions.getDefaultWaypoints(startPos, attack, firstunitpos, reactivated) + + group:getController():setTask(mis) + TaskExtensions.setDefaultAG(group) + end + + function TaskExtensions.executeStrikeMission(group,targets, expend, altitude, reactivated) + if not group then return end + if not group:isExist() or group:getSize()==0 then return end + local startPos = group:getUnit(1):getPoint() + + if reactivated then + reactivated.currentPos = startPos + startPos = reactivated.homePos + end + + local expCount = AI.Task.WeaponExpend.ALL + if expend then + expCount = expend + end + + local alt = 4572 + if altitude then + alt = altitude/3.281 + end + + local attack = { + id = 'ComboTask', + params = { + tasks = { + } + } + } + + for i,v in pairs(targets) do + if v.type == 'upgrade' and v.side ~= group:getCoalition() then + local task = TaskExtensions.getAttackTask(v.name, expCount, alt) + table.insert(attack.params.tasks, task) + end + end + + local mis = TaskExtensions.getDefaultWaypoints(startPos, attack, nil, reactivated) + + group:getController():setTask(mis) + TaskExtensions.setDefaultAG(group) + end + + function TaskExtensions.executeCasMission(group, targets, expend, altitude, reactivated) + if not group then return end + if not group:isExist() or group:getSize()==0 then return end + local startPos = group:getUnit(1):getPoint() + + if reactivated then + reactivated.currentPos = startPos + startPos = reactivated.homePos + end + + local attack = { + id = 'ComboTask', + params = { + tasks = { + } + } + } + + local expCount = AI.Task.WeaponExpend.ONE + if expend then + expCount = expend + end + + local alt = 4572 + if altitude then + alt = altitude/3.281 + end + + for i,v in pairs(targets) do + if v.type == 'defense' then + local g = Group.getByName(i) + if g and g:getCoalition()~=group:getCoalition() then + local task = TaskExtensions.getAttackTask(i, expCount, alt) + table.insert(attack.params.tasks, task) + end + end + end + + local mis = TaskExtensions.getDefaultWaypoints(startPos, attack, nil, reactivated) + + group:getController():setTask(mis) + TaskExtensions.setDefaultAG(group) + end + + function TaskExtensions.executeBaiMission(group, targets, expend, altitude, reactivated) + if not group then return end + if not group:isExist() or group:getSize()==0 then return end + local startPos = group:getUnit(1):getPoint() + + if reactivated then + reactivated.currentPos = startPos + startPos = reactivated.homePos + end + + local attack = { + id = 'ComboTask', + params = { + tasks = { + { + id = 'EngageTargets', + params = { + targetTypes = {'Vehicles'} + } + } + } + } + } + + local expCount = AI.Task.WeaponExpend.ONE + if expend then + expCount = expend + end + + local alt = 4572 + if altitude then + alt = altitude/3.281 + end + + for i,v in pairs(targets) do + if v.type == 'mission' and (v.missionType == 'assault' or v.missionType == 'supply_convoy') then + local g = Group.getByName(i) + if g and g:getSize()>0 and g:getCoalition()~=group:getCoalition() then + local task = TaskExtensions.getAttackTask(i, expCount, alt) + table.insert(attack.params.tasks, task) + end + end + end + + local mis = TaskExtensions.getDefaultWaypoints(startPos, attack, nil, reactivated) + + group:getController():setTask(mis) + TaskExtensions.setDefaultAG(group) + end + + function TaskExtensions.heloEngageTargets(group, targets, expend) + if not group then return end + if not group:isExist() or group:getSize()==0 then return end + local startPos = group:getUnit(1):getPoint() + + local attack = { + id = 'ComboTask', + params = { + tasks = { + } + } + } + + local expCount = AI.Task.WeaponExpend.ONE + if expend then + expCount = expend + end + + for i,v in pairs(targets) do + local task = { + id = 'AttackUnit', + params = { + unitId = v:getID(), + expend = expend, + weaponType = Weapon.flag.AnyWeapon, + groupAttack = true + } + } + + table.insert(attack.params.tasks, task) + end + + group:getController():pushTask(attack) + end + + function TaskExtensions.executeHeloCasMission(group, targets, expend, altitude, reactivated) + if not group then return end + if not group:isExist() or group:getSize()==0 then return end + local startPos = group:getUnit(1):getPoint() + + if reactivated then + reactivated.currentPos = startPos + startPos = reactivated.homePos + end + + local attack = { + id = 'ComboTask', + params = { + tasks = { + } + } + } + + local expCount = AI.Task.WeaponExpend.ONE + if expend then + expCount = expend + end + + local alt = 61 + if altitude then + alt = altitude/3.281 + end + + for i,v in pairs(targets) do + if v.type == 'defense' then + local g = Group.getByName(i) + if g and g:getCoalition()~=group:getCoalition() then + local task = TaskExtensions.getAttackTask(i, expCount, alt) + table.insert(attack.params.tasks, task) + end + end + end + + local land = { + id='Land', + params = { + point = {x = startPos.x, y=startPos.z} + } + } + + local mis = { + id='Mission', + params = { + route = { + airborne = true, + points = {} + } + } + } + + if reactivated then + table.insert(mis.params.route.points, { + type= AI.Task.WaypointType.TURNING_POINT, + x = reactivated.currentPos.x+1000, + y = reactivated.currentPos.z+1000, + speed = 257, + action = AI.Task.TurnMethod.FLY_OVER_POINT, + alt = alt, + alt_type = AI.Task.AltitudeType.RADIO, + task = attack + }) + else + table.insert(mis.params.route.points, { + type= AI.Task.WaypointType.TAKEOFF, + x = startPos.x, + y = startPos.z, + speed = 0, + action = AI.Task.TurnMethod.FIN_POINT, + alt = 0, + alt_type = AI.Task.AltitudeType.RADIO, + }) + + table.insert(mis.params.route.points, { + type= AI.Task.WaypointType.TURNING_POINT, + x = startPos.x+1000, + y = startPos.z+1000, + speed = 257, + action = AI.Task.TurnMethod.FLY_OVER_POINT, + alt = alt, + alt_type = AI.Task.AltitudeType.RADIO, + task = attack + }) + end + + table.insert(mis.params.route.points, { + type= AI.Task.WaypointType.TURNING_POINT, + x = startPos.x, + y = startPos.z, + speed = 257, + action = AI.Task.TurnMethod.FIN_POINT, + alt = alt, + alt_type = AI.Task.AltitudeType.RADIO, + task = land + }) + + group:getController():setTask(mis) + TaskExtensions.setDefaultAG(group) + end + + function TaskExtensions.executeTankerMission(group, point, altitude, frequency, tacan, reactivated) + if not group then return end + if not group:isExist() or group:getSize()==0 then return end + local startPos = group:getUnit(1):getPoint() + if reactivated then + reactivated.currentPos = startPos + startPos = reactivated.homePos + end + + local alt = 4572 + if altitude then + alt = altitude/3.281 + end + + local freq = 259500000 + if frequency then + freq = math.floor(frequency*1000000) + end + + local setfreq = { + id = 'SetFrequency', + params = { + frequency = freq, + modulation = 0 + } + } + + local setbeacon = { + id = 'ActivateBeacon', + params = { + type = 4, -- TACAN type + system = 4, -- Tanker TACAN + name = 'tacan task', + callsign = group:getUnit(1):getCallsign():sub(1,3), + frequency = tacan + } + } + + local distFromPoint = 20000 + local theta = math.random() * 2 * math.pi + + local dx = distFromPoint * math.cos(theta) + local dy = distFromPoint * math.sin(theta) + + local pos1 = { + x = point.x + dx, + y = point.z + dy + } + + local pos2 = { + x = point.x - dx, + y = point.z - dy + } + + local orbit = { + id = 'Orbit', + params = { + pattern = AI.Task.OrbitPattern.RACE_TRACK, + point = pos1, + point2 = pos2, + speed = 195, + altitude = alt + } + } + + local script = { + id = "WrappedAction", + params = { + action = { + id = "Script", + params = + { + command = "trigger.action.outTextForCoalition("..group:getCoalition()..", 'Tanker on station. "..(freq/1000000).." AM', 15)", + } + } + } + } + + local tanker = { + id = 'Tanker', + params = { + } + } + + local task = { + id='Mission', + params = { + route = { + airborne = true, + points = {} + } + } + } + + if reactivated then + table.insert(task.params.route.points, { + type= AI.Task.WaypointType.TURNING_POINT, + x = pos1.x, + y = pos1.y, + speed = 450, + action = AI.Task.TurnMethod.FLY_OVER_POINT, + alt = alt, + alt_type = AI.Task.AltitudeType.BARO, + task = tanker + }) + else + table.insert(task.params.route.points, { + type= AI.Task.WaypointType.TAKEOFF, + x = startPos.x, + y = startPos.z, + speed = 0, + action = AI.Task.TurnMethod.FIN_POINT, + alt = 0, + alt_type = AI.Task.AltitudeType.RADIO, + task = tanker + }) + end + + table.insert(task.params.route.points, { + type= AI.Task.WaypointType.TURNING_POINT, + x = pos1.x, + y = pos1.y, + speed = 195, + action = AI.Task.TurnMethod.FLY_OVER_POINT, + alt = alt, + alt_type = AI.Task.AltitudeType.BARO, + task = { + id = 'ComboTask', + params = { + tasks = { + script + } + } + } + }) + + table.insert(task.params.route.points, { + type= AI.Task.WaypointType.TURNING_POINT, + x = pos2.x, + y = pos2.y, + speed = 195, + action = AI.Task.TurnMethod.FLY_OVER_POINT, + alt = alt, + alt_type = AI.Task.AltitudeType.BARO, + task = { + id = 'ComboTask', + params = { + tasks = { + orbit + } + } + } + }) + + table.insert(task.params.route.points, { + type= AI.Task.WaypointType.LAND, + x = startPos.x, + y = startPos.z, + speed = 450, + action = AI.Task.TurnMethod.FIN_POINT, + alt = 0, + alt_type = AI.Task.AltitudeType.RADIO + }) + + group:getController():setTask(task) + group:getController():setOption(AI.Option.Air.id.REACTION_ON_THREAT, AI.Option.Air.val.REACTION_ON_THREAT.PASSIVE_DEFENCE) + group:getController():setCommand(setfreq) + group:getController():setCommand(setbeacon) + end + + function TaskExtensions.executeAwacsMission(group, point, altitude, frequency, reactivated) + if not group then return end + if not group:isExist() or group:getSize()==0 then return end + local startPos = group:getUnit(1):getPoint() + if reactivated then + reactivated.currentPos = startPos + startPos = reactivated.homePos + end + + local alt = 4572 + if altitude then + alt = altitude/3.281 + end + + local freq = 259500000 + if frequency then + freq = math.floor(frequency*1000000) + end + + local setfreq = { + id = 'SetFrequency', + params = { + frequency = freq, + modulation = 0 + } + } + + local distFromPoint = 10000 + local theta = math.random() * 2 * math.pi + + local dx = distFromPoint * math.cos(theta) + local dy = distFromPoint * math.sin(theta) + + local pos1 = { + x = point.x + dx, + y = point.z + dy + } + + local pos2 = { + x = point.x - dx, + y = point.z - dy + } + + local orbit = { + id = 'Orbit', + params = { + pattern = AI.Task.OrbitPattern.RACE_TRACK, + point = pos1, + point2 = pos2, + altitude = alt + } + } + + local script = { + id = "WrappedAction", + params = { + action = { + id = "Script", + params = + { + command = "trigger.action.outTextForCoalition("..group:getCoalition()..", 'AWACS on station. "..(freq/1000000).." AM', 15)", + } + } + } + } + + local awacs = { + id = 'AWACS', + params = { + } + } + + local task = { + id='Mission', + params = { + route = { + airborne = true, + points = {} + } + } + } + + if reactivated then + table.insert(task.params.route.points, { + type= AI.Task.WaypointType.TURNING_POINT, + x = pos1.x, + y = pos1.y, + speed = 257, + action = AI.Task.TurnMethod.FLY_OVER_POINT, + alt = alt, + alt_type = AI.Task.AltitudeType.BARO, + task = awacs + }) + else + table.insert(task.params.route.points, { + type= AI.Task.WaypointType.TAKEOFF, + x = startPos.x, + y = startPos.z, + speed = 0, + action = AI.Task.TurnMethod.FIN_POINT, + alt = 0, + alt_type = AI.Task.AltitudeType.RADIO, + task = awacs + }) + end + + table.insert(task.params.route.points, { + type= AI.Task.WaypointType.TURNING_POINT, + x = pos1.x, + y = pos1.y, + speed = 257, + action = AI.Task.TurnMethod.FLY_OVER_POINT, + alt = alt, + alt_type = AI.Task.AltitudeType.BARO, + task = { + id = 'ComboTask', + params = { + tasks = { + script + } + } + } + }) + + table.insert(task.params.route.points, { + type= AI.Task.WaypointType.TURNING_POINT, + x = pos2.x, + y = pos2.y, + speed = 257, + action = AI.Task.TurnMethod.FLY_OVER_POINT, + alt = alt, + alt_type = AI.Task.AltitudeType.BARO, + task = { + id = 'ComboTask', + params = { + tasks = { + orbit + } + } + } + }) + + table.insert(task.params.route.points, { + type= AI.Task.WaypointType.LAND, + x = startPos.x, + y = startPos.z, + speed = 257, + action = AI.Task.TurnMethod.FIN_POINT, + alt = 0, + alt_type = AI.Task.AltitudeType.RADIO + }) + + group:getController():setTask(task) + group:getController():setOption(AI.Option.Air.id.REACTION_ON_THREAT, AI.Option.Air.val.REACTION_ON_THREAT.PASSIVE_DEFENCE) + group:getController():setCommand(setfreq) + end + + function TaskExtensions.executePatrolMission(group, point, altitude, range, reactivated) + if not group then return end + if not group:isExist() or group:getSize()==0 then return end + local startPos = group:getUnit(1):getPoint() + + if reactivated then + reactivated.currentPos = startPos + startPos = reactivated.homePos + end + + local rng = 25 * 1852 + if range then + rng = range * 1852 + end + + local alt = 4572 + if altitude then + alt = altitude/3.281 + end + + local search = { + id = 'EngageTargets', + params = { + maxDist = rng, + targetTypes = { 'Planes', 'Helicopters' } + } + } + + local distFromPoint = 10000 + local theta = math.random() * 2 * math.pi + + local dx = distFromPoint * math.cos(theta) + local dy = distFromPoint * math.sin(theta) + + local p1 = { + x = point.x + dx, + y = point.z + dy + } + + local p2 = { + x = point.x - dx, + y = point.z - dy + } + + local orbit = { + id = 'Orbit', + params = { + pattern = AI.Task.OrbitPattern.RACE_TRACK, + point = p1, + point2 = p2, + speed = 154, + altitude = alt + } + } + + local task = { + id='Mission', + params = { + route = { + airborne = true, + points = {} + } + } + } + + if not reactivated then + table.insert(task.params.route.points, { + type= AI.Task.WaypointType.TAKEOFF, + x = startPos.x, + y = startPos.z, + speed = 0, + action = AI.Task.TurnMethod.FIN_POINT, + alt = 0, + alt_type = AI.Task.AltitudeType.RADIO, + task = search + }) + else + table.insert(task.params.route.points, { + type= AI.Task.WaypointType.TURNING_POINT, + x = reactivated.currentPos.x, + y = reactivated.currentPos.z, + speed = 257, + action = AI.Task.TurnMethod.FIN_POINT, + alt = alt, + alt_type = AI.Task.AltitudeType.BARO, + task = search + }) + end + + table.insert(task.params.route.points, { + type= AI.Task.WaypointType.TURNING_POINT, + x = p1.x, + y = p1.y, + speed = 257, + action = AI.Task.TurnMethod.FLY_OVER_POINT, + alt = alt, + alt_type = AI.Task.AltitudeType.BARO + }) + + table.insert(task.params.route.points, { + type= AI.Task.WaypointType.TURNING_POINT, + x = p2.x, + y = p2.y, + speed = 257, + action = AI.Task.TurnMethod.FLY_OVER_POINT, + alt = alt, + alt_type = AI.Task.AltitudeType.BARO, + task = orbit + }) + + table.insert(task.params.route.points, { + type= AI.Task.WaypointType.LAND, + x = startPos.x, + y = startPos.z, + speed = 257, + action = AI.Task.TurnMethod.FIN_POINT, + alt = 0, + alt_type = AI.Task.AltitudeType.RADIO + }) + + group:getController():setTask(task) + TaskExtensions.setDefaultAA(group) + end + + function TaskExtensions.setDefaultAA(group) + group:getController():setOption(AI.Option.Air.id.PROHIBIT_AG, true) + group:getController():setOption(AI.Option.Air.id.JETT_TANKS_IF_EMPTY, true) + group:getController():setOption(AI.Option.Air.id.PROHIBIT_JETT, true) + group:getController():setOption(AI.Option.Air.id.REACTION_ON_THREAT, AI.Option.Air.val.REACTION_ON_THREAT.EVADE_FIRE) + group:getController():setOption(AI.Option.Air.id.MISSILE_ATTACK, AI.Option.Air.val.MISSILE_ATTACK.MAX_RANGE) + + local weapons = 268402688 -- AnyMissile + group:getController():setOption(AI.Option.Air.id.RTB_ON_OUT_OF_AMMO, weapons) + end + + function TaskExtensions.setDefaultAG(group) + --group:getController():setOption(AI.Option.Air.id.PROHIBIT_AA, true) + group:getController():setOption(AI.Option.Air.id.JETT_TANKS_IF_EMPTY, true) + group:getController():setOption(AI.Option.Air.id.PROHIBIT_JETT, true) + group:getController():setOption(AI.Option.Air.id.REACTION_ON_THREAT, AI.Option.Air.val.REACTION_ON_THREAT.EVADE_FIRE) + + local weapons = 2147485694 + 30720 + 4161536 -- AnyBomb + AnyRocket + AnyASM + group:getController():setOption(AI.Option.Air.id.RTB_ON_OUT_OF_AMMO, weapons) + end + + function TaskExtensions.stopAndDisperse(group) + if not group then return end + if not group:isExist() or group:getSize()==0 then return end + local pos = group:getUnit(1):getPoint() + group:getController():setTask({ + id='Mission', + params = { + route = { + points = { + [1] = { + type= AI.Task.WaypointType.TURNING_POINT, + x = pos.x, + y = pos.z, + speed = 1000, + action = AI.Task.VehicleFormation.OFF_ROAD + }, + [2] = { + type= AI.Task.WaypointType.TURNING_POINT, + x = pos.x+math.random(25), + y = pos.z+math.random(25), + speed = 1000, + action = AI.Task.VehicleFormation.DIAMOND + }, + } + } + } + }) + end + + function TaskExtensions.moveOnRoadToPointAndAssault(group, point, targets) + if not group or not point then return end + if not group:isExist() or group:getSize()==0 then return end + local startPos = group:getUnit(1):getPoint() + + local srx, sry = land.getClosestPointOnRoads('roads', startPos.x, startPos.z) + local erx, ery = land.getClosestPointOnRoads('roads', point.x, point.y) + + local mis = { + id='Mission', + params = { + route = { + points = { + [1] = { + type= AI.Task.WaypointType.TURNING_POINT, + x = srx, + y = sry, + speed = 1000, + action = AI.Task.VehicleFormation.ON_ROAD + }, + [2] = { + type= AI.Task.WaypointType.TURNING_POINT, + x = erx, + y = ery, + speed = 1000, + action = AI.Task.VehicleFormation.ON_ROAD + }, + [3] = { + type= AI.Task.WaypointType.TURNING_POINT, + x = point.x, + y = point.y, + speed = 1000, + action = AI.Task.VehicleFormation.DIAMOND + } + } + } + } + } + + for i,v in pairs(targets) do + if v.type == 'defense' then + local group = Group.getByName(v.name) + if group then + for i,v in ipairs(group:getUnits()) do + local unpos = v:getPoint() + local pnt = {x=unpos.x, y = unpos.z} + + table.insert(mis.params.route.points, { + type= AI.Task.WaypointType.TURNING_POINT, + x = pnt.x, + y = pnt.y, + speed = 10, + action = AI.Task.VehicleFormation.DIAMOND + }) + end + end + end + end + group:getController():setTask(mis) + end + + function TaskExtensions.moveOnRoadToPoint(group, point) -- point = {x,y} + if not group or not point then return end + if not group:isExist() or group:getSize()==0 then return end + local startPos = group:getUnit(1):getPoint() + + local srx, sry = land.getClosestPointOnRoads('roads', startPos.x, startPos.z) + local erx, ery = land.getClosestPointOnRoads('roads', point.x, point.y) + + local mis = { + id='Mission', + params = { + route = { + points = { + [1] = { + type= AI.Task.WaypointType.TURNING_POINT, + x = srx, + y = sry, + speed = 1000, + action = AI.Task.VehicleFormation.ON_ROAD + }, + [2] = { + type= AI.Task.WaypointType.TURNING_POINT, + x = erx, + y = ery, + speed = 1000, + action = AI.Task.VehicleFormation.ON_ROAD + }, + [3] = { + type= AI.Task.WaypointType.TURNING_POINT, + x = point.x, + y = point.y, + speed = 1000, + action = AI.Task.VehicleFormation.OFF_ROAD + } + } + } + } + } + group:getController():setTask(mis) + end + + function TaskExtensions.landAtPointFromAir(group, point, alt) + if not group or not point then return end + if not group:isExist() or group:getSize()==0 then return end + local startPos = group:getUnit(1):getPoint() + + local atype = AI.Task.AltitudeType.RADIO + if alt then + atype = AI.Task.AltitudeType.BARO + else + alt = 500 + end + + local land = { + id='Land', + params = { + point = point + } + } + + local mis = { + id='Mission', + params = { + route = { + airborne = true, + points = { + [1] = { + type= AI.Task.WaypointType.TURNING_POINT, + x = startPos.x, + y = startPos.z, + speed = 500, + action = AI.Task.TurnMethod.FIN_POINT, + alt = alt, + alt_type = atype + }, + [2] = { + type= AI.Task.WaypointType.TURNING_POINT, + x = point.x, + y = point.y, + speed = 257, + action = AI.Task.TurnMethod.FIN_POINT, + alt = alt, + alt_type = atype, + task = land + } + } + } + } + } + + group:getController():setTask(mis) + end + + function TaskExtensions.landAtPoint(group, point, alt) -- point = {x,y} + if not group or not point then return end + if not group:isExist() or group:getSize()==0 then return end + local startPos = group:getUnit(1):getPoint() + + local atype = AI.Task.AltitudeType.RADIO + if alt then + atype = AI.Task.AltitudeType.BARO + else + alt = 500 + end + + local land = { + id='Land', + params = { + point = point + } + } + + local mis = { + id='Mission', + params = { + route = { + airborne = true, + points = { + [1] = { + type = AI.Task.WaypointType.TAKEOFF, + x = startPos.x, + y = startPos.z, + speed = 0, + action = AI.Task.TurnMethod.FIN_POINT, + alt = alt, + alt_type = atype + }, + [2] = { + type = AI.Task.WaypointType.TURNING_POINT, + x = point.x, + y = point.y, + speed = 257, + action = AI.Task.TurnMethod.FIN_POINT, + alt = alt, + alt_type = atype, + task = land + } + } + } + } + } + + group:getController():setTask(mis) + end + + function TaskExtensions.landAtAirfield(group, point) -- point = {x,y} + if not group or not point then return end + if not group:isExist() or group:getSize()==0 then return end + + local mis = { + id='Mission', + params = { + route = { + airborne = true, + points = { + [1] = { + type= AI.Task.WaypointType.TURNING_POINT, + x = point.x, + y = point.z, + speed = 257, + action = AI.Task.TurnMethod.FIN_POINT, + alt = 4572, + alt_type = AI.Task.AltitudeType.BARO + }, + [2] = { + type= AI.Task.WaypointType.LAND, + x = point.x, + y = point.z, + speed = 257, + action = AI.Task.TurnMethod.FIN_POINT, + alt = 0, + alt_type = AI.Task.AltitudeType.RADIO + } + } + } + } + } + + group:getController():setTask(mis) + end +end + +-----------------[[ END OF TaskExtensions.lua ]]----------------- + + + +-----------------[[ PlayerLogistics.lua ]]----------------- + +PlayerLogistics = {} +do + PlayerLogistics.allowedTypes = {} + PlayerLogistics.allowedTypes['Mi-24P'] = { supplies = true, personCapacity = 8 } + PlayerLogistics.allowedTypes['Mi-8MT'] = { supplies = true, personCapacity = 24 } + PlayerLogistics.allowedTypes['UH-1H'] = { supplies = true, personCapacity = 12} + PlayerLogistics.allowedTypes['Hercules'] = { supplies = true, personCapacity = 92 } + PlayerLogistics.allowedTypes['UH-60L'] = { supplies = true, personCapacity = 12 } + PlayerLogistics.allowedTypes['Ka-50'] = { supplies = false } + PlayerLogistics.allowedTypes['Ka-50_3'] = { supplies = false } + PlayerLogistics.allowedTypes['SA342L'] = { supplies = false, personCapacity = 2} + PlayerLogistics.allowedTypes['SA342M'] = { supplies = false, personCapacity = 2} + PlayerLogistics.allowedTypes['SA342Minigun'] = { supplies = false, personCapacity = 2} + PlayerLogistics.allowedTypes['AH-64D_BLK_II'] = { supplies = false } + + PlayerLogistics.infantryTypes = { + capture = 'capture', + sabotage = 'sabotage', + ambush = 'ambush', + engineer = 'engineer', + manpads = 'manpads', + spy = 'spy', + rapier = 'rapier', + extractable = 'extractable' + } + + function PlayerLogistics.getInfantryName(infType) + if infType==PlayerLogistics.infantryTypes.capture then + return "Capture Squad" + elseif infType==PlayerLogistics.infantryTypes.sabotage then + return "Sabotage Squad" + elseif infType==PlayerLogistics.infantryTypes.ambush then + return "Ambush Squad" + elseif infType==PlayerLogistics.infantryTypes.engineer then + return "Engineer" + elseif infType==PlayerLogistics.infantryTypes.manpads then + return "MANPADS" + elseif infType==PlayerLogistics.infantryTypes.spy then + return "Spy" + elseif infType==PlayerLogistics.infantryTypes.rapier then + return "Rapier SAM" + elseif infType==PlayerLogistics.infantryTypes.extractable then + return "Extracted infantry" + end + + return "INVALID SQUAD" + end + + function PlayerLogistics:new(misTracker, plyTracker, squadTracker, csarTracker) + local obj = {} + obj.groupMenus = {} -- groupid = path + obj.carriedCargo = {} -- groupid = source + obj.carriedInfantry = {} -- groupid = source + obj.carriedPilots = {} --groupid = source + obj.registeredSquadGroups = {} + obj.lastLoaded = {} -- groupid = zonename + obj.missionTracker = misTracker + obj.playerTracker = plyTracker + obj.squadTracker = squadTracker + obj.csarTracker = csarTracker + + obj.hercTracker = { + cargos = {}, + cargoCheckFunctions = {} + } + + obj.hercPreparedDrops = {} + + setmetatable(obj, self) + self.__index = self + + obj:start() + + return obj + end + + function PlayerLogistics:registerSquadGroup(squadType, groupname, weight, cost, jobtime, extracttime, squadSize) + self.registeredSquadGroups[squadType] = { name=groupname, type=squadType, weight=weight, cost=cost, jobtime=jobtime, extracttime=extracttime, size = squadSize} + end + + function PlayerLogistics:start() + if not ZoneCommand then return end + + MenuRegistry:register(3, function(event, context) + if event.id == world.event.S_EVENT_BIRTH and event.initiator and event.initiator.getPlayerName then + local player = event.initiator:getPlayerName() + if player then + local unitType = event.initiator:getDesc()['typeName'] + local groupid = event.initiator:getGroup():getID() + local groupname = event.initiator:getGroup():getName() + + local logistics = context.allowedTypes[unitType] + if logistics and (logistics.supplies or logistics.personCapacity)then + + if context.groupMenus[groupid] then + missionCommands.removeItemForGroup(groupid, context.groupMenus[groupid]) + context.groupMenus[groupid] = nil + end + + if not context.groupMenus[groupid] then + local size = event.initiator:getGroup():getSize() + if size > 1 then + trigger.action.outText('WARNING: group '..groupname..' has '..size..' units. Logistics will only function for group leader', 10) + end + + local cargomenu = missionCommands.addSubMenuForGroup(groupid, 'Logistics') + if logistics.supplies then + local supplyMenu = missionCommands.addSubMenuForGroup(groupid, 'Supplies', cargomenu) + local loadMenu = missionCommands.addSubMenuForGroup(groupid, 'Load', supplyMenu) + missionCommands.addCommandForGroup(groupid, 'Load 100 supplies', loadMenu, Utils.log(context.loadSupplies), context, {group=groupname, amount=100}) + missionCommands.addCommandForGroup(groupid, 'Load 500 supplies', loadMenu, Utils.log(context.loadSupplies), context, {group=groupname, amount=500}) + missionCommands.addCommandForGroup(groupid, 'Load 1000 supplies', loadMenu, Utils.log(context.loadSupplies), context, {group=groupname, amount=1000}) + missionCommands.addCommandForGroup(groupid, 'Load 2000 supplies', loadMenu, Utils.log(context.loadSupplies), context, {group=groupname, amount=2000}) + missionCommands.addCommandForGroup(groupid, 'Load 5000 supplies', loadMenu, Utils.log(context.loadSupplies), context, {group=groupname, amount=5000}) + + local unloadMenu = missionCommands.addSubMenuForGroup(groupid, 'Unload', supplyMenu) + missionCommands.addCommandForGroup(groupid, 'Unload 100 supplies', unloadMenu, Utils.log(context.unloadSupplies), context, {group=groupname, amount=100}) + missionCommands.addCommandForGroup(groupid, 'Unload 500 supplies', unloadMenu, Utils.log(context.unloadSupplies), context, {group=groupname, amount=500}) + missionCommands.addCommandForGroup(groupid, 'Unload 1000 supplies', unloadMenu, Utils.log(context.unloadSupplies), context, {group=groupname, amount=1000}) + missionCommands.addCommandForGroup(groupid, 'Unload 2000 supplies', unloadMenu, Utils.log(context.unloadSupplies), context, {group=groupname, amount=2000}) + missionCommands.addCommandForGroup(groupid, 'Unload 5000 supplies', unloadMenu, Utils.log(context.unloadSupplies), context, {group=groupname, amount=5000}) + missionCommands.addCommandForGroup(groupid, 'Unload all supplies', unloadMenu, Utils.log(context.unloadSupplies), context, {group=groupname, amount=9999999}) + end + + local sqs = {} + for sqType,_ in pairs(context.registeredSquadGroups) do + table.insert(sqs,sqType) + end + table.sort(sqs) + + if logistics.personCapacity then + local infMenu = missionCommands.addSubMenuForGroup(groupid, 'Infantry', cargomenu) + + local loadInfMenu = missionCommands.addSubMenuForGroup(groupid, 'Load', infMenu) + for _,sqType in ipairs(sqs) do + local menuName = 'Load '..PlayerLogistics.getInfantryName(sqType) + missionCommands.addCommandForGroup(groupid, menuName, loadInfMenu, Utils.log(context.loadInfantry), context, {group=groupname, type=sqType}) + end + + local unloadInfMenu = missionCommands.addSubMenuForGroup(groupid, 'Unload', infMenu) + for _,sqType in ipairs(sqs) do + local menuName = 'Unload '..PlayerLogistics.getInfantryName(sqType) + missionCommands.addCommandForGroup(groupid, menuName, unloadInfMenu, Utils.log(context.unloadInfantry), context, {group=groupname, type=sqType}) + end + missionCommands.addCommandForGroup(groupid, 'Unload Extracted squad', unloadInfMenu, Utils.log(context.unloadInfantry), context, {group=groupname, type=PlayerLogistics.infantryTypes.extractable}) + + missionCommands.addCommandForGroup(groupid, 'Extract squad', infMenu, Utils.log(context.extractSquad), context, groupname) + missionCommands.addCommandForGroup(groupid, 'Unload all', infMenu, Utils.log(context.unloadInfantry), context, {group=groupname}) + + local csarMenu = missionCommands.addSubMenuForGroup(groupid, 'CSAR', cargomenu) + missionCommands.addCommandForGroup(groupid, 'Show info (closest)', csarMenu, Utils.log(context.showPilot), context, groupname) + missionCommands.addCommandForGroup(groupid, 'Smoke marker (closest)', csarMenu, Utils.log(context.smokePilot), context, groupname) + missionCommands.addCommandForGroup(groupid, 'Flare (closest)', csarMenu, Utils.log(context.flarePilot), context, groupname) + missionCommands.addCommandForGroup(groupid, 'Extract pilot', csarMenu, Utils.log(context.extractPilot), context, groupname) + missionCommands.addCommandForGroup(groupid, 'Unload pilots', csarMenu, Utils.log(context.unloadPilots), context, groupname) + end + + missionCommands.addCommandForGroup(groupid, 'Cargo status', cargomenu, Utils.log(context.cargoStatus), context, groupname) + missionCommands.addCommandForGroup(groupid, 'Unload Everything', cargomenu, Utils.log(context.unloadAll), context, groupname) + + if unitType == 'Hercules' then + local loadmasterMenu = missionCommands.addSubMenuForGroup(groupid, 'Loadmaster', cargomenu) + + for _,sqType in ipairs(sqs) do + local menuName = 'Prepare '..PlayerLogistics.getInfantryName(sqType) + missionCommands.addCommandForGroup(groupid, menuName, loadmasterMenu, Utils.log(context.hercPrepareDrop), context, {group=groupname, type=sqType}) + end + + missionCommands.addCommandForGroup(groupid, 'Prepare Supplies', loadmasterMenu, Utils.log(context.hercPrepareDrop), context, {group=groupname, type='supplies'}) + end + + + context.groupMenus[groupid] = cargomenu + end + + if context.carriedCargo[groupid] then + context.carriedCargo[groupid] = 0 + end + + if context.carriedInfantry[groupid] then + context.carriedInfantry[groupid] = {} + end + + if context.carriedPilots[groupid] then + context.carriedPilots[groupid] = {} + end + + if context.lastLoaded[groupid] then + context.lastLoaded[groupid] = nil + end + + if context.hercPreparedDrops[groupid] then + context.hercPreparedDrops[groupid] = nil + end + end + end + elseif (event.id == world.event.S_EVENT_PLAYER_LEAVE_UNIT or event.id == world.event.S_EVENT_DEAD) and event.initiator and event.initiator.getPlayerName then + local player = event.initiator:getPlayerName() + if player then + local groupid = event.initiator:getGroup():getID() + + if context.groupMenus[groupid] then + missionCommands.removeItemForGroup(groupid, context.groupMenus[groupid]) + context.groupMenus[groupid] = nil + end + end + end + end, self) + + local ev = {} + ev.context = self + function ev:onEvent(event) + if event.id == world.event.S_EVENT_SHOT and event.initiator and event.initiator:isExist() then + local unitName = event.initiator:getName() + local groupId = event.initiator:getGroup():getID() + local name = event.weapon:getDesc().typeName + if name == 'weapons.bombs.Generic Crate [20000lb]' then + local prepared = self.context.hercPreparedDrops[groupId] + + if not prepared then + prepared = 'supplies' + + if self.context.carriedInfantry[groupId] then + for _,v in ipairs(self.context.carriedInfantry[groupId]) do + if v.type ~= PlayerLogistics.infantryTypes.extractable then + prepared = v.type + break + end + end + end + + env.info('PlayerLogistics - Hercules - auto preparing '..prepared) + end + + if prepared then + if prepared == 'supplies' then + env.info('PlayerLogistics - Hercules - supplies getting dropped') + local carried = self.context.carriedCargo[groupId] + local amount = 0 + if carried and carried > 0 then + amount = 9000 + if carried < amount then + amount = carried + end + end + + if amount > 0 then + self.context.carriedCargo[groupId] = math.max(0,self.context.carriedCargo[groupId] - amount) + if not self.context.hercTracker.cargos[unitName] then + self.context.hercTracker.cargos[unitName] = {} + end + + table.insert(self.context.hercTracker.cargos[unitName],{ + object = event.weapon, + supply = amount, + lastLoaded = self.context.lastLoaded[groupId], + unit = event.initiator + }) + + env.info('PlayerLogistics - Hercules - '..unitName..'deployed crate with '..amount..' supplies') + self.context:processHercCargos(unitName) + self.context.hercPreparedDrops[groupId] = nil + trigger.action.outTextForUnit(event.initiator:getID(), 'Crate with '..amount..' supplies deployed', 10) + else + trigger.action.outTextForUnit(event.initiator:getID(), 'Empty crate deployed', 10) + end + else + env.info('PlayerLogistics - Hercules - searching for prepared infantry') + local toDrop = nil + local remaining = {} + if self.context.carriedInfantry[groupId] then + for _,v in ipairs(self.context.carriedInfantry[groupId]) do + if v.type == prepared and toDrop == nil then + toDrop = v + else + table.insert(remaining, v) + end + end + end + + + if toDrop then + env.info('PlayerLogistics - Hercules - dropping '..toDrop.type) + if not self.context.hercTracker.cargos[unitName] then + self.context.hercTracker.cargos[unitName] = {} + end + + table.insert(self.context.hercTracker.cargos[unitName],{ + object = event.weapon, + squad = toDrop, + lastLoaded = self.context.lastLoaded[groupId], + unit = event.initiator + }) + + env.info('PlayerLogistics - Hercules - '..unitName..'deployed crate with '..toDrop.type) + self.context:processHercCargos(unitName) + self.context.hercPreparedDrops[groupId] = nil + + local squadName = PlayerLogistics.getInfantryName(prepared) + trigger.action.outTextForUnit(event.initiator:getID(), squadName..' crate deployed.', 10) + self.context.carriedInfantry[groupId] = remaining + local weight = self.context:getCarriedPersonWeight(event.initiator:getGroup():getName()) + trigger.action.setUnitInternalCargo(event.initiator:getName(), weight) + else + trigger.action.outTextForUnit(event.initiator:getID(), 'Empty crate deployed', 10) + end + end + else + trigger.action.outTextForUnit(event.initiator:getID(), 'Empty crate deployed', 10) + end + end + end + end + + world.addEventHandler(ev) + end + + function PlayerLogistics:processHercCargos(unitName) + if not self.hercTracker.cargoCheckFunctions[unitName] then + env.info('PlayerLogistics - Hercules - start tracking cargos of '..unitName) + self.hercTracker.cargoCheckFunctions[unitName] = timer.scheduleFunction(function(params, time) + local reschedule = params.context:checkHercCargo(params.unitName, time) + if not reschedule then + params.context.hercTracker.cargoCheckFunctions[params.unitName] = nil + end + + return reschedule + end, {unitName=unitName, context = self}, timer.getTime() + 0.1) + end + end + + function PlayerLogistics:checkHercCargo(unitName, time) + local cargos = self.hercTracker.cargos[unitName] + if cargos and #cargos > 0 then + local remaining = {} + for _,cargo in ipairs(cargos) do + if cargo.object and cargo.object:isExist() then + local alt = Utils.getAGL(cargo.object) + if alt < 5 then + self:deliverHercCargo(cargo) + else + table.insert(remaining, cargo) + end + else + env.info('PlayerLogistics - Hercules - cargo crashed') + if cargo.unit and cargo.unit:isExist() then + trigger.action.outTextForUnit(cargo.unit:getID(), 'Cargo drop of '..cargo.unit:getPlayerName()..' crashed', 10) + end + end + end + + if #remaining > 0 then + self.hercTracker.cargos[unitName] = remaining + return time + 0.1 + end + end + end + + function PlayerLogistics:deliverHercCargo(cargo) + if cargo.object and cargo.object:isExist() then + if cargo.supply then + local zone = ZoneCommand.getZoneOfWeapon(cargo.object) + if zone then + zone:addResource(cargo.supply) + cargo.object:destroy() + env.info('PlayerLogistics - Hercules - '..cargo.supply..' delivered to '..zone.name) + + self:awardSupplyXP(cargo.lastLoaded, zone, cargo.unit, cargo.supply) + end + elseif cargo.squad then + local pos = Utils.getPointOnSurface(cargo.object:getPoint()) + local surface = land.getSurfaceType(pos) + if surface == land.SurfaceType.LAND or surface == land.SurfaceType.ROAD or surface == land.SurfaceType.RUNWAY then + local zn = ZoneCommand.getZoneOfPoint(pos) + + local lastLoad = cargo.squad.loadedAt + if lastLoad and zn and zn.side == cargo.object:getCoalition() and zn.name==lastLoad.name then + if self.registeredSquadGroups[cargo.squad.type] then + local cost = self.registeredSquadGroups[cargo.squad.type].cost + zn:addResource(cost) + zn:refreshText() + if cargo.unit and cargo.unit:isExist() then + local squadName = PlayerLogistics.getInfantryName(cargo.squad.type) + trigger.action.outTextForUnit(cargo.unit:getID(), squadName..' unloaded', 10) + end + end + else + local error = self.squadTracker:spawnInfantry(self.registeredSquadGroups[cargo.squad.type], pos) + if not error then + cargo.object:destroy() + env.info('PlayerLogistics - Hercules - '..cargo.squad.type..' deployed') + + local squadName = PlayerLogistics.getInfantryName(cargo.squad.type) + trigger.action.outTextForUnit(cargo.unit:getID(), squadName..' deployed', 10) + + if cargo.unit and cargo.unit:isExist() and cargo.unit.getPlayerName then + local player = cargo.unit:getPlayerName() + local xp = RewardDefinitions.actions.squadDeploy + + self.playerTracker:addStat(player, math.floor(xp), PlayerTracker.statTypes.xp) + + if zn then + self.missionTracker:tallyUnloadSquad(player, zn.name, cargo.squad.type) + else + self.missionTracker:tallyUnloadSquad(player, '', cargo.squad.type) + end + trigger.action.outTextForUnit(cargo.unit:getID(), '+'..math.floor(xp)..' XP', 10) + end + end + end + end + end + end + end + + function PlayerLogistics:hercPrepareDrop(params) + local groupname = params.group + local type = params.type + local gr = Group.getByName(groupname) + if gr then + local un = gr:getUnit(1) + + if type == 'supplies' then + local cargo = self.carriedCargo[gr:getID()] + if cargo and cargo > 0 then + self.hercPreparedDrops[gr:getID()] = type + trigger.action.outTextForUnit(un:getID(), 'Supply drop prepared', 10) + else + trigger.action.outTextForUnit(un:getID(), 'No supplies onboard the aircraft', 10) + end + else + local exists = false + if self.carriedInfantry[gr:getID()] then + for i,v in ipairs(self.carriedInfantry[gr:getID()]) do + if v.type == type then + exists = true + break + end + end + end + + if exists then + self.hercPreparedDrops[gr:getID()] = type + local squadName = PlayerLogistics.getInfantryName(type) + trigger.action.outTextForUnit(un:getID(), squadName..' drop prepared', 10) + else + local squadName = PlayerLogistics.getInfantryName(type) + trigger.action.outTextForUnit(un:getID(), 'No '..squadName..' onboard the aircraft', 10) + end + end + end + end + + function PlayerLogistics:awardSupplyXP(lastLoad, zone, unit, amount) + if lastLoad and zone.name~=lastLoad.name then + if unit and unit.isExist and unit:isExist() and unit.getPlayerName then + local player = unit:getPlayerName() + local xp = amount*RewardDefinitions.actions.supplyRatio + + local totalboost = 0 + local dist = mist.utils.get2DDist(lastLoad.zone.point, zone.zone.point) + if dist > 15000 then + local extradist = math.max(dist - 15000, 85000) + local kmboost = extradist/85000 + local actualboost = xp * kmboost * 1 + totalboost = totalboost + actualboost + end + + local both = true + if zone:criticalOnSupplies() then + local actualboost = xp * RewardDefinitions.actions.supplyBoost + totalboost = totalboost + actualboost + else + both = false + end + + if zone.distToFront == 0 then + local actualboost = xp * RewardDefinitions.actions.supplyBoost + totalboost = totalboost + actualboost + else + both = false + end + + if both then + local actualboost = xp * 1 + totalboost = totalboost + actualboost + end + + xp = xp + totalboost + + if lastLoad.distToFront >= zone.distToFront then + xp = xp * 0.25 + end + + self.playerTracker:addStat(player, math.floor(xp), PlayerTracker.statTypes.xp) + self.missionTracker:tallySupplies(player, amount, zone.name) + trigger.action.outTextForUnit(unit:getID(), '+'..math.floor(xp)..' XP', 10) + end + end + end + + function PlayerLogistics.markWithSmoke(zonename) + local zone = CustomZone:getByName(zonename) + local p = Utils.getPointOnSurface(zone.point) + trigger.action.smoke(p, 0) + end + + function PlayerLogistics.getWeight(supplies) + return math.floor(supplies) + end + + function PlayerLogistics:getCarriedPersonWeight(groupname) + local gr = Group.getByName(groupname) + local un = gr:getUnit(1) + if un then + if not PlayerLogistics.allowedTypes[un:getDesc().typeName] then return 0 end + + local max = PlayerLogistics.allowedTypes[un:getDesc().typeName].personCapacity + + local pilotWeight = 0 + local squadWeight = 0 + if not self.carriedPilots[gr:getID()] then self.carriedPilots[gr:getID()] = {} end + local pilots = self.carriedPilots[gr:getID()] + if pilots then + pilotWeight = 100 * #pilots + end + + if not self.carriedInfantry[gr:getID()] then self.carriedInfantry[gr:getID()] = {} end + local squads = self.carriedInfantry[gr:getID()] + if squads then + for _,squad in ipairs(squads) do + squadWeight = squadWeight + squad.weight + end + end + + return pilotWeight + squadWeight + end + end + + function PlayerLogistics:getOccupiedPersonCapacity(groupname) + local gr = Group.getByName(groupname) + local un = gr:getUnit(1) + if un then + if not PlayerLogistics.allowedTypes[un:getDesc().typeName] then return 0 end + if self.carriedCargo[gr:getID()] and self.carriedCargo[gr:getID()] > 0 then return 0 end + + local max = PlayerLogistics.allowedTypes[un:getDesc().typeName].personCapacity + + local pilotCount = 0 + local squadCount = 0 + if not self.carriedPilots[gr:getID()] then self.carriedPilots[gr:getID()] = {} end + local pilots = self.carriedPilots[gr:getID()] + if pilots then + pilotCount = #pilots + end + + if not self.carriedInfantry[gr:getID()] then self.carriedInfantry[gr:getID()] = {} end + local squads = self.carriedInfantry[gr:getID()] + if squads then + for _,squad in ipairs(squads) do + squadCount = squadCount + squad.size + end + end + + local total = pilotCount + squadCount + + return total + end + end + + function PlayerLogistics:getRemainingPersonCapacity(groupname) + local gr = Group.getByName(groupname) + local un = gr:getUnit(1) + if un then + if not PlayerLogistics.allowedTypes[un:getDesc().typeName] then return 0 end + if self.carriedCargo[gr:getID()] and self.carriedCargo[gr:getID()] > 0 then return 0 end + + local max = PlayerLogistics.allowedTypes[un:getDesc().typeName].personCapacity + + local total = self:getOccupiedPersonCapacity(groupname) + + return max - total + end + end + + function PlayerLogistics:canFitCargo(groupname) + local gr = Group.getByName(groupname) + local un = gr:getUnit(1) + if un then + if not PlayerLogistics.allowedTypes[un:getDesc().typeName] then return false end + return self:getOccupiedPersonCapacity(groupname) == 0 + end + end + + function PlayerLogistics:canFitPersonnel(groupname, toFit) + local gr = Group.getByName(groupname) + local un = gr:getUnit(1) + if un then + if not PlayerLogistics.allowedTypes[un:getDesc().typeName] then return false end + + return self:getRemainingPersonCapacity(groupname) >= toFit + end + end + + function PlayerLogistics:showPilot(groupname) + local gr = Group.getByName(groupname) + if gr then + local un = gr:getUnit(1) + if un then + local data = self.csarTracker:getClosestPilot(un:getPoint()) + + if not data then + trigger.action.outTextForUnit(un:getID(), 'No pilots in need of extraction', 10) + return + end + + local pos = data.pilot:getUnit(1):getPoint() + local brg = math.floor(Utils.getBearing(un:getPoint(), data.pilot:getUnit(1):getPoint())) + local dist = data.dist + local dstft = math.floor(dist/0.3048) + + local msg = data.name..' requesting extraction' + msg = msg..'\n\n Distance: ' + if dist>1000 then + local dstkm = string.format('%.2f',dist/1000) + local dstnm = string.format('%.2f',dist/1852) + + msg = msg..dstkm..'km | '..dstnm..'nm' + else + local dstft = math.floor(dist/0.3048) + msg = msg..math.floor(dist)..'m | '..dstft..'ft' + end + + msg = msg..'\n Bearing: '..brg + + trigger.action.outTextForUnit(un:getID(), msg, 10) + end + end + end + + function PlayerLogistics:smokePilot(groupname) + local gr = Group.getByName(groupname) + if gr then + local un = gr:getUnit(1) + if un then + local data = self.csarTracker:getClosestPilot(un:getPoint()) + + if not data or data.dist >= 5000 then + trigger.action.outTextForUnit(un:getID(), 'No pilots nearby', 10) + return + end + + self.csarTracker:markPilot(data) + trigger.action.outTextForUnit(un:getID(), 'Location of '..data.name..' marked with green smoke.', 10) + end + end + end + + function PlayerLogistics:flarePilot(groupname) + local gr = Group.getByName(groupname) + if gr then + local un = gr:getUnit(1) + if un then + local data = self.csarTracker:getClosestPilot(un:getPoint()) + + if not data or data.dist >= 5000 then + trigger.action.outTextForUnit(un:getID(), 'No pilots nearby', 10) + return + end + + self.csarTracker:flarePilot(data) + trigger.action.outTextForUnit(un:getID(), data.name..' has deployed a green flare', 10) + end + end + end + + function PlayerLogistics:unloadPilots(groupname) + local gr = Group.getByName(groupname) + if gr then + local un = gr:getUnit(1) + if un then + local pilots = self.carriedPilots[gr:getID()] + if not pilots or #pilots==0 then + trigger.action.outTextForUnit(un:getID(), 'No pilots onboard', 10) + return + end + + if Utils.isInAir(un) then + trigger.action.outTextForUnit(un:getID(), 'Can not unload pilot while in air', 10) + return + end + + if not self:isCargoDoorOpen(un) then + trigger.action.outTextForUnit(un:getID(), 'Can not unload pilot while cargo door closed', 10) + return + end + + local zn = ZoneCommand.getZoneOfUnit(un:getName()) + if not zn then + trigger.action.outTextForUnit(un:getID(), 'Can only unload extracted pilots while within a friendly zone', 10) + return + end + + if zn.side ~= 0 and zn.side ~= un:getCoalition()then + trigger.action.outTextForUnit(un:getID(), 'Can only unload extracted pilots while within a friendly zone', 10) + return + end + + zn:addResource(200*#pilots) + zn:refreshText() + + if un.getPlayerName then + local player = un:getPlayerName() + + local xp = #pilots*RewardDefinitions.actions.pilotExtract + + self.playerTracker:addStat(player, math.floor(xp), PlayerTracker.statTypes.xp) + self.missionTracker:tallyUnloadPilot(player, zn.name) + trigger.action.outTextForUnit(un:getID(), '+'..math.floor(xp)..' XP', 10) + end + + self.carriedPilots[gr:getID()] = {} + trigger.action.setUnitInternalCargo(un:getName(), 0) + trigger.action.outTextForUnit(un:getID(), 'Pilots unloaded', 10) + end + end + end + + function PlayerLogistics:extractPilot(groupname) + local gr = Group.getByName(groupname) + if gr then + local un = gr:getUnit(1) + if un then + if not self:canFitPersonnel(groupname, 1) then + trigger.action.outTextForUnit(un:getID(), 'Not enough free space onboard. (Need 1)', 10) + return + end + + timer.scheduleFunction(function(param,time) + local self = param.context + local un = param.unit + if not un then return end + if not un:isExist() then return end + local gr = un:getGroup() + + local data = self.csarTracker:getClosestPilot(un:getPoint()) + + if not data or data.dist > 500 then + trigger.action.outTextForUnit(un:getID(), 'There is no pilot nearby that needs extraction', 10) + return + else + if not self:isCargoDoorOpen(un) then + trigger.action.outTextForUnit(un:getID(), 'Cargo door closed', 1) + elseif Utils.getAGL(un) > 70 then + trigger.action.outTextForUnit(un:getID(), 'Altitude too high (< 70 m). Current: '..string.format('%.2f',Utils.getAGL(un))..' m', 1) + elseif mist.vec.mag(un:getVelocity())>5 then + trigger.action.outTextForUnit(un:getID(), 'Moving too fast (< 5 m/s). Current: '..string.format('%.2f',mist.vec.mag(un:getVelocity()))..' m/s', 1) + else + if data.dist > 100 then + trigger.action.outTextForUnit(un:getID(), 'Too far (< 100m). Current: '..string.format('%.2f',data.dist)..' m', 1) + else + if not self.carriedPilots[gr:getID()] then self.carriedPilots[gr:getID()] = {} end + table.insert(self.carriedPilots[gr:getID()], data.name) + local player = un:getPlayerName() + self.missionTracker:tallyLoadPilot(player, data) + self.csarTracker:removePilot(data.name) + local weight = self:getCarriedPersonWeight(gr:getName()) + trigger.action.setUnitInternalCargo(un:getName(), weight) + trigger.action.outTextForUnit(un:getID(), data.name..' onboard. ('..weight..' kg)', 10) + return + end + end + end + + param.trys = param.trys - 1 + if param.trys > 0 then + return time+1 + end + end, {context = self, unit = un, trys = 60}, timer.getTime()+0.1) + end + end + end + + function PlayerLogistics:extractSquad(groupname) + local gr = Group.getByName(groupname) + if gr then + local un = gr:getUnit(1) + if un then + + if Utils.isInAir(un) then + trigger.action.outTextForUnit(un:getID(), 'Can not load infantry while in air', 10) + return + end + + if not self:isCargoDoorOpen(un) then + trigger.action.outTextForUnit(un:getID(), 'Can not load infantry while cargo door closed', 10) + return + end + + local squad, distance = self.squadTracker:getClosestExtractableSquad(un:getPoint()) + if squad and distance < 50 then + local squadgr = Group.getByName(squad.name) + if squadgr and squadgr:isExist() then + local sqsize = squadgr:getSize() + if not self:canFitPersonnel(groupname, sqsize) then + trigger.action.outTextForUnit(un:getID(), 'Not enough free space onboard. (Need '..sqsize..')', 10) + return + end + + if not self.carriedInfantry[gr:getID()] then self.carriedInfantry[gr:getID()] = {} end + table.insert(self.carriedInfantry[gr:getID()],{type = PlayerLogistics.infantryTypes.extractable, size = sqsize, weight = sqsize * 100}) + + local weight = self:getCarriedPersonWeight(gr:getName()) + + trigger.action.setUnitInternalCargo(un:getName(), weight) + + local loadedInfName = PlayerLogistics.getInfantryName(PlayerLogistics.infantryTypes.extractable) + trigger.action.outTextForUnit(un:getID(), loadedInfName..' onboard. ('..weight..' kg)', 10) + + local player = un:getPlayerName() + self.missionTracker:tallyLoadSquad(player, squad) + self.squadTracker:removeSquad(squad.name) + + squadgr:destroy() + end + else + trigger.action.outTextForUnit(un:getID(), 'There is no infantry nearby that is ready to be extracted.', 10) + end + end + end + end + + function PlayerLogistics:loadInfantry(params) + if not ZoneCommand then return end + + local gr = Group.getByName(params.group) + local squadType = params.type + local squadName = PlayerLogistics.getInfantryName(squadType) + + local squadCost = 0 + local squadSize = 999999 + local squadWeight = 0 + if self.registeredSquadGroups[squadType] then + squadCost = self.registeredSquadGroups[squadType].cost + squadSize = self.registeredSquadGroups[squadType].size + squadWeight = self.registeredSquadGroups[squadType].weight + end + + if gr then + local un = gr:getUnit(1) + if un then + if Utils.isInAir(un) then + trigger.action.outTextForUnit(un:getID(), 'Can not load infantry while in air', 10) + return + end + + local zn = ZoneCommand.getZoneOfUnit(un:getName()) + if not zn then + trigger.action.outTextForUnit(un:getID(), 'Can only load infantry while within a friendly zone', 10) + return + end + + if zn.side ~= un:getCoalition() then + trigger.action.outTextForUnit(un:getID(), 'Can only load infantry while within a friendly zone', 10) + return + end + + if not self:isCargoDoorOpen(un) then + trigger.action.outTextForUnit(un:getID(), 'Can not load infantry while cargo door closed', 10) + return + end + + if zn:criticalOnSupplies() then + trigger.action.outTextForUnit(un:getID(), 'Can not load infantry, zone is low on resources', 10) + return + end + + if zn.resource < zn.spendTreshold + squadCost then + trigger.action.outTextForUnit(un:getID(), 'Can not afford to load '..squadName..' (Cost: '..squadCost..'). Resources would fall to a critical level.', 10) + return + end + + if not self:canFitPersonnel(params.group, squadSize) then + trigger.action.outTextForUnit(un:getID(), 'Not enough free space on board. (Need '..squadSize..')', 10) + return + end + + zn:removeResource(squadCost) + zn:refreshText() + if not self.carriedInfantry[gr:getID()] then self.carriedInfantry[gr:getID()] = {} end + table.insert(self.carriedInfantry[gr:getID()],{ type = squadType, size = squadSize, weight = squadWeight, loadedAt = zn }) + self.lastLoaded[gr:getID()] = zn + + local weight = self:getCarriedPersonWeight(gr:getName()) + trigger.action.setUnitInternalCargo(un:getName(), weight) + + local loadedInfName = PlayerLogistics.getInfantryName(squadType) + trigger.action.outTextForUnit(un:getID(), loadedInfName..' onboard. ('..weight..' kg)', 10) + end + end + end + + function PlayerLogistics:unloadInfantry(params) + if not ZoneCommand then return end + local groupname = params.group + local sqtype = params.type + + local gr = Group.getByName(groupname) + if gr then + local un = gr:getUnit(1) + if un then + if Utils.isInAir(un) then + trigger.action.outTextForUnit(un:getID(), 'Can not unload infantry while in air', 10) + return + end + + if not self:isCargoDoorOpen(un) then + trigger.action.outTextForUnit(un:getID(), 'Can not unload infantry while cargo door closed', 10) + return + end + + local carriedSquads = self.carriedInfantry[gr:getID()] + if not carriedSquads or #carriedSquads == 0 then + trigger.action.outTextForUnit(un:getID(), 'No infantry onboard', 10) + return + end + + local toUnload = carriedSquads + local remaining = {} + if sqtype then + toUnload = {} + local sqToUnload = nil + for _,sq in ipairs(carriedSquads) do + if sq.type == sqtype and not sqToUnload then + sqToUnload = sq + else + table.insert(remaining, sq) + end + end + + if sqToUnload then toUnload = { sqToUnload } end + end + + if #toUnload == 0 then + if sqtype then + local squadName = PlayerLogistics.getInfantryName(sqtype) + trigger.action.outTextForUnit(un:getID(), 'No '..squadName..' onboard.', 10) + else + trigger.action.outTextForUnit(un:getID(), 'No infantry onboard.', 10) + end + + return + end + + local zn = ZoneCommand.getZoneOfUnit(un:getName()) + + for _, sq in ipairs(toUnload) do + local squadName = PlayerLogistics.getInfantryName(sq.type) + local lastLoad = sq.loadedAt + if lastLoad and zn and zn.side == un:getCoalition() and zn.name==lastLoad.name then + if self.registeredSquadGroups[sq.type] then + local cost = self.registeredSquadGroups[sq.type].cost + zn:addResource(cost) + zn:refreshText() + trigger.action.outTextForUnit(un:getID(), squadName..' unloaded', 10) + end + else + if sq.type == PlayerLogistics.infantryTypes.extractable then + if not zn then + trigger.action.outTextForUnit(un:getID(), 'Can only unload extracted infantry while within a friendly zone', 10) + table.insert(remaining, sq) + elseif zn.side ~= un:getCoalition() then + trigger.action.outTextForUnit(un:getID(), 'Can only unload extracted infantry while within a friendly zone', 10) + table.insert(remaining, sq) + else + trigger.action.outTextForUnit(un:getID(), 'Infantry recovered', 10) + zn:addResource(200) + zn:refreshText() + + if un.getPlayerName then + local player = un:getPlayerName() + local xp = RewardDefinitions.actions.squadExtract + + self.playerTracker:addStat(player, math.floor(xp), PlayerTracker.statTypes.xp) + self.missionTracker:tallyUnloadSquad(player, zn.name, sq.type) + trigger.action.outTextForUnit(un:getID(), '+'..math.floor(xp)..' XP', 10) + end + end + elseif self.registeredSquadGroups[sq.type] then + local pos = Utils.getPointOnSurface(un:getPoint()) + + local error = self.squadTracker:spawnInfantry(self.registeredSquadGroups[sq.type], pos) + + if not error then + trigger.action.outTextForUnit(un:getID(), squadName..' deployed', 10) + + if un.getPlayerName then + local player = un:getPlayerName() + local xp = RewardDefinitions.actions.squadDeploy + + self.playerTracker:addStat(player, math.floor(xp), PlayerTracker.statTypes.xp) + + if zn then + self.missionTracker:tallyUnloadSquad(player, zn.name, sq.type) + else + self.missionTracker:tallyUnloadSquad(player, '', sq.type) + end + trigger.action.outTextForUnit(un:getID(), '+'..math.floor(xp)..' XP', 10) + end + else + trigger.action.outTextForUnit(un:getID(), 'Failed to deploy squad, no suitable location nearby', 10) + table.insert(remaining, sq) + end + else + trigger.action.outText("ERROR: SQUAD TYPE NOT REGISTERED", 60) + end + end + end + + self.carriedInfantry[gr:getID()] = remaining + local weight = self:getCarriedPersonWeight(groupname) + trigger.action.setUnitInternalCargo(un:getName(), weight) + end + end + end + + function PlayerLogistics:unloadAll(groupname) + local gr = Group.getByName(groupname) + if gr then + local un = gr:getUnit(1) + if un then + local cargo = self.carriedCargo[gr:getID()] + local squad = self.carriedInfantry[gr:getID()] + local pilot = self.carriedPilots[gr:getID()] + + if cargo and cargo>0 then + self:unloadSupplies({group=groupname, amount=9999999}) + end + + if squad and #squad>0 then + self:unloadInfantry({group=groupname}) + end + + if pilot and #pilot>0 then + self:unloadPilots(groupname) + end + end + end + end + + function PlayerLogistics:cargoStatus(groupName) + local gr = Group.getByName(groupName) + if gr then + local un = gr:getUnit(1) + if un then + local onboard = self.carriedCargo[gr:getID()] + if onboard and onboard > 0 then + local weight = self.getWeight(onboard) + trigger.action.outTextForUnit(un:getID(), onboard..' supplies onboard. ('..weight..' kg)', 10) + else + local msg = '' + local squads = self.carriedInfantry[gr:getID()] + if squads and #squads>0 then + msg = msg..'Squads:\n' + + for _,squad in ipairs(squads) do + local infName = PlayerLogistics.getInfantryName(squad.type) + msg = msg..' \n'..infName..' (Size: '..squad.size..')' + end + end + + local pilots = self.carriedPilots[gr:getID()] + if pilots and #pilots>0 then + msg = msg.."\n\nPilots:\n" + for i,v in ipairs(pilots) do + msg = msg..'\n '..v + end + + end + + local max = PlayerLogistics.allowedTypes[un:getDesc().typeName].personCapacity + local occupied = self:getOccupiedPersonCapacity(groupName) + + msg = msg..'\n\nCapacity: '..occupied..'/'..max + + msg = msg..'\n('..self:getCarriedPersonWeight(groupName)..' kg)' + + if un:getDesc().typeName == 'Hercules' then + local preped = self.hercPreparedDrops[gr:getID()] + if preped then + if preped == 'supplies' then + msg = msg..'\nSupplies prepared for next drop.' + else + local squadName = PlayerLogistics.getInfantryName(preped) + msg = msg..'\n'..squadName..' prepared for next drop.' + end + end + end + + trigger.action.outTextForUnit(un:getID(), msg, 10) + end + end + end + end + + function PlayerLogistics:isCargoDoorOpen(unit) + if unit then + local tp = unit:getDesc().typeName + if tp == "Mi-8MT" then + if unit:getDrawArgumentValue(86) == 1 then return true end + if unit:getDrawArgumentValue(38) > 0.85 then return true end + elseif tp == "UH-1H" then + if unit:getDrawArgumentValue(43) == 1 then return true end + if unit:getDrawArgumentValue(44) == 1 then return true end + elseif tp == "Mi-24P" then + if unit:getDrawArgumentValue(38) == 1 then return true end + if unit:getDrawArgumentValue(86) == 1 then return true end + elseif tp == "Hercules" then + if unit:getDrawArgumentValue(1215) == 1 and unit:getDrawArgumentValue(1216) == 1 then return true end + elseif tp == "UH-60L" then + if unit:getDrawArgumentValue(401) == 1 then return true end + if unit:getDrawArgumentValue(402) == 1 then return true end + elseif tp == "SA342Mistral" then + if unit:getDrawArgumentValue(34) == 1 then return true end + if unit:getDrawArgumentValue(38) == 1 then return true end + elseif tp == "SA342L" then + if unit:getDrawArgumentValue(34) == 1 then return true end + if unit:getDrawArgumentValue(38) == 1 then return true end + elseif tp == "SA342M" then + if unit:getDrawArgumentValue(34) == 1 then return true end + if unit:getDrawArgumentValue(38) == 1 then return true end + elseif tp == "SA342Minigun" then + if unit:getDrawArgumentValue(34) == 1 then return true end + if unit:getDrawArgumentValue(38) == 1 then return true end + else + return true + end + end + end + + function PlayerLogistics:loadSupplies(params) + if not ZoneCommand then return end + + local groupName = params.group + local amount = params.amount + + local gr = Group.getByName(groupName) + if gr then + local un = gr:getUnit(1) + if un then + if not self:canFitCargo(groupName) then + trigger.action.outTextForUnit(un:getID(), 'Can not load cargo. Personnel onboard.', 10) + return + end + + if Utils.isInAir(un) then + trigger.action.outTextForUnit(un:getID(), 'Can not load supplies while in air', 10) + return + end + + local zn = ZoneCommand.getZoneOfUnit(un:getName()) + if not zn then + trigger.action.outTextForUnit(un:getID(), 'Can only load supplies while within a friendly zone', 10) + return + end + + if zn.side ~= un:getCoalition() then + trigger.action.outTextForUnit(un:getID(), 'Can only load supplies while within a friendly zone', 10) + return + end + + if not self:isCargoDoorOpen(un) then + trigger.action.outTextForUnit(un:getID(), 'Can not load supplies while cargo door closed', 10) + return + end + + if zn:criticalOnSupplies() then + trigger.action.outTextForUnit(un:getID(), 'Can not load supplies, zone is low on resources', 10) + return + end + + if zn.resource < zn.spendTreshold + amount then + trigger.action.outTextForUnit(un:getID(), 'Can not load supplies if resources would fall to a critical level.', 10) + return + end + + local carried = self.carriedCargo[gr:getID()] or 0 + if amount > zn.resource then + amount = zn.resource + end + + zn:removeResource(amount) + zn:refreshText() + self.carriedCargo[gr:getID()] = carried + amount + self.lastLoaded[gr:getID()] = zn + local onboard = self.carriedCargo[gr:getID()] + local weight = self.getWeight(onboard) + + if un:getDesc().typeName == "Hercules" then + local loadedInCrates = 0 + local ammo = un:getAmmo() + for _,load in ipairs(ammo) do + if load.desc.typeName == 'weapons.bombs.Generic Crate [20000lb]' then + loadedInCrates = 9000 * load.count + end + end + + local internal = 0 + if weight > loadedInCrates then + internal = weight - loadedInCrates + end + + trigger.action.setUnitInternalCargo(un:getName(), internal) + else + trigger.action.setUnitInternalCargo(un:getName(), weight) + end + + trigger.action.outTextForUnit(un:getID(), amount..' supplies loaded', 10) + trigger.action.outTextForUnit(un:getID(), onboard..' supplies onboard. ('..weight..' kg)', 10) + end + end + end + + function PlayerLogistics:unloadSupplies(params) + if not ZoneCommand then return end + + local groupName = params.group + local amount = params.amount + + local gr = Group.getByName(groupName) + if gr then + local un = gr:getUnit(1) + if un then + if Utils.isInAir(un) then + trigger.action.outTextForUnit(un:getID(), 'Can not unload supplies while in air', 10) + return + end + + local zn = ZoneCommand.getZoneOfUnit(un:getName()) + if not zn then + trigger.action.outTextForUnit(un:getID(), 'Can only unload supplies while within a friendly zone', 10) + return + end + + if zn.side ~= 0 and zn.side ~= un:getCoalition()then + trigger.action.outTextForUnit(un:getID(), 'Can only unload supplies while within a friendly zone', 10) + return + end + + if not self:isCargoDoorOpen(un) then + trigger.action.outTextForUnit(un:getID(), 'Can not unload supplies while cargo door closed', 10) + return + end + + if not self.carriedCargo[gr:getID()] or self.carriedCargo[gr:getID()] == 0 then + trigger.action.outTextForUnit(un:getID(), 'No supplies loaded', 10) + return + end + + local carried = self.carriedCargo[gr:getID()] + if amount > carried then + amount = carried + end + + self.carriedCargo[gr:getID()] = carried-amount + zn:addResource(amount) + + local lastLoad = self.lastLoaded[gr:getID()] + self:awardSupplyXP(lastLoad, zn, un, amount) + + zn:refreshText() + local onboard = self.carriedCargo[gr:getID()] + local weight = self.getWeight(onboard) + + if un:getDesc().typeName == "Hercules" then + local loadedInCrates = 0 + local ammo = un:getAmmo() + for _,load in ipairs(ammo) do + if load.desc.typeName == 'weapons.bombs.Generic Crate [20000lb]' then + loadedInCrates = 9000 * load.count + end + end + + local internal = 0 + if weight > loadedInCrates then + internal = weight - loadedInCrates + end + + trigger.action.setUnitInternalCargo(un:getName(), internal) + else + trigger.action.setUnitInternalCargo(un:getName(), weight) + end + + trigger.action.outTextForUnit(un:getID(), amount..' supplies unloaded', 10) + trigger.action.outTextForUnit(un:getID(), onboard..' supplies remaining onboard. ('..weight..' kg)', 10) + end + end + end +end + +-----------------[[ END OF PlayerLogistics.lua ]]----------------- + + + +-----------------[[ MarkerCommands.lua ]]----------------- + +MarkerCommands = {} +do + function MarkerCommands:new() + local obj = {} + obj.commands = {} --{command=string, action=function} + + setmetatable(obj, self) + self.__index = self + + obj:start() + + return obj + end + + function MarkerCommands:addCommand(command, action, hasParam, state) + table.insert(self.commands, {command = command, action = action, hasParam = hasParam, state = state}) + end + + function MarkerCommands:start() + local markEditedEvent = {} + markEditedEvent.context = self + function markEditedEvent:onEvent(event) + if event.id == 26 and event.text and (event.coalition == 1 or event.coalition == 2) then -- mark changed + local success = false + env.info('MarkerCommands - input: '..event.text) + + for i,v in ipairs(self.context.commands) do + if (not v.hasParam) and event.text == v.command then + success = v.action(event, nil, v.state) + break + elseif v.hasParam and event.text:find('^'..v.command..':') then + local param = event.text:gsub('^'..v.command..':', '') + success = v.action(event, param, v.state) + break + end + end + + if success then + trigger.action.removeMark(event.idx) + end + end + end + + world.addEventHandler(markEditedEvent) + end +end + + +-----------------[[ END OF MarkerCommands.lua ]]----------------- + + + +-----------------[[ ZoneCommand.lua ]]----------------- + +ZoneCommand = {} +do + ZoneCommand.currentZoneIndex = 1000 + ZoneCommand.allZones = {} + ZoneCommand.buildSpeed = Config.buildSpeed + ZoneCommand.supplyBuildSpeed = Config.supplyBuildSpeed + ZoneCommand.missionValidChance = 0.9 + ZoneCommand.missionBuildSpeedReduction = Config.missionBuildSpeedReduction + ZoneCommand.revealTime = 0 + ZoneCommand.staticRegistry = {} + + ZoneCommand.modes = { + normal = 'normal', + supply = 'supply', + export = 'export' + } + + ZoneCommand.productTypes = { + upgrade = 'upgrade', + mission = 'mission', + defense = 'defense' + } + + ZoneCommand.missionTypes = { + supply_air = 'supply_air', + supply_convoy = 'supply_convoy', + cas = 'cas', + cas_helo = 'cas_helo', + strike = 'strike', + patrol = 'patrol', + sead = 'sead', + assault = 'assault', + bai = 'bai', + supply_transfer = 'supply_transfer', + awacs = 'awacs', + tanker = 'tanker' + } + + function ZoneCommand:new(zonename) + local obj = {} + obj.name = zonename + obj.side = 0 + obj.resource = 0 + obj.resourceChange = 0 + obj.maxResource = 20000 + obj.spendTreshold = 5000 + obj.keepActive = false + obj.boostScale = 1.0 + obj.extraBuildResources = 0 + obj.reservedMissions = {} + obj.isHeloSpawn = false + obj.isPlaneSpawn = false + + obj.connectionManager = nil + + obj.zone = CustomZone:getByName(zonename) + obj.products = {} + obj.mode = 'normal' + --[[ + normal: buys whatever it can + supply: buys only supply missions + export: supply mode, but also sells all defense groups from the zone + ]]-- + obj.index = ZoneCommand.currentZoneIndex + ZoneCommand.currentZoneIndex = ZoneCommand.currentZoneIndex + 1 + + obj.built = {} + obj.income = 0 + + --group restrictions + obj.spawns = {} + for i,v in pairs(mist.DBs.groupsByName) do + if v.units[1].skill == 'Client' then + local zn = obj.zone + local pos3d = { + x = v.units[1].point.x, + y = 0, + z = v.units[1].point.y + } + + if zn and zn:isInside(pos3d) then + local coa = 0 + if v.coalition=='blue' then + coa = 2 + elseif v.coalition=='red' then + coa = 1 + end + + table.insert(obj.spawns, {name=i, side=coa}) + end + end + end + + --draw graphics + local color = {0.7,0.7,0.7,0.3} + if obj.side == 1 then + color = {1,0,0,0.3} + elseif obj.side == 2 then + color = {0,0,1,0.3} + end + + obj.zone:draw(obj.index, color, color) + + local point = obj.zone.point + + if obj.zone:isCircle() then + point = { + x = obj.zone.point.x, + y = obj.zone.point.y, + z = obj.zone.point.z + obj.zone.radius + } + elseif obj.zone:isQuad() then + local largestZ = obj.zone.vertices[1].z + local largestX = obj.zone.vertices[1].x + for i=2,4,1 do + if obj.zone.vertices[i].z > largestZ then + largestZ = obj.zone.vertices[i].z + largestX = obj.zone.vertices[i].x + end + end + + point = { + x = largestX, + y = obj.zone.point.y, + z = largestZ + } + end + + --trigger.action.textToAll(1,1000+obj.index,point, {0,0,0,0.8}, {1,1,1,0.5}, 15, true, '') + --trigger.action.textToAll(2,2000+obj.index,point, {0,0,0,0.8}, {1,1,1,0.5}, 15, true, '') + trigger.action.textToAll(-1,2000+obj.index,point, {0,0,0,0.8}, {1,1,1,0.5}, 15, true, '') --show blue to all + setmetatable(obj, self) + self.__index = self + + obj:refreshText() + obj:start() + obj:refreshSpawnBlocking() + ZoneCommand.allZones[obj.name] = obj + return obj + end + + function ZoneCommand:refreshSpawnBlocking() + for _,v in ipairs(self.spawns) do + trigger.action.setUserFlag(v.name, v.side ~= self.side) + end + end + + function ZoneCommand.setNeighbours(conManager) + for name,zone in pairs(ZoneCommand.allZones) do + zone.connectionManager = conManager + local neighbours = conManager:getConnectionsOfZone(name) + zone.neighbours = {} + for _,zname in ipairs(neighbours) do + zone.neighbours[zname] = ZoneCommand.getZoneByName(zname) + end + end + end + + function ZoneCommand.getZoneByName(name) + if not name then return nil end + return ZoneCommand.allZones[name] + end + + function ZoneCommand.getAllZones() + return ZoneCommand.allZones + end + + function ZoneCommand.getZoneOfUnit(unitname) + local un = Unit.getByName(unitname) + + if not un then + return nil + end + + for i,v in pairs(ZoneCommand.allZones) do + if Utils.isInZone(un, i) then + return v + end + end + + return nil + end + + function ZoneCommand.getZoneOfWeapon(weapon) + if not weapon then + return nil + end + + for i,v in pairs(ZoneCommand.allZones) do + if Utils.isInZone(weapon, i) then + return v + end + end + + return nil + end + + function ZoneCommand.getClosestZoneToPoint(point) + local minDist = 9999999 + local closest = nil + for i,v in pairs(ZoneCommand.allZones) do + local d = mist.utils.get2DDist(v.zone.point, point) + if d < minDist then + minDist = d + closest = v + end + end + + return closest, minDist + end + + function ZoneCommand.getZoneOfPoint(point) + for i,v in pairs(ZoneCommand.allZones) do + local z = CustomZone:getByName(i) + if z and z:isInside(point) then + return v + end + end + + return nil + end + + function ZoneCommand:boostProduction(amount) + self.extraBuildResources = self.extraBuildResources + amount + env.info('ZoneCommand:boostProduction - '..self.name..' production boosted by '..amount..' to a total of '..self.extraBuildResources) + end + + function ZoneCommand:sabotage(explosionSize, sourcePoint) + local minDist = 99999999 + local closest = nil + for i,v in pairs(self.built) do + if v.type == 'upgrade' then + local st = StaticObject.getByName(v.name) + if not st then st = Group.getByName(v.name) end + local pos = st:getPoint() + + local d = mist.utils.get2DDist(pos, sourcePoint) + if d < minDist then + minDist = d; + closest = pos + end + end + end + + if closest then + trigger.action.explosion(closest, explosionSize) + env.info('ZoneCommand:sabotage - Structure has been sabotaged at '..self.name) + end + + local damagedResources = math.random(2000,5000) + self:removeResource(damagedResources) + self:refreshText() + end + + function ZoneCommand:refreshText() + local build = '' + if self.currentBuild then + local job = '' + local display = self.currentBuild.product.display + if self.currentBuild.product.type == 'upgrade' then + job = display + elseif self.currentBuild.product.type == 'defense' then + if self.currentBuild.isRepair then + job = display..' (repair)' + else + job = display + end + elseif self.currentBuild.product.type == 'mission' then + job = display + end + + build = '\n['..job..' '..math.min(math.floor((self.currentBuild.progress/self.currentBuild.product.cost)*100),100)..'%]' + end + + local mBuild = '' + if self.currentMissionBuild then + local job = '' + local display = self.currentMissionBuild.product.display + job = display + + mBuild = '\n['..job..' '..math.min(math.floor((self.currentMissionBuild.progress/self.currentMissionBuild.product.cost)*100),100)..'%]' + end + + local status='' + if self.side ~= 0 and self:criticalOnSupplies() then + status = '(!)' + end + + local color = {0.3,0.3,0.3,1} + if self.side == 1 then + color = {0.7,0,0,1} + elseif self.side == 2 then + color = {0,0,0.7,1} + end + + --trigger.action.setMarkupColor(1000+self.index, color) + trigger.action.setMarkupColor(2000+self.index, color) + + local label = '['..self.resource..'/'..self.maxResource..']'..status..build..mBuild + + if self.side == 1 then + --trigger.action.setMarkupText(1000+self.index, self.name..label) + + if self.revealTime > 0 then + trigger.action.setMarkupText(2000+self.index, self.name..label) + else + trigger.action.setMarkupText(2000+self.index, self.name) + end + elseif self.side == 2 then + --if self.revealTime > 0 then + -- trigger.action.setMarkupText(1000+self.index, self.name..label) + --else + -- trigger.action.setMarkupText(1000+self.index, self.name) + --end + trigger.action.setMarkupText(2000+self.index, self.name..label) + elseif self.side == 0 then + --trigger.action.setMarkupText(1000+self.index, ' '..self.name..' ') + trigger.action.setMarkupText(2000+self.index, ' '..self.name..' ') + end + end + + function ZoneCommand:setSide(side) + self.side = side + self:refreshSpawnBlocking() + + if side == 0 then + self.revealTime = 0 + end + + local color = {0.7,0.7,0.7,0.3} + if self.side==1 then + color = {1,0,0,0.3} + elseif self.side==2 then + color = {0,0,1,0.3} + end + + trigger.action.setMarkupColorFill(self.index, color) + trigger.action.setMarkupColor(self.index, color) + trigger.action.setMarkupTypeLine(self.index, 1) + + if self.side == 2 and (self.isHeloSpawn or self.isPlaneSpawn) then + trigger.action.setMarkupTypeLine(self.index, 2) + trigger.action.setMarkupColor(self.index, {0,1,0,1}) + end + + self:refreshText() + end + + function ZoneCommand:addResource(amount) + self.resource = self.resource+amount + self.resource = math.floor(math.min(self.resource, self.maxResource)) + end + + function ZoneCommand:removeResource(amount) + self.resource = self.resource-amount + self.resource = math.floor(math.max(self.resource, 0)) + end + + function ZoneCommand:reveal() + self.revealTime = 60*30 + self:refreshText() + end + + function ZoneCommand:needsSupplies(sendamount) + return self.resource + sendamount= cost then + self:removeResource(cost) + else + break + end + end + + self:instantBuild(v) + + for i2,v2 in ipairs(v.products) do + if (v2.type == 'defense' or v2.type=='upgrade') and v2.cost > 0 then + if useCost then + local cost = v2.cost * useCost + if self.resource >= cost then + self:removeResource(cost) + else + break + end + end + + self:instantBuild(v2) + end + end + end + end + + function ZoneCommand:start() + timer.scheduleFunction(function(param, time) + local self = param.context + local initialRes = self.resource + + --generate income + if self.side ~= 0 then + self:addResource(self.income) + end + + --untrack destroyed zone upgrades + for i,v in pairs(self.built) do + local u = Group.getByName(i) + if u and u:getSize() == 0 then + u:destroy() + self.built[i] = nil + end + + if not u then + u = StaticObject.getByName(i) + if u and u:getLife()<1 then + u:destroy() + self.built[i] = nil + end + end + + if not u then + self.built[i] = nil + end + end + + --upkeep costs for defenses + for i,v in pairs(self.built) do + if v.type == 'defense' and v.upkeep then + v.strikes = v.strikes or 0 + if self.resource >= v.upkeep then + self:removeResource(v.upkeep) + v.strikes = 0 + else + if v.strikes < 6 then + v.strikes = v.strikes+1 + else + local u = Group.getByName(i) + if u then + v.strikes = nil + u:destroy() + self.built[i] = nil + end + end + end + elseif v.type == 'upgrade' and v.income then + self:addResource(v.income) + end + end + + --check if zone should be reverted to neutral + local hasUpgrade = false + for i,v in pairs(self.built) do + if v.type=='upgrade' then + hasUpgrade = true + break + end + end + + if not hasUpgrade and self.side ~= 0 then + local sidetxt = "Neutral" + if self.side == 1 then + sidetxt = "Red" + elseif self.side == 2 then + sidetxt = "Blue" + end + + trigger.action.outText(sidetxt.." has lost control of "..self.name, 15) + + self:setSide(0) + self.mode = 'normal' + self.currentBuild = nil + self.currentMissionBuild = nil + end + + --sell defenses if export mode + if self.side ~= 0 and self.mode == 'export' then + for i,v in pairs(self.built) do + if v.type=='defense' then + local g = Group.getByName(i) + if g then g:destroy() end + self:addResource(math.floor(v.cost/2)) + self.built[i] = nil + end + end + end + + self:verifyBuildValid() + self:chooseBuild() + self:progressBuild() + + self.resourceChange = self.resource - initialRes + self:refreshText() + + --use revealTime resource + if self.revealTime > 0 then + self.revealTime = math.max(0,self.revealTime-10) + end + + return time+10 + end, {context = self}, timer.getTime()+1) + end + + function ZoneCommand:verifyBuildValid() + if self.currentBuild then + if self.side == 0 then + self.currentBuild = nil + env.info('ZoneCommand:verifyBuildValid - stopping build, zone is neutral') + end + + if self.mode == 'export' or self.mode == 'supply' then + if not (self.currentBuild.product.type == ZoneCommand.productTypes.upgrade or + self.currentBuild.product.missionType == ZoneCommand.missionTypes.supply_air or + self.currentBuild.product.missionType == ZoneCommand.missionTypes.supply_convoy or + self.currentBuild.product.missionType == ZoneCommand.missionTypes.supply_transfer) then + env.info('ZoneCommand:verifyBuildValid - stopping build, mode is '..self.mode..' but mission is not supply') + self.currentBuild = nil + end + end + + if self.currentBuild and (self.currentBuild.product.type == 'defense' or self.currentBuild.product.type == 'mission') then + for i,v in ipairs(self.upgrades[self.currentBuild.side]) do + for i2,v2 in ipairs(v.products) do + if v2.name == self.currentBuild.product.name then + local g = Group.getByName(v.name) + if not g then g = StaticObject.getByName(v.name) end + + if not g then + env.info('ZoneCommand:verifyBuildValid - stopping build, required upgrade no longer exists') + self.currentBuild = nil + break + end + end + end + + if not self.currentBuild then + break + end + end + end + end + + if self.currentMissionBuild then + if self.side == 0 then + self.currentMissionBuild = nil + env.info('ZoneCommand:verifyBuildValid - stopping mission build, zone is neutral') + end + + if (self.mode == 'export' and not self.keepActive) or self.mode == 'supply' then + env.info('ZoneCommand:verifyBuildValid - stopping mission build, mode is '..self.mode..'') + self.currentMissionBuild = nil + end + + if self.currentMissionBuild and self.currentMissionBuild.product.type == 'mission' then + for i,v in ipairs(self.upgrades[self.currentMissionBuild.side]) do + for i2,v2 in ipairs(v.products) do + if v2.name == self.currentMissionBuild.product.name then + local g = Group.getByName(v.name) + if not g then g = StaticObject.getByName(v.name) end + + if not g then + env.info('ZoneCommand:verifyBuildValid - stopping mission build, required upgrade no longer exists') + self.currentMissionBuild = nil + break + end + end + end + + if not self.currentMissionBuild then + break + end + end + end + end + end + + function ZoneCommand:chooseBuild() + local treshhold = self.spendTreshold + --local treshhold = 0 + if self.side ~= 0 and self.currentBuild == nil then + local canAfford = {} + for _,v in ipairs(self.upgrades[self.side]) do + local u = Group.getByName(v.name) + if not u then u = StaticObject.getByName(v.name) end + + if not u then + table.insert(canAfford, {product = v, reason='upgrade'}) + elseif u ~= nil then + for _,v2 in ipairs(v.products) do + if v2.type == 'mission' then + if self.resource > treshhold and + (v2.missionType == ZoneCommand.missionTypes.supply_air or + v2.missionType == ZoneCommand.missionTypes.supply_convoy or + v2.missionType == ZoneCommand.missionTypes.supply_transfer) then + if self:isMissionValid(v2) and math.random() < ZoneCommand.missionValidChance then + table.insert(canAfford, {product = v2, reason='mission'}) + if v2.bias then + for _=1,v2.bias,1 do + table.insert(canAfford, {product = v2, reason='mission'}) + end + end + end + end + elseif v2.type=='defense' and self.mode ~='export' and self.mode ~='supply' and v2.cost > 0 then + local g = Group.getByName(v2.name) + if not g then + table.insert(canAfford, {product = v2, reason='defense'}) + elseif g:getSize() < (g:getInitialSize()*math.random(40,100)/100) then + table.insert(canAfford, {product = v2, reason='repair'}) + end + end + end + end + end + + if #canAfford > 0 then + local choice = math.random(1, #canAfford) + + if canAfford[choice] then + local p = canAfford[choice] + if p.reason == 'repair' then + self:queueBuild(p.product, self.side, true) + else + self:queueBuild(p.product, self.side) + end + end + end + end + + if self.side ~= 0 and self.currentMissionBuild == nil then + local canMission = {} + for _,v in ipairs(self.upgrades[self.side]) do + local u = Group.getByName(v.name) + if not u then u = StaticObject.getByName(v.name) end + if u ~= nil then + for _,v2 in ipairs(v.products) do + if v2.type == 'mission' then + if v2.missionType ~= ZoneCommand.missionTypes.supply_air and + v2.missionType ~= ZoneCommand.missionTypes.supply_convoy and + v2.missionType ~= ZoneCommand.missionTypes.supply_transfer then + if self:isMissionValid(v2) and math.random() < ZoneCommand.missionValidChance then + table.insert(canMission, {product = v2, reason='mission'}) + if v2.bias then + for _=1,v2.bias,1 do + table.insert(canMission, {product = v2, reason='mission'}) + end + end + end + end + end + end + end + end + + if #canMission > 0 then + local choice = math.random(1, #canMission) + + if canMission[choice] then + local p = canMission[choice] + self:queueBuild(p.product, self.side) + end + end + end + end + + function ZoneCommand:progressBuild() + if self.currentBuild and self.currentBuild.side ~= self.side then + env.info('ZoneCommand:progressBuild '..self.name..' - stopping build, zone changed owner') + self.currentBuild = nil + end + + if self.currentMissionBuild and self.currentMissionBuild.side ~= self.side then + env.info('ZoneCommand:progressBuild '..self.name..' - stopping mission build, zone changed owner') + self.currentMissionBuild = nil + end + + if self.currentBuild then + if self.currentBuild.product.type == 'mission' and not self:isMissionValid(self.currentBuild.product) then + env.info('ZoneCommand:progressBuild '..self.name..' - stopping build, mission no longer valid') + self.currentBuild = nil + else + local cost = self.currentBuild.product.cost + if self.currentBuild.isRepair then + cost = math.floor(self.currentBuild.product.cost/2) + end + + if self.currentBuild.progress < cost then + if self.currentBuild.isRepair and not Group.getByName(self.currentBuild.product.name) then + env.info('ZoneCommand:progressBuild '..self.name..' - stopping build, group to repair no longer exists') + self.currentBuild = nil + else + if self.currentBuild.isRepair then + local gr = Group.getByName(self.currentBuild.product.name) + if gr and self.currentBuild.unitcount and gr:getSize() < self.currentBuild.unitcount then + env.info('ZoneCommand:progressBuild '..self.name..' - restarting build, group to repair has casualties') + self.currentBuild.unitcount = gr:getSize() + self:addResource(self.currentBuild.progress) + self.currentBuild.progress = 0 + end + end + + local step = math.floor(ZoneCommand.buildSpeed * self.boostScale) + if self.currentBuild.product.type == ZoneCommand.productTypes.mission then + if self.currentBuild.product.missionType == ZoneCommand.missionTypes.supply_air or + self.currentBuild.product.missionType == ZoneCommand.missionTypes.supply_convoy or + self.currentBuild.product.missionType == ZoneCommand.missionTypes.supply_transfer then + step = math.floor(ZoneCommand.supplyBuildSpeed * self.boostScale) + + if self.currentBuild.product.missionType == ZoneCommand.missionTypes.supply_transfer then + step = math.floor(step*2) + end + end + end + + if step > self.resource then step = 1 end + if step <= self.resource then + self:removeResource(step) + self.currentBuild.progress = self.currentBuild.progress + step + + if self.extraBuildResources > 0 then + local extrastep = step + if self.extraBuildResources < extrastep then + extrastep = self.extraBuildResources + end + + self.extraBuildResources = math.max(self.extraBuildResources - extrastep, 0) + self.currentBuild.progress = self.currentBuild.progress + extrastep + + env.info('ZoneCommand:progressBuild - '..self.name..' consumed '..extrastep..' extra resources, remaining '..self.extraBuildResources) + end + end + end + else + if self.currentBuild.product.type == 'mission' then + if self:isMissionValid(self.currentBuild.product) then + self:activateMission(self.currentBuild.product) + else + self:addResource(self.currentBuild.product.cost) + end + elseif self.currentBuild.product.type == 'defense' or self.currentBuild.product.type=='upgrade' then + if self.currentBuild.isRepair then + if Group.getByName(self.currentBuild.product.name) then + self.zone:spawnGroup(self.currentBuild.product) + end + else + self.zone:spawnGroup(self.currentBuild.product) + end + + self.built[self.currentBuild.product.name] = self.currentBuild.product + end + + self.currentBuild = nil + end + end + end + + if self.currentMissionBuild then + if self.currentMissionBuild.product.type == 'mission' and not self:isMissionValid(self.currentMissionBuild.product) then + env.info('ZoneCommand:progressBuild '..self.name..' - stopping build, mission no longer valid') + self.currentMissionBuild = nil + else + local cost = self.currentMissionBuild.product.cost + + if self.currentMissionBuild.progress < cost then + local step = math.floor(ZoneCommand.buildSpeed * self.boostScale) + + if step > self.resource then step = 1 end + + local progress = step*self.missionBuildSpeedReduction + local reducedCost = math.max(1, math.floor(progress)) + if reducedCost <= self.resource then + self:removeResource(reducedCost) + self.currentMissionBuild.progress = self.currentMissionBuild.progress + progress + end + else + if self:isMissionValid(self.currentMissionBuild.product) then + self:activateMission(self.currentMissionBuild.product) + else + self:addResource(self.currentMissionBuild.product.cost) + end + + self.currentMissionBuild = nil + end + end + end + end + + function ZoneCommand:queueBuild(product, side, isRepair, progress) + if product.type ~= ZoneCommand.productTypes.mission or + (product.missionType == ZoneCommand.missionTypes.supply_air or + product.missionType == ZoneCommand.missionTypes.supply_convoy or + product.missionType == ZoneCommand.missionTypes.supply_transfer) then + + local unitcount = nil + if isRepair then + local g = Group.getByName(product.name) + if g then + unitcount = g:getSize() + env.info('ZoneCommand:queueBuild - '..self.name..' '..product.name..' has '..unitcount..' units') + end + end + + self.currentBuild = { product = product, progress = (progress or 0), side = side, isRepair = isRepair, unitcount = unitcount} + env.info('ZoneCommand:queueBuild - '..self.name..' chose '..product.name..'('..product.display..') as its build') + else + self.currentMissionBuild = { product = product, progress = (progress or 0), side = side} + env.info('ZoneCommand:queueBuild - '..self.name..' chose '..product.name..'('..product.display..') as its mission build') + end + end + + function ZoneCommand:reserveMission(product) + self.reservedMissions[product.name] = product + end + + function ZoneCommand:unReserveMission(product) + self.reservedMissions[product.name] = nil + end + + function ZoneCommand:isMissionValid(product) + if Group.getByName(product.name) then return false end + + if self.reservedMissions[product.name] then + return false + end + + if product.missionType == ZoneCommand.missionTypes.supply_convoy then + if self.distToFront == nil then return false end + + for _,tgt in pairs(self.neighbours) do + if self:isSupplyMissionValid(product, tgt) then + return true + end + end + elseif product.missionType == ZoneCommand.missionTypes.supply_transfer then + if self.distToFront == nil then return false end + for _,tgt in pairs(self.neighbours) do + if self:isSupplyTransferMissionValid(product, tgt) then + return true + end + end + elseif product.missionType == ZoneCommand.missionTypes.supply_air then + if self.distToFront == nil then return false end + + for _,tgt in pairs(self.neighbours) do + if self:isSupplyMissionValid(product, tgt) then + return true + else + for _,subtgt in pairs(tgt.neighbours) do + if subtgt.name ~= self.name and self:isSupplyMissionValid(product, subtgt) then + local dist = mist.utils.get2DDist(self.zone.point, subtgt.zone.point) + if dist < 50000 then + return true + end + end + end + end + end + elseif product.missionType == ZoneCommand.missionTypes.assault then + if self.mode ~= ZoneCommand.modes.normal then return false end + for _,tgt in pairs(self.neighbours) do + if self:isAssaultMissionValid(product, tgt) then + return true + end + end + elseif product.missionType == ZoneCommand.missionTypes.cas then + if self.mode ~= ZoneCommand.modes.normal and not self.keepActive then return false end + + for _,tgt in pairs(ZoneCommand.getAllZones()) do + if self:isCasMissionValid(product, tgt) then + return true + end + end + elseif product.missionType == ZoneCommand.missionTypes.cas_helo then + if self.mode ~= ZoneCommand.modes.normal and not self.keepActive then return false end + + for _,tgt in pairs(self.neighbours) do + if self:isCasMissionValid(product, tgt) then + return true + end + end + elseif product.missionType == ZoneCommand.missionTypes.strike then + if self.mode ~= ZoneCommand.modes.normal and not self.keepActive then return false end + + for _,tgt in pairs(ZoneCommand.getAllZones()) do + if self:isStrikeMissionValid(product, tgt) then + return true + end + end + elseif product.missionType == ZoneCommand.missionTypes.sead then + if self.mode ~= ZoneCommand.modes.normal and not self.keepActive then return false end + + for _,tgt in pairs(ZoneCommand.getAllZones()) do + if self:isSeadMissionValid(product, tgt) then + return true + end + end + elseif product.missionType == ZoneCommand.missionTypes.patrol then + if self.mode ~= ZoneCommand.modes.normal and not self.keepActive then return false end + + for _,tgt in pairs(ZoneCommand.getAllZones()) do + if self:isPatrolMissionValid(product, tgt) then + return true + end + end + elseif product.missionType == ZoneCommand.missionTypes.bai then + if not ZoneCommand.groupMonitor then return false end + if self.mode ~= ZoneCommand.modes.normal and not self.keepActive then return false end + + for _,tgt in pairs(ZoneCommand.groupMonitor.groups) do + if self:isBaiMissionValid(product, tgt) then + return true + end + end + elseif product.missionType == ZoneCommand.missionTypes.awacs then + if not ZoneCommand.groupMonitor then return false end + if self.mode ~= ZoneCommand.modes.normal and not self.keepActive then return false end + for _,tgt in pairs(ZoneCommand.getAllZones()) do + if self:isAwacsMissionValid(product, tgt) then + return true + end + end + elseif product.missionType == ZoneCommand.missionTypes.tanker then + if not ZoneCommand.groupMonitor then return false end + if self.mode ~= ZoneCommand.modes.normal and not self.keepActive then return false end + if not self.distToFront or self.distToFront == 0 then return false end + for _,tgt in pairs(ZoneCommand.getAllZones()) do + if self:isTankerMissionValid(product, tgt) then + return true + end + end + end + end + + function ZoneCommand:activateMission(product) + if product.missionType == ZoneCommand.missionTypes.supply_convoy then + self:activateSupplyConvoyMission(product) + elseif product.missionType == ZoneCommand.missionTypes.assault then + self:activateAssaultMission(product) + elseif product.missionType == ZoneCommand.missionTypes.supply_air then + self:activateAirSupplyMission(product) + elseif product.missionType == ZoneCommand.missionTypes.supply_transfer then + self:activateSupplyTransferMission(product) + elseif product.missionType == ZoneCommand.missionTypes.cas then + self:activateCasMission(product) + elseif product.missionType == ZoneCommand.missionTypes.cas_helo then + self:activateCasMission(product, true) + elseif product.missionType == ZoneCommand.missionTypes.strike then + self:activateStrikeMission(product) + elseif product.missionType == ZoneCommand.missionTypes.sead then + self:activateSeadMission(product) + elseif product.missionType == ZoneCommand.missionTypes.patrol then + self:activatePatrolMission(product) + elseif product.missionType == ZoneCommand.missionTypes.bai then + self:activateBaiMission(product) + elseif product.missionType == ZoneCommand.missionTypes.awacs then + self:activateAwacsMission(product) + elseif product.missionType == ZoneCommand.missionTypes.tanker then + self:activateTankerMission(product) + end + + env.info('ZoneCommand:activateMission - '..self.name..' activating mission '..product.name..'('..product.display..')') + end + + function ZoneCommand:reActivateMission(savedData) + local product = self:getProductByName(savedData.productName) + + if product.missionType == ZoneCommand.missionTypes.supply_convoy then + self:reActivateSupplyConvoyMission(product, savedData) + elseif product.missionType == ZoneCommand.missionTypes.assault then + self:reActivateAssaultMission(product, savedData) + elseif product.missionType == ZoneCommand.missionTypes.supply_air then + self:reActivateAirSupplyMission(product, savedData) + elseif product.missionType == ZoneCommand.missionTypes.supply_transfer then + self:reActivateSupplyTransferMission(product, savedData) + elseif product.missionType == ZoneCommand.missionTypes.cas then + self:reActivateCasMission(product, nil, savedData) + elseif product.missionType == ZoneCommand.missionTypes.cas_helo then + self:reActivateCasMission(product, true, savedData) + elseif product.missionType == ZoneCommand.missionTypes.strike then + self:reActivateStrikeMission(product, savedData) + elseif product.missionType == ZoneCommand.missionTypes.sead then + self:reActivateSeadMission(product, savedData) + elseif product.missionType == ZoneCommand.missionTypes.patrol then + self:reActivatePatrolMission(product, savedData) + elseif product.missionType == ZoneCommand.missionTypes.bai then + self:reActivateBaiMission(product, savedData) + elseif product.missionType == ZoneCommand.missionTypes.awacs then + self:reActivateAwacsMission(product, savedData) + elseif product.missionType == ZoneCommand.missionTypes.tanker then + self:reActivateTankerMission(product, savedData) + end + + env.info('ZoneCommand:reActivateMission - '..self.name..' reactivating mission '..product.name..'('..product.display..')') + end + + local function getDefaultPos(savedData, isAir) + local action = 'Off Road' + local speed = 0 + if isAir then + action = 'Turning Point' + speed = 250 + end + + local vars = { + groupName = savedData.productName, + point = savedData.position, + action = 'respawn', + heading = savedData.heading, + initTasks = false, + route = { + [1] = { + alt = savedData.position.y, + type = 'Turning Point', + action = action, + alt_type = 'BARO', + x = savedData.position.x, + y = savedData.position.z, + speed = speed + } + } + } + + return vars + end + + local function teleportToPos(groupName, pos) + if pos.y == nil then + pos.y = land.getHeight({ x = pos.x, y = pos.z }) + end + + local vars = { + groupName = groupName, + point = pos, + action = 'respawn', + initTasks = false + } + + mist.teleportToPoint(vars) + end + + function ZoneCommand:reActivateSupplyConvoyMission(product, savedData) + local zone = ZoneCommand.getZoneByName(savedData.lastMission.zoneName) + + local supplyPoint = trigger.misc.getZone(zone.name..'-sp') + if not supplyPoint then + supplyPoint = trigger.misc.getZone(zone.name) + end + if supplyPoint then + mist.teleportToPoint(getDefaultPos(savedData, false)) + if ZoneCommand.groupMonitor then + ZoneCommand.groupMonitor:registerGroup(product, zone, self, savedData) + end + + product.lastMission = {zoneName = zone.name} + timer.scheduleFunction(function(param) + local gr = Group.getByName(param.name) + TaskExtensions.moveOnRoadToPoint(gr, param.point) + end, {name=product.name, point={ x=supplyPoint.point.x, y = supplyPoint.point.z}}, timer.getTime()+1) + end + end + + function ZoneCommand:reActivateAssaultMission(product, savedData) + local zone = ZoneCommand.getZoneByName(savedData.lastMission.zoneName) + + local supplyPoint = trigger.misc.getZone(zone.name..'-sp') + if not supplyPoint then + supplyPoint = trigger.misc.getZone(zone.name) + end + if supplyPoint then + mist.teleportToPoint(getDefaultPos(savedData, false)) + if ZoneCommand.groupMonitor then + ZoneCommand.groupMonitor:registerGroup(product, zone, self, savedData) + end + + local tgtPoint = trigger.misc.getZone(zone.name) + + if tgtPoint then + product.lastMission = {zoneName = zone.name} + timer.scheduleFunction(function(param) + local gr = Group.getByName(param.name) + TaskExtensions.moveOnRoadToPointAndAssault(gr, param.point, param.targets) + end, {name=product.name, point={ x=tgtPoint.point.x, y = tgtPoint.point.z}, targets=zone.built}, timer.getTime()+1) + end + end + end + + function ZoneCommand:reActivateAirSupplyMission(product, savedData) + local zone = ZoneCommand.getZoneByName(savedData.lastMission.zoneName) + + mist.teleportToPoint(getDefaultPos(savedData, true)) + if ZoneCommand.groupMonitor then + ZoneCommand.groupMonitor:registerGroup(product, zone, self, savedData) + end + + local supplyPoint = trigger.misc.getZone(zone.name..'-hsp') + if not supplyPoint then + supplyPoint = trigger.misc.getZone(zone.name) + end + + if supplyPoint then + product.lastMission = {zoneName = zone.name} + local alt = self.connectionManager:getHeliAlt(self.name, zone.name) + timer.scheduleFunction(function(param) + local gr = Group.getByName(param.name) + TaskExtensions.landAtPoint(gr, param.point, param.alt) + end, {name=product.name, point={ x=supplyPoint.point.x, y = supplyPoint.point.z}, alt = alt}, timer.getTime()+1) + end + end + + function ZoneCommand:reActivateSupplyTransferMission(product, savedData) + -- not needed + end + + function ZoneCommand:reActivateCasMission(product, isHelo, savedData) + local zone = ZoneCommand.getZoneByName(savedData.lastMission.zoneName) + + mist.teleportToPoint(getDefaultPos(savedData, true)) + if ZoneCommand.groupMonitor then + ZoneCommand.groupMonitor:registerGroup(product, zone, self, savedData) + end + + local homePos = trigger.misc.getZone(savedData.homeName).point + + if zone then + product.lastMission = {zoneName = zone.name} + timer.scheduleFunction(function(param) + local gr = Group.getByName(param.prod.name) + if param.helo then + TaskExtensions.executeHeloCasMission(gr, param.targets, param.prod.expend, param.prod.altitude, {homePos = homePos}) + else + TaskExtensions.executeCasMission(gr, param.targets, param.prod.expend, param.prod.altitude, {homePos = homePos}) + end + end, {prod=product, targets=zone.built, helo = isHelo, homePos = homePos}, timer.getTime()+1) + end + end + + function ZoneCommand:reActivateStrikeMission(product, savedData) + local zone = ZoneCommand.getZoneByName(savedData.lastMission.zoneName) + + mist.teleportToPoint(getDefaultPos(savedData, true)) + + if ZoneCommand.groupMonitor then + ZoneCommand.groupMonitor:registerGroup(product, zone, self, savedData) + end + + local homePos = trigger.misc.getZone(savedData.homeName).point + + if zone then + product.lastMission = {zoneName = zone.name} + timer.scheduleFunction(function(param) + local gr = Group.getByName(param.prod.name) + TaskExtensions.executeStrikeMission(gr, param.targets, param.prod.expend, param.prod.altitude, {homePos = homePos}) + end, {prod=product, targets=zone.built, homePos = homePos}, timer.getTime()+1) + end + end + + function ZoneCommand:reActivateSeadMission(product, savedData) + local zone = ZoneCommand.getZoneByName(savedData.lastMission.zoneName) + + mist.teleportToPoint(getDefaultPos(savedData, true)) + if ZoneCommand.groupMonitor then + ZoneCommand.groupMonitor:registerGroup(product, zone, self, savedData) + end + + local homePos = trigger.misc.getZone(savedData.homeName).point + + if zone then + product.lastMission = {zoneName = zone.name} + timer.scheduleFunction(function(param) + local gr = Group.getByName(param.prod.name) + TaskExtensions.executeSeadMission(gr, param.targets, param.prod.expend, param.prod.altitude, {homePos = homePos}) + end, {prod=product, targets=zone.built, homePos = homePos}, timer.getTime()+1) + end + end + + function ZoneCommand:reActivatePatrolMission(product, savedData) + + local zn1 = ZoneCommand.getZoneByName(savedData.lastMission.zone1name) + + mist.teleportToPoint(getDefaultPos(savedData, true)) + if ZoneCommand.groupMonitor then + ZoneCommand.groupMonitor:registerGroup(product, zn1, self, savedData) + end + + local homePos = trigger.misc.getZone(savedData.homeName).point + + if zn1 then + product.lastMission = {zone1name = zn1.name} + timer.scheduleFunction(function(param) + local gr = Group.getByName(param.prod.name) + + local point = trigger.misc.getZone(param.zone1.name).point + + TaskExtensions.executePatrolMission(gr, point, param.prod.altitude, param.prod.range, {homePos = param.homePos}) + end, {prod=product, zone1 = zn1, homePos = homePos}, timer.getTime()+1) + end + end + + function ZoneCommand:reActivateBaiMission(product, savedData) + local targets = {} + local hasTarget = false + for _,tgt in pairs(ZoneCommand.groupMonitor.groups) do + if self:isBaiMissionValid(product, tgt) then + targets[tgt.product.name] = tgt.product + hasTarget = true + end + end + + local homePos = trigger.misc.getZone(savedData.homeName).point + + if hasTarget then + mist.teleportToPoint(getDefaultPos(savedData, true)) + if ZoneCommand.groupMonitor then + ZoneCommand.groupMonitor:registerGroup(product, nil, self, savedData) + end + + product.lastMission = { active = true } + timer.scheduleFunction(function(param) + local gr = Group.getByName(param.prod.name) + TaskExtensions.executeBaiMission(gr, param.targets, param.prod.expend, param.prod.altitude, {homePos = param.homePos}) + end, {prod=product, targets=targets, homePos = homePos}, timer.getTime()+1) + end + end + + function ZoneCommand:reActivateAwacsMission(product, savedData) + + local zone = ZoneCommand.getZoneByName(savedData.lastMission.zoneName) + local homePos = trigger.misc.getZone(savedData.homeName).point + + mist.teleportToPoint(getDefaultPos(savedData, true)) + if ZoneCommand.groupMonitor then + ZoneCommand.groupMonitor:registerGroup(product, nil, self, savedData) + end + timer.scheduleFunction(function(param) + local gr = Group.getByName(param.prod.name) + if gr then + local un = gr:getUnit(1) + if un then + local callsign = un:getCallsign() + RadioFrequencyTracker.registerRadio(param.prod.name, '[AWACS] '..callsign, param.prod.freq..' AM') + end + + local point = trigger.misc.getZone(param.target.name).point + product.lastMission = { zoneName = param.target.name } + TaskExtensions.executeAwacsMission(gr, point, param.prod.altitude, param.prod.freq, {homePos = param.homePos}) + end + end, {prod=product, target=zone, homePos = homePos}, timer.getTime()+1) + end + + function ZoneCommand:reActivateTankerMission(product, savedData) + + local zone = ZoneCommand.getZoneByName(savedData.lastMission.zoneName) + + local homePos = trigger.misc.getZone(savedData.homeName).point + mist.teleportToPoint(getDefaultPos(savedData, true)) + if ZoneCommand.groupMonitor then + ZoneCommand.groupMonitor:registerGroup(product, zone, self, savedData) + end + + timer.scheduleFunction(function(param) + local gr = Group.getByName(param.prod.name) + if gr then + local un = gr:getUnit(1) + if un then + local callsign = un:getCallsign() + RadioFrequencyTracker.registerRadio(param.prod.name, '[Tanker('..param.prod.variant..')] '..callsign, param.prod.freq..' AM | TCN '..param.prod.tacan..'X') + end + + local point = trigger.misc.getZone(param.target.name).point + product.lastMission = { zoneName = param.target.name } + TaskExtensions.executeTankerMission(gr, point, param.prod.altitude, param.prod.freq, param.prod.tacan, {homePos = param.homePos}) + end + end, {prod=product, target=zone, homePos = homePos}, timer.getTime()+1) + end + + function ZoneCommand:isBaiMissionValid(product, tgtgroup) + if product.side == tgtgroup.product.side then return false end + if tgtgroup.product.type ~= ZoneCommand.productTypes.mission then return false end + if tgtgroup.product.missionType == ZoneCommand.missionTypes.assault then return true end + if tgtgroup.product.missionType == ZoneCommand.missionTypes.supply_convoy then return true end + end + + function ZoneCommand:activateBaiMission(product) + --{name = product.name, lastStateTime = timer.getAbsTime(), product = product, target = target} + local targets = {} + local hasTarget = false + for _,tgt in pairs(ZoneCommand.groupMonitor.groups) do + if self:isBaiMissionValid(product, tgt) then + targets[tgt.product.name] = tgt.product + hasTarget = true + end + end + + if hasTarget then + local og = Utils.getOriginalGroup(product.name) + if og then + teleportToPos(product.name, {x=og.x, z=og.y}) + env.info("ZoneCommand - activateBaiMission teleporting to OG pos") + else + mist.respawnGroup(product.name, true) + env.info("ZoneCommand - activateBaiMission fallback to respawnGroup") + end + + if ZoneCommand.groupMonitor then + ZoneCommand.groupMonitor:registerGroup(product, nil, self) + end + + product.lastMission = { active = true } + timer.scheduleFunction(function(param) + local gr = Group.getByName(param.prod.name) + TaskExtensions.executeBaiMission(gr, param.targets, param.prod.expend, param.prod.altitude) + end, {prod=product, targets=targets}, timer.getTime()+1) + + env.info("ZoneCommand - "..product.name.." targeting convoys") + end + end + + local function prioritizeSupplyTargets(a,b) + --if a:criticalOnSupplies() and not b:criticalOnSupplies() then return true end + --if b:criticalOnSupplies() and not a:criticalOnSupplies() then return false end + + if a.distToFront~=nil and b.distToFront == nil then + return true + elseif a.distToFront == nil and b.distToFront ~= nil then + return false + elseif a.distToFront == b.distToFront then + return a.resource < b.resource + else + return a.distToFront 1 and target.distToFront > 1 then return false end -- skip regular missions if not close to front + + if self.mode == 'normal' and self.distToFront == 0 and target.distToFront == 0 then + return target:needsSupplies(product.cost*0.5) + end + + if target:needsSupplies(product.cost*0.5) and target.distToFront < self.distToFront then + return true + elseif target:criticalOnSupplies() and self.distToFront>=target.distToFront then + return true + end + + if target.mode == 'normal' and target:needsSupplies(product.cost*0.5) then + return true + end + end + end + + function ZoneCommand:activateSupplyConvoyMission(product) + local tgtzones = {} + for _,v in pairs(self.neighbours) do + if (v.side == 0 or v.side==product.side) then + table.insert(tgtzones, v) + end + end + + if #tgtzones == 0 then + env.info('ZoneCommand:activateSupplyConvoyMission - '..self.name..' no valid tgtzones') + return + end + + table.sort(tgtzones, prioritizeSupplyTargets) + + if BattlefieldManager and BattlefieldManager.priorityZones[self.side] then + local prioZone = BattlefieldManager.priorityZones[self.side] + if prioZone.side == 0 and self.neighbours[prioZone.name] and self:isSupplyMissionValid(product, prioZone) then + tgtzones = { prioZone } + end + end + + for i,v in ipairs(tgtzones) do + if self:isSupplyMissionValid(product, v) then + + local supplyPoint = trigger.misc.getZone(v.name..'-sp') + if not supplyPoint then + supplyPoint = trigger.misc.getZone(v.name) + end + + if supplyPoint then + + local og = Utils.getOriginalGroup(product.name) + if og then + teleportToPos(product.name, {x=og.x, z=og.y}) + env.info("ZoneCommand - activateSupplyConvoyMission teleporting to OG pos") + else + mist.respawnGroup(product.name, true) + env.info("ZoneCommand - activateSupplyConvoyMission fallback to respawnGroup") + end + + if ZoneCommand.groupMonitor then + ZoneCommand.groupMonitor:registerGroup(product, v, self) + end + + product.lastMission = {zoneName = v.name} + timer.scheduleFunction(function(param) + local gr = Group.getByName(param.name) + TaskExtensions.moveOnRoadToPoint(gr, param.point) + end, {name=product.name, point={ x=supplyPoint.point.x, y = supplyPoint.point.z}}, timer.getTime()+1) + + env.info("ZoneCommand - "..product.name.." targeting "..v.name) + end + + break + end + end + end + + function ZoneCommand:isAssaultMissionValid(product, target) + if not self.connectionManager then + env.info("ZoneCommand - ERROR missing connection manager") + end + + if product.missionType == ZoneCommand.missionTypes.assault then + if self.connectionManager:isRoadBlocked(self.name, target.name) then + return false + end + end + + if target.side ~= product.side and target.side ~= 0 then + return true + end + end + + function ZoneCommand:activateAssaultMission(product) + local tgtzones = {} + for _,v in pairs(self.neighbours) do + table.insert(tgtzones, {zone = v, rank = math.random()}) + end + + table.sort(tgtzones, function(a,b) return a.rank < b.rank end) + + local sorted = {} + for i,v in ipairs(tgtzones) do + table.insert(sorted, v.zone) + end + tgtzones = sorted + + if BattlefieldManager and BattlefieldManager.priorityZones[self.side] then + local prioZone = BattlefieldManager.priorityZones[self.side] + if self.neighbours[prioZone.name] and self:isAssaultMissionValid(product, prioZone) then + tgtzones = { prioZone } + end + end + + for i,v in ipairs(tgtzones) do + if self:isAssaultMissionValid(product, v) then + + local og = Utils.getOriginalGroup(product.name) + if og then + teleportToPos(product.name, {x=og.x, z=og.y}) + env.info("ZoneCommand - activateAssaultMission teleporting to OG pos") + else + mist.respawnGroup(product.name, true) + env.info("ZoneCommand - activateAssaultMission fallback to respawnGroup") + end + + if ZoneCommand.groupMonitor then + ZoneCommand.groupMonitor:registerGroup(product, v, self) + end + + local tgtPoint = trigger.misc.getZone(v.name) + + if tgtPoint then + product.lastMission = {zoneName = v.name} + timer.scheduleFunction(function(param) + local gr = Group.getByName(param.name) + TaskExtensions.moveOnRoadToPointAndAssault(gr, param.point, param.targets) + end, {name=product.name, point={ x=tgtPoint.point.x, y = tgtPoint.point.z}, targets=v.built}, timer.getTime()+1) + + env.info("ZoneCommand - "..product.name.." targeting "..v.name) + end + + break + end + end + end + + function ZoneCommand:isAwacsMissionValid(product, target) + if target.side ~= product.side then return false end + if target.name == self.name then return false end + if not target.distToFront or target.distToFront ~= 4 then return false end + + return true + end + + function ZoneCommand:activateAwacsMission(product) + local tgtzones = {} + for _,v in pairs(ZoneCommand.getAllZones()) do + if self:isAwacsMissionValid(product, v) then + table.insert(tgtzones, v) + end + end + + local choice1 = math.random(1,#tgtzones) + local zn = tgtzones[choice1] + + local og = Utils.getOriginalGroup(product.name) + if og then + teleportToPos(product.name, {x=og.x, z=og.y}) + env.info("ZoneCommand - activateAwacsMission teleporting to OG pos") + else + mist.respawnGroup(product.name, true) + env.info("ZoneCommand - activateAwacsMission fallback to respawnGroup") + end + + if ZoneCommand.groupMonitor then + ZoneCommand.groupMonitor:registerGroup(product, zn, self) + end + timer.scheduleFunction(function(param) + local gr = Group.getByName(param.prod.name) + if gr then + local un = gr:getUnit(1) + if un then + local callsign = un:getCallsign() + RadioFrequencyTracker.registerRadio(param.prod.name, '[AWACS] '..callsign, param.prod.freq..' AM') + end + + local point = trigger.misc.getZone(param.target.name).point + product.lastMission = { zoneName = param.target.name } + TaskExtensions.executeAwacsMission(gr, point, param.prod.altitude, param.prod.freq) + + end + end, {prod=product, target=zn}, timer.getTime()+1) + + env.info("ZoneCommand - "..product.name.." targeting "..zn.name) + end + + function ZoneCommand:isTankerMissionValid(product, target) + if target.side ~= product.side then return false end + if target.name == self.name then return false end + if not target.distToFront or target.distToFront ~= 4 then return false end + + return true + end + + function ZoneCommand:activateTankerMission(product) + + local tgtzones = {} + for _,v in pairs(ZoneCommand.getAllZones()) do + if self:isTankerMissionValid(product, v) then + table.insert(tgtzones, v) + end + end + + local choice1 = math.random(1,#tgtzones) + local zn = tgtzones[choice1] + table.remove(tgtzones, choice1) + + local og = Utils.getOriginalGroup(product.name) + if og then + teleportToPos(product.name, {x=og.x, z=og.y}) + env.info("ZoneCommand - activateTankerMission teleporting to OG pos") + else + mist.respawnGroup(product.name, true) + env.info("ZoneCommand - activateTankerMission fallback to respawnGroup") + end + + if ZoneCommand.groupMonitor then + ZoneCommand.groupMonitor:registerGroup(product, zn, self) + end + + timer.scheduleFunction(function(param) + local gr = Group.getByName(param.prod.name) + if gr then + local un = gr:getUnit(1) + if un then + local callsign = un:getCallsign() + RadioFrequencyTracker.registerRadio(param.prod.name, '[Tanker('..param.prod.variant..')] '..callsign, param.prod.freq..' AM | TCN '..param.prod.tacan..'X') + end + + local point = trigger.misc.getZone(param.target.name).point + product.lastMission = { zoneName = param.target.name } + TaskExtensions.executeTankerMission(gr, point, param.prod.altitude, param.prod.freq, param.prod.tacan) + end + end, {prod=product, target=zn}, timer.getTime()+1) + + env.info("ZoneCommand - "..product.name.." targeting "..zn.name) + end + + function ZoneCommand:isPatrolMissionValid(product, target) + --if target.side ~= product.side then return false end + if target.name == self.name then return false end + if not target.distToFront or target.distToFront > 1 then return false end + if target.side ~= product.side and target.side ~= 0 then return false end + local dist = mist.utils.get2DDist(self.zone.point, target.zone.point) + if dist > 150000 then return false end + + return true + end + + function ZoneCommand:activatePatrolMission(product) + local tgtzones = {} + for _,v in pairs(ZoneCommand.getAllZones()) do + if self:isPatrolMissionValid(product, v) then + table.insert(tgtzones, v) + end + end + + local choice1 = math.random(1,#tgtzones) + local zn1 = tgtzones[choice1] + + if BattlefieldManager and BattlefieldManager.priorityZones[self.side] then + local prioZone = BattlefieldManager.priorityZones[self.side] + if self:isPatrolMissionValid(product, prioZone) then + zn1 = prioZone + end + end + + local og = Utils.getOriginalGroup(product.name) + if og then + teleportToPos(product.name, {x=og.x, z=og.y}) + env.info("ZoneCommand - activatePatrolMission teleporting to OG pos") + else + mist.respawnGroup(product.name, true) + env.info("ZoneCommand - activatePatrolMission fallback to respawnGroup") + end + + if ZoneCommand.groupMonitor then + ZoneCommand.groupMonitor:registerGroup(product, zn1, self) + end + + if zn1 then + product.lastMission = {zone1name = zn1.name} + timer.scheduleFunction(function(param) + local gr = Group.getByName(param.prod.name) + + local point = trigger.misc.getZone(param.zone1.name).point + + TaskExtensions.executePatrolMission(gr, point, param.prod.altitude, param.prod.range) + end, {prod=product, zone1 = zn1}, timer.getTime()+1) + + env.info("ZoneCommand - "..product.name.." targeting "..zn1.name) + end + end + + function ZoneCommand:isSeadMissionValid(product, target) + if target.side == 0 then return false end + if not target.distToFront or target.distToFront > 1 then return false end + + --if MissionTargetRegistry.isZoneTargeted(target.name) then return false end + + return target:hasEnemySAMRadar(product) + end + + function ZoneCommand:hasEnemySAMRadar(product) + if product.side == 1 then + return self:hasSAMRadarOnSide(2) + elseif product.side == 2 then + return self:hasSAMRadarOnSide(1) + end + end + + function ZoneCommand:hasSAMRadarOnSide(side) + for i,v in pairs(self.built) do + if v.type == ZoneCommand.productTypes.defense and v.side == side then + local gr = Group.getByName(v.name) + if gr then + for _,unit in ipairs(gr:getUnits()) do + if unit:hasAttribute('SAM SR') or unit:hasAttribute('SAM TR') then + return true + end + end + end + end + end + end + + function ZoneCommand:hasRunway() + local zones = self:getRunwayZones() + return #zones > 0 + end + + function ZoneCommand:getRunwayZones() + local runways = {} + for i=1,10,1 do + local name = self.name..'-runway-'..i + local zone = trigger.misc.getZone(name) + if zone then + runways[i] = {name = name, zone = zone} + else + break + end + end + + return runways + end + + function ZoneCommand:getRandomUnitWithAttributeOnSide(attributes, side) + local available = {} + for i,v in pairs(self.built) do + if v.type == ZoneCommand.productTypes.upgrade and v.side == side then + local st = StaticObject.getByName(v.name) + if st then + for _,a in ipairs(attributes) do + if a == "Buildings" and ZoneCommand.staticRegistry[v.name] then -- dcs does not consider all statics buildings so we compensate + table.insert(available, v) + end + end + end + elseif v.type == ZoneCommand.productTypes.defense and v.side == side then + local gr = Group.getByName(v.name) + if gr then + for _,unit in ipairs(gr:getUnits()) do + for _,a in ipairs(attributes) do + if unit:hasAttribute(a) then + table.insert(available, v) + end + end + end + end + end + end + + if #available > 0 then + return available[math.random(1, #available)] + end + end + + function ZoneCommand:hasUnitWithAttributeOnSide(attributes, side, amount) + local count = 0 + + for i,v in pairs(self.built) do + if v.type == ZoneCommand.productTypes.upgrade and v.side == side then + local st = StaticObject.getByName(v.name) + if st then + for _,a in ipairs(attributes) do + if a == "Buildings" and ZoneCommand.staticRegistry[v.name] then -- dcs does not consider all statics buildings so we compensate + if amount==nil then + return true + else + count = count + 1 + if count >= amount then return true end + end + end + end + end + elseif v.type == ZoneCommand.productTypes.defense and v.side == side then + local gr = Group.getByName(v.name) + if gr then + for _,unit in ipairs(gr:getUnits()) do + for _,a in ipairs(attributes) do + if unit:hasAttribute(a) then + if amount==nil then + return true + else + count = count + 1 + if count >= amount then return true end + end + end + end + end + end + end + end + end + + function ZoneCommand:getUnitCountWithAttributeOnSide(attributes, side) + local count = 0 + + for i,v in pairs(self.built) do + if v.type == ZoneCommand.productTypes.upgrade and v.side == side then + local st = StaticObject.getByName(v.name) + if st then + for _,a in ipairs(attributes) do + if a == "Buildings" and ZoneCommand.staticRegistry[v.name] then + count = count + 1 + break + end + end + end + elseif v.type == ZoneCommand.productTypes.defense and v.side == side then + local gr = Group.getByName(v.name) + if gr then + for _,unit in ipairs(gr:getUnits()) do + for _,a in ipairs(attributes) do + if unit:hasAttribute(a) then + count = count + 1 + break + end + end + end + end + end + end + + return count + end + + function ZoneCommand:activateSeadMission(product) + local tgtzones = {} + for _,v in pairs(ZoneCommand.getAllZones()) do + if self:isSeadMissionValid(product, v) then + table.insert(tgtzones, v) + end + end + + local choice = math.random(1,#tgtzones) + local target = tgtzones[choice] + + if BattlefieldManager and BattlefieldManager.priorityZones[self.side] then + local prioZone = BattlefieldManager.priorityZones[self.side] + if self:isSeadMissionValid(product, prioZone) then + target = prioZone + end + end + + local og = Utils.getOriginalGroup(product.name) + if og then + teleportToPos(product.name, {x=og.x, z=og.y}) + env.info("ZoneCommand - activateSeadMission teleporting to OG pos") + else + mist.respawnGroup(product.name, true) + env.info("ZoneCommand - activateSeadMission fallback to respawnGroup") + end + + if ZoneCommand.groupMonitor then + ZoneCommand.groupMonitor:registerGroup(product, target, self) + end + + if target then + product.lastMission = {zoneName = target.name} + timer.scheduleFunction(function(param) + local gr = Group.getByName(param.prod.name) + TaskExtensions.executeSeadMission(gr, param.targets, param.prod.expend, param.prod.altitude) + end, {prod=product, targets=target.built}, timer.getTime()+1) + + env.info("ZoneCommand - "..product.name.." targeting "..target.name) + end + end + + function ZoneCommand:isStrikeMissionValid(product, target) + if target.side == 0 then return false end + if target.side == product.side then return false end + if not target.distToFront or target.distToFront > 0 then return false end + + if target:hasEnemySAMRadar(product) then return false end + + --if MissionTargetRegistry.isZoneTargeted(target.name) then return false end + + for i,v in pairs(target.built) do + if v.type == ZoneCommand.productTypes.upgrade and v.side ~= product.side then + return true + end + end + end + + function ZoneCommand:activateStrikeMission(product) + local tgtzones = {} + for _,v in pairs(ZoneCommand.getAllZones()) do + if self:isStrikeMissionValid(product, v) then + table.insert(tgtzones, v) + end + end + + local choice = math.random(1,#tgtzones) + local target = tgtzones[choice] + + if BattlefieldManager and BattlefieldManager.priorityZones[self.side] then + local prioZone = BattlefieldManager.priorityZones[self.side] + if self:isStrikeMissionValid(product, prioZone) then + target = prioZone + end + end + + local og = Utils.getOriginalGroup(product.name) + if og then + teleportToPos(product.name, {x=og.x, z=og.y}) + env.info("ZoneCommand - activateStrikeMission teleporting to OG pos") + else + mist.respawnGroup(product.name, true) + env.info("ZoneCommand - activateStrikeMission fallback to respawnGroup") + end + + if ZoneCommand.groupMonitor then + ZoneCommand.groupMonitor:registerGroup(product, target, self) + end + + if target then + product.lastMission = {zoneName = target.name} + timer.scheduleFunction(function(param) + local gr = Group.getByName(param.prod.name) + TaskExtensions.executeStrikeMission(gr, param.targets, param.prod.expend, param.prod.altitude) + end, {prod=product, targets=target.built}, timer.getTime()+1) + + env.info("ZoneCommand - "..product.name.." targeting "..target.name) + end + end + + function ZoneCommand:isCasMissionValid(product, target) + if target.side == product.side then return false end + if not target.distToFront or target.distToFront > 0 then return false end + + if target:hasEnemySAMRadar(product) then return false end + + --if MissionTargetRegistry.isZoneTargeted(target.name) then return false end + + for i,v in pairs(target.built) do + if v.type == ZoneCommand.productTypes.defense and v.side ~= product.side then + return true + end + end + end + + function ZoneCommand:activateCasMission(product, ishelo) + local viablezones = {} + if ishelo then + viablezones = self.neighbours + else + viablezones = ZoneCommand.getAllZones() + end + + local tgtzones = {} + for _,v in pairs(viablezones) do + if self:isCasMissionValid(product, v) then + table.insert(tgtzones, v) + end + end + + local choice = math.random(1,#tgtzones) + local target = tgtzones[choice] + + if BattlefieldManager and BattlefieldManager.priorityZones[self.side] then + local prioZone = BattlefieldManager.priorityZones[self.side] + if viablezones[prioZone.name] and self:isCasMissionValid(product, prioZone) then + target = prioZone + end + end + + local og = Utils.getOriginalGroup(product.name) + if og then + teleportToPos(product.name, {x=og.x, z=og.y}) + env.info("ZoneCommand - activateCasMission teleporting to OG pos") + else + mist.respawnGroup(product.name, true) + env.info("ZoneCommand - activateCasMission fallback to respawnGroup") + end + + if ZoneCommand.groupMonitor then + ZoneCommand.groupMonitor:registerGroup(product, target, self) + end + + if target then + product.lastMission = {zoneName = target.name} + timer.scheduleFunction(function(param) + local gr = Group.getByName(param.prod.name) + if param.helo then + TaskExtensions.executeHeloCasMission(gr, param.targets, param.prod.expend, param.prod.altitude) + else + TaskExtensions.executeCasMission(gr, param.targets, param.prod.expend, param.prod.altitude) + end + end, {prod=product, targets=target.built, helo = ishelo}, timer.getTime()+1) + + env.info("ZoneCommand - "..product.name.." targeting "..target.name) + end + end + + function ZoneCommand:defineUpgrades(upgrades) + self.upgrades = upgrades + + for side,sd in ipairs(self.upgrades) do + for _,v in ipairs(sd) do + v.side = side + + local cat = TemplateDB.getData(v.template) + if cat.dataCategory == TemplateDB.type.static then + ZoneCommand.staticRegistry[v.name] = true + end + + for _,v2 in ipairs(v.products) do + v2.side = side + + if v2.type == "mission" then + local gr = Group.getByName(v2.name) + + if not gr then + if v2.missionType ~= ZoneCommand.missionTypes.supply_transfer then + env.info("ZoneCommand - ERROR declared group does not exist in mission: ".. v2.name) + end + else + gr:destroy() + end + end + end + end + end + end + + function ZoneCommand:getProductByName(name) + for i,v in ipairs(self.upgrades) do + for i2,v2 in ipairs(v) do + if v2.name == name then + return v2 + else + for i3,v3 in ipairs(v2.products) do + if v3.name == name then + return v3 + end + end + end + end + end + + return nil + end + + function ZoneCommand:cleanup() + local zn = trigger.misc.getZone(self.name) + local pos = { + x = zn.point.x, + y = land.getHeight({x = zn.point.x, y = zn.point.z}), + z= zn.point.z + } + local radius = zn.radius*2 + world.removeJunk({id = world.VolumeType.SPHERE,params = {point = pos, radius = radius}}) + end +end + + + + +-----------------[[ END OF ZoneCommand.lua ]]----------------- + + + +-----------------[[ BattlefieldManager.lua ]]----------------- + +BattlefieldManager = {} +do + BattlefieldManager.closeOverride = 27780 -- 15nm + BattlefieldManager.farOverride = Config.maxDistFromFront -- default 100nm + BattlefieldManager.boostScale = {[0] = 1.0, [1]=1.0, [2]=1.0} + BattlefieldManager.noRedZones = false + BattlefieldManager.noBlueZones = false + + BattlefieldManager.priorityZones = { + [1] = nil, + [2] = nil + } + + BattlefieldManager.overridePriorityZones = { + [1] = nil, + [2] = nil + } + + function BattlefieldManager:new() + local obj = {} + + setmetatable(obj, self) + self.__index = self + obj:start() + return obj + end + + function BattlefieldManager:start() + timer.scheduleFunction(function(param, time) + local self = param.context + + local zones = ZoneCommand.getAllZones() + local torank = {} + + --reset ranks and define frontline + for name,zone in pairs(zones) do + zone.distToFront = nil + zone.closestEnemyDist = nil + + if zone.neighbours then + for nName, nZone in pairs(zone.neighbours) do + if zone.side ~= nZone.side then + zone.distToFront = 0 + end + end + end + + --set dist to closest enemy + for name2,zone2 in pairs(zones) do + if zone.side ~= zone2.side then + local dist = mist.utils.get2DDist(zone.zone.point, zone2.zone.point) + if not zone.closestEnemyDist or dist < zone.closestEnemyDist then + zone.closestEnemyDist = dist + end + end + end + end + + for name,zone in pairs(zones) do + if zone.distToFront == 0 then + for nName, nZone in pairs(zone.neighbours) do + if nZone.distToFrount == nil then + table.insert(torank, nZone) + end + end + end + end + + -- build ranks of every other zone + while #torank > 0 do + local nexttorank = {} + for _,zone in ipairs(torank) do + if not zone.distToFront then + local minrank = 999 + for nName,nZone in pairs(zone.neighbours) do + if nZone.distToFront then + if nZone.distToFront BattlefieldManager.farOverride and zone.distToFront > 3 then + zone.mode = ZoneCommand.modes.export + else + if zone.mode ~= ZoneCommand.modes.normal then + zone:fullBuild(1.0) + end + zone.mode = ZoneCommand.modes.normal + end + else + if not zone.distToFront or zone.distToFront == 0 or (zone.closestEnemyDist and zone.closestEnemyDist < BattlefieldManager.closeOverride) then + if zone.mode ~= ZoneCommand.modes.normal then + zone:fullBuild(1.0) + end + zone.mode = ZoneCommand.modes.normal + elseif zone.distToFront == 1 then + zone.mode = ZoneCommand.modes.supply + elseif zone.distToFront > 1 then + zone.mode = ZoneCommand.modes.export + end + end + + zone.boostScale = self.boostScale[zone.side] + end + + return time+60 + end, {context = self}, timer.getTime()+1) + + timer.scheduleFunction(function(param, time) + local self = param.context + + local zones = ZoneCommand.getAllZones() + + local noRed = true + local noBlue = true + for name, zone in pairs(zones) do + if zone.side == 1 then + noRed = false + elseif zone.side == 2 then + noBlue = false + end + + if not noRed and not noBlue then + break + end + end + + if noRed then + BattlefieldManager.noRedZones = true + end + + if noBlue then + BattlefieldManager.noBlueZones = true + end + + return time+10 + end, {context = self}, timer.getTime()+1) + + timer.scheduleFunction(function(param, time) + local self = param.context + + local zones = ZoneCommand.getAllZones() + + local frontLineRed = {} + local frontLineBlue = {} + for name, zone in pairs(zones) do + if zone.distToFront == 0 then + if zone.side == 1 then + table.insert(frontLineRed, zone) + elseif zone.side == 2 then + table.insert(frontLineBlue, zone) + else + table.insert(frontLineRed, zone) + table.insert(frontLineBlue, zone) + end + end + end + + if BattlefieldManager.overridePriorityZones[1] and BattlefieldManager.overridePriorityZones[1].ticks > 0 then + BattlefieldManager.priorityZones[1] = BattlefieldManager.overridePriorityZones[1].zone + BattlefieldManager.overridePriorityZones[1].ticks = BattlefieldManager.overridePriorityZones[1].ticks - 1 + else + local redChangeChance = 1 + if BattlefieldManager.priorityZones[1] and BattlefieldManager.priorityZones[1].side ~= 1 then + redChangeChance = 0.1 + end + + if #frontLineBlue > 0 then + if math.random() <= redChangeChance then + BattlefieldManager.priorityZones[1] = frontLineBlue[math.random(1,#frontLineBlue)] + end + else + BattlefieldManager.priorityZones[1] = nil + end + end + + if BattlefieldManager.overridePriorityZones[2] and BattlefieldManager.overridePriorityZones[2].ticks > 0 then + BattlefieldManager.priorityZones[2] = BattlefieldManager.overridePriorityZones[2].zone + BattlefieldManager.overridePriorityZones[2].ticks = BattlefieldManager.overridePriorityZones[2].ticks - 1 + else + local blueChangeChance = 1 + if BattlefieldManager.priorityZones[2] and BattlefieldManager.priorityZones[2].side ~= 2 then + blueChangeChance = 0.1 + end + + if #frontLineRed > 0 then + if math.random() <= blueChangeChance then + BattlefieldManager.priorityZones[2] = frontLineRed[math.random(1,#frontLineRed)] + end + else + BattlefieldManager.priorityZones[2] = nil + end + end + + if BattlefieldManager.priorityZones[1] then + env.info('BattlefieldManager - red priority: '..BattlefieldManager.priorityZones[1].name) + else + env.info('BattlefieldManager - red no priority') + end + + if BattlefieldManager.priorityZones[2] then + env.info('BattlefieldManager - blue priority: '..BattlefieldManager.priorityZones[2].name) + else + env.info('BattlefieldManager - blue no priority') + end + + if BattlefieldManager.overridePriorityZones[1] and BattlefieldManager.overridePriorityZones[1].ticks == 0 then + BattlefieldManager.overridePriorityZones[1] = nil + end + + if BattlefieldManager.overridePriorityZones[2] and BattlefieldManager.overridePriorityZones[2].ticks == 0 then + BattlefieldManager.overridePriorityZones[2] = nil + end + + return time+(60*30) + end, {context = self}, timer.getTime()+10) + + timer.scheduleFunction(function(param, time) + local x = math.random(-50,50) -- the lower limit benefits blue, higher limit benefits red, adjust to increase limit of random boost variance, default (-50,50) + local boostIntensity = Config.randomBoost -- adjusts the intensity of the random boost variance, default value = 0.0004 + local factor = (x*x*x*boostIntensity)/100 -- the farther x is the higher the factor, negative beneifts blue, pozitive benefits red + param.context.boostScale[1] = 1.0+factor + param.context.boostScale[2] = 1.0-factor + + local red = 0 + local blue = 0 + for i,v in pairs(ZoneCommand.getAllZones()) do + if v.side == 1 then + red = red + 1 + elseif v.side == 2 then + blue = blue + 1 + end + + --v:cleanup() + end + + -- push factor towards coalition with less zones (up to 0.5) + local multiplier = Config.lossCompensation -- adjust this to boost losing side production(higher means losing side gains more advantage) (default 1.25) + local total = red + blue + local redp = (0.5-(red/total))*multiplier + local bluep = (0.5-(blue/total))*multiplier + + -- cap factor to avoid increasing difficulty until the end + redp = math.min(redp, 0.15) + bluep = math.max(bluep, -0.15) + + param.context.boostScale[1] = param.context.boostScale[1] + redp + param.context.boostScale[2] = param.context.boostScale[2] + bluep + + --limit to numbers above 0 + param.context.boostScale[1] = math.max(0.01,param.context.boostScale[1]) + param.context.boostScale[2] = math.max(0.01,param.context.boostScale[2]) + + env.info('BattlefieldManager - power red = '..param.context.boostScale[1]) + env.info('BattlefieldManager - power blue = '..param.context.boostScale[2]) + + return time+(60*30) + end, {context = self}, timer.getTime()+1) + end + + function BattlefieldManager.overridePriority(side, zone, ticks) + BattlefieldManager.overridePriorityZones[side] = { zone = zone, ticks = ticks } + BattlefieldManager.priorityZones[side] = zone + + env.info('BattlefieldManager.overridePriority - '..side..' focusing on '..zone.name) + end +end + +-----------------[[ END OF BattlefieldManager.lua ]]----------------- + + + +-----------------[[ Preset.lua ]]----------------- + +Preset = {} +do + function Preset:new(obj) + setmetatable(obj, self) + self.__index = self + return obj + end + + function Preset:extend(new) + return Preset:new(Utils.merge(self, new)) + end +end + +-----------------[[ END OF Preset.lua ]]----------------- + + + +-----------------[[ PlayerTracker.lua ]]----------------- + +PlayerTracker = {} +do + PlayerTracker.savefile = 'player_stats.json' + PlayerTracker.statTypes = { + xp = 'XP', + cmd = "CMD" + } + + PlayerTracker.cmdShopTypes = { + smoke = 'smoke', + prio = 'prio', + jtac = 'jtac', + bribe1 = 'bribe1', + bribe2 = 'bribe2', + } + + PlayerTracker.cmdShopPrices = { + [PlayerTracker.cmdShopTypes.smoke] = 1, + [PlayerTracker.cmdShopTypes.prio] = 2, + [PlayerTracker.cmdShopTypes.jtac] = 3, + [PlayerTracker.cmdShopTypes.bribe1] = 1, + [PlayerTracker.cmdShopTypes.bribe2] = 3, + } + + function PlayerTracker:new(markerCommands) + local obj = {} + obj.markerCommands = markerCommands + obj.stats = {} + obj.tempStats = {} + obj.groupMenus = {} + obj.groupShopMenus = {} + obj.groupTgtMenus = {} + obj.playerAircraft = {} + obj.playerWeaponStock = {} + + if lfs then + local dir = lfs.writedir()..'Missions/Saves/' + lfs.mkdir(dir) + PlayerTracker.savefile = dir..PlayerTracker.savefile + env.info('Pretense - Player stats file path: '..PlayerTracker.savefile) + end + + local save = Utils.loadTable(PlayerTracker.savefile) + if save then + obj.stats = save.stats or {} + obj.playerWeaponStock = save.playerWeaponStock or {} + end + + setmetatable(obj, self) + self.__index = self + + obj:init() + + return obj + end + + function PlayerTracker:init() + local ev = {} + ev.context = self + function ev:onEvent(event) + if not event.initiator then return end + if not event.initiator.getPlayerName then return end + if not event.initiator.getCoalition then return end + + local player = event.initiator:getPlayerName() + if not player then return end + + if event.id==world.event.S_EVENT_PLAYER_ENTER_UNIT then + if event.initiator and event.initiator:getCategory() == Object.Category.UNIT and + (event.initiator:getDesc().category == Unit.Category.AIRPLANE or event.initiator:getDesc().category == Unit.Category.HELICOPTER) then + + local pname = event.initiator:getPlayerName() + if pname then + local gr = event.initiator:getGroup() + if trigger.misc.getUserFlag(gr:getName())==1 then + trigger.action.outTextForGroup(gr:getID(), 'Can not spawn as '..gr:getName()..' in enemy/neutral zone',5) + event.initiator:destroy() + end + end + end + end + + if event.id == world.event.S_EVENT_BIRTH then + -- init stats for player if not exist + if not self.context.stats[player] then + self.context.stats[player] = {} + end + + -- reset temp track for player + self.context.tempStats[player] = nil + -- reset playeraircraft + self.context.playerAircraft[player] = nil + end + + if event.id == world.event.S_EVENT_KILL then + local target = event.target + + if not target then return end + if not target.getCoalition then return end + + if target:getCoalition() == event.initiator:getCoalition() then return end + + local xpkey = PlayerTracker.statTypes.xp + local award = PlayerTracker.getXP(target) + + local instantxp = math.floor(award*0.25) + local tempxp = award - instantxp + + self.context:addStat(player, instantxp, PlayerTracker.statTypes.xp) + local msg = '[XP] '..self.context.stats[player][xpkey]..' (+'..instantxp..')' + env.info("PlayerTracker.kill - "..player..' awarded '..tostring(instantxp)..' xp') + + self.context:addTempStat(player, tempxp, PlayerTracker.statTypes.xp) + msg = msg..'\n+'..tempxp..' XP (unclaimed)' + env.info("PlayerTracker.kill - "..player..' awarded '..tostring(tempxp)..' xp (unclaimed)') + + trigger.action.outTextForUnit(event.initiator:getID(), msg, 5) + end + + if event.id==world.event.S_EVENT_EJECTION then + self.context.stats[player] = self.context.stats[player] or {} + local ts = self.context.tempStats[player] + if ts then + local un = event.initiator + local key = PlayerTracker.statTypes.xp + local xp = self.context.tempStats[player][key] + if xp then + local isFree = event.initiator:getGroup():getName():find("(FREE)") + trigger.action.outTextForUnit(un:getID(), 'Ejection. 30\% XP claimed', 5) + self.context:addStat(player, math.floor(xp*0.3), PlayerTracker.statTypes.xp) + trigger.action.outTextForUnit(un:getID(), '[XP] '..self.context.stats[player][key]..' (+'..math.floor(xp*0.3)..')', 5) + end + + self.context.tempStats[player] = nil + end + end + + if event.id==world.event.S_EVENT_TAKEOFF then + local un = event.initiator + local zn = ZoneCommand.getZoneOfUnit(event.initiator:getName()) + env.info('PlayerTracker - '..player..' took off in '..tostring(un:getID())..' '..un:getName()) + if un and zn and zn.side == un:getCoalition() then + timer.scheduleFunction(function(param, time) + local un = param.unit + if not un or not un:isExist() then return end + local player = param.player + local inAir = Utils.isInAir(un) + env.info('PlayerTracker - '..player..' checking if in air: '..tostring(inAir)) + if inAir and param.context.playerAircraft[player] == nil then + if param.context.playerAircraft[player] == nil then + param.context.playerAircraft[player] = { unitID = un:getID() } + end + end + end, {player = player, unit = event.initiator, context = self.context}, timer.getTime()+10) + end + end + + if event.id==world.event.S_EVENT_LAND then + local un = event.initiator + local zn = ZoneCommand.getZoneOfUnit(event.initiator:getName()) + local aircraft = self.context.playerAircraft[player] + env.info('PlayerTracker - '..player..' landed in '..tostring(un:getID())..' '..un:getName()) + if aircraft and un and zn and zn.side == un:getCoalition() then + trigger.action.outTextForUnit(event.initiator:getID(), "Wait 10 seconds to validate landing...", 10) + timer.scheduleFunction(function(param, time) + local un = param.unit + if not un or not un:isExist() then return end + + local player = param.player + local isLanded = Utils.isLanded(un, true) + local zn = ZoneCommand.getZoneOfUnit(un:getName()) + + env.info('PlayerTracker - '..player..' checking if landed: '..tostring(isLanded)) + + if isLanded then + if self.context.tempStats[player] then + if zn and zn.side == un:getCoalition() then + self.context.stats[player] = self.context.stats[player] or {} + + trigger.action.outTextForUnit(un:getID(), 'Rewards claimed', 5) + for _,key in pairs(PlayerTracker.statTypes) do + local value = self.context.tempStats[player][key] + env.info("PlayerTracker.landing - "..player..' redeeming '..tostring(value)..' '..key) + if value then + self.context:commitTempStat(player, key) + trigger.action.outTextForUnit(un:getID(), key..' +'..value..'', 5) + end + end + end + end + + local aircraft = param.context.playerAircraft[player] + if aircraft and aircraft.unitID == un:getID() then + param.context.playerAircraft[player] = nil + end + end + end, {player = player, unit = event.initiator, context = self.context}, timer.getTime()+10) + end + + end + end + + world.addEventHandler(ev) + self:periodicSave() + self:menuSetup() + end + + function PlayerTracker:addTempStat(player, amount, stattype) + self.tempStats[player] = self.tempStats[player] or {} + self.tempStats[player][stattype] = self.tempStats[player][stattype] or 0 + self.tempStats[player][stattype] = self.tempStats[player][stattype] + amount + end + + function PlayerTracker:addStat(player, amount, stattype) + self.stats[player] = self.stats[player] or {} + self.stats[player][stattype] = self.stats[player][stattype] or 0 + + if stattype == PlayerTracker.statTypes.xp then + local cur = self:getRank(self.stats[player][stattype]) + if cur then + local nxt = self:getRank(self.stats[player][stattype] + amount) + if nxt and cur.rank < nxt.rank then + trigger.action.outText(player..' has leveled up to rank: '..nxt.name, 10) + if nxt.cmdAward and nxt.cmdAward > 0 then + self:addStat(player, nxt.cmdAward, PlayerTracker.statTypes.cmd) + trigger.action.outText(player.." awarded "..nxt.cmdAward.." CMD tokens", 10) + env.info("PlayerTracker.addStat - Awarded "..player.." "..nxt.cmdAward.." CMD tokens for rank up to "..nxt.name) + end + end + end + end + + self.stats[player][stattype] = self.stats[player][stattype] + amount + end + + function PlayerTracker:commitTempStat(player, statkey) + local value = self.tempStats[player][statkey] + if value then + self:addStat(player, value, statkey) + + self.tempStats[player][statkey] = nil + end + end + + function PlayerTracker:addRankRewards(player, unit, isTemp) + local rank = self:getPlayerRank(player) + if not rank then return end + + local cmdChance = rank.cmdChance + if cmdChance > 0 then + local die = math.random() + if die <= cmdChance then + if isTemp then + self:addTempStat(player, 1, PlayerTracker.statTypes.cmd) + else + self:addStat(player, 1, PlayerTracker.statTypes.cmd) + end + + local msg = "" + if isTemp then + msg = '+1 CMD (unclaimed)' + else + msg = '[CMD] '..self.stats[player][PlayerTracker.statTypes.cmd]..' (+1)' + end + + trigger.action.outTextForUnit(unit:getID(), msg, 5) + env.info("PlayerTracker.addRankRewards - Awarded "..player.." a CMD token with chance "..cmdChance.." die roll "..die) + end + end + end + + function PlayerTracker.getXP(unit) + local xp = 30 + + if unit:hasAttribute('Planes') then xp = xp + 20 end + if unit:hasAttribute('Helicopters') then xp = xp + 20 end + if unit:hasAttribute('Infantry') then xp = xp + 10 end + if unit:hasAttribute('SAM SR') then xp = xp + 15 end + if unit:hasAttribute('SAM TR') then xp = xp + 15 end + if unit:hasAttribute('IR Guided SAM') then xp = xp + 10 end + if unit:hasAttribute('Ships') then xp = xp + 20 end + if unit:hasAttribute('Buildings') then xp = xp + 30 end + if unit:hasAttribute('Tanks') then xp = xp + 10 end + + return xp + end + + function PlayerTracker:menuSetup() + + MenuRegistry:register(1, function(event, context) + if event.id == world.event.S_EVENT_BIRTH and event.initiator and event.initiator.getPlayerName then + local player = event.initiator:getPlayerName() + if player then + local groupid = event.initiator:getGroup():getID() + local groupname = event.initiator:getGroup():getName() + + if context.groupMenus[groupid] then + missionCommands.removeItemForGroup(groupid, context.groupMenus[groupid]) + context.groupMenus[groupid] = nil + end + + if not context.groupMenus[groupid] then + + local menu = missionCommands.addSubMenuForGroup(groupid, 'Information') + missionCommands.addCommandForGroup(groupid, 'Player', menu, Utils.log(context.showGroupStats), context, groupname) + missionCommands.addCommandForGroup(groupid, 'Frequencies', menu, Utils.log(context.showFrequencies), context, groupname) + + context.groupMenus[groupid] = menu + end + end + elseif (event.id == world.event.S_EVENT_PLAYER_LEAVE_UNIT or event.id == world.event.S_EVENT_DEAD) and event.initiator and event.initiator.getPlayerName then + local player = event.initiator:getPlayerName() + if player then + local groupid = event.initiator:getGroup():getID() + + if context.groupMenus[groupid] then + missionCommands.removeItemForGroup(groupid, context.groupMenus[groupid]) + context.groupMenus[groupid] = nil + end + end + end + end, self) + + MenuRegistry:register(4, function(event, context) + if event.id == world.event.S_EVENT_BIRTH and event.initiator and event.initiator.getPlayerName then + local player = event.initiator:getPlayerName() + if player then + local rank = context:getPlayerRank(player) + if not rank then return end + + if rank.cmdChance > 0 then + local groupid = event.initiator:getGroup():getID() + local groupname = event.initiator:getGroup():getName() + + if context.groupShopMenus[groupid] then + missionCommands.removeItemForGroup(groupid, context.groupShopMenus[groupid]) + context.groupShopMenus[groupid] = nil + end + + if context.groupTgtMenus[groupid] then + missionCommands.removeItemForGroup(groupid, context.groupTgtMenus[groupid]) + context.groupTgtMenus[groupid] = nil + end + + if not context.groupShopMenus[groupid] then + + local menu = missionCommands.addSubMenuForGroup(groupid, 'Command & Control') + missionCommands.addCommandForGroup(groupid, 'Deploy Smoke ['..PlayerTracker.cmdShopPrices[PlayerTracker.cmdShopTypes.smoke]..' CMD]', menu, Utils.log(context.buyCommand), context, groupname, PlayerTracker.cmdShopTypes.smoke) + missionCommands.addCommandForGroup(groupid, 'Hack enemy comms ['..PlayerTracker.cmdShopPrices[PlayerTracker.cmdShopTypes.bribe1]..' CMD]', menu, Utils.log(context.buyCommand), context, groupname, PlayerTracker.cmdShopTypes.bribe1) + missionCommands.addCommandForGroup(groupid, 'Prioritize zone ['..PlayerTracker.cmdShopPrices[PlayerTracker.cmdShopTypes.prio]..' CMD]', menu, Utils.log(context.buyCommand), context, groupname, PlayerTracker.cmdShopTypes.prio) + missionCommands.addCommandForGroup(groupid, 'Bribe enemy officer ['..PlayerTracker.cmdShopPrices[PlayerTracker.cmdShopTypes.bribe2]..' CMD]', menu, Utils.log(context.buyCommand), context, groupname, PlayerTracker.cmdShopTypes.bribe2) + + if CommandFunctions.jtac then + missionCommands.addCommandForGroup(groupid, 'Deploy JTAC ['..PlayerTracker.cmdShopPrices[PlayerTracker.cmdShopTypes.jtac]..' CMD]', menu, Utils.log(context.buyCommand), context, groupname, PlayerTracker.cmdShopTypes.jtac) + end + + context.groupShopMenus[groupid] = menu + end + end + end + elseif (event.id == world.event.S_EVENT_PLAYER_LEAVE_UNIT or event.id == world.event.S_EVENT_DEAD) and event.initiator and event.initiator.getPlayerName then + local player = event.initiator:getPlayerName() + if player then + local groupid = event.initiator:getGroup():getID() + + if context.groupShopMenus[groupid] then + missionCommands.removeItemForGroup(groupid, context.groupShopMenus[groupid]) + context.groupShopMenus[groupid] = nil + end + + if context.groupTgtMenus[groupid] then + missionCommands.removeItemForGroup(groupid, context.groupTgtMenus[groupid]) + context.groupTgtMenus[groupid] = nil + end + end + end + end, self) + + self.markerCommands:addCommand('stats',function(event, _, state) + local unit = nil + if event.initiator then + unit = event.initiator + elseif world.getPlayer() then + unit = world.getPlayer() + end + + if not unit then return false end + + state:showGroupStats(unit:getGroup():getName()) + return true + end, false, self) + + self.markerCommands:addCommand('freqs',function(event, _, state) + local unit = nil + if event.initiator then + unit = event.initiator + elseif world.getPlayer() then + unit = world.getPlayer() + end + + if not unit then return false end + + state:showFrequencies(unit:getGroup():getName()) + return true + end, false, self) + end + + function PlayerTracker:buyCommand(groupname, itemType) + local gr = Group.getByName(groupname) + if gr and gr:getSize()>0 then + local un = gr:getUnit(1) + if un then + local player = un:getPlayerName() + local cost = PlayerTracker.cmdShopPrices[itemType] + local cmdTokens = self.stats[player][PlayerTracker.statTypes.cmd] + + if cmdTokens and cost <= cmdTokens then + if self.groupTgtMenus[gr:getID()] then + missionCommands.removeItemForGroup(gr:getID(), self.groupTgtMenus[gr:getID()]) + self.groupTgtMenus[gr:getID()] = nil + end + + if itemType == PlayerTracker.cmdShopTypes.smoke then + + self.groupTgtMenus[gr:getID()] = MenuRegistry.showTargetZoneMenu(gr:getID(), "Smoke Marker target", function(params) + CommandFunctions.smokeTargets(params.zone, 5) + trigger.action.outTextForGroup(params.groupid, "Targets marked at "..params.zone.name.." with red smoke", 5) + end, 1, 1) + trigger.action.outTextForGroup(gr:getID(), "Select target from radio menu",10) + + elseif itemType == PlayerTracker.cmdShopTypes.jtac then + + self.groupTgtMenus[gr:getID()] = MenuRegistry.showTargetZoneMenu(gr:getID(), "JTAC target", function(params) + + CommandFunctions.spawnJtac(params.zone) + trigger.action.outTextForGroup(params.groupid, "Reaper orbiting "..params.zone.name,5) + + end, 1, 1) + trigger.action.outTextForGroup(gr:getID(), "Select target from radio menu",10) + + elseif itemType== PlayerTracker.cmdShopTypes.prio then + + self.groupTgtMenus[gr:getID()] = MenuRegistry.showTargetZoneMenu(gr:getID(), "Priority zone", function(params) + BattlefieldManager.overridePriority(2, params.zone, 2) + trigger.action.outTextForGroup(params.groupid, "Blue is concentrating efforts on "..params.zone.name.." for the next hour", 5) + end, nil, 1) + trigger.action.outTextForGroup(gr:getID(), "Select target from radio menu",10) + + elseif itemType== PlayerTracker.cmdShopTypes.bribe1 then + + timer.scheduleFunction(function(params, time) + local count = 0 + for i,v in pairs(ZoneCommand.getAllZones()) do + if v.side == 1 and v.distToFront <= 1 then + if math.random()<0.5 then + v:reveal() + count = count + 1 + end + end + end + if count > 0 then + trigger.action.outTextForGroup(params.groupid, "Intercepted enemy communications have revealed information on "..count.." enemy zones",20) + else + trigger.action.outTextForGroup(params.groupid, "No useful information has been intercepted",20) + end + end, {groupid=gr:getID()}, timer.getTime()+60) + + trigger.action.outTextForGroup(gr:getID(), "Attempting to intercept enemy comms...",60) + + elseif itemType == PlayerTracker.cmdShopTypes.bribe2 then + timer.scheduleFunction(function(params, time) + local count = 0 + for i,v in pairs(ZoneCommand.getAllZones()) do + if v.side == 1 then + if math.random()<0.5 then + v:reveal() + count = count + 1 + end + end + end + + if count > 0 then + trigger.action.outTextForGroup(params.groupid, "Bribed officer has shared intel on "..count.." enemy zones",20) + else + trigger.action.outTextForGroup(params.groupid, "Bribed officer has stopped responding to attempted communications.",20) + end + end, {groupid=gr:getID()}, timer.getTime()+(60*5)) + + trigger.action.outTextForGroup(gr:getID(), "Bribe has been transfered to enemy officer. Waiting for contact...",20) + end + + self.stats[player][PlayerTracker.statTypes.cmd] = self.stats[player][PlayerTracker.statTypes.cmd] - cost + else + trigger.action.outTextForUnit(un:getID(), "Insufficient CMD to buy selected item", 5) + end + end + end + end + + function PlayerTracker:showFrequencies(groupname) + local gr = Group.getByName(groupname) + if gr then + for i,v in pairs(gr:getUnits()) do + if v.getPlayerName and v:getPlayerName() then + local message = RadioFrequencyTracker.getRadioFrequencyMessage(gr:getCoalition()) + trigger.action.outTextForUnit(v:getID(), message, 20) + end + end + end + end + + function PlayerTracker:showGroupStats(groupname) + local gr = Group.getByName(groupname) + if gr then + for i,v in pairs(gr:getUnits()) do + if v.getPlayerName and v:getPlayerName() then + local player = v:getPlayerName() + local message = '['..player..']\n' + + local stats = self.stats[player] + if stats then + local xp = stats[PlayerTracker.statTypes.xp] + if xp then + local rank, nextRank = self:getRank(xp) + + message = message ..'\nXP: '..xp + + if rank then + message = message..'\nRank: '..rank.name + end + + if nextRank then + message = message..'\nXP needed for promotion: '..(nextRank.requiredXP-xp) + end + end + + local cmd = stats[PlayerTracker.statTypes.cmd] + if cmd then + message = message ..'\n\nCMD: '..cmd + end + end + + local tstats = self.tempStats[player] + if tstats then + message = message..'\n' + local tempxp = tstats[PlayerTracker.statTypes.xp] + if tempxp and tempxp > 0 then + message = message .. '\nUnclaimed XP: '..tempxp + end + end + + trigger.action.outTextForUnit(v:getID(), message, 10) + end + end + end + end + + function PlayerTracker:periodicSave() + timer.scheduleFunction(function(param, time) + local tosave = {} + tosave.stats = param.stats + tosave.playerWeaponStock = param.playerWeaponStock + + --temp mission stat tracking + tosave.zones = {} + tosave.zones.red = {} + tosave.zones.blue = {} + tosave.zones.neutral = {} + for i,v in pairs(ZoneCommand.getAllZones()) do + if v.side == 1 then + table.insert(tosave.zones.red,v.name) + elseif v.side == 2 then + table.insert(tosave.zones.blue,v.name) + elseif v.side == 0 then + table.insert(tosave.zones.neutral,v.name) + end + end + + tosave.players = {} + for i,v in ipairs(coalition.getPlayers(2)) do + if v and v:isExist() and v.getPlayerName then + table.insert(tosave.players, {name=v:getPlayerName(), unit=v:getDesc().typeName}) + end + end + + --end mission stat tracking + + Utils.saveTable(PlayerTracker.savefile, tosave) + env.info("PlayerTracker - state saved") + return time+60 + end, self, timer.getTime()+60) + end + + PlayerTracker.ranks = {} + PlayerTracker.ranks[1] = { rank=1, name='E-1 Airman basic', requiredXP = 0, cmdChance = 0, cmdAward=0} + PlayerTracker.ranks[2] = { rank=2, name='E-2 Airman', requiredXP = 2000, cmdChance = 0, cmdAward=0} + PlayerTracker.ranks[3] = { rank=3, name='E-3 Airman first class', requiredXP = 4500, cmdChance = 0, cmdAward=0} + PlayerTracker.ranks[4] = { rank=4, name='E-4 Senior airman', requiredXP = 7700, cmdChance = 0, cmdAward=0} + PlayerTracker.ranks[5] = { rank=5, name='E-5 Staff sergeant', requiredXP = 11800, cmdChance = 0, cmdAward=0} + PlayerTracker.ranks[6] = { rank=6, name='E-6 Technical sergeant', requiredXP = 17000, cmdChance = 0.01, cmdAward=1} + PlayerTracker.ranks[7] = { rank=7, name='E-7 Master sergeant', requiredXP = 23500, cmdChance = 0.02, cmdAward=1} + PlayerTracker.ranks[8] = { rank=8, name='E-8 Senior master sergeant', requiredXP = 31500, cmdChance = 0.03, cmdAward=1} + PlayerTracker.ranks[9] = { rank=9, name='E-9 Chief master sergeant', requiredXP = 42000, cmdChance = 0.05, cmdAward=1} + PlayerTracker.ranks[10] = { rank=10, name='O-1 Second lieutenant', requiredXP = 52800, cmdChance = 0.08, cmdAward=2} + PlayerTracker.ranks[11] = { rank=11, name='O-2 First lieutenant', requiredXP = 66500, cmdChance = 0.10, cmdAward=2} + PlayerTracker.ranks[12] = { rank=12, name='O-3 Captain', requiredXP = 82500, cmdChance = 0.14, cmdAward=2} + PlayerTracker.ranks[13] = { rank=13, name='O-4 Major', requiredXP = 101000, cmdChance = 0.17, cmdAward=2} + PlayerTracker.ranks[14] = { rank=14, name='O-5 Lieutenant colonel', requiredXP = 122200, cmdChance = 0.22, cmdAward=3} + PlayerTracker.ranks[15] = { rank=15, name='O-6 Colonel', requiredXP = 146300, cmdChance = 0.26, cmdAward=3} + PlayerTracker.ranks[16] = { rank=16, name='O-7 Brigadier general', requiredXP = 173500, cmdChance = 0.32, cmdAward=3} + PlayerTracker.ranks[17] = { rank=17, name='O-8 Major general', requiredXP = 204000, cmdChance = 0.37, cmdAward=4} + PlayerTracker.ranks[18] = { rank=18, name='O-9 Lieutenant general', requiredXP = 238000, cmdChance = 0.43, cmdAward=4} + PlayerTracker.ranks[19] = { rank=19, name='O-10 General', requiredXP = 275700, cmdChance = 0.50, cmdAward=5} + + function PlayerTracker:getPlayerRank(playername) + if self.stats[playername] then + local xp = self.stats[playername][PlayerTracker.statTypes.xp] + if xp then + return self:getRank(xp) + end + end + end + + function PlayerTracker:getRank(xp) + local rank = nil + local nextRank = nil + for _, rnk in ipairs(PlayerTracker.ranks) do + if rnk.requiredXP <= xp then + rank = rnk + else + nextRank = rnk + break + end + end + + return rank, nextRank + end +end + +-----------------[[ END OF PlayerTracker.lua ]]----------------- + + + +-----------------[[ MissionTargetRegistry.lua ]]----------------- + +MissionTargetRegistry = {} +do + MissionTargetRegistry.playerTargetZones = {} + + function MissionTargetRegistry.addZone(zone) + MissionTargetRegistry.playerTargetZones[zone] = true + end + + function MissionTargetRegistry.removeZone(zone) + MissionTargetRegistry.playerTargetZones[zone] = nil + end + + function MissionTargetRegistry.isZoneTargeted(zone) + return MissionTargetRegistry.playerTargetZones[zone] ~= nil + end + + MissionTargetRegistry.baiTargets = {} + + function MissionTargetRegistry.addBaiTarget(target) + MissionTargetRegistry.baiTargets[target.name] = target + env.info('MissionTargetRegistry - bai target added '..target.name) + end + + function MissionTargetRegistry.baiTargetsAvailable(coalition) + local targets = {} + for i,v in pairs(MissionTargetRegistry.baiTargets) do + if v.product.side == coalition then + local tgt = Group.getByName(v.name) + + if not tgt or not tgt:isExist() or tgt:getSize()==0 then + MissionTargetRegistry.removeBaiTarget(v) + elseif not v.state or v.state ~= 'enroute' then + MissionTargetRegistry.removeBaiTarget(v) + else + table.insert(targets, v) + end + end + end + + return #targets > 0 + end + + function MissionTargetRegistry.getRandomBaiTarget(coalition) + local targets = {} + for i,v in pairs(MissionTargetRegistry.baiTargets) do + if v.product.side == coalition then + local tgt = Group.getByName(v.name) + + if not tgt or not tgt:isExist() or tgt:getSize()==0 then + MissionTargetRegistry.removeBaiTarget(v) + elseif not v.state or v.state ~= 'enroute' then + MissionTargetRegistry.removeBaiTarget(v) + else + table.insert(targets, v) + end + end + end + + if #targets == 0 then return end + + local dice = math.random(1,#targets) + + return targets[dice] + end + + function MissionTargetRegistry.removeBaiTarget(target) + MissionTargetRegistry.baiTargets[target.name] = nil + env.info('MissionTargetRegistry - bai target removed '..target.name) + end + + MissionTargetRegistry.strikeTargetExpireTime = 30*60 + MissionTargetRegistry.strikeTargets = {} + + function MissionTargetRegistry.addStrikeTarget(target, zone, isDeep) + MissionTargetRegistry.strikeTargets[target.name] = {data=target, zone=zone, addedTime = timer.getAbsTime(), isDeep = isDeep} + env.info('MissionTargetRegistry - strike target added '..target.name) + end + + function MissionTargetRegistry.strikeTargetsAvailable(coalition, isDeep) + for i,v in pairs(MissionTargetRegistry.strikeTargets) do + if v.data.side == coalition then + local tgt = StaticObject.getByName(v.data.name) + if not tgt then tgt = Group.getByName(v.data.name) end + + if not tgt or not tgt:isExist() then + MissionTargetRegistry.removeStrikeTarget(v) + elseif timer.getAbsTime() - v.addedTime > MissionTargetRegistry.strikeTargetExpireTime then + MissionTargetRegistry.removeStrikeTarget(v) + elseif v.isDeep == isDeep then + return true + end + end + end + + return false + end + + function MissionTargetRegistry.getRandomStrikeTarget(coalition, isDeep) + local targets = {} + for i,v in pairs(MissionTargetRegistry.strikeTargets) do + if v.data.side == coalition then + local tgt = StaticObject.getByName(v.data.name) + if not tgt then tgt = Group.getByName(v.data.name) end + + if not tgt or not tgt:isExist() then + MissionTargetRegistry.removeStrikeTarget(v) + elseif timer.getAbsTime() - v.addedTime > MissionTargetRegistry.strikeTargetExpireTime then + MissionTargetRegistry.removeStrikeTarget(v) + elseif v.isDeep == isDeep then + table.insert(targets, v) + end + end + end + + if #targets == 0 then return end + + local dice = math.random(1,#targets) + + return targets[dice] + end + + function MissionTargetRegistry.removeStrikeTarget(target) + MissionTargetRegistry.strikeTargets[target.data.name] = nil + env.info('MissionTargetRegistry - strike target removed '..target.data.name) + end + + MissionTargetRegistry.extractableSquads = {} + + function MissionTargetRegistry.addSquad(squad) + MissionTargetRegistry.extractableSquads[squad.name] = squad + env.info('MissionTargetRegistry - squad added '..squad.name) + end + + function MissionTargetRegistry.squadsReadyToExtract() + for i,v in pairs(MissionTargetRegistry.extractableSquads) do + local gr = Group.getByName(i) + if gr and gr:isExist() and gr:getSize() > 0 then + return true + end + end + + return false + end + + function MissionTargetRegistry.getRandomSquad() + local targets = {} + for i,v in pairs(MissionTargetRegistry.extractableSquads) do + local gr = Group.getByName(i) + if gr and gr:isExist() and gr:getSize() > 0 then + table.insert(targets, v) + end + end + + if #targets == 0 then return end + + local dice = math.random(1,#targets) + + return targets[dice] + end + + function MissionTargetRegistry.removeSquad(squad) + MissionTargetRegistry.extractableSquads[squad.name] = nil + env.info('MissionTargetRegistry - squad removed '..squad.name) + end + + MissionTargetRegistry.extractablePilots = {} + + function MissionTargetRegistry.addPilot(pilot) + MissionTargetRegistry.extractablePilots[pilot.name] = pilot + env.info('MissionTargetRegistry - pilot added '..pilot.name) + end + + function MissionTargetRegistry.pilotsAvailableToExtract() + for i,v in pairs(MissionTargetRegistry.extractablePilots) do + if v.pilot:isExist() and v.pilot:getSize() > 0 and v.remainingTime > 30*60 then + return true + end + end + + return false + end + + function MissionTargetRegistry.getRandomPilot() + local targets = {} + for i,v in pairs(MissionTargetRegistry.extractablePilots) do + if v.pilot:isExist() and v.pilot:getSize() > 0 and v.remainingTime > 30*60 then + table.insert(targets, v) + end + end + + if #targets == 0 then return end + + local dice = math.random(1,#targets) + + return targets[dice] + end + + function MissionTargetRegistry.removePilot(pilot) + MissionTargetRegistry.extractablePilots[pilot.name] = nil + env.info('MissionTargetRegistry - pilot removed '..pilot.name) + end +end + +-----------------[[ END OF MissionTargetRegistry.lua ]]----------------- + + + +-----------------[[ RadioFrequencyTracker.lua ]]----------------- + +RadioFrequencyTracker = {} + +do + RadioFrequencyTracker.radios = {} + + function RadioFrequencyTracker.registerRadio(groupname, name, frequency) + RadioFrequencyTracker.radios[groupname] = {name = name, frequency = frequency} + end + + function RadioFrequencyTracker.getRadioFrequencyMessage(side) + local radios ={} + for i,v in pairs(RadioFrequencyTracker.radios) do + local gr = Group.getByName(i) + if gr and gr:getCoalition()==side then + table.insert(radios, v) + else + RadioFrequencyTracker.radios[i] = nil + end + end + + table.sort(radios, function (a,b) return a.name < b.name end) + + local msg = 'Active frequencies:' + for i,v in ipairs(radios) do + msg = msg..'\n '..v.name..' ['..v.frequency..']' + end + + return msg + end +end + + +-----------------[[ END OF RadioFrequencyTracker.lua ]]----------------- + + + +-----------------[[ PersistenceManager.lua ]]----------------- + +PersistenceManager = {} + +do + + function PersistenceManager:new(path, groupManager, squadTracker, csarTracker, playerLogistics) + local obj = { + path = path, + groupManager = groupManager, + squadTracker = squadTracker, + csarTracker = csarTracker, + playerLogistics = playerLogistics, + data = nil + } + + setmetatable(obj, self) + self.__index = self + return obj + end + + function PersistenceManager:restoreZones() + local save = self.data + for i,v in pairs(save.zones) do + local z = ZoneCommand.getZoneByName(i) + if z then + z:setSide(v.side) + z.resource = v.resource + z.revealTime = v.revealTime + z.extraBuildResources = v.extraBuildResources + z.mode = v.mode + z.distToFront = v.distToFront + z.closestEnemyDist = v.closestEnemyDist + for name,data in pairs(v.built) do + local pr = z:getProductByName(name) + z:instantBuild(pr) + + if pr.type == 'defense' and type(data) == "table" then + local unitTypes = {} + for _,typeName in ipairs(data) do + if not unitTypes[typeName] then + unitTypes[typeName] = 0 + end + unitTypes[typeName] = unitTypes[typeName] + 1 + end + + timer.scheduleFunction(function(param, time) + local gr = Group.getByName(param.name) + if gr then + local types = param.data + local toKill = {} + for _,un in ipairs(gr:getUnits()) do + local tp = un:getDesc().typeName + if types[tp] and types[tp] > 0 then + types[tp] = types[tp] - 1 + else + table.insert(toKill, un) + end + end + + for _,un in ipairs(toKill) do + un:destroy() + end + end + end, {data=unitTypes, name=name}, timer.getTime()+2) + end + end + + if v.currentBuild then + local pr = z:getProductByName(v.currentBuild.name) + z:queueBuild(pr, v.currentBuild.side, v.currentBuild.isRepair, v.currentBuild.progress) + end + + if v.currentMissionBuild then + local pr = z:getProductByName(v.currentMissionBuild.name) + z:queueBuild(pr, v.currentMissionBuild.side, false, v.currentMissionBuild.progress) + end + + z:refreshText() + end + end + + end + + function PersistenceManager:restoreAIMissions() + local save = self.data + local instantBuildStates = { + ['uninitialized'] = true, + ['takeoff'] = true, + } + + local reActivateStates = { + ['inair'] = true, + ['enroute'] = true, + ['atdestination'] = true, + ['siege'] = true + } + + for i,v in pairs(save.activeGroups) do + if v.homeName then + if instantBuildStates[v.state] then + local z = ZoneCommand.getZoneByName(v.homeName) + if z then + local pr = z:getProductByName(v.productName) + if z.side == pr.side then + z:instantBuild(pr) + end + end + elseif v.lastMission and reActivateStates[v.state] then + timer.scheduleFunction(function(param, time) + local z = ZoneCommand.getZoneByName(param.homeName) + if z then + z:reActivateMission(param) + end + end, v, timer.getTime()+3) + end + end + end + end + + function PersistenceManager:restoreBattlefield() + local save = self.data + if save.battlefieldManager then + if save.battlefieldManager.priorityZones then + if save.battlefieldManager.priorityZones['1'] then + BattlefieldManager.priorityZones[1] = ZoneCommand.getZoneByName(save.battlefieldManager.priorityZones[1]) + end + + + if save.battlefieldManager.priorityZones['2'] then + BattlefieldManager.priorityZones[2] = ZoneCommand.getZoneByName(save.battlefieldManager.priorityZones[2]) + end + end + + if save.battlefieldManager.overridePriorityZones then + if save.battlefieldManager.overridePriorityZones['1'] then + BattlefieldManager.overridePriorityZones[1] = { + zone = ZoneCommand.getZoneByName(save.battlefieldManager.overridePriorityZones['1'].zone), + ticks = save.battlefieldManager.overridePriorityZones['1'].ticks + } + end + + if save.battlefieldManager.overridePriorityZones['2'] then + BattlefieldManager.overridePriorityZones[2] = { + zone = ZoneCommand.getZoneByName(save.battlefieldManager.overridePriorityZones['2'].zone), + ticks = save.battlefieldManager.overridePriorityZones['2'].ticks + } + end + end + end + end + + function PersistenceManager:restoreCsar() + local save = self.data + if save.csarTracker then + for i,v in pairs(save.csarTracker) do + self.csarTracker:restorePilot(v) + end + end + end + + function PersistenceManager:restoreSquads() + local save = self.data + if save.squadTracker then + for i,v in pairs(save.squadTracker) do + local sdata = self.playerLogistics.registeredSquadGroups[v.type] + if sdata then + v.data = sdata + self.squadTracker:restoreInfantry(v) + end + end + end + end + + function PersistenceManager:canRestore() + return self.data ~= nil + end + + function PersistenceManager:load() + self.data = Utils.loadTable(self.path) + end + + function PersistenceManager:save() + local tosave = {} + + tosave.zones = {} + for i,v in pairs(ZoneCommand.getAllZones()) do + + tosave.zones[i] = { + name = v.name, + side = v.side, + resource = v.resource, + mode = v.mode, + distToFront = v.distToFront, + closestEnemyDist = v.closestEnemyDist, + extraBuildResources = v.extraBuildResources, + revealTime = v.revealTime, + built = {} + } + + for n,b in pairs(v.built) do + if b.type == 'defense' then + local typeList = {} + local gr = Group.getByName(b.name) + for _,unit in ipairs(gr:getUnits()) do + table.insert(typeList, unit:getDesc().typeName) + end + + tosave.zones[i].built[n] = typeList + else + tosave.zones[i].built[n] = true + end + + end + + if v.currentBuild then + tosave.zones[i].currentBuild = { + name = v.currentBuild.product.name, + progress = v.currentBuild.progress, + side = v.currentBuild.side, + isRepair = v.currentBuild.isRepair + } + end + + if v.currentMissionBuild then + tosave.zones[i].currentMissionBuild = { + name = v.currentMissionBuild.product.name, + progress = v.currentMissionBuild.progress, + side = v.currentMissionBuild.side + } + end + end + + tosave.activeGroups = {} + for i,v in pairs(self.groupManager.groups) do + tosave.activeGroups[i] = { + productName = v.product.name, + type = v.product.missionType + } + + local gr = Group.getByName(v.product.name) + if gr and gr:getSize()>0 then + local un = gr:getUnit(1) + if un then + tosave.activeGroups[i].position = un:getPoint() + tosave.activeGroups[i].lastMission = v.product.lastMission + tosave.activeGroups[i].heading = math.atan2(un:getPosition().x.z, un:getPosition().x.x) + end + end + + if v.target then + tosave.activeGroups[i].targetName = v.target.name + end + + if v.home then + tosave.activeGroups[i].homeName = v.home.name + end + + if v.state then + tosave.activeGroups[i].state = v.state + tosave.activeGroups[i].lastStateDuration = timer.getAbsTime() - v.lastStateTime + else + tosave.activeGroups[i].state = 'uninitialized' + tosave.activeGroups[i].lastStateDuration = 0 + end + end + + tosave.battlefieldManager = { + priorityZones = {}, + overridePriorityZones = {} + } + + if BattlefieldManager.priorityZones[1] then + tosave.battlefieldManager.priorityZones['1'] = BattlefieldManager.priorityZones[1].name + end + + if BattlefieldManager.priorityZones[2] then + tosave.battlefieldManager.priorityZones['2'] = BattlefieldManager.priorityZones[2].name + end + + if BattlefieldManager.overridePriorityZones[1] then + tosave.battlefieldManager.overridePriorityZones['1'] = { + zone = BattlefieldManager.overridePriorityZones[1].zone.name, + ticks = BattlefieldManager.overridePriorityZones[1].ticks + } + end + + if BattlefieldManager.overridePriorityZones[2] then + tosave.battlefieldManager.overridePriorityZones['2'] = { + zone = BattlefieldManager.overridePriorityZones[2].zone.name, + ticks = BattlefieldManager.overridePriorityZones[2].ticks + } + end + + + tosave.csarTracker = {} + + for i,v in pairs(self.csarTracker.activePilots) do + if v.pilot:isExist() and v.pilot:getSize()>0 and v.remainingTime>60 then + tosave.csarTracker[i] = { + name = v.name, + remainingTime = v.remainingTime, + pos = v.pilot:getUnit(1):getPoint() + } + end + end + + tosave.squadTracker = {} + + for i,v in pairs(self.squadTracker.activeInfantrySquads) do + tosave.squadTracker[i] = { + state = v.state, + remainingStateTime = v.remainingStateTime, + position = v.position, + name = v.name, + type = v.data.type + } + end + + Utils.saveTable(self.path, tosave) + end +end + +-----------------[[ END OF PersistenceManager.lua ]]----------------- + + + +-----------------[[ TemplateDB.lua ]]----------------- + +TemplateDB = {} + +do + TemplateDB.type = { + group = 'group', + static = 'static', + } + + TemplateDB.templates = {} + function TemplateDB.getData(objtype) + return TemplateDB.templates[objtype] + end + + TemplateDB.templates["pilot-replacement"] = { + units = { "Soldier M4 GRG" }, + skill = "Good", + dataCategory= TemplateDB.type.group + } + + TemplateDB.templates["capture-squad"] = { + units = { + "Soldier M4 GRG", + "Soldier M4 GRG", + "Soldier M249", + "Soldier M4 GRG" + }, + skill = "Good", + dataCategory= TemplateDB.type.group + } + + TemplateDB.templates["sabotage-squad"] = { + units = { + "Soldier M4 GRG", + "Soldier M249", + "Soldier M249", + "Soldier M4 GRG" + }, + skill = "Good", + dataCategory= TemplateDB.type.group + } + + TemplateDB.templates["ambush-squad"] = { + units = { + "Soldier RPG", + "Soldier RPG", + "Soldier M249", + "Soldier M4 GRG", + "Soldier M4 GRG" + }, + skill = "Good", + invisible = true, + dataCategory= TemplateDB.type.group + } + + TemplateDB.templates["manpads-squad"] = { + units = { + "Soldier M4 GRG", + "Soldier M249", + "Soldier stinger", + "Soldier stinger", + "Soldier M4 GRG" + }, + skill = "Good", + dataCategory= TemplateDB.type.group + } + + TemplateDB.templates["engineer-squad"] = { + units = { + "Soldier M4 GRG", + "Soldier M4 GRG" + }, + skill = "Good", + dataCategory= TemplateDB.type.group + } + + TemplateDB.templates["spy-squad"] = { + units = { + "Infantry AK" + }, + skill = "Good", + invisible = true, + dataCategory= TemplateDB.type.group + } + + TemplateDB.templates["rapier-squad"] = { + units = { + "rapier_fsa_blindfire_radar", + "rapier_fsa_optical_tracker_unit", + "rapier_fsa_launcher", + "rapier_fsa_launcher", + "Soldier M4 GRG", + "Soldier M4 GRG" + }, + skill = "Excellent", + dataCategory= TemplateDB.type.group + } + + TemplateDB.templates["tent"] = { type="FARP Tent", category="Fortifications", shape="PalatkaB", dataCategory=TemplateDB.type.static } + + TemplateDB.templates["barracks"] = { type="house1arm", category="Fortifications", shape=nil, dataCategory=TemplateDB.type.static } + + TemplateDB.templates["outpost"] = { type="outpost", category="Fortifications", shape=nil, dataCategory=TemplateDB.type.static } + + TemplateDB.templates["ammo-cache"] = { type="FARP Ammo Dump Coating", category="Fortifications", shape="SetkaKP", dataCategory=TemplateDB.type.static } + + TemplateDB.templates["ammo-depot"] = { type=".Ammunition depot", category="Warehouses", shape="SkladC", dataCategory=TemplateDB.type.static } + + TemplateDB.templates["fuel-cache"] = { type="FARP Fuel Depot", category="Fortifications", shape="GSM Rus", dataCategory=TemplateDB.type.static } + + TemplateDB.templates["fuel-tank-small"] = { type="Fuel tank", category="Fortifications", shape="toplivo-bak", dataCategory=TemplateDB.type.static } + + TemplateDB.templates["fuel-tank-big"] = { type="Tank", category="Warehouses", shape="bak", dataCategory=TemplateDB.type.static } + + TemplateDB.templates["chem-tank"] = { type="Chemical tank A", category="Fortifications", shape="him_bak_a", dataCategory=TemplateDB.type.static } + + TemplateDB.templates["factory-1"] = { type="Tech combine", category="Fortifications", shape="kombinat", dataCategory=TemplateDB.type.static } + + TemplateDB.templates["factory-2"] = { type="Workshop A", category="Fortifications", shape="tec_a", dataCategory=TemplateDB.type.static } + + TemplateDB.templates["oil-pump"] = { type="Pump station", category="Fortifications", shape="nasos", dataCategory=TemplateDB.type.static } + + TemplateDB.templates["hangar"] = { type="Hangar A", category="Fortifications", shape="angar_a", dataCategory=TemplateDB.type.static } + + TemplateDB.templates["excavator"] = { type="345 Excavator", category="Fortifications", shape="cat_3451", dataCategory=TemplateDB.type.static } + + TemplateDB.templates["farm-house-1"] = { type="Farm A", category="Fortifications", shape="ferma_a", dataCategory=TemplateDB.type.static } + + TemplateDB.templates["farm-house-2"] = { type="Farm B", category="Fortifications", shape="ferma_b", dataCategory=TemplateDB.type.static } + + TemplateDB.templates["antenna"] = { type="Comms tower M", category="Fortifications", shape="tele_bash_m", dataCategory=TemplateDB.type.static } + + TemplateDB.templates["tv-tower"] = { type="TV tower", category="Fortifications", shape="tele_bash", dataCategory=TemplateDB.type.static } + + TemplateDB.templates["command-center"] = { type=".Command Center", category="Fortifications", shape="ComCenter", dataCategory=TemplateDB.type.static } + + TemplateDB.templates["military-staff"] = { type="Military staff", category="Fortifications", shape="aviashtab", dataCategory=TemplateDB.type.static } +end + +-----------------[[ END OF TemplateDB.lua ]]----------------- + + + +-----------------[[ Spawner.lua ]]----------------- + +Spawner = {} + +do + function Spawner.createPilot(name, pos) + local groupData = Spawner.getData("pilot-replacement", name, pos, nil, 5, { + [land.SurfaceType.LAND] = true, + [land.SurfaceType.ROAD] = true, + [land.SurfaceType.RUNWAY] = true, + }) + + return coalition.addGroup(country.id.CJTF_BLUE, Group.Category.GROUND, groupData) + end + + function Spawner.createObject(name, objType, pos, side, minDist, maxDist, surfaceTypes, zone) + if zone then + zone = CustomZone:getByName(zone) -- expand zone name to CustomZone object + end + + local data = Spawner.getData(objType, name, pos, minDist, maxDist, surfaceTypes, zone) + + if not data then return end + + local cnt = country.id.CJTF_BLUE + if side == 1 then + cnt = country.id.CJTF_RED + end + + if data.dataCategory == TemplateDB.type.static then + return coalition.addStaticObject(cnt, data) + elseif data.dataCategory == TemplateDB.type.group then + return coalition.addGroup(cnt, Group.Category.GROUND, data) + end + end + + function Spawner.getUnit(unitType, name, pos, skill, minDist, maxDist, surfaceTypes, zone) + local nudgedPos = nil + for i=1,500,1 do + nudgedPos = mist.getRandPointInCircle(pos, maxDist, minDist) + + if zone then + if zone:isInside(nudgedPos) and surfaceTypes[land.getSurfaceType(nudgedPos)] then + break + end + else + if surfaceTypes[land.getSurfaceType(nudgedPos)] then + break + end + end + + if i==500 then env.info('Spawner - ERROR: failed to find good location') end + end + + return { + ["type"] = unitType, + ["skill"] = skill, + ["coldAtStart"] = false, + ["x"] = nudgedPos.x, + ["y"] = nudgedPos.y, + ["name"] = name, + ['heading'] = math.random()*math.pi*2, + ["playerCanDrive"] = false + } + end + + function Spawner.getData(objtype, name, pos, minDist, maxDist, surfaceTypes, zone) + if not maxDist then maxDist = 150 end + if not surfaceTypes then surfaceTypes = { [land.SurfaceType.LAND]=true } end + + local data = TemplateDB.getData(objtype) + if not data then + env.info("Spawner - ERROR: cant find group data "..tostring(objtype).." for group name "..name) + return + end + + local spawnData = {} + + if data.dataCategory == TemplateDB.type.static then + if not surfaceTypes[land.getSurfaceType(pos)] then + for i=1,500,1 do + pos = mist.getRandPointInCircle(pos, maxDist) + + if zone then + if zone:isInside(pos) and surfaceTypes[land.getSurfaceType(pos)] then + break + end + else + if surfaceTypes[land.getSurfaceType(pos)] then + break + end + end + + if i==500 then env.info('Spawner - ERROR: failed to find good location') end + end + end + + spawnData = { + ["type"] = data.type, + ["name"] = name, + ["shape_name"] = data.shape, + ["category"] = data.category, + ["x"] = pos.x, + ["y"] = pos.y, + ['heading'] = math.random()*math.pi*2 + } + elseif data.dataCategory== TemplateDB.type.group then + spawnData = { + ["units"] = {}, + ["name"] = name, + ["task"] = "Ground Nothing", + ["route"] = { + ["points"]={ + { + ["x"] = pos.x, + ["y"] = pos.y, + ["action"] = "Off Road", + ["speed"] = 0, + ["type"] = "Turning Point", + ["ETA"] = 0, + ["formation_template"] = "", + ["task"] = Spawner.getDefaultTask(data.invisible) + } + } + } + } + + if data.minDist then + minDist = data.minDist + end + + if data.maxDist then + maxDist = data.maxDist + end + + for i,v in ipairs(data.units) do + table.insert(spawnData.units, Spawner.getUnit(v, name.."-"..i, pos, data.skill, minDist, maxDist, surfaceTypes, zone)) + end + end + + spawnData.dataCategory = data.dataCategory + + return spawnData + end + + function Spawner.getDefaultTask(invisible) + local defTask = { + ["id"] = "ComboTask", + ["params"] = + { + ["tasks"] = + { + [1] = + { + ["enabled"] = true, + ["auto"] = false, + ["id"] = "WrappedAction", + ["number"] = 1, + ["params"] = + { + ["action"] = + { + ["id"] = "Option", + ["params"] = + { + ["name"] = 9, + ["value"] = 2, + }, + }, + }, + }, + [2] = + { + ["enabled"] = true, + ["auto"] = false, + ["id"] = "WrappedAction", + ["number"] = 2, + ["params"] = + { + ["action"] = + { + ["id"] = "Option", + ["params"] = + { + ["name"] = 0, + ["value"] = 0, + } + } + } + } + } + } + } + + if invisible then + table.insert(defTask.params.tasks, { + ["number"] = 3, + ["auto"] = false, + ["id"] = "WrappedAction", + ["enabled"] = true, + ["params"] = + { + ["action"] = + { + ["id"] = "SetInvisible", + ["params"] = + { + ["value"] = true, + } + } + } + }) + end + + return defTask + end +end + +-----------------[[ END OF Spawner.lua ]]----------------- + + + +-----------------[[ CommandFunctions.lua ]]----------------- + +CommandFunctions = {} + +do + CommandFunctions.jtac = nil + + function CommandFunctions.spawnJtac(zone) + if CommandFunctions.jtac then + CommandFunctions.jtac:deployAtZone(zone) + CommandFunctions.jtac:showMenu() + CommandFunctions.jtac:setLifeTime(60) + end + end + + function CommandFunctions.smokeTargets(zone, count) + local units = {} + for i,v in pairs(zone.built) do + local g = Group.getByName(v.name) + if g then + for i2,v2 in ipairs(g:getUnits()) do + table.insert(units, v2) + end + else + local s = StaticObject.getByName(v.name) + if s then + table.insert(units, s) + end + end + end + + local tgts = {} + for i=1,count,1 do + if #units > 0 then + local selected = math.random(1,#units) + table.insert(tgts, units[selected]) + table.remove(units, selected) + end + end + + for i,v in ipairs(tgts) do + local pos = v:getPoint() + trigger.action.smoke(pos, 1) + end + end +end + +-----------------[[ END OF CommandFunctions.lua ]]----------------- + + + +-----------------[[ JTAC.lua ]]----------------- + +JTAC = {} +do + JTAC.categories = {} + JTAC.categories['SAM'] = {'SAM SR', 'SAM TR', 'IR Guided SAM','SAM LL','SAM CC'} + JTAC.categories['Infantry'] = {'Infantry'} + JTAC.categories['Armor'] = {'Tanks','IFV','APC'} + JTAC.categories['Support'] = {'Unarmed vehicles','Artillery'} + JTAC.categories['Structures'] = {'StaticObjects'} + + --{name = 'groupname'} + function JTAC:new(obj) + obj = obj or {} + obj.lasers = {tgt=nil, ir=nil} + obj.target = nil + obj.timerReference = nil + obj.remainingLife = nil + obj.tgtzone = nil + obj.priority = nil + obj.jtacMenu = nil + obj.laserCode = 1688 + obj.side = Group.getByName(obj.name):getCoalition() + setmetatable(obj, self) + self.__index = self + obj:initCodeListener() + return obj + end + + function JTAC:initCodeListener() + local ev = {} + ev.context = self + function ev:onEvent(event) + if event.id == 26 then + if event.text:find('^jtac%-code:') then + local s = event.text:gsub('^jtac%-code:', '') + local code = tonumber(s) + self.context:setCode(code) + trigger.action.removeMark(event.idx) + end + end + end + + world.addEventHandler(ev) + end + + function JTAC:setCode(code) + if code>=1111 and code <= 1788 then + self.laserCode = code + trigger.action.outTextForCoalition(self.side, 'JTAC code set to '..code, 10) + else + trigger.action.outTextForCoalition(self.side, 'Invalid laser code. Must be between 1111 and 1788 ', 10) + end + end + + function JTAC:showMenu() + local gr = Group.getByName(self.name) + if not gr then + return + end + + if not self.jtacMenu then + self.jtacMenu = missionCommands.addSubMenuForCoalition(self.side, 'JTAC') + + missionCommands.addCommandForCoalition(self.side, 'Target report', self.jtacMenu, function(dr) + if Group.getByName(dr.name) then + dr:printTarget(true) + else + missionCommands.removeItemForCoalition(dr.side, dr.jtacMenu) + dr.jtacMenu = nil + end + end, self) + + missionCommands.addCommandForCoalition(self.side, 'Next Target', self.jtacMenu, function(dr) + if Group.getByName(dr.name) then + dr:searchTarget() + else + missionCommands.removeItemForCoalition(dr.side, dr.jtacMenu) + dr.jtacMenu = nil + end + end, self) + + missionCommands.addCommandForCoalition(self.side, 'Deploy Smoke', self.jtacMenu, function(dr) + if Group.getByName(dr.name) then + local tgtunit = Unit.getByName(dr.target) + if not tgtunit then + tgtunit = StaticObject.getByName(dr.target) + end + + if tgtunit then + trigger.action.smoke(tgtunit:getPoint(), 3) + trigger.action.outTextForCoalition(dr.side, 'JTAC target marked with ORANGE smoke', 10) + end + else + missionCommands.removeItemForCoalition(dr.side, dr.jtacMenu) + dr.jtacMenu = nil + end + end, self) + + local priomenu = missionCommands.addSubMenuForCoalition(self.side, 'Set Priority', self.jtacMenu) + for i,v in pairs(JTAC.categories) do + missionCommands.addCommandForCoalition(self.side, i, priomenu, function(dr, cat) + if Group.getByName(dr.name) then + dr:setPriority(cat) + dr:searchTarget() + else + missionCommands.removeItemForCoalition(dr.side, dr.jtacMenu) + dr.jtacMenu = nil + end + end, self, i) + end + + local dial = missionCommands.addSubMenuForCoalition(self.side, 'Set Laser Code', self.jtacMenu) + for i2=1,7,1 do + local digit2 = missionCommands.addSubMenuForCoalition(self.side, '1'..i2..'__', dial) + for i3=1,9,1 do + local digit3 = missionCommands.addSubMenuForCoalition(self.side, '1'..i2..i3..'_', digit2) + for i4=1,9,1 do + local digit4 = missionCommands.addSubMenuForCoalition(self.side, '1'..i2..i3..i4, digit3) + local code = tonumber('1'..i2..i3..i4) + missionCommands.addCommandForCoalition(self.side, 'Accept', digit4, Utils.log(self.setCode), self, code) + end + end + end + + missionCommands.addCommandForCoalition(self.side, "Clear", priomenu, function(dr) + if Group.getByName(dr.name) then + dr:clearPriority() + dr:searchTarget() + else + missionCommands.removeItemForCoalition(dr.side, dr.jtacMenu) + dr.jtacMenu = nil + end + end, self) + end + end + + function JTAC:setPriority(prio) + self.priority = JTAC.categories[prio] + self.prioname = prio + end + + function JTAC:clearPriority() + self.priority = nil + end + + function JTAC:setTarget(unit) + + if self.lasers.tgt then + self.lasers.tgt:destroy() + self.lasers.tgt = nil + end + + if self.lasers.ir then + self.lasers.ir:destroy() + self.lasers.ir = nil + end + + local me = Group.getByName(self.name) + if not me then return end + + local pnt = unit:getPoint() + self.lasers.tgt = Spot.createLaser(me:getUnit(1), { x = 0, y = 2.0, z = 0 }, pnt, self.laserCode) + self.lasers.ir = Spot.createInfraRed(me:getUnit(1), { x = 0, y = 2.0, z = 0 }, pnt) + + self.target = unit:getName() + end + + function JTAC:setLifeTime(minutes) + self.remainingLife = minutes + + timer.scheduleFunction(function(param, time) + if param.remainingLife == nil then return end + + local gr = Group.getByName(self.name) + if not gr then + param.remainingLife = nil + return + end + + param.remainingLife = param.remainingLife - 1 + if param.remainingLife < 0 then + param:clearTarget() + return + end + + return time+60 + end, self, timer.getTime()+60) + end + + function JTAC:printTarget(makeitlast) + local toprint = '' + if self.target and self.tgtzone then + local tgtunit = Unit.getByName(self.target) + local isStructure = false + if not tgtunit then + tgtunit = StaticObject.getByName(self.target) + isStructure = true + end + + if tgtunit then + local pnt = tgtunit:getPoint() + local tgttype = "Unidentified" + if isStructure then + tgttype = "Structure" + else + tgttype = tgtunit:getTypeName() + end + + if self.priority then + toprint = 'Priority targets: '..self.prioname..'\n' + end + + toprint = toprint..'Lasing '..tgttype..' at '..self.tgtzone.name..'\nCode: '..self.laserCode..'\n' + local lat,lon,alt = coord.LOtoLL(pnt) + local mgrs = coord.LLtoMGRS(coord.LOtoLL(pnt)) + toprint = toprint..'\nDDM: '.. mist.tostringLL(lat,lon,3) + toprint = toprint..'\nDMS: '.. mist.tostringLL(lat,lon,2,true) + toprint = toprint..'\nMGRS: '.. mist.tostringMGRS(mgrs, 5) + toprint = toprint..'\n\nAlt: '..math.floor(alt)..'m'..' | '..math.floor(alt*3.280839895)..'ft' + else + makeitlast = false + toprint = 'No Target' + end + else + makeitlast = false + toprint = 'No target' + end + + local gr = Group.getByName(self.name) + if makeitlast then + trigger.action.outTextForCoalition(gr:getCoalition(), toprint, 60) + else + trigger.action.outTextForCoalition(gr:getCoalition(), toprint, 10) + end + end + + function JTAC:clearTarget() + self.target = nil + + if self.lasers.tgt then + self.lasers.tgt:destroy() + self.lasers.tgt = nil + end + + if self.lasers.ir then + self.lasers.ir:destroy() + self.lasers.ir = nil + end + + if self.timerReference then + mist.removeFunction(self.timerReference) + self.timerReference = nil + end + + local gr = Group.getByName(self.name) + if gr then + gr:destroy() + missionCommands.removeItemForCoalition(self.side, self.jtacMenu) + self.jtacMenu = nil + end + end + + function JTAC:searchTarget() + local gr = Group.getByName(self.name) + if gr then + if self.tgtzone and self.tgtzone.side~=0 and self.tgtzone.side~=gr:getCoalition() then + local viabletgts = {} + for i,v in pairs(self.tgtzone.built) do + local tgtgr = Group.getByName(v.name) + if tgtgr and tgtgr:getSize()>0 then + for i2,v2 in ipairs(tgtgr:getUnits()) do + if v2:getLife()>=1 then + table.insert(viabletgts, v2) + end + end + else + tgtgr = StaticObject.getByName(v.name) + if tgtgr then + table.insert(viabletgts, tgtgr) + end + end + end + + if self.priority then + local priorityTargets = {} + for i,v in ipairs(viabletgts) do + for i2,v2 in ipairs(self.priority) do + if v2 == "StaticObjects" and ZoneCommand.staticRegistry[v:getName()] then + table.insert(priorityTargets, v) + break + elseif v:hasAttribute(v2) and v:getLife()>=1 then + table.insert(priorityTargets, v) + break + end + end + end + + if #priorityTargets>0 then + viabletgts = priorityTargets + else + self:clearPriority() + trigger.action.outTextForCoalition(gr:getCoalition(), 'JTAC: No priority targets found', 10) + end + end + + if #viabletgts>0 then + local chosentgt = math.random(1, #viabletgts) + self:setTarget(viabletgts[chosentgt]) + self:printTarget() + else + self:clearTarget() + end + else + self:clearTarget() + end + end + end + + function JTAC:searchIfNoTarget() + if Group.getByName(self.name) then + if not self.target or (not Unit.getByName(self.target) and not StaticObject.getByName(self.target)) then + self:searchTarget() + elseif self.target then + local un = Unit.getByName(self.target) + if un then + if un:getLife()>=1 then + self:setTarget(un) + else + self:searchTarget() + end + else + local st = StaticObject.getByName(self.target) + if st then + self:setTarget(st) + end + end + end + else + self:clearTarget() + end + end + + function JTAC:deployAtZone(zoneCom) + self.remainingLife = nil + self.tgtzone = zoneCom + local p = CustomZone:getByName(self.tgtzone.name).point + local vars = {} + vars.gpName = self.name + vars.action = 'respawn' + vars.point = {x=p.x, y=5000, z = p.z} + mist.teleportToPoint(vars) + + mist.scheduleFunction(self.setOrbit, {self, self.tgtzone.zone, p}, timer.getTime()+1) + + if not self.timerReference then + self.timerReference = mist.scheduleFunction(self.searchIfNoTarget, {self}, timer.getTime()+5, 5) + end + end + + function JTAC:setOrbit(zonename, point) + local gr = Group.getByName(self.name) + if not gr then + return + end + + local cnt = gr:getController() + cnt:setCommand({ + id = 'SetInvisible', + params = { + value = true + } + }) + + cnt:setTask({ + id = 'Orbit', + params = { + pattern = 'Circle', + point = {x = point.x, y=point.z}, + altitude = 5000 + } + }) + + self:searchTarget() + end +end + +-----------------[[ END OF JTAC.lua ]]----------------- + + + +-----------------[[ Objectives/Objective.lua ]]----------------- + +Objective = {} + +do + Objective.types = { + fly_to_zone_seq = 'fly_to_zone_seq', -- any of playerlist inside [zone] in sequence + recon_zone = 'recon_zone', -- within X km, facing Y angle +-, % of enemy units in LOS progress faster + destroy_attr = 'destroy_attr', -- any of playerlist kill event on target with any of [attribute] + destroy_attr_at_zone = 'destroy_attr_at_zone', -- any of playerlist kill event on target at [zone] with any of [attribute] + clear_attr_at_zone = 'clear_attr_at_zone', -- [zone] does not have any units with [attribute] + destroy_structure = 'destroy_structure', -- [structure] is killed by any player (getDesc().displayName or getDesc().typeName:gsub('%.','') must match) + destroy_group = 'destroy_group', -- [group] is missing from mission AND any player killed unit from group at least once + supply = 'supply', -- any of playerlist unload [amount] supply at [zone] + extract_pilot = 'extract_pilot', -- players extracted specific ejected pilots + extract_squad = 'extract_squad', -- players extracted specific squad + unloaded_pilot_or_squad = 'unloaded_pilot_or_squad', -- unloaded pilot or squad + deploy_squad = 'deploy_squad', --deploy squad at zone + escort = 'escort', -- escort convoy + protect = 'protect', -- protect other mission + air_kill_bonus = 'air_kill_bonus', -- award bonus for air kills + bomb_in_zone = 'bomb_in_zone', -- bombs tallied inside zone + player_close_to_zone = 'player_close_to_zone' -- player is close to point + } + + function Objective:new(type) + + local obj = { + type = type, + mission = nil, + param = {}, + isComplete = false, + isFailed = false + } + + setmetatable(obj, self) + self.__index = self + + return obj + end + + function Objective:initialize(mission, param) + self.mission = mission + self:validateParameters(param) + self.param = param + end + + function Objective:getType() + return self.type + end + + function Objective:validateParameters(param) + for i,v in pairs(self.requiredParams) do + if v and param[i] == nil then + env.error("Objective - missing parameter: "..i..' in '..self:getType(), true) + end + end + end + + -- virtual + Objective.requiredParams = {} + + function Objective:getText() + env.error("Objective - getText not implemented") + return "NOT IMPLEMENTED" + end + + function Objective:update() + env.error("Objective - update not implemented") + end + + function Objective:checkFail() + env.error("Objective - checkFail not implemented") + end + --end virtual +end + +-----------------[[ END OF Objectives/Objective.lua ]]----------------- + + + +-----------------[[ Objectives/ObjAirKillBonus.lua ]]----------------- + +ObjAirKillBonus = Objective:new(Objective.types.air_kill_bonus) +do + ObjAirKillBonus.requiredParams = { + ['attr'] = true, + ['bonus'] = true, + ['count'] = true, + ['linkedObjectives'] = true + } + + function ObjAirKillBonus:getText() + local msg = 'Destroy: ' + for _,v in ipairs(self.param.attr) do + msg = msg..v..', ' + end + msg = msg:sub(1,#msg-2) + msg = msg..'\n Kills increase mission reward (Ends when other objectives are completed)' + msg = msg..'\n Kills: '..self.param.count + return msg + end + + function ObjAirKillBonus:update() + if not self.isComplete and not self.isFailed then + local allcomplete = true + for _,obj in pairs(self.param.linkedObjectives) do + if obj.isFailed then self.isFailed = true end + if not obj.isComplete then allcomplete = false end + end + + self.isComplete = allcomplete + end + end + + function ObjAirKillBonus:checkFail() + if not self.isComplete and not self.isFailed then + local allcomplete = true + for _,obj in pairs(self.param.linkedObjectives) do + if obj.isFailed then self.isFailed = true end + if not obj.isComplete then allcomplete = false end + end + + self.isComplete = allcomplete + end + end +end + +-----------------[[ END OF Objectives/ObjAirKillBonus.lua ]]----------------- + + + +-----------------[[ Objectives/ObjBombInsideZone.lua ]]----------------- + +ObjBombInsideZone = Objective:new(Objective.types.bomb_in_zone) +do + ObjBombInsideZone.requiredParams = { + ['targetZone'] = true, + ['max'] = true, + ['required'] = true, + ['dropped'] = true, + ['isFinishStarted'] = true, + ['bonus'] = true + } + + function ObjBombInsideZone:getText() + local msg = 'Bomb runways at '..self.param.targetZone.name..'\n' + + local ratio = self.param.dropped/self.param.required + local percent = string.format('%.1f',ratio*100) + + msg = msg..'\n Runway bombed: '..percent..'%\n' + + msg = msg..'\n Cluster bombs do not deal enough damage to complete this mission' + + return msg + end + + function ObjBombInsideZone:update() + if not self.isComplete and not self.isFailed then + if self.param.targetZone.side ~= 1 then + self.isFailed = true + self.mission.failureReason = self.param.targetZone.name..' is no longer controlled by the enemy.' + end + + if not self.param.isFinishStarted then + if self.param.dropped >= self.param.required then + self.param.isFinishStarted = true + timer.scheduleFunction(function(o) + o.isComplete = true + end, self, timer.getTime()+5) + end + end + end + end + + function ObjBombInsideZone:checkFail() + if not self.isComplete and not self.isFailed then + if self.param.targetZone.side ~= 1 then + self.isFailed = true + end + end + end +end + +-----------------[[ END OF Objectives/ObjBombInsideZone.lua ]]----------------- + + + +-----------------[[ Objectives/ObjClearZoneOfUnitsWithAttribute.lua ]]----------------- + +ObjClearZoneOfUnitsWithAttribute = Objective:new(Objective.types.clear_attr_at_zone) +do + ObjClearZoneOfUnitsWithAttribute.requiredParams = { + ['attr'] = true, + ['tgtzone'] = true + } + + function ObjClearZoneOfUnitsWithAttribute:getText() + local msg = 'Clear '..self.param.tgtzone.name..' of: ' + for _,v in ipairs(self.param.attr) do + msg = msg..v..', ' + end + msg = msg:sub(1,#msg-2) + msg = msg..'\n Progress: '..self.param.tgtzone:getUnitCountWithAttributeOnSide(self.param.attr, 1)..' left' + return msg + end + + function ObjClearZoneOfUnitsWithAttribute:update() + if not self.isComplete and not self.isFailed then + local zn = self.param.tgtzone + if zn.side ~= 1 or not zn:hasUnitWithAttributeOnSide(self.param.attr, 1) then + self.isComplete = true + return true + end + end + end + + function ObjClearZoneOfUnitsWithAttribute:checkFail() + -- can not fail + end +end + +-----------------[[ END OF Objectives/ObjClearZoneOfUnitsWithAttribute.lua ]]----------------- + + + +-----------------[[ Objectives/ObjDestroyGroup.lua ]]----------------- + +ObjDestroyGroup = Objective:new(Objective.types.destroy_group) +do + ObjDestroyGroup.requiredParams = { + ['target'] = true, + ['targetUnitNames'] = true, + ['lastUpdate'] = true + } + + function ObjDestroyGroup:getText() + local msg = 'Destroy '..self.param.target.product.display..' before it reaches its destination.\n' + + local gr = Group.getByName(self.param.target.name) + if gr and gr:getSize()>0 then + local killcount = 0 + for i,v in pairs(self.param.targetUnitNames) do + if v == true then + killcount = killcount + 1 + end + end + + msg = msg..'\n '..gr:getSize()..' units remaining. (killed '..killcount..')\n' + for name, unit in pairs(self.mission.players) do + if unit and unit:isExist() then + local tgtUnit = gr:getUnit(1) + local dist = mist.utils.get2DDist(unit:getPoint(), tgtUnit:getPoint()) + + local m = '\n '..name..': Distance: ' + m = m..string.format('%.2f',dist/1000)..'km' + m = m..' Bearing: '..math.floor(Utils.getBearing(unit:getPoint(), tgtUnit:getPoint())) + msg = msg..m + end + end + end + + return msg + end + + function ObjDestroyGroup:update() + if not self.isComplete and not self.isFailed then + local target = self.param.target + local exists = false + local gr = Group.getByName(target.name) + + if gr and gr:getSize() > 0 then + local updateFrequency = 5 -- seconds + local shouldUpdateMsg = (timer.getAbsTime() - self.param.lastUpdate) > updateFrequency + + if shouldUpdateMsg then + for _, unit in pairs(self.mission.players) do + if unit and unit:isExist() then + local tgtUnit = gr:getUnit(1) + local dist = mist.utils.get2DDist(unit:getPoint(), tgtUnit:getPoint()) + local dstkm = string.format('%.2f',dist/1000) + local dstnm = string.format('%.2f',dist/1852) + + local m = 'Distance: ' + m = m..dstkm..'km | '..dstnm..'nm' + + m = m..'\nBearing: '..math.floor(Utils.getBearing(unit:getPoint(), tgtUnit:getPoint())) + trigger.action.outTextForUnit(unit:getID(), m, updateFrequency) + end + end + + self.param.lastUpdate = timer.getAbsTime() + end + elseif target.state == 'enroute' then + for i,v in pairs(self.param.targetUnitNames) do + if v == true then + self.isComplete = true + return true + end + end + + self.isFailed = true + self.mission.failureReason = 'Convoy was killed by someone else.' + return true + else + self.isFailed = true + self.mission.failureReason = 'Convoy has reached its destination.' + return true + end + end + end + + function ObjDestroyGroup:checkFail() + if not self.isComplete and not self.isFailed then + local target = self.param.target + local gr = Group.getByName(target.name) + + if target.state ~= 'enroute' or not gr or gr:getSize() == 0 then + self.isFailed = true + end + end + end +end + +-----------------[[ END OF Objectives/ObjDestroyGroup.lua ]]----------------- + + + +-----------------[[ Objectives/ObjDestroyStructure.lua ]]----------------- + +ObjDestroyStructure = Objective:new(Objective.types.destroy_structure) +do + ObjDestroyStructure.requiredParams = { + ['target']=true, + ['tgtzone']=true, + ['killed']=true + } + + function ObjDestroyStructure:getText() + local msg = 'Destroy '..self.param.target.display..' at '..self.param.tgtzone.name..'\n' + + local point = nil + local st = StaticObject.getByName(self.param.target.name) + if st then + point = st:getPoint() + else + st = Group.getByName(self.param.target.name) + if st and st:getSize()>0 then + point = st:getUnit(1):getPoint() + end + end + + if point then + local lat,lon,alt = coord.LOtoLL(point) + local mgrs = coord.LLtoMGRS(coord.LOtoLL(point)) + msg = msg..'\n DDM: '.. mist.tostringLL(lat,lon,3) + msg = msg..'\n DMS: '.. mist.tostringLL(lat,lon,2,true) + msg = msg..'\n MGRS: '.. mist.tostringMGRS(mgrs, 5) + msg = msg..'\n Altitude: '..math.floor(alt)..'m'..' | '..math.floor(alt*3.280839895)..'ft' + end + + return msg + end + + function ObjDestroyStructure:update() + if not self.isComplete and not self.isFailed then + if self.param.killed then + self.isComplete = true + return true + end + + local target = self.param.target + local exists = false + local st = StaticObject.getByName(target.name) + if st then + exists = true + else + st = Group.getByName(target.name) + if st and st:getSize()>0 then + exists = true + end + end + + if not exists then + if not self.firstFailure then + self.firstFailure = timer.getAbsTime() + end + end + + if self.firstFailure and (timer.getAbsTime() - self.firstFailure > 1*60) then + self.isFailed = true + self.mission.failureReason = 'Structure was destoyed by someone else.' + return true + end + end + end + + function ObjDestroyStructure:checkFail() + if not self.isComplete and not self.isFailed then + local target = self.param.target + local exists = false + local st = StaticObject.getByName(target.name) + if st then + exists = true + else + st = Group.getByName(target.name) + if st and st:getSize()>0 then + exists = true + end + end + + if not exists then + self.isFailed = true + end + end + end +end + +-----------------[[ END OF Objectives/ObjDestroyStructure.lua ]]----------------- + + + +-----------------[[ Objectives/ObjDestroyUnitsWithAttribute.lua ]]----------------- + +ObjDestroyUnitsWithAttribute = Objective:new(Objective.types.destroy_attr) +do + ObjDestroyUnitsWithAttribute.requiredParams = { + ['attr'] = true, + ['amount'] = true, + ['killed'] = true + } + + function ObjDestroyUnitsWithAttribute:getText() + local msg = 'Destroy: ' + for _,v in ipairs(self.param.attr) do + msg = msg..v..', ' + end + msg = msg:sub(1,#msg-2) + msg = msg..'\n Progress: '..self.param.killed..'/'..self.param.amount + return msg + end + + function ObjDestroyUnitsWithAttribute:update() + if not self.isComplete and not self.isFailed then + if self.param.killed >= self.param.amount then + self.isComplete = true + return true + end + end + end + + function ObjDestroyUnitsWithAttribute:checkFail() + -- can not fail + end +end + +-----------------[[ END OF Objectives/ObjDestroyUnitsWithAttribute.lua ]]----------------- + + + +-----------------[[ Objectives/ObjDestroyUnitsWithAttributeAtZone.lua ]]----------------- + +ObjDestroyUnitsWithAttributeAtZone = Objective:new(Objective.types.destroy_attr_at_zone) +do + ObjDestroyUnitsWithAttributeAtZone.requiredParams = { + ['attr']=true, + ['amount'] = true, + ['killed'] = true, + ['tgtzone'] = true + } + + function ObjDestroyUnitsWithAttributeAtZone:getText() + local msg = 'Destroy at '..self.param.tgtzone.name..': ' + for _,v in ipairs(self.param.attr) do + msg = msg..v..', ' + end + msg = msg:sub(1,#msg-2) + msg = msg..'\n Progress: '..self.param.killed..'/'..self.param.amount + return msg + end + + function ObjDestroyUnitsWithAttributeAtZone:update() + if not self.isComplete and not self.isFailed then + if self.param.killed >= self.param.amount then + self.isComplete = true + return true + end + + local zn = self.param.tgtzone + if zn.side ~= 1 or not zn:hasUnitWithAttributeOnSide(self.param.attr, 1) then + if self.firstFailure == nil then + self.firstFailure = timer.getAbsTime() + else + if timer.getAbsTime() - self.firstFailure > 5*60 then + self.isFailed = true + self.mission.failureReason = zn.name..' no longer has targets matching the description.' + return true + end + end + else + if self.firstFailure ~= nil then + self.firstFailure = nil + end + end + end + end + + function ObjDestroyUnitsWithAttributeAtZone:checkFail() + if not self.isComplete and not self.isFailed then + local zn = self.param.tgtzone + if zn.side ~= 1 or not zn:hasUnitWithAttributeOnSide(self.param.attr, 1) then + self.isFailed = true + end + end + end +end + +-----------------[[ END OF Objectives/ObjDestroyUnitsWithAttributeAtZone.lua ]]----------------- + + + +-----------------[[ Objectives/ObjEscortGroup.lua ]]----------------- + +ObjEscortGroup = Objective:new(Objective.types.escort) +do + ObjEscortGroup.requiredParams = { + ['maxAmount']=true, + ['amount'] = true, + ['proxDist']= true, + ['target'] = true, + ['lastUpdate']= true + } + + function ObjEscortGroup:getText() + local msg = 'Stay in close proximity of the convoy' + + local gr = Group.getByName(self.param.target.name) + if gr and gr:getSize()>0 then + local grunit = gr:getUnit(1) + local lat,lon,alt = coord.LOtoLL(grunit:getPoint()) + local mgrs = coord.LLtoMGRS(coord.LOtoLL(grunit:getPoint())) + msg = msg..'\n DDM: '.. mist.tostringLL(lat,lon,3) + msg = msg..'\n DMS: '.. mist.tostringLL(lat,lon,2,true) + msg = msg..'\n MGRS: '.. mist.tostringMGRS(mgrs, 5) + end + + local prg = math.floor(((self.param.maxAmount - self.param.amount)/self.param.maxAmount)*100) + msg = msg.. '\n Progress: '..prg..'%' + return msg + end + + function ObjEscortGroup:update() + if not self.isComplete and not self.isFailed then + local gr = Group.getByName(self.param.target.name) + if not gr or gr:getSize()==0 then + self.isFailed = true + self.mission.failureReason = 'Group has been destroyed.' + return true + end + local grunit = gr:getUnit(1) + + if self.param.target.state == 'atdestination' or self.param.target.state == 'siege' then + for name, unit in pairs(self.mission.players) do + if unit and unit:isExist() then + local dist = mist.utils.get3DDist(unit:getPoint(), grunit:getPoint()) + if dist < self.param.proxDist then + self.isComplete = true + break + end + end + end + + if not self.isComplete then + self.isFailed = true + self.mission.failureReason = 'Group has reached its destination without an escort.' + end + end + + if not self.isComplete and not self.isFailed then + local plycount = Utils.getTableSize(self.mission.players) + if plycount == 0 then plycount = 1 end + local updateFrequency = 5 -- seconds + local shouldUpdateMsg = (timer.getAbsTime() - self.param.lastUpdate) > updateFrequency + for name, unit in pairs(self.mission.players) do + if unit and unit:isExist() then + local dist = mist.utils.get3DDist(unit:getPoint(), grunit:getPoint()) + if dist < self.param.proxDist then + self.param.amount = self.param.amount - (1/plycount) + + if shouldUpdateMsg then + local prg = string.format('%.1f',((self.param.maxAmount - self.param.amount)/self.param.maxAmount)*100) + trigger.action.outTextForUnit(unit:getID(), 'Progress: '..prg..'%', updateFrequency) + end + else + if shouldUpdateMsg then + local m = 'Distance: ' + if dist>1000 then + local dstkm = string.format('%.2f',dist/1000) + local dstnm = string.format('%.2f',dist/1852) + + m = m..dstkm..'km | '..dstnm..'nm' + else + local dstft = math.floor(dist/0.3048) + m = m..math.floor(dist)..'m | '..dstft..'ft' + end + + m = m..'\nBearing: '..math.floor(Utils.getBearing(unit:getPoint(), grunit:getPoint())) + trigger.action.outTextForUnit(unit:getID(), m, updateFrequency) + end + end + end + end + + if shouldUpdateMsg then + self.param.lastUpdate = timer.getAbsTime() + end + end + + if self.param.amount <= 0 then + self.isComplete = true + return true + end + end + end + + function ObjEscortGroup:checkFail() + if not self.isComplete and not self.isFailed then + local tg = self.param.target + local gr = Group.getByName(tg.name) + if not gr or gr:getSize() == 0 then + self.isFailed = true + end + + if self.mission.state == Mission.states.new then + if tg.state == 'enroute' and (timer.getAbsTime() - tg.lastStateTime) >= 7*60 then + self.isFailed = true + end + end + end + end +end + +-----------------[[ END OF Objectives/ObjEscortGroup.lua ]]----------------- + + + +-----------------[[ Objectives/ObjFlyToZoneSequence.lua ]]----------------- + +ObjFlyToZoneSequence = Objective:new(Objective.types.fly_to_zone_seq) +do + ObjFlyToZoneSequence.requiredParams = { + ['waypoints'] = true, + ['failZones'] = true + } + + function ObjFlyToZoneSequence:getText() + local msg = 'Fly route: ' + + for i,v in ipairs(self.param.waypoints) do + if v.complete then + msg = msg..'\n [✓] '..i..'. '..v.zone.name + else + msg = msg..'\n --> '..i..'. '..v.zone.name + end + end + return msg + end + + function ObjFlyToZoneSequence:update() + if not self.isComplete and not self.isFailed then + if self.param.failZones[1] then + for _,zn in ipairs(self.param.failZones[1]) do + if zn.side ~= 1 then + self.isFailed = true + self.mission.failureReason = zn.name..' is no longer controlled by the enemy.' + break + end + end + end + + if self.param.failZones[2] then + for _,zn in ipairs(self.param.failZones[2]) do + if zn.side ~= 2 then + self.isFailed = true + self.mission.failureReason = zn.name..' was lost.' + break + end + end + end + + if not self.isFailed then + local firstWP = nil + local nextWP = nil + for i,leg in ipairs(self.param.waypoints) do + if not leg.complete then + firstWP = leg + nextWP = self.param.waypoints[i+1] + break + end + end + + if firstWP then + local point = firstWP.zone.zone.point + local range = 3000 --meters + local allInside = true + for p,u in pairs(self.mission.players) do + if u and u:isExist() then + if Utils.isLanded(u,true) then + allInside = false + break + end + + local pos = u:getPoint() + local dist = mist.utils.get2DDist(point, pos) + if dist > range then + allInside = false + break + end + end + end + + if allInside then + firstWP.complete = true + self.mission:pushMessageToPlayers(firstWP.zone.name..' reached') + if nextWP then + self.mission:pushMessageToPlayers('Next point: '..nextWP.zone.name) + end + end + else + self.isComplete = true + return true + end + end + end + end + + function ObjFlyToZoneSequence:checkFail() + if not self.isComplete and not self.isFailed then + if self.param.failZones[1] then + for _,zn in ipairs(self.param.failZones[1]) do + if zn.side ~= 1 then + self.isFailed = true + break + end + end + end + + if self.param.failZones[2] then + for _,zn in ipairs(self.param.failZones[2]) do + if zn.side ~= 2 then + self.isFailed = true + break + end + end + end + end + end +end + +-----------------[[ END OF Objectives/ObjFlyToZoneSequence.lua ]]----------------- + + + +-----------------[[ Objectives/ObjProtectMission.lua ]]----------------- + +ObjProtectMission = Objective:new(Objective.types.protect) +do + ObjProtectMission.requiredParams = { + ['mis'] = true + } + + function ObjProtectMission:getText() + local msg = 'Prevent enemy aircraft from interfering with '..self.param.mis:getMissionName()..' mission.' + + if self.param.mis.info and self.param.mis.info.targetzone then + msg = msg..'\n Target zone: '..self.param.mis.info.targetzone.name + end + + msg = msg..'\n Protect players: ' + for i,v in pairs(self.param.mis.players) do + msg = msg..'\n '..i + end + + msg = msg..'\n Mission success depends on '..self.param.mis:getMissionName()..' mission success.' + return msg + end + + function ObjProtectMission:update() + if not self.isComplete and not self.isFailed then + if self.param.mis.state == Mission.states.failed then + self.isFailed = true + self.mission.failureReason = "Failed to protect players of "..self.param.mis.name.." mission." + end + + if self.param.mis.state == Mission.states.completed then + self.isComplete = true + end + end + end + + function ObjProtectMission:checkFail() + if not self.isComplete and not self.isFailed then + if self.param.mis.state == Mission.states.failed then + self.isFailed = true + end + + if self.param.mis.state == Mission.states.completed then + if self.state == Mission.states.new or + self.state == Mission.states.preping or + self.state == Mission.states.comencing then + + self.isFailed = true + end + end + end + end +end + +-----------------[[ END OF Objectives/ObjProtectMission.lua ]]----------------- + + + +-----------------[[ Objectives/ObjReconZone.lua ]]----------------- + +ObjReconZone = Objective:new(Objective.types.recon_zone) +do + ObjReconZone.requiredParams = { + ['target'] = true, + ['maxAmount'] = true, + ['amount'] = true, + ['allowedDeviation'] = true, + ['proxDist'] = true, + ['lastUpdate'] = true, + ['failZones'] = true + } + + function ObjReconZone:getText() + local msg = 'Stay within range of '..self.param.target.name..' and observe the enemy.' + + local prg = math.floor(((self.param.maxAmount - self.param.amount)/self.param.maxAmount)*100) + msg = msg.. '\n Progress: '..prg..'%' + return msg + end + + function ObjReconZone:update() + if not self.isComplete and not self.isFailed then + if self.param.failZones[1] then + for _,zn in ipairs(self.param.failZones[1]) do + if zn.side ~= 1 then + self.isFailed = true + self.mission.failureReason = zn.name..' is no longer controlled by the enemy.' + break + end + end + end + + if self.param.failZones[2] then + for _,zn in ipairs(self.param.failZones[2]) do + if zn.side ~= 2 then + self.isFailed = true + self.mission.failureReason = zn.name..' was lost.' + break + end + end + end + + if not self.isFailed then + local plycount = Utils.getTableSize(self.mission.players) + if plycount == 0 then plycount = 1 end + local updateFrequency = 5 -- seconds + local shouldUpdateMsg = (timer.getAbsTime() - self.param.lastUpdate) > updateFrequency + + for name, unit in pairs(self.mission.players) do + if unit and unit:isExist() then + local dist = mist.utils.get2DDist(unit:getPoint(), self.param.target.zone.point) + if dist < self.param.proxDist then + local unitPos = unit:getPosition() + local unitheading = math.deg(math.atan2(unitPos.x.z, unitPos.x.x)) + local bearing = Utils.getBearing(unit:getPoint(), self.param.target.zone.point) + + local diff = Utils.getHeadingDiff(unitheading, bearing) + + if math.abs(diff) <= self.param.allowedDeviation then + local unitsCount = 0 + local visibleCount = 0 + for _,product in pairs(self.param.target.built) do + if product.side ~= unit:getCoalition() then + local gr = Group.getByName(product.name) + if gr then + for _,enemyUnit in ipairs(gr:getUnits()) do + unitsCount = unitsCount+1 + local from = unit:getPoint() + from.y = from.y+1.5 + local to = enemyUnit:getPoint() + to.y = to.y+1.5 + if land.isVisible(from, to) then + visibleCount = visibleCount+1 + end + end + else + local st = StaticObject.getByName(product.name) + if st then + unitsCount = unitsCount+1 + local from = unit:getPoint() + from.y = from.y+1.5 + local to = st:getPoint() + to.y = to.y+1.5 + if land.isVisible(from, to) then + visibleCount = visibleCount+1 + end + end + end + end + end + + local percentVisible = 0 + if unitsCount > 0 and visibleCount > 0 then + percentVisible = visibleCount/unitsCount + if percentVisible > 0.5 then + self.param.amount = self.param.amount - percentVisible + else + + end + env.info('Scout_Helo - player can see '..string.format('%.2f',percentVisible)..'%') + end + + + if shouldUpdateMsg then + if visibleCount == 0 then + local prg = string.format('%.1f',((self.param.maxAmount - self.param.amount)/self.param.maxAmount)*100) + trigger.action.outTextForUnit(unit:getID(), 'No enemy visible.\nProgress: '..prg..'%', updateFrequency) + else + local percent = string.format('%.1f',percentVisible*100) + + local prg = string.format('%.1f',((self.param.maxAmount - self.param.amount)/self.param.maxAmount)*100) + trigger.action.outTextForUnit(unit:getID(), percent..'% of enemies visible.\nProgress: '..prg..'%', updateFrequency) + end + end + else + if shouldUpdateMsg then + local m = 'Within range\nTurn heading: '..math.floor(Utils.getBearing(unit:getPoint(), self.param.target.zone.point)) + trigger.action.outTextForUnit(unit:getID(), m, updateFrequency) + end + end + else + if shouldUpdateMsg then + local dstkm = string.format('%.2f',dist/1000) + local dstnm = string.format('%.2f',dist/1852) + + local m = 'Distance: ' + m = m..dstkm..'km | '..dstnm..'nm' + + m = m..'\nBearing: '..math.floor(Utils.getBearing(unit:getPoint(), self.param.target.zone.point)) + trigger.action.outTextForUnit(unit:getID(), m, updateFrequency) + end + end + end + end + + if shouldUpdateMsg then + self.param.lastUpdate = timer.getAbsTime() + end + + if self.param.amount <= 0 then + self.isComplete = true + return true + end + end + end + end + + function ObjReconZone:checkFail() + if not self.isComplete and not self.isFailed then + if self.param.failZones[1] then + for _,zn in ipairs(self.param.failZones[1]) do + if zn.side ~= 1 then + self.isFailed = true + break + end + end + end + + if self.param.failZones[2] then + for _,zn in ipairs(self.param.failZones[2]) do + if zn.side ~= 2 then + self.isFailed = true + break + end + end + end + end + end +end + +-----------------[[ END OF Objectives/ObjReconZone.lua ]]----------------- + + + +-----------------[[ Objectives/ObjSupplyZone.lua ]]----------------- + +ObjSupplyZone = Objective:new(Objective.types.supply) +do + ObjSupplyZone.requiredParams = { + ['amount']=true, + ['delivered']=true, + ['tgtzone']=true + } + + function ObjSupplyZone:getText() + local msg = 'Deliver '..self.param.amount..' to '..self.param.tgtzone.name..': ' + msg = msg..'\n Progress: '..self.param.delivered..'/'..self.param.amount + return msg + end + + function ObjSupplyZone:update() + if not self.isComplete and not self.isFailed then + if self.param.delivered >= self.param.amount then + self.isComplete = true + return true + end + + local zn = self.param.tgtzone + if zn.side ~= 2 then + self.isFailed = true + self.mission.failureReason = zn.name..' was lost.' + return true + end + end + end + + function ObjSupplyZone:checkFail() + if not self.isComplete and not self.isFailed then + local zn = self.param.tgtzone + if zn.side ~= 2 then + self.isFailed = true + return true + end + end + end +end + +-----------------[[ END OF Objectives/ObjSupplyZone.lua ]]----------------- + + + +-----------------[[ Objectives/ObjExtractSquad.lua ]]----------------- + +ObjExtractSquad = Objective:new(Objective.types.extract_squad) +do + ObjExtractSquad.requiredParams = { + ['target']=true, + ['loadedBy']=false, + ['lastUpdate']= true + } + + function ObjExtractSquad:getText() + local infName = PlayerLogistics.getInfantryName(self.param.target.data.type) + local msg = 'Extract '..infName..' '..self.param.target.name..'\n' + + if not self.param.loadedBy then + local gr = Group.getByName(self.param.target.name) + if gr and gr:getSize()>0 then + local point = gr:getUnit(1):getPoint() + + local lat,lon,alt = coord.LOtoLL(point) + local mgrs = coord.LLtoMGRS(coord.LOtoLL(point)) + msg = msg..'\n DDM: '.. mist.tostringLL(lat,lon,3) + msg = msg..'\n DMS: '.. mist.tostringLL(lat,lon,2,true) + msg = msg..'\n MGRS: '.. mist.tostringMGRS(mgrs, 5) + msg = msg..'\n Altitude: '..math.floor(alt)..'m'..' | '..math.floor(alt*3.280839895)..'ft' + end + end + + return msg + end + + function ObjExtractSquad:update() + if not self.isComplete and not self.isFailed then + + if self.param.loadedBy then + self.isComplete = true + return true + else + local target = self.param.target + + local gr = Group.getByName(target.name) + if not gr or gr:getSize()==0 then + self.isFailed = true + self.mission.failureReason = 'Squad was not rescued in time, and went MIA.' + return true + end + end + + local updateFrequency = 5 -- seconds + local shouldUpdateMsg = (timer.getAbsTime() - self.param.lastUpdate) > updateFrequency + if shouldUpdateMsg then + for name, unit in pairs(self.mission.players) do + if unit and unit:isExist() then + local gr = Group.getByName(self.param.target.name) + local un = gr:getUnit(1) + local dist = mist.utils.get3DDist(unit:getPoint(), un:getPoint()) + local m = 'Distance: ' + if dist>1000 then + local dstkm = string.format('%.2f',dist/1000) + local dstnm = string.format('%.2f',dist/1852) + + m = m..dstkm..'km | '..dstnm..'nm' + else + local dstft = math.floor(dist/0.3048) + m = m..math.floor(dist)..'m | '..dstft..'ft' + end + + m = m..'\nBearing: '..math.floor(Utils.getBearing(unit:getPoint(), un:getPoint())) + trigger.action.outTextForUnit(unit:getID(), m, updateFrequency) + end + end + + self.param.lastUpdate = timer.getAbsTime() + end + end + end + + function ObjExtractSquad:checkFail() + if not self.isComplete and not self.isFailed then + local target = self.param.target + + local gr = Group.getByName(target.name) + if not gr or not gr:isExist() or gr:getSize()==0 then + self.isFailed = true + return true + end + end + end +end + +-----------------[[ END OF Objectives/ObjExtractSquad.lua ]]----------------- + + + +-----------------[[ Objectives/ObjExtractPilot.lua ]]----------------- + +ObjExtractPilot = Objective:new(Objective.types.extract_pilot) +do + ObjExtractPilot.requiredParams = { + ['target']=true, + ['loadedBy']=false, + ['lastUpdate']= true + } + + function ObjExtractPilot:getText() + local msg = 'Rescue '..self.param.target.name..'\n' + + if not self.param.loadedBy then + + if self.param.target.pilot:isExist() then + local point = self.param.target.pilot:getUnit(1):getPoint() + + local lat,lon,alt = coord.LOtoLL(point) + local mgrs = coord.LLtoMGRS(coord.LOtoLL(point)) + msg = msg..'\n DDM: '.. mist.tostringLL(lat,lon,3) + msg = msg..'\n DMS: '.. mist.tostringLL(lat,lon,2,true) + msg = msg..'\n MGRS: '.. mist.tostringMGRS(mgrs, 5) + msg = msg..'\n Altitude: '..math.floor(alt)..'m'..' | '..math.floor(alt*3.280839895)..'ft' + end + end + + return msg + end + + function ObjExtractPilot:update() + if not self.isComplete and not self.isFailed then + + if self.param.loadedBy then + self.isComplete = true + return true + else + if not self.param.target.pilot:isExist() or self.param.target.remainingTime <= 0 then + self.isFailed = true + self.mission.failureReason = 'Pilot was not rescued in time, and went MIA.' + return true + end + end + + local updateFrequency = 5 -- seconds + local shouldUpdateMsg = (timer.getAbsTime() - self.param.lastUpdate) > updateFrequency + if shouldUpdateMsg then + for name, unit in pairs(self.mission.players) do + if unit and unit:isExist() then + local gr = Group.getByName(self.param.target.name) + if gr and gr:getSize() > 0 then + local un = gr:getUnit(1) + if un then + local dist = mist.utils.get3DDist(unit:getPoint(), un:getPoint()) + local m = 'Distance: ' + if dist>1000 then + local dstkm = string.format('%.2f',dist/1000) + local dstnm = string.format('%.2f',dist/1852) + + m = m..dstkm..'km | '..dstnm..'nm' + else + local dstft = math.floor(dist/0.3048) + m = m..math.floor(dist)..'m | '..dstft..'ft' + end + + m = m..'\nBearing: '..math.floor(Utils.getBearing(unit:getPoint(), un:getPoint())) + trigger.action.outTextForUnit(unit:getID(), m, updateFrequency) + end + end + end + end + + self.param.lastUpdate = timer.getAbsTime() + end + end + end + + function ObjExtractPilot:checkFail() + if not self.isComplete and not self.isFailed then + if not self.param.target.pilot:isExist() or self.param.target.remainingTime <= 0 then + self.isFailed = true + return true + end + end + end +end + +-----------------[[ END OF Objectives/ObjExtractPilot.lua ]]----------------- + + + +-----------------[[ Objectives/ObjUnloadExtractedPilotOrSquad.lua ]]----------------- + +ObjUnloadExtractedPilotOrSquad = Objective:new(Objective.types.unloaded_pilot_or_squad) +do + ObjUnloadExtractedPilotOrSquad.requiredParams = { + ['targetZone']=false, + ['extractObjective']=true, + ['unloadedAt']=false + } + + function ObjUnloadExtractedPilotOrSquad:getText() + local msg = 'Drop off personnel ' + if self.param.targetZone then + msg = msg..'at '..self.param.targetZone.name..'\n' + else + msg = msg..'at a friendly zone\n' + end + + return msg + end + + function ObjUnloadExtractedPilotOrSquad:update() + if not self.isComplete and not self.isFailed then + + if self.param.extractObjective.isComplete and self.param.unloadedAt then + if self.param.targetZone then + if self.param.unloadedAt == self.param.targetZone.name then + self.isComplete = true + return true + else + self.isFailed = true + self.mission.failureReason = 'Personnel dropped off at wrong zone.' + return true + end + else + self.isComplete = true + return true + end + end + + if self.param.extractObjective.isFailed then + self.isFailed = true + return true + end + + if self.param.targetZone and self.param.targetZone.side ~= 2 then + self.isFailed = true + self.mission.failureReason = self.param.targetZone.name..' was lost.' + return true + end + end + end + + function ObjUnloadExtractedPilotOrSquad:checkFail() + if not self.isComplete and not self.isFailed then + + if self.param.extractObjective.isFailed then + self.isFailed = true + return true + end + + if self.param.targetZone and self.param.targetZone.side ~= 2 then + self.isFailed = true + return true + end + end + end +end + +-----------------[[ END OF Objectives/ObjUnloadExtractedPilotOrSquad.lua ]]----------------- + + + +-----------------[[ Objectives/ObjPlayerCloseToZone.lua ]]----------------- + +ObjPlayerCloseToZone = Objective:new(Objective.types.player_close_to_zone) +do + ObjPlayerCloseToZone.requiredParams = { + ['target']=true, + ['range'] = true, + ['amount']= true, + ['maxAmount'] = true, + ['lastUpdate']= true + } + + function ObjPlayerCloseToZone:getText() + local msg = 'Patrol area around '..self.param.target.name + + local prg = math.floor(((self.param.maxAmount - self.param.amount)/self.param.maxAmount)*100) + msg = msg.. '\n Progress: '..prg..'%' + return msg + end + + function ObjPlayerCloseToZone:update() + if not self.isComplete and not self.isFailed then + + if self.param.target.side ~= 2 then + self.isFailed = true + self.mission.failureReason = self.param.target.name..' was lost.' + return true + end + + local plycount = Utils.getTableSize(self.mission.players) + if plycount == 0 then plycount = 1 end + local updateFrequency = 5 -- seconds + local shouldUpdateMsg = (timer.getAbsTime() - self.param.lastUpdate) > updateFrequency + for name, unit in pairs(self.mission.players) do + if unit and unit:isExist() and Utils.isInAir(unit) then + local dist = mist.utils.get2DDist(unit:getPoint(), self.param.target.zone.point) + if dist < self.param.range then + self.param.amount = self.param.amount - (1/plycount) + + if shouldUpdateMsg then + local prg = string.format('%.1f',((self.param.maxAmount - self.param.amount)/self.param.maxAmount)*100) + trigger.action.outTextForUnit(unit:getID(), '['..self.param.target.name..'] Progress: '..prg..'%', updateFrequency) + end + end + end + end + + if shouldUpdateMsg then + self.param.lastUpdate = timer.getAbsTime() + end + + if self.param.amount <= 0 then + self.isComplete = true + for name, unit in pairs(self.mission.players) do + if unit and unit:isExist() and Utils.isInAir(unit) then + trigger.action.outTextForUnit(unit:getID(), '['..self.param.target.name..'] Complete', updateFrequency) + end + end + return true + end + end + end + + function ObjPlayerCloseToZone:checkFail() + if not self.isComplete and not self.isFailed then + if self.param.target.side ~= 2 then + self.isFailed = true + end + end + end +end + +-----------------[[ END OF Objectives/ObjPlayerCloseToZone.lua ]]----------------- + + + +-----------------[[ Objectives/ObjDeploySquad.lua ]]----------------- + +ObjDeploySquad = Objective:new(Objective.types.deploy_squad) +do + ObjDeploySquad.requiredParams = { + ['squadType']=true, + ['targetZone']=true, + ['requiredZoneSide']=true, + ['unloadedType']=false, + ['unloadedAt']=false + } + + function ObjDeploySquad:getText() + local infName = PlayerLogistics.getInfantryName(self.param.squadType) + local msg = 'Deploy '..infName..' at '..self.param.targetZone.name + return msg + end + + function ObjDeploySquad:update() + if not self.isComplete and not self.isFailed then + + if self.param.unloadedType and self.param.unloadedAt then + if self.param.targetZone.name == self.param.unloadedAt then + if self.param.squadType == self.param.unloadedType then + self.isComplete = true + return true + end + end + end + + if self.param.targetZone.side ~= self.param.requiredZoneSide then + self.isFailed = true + + local side = '' + if self.param.requiredZoneSide == 0 then side = 'neutral' + elseif self.param.requiredZoneSide == 1 then side = 'controlled by Red' + elseif self.param.requiredZoneSide == 2 then side = 'controlled by Blue' + end + + self.mission.failureReason = self.param.targetZone.name..' is no longer '..side + return true + end + end + end + + function ObjDeploySquad:checkFail() + if not self.isComplete and not self.isFailed then + if self.param.targetZone.side ~= self.param.requiredZoneSide then + self.isFailed = true + return true + end + end + end +end + +-----------------[[ END OF Objectives/ObjDeploySquad.lua ]]----------------- + + + +-----------------[[ Missions/Mission.lua ]]----------------- + +Mission = {} +do + Mission.states = { + new = 'new', -- mission was just generated and is listed publicly + preping = 'preping', -- mission was accepted by a player, was delisted, and player recieved a join code that can be shared + comencing = 'comencing', -- a player that is subscribed to the mission has taken off, join code is invalidated + active = 'active', -- all players subscribed to the mission have taken off, objective can now be accomplished + completed = 'completed', -- mission objective was completed, players need to land to claim rewards + failed = 'failed' -- mission lost all players OR mission objective no longer possible to accomplish + } + + --[[ + new -> preping -> comencing -> active -> completed + | | | |-> failed + | | |->failed + | |->failed + |->failed + --]] + + Mission.types = { + cap_easy = 'cap_easy', -- fly over zn A-B-A-B-A-B OR destroy few enemy aircraft + cap_medium = 'cap_medium', -- fly over zn A-B-A-B-A-B AND destroy few enemy aircraft -- push list of aircraft within range of target zones + tarcap = 'tarcap', -- protect other mission, air kills increase reward + --tarcap = 'tarcap', -- update target mission list after all other missions are in + + cas_easy = 'cas_easy', -- destroy small amount of ground units + cas_medium = 'cas_medium', -- destroy large amount of ground units + cas_hard = 'cas_hard', -- destroy all defenses at zone A + bai = 'bai', -- destroy any enemy convoy - show "last" location of convoi (BRA or LatLon) update every 30 seconds + + sead = 'sead', -- destroy any SAM TR or SAM SR at zone A + dead = 'dead', -- destroy all SAM TR or SAM SR, or IR Guided SAM at zone A + + strike_veryeasy = 'strike_veryeasy', -- destroy 1 building + strike_easy = 'strike_easy', -- destroy any structure at zone A + strike_medium = 'strike_medium',-- destroy specific structure at zone A - show LatLon and Alt in mission description + strike_hard = 'strike_hard', -- destroy all structures at zone A and turn it neutral + deep_strike = 'deep_strike', -- destroy specific structure taken from strike queue - show LatLon and Alt in mission description + + recon_plane ='recon_plane', -- overly target zone and survive + recon_plane_deep = 'recon_plane_deep', -- overfly zone where distance from front == 2, add target to a strike queue, stays there until building exists, or 1hr passes + anti_runway = 'anti_runway', -- drop at least X anti runway bombs on runway zone (if player unit launches correct weapon, track, if agl>10m check if in zone, tally), define list of runway zones somewhere + + supply_easy = 'supply_easy', -- transfer resources to zone A(low supply) + supply_hard = 'supply_hard', -- transfer resources to zone A(low supply), high resource number + escort = 'escort', -- follow and protect friendly convoy until they get to target OR 10 minutes pass + csar = 'csar', -- extract specific pilot to friendly zone, track friendly pilots ejected + scout_helo = 'scout_helo', -- within X km, facing Y angle +-, % of enemy units in LOS progress faster + extraction = 'extraction', -- extract a deployed squad to friendly zone, generate mission if squad has extractionReady state + deploy_squad = 'deploy_squad', -- deploy squad to zone + } + + Mission.completion_type = { + any = 'any', + all = 'all' + } + + function Mission:new(id, type) + local expire = math.random(60*15, 60*30) + + local obj = { + missionID = id, + type = type, + name = '', + description = '', + failureReason = nil, + state = Mission.states.new, + expireTime = expire, + lastStateTime = timer.getAbsTime(), + objectives = {}, + completionType = Mission.completion_type.any, + rewards = {}, + players = {}, + info = {} + } + + setmetatable(obj, self) + self.__index = self + + if obj.getExpireTime then obj.expireTime = obj:getExpireTime() end + if obj.getMissionName then obj.name = obj:getMissionName() end + if obj.generateObjectives then obj:generateObjectives() end + if obj.generateRewards then obj:generateRewards() end + + return obj + end + + function Mission:updateState(newstate) + env.info('Mission - code'..self.missionID..' updateState state changed from '..self.state..' to '..newstate) + self.state = newstate + self.lastStateTime = timer.getAbsTime() + if self.state == self.states.preping then + if self.info.targetzone then + MissionTargetRegistry.addZone(self.info.targetzone.name) + end + elseif self.state == self.states.completed or self.state == self.states.failed then + if self.info.targetzone then + MissionTargetRegistry.removeZone(self.info.targetzone.name) + end + end + end + + function Mission:pushMessageToPlayers(msg, duration) + if not duration then + duration = 10 + end + + for _,un in pairs(self.players) do + if un and un:isExist() then + trigger.action.outTextForUnit(un:getID(), msg, duration) + end + end + end + + function Mission:pushSoundToPlayers(sound) + for _,un in pairs(self.players) do + if un and un:isExist() then + --trigger.action.outSoundForUnit(un:getID(), sound) -- does not work correctly in multiplayer + trigger.action.outSoundForGroup(un:getGroup():getID(), sound) + end + end + end + + function Mission:removePlayer(player) + for pl,un in pairs(self.players) do + if pl == player then + self.players[pl] = nil + break + end + end + end + + function Mission:isInstantReward() + return false + end + + function Mission:hasPlayers() + return Utils.getTableSize(self.players) > 0 + end + + function Mission:getPlayerUnit(player) + return self.players[player] + end + + function Mission:addPlayer(player, unit) + self.players[player] = unit + end + + function Mission:checkFailConditions() + if self.state == Mission.states.active then return end + + for _,obj in ipairs(self.objectives) do + local shouldBreak = obj:checkFail() + + if shouldBreak then break end + end + end + + function Mission:updateObjectives() + if self.state ~= self.states.active then return end + + for _,obj in ipairs(self.objectives) do + local shouldBreak = obj:update() + + if obj.isFailed and self.objectiveFailedCallback then self:objectiveFailedCallback(obj) end + if not obj.isFailed and obj.isComplete and self.objectiveCompletedCallback then self:objectiveCompletedCallback(obj) end + + if shouldBreak then break end + end + end + + function Mission:updateIsFailed() + self:checkFailConditions() + + local allFailed = true + for _,obj in ipairs(self.objectives) do + if self.state == Mission.states.new then + if obj.isFailed then + self:updateState(Mission.states.failed) + env.info("Mission code"..self.missionID.." objective cancelled:\n"..obj:getText()) + break + end + end + + if self.completionType == Mission.completion_type.all then + if obj.isFailed then + self:updateState(Mission.states.failed) + env.info("Mission code"..self.missionID.." (all) objective failed:\n"..obj:getText()) + break + end + end + + if not obj.isFailed then + allFailed = false + end + end + + if self.completionType == Mission.completion_type.any and allFailed then + self:updateState(Mission.states.failed) + env.info("Mission code"..self.missionID.." all objectives failed") + end + end + + function Mission:updateIsCompleted() + if self.completionType == self.completion_type.any then + for _,obj in ipairs(self.objectives) do + if obj.isComplete then + self:updateState(self.states.completed) + env.info("Mission code"..self.missionID.." (any) objective completed:\n"..obj:getText()) + break + end + end + elseif self.completionType == self.completion_type.all then + local allComplete = true + for _,obj in ipairs(self.objectives) do + if not obj.isComplete then + allComplete = false + break + end + end + + if allComplete then + self:updateState(self.states.completed) + env.info("Mission code"..self.missionID.." all objectives complete") + end + end + end + + function Mission:tallyWeapon(weapon) + for _,obj in ipairs(self.objectives) do + if not obj.isComplete and not obj.isFailed then + if obj.type == ObjBombInsideZone:getType() then + for i,v in ipairs(obj.param.targetZone:getRunwayZones()) do + if Utils.isInZone(weapon, v.name) then + if obj.param.dropped < obj.param.max then + obj.param.dropped = obj.param.dropped + 1 + if obj.param.dropped > obj.param.required then + for _,rew in ipairs(self.rewards) do + if obj.param.bonus[rew.type] then + rew.amount = rew.amount + obj.param.bonus[rew.type] + + if rew.type == PlayerTracker.statTypes.xp then + self:pushMessageToPlayers("Bonus: + "..obj.param.bonus[rew.type]..' XP') + end + end + end + end + end + break + end + end + end + end + end + end + + function Mission:tallyKill(kill) + for _,obj in ipairs(self.objectives) do + if not obj.isComplete and not obj.isFailed then + if obj.type == ObjDestroyUnitsWithAttribute:getType() then + for _,a in ipairs(obj.param.attr) do + if kill:hasAttribute(a) then + obj.param.killed = obj.param.killed + 1 + break + elseif a == 'Buildings' and ZoneCommand and ZoneCommand.staticRegistry[kill:getName()] then + obj.param.killed = obj.param.killed + 1 + break + end + end + elseif obj.type == ObjDestroyStructure:getType() then + if obj.param.target.name == kill:getName() then + obj.param.killed = true + end + elseif obj.type == ObjDestroyGroup:getType() then + if kill.getName then + if obj.param.targetUnitNames[kill:getName()] ~= nil then + obj.param.targetUnitNames[kill:getName()] = true + end + end + elseif obj.type == ObjAirKillBonus:getType() then + for _,a in ipairs(obj.param.attr) do + if kill:hasAttribute(a) then + for _,rew in ipairs(self.rewards) do + if obj.param.bonus[rew.type] then + rew.amount = rew.amount + obj.param.bonus[rew.type] + obj.param.count = obj.param.count + 1 + if rew.type == PlayerTracker.statTypes.xp then + self:pushMessageToPlayers("Reward increased: + "..obj.param.bonus[rew.type]..' XP') + end + end + end + break + elseif a == 'Buildings' and ZoneCommand and ZoneCommand.staticRegistry[kill:getName()] then + for _,rew in ipairs(self.rewards) do + if obj.param.bonus[rew.type] then + rew.amount = rew.amount + obj.param.bonus[rew.type] + obj.param.count = obj.param.count + 1 + + if rew.type == PlayerTracker.statTypes.xp then + self:pushMessageToPlayers("Reward increased: + "..obj.param.bonus[rew.type]..' XP') + end + end + end + break + end + end + elseif obj.type == ObjDestroyUnitsWithAttributeAtZone:getType() then + local zn = obj.param.tgtzone + if zn then + local validzone = false + if Utils.isInZone(kill, zn.name) then + validzone = true + else + for nm,_ in pairs(zn.built) do + local gr = Group.getByName(nm) + if gr then + for _,un in ipairs(gr:getUnits()) do + if un:getID() == kill:getID() then + validzone = true + break + end + end + end + + if validzone then break end + end + end + + if validzone then + for _,a in ipairs(obj.param.attr) do + if kill:hasAttribute(a) then + obj.param.killed = obj.param.killed + 1 + break + elseif a == 'Buildings' and ZoneCommand and ZoneCommand.staticRegistry[kill:getName()] then + obj.param.killed = obj.param.killed + 1 + break + end + end + end + end + end + end + end + end + + function Mission:isUnitTypeAllowed(unit) + return true + end + + function Mission:tallySupplies(amount, zonename) + for _,obj in ipairs(self.objectives) do + if not obj.isComplete and not obj.isFailed then + if obj.type == ObjSupplyZone:getType() then + if obj.param.tgtzone.name == zonename then + obj.param.delivered = obj.param.delivered + amount + end + end + end + end + end + + function Mission:tallyLoadPilot(player, pilot) + for _,obj in ipairs(self.objectives) do + if not obj.isComplete and not obj.isFailed then + if obj.type == ObjExtractPilot:getType() then + if obj.param.target.name == pilot.name then + obj.param.loadedBy = player + end + end + end + end + end + + function Mission:tallyUnloadPilot(player, zonename) + for _,obj in ipairs(self.objectives) do + if not obj.isComplete and not obj.isFailed then + if obj.type == ObjUnloadExtractedPilotOrSquad:getType() then + if obj.param.extractObjective.param.loadedBy == player then + obj.param.unloadedAt = zonename + end + end + end + end + end + + function Mission:tallyLoadSquad(player, squad) + for _,obj in ipairs(self.objectives) do + if not obj.isComplete and not obj.isFailed then + if obj.type == ObjExtractSquad:getType() then + if obj.param.target.name == squad.name then + obj.param.loadedBy = player + end + end + end + end + end + + function Mission:tallyUnloadSquad(player, zonename, unloadedType) + for _,obj in ipairs(self.objectives) do + if not obj.isComplete and not obj.isFailed then + if obj.type == ObjUnloadExtractedPilotOrSquad:getType() then + if obj.param.extractObjective.param.loadedBy == player and unloadedType == PlayerLogistics.infantryTypes.extractable then + obj.param.unloadedAt = zonename + end + elseif obj.type == ObjDeploySquad:getType() then + obj.param.unloadedType = unloadedType + obj.param.unloadedAt = zonename + end + end + end + end + + function Mission:getBriefDescription() + local msg = '~~~~~'..self.name..' ['..self.missionID..']~~~~~\n'..self.description..'\n' + + msg = msg..' Reward:' + + for _,r in ipairs(self.rewards) do + msg = msg..' ['..r.type..': '..r.amount..']' + end + + return msg + end + + function Mission:generateRewards() + if not self.type then return end + + local rewardDef = RewardDefinitions.missions[self.type] + + self.rewards = {} + table.insert(self.rewards, { + type = PlayerTracker.statTypes.xp, + amount = math.random(rewardDef.xp.low,rewardDef.xp.high)*50 + }) + end + + function Mission:getDetailedDescription() + local msg = '['..self.name..']' + + if self.state == Mission.states.comencing or self.state == Mission.states.preping then + msg = msg..'\nJoin code ['..self.missionID..']' + end + + msg = msg..'\nReward:' + + for _,r in ipairs(self.rewards) do + msg = msg..' ['..r.type..': '..r.amount..']' + end + msg = msg..'\n' + + if #self.objectives>1 then + msg = msg..'\nObjectives: ' + if self.completionType == Mission.completion_type.all then + msg = msg..'(Complete ALL)\n' + elseif self.completionType == Mission.completion_type.any then + msg = msg..'(Complete ONE)\n' + end + elseif #self.objectives==1 then + msg = msg..'\nObjective: \n' + end + + for i,v in ipairs(self.objectives) do + local obj = v:getText() + if v.isComplete then + obj = '[✓]'..obj + elseif v.isFailed then + obj = '[X]'..obj + else + obj = '[ ]'..obj + end + + msg = msg..'\n'..obj..'\n' + end + + msg = msg..'\nPlayers:' + for i,_ in pairs(self.players) do + msg = msg..'\n '..i + end + + return msg + end +end + +-----------------[[ END OF Missions/Mission.lua ]]----------------- + + + +-----------------[[ Missions/CAP_Easy.lua ]]----------------- + +CAP_Easy = Mission:new() +do + function CAP_Easy.canCreate() + local zoneNum = 0 + for _,zone in pairs(ZoneCommand.getAllZones()) do + if zone.side == 2 and zone.distToFront == 0 then + zoneNum = zoneNum + 1 + end + + if zoneNum >= 2 then return true end + end + end + + function CAP_Easy:getMissionName() + return 'CAP' + end + + function CAP_Easy:isUnitTypeAllowed(unit) + return unit:hasAttribute('Planes') + end + + function CAP_Easy:generateObjectives() + self.completionType = Mission.completion_type.any + local description = '' + local viableZones = {} + for _,zone in pairs(ZoneCommand.getAllZones()) do + if zone.side == 2 and zone.distToFront == 0 then + table.insert(viableZones, zone) + end + end + + if #viableZones >= 2 then + local choice1 = math.random(1,#viableZones) + local zn1 = viableZones[choice1] + + local patrol1 = ObjPlayerCloseToZone:new() + patrol1:initialize(self, { + target = zn1, + range = 20000, + amount = 15*60, + maxAmount = 15*60, + lastUpdate = 0 + }) + + table.insert(self.objectives, patrol1) + description = description..' Patrol airspace near '..zn1.name..'\n OR\n' + end + + local kills = ObjDestroyUnitsWithAttribute:new() + kills:initialize(self, { + attr = {'Planes', 'Helicopters'}, + amount = math.random(2,4), + killed = 0 + }) + + table.insert(self.objectives, kills) + description = description..' Kill '..kills.param.amount..' aircraft' + self.description = self.description..description + end +end + +-----------------[[ END OF Missions/CAP_Easy.lua ]]----------------- + + + +-----------------[[ Missions/CAP_Medium.lua ]]----------------- + +CAP_Medium = Mission:new() +do + function CAP_Medium.canCreate() + local zoneNum = 0 + for _,zone in pairs(ZoneCommand.getAllZones()) do + if zone.side == 2 and zone.distToFront == 0 then + zoneNum = zoneNum + 1 + end + + if zoneNum >= 2 then return true end + end + end + + function CAP_Medium:getMissionName() + return 'CAP' + end + + function CAP_Medium:isUnitTypeAllowed(unit) + return unit:hasAttribute('Planes') + end + + function CAP_Medium:generateObjectives() + self.completionType = Mission.completion_type.all + local description = '' + local viableZones = {} + for _,zone in pairs(ZoneCommand.getAllZones()) do + if zone.side == 2 and zone.distToFront == 0 then + table.insert(viableZones, zone) + end + end + + if #viableZones >= 2 then + local choice1 = math.random(1,#viableZones) + local zn1 = viableZones[choice1] + table.remove(viableZones,choice1) + local choice2 = math.random(1,#viableZones) + local zn2 = viableZones[choice2] + + local patrol1 = ObjPlayerCloseToZone:new() + patrol1:initialize(self, { + target = zn1, + range = 20000, + amount = 10*60, + maxAmount = 10*60, + lastUpdate = 0 + }) + + table.insert(self.objectives, patrol1) + + local patrol2 = ObjPlayerCloseToZone:new() + patrol2:initialize(self, { + target = zn2, + range = 20000, + amount = 10*60, + maxAmount = 10*60, + lastUpdate = 0 + }) + + table.insert(self.objectives, patrol2) + description = description..' Patrol airspace near '..zn1.name..' and '..zn2.name..'\n' + + local rewardDef = RewardDefinitions.missions[self.type] + + local kills = ObjAirKillBonus:new() + kills:initialize(self, { + attr = {'Planes', 'Helicopters'}, + bonus = { + [PlayerTracker.statTypes.xp] = rewardDef.xp.boost + }, + count = 0, + linkedObjectives = {patrol1, patrol2} + }) + + table.insert(self.objectives, kills) + description = description..' Aircraft kills increase reward' + end + + self.description = self.description..description + end +end + +-----------------[[ END OF Missions/CAP_Medium.lua ]]----------------- + + + +-----------------[[ Missions/CAS_Easy.lua ]]----------------- + +CAS_Easy = Mission:new() +do + function CAS_Easy.canCreate() + return true + end + + function CAS_Easy:getMissionName() + return 'CAS' + end + + function CAS_Easy:generateObjectives() + self.completionType = Mission.completion_type.all + local description = '' + + local kills = ObjDestroyUnitsWithAttribute:new() + kills:initialize(self, { + attr = {'Ground Units'}, + amount = math.random(3,6), + killed = 0 + }) + + table.insert(self.objectives, kills) + description = description..' Destroy '..kills.param.amount..' Ground Units' + self.description = self.description..description + end +end + +-----------------[[ END OF Missions/CAS_Easy.lua ]]----------------- + + + +-----------------[[ Missions/CAS_Medium.lua ]]----------------- + +CAS_Medium = Mission:new() +do + function CAS_Medium.canCreate() + return true + end + + function CAS_Medium:getMissionName() + return 'CAS' + end + + function CAS_Medium:generateObjectives() + self.completionType = Mission.completion_type.all + local description = '' + + local kills = ObjDestroyUnitsWithAttribute:new() + kills:initialize(self, { + attr = {'Ground Units'}, + amount = math.random(8,12), + killed = 0 + }) + + table.insert(self.objectives, kills) + description = description..' Destroy '..kills.param.amount..' Ground Units' + self.description = self.description..description + end +end + +-----------------[[ END OF Missions/CAS_Medium.lua ]]----------------- + + + +-----------------[[ Missions/CAS_Hard.lua ]]----------------- + +CAS_Hard = Mission:new() +do + function CAS_Hard.canCreate() + for _,zone in pairs(ZoneCommand.getAllZones()) do + if zone.side == 1 and zone.distToFront and zone.distToFront <=1 and zone:hasUnitWithAttributeOnSide({"Ground Units"}, 1, 6) then + if not MissionTargetRegistry.isZoneTargeted(zone.name) then + return true + end + end + end + end + + function CAS_Hard:getMissionName() + return 'CAS' + end + + function CAS_Hard:generateObjectives() + self.completionType = Mission.completion_type.all + local description = '' + local viableZones = {} + for _,zone in pairs(ZoneCommand.getAllZones()) do + if zone.side == 1 and zone.distToFront == 0 and zone:hasUnitWithAttributeOnSide({"Ground Units"}, 1, 6) then + if not MissionTargetRegistry.isZoneTargeted(zone.name) then + table.insert(viableZones, zone) + end + end + end + + if #viableZones == 0 then + for _,zone in pairs(ZoneCommand.getAllZones()) do + if zone.side == 1 and zone.distToFront == 1 and zone:hasUnitWithAttributeOnSide({"Ground Units"}, 1, 6) then + if not MissionTargetRegistry.isZoneTargeted(zone.name) then + table.insert(viableZones, zone) + end + end + end + end + + if #viableZones > 0 then + local choice = math.random(1,#viableZones) + local zn = viableZones[choice] + + local kill = ObjDestroyUnitsWithAttributeAtZone:new() + kill:initialize(self, { + attr = {"Ground Units"}, + amount = 1, + killed = 0, + tgtzone = zn + }) + table.insert(self.objectives, kill) + + local clear = ObjClearZoneOfUnitsWithAttribute:new() + clear:initialize(self, { + attr = {"Ground Units"}, + tgtzone = zn + }) + table.insert(self.objectives, clear) + + description = description..' Clear '..zn.name..' of ground units' + self.info = { + targetzone = zn + } + end + self.description = self.description..description + end +end + +-----------------[[ END OF Missions/CAS_Hard.lua ]]----------------- + + + +-----------------[[ Missions/SEAD.lua ]]----------------- + +SEAD = Mission:new() +do + function SEAD.canCreate() + for _,zone in pairs(ZoneCommand.getAllZones()) do + if zone.side == 1 and zone.distToFront and zone.distToFront <=1 and zone:hasSAMRadarOnSide(1) then + if not MissionTargetRegistry.isZoneTargeted(zone.name) then + return true + end + end + end + end + + function SEAD:getMissionName() + return 'SEAD' + end + + function SEAD:generateObjectives() + self.completionType = Mission.completion_type.all + local description = '' + local viableZones = {} + for _,zone in pairs(ZoneCommand.getAllZones()) do + if zone.side == 1 and zone.distToFront == 0 and zone:hasSAMRadarOnSide(1) then + if not MissionTargetRegistry.isZoneTargeted(zone.name) then + table.insert(viableZones, zone) + end + end + end + + if #viableZones == 0 then + for _,zone in pairs(ZoneCommand.getAllZones()) do + if zone.side == 1 and zone.distToFront == 1 and zone:hasSAMRadarOnSide(1) then + if not MissionTargetRegistry.isZoneTargeted(zone.name) then + table.insert(viableZones, zone) + end + end + end + end + + if #viableZones > 0 then + local choice = math.random(1,#viableZones) + local zn = viableZones[choice] + + local kill = ObjDestroyUnitsWithAttributeAtZone:new() + kill:initialize(self, { + attr = {'SAM SR','SAM TR'}, + amount = 1, + killed = 0, + tgtzone = zn + }) + + table.insert(self.objectives, kill) + description = description..' Destroy '..kill.param.amount..' Search Radar or Track Radar at '..zn.name + self.info = { + targetzone = zn + } + end + self.description = self.description..description + end +end + +-----------------[[ END OF Missions/SEAD.lua ]]----------------- + + + +-----------------[[ Missions/DEAD.lua ]]----------------- + +DEAD = Mission:new() +do + function DEAD.canCreate() + for _,zone in pairs(ZoneCommand.getAllZones()) do + if zone.side == 1 and zone.distToFront and zone.distToFront <=1 and zone:hasUnitWithAttributeOnSide({"Air Defence"}, 1, 4) then + if not MissionTargetRegistry.isZoneTargeted(zone.name) then + return true + end + end + end + end + + function DEAD:getMissionName() + return 'DEAD' + end + + function DEAD:generateObjectives() + self.completionType = Mission.completion_type.all + local description = '' + local viableZones = {} + for _,zone in pairs(ZoneCommand.getAllZones()) do + if zone.side == 1 and zone.distToFront <=1 and zone:hasUnitWithAttributeOnSide({"Air Defence"}, 1, 4) then + if not MissionTargetRegistry.isZoneTargeted(zone.name) then + table.insert(viableZones, zone) + end + end + end + + if #viableZones > 0 then + local choice = math.random(1,#viableZones) + local zn = viableZones[choice] + + local kill = ObjDestroyUnitsWithAttributeAtZone:new() + kill:initialize(self, { + attr = {"Air Defence"}, + amount = 1, + killed = 0, + tgtzone = zn + }) + table.insert(self.objectives, kill) + + local clear = ObjClearZoneOfUnitsWithAttribute:new() + clear:initialize(self, { + attr = {"Air Defence"}, + tgtzone = zn + }) + table.insert(self.objectives, clear) + + description = description..' Clear '..zn.name..' of any Air Defenses' + self.info = { + targetzone = zn + } + end + self.description = self.description..description + end +end + +-----------------[[ END OF Missions/DEAD.lua ]]----------------- + + + +-----------------[[ Missions/Supply_Easy.lua ]]----------------- + +Supply_Easy = Mission:new() +do + function Supply_Easy.canCreate() + for _,zone in pairs(ZoneCommand.getAllZones()) do + if zone.side == 2 and zone.distToFront and zone.distToFront <=1 and zone:criticalOnSupplies() then + return true + end + end + end + + function Supply_Easy:getMissionName() + return "Supply delivery" + end + + function Supply_Easy:isInstantReward() + return true + end + + function Supply_Easy:isUnitTypeAllowed(unit) + if PlayerLogistics then + local unitType = unit:getDesc()['typeName'] + return PlayerLogistics.allowedTypes[unitType] and PlayerLogistics.allowedTypes[unitType].supplies + end + end + + function Supply_Easy:generateObjectives() + self.completionType = Mission.completion_type.all + local description = '' + + local viableZones = {} + for _,zone in pairs(ZoneCommand.getAllZones()) do + if zone.side == 2 and zone.distToFront <=1 and zone:criticalOnSupplies() then + table.insert(viableZones, zone) + end + end + + if #viableZones > 0 then + local choice = math.random(1,#viableZones) + local zn = viableZones[choice] + + local deliver = ObjSupplyZone:new() + deliver:initialize(self, { + amount = math.random(2,6)*250, + delivered = 0, + tgtzone = zn + }) + + table.insert(self.objectives, deliver) + description = description..' Deliver '..deliver.param.amount..' of supplies to '..zn.name + self.info = { + targetzone = zn + } + end + + self.description = self.description..description + end +end + +-----------------[[ END OF Missions/Supply_Easy.lua ]]----------------- + + + +-----------------[[ Missions/Supply_Hard.lua ]]----------------- + +Supply_Hard = Mission:new() +do + function Supply_Hard.canCreate() + for _,zone in pairs(ZoneCommand.getAllZones()) do + if zone.side == 2 and zone.distToFront and zone.distToFront <=1 and zone:criticalOnSupplies() then + return true + end + end + end + + function Supply_Hard:getMissionName() + return "Supply delivery" + end + + function Supply_Hard:isInstantReward() + return true + end + + function Supply_Hard:isUnitTypeAllowed(unit) + if PlayerLogistics then + local unitType = unit:getDesc()['typeName'] + return PlayerLogistics.allowedTypes[unitType] and PlayerLogistics.allowedTypes[unitType].supplies + end + end + + function Supply_Hard:generateObjectives() + self.completionType = Mission.completion_type.all + local description = '' + + local viableZones = {} + for _,zone in pairs(ZoneCommand.getAllZones()) do + if zone.side == 2 and zone.distToFront == 0 and zone:criticalOnSupplies() then + table.insert(viableZones, zone) + end + end + + if #viableZones == 0 then + for _,zone in pairs(ZoneCommand.getAllZones()) do + if zone.side == 2 and zone.distToFront == 1 and zone:criticalOnSupplies() then + table.insert(viableZones, zone) + end + end + end + + if #viableZones > 0 then + local choice = math.random(1,#viableZones) + local zn = viableZones[choice] + + local deliver = ObjSupplyZone:new() + deliver:initialize(self, { + amount = math.random(18,24)*250, + delivered = 0, + tgtzone = zn + }) + + table.insert(self.objectives, deliver) + description = description..' Deliver '..deliver.param.amount..' of supplies to '..zn.name + self.info = { + targetzone = zn + } + end + + self.description = self.description..description + end +end + +-----------------[[ END OF Missions/Supply_Hard.lua ]]----------------- + + + +-----------------[[ Missions/Strike_VeryEasy.lua ]]----------------- + +Strike_VeryEasy = Mission:new() +do + function Strike_VeryEasy.canCreate() + return true + end + + function Strike_VeryEasy:getMissionName() + return 'Strike' + end + + function Strike_VeryEasy:generateObjectives() + self.completionType = Mission.completion_type.all + local description = '' + + local kills = ObjDestroyUnitsWithAttribute:new() + kills:initialize(self, { + attr = {'Buildings'}, + amount = 1, + killed = 0 + }) + + table.insert(self.objectives, kills) + description = description..' Destroy '..kills.param.amount..' building' + self.description = self.description..description + end +end + +-----------------[[ END OF Missions/Strike_VeryEasy.lua ]]----------------- + + + +-----------------[[ Missions/Strike_Easy.lua ]]----------------- + +Strike_Easy = Mission:new() +do + function Strike_Easy.canCreate() + for _,zone in pairs(ZoneCommand.getAllZones()) do + if zone.side == 1 and zone.distToFront and zone.distToFront <=1 and zone:hasUnitWithAttributeOnSide({'Buildings'}, 1) then + if not MissionTargetRegistry.isZoneTargeted(zone.name) then + return true + end + end + end + end + + function Strike_Easy:getMissionName() + return 'Strike' + end + + function Strike_Easy:generateObjectives() + self.completionType = Mission.completion_type.all + local description = '' + local viableZones = {} + for _,zone in pairs(ZoneCommand.getAllZones()) do + if zone.side == 1 and zone.distToFront == 0 and zone:hasUnitWithAttributeOnSide({'Buildings'}, 1) then + if not MissionTargetRegistry.isZoneTargeted(zone.name) then + table.insert(viableZones, zone) + end + end + end + + if #viableZones == 0 then + for _,zone in pairs(ZoneCommand.getAllZones()) do + if zone.side == 1 and zone.distToFront == 1 and zone:hasUnitWithAttributeOnSide({'Buildings'}, 1) then + if not MissionTargetRegistry.isZoneTargeted(zone.name) then + table.insert(viableZones, zone) + end + end + end + end + + if #viableZones > 0 then + local choice = math.random(1,#viableZones) + local zn = viableZones[choice] + + local kill = ObjDestroyUnitsWithAttributeAtZone:new() + kill:initialize(self, { + attr = {'Buildings'}, + amount = 1, + killed = 0, + tgtzone = zn + }) + + table.insert(self.objectives, kill) + description = description..' Destroy '..kill.param.amount..' Building at '..zn.name + self.info = { + targetzone = zn + } + end + self.description = self.description..description + end +end + +-----------------[[ END OF Missions/Strike_Easy.lua ]]----------------- + + + +-----------------[[ Missions/Strike_Medium.lua ]]----------------- + +Strike_Medium = Mission:new() +do + function Strike_Medium.canCreate() + return MissionTargetRegistry.strikeTargetsAvailable(1, false) + end + + function Strike_Medium:getMissionName() + return 'Strike' + end + + function Strike_Medium:generateObjectives() + self.completionType = Mission.completion_type.all + local description = '' + local viableZones = {} + + local tgt = MissionTargetRegistry.getRandomStrikeTarget(1, false) + + if tgt then + local chozenTarget = tgt.data + local zn = tgt.zone + + local kill = ObjDestroyStructure:new() + kill:initialize(self, { + target = chozenTarget, + tgtzone = zn, + killed = false + }) + + table.insert(self.objectives, kill) + description = description..' Destroy '..chozenTarget.display..' at '..zn.name + self.info = { + targetzone = zn + } + + MissionTargetRegistry.removeStrikeTarget(tgt) + end + self.description = self.description..description + end +end + +-----------------[[ END OF Missions/Strike_Medium.lua ]]----------------- + + + +-----------------[[ Missions/Strike_Hard.lua ]]----------------- + +Strike_Hard = Mission:new() +do + function Strike_Hard.canCreate() + for _,zone in pairs(ZoneCommand.getAllZones()) do + if zone.side == 1 and zone.distToFront and zone.distToFront <=1 and zone:hasUnitWithAttributeOnSide({"Buildings"}, 1, 3) then + if not MissionTargetRegistry.isZoneTargeted(zone.name) then + return true + end + end + end + end + + function Strike_Hard:getMissionName() + return 'Strike' + end + + function Strike_Hard:generateObjectives() + self.completionType = Mission.completion_type.all + local description = '' + local viableZones = {} + for _,zone in pairs(ZoneCommand.getAllZones()) do + if zone.side == 1 and zone.distToFront == 0 and zone:hasUnitWithAttributeOnSide({"Buildings"}, 1, 3) then + if not MissionTargetRegistry.isZoneTargeted(zone.name) then + table.insert(viableZones, zone) + end + end + end + + if #viableZones == 0 then + for _,zone in pairs(ZoneCommand.getAllZones()) do + if zone.side == 1 and zone.distToFront == 1 and zone:hasUnitWithAttributeOnSide({"Buildings"}, 1, 3) then + if not MissionTargetRegistry.isZoneTargeted(zone.name) then + table.insert(viableZones, zone) + end + end + end + end + + if #viableZones > 0 then + local choice = math.random(1,#viableZones) + local zn = viableZones[choice] + + local kill = ObjDestroyUnitsWithAttributeAtZone:new() + kill:initialize(self, { + attr = {"Buildings"}, + amount = 1, + killed = 0, + tgtzone = zn + }) + + table.insert(self.objectives, kill) + + local clear = ObjClearZoneOfUnitsWithAttribute:new() + clear:initialize(self, { + attr = {"Buildings"}, + tgtzone = zn + }) + table.insert(self.objectives, clear) + + description = description..' Destroy every structure at '..zn.name + self.info = { + targetzone = zn + } + end + self.description = self.description..description + end +end + +-----------------[[ END OF Missions/Strike_Hard.lua ]]----------------- + + + +-----------------[[ Missions/Deep_Strike.lua ]]----------------- + +Deep_Strike = Mission:new() +do + function Deep_Strike.canCreate() + return MissionTargetRegistry.strikeTargetsAvailable(1, true) + end + + function Deep_Strike:getMissionName() + return 'Deep Strike' + end + + function Deep_Strike:generateObjectives() + self.completionType = Mission.completion_type.all + local description = '' + local viableZones = {} + + local tgt = MissionTargetRegistry.getRandomStrikeTarget(1, true) + + if tgt then + local chozenTarget = tgt.data + local zn = tgt.zone + + local kill = ObjDestroyStructure:new() + kill:initialize(self, { + target = chozenTarget, + tgtzone = zn, + killed = false + }) + + table.insert(self.objectives, kill) + description = description..' Destroy '..chozenTarget.display..' at '..zn.name + self.info = { + targetzone = zn + } + + MissionTargetRegistry.removeStrikeTarget(tgt) + end + self.description = self.description..description + end +end + +-----------------[[ END OF Missions/Deep_Strike.lua ]]----------------- + + + +-----------------[[ Missions/Escort.lua ]]----------------- + +Escort = Mission:new() +do + function Escort.canCreate() + local currentTime = timer.getAbsTime() + for _,gr in pairs(ZoneCommand.groupMonitor.groups) do + if gr.product.side == 2 and gr.product.type == 'mission' and (gr.product.missionType == 'supply_convoy' or gr.product.missionType == 'assault') then + local z = gr.target + if z.distToFront == 0 and z.side~= 2 then + if gr.state == nil or gr.state == 'started' or (gr.state == 'enroute' and (currentTime - gr.lastStateTime < 7*60)) then + return true + end + end + end + end + end + + function Escort:getMissionName() + return "Escort convoy" + end + + function Escort:isUnitTypeAllowed(unit) + return unit:hasAttribute('Helicopters') + end + + function Escort:generateObjectives() + self.completionType = Mission.completion_type.all + local description = '' + + local currentTime = timer.getAbsTime() + local viableConvoys = {} + for _,gr in pairs(ZoneCommand.groupMonitor.groups) do + if gr.product.side == 2 and gr.product.type == 'mission' and (gr.product.missionType == 'supply_convoy' or gr.product.missionType == 'assault') then + local z = gr.target + if z.distToFront == 0 and z.side ~= 2 then + if gr.state == nil or gr.state == 'started' or (gr.state == 'enroute' and (currentTime - gr.lastStateTime < 7*60)) then + table.insert(viableConvoys, gr) + end + end + end + end + + if #viableConvoys > 0 then + local choice = math.random(1,#viableConvoys) + local convoy = viableConvoys[choice] + + local escort = ObjEscortGroup:new() + escort:initialize(self, { + maxAmount = 60*7, + amount = 60*7, + proxDist = 400, + target = convoy, + lastUpdate = timer.getAbsTime() + }) + + table.insert(self.objectives, escort) + + local nearzone = "" + local gr = Group.getByName(convoy.name) + if gr and gr:getSize()>0 then + local un = gr:getUnit(1) + local closest = ZoneCommand.getClosestZoneToPoint(un:getPoint()) + if closest then + nearzone = ' near '..closest.name..'' + end + end + + description = description..' Escort convoy'..nearzone..' on route to their destination' + --description = description..'\n Target will be assigned after accepting mission' + + end + + self.description = self.description..description + end +end + +-----------------[[ END OF Missions/Escort.lua ]]----------------- + + + +-----------------[[ Missions/TARCAP.lua ]]----------------- + +TARCAP = Mission:new() +do + TARCAP.relevantMissions = { + Mission.types.cas_hard, + Mission.types.dead, + Mission.types.sead, + Mission.types.strike_easy, + Mission.types.strike_hard + } + + function TARCAP:new(id, type, activeMissions) + self = Mission.new(self, id, type) + self:generateObjectivesOverload(activeMissions) + return self + end + + function TARCAP.canCreate(activeMissions) + for _,mis in pairs(activeMissions) do + for _,tp in ipairs(TARCAP.relevantMissions) do + if mis.type == tp then return true end + end + end + end + + function TARCAP:getMissionName() + return 'TARCAP' + end + + function TARCAP:isUnitTypeAllowed(unit) + return unit:hasAttribute('Planes') + end + + function TARCAP:generateObjectivesOverload(activeMissions) + self.completionType = Mission.completion_type.any + local description = '' + local viableMissions = {} + for _,mis in pairs(activeMissions) do + for _,tp in ipairs(TARCAP.relevantMissions) do + if mis.type == tp then + table.insert(viableMissions, mis) + break + end + end + end + + if #viableMissions >= 1 then + local choice = math.random(1,#viableMissions) + local mis = viableMissions[choice] + + local protect = ObjProtectMission:new() + protect:initialize(self, { + mis = mis + }) + + table.insert(self.objectives, protect) + description = description..' Prevent enemy aircraft from interfering with friendly '..mis:getMissionName()..' mission' + if mis.info and mis.info.targetzone then + description = description..' at '..mis.info.targetzone.name + end + + local rewardDef = RewardDefinitions.missions[self.type] + + local kills = ObjAirKillBonus:new() + kills:initialize(self, { + attr = {'Planes'}, + bonus = { + [PlayerTracker.statTypes.xp] = rewardDef.xp.boost + }, + count = 0, + linkedObjectives = {protect} + }) + + table.insert(self.objectives, kills) + + description = description..'\n Aircraft kills increase reward' + end + + self.description = self.description..description + end +end + +-----------------[[ END OF Missions/TARCAP.lua ]]----------------- + + + +-----------------[[ Missions/Recon_Plane.lua ]]----------------- + +Recon_Plane = Mission:new() +do + function Recon_Plane.canCreate() + local zoneNum = 0 + for _,zone in pairs(ZoneCommand.getAllZones()) do + if zone.side == 1 and zone.distToFront == 0 then + return true + end + end + end + + function Recon_Plane:getMissionName() + return 'Recon' + end + + function Recon_Plane:isUnitTypeAllowed(unit) + return unit:hasAttribute('Planes') + end + + function Recon_Plane:generateObjectives() + self.completionType = Mission.completion_type.any + local description = '' + local viableZones = {} + local secondaryViableZones = {} + for _,zone in pairs(ZoneCommand.getAllZones()) do + if zone.side == 1 and zone.distToFront == 0 then + if zone.revealTime <= 60*5 then + table.insert(viableZones, zone) + else + table.insert(secondaryViableZones, zone) + end + end + end + + if #viableZones == 0 then + viableZones = secondaryViableZones + end + + + if #viableZones > 0 then + local choice1 = math.random(1,#viableZones) + local zn1 = viableZones[choice1] + + local recon = ObjFlyToZoneSequence:new() + recon:initialize(self,{ + waypoints = { + [1] = {zone = zn1, complete = false} + }, + failZones = { + [1] = {zn1} + } + }) + + table.insert(self.objectives, recon) + description = description..' Overfly '..zn1.name..'\n' + end + + self.description = self.description..description + end + + function Recon_Plane:objectiveCompletedCallback(objective) + if objective.type == ObjFlyToZoneSequence:getType() then + local firstWP = objective.param.waypoints[1] + + if firstWP and firstWP.zone:hasUnitWithAttributeOnSide({'Buildings'}, 1) then + local tgt = firstWP.zone:getRandomUnitWithAttributeOnSide({'Buildings'}, 1) + if tgt then + MissionTargetRegistry.addStrikeTarget(tgt, firstWP.zone, true) + self:pushMessageToPlayers(tgt.display..' discovered at '..firstWP.zone.name) + firstWP.zone:reveal() + end + end + end + end +end + +-----------------[[ END OF Missions/Recon_Plane.lua ]]----------------- + + + +-----------------[[ Missions/Deep_Recon_Plane.lua ]]----------------- + +Deep_Recon_Plane = Mission:new() +do + function Deep_Recon_Plane.canCreate() + local zoneNum = 0 + for _,zone in pairs(ZoneCommand.getAllZones()) do + if zone.side == 1 and zone.distToFront == 2 then + return true + end + end + end + + function Deep_Recon_Plane:getMissionName() + return 'Deep Recon' + end + + function Deep_Recon_Plane:isUnitTypeAllowed(unit) + return unit:hasAttribute('Planes') + end + + function Deep_Recon_Plane:generateObjectives() + self.completionType = Mission.completion_type.any + local description = '' + local viableZones = {} + local secondaryViableZones = {} + for _,zone in pairs(ZoneCommand.getAllZones()) do + if zone.side == 1 and zone.distToFront == 2 then + if zone.revealTime <= 60*5 then + table.insert(viableZones, zone) + else + table.insert(secondaryViableZones, zone) + end + end + end + + if #viableZones == 0 then + viableZones = secondaryViableZones + end + + if #viableZones > 0 then + local choice1 = math.random(1,#viableZones) + local zn1 = viableZones[choice1] + + local recon = ObjFlyToZoneSequence:new() + recon:initialize(self, { + waypoints = { + [1] = {zone = zn1, complete = false} + }, + failZones = { + [1] = {zn1} + } + }) + + table.insert(self.objectives, recon) + description = description..' Overfly '..zn1.name..'\n' + end + + self.description = self.description..description + end + + function Deep_Recon_Plane:objectiveCompletedCallback(objective) + if objective.type == ObjFlyToZoneSequence:getType() then + local firstWP = objective.param.waypoints[1] + + if firstWP and firstWP.zone:hasUnitWithAttributeOnSide({'Buildings'}, 1) then + local tgt = firstWP.zone:getRandomUnitWithAttributeOnSide({'Buildings'}, 1) + if tgt then + MissionTargetRegistry.addStrikeTarget(tgt, firstWP.zone, true) + self:pushMessageToPlayers(tgt.display..' discovered at '..firstWP.zone.name) + firstWP.zone:reveal() + end + end + end + end +end + +-----------------[[ END OF Missions/Deep_Recon_Plane.lua ]]----------------- + + + +-----------------[[ Missions/Scout_Helo.lua ]]----------------- + +Scout_Helo = Mission:new() +do + function Scout_Helo.canCreate() + local zoneNum = 0 + for _,zone in pairs(ZoneCommand.getAllZones()) do + if zone.side == 1 and zone.distToFront == 0 then + return true + end + end + end + + function Scout_Helo:getMissionName() + return 'Recon' + end + + function Scout_Helo:isUnitTypeAllowed(unit) + return unit:hasAttribute('Helicopters') + end + + function Scout_Helo:generateObjectives() + self.completionType = Mission.completion_type.any + local description = '' + local viableZones = {} + local secondaryViableZones = {} + for _,zone in pairs(ZoneCommand.getAllZones()) do + if zone.side == 1 and zone.distToFront == 0 then + if zone.revealTime <= 60*5 then + table.insert(viableZones, zone) + else + table.insert(secondaryViableZones, zone) + end + end + end + + if #viableZones == 0 then + viableZones = secondaryViableZones + end + + if #viableZones > 0 then + local choice1 = math.random(1,#viableZones) + local zn1 = viableZones[choice1] + + local recon = ObjReconZone:new() + recon:initialize(self, { + target = zn1, + maxAmount = 60*1.5, + amount = 60*1.5, + allowedDeviation = 20, + proxDist = 10000, + lastUpdate = timer.getAbsTime(), + failZones = { + [1] = {zn1} + } + }) + + table.insert(self.objectives, recon) + description = description..' Observe enemies at '..zn1.name..'\n' + end + + self.description = self.description..description + end + + function Scout_Helo:objectiveCompletedCallback(objective) + if objective.type == ObjReconZone:getType() then + if objective.param.target:hasUnitWithAttributeOnSide({'Buildings'}, 1) then + local tgt = objective.param.target:getRandomUnitWithAttributeOnSide({'Buildings'}, 1) + if tgt then + MissionTargetRegistry.addStrikeTarget(tgt, objective.param.target, false) + self:pushMessageToPlayers(tgt.display..' discovered at '..objective.param.target.name) + objective.param.target:reveal() + end + end + end + end +end + +-----------------[[ END OF Missions/Scout_Helo.lua ]]----------------- + + + +-----------------[[ Missions/BAI.lua ]]----------------- + +BAI = Mission:new() +do + function BAI.canCreate() + return MissionTargetRegistry.baiTargetsAvailable(1) + end + + function BAI:getMissionName() + return 'BAI' + end + + function BAI:generateObjectives() + self.completionType = Mission.completion_type.all + local description = '' + local viableZones = {} + + local tgt = MissionTargetRegistry.getRandomBaiTarget(1) + + if tgt then + + local gr = Group.getByName(tgt.name) + if gr and gr:getSize()>0 then + local units = {} + for i,v in ipairs(gr:getUnits()) do + units[v:getName()] = false + end + + local kill = ObjDestroyGroup:new() + kill:initialize(self, { + target = tgt, + targetUnitNames = units, + lastUpdate = timer.getAbsTime() + }) + + table.insert(self.objectives, kill) + + local nearzone = "" + local un = gr:getUnit(1) + local closest = ZoneCommand.getClosestZoneToPoint(un:getPoint()) + if closest then + nearzone = ' near '..closest.name..'' + end + + description = description..' Destroy '..tgt.product.display..nearzone..' before it reaches its destination.' + end + + MissionTargetRegistry.removeBaiTarget(tgt) + end + self.description = self.description..description + end +end + +-----------------[[ END OF Missions/BAI.lua ]]----------------- + + + +-----------------[[ Missions/Anti_Runway.lua ]]----------------- + +Anti_Runway = Mission:new() +do + function Anti_Runway.canCreate() + for _,zone in pairs(ZoneCommand.getAllZones()) do + if zone.side == 1 and zone.distToFront <=2 and zone:hasRunway() then + return true + end + end + end + + function Anti_Runway:getMissionName() + return 'Runway Attack' + end + + function Anti_Runway:generateObjectives() + self.completionType = Mission.completion_type.all + local description = '' + local viableZones = {} + + local tgts = {} + for _,zone in pairs(ZoneCommand.getAllZones()) do + if zone.side == 1 and zone.distToFront <=2 and zone:hasRunway() then + if not MissionTargetRegistry.isZoneTargeted(zone.name) then + table.insert(tgts, zone) + end + end + end + + if #tgts > 0 then + local tgt = tgts[math.random(1,#tgts)] + + local rewardDef = RewardDefinitions.missions[self.type] + + local bomb = ObjBombInsideZone:new() + bomb:initialize(self,{ + targetZone = tgt, + max = 20, + required = 5, + dropped = 0, + isFinishStarted = false, + bonus = { + [PlayerTracker.statTypes.xp] = rewardDef.xp.boost + } + }) + + table.insert(self.objectives, bomb) + description = description..' Bomb runway at '..bomb.param.targetZone.name + end + self.description = self.description..description + end +end + +-----------------[[ END OF Missions/Anti_Runway.lua ]]----------------- + + + +-----------------[[ Missions/CSAR.lua ]]----------------- + +CSAR = Mission:new() +do + function CSAR.canCreate() + return MissionTargetRegistry.pilotsAvailableToExtract() + end + + function CSAR:getMissionName() + return 'CSAR' + end + + function CSAR:isInstantReward() + return true + end + + function CSAR:isUnitTypeAllowed(unit) + if PlayerLogistics then + local unitType = unit:getDesc()['typeName'] + return PlayerLogistics.allowedTypes[unitType] and PlayerLogistics.allowedTypes[unitType].personCapacity and PlayerLogistics.allowedTypes[unitType].personCapacity > 0 + end + end + + function CSAR:generateObjectives() + self.completionType = Mission.completion_type.all + local description = '' + + if MissionTargetRegistry.pilotsAvailableToExtract() then + local tgt = MissionTargetRegistry.getRandomPilot() + + local extract = ObjExtractPilot:new() + extract:initialize(self, { + target = tgt, + loadedBy = nil, + lastUpdate = timer.getAbsTime() + }) + table.insert(self.objectives, extract) + + local unload = ObjUnloadExtractedPilotOrSquad:new() + unload:initialize(self, { + extractObjective = extract + }) + table.insert(self.objectives, unload) + + local nearzone = '' + local closest = ZoneCommand.getClosestZoneToPoint(tgt.pilot:getUnit(1):getPoint()) + if closest then + nearzone = ' near '..closest.name..'' + end + + description = description..' Rescue '..tgt.name..nearzone..' and deliver them to a friendly zone' + --local mgrs = coord.LLtoMGRS(coord.LOtoLL(tgt.pilot:getUnit(1):getPoint())) + --local grid = mist.tostringMGRS(mgrs, 2):gsub(' ','') + --description = description..' ['..grid..']' + end + self.description = self.description..description + end +end + +-----------------[[ END OF Missions/CSAR.lua ]]----------------- + + + +-----------------[[ Missions/Extraction.lua ]]----------------- + +Extraction = Mission:new() +do + function Extraction.canCreate() + return MissionTargetRegistry.squadsReadyToExtract() + end + + function Extraction:getMissionName() + return 'Extraction' + end + + function Extraction:isInstantReward() + return true + end + + function Extraction:isUnitTypeAllowed(unit) + if PlayerLogistics then + local unitType = unit:getDesc()['typeName'] + return PlayerLogistics.allowedTypes[unitType] and PlayerLogistics.allowedTypes[unitType].personCapacity + end + end + + function Extraction:generateObjectives() + self.completionType = Mission.completion_type.all + local description = '' + + if MissionTargetRegistry.squadsReadyToExtract() then + local tgt = MissionTargetRegistry.getRandomSquad() + if tgt then + local extract = ObjExtractSquad:new() + extract:initialize(self, { + target = tgt, + loadedBy = nil, + lastUpdate = timer.getAbsTime() + }) + table.insert(self.objectives, extract) + + local unload = ObjUnloadExtractedPilotOrSquad:new() + unload:initialize(self, { + extractObjective = extract + }) + table.insert(self.objectives, unload) + + local infName = PlayerLogistics.getInfantryName(tgt.data.type) + + + local nearzone = '' + local gr = Group.getByName(tgt.name) + if gr and gr:isExist() and gr:getSize()>0 then + local un = gr:getUnit(1) + local closest = ZoneCommand.getClosestZoneToPoint(un:getPoint()) + if closest then + nearzone = ' near '..closest.name..'' + end + --local mgrs = coord.LLtoMGRS(coord.LOtoLL(un:getPoint())) + --local grid = mist.tostringMGRS(mgrs, 2):gsub(' ','') + --description = description..' ['..grid..']' + end + + description = description..' Extract '..infName..nearzone..' to a friendly zone' + end + end + self.description = self.description..description + end +end + +-----------------[[ END OF Missions/Extraction.lua ]]----------------- + + + +-----------------[[ Missions/DeploySquad.lua ]]----------------- + +DeploySquad = Mission:new() +do + function DeploySquad.canCreate() + for _,zone in pairs(ZoneCommand.getAllZones()) do + if zone.distToFront and zone.distToFront == 0 then + if not MissionTargetRegistry.isZoneTargeted(zone.name) then + return true + end + end + end + end + + function DeploySquad:getMissionName() + return 'Deploy infantry' + end + + function DeploySquad:isInstantReward() + local friendlyDeployments = { + [PlayerLogistics.infantryTypes.engineer] = true, + } + + if self.objectives and self.objectives[1] then + local sqType = self.objectives[1].param.squadType + if friendlyDeployments[sqType] then + return true + end + end + + return false + end + + function DeploySquad:isUnitTypeAllowed(unit) + if PlayerLogistics then + local unitType = unit:getDesc()['typeName'] + return PlayerLogistics.allowedTypes[unitType] and PlayerLogistics.allowedTypes[unitType].personCapacity + end + end + + function DeploySquad:generateObjectives() + self.completionType = Mission.completion_type.all + local description = '' + + local viableZones = {} + for _,zone in pairs(ZoneCommand.getAllZones()) do + if zone.distToFront and zone.distToFront == 0 then + if not MissionTargetRegistry.isZoneTargeted(zone.name) then + table.insert(viableZones, zone) + end + end + end + + if #viableZones > 0 then + local tgt = viableZones[math.random(1,#viableZones)] + if tgt then + local squadType = nil + + if tgt.side == 0 then + squadType = PlayerLogistics.infantryTypes.capture + elseif tgt.side == 1 then + if math.random()>0.5 then + squadType = PlayerLogistics.infantryTypes.sabotage + else + squadType = PlayerLogistics.infantryTypes.spy + end + elseif tgt.side == 2 then + squadType = PlayerLogistics.infantryTypes.engineer + end + + local deploy = ObjDeploySquad:new() + deploy:initialize(self, { + squadType = squadType, + targetZone = tgt, + requiredZoneSide = tgt.side, + unloadedType = nil, + unloadedAt = nil + }) + table.insert(self.objectives, deploy) + + local infName = PlayerLogistics.getInfantryName(squadType) + + description = description..' Deploy '..infName..' to '..tgt.name + + self.info = { + targetzone = tgt + } + end + end + self.description = self.description..description + end +end + +-----------------[[ END OF Missions/DeploySquad.lua ]]----------------- + + + +-----------------[[ RewardDefinitions.lua ]]----------------- + +RewardDefinitions = {} + +do + RewardDefinitions.missions = { + [Mission.types.cap_easy] = { xp = { low = 10, high = 20, boost = 0 } }, + [Mission.types.cap_medium] = { xp = { low = 10, high = 20, boost = 100 } }, + [Mission.types.tarcap] = { xp = { low = 10, high = 10, boost = 150 } }, + [Mission.types.cas_easy] = { xp = { low = 10, high = 20, boost = 0 } }, + [Mission.types.cas_medium] = { xp = { low = 20, high = 30, boost = 0 } }, + [Mission.types.cas_hard] = { xp = { low = 30, high = 40, boost = 0 } }, + [Mission.types.bai] = { xp = { low = 20, high = 30, boost = 0 } }, + [Mission.types.sead] = { xp = { low = 10, high = 20, boost = 0 } }, + [Mission.types.dead] = { xp = { low = 30, high = 40, boost = 0 } }, + [Mission.types.strike_veryeasy] = { xp = { low = 5, high = 10, boost = 0 } }, + [Mission.types.strike_easy] = { xp = { low = 10, high = 20, boost = 0 } }, + [Mission.types.strike_medium] = { xp = { low = 20, high = 30, boost = 0 } }, + [Mission.types.strike_hard] = { xp = { low = 30, high = 40, boost = 0 } }, + [Mission.types.deep_strike] = { xp = { low = 30, high = 40, boost = 0 } }, + [Mission.types.recon_plane] = { xp = { low = 20, high = 30, boost = 0 } }, + [Mission.types.recon_plane_deep]= { xp = { low = 30, high = 40, boost = 0 } }, + [Mission.types.anti_runway] = { xp = { low = 20, high = 30, boost = 25 } }, + [Mission.types.supply_easy] = { xp = { low = 10, high = 20, boost = 0 } }, + [Mission.types.supply_hard] = { xp = { low = 20, high = 30, boost = 0 } }, + [Mission.types.escort] = { xp = { low = 20, high = 30, boost = 0 } }, + [Mission.types.scout_helo] = { xp = { low = 20, high = 30, boost = 0 } }, + [Mission.types.csar] = { xp = { low = 20, high = 30, boost = 0 } }, + [Mission.types.extraction] = { xp = { low = 20, high = 30, boost = 0 } }, + [Mission.types.deploy_squad] = { xp = { low = 20, high = 30, boost = 0 } } + } + + RewardDefinitions.actions = { + pilotExtract = 100, + squadDeploy = 150, + squadExtract = 150, + supplyRatio = 0.06, + supplyBoost = 0.5 + } +end + +-----------------[[ END OF RewardDefinitions.lua ]]----------------- + + + +-----------------[[ MissionTracker.lua ]]----------------- + +MissionTracker = {} +do + MissionTracker.maxMissionCount = { + [Mission.types.cap_easy] = 1, + [Mission.types.cap_medium] = 1, + [Mission.types.cas_easy] = 1, + [Mission.types.cas_medium] = 1, + [Mission.types.cas_hard] = 1, + [Mission.types.sead] = 1, + [Mission.types.supply_easy] = 1, + [Mission.types.supply_hard] = 1, + [Mission.types.strike_veryeasy] = 1, + [Mission.types.strike_easy] = 1, + [Mission.types.strike_medium] = 3, + [Mission.types.strike_hard] = 1, + [Mission.types.dead] = 1, + [Mission.types.escort] = 1, + [Mission.types.tarcap] = 1, + [Mission.types.recon_plane] = 1, + [Mission.types.recon_plane_deep] = 1, + [Mission.types.deep_strike] = 3, + [Mission.types.scout_helo] = 1, + [Mission.types.bai] = 1, + [Mission.types.anti_runway] = 1, + [Mission.types.csar] = 1, + [Mission.types.extraction] = 1, + [Mission.types.deploy_squad] = 1, + } + + if Config.missions then + for i,v in pairs(Config.missions) do + if MissionTracker.maxMissionCount[i] then + MissionTracker.maxMissionCount[i] = v + end + end + end + + MissionTracker.missionBoardSize = 10 + + function MissionTracker:new(playerTracker, markerCommands) + local obj = {} + obj.playerTracker = playerTracker + obj.markerCommands = markerCommands + obj.groupMenus = {} + obj.missionIDPool = {} + obj.missionBoard = {} + obj.activeMissions = {} + + setmetatable(obj, self) + self.__index = self + + obj.markerCommands:addCommand('list', function(event, _, state) + if event.initiator then + state:printMissionBoard(event.initiator:getID(), nil, event.initiator:getGroup():getName()) + elseif world.getPlayer() then + local unit = world.getPlayer() + state:printMissionBoard(unit:getID(), nil, event.initiator:getGroup():getName()) + end + return true + end, nil, obj) + + obj.markerCommands:addCommand('help', function(event, _, state) + if event.initiator then + state:printHelp(event.initiator:getID()) + elseif world.getPlayer() then + local unit = world.getPlayer() + state:printHelp(unit:getID()) + end + return true + end, nil, obj) + + obj.markerCommands:addCommand('active', function(event, _, state) + if event.initiator then + state:printActiveMission(event.initiator:getID(), nil, event.initiator:getPlayerName()) + elseif world.getPlayer() then + state:printActiveMission(nil, nil, world.getPlayer():getPlayerName()) + end + return true + end, nil, obj) + + obj.markerCommands:addCommand('accept',function(event, code, state) + local numcode = tonumber(code) + if not numcode or numcode<1000 or numcode > 9999 then return false end + + local player = '' + local unit = nil + if event.initiator then + player = event.initiator:getPlayerName() + unit = event.initiator + elseif world.getPlayer() then + player = world.getPlayer():getPlayerName() + unit = world.getPlayer() + end + + return state:activateMission(numcode, player, unit) + end, true, obj) + + obj.markerCommands:addCommand('join',function(event, code, state) + local numcode = tonumber(code) + if not numcode or numcode<1000 or numcode > 9999 then return false end + + local player = '' + local unit = nil + if event.initiator then + player = event.initiator:getPlayerName() + unit = event.initiator + elseif world.getPlayer() then + player = world.getPlayer():getPlayerName() + unit = world.getPlayer() + end + + return state:joinMission(numcode, player, unit) + end, true, obj) + + obj.markerCommands:addCommand('leave',function(event, _, state) + local player = '' + if event.initiator then + player = event.initiator:getPlayerName() + elseif world.getPlayer() then + player = world.getPlayer():getPlayerName() + end + + return state:leaveMission(player) + end, nil, obj) + + obj:menuSetup() + obj:start() + return obj + end + + function MissionTracker:menuSetup() + MenuRegistry:register(2, function(event, context) + if event.id == world.event.S_EVENT_BIRTH and event.initiator and event.initiator.getPlayerName then + local player = event.initiator:getPlayerName() + if player then + local groupid = event.initiator:getGroup():getID() + local groupname = event.initiator:getGroup():getName() + + if context.groupMenus[groupid] then + missionCommands.removeItemForGroup(groupid, context.groupMenus[groupid]) + context.groupMenus[groupid] = nil + end + + if not context.groupMenus[groupid] then + local menu = missionCommands.addSubMenuForGroup(groupid, 'Missions') + missionCommands.addCommandForGroup(groupid, 'List Missions', menu, Utils.log(context.printMissionBoard), context, nil, groupid, groupname) + missionCommands.addCommandForGroup(groupid, 'Active Mission', menu, Utils.log(context.printActiveMission), context, nil, groupid, nil, groupname) + + local dial = missionCommands.addSubMenuForGroup(groupid, 'Dial Code', menu) + for i1=1,5,1 do + local digit1 = missionCommands.addSubMenuForGroup(groupid, i1..'___', dial) + for i2=1,5,1 do + local digit2 = missionCommands.addSubMenuForGroup(groupid, i1..i2..'__', digit1) + for i3=1,5,1 do + local digit3 = missionCommands.addSubMenuForGroup(groupid, i1..i2..i3..'_', digit2) + for i4=1,5,1 do + local code = tonumber(i1..i2..i3..i4) + local digit4 = missionCommands.addCommandForGroup(groupid, i1..i2..i3..i4, digit3, Utils.log(context.activateOrJoinMissionForGroup), context, code, groupname) + end + end + end + end + + local leavemenu = missionCommands.addSubMenuForGroup(groupid, 'Leave Mission', menu) + missionCommands.addCommandForGroup(groupid, 'Confirm to leave mission', leavemenu, Utils.log(context.leaveMission), context, player) + missionCommands.addCommandForGroup(groupid, 'Cancel', leavemenu, function() end) + + missionCommands.addCommandForGroup(groupid, 'Help', menu, Utils.log(context.printHelp), context, nil, groupid) + + context.groupMenus[groupid] = menu + end + end + elseif (event.id == world.event.S_EVENT_PLAYER_LEAVE_UNIT or event.id == world.event.S_EVENT_DEAD) and event.initiator and event.initiator.getPlayerName then + local player = event.initiator:getPlayerName() + if player then + local groupid = event.initiator:getGroup():getID() + + if context.groupMenus[groupid] then + missionCommands.removeItemForGroup(groupid, context.groupMenus[groupid]) + context.groupMenus[groupid] = nil + end + end + end + end, self) + end + + function MissionTracker:printHelp(unitid, groupid) + local msg = 'Missions can only be accepted or joined while landed at a friendly zone.\n' + msg = msg.. 'Rewards from mission completion need to be claimed by landing at a friendly zone.\n\n' + msg = msg.. 'Accept mission:\n' + msg = msg.. ' Each mission has a 4 digit code listed next to its name.\n To accept a mission, either dial its code from the mission radio menu,\n or create a marker on the map and set its text to:\n' + msg = msg.. ' accept:code\n' + msg = msg.. ' (ex. accept:4126)\n\n' + msg = msg.. 'Join mission:\n' + msg = msg.. ' You can team up with other players, by joining a mission they already accepted.\n' + msg = msg.. ' Missions can only be joined if all players who are already part of that mission\n have not taken off yet.\n' + msg = msg.. ' When a mission is completed each player has to land to claim their reward individually.\n' + msg = msg.. ' To join a mission, ask for the join code from a player who is already part of the mission,\n dial it in from the mission radio menu,\n or create a marker on the map and set its text to:\n' + msg = msg.. ' join:code\n' + msg = msg.. ' (ex. join:4126)\n\n' + msg = msg.. 'Map marker commands:\n' + msg = msg.. ' list - displays mission board\n' + msg = msg.. ' accept:code - accepts mission with corresponding code\n' + msg = msg.. ' join:code - joins other players mission with corresponding code\n' + msg = msg.. ' active - displays active mission\n' + msg = msg.. ' leave - leaves active mission\n' + msg = msg.. ' help - displays this message' + + if unitid then + trigger.action.outTextForUnit(unitid, msg, 30) + elseif groupid then + trigger.action.outTextForGroup(groupid, msg, 30) + else + --trigger.action.outText(msg, 30) + end + end + + function MissionTracker:printActiveMission(unitid, groupid, playername, groupname) + if not playername and groupname then + env.info('MissionTracker - printActiveMission: '..tostring(groupname)..' requested group print.') + local gr = Group.getByName(groupname) + for i,v in ipairs(gr:getUnits()) do + if v.getPlayerName and v:getPlayerName() then + self:printActiveMission(v:getID(), gr:getID(), v:getPlayerName()) + end + end + return + end + + local mis = nil + for i,v in pairs(self.activeMissions) do + for pl,un in pairs(v.players) do + if pl == playername then + mis = v + break + end + end + + if mis then break end + end + + local msg = '' + if mis then + msg = mis:getDetailedDescription() + else + msg = 'No active mission' + end + + if unitid then + trigger.action.outTextForUnit(unitid, msg, 30) + elseif groupid then + trigger.action.outTextForGroup(groupid, msg, 30) + else + --trigger.action.outText(msg, 30) + end + end + + function MissionTracker:printMissionBoard(unitid, groupid, groupname) + local gr = Group.getByName(groupname) + local un = gr:getUnit(1) + + local msg = 'Mission Board\n' + local empty = true + local invalidCount = 0 + for i,v in pairs(self.missionBoard) do + if v:isUnitTypeAllowed(un) then + empty = false + msg = msg..'\n'..v:getBriefDescription()..'\n' + else + invalidCount = invalidCount + 1 + end + end + + if empty then + msg = msg..'\n No missions available' + end + + if invalidCount > 0 then + msg = msg..'\n'..invalidCount..' additional missions are not compatible with current aircraft\n' + end + + if unitid then + trigger.action.outTextForUnit(unitid, msg, 30) + elseif groupid then + trigger.action.outTextForGroup(groupid, msg, 30) + else + --trigger.action.outText(msg, 30) + end + end + + function MissionTracker:getNewMissionID() + if #self.missionIDPool == 0 then + for i=1111,5555,1 do + if not tostring(i):find('[06789]') then + if not self.missionBoard[i] and not self.activeMissions[i] then + table.insert(self.missionIDPool, i) + end + end + end + end + + local choice = math.random(1,#self.missionIDPool) + local newId = self.missionIDPool[choice] + table.remove(self.missionIDPool,choice) + return newId + end + + function MissionTracker:start() + timer.scheduleFunction(function(param, time) + for code,mis in pairs(param.missionBoard) do + if timer.getAbsTime() - mis.lastStateTime > mis.expireTime then + param.missionBoard[code].state = Mission.states.failed + param.missionBoard[code] = nil + env.info('Mission code'..code..' expired.') + else + mis:updateIsFailed() + if mis.state == Mission.states.failed then + param.missionBoard[code]=nil + env.info('Mission code'..code..' canceled due to objectives failed') + trigger.action.outTextForCoalition(2,'Mission ['..mis.missionID..'] '..mis.name..' was cancelled',5) + end + end + end + + local misCount = Utils.getTableSize(param.missionBoard) + local toGen = MissionTracker.missionBoardSize-misCount + if toGen > 0 then + local validMissions = {} + for _,v in pairs(Mission.types) do + if self:canCreateMission(v) then + table.insert(validMissions,v) + end + end + + if #validMissions > 0 then + for i=1,toGen,1 do + if #validMissions > 0 then + local choice = math.random(1,#validMissions) + local misType = validMissions[choice] + table.remove(validMissions, choice) + param:generateMission(misType) + else + break + end + end + end + end + + return time+1 + end, self, timer.getTime()+1) + + timer.scheduleFunction(function(param, time) + for code,mis in pairs(param.activeMissions) do + -- check if players exist and in same unit as when joined + -- remove from mission if false + for pl,un in pairs(mis.players) do + if not un or + not un:isExist() then + + mis:removePlayer(pl) + env.info('Mission code'..code..' removing player '..pl..', unit no longer exists') + end + end + + -- check if mission has 0 players, delete mission if true + if not mis:hasPlayers() then + param.activeMissions[code]:updateState(Mission.states.failed) + param.activeMissions[code] = nil + env.info('Mission code'..code..' canceled due to no players') + else + --check if mission objectives can still be completed, cancel mission if not + mis:updateIsFailed() + mis:updateIsCompleted() + + if mis.state == Mission.states.preping then + --check if any player in air and move to comencing if true + for pl,un in pairs(mis.players) do + if Utils.isInAir(un) then + mis:updateState(Mission.states.comencing) + mis:pushMessageToPlayers(mis.name..' mission is starting') + break + end + end + elseif mis.state == Mission.states.comencing then + --check if all players in air and move to active if true + --if all players landed, move to preping + local allInAir = true + local allLanded = true + for pl,un in pairs(mis.players) do + if Utils.isInAir(un) then + allLanded = false + else + allInAir = false + end + end + + if allLanded then + mis:updateState(Mission.states.preping) + mis:pushMessageToPlayers(mis.name..' mission is in the prep phase') + end + + if allInAir then + mis:updateState(Mission.states.active) + mis:pushMessageToPlayers(mis.name..' mission has started') + local missionstatus = mis:getDetailedDescription() + mis:pushMessageToPlayers(missionstatus) + end + elseif mis.state == Mission.states.active then + mis:updateObjectives() + elseif mis.state == Mission.states.completed then + local isInstant = mis:isInstantReward() + if isInstant then + mis:pushMessageToPlayers(mis.name..' mission complete.', 60) + else + mis:pushMessageToPlayers(mis.name..' mission complete. Land to claim rewards.', 60) + end + + for _,reward in ipairs(mis.rewards) do + for p,_ in pairs(mis.players) do + if isInstant then + param.playerTracker:addStat(p, reward.amount, reward.type) + else + param.playerTracker:addTempStat(p, reward.amount, reward.type) + end + end + + if isInstant then + mis:pushMessageToPlayers('+'..reward.amount..' '..reward.type) + end + end + + for p,u in pairs(mis.players) do + param.playerTracker:addRankRewards(p,u, not isInstant) + end + + mis:pushSoundToPlayers("success.ogg") + param.activeMissions[code] = nil + env.info('Mission code'..code..' removed due to completion') + elseif mis.state == Mission.states.failed then + local msg = mis.name..' mission failed.' + if mis.failureReason then + msg = msg..'\n'..mis.failureReason + end + + mis:pushMessageToPlayers(msg, 60) + + mis:pushSoundToPlayers("fail.ogg") + param.activeMissions[code] = nil + env.info('Mission code'..code..' removed due to failure') + end + end + end + + return time+1 + end, self, timer.getTime()+1) + + local ev = {} + ev.context = self + function ev:onEvent(event) + if event.id == world.event.S_EVENT_KILL and event.initiator and event.target and event.initiator.getPlayerName then + local player = event.initiator:getPlayerName() + if player and + event.initiator:isExist() and + event.initiator.getCoalition and + event.target.getCoalition and + event.initiator:getCoalition() ~= event.target:getCoalition() then + self.context:tallyKill(player, event.target) + end + end + + if event.id == world.event.S_EVENT_SHOT and event.initiator and event.weapon and event.initiator.getPlayerName then + local player = event.initiator:getPlayerName() + if player and event.initiator:isExist() and event.weapon:isExist() then + self.context:tallyWeapon(player, event.weapon) + end + end + end + + world.addEventHandler(ev) + end + + function MissionTracker:generateMission(misType) + local misid = self:getNewMissionID() + env.info('MissionTracker - generating mission type ['..misType..'] id code['..misid..']') + + local newmis = nil + if misType == Mission.types.cap_easy then + newmis = CAP_Easy:new(misid, misType) + elseif misType == Mission.types.cap_medium then + newmis = CAP_Medium:new(misid, misType) + elseif misType == Mission.types.cas_easy then + newmis = CAS_Easy:new(misid, misType) + elseif misType == Mission.types.cas_medium then + newmis = CAS_Medium:new(misid, misType) + elseif misType == Mission.types.cas_hard then + newmis = CAS_Hard:new(misid, misType) + elseif misType == Mission.types.sead then + newmis = SEAD:new(misid, misType) + elseif misType == Mission.types.supply_easy then + newmis = Supply_Easy:new(misid, misType) + elseif misType == Mission.types.supply_hard then + newmis = Supply_Hard:new(misid, misType) + elseif misType == Mission.types.strike_veryeasy then + newmis = Strike_VeryEasy:new(misid, misType) + elseif misType == Mission.types.strike_easy then + newmis = Strike_Easy:new(misid, misType) + elseif misType == Mission.types.strike_medium then + newmis = Strike_Medium:new(misid, misType) + elseif misType == Mission.types.strike_hard then + newmis = Strike_Hard:new(misid, misType) + elseif misType == Mission.types.deep_strike then + newmis = Deep_Strike:new(misid, misType) + elseif misType == Mission.types.dead then + newmis = DEAD:new(misid, misType) + elseif misType == Mission.types.escort then + newmis = Escort:new(misid, misType) + elseif misType == Mission.types.tarcap then + newmis = TARCAP:new(misid, misType, self.activeMissions) + elseif misType == Mission.types.recon_plane then + newmis = Recon_Plane:new(misid, misType) + elseif misType == Mission.types.recon_plane_deep then + newmis = Deep_Recon_Plane:new(misid, misType) + elseif misType == Mission.types.scout_helo then + newmis = Scout_Helo:new(misid, misType) + elseif misType == Mission.types.bai then + newmis = BAI:new(misid, misType) + elseif misType == Mission.types.anti_runway then + newmis = Anti_Runway:new(misid, misType) + elseif misType == Mission.types.csar then + newmis = CSAR:new(misid, misType) + elseif misType == Mission.types.extraction then + newmis = Extraction:new(misid, misType) + elseif misType == Mission.types.deploy_squad then + newmis = DeploySquad:new(misid, misType) + end + + if not newmis then return end + + if #newmis.objectives == 0 then return end + + self.missionBoard[misid] = newmis + env.info('MissionTracker - generated mission id code'..misid..' \n'..newmis.description) + trigger.action.outTextForCoalition(2,'New mission available: '..newmis.name,5) + end + + function MissionTracker:tallyWeapon(player, weapon) + for _,m in pairs(self.activeMissions) do + if m.players[player] then + if m.state == Mission.states.active then + if weapon:getDesc().category == Weapon.Category.BOMB then + timer.scheduleFunction(function (params, time) + if not params.weapon:isExist() then + return nil -- weapon despawned + end + + local alt = Utils.getAGL(params.weapon) + if alt < 5 then + params.mission:tallyWeapon(params.weapon) + return nil + end + + if alt < 20 then + return time+0.01 + end + + return time+0.1 + end, {player = player, weapon = weapon, mission = m}, timer.getTime()+0.1) + end + end + end + end + end + + function MissionTracker:tallyKill(player,kill) + env.info("MissionTracker - tallyKill: "..player.." killed "..kill:getName()) + for _,m in pairs(self.activeMissions) do + if m.players[player] then + if m.state == Mission.states.active then + m:tallyKill(kill) + end + end + end + end + + function MissionTracker:tallySupplies(player, amount, zonename) + env.info("MissionTracker - tallySupplies: "..player.." delivered "..amount.." of supplies to "..zonename) + for _,m in pairs(self.activeMissions) do + if m.players[player] then + if m.state == Mission.states.active then + m:tallySupplies(amount, zonename) + end + end + end + end + + function MissionTracker:tallyLoadPilot(player, pilot) + env.info("MissionTracker - tallyLoadPilot: "..player.." loaded pilot "..pilot.name) + for _,m in pairs(self.activeMissions) do + if m.players[player] then + if m.state == Mission.states.active then + m:tallyLoadPilot(player, pilot) + end + end + end + end + + function MissionTracker:tallyUnloadPilot(player, zonename) + env.info("MissionTracker - tallyUnloadPilot: "..player.." unloaded pilots at "..zonename) + for _,m in pairs(self.activeMissions) do + if m.players[player] then + if m.state == Mission.states.active then + m:tallyUnloadPilot(player, zonename) + end + end + end + end + + function MissionTracker:tallyLoadSquad(player, squad) + env.info("MissionTracker - tallyLoadSquad: "..player.." loaded squad "..squad.name) + for _,m in pairs(self.activeMissions) do + if m.players[player] then + if m.state == Mission.states.active then + m:tallyLoadSquad(player, squad) + end + end + end + end + + function MissionTracker:tallyUnloadSquad(player, zonename, squadType) + env.info("MissionTracker - tallyUnloadSquad: "..player.." unloaded "..squadType.." squad at "..zonename) + for _,m in pairs(self.activeMissions) do + if m.players[player] then + if m.state == Mission.states.active then + m:tallyUnloadSquad(player, zonename, squadType) + end + end + end + end + + function MissionTracker:activateOrJoinMissionForGroup(code, groupname) + if groupname then + env.info('MissionTracker - activateOrJoinMissionForGroup: '..tostring(groupname)..' requested activate or join '..code) + local gr = Group.getByName(groupname) + for i,v in ipairs(gr:getUnits()) do + if v.getPlayerName and v:getPlayerName() then + local mis = self.activeMissions[code] + if mis then + self:joinMission(code, v:getPlayerName(), v) + else + self:activateMission(code, v:getPlayerName(), v) + end + return + end + end + end + end + + function MissionTracker:activateMission(code, player, unit) + if not unit or not unit:isExist() or not Utils.isLanded(unit, true) then + if unit and unit:isExist() then trigger.action.outTextForUnit(unit:getID(), 'Can only accept mission while landed', 5) end + return false + end + + local zn = ZoneCommand.getZoneOfUnit(unit:getName()) + if not zn or zn.side ~= unit:getCoalition() then + trigger.action.outTextForUnit(unit:getID(), 'Can only accept mission while inside friendly zone', 5) + return false + end + + for c,m in pairs(self.activeMissions) do + if m:getPlayerUnit(player) then + trigger.action.outTextForUnit(unit:getID(), 'A mission is already active.', 5) + return false + end + end + + local mis = self.missionBoard[code] + if not mis then + trigger.action.outTextForUnit(unit:getID(), 'Invalid mission code', 5) + return false + end + + if mis.state ~= Mission.states.new then + trigger.action.outTextForUnit(unit:getID(), 'Invalid mission.', 5) + return false + end + + if not mis:isUnitTypeAllowed(unit) then + trigger.action.outTextForUnit(unit:getID(), 'Current aircraft type is not compatible with this mission.', 5) + return false + end + + self.missionBoard[code] = nil + + trigger.action.outTextForCoalition(2,'Mission ['..mis.missionID..'] '..mis.name..' was accepted by '..player,5) + mis:updateState(Mission.states.preping) + mis.missionID = self:getNewMissionID() + mis:addPlayer(player, unit) + + mis:pushMessageToPlayers(mis.name..' accepted.\nJoin code: ['..mis.missionID..']') + + env.info('Mission code'..code..' changed to code'..mis.missionID) + env.info('Mission code'..mis.missionID..' accepted by '..player) + self.activeMissions[mis.missionID] = mis + return true + end + + function MissionTracker:joinMission(code, player, unit) + if not unit or not unit:isExist() or not Utils.isLanded(unit, true) then + if unit and unit:isExist() then trigger.action.outTextForUnit(unit:getID(), 'Can only join mission while landed', 5) end + return false + end + + local zn = ZoneCommand.getZoneOfUnit(unit:getName()) + if not zn or zn.side ~= unit:getCoalition() then + trigger.action.outTextForUnit(unit:getID(), 'Can only join mission while inside friendly zone', 5) + return false + end + + for c,m in pairs(self.activeMissions) do + if m:getPlayerUnit(player) then + trigger.action.outTextForUnit(unit:getID(), 'A mission is already active.', 5) + return false + end + end + + local mis = self.activeMissions[code] + if not mis then + trigger.action.outTextForUnit(unit:getID(), 'Invalid mission code', 5) + return false + end + + if mis.state ~= Mission.states.preping then + trigger.action.outTextForUnit(unit:getID(), 'Mission can only be joined if its members have not taken off yet.', 5) + return false + end + + if not mis:isUnitTypeAllowed(unit) then + trigger.action.outTextForUnit(unit:getID(), 'Current aircraft type is not compatible with this mission.', 5) + return false + end + + mis:addPlayer(player, unit) + mis:pushMessageToPlayers(player..' has joined mission '..mis.name) + env.info('Mission code'..code..' joined by '..player) + return true + end + + function MissionTracker:leaveMission(player) + for _,mis in pairs(self.activeMissions) do + if mis:getPlayerUnit(player) then + mis:pushMessageToPlayers(player..' has left mission '..mis.name) + mis:removePlayer(player) + env.info('Mission code'..mis.missionID..' left by '..player) + if not mis:hasPlayers() then + self.activeMissions[mis.missionID]:updateState(Mission.states.failed) + self.activeMissions[mis.missionID] = nil + env.info('Mission code'..mis.missionID..' canceled due to all players leaving') + end + + break + end + end + + return true + end + + function MissionTracker:canCreateMission(misType) + if not MissionTracker.maxMissionCount[misType] then return false end + + local missionCount = 0 + for i,v in pairs(self.missionBoard) do + if v.type == misType then missionCount = missionCount + 1 end + end + + for i,v in pairs(self.activeMissions) do + if v.type == misType then missionCount = missionCount + 1 end + end + + if missionCount >= MissionTracker.maxMissionCount[misType] then return false end + + if misType == Mission.types.cap_easy then + return CAP_Easy.canCreate() + elseif misType == Mission.types.cap_medium then + return CAP_Medium.canCreate() + elseif misType == Mission.types.cas_easy then + return CAS_Easy.canCreate() + elseif misType == Mission.types.cas_medium then + return CAS_Medium.canCreate() + elseif misType == Mission.types.sead then + return SEAD.canCreate() + elseif misType == Mission.types.dead then + return DEAD.canCreate() + elseif misType == Mission.types.cas_hard then + return CAS_Hard.canCreate() + elseif misType == Mission.types.supply_easy then + return Supply_Easy.canCreate() + elseif misType == Mission.types.supply_hard then + return Supply_Hard.canCreate() + elseif misType == Mission.types.strike_veryeasy then + return Strike_VeryEasy.canCreate() + elseif misType == Mission.types.strike_easy then + return Strike_Easy.canCreate() + elseif misType == Mission.types.strike_medium then + return Strike_Medium.canCreate() + elseif misType == Mission.types.strike_hard then + return Strike_Hard.canCreate() + elseif misType == Mission.types.deep_strike then + return Deep_Strike.canCreate() + elseif misType == Mission.types.escort then + return Escort.canCreate() + elseif misType == Mission.types.tarcap then + return TARCAP.canCreate(self.activeMissions) + elseif misType == Mission.types.recon_plane then + return Recon_Plane.canCreate() + elseif misType == Mission.types.recon_plane_deep then + return Deep_Recon_Plane.canCreate() + elseif misType == Mission.types.scout_helo then + return Scout_Helo.canCreate() + elseif misType == Mission.types.bai then + return BAI.canCreate() + elseif misType == Mission.types.anti_runway then + return Anti_Runway.canCreate() + elseif misType == Mission.types.csar then + return CSAR.canCreate() + elseif misType == Mission.types.extraction then + return Extraction.canCreate() + elseif misType == Mission.types.deploy_squad then + return DeploySquad.canCreate() + end + + return false + end + +end + + +-----------------[[ END OF MissionTracker.lua ]]----------------- + + + +-----------------[[ SquadTracker.lua ]]----------------- + +SquadTracker = {} +do + function SquadTracker:new() + local obj = {} + obj.activeInfantrySquads = {} + setmetatable(obj, self) + self.__index = self + + obj:start() + return obj + end + + SquadTracker.infantryCallsigns = { + adjectives = {"Sapphire", "Emerald", "Whisper", "Vortex", "Blaze", "Nova", "Silent", "Zephyr", "Radiant", "Shadow", "Lively", "Dynamo", "Dusk", "Rapid", "Stellar", "Tundra", "Obsidian", "Cascade", "Zenith", "Solar"}, + nouns = {"Journey", "Quasar", "Galaxy", "Moonbeam", "Comet", "Starling", "Serenade", "Raven", "Breeze", "Echo", "Avalanche", "Harmony", "Stardust", "Horizon", "Firefly", "Solstice", "Labyrinth", "Whisper", "Cosmos", "Mystique"} + } + + function SquadTracker:generateCallsign() + local adjective = self.infantryCallsigns.adjectives[math.random(1,#self.infantryCallsigns.adjectives)] + local noun = self.infantryCallsigns.nouns[math.random(1,#self.infantryCallsigns.nouns)] + + local callsign = adjective..noun + + if self.activeInfantrySquads[callsign] then + for i=1,1000,1 do + local try = callsign..'-'..i + if not self.activeInfantrySquads[try] then + callsign = try + break + end + end + end + + if not self.activeInfantrySquads[callsign] then + return callsign + end + end + + function SquadTracker:restoreInfantry(save) + + Spawner.createObject(save.name, save.data.name, save.position, 2, 10, 20,{ + [land.SurfaceType.LAND] = true, + [land.SurfaceType.ROAD] = true, + [land.SurfaceType.RUNWAY] = true, + }) + + self.activeInfantrySquads[save.name] = { + name = save.name, + position = save.position, + state = save.state, + remainingStateTime=save.remainingStateTime, + data = save.data + } + + if save.state == "extractReady" then + MissionTargetRegistry.addSquad(self.activeInfantrySquads[save.name]) + end + + env.info('SquadTracker - '..save.name..'('..save.data.type..') restored') + end + + function SquadTracker:spawnInfantry(infantryData, position) + local callsign = self:generateCallsign() + if callsign then + Spawner.createObject(callsign, infantryData.name, position, 2, 10, 20,{ + [land.SurfaceType.LAND] = true, + [land.SurfaceType.ROAD] = true, + [land.SurfaceType.RUNWAY] = true, + }) + + self:registerInfantry(infantryData, callsign, position) + end + end + + function SquadTracker:registerInfantry(infantryData, groupname, position) + self.activeInfantrySquads[groupname] = {name = groupname, position = position, state = "deployed", remainingStateTime=0, data = infantryData} + + env.info('SquadTracker - '..groupname..'('..infantryData.type..') deployed') + end + + function SquadTracker:start() + if not ZoneCommand then return end + + timer.scheduleFunction(function(param, time) + local self = param.context + + for i,v in pairs(self.activeInfantrySquads) do + local remove = self:processInfantrySquad(v) + if remove then + MissionTargetRegistry.removeSquad(v) + self.activeInfantrySquads[v.name] = nil + end + end + + return time+10 + end, {context = self}, timer.getTime()+1) + end + + function SquadTracker:removeSquad(squadname) + local squad = self.activeInfantrySquads[squadname] + if squad then + MissionTargetRegistry.removeSquad(squad) + squad.state = 'extracted' + squad.remainingStateTime = 0 + self.activeInfantrySquads[squadname] = nil + end + end + + function SquadTracker:getClosestExtractableSquad(sourcePoint) + local minDist = 99999999 + local squad = nil + + for i,v in pairs(self.activeInfantrySquads) do + if v.state == 'extractReady' then + local gr = Group.getByName(v.name) + if gr and gr:getSize()>0 then + local dist = mist.utils.get2DDist(sourcePoint, gr:getUnit(1):getPoint()) + if dist (5*60) then + if gr:getSize()>0 then + local unPos = gr:getUnit(1):getPoint() + local p = Utils.getPointOnSurface(unPos) + p.x = p.x + math.random(-5,5) + p.z = p.z + math.random(-5,5) + trigger.action.smoke(p, trigger.smokeColor.Green) + squad.lastMarkerDeployedTime = timer.getAbsTime() + end + end + end + end +end + +-----------------[[ END OF SquadTracker.lua ]]----------------- + + + +-----------------[[ CSARTracker.lua ]]----------------- + +CSARTracker = {} +do + function CSARTracker:new() + local obj = {} + obj.activePilots = {} + setmetatable(obj, self) + self.__index = self + + obj:start() + return obj + end + + function CSARTracker:start() + if not ZoneCommand then return end + + local ev = {} + ev.context = self + function ev:onEvent(event) + if event.id == world.event.S_EVENT_LANDING_AFTER_EJECTION then + if event.initiator and event.initiator:isExist() then + if event.initiator:getCoalition() == 2 then + local z = ZoneCommand.getZoneOfPoint(event.initiator:getPoint()) + if not z then + local name = self.context:generateCallsign() + if name then + local pos = { + x = event.initiator:getPoint().x, + y = event.initiator:getPoint().z + } + + if pos.x ~= 0 and pos.y ~= 0 then + local srfType = land.getSurfaceType(pos) + if srfType ~= land.SurfaceType.WATER and srfType ~= land.SurfaceType.SHALLOW_WATER then + local gr = Spawner.createPilot(name, pos) + self.context:addPilot(name, gr) + end + end + end + end + end + + event.initiator:destroy() + end + end + end + + world.addEventHandler(ev) + + timer.scheduleFunction(function(param, time) + for i,v in pairs(param.context.activePilots) do + v.remainingTime = v.remainingTime - 10 + if not v.pilot:isExist() or v.remainingTime <=0 then + param.context:removePilot(i) + end + end + + return time+10 + end, {context = self}, timer.getTime()+1) + end + + function CSARTracker:markPilot(data) + local pilot = data.pilot + if pilot:isExist() then + local pos = pilot:getUnit(1):getPoint() + local p = Utils.getPointOnSurface(pos) + p.x = p.x + math.random(-5,5) + p.z = p.z + math.random(-5,5) + trigger.action.smoke(p, trigger.smokeColor.Green) + end + end + + function CSARTracker:flarePilot(data) + local pilot = data.pilot + if pilot:isExist() then + local pos = pilot:getUnit(1):getPoint() + local p = Utils.getPointOnSurface(pos) + trigger.action.signalFlare(p, trigger.flareColor.Green, math.random(1,360)) + end + end + + function CSARTracker:removePilot(name) + local data = self.activePilots[name] + if data.pilot and data.pilot:isExist() then data.pilot:destroy() end + + MissionTargetRegistry.removePilot(data) + self.activePilots[name] = nil + end + + function CSARTracker:addPilot(name, pilot) + self.activePilots[name] = {pilot = pilot, name = name, remainingTime = 45*60} + MissionTargetRegistry.addPilot(self.activePilots[name]) + end + + function CSARTracker:restorePilot(save) + local gr = Spawner.createPilot(save.name, save.pos) + + self.activePilots[save.name] = { + pilot = gr, + name = save.name, + remainingTime = save.remainingTime + } + + MissionTargetRegistry.addPilot(self.activePilots[save.name]) + end + + function CSARTracker:getClosestPilot(toPosition) + local minDist = 99999999 + local data = nil + local name = nil + + for i,v in pairs(self.activePilots) do + if v.pilot:isExist() and v.remainingTime > 0 then + local dist = mist.utils.get2DDist(toPosition, v.pilot:getUnit(1):getPoint()) + if dist 0 then + local msg = "Warning radius set to "..warningRadius + if metric then + msg=msg.."km" + else + msg=msg.."nm" + end + + if metric then + warningRadius = warningRadius * 1000 + else + warningRadius = warningRadius * 1852 + end + + self.players[name] = { + unit = unit, + warningRadius = warningRadius, + metric = metric + } + + trigger.action.outTextForUnit(unit:getID(), msg, 10) + else + self.players[name] = nil + trigger.action.outTextForUnit(unit:getID(), "GCI Reports disabled", 10) + end + end + + function GCI:start() + MenuRegistry:register(5, function(event, context) + if event.id == world.event.S_EVENT_BIRTH and event.initiator and event.initiator.getPlayerName then + local player = event.initiator:getPlayerName() + if player then + local groupid = event.initiator:getGroup():getID() + local groupname = event.initiator:getGroup():getName() + local unit = event.initiator + + if context.groupMenus[groupid] then + missionCommands.removeItemForGroup(groupid, context.groupMenus[groupid]) + context.groupMenus[groupid] = nil + end + + if not context.groupMenus[groupid] then + + local menu = missionCommands.addSubMenuForGroup(groupid, 'GCI') + local setWR = missionCommands.addSubMenuForGroup(groupid, 'Set Warning Radius', menu) + local kmMenu = missionCommands.addSubMenuForGroup(groupid, 'KM', setWR) + local nmMenu = missionCommands.addSubMenuForGroup(groupid, 'NM', setWR) + + missionCommands.addCommandForGroup(groupid, '10 KM', kmMenu, Utils.log(context.registerPlayer), context, player, unit, 10, true) + missionCommands.addCommandForGroup(groupid, '25 KM', kmMenu, Utils.log(context.registerPlayer), context, player, unit, 25, true) + missionCommands.addCommandForGroup(groupid, '50 KM', kmMenu, Utils.log(context.registerPlayer), context, player, unit, 50, true) + missionCommands.addCommandForGroup(groupid, '100 KM', kmMenu, Utils.log(context.registerPlayer), context, player, unit, 100, true) + missionCommands.addCommandForGroup(groupid, '150 KM', kmMenu, Utils.log(context.registerPlayer), context, player, unit, 150, true) + + missionCommands.addCommandForGroup(groupid, '5 NM', nmMenu, Utils.log(context.registerPlayer), context, player, unit, 5, false) + missionCommands.addCommandForGroup(groupid, '10 NM', nmMenu, Utils.log(context.registerPlayer), context, player, unit, 10, false) + missionCommands.addCommandForGroup(groupid, '25 NM', nmMenu, Utils.log(context.registerPlayer), context, player, unit, 25, false) + missionCommands.addCommandForGroup(groupid, '50 NM', nmMenu, Utils.log(context.registerPlayer), context, player, unit, 50, false) + missionCommands.addCommandForGroup(groupid, '80 NM', nmMenu, Utils.log(context.registerPlayer), context, player, unit, 80, false) + missionCommands.addCommandForGroup(groupid, 'Disable', menu, Utils.log(context.registerPlayer), context, player, unit, 0, false) + + context.groupMenus[groupid] = menu + end + end + elseif (event.id == world.event.S_EVENT_PLAYER_LEAVE_UNIT or event.id == world.event.S_EVENT_DEAD) and event.initiator and event.initiator.getPlayerName then + local player = event.initiator:getPlayerName() + if player then + local groupid = event.initiator:getGroup():getID() + + if context.groupMenus[groupid] then + missionCommands.removeItemForGroup(groupid, context.groupMenus[groupid]) + context.groupMenus[groupid] = nil + end + end + end + end, self) + + timer.scheduleFunction(function(param, time) + local self = param.context + local allunits = coalition.getGroups(self.side) + + local radars = {} + for _,g in ipairs(allunits) do + for _,u in ipairs(g:getUnits()) do + for _,a in ipairs(self.radarTypes) do + if u:hasAttribute(a) then + table.insert(radars, u) + break + end + end + end + end + + self.radars = radars + env.info("GCI - tracking "..#radars.." radar enabled units") + + return time+10 + end, {context = self}, timer.getTime()+1) + + timer.scheduleFunction(function(param, time) + local self = param.context + + local plyCount = 0 + for i,v in pairs(self.players) do + if not v.unit or not v.unit:isExist() then + self.players[i] = nil + else + plyCount = plyCount + 1 + end + end + + env.info("GCI - reporting to "..plyCount.." players") + if plyCount >0 then + local dect = {} + local dcount = 0 + for _,u in ipairs(self.radars) do + if u:isExist() then + local detected = u:getController():getDetectedTargets(Controller.Detection.RADAR) + for _,d in ipairs(detected) do + if d and d.object and d.object.isExist and d.object:isExist() and + d.object:getCategory() == Object.Category.UNIT and + d.object.getCoalition and + d.object:getCoalition() == self.tgtSide then + + if not dect[d.object:getName()] then + dect[d.object:getName()] = d.object + dcount = dcount + 1 + end + end + end + end + end + + env.info("GCI - aware of "..dcount.." enemy units") + + for name, data in pairs(self.players) do + if data.unit and data.unit:isExist() then + local closeUnits = {} + + local wr = data.warningRadius + if wr > 0 then + for _,dt in pairs(dect) do + if dt:isExist() then + local tgtPnt = dt:getPoint() + local dist = mist.utils.get2DDist(data.unit:getPoint(), tgtPnt) + if dist <= wr then + local brg = math.floor(Utils.getBearing(data.unit:getPoint(), tgtPnt)) + + local myPos = data.unit:getPosition() + local tgtPos = dt:getPosition() + local tgtHeading = math.deg(math.atan2(tgtPos.x.z, tgtPos.x.x)) + local tgtBearing = Utils.getBearing(tgtPos.p, myPos.p) + + local diff = math.abs(Utils.getHeadingDiff(tgtBearing, tgtHeading)) + local aspect = '' + local priority = 1 + if diff <= 30 then + aspect = "Hot" + priority = 1 + elseif diff <= 60 then + aspect = "Flanking" + priority = 1 + elseif diff <= 120 then + aspect = "Beaming" + priority = 2 + else + aspect = "Cold" + priority = 3 + end + + table.insert(closeUnits, { + type = dt:getDesc().typeName, + bearing = brg, + range = dist, + altitude = tgtPnt.y, + score = dist*priority, + aspect = aspect + }) + end + end + end + end + + env.info("GCI - "..#closeUnits.." enemy units within "..wr.."m of "..name) + if #closeUnits > 0 then + table.sort(closeUnits, function(a, b) return a.range < b.range end) + + local msg = "GCI Report:\n" + local count = 0 + for _,tgt in ipairs(closeUnits) do + if data.metric then + local km = tgt.range/1000 + if km < 1 then + msg = msg..'\n'..tgt.type..' MERGED' + else + msg = msg..'\n'..tgt.type..' BRA: '..tgt.bearing..' for ' + msg = msg..Utils.round(km)..'km at ' + msg = msg..(Utils.round(tgt.altitude/250)*250)..'m, ' + msg = msg..tostring(tgt.aspect) + end + else + local nm = tgt.range/1852 + if nm < 1 then + msg = msg..'\n'..tgt.type..' MERGED' + else + msg = msg..'\n'..tgt.type..' BRA: '..tgt.bearing..' for ' + msg = msg..Utils.round(nm)..'nm at ' + msg = msg..(Utils.round((tgt.altitude/0.3048)/1000)*1000)..'ft, ' + msg = msg..tostring(tgt.aspect) + end + end + + count = count + 1 + if count >= 10 then break end + end + + trigger.action.outTextForUnit(data.unit:getID(), msg, 19) + end + else + self.players[name] = nil + end + end + end + + return time+20 + end, {context = self}, timer.getTime()+6) + end +end + +-----------------[[ END OF GCI.lua ]]----------------- + + + +-----------------[[ Starter.lua ]]----------------- + +Starter = {} +do + Starter.neutralChance = 0.1 + + function Starter.start(zones) + if Starter.shouldRandomize() then + Starter.randomize(zones) + else + Starter.normalStart(zones) + end + end + + function Starter.randomize(zones) + local startZones = {} + for _,z in pairs(zones) do + if z.isHeloSpawn and z.isPlaneSpawn then + table.insert(startZones, z) + end + end + + if #startZones > 0 then + local sz = startZones[math.random(1,#startZones)] + + sz:capture(2, true) + Starter.captureNeighbours(sz, math.random(1,3)) + end + + for _,z in pairs(zones) do + if z.side == 0 then + if math.random() > Starter.neutralChance then + z:capture(1,true) + end + end + + if z.side ~= 0 then + z:fullUpgrade(math.random(1,30)/100) + end + end + end + + function Starter.captureNeighbours(zone, stepsLeft) + if stepsLeft > 0 then + for _,v in pairs(zone.neighbours) do + if v.side == 0 then + if math.random() > Starter.neutralChance then + v:capture(2,true) + end + Starter.captureNeighbours(v, stepsLeft-1) + end + end + end + end + + function Starter.shouldRandomize() + if lfs then + local filename = lfs.writedir()..'Missions/Saves/randomize.lua' + if lfs.attributes(filename) then + return true + end + end + end + + function Starter.normalStart(zones) + for _,z in pairs(zones) do + local i = z.initialState + if i then + if i.side and i.side ~= 0 then + z:capture(i.side, true) + z:fullUpgrade() + z:boostProduction(math.random(1,200)) + end + end + end + end +end + +-----------------[[ END OF Starter.lua ]]----------------- + From b6f0ec4da04b4a09fceba8b04710ae5d599e851b Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Sun, 10 Sep 2023 16:11:30 +0300 Subject: [PATCH 112/243] Copied init.lua into init_header.lua, init_body.lua and init_footer.lua The init.lua script will be generated by 1. injecting the header, then 2. generating the ZoneCommand zones, then 3. generating ConnectionManager connections, then 4. injecting init_body.lua, then 5. generating sypply injection and finally by 6. injecting init_footer.lua --- .../pretense/{init.lua => init_body.lua} | 0 resources/plugins/pretense/init_footer.lua | 4670 +++++++++++++++++ resources/plugins/pretense/init_header.lua | 4670 +++++++++++++++++ 3 files changed, 9340 insertions(+) rename resources/plugins/pretense/{init.lua => init_body.lua} (100%) create mode 100644 resources/plugins/pretense/init_footer.lua create mode 100644 resources/plugins/pretense/init_header.lua diff --git a/resources/plugins/pretense/init.lua b/resources/plugins/pretense/init_body.lua similarity index 100% rename from resources/plugins/pretense/init.lua rename to resources/plugins/pretense/init_body.lua diff --git a/resources/plugins/pretense/init_footer.lua b/resources/plugins/pretense/init_footer.lua new file mode 100644 index 00000000..9df8452d --- /dev/null +++ b/resources/plugins/pretense/init_footer.lua @@ -0,0 +1,4670 @@ + + +local savefile = 'pretense_1.1.json' +if lfs then + local dir = lfs.writedir()..'Missions/Saves/' + lfs.mkdir(dir) + savefile = dir..savefile + env.info('Pretense - Save file path: '..savefile) +end + + +do + TemplateDB.templates["infantry-red"] = { + units = { + "BTR_D", + "T-90", + "T-90", + "Infantry AK ver2", + "Infantry AK", + "Infantry AK", + "Paratrooper RPG-16", + "Infantry AK ver3", + "SA-18 Igla manpad" + }, + skill = "Excellent", + dataCategory= TemplateDB.type.group + } + + TemplateDB.templates["infantry-blue"] = { + units = { + "M1045 HMMWV TOW", + "Soldier stinger", + "Soldier M4 GRG", + "Soldier M4 GRG", + "Soldier M4 GRG", + "Soldier M4 GRG", + "Soldier M4 GRG", + "M1043 HMMWV Armament" + }, + skill = "Excellent", + dataCategory= TemplateDB.type.group + } + + TemplateDB.templates["defense-red"] = { + units = { + "Infantry AK ver2", + "Infantry AK", + "Infantry AK ver3", + "Paratrooper RPG-16", + "SA-18 Igla manpad" + }, + skill = "Excellent", + dataCategory= TemplateDB.type.group + } + + TemplateDB.templates["defense-blue"] = { + units = { + "Soldier M4 GRG", + "Soldier M4 GRG", + "Soldier M4 GRG", + "Soldier RPG", + "Soldier stinger", + }, + skill = "Excellent", + dataCategory= TemplateDB.type.group + } + + TemplateDB.templates["shorad-red"] = { + units = { + "Strela-10M3", + "Strela-10M3", + "Ural-4320T", + "2S6 Tunguska" + }, + maxDist = 300, + skill = "Excellent", + dataCategory= TemplateDB.type.group + } + + TemplateDB.templates["shorad-blue"] = { + units = { + "Roland ADS", + "M48 Chaparral", + "M 818", + "Gepard", + "Gepard" + }, + maxDist = 300, + skill = "Excellent", + dataCategory= TemplateDB.type.group + } + + TemplateDB.templates["sam-red"] = { + units = { + "p-19 s-125 sr", + "Ural-4320T", + "Ural-4320T", + "S_75M_Volhov", + "S_75M_Volhov", + "S_75M_Volhov", + "S_75M_Volhov", + "S_75M_Volhov", + "Tor 9A331", + "SNR_75V" + }, + maxDist = 300, + skill = "Excellent", + dataCategory= TemplateDB.type.group + } + + TemplateDB.templates["sam-blue"] = { + units = { + "Hawk pcp", + "Hawk cwar", + "Hawk ln", + "Hawk ln", + "Hawk ln", + "Hawk ln", + "Hawk ln", + "Hawk tr", + "M 818", + "Hawk sr" + }, + maxDist = 300, + skill = "Good", + dataCategory= TemplateDB.type.group + } + + TemplateDB.templates["patriot"] = { + units = { + "Patriot cp", + "Patriot str", + "M 818", + "M 818", + "Patriot ln", + "Patriot ln", + "Patriot ln", + "Patriot ln", + "Patriot str", + "Patriot str", + "Patriot str", + "Patriot EPP", + "Patriot ECS", + "Patriot AMG" + }, + maxDist = 300, + skill = "Good", + dataCategory= TemplateDB.type.group + } + + TemplateDB.templates["sa3"] = { + units = { + "p-19 s-125 sr", + "snr s-125 tr", + "5p73 s-125 ln", + "5p73 s-125 ln", + "Ural-4320T", + "5p73 s-125 ln", + "5p73 s-125 ln" + }, + maxDist = 300, + skill = "Excellent", + dataCategory= TemplateDB.type.group + } + + TemplateDB.templates["sa6"] = { + units = { + "Kub 1S91 str", + "Kub 2P25 ln", + "Kub 2P25 ln", + "Kub 2P25 ln", + "Kub 2P25 ln", + "2S6 Tunguska", + "Ural-4320T", + "2S6 Tunguska", + "Kub 2P25 ln" + }, + maxDist = 300, + skill = "Excellent", + dataCategory= TemplateDB.type.group + } + + TemplateDB.templates["sa10"] = { + units = { + "S-300PS 54K6 cp", + "S-300PS 5P85C ln", + "S-300PS 5P85C ln", + "S-300PS 5P85C ln", + "GAZ-66", + "GAZ-66", + "GAZ-66", + "S-300PS 5P85C ln", + "S-300PS 5P85C ln", + "S-300PS 5P85C ln", + "S-300PS 40B6MD sr", + "S-300PS 40B6M tr", + "S-300PS 64H6E sr" + }, + maxDist = 300, + skill = "Excellent", + dataCategory= TemplateDB.type.group + } + + TemplateDB.templates["sa5"] = { + units = { + "RLS_19J6", + "Ural-4320T", + "Ural-4320T", + "RPC_5N62V", + "S-200_Launcher", + "S-200_Launcher", + "S-200_Launcher", + "S-200_Launcher", + "S-200_Launcher", + "S-200_Launcher" + }, + maxDist = 300, + skill = "Excellent", + dataCategory= TemplateDB.type.group + } + + TemplateDB.templates["sa11"] = { + units = { + "SA-11 Buk SR 9S18M1", + "SA-11 Buk LN 9A310M1", + "SA-11 Buk LN 9A310M1", + "SA-11 Buk LN 9A310M1", + "SA-11 Buk LN 9A310M1", + "SA-11 Buk LN 9A310M1", + "2S6 Tunguska", + "SA-11 Buk SR 9S18M1", + "GAZ-66", + "GAZ-66", + "SA-11 Buk CC 9S470M1" + }, + maxDist = 300, + skill = "Excellent", + dataCategory= TemplateDB.type.group + } + + TemplateDB.templates["nasams"] = { + units = { + "NASAMS_Command_Post", + "NASAMS_Radar_MPQ64F1", + "Vulcan", + "M 818", + "M 818", + "Roland ADS", + "Roland ADS", + "NASAMS_LN_C", + "NASAMS_LN_C", + "NASAMS_LN_C", + "NASAMS_LN_C", + "NASAMS_Radar_MPQ64F1", + "NASAMS_Radar_MPQ64F1", + "NASAMS_Radar_MPQ64F1" + }, + maxDist = 300, + skill = "Excellent", + dataCategory= TemplateDB.type.group + } +end + +presets = { + upgrades = { + basic = { + tent = Preset:new({ + display = 'Tent', + cost = 1500, + type = 'upgrade', + template = "tent" + }), + comPost = Preset:new({ + display = 'Barracks', + cost = 1500, + type = 'upgrade', + template = "barracks" + }), + outpost = Preset:new({ + display = 'Outpost', + cost = 1500, + type = 'upgrade', + template = "outpost" + }) + }, + attack = { + ammoCache = Preset:new({ + display = 'Ammo Cache', + cost = 1500, + type = 'upgrade', + template = "ammo-cache" + }), + ammoDepot = Preset:new({ + display = 'Ammo Depot', + cost = 2000, + type = 'upgrade', + template = "ammo-depot" + }) + }, + supply = { + fuelCache = Preset:new({ + display = 'Fuel Cache', + cost = 1500, + type = 'upgrade', + template = "fuel-cache" + }), + fuelTank = Preset:new({ + display = 'Fuel Tank', + cost = 1500, + type = 'upgrade', + template = "fuel-tank-big" + }), + fuelTankFarp = Preset:new({ + display = 'Fuel Tank', + cost = 1500, + type = 'upgrade', + template = "fuel-tank-small" + }), + factory1 = Preset:new({ + display='Factory', + cost = 2000, + type ='upgrade', + income = 20, + template = "factory-1" + }), + factory2 = Preset:new({ + display='Factory', + cost = 2000, + type ='upgrade', + income = 20, + template = "factory-2" + }), + factoryTank = Preset:new({ + display='Storage Tank', + cost = 1500, + type ='upgrade', + income = 10, + template = "chem-tank" + }), + ammoDepot = Preset:new({ + display = 'Ammo Depot', + cost = 2000, + type = 'upgrade', + income = 40, + template = "ammo-depot" + }), + oilPump = Preset:new({ + display = 'Oil Pump', + cost = 1500, + type = 'upgrade', + income = 20, + template = "oil-pump" + }), + hangar = Preset:new({ + display = 'Hangar', + cost = 2000, + type = 'upgrade', + income = 30, + template = "hangar" + }), + excavator = Preset:new({ + display = 'Excavator', + cost = 2000, + type = 'upgrade', + income = 20, + template = "excavator" + }), + farm1 = Preset:new({ + display = 'Farm House', + cost = 2000, + type = 'upgrade', + income = 40, + template = "farm-house-1" + }), + farm2 = Preset:new({ + display = 'Farm House', + cost = 2000, + type = 'upgrade', + income = 40, + template = "farm-house-2" + }), + refinery1 = Preset:new({ + display='Refinery', + cost = 2000, + type ='upgrade', + income = 100, + template = "factory-1" + }), + powerplant1 = Preset:new({ + display='Power Plant', + cost = 1500, + type ='upgrade', + income = 25, + template = "factory-1" + }), + powerplant2 = Preset:new({ + display='Power Plant', + cost = 1500, + type ='upgrade', + income = 25, + template = "factory-2" + }), + antenna = Preset:new({ + display='Antenna', + cost = 1000, + type ='upgrade', + income = 10, + template = "antenna" + }), + hq = Preset:new({ + display='HQ Building', + cost = 2000, + type ='upgrade', + income = 50, + template = "tv-tower" + }) + }, + airdef = { + comCenter = Preset:new({ + display = 'Command Center', + cost = 2500, + type = 'upgrade', + template = "command-center" + }) + } + }, + defenses = { + red = { + infantry = Preset:new({ + display = 'Infantry', + cost=2000, + type='defense', + template='infantry-red', + }), + shorad = Preset:new({ + display = 'SAM', + cost=2500, + type='defense', + template='shorad-red', + }), + sam = Preset:new({ + display = 'SAM', + cost=3000, + type='defense', + template='sam-red', + }), + sa10 = Preset:new({ + display = 'SAM', + cost=3000, + type='defense', + template='sa10', + }), + sa5 = Preset:new({ + display = 'SAM', + cost=3000, + type='defense', + template='sa5', + }), + sa3 = Preset:new({ + display = 'SAM', + cost=3000, + type='defense', + template='sa3', + }), + sa6 = Preset:new({ + display = 'SAM', + cost=3000, + type='defense', + template='sa6', + }), + sa11 = Preset:new({ + display = 'SAM', + cost=3000, + type='defense', + template='sa11', + }) + }, + blue = { + infantry = Preset:new({ + display = 'Infantry', + cost=2000, + type='defense', + template='infantry-blue', + }), + shorad = Preset:new({ + display = 'SAM', + cost=2500, + type='defense', + template='shorad-blue', + }), + sam = Preset:new({ + display = 'SAM', + cost=3000, + type='defense', + template='sam-blue', + }), + patriot = Preset:new({ + display = 'SAM', + cost=3000, + type='defense', + template='patriot', + }), + nasams = Preset:new({ + display = 'SAM', + cost=3000, + type='defense', + template='nasams', + }) + } + }, + missions = { + supply = { + convoy = Preset:new({ + display = 'Supply convoy', + cost = 4000, + type = 'mission', + missionType = ZoneCommand.missionTypes.supply_convoy + }), + convoy_escorted = Preset:new({ + display = 'Supply convoy', + cost = 3000, + type = 'mission', + missionType = ZoneCommand.missionTypes.supply_convoy + }), + helo = Preset:new({ + display = 'Supply helicopter', + cost = 2500, + type='mission', + missionType = ZoneCommand.missionTypes.supply_air + }), + transfer = Preset:new({ + display = 'Supply transfer', + cost = 1000, + type='mission', + missionType = ZoneCommand.missionTypes.supply_transfer + }) + }, + attack = { + surface = Preset:new({ + display = 'Ground assault', + cost = 100, + type = 'mission', + missionType = ZoneCommand.missionTypes.assault, + }), + cas = Preset:new({ + display = 'CAS', + cost = 200, + type='mission', + missionType = ZoneCommand.missionTypes.cas + }), + bai = Preset:new({ + display = 'BAI', + cost = 200, + type='mission', + missionType = ZoneCommand.missionTypes.bai + }), + strike = Preset:new({ + display = 'Strike', + cost = 300, + type='mission', + missionType = ZoneCommand.missionTypes.strike + }), + sead = Preset:new({ + display = 'SEAD', + cost = 200, + type='mission', + missionType = ZoneCommand.missionTypes.sead + }), + helo = Preset:new({ + display = 'CAS', + cost = 100, + type='mission', + missionType = ZoneCommand.missionTypes.cas_helo + }) + }, + patrol={ + aircraft = Preset:new({ + display= "Patrol", + cost = 100, + type='mission', + missionType = ZoneCommand.missionTypes.patrol + }) + }, + support ={ + awacs = Preset:new({ + display= "AWACS", + cost = 300, + type='mission', + bias='5', + missionType = ZoneCommand.missionTypes.awacs + }), + tanker = Preset:new({ + display= "Tanker", + cost = 200, + type='mission', + bias='2', + missionType = ZoneCommand.missionTypes.tanker + }) + } + }, + special = { + red = { + infantry = Preset:new({ + display = 'Infantry', + cost=-1, + type='defense', + template='defense-red', + }), + }, + blue = { + infantry = Preset:new({ + display = 'Infantry', + cost=-1, + type='defense', + template='defense-blue', + }) + } + } +} + +zones = {} +do + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Batumi.lua ]]----------------- + +zones.batumi = ZoneCommand:new('Batumi') +zones.batumi.initialState = { side=2 } +zones.batumi.keepActive = true +zones.batumi.isHeloSpawn = true +zones.batumi.isPlaneSpawn = true +zones.batumi.maxResource = 50000 +zones.batumi:defineUpgrades({ + [1] = { --red side + presets.upgrades.basic.comPost:extend({ + name = 'batumi-com-red', + products = { + presets.special.red.infantry:extend({ name='batumi-defense-red'}), + presets.defenses.red.infantry:extend({ name='batumi-garrison-red' }) + } + }), + }, + [2] = --blue side + { + presets.upgrades.basic.comPost:extend({ + name = 'batumi-com-blue', + products = { + presets.special.blue.infantry:extend({ name='batumi-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='batumi-garrison-blue' }) + } + }), + presets.upgrades.supply.fuelTank:extend({ + name = 'batumi-fueltank-blue', + products = { + presets.missions.supply.convoy_escorted:extend({ name='batumi-supply-convoy-1'}), + presets.missions.supply.helo:extend({ name='batumi-supply-blue-1' }), + presets.missions.supply.transfer:extend({name='batumi-transfer-blue'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name = 'batumi-mission-command-blue', + products = { + presets.defenses.blue.shorad:extend({ name='batumi-sam-blue' }), + presets.missions.attack.sead:extend({name='batumi-sead-blue-1', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.attack.cas:extend({name='batumi-cas-blue-1', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.bai:extend({name='batumi-cas-blue-1', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.strike:extend({name='batumi-strike-blue-1', altitude=20000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.patrol.aircraft:extend({name='batumi-patrol-blue-1', altitude=25000, range=25}), + presets.missions.support.awacs:extend({name='batumi-awacs-blue', altitude=30000, freq=257.5}), + presets.missions.support.tanker:extend({name='batumi-tanker-blue', altitude=25000, freq=257, tacan='37', variant="Drogue"}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Batumi.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Mike.lua ]]----------------- + +zones.mike = ZoneCommand:new("Mike") +zones.mike.initialState = { side=1 } +zones.mike.keepActive = true +zones.mike:defineUpgrades({ + [1] = + { + presets.upgrades.basic.tent:extend({ + name='mike-tent-red', + products = { + presets.special.red.infantry:extend({ name='mike-defense-red'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='mike-comcenter-red', + products = { + presets.defenses.red.sam:extend({ name='mike-sam-red'}) + } + }) + }, + [2] = + { + presets.upgrades.basic.tent:extend({ + name='mike-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='mike-defense-blue'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='mike-comcenter-blue', + products = { + presets.defenses.blue.sam:extend({ name='mike-sam-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Mike.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Tyrnyauz.lua ]]----------------- + +zones.tyrnyauz = ZoneCommand:new("Tyrnyauz") +zones.tyrnyauz.initialState = { side=1 } +zones.tyrnyauz.isHeloSpawn = true +zones.tyrnyauz:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='tyrnyauz-tent-red', + products = { + presets.special.red.infantry:extend({ name='tyrnyauz-defense-red'}), + presets.defenses.red.infantry:extend({ name='tyrnyauz-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='tyrnyauz-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='tyrnyauz-supply-red'}), + presets.missions.supply.helo:extend({name='tyrnyauz-supply-red-2'}), + presets.missions.supply.transfer:extend({name='tyrnyauz-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='tyrnyauz-ammo-red', + products = { + presets.missions.attack.surface:extend({name='tyrnyauz-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='tyrnyauz-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='tyrnyauz-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='tyrnyauz-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='tyrnyauz-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='tyrnyauz-supply-blue'}), + presets.missions.supply.helo:extend({name='tyrnyauz-supply-blue-2'}), + presets.missions.supply.transfer:extend({name='tyrnyauz-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='tyrnyauz-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='tyrnyauz-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Tyrnyauz.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/India.lua ]]----------------- + +zones.india = ZoneCommand:new("India") +zones.india.initialState = nil +zones.india:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='india-tent-red', + products = { + presets.special.red.infantry:extend({ name='india-defense-red'}), + presets.defenses.red.infantry:extend({ name='india-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='india-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='india-supply-red'}), + presets.missions.supply.transfer:extend({name='india-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='india-ammo-red', + products = { + presets.missions.attack.surface:extend({name='india-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='india-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='india-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='india-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='india-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='india-supply-blue'}), + presets.missions.supply.transfer:extend({name='india-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='india-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='india-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/India.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/IntelCenter.lua ]]----------------- + +zones.intelcenter = ZoneCommand:new("Intel Center") +zones.intelcenter.initialState = { side=1 } +zones.intelcenter:defineUpgrades({ + [1] = + { + presets.upgrades.basic.tent:extend({ + name='intelcenter-tent-red', + products = { + presets.special.red.infantry:extend({ name='intelcenter-defense-red'}), + presets.defenses.red.infantry:extend({ name='intelcenter-garrison-red'}) + } + }), + presets.upgrades.supply.hq:extend({ + name='intelcenter-hq-red', + products = { + presets.missions.supply.convoy:extend({ name='intelcenter-supply-red'}), + presets.missions.supply.convoy:extend({ name='intelcenter-supply-red-1'}), + presets.missions.supply.transfer:extend({name='intelcenter-transfer-red'}) + } + }), + presets.upgrades.supply.antenna:extend({ + name='intelcenter-antenna-red', + products = { + } + }), + presets.upgrades.supply.antenna:extend({ + name='intelcenter-antenna-red-1', + products = { + } + }), + presets.upgrades.supply.antenna:extend({ + name='intelcenter-antenna-red-2', + products = { + } + }) + }, + [2] = + { + presets.upgrades.basic.tent:extend({ + name='intelcenter-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='intelcenter-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='intelcenter-garrison-blue'}) + } + }), + presets.upgrades.supply.hq:extend({ + name='intelcenter-hq-blue', + products = { + presets.missions.supply.convoy:extend({ name='intelcenter-supply-blue'}), + presets.missions.supply.convoy:extend({ name='intelcenter-supply-blue-1'}), + presets.missions.supply.transfer:extend({name='intelcenter-transfer-blue'}) + } + }), + presets.upgrades.supply.antenna:extend({ + name='intelcenter-antenna-blue', + products = { + } + }), + presets.upgrades.supply.antenna:extend({ + name='intelcenter-antenna-blue-1', + products = { + } + }), + presets.upgrades.supply.antenna:extend({ + name='intelcenter-antenna-blue-2', + products = { + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/IntelCenter.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Mineralnye.lua ]]----------------- + +zones.mineralnye = ZoneCommand:new("Mineralnye") +zones.mineralnye.initialState = { side=1 } +zones.mineralnye.keepActive = true +zones.mineralnye.isHeloSpawn = true +zones.mineralnye.isPlaneSpawn = true +zones.mineralnye:defineUpgrades({ + [1] = + { + presets.upgrades.basic.comPost:extend({ + name='mineralnye-compost-red', + products = { + presets.special.red.infantry:extend({ name='mineralnye-defense-red'}), + presets.defenses.red.infantry:extend({ name='mineralnye-garrison-red'}) + } + }), + presets.upgrades.supply.fuelTank:extend({ + name='mineralnye-fuel-red', + products = { + presets.missions.supply.helo:extend({name='mineralnye-supply-red'}), + presets.missions.supply.helo:extend({name='mineralnye-supply-red-1'}), + presets.missions.supply.transfer:extend({name='mineralnye-transfer-red'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='mineralnye-comcenter-red', + products = { + presets.defenses.red.sa11:extend({ name='mineralnye-airdef-red'}), + presets.missions.attack.cas:extend({name='mineralnye-cas-red', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.bai:extend({name='mineralnye-cas-red', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.strike:extend({name='mineralnye-strike-red', altitude=30000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.attack.strike:extend({name='mineralnye-strike-red-1', altitude=30000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.patrol.aircraft:extend({name='mineralnye-patrol-red', altitude=25000, range=25}) + } + }) + }, + [2] = + { + presets.upgrades.basic.comPost:extend({ + name='mineralnye-compost-blue', + products = { + presets.special.blue.infantry:extend({ name='mineralnye-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='mineralnye-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelTank:extend({ + name='mineralnye-fuel-blue', + products = { + presets.missions.supply.helo:extend({name='mineralnye-supply-blue'}), + presets.missions.supply.helo:extend({name='mineralnye-supply-blue-1'}), + presets.missions.supply.transfer:extend({name='mineralnye-transfer-blue'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='mineralnye-comcenter-blue', + products = { + presets.defenses.blue.nasams:extend({ name='mineralnye-airdef-blue'}), + presets.missions.attack.cas:extend({name='mineralnye-cas-blue', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.bai:extend({name='mineralnye-cas-blue', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.strike:extend({name='mineralnye-strike-blue', altitude=30000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.attack.strike:extend({name='mineralnye-strike-blue-1', altitude=30000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.patrol.aircraft:extend({name='mineralnye-patrol-blue', altitude=25000, range=25}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Mineralnye.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/PowerPlant.lua ]]----------------- + +zones.powerplant = ZoneCommand:new("Power Plant") +zones.powerplant.initialState = { side=1 } +zones.powerplant:defineUpgrades({ + [1] = + { + presets.upgrades.basic.tent:extend({ + name='powerplant-tent-red', + products = { + presets.special.red.infantry:extend({ name='powerplant-defense-red'}), + presets.defenses.red.infantry:extend({ name='powerplant-garrison-red'}) + } + }), + presets.upgrades.supply.powerplant1:extend({ + name='powerplant-building-red-1', + products = { + presets.missions.supply.convoy:extend({ name='powerplant-supply-red'}), + presets.missions.supply.transfer:extend({name='powerplant-transfer-red'}) + } + }), + presets.upgrades.supply.powerplant2:extend({ + name='powerplant-building-red-2', + products = { + presets.missions.supply.convoy:extend({ name='powerplant-supply-red-1'}), + presets.missions.supply.transfer:extend({name='powerplant-transfer-red'}) + } + }) + }, + [2] = + { + presets.upgrades.basic.tent:extend({ + name='powerplant-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='powerplant-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='powerplant-garrison-blue'}) + } + }), + presets.upgrades.supply.powerplant1:extend({ + name='powerplant-building-blue-1', + products = { + presets.missions.supply.convoy:extend({ name='powerplant-supply-blue'}), + presets.missions.supply.transfer:extend({name='powerplant-transfer-blue'}) + } + }), + presets.upgrades.supply.powerplant2:extend({ + name='powerplant-building-blue-2', + products = { + presets.missions.supply.convoy:extend({ name='powerplant-supply-blue-1'}), + presets.missions.supply.transfer:extend({name='powerplant-transfer-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/PowerPlant.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Zugdidi.lua ]]----------------- + +zones.zugdidi = ZoneCommand:new("Zugdidi") +zones.zugdidi.initialState = { side=1 } +zones.zugdidi:defineUpgrades({ + [1] = + { + presets.upgrades.basic.comPost:extend({ + name='zugdidi-compost-red', + products = { + presets.missions.supply.transfer:extend({name='zugdidi-transfer-red'}), + presets.special.red.infantry:extend({ name='zugdidi-defense-red'}), + presets.defenses.red.infantry:extend({ name='zugdidi-garrison-red'}), + presets.missions.attack.surface:extend({name='zugdidi-attack-red'}), + presets.missions.supply.convoy:extend({name='zugdidi-supply-red'}) + } + }), + presets.upgrades.supply.hangar:extend({ + name='zugdidi-hangar-red-1', + products = { + presets.missions.supply.helo:extend({name='zugdidi-supply-red-1'}) + } + }), + presets.upgrades.supply.hangar:extend({ + name='zugdidi-hangar-red-2', + products = { + presets.missions.supply.helo:extend({name='zugdidi-supply-red-2'}) + } + }), + presets.upgrades.supply.hangar:extend({ + name='zugdidi-hangar-red-3', + products = { + presets.missions.supply.helo:extend({name='zugdidi-supply-red-3'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='zugdidi-comcenter-red', + products = { + presets.defenses.red.sa6:extend({ name='zugdidi-sam-red'}) + } + }) + }, + [2] = + { + presets.upgrades.basic.tent:extend({ + name='zugdidi-compost-blue', + products = { + presets.missions.supply.transfer:extend({name='zugdidi-transfer-blue'}), + presets.special.blue.infantry:extend({ name='zugdidi-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='zugdidi-garrison-blue'}), + presets.missions.attack.surface:extend({name='zugdidi-attack-blue'}), + presets.missions.supply.convoy:extend({name='zugdidi-supply-blue'}) + } + }), + presets.upgrades.supply.hangar:extend({ + name='zugdidi-hangar-blue-1', + products = { + presets.missions.supply.helo:extend({name='zugdidi-supply-blue-1'}) + } + }), + presets.upgrades.supply.hangar:extend({ + name='zugdidi-hangar-blue-2', + products = { + presets.missions.supply.helo:extend({name='zugdidi-supply-blue-2'}) + } + }), + presets.upgrades.supply.hangar:extend({ + name='zugdidi-hangar-blue-3', + products = { + presets.missions.supply.helo:extend({name='zugdidi-supply-blue-3'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='zugdidi-comcenter-blue', + products = { + presets.defenses.blue.shorad:extend({ name='zugdidi-sam-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Zugdidi.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Babugent.lua ]]----------------- + +zones.babugent = ZoneCommand:new("Babugent") +zones.babugent.initialState = { side=1 } +zones.babugent:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='babugent-tent-red', + products = { + presets.special.red.infantry:extend({ name='babugent-defense-red'}), + presets.defenses.red.infantry:extend({ name='babugent-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='babugent-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='babugent-supply-red'}), + presets.missions.supply.helo:extend({name='babugent-supply-red-2'}), + presets.missions.supply.transfer:extend({name='babugent-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='babugent-ammo-red', + products = { + presets.missions.attack.surface:extend({name='babugent-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='babugent-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='babugent-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='babugent-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='babugent-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='babugent-supply-blue'}), + presets.missions.supply.helo:extend({name='babugent-supply-blue-2'}), + presets.missions.supply.transfer:extend({name='babugent-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='babugent-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='babugent-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Babugent.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Kislovodsk.lua ]]----------------- + +zones.kislovodsk = ZoneCommand:new("Kislovodsk") +zones.kislovodsk.initialState = { side=1 } +zones.kislovodsk:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='kislovodsk-tent-red', + products = { + presets.special.red.infantry:extend({ name='kislovodsk-defense-red'}), + presets.defenses.red.infantry:extend({ name='kislovodsk-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='kislovodsk-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='kislovodsk-supply-red'}), + presets.missions.supply.transfer:extend({name='kislovodsk-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='kislovodsk-ammo-red', + products = { + presets.missions.attack.surface:extend({name='kislovodsk-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='kislovodsk-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='kislovodsk-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='kislovodsk-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='kislovodsk-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='kislovodsk-supply-blue'}), + presets.missions.supply.transfer:extend({name='kislovodsk-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='kislovodsk-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='kislovodsk-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Kislovodsk.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Gudauta.lua ]]----------------- + +zones.gudauta = ZoneCommand:new("Gudauta") +zones.gudauta.initialState = { side=1 } +zones.gudauta.keepActive = true +zones.gudauta.maxResource = 50000 +zones.gudauta:defineUpgrades({ + [1] = + { + presets.upgrades.basic.comPost:extend({ + name='gudauta-compost-red', + products = { + presets.special.red.infantry:extend({ name='gudauta-defense-red'}), + presets.defenses.red.infantry:extend({ name='gudauta-garrison-red'}) + } + }), + presets.upgrades.supply.fuelTank:extend({ + name='gudauta-fuel-red', + products = { + presets.missions.supply.helo:extend({name='gudauta-supply-red'}), + presets.missions.supply.helo:extend({name='gudauta-supply-red-1'}), + presets.missions.supply.transfer:extend({name='gudauta-transfer-red'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='gudauta-comcenter-red', + products = { + presets.defenses.red.sam:extend({ name='gudauta-airdef-red'}), + presets.missions.attack.sead:extend({name='gudauta-sead-red', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.attack.sead:extend({name='gudauta-sead-red-1', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.attack.cas:extend({name='gudauta-cas-red', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.bai:extend({name='gudauta-cas-red', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.patrol.aircraft:extend({name='gudauta-patrol-red', altitude=25000, range=25}) + } + }) + }, + [2] = + { + presets.upgrades.basic.comPost:extend({ + name='gudauta-compost-blue', + products = { + presets.special.blue.infantry:extend({ name='gudauta-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='gudauta-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelTank:extend({ + name='gudauta-fuel-blue', + products = { + presets.missions.supply.helo:extend({name='gudauta-supply-blue'}), + presets.missions.supply.helo:extend({name='gudauta-supply-blue-1'}), + presets.missions.supply.transfer:extend({name='gudauta-transfer-blue'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='gudauta-comcenter-blue', + products = { + presets.defenses.blue.sam:extend({ name='gudauta-airdef-blue'}), + presets.missions.attack.sead:extend({name='gudauta-sead-blue', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.attack.sead:extend({name='gudauta-sead-blue-1', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.attack.cas:extend({name='gudauta-cas-blue', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.bai:extend({name='gudauta-cas-blue', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.patrol.aircraft:extend({name='gudauta-patrol-blue', altitude=25000, range=25}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Gudauta.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Distillery.lua ]]----------------- + +zones.distillery = ZoneCommand:new("Distillery") +zones.distillery.initialState = { side=1 } +zones.distillery:defineUpgrades({ + [1] = + { + presets.upgrades.basic.tent:extend({ + name='distillery-tent-red', + products = { + presets.special.red.infantry:extend({ name='distillery-defense-red'}), + presets.defenses.red.infantry:extend({ name='distillery-garrison-red'}) + } + }), + presets.upgrades.supply.factory1:extend({ + name='distillery-prod-red-1', + products = { + presets.missions.supply.convoy:extend({ name='distillery-supply-red-1'}), + presets.missions.supply.transfer:extend({name='distillery-transfer-red'}) + } + }), + presets.upgrades.supply.factory2:extend({ + name='distillery-prod-red-2', + products = { + presets.missions.supply.convoy:extend({ name='distillery-supply-red-2', cost=2000}), + presets.missions.supply.transfer:extend({name='distillery-transfer-red2'}) + } + }), + presets.upgrades.supply.factoryTank:extend({ + name='distillery-tank-red-1', + products = { + } + }), + presets.upgrades.supply.factoryTank:extend({ + name='distillery-tank-red-2', + products = { + } + }), + presets.upgrades.supply.factoryTank:extend({ + name='distillery-tank-red-3', + products = { + } + }) + }, + [2] = + { + presets.upgrades.basic.tent:extend({ + name='distillery-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='distillery-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='distillery-garrison-blue'}) + } + }), + presets.upgrades.supply.factory1:extend({ + name='distillery-prod-blue-1', + products = { + presets.missions.supply.convoy:extend({ name='distillery-supply-blue-1'}), + presets.missions.supply.transfer:extend({name='distillery-transfer-blue'}) + } + }), + presets.upgrades.supply.factory2:extend({ + name='distillery-prod-blue-2', + products = { + presets.missions.supply.convoy:extend({ name='distillery-supply-blue-2', cost=2000}), + presets.missions.supply.transfer:extend({name='distillery-transfer-blue2'}) + } + }), + presets.upgrades.supply.factoryTank:extend({ + name='distillery-tank-blue-1', + products = { + } + }), + presets.upgrades.supply.factoryTank:extend({ + name='distillery-tank-blue-2', + products = { + } + }), + presets.upgrades.supply.factoryTank:extend({ + name='distillery-tank-blue-3', + products = { + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Distillery.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Sochi.lua ]]----------------- + +zones.sochi = ZoneCommand:new("Sochi") +zones.sochi.initialState = { side=1 } +zones.sochi.keepActive = true +zones.sochi.maxResource = 50000 +zones.sochi:defineUpgrades({ + [1] = + { + presets.upgrades.basic.comPost:extend({ + name='sochi-compost-red', + products = { + presets.special.red.infantry:extend({ name='sochi-defense-red'}), + presets.defenses.red.infantry:extend({ name='sochi-garrison-red'}) + } + }), + presets.upgrades.supply.fuelTank:extend({ + name='sochi-fuel-red', + products = { + presets.missions.supply.helo:extend({name='sochi-supply-red-1'}), + presets.missions.supply.helo:extend({name='sochi-supply-red-2'}), + presets.missions.supply.convoy_escorted:extend({name='sochi-supply-red-3'}), + presets.missions.supply.transfer:extend({name='sochi-transfer-red'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='sochi-comcenter-red', + products = { + presets.defenses.red.sa10:extend({ name='sochi-airdef-red'}), + presets.missions.attack.sead:extend({name='sochi-sead-red', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.attack.strike:extend({name='sochi-strike-red', altitude=30000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.patrol.aircraft:extend({name='sochi-patrol-red', altitude=25000, range=25}), + presets.missions.patrol.aircraft:extend({name='sochi-patrol-red-1', altitude=25000, range=25}), + presets.missions.attack.cas:extend({name='sochi-cas-red', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.bai:extend({name='sochi-cas-red', altitude=10000, expend=AI.Task.WeaponExpend.ONE}) + } + }) + }, + [2] = + { + presets.upgrades.basic.comPost:extend({ + name='sochi-compost-blue', + products = { + presets.special.blue.infantry:extend({ name='sochi-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='sochi-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelTank:extend({ + name='sochi-fuel-blue', + products = { + presets.missions.supply.helo:extend({name='sochi-supply-blue-1'}), + presets.missions.supply.helo:extend({name='sochi-supply-blue-2'}), + presets.missions.supply.convoy_escorted:extend({name='sochi-supply-blue-3'}), + presets.missions.supply.transfer:extend({name='sochi-transfer-blue'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='sochi-comcenter-blue', + products = { + presets.defenses.blue.patriot:extend({ name='sochi-airdef-blue'}), + presets.missions.attack.sead:extend({name='sochi-sead-blue', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.attack.strike:extend({name='sochi-strike-blue', altitude=30000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.patrol.aircraft:extend({name='sochi-patrol-blue', altitude=25000, range=25}), + presets.missions.attack.cas:extend({name='sochi-cas-blue', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.bai:extend({name='sochi-cas-blue', altitude=10000, expend=AI.Task.WeaponExpend.ONE}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Sochi.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Golf.lua ]]----------------- + +zones.golf = ZoneCommand:new("Golf") +zones.golf.initialState = nil +zones.golf.isHeloSpawn = true +zones.golf:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='golf-tent-red', + products = { + presets.special.red.infantry:extend({ name='golf-defense-red'}), + presets.defenses.red.infantry:extend({ name='golf-garrison-red'}) + } + }), + presets.upgrades.supply.fuelTank:extend({ + name='golf-fuel-red', + products = { + presets.missions.supply.helo:extend({name='golf-supply-red'}), + presets.missions.supply.helo:extend({name='golf-supply-red-1'}), + presets.missions.supply.transfer:extend({name='golf-transfer-red'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='golf-comcenter-red', + products = { + presets.defenses.red.shorad:extend({name='golf-sam-red'}), + presets.missions.attack.helo:extend({name='golf-cas-red', altitude=200, expend=AI.Task.WeaponExpend.HALF }) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='golf-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='golf-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='golf-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelTankFarp:extend({ + name='golf-fuel-blue', + products = { + presets.missions.supply.helo:extend({name='golf-supply-blue'}), + presets.missions.supply.helo:extend({name='golf-supply-blue-1'}), + presets.missions.supply.transfer:extend({name='golf-transfer-blue'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='golf-comcenter-blue', + products = { + presets.defenses.blue.shorad:extend({name='golf-sam-blue'}), + presets.missions.attack.helo:extend({name='golf-cas-blue', altitude=200, expend=AI.Task.WeaponExpend.HALF }) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Golf.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Charlie.lua ]]----------------- + +zones.charlie = ZoneCommand:new("Charlie") +zones.charlie.initialState = { side=2 } +zones.charlie.keepActive = true +zones.charlie:defineUpgrades({ + [1] = + { + presets.upgrades.basic.tent:extend({ + name='charlie-tent-red', + products = { + presets.special.red.infantry:extend({ name='charlie-defense-red'}), + presets.defenses.red.infantry:extend({ name='charlie-garrison-red'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='charlie-comcenter-red', + products = { + presets.defenses.red.sam:extend({ name='charlie-sam-red'}) + } + }) + }, + [2] = + { + presets.upgrades.basic.tent:extend({ + name='charlie-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='charlie-defense-red'}), + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='charlie-comcenter-blue', + products = { + presets.defenses.blue.sam:extend({ name='charlie-sam-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Charlie.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Lentehi.lua ]]----------------- + +zones.lentehi = ZoneCommand:new("Lentehi") +zones.lentehi.initialState = { side=1 } +zones.lentehi:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='lentehi-tent-red', + products = { + presets.special.red.infantry:extend({ name='lentehi-defense-red'}), + presets.defenses.red.infantry:extend({ name='lentehi-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='lentehi-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='lentehi-supply-red'}), + presets.missions.supply.helo:extend({name='lentehi-supply-red-2'}), + presets.missions.supply.transfer:extend({name='lentehi-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='lentehi-ammo-red', + products = { + presets.missions.attack.surface:extend({name='lentehi-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='lentehi-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='lentehi-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='lentehi-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='lentehi-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='lentehi-supply-blue'}), + presets.missions.supply.helo:extend({name='lentehi-supply-blue-2'}), + presets.missions.supply.transfer:extend({name='lentehi-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='lentehi-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='lentehi-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Lentehi.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Refinery.lua ]]----------------- + +zones.refinery = ZoneCommand:new("Refinery") +zones.refinery.initialState = { side=1 } +zones.refinery:defineUpgrades({ + [1] = + { + presets.upgrades.basic.tent:extend({ + name='refinery-tent-red', + products = { + presets.special.red.infantry:extend({ name='refinery-defense-red'}), + presets.defenses.red.infantry:extend({ name='refinery-garrison-red'}) + } + }), + presets.upgrades.supply.refinery1:extend({ + name='refinery-building-red', + products = { + presets.missions.supply.convoy:extend({ name='refinery-supply-red'}), + presets.missions.supply.convoy:extend({ name='refinery-supply-red-1'}), + presets.missions.supply.helo:extend({ name='refinery-supply-red-2'}), + presets.missions.supply.transfer:extend({name='refinery-transfer-red'}) + } + }) + }, + [2] = + { + presets.upgrades.basic.tent:extend({ + name='refinery-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='refinery-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='refinery-garrison-blue'}) + } + }), + presets.upgrades.supply.refinery1:extend({ + name='refinery-building-blue', + products = { + presets.missions.supply.convoy:extend({ name='refinery-supply-blue'}), + presets.missions.supply.convoy:extend({ name='refinery-supply-blue-1'}), + presets.missions.supply.helo:extend({ name='refinery-supply-blue-2'}), + presets.missions.supply.transfer:extend({name='refinery-transfer-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Refinery.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Mozdok.lua ]]----------------- + +zones.mozdok = ZoneCommand:new("Mozdok") +zones.mozdok.initialState = { side=1 } +zones.mozdok.keepActive = true +zones.mozdok.maxResource = 50000 +zones.mozdok:defineUpgrades({ + [1] = + { + presets.upgrades.basic.comPost:extend({ + name='mozdok-compost-red', + products = { + presets.special.red.infantry:extend({ name='mozdok-defense-red'}), + presets.defenses.red.infantry:extend({ name='mozdok-garrison-red'}) + } + }), + presets.upgrades.supply.fuelTank:extend({ + name='mozdok-fuel-red', + products = { + presets.missions.supply.helo:extend({name='mozdok-supply-red-1'}), + presets.missions.supply.helo:extend({name='mozdok-supply-red-2'}), + presets.missions.supply.convoy_escorted:extend({name='mozdok-supply-red-3'}), + presets.missions.supply.transfer:extend({name='mozdok-transfer-red'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='mozdok-comcenter-red', + products = { + presets.defenses.red.sa10:extend({ name='mozdok-airdef-red'}), + presets.missions.patrol.aircraft:extend({name='mozdok-patrol-red', altitude=25000, range=25}), + presets.missions.attack.cas:extend({name='mozdok-cas-red', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.bai:extend({name='mozdok-cas-red', altitude=10000, expend=AI.Task.WeaponExpend.ONE}) + } + }) + }, + [2] = + { + presets.upgrades.basic.comPost:extend({ + name='mozdok-compost-blue', + products = { + presets.special.blue.infantry:extend({ name='mozdok-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='mozdok-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelTank:extend({ + name='mozdok-fuel-blue', + products = { + presets.missions.supply.helo:extend({name='mozdok-supply-blue-1'}), + presets.missions.supply.helo:extend({name='mozdok-supply-blue-2'}), + presets.missions.supply.convoy_escorted:extend({name='mozdok-supply-blue-3'}), + presets.missions.supply.transfer:extend({name='mozdok-transfer-blue'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='mozdok-comcenter-blue', + products = { + presets.defenses.blue.shorad:extend({ name='mozdok-airdef-blue'}), + presets.missions.patrol.aircraft:extend({name='mozdok-patrol-blue', altitude=25000, range=25}), + presets.missions.attack.cas:extend({name='mozdok-cas-blue', altitude=15000, cost=50, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.cas:extend({name='mozdok-cas-blue-1', altitude=15000, cost=50, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.bai:extend({name='mozdok-cas-blue', altitude=15000, cost=50, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.bai:extend({name='mozdok-cas-blue-1', altitude=15000, cost=50, expend=AI.Task.WeaponExpend.ONE}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Mozdok.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Lima.lua ]]----------------- + +zones.lima = ZoneCommand:new("Lima") +zones.lima.initialState = { side=1 } +zones.lima:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='lima-tent-red', + products = { + presets.special.red.infantry:extend({ name='lima-defense-red'}), + presets.defenses.red.infantry:extend({ name='lima-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='lima-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='lima-supply-red'}), + presets.missions.supply.helo:extend({name='lima-supply-red-1'}), + presets.missions.supply.transfer:extend({name='lima-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='lima-ammo-red', + products = { + presets.missions.attack.surface:extend({name='lima-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='lima-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='lima-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='lima-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='lima-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='lima-supply-blue'}), + presets.missions.supply.helo:extend({name='lima-supply-blue-1'}), + presets.missions.supply.transfer:extend({name='lima-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='lima-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='lima-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Lima.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Oscar.lua ]]----------------- + +zones.oscar = ZoneCommand:new("Oscar") +zones.oscar.initialState = { side=1 } +zones.oscar:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='oscar-tent-red', + products = { + presets.special.red.infantry:extend({ name='oscar-defense-red'}), + presets.defenses.red.infantry:extend({ name='oscar-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='oscar-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='oscar-supply-red'}), + presets.missions.supply.transfer:extend({name='oscar-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='oscar-ammo-red', + products = { + presets.missions.attack.surface:extend({name='oscar-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='oscar-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='oscar-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='oscar-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='oscar-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='oscar-supply-blue'}), + presets.missions.supply.transfer:extend({name='oscar-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='oscar-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='oscar-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Oscar.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Nalchik.lua ]]----------------- + +zones.nalchik = ZoneCommand:new("Nalchik") +zones.nalchik.initialState = { side=1 } +zones.nalchik.keepActive = true +zones.nalchik.isHeloSpawn = true +zones.nalchik.isPlaneSpawn = true +zones.nalchik:defineUpgrades({ + [1] = + { + presets.upgrades.basic.comPost:extend({ + name='nalchik-compost-red', + products = { + presets.special.red.infantry:extend({ name='nalchik-defense-red'}), + presets.defenses.red.infantry:extend({ name='nalchik-garrison-red'}) + } + }), + presets.upgrades.supply.fuelTank:extend({ + name='nalchik-fuel-red', + products = { + presets.missions.supply.helo:extend({name='nalchik-supply-red-1'}), + presets.missions.supply.helo:extend({name='nalchik-supply-red-2'}), + presets.missions.supply.transfer:extend({name='nalchik-transfer-red'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='nalchik-comcenter-red', + products = { + presets.defenses.red.sa3:extend({ name='nalchik-airdef-red'}), + presets.missions.attack.sead:extend({name='nalchik-sead-red', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.attack.cas:extend({name='nalchik-cas-red', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.bai:extend({name='nalchik-cas-red', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.strike:extend({name='nalchik-strike-red', altitude=30000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.patrol.aircraft:extend({name='nalchik-patrol-red', altitude=25000, range=25}), + presets.missions.patrol.aircraft:extend({name='nalchik-patrol-red-2', altitude=25000, range=25}), + presets.missions.support.awacs:extend({name='nalchik-awacs-red', altitude=30000, freq=251.2}), + presets.missions.support.tanker:extend({name='nalchik-tanker-red', altitude=30000, freq=252.2, tacan='40', variant='Drogue'}) + } + }) + }, + [2] = + { + presets.upgrades.basic.comPost:extend({ + name='nalchik-compost-blue', + products = { + presets.special.blue.infantry:extend({ name='nalchik-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='nalchik-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelTank:extend({ + name='nalchik-fuel-blue', + products = { + presets.missions.supply.helo:extend({name='nalchik-supply-blue-1'}), + presets.missions.supply.helo:extend({name='nalchik-supply-blue-2'}), + presets.missions.supply.transfer:extend({name='nalchik-transfer-blue'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='nalchik-comcenter-blue', + products = { + presets.defenses.blue.nasams:extend({ name='nalchik-airdef-blue'}), + presets.missions.support.awacs:extend({name='nalchik-awacs-blue', altitude=30000, freq=259.5}), + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Nalchik.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Digora.lua ]]----------------- + +zones.digora = ZoneCommand:new("Digora") +zones.digora.initialState = { side=1 } +zones.digora:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='digora-tent-red', + products = { + presets.special.red.infantry:extend({ name='digora-defense-red'}), + presets.defenses.red.infantry:extend({ name='digora-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='digora-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='digora-supply-red'}), + presets.missions.supply.transfer:extend({name='digora-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='digora-ammo-red', + products = { + presets.missions.attack.surface:extend({name='digora-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='digora-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='digora-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='digora-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='digora-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='digora-supply-blue'}), + presets.missions.supply.transfer:extend({name='digora-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='digora-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='digora-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Digora.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Uniform.lua ]]----------------- + +zones.uniform = ZoneCommand:new("Uniform") +zones.uniform.initialState = { side=1 } +zones.uniform:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='uniform-tent-red', + products = { + presets.special.red.infantry:extend({ name='uniform-defense-red'}), + presets.defenses.red.infantry:extend({ name='uniform-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='uniform-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='uniform-supply-red'}), + presets.missions.supply.transfer:extend({name='uniform-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='uniform-ammo-red', + products = { + presets.missions.attack.surface:extend({name='uniform-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='uniform-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='uniform-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='uniform-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='uniform-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='uniform-supply-blue'}), + presets.missions.supply.transfer:extend({name='uniform-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='uniform-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='uniform-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Uniform.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Factory.lua ]]----------------- + +zones.factory = ZoneCommand:new("Factory") +zones.factory.initialState = { side=2 } +zones.factory:defineUpgrades({ + [1] = + { + presets.upgrades.basic.tent:extend({ + name='factory-tent-red', + products = { + presets.special.red.infantry:extend({ name='factory-defense-red'}), + presets.defenses.red.infantry:extend({ name='factory-garrison-red'}) + } + }), + presets.upgrades.supply.factory1:extend({ + name='factory-prod-red-1', + products = { + presets.missions.supply.convoy:extend({ name='factory-supply-red-1'}), + presets.missions.supply.transfer:extend({name='factory-transfer-red'}) + } + }), + presets.upgrades.supply.factory2:extend({ + name='factory-prod-red-2', + products = { + presets.missions.supply.convoy:extend({ name='factory-supply-red-2', cost=2000}), + presets.missions.supply.transfer:extend({name='factory-transfer-red2'}) + } + }), + presets.upgrades.supply.factoryTank:extend({ + name='factory-tank-red-1', + products = { + } + }), + presets.upgrades.supply.factoryTank:extend({ + name='factory-tank-red-2', + products = { + } + }), + presets.upgrades.supply.factoryTank:extend({ + name='factory-tank-red-3', + products = { + } + }) + }, + [2] = + { + presets.upgrades.basic.tent:extend({ + name='factory-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='factory-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='factory-garrison-blue'}) + } + }), + presets.upgrades.supply.factory1:extend({ + name='factory-prod-blue-1', + products = { + presets.missions.supply.convoy:extend({ name='factory-supply-blue-1'}), + presets.missions.supply.transfer:extend({name='factory-transfer-blue'}) + } + }), + presets.upgrades.supply.factory2:extend({ + name='factory-prod-blue-2', + products = { + presets.missions.supply.convoy:extend({ name='factory-supply-blue-2', cost=2000}), + presets.missions.supply.transfer:extend({name='factory-transfer-blue2'}) + } + }), + presets.upgrades.supply.factoryTank:extend({ + name='factory-tank-blue-1', + products = { + } + }), + presets.upgrades.supply.factoryTank:extend({ + name='factory-tank-blue-2', + products = { + } + }), + presets.upgrades.supply.factoryTank:extend({ + name='factory-tank-blue-3', + products = { + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Factory.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Senaki.lua ]]----------------- + +zones.senaki = ZoneCommand:new("Senaki") +zones.senaki.initialState = { side=1 } +zones.senaki.keepActive = true +zones.senaki.isHeloSpawn = true +zones.senaki.isPlaneSpawn = true +zones.senaki.maxResource = 50000 +zones.senaki:defineUpgrades({ + [1] = + { + presets.upgrades.basic.comPost:extend({ + name='senaki-compost-red', + products = { + presets.special.red.infantry:extend({ name='senaki-defense-red'}), + presets.defenses.red.infantry:extend({ name='senaki-garrison-red'}) + } + }), + presets.upgrades.supply.fuelTank:extend({ + name='senaki-fuel-red', + products = { + presets.missions.supply.helo:extend({name='senaki-supply-red-1'}), + presets.missions.supply.helo:extend({name='senaki-supply-red-2'}), + presets.missions.supply.transfer:extend({name='senaki-transfer-red'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='senaki-comcenter-red', + products = { + presets.defenses.red.sa3:extend({ name='senaki-airdef-red'}), + presets.missions.attack.sead:extend({name='senaki-sead-red', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.attack.cas:extend({name='senaki-cas-red', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.bai:extend({name='senaki-cas-red', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.strike:extend({name='senaki-strike-red', altitude=20000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.patrol.aircraft:extend({name='senaki-patrol-red', altitude=25000, range=25}), + presets.missions.patrol.aircraft:extend({name='senaki-patrol-red-2', altitude=20000, range=25}) + } + }) + }, + [2] = + { + presets.upgrades.basic.comPost:extend({ + name='senaki-compost-blue', + products = { + presets.special.blue.infantry:extend({ name='senaki-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='senaki-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelTank:extend({ + name='senaki-fuel-blue', + products = { + presets.missions.supply.helo:extend({name='senaki-supply-blue-1'}), + presets.missions.supply.helo:extend({name='senaki-supply-blue-2'}), + presets.missions.supply.transfer:extend({name='senaki-transfer-blue'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='senaki-comcenter-blue', + products = { + presets.defenses.blue.shorad:extend({ name='senaki-airdef-blue'}), + presets.missions.attack.sead:extend({name='senaki-sead-blue', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.attack.cas:extend({name='senaki-cas-blue', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.bai:extend({name='senaki-cas-blue', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.strike:extend({name='senaki-strike-blue', altitude=20000, expend=AI.Task.WeaponExpend.TWO}), + presets.missions.patrol.aircraft:extend({name='senaki-patrol-blue', altitude=25000, range=25}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Senaki.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Kutaisi.lua ]]----------------- + +zones.kutaisi = ZoneCommand:new("Kutaisi") +zones.kutaisi.initialState = { side=1 } +zones.kutaisi.keepActive = true +zones.kutaisi.maxResource = 50000 +zones.kutaisi:defineUpgrades({ + [1] = + { + presets.upgrades.basic.comPost:extend({ + name='kutaisi-compost-red', + products = { + presets.special.red.infantry:extend({ name='kutaisi-defense-red'}), + presets.defenses.red.infantry:extend({ name='kutaisi-garrison-red'}) + } + }), + presets.upgrades.supply.fuelTank:extend({ + name='kutaisi-fuel-red', + products = { + presets.missions.supply.helo:extend({name='kutaisi-supply-red-1'}), + presets.missions.supply.helo:extend({name='kutaisi-supply-red-2'}), + presets.missions.supply.transfer:extend({name='kutaisi-transfer-red'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='kutaisi-comcenter-red', + products = { + presets.defenses.red.shorad:extend({ name='kutaisi-airdef-red'}), + presets.missions.attack.sead:extend({name='kutaisi-sead-red', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.attack.cas:extend({name='kutaisi-cas-red', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.bai:extend({name='kutaisi-cas-red', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.strike:extend({name='kutaisi-strike-red', altitude=20000, expend=AI.Task.WeaponExpend.HALF}), + presets.missions.patrol.aircraft:extend({name='kutaisi-patrol-red', altitude=25000, range=25}), + presets.missions.patrol.aircraft:extend({name='kutaisi-patrol-red-2', altitude=25000, range=25}) + } + }) + }, + [2] = + { + presets.upgrades.basic.comPost:extend({ + name='kutaisi-compost-blue', + products = { + presets.special.blue.infantry:extend({ name='kutaisi-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='kutaisi-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelTank:extend({ + name='kutaisi-fuel-blue', + products = { + presets.missions.supply.helo:extend({name='kutaisi-supply-blue-1'}), + presets.missions.supply.helo:extend({name='kutaisi-supply-blue-2'}), + presets.missions.supply.transfer:extend({name='kutaisi-transfer-blue'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='kutaisi-comcenter-blue', + products = { + presets.defenses.blue.shorad:extend({ name='kutaisi-airdef-blue'}), + presets.missions.attack.sead:extend({name='kutaisi-sead-blue', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.attack.cas:extend({name='kutaisi-cas-blue', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.bai:extend({name='kutaisi-cas-blue', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.strike:extend({name='kutaisi-strike-blue', altitude=20000, expend=AI.Task.WeaponExpend.TWO}), + presets.missions.patrol.aircraft:extend({name='kutaisi-patrol-blue', altitude=25000, range=25}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Kutaisi.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Prohladniy.lua ]]----------------- + +zones.prohladniy = ZoneCommand:new("Prohladniy") +zones.prohladniy.initialState = { side=1 } +zones.prohladniy:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='prohladniy-tent-red', + products = { + presets.special.red.infantry:extend({ name='prohladniy-defense-red'}), + presets.defenses.red.infantry:extend({ name='prohladniy-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='prohladniy-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='prohladniy-supply-red'}), + presets.missions.supply.transfer:extend({name='prohladniy-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='prohladniy-ammo-red', + products = { + presets.missions.attack.surface:extend({name='prohladniy-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='prohladniy-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='prohladniy-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='prohladniy-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='prohladniy-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='prohladniy-supply-blue'}), + presets.missions.supply.transfer:extend({name='prohladniy-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='prohladniy-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='prohladniy-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Prohladniy.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Tallyk.lua ]]----------------- + +zones.tallyk = ZoneCommand:new("Tallyk") +zones.tallyk.initialState = { side=1 } +zones.tallyk.keepActive = true +zones.tallyk:defineUpgrades({ + [1] = + { + presets.upgrades.basic.tent:extend({ + name='tallyk-tent-red', + products = { + presets.special.red.infantry:extend({ name='tallyk-defense-red'}), + presets.defenses.red.infantry:extend({ name='tallyk-garrison-red'}), + presets.missions.attack.surface:extend({name='tallyk-assault-red'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='tallyk-comcenter-red', + products = { + presets.defenses.red.sam:extend({ name='tallyk-sam-red'}) + } + }) + }, + [2] = + { + presets.upgrades.basic.tent:extend({ + name='tallyk-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='tallyk-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='tallyk-garrison-blue'}), + presets.missions.attack.surface:extend({name='tallyk-assault-blue'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='tallyk-comcenter-blue', + products = { + presets.defenses.blue.sam:extend({ name='tallyk-sam-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Tallyk.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Terek.lua ]]----------------- + +zones.terek = ZoneCommand:new("Terek") +zones.terek.initialState = { side=1 } +zones.terek:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='terek-tent-red', + products = { + presets.special.red.infantry:extend({ name='terek-defense-red'}), + presets.defenses.red.infantry:extend({ name='terek-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='terek-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='terek-supply-red'}), + presets.missions.supply.transfer:extend({name='terek-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='terek-ammo-red', + products = { + presets.missions.attack.surface:extend({name='terek-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='terek-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='terek-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='terek-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='terek-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='terek-supply-blue'}), + presets.missions.supply.transfer:extend({name='terek-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='terek-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='terek-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Terek.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Humara.lua ]]----------------- + +zones.humara = ZoneCommand:new("Humara") +zones.humara.initialState = { side=1 } +zones.humara:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='humara-tent-red', + products = { + presets.special.red.infantry:extend({ name='humara-defense-red'}), + presets.defenses.red.infantry:extend({ name='humara-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='humara-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='humara-supply-red'}), + presets.missions.supply.transfer:extend({name='humara-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='humara-ammo-red', + products = { + presets.missions.attack.surface:extend({name='humara-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='humara-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='humara-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='humara-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='humara-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='humara-supply-blue'}), + presets.missions.supply.transfer:extend({name='humara-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='humara-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='humara-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Humara.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Ochamchira.lua ]]----------------- + +zones.ochamchira = ZoneCommand:new("Ochamchira") +zones.ochamchira.initialState = { side=1 } +zones.ochamchira:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='ochamchira-tent-red', + products = { + presets.special.red.infantry:extend({ name='ochamchira-defense-red'}), + presets.defenses.red.infantry:extend({ name='ochamchira-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='ochamchira-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='ochamchira-supply-red'}), + presets.missions.supply.transfer:extend({name='ochamchira-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='ochamchira-ammo-red', + products = { + presets.missions.attack.surface:extend({name='ochamchira-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='ochamchira-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='ochamchira-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='ochamchira-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='ochamchira-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='ochamchira-supply-blue'}), + presets.missions.supply.transfer:extend({name='ochamchira-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='ochamchira-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='ochamchira-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Ochamchira.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/November.lua ]]----------------- + +zones.november = ZoneCommand:new("November") +zones.november.initialState = { side=1 } +zones.november.isHeloSpawn = true +zones.november:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='november-tent-red', + products = { + presets.special.red.infantry:extend({ name='november-defense-red'}), + presets.defenses.red.infantry:extend({ name='november-garrison-red'}) + } + }), + presets.upgrades.supply.fuelTankFarp:extend({ + name='november-fuel-red', + products = { + presets.missions.supply.helo:extend({name='november-supply-red'}), + presets.missions.supply.helo:extend({name='november-supply-red-1'}), + presets.missions.supply.transfer:extend({name='november-transfer-red'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='november-comcenter-red', + products = { + presets.defenses.red.shorad:extend({name='november-sam-red'}), + presets.missions.attack.helo:extend({name='november-cas-red', altitude=200, expend=AI.Task.WeaponExpend.HALF }) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='november-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='november-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='november-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelTankFarp:extend({ + name='november-fuel-blue', + products = { + presets.missions.supply.helo:extend({name='november-supply-blue'}), + presets.missions.supply.helo:extend({name='november-supply-blue-1'}), + presets.missions.supply.transfer:extend({name='november-transfer-blue'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='november-comcenter-blue', + products = { + presets.defenses.blue.shorad:extend({name='november-sam-blue'}), + presets.missions.attack.helo:extend({name='november-cas-blue', altitude=200, expend=AI.Task.WeaponExpend.HALF }) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/November.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/XRay.lua ]]----------------- + +zones.xray = ZoneCommand:new("XRay") +zones.xray.initialState = { side=1 } +zones.xray:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='xray-tent-red', + products = { + presets.special.red.infantry:extend({ name='xray-defense-red'}), + presets.defenses.red.infantry:extend({ name='xray-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='xray-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='xray-supply-red'}), + presets.missions.supply.helo:extend({name='xray-supply-red-2'}), + presets.missions.supply.transfer:extend({name='xray-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='xray-ammo-red', + products = { + presets.missions.attack.surface:extend({name='xray-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='xray-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='xray-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='xray-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='xray-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='xray-supply-blue'}), + presets.missions.supply.helo:extend({name='xray-supply-blue-2'}), + presets.missions.supply.transfer:extend({name='xray-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='xray-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='xray-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/XRay.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Whiskey.lua ]]----------------- + +zones.whiskey = ZoneCommand:new("Whiskey") +zones.whiskey.initialState = { side=1 } +zones.whiskey:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='whiskey-tent-red', + products = { + presets.special.red.infantry:extend({ name='whiskey-defense-red'}), + presets.defenses.red.infantry:extend({ name='whiskey-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='whiskey-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='whiskey-supply-red'}), + presets.missions.supply.transfer:extend({name='whiskey-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='whiskey-ammo-red', + products = { + presets.missions.attack.surface:extend({name='whiskey-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='whiskey-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='whiskey-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='whiskey-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='whiskey-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='whiskey-supply-blue'}), + presets.missions.supply.transfer:extend({name='whiskey-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='whiskey-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='whiskey-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Whiskey.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Mine.lua ]]----------------- + +zones.mine = ZoneCommand:new("Mine") +zones.mine.initialState = { side=1 } +zones.mine:defineUpgrades({ + [1] = + { + presets.upgrades.basic.tent:extend({ + name='mine-tent-red', + products = { + presets.special.red.infantry:extend({ name='mine-defense-red'}), + presets.defenses.red.infantry:extend({ name='mine-garrison-red'}) + } + }), + presets.upgrades.supply.excavator:extend({ + name='mine-excavator-red-1', + products = { + presets.missions.supply.convoy:extend({ name='mine-supply-red'}), + presets.missions.supply.transfer:extend({name='mine-transfer-red'}) + } + }), + presets.upgrades.supply.excavator:extend({ + name='mine-excavator-red-2', + products = { + presets.missions.supply.convoy:extend({ name='mine-supply-red'}), + presets.missions.supply.transfer:extend({name='mine-transfer-red'}) + } + }), + presets.upgrades.supply.excavator:extend({ + name='mine-excavator-red-3', + products = { + presets.missions.supply.convoy:extend({ name='mine-supply-red'}), + presets.missions.supply.transfer:extend({name='mine-transfer-red'}) + } + }) + }, + [2] = + { + presets.upgrades.basic.tent:extend({ + name='mine-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='mine-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='mine-garrison-blue'}) + } + }), + presets.upgrades.supply.excavator:extend({ + name='mine-excavator-blue-1', + products = { + presets.missions.supply.convoy:extend({ name='mine-supply-blue'}), + presets.missions.supply.transfer:extend({name='mine-transfer-blue'}) + } + }), + presets.upgrades.supply.excavator:extend({ + name='mine-excavator-blue-2', + products = { + presets.missions.supply.convoy:extend({ name='mine-supply-blue'}), + presets.missions.supply.transfer:extend({name='mine-transfer-blue'}) + } + }), + presets.upgrades.supply.excavator:extend({ + name='mine-excavator-blue-3', + products = { + presets.missions.supply.convoy:extend({ name='mine-supply-blue'}), + presets.missions.supply.transfer:extend({name='mine-transfer-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Mine.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Papa.lua ]]----------------- + +zones.papa = ZoneCommand:new("Papa") +zones.papa.initialState = { side=1 } +zones.papa.keepActive = true +zones.papa:defineUpgrades({ + [1] = + { + presets.upgrades.basic.tent:extend({ + name='papa-tent-red', + products = { + presets.special.red.infantry:extend({ name='papa-defense-red'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='papa-comcenter-red', + products = { + presets.defenses.red.sam:extend({ name='papa-sam-red'}) + } + }) + }, + [2] = + { + presets.upgrades.basic.tent:extend({ + name='papa-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='papa-defense-blue'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='papa-comcenter-blue', + products = { + presets.defenses.blue.sam:extend({ name='papa-sam-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Papa.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Sukhumi.lua ]]----------------- + +zones.sukhumi = ZoneCommand:new("Sukhumi") +zones.sukhumi.initialState = { side=1 } +zones.sukhumi.keepActive = true +zones.sukhumi.maxResource = 50000 +zones.sukhumi:defineUpgrades({ + [1] = + { + presets.upgrades.basic.comPost:extend({ + name='sukhumi-compost-red', + products = { + presets.special.red.infantry:extend({ name='sukhumi-defense-red'}), + presets.defenses.red.infantry:extend({ name='sukhumi-garrison-red'}) + } + }), + presets.upgrades.supply.fuelTank:extend({ + name='sukhumi-fuel-red', + products = { + presets.missions.supply.helo:extend({name='sukhumi-supply-red-1'}), + presets.missions.supply.helo:extend({name='sukhumi-supply-red-2'}), + presets.missions.supply.transfer:extend({name='sukhumi-transfer-red'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='sukhumi-comcenter-red', + products = { + presets.defenses.red.sa11:extend({ name='sukhumi-airdef-red'}), + presets.missions.attack.sead:extend({name='sukhumi-sead-red', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.attack.cas:extend({name='sukhumi-cas-red', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.bai:extend({name='sukhumi-cas-red', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.strike:extend({name='sukhumi-strike-red', altitude=20000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.patrol.aircraft:extend({name='sukhumi-patrol-red', altitude=25000, range=25}), + presets.missions.patrol.aircraft:extend({name='sukhumi-patrol-red-2', altitude=25000, range=25}) + } + }) + }, + [2] = + { + presets.upgrades.basic.comPost:extend({ + name='sukhumi-compost-blue', + products = { + presets.special.blue.infantry:extend({ name='sukhumi-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='sukhumi-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelTank:extend({ + name='sukhumi-fuel-blue', + products = { + presets.missions.supply.helo:extend({name='sukhumi-supply-blue-1'}), + presets.missions.supply.helo:extend({name='sukhumi-supply-blue-2'}), + presets.missions.supply.transfer:extend({name='sukhumi-transfer-blue'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='sukhumi-comcenter-blue', + products = { + presets.defenses.blue.nasams:extend({ name='sukhumi-airdef-blue'}), + presets.missions.attack.sead:extend({name='sukhumi-sead-blue', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.attack.cas:extend({name='sukhumi-cas-blue', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.bai:extend({name='sukhumi-cas-blue', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.strike:extend({name='sukhumi-strike-blue', altitude=20000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.patrol.aircraft:extend({name='sukhumi-patrol-blue', altitude=25000, range=25}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Sukhumi.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Farm.lua ]]----------------- + +zones.farm = ZoneCommand:new("Farm") +zones.farm.initialState = { side=1 } +zones.farm:defineUpgrades({ + [1] = + { + presets.upgrades.basic.tent:extend({ + name='farm-tent-red', + products = { + presets.special.red.infantry:extend({ name='farm-defense-red'}), + presets.defenses.red.infantry:extend({ name='farm-garrison-red'}) + } + }), + presets.upgrades.supply.farm1:extend({ + name='farm-prod-red-1', + products = { + presets.missions.supply.convoy:extend({ name='farm-supply-red'}), + presets.missions.supply.transfer:extend({name='farm-transfer-red'}) + } + }), + presets.upgrades.supply.farm2:extend({ + name='farm-prod-red-2', + products = { + presets.missions.supply.convoy:extend({ name='farm-supply-red'}), + presets.missions.supply.transfer:extend({name='farm-transfer-red'}) + } + }) + }, + [2] = + { + presets.upgrades.basic.tent:extend({ + name='farm-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='farm-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='farm-garrison-blue'}) + } + }), + presets.upgrades.supply.farm1:extend({ + name='farm-prod-blue-1', + products = { + presets.missions.supply.convoy:extend({ name='farm-supply-blue'}), + presets.missions.supply.transfer:extend({name='farm-transfer-blue'}) + } + }), + presets.upgrades.supply.farm2:extend({ + name='farm-prod-blue-2', + products = { + presets.missions.supply.convoy:extend({ name='farm-supply-blue'}), + presets.missions.supply.transfer:extend({name='farm-transfer-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Farm.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Romeo.lua ]]----------------- + +zones.romeo = ZoneCommand:new("Romeo") +zones.romeo.initialState = { side=1 } +zones.romeo:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='romeo-tent-red', + products = { + presets.special.red.infantry:extend({ name='romeo-defense-red'}), + presets.defenses.red.infantry:extend({ name='romeo-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='romeo-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='romeo-supply-red'}), + presets.missions.supply.transfer:extend({name='romeo-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='romeo-ammo-red', + products = { + presets.missions.attack.surface:extend({name='romeo-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='romeo-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='romeo-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='romeo-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='romeo-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='romeo-supply-blue'}), + presets.missions.supply.transfer:extend({name='romeo-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='romeo-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='romeo-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Romeo.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Zulu.lua ]]----------------- + +zones.zulu = ZoneCommand:new("Zulu") +zones.zulu.initialState = { side=1 } +zones.zulu:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='zulu-tent-red', + products = { + presets.special.red.infantry:extend({ name='zulu-defense-red'}), + presets.defenses.red.infantry:extend({ name='zulu-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='zulu-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='zulu-supply-red'}), + presets.missions.supply.transfer:extend({name='zulu-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='zulu-ammo-red', + products = { + presets.missions.attack.surface:extend({name='zulu-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='zulu-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='zulu-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='zulu-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='zulu-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='zulu-supply-blue'}), + presets.missions.supply.transfer:extend({name='zulu-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='zulu-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='zulu-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Zulu.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Yankee.lua ]]----------------- + +zones.yankee = ZoneCommand:new("Yankee") +zones.yankee.initialState = { side=1 } +zones.yankee:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='yankee-tent-red', + products = { + presets.special.red.infantry:extend({ name='yankee-defense-red'}), + presets.defenses.red.infantry:extend({ name='yankee-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='yankee-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='yankee-supply-red'}), + presets.missions.supply.transfer:extend({name='yankee-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='yankee-ammo-red', + products = { + presets.missions.attack.surface:extend({name='yankee-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='yankee-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='yankee-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='yankee-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='yankee-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='yankee-supply-blue'}), + presets.missions.supply.transfer:extend({name='yankee-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='yankee-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='yankee-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Yankee.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Malgobek.lua ]]----------------- + +zones.malgobek = ZoneCommand:new("Malgobek") +zones.malgobek.initialState = { side=1 } +zones.malgobek:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='malgobek-tent-red', + products = { + presets.special.red.infantry:extend({ name='malgobek-defense-red'}), + presets.defenses.red.infantry:extend({ name='malgobek-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='malgobek-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='malgobek-supply-red'}), + presets.missions.supply.transfer:extend({name='malgobek-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='malgobek-ammo-red', + products = { + presets.missions.attack.surface:extend({name='malgobek-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='malgobek-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='malgobek-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='malgobek-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='malgobek-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='malgobek-supply-blue'}), + presets.missions.supply.transfer:extend({name='malgobek-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='malgobek-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='malgobek-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Malgobek.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Kilo.lua ]]----------------- + +zones.kilo = ZoneCommand:new("Kilo") +zones.kilo.initialState = { side=1 } +zones.kilo:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='kilo-tent-red', + products = { + presets.special.red.infantry:extend({ name='kilo-defense-red'}), + presets.defenses.red.infantry:extend({ name='kilo-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='kilo-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='kilo-supply-red'}), + presets.missions.supply.transfer:extend({name='kilo-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='kilo-ammo-red', + products = { + presets.missions.attack.surface:extend({name='kilo-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='kilo-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='kilo-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='kilo-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='kilo-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='kilo-supply-blue'}), + presets.missions.supply.transfer:extend({name='kilo-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='kilo-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='kilo-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Kilo.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Quebec.lua ]]----------------- + +zones.quebec = ZoneCommand:new("Quebec") +zones.quebec.initialState = { side=1 } +zones.quebec.keepActive = true +zones.quebec:defineUpgrades({ + [1] = + { + presets.upgrades.basic.tent:extend({ + name='quebec-tent-red', + products = { + presets.special.red.infantry:extend({ name='quebec-defense-red'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='quebec-comcenter-red', + products = { + presets.defenses.red.sam:extend({ name='quebec-sam-red'}) + } + }) + }, + [2] = + { + presets.upgrades.basic.tent:extend({ + name='quebec-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='quebec-defense-blue'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='quebec-comcenter-blue', + products = { + presets.defenses.blue.sam:extend({ name='quebec-sam-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Quebec.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/OilFields.lua ]]----------------- + +zones.oilfields = ZoneCommand:new("Oil Fields") +zones.oilfields.initialState = { side=1 } +zones.oilfields:defineUpgrades({ + [1] = { + presets.upgrades.basic.outpost:extend({ + name='oilfields-outpost-red', + products = { + presets.special.red.infantry:extend({ name='oilfields-defense-red'}), + presets.defenses.red.infantry:extend({ name='oilfields-garrison-red'}) + } + }), + presets.upgrades.supply.oilPump:extend({ + name='oilfields-pump-red-1', + products = { + presets.missions.supply.transfer:extend({name='oilfields-transfer-red1'}) + } + }), + presets.upgrades.supply.oilPump:extend({ + name='oilfields-pump-red-2', + products = { + presets.missions.supply.convoy:extend({name='oilfields-supply-red-1'}) + } + }), + presets.upgrades.supply.oilPump:extend({ + name='oilfields-pump-red-3', + products = { + presets.missions.supply.transfer:extend({name='oilfields-transfer-red2'}) + } + }), + presets.upgrades.supply.oilPump:extend({ + name='oilfields-pump-red-4', + products = { + presets.missions.supply.convoy:extend({name='oilfields-supply-red-2'}) + } + }) + }, + [2] = { + presets.upgrades.basic.outpost:extend({ + name='oilfields-outpost-blue', + products = { + presets.special.blue.infantry:extend({ name='oilfields-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='oilfields-garrison-blue'}) + } + }), + presets.upgrades.supply.oilPump:extend({ + name='oilfields-pump-blue-1', + products = { + presets.missions.supply.transfer:extend({name='oilfields-transfer-blue1'}) + } + }), + presets.upgrades.supply.oilPump:extend({ + name='oilfields-pump-blue-2', + products = { + presets.missions.supply.convoy:extend({name='oilfields-supply-blue-1'}) + } + }), + presets.upgrades.supply.oilPump:extend({ + name='oilfields-pump-blue-3', + products = { + presets.missions.supply.transfer:extend({name='oilfields-transfer-blue2'}) + } + }), + presets.upgrades.supply.oilPump:extend({ + name='oilfields-pump-blue-4', + products = { + presets.missions.supply.convoy:extend({name='oilfields-supply-blue-2'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/OilFields.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Echo.lua ]]----------------- + +zones.echo = ZoneCommand:new("Echo") +zones.echo.initialState = { side=2 } +zones.echo:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='echo-tent-red', + products = { + presets.special.red.infantry:extend({ name='echo-defense-red'}), + presets.defenses.red.infantry:extend({ name='echo-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='echo-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='echo-supply-red'}), + presets.missions.supply.transfer:extend({name='echo-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='echo-ammo-red', + products = { + presets.missions.attack.surface:extend({name='echo-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='echo-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='echo-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='echo-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='echo-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='echo-supply-blue'}), + presets.missions.supply.transfer:extend({name='echo-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='echo-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='echo-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Echo.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Kobuleti.lua ]]----------------- + +zones.kobuleti = ZoneCommand:new("Kobuleti") +zones.kobuleti.initialState = { side=2 } +zones.kobuleti.keepActive = true +zones.kobuleti.isHeloSpawn = true +zones.kobuleti.isPlaneSpawn = true +zones.kobuleti.maxResource = 50000 +zones.kobuleti:defineUpgrades({ + [1] = + { + presets.upgrades.basic.comPost:extend({ + name='kobuleti-compost-red', + products = { + presets.special.red.infantry:extend({ name='kobuleti-defense-red'}), + presets.defenses.red.infantry:extend({ name='kobuleti-garrison-red'}), + presets.missions.attack.surface:extend({ name='kobuleti-assault-red'}) + } + }), + presets.upgrades.supply.fuelTank:extend({ + name='kobuleti-fuel-red', + products = { + presets.missions.supply.helo:extend({name='kobuleti-supply-red-1'}), + presets.missions.supply.helo:extend({name='kobuleti-supply-red-2'}), + presets.missions.supply.transfer:extend({name='kobuleti-transfer-red'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='kobuleti-comcenter-red', + products = { + presets.defenses.red.shorad:extend({ name='kobuleti-airdef-red'}), + presets.missions.attack.sead:extend({name='kobuleti-sead-red', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.attack.cas:extend({name='kobuleti-cas-red', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.bai:extend({name='kobuleti-cas-red', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.strike:extend({name='kobuleti-strike-red', altitude=20000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.patrol.aircraft:extend({name='kobuleti-patrol-red', altitude=25000, range=25}) + } + }) + }, + [2] = + { + presets.upgrades.basic.comPost:extend({ + name='kobuleti-compost-blue', + products = { + presets.special.blue.infantry:extend({ name='kobuleti-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='kobuleti-garrison-blue'}), + presets.missions.attack.surface:extend({ name='kobuleti-assault-blue'}) + } + }), + presets.upgrades.supply.fuelTank:extend({ + name='kobuleti-fuel-blue', + products = { + presets.missions.supply.helo:extend({name='kobuleti-supply-blue-1'}), + presets.missions.supply.helo:extend({name='kobuleti-supply-blue-2'}), + presets.missions.supply.transfer:extend({name='kobuleti-transfer-blue'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='kobuleti-comcenter-blue', + products = { + presets.defenses.blue.shorad:extend({ name='kobuleti-airdef-blue'}), + presets.missions.attack.sead:extend({name='kobuleti-sead-blue', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.attack.cas:extend({name='kobuleti-cas-blue', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.bai:extend({name='kobuleti-cas-blue', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.strike:extend({name='kobuleti-strike-blue', altitude=20000, expend=AI.Task.WeaponExpend.TWO}), + presets.missions.patrol.aircraft:extend({name='kobuleti-patrol-blue', altitude=25000, range=25}), + presets.missions.support.awacs:extend({name='kobuleti-awacs-blue', altitude=30000, freq=258.5}), + presets.missions.support.tanker:extend({name='kobuleti-tanker-blue', altitude=23000, freq=258, tacan='38', variant='Boom'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Kobuleti.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Alpha.lua ]]----------------- + +zones.alpha = ZoneCommand:new('Alpha') +zones.alpha.initialState = { side=2 } +zones.alpha:defineUpgrades({ + [1] = --red side + { + presets.upgrades.basic.tent:extend({ + name = 'alpha-tent-red', + products = { + presets.special.red.infantry:extend({ name='alpha-defense-red'}), + presets.defenses.red.infantry:extend({ name='alpha-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name = 'alpha-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({ name='alpha-supply-red'}), + presets.missions.supply.transfer:extend({name='alpha-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name = 'alpha-ammo-red', + products = { + presets.missions.attack.surface:extend({ name='alpha-assault-red'}) + } + }) + }, + [2] = --blue side + { + presets.upgrades.basic.tent:extend({ + name = 'alpha-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='alpha-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='alpha-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name = 'alpha-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({ name='alpha-supply-blue'}), + presets.missions.supply.transfer:extend({name='alpha-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name = 'alpha-ammo-blue', + products = { + presets.missions.attack.surface:extend({ name='alpha-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Alpha.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Foxtrot.lua ]]----------------- + +zones.foxtrot = ZoneCommand:new("Foxtrot") +zones.foxtrot.initialState = { side=2 } +zones.foxtrot:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='foxtrot-tent-red', + products = { + presets.special.red.infantry:extend({ name='foxtrot-defense-red'}), + presets.defenses.red.infantry:extend({ name='foxtrot-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='foxtrot-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='foxtrot-supply-red'}), + presets.missions.supply.transfer:extend({name='foxtrot-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='foxtrot-ammo-red', + products = { + presets.missions.attack.surface:extend({name='foxtrot-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='foxtrot-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='foxtrot-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='foxtrot-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='foxtrot-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='foxtrot-supply-blue'}), + presets.missions.supply.transfer:extend({name='foxtrot-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='foxtrot-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='foxtrot-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Foxtrot.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Sierra.lua ]]----------------- + +zones.sierra = ZoneCommand:new("Sierra") +zones.sierra.initialState = { side=1 } +zones.sierra.isHeloSpawn = true +zones.sierra:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='sierra-tent-red', + products = { + presets.special.red.infantry:extend({ name='sierra-defense-red'}), + presets.defenses.red.infantry:extend({ name='sierra-garrison-red'}) + } + }), + presets.upgrades.supply.fuelTankFarp:extend({ + name='sierra-fuel-red', + products = { + presets.missions.supply.helo:extend({name='sierra-supply-red'}), + presets.missions.supply.helo:extend({name='sierra-supply-red-1'}), + presets.missions.supply.transfer:extend({name='sierra-transfer-red'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='sierra-comcenter-red', + products = { + presets.defenses.red.shorad:extend({name='sierra-sam-red'}), + presets.missions.attack.helo:extend({name='sierra-cas-red', altitude=200, expend=AI.Task.WeaponExpend.HALF }) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='sierra-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='sierra-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='sierra-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelTankFarp:extend({ + name='sierra-fuel-blue', + products = { + presets.missions.supply.helo:extend({name='sierra-supply-blue'}), + presets.missions.supply.helo:extend({name='sierra-supply-blue-1'}), + presets.missions.supply.transfer:extend({name='sierra-transfer-blue'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='sierra-comcenter-blue', + products = { + presets.defenses.blue.shorad:extend({name='sierra-sam-blue'}), + presets.missions.attack.helo:extend({name='sierra-cas-blue', altitude=200, expend=AI.Task.WeaponExpend.HALF }) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Sierra.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Oni.lua ]]----------------- + +zones.oni = ZoneCommand:new("Oni") +zones.oni.initialState = { side=1 } +zones.oni:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='oni-tent-red', + products = { + presets.special.red.infantry:extend({ name='oni-defense-red'}), + presets.defenses.red.infantry:extend({ name='oni-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='oni-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='oni-supply-red'}), + presets.missions.supply.helo:extend({name='oni-supply-red-2'}), + presets.missions.supply.transfer:extend({name='oni-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='oni-ammo-red', + products = { + presets.missions.attack.surface:extend({name='oni-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='oni-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='oni-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='oni-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='oni-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='oni-supply-blue'}), + presets.missions.supply.helo:extend({name='oni-supply-blue-2'}), + presets.missions.supply.transfer:extend({name='oni-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='oni-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='oni-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Oni.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Hotel.lua ]]----------------- + +zones.hotel = ZoneCommand:new("Hotel") +zones.hotel.initialState = nil +zones.hotel:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='hotel-tent-red', + products = { + presets.special.red.infantry:extend({ name='hotel-defense-red'}), + presets.defenses.red.infantry:extend({ name='hotel-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='hotel-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='hotel-supply-red'}), + presets.missions.supply.transfer:extend({name='hotel-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='hotel-ammo-red', + products = { + presets.missions.attack.surface:extend({name='hotel-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='hotel-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='hotel-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='hotel-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='hotel-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='hotel-supply-blue'}), + presets.missions.supply.transfer:extend({name='hotel-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='hotel-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='hotel-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Hotel.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Victor.lua ]]----------------- + +zones.victor = ZoneCommand:new("Victor") +zones.victor.initialState = { side=1 } +zones.victor:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='victor-tent-red', + products = { + presets.special.red.infantry:extend({ name='victor-defense-red'}), + presets.defenses.red.infantry:extend({ name='victor-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='victor-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='victor-supply-red'}), + presets.missions.supply.helo:extend({name='victor-supply-red-2'}), + presets.missions.supply.transfer:extend({name='victor-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='victor-ammo-red', + products = { + presets.missions.attack.surface:extend({name='victor-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='victor-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='victor-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='victor-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='victor-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='victor-supply-blue'}), + presets.missions.supply.helo:extend({name='victor-supply-blue-2'}), + presets.missions.supply.transfer:extend({name='victor-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='victor-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='victor-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Victor.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Tango.lua ]]----------------- + +zones.tango = ZoneCommand:new("Tango") +zones.tango.initialState = { side=1 } +zones.tango.isHeloSpawn = true +zones.tango:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='tango-tent-red', + products = { + presets.special.red.infantry:extend({ name='tango-defense-red'}), + presets.defenses.red.infantry:extend({ name='tango-garrison-red'}) + } + }), + presets.upgrades.supply.fuelTankFarp:extend({ + name='tango-fuel-red', + products = { + presets.missions.supply.helo:extend({name='tango-supply-red'}), + presets.missions.supply.helo:extend({name='tango-supply-red-1'}), + presets.missions.supply.transfer:extend({name='tango-transfer-red'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='tango-comcenter-red', + products = { + presets.defenses.red.shorad:extend({name='tango-sam-red'}), + presets.missions.attack.helo:extend({name='tango-cas-red', altitude=200, expend=AI.Task.WeaponExpend.HALF }) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='tango-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='tango-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='tango-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelTankFarp:extend({ + name='tango-fuel-blue', + products = { + presets.missions.supply.helo:extend({name='tango-supply-blue'}), + presets.missions.supply.helo:extend({name='tango-supply-blue-1'}), + presets.missions.supply.transfer:extend({name='tango-transfer-blue'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='tango-comcenter-blue', + products = { + presets.defenses.blue.shorad:extend({name='tango-sam-blue'}), + presets.missions.attack.helo:extend({name='tango-cas-blue', altitude=200, expend=AI.Task.WeaponExpend.HALF }) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Tango.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Unal.lua ]]----------------- + +zones.unal = ZoneCommand:new("Unal") +zones.unal.initialState = { side=1 } +zones.unal.isHeloSpawn = true +zones.unal:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='unal-tent-red', + products = { + presets.special.red.infantry:extend({ name='unal-defense-red'}), + presets.defenses.red.infantry:extend({ name='unal-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='unal-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='unal-supply-red'}), + presets.missions.supply.helo:extend({name='unal-supply-red-2'}), + presets.missions.supply.transfer:extend({name='unal-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='unal-ammo-red', + products = { + presets.missions.attack.surface:extend({name='unal-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='unal-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='unal-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='unal-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='unal-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='unal-supply-blue'}), + presets.missions.supply.helo:extend({name='unal-supply-blue-2'}), + presets.missions.supply.transfer:extend({name='unal-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='unal-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='unal-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Unal.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Beslan.lua ]]----------------- + +zones.beslan = ZoneCommand:new("Beslan") +zones.beslan.initialState = { side=1 } +zones.beslan.keepActive = true +zones.beslan.maxResource = 50000 +zones.beslan:defineUpgrades({ + [1] = + { + presets.upgrades.basic.comPost:extend({ + name='beslan-compost-red', + products = { + presets.special.red.infantry:extend({ name='beslan-defense-red'}), + presets.defenses.red.infantry:extend({ name='beslan-garrison-red'}) + } + }), + presets.upgrades.supply.fuelTank:extend({ + name='beslan-fuel-red', + products = { + presets.missions.supply.helo:extend({name='beslan-supply-red-1'}), + presets.missions.supply.helo:extend({name='beslan-supply-red-2'}), + presets.missions.supply.convoy_escorted:extend({name='beslan-supply-red-3'}), + presets.missions.supply.transfer:extend({name='beslan-transfer-red'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='beslan-comcenter-red', + products = { + presets.defenses.red.sa5:extend({ name='beslan-airdef-red'}), + presets.missions.attack.sead:extend({name='beslan-sead-red', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.attack.strike:extend({name='beslan-strike-red', altitude=30000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.attack.strike:extend({name='beslan-strike-red-1', altitude=30000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.patrol.aircraft:extend({name='beslan-patrol-red', altitude=25000, range=25}) + } + }) + }, + [2] = + { + presets.upgrades.basic.comPost:extend({ + name='beslan-compost-blue', + products = { + presets.special.blue.infantry:extend({ name='beslan-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='beslan-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelTank:extend({ + name='beslan-fuel-blue', + products = { + presets.missions.supply.helo:extend({name='beslan-supply-blue-1'}), + presets.missions.supply.helo:extend({name='beslan-supply-blue-2'}), + presets.missions.supply.convoy_escorted:extend({name='beslan-supply-blue-3'}), + presets.missions.supply.transfer:extend({name='beslan-transfer-blue'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='beslan-comcenter-blue', + products = { + presets.defenses.blue.patriot:extend({ name='beslan-airdef-blue'}), + presets.missions.attack.sead:extend({name='beslan-sead-blue', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.attack.strike:extend({name='beslan-strike-blue', altitude=30000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.attack.strike:extend({name='beslan-strike-blue-1', altitude=30000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.patrol.aircraft:extend({name='beslan-patrol-blue', altitude=25000, range=25}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Beslan.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Bravo.lua ]]----------------- + +zones.bravo = ZoneCommand:new("Bravo") +zones.bravo.initialState = { side=2 } +zones.bravo:defineUpgrades({ + [1] = + { + presets.upgrades.basic.comPost:extend({ + name='bravo-compost-red', + products = { + presets.special.red.infantry:extend({ name='bravo-defense-red'}), + presets.defenses.red.infantry:extend({ name='bravo-garrison-red'}) + } + }), + presets.upgrades.supply.fuelTankFarp:extend({ + name='bravo-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({ name='bravo-supply-red'}), + presets.missions.supply.transfer:extend({name='bravo-transfer-red'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='bravo-comcenter-red', + products = { + presets.defenses.red.shorad:extend({ name='bravo-airdef-red'}), + presets.missions.attack.helo:extend({name='bravo-attack-red', altitude=200, expend=AI.Task.WeaponExpend.HALF}) + } + }) + }, + [2] = + { + presets.upgrades.basic.comPost:extend({ + name='bravo-compost-blue', + products = { + presets.special.blue.infantry:extend({ name='bravo-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='bravo-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelTankFarp:extend({ + name='bravo-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({ name='bravo-supply-blue'}), + presets.missions.supply.transfer:extend({name='bravo-transfer-blue'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='bravo-comcenter-blue', + products = { + presets.defenses.blue.shorad:extend({ name='bravo-airdef-blue'}), + presets.missions.attack.helo:extend({name='bravo-attack-blue', altitude=200, expend=AI.Task.WeaponExpend.HALF }) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Bravo.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/WeaponDepot.lua ]]----------------- + +zones.weapondepot = ZoneCommand:new("Weapon Depot") +zones.weapondepot.initialState = { side=1 } +zones.weapondepot:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='weapons-tent-red', + products = { + presets.special.red.infantry:extend({ name='weapons-defense-red'}), + presets.defenses.red.infantry:extend({ name='weapons-garrison-red'}) + } + }), + presets.upgrades.supply.ammoDepot:extend({ + name='weapons-ammo-red-1', + products = { + presets.missions.supply.convoy:extend({name='weapons-supply-red-1'}), + presets.missions.supply.transfer:extend({name='weapons-transfer-red-1'}) + } + }), + presets.upgrades.supply.ammoDepot:extend({ + name='weapons-ammo-red-2', + products = { + presets.missions.supply.convoy:extend({name='weapons-supply-red-2'}), + presets.missions.supply.transfer:extend({name='weapons-transfer-red-2'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='weapons-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='weapons-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='weapons-garrison-blue'}) + } + }), + presets.upgrades.supply.ammoDepot:extend({ + name='weapons-ammo-blue-1', + products = { + presets.missions.supply.convoy:extend({name='weapons-supply-blue-1'}), + presets.missions.supply.transfer:extend({name='weapons-transfer-blue-1'}) + } + }), + presets.upgrades.supply.ammoDepot:extend({ + name='weapons-ammo-blue-2', + products = { + presets.missions.supply.convoy:extend({name='weapons-supply-blue-2'}), + presets.missions.supply.transfer:extend({name='weapons-transfer-blue-2'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/WeaponDepot.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Delta.lua ]]----------------- + +zones.delta = ZoneCommand:new("Delta") +zones.delta.initialState = { side=2 } +zones.delta:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='delta-tent-red', + products = { + presets.special.red.infantry:extend({ name='delta-defense-red'}), + presets.defenses.red.infantry:extend({ name='delta-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='delta-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='delta-supply-red'}), + presets.missions.supply.transfer:extend({name='delta-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='delta-ammo-red', + products = { + presets.missions.attack.surface:extend({name='delta-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='delta-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='delta-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='delta-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='delta-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='delta-supply-blue'}), + presets.missions.supply.transfer:extend({name='delta-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='delta-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='delta-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Delta.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Cherkessk.lua ]]----------------- + +zones.cherkessk = ZoneCommand:new("Cherkessk") +zones.cherkessk.initialState = { side=1 } +zones.cherkessk.isHeloSpawn = true +zones.cherkessk:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='cherkessk-tent-red', + products = { + presets.special.red.infantry:extend({ name='cherkessk-defense-red'}), + presets.defenses.red.infantry:extend({ name='cherkessk-garrison-red'}) + } + }), + presets.upgrades.supply.fuelTankFarp:extend({ + name='cherkessk-fuel-red', + products = { + presets.missions.supply.helo:extend({name='cherkessk-supply-red'}), + presets.missions.supply.helo:extend({name='cherkessk-supply-red-1'}), + presets.missions.supply.transfer:extend({name='cherkessk-transfer-red'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='cherkessk-comcenter-red', + products = { + presets.defenses.red.shorad:extend({name='cherkessk-sam-red'}), + presets.missions.attack.helo:extend({name='cherkessk-cas-red', altitude=200, expend=AI.Task.WeaponExpend.HALF }), + presets.missions.attack.helo:extend({name='cherkessk-cas-red-1', altitude=200, expend=AI.Task.WeaponExpend.HALF }), + presets.missions.attack.surface:extend({name='cherkessk-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='cherkessk-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='cherkessk-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='cherkessk-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelTankFarp:extend({ + name='cherkessk-fuel-blue', + products = { + presets.missions.supply.helo:extend({name='cherkessk-supply-blue'}), + presets.missions.supply.helo:extend({name='cherkessk-supply-blue-1'}), + presets.missions.supply.transfer:extend({name='cherkessk-transfer-blue'}), + presets.missions.attack.surface:extend({name='cherkessk-assault-blue'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='cherkessk-comcenter-blue', + products = { + presets.defenses.blue.shorad:extend({name='cherkessk-sam-blue'}), + presets.missions.attack.helo:extend({name='cherkessk-cas-blue', altitude=200, expend=AI.Task.WeaponExpend.HALF }), + presets.missions.attack.helo:extend({name='cherkessk-cas-blue-1', altitude=200, expend=AI.Task.WeaponExpend.HALF }) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Cherkessk.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Juliett.lua ]]----------------- + +zones.juliett = ZoneCommand:new("Juliett") +zones.initialState = nil +zones.juliett:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='juliett-tent-red', + products = { + presets.special.red.infantry:extend({ name='juliett-defense-red'}), + presets.defenses.red.infantry:extend({ name='juliett-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='juliett-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='juliett-supply-red'}), + presets.missions.supply.transfer:extend({name='juliett-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='juliett-ammo-red', + products = { + presets.missions.attack.surface:extend({name='juliett-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='juliett-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='juliett-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='juliett-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='juliett-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='juliett-supply-blue'}), + presets.missions.supply.transfer:extend({name='juliett-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='juliett-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='juliett-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Juliett.lua ]]----------------- + + + + cm = ConnectionManager:new() + cm:addConnection('Batumi', 'Alpha') + cm:addConnection('Alpha', 'Bravo') + cm:addConnection('Bravo', 'Kobuleti') + cm:addConnection('Bravo', 'Factory') + cm:addConnection('Kobuleti', 'Factory') + cm:addConnection('Kobuleti', 'Charlie') + cm:addConnection('Foxtrot', 'Charlie') + cm:addConnection('Foxtrot', 'Kobuleti') + cm:addConnection('Delta','Foxtrot') + cm:addConnection('Delta','Kobuleti') + cm:addConnection('Delta','Factory') + cm:addConnection('Echo','Charlie') + cm:addConnection('Golf','Echo') + cm:addConnection('Golf','Foxtrot') + cm:addConnection('India','Delta') + cm:addConnection('Hotel','Golf') + cm:addConnection('Hotel','Foxtrot') + cm:addConnection('Hotel','Delta') + cm:addConnection('Hotel','India') + cm:addConnection('Juliett','Echo') + cm:addConnection('Juliett','Golf') + cm:addConnection('Senaki','Juliett') + cm:addConnection('Senaki','Golf') + cm:addConnection('Senaki','Hotel') + cm:addConnection('Kutaisi','Hotel') + cm:addConnection('Kutaisi','India') + cm:addConnection('Kilo','Juliett') + cm:addConnection('Mike','Kutaisi') + cm:addConnection('Mike','Senaki') + cm:addConnection('Romeo','Mike') + cm:addConnection('Romeo','Kutaisi') + cm:addConnection('Weapon Depot','Juliett') + cm:addConnection('Weapon Depot','Senaki') + cm:addConnection('Weapon Depot','Kilo') + cm:addConnection('November','Weapon Depot') + cm:addConnection('November','Senaki') + cm:addConnection('November','Mike') + cm:addConnection('Oil Fields','Romeo') + cm:addConnection('Quebec','Kilo') + cm:addConnection('Zugdidi','Weapon Depot') + cm:addConnection('Zugdidi','Quebec') + cm:addConnection('Zugdidi','November') + cm:addConnection('Zugdidi','Kilo') + cm:addConnection('Distillery','November') + cm:addConnection('Distillery','Mike') + cm:addConnection('Zugdidi','Papa') + cm:addConnection('November','Papa') + cm:addConnection('Sierra','Papa') + cm:addConnection('Sierra','Zugdidi') + cm:addConnection('Sierra','Uniform') + cm:addConnection('Mine','Uniform') + cm:addConnection('Tango','Quebec') + cm:addConnection('Tango','Zugdidi') + cm:addConnection('Sierra','Tango') + cm:addConnection('Whiskey','Tango') + cm:addConnection('Ochamchira','Tango') + cm:addConnection('Ochamchira','Whiskey') + cm:addConnection('Ochamchira','Farm') + cm:addConnection('Ochamchira','Zulu') + cm:addConnection('Farm','Zulu') + cm:addConnection('Sukhumi','Zulu') + cm:addConnection('Lentehi','Distillery', true, 3000) + cm:addConnection('Lentehi','Babugent', true, 5000) + cm:addConnection('Nalchik','Babugent') + cm:addConnection('Victor','Distillery', true, 2000) + cm:addConnection('Victor','Romeo') + cm:addConnection('Victor','Lentehi') + cm:addConnection('Victor','Oil Fields', true, 2000) + cm:addConnection('Victor','Oni') + cm:addConnection('Unal','Oni', true, 4500) + cm:addConnection('Beslan','Unal') + cm:addConnection('Digora','Beslan') + cm:addConnection('Digora','Unal') + cm:addConnection('Digora','Babugent') + cm:addConnection('Terek','Digora') + cm:addConnection('Terek','Nalchik') + cm:addConnection('Terek','Beslan') + cm:addConnection('Prohladniy','Terek') + cm:addConnection('Prohladniy','Nalchik') + cm:addConnection('Malgobek','Terek') + cm:addConnection('Malgobek','Beslan') + cm:addConnection('Lima','Mine') + cm:addConnection('Lima','Lentehi', true, 4000) + cm:addConnection('Tyrnyauz','Lima', true, 4000) + cm:addConnection('Tyrnyauz','Nalchik') + cm:addConnection('XRay','Sukhumi') + cm:addConnection('Oscar','Sukhumi') + cm:addConnection('Oscar','XRay') + cm:addConnection('Mozdok','Malgobek') + cm:addConnection('Mozdok','Prohladniy') + cm:addConnection('Gudauta','Oscar') + cm:addConnection('Yankee','Gudauta') + cm:addConnection('Sochi','Yankee') + cm:addConnection('Refinery','XRay', true, 4000) + cm:addConnection('Refinery','Humara') + cm:addConnection('Intel Center','Tyrnyauz') + cm:addConnection('Intel Center','Nalchik') + cm:addConnection('Intel Center','Prohladniy') + cm:addConnection('Intel Center','Kislovodsk') + cm:addConnection('Mineralnye','Intel Center') + cm:addConnection('Kislovodsk','Mineralnye') + cm:addConnection('Tallyk','Mineralnye') + cm:addConnection('Tallyk','Kislovodsk') + cm:addConnection('Power Plant','Mineralnye') + cm:addConnection('Power Plant','Tallyk') + cm:addConnection('Cherkessk','Tallyk') + cm:addConnection('Cherkessk','Power Plant') + cm:addConnection('Cherkessk','Humara') +end + +ZoneCommand.setNeighbours(cm) + +bm = BattlefieldManager:new() + +mc = MarkerCommands:new() + +pt = PlayerTracker:new(mc) + +mt = MissionTracker:new(pt, mc) + +st = SquadTracker:new() + +ct = CSARTracker:new() + +pl = PlayerLogistics:new(mt, pt, st, ct) + +gci = GCI:new(2) + +gm = GroupMonitor:new(cm) +ZoneCommand.groupMonitor = gm + +-- PlayerLogistics:registerSquadGroup(squadType, groupname, weight,cost,jobtime,extracttime, squadSize) +pl:registerSquadGroup(PlayerLogistics.infantryTypes.capture, 'capture-squad', 700, 200, 60, 60*30, 4) +pl:registerSquadGroup(PlayerLogistics.infantryTypes.sabotage, 'sabotage-squad', 800, 500, 60*5, 60*30, 4) +pl:registerSquadGroup(PlayerLogistics.infantryTypes.ambush, 'ambush-squad', 900, 300, 60*20, 60*30, 5) +pl:registerSquadGroup(PlayerLogistics.infantryTypes.engineer, 'engineer-squad', 200, 1000,60, 60*30, 2) +pl:registerSquadGroup(PlayerLogistics.infantryTypes.manpads, 'manpads-squad', 900, 500, 60*20, 60*30, 5) +pl:registerSquadGroup(PlayerLogistics.infantryTypes.spy, 'spy-squad', 100, 300, 60*10, 60*30, 1) +pl:registerSquadGroup(PlayerLogistics.infantryTypes.rapier, 'rapier-squad', 1200,2000,60*60, 60*30, 8) + +Group.getByName('jtacDrone'):destroy() +CommandFunctions.jtac = JTAC:new({name = 'jtacDrone'}) + +pm = PersistenceManager:new(savefile, gm, st, ct, pl) +pm:load() + +if pm:canRestore() then + pm:restoreZones() + pm:restoreAIMissions() + pm:restoreBattlefield() + pm:restoreCsar() + pm:restoreSquads() +else + --initial states + Starter.start(zones) +end + +timer.scheduleFunction(function(param, time) + pm:save() + env.info("Mission state saved") + return time+60 +end, zones, timer.getTime()+60) + + +--make sure support units are present where needed +ensureSpawn = { + ['golf-farp-suport'] = zones.golf, + ['november-farp-suport'] = zones.november, + ['tango-farp-suport'] = zones.tango, + ['sierra-farp-suport'] = zones.sierra, + ['cherkessk-farp-suport'] = zones.cherkessk, + ['unal-farp-suport'] = zones.unal, + ['tyrnyauz-farp-suport'] = zones.tyrnyauz +} + +for grname, zn in pairs(ensureSpawn) do + local g = Group.getByName(grname) + if g then g:destroy() end +end + +timer.scheduleFunction(function(param, time) + + for grname, zn in pairs(ensureSpawn) do + local g = Group.getByName(grname) + if zn.side == 2 then + if not g then + local err, msg = pcall(mist.respawnGroup,grname,true) + if not err then + env.info("ERROR spawning "..grname) + env.info(msg) + end + end + else + if g then g:destroy() end + end + end + + return time+30 +end, {}, timer.getTime()+30) + + +--supply injection +local blueSupply = {'offmap-supply-blue-1','offmap-supply-blue-2','offmap-supply-blue-3','offmap-supply-blue-4','offmap-supply-blue-5'} +local redSupply = {'offmap-supply-red-1','offmap-supply-red-2','offmap-supply-red-3','offmap-supply-red-4','offmap-supply-red-5'} +local offmapZones = { + zones.batumi, + zones.sochi, + zones.nalchik, + zones.beslan, + zones.mozdok, + zones.mineralnye, +-- zones.senaki, +-- zones.sukhumi, +-- zones.gudauta, +-- zones.kobuleti, +} + +supplyPointRegistry = { + blue = {}, + red = {} +} + +for i,v in ipairs(blueSupply) do + local g = Group.getByName(v) + if g then + supplyPointRegistry.blue[v] = g:getUnit(1):getPoint() + end +end + +for i,v in ipairs(redSupply) do + local g = Group.getByName(v) + if g then + supplyPointRegistry.red[v] = g:getUnit(1):getPoint() + end +end + +offmapSupplyRegistry = {} +timer.scheduleFunction(function(param, time) + local availableBlue = {} + for i,v in ipairs(param.blue) do + if offmapSupplyRegistry[v] == nil then + table.insert(availableBlue, v) + end + end + + local availableRed = {} + for i,v in ipairs(param.red) do + if offmapSupplyRegistry[v] == nil then + table.insert(availableRed, v) + end + end + + local redtargets = {} + local bluetargets = {} + for _, zn in ipairs(param.offmapZones) do + if zn:needsSupplies(3000) then + local isOnRoute = false + for _,data in pairs(offmapSupplyRegistry) do + if data.zone.name == zn.name then + isOnRoute = true + break + end + end + if not isOnRoute then + if zn.side == 1 then + table.insert(redtargets, zn) + elseif zn.side == 2 then + table.insert(bluetargets, zn) + end + end + end + end + + if #availableRed > 0 and #redtargets > 0 then + local zn = redtargets[math.random(1,#redtargets)] + + local red = nil + local minD = 999999999 + for i,v in ipairs(availableRed) do + local d = mist.utils.get2DDist(zn.zone.point, supplyPointRegistry.red[v]) + if d < minD then + red = v + minD = d + end + end + + if not red then red = availableRed[math.random(1,#availableRed)] end + + local gr = red + red = nil + mist.respawnGroup(gr, true) + offmapSupplyRegistry[gr] = {zone = zn, assigned = timer.getAbsTime()} + env.info(gr..' was deployed') + timer.scheduleFunction(function(param,time) + local g = Group.getByName(param.group) + TaskExtensions.landAtAirfield(g, param.target.zone.point) + env.info(param.group..' going to '..param.target.name) + end, {group=gr, target=zn}, timer.getTime()+2) + end + + if #availableBlue > 0 and #bluetargets>0 then + local zn = bluetargets[math.random(1,#bluetargets)] + + local blue = nil + local minD = 999999999 + for i,v in ipairs(availableBlue) do + local d = mist.utils.get2DDist(zn.zone.point, supplyPointRegistry.blue[v]) + if d < minD then + blue = v + minD = d + end + end + + if not blue then blue = availableBlue[math.random(1,#availableBlue)] end + + local gr = blue + blue = nil + mist.respawnGroup(gr, true) + offmapSupplyRegistry[gr] = {zone = zn, assigned = timer.getAbsTime()} + env.info(gr..' was deployed') + timer.scheduleFunction(function(param,time) + local g = Group.getByName(param.group) + TaskExtensions.landAtAirfield(g, param.target.zone.point) + env.info(param.group..' going to '..param.target.name) + end, {group=gr, target=zn}, timer.getTime()+2) + end + + return time+(60*5) +end, {blue = blueSupply, red = redSupply, offmapZones = offmapZones}, timer.getTime()+60) + + + +timer.scheduleFunction(function(param, time) + + for groupname,data in pairs(offmapSupplyRegistry) do + local gr = Group.getByName(groupname) + if not gr then + offmapSupplyRegistry[groupname] = nil + env.info(groupname..' was destroyed') + end + + if gr and ((timer.getAbsTime() - data.assigned) > (60*60)) then + gr:destroy() + offmapSupplyRegistry[groupname] = nil + env.info(groupname..' despawned due to being alive for too long') + end + + if gr and Utils.allGroupIsLanded(gr) and Utils.someOfGroupInZone(gr, data.zone.name) then + data.zone:addResource(15000) + gr:destroy() + offmapSupplyRegistry[groupname] = nil + env.info(groupname..' landed at '..data.zone.name..' and delivered 15000 resources') + end + end + + return time+180 +end, {}, timer.getTime()+180) \ No newline at end of file diff --git a/resources/plugins/pretense/init_header.lua b/resources/plugins/pretense/init_header.lua new file mode 100644 index 00000000..9df8452d --- /dev/null +++ b/resources/plugins/pretense/init_header.lua @@ -0,0 +1,4670 @@ + + +local savefile = 'pretense_1.1.json' +if lfs then + local dir = lfs.writedir()..'Missions/Saves/' + lfs.mkdir(dir) + savefile = dir..savefile + env.info('Pretense - Save file path: '..savefile) +end + + +do + TemplateDB.templates["infantry-red"] = { + units = { + "BTR_D", + "T-90", + "T-90", + "Infantry AK ver2", + "Infantry AK", + "Infantry AK", + "Paratrooper RPG-16", + "Infantry AK ver3", + "SA-18 Igla manpad" + }, + skill = "Excellent", + dataCategory= TemplateDB.type.group + } + + TemplateDB.templates["infantry-blue"] = { + units = { + "M1045 HMMWV TOW", + "Soldier stinger", + "Soldier M4 GRG", + "Soldier M4 GRG", + "Soldier M4 GRG", + "Soldier M4 GRG", + "Soldier M4 GRG", + "M1043 HMMWV Armament" + }, + skill = "Excellent", + dataCategory= TemplateDB.type.group + } + + TemplateDB.templates["defense-red"] = { + units = { + "Infantry AK ver2", + "Infantry AK", + "Infantry AK ver3", + "Paratrooper RPG-16", + "SA-18 Igla manpad" + }, + skill = "Excellent", + dataCategory= TemplateDB.type.group + } + + TemplateDB.templates["defense-blue"] = { + units = { + "Soldier M4 GRG", + "Soldier M4 GRG", + "Soldier M4 GRG", + "Soldier RPG", + "Soldier stinger", + }, + skill = "Excellent", + dataCategory= TemplateDB.type.group + } + + TemplateDB.templates["shorad-red"] = { + units = { + "Strela-10M3", + "Strela-10M3", + "Ural-4320T", + "2S6 Tunguska" + }, + maxDist = 300, + skill = "Excellent", + dataCategory= TemplateDB.type.group + } + + TemplateDB.templates["shorad-blue"] = { + units = { + "Roland ADS", + "M48 Chaparral", + "M 818", + "Gepard", + "Gepard" + }, + maxDist = 300, + skill = "Excellent", + dataCategory= TemplateDB.type.group + } + + TemplateDB.templates["sam-red"] = { + units = { + "p-19 s-125 sr", + "Ural-4320T", + "Ural-4320T", + "S_75M_Volhov", + "S_75M_Volhov", + "S_75M_Volhov", + "S_75M_Volhov", + "S_75M_Volhov", + "Tor 9A331", + "SNR_75V" + }, + maxDist = 300, + skill = "Excellent", + dataCategory= TemplateDB.type.group + } + + TemplateDB.templates["sam-blue"] = { + units = { + "Hawk pcp", + "Hawk cwar", + "Hawk ln", + "Hawk ln", + "Hawk ln", + "Hawk ln", + "Hawk ln", + "Hawk tr", + "M 818", + "Hawk sr" + }, + maxDist = 300, + skill = "Good", + dataCategory= TemplateDB.type.group + } + + TemplateDB.templates["patriot"] = { + units = { + "Patriot cp", + "Patriot str", + "M 818", + "M 818", + "Patriot ln", + "Patriot ln", + "Patriot ln", + "Patriot ln", + "Patriot str", + "Patriot str", + "Patriot str", + "Patriot EPP", + "Patriot ECS", + "Patriot AMG" + }, + maxDist = 300, + skill = "Good", + dataCategory= TemplateDB.type.group + } + + TemplateDB.templates["sa3"] = { + units = { + "p-19 s-125 sr", + "snr s-125 tr", + "5p73 s-125 ln", + "5p73 s-125 ln", + "Ural-4320T", + "5p73 s-125 ln", + "5p73 s-125 ln" + }, + maxDist = 300, + skill = "Excellent", + dataCategory= TemplateDB.type.group + } + + TemplateDB.templates["sa6"] = { + units = { + "Kub 1S91 str", + "Kub 2P25 ln", + "Kub 2P25 ln", + "Kub 2P25 ln", + "Kub 2P25 ln", + "2S6 Tunguska", + "Ural-4320T", + "2S6 Tunguska", + "Kub 2P25 ln" + }, + maxDist = 300, + skill = "Excellent", + dataCategory= TemplateDB.type.group + } + + TemplateDB.templates["sa10"] = { + units = { + "S-300PS 54K6 cp", + "S-300PS 5P85C ln", + "S-300PS 5P85C ln", + "S-300PS 5P85C ln", + "GAZ-66", + "GAZ-66", + "GAZ-66", + "S-300PS 5P85C ln", + "S-300PS 5P85C ln", + "S-300PS 5P85C ln", + "S-300PS 40B6MD sr", + "S-300PS 40B6M tr", + "S-300PS 64H6E sr" + }, + maxDist = 300, + skill = "Excellent", + dataCategory= TemplateDB.type.group + } + + TemplateDB.templates["sa5"] = { + units = { + "RLS_19J6", + "Ural-4320T", + "Ural-4320T", + "RPC_5N62V", + "S-200_Launcher", + "S-200_Launcher", + "S-200_Launcher", + "S-200_Launcher", + "S-200_Launcher", + "S-200_Launcher" + }, + maxDist = 300, + skill = "Excellent", + dataCategory= TemplateDB.type.group + } + + TemplateDB.templates["sa11"] = { + units = { + "SA-11 Buk SR 9S18M1", + "SA-11 Buk LN 9A310M1", + "SA-11 Buk LN 9A310M1", + "SA-11 Buk LN 9A310M1", + "SA-11 Buk LN 9A310M1", + "SA-11 Buk LN 9A310M1", + "2S6 Tunguska", + "SA-11 Buk SR 9S18M1", + "GAZ-66", + "GAZ-66", + "SA-11 Buk CC 9S470M1" + }, + maxDist = 300, + skill = "Excellent", + dataCategory= TemplateDB.type.group + } + + TemplateDB.templates["nasams"] = { + units = { + "NASAMS_Command_Post", + "NASAMS_Radar_MPQ64F1", + "Vulcan", + "M 818", + "M 818", + "Roland ADS", + "Roland ADS", + "NASAMS_LN_C", + "NASAMS_LN_C", + "NASAMS_LN_C", + "NASAMS_LN_C", + "NASAMS_Radar_MPQ64F1", + "NASAMS_Radar_MPQ64F1", + "NASAMS_Radar_MPQ64F1" + }, + maxDist = 300, + skill = "Excellent", + dataCategory= TemplateDB.type.group + } +end + +presets = { + upgrades = { + basic = { + tent = Preset:new({ + display = 'Tent', + cost = 1500, + type = 'upgrade', + template = "tent" + }), + comPost = Preset:new({ + display = 'Barracks', + cost = 1500, + type = 'upgrade', + template = "barracks" + }), + outpost = Preset:new({ + display = 'Outpost', + cost = 1500, + type = 'upgrade', + template = "outpost" + }) + }, + attack = { + ammoCache = Preset:new({ + display = 'Ammo Cache', + cost = 1500, + type = 'upgrade', + template = "ammo-cache" + }), + ammoDepot = Preset:new({ + display = 'Ammo Depot', + cost = 2000, + type = 'upgrade', + template = "ammo-depot" + }) + }, + supply = { + fuelCache = Preset:new({ + display = 'Fuel Cache', + cost = 1500, + type = 'upgrade', + template = "fuel-cache" + }), + fuelTank = Preset:new({ + display = 'Fuel Tank', + cost = 1500, + type = 'upgrade', + template = "fuel-tank-big" + }), + fuelTankFarp = Preset:new({ + display = 'Fuel Tank', + cost = 1500, + type = 'upgrade', + template = "fuel-tank-small" + }), + factory1 = Preset:new({ + display='Factory', + cost = 2000, + type ='upgrade', + income = 20, + template = "factory-1" + }), + factory2 = Preset:new({ + display='Factory', + cost = 2000, + type ='upgrade', + income = 20, + template = "factory-2" + }), + factoryTank = Preset:new({ + display='Storage Tank', + cost = 1500, + type ='upgrade', + income = 10, + template = "chem-tank" + }), + ammoDepot = Preset:new({ + display = 'Ammo Depot', + cost = 2000, + type = 'upgrade', + income = 40, + template = "ammo-depot" + }), + oilPump = Preset:new({ + display = 'Oil Pump', + cost = 1500, + type = 'upgrade', + income = 20, + template = "oil-pump" + }), + hangar = Preset:new({ + display = 'Hangar', + cost = 2000, + type = 'upgrade', + income = 30, + template = "hangar" + }), + excavator = Preset:new({ + display = 'Excavator', + cost = 2000, + type = 'upgrade', + income = 20, + template = "excavator" + }), + farm1 = Preset:new({ + display = 'Farm House', + cost = 2000, + type = 'upgrade', + income = 40, + template = "farm-house-1" + }), + farm2 = Preset:new({ + display = 'Farm House', + cost = 2000, + type = 'upgrade', + income = 40, + template = "farm-house-2" + }), + refinery1 = Preset:new({ + display='Refinery', + cost = 2000, + type ='upgrade', + income = 100, + template = "factory-1" + }), + powerplant1 = Preset:new({ + display='Power Plant', + cost = 1500, + type ='upgrade', + income = 25, + template = "factory-1" + }), + powerplant2 = Preset:new({ + display='Power Plant', + cost = 1500, + type ='upgrade', + income = 25, + template = "factory-2" + }), + antenna = Preset:new({ + display='Antenna', + cost = 1000, + type ='upgrade', + income = 10, + template = "antenna" + }), + hq = Preset:new({ + display='HQ Building', + cost = 2000, + type ='upgrade', + income = 50, + template = "tv-tower" + }) + }, + airdef = { + comCenter = Preset:new({ + display = 'Command Center', + cost = 2500, + type = 'upgrade', + template = "command-center" + }) + } + }, + defenses = { + red = { + infantry = Preset:new({ + display = 'Infantry', + cost=2000, + type='defense', + template='infantry-red', + }), + shorad = Preset:new({ + display = 'SAM', + cost=2500, + type='defense', + template='shorad-red', + }), + sam = Preset:new({ + display = 'SAM', + cost=3000, + type='defense', + template='sam-red', + }), + sa10 = Preset:new({ + display = 'SAM', + cost=3000, + type='defense', + template='sa10', + }), + sa5 = Preset:new({ + display = 'SAM', + cost=3000, + type='defense', + template='sa5', + }), + sa3 = Preset:new({ + display = 'SAM', + cost=3000, + type='defense', + template='sa3', + }), + sa6 = Preset:new({ + display = 'SAM', + cost=3000, + type='defense', + template='sa6', + }), + sa11 = Preset:new({ + display = 'SAM', + cost=3000, + type='defense', + template='sa11', + }) + }, + blue = { + infantry = Preset:new({ + display = 'Infantry', + cost=2000, + type='defense', + template='infantry-blue', + }), + shorad = Preset:new({ + display = 'SAM', + cost=2500, + type='defense', + template='shorad-blue', + }), + sam = Preset:new({ + display = 'SAM', + cost=3000, + type='defense', + template='sam-blue', + }), + patriot = Preset:new({ + display = 'SAM', + cost=3000, + type='defense', + template='patriot', + }), + nasams = Preset:new({ + display = 'SAM', + cost=3000, + type='defense', + template='nasams', + }) + } + }, + missions = { + supply = { + convoy = Preset:new({ + display = 'Supply convoy', + cost = 4000, + type = 'mission', + missionType = ZoneCommand.missionTypes.supply_convoy + }), + convoy_escorted = Preset:new({ + display = 'Supply convoy', + cost = 3000, + type = 'mission', + missionType = ZoneCommand.missionTypes.supply_convoy + }), + helo = Preset:new({ + display = 'Supply helicopter', + cost = 2500, + type='mission', + missionType = ZoneCommand.missionTypes.supply_air + }), + transfer = Preset:new({ + display = 'Supply transfer', + cost = 1000, + type='mission', + missionType = ZoneCommand.missionTypes.supply_transfer + }) + }, + attack = { + surface = Preset:new({ + display = 'Ground assault', + cost = 100, + type = 'mission', + missionType = ZoneCommand.missionTypes.assault, + }), + cas = Preset:new({ + display = 'CAS', + cost = 200, + type='mission', + missionType = ZoneCommand.missionTypes.cas + }), + bai = Preset:new({ + display = 'BAI', + cost = 200, + type='mission', + missionType = ZoneCommand.missionTypes.bai + }), + strike = Preset:new({ + display = 'Strike', + cost = 300, + type='mission', + missionType = ZoneCommand.missionTypes.strike + }), + sead = Preset:new({ + display = 'SEAD', + cost = 200, + type='mission', + missionType = ZoneCommand.missionTypes.sead + }), + helo = Preset:new({ + display = 'CAS', + cost = 100, + type='mission', + missionType = ZoneCommand.missionTypes.cas_helo + }) + }, + patrol={ + aircraft = Preset:new({ + display= "Patrol", + cost = 100, + type='mission', + missionType = ZoneCommand.missionTypes.patrol + }) + }, + support ={ + awacs = Preset:new({ + display= "AWACS", + cost = 300, + type='mission', + bias='5', + missionType = ZoneCommand.missionTypes.awacs + }), + tanker = Preset:new({ + display= "Tanker", + cost = 200, + type='mission', + bias='2', + missionType = ZoneCommand.missionTypes.tanker + }) + } + }, + special = { + red = { + infantry = Preset:new({ + display = 'Infantry', + cost=-1, + type='defense', + template='defense-red', + }), + }, + blue = { + infantry = Preset:new({ + display = 'Infantry', + cost=-1, + type='defense', + template='defense-blue', + }) + } + } +} + +zones = {} +do + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Batumi.lua ]]----------------- + +zones.batumi = ZoneCommand:new('Batumi') +zones.batumi.initialState = { side=2 } +zones.batumi.keepActive = true +zones.batumi.isHeloSpawn = true +zones.batumi.isPlaneSpawn = true +zones.batumi.maxResource = 50000 +zones.batumi:defineUpgrades({ + [1] = { --red side + presets.upgrades.basic.comPost:extend({ + name = 'batumi-com-red', + products = { + presets.special.red.infantry:extend({ name='batumi-defense-red'}), + presets.defenses.red.infantry:extend({ name='batumi-garrison-red' }) + } + }), + }, + [2] = --blue side + { + presets.upgrades.basic.comPost:extend({ + name = 'batumi-com-blue', + products = { + presets.special.blue.infantry:extend({ name='batumi-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='batumi-garrison-blue' }) + } + }), + presets.upgrades.supply.fuelTank:extend({ + name = 'batumi-fueltank-blue', + products = { + presets.missions.supply.convoy_escorted:extend({ name='batumi-supply-convoy-1'}), + presets.missions.supply.helo:extend({ name='batumi-supply-blue-1' }), + presets.missions.supply.transfer:extend({name='batumi-transfer-blue'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name = 'batumi-mission-command-blue', + products = { + presets.defenses.blue.shorad:extend({ name='batumi-sam-blue' }), + presets.missions.attack.sead:extend({name='batumi-sead-blue-1', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.attack.cas:extend({name='batumi-cas-blue-1', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.bai:extend({name='batumi-cas-blue-1', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.strike:extend({name='batumi-strike-blue-1', altitude=20000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.patrol.aircraft:extend({name='batumi-patrol-blue-1', altitude=25000, range=25}), + presets.missions.support.awacs:extend({name='batumi-awacs-blue', altitude=30000, freq=257.5}), + presets.missions.support.tanker:extend({name='batumi-tanker-blue', altitude=25000, freq=257, tacan='37', variant="Drogue"}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Batumi.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Mike.lua ]]----------------- + +zones.mike = ZoneCommand:new("Mike") +zones.mike.initialState = { side=1 } +zones.mike.keepActive = true +zones.mike:defineUpgrades({ + [1] = + { + presets.upgrades.basic.tent:extend({ + name='mike-tent-red', + products = { + presets.special.red.infantry:extend({ name='mike-defense-red'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='mike-comcenter-red', + products = { + presets.defenses.red.sam:extend({ name='mike-sam-red'}) + } + }) + }, + [2] = + { + presets.upgrades.basic.tent:extend({ + name='mike-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='mike-defense-blue'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='mike-comcenter-blue', + products = { + presets.defenses.blue.sam:extend({ name='mike-sam-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Mike.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Tyrnyauz.lua ]]----------------- + +zones.tyrnyauz = ZoneCommand:new("Tyrnyauz") +zones.tyrnyauz.initialState = { side=1 } +zones.tyrnyauz.isHeloSpawn = true +zones.tyrnyauz:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='tyrnyauz-tent-red', + products = { + presets.special.red.infantry:extend({ name='tyrnyauz-defense-red'}), + presets.defenses.red.infantry:extend({ name='tyrnyauz-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='tyrnyauz-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='tyrnyauz-supply-red'}), + presets.missions.supply.helo:extend({name='tyrnyauz-supply-red-2'}), + presets.missions.supply.transfer:extend({name='tyrnyauz-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='tyrnyauz-ammo-red', + products = { + presets.missions.attack.surface:extend({name='tyrnyauz-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='tyrnyauz-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='tyrnyauz-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='tyrnyauz-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='tyrnyauz-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='tyrnyauz-supply-blue'}), + presets.missions.supply.helo:extend({name='tyrnyauz-supply-blue-2'}), + presets.missions.supply.transfer:extend({name='tyrnyauz-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='tyrnyauz-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='tyrnyauz-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Tyrnyauz.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/India.lua ]]----------------- + +zones.india = ZoneCommand:new("India") +zones.india.initialState = nil +zones.india:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='india-tent-red', + products = { + presets.special.red.infantry:extend({ name='india-defense-red'}), + presets.defenses.red.infantry:extend({ name='india-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='india-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='india-supply-red'}), + presets.missions.supply.transfer:extend({name='india-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='india-ammo-red', + products = { + presets.missions.attack.surface:extend({name='india-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='india-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='india-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='india-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='india-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='india-supply-blue'}), + presets.missions.supply.transfer:extend({name='india-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='india-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='india-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/India.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/IntelCenter.lua ]]----------------- + +zones.intelcenter = ZoneCommand:new("Intel Center") +zones.intelcenter.initialState = { side=1 } +zones.intelcenter:defineUpgrades({ + [1] = + { + presets.upgrades.basic.tent:extend({ + name='intelcenter-tent-red', + products = { + presets.special.red.infantry:extend({ name='intelcenter-defense-red'}), + presets.defenses.red.infantry:extend({ name='intelcenter-garrison-red'}) + } + }), + presets.upgrades.supply.hq:extend({ + name='intelcenter-hq-red', + products = { + presets.missions.supply.convoy:extend({ name='intelcenter-supply-red'}), + presets.missions.supply.convoy:extend({ name='intelcenter-supply-red-1'}), + presets.missions.supply.transfer:extend({name='intelcenter-transfer-red'}) + } + }), + presets.upgrades.supply.antenna:extend({ + name='intelcenter-antenna-red', + products = { + } + }), + presets.upgrades.supply.antenna:extend({ + name='intelcenter-antenna-red-1', + products = { + } + }), + presets.upgrades.supply.antenna:extend({ + name='intelcenter-antenna-red-2', + products = { + } + }) + }, + [2] = + { + presets.upgrades.basic.tent:extend({ + name='intelcenter-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='intelcenter-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='intelcenter-garrison-blue'}) + } + }), + presets.upgrades.supply.hq:extend({ + name='intelcenter-hq-blue', + products = { + presets.missions.supply.convoy:extend({ name='intelcenter-supply-blue'}), + presets.missions.supply.convoy:extend({ name='intelcenter-supply-blue-1'}), + presets.missions.supply.transfer:extend({name='intelcenter-transfer-blue'}) + } + }), + presets.upgrades.supply.antenna:extend({ + name='intelcenter-antenna-blue', + products = { + } + }), + presets.upgrades.supply.antenna:extend({ + name='intelcenter-antenna-blue-1', + products = { + } + }), + presets.upgrades.supply.antenna:extend({ + name='intelcenter-antenna-blue-2', + products = { + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/IntelCenter.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Mineralnye.lua ]]----------------- + +zones.mineralnye = ZoneCommand:new("Mineralnye") +zones.mineralnye.initialState = { side=1 } +zones.mineralnye.keepActive = true +zones.mineralnye.isHeloSpawn = true +zones.mineralnye.isPlaneSpawn = true +zones.mineralnye:defineUpgrades({ + [1] = + { + presets.upgrades.basic.comPost:extend({ + name='mineralnye-compost-red', + products = { + presets.special.red.infantry:extend({ name='mineralnye-defense-red'}), + presets.defenses.red.infantry:extend({ name='mineralnye-garrison-red'}) + } + }), + presets.upgrades.supply.fuelTank:extend({ + name='mineralnye-fuel-red', + products = { + presets.missions.supply.helo:extend({name='mineralnye-supply-red'}), + presets.missions.supply.helo:extend({name='mineralnye-supply-red-1'}), + presets.missions.supply.transfer:extend({name='mineralnye-transfer-red'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='mineralnye-comcenter-red', + products = { + presets.defenses.red.sa11:extend({ name='mineralnye-airdef-red'}), + presets.missions.attack.cas:extend({name='mineralnye-cas-red', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.bai:extend({name='mineralnye-cas-red', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.strike:extend({name='mineralnye-strike-red', altitude=30000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.attack.strike:extend({name='mineralnye-strike-red-1', altitude=30000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.patrol.aircraft:extend({name='mineralnye-patrol-red', altitude=25000, range=25}) + } + }) + }, + [2] = + { + presets.upgrades.basic.comPost:extend({ + name='mineralnye-compost-blue', + products = { + presets.special.blue.infantry:extend({ name='mineralnye-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='mineralnye-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelTank:extend({ + name='mineralnye-fuel-blue', + products = { + presets.missions.supply.helo:extend({name='mineralnye-supply-blue'}), + presets.missions.supply.helo:extend({name='mineralnye-supply-blue-1'}), + presets.missions.supply.transfer:extend({name='mineralnye-transfer-blue'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='mineralnye-comcenter-blue', + products = { + presets.defenses.blue.nasams:extend({ name='mineralnye-airdef-blue'}), + presets.missions.attack.cas:extend({name='mineralnye-cas-blue', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.bai:extend({name='mineralnye-cas-blue', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.strike:extend({name='mineralnye-strike-blue', altitude=30000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.attack.strike:extend({name='mineralnye-strike-blue-1', altitude=30000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.patrol.aircraft:extend({name='mineralnye-patrol-blue', altitude=25000, range=25}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Mineralnye.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/PowerPlant.lua ]]----------------- + +zones.powerplant = ZoneCommand:new("Power Plant") +zones.powerplant.initialState = { side=1 } +zones.powerplant:defineUpgrades({ + [1] = + { + presets.upgrades.basic.tent:extend({ + name='powerplant-tent-red', + products = { + presets.special.red.infantry:extend({ name='powerplant-defense-red'}), + presets.defenses.red.infantry:extend({ name='powerplant-garrison-red'}) + } + }), + presets.upgrades.supply.powerplant1:extend({ + name='powerplant-building-red-1', + products = { + presets.missions.supply.convoy:extend({ name='powerplant-supply-red'}), + presets.missions.supply.transfer:extend({name='powerplant-transfer-red'}) + } + }), + presets.upgrades.supply.powerplant2:extend({ + name='powerplant-building-red-2', + products = { + presets.missions.supply.convoy:extend({ name='powerplant-supply-red-1'}), + presets.missions.supply.transfer:extend({name='powerplant-transfer-red'}) + } + }) + }, + [2] = + { + presets.upgrades.basic.tent:extend({ + name='powerplant-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='powerplant-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='powerplant-garrison-blue'}) + } + }), + presets.upgrades.supply.powerplant1:extend({ + name='powerplant-building-blue-1', + products = { + presets.missions.supply.convoy:extend({ name='powerplant-supply-blue'}), + presets.missions.supply.transfer:extend({name='powerplant-transfer-blue'}) + } + }), + presets.upgrades.supply.powerplant2:extend({ + name='powerplant-building-blue-2', + products = { + presets.missions.supply.convoy:extend({ name='powerplant-supply-blue-1'}), + presets.missions.supply.transfer:extend({name='powerplant-transfer-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/PowerPlant.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Zugdidi.lua ]]----------------- + +zones.zugdidi = ZoneCommand:new("Zugdidi") +zones.zugdidi.initialState = { side=1 } +zones.zugdidi:defineUpgrades({ + [1] = + { + presets.upgrades.basic.comPost:extend({ + name='zugdidi-compost-red', + products = { + presets.missions.supply.transfer:extend({name='zugdidi-transfer-red'}), + presets.special.red.infantry:extend({ name='zugdidi-defense-red'}), + presets.defenses.red.infantry:extend({ name='zugdidi-garrison-red'}), + presets.missions.attack.surface:extend({name='zugdidi-attack-red'}), + presets.missions.supply.convoy:extend({name='zugdidi-supply-red'}) + } + }), + presets.upgrades.supply.hangar:extend({ + name='zugdidi-hangar-red-1', + products = { + presets.missions.supply.helo:extend({name='zugdidi-supply-red-1'}) + } + }), + presets.upgrades.supply.hangar:extend({ + name='zugdidi-hangar-red-2', + products = { + presets.missions.supply.helo:extend({name='zugdidi-supply-red-2'}) + } + }), + presets.upgrades.supply.hangar:extend({ + name='zugdidi-hangar-red-3', + products = { + presets.missions.supply.helo:extend({name='zugdidi-supply-red-3'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='zugdidi-comcenter-red', + products = { + presets.defenses.red.sa6:extend({ name='zugdidi-sam-red'}) + } + }) + }, + [2] = + { + presets.upgrades.basic.tent:extend({ + name='zugdidi-compost-blue', + products = { + presets.missions.supply.transfer:extend({name='zugdidi-transfer-blue'}), + presets.special.blue.infantry:extend({ name='zugdidi-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='zugdidi-garrison-blue'}), + presets.missions.attack.surface:extend({name='zugdidi-attack-blue'}), + presets.missions.supply.convoy:extend({name='zugdidi-supply-blue'}) + } + }), + presets.upgrades.supply.hangar:extend({ + name='zugdidi-hangar-blue-1', + products = { + presets.missions.supply.helo:extend({name='zugdidi-supply-blue-1'}) + } + }), + presets.upgrades.supply.hangar:extend({ + name='zugdidi-hangar-blue-2', + products = { + presets.missions.supply.helo:extend({name='zugdidi-supply-blue-2'}) + } + }), + presets.upgrades.supply.hangar:extend({ + name='zugdidi-hangar-blue-3', + products = { + presets.missions.supply.helo:extend({name='zugdidi-supply-blue-3'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='zugdidi-comcenter-blue', + products = { + presets.defenses.blue.shorad:extend({ name='zugdidi-sam-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Zugdidi.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Babugent.lua ]]----------------- + +zones.babugent = ZoneCommand:new("Babugent") +zones.babugent.initialState = { side=1 } +zones.babugent:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='babugent-tent-red', + products = { + presets.special.red.infantry:extend({ name='babugent-defense-red'}), + presets.defenses.red.infantry:extend({ name='babugent-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='babugent-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='babugent-supply-red'}), + presets.missions.supply.helo:extend({name='babugent-supply-red-2'}), + presets.missions.supply.transfer:extend({name='babugent-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='babugent-ammo-red', + products = { + presets.missions.attack.surface:extend({name='babugent-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='babugent-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='babugent-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='babugent-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='babugent-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='babugent-supply-blue'}), + presets.missions.supply.helo:extend({name='babugent-supply-blue-2'}), + presets.missions.supply.transfer:extend({name='babugent-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='babugent-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='babugent-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Babugent.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Kislovodsk.lua ]]----------------- + +zones.kislovodsk = ZoneCommand:new("Kislovodsk") +zones.kislovodsk.initialState = { side=1 } +zones.kislovodsk:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='kislovodsk-tent-red', + products = { + presets.special.red.infantry:extend({ name='kislovodsk-defense-red'}), + presets.defenses.red.infantry:extend({ name='kislovodsk-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='kislovodsk-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='kislovodsk-supply-red'}), + presets.missions.supply.transfer:extend({name='kislovodsk-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='kislovodsk-ammo-red', + products = { + presets.missions.attack.surface:extend({name='kislovodsk-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='kislovodsk-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='kislovodsk-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='kislovodsk-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='kislovodsk-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='kislovodsk-supply-blue'}), + presets.missions.supply.transfer:extend({name='kislovodsk-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='kislovodsk-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='kislovodsk-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Kislovodsk.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Gudauta.lua ]]----------------- + +zones.gudauta = ZoneCommand:new("Gudauta") +zones.gudauta.initialState = { side=1 } +zones.gudauta.keepActive = true +zones.gudauta.maxResource = 50000 +zones.gudauta:defineUpgrades({ + [1] = + { + presets.upgrades.basic.comPost:extend({ + name='gudauta-compost-red', + products = { + presets.special.red.infantry:extend({ name='gudauta-defense-red'}), + presets.defenses.red.infantry:extend({ name='gudauta-garrison-red'}) + } + }), + presets.upgrades.supply.fuelTank:extend({ + name='gudauta-fuel-red', + products = { + presets.missions.supply.helo:extend({name='gudauta-supply-red'}), + presets.missions.supply.helo:extend({name='gudauta-supply-red-1'}), + presets.missions.supply.transfer:extend({name='gudauta-transfer-red'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='gudauta-comcenter-red', + products = { + presets.defenses.red.sam:extend({ name='gudauta-airdef-red'}), + presets.missions.attack.sead:extend({name='gudauta-sead-red', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.attack.sead:extend({name='gudauta-sead-red-1', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.attack.cas:extend({name='gudauta-cas-red', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.bai:extend({name='gudauta-cas-red', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.patrol.aircraft:extend({name='gudauta-patrol-red', altitude=25000, range=25}) + } + }) + }, + [2] = + { + presets.upgrades.basic.comPost:extend({ + name='gudauta-compost-blue', + products = { + presets.special.blue.infantry:extend({ name='gudauta-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='gudauta-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelTank:extend({ + name='gudauta-fuel-blue', + products = { + presets.missions.supply.helo:extend({name='gudauta-supply-blue'}), + presets.missions.supply.helo:extend({name='gudauta-supply-blue-1'}), + presets.missions.supply.transfer:extend({name='gudauta-transfer-blue'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='gudauta-comcenter-blue', + products = { + presets.defenses.blue.sam:extend({ name='gudauta-airdef-blue'}), + presets.missions.attack.sead:extend({name='gudauta-sead-blue', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.attack.sead:extend({name='gudauta-sead-blue-1', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.attack.cas:extend({name='gudauta-cas-blue', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.bai:extend({name='gudauta-cas-blue', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.patrol.aircraft:extend({name='gudauta-patrol-blue', altitude=25000, range=25}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Gudauta.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Distillery.lua ]]----------------- + +zones.distillery = ZoneCommand:new("Distillery") +zones.distillery.initialState = { side=1 } +zones.distillery:defineUpgrades({ + [1] = + { + presets.upgrades.basic.tent:extend({ + name='distillery-tent-red', + products = { + presets.special.red.infantry:extend({ name='distillery-defense-red'}), + presets.defenses.red.infantry:extend({ name='distillery-garrison-red'}) + } + }), + presets.upgrades.supply.factory1:extend({ + name='distillery-prod-red-1', + products = { + presets.missions.supply.convoy:extend({ name='distillery-supply-red-1'}), + presets.missions.supply.transfer:extend({name='distillery-transfer-red'}) + } + }), + presets.upgrades.supply.factory2:extend({ + name='distillery-prod-red-2', + products = { + presets.missions.supply.convoy:extend({ name='distillery-supply-red-2', cost=2000}), + presets.missions.supply.transfer:extend({name='distillery-transfer-red2'}) + } + }), + presets.upgrades.supply.factoryTank:extend({ + name='distillery-tank-red-1', + products = { + } + }), + presets.upgrades.supply.factoryTank:extend({ + name='distillery-tank-red-2', + products = { + } + }), + presets.upgrades.supply.factoryTank:extend({ + name='distillery-tank-red-3', + products = { + } + }) + }, + [2] = + { + presets.upgrades.basic.tent:extend({ + name='distillery-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='distillery-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='distillery-garrison-blue'}) + } + }), + presets.upgrades.supply.factory1:extend({ + name='distillery-prod-blue-1', + products = { + presets.missions.supply.convoy:extend({ name='distillery-supply-blue-1'}), + presets.missions.supply.transfer:extend({name='distillery-transfer-blue'}) + } + }), + presets.upgrades.supply.factory2:extend({ + name='distillery-prod-blue-2', + products = { + presets.missions.supply.convoy:extend({ name='distillery-supply-blue-2', cost=2000}), + presets.missions.supply.transfer:extend({name='distillery-transfer-blue2'}) + } + }), + presets.upgrades.supply.factoryTank:extend({ + name='distillery-tank-blue-1', + products = { + } + }), + presets.upgrades.supply.factoryTank:extend({ + name='distillery-tank-blue-2', + products = { + } + }), + presets.upgrades.supply.factoryTank:extend({ + name='distillery-tank-blue-3', + products = { + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Distillery.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Sochi.lua ]]----------------- + +zones.sochi = ZoneCommand:new("Sochi") +zones.sochi.initialState = { side=1 } +zones.sochi.keepActive = true +zones.sochi.maxResource = 50000 +zones.sochi:defineUpgrades({ + [1] = + { + presets.upgrades.basic.comPost:extend({ + name='sochi-compost-red', + products = { + presets.special.red.infantry:extend({ name='sochi-defense-red'}), + presets.defenses.red.infantry:extend({ name='sochi-garrison-red'}) + } + }), + presets.upgrades.supply.fuelTank:extend({ + name='sochi-fuel-red', + products = { + presets.missions.supply.helo:extend({name='sochi-supply-red-1'}), + presets.missions.supply.helo:extend({name='sochi-supply-red-2'}), + presets.missions.supply.convoy_escorted:extend({name='sochi-supply-red-3'}), + presets.missions.supply.transfer:extend({name='sochi-transfer-red'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='sochi-comcenter-red', + products = { + presets.defenses.red.sa10:extend({ name='sochi-airdef-red'}), + presets.missions.attack.sead:extend({name='sochi-sead-red', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.attack.strike:extend({name='sochi-strike-red', altitude=30000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.patrol.aircraft:extend({name='sochi-patrol-red', altitude=25000, range=25}), + presets.missions.patrol.aircraft:extend({name='sochi-patrol-red-1', altitude=25000, range=25}), + presets.missions.attack.cas:extend({name='sochi-cas-red', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.bai:extend({name='sochi-cas-red', altitude=10000, expend=AI.Task.WeaponExpend.ONE}) + } + }) + }, + [2] = + { + presets.upgrades.basic.comPost:extend({ + name='sochi-compost-blue', + products = { + presets.special.blue.infantry:extend({ name='sochi-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='sochi-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelTank:extend({ + name='sochi-fuel-blue', + products = { + presets.missions.supply.helo:extend({name='sochi-supply-blue-1'}), + presets.missions.supply.helo:extend({name='sochi-supply-blue-2'}), + presets.missions.supply.convoy_escorted:extend({name='sochi-supply-blue-3'}), + presets.missions.supply.transfer:extend({name='sochi-transfer-blue'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='sochi-comcenter-blue', + products = { + presets.defenses.blue.patriot:extend({ name='sochi-airdef-blue'}), + presets.missions.attack.sead:extend({name='sochi-sead-blue', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.attack.strike:extend({name='sochi-strike-blue', altitude=30000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.patrol.aircraft:extend({name='sochi-patrol-blue', altitude=25000, range=25}), + presets.missions.attack.cas:extend({name='sochi-cas-blue', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.bai:extend({name='sochi-cas-blue', altitude=10000, expend=AI.Task.WeaponExpend.ONE}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Sochi.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Golf.lua ]]----------------- + +zones.golf = ZoneCommand:new("Golf") +zones.golf.initialState = nil +zones.golf.isHeloSpawn = true +zones.golf:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='golf-tent-red', + products = { + presets.special.red.infantry:extend({ name='golf-defense-red'}), + presets.defenses.red.infantry:extend({ name='golf-garrison-red'}) + } + }), + presets.upgrades.supply.fuelTank:extend({ + name='golf-fuel-red', + products = { + presets.missions.supply.helo:extend({name='golf-supply-red'}), + presets.missions.supply.helo:extend({name='golf-supply-red-1'}), + presets.missions.supply.transfer:extend({name='golf-transfer-red'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='golf-comcenter-red', + products = { + presets.defenses.red.shorad:extend({name='golf-sam-red'}), + presets.missions.attack.helo:extend({name='golf-cas-red', altitude=200, expend=AI.Task.WeaponExpend.HALF }) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='golf-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='golf-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='golf-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelTankFarp:extend({ + name='golf-fuel-blue', + products = { + presets.missions.supply.helo:extend({name='golf-supply-blue'}), + presets.missions.supply.helo:extend({name='golf-supply-blue-1'}), + presets.missions.supply.transfer:extend({name='golf-transfer-blue'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='golf-comcenter-blue', + products = { + presets.defenses.blue.shorad:extend({name='golf-sam-blue'}), + presets.missions.attack.helo:extend({name='golf-cas-blue', altitude=200, expend=AI.Task.WeaponExpend.HALF }) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Golf.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Charlie.lua ]]----------------- + +zones.charlie = ZoneCommand:new("Charlie") +zones.charlie.initialState = { side=2 } +zones.charlie.keepActive = true +zones.charlie:defineUpgrades({ + [1] = + { + presets.upgrades.basic.tent:extend({ + name='charlie-tent-red', + products = { + presets.special.red.infantry:extend({ name='charlie-defense-red'}), + presets.defenses.red.infantry:extend({ name='charlie-garrison-red'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='charlie-comcenter-red', + products = { + presets.defenses.red.sam:extend({ name='charlie-sam-red'}) + } + }) + }, + [2] = + { + presets.upgrades.basic.tent:extend({ + name='charlie-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='charlie-defense-red'}), + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='charlie-comcenter-blue', + products = { + presets.defenses.blue.sam:extend({ name='charlie-sam-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Charlie.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Lentehi.lua ]]----------------- + +zones.lentehi = ZoneCommand:new("Lentehi") +zones.lentehi.initialState = { side=1 } +zones.lentehi:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='lentehi-tent-red', + products = { + presets.special.red.infantry:extend({ name='lentehi-defense-red'}), + presets.defenses.red.infantry:extend({ name='lentehi-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='lentehi-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='lentehi-supply-red'}), + presets.missions.supply.helo:extend({name='lentehi-supply-red-2'}), + presets.missions.supply.transfer:extend({name='lentehi-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='lentehi-ammo-red', + products = { + presets.missions.attack.surface:extend({name='lentehi-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='lentehi-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='lentehi-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='lentehi-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='lentehi-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='lentehi-supply-blue'}), + presets.missions.supply.helo:extend({name='lentehi-supply-blue-2'}), + presets.missions.supply.transfer:extend({name='lentehi-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='lentehi-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='lentehi-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Lentehi.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Refinery.lua ]]----------------- + +zones.refinery = ZoneCommand:new("Refinery") +zones.refinery.initialState = { side=1 } +zones.refinery:defineUpgrades({ + [1] = + { + presets.upgrades.basic.tent:extend({ + name='refinery-tent-red', + products = { + presets.special.red.infantry:extend({ name='refinery-defense-red'}), + presets.defenses.red.infantry:extend({ name='refinery-garrison-red'}) + } + }), + presets.upgrades.supply.refinery1:extend({ + name='refinery-building-red', + products = { + presets.missions.supply.convoy:extend({ name='refinery-supply-red'}), + presets.missions.supply.convoy:extend({ name='refinery-supply-red-1'}), + presets.missions.supply.helo:extend({ name='refinery-supply-red-2'}), + presets.missions.supply.transfer:extend({name='refinery-transfer-red'}) + } + }) + }, + [2] = + { + presets.upgrades.basic.tent:extend({ + name='refinery-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='refinery-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='refinery-garrison-blue'}) + } + }), + presets.upgrades.supply.refinery1:extend({ + name='refinery-building-blue', + products = { + presets.missions.supply.convoy:extend({ name='refinery-supply-blue'}), + presets.missions.supply.convoy:extend({ name='refinery-supply-blue-1'}), + presets.missions.supply.helo:extend({ name='refinery-supply-blue-2'}), + presets.missions.supply.transfer:extend({name='refinery-transfer-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Refinery.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Mozdok.lua ]]----------------- + +zones.mozdok = ZoneCommand:new("Mozdok") +zones.mozdok.initialState = { side=1 } +zones.mozdok.keepActive = true +zones.mozdok.maxResource = 50000 +zones.mozdok:defineUpgrades({ + [1] = + { + presets.upgrades.basic.comPost:extend({ + name='mozdok-compost-red', + products = { + presets.special.red.infantry:extend({ name='mozdok-defense-red'}), + presets.defenses.red.infantry:extend({ name='mozdok-garrison-red'}) + } + }), + presets.upgrades.supply.fuelTank:extend({ + name='mozdok-fuel-red', + products = { + presets.missions.supply.helo:extend({name='mozdok-supply-red-1'}), + presets.missions.supply.helo:extend({name='mozdok-supply-red-2'}), + presets.missions.supply.convoy_escorted:extend({name='mozdok-supply-red-3'}), + presets.missions.supply.transfer:extend({name='mozdok-transfer-red'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='mozdok-comcenter-red', + products = { + presets.defenses.red.sa10:extend({ name='mozdok-airdef-red'}), + presets.missions.patrol.aircraft:extend({name='mozdok-patrol-red', altitude=25000, range=25}), + presets.missions.attack.cas:extend({name='mozdok-cas-red', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.bai:extend({name='mozdok-cas-red', altitude=10000, expend=AI.Task.WeaponExpend.ONE}) + } + }) + }, + [2] = + { + presets.upgrades.basic.comPost:extend({ + name='mozdok-compost-blue', + products = { + presets.special.blue.infantry:extend({ name='mozdok-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='mozdok-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelTank:extend({ + name='mozdok-fuel-blue', + products = { + presets.missions.supply.helo:extend({name='mozdok-supply-blue-1'}), + presets.missions.supply.helo:extend({name='mozdok-supply-blue-2'}), + presets.missions.supply.convoy_escorted:extend({name='mozdok-supply-blue-3'}), + presets.missions.supply.transfer:extend({name='mozdok-transfer-blue'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='mozdok-comcenter-blue', + products = { + presets.defenses.blue.shorad:extend({ name='mozdok-airdef-blue'}), + presets.missions.patrol.aircraft:extend({name='mozdok-patrol-blue', altitude=25000, range=25}), + presets.missions.attack.cas:extend({name='mozdok-cas-blue', altitude=15000, cost=50, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.cas:extend({name='mozdok-cas-blue-1', altitude=15000, cost=50, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.bai:extend({name='mozdok-cas-blue', altitude=15000, cost=50, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.bai:extend({name='mozdok-cas-blue-1', altitude=15000, cost=50, expend=AI.Task.WeaponExpend.ONE}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Mozdok.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Lima.lua ]]----------------- + +zones.lima = ZoneCommand:new("Lima") +zones.lima.initialState = { side=1 } +zones.lima:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='lima-tent-red', + products = { + presets.special.red.infantry:extend({ name='lima-defense-red'}), + presets.defenses.red.infantry:extend({ name='lima-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='lima-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='lima-supply-red'}), + presets.missions.supply.helo:extend({name='lima-supply-red-1'}), + presets.missions.supply.transfer:extend({name='lima-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='lima-ammo-red', + products = { + presets.missions.attack.surface:extend({name='lima-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='lima-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='lima-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='lima-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='lima-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='lima-supply-blue'}), + presets.missions.supply.helo:extend({name='lima-supply-blue-1'}), + presets.missions.supply.transfer:extend({name='lima-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='lima-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='lima-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Lima.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Oscar.lua ]]----------------- + +zones.oscar = ZoneCommand:new("Oscar") +zones.oscar.initialState = { side=1 } +zones.oscar:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='oscar-tent-red', + products = { + presets.special.red.infantry:extend({ name='oscar-defense-red'}), + presets.defenses.red.infantry:extend({ name='oscar-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='oscar-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='oscar-supply-red'}), + presets.missions.supply.transfer:extend({name='oscar-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='oscar-ammo-red', + products = { + presets.missions.attack.surface:extend({name='oscar-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='oscar-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='oscar-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='oscar-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='oscar-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='oscar-supply-blue'}), + presets.missions.supply.transfer:extend({name='oscar-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='oscar-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='oscar-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Oscar.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Nalchik.lua ]]----------------- + +zones.nalchik = ZoneCommand:new("Nalchik") +zones.nalchik.initialState = { side=1 } +zones.nalchik.keepActive = true +zones.nalchik.isHeloSpawn = true +zones.nalchik.isPlaneSpawn = true +zones.nalchik:defineUpgrades({ + [1] = + { + presets.upgrades.basic.comPost:extend({ + name='nalchik-compost-red', + products = { + presets.special.red.infantry:extend({ name='nalchik-defense-red'}), + presets.defenses.red.infantry:extend({ name='nalchik-garrison-red'}) + } + }), + presets.upgrades.supply.fuelTank:extend({ + name='nalchik-fuel-red', + products = { + presets.missions.supply.helo:extend({name='nalchik-supply-red-1'}), + presets.missions.supply.helo:extend({name='nalchik-supply-red-2'}), + presets.missions.supply.transfer:extend({name='nalchik-transfer-red'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='nalchik-comcenter-red', + products = { + presets.defenses.red.sa3:extend({ name='nalchik-airdef-red'}), + presets.missions.attack.sead:extend({name='nalchik-sead-red', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.attack.cas:extend({name='nalchik-cas-red', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.bai:extend({name='nalchik-cas-red', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.strike:extend({name='nalchik-strike-red', altitude=30000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.patrol.aircraft:extend({name='nalchik-patrol-red', altitude=25000, range=25}), + presets.missions.patrol.aircraft:extend({name='nalchik-patrol-red-2', altitude=25000, range=25}), + presets.missions.support.awacs:extend({name='nalchik-awacs-red', altitude=30000, freq=251.2}), + presets.missions.support.tanker:extend({name='nalchik-tanker-red', altitude=30000, freq=252.2, tacan='40', variant='Drogue'}) + } + }) + }, + [2] = + { + presets.upgrades.basic.comPost:extend({ + name='nalchik-compost-blue', + products = { + presets.special.blue.infantry:extend({ name='nalchik-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='nalchik-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelTank:extend({ + name='nalchik-fuel-blue', + products = { + presets.missions.supply.helo:extend({name='nalchik-supply-blue-1'}), + presets.missions.supply.helo:extend({name='nalchik-supply-blue-2'}), + presets.missions.supply.transfer:extend({name='nalchik-transfer-blue'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='nalchik-comcenter-blue', + products = { + presets.defenses.blue.nasams:extend({ name='nalchik-airdef-blue'}), + presets.missions.support.awacs:extend({name='nalchik-awacs-blue', altitude=30000, freq=259.5}), + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Nalchik.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Digora.lua ]]----------------- + +zones.digora = ZoneCommand:new("Digora") +zones.digora.initialState = { side=1 } +zones.digora:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='digora-tent-red', + products = { + presets.special.red.infantry:extend({ name='digora-defense-red'}), + presets.defenses.red.infantry:extend({ name='digora-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='digora-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='digora-supply-red'}), + presets.missions.supply.transfer:extend({name='digora-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='digora-ammo-red', + products = { + presets.missions.attack.surface:extend({name='digora-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='digora-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='digora-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='digora-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='digora-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='digora-supply-blue'}), + presets.missions.supply.transfer:extend({name='digora-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='digora-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='digora-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Digora.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Uniform.lua ]]----------------- + +zones.uniform = ZoneCommand:new("Uniform") +zones.uniform.initialState = { side=1 } +zones.uniform:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='uniform-tent-red', + products = { + presets.special.red.infantry:extend({ name='uniform-defense-red'}), + presets.defenses.red.infantry:extend({ name='uniform-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='uniform-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='uniform-supply-red'}), + presets.missions.supply.transfer:extend({name='uniform-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='uniform-ammo-red', + products = { + presets.missions.attack.surface:extend({name='uniform-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='uniform-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='uniform-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='uniform-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='uniform-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='uniform-supply-blue'}), + presets.missions.supply.transfer:extend({name='uniform-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='uniform-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='uniform-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Uniform.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Factory.lua ]]----------------- + +zones.factory = ZoneCommand:new("Factory") +zones.factory.initialState = { side=2 } +zones.factory:defineUpgrades({ + [1] = + { + presets.upgrades.basic.tent:extend({ + name='factory-tent-red', + products = { + presets.special.red.infantry:extend({ name='factory-defense-red'}), + presets.defenses.red.infantry:extend({ name='factory-garrison-red'}) + } + }), + presets.upgrades.supply.factory1:extend({ + name='factory-prod-red-1', + products = { + presets.missions.supply.convoy:extend({ name='factory-supply-red-1'}), + presets.missions.supply.transfer:extend({name='factory-transfer-red'}) + } + }), + presets.upgrades.supply.factory2:extend({ + name='factory-prod-red-2', + products = { + presets.missions.supply.convoy:extend({ name='factory-supply-red-2', cost=2000}), + presets.missions.supply.transfer:extend({name='factory-transfer-red2'}) + } + }), + presets.upgrades.supply.factoryTank:extend({ + name='factory-tank-red-1', + products = { + } + }), + presets.upgrades.supply.factoryTank:extend({ + name='factory-tank-red-2', + products = { + } + }), + presets.upgrades.supply.factoryTank:extend({ + name='factory-tank-red-3', + products = { + } + }) + }, + [2] = + { + presets.upgrades.basic.tent:extend({ + name='factory-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='factory-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='factory-garrison-blue'}) + } + }), + presets.upgrades.supply.factory1:extend({ + name='factory-prod-blue-1', + products = { + presets.missions.supply.convoy:extend({ name='factory-supply-blue-1'}), + presets.missions.supply.transfer:extend({name='factory-transfer-blue'}) + } + }), + presets.upgrades.supply.factory2:extend({ + name='factory-prod-blue-2', + products = { + presets.missions.supply.convoy:extend({ name='factory-supply-blue-2', cost=2000}), + presets.missions.supply.transfer:extend({name='factory-transfer-blue2'}) + } + }), + presets.upgrades.supply.factoryTank:extend({ + name='factory-tank-blue-1', + products = { + } + }), + presets.upgrades.supply.factoryTank:extend({ + name='factory-tank-blue-2', + products = { + } + }), + presets.upgrades.supply.factoryTank:extend({ + name='factory-tank-blue-3', + products = { + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Factory.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Senaki.lua ]]----------------- + +zones.senaki = ZoneCommand:new("Senaki") +zones.senaki.initialState = { side=1 } +zones.senaki.keepActive = true +zones.senaki.isHeloSpawn = true +zones.senaki.isPlaneSpawn = true +zones.senaki.maxResource = 50000 +zones.senaki:defineUpgrades({ + [1] = + { + presets.upgrades.basic.comPost:extend({ + name='senaki-compost-red', + products = { + presets.special.red.infantry:extend({ name='senaki-defense-red'}), + presets.defenses.red.infantry:extend({ name='senaki-garrison-red'}) + } + }), + presets.upgrades.supply.fuelTank:extend({ + name='senaki-fuel-red', + products = { + presets.missions.supply.helo:extend({name='senaki-supply-red-1'}), + presets.missions.supply.helo:extend({name='senaki-supply-red-2'}), + presets.missions.supply.transfer:extend({name='senaki-transfer-red'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='senaki-comcenter-red', + products = { + presets.defenses.red.sa3:extend({ name='senaki-airdef-red'}), + presets.missions.attack.sead:extend({name='senaki-sead-red', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.attack.cas:extend({name='senaki-cas-red', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.bai:extend({name='senaki-cas-red', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.strike:extend({name='senaki-strike-red', altitude=20000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.patrol.aircraft:extend({name='senaki-patrol-red', altitude=25000, range=25}), + presets.missions.patrol.aircraft:extend({name='senaki-patrol-red-2', altitude=20000, range=25}) + } + }) + }, + [2] = + { + presets.upgrades.basic.comPost:extend({ + name='senaki-compost-blue', + products = { + presets.special.blue.infantry:extend({ name='senaki-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='senaki-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelTank:extend({ + name='senaki-fuel-blue', + products = { + presets.missions.supply.helo:extend({name='senaki-supply-blue-1'}), + presets.missions.supply.helo:extend({name='senaki-supply-blue-2'}), + presets.missions.supply.transfer:extend({name='senaki-transfer-blue'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='senaki-comcenter-blue', + products = { + presets.defenses.blue.shorad:extend({ name='senaki-airdef-blue'}), + presets.missions.attack.sead:extend({name='senaki-sead-blue', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.attack.cas:extend({name='senaki-cas-blue', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.bai:extend({name='senaki-cas-blue', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.strike:extend({name='senaki-strike-blue', altitude=20000, expend=AI.Task.WeaponExpend.TWO}), + presets.missions.patrol.aircraft:extend({name='senaki-patrol-blue', altitude=25000, range=25}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Senaki.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Kutaisi.lua ]]----------------- + +zones.kutaisi = ZoneCommand:new("Kutaisi") +zones.kutaisi.initialState = { side=1 } +zones.kutaisi.keepActive = true +zones.kutaisi.maxResource = 50000 +zones.kutaisi:defineUpgrades({ + [1] = + { + presets.upgrades.basic.comPost:extend({ + name='kutaisi-compost-red', + products = { + presets.special.red.infantry:extend({ name='kutaisi-defense-red'}), + presets.defenses.red.infantry:extend({ name='kutaisi-garrison-red'}) + } + }), + presets.upgrades.supply.fuelTank:extend({ + name='kutaisi-fuel-red', + products = { + presets.missions.supply.helo:extend({name='kutaisi-supply-red-1'}), + presets.missions.supply.helo:extend({name='kutaisi-supply-red-2'}), + presets.missions.supply.transfer:extend({name='kutaisi-transfer-red'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='kutaisi-comcenter-red', + products = { + presets.defenses.red.shorad:extend({ name='kutaisi-airdef-red'}), + presets.missions.attack.sead:extend({name='kutaisi-sead-red', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.attack.cas:extend({name='kutaisi-cas-red', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.bai:extend({name='kutaisi-cas-red', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.strike:extend({name='kutaisi-strike-red', altitude=20000, expend=AI.Task.WeaponExpend.HALF}), + presets.missions.patrol.aircraft:extend({name='kutaisi-patrol-red', altitude=25000, range=25}), + presets.missions.patrol.aircraft:extend({name='kutaisi-patrol-red-2', altitude=25000, range=25}) + } + }) + }, + [2] = + { + presets.upgrades.basic.comPost:extend({ + name='kutaisi-compost-blue', + products = { + presets.special.blue.infantry:extend({ name='kutaisi-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='kutaisi-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelTank:extend({ + name='kutaisi-fuel-blue', + products = { + presets.missions.supply.helo:extend({name='kutaisi-supply-blue-1'}), + presets.missions.supply.helo:extend({name='kutaisi-supply-blue-2'}), + presets.missions.supply.transfer:extend({name='kutaisi-transfer-blue'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='kutaisi-comcenter-blue', + products = { + presets.defenses.blue.shorad:extend({ name='kutaisi-airdef-blue'}), + presets.missions.attack.sead:extend({name='kutaisi-sead-blue', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.attack.cas:extend({name='kutaisi-cas-blue', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.bai:extend({name='kutaisi-cas-blue', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.strike:extend({name='kutaisi-strike-blue', altitude=20000, expend=AI.Task.WeaponExpend.TWO}), + presets.missions.patrol.aircraft:extend({name='kutaisi-patrol-blue', altitude=25000, range=25}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Kutaisi.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Prohladniy.lua ]]----------------- + +zones.prohladniy = ZoneCommand:new("Prohladniy") +zones.prohladniy.initialState = { side=1 } +zones.prohladniy:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='prohladniy-tent-red', + products = { + presets.special.red.infantry:extend({ name='prohladniy-defense-red'}), + presets.defenses.red.infantry:extend({ name='prohladniy-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='prohladniy-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='prohladniy-supply-red'}), + presets.missions.supply.transfer:extend({name='prohladniy-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='prohladniy-ammo-red', + products = { + presets.missions.attack.surface:extend({name='prohladniy-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='prohladniy-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='prohladniy-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='prohladniy-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='prohladniy-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='prohladniy-supply-blue'}), + presets.missions.supply.transfer:extend({name='prohladniy-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='prohladniy-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='prohladniy-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Prohladniy.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Tallyk.lua ]]----------------- + +zones.tallyk = ZoneCommand:new("Tallyk") +zones.tallyk.initialState = { side=1 } +zones.tallyk.keepActive = true +zones.tallyk:defineUpgrades({ + [1] = + { + presets.upgrades.basic.tent:extend({ + name='tallyk-tent-red', + products = { + presets.special.red.infantry:extend({ name='tallyk-defense-red'}), + presets.defenses.red.infantry:extend({ name='tallyk-garrison-red'}), + presets.missions.attack.surface:extend({name='tallyk-assault-red'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='tallyk-comcenter-red', + products = { + presets.defenses.red.sam:extend({ name='tallyk-sam-red'}) + } + }) + }, + [2] = + { + presets.upgrades.basic.tent:extend({ + name='tallyk-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='tallyk-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='tallyk-garrison-blue'}), + presets.missions.attack.surface:extend({name='tallyk-assault-blue'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='tallyk-comcenter-blue', + products = { + presets.defenses.blue.sam:extend({ name='tallyk-sam-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Tallyk.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Terek.lua ]]----------------- + +zones.terek = ZoneCommand:new("Terek") +zones.terek.initialState = { side=1 } +zones.terek:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='terek-tent-red', + products = { + presets.special.red.infantry:extend({ name='terek-defense-red'}), + presets.defenses.red.infantry:extend({ name='terek-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='terek-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='terek-supply-red'}), + presets.missions.supply.transfer:extend({name='terek-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='terek-ammo-red', + products = { + presets.missions.attack.surface:extend({name='terek-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='terek-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='terek-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='terek-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='terek-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='terek-supply-blue'}), + presets.missions.supply.transfer:extend({name='terek-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='terek-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='terek-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Terek.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Humara.lua ]]----------------- + +zones.humara = ZoneCommand:new("Humara") +zones.humara.initialState = { side=1 } +zones.humara:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='humara-tent-red', + products = { + presets.special.red.infantry:extend({ name='humara-defense-red'}), + presets.defenses.red.infantry:extend({ name='humara-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='humara-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='humara-supply-red'}), + presets.missions.supply.transfer:extend({name='humara-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='humara-ammo-red', + products = { + presets.missions.attack.surface:extend({name='humara-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='humara-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='humara-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='humara-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='humara-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='humara-supply-blue'}), + presets.missions.supply.transfer:extend({name='humara-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='humara-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='humara-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Humara.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Ochamchira.lua ]]----------------- + +zones.ochamchira = ZoneCommand:new("Ochamchira") +zones.ochamchira.initialState = { side=1 } +zones.ochamchira:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='ochamchira-tent-red', + products = { + presets.special.red.infantry:extend({ name='ochamchira-defense-red'}), + presets.defenses.red.infantry:extend({ name='ochamchira-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='ochamchira-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='ochamchira-supply-red'}), + presets.missions.supply.transfer:extend({name='ochamchira-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='ochamchira-ammo-red', + products = { + presets.missions.attack.surface:extend({name='ochamchira-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='ochamchira-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='ochamchira-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='ochamchira-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='ochamchira-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='ochamchira-supply-blue'}), + presets.missions.supply.transfer:extend({name='ochamchira-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='ochamchira-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='ochamchira-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Ochamchira.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/November.lua ]]----------------- + +zones.november = ZoneCommand:new("November") +zones.november.initialState = { side=1 } +zones.november.isHeloSpawn = true +zones.november:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='november-tent-red', + products = { + presets.special.red.infantry:extend({ name='november-defense-red'}), + presets.defenses.red.infantry:extend({ name='november-garrison-red'}) + } + }), + presets.upgrades.supply.fuelTankFarp:extend({ + name='november-fuel-red', + products = { + presets.missions.supply.helo:extend({name='november-supply-red'}), + presets.missions.supply.helo:extend({name='november-supply-red-1'}), + presets.missions.supply.transfer:extend({name='november-transfer-red'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='november-comcenter-red', + products = { + presets.defenses.red.shorad:extend({name='november-sam-red'}), + presets.missions.attack.helo:extend({name='november-cas-red', altitude=200, expend=AI.Task.WeaponExpend.HALF }) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='november-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='november-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='november-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelTankFarp:extend({ + name='november-fuel-blue', + products = { + presets.missions.supply.helo:extend({name='november-supply-blue'}), + presets.missions.supply.helo:extend({name='november-supply-blue-1'}), + presets.missions.supply.transfer:extend({name='november-transfer-blue'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='november-comcenter-blue', + products = { + presets.defenses.blue.shorad:extend({name='november-sam-blue'}), + presets.missions.attack.helo:extend({name='november-cas-blue', altitude=200, expend=AI.Task.WeaponExpend.HALF }) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/November.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/XRay.lua ]]----------------- + +zones.xray = ZoneCommand:new("XRay") +zones.xray.initialState = { side=1 } +zones.xray:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='xray-tent-red', + products = { + presets.special.red.infantry:extend({ name='xray-defense-red'}), + presets.defenses.red.infantry:extend({ name='xray-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='xray-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='xray-supply-red'}), + presets.missions.supply.helo:extend({name='xray-supply-red-2'}), + presets.missions.supply.transfer:extend({name='xray-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='xray-ammo-red', + products = { + presets.missions.attack.surface:extend({name='xray-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='xray-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='xray-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='xray-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='xray-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='xray-supply-blue'}), + presets.missions.supply.helo:extend({name='xray-supply-blue-2'}), + presets.missions.supply.transfer:extend({name='xray-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='xray-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='xray-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/XRay.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Whiskey.lua ]]----------------- + +zones.whiskey = ZoneCommand:new("Whiskey") +zones.whiskey.initialState = { side=1 } +zones.whiskey:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='whiskey-tent-red', + products = { + presets.special.red.infantry:extend({ name='whiskey-defense-red'}), + presets.defenses.red.infantry:extend({ name='whiskey-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='whiskey-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='whiskey-supply-red'}), + presets.missions.supply.transfer:extend({name='whiskey-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='whiskey-ammo-red', + products = { + presets.missions.attack.surface:extend({name='whiskey-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='whiskey-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='whiskey-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='whiskey-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='whiskey-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='whiskey-supply-blue'}), + presets.missions.supply.transfer:extend({name='whiskey-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='whiskey-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='whiskey-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Whiskey.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Mine.lua ]]----------------- + +zones.mine = ZoneCommand:new("Mine") +zones.mine.initialState = { side=1 } +zones.mine:defineUpgrades({ + [1] = + { + presets.upgrades.basic.tent:extend({ + name='mine-tent-red', + products = { + presets.special.red.infantry:extend({ name='mine-defense-red'}), + presets.defenses.red.infantry:extend({ name='mine-garrison-red'}) + } + }), + presets.upgrades.supply.excavator:extend({ + name='mine-excavator-red-1', + products = { + presets.missions.supply.convoy:extend({ name='mine-supply-red'}), + presets.missions.supply.transfer:extend({name='mine-transfer-red'}) + } + }), + presets.upgrades.supply.excavator:extend({ + name='mine-excavator-red-2', + products = { + presets.missions.supply.convoy:extend({ name='mine-supply-red'}), + presets.missions.supply.transfer:extend({name='mine-transfer-red'}) + } + }), + presets.upgrades.supply.excavator:extend({ + name='mine-excavator-red-3', + products = { + presets.missions.supply.convoy:extend({ name='mine-supply-red'}), + presets.missions.supply.transfer:extend({name='mine-transfer-red'}) + } + }) + }, + [2] = + { + presets.upgrades.basic.tent:extend({ + name='mine-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='mine-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='mine-garrison-blue'}) + } + }), + presets.upgrades.supply.excavator:extend({ + name='mine-excavator-blue-1', + products = { + presets.missions.supply.convoy:extend({ name='mine-supply-blue'}), + presets.missions.supply.transfer:extend({name='mine-transfer-blue'}) + } + }), + presets.upgrades.supply.excavator:extend({ + name='mine-excavator-blue-2', + products = { + presets.missions.supply.convoy:extend({ name='mine-supply-blue'}), + presets.missions.supply.transfer:extend({name='mine-transfer-blue'}) + } + }), + presets.upgrades.supply.excavator:extend({ + name='mine-excavator-blue-3', + products = { + presets.missions.supply.convoy:extend({ name='mine-supply-blue'}), + presets.missions.supply.transfer:extend({name='mine-transfer-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Mine.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Papa.lua ]]----------------- + +zones.papa = ZoneCommand:new("Papa") +zones.papa.initialState = { side=1 } +zones.papa.keepActive = true +zones.papa:defineUpgrades({ + [1] = + { + presets.upgrades.basic.tent:extend({ + name='papa-tent-red', + products = { + presets.special.red.infantry:extend({ name='papa-defense-red'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='papa-comcenter-red', + products = { + presets.defenses.red.sam:extend({ name='papa-sam-red'}) + } + }) + }, + [2] = + { + presets.upgrades.basic.tent:extend({ + name='papa-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='papa-defense-blue'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='papa-comcenter-blue', + products = { + presets.defenses.blue.sam:extend({ name='papa-sam-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Papa.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Sukhumi.lua ]]----------------- + +zones.sukhumi = ZoneCommand:new("Sukhumi") +zones.sukhumi.initialState = { side=1 } +zones.sukhumi.keepActive = true +zones.sukhumi.maxResource = 50000 +zones.sukhumi:defineUpgrades({ + [1] = + { + presets.upgrades.basic.comPost:extend({ + name='sukhumi-compost-red', + products = { + presets.special.red.infantry:extend({ name='sukhumi-defense-red'}), + presets.defenses.red.infantry:extend({ name='sukhumi-garrison-red'}) + } + }), + presets.upgrades.supply.fuelTank:extend({ + name='sukhumi-fuel-red', + products = { + presets.missions.supply.helo:extend({name='sukhumi-supply-red-1'}), + presets.missions.supply.helo:extend({name='sukhumi-supply-red-2'}), + presets.missions.supply.transfer:extend({name='sukhumi-transfer-red'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='sukhumi-comcenter-red', + products = { + presets.defenses.red.sa11:extend({ name='sukhumi-airdef-red'}), + presets.missions.attack.sead:extend({name='sukhumi-sead-red', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.attack.cas:extend({name='sukhumi-cas-red', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.bai:extend({name='sukhumi-cas-red', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.strike:extend({name='sukhumi-strike-red', altitude=20000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.patrol.aircraft:extend({name='sukhumi-patrol-red', altitude=25000, range=25}), + presets.missions.patrol.aircraft:extend({name='sukhumi-patrol-red-2', altitude=25000, range=25}) + } + }) + }, + [2] = + { + presets.upgrades.basic.comPost:extend({ + name='sukhumi-compost-blue', + products = { + presets.special.blue.infantry:extend({ name='sukhumi-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='sukhumi-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelTank:extend({ + name='sukhumi-fuel-blue', + products = { + presets.missions.supply.helo:extend({name='sukhumi-supply-blue-1'}), + presets.missions.supply.helo:extend({name='sukhumi-supply-blue-2'}), + presets.missions.supply.transfer:extend({name='sukhumi-transfer-blue'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='sukhumi-comcenter-blue', + products = { + presets.defenses.blue.nasams:extend({ name='sukhumi-airdef-blue'}), + presets.missions.attack.sead:extend({name='sukhumi-sead-blue', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.attack.cas:extend({name='sukhumi-cas-blue', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.bai:extend({name='sukhumi-cas-blue', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.strike:extend({name='sukhumi-strike-blue', altitude=20000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.patrol.aircraft:extend({name='sukhumi-patrol-blue', altitude=25000, range=25}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Sukhumi.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Farm.lua ]]----------------- + +zones.farm = ZoneCommand:new("Farm") +zones.farm.initialState = { side=1 } +zones.farm:defineUpgrades({ + [1] = + { + presets.upgrades.basic.tent:extend({ + name='farm-tent-red', + products = { + presets.special.red.infantry:extend({ name='farm-defense-red'}), + presets.defenses.red.infantry:extend({ name='farm-garrison-red'}) + } + }), + presets.upgrades.supply.farm1:extend({ + name='farm-prod-red-1', + products = { + presets.missions.supply.convoy:extend({ name='farm-supply-red'}), + presets.missions.supply.transfer:extend({name='farm-transfer-red'}) + } + }), + presets.upgrades.supply.farm2:extend({ + name='farm-prod-red-2', + products = { + presets.missions.supply.convoy:extend({ name='farm-supply-red'}), + presets.missions.supply.transfer:extend({name='farm-transfer-red'}) + } + }) + }, + [2] = + { + presets.upgrades.basic.tent:extend({ + name='farm-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='farm-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='farm-garrison-blue'}) + } + }), + presets.upgrades.supply.farm1:extend({ + name='farm-prod-blue-1', + products = { + presets.missions.supply.convoy:extend({ name='farm-supply-blue'}), + presets.missions.supply.transfer:extend({name='farm-transfer-blue'}) + } + }), + presets.upgrades.supply.farm2:extend({ + name='farm-prod-blue-2', + products = { + presets.missions.supply.convoy:extend({ name='farm-supply-blue'}), + presets.missions.supply.transfer:extend({name='farm-transfer-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Farm.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Romeo.lua ]]----------------- + +zones.romeo = ZoneCommand:new("Romeo") +zones.romeo.initialState = { side=1 } +zones.romeo:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='romeo-tent-red', + products = { + presets.special.red.infantry:extend({ name='romeo-defense-red'}), + presets.defenses.red.infantry:extend({ name='romeo-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='romeo-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='romeo-supply-red'}), + presets.missions.supply.transfer:extend({name='romeo-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='romeo-ammo-red', + products = { + presets.missions.attack.surface:extend({name='romeo-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='romeo-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='romeo-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='romeo-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='romeo-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='romeo-supply-blue'}), + presets.missions.supply.transfer:extend({name='romeo-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='romeo-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='romeo-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Romeo.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Zulu.lua ]]----------------- + +zones.zulu = ZoneCommand:new("Zulu") +zones.zulu.initialState = { side=1 } +zones.zulu:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='zulu-tent-red', + products = { + presets.special.red.infantry:extend({ name='zulu-defense-red'}), + presets.defenses.red.infantry:extend({ name='zulu-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='zulu-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='zulu-supply-red'}), + presets.missions.supply.transfer:extend({name='zulu-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='zulu-ammo-red', + products = { + presets.missions.attack.surface:extend({name='zulu-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='zulu-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='zulu-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='zulu-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='zulu-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='zulu-supply-blue'}), + presets.missions.supply.transfer:extend({name='zulu-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='zulu-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='zulu-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Zulu.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Yankee.lua ]]----------------- + +zones.yankee = ZoneCommand:new("Yankee") +zones.yankee.initialState = { side=1 } +zones.yankee:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='yankee-tent-red', + products = { + presets.special.red.infantry:extend({ name='yankee-defense-red'}), + presets.defenses.red.infantry:extend({ name='yankee-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='yankee-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='yankee-supply-red'}), + presets.missions.supply.transfer:extend({name='yankee-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='yankee-ammo-red', + products = { + presets.missions.attack.surface:extend({name='yankee-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='yankee-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='yankee-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='yankee-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='yankee-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='yankee-supply-blue'}), + presets.missions.supply.transfer:extend({name='yankee-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='yankee-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='yankee-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Yankee.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Malgobek.lua ]]----------------- + +zones.malgobek = ZoneCommand:new("Malgobek") +zones.malgobek.initialState = { side=1 } +zones.malgobek:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='malgobek-tent-red', + products = { + presets.special.red.infantry:extend({ name='malgobek-defense-red'}), + presets.defenses.red.infantry:extend({ name='malgobek-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='malgobek-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='malgobek-supply-red'}), + presets.missions.supply.transfer:extend({name='malgobek-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='malgobek-ammo-red', + products = { + presets.missions.attack.surface:extend({name='malgobek-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='malgobek-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='malgobek-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='malgobek-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='malgobek-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='malgobek-supply-blue'}), + presets.missions.supply.transfer:extend({name='malgobek-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='malgobek-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='malgobek-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Malgobek.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Kilo.lua ]]----------------- + +zones.kilo = ZoneCommand:new("Kilo") +zones.kilo.initialState = { side=1 } +zones.kilo:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='kilo-tent-red', + products = { + presets.special.red.infantry:extend({ name='kilo-defense-red'}), + presets.defenses.red.infantry:extend({ name='kilo-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='kilo-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='kilo-supply-red'}), + presets.missions.supply.transfer:extend({name='kilo-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='kilo-ammo-red', + products = { + presets.missions.attack.surface:extend({name='kilo-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='kilo-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='kilo-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='kilo-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='kilo-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='kilo-supply-blue'}), + presets.missions.supply.transfer:extend({name='kilo-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='kilo-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='kilo-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Kilo.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Quebec.lua ]]----------------- + +zones.quebec = ZoneCommand:new("Quebec") +zones.quebec.initialState = { side=1 } +zones.quebec.keepActive = true +zones.quebec:defineUpgrades({ + [1] = + { + presets.upgrades.basic.tent:extend({ + name='quebec-tent-red', + products = { + presets.special.red.infantry:extend({ name='quebec-defense-red'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='quebec-comcenter-red', + products = { + presets.defenses.red.sam:extend({ name='quebec-sam-red'}) + } + }) + }, + [2] = + { + presets.upgrades.basic.tent:extend({ + name='quebec-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='quebec-defense-blue'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='quebec-comcenter-blue', + products = { + presets.defenses.blue.sam:extend({ name='quebec-sam-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Quebec.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/OilFields.lua ]]----------------- + +zones.oilfields = ZoneCommand:new("Oil Fields") +zones.oilfields.initialState = { side=1 } +zones.oilfields:defineUpgrades({ + [1] = { + presets.upgrades.basic.outpost:extend({ + name='oilfields-outpost-red', + products = { + presets.special.red.infantry:extend({ name='oilfields-defense-red'}), + presets.defenses.red.infantry:extend({ name='oilfields-garrison-red'}) + } + }), + presets.upgrades.supply.oilPump:extend({ + name='oilfields-pump-red-1', + products = { + presets.missions.supply.transfer:extend({name='oilfields-transfer-red1'}) + } + }), + presets.upgrades.supply.oilPump:extend({ + name='oilfields-pump-red-2', + products = { + presets.missions.supply.convoy:extend({name='oilfields-supply-red-1'}) + } + }), + presets.upgrades.supply.oilPump:extend({ + name='oilfields-pump-red-3', + products = { + presets.missions.supply.transfer:extend({name='oilfields-transfer-red2'}) + } + }), + presets.upgrades.supply.oilPump:extend({ + name='oilfields-pump-red-4', + products = { + presets.missions.supply.convoy:extend({name='oilfields-supply-red-2'}) + } + }) + }, + [2] = { + presets.upgrades.basic.outpost:extend({ + name='oilfields-outpost-blue', + products = { + presets.special.blue.infantry:extend({ name='oilfields-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='oilfields-garrison-blue'}) + } + }), + presets.upgrades.supply.oilPump:extend({ + name='oilfields-pump-blue-1', + products = { + presets.missions.supply.transfer:extend({name='oilfields-transfer-blue1'}) + } + }), + presets.upgrades.supply.oilPump:extend({ + name='oilfields-pump-blue-2', + products = { + presets.missions.supply.convoy:extend({name='oilfields-supply-blue-1'}) + } + }), + presets.upgrades.supply.oilPump:extend({ + name='oilfields-pump-blue-3', + products = { + presets.missions.supply.transfer:extend({name='oilfields-transfer-blue2'}) + } + }), + presets.upgrades.supply.oilPump:extend({ + name='oilfields-pump-blue-4', + products = { + presets.missions.supply.convoy:extend({name='oilfields-supply-blue-2'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/OilFields.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Echo.lua ]]----------------- + +zones.echo = ZoneCommand:new("Echo") +zones.echo.initialState = { side=2 } +zones.echo:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='echo-tent-red', + products = { + presets.special.red.infantry:extend({ name='echo-defense-red'}), + presets.defenses.red.infantry:extend({ name='echo-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='echo-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='echo-supply-red'}), + presets.missions.supply.transfer:extend({name='echo-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='echo-ammo-red', + products = { + presets.missions.attack.surface:extend({name='echo-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='echo-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='echo-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='echo-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='echo-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='echo-supply-blue'}), + presets.missions.supply.transfer:extend({name='echo-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='echo-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='echo-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Echo.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Kobuleti.lua ]]----------------- + +zones.kobuleti = ZoneCommand:new("Kobuleti") +zones.kobuleti.initialState = { side=2 } +zones.kobuleti.keepActive = true +zones.kobuleti.isHeloSpawn = true +zones.kobuleti.isPlaneSpawn = true +zones.kobuleti.maxResource = 50000 +zones.kobuleti:defineUpgrades({ + [1] = + { + presets.upgrades.basic.comPost:extend({ + name='kobuleti-compost-red', + products = { + presets.special.red.infantry:extend({ name='kobuleti-defense-red'}), + presets.defenses.red.infantry:extend({ name='kobuleti-garrison-red'}), + presets.missions.attack.surface:extend({ name='kobuleti-assault-red'}) + } + }), + presets.upgrades.supply.fuelTank:extend({ + name='kobuleti-fuel-red', + products = { + presets.missions.supply.helo:extend({name='kobuleti-supply-red-1'}), + presets.missions.supply.helo:extend({name='kobuleti-supply-red-2'}), + presets.missions.supply.transfer:extend({name='kobuleti-transfer-red'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='kobuleti-comcenter-red', + products = { + presets.defenses.red.shorad:extend({ name='kobuleti-airdef-red'}), + presets.missions.attack.sead:extend({name='kobuleti-sead-red', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.attack.cas:extend({name='kobuleti-cas-red', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.bai:extend({name='kobuleti-cas-red', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.strike:extend({name='kobuleti-strike-red', altitude=20000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.patrol.aircraft:extend({name='kobuleti-patrol-red', altitude=25000, range=25}) + } + }) + }, + [2] = + { + presets.upgrades.basic.comPost:extend({ + name='kobuleti-compost-blue', + products = { + presets.special.blue.infantry:extend({ name='kobuleti-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='kobuleti-garrison-blue'}), + presets.missions.attack.surface:extend({ name='kobuleti-assault-blue'}) + } + }), + presets.upgrades.supply.fuelTank:extend({ + name='kobuleti-fuel-blue', + products = { + presets.missions.supply.helo:extend({name='kobuleti-supply-blue-1'}), + presets.missions.supply.helo:extend({name='kobuleti-supply-blue-2'}), + presets.missions.supply.transfer:extend({name='kobuleti-transfer-blue'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='kobuleti-comcenter-blue', + products = { + presets.defenses.blue.shorad:extend({ name='kobuleti-airdef-blue'}), + presets.missions.attack.sead:extend({name='kobuleti-sead-blue', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.attack.cas:extend({name='kobuleti-cas-blue', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.bai:extend({name='kobuleti-cas-blue', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), + presets.missions.attack.strike:extend({name='kobuleti-strike-blue', altitude=20000, expend=AI.Task.WeaponExpend.TWO}), + presets.missions.patrol.aircraft:extend({name='kobuleti-patrol-blue', altitude=25000, range=25}), + presets.missions.support.awacs:extend({name='kobuleti-awacs-blue', altitude=30000, freq=258.5}), + presets.missions.support.tanker:extend({name='kobuleti-tanker-blue', altitude=23000, freq=258, tacan='38', variant='Boom'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Kobuleti.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Alpha.lua ]]----------------- + +zones.alpha = ZoneCommand:new('Alpha') +zones.alpha.initialState = { side=2 } +zones.alpha:defineUpgrades({ + [1] = --red side + { + presets.upgrades.basic.tent:extend({ + name = 'alpha-tent-red', + products = { + presets.special.red.infantry:extend({ name='alpha-defense-red'}), + presets.defenses.red.infantry:extend({ name='alpha-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name = 'alpha-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({ name='alpha-supply-red'}), + presets.missions.supply.transfer:extend({name='alpha-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name = 'alpha-ammo-red', + products = { + presets.missions.attack.surface:extend({ name='alpha-assault-red'}) + } + }) + }, + [2] = --blue side + { + presets.upgrades.basic.tent:extend({ + name = 'alpha-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='alpha-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='alpha-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name = 'alpha-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({ name='alpha-supply-blue'}), + presets.missions.supply.transfer:extend({name='alpha-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name = 'alpha-ammo-blue', + products = { + presets.missions.attack.surface:extend({ name='alpha-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Alpha.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Foxtrot.lua ]]----------------- + +zones.foxtrot = ZoneCommand:new("Foxtrot") +zones.foxtrot.initialState = { side=2 } +zones.foxtrot:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='foxtrot-tent-red', + products = { + presets.special.red.infantry:extend({ name='foxtrot-defense-red'}), + presets.defenses.red.infantry:extend({ name='foxtrot-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='foxtrot-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='foxtrot-supply-red'}), + presets.missions.supply.transfer:extend({name='foxtrot-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='foxtrot-ammo-red', + products = { + presets.missions.attack.surface:extend({name='foxtrot-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='foxtrot-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='foxtrot-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='foxtrot-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='foxtrot-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='foxtrot-supply-blue'}), + presets.missions.supply.transfer:extend({name='foxtrot-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='foxtrot-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='foxtrot-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Foxtrot.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Sierra.lua ]]----------------- + +zones.sierra = ZoneCommand:new("Sierra") +zones.sierra.initialState = { side=1 } +zones.sierra.isHeloSpawn = true +zones.sierra:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='sierra-tent-red', + products = { + presets.special.red.infantry:extend({ name='sierra-defense-red'}), + presets.defenses.red.infantry:extend({ name='sierra-garrison-red'}) + } + }), + presets.upgrades.supply.fuelTankFarp:extend({ + name='sierra-fuel-red', + products = { + presets.missions.supply.helo:extend({name='sierra-supply-red'}), + presets.missions.supply.helo:extend({name='sierra-supply-red-1'}), + presets.missions.supply.transfer:extend({name='sierra-transfer-red'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='sierra-comcenter-red', + products = { + presets.defenses.red.shorad:extend({name='sierra-sam-red'}), + presets.missions.attack.helo:extend({name='sierra-cas-red', altitude=200, expend=AI.Task.WeaponExpend.HALF }) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='sierra-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='sierra-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='sierra-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelTankFarp:extend({ + name='sierra-fuel-blue', + products = { + presets.missions.supply.helo:extend({name='sierra-supply-blue'}), + presets.missions.supply.helo:extend({name='sierra-supply-blue-1'}), + presets.missions.supply.transfer:extend({name='sierra-transfer-blue'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='sierra-comcenter-blue', + products = { + presets.defenses.blue.shorad:extend({name='sierra-sam-blue'}), + presets.missions.attack.helo:extend({name='sierra-cas-blue', altitude=200, expend=AI.Task.WeaponExpend.HALF }) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Sierra.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Oni.lua ]]----------------- + +zones.oni = ZoneCommand:new("Oni") +zones.oni.initialState = { side=1 } +zones.oni:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='oni-tent-red', + products = { + presets.special.red.infantry:extend({ name='oni-defense-red'}), + presets.defenses.red.infantry:extend({ name='oni-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='oni-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='oni-supply-red'}), + presets.missions.supply.helo:extend({name='oni-supply-red-2'}), + presets.missions.supply.transfer:extend({name='oni-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='oni-ammo-red', + products = { + presets.missions.attack.surface:extend({name='oni-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='oni-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='oni-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='oni-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='oni-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='oni-supply-blue'}), + presets.missions.supply.helo:extend({name='oni-supply-blue-2'}), + presets.missions.supply.transfer:extend({name='oni-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='oni-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='oni-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Oni.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Hotel.lua ]]----------------- + +zones.hotel = ZoneCommand:new("Hotel") +zones.hotel.initialState = nil +zones.hotel:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='hotel-tent-red', + products = { + presets.special.red.infantry:extend({ name='hotel-defense-red'}), + presets.defenses.red.infantry:extend({ name='hotel-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='hotel-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='hotel-supply-red'}), + presets.missions.supply.transfer:extend({name='hotel-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='hotel-ammo-red', + products = { + presets.missions.attack.surface:extend({name='hotel-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='hotel-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='hotel-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='hotel-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='hotel-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='hotel-supply-blue'}), + presets.missions.supply.transfer:extend({name='hotel-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='hotel-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='hotel-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Hotel.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Victor.lua ]]----------------- + +zones.victor = ZoneCommand:new("Victor") +zones.victor.initialState = { side=1 } +zones.victor:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='victor-tent-red', + products = { + presets.special.red.infantry:extend({ name='victor-defense-red'}), + presets.defenses.red.infantry:extend({ name='victor-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='victor-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='victor-supply-red'}), + presets.missions.supply.helo:extend({name='victor-supply-red-2'}), + presets.missions.supply.transfer:extend({name='victor-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='victor-ammo-red', + products = { + presets.missions.attack.surface:extend({name='victor-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='victor-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='victor-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='victor-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='victor-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='victor-supply-blue'}), + presets.missions.supply.helo:extend({name='victor-supply-blue-2'}), + presets.missions.supply.transfer:extend({name='victor-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='victor-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='victor-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Victor.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Tango.lua ]]----------------- + +zones.tango = ZoneCommand:new("Tango") +zones.tango.initialState = { side=1 } +zones.tango.isHeloSpawn = true +zones.tango:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='tango-tent-red', + products = { + presets.special.red.infantry:extend({ name='tango-defense-red'}), + presets.defenses.red.infantry:extend({ name='tango-garrison-red'}) + } + }), + presets.upgrades.supply.fuelTankFarp:extend({ + name='tango-fuel-red', + products = { + presets.missions.supply.helo:extend({name='tango-supply-red'}), + presets.missions.supply.helo:extend({name='tango-supply-red-1'}), + presets.missions.supply.transfer:extend({name='tango-transfer-red'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='tango-comcenter-red', + products = { + presets.defenses.red.shorad:extend({name='tango-sam-red'}), + presets.missions.attack.helo:extend({name='tango-cas-red', altitude=200, expend=AI.Task.WeaponExpend.HALF }) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='tango-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='tango-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='tango-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelTankFarp:extend({ + name='tango-fuel-blue', + products = { + presets.missions.supply.helo:extend({name='tango-supply-blue'}), + presets.missions.supply.helo:extend({name='tango-supply-blue-1'}), + presets.missions.supply.transfer:extend({name='tango-transfer-blue'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='tango-comcenter-blue', + products = { + presets.defenses.blue.shorad:extend({name='tango-sam-blue'}), + presets.missions.attack.helo:extend({name='tango-cas-blue', altitude=200, expend=AI.Task.WeaponExpend.HALF }) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Tango.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Unal.lua ]]----------------- + +zones.unal = ZoneCommand:new("Unal") +zones.unal.initialState = { side=1 } +zones.unal.isHeloSpawn = true +zones.unal:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='unal-tent-red', + products = { + presets.special.red.infantry:extend({ name='unal-defense-red'}), + presets.defenses.red.infantry:extend({ name='unal-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='unal-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='unal-supply-red'}), + presets.missions.supply.helo:extend({name='unal-supply-red-2'}), + presets.missions.supply.transfer:extend({name='unal-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='unal-ammo-red', + products = { + presets.missions.attack.surface:extend({name='unal-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='unal-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='unal-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='unal-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='unal-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='unal-supply-blue'}), + presets.missions.supply.helo:extend({name='unal-supply-blue-2'}), + presets.missions.supply.transfer:extend({name='unal-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='unal-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='unal-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Unal.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Beslan.lua ]]----------------- + +zones.beslan = ZoneCommand:new("Beslan") +zones.beslan.initialState = { side=1 } +zones.beslan.keepActive = true +zones.beslan.maxResource = 50000 +zones.beslan:defineUpgrades({ + [1] = + { + presets.upgrades.basic.comPost:extend({ + name='beslan-compost-red', + products = { + presets.special.red.infantry:extend({ name='beslan-defense-red'}), + presets.defenses.red.infantry:extend({ name='beslan-garrison-red'}) + } + }), + presets.upgrades.supply.fuelTank:extend({ + name='beslan-fuel-red', + products = { + presets.missions.supply.helo:extend({name='beslan-supply-red-1'}), + presets.missions.supply.helo:extend({name='beslan-supply-red-2'}), + presets.missions.supply.convoy_escorted:extend({name='beslan-supply-red-3'}), + presets.missions.supply.transfer:extend({name='beslan-transfer-red'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='beslan-comcenter-red', + products = { + presets.defenses.red.sa5:extend({ name='beslan-airdef-red'}), + presets.missions.attack.sead:extend({name='beslan-sead-red', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.attack.strike:extend({name='beslan-strike-red', altitude=30000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.attack.strike:extend({name='beslan-strike-red-1', altitude=30000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.patrol.aircraft:extend({name='beslan-patrol-red', altitude=25000, range=25}) + } + }) + }, + [2] = + { + presets.upgrades.basic.comPost:extend({ + name='beslan-compost-blue', + products = { + presets.special.blue.infantry:extend({ name='beslan-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='beslan-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelTank:extend({ + name='beslan-fuel-blue', + products = { + presets.missions.supply.helo:extend({name='beslan-supply-blue-1'}), + presets.missions.supply.helo:extend({name='beslan-supply-blue-2'}), + presets.missions.supply.convoy_escorted:extend({name='beslan-supply-blue-3'}), + presets.missions.supply.transfer:extend({name='beslan-transfer-blue'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='beslan-comcenter-blue', + products = { + presets.defenses.blue.patriot:extend({ name='beslan-airdef-blue'}), + presets.missions.attack.sead:extend({name='beslan-sead-blue', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.attack.strike:extend({name='beslan-strike-blue', altitude=30000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.attack.strike:extend({name='beslan-strike-blue-1', altitude=30000, expend=AI.Task.WeaponExpend.ALL}), + presets.missions.patrol.aircraft:extend({name='beslan-patrol-blue', altitude=25000, range=25}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Beslan.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Bravo.lua ]]----------------- + +zones.bravo = ZoneCommand:new("Bravo") +zones.bravo.initialState = { side=2 } +zones.bravo:defineUpgrades({ + [1] = + { + presets.upgrades.basic.comPost:extend({ + name='bravo-compost-red', + products = { + presets.special.red.infantry:extend({ name='bravo-defense-red'}), + presets.defenses.red.infantry:extend({ name='bravo-garrison-red'}) + } + }), + presets.upgrades.supply.fuelTankFarp:extend({ + name='bravo-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({ name='bravo-supply-red'}), + presets.missions.supply.transfer:extend({name='bravo-transfer-red'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='bravo-comcenter-red', + products = { + presets.defenses.red.shorad:extend({ name='bravo-airdef-red'}), + presets.missions.attack.helo:extend({name='bravo-attack-red', altitude=200, expend=AI.Task.WeaponExpend.HALF}) + } + }) + }, + [2] = + { + presets.upgrades.basic.comPost:extend({ + name='bravo-compost-blue', + products = { + presets.special.blue.infantry:extend({ name='bravo-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='bravo-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelTankFarp:extend({ + name='bravo-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({ name='bravo-supply-blue'}), + presets.missions.supply.transfer:extend({name='bravo-transfer-blue'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='bravo-comcenter-blue', + products = { + presets.defenses.blue.shorad:extend({ name='bravo-airdef-blue'}), + presets.missions.attack.helo:extend({name='bravo-attack-blue', altitude=200, expend=AI.Task.WeaponExpend.HALF }) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Bravo.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/WeaponDepot.lua ]]----------------- + +zones.weapondepot = ZoneCommand:new("Weapon Depot") +zones.weapondepot.initialState = { side=1 } +zones.weapondepot:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='weapons-tent-red', + products = { + presets.special.red.infantry:extend({ name='weapons-defense-red'}), + presets.defenses.red.infantry:extend({ name='weapons-garrison-red'}) + } + }), + presets.upgrades.supply.ammoDepot:extend({ + name='weapons-ammo-red-1', + products = { + presets.missions.supply.convoy:extend({name='weapons-supply-red-1'}), + presets.missions.supply.transfer:extend({name='weapons-transfer-red-1'}) + } + }), + presets.upgrades.supply.ammoDepot:extend({ + name='weapons-ammo-red-2', + products = { + presets.missions.supply.convoy:extend({name='weapons-supply-red-2'}), + presets.missions.supply.transfer:extend({name='weapons-transfer-red-2'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='weapons-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='weapons-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='weapons-garrison-blue'}) + } + }), + presets.upgrades.supply.ammoDepot:extend({ + name='weapons-ammo-blue-1', + products = { + presets.missions.supply.convoy:extend({name='weapons-supply-blue-1'}), + presets.missions.supply.transfer:extend({name='weapons-transfer-blue-1'}) + } + }), + presets.upgrades.supply.ammoDepot:extend({ + name='weapons-ammo-blue-2', + products = { + presets.missions.supply.convoy:extend({name='weapons-supply-blue-2'}), + presets.missions.supply.transfer:extend({name='weapons-transfer-blue-2'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/WeaponDepot.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Delta.lua ]]----------------- + +zones.delta = ZoneCommand:new("Delta") +zones.delta.initialState = { side=2 } +zones.delta:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='delta-tent-red', + products = { + presets.special.red.infantry:extend({ name='delta-defense-red'}), + presets.defenses.red.infantry:extend({ name='delta-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='delta-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='delta-supply-red'}), + presets.missions.supply.transfer:extend({name='delta-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='delta-ammo-red', + products = { + presets.missions.attack.surface:extend({name='delta-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='delta-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='delta-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='delta-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='delta-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='delta-supply-blue'}), + presets.missions.supply.transfer:extend({name='delta-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='delta-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='delta-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Delta.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Cherkessk.lua ]]----------------- + +zones.cherkessk = ZoneCommand:new("Cherkessk") +zones.cherkessk.initialState = { side=1 } +zones.cherkessk.isHeloSpawn = true +zones.cherkessk:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='cherkessk-tent-red', + products = { + presets.special.red.infantry:extend({ name='cherkessk-defense-red'}), + presets.defenses.red.infantry:extend({ name='cherkessk-garrison-red'}) + } + }), + presets.upgrades.supply.fuelTankFarp:extend({ + name='cherkessk-fuel-red', + products = { + presets.missions.supply.helo:extend({name='cherkessk-supply-red'}), + presets.missions.supply.helo:extend({name='cherkessk-supply-red-1'}), + presets.missions.supply.transfer:extend({name='cherkessk-transfer-red'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='cherkessk-comcenter-red', + products = { + presets.defenses.red.shorad:extend({name='cherkessk-sam-red'}), + presets.missions.attack.helo:extend({name='cherkessk-cas-red', altitude=200, expend=AI.Task.WeaponExpend.HALF }), + presets.missions.attack.helo:extend({name='cherkessk-cas-red-1', altitude=200, expend=AI.Task.WeaponExpend.HALF }), + presets.missions.attack.surface:extend({name='cherkessk-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='cherkessk-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='cherkessk-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='cherkessk-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelTankFarp:extend({ + name='cherkessk-fuel-blue', + products = { + presets.missions.supply.helo:extend({name='cherkessk-supply-blue'}), + presets.missions.supply.helo:extend({name='cherkessk-supply-blue-1'}), + presets.missions.supply.transfer:extend({name='cherkessk-transfer-blue'}), + presets.missions.attack.surface:extend({name='cherkessk-assault-blue'}) + } + }), + presets.upgrades.airdef.comCenter:extend({ + name='cherkessk-comcenter-blue', + products = { + presets.defenses.blue.shorad:extend({name='cherkessk-sam-blue'}), + presets.missions.attack.helo:extend({name='cherkessk-cas-blue', altitude=200, expend=AI.Task.WeaponExpend.HALF }), + presets.missions.attack.helo:extend({name='cherkessk-cas-blue-1', altitude=200, expend=AI.Task.WeaponExpend.HALF }) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Cherkessk.lua ]]----------------- + + + +-----------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Juliett.lua ]]----------------- + +zones.juliett = ZoneCommand:new("Juliett") +zones.initialState = nil +zones.juliett:defineUpgrades({ + [1] = { + presets.upgrades.basic.tent:extend({ + name='juliett-tent-red', + products = { + presets.special.red.infantry:extend({ name='juliett-defense-red'}), + presets.defenses.red.infantry:extend({ name='juliett-garrison-red'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='juliett-fuel-red', + products = { + presets.missions.supply.convoy_escorted:extend({name='juliett-supply-red'}), + presets.missions.supply.transfer:extend({name='juliett-transfer-red'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='juliett-ammo-red', + products = { + presets.missions.attack.surface:extend({name='juliett-assault-red'}) + } + }) + }, + [2] = { + presets.upgrades.basic.tent:extend({ + name='juliett-tent-blue', + products = { + presets.special.blue.infantry:extend({ name='juliett-defense-blue'}), + presets.defenses.blue.infantry:extend({ name='juliett-garrison-blue'}) + } + }), + presets.upgrades.supply.fuelCache:extend({ + name='juliett-fuel-blue', + products = { + presets.missions.supply.convoy_escorted:extend({name='juliett-supply-blue'}), + presets.missions.supply.transfer:extend({name='juliett-transfer-blue'}) + } + }), + presets.upgrades.attack.ammoCache:extend({ + name='juliett-ammo-blue', + products = { + presets.missions.attack.surface:extend({name='juliett-assault-blue'}) + } + }) + } +}) + +-----------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Juliett.lua ]]----------------- + + + + cm = ConnectionManager:new() + cm:addConnection('Batumi', 'Alpha') + cm:addConnection('Alpha', 'Bravo') + cm:addConnection('Bravo', 'Kobuleti') + cm:addConnection('Bravo', 'Factory') + cm:addConnection('Kobuleti', 'Factory') + cm:addConnection('Kobuleti', 'Charlie') + cm:addConnection('Foxtrot', 'Charlie') + cm:addConnection('Foxtrot', 'Kobuleti') + cm:addConnection('Delta','Foxtrot') + cm:addConnection('Delta','Kobuleti') + cm:addConnection('Delta','Factory') + cm:addConnection('Echo','Charlie') + cm:addConnection('Golf','Echo') + cm:addConnection('Golf','Foxtrot') + cm:addConnection('India','Delta') + cm:addConnection('Hotel','Golf') + cm:addConnection('Hotel','Foxtrot') + cm:addConnection('Hotel','Delta') + cm:addConnection('Hotel','India') + cm:addConnection('Juliett','Echo') + cm:addConnection('Juliett','Golf') + cm:addConnection('Senaki','Juliett') + cm:addConnection('Senaki','Golf') + cm:addConnection('Senaki','Hotel') + cm:addConnection('Kutaisi','Hotel') + cm:addConnection('Kutaisi','India') + cm:addConnection('Kilo','Juliett') + cm:addConnection('Mike','Kutaisi') + cm:addConnection('Mike','Senaki') + cm:addConnection('Romeo','Mike') + cm:addConnection('Romeo','Kutaisi') + cm:addConnection('Weapon Depot','Juliett') + cm:addConnection('Weapon Depot','Senaki') + cm:addConnection('Weapon Depot','Kilo') + cm:addConnection('November','Weapon Depot') + cm:addConnection('November','Senaki') + cm:addConnection('November','Mike') + cm:addConnection('Oil Fields','Romeo') + cm:addConnection('Quebec','Kilo') + cm:addConnection('Zugdidi','Weapon Depot') + cm:addConnection('Zugdidi','Quebec') + cm:addConnection('Zugdidi','November') + cm:addConnection('Zugdidi','Kilo') + cm:addConnection('Distillery','November') + cm:addConnection('Distillery','Mike') + cm:addConnection('Zugdidi','Papa') + cm:addConnection('November','Papa') + cm:addConnection('Sierra','Papa') + cm:addConnection('Sierra','Zugdidi') + cm:addConnection('Sierra','Uniform') + cm:addConnection('Mine','Uniform') + cm:addConnection('Tango','Quebec') + cm:addConnection('Tango','Zugdidi') + cm:addConnection('Sierra','Tango') + cm:addConnection('Whiskey','Tango') + cm:addConnection('Ochamchira','Tango') + cm:addConnection('Ochamchira','Whiskey') + cm:addConnection('Ochamchira','Farm') + cm:addConnection('Ochamchira','Zulu') + cm:addConnection('Farm','Zulu') + cm:addConnection('Sukhumi','Zulu') + cm:addConnection('Lentehi','Distillery', true, 3000) + cm:addConnection('Lentehi','Babugent', true, 5000) + cm:addConnection('Nalchik','Babugent') + cm:addConnection('Victor','Distillery', true, 2000) + cm:addConnection('Victor','Romeo') + cm:addConnection('Victor','Lentehi') + cm:addConnection('Victor','Oil Fields', true, 2000) + cm:addConnection('Victor','Oni') + cm:addConnection('Unal','Oni', true, 4500) + cm:addConnection('Beslan','Unal') + cm:addConnection('Digora','Beslan') + cm:addConnection('Digora','Unal') + cm:addConnection('Digora','Babugent') + cm:addConnection('Terek','Digora') + cm:addConnection('Terek','Nalchik') + cm:addConnection('Terek','Beslan') + cm:addConnection('Prohladniy','Terek') + cm:addConnection('Prohladniy','Nalchik') + cm:addConnection('Malgobek','Terek') + cm:addConnection('Malgobek','Beslan') + cm:addConnection('Lima','Mine') + cm:addConnection('Lima','Lentehi', true, 4000) + cm:addConnection('Tyrnyauz','Lima', true, 4000) + cm:addConnection('Tyrnyauz','Nalchik') + cm:addConnection('XRay','Sukhumi') + cm:addConnection('Oscar','Sukhumi') + cm:addConnection('Oscar','XRay') + cm:addConnection('Mozdok','Malgobek') + cm:addConnection('Mozdok','Prohladniy') + cm:addConnection('Gudauta','Oscar') + cm:addConnection('Yankee','Gudauta') + cm:addConnection('Sochi','Yankee') + cm:addConnection('Refinery','XRay', true, 4000) + cm:addConnection('Refinery','Humara') + cm:addConnection('Intel Center','Tyrnyauz') + cm:addConnection('Intel Center','Nalchik') + cm:addConnection('Intel Center','Prohladniy') + cm:addConnection('Intel Center','Kislovodsk') + cm:addConnection('Mineralnye','Intel Center') + cm:addConnection('Kislovodsk','Mineralnye') + cm:addConnection('Tallyk','Mineralnye') + cm:addConnection('Tallyk','Kislovodsk') + cm:addConnection('Power Plant','Mineralnye') + cm:addConnection('Power Plant','Tallyk') + cm:addConnection('Cherkessk','Tallyk') + cm:addConnection('Cherkessk','Power Plant') + cm:addConnection('Cherkessk','Humara') +end + +ZoneCommand.setNeighbours(cm) + +bm = BattlefieldManager:new() + +mc = MarkerCommands:new() + +pt = PlayerTracker:new(mc) + +mt = MissionTracker:new(pt, mc) + +st = SquadTracker:new() + +ct = CSARTracker:new() + +pl = PlayerLogistics:new(mt, pt, st, ct) + +gci = GCI:new(2) + +gm = GroupMonitor:new(cm) +ZoneCommand.groupMonitor = gm + +-- PlayerLogistics:registerSquadGroup(squadType, groupname, weight,cost,jobtime,extracttime, squadSize) +pl:registerSquadGroup(PlayerLogistics.infantryTypes.capture, 'capture-squad', 700, 200, 60, 60*30, 4) +pl:registerSquadGroup(PlayerLogistics.infantryTypes.sabotage, 'sabotage-squad', 800, 500, 60*5, 60*30, 4) +pl:registerSquadGroup(PlayerLogistics.infantryTypes.ambush, 'ambush-squad', 900, 300, 60*20, 60*30, 5) +pl:registerSquadGroup(PlayerLogistics.infantryTypes.engineer, 'engineer-squad', 200, 1000,60, 60*30, 2) +pl:registerSquadGroup(PlayerLogistics.infantryTypes.manpads, 'manpads-squad', 900, 500, 60*20, 60*30, 5) +pl:registerSquadGroup(PlayerLogistics.infantryTypes.spy, 'spy-squad', 100, 300, 60*10, 60*30, 1) +pl:registerSquadGroup(PlayerLogistics.infantryTypes.rapier, 'rapier-squad', 1200,2000,60*60, 60*30, 8) + +Group.getByName('jtacDrone'):destroy() +CommandFunctions.jtac = JTAC:new({name = 'jtacDrone'}) + +pm = PersistenceManager:new(savefile, gm, st, ct, pl) +pm:load() + +if pm:canRestore() then + pm:restoreZones() + pm:restoreAIMissions() + pm:restoreBattlefield() + pm:restoreCsar() + pm:restoreSquads() +else + --initial states + Starter.start(zones) +end + +timer.scheduleFunction(function(param, time) + pm:save() + env.info("Mission state saved") + return time+60 +end, zones, timer.getTime()+60) + + +--make sure support units are present where needed +ensureSpawn = { + ['golf-farp-suport'] = zones.golf, + ['november-farp-suport'] = zones.november, + ['tango-farp-suport'] = zones.tango, + ['sierra-farp-suport'] = zones.sierra, + ['cherkessk-farp-suport'] = zones.cherkessk, + ['unal-farp-suport'] = zones.unal, + ['tyrnyauz-farp-suport'] = zones.tyrnyauz +} + +for grname, zn in pairs(ensureSpawn) do + local g = Group.getByName(grname) + if g then g:destroy() end +end + +timer.scheduleFunction(function(param, time) + + for grname, zn in pairs(ensureSpawn) do + local g = Group.getByName(grname) + if zn.side == 2 then + if not g then + local err, msg = pcall(mist.respawnGroup,grname,true) + if not err then + env.info("ERROR spawning "..grname) + env.info(msg) + end + end + else + if g then g:destroy() end + end + end + + return time+30 +end, {}, timer.getTime()+30) + + +--supply injection +local blueSupply = {'offmap-supply-blue-1','offmap-supply-blue-2','offmap-supply-blue-3','offmap-supply-blue-4','offmap-supply-blue-5'} +local redSupply = {'offmap-supply-red-1','offmap-supply-red-2','offmap-supply-red-3','offmap-supply-red-4','offmap-supply-red-5'} +local offmapZones = { + zones.batumi, + zones.sochi, + zones.nalchik, + zones.beslan, + zones.mozdok, + zones.mineralnye, +-- zones.senaki, +-- zones.sukhumi, +-- zones.gudauta, +-- zones.kobuleti, +} + +supplyPointRegistry = { + blue = {}, + red = {} +} + +for i,v in ipairs(blueSupply) do + local g = Group.getByName(v) + if g then + supplyPointRegistry.blue[v] = g:getUnit(1):getPoint() + end +end + +for i,v in ipairs(redSupply) do + local g = Group.getByName(v) + if g then + supplyPointRegistry.red[v] = g:getUnit(1):getPoint() + end +end + +offmapSupplyRegistry = {} +timer.scheduleFunction(function(param, time) + local availableBlue = {} + for i,v in ipairs(param.blue) do + if offmapSupplyRegistry[v] == nil then + table.insert(availableBlue, v) + end + end + + local availableRed = {} + for i,v in ipairs(param.red) do + if offmapSupplyRegistry[v] == nil then + table.insert(availableRed, v) + end + end + + local redtargets = {} + local bluetargets = {} + for _, zn in ipairs(param.offmapZones) do + if zn:needsSupplies(3000) then + local isOnRoute = false + for _,data in pairs(offmapSupplyRegistry) do + if data.zone.name == zn.name then + isOnRoute = true + break + end + end + if not isOnRoute then + if zn.side == 1 then + table.insert(redtargets, zn) + elseif zn.side == 2 then + table.insert(bluetargets, zn) + end + end + end + end + + if #availableRed > 0 and #redtargets > 0 then + local zn = redtargets[math.random(1,#redtargets)] + + local red = nil + local minD = 999999999 + for i,v in ipairs(availableRed) do + local d = mist.utils.get2DDist(zn.zone.point, supplyPointRegistry.red[v]) + if d < minD then + red = v + minD = d + end + end + + if not red then red = availableRed[math.random(1,#availableRed)] end + + local gr = red + red = nil + mist.respawnGroup(gr, true) + offmapSupplyRegistry[gr] = {zone = zn, assigned = timer.getAbsTime()} + env.info(gr..' was deployed') + timer.scheduleFunction(function(param,time) + local g = Group.getByName(param.group) + TaskExtensions.landAtAirfield(g, param.target.zone.point) + env.info(param.group..' going to '..param.target.name) + end, {group=gr, target=zn}, timer.getTime()+2) + end + + if #availableBlue > 0 and #bluetargets>0 then + local zn = bluetargets[math.random(1,#bluetargets)] + + local blue = nil + local minD = 999999999 + for i,v in ipairs(availableBlue) do + local d = mist.utils.get2DDist(zn.zone.point, supplyPointRegistry.blue[v]) + if d < minD then + blue = v + minD = d + end + end + + if not blue then blue = availableBlue[math.random(1,#availableBlue)] end + + local gr = blue + blue = nil + mist.respawnGroup(gr, true) + offmapSupplyRegistry[gr] = {zone = zn, assigned = timer.getAbsTime()} + env.info(gr..' was deployed') + timer.scheduleFunction(function(param,time) + local g = Group.getByName(param.group) + TaskExtensions.landAtAirfield(g, param.target.zone.point) + env.info(param.group..' going to '..param.target.name) + end, {group=gr, target=zn}, timer.getTime()+2) + end + + return time+(60*5) +end, {blue = blueSupply, red = redSupply, offmapZones = offmapZones}, timer.getTime()+60) + + + +timer.scheduleFunction(function(param, time) + + for groupname,data in pairs(offmapSupplyRegistry) do + local gr = Group.getByName(groupname) + if not gr then + offmapSupplyRegistry[groupname] = nil + env.info(groupname..' was destroyed') + end + + if gr and ((timer.getAbsTime() - data.assigned) > (60*60)) then + gr:destroy() + offmapSupplyRegistry[groupname] = nil + env.info(groupname..' despawned due to being alive for too long') + end + + if gr and Utils.allGroupIsLanded(gr) and Utils.someOfGroupInZone(gr, data.zone.name) then + data.zone:addResource(15000) + gr:destroy() + offmapSupplyRegistry[groupname] = nil + env.info(groupname..' landed at '..data.zone.name..' and delivered 15000 resources') + end + end + + return time+180 +end, {}, timer.getTime()+180) \ No newline at end of file From 5c9a5a5e35bddf5231b6c00a5359818ee4ffeed6 Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Sun, 10 Sep 2023 16:18:22 +0300 Subject: [PATCH 113/243] Split init.lua code into three pieces. --- resources/plugins/pretense/init_body.lua | 4564 -------------------- resources/plugins/pretense/init_footer.lua | 4530 ------------------- resources/plugins/pretense/init_header.lua | 4050 +---------------- 3 files changed, 1 insertion(+), 13143 deletions(-) diff --git a/resources/plugins/pretense/init_body.lua b/resources/plugins/pretense/init_body.lua index 9df8452d..a4452456 100644 --- a/resources/plugins/pretense/init_body.lua +++ b/resources/plugins/pretense/init_body.lua @@ -1,4426 +1,3 @@ - - -local savefile = 'pretense_1.1.json' -if lfs then - local dir = lfs.writedir()..'Missions/Saves/' - lfs.mkdir(dir) - savefile = dir..savefile - env.info('Pretense - Save file path: '..savefile) -end - - -do - TemplateDB.templates["infantry-red"] = { - units = { - "BTR_D", - "T-90", - "T-90", - "Infantry AK ver2", - "Infantry AK", - "Infantry AK", - "Paratrooper RPG-16", - "Infantry AK ver3", - "SA-18 Igla manpad" - }, - skill = "Excellent", - dataCategory= TemplateDB.type.group - } - - TemplateDB.templates["infantry-blue"] = { - units = { - "M1045 HMMWV TOW", - "Soldier stinger", - "Soldier M4 GRG", - "Soldier M4 GRG", - "Soldier M4 GRG", - "Soldier M4 GRG", - "Soldier M4 GRG", - "M1043 HMMWV Armament" - }, - skill = "Excellent", - dataCategory= TemplateDB.type.group - } - - TemplateDB.templates["defense-red"] = { - units = { - "Infantry AK ver2", - "Infantry AK", - "Infantry AK ver3", - "Paratrooper RPG-16", - "SA-18 Igla manpad" - }, - skill = "Excellent", - dataCategory= TemplateDB.type.group - } - - TemplateDB.templates["defense-blue"] = { - units = { - "Soldier M4 GRG", - "Soldier M4 GRG", - "Soldier M4 GRG", - "Soldier RPG", - "Soldier stinger", - }, - skill = "Excellent", - dataCategory= TemplateDB.type.group - } - - TemplateDB.templates["shorad-red"] = { - units = { - "Strela-10M3", - "Strela-10M3", - "Ural-4320T", - "2S6 Tunguska" - }, - maxDist = 300, - skill = "Excellent", - dataCategory= TemplateDB.type.group - } - - TemplateDB.templates["shorad-blue"] = { - units = { - "Roland ADS", - "M48 Chaparral", - "M 818", - "Gepard", - "Gepard" - }, - maxDist = 300, - skill = "Excellent", - dataCategory= TemplateDB.type.group - } - - TemplateDB.templates["sam-red"] = { - units = { - "p-19 s-125 sr", - "Ural-4320T", - "Ural-4320T", - "S_75M_Volhov", - "S_75M_Volhov", - "S_75M_Volhov", - "S_75M_Volhov", - "S_75M_Volhov", - "Tor 9A331", - "SNR_75V" - }, - maxDist = 300, - skill = "Excellent", - dataCategory= TemplateDB.type.group - } - - TemplateDB.templates["sam-blue"] = { - units = { - "Hawk pcp", - "Hawk cwar", - "Hawk ln", - "Hawk ln", - "Hawk ln", - "Hawk ln", - "Hawk ln", - "Hawk tr", - "M 818", - "Hawk sr" - }, - maxDist = 300, - skill = "Good", - dataCategory= TemplateDB.type.group - } - - TemplateDB.templates["patriot"] = { - units = { - "Patriot cp", - "Patriot str", - "M 818", - "M 818", - "Patriot ln", - "Patriot ln", - "Patriot ln", - "Patriot ln", - "Patriot str", - "Patriot str", - "Patriot str", - "Patriot EPP", - "Patriot ECS", - "Patriot AMG" - }, - maxDist = 300, - skill = "Good", - dataCategory= TemplateDB.type.group - } - - TemplateDB.templates["sa3"] = { - units = { - "p-19 s-125 sr", - "snr s-125 tr", - "5p73 s-125 ln", - "5p73 s-125 ln", - "Ural-4320T", - "5p73 s-125 ln", - "5p73 s-125 ln" - }, - maxDist = 300, - skill = "Excellent", - dataCategory= TemplateDB.type.group - } - - TemplateDB.templates["sa6"] = { - units = { - "Kub 1S91 str", - "Kub 2P25 ln", - "Kub 2P25 ln", - "Kub 2P25 ln", - "Kub 2P25 ln", - "2S6 Tunguska", - "Ural-4320T", - "2S6 Tunguska", - "Kub 2P25 ln" - }, - maxDist = 300, - skill = "Excellent", - dataCategory= TemplateDB.type.group - } - - TemplateDB.templates["sa10"] = { - units = { - "S-300PS 54K6 cp", - "S-300PS 5P85C ln", - "S-300PS 5P85C ln", - "S-300PS 5P85C ln", - "GAZ-66", - "GAZ-66", - "GAZ-66", - "S-300PS 5P85C ln", - "S-300PS 5P85C ln", - "S-300PS 5P85C ln", - "S-300PS 40B6MD sr", - "S-300PS 40B6M tr", - "S-300PS 64H6E sr" - }, - maxDist = 300, - skill = "Excellent", - dataCategory= TemplateDB.type.group - } - - TemplateDB.templates["sa5"] = { - units = { - "RLS_19J6", - "Ural-4320T", - "Ural-4320T", - "RPC_5N62V", - "S-200_Launcher", - "S-200_Launcher", - "S-200_Launcher", - "S-200_Launcher", - "S-200_Launcher", - "S-200_Launcher" - }, - maxDist = 300, - skill = "Excellent", - dataCategory= TemplateDB.type.group - } - - TemplateDB.templates["sa11"] = { - units = { - "SA-11 Buk SR 9S18M1", - "SA-11 Buk LN 9A310M1", - "SA-11 Buk LN 9A310M1", - "SA-11 Buk LN 9A310M1", - "SA-11 Buk LN 9A310M1", - "SA-11 Buk LN 9A310M1", - "2S6 Tunguska", - "SA-11 Buk SR 9S18M1", - "GAZ-66", - "GAZ-66", - "SA-11 Buk CC 9S470M1" - }, - maxDist = 300, - skill = "Excellent", - dataCategory= TemplateDB.type.group - } - - TemplateDB.templates["nasams"] = { - units = { - "NASAMS_Command_Post", - "NASAMS_Radar_MPQ64F1", - "Vulcan", - "M 818", - "M 818", - "Roland ADS", - "Roland ADS", - "NASAMS_LN_C", - "NASAMS_LN_C", - "NASAMS_LN_C", - "NASAMS_LN_C", - "NASAMS_Radar_MPQ64F1", - "NASAMS_Radar_MPQ64F1", - "NASAMS_Radar_MPQ64F1" - }, - maxDist = 300, - skill = "Excellent", - dataCategory= TemplateDB.type.group - } -end - -presets = { - upgrades = { - basic = { - tent = Preset:new({ - display = 'Tent', - cost = 1500, - type = 'upgrade', - template = "tent" - }), - comPost = Preset:new({ - display = 'Barracks', - cost = 1500, - type = 'upgrade', - template = "barracks" - }), - outpost = Preset:new({ - display = 'Outpost', - cost = 1500, - type = 'upgrade', - template = "outpost" - }) - }, - attack = { - ammoCache = Preset:new({ - display = 'Ammo Cache', - cost = 1500, - type = 'upgrade', - template = "ammo-cache" - }), - ammoDepot = Preset:new({ - display = 'Ammo Depot', - cost = 2000, - type = 'upgrade', - template = "ammo-depot" - }) - }, - supply = { - fuelCache = Preset:new({ - display = 'Fuel Cache', - cost = 1500, - type = 'upgrade', - template = "fuel-cache" - }), - fuelTank = Preset:new({ - display = 'Fuel Tank', - cost = 1500, - type = 'upgrade', - template = "fuel-tank-big" - }), - fuelTankFarp = Preset:new({ - display = 'Fuel Tank', - cost = 1500, - type = 'upgrade', - template = "fuel-tank-small" - }), - factory1 = Preset:new({ - display='Factory', - cost = 2000, - type ='upgrade', - income = 20, - template = "factory-1" - }), - factory2 = Preset:new({ - display='Factory', - cost = 2000, - type ='upgrade', - income = 20, - template = "factory-2" - }), - factoryTank = Preset:new({ - display='Storage Tank', - cost = 1500, - type ='upgrade', - income = 10, - template = "chem-tank" - }), - ammoDepot = Preset:new({ - display = 'Ammo Depot', - cost = 2000, - type = 'upgrade', - income = 40, - template = "ammo-depot" - }), - oilPump = Preset:new({ - display = 'Oil Pump', - cost = 1500, - type = 'upgrade', - income = 20, - template = "oil-pump" - }), - hangar = Preset:new({ - display = 'Hangar', - cost = 2000, - type = 'upgrade', - income = 30, - template = "hangar" - }), - excavator = Preset:new({ - display = 'Excavator', - cost = 2000, - type = 'upgrade', - income = 20, - template = "excavator" - }), - farm1 = Preset:new({ - display = 'Farm House', - cost = 2000, - type = 'upgrade', - income = 40, - template = "farm-house-1" - }), - farm2 = Preset:new({ - display = 'Farm House', - cost = 2000, - type = 'upgrade', - income = 40, - template = "farm-house-2" - }), - refinery1 = Preset:new({ - display='Refinery', - cost = 2000, - type ='upgrade', - income = 100, - template = "factory-1" - }), - powerplant1 = Preset:new({ - display='Power Plant', - cost = 1500, - type ='upgrade', - income = 25, - template = "factory-1" - }), - powerplant2 = Preset:new({ - display='Power Plant', - cost = 1500, - type ='upgrade', - income = 25, - template = "factory-2" - }), - antenna = Preset:new({ - display='Antenna', - cost = 1000, - type ='upgrade', - income = 10, - template = "antenna" - }), - hq = Preset:new({ - display='HQ Building', - cost = 2000, - type ='upgrade', - income = 50, - template = "tv-tower" - }) - }, - airdef = { - comCenter = Preset:new({ - display = 'Command Center', - cost = 2500, - type = 'upgrade', - template = "command-center" - }) - } - }, - defenses = { - red = { - infantry = Preset:new({ - display = 'Infantry', - cost=2000, - type='defense', - template='infantry-red', - }), - shorad = Preset:new({ - display = 'SAM', - cost=2500, - type='defense', - template='shorad-red', - }), - sam = Preset:new({ - display = 'SAM', - cost=3000, - type='defense', - template='sam-red', - }), - sa10 = Preset:new({ - display = 'SAM', - cost=3000, - type='defense', - template='sa10', - }), - sa5 = Preset:new({ - display = 'SAM', - cost=3000, - type='defense', - template='sa5', - }), - sa3 = Preset:new({ - display = 'SAM', - cost=3000, - type='defense', - template='sa3', - }), - sa6 = Preset:new({ - display = 'SAM', - cost=3000, - type='defense', - template='sa6', - }), - sa11 = Preset:new({ - display = 'SAM', - cost=3000, - type='defense', - template='sa11', - }) - }, - blue = { - infantry = Preset:new({ - display = 'Infantry', - cost=2000, - type='defense', - template='infantry-blue', - }), - shorad = Preset:new({ - display = 'SAM', - cost=2500, - type='defense', - template='shorad-blue', - }), - sam = Preset:new({ - display = 'SAM', - cost=3000, - type='defense', - template='sam-blue', - }), - patriot = Preset:new({ - display = 'SAM', - cost=3000, - type='defense', - template='patriot', - }), - nasams = Preset:new({ - display = 'SAM', - cost=3000, - type='defense', - template='nasams', - }) - } - }, - missions = { - supply = { - convoy = Preset:new({ - display = 'Supply convoy', - cost = 4000, - type = 'mission', - missionType = ZoneCommand.missionTypes.supply_convoy - }), - convoy_escorted = Preset:new({ - display = 'Supply convoy', - cost = 3000, - type = 'mission', - missionType = ZoneCommand.missionTypes.supply_convoy - }), - helo = Preset:new({ - display = 'Supply helicopter', - cost = 2500, - type='mission', - missionType = ZoneCommand.missionTypes.supply_air - }), - transfer = Preset:new({ - display = 'Supply transfer', - cost = 1000, - type='mission', - missionType = ZoneCommand.missionTypes.supply_transfer - }) - }, - attack = { - surface = Preset:new({ - display = 'Ground assault', - cost = 100, - type = 'mission', - missionType = ZoneCommand.missionTypes.assault, - }), - cas = Preset:new({ - display = 'CAS', - cost = 200, - type='mission', - missionType = ZoneCommand.missionTypes.cas - }), - bai = Preset:new({ - display = 'BAI', - cost = 200, - type='mission', - missionType = ZoneCommand.missionTypes.bai - }), - strike = Preset:new({ - display = 'Strike', - cost = 300, - type='mission', - missionType = ZoneCommand.missionTypes.strike - }), - sead = Preset:new({ - display = 'SEAD', - cost = 200, - type='mission', - missionType = ZoneCommand.missionTypes.sead - }), - helo = Preset:new({ - display = 'CAS', - cost = 100, - type='mission', - missionType = ZoneCommand.missionTypes.cas_helo - }) - }, - patrol={ - aircraft = Preset:new({ - display= "Patrol", - cost = 100, - type='mission', - missionType = ZoneCommand.missionTypes.patrol - }) - }, - support ={ - awacs = Preset:new({ - display= "AWACS", - cost = 300, - type='mission', - bias='5', - missionType = ZoneCommand.missionTypes.awacs - }), - tanker = Preset:new({ - display= "Tanker", - cost = 200, - type='mission', - bias='2', - missionType = ZoneCommand.missionTypes.tanker - }) - } - }, - special = { - red = { - infantry = Preset:new({ - display = 'Infantry', - cost=-1, - type='defense', - template='defense-red', - }), - }, - blue = { - infantry = Preset:new({ - display = 'Infantry', - cost=-1, - type='defense', - template='defense-blue', - }) - } - } -} - -zones = {} -do - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Batumi.lua ]]----------------- - -zones.batumi = ZoneCommand:new('Batumi') -zones.batumi.initialState = { side=2 } -zones.batumi.keepActive = true -zones.batumi.isHeloSpawn = true -zones.batumi.isPlaneSpawn = true -zones.batumi.maxResource = 50000 -zones.batumi:defineUpgrades({ - [1] = { --red side - presets.upgrades.basic.comPost:extend({ - name = 'batumi-com-red', - products = { - presets.special.red.infantry:extend({ name='batumi-defense-red'}), - presets.defenses.red.infantry:extend({ name='batumi-garrison-red' }) - } - }), - }, - [2] = --blue side - { - presets.upgrades.basic.comPost:extend({ - name = 'batumi-com-blue', - products = { - presets.special.blue.infantry:extend({ name='batumi-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='batumi-garrison-blue' }) - } - }), - presets.upgrades.supply.fuelTank:extend({ - name = 'batumi-fueltank-blue', - products = { - presets.missions.supply.convoy_escorted:extend({ name='batumi-supply-convoy-1'}), - presets.missions.supply.helo:extend({ name='batumi-supply-blue-1' }), - presets.missions.supply.transfer:extend({name='batumi-transfer-blue'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name = 'batumi-mission-command-blue', - products = { - presets.defenses.blue.shorad:extend({ name='batumi-sam-blue' }), - presets.missions.attack.sead:extend({name='batumi-sead-blue-1', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.attack.cas:extend({name='batumi-cas-blue-1', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.bai:extend({name='batumi-cas-blue-1', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.strike:extend({name='batumi-strike-blue-1', altitude=20000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.patrol.aircraft:extend({name='batumi-patrol-blue-1', altitude=25000, range=25}), - presets.missions.support.awacs:extend({name='batumi-awacs-blue', altitude=30000, freq=257.5}), - presets.missions.support.tanker:extend({name='batumi-tanker-blue', altitude=25000, freq=257, tacan='37', variant="Drogue"}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Batumi.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Mike.lua ]]----------------- - -zones.mike = ZoneCommand:new("Mike") -zones.mike.initialState = { side=1 } -zones.mike.keepActive = true -zones.mike:defineUpgrades({ - [1] = - { - presets.upgrades.basic.tent:extend({ - name='mike-tent-red', - products = { - presets.special.red.infantry:extend({ name='mike-defense-red'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='mike-comcenter-red', - products = { - presets.defenses.red.sam:extend({ name='mike-sam-red'}) - } - }) - }, - [2] = - { - presets.upgrades.basic.tent:extend({ - name='mike-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='mike-defense-blue'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='mike-comcenter-blue', - products = { - presets.defenses.blue.sam:extend({ name='mike-sam-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Mike.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Tyrnyauz.lua ]]----------------- - -zones.tyrnyauz = ZoneCommand:new("Tyrnyauz") -zones.tyrnyauz.initialState = { side=1 } -zones.tyrnyauz.isHeloSpawn = true -zones.tyrnyauz:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='tyrnyauz-tent-red', - products = { - presets.special.red.infantry:extend({ name='tyrnyauz-defense-red'}), - presets.defenses.red.infantry:extend({ name='tyrnyauz-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='tyrnyauz-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='tyrnyauz-supply-red'}), - presets.missions.supply.helo:extend({name='tyrnyauz-supply-red-2'}), - presets.missions.supply.transfer:extend({name='tyrnyauz-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='tyrnyauz-ammo-red', - products = { - presets.missions.attack.surface:extend({name='tyrnyauz-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='tyrnyauz-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='tyrnyauz-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='tyrnyauz-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='tyrnyauz-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='tyrnyauz-supply-blue'}), - presets.missions.supply.helo:extend({name='tyrnyauz-supply-blue-2'}), - presets.missions.supply.transfer:extend({name='tyrnyauz-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='tyrnyauz-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='tyrnyauz-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Tyrnyauz.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/India.lua ]]----------------- - -zones.india = ZoneCommand:new("India") -zones.india.initialState = nil -zones.india:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='india-tent-red', - products = { - presets.special.red.infantry:extend({ name='india-defense-red'}), - presets.defenses.red.infantry:extend({ name='india-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='india-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='india-supply-red'}), - presets.missions.supply.transfer:extend({name='india-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='india-ammo-red', - products = { - presets.missions.attack.surface:extend({name='india-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='india-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='india-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='india-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='india-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='india-supply-blue'}), - presets.missions.supply.transfer:extend({name='india-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='india-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='india-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/India.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/IntelCenter.lua ]]----------------- - -zones.intelcenter = ZoneCommand:new("Intel Center") -zones.intelcenter.initialState = { side=1 } -zones.intelcenter:defineUpgrades({ - [1] = - { - presets.upgrades.basic.tent:extend({ - name='intelcenter-tent-red', - products = { - presets.special.red.infantry:extend({ name='intelcenter-defense-red'}), - presets.defenses.red.infantry:extend({ name='intelcenter-garrison-red'}) - } - }), - presets.upgrades.supply.hq:extend({ - name='intelcenter-hq-red', - products = { - presets.missions.supply.convoy:extend({ name='intelcenter-supply-red'}), - presets.missions.supply.convoy:extend({ name='intelcenter-supply-red-1'}), - presets.missions.supply.transfer:extend({name='intelcenter-transfer-red'}) - } - }), - presets.upgrades.supply.antenna:extend({ - name='intelcenter-antenna-red', - products = { - } - }), - presets.upgrades.supply.antenna:extend({ - name='intelcenter-antenna-red-1', - products = { - } - }), - presets.upgrades.supply.antenna:extend({ - name='intelcenter-antenna-red-2', - products = { - } - }) - }, - [2] = - { - presets.upgrades.basic.tent:extend({ - name='intelcenter-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='intelcenter-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='intelcenter-garrison-blue'}) - } - }), - presets.upgrades.supply.hq:extend({ - name='intelcenter-hq-blue', - products = { - presets.missions.supply.convoy:extend({ name='intelcenter-supply-blue'}), - presets.missions.supply.convoy:extend({ name='intelcenter-supply-blue-1'}), - presets.missions.supply.transfer:extend({name='intelcenter-transfer-blue'}) - } - }), - presets.upgrades.supply.antenna:extend({ - name='intelcenter-antenna-blue', - products = { - } - }), - presets.upgrades.supply.antenna:extend({ - name='intelcenter-antenna-blue-1', - products = { - } - }), - presets.upgrades.supply.antenna:extend({ - name='intelcenter-antenna-blue-2', - products = { - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/IntelCenter.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Mineralnye.lua ]]----------------- - -zones.mineralnye = ZoneCommand:new("Mineralnye") -zones.mineralnye.initialState = { side=1 } -zones.mineralnye.keepActive = true -zones.mineralnye.isHeloSpawn = true -zones.mineralnye.isPlaneSpawn = true -zones.mineralnye:defineUpgrades({ - [1] = - { - presets.upgrades.basic.comPost:extend({ - name='mineralnye-compost-red', - products = { - presets.special.red.infantry:extend({ name='mineralnye-defense-red'}), - presets.defenses.red.infantry:extend({ name='mineralnye-garrison-red'}) - } - }), - presets.upgrades.supply.fuelTank:extend({ - name='mineralnye-fuel-red', - products = { - presets.missions.supply.helo:extend({name='mineralnye-supply-red'}), - presets.missions.supply.helo:extend({name='mineralnye-supply-red-1'}), - presets.missions.supply.transfer:extend({name='mineralnye-transfer-red'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='mineralnye-comcenter-red', - products = { - presets.defenses.red.sa11:extend({ name='mineralnye-airdef-red'}), - presets.missions.attack.cas:extend({name='mineralnye-cas-red', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.bai:extend({name='mineralnye-cas-red', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.strike:extend({name='mineralnye-strike-red', altitude=30000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.attack.strike:extend({name='mineralnye-strike-red-1', altitude=30000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.patrol.aircraft:extend({name='mineralnye-patrol-red', altitude=25000, range=25}) - } - }) - }, - [2] = - { - presets.upgrades.basic.comPost:extend({ - name='mineralnye-compost-blue', - products = { - presets.special.blue.infantry:extend({ name='mineralnye-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='mineralnye-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelTank:extend({ - name='mineralnye-fuel-blue', - products = { - presets.missions.supply.helo:extend({name='mineralnye-supply-blue'}), - presets.missions.supply.helo:extend({name='mineralnye-supply-blue-1'}), - presets.missions.supply.transfer:extend({name='mineralnye-transfer-blue'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='mineralnye-comcenter-blue', - products = { - presets.defenses.blue.nasams:extend({ name='mineralnye-airdef-blue'}), - presets.missions.attack.cas:extend({name='mineralnye-cas-blue', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.bai:extend({name='mineralnye-cas-blue', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.strike:extend({name='mineralnye-strike-blue', altitude=30000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.attack.strike:extend({name='mineralnye-strike-blue-1', altitude=30000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.patrol.aircraft:extend({name='mineralnye-patrol-blue', altitude=25000, range=25}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Mineralnye.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/PowerPlant.lua ]]----------------- - -zones.powerplant = ZoneCommand:new("Power Plant") -zones.powerplant.initialState = { side=1 } -zones.powerplant:defineUpgrades({ - [1] = - { - presets.upgrades.basic.tent:extend({ - name='powerplant-tent-red', - products = { - presets.special.red.infantry:extend({ name='powerplant-defense-red'}), - presets.defenses.red.infantry:extend({ name='powerplant-garrison-red'}) - } - }), - presets.upgrades.supply.powerplant1:extend({ - name='powerplant-building-red-1', - products = { - presets.missions.supply.convoy:extend({ name='powerplant-supply-red'}), - presets.missions.supply.transfer:extend({name='powerplant-transfer-red'}) - } - }), - presets.upgrades.supply.powerplant2:extend({ - name='powerplant-building-red-2', - products = { - presets.missions.supply.convoy:extend({ name='powerplant-supply-red-1'}), - presets.missions.supply.transfer:extend({name='powerplant-transfer-red'}) - } - }) - }, - [2] = - { - presets.upgrades.basic.tent:extend({ - name='powerplant-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='powerplant-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='powerplant-garrison-blue'}) - } - }), - presets.upgrades.supply.powerplant1:extend({ - name='powerplant-building-blue-1', - products = { - presets.missions.supply.convoy:extend({ name='powerplant-supply-blue'}), - presets.missions.supply.transfer:extend({name='powerplant-transfer-blue'}) - } - }), - presets.upgrades.supply.powerplant2:extend({ - name='powerplant-building-blue-2', - products = { - presets.missions.supply.convoy:extend({ name='powerplant-supply-blue-1'}), - presets.missions.supply.transfer:extend({name='powerplant-transfer-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/PowerPlant.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Zugdidi.lua ]]----------------- - -zones.zugdidi = ZoneCommand:new("Zugdidi") -zones.zugdidi.initialState = { side=1 } -zones.zugdidi:defineUpgrades({ - [1] = - { - presets.upgrades.basic.comPost:extend({ - name='zugdidi-compost-red', - products = { - presets.missions.supply.transfer:extend({name='zugdidi-transfer-red'}), - presets.special.red.infantry:extend({ name='zugdidi-defense-red'}), - presets.defenses.red.infantry:extend({ name='zugdidi-garrison-red'}), - presets.missions.attack.surface:extend({name='zugdidi-attack-red'}), - presets.missions.supply.convoy:extend({name='zugdidi-supply-red'}) - } - }), - presets.upgrades.supply.hangar:extend({ - name='zugdidi-hangar-red-1', - products = { - presets.missions.supply.helo:extend({name='zugdidi-supply-red-1'}) - } - }), - presets.upgrades.supply.hangar:extend({ - name='zugdidi-hangar-red-2', - products = { - presets.missions.supply.helo:extend({name='zugdidi-supply-red-2'}) - } - }), - presets.upgrades.supply.hangar:extend({ - name='zugdidi-hangar-red-3', - products = { - presets.missions.supply.helo:extend({name='zugdidi-supply-red-3'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='zugdidi-comcenter-red', - products = { - presets.defenses.red.sa6:extend({ name='zugdidi-sam-red'}) - } - }) - }, - [2] = - { - presets.upgrades.basic.tent:extend({ - name='zugdidi-compost-blue', - products = { - presets.missions.supply.transfer:extend({name='zugdidi-transfer-blue'}), - presets.special.blue.infantry:extend({ name='zugdidi-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='zugdidi-garrison-blue'}), - presets.missions.attack.surface:extend({name='zugdidi-attack-blue'}), - presets.missions.supply.convoy:extend({name='zugdidi-supply-blue'}) - } - }), - presets.upgrades.supply.hangar:extend({ - name='zugdidi-hangar-blue-1', - products = { - presets.missions.supply.helo:extend({name='zugdidi-supply-blue-1'}) - } - }), - presets.upgrades.supply.hangar:extend({ - name='zugdidi-hangar-blue-2', - products = { - presets.missions.supply.helo:extend({name='zugdidi-supply-blue-2'}) - } - }), - presets.upgrades.supply.hangar:extend({ - name='zugdidi-hangar-blue-3', - products = { - presets.missions.supply.helo:extend({name='zugdidi-supply-blue-3'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='zugdidi-comcenter-blue', - products = { - presets.defenses.blue.shorad:extend({ name='zugdidi-sam-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Zugdidi.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Babugent.lua ]]----------------- - -zones.babugent = ZoneCommand:new("Babugent") -zones.babugent.initialState = { side=1 } -zones.babugent:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='babugent-tent-red', - products = { - presets.special.red.infantry:extend({ name='babugent-defense-red'}), - presets.defenses.red.infantry:extend({ name='babugent-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='babugent-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='babugent-supply-red'}), - presets.missions.supply.helo:extend({name='babugent-supply-red-2'}), - presets.missions.supply.transfer:extend({name='babugent-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='babugent-ammo-red', - products = { - presets.missions.attack.surface:extend({name='babugent-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='babugent-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='babugent-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='babugent-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='babugent-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='babugent-supply-blue'}), - presets.missions.supply.helo:extend({name='babugent-supply-blue-2'}), - presets.missions.supply.transfer:extend({name='babugent-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='babugent-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='babugent-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Babugent.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Kislovodsk.lua ]]----------------- - -zones.kislovodsk = ZoneCommand:new("Kislovodsk") -zones.kislovodsk.initialState = { side=1 } -zones.kislovodsk:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='kislovodsk-tent-red', - products = { - presets.special.red.infantry:extend({ name='kislovodsk-defense-red'}), - presets.defenses.red.infantry:extend({ name='kislovodsk-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='kislovodsk-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='kislovodsk-supply-red'}), - presets.missions.supply.transfer:extend({name='kislovodsk-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='kislovodsk-ammo-red', - products = { - presets.missions.attack.surface:extend({name='kislovodsk-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='kislovodsk-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='kislovodsk-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='kislovodsk-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='kislovodsk-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='kislovodsk-supply-blue'}), - presets.missions.supply.transfer:extend({name='kislovodsk-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='kislovodsk-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='kislovodsk-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Kislovodsk.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Gudauta.lua ]]----------------- - -zones.gudauta = ZoneCommand:new("Gudauta") -zones.gudauta.initialState = { side=1 } -zones.gudauta.keepActive = true -zones.gudauta.maxResource = 50000 -zones.gudauta:defineUpgrades({ - [1] = - { - presets.upgrades.basic.comPost:extend({ - name='gudauta-compost-red', - products = { - presets.special.red.infantry:extend({ name='gudauta-defense-red'}), - presets.defenses.red.infantry:extend({ name='gudauta-garrison-red'}) - } - }), - presets.upgrades.supply.fuelTank:extend({ - name='gudauta-fuel-red', - products = { - presets.missions.supply.helo:extend({name='gudauta-supply-red'}), - presets.missions.supply.helo:extend({name='gudauta-supply-red-1'}), - presets.missions.supply.transfer:extend({name='gudauta-transfer-red'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='gudauta-comcenter-red', - products = { - presets.defenses.red.sam:extend({ name='gudauta-airdef-red'}), - presets.missions.attack.sead:extend({name='gudauta-sead-red', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.attack.sead:extend({name='gudauta-sead-red-1', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.attack.cas:extend({name='gudauta-cas-red', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.bai:extend({name='gudauta-cas-red', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.patrol.aircraft:extend({name='gudauta-patrol-red', altitude=25000, range=25}) - } - }) - }, - [2] = - { - presets.upgrades.basic.comPost:extend({ - name='gudauta-compost-blue', - products = { - presets.special.blue.infantry:extend({ name='gudauta-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='gudauta-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelTank:extend({ - name='gudauta-fuel-blue', - products = { - presets.missions.supply.helo:extend({name='gudauta-supply-blue'}), - presets.missions.supply.helo:extend({name='gudauta-supply-blue-1'}), - presets.missions.supply.transfer:extend({name='gudauta-transfer-blue'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='gudauta-comcenter-blue', - products = { - presets.defenses.blue.sam:extend({ name='gudauta-airdef-blue'}), - presets.missions.attack.sead:extend({name='gudauta-sead-blue', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.attack.sead:extend({name='gudauta-sead-blue-1', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.attack.cas:extend({name='gudauta-cas-blue', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.bai:extend({name='gudauta-cas-blue', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.patrol.aircraft:extend({name='gudauta-patrol-blue', altitude=25000, range=25}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Gudauta.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Distillery.lua ]]----------------- - -zones.distillery = ZoneCommand:new("Distillery") -zones.distillery.initialState = { side=1 } -zones.distillery:defineUpgrades({ - [1] = - { - presets.upgrades.basic.tent:extend({ - name='distillery-tent-red', - products = { - presets.special.red.infantry:extend({ name='distillery-defense-red'}), - presets.defenses.red.infantry:extend({ name='distillery-garrison-red'}) - } - }), - presets.upgrades.supply.factory1:extend({ - name='distillery-prod-red-1', - products = { - presets.missions.supply.convoy:extend({ name='distillery-supply-red-1'}), - presets.missions.supply.transfer:extend({name='distillery-transfer-red'}) - } - }), - presets.upgrades.supply.factory2:extend({ - name='distillery-prod-red-2', - products = { - presets.missions.supply.convoy:extend({ name='distillery-supply-red-2', cost=2000}), - presets.missions.supply.transfer:extend({name='distillery-transfer-red2'}) - } - }), - presets.upgrades.supply.factoryTank:extend({ - name='distillery-tank-red-1', - products = { - } - }), - presets.upgrades.supply.factoryTank:extend({ - name='distillery-tank-red-2', - products = { - } - }), - presets.upgrades.supply.factoryTank:extend({ - name='distillery-tank-red-3', - products = { - } - }) - }, - [2] = - { - presets.upgrades.basic.tent:extend({ - name='distillery-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='distillery-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='distillery-garrison-blue'}) - } - }), - presets.upgrades.supply.factory1:extend({ - name='distillery-prod-blue-1', - products = { - presets.missions.supply.convoy:extend({ name='distillery-supply-blue-1'}), - presets.missions.supply.transfer:extend({name='distillery-transfer-blue'}) - } - }), - presets.upgrades.supply.factory2:extend({ - name='distillery-prod-blue-2', - products = { - presets.missions.supply.convoy:extend({ name='distillery-supply-blue-2', cost=2000}), - presets.missions.supply.transfer:extend({name='distillery-transfer-blue2'}) - } - }), - presets.upgrades.supply.factoryTank:extend({ - name='distillery-tank-blue-1', - products = { - } - }), - presets.upgrades.supply.factoryTank:extend({ - name='distillery-tank-blue-2', - products = { - } - }), - presets.upgrades.supply.factoryTank:extend({ - name='distillery-tank-blue-3', - products = { - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Distillery.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Sochi.lua ]]----------------- - -zones.sochi = ZoneCommand:new("Sochi") -zones.sochi.initialState = { side=1 } -zones.sochi.keepActive = true -zones.sochi.maxResource = 50000 -zones.sochi:defineUpgrades({ - [1] = - { - presets.upgrades.basic.comPost:extend({ - name='sochi-compost-red', - products = { - presets.special.red.infantry:extend({ name='sochi-defense-red'}), - presets.defenses.red.infantry:extend({ name='sochi-garrison-red'}) - } - }), - presets.upgrades.supply.fuelTank:extend({ - name='sochi-fuel-red', - products = { - presets.missions.supply.helo:extend({name='sochi-supply-red-1'}), - presets.missions.supply.helo:extend({name='sochi-supply-red-2'}), - presets.missions.supply.convoy_escorted:extend({name='sochi-supply-red-3'}), - presets.missions.supply.transfer:extend({name='sochi-transfer-red'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='sochi-comcenter-red', - products = { - presets.defenses.red.sa10:extend({ name='sochi-airdef-red'}), - presets.missions.attack.sead:extend({name='sochi-sead-red', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.attack.strike:extend({name='sochi-strike-red', altitude=30000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.patrol.aircraft:extend({name='sochi-patrol-red', altitude=25000, range=25}), - presets.missions.patrol.aircraft:extend({name='sochi-patrol-red-1', altitude=25000, range=25}), - presets.missions.attack.cas:extend({name='sochi-cas-red', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.bai:extend({name='sochi-cas-red', altitude=10000, expend=AI.Task.WeaponExpend.ONE}) - } - }) - }, - [2] = - { - presets.upgrades.basic.comPost:extend({ - name='sochi-compost-blue', - products = { - presets.special.blue.infantry:extend({ name='sochi-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='sochi-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelTank:extend({ - name='sochi-fuel-blue', - products = { - presets.missions.supply.helo:extend({name='sochi-supply-blue-1'}), - presets.missions.supply.helo:extend({name='sochi-supply-blue-2'}), - presets.missions.supply.convoy_escorted:extend({name='sochi-supply-blue-3'}), - presets.missions.supply.transfer:extend({name='sochi-transfer-blue'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='sochi-comcenter-blue', - products = { - presets.defenses.blue.patriot:extend({ name='sochi-airdef-blue'}), - presets.missions.attack.sead:extend({name='sochi-sead-blue', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.attack.strike:extend({name='sochi-strike-blue', altitude=30000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.patrol.aircraft:extend({name='sochi-patrol-blue', altitude=25000, range=25}), - presets.missions.attack.cas:extend({name='sochi-cas-blue', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.bai:extend({name='sochi-cas-blue', altitude=10000, expend=AI.Task.WeaponExpend.ONE}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Sochi.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Golf.lua ]]----------------- - -zones.golf = ZoneCommand:new("Golf") -zones.golf.initialState = nil -zones.golf.isHeloSpawn = true -zones.golf:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='golf-tent-red', - products = { - presets.special.red.infantry:extend({ name='golf-defense-red'}), - presets.defenses.red.infantry:extend({ name='golf-garrison-red'}) - } - }), - presets.upgrades.supply.fuelTank:extend({ - name='golf-fuel-red', - products = { - presets.missions.supply.helo:extend({name='golf-supply-red'}), - presets.missions.supply.helo:extend({name='golf-supply-red-1'}), - presets.missions.supply.transfer:extend({name='golf-transfer-red'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='golf-comcenter-red', - products = { - presets.defenses.red.shorad:extend({name='golf-sam-red'}), - presets.missions.attack.helo:extend({name='golf-cas-red', altitude=200, expend=AI.Task.WeaponExpend.HALF }) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='golf-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='golf-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='golf-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelTankFarp:extend({ - name='golf-fuel-blue', - products = { - presets.missions.supply.helo:extend({name='golf-supply-blue'}), - presets.missions.supply.helo:extend({name='golf-supply-blue-1'}), - presets.missions.supply.transfer:extend({name='golf-transfer-blue'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='golf-comcenter-blue', - products = { - presets.defenses.blue.shorad:extend({name='golf-sam-blue'}), - presets.missions.attack.helo:extend({name='golf-cas-blue', altitude=200, expend=AI.Task.WeaponExpend.HALF }) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Golf.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Charlie.lua ]]----------------- - -zones.charlie = ZoneCommand:new("Charlie") -zones.charlie.initialState = { side=2 } -zones.charlie.keepActive = true -zones.charlie:defineUpgrades({ - [1] = - { - presets.upgrades.basic.tent:extend({ - name='charlie-tent-red', - products = { - presets.special.red.infantry:extend({ name='charlie-defense-red'}), - presets.defenses.red.infantry:extend({ name='charlie-garrison-red'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='charlie-comcenter-red', - products = { - presets.defenses.red.sam:extend({ name='charlie-sam-red'}) - } - }) - }, - [2] = - { - presets.upgrades.basic.tent:extend({ - name='charlie-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='charlie-defense-red'}), - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='charlie-comcenter-blue', - products = { - presets.defenses.blue.sam:extend({ name='charlie-sam-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Charlie.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Lentehi.lua ]]----------------- - -zones.lentehi = ZoneCommand:new("Lentehi") -zones.lentehi.initialState = { side=1 } -zones.lentehi:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='lentehi-tent-red', - products = { - presets.special.red.infantry:extend({ name='lentehi-defense-red'}), - presets.defenses.red.infantry:extend({ name='lentehi-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='lentehi-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='lentehi-supply-red'}), - presets.missions.supply.helo:extend({name='lentehi-supply-red-2'}), - presets.missions.supply.transfer:extend({name='lentehi-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='lentehi-ammo-red', - products = { - presets.missions.attack.surface:extend({name='lentehi-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='lentehi-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='lentehi-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='lentehi-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='lentehi-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='lentehi-supply-blue'}), - presets.missions.supply.helo:extend({name='lentehi-supply-blue-2'}), - presets.missions.supply.transfer:extend({name='lentehi-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='lentehi-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='lentehi-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Lentehi.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Refinery.lua ]]----------------- - -zones.refinery = ZoneCommand:new("Refinery") -zones.refinery.initialState = { side=1 } -zones.refinery:defineUpgrades({ - [1] = - { - presets.upgrades.basic.tent:extend({ - name='refinery-tent-red', - products = { - presets.special.red.infantry:extend({ name='refinery-defense-red'}), - presets.defenses.red.infantry:extend({ name='refinery-garrison-red'}) - } - }), - presets.upgrades.supply.refinery1:extend({ - name='refinery-building-red', - products = { - presets.missions.supply.convoy:extend({ name='refinery-supply-red'}), - presets.missions.supply.convoy:extend({ name='refinery-supply-red-1'}), - presets.missions.supply.helo:extend({ name='refinery-supply-red-2'}), - presets.missions.supply.transfer:extend({name='refinery-transfer-red'}) - } - }) - }, - [2] = - { - presets.upgrades.basic.tent:extend({ - name='refinery-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='refinery-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='refinery-garrison-blue'}) - } - }), - presets.upgrades.supply.refinery1:extend({ - name='refinery-building-blue', - products = { - presets.missions.supply.convoy:extend({ name='refinery-supply-blue'}), - presets.missions.supply.convoy:extend({ name='refinery-supply-blue-1'}), - presets.missions.supply.helo:extend({ name='refinery-supply-blue-2'}), - presets.missions.supply.transfer:extend({name='refinery-transfer-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Refinery.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Mozdok.lua ]]----------------- - -zones.mozdok = ZoneCommand:new("Mozdok") -zones.mozdok.initialState = { side=1 } -zones.mozdok.keepActive = true -zones.mozdok.maxResource = 50000 -zones.mozdok:defineUpgrades({ - [1] = - { - presets.upgrades.basic.comPost:extend({ - name='mozdok-compost-red', - products = { - presets.special.red.infantry:extend({ name='mozdok-defense-red'}), - presets.defenses.red.infantry:extend({ name='mozdok-garrison-red'}) - } - }), - presets.upgrades.supply.fuelTank:extend({ - name='mozdok-fuel-red', - products = { - presets.missions.supply.helo:extend({name='mozdok-supply-red-1'}), - presets.missions.supply.helo:extend({name='mozdok-supply-red-2'}), - presets.missions.supply.convoy_escorted:extend({name='mozdok-supply-red-3'}), - presets.missions.supply.transfer:extend({name='mozdok-transfer-red'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='mozdok-comcenter-red', - products = { - presets.defenses.red.sa10:extend({ name='mozdok-airdef-red'}), - presets.missions.patrol.aircraft:extend({name='mozdok-patrol-red', altitude=25000, range=25}), - presets.missions.attack.cas:extend({name='mozdok-cas-red', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.bai:extend({name='mozdok-cas-red', altitude=10000, expend=AI.Task.WeaponExpend.ONE}) - } - }) - }, - [2] = - { - presets.upgrades.basic.comPost:extend({ - name='mozdok-compost-blue', - products = { - presets.special.blue.infantry:extend({ name='mozdok-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='mozdok-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelTank:extend({ - name='mozdok-fuel-blue', - products = { - presets.missions.supply.helo:extend({name='mozdok-supply-blue-1'}), - presets.missions.supply.helo:extend({name='mozdok-supply-blue-2'}), - presets.missions.supply.convoy_escorted:extend({name='mozdok-supply-blue-3'}), - presets.missions.supply.transfer:extend({name='mozdok-transfer-blue'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='mozdok-comcenter-blue', - products = { - presets.defenses.blue.shorad:extend({ name='mozdok-airdef-blue'}), - presets.missions.patrol.aircraft:extend({name='mozdok-patrol-blue', altitude=25000, range=25}), - presets.missions.attack.cas:extend({name='mozdok-cas-blue', altitude=15000, cost=50, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.cas:extend({name='mozdok-cas-blue-1', altitude=15000, cost=50, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.bai:extend({name='mozdok-cas-blue', altitude=15000, cost=50, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.bai:extend({name='mozdok-cas-blue-1', altitude=15000, cost=50, expend=AI.Task.WeaponExpend.ONE}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Mozdok.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Lima.lua ]]----------------- - -zones.lima = ZoneCommand:new("Lima") -zones.lima.initialState = { side=1 } -zones.lima:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='lima-tent-red', - products = { - presets.special.red.infantry:extend({ name='lima-defense-red'}), - presets.defenses.red.infantry:extend({ name='lima-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='lima-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='lima-supply-red'}), - presets.missions.supply.helo:extend({name='lima-supply-red-1'}), - presets.missions.supply.transfer:extend({name='lima-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='lima-ammo-red', - products = { - presets.missions.attack.surface:extend({name='lima-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='lima-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='lima-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='lima-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='lima-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='lima-supply-blue'}), - presets.missions.supply.helo:extend({name='lima-supply-blue-1'}), - presets.missions.supply.transfer:extend({name='lima-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='lima-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='lima-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Lima.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Oscar.lua ]]----------------- - -zones.oscar = ZoneCommand:new("Oscar") -zones.oscar.initialState = { side=1 } -zones.oscar:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='oscar-tent-red', - products = { - presets.special.red.infantry:extend({ name='oscar-defense-red'}), - presets.defenses.red.infantry:extend({ name='oscar-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='oscar-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='oscar-supply-red'}), - presets.missions.supply.transfer:extend({name='oscar-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='oscar-ammo-red', - products = { - presets.missions.attack.surface:extend({name='oscar-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='oscar-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='oscar-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='oscar-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='oscar-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='oscar-supply-blue'}), - presets.missions.supply.transfer:extend({name='oscar-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='oscar-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='oscar-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Oscar.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Nalchik.lua ]]----------------- - -zones.nalchik = ZoneCommand:new("Nalchik") -zones.nalchik.initialState = { side=1 } -zones.nalchik.keepActive = true -zones.nalchik.isHeloSpawn = true -zones.nalchik.isPlaneSpawn = true -zones.nalchik:defineUpgrades({ - [1] = - { - presets.upgrades.basic.comPost:extend({ - name='nalchik-compost-red', - products = { - presets.special.red.infantry:extend({ name='nalchik-defense-red'}), - presets.defenses.red.infantry:extend({ name='nalchik-garrison-red'}) - } - }), - presets.upgrades.supply.fuelTank:extend({ - name='nalchik-fuel-red', - products = { - presets.missions.supply.helo:extend({name='nalchik-supply-red-1'}), - presets.missions.supply.helo:extend({name='nalchik-supply-red-2'}), - presets.missions.supply.transfer:extend({name='nalchik-transfer-red'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='nalchik-comcenter-red', - products = { - presets.defenses.red.sa3:extend({ name='nalchik-airdef-red'}), - presets.missions.attack.sead:extend({name='nalchik-sead-red', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.attack.cas:extend({name='nalchik-cas-red', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.bai:extend({name='nalchik-cas-red', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.strike:extend({name='nalchik-strike-red', altitude=30000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.patrol.aircraft:extend({name='nalchik-patrol-red', altitude=25000, range=25}), - presets.missions.patrol.aircraft:extend({name='nalchik-patrol-red-2', altitude=25000, range=25}), - presets.missions.support.awacs:extend({name='nalchik-awacs-red', altitude=30000, freq=251.2}), - presets.missions.support.tanker:extend({name='nalchik-tanker-red', altitude=30000, freq=252.2, tacan='40', variant='Drogue'}) - } - }) - }, - [2] = - { - presets.upgrades.basic.comPost:extend({ - name='nalchik-compost-blue', - products = { - presets.special.blue.infantry:extend({ name='nalchik-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='nalchik-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelTank:extend({ - name='nalchik-fuel-blue', - products = { - presets.missions.supply.helo:extend({name='nalchik-supply-blue-1'}), - presets.missions.supply.helo:extend({name='nalchik-supply-blue-2'}), - presets.missions.supply.transfer:extend({name='nalchik-transfer-blue'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='nalchik-comcenter-blue', - products = { - presets.defenses.blue.nasams:extend({ name='nalchik-airdef-blue'}), - presets.missions.support.awacs:extend({name='nalchik-awacs-blue', altitude=30000, freq=259.5}), - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Nalchik.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Digora.lua ]]----------------- - -zones.digora = ZoneCommand:new("Digora") -zones.digora.initialState = { side=1 } -zones.digora:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='digora-tent-red', - products = { - presets.special.red.infantry:extend({ name='digora-defense-red'}), - presets.defenses.red.infantry:extend({ name='digora-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='digora-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='digora-supply-red'}), - presets.missions.supply.transfer:extend({name='digora-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='digora-ammo-red', - products = { - presets.missions.attack.surface:extend({name='digora-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='digora-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='digora-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='digora-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='digora-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='digora-supply-blue'}), - presets.missions.supply.transfer:extend({name='digora-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='digora-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='digora-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Digora.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Uniform.lua ]]----------------- - -zones.uniform = ZoneCommand:new("Uniform") -zones.uniform.initialState = { side=1 } -zones.uniform:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='uniform-tent-red', - products = { - presets.special.red.infantry:extend({ name='uniform-defense-red'}), - presets.defenses.red.infantry:extend({ name='uniform-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='uniform-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='uniform-supply-red'}), - presets.missions.supply.transfer:extend({name='uniform-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='uniform-ammo-red', - products = { - presets.missions.attack.surface:extend({name='uniform-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='uniform-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='uniform-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='uniform-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='uniform-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='uniform-supply-blue'}), - presets.missions.supply.transfer:extend({name='uniform-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='uniform-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='uniform-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Uniform.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Factory.lua ]]----------------- - -zones.factory = ZoneCommand:new("Factory") -zones.factory.initialState = { side=2 } -zones.factory:defineUpgrades({ - [1] = - { - presets.upgrades.basic.tent:extend({ - name='factory-tent-red', - products = { - presets.special.red.infantry:extend({ name='factory-defense-red'}), - presets.defenses.red.infantry:extend({ name='factory-garrison-red'}) - } - }), - presets.upgrades.supply.factory1:extend({ - name='factory-prod-red-1', - products = { - presets.missions.supply.convoy:extend({ name='factory-supply-red-1'}), - presets.missions.supply.transfer:extend({name='factory-transfer-red'}) - } - }), - presets.upgrades.supply.factory2:extend({ - name='factory-prod-red-2', - products = { - presets.missions.supply.convoy:extend({ name='factory-supply-red-2', cost=2000}), - presets.missions.supply.transfer:extend({name='factory-transfer-red2'}) - } - }), - presets.upgrades.supply.factoryTank:extend({ - name='factory-tank-red-1', - products = { - } - }), - presets.upgrades.supply.factoryTank:extend({ - name='factory-tank-red-2', - products = { - } - }), - presets.upgrades.supply.factoryTank:extend({ - name='factory-tank-red-3', - products = { - } - }) - }, - [2] = - { - presets.upgrades.basic.tent:extend({ - name='factory-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='factory-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='factory-garrison-blue'}) - } - }), - presets.upgrades.supply.factory1:extend({ - name='factory-prod-blue-1', - products = { - presets.missions.supply.convoy:extend({ name='factory-supply-blue-1'}), - presets.missions.supply.transfer:extend({name='factory-transfer-blue'}) - } - }), - presets.upgrades.supply.factory2:extend({ - name='factory-prod-blue-2', - products = { - presets.missions.supply.convoy:extend({ name='factory-supply-blue-2', cost=2000}), - presets.missions.supply.transfer:extend({name='factory-transfer-blue2'}) - } - }), - presets.upgrades.supply.factoryTank:extend({ - name='factory-tank-blue-1', - products = { - } - }), - presets.upgrades.supply.factoryTank:extend({ - name='factory-tank-blue-2', - products = { - } - }), - presets.upgrades.supply.factoryTank:extend({ - name='factory-tank-blue-3', - products = { - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Factory.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Senaki.lua ]]----------------- - -zones.senaki = ZoneCommand:new("Senaki") -zones.senaki.initialState = { side=1 } -zones.senaki.keepActive = true -zones.senaki.isHeloSpawn = true -zones.senaki.isPlaneSpawn = true -zones.senaki.maxResource = 50000 -zones.senaki:defineUpgrades({ - [1] = - { - presets.upgrades.basic.comPost:extend({ - name='senaki-compost-red', - products = { - presets.special.red.infantry:extend({ name='senaki-defense-red'}), - presets.defenses.red.infantry:extend({ name='senaki-garrison-red'}) - } - }), - presets.upgrades.supply.fuelTank:extend({ - name='senaki-fuel-red', - products = { - presets.missions.supply.helo:extend({name='senaki-supply-red-1'}), - presets.missions.supply.helo:extend({name='senaki-supply-red-2'}), - presets.missions.supply.transfer:extend({name='senaki-transfer-red'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='senaki-comcenter-red', - products = { - presets.defenses.red.sa3:extend({ name='senaki-airdef-red'}), - presets.missions.attack.sead:extend({name='senaki-sead-red', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.attack.cas:extend({name='senaki-cas-red', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.bai:extend({name='senaki-cas-red', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.strike:extend({name='senaki-strike-red', altitude=20000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.patrol.aircraft:extend({name='senaki-patrol-red', altitude=25000, range=25}), - presets.missions.patrol.aircraft:extend({name='senaki-patrol-red-2', altitude=20000, range=25}) - } - }) - }, - [2] = - { - presets.upgrades.basic.comPost:extend({ - name='senaki-compost-blue', - products = { - presets.special.blue.infantry:extend({ name='senaki-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='senaki-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelTank:extend({ - name='senaki-fuel-blue', - products = { - presets.missions.supply.helo:extend({name='senaki-supply-blue-1'}), - presets.missions.supply.helo:extend({name='senaki-supply-blue-2'}), - presets.missions.supply.transfer:extend({name='senaki-transfer-blue'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='senaki-comcenter-blue', - products = { - presets.defenses.blue.shorad:extend({ name='senaki-airdef-blue'}), - presets.missions.attack.sead:extend({name='senaki-sead-blue', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.attack.cas:extend({name='senaki-cas-blue', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.bai:extend({name='senaki-cas-blue', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.strike:extend({name='senaki-strike-blue', altitude=20000, expend=AI.Task.WeaponExpend.TWO}), - presets.missions.patrol.aircraft:extend({name='senaki-patrol-blue', altitude=25000, range=25}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Senaki.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Kutaisi.lua ]]----------------- - -zones.kutaisi = ZoneCommand:new("Kutaisi") -zones.kutaisi.initialState = { side=1 } -zones.kutaisi.keepActive = true -zones.kutaisi.maxResource = 50000 -zones.kutaisi:defineUpgrades({ - [1] = - { - presets.upgrades.basic.comPost:extend({ - name='kutaisi-compost-red', - products = { - presets.special.red.infantry:extend({ name='kutaisi-defense-red'}), - presets.defenses.red.infantry:extend({ name='kutaisi-garrison-red'}) - } - }), - presets.upgrades.supply.fuelTank:extend({ - name='kutaisi-fuel-red', - products = { - presets.missions.supply.helo:extend({name='kutaisi-supply-red-1'}), - presets.missions.supply.helo:extend({name='kutaisi-supply-red-2'}), - presets.missions.supply.transfer:extend({name='kutaisi-transfer-red'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='kutaisi-comcenter-red', - products = { - presets.defenses.red.shorad:extend({ name='kutaisi-airdef-red'}), - presets.missions.attack.sead:extend({name='kutaisi-sead-red', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.attack.cas:extend({name='kutaisi-cas-red', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.bai:extend({name='kutaisi-cas-red', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.strike:extend({name='kutaisi-strike-red', altitude=20000, expend=AI.Task.WeaponExpend.HALF}), - presets.missions.patrol.aircraft:extend({name='kutaisi-patrol-red', altitude=25000, range=25}), - presets.missions.patrol.aircraft:extend({name='kutaisi-patrol-red-2', altitude=25000, range=25}) - } - }) - }, - [2] = - { - presets.upgrades.basic.comPost:extend({ - name='kutaisi-compost-blue', - products = { - presets.special.blue.infantry:extend({ name='kutaisi-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='kutaisi-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelTank:extend({ - name='kutaisi-fuel-blue', - products = { - presets.missions.supply.helo:extend({name='kutaisi-supply-blue-1'}), - presets.missions.supply.helo:extend({name='kutaisi-supply-blue-2'}), - presets.missions.supply.transfer:extend({name='kutaisi-transfer-blue'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='kutaisi-comcenter-blue', - products = { - presets.defenses.blue.shorad:extend({ name='kutaisi-airdef-blue'}), - presets.missions.attack.sead:extend({name='kutaisi-sead-blue', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.attack.cas:extend({name='kutaisi-cas-blue', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.bai:extend({name='kutaisi-cas-blue', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.strike:extend({name='kutaisi-strike-blue', altitude=20000, expend=AI.Task.WeaponExpend.TWO}), - presets.missions.patrol.aircraft:extend({name='kutaisi-patrol-blue', altitude=25000, range=25}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Kutaisi.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Prohladniy.lua ]]----------------- - -zones.prohladniy = ZoneCommand:new("Prohladniy") -zones.prohladniy.initialState = { side=1 } -zones.prohladniy:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='prohladniy-tent-red', - products = { - presets.special.red.infantry:extend({ name='prohladniy-defense-red'}), - presets.defenses.red.infantry:extend({ name='prohladniy-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='prohladniy-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='prohladniy-supply-red'}), - presets.missions.supply.transfer:extend({name='prohladniy-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='prohladniy-ammo-red', - products = { - presets.missions.attack.surface:extend({name='prohladniy-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='prohladniy-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='prohladniy-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='prohladniy-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='prohladniy-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='prohladniy-supply-blue'}), - presets.missions.supply.transfer:extend({name='prohladniy-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='prohladniy-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='prohladniy-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Prohladniy.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Tallyk.lua ]]----------------- - -zones.tallyk = ZoneCommand:new("Tallyk") -zones.tallyk.initialState = { side=1 } -zones.tallyk.keepActive = true -zones.tallyk:defineUpgrades({ - [1] = - { - presets.upgrades.basic.tent:extend({ - name='tallyk-tent-red', - products = { - presets.special.red.infantry:extend({ name='tallyk-defense-red'}), - presets.defenses.red.infantry:extend({ name='tallyk-garrison-red'}), - presets.missions.attack.surface:extend({name='tallyk-assault-red'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='tallyk-comcenter-red', - products = { - presets.defenses.red.sam:extend({ name='tallyk-sam-red'}) - } - }) - }, - [2] = - { - presets.upgrades.basic.tent:extend({ - name='tallyk-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='tallyk-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='tallyk-garrison-blue'}), - presets.missions.attack.surface:extend({name='tallyk-assault-blue'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='tallyk-comcenter-blue', - products = { - presets.defenses.blue.sam:extend({ name='tallyk-sam-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Tallyk.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Terek.lua ]]----------------- - -zones.terek = ZoneCommand:new("Terek") -zones.terek.initialState = { side=1 } -zones.terek:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='terek-tent-red', - products = { - presets.special.red.infantry:extend({ name='terek-defense-red'}), - presets.defenses.red.infantry:extend({ name='terek-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='terek-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='terek-supply-red'}), - presets.missions.supply.transfer:extend({name='terek-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='terek-ammo-red', - products = { - presets.missions.attack.surface:extend({name='terek-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='terek-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='terek-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='terek-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='terek-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='terek-supply-blue'}), - presets.missions.supply.transfer:extend({name='terek-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='terek-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='terek-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Terek.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Humara.lua ]]----------------- - -zones.humara = ZoneCommand:new("Humara") -zones.humara.initialState = { side=1 } -zones.humara:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='humara-tent-red', - products = { - presets.special.red.infantry:extend({ name='humara-defense-red'}), - presets.defenses.red.infantry:extend({ name='humara-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='humara-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='humara-supply-red'}), - presets.missions.supply.transfer:extend({name='humara-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='humara-ammo-red', - products = { - presets.missions.attack.surface:extend({name='humara-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='humara-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='humara-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='humara-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='humara-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='humara-supply-blue'}), - presets.missions.supply.transfer:extend({name='humara-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='humara-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='humara-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Humara.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Ochamchira.lua ]]----------------- - -zones.ochamchira = ZoneCommand:new("Ochamchira") -zones.ochamchira.initialState = { side=1 } -zones.ochamchira:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='ochamchira-tent-red', - products = { - presets.special.red.infantry:extend({ name='ochamchira-defense-red'}), - presets.defenses.red.infantry:extend({ name='ochamchira-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='ochamchira-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='ochamchira-supply-red'}), - presets.missions.supply.transfer:extend({name='ochamchira-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='ochamchira-ammo-red', - products = { - presets.missions.attack.surface:extend({name='ochamchira-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='ochamchira-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='ochamchira-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='ochamchira-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='ochamchira-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='ochamchira-supply-blue'}), - presets.missions.supply.transfer:extend({name='ochamchira-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='ochamchira-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='ochamchira-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Ochamchira.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/November.lua ]]----------------- - -zones.november = ZoneCommand:new("November") -zones.november.initialState = { side=1 } -zones.november.isHeloSpawn = true -zones.november:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='november-tent-red', - products = { - presets.special.red.infantry:extend({ name='november-defense-red'}), - presets.defenses.red.infantry:extend({ name='november-garrison-red'}) - } - }), - presets.upgrades.supply.fuelTankFarp:extend({ - name='november-fuel-red', - products = { - presets.missions.supply.helo:extend({name='november-supply-red'}), - presets.missions.supply.helo:extend({name='november-supply-red-1'}), - presets.missions.supply.transfer:extend({name='november-transfer-red'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='november-comcenter-red', - products = { - presets.defenses.red.shorad:extend({name='november-sam-red'}), - presets.missions.attack.helo:extend({name='november-cas-red', altitude=200, expend=AI.Task.WeaponExpend.HALF }) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='november-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='november-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='november-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelTankFarp:extend({ - name='november-fuel-blue', - products = { - presets.missions.supply.helo:extend({name='november-supply-blue'}), - presets.missions.supply.helo:extend({name='november-supply-blue-1'}), - presets.missions.supply.transfer:extend({name='november-transfer-blue'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='november-comcenter-blue', - products = { - presets.defenses.blue.shorad:extend({name='november-sam-blue'}), - presets.missions.attack.helo:extend({name='november-cas-blue', altitude=200, expend=AI.Task.WeaponExpend.HALF }) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/November.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/XRay.lua ]]----------------- - -zones.xray = ZoneCommand:new("XRay") -zones.xray.initialState = { side=1 } -zones.xray:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='xray-tent-red', - products = { - presets.special.red.infantry:extend({ name='xray-defense-red'}), - presets.defenses.red.infantry:extend({ name='xray-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='xray-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='xray-supply-red'}), - presets.missions.supply.helo:extend({name='xray-supply-red-2'}), - presets.missions.supply.transfer:extend({name='xray-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='xray-ammo-red', - products = { - presets.missions.attack.surface:extend({name='xray-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='xray-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='xray-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='xray-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='xray-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='xray-supply-blue'}), - presets.missions.supply.helo:extend({name='xray-supply-blue-2'}), - presets.missions.supply.transfer:extend({name='xray-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='xray-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='xray-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/XRay.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Whiskey.lua ]]----------------- - -zones.whiskey = ZoneCommand:new("Whiskey") -zones.whiskey.initialState = { side=1 } -zones.whiskey:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='whiskey-tent-red', - products = { - presets.special.red.infantry:extend({ name='whiskey-defense-red'}), - presets.defenses.red.infantry:extend({ name='whiskey-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='whiskey-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='whiskey-supply-red'}), - presets.missions.supply.transfer:extend({name='whiskey-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='whiskey-ammo-red', - products = { - presets.missions.attack.surface:extend({name='whiskey-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='whiskey-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='whiskey-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='whiskey-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='whiskey-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='whiskey-supply-blue'}), - presets.missions.supply.transfer:extend({name='whiskey-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='whiskey-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='whiskey-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Whiskey.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Mine.lua ]]----------------- - -zones.mine = ZoneCommand:new("Mine") -zones.mine.initialState = { side=1 } -zones.mine:defineUpgrades({ - [1] = - { - presets.upgrades.basic.tent:extend({ - name='mine-tent-red', - products = { - presets.special.red.infantry:extend({ name='mine-defense-red'}), - presets.defenses.red.infantry:extend({ name='mine-garrison-red'}) - } - }), - presets.upgrades.supply.excavator:extend({ - name='mine-excavator-red-1', - products = { - presets.missions.supply.convoy:extend({ name='mine-supply-red'}), - presets.missions.supply.transfer:extend({name='mine-transfer-red'}) - } - }), - presets.upgrades.supply.excavator:extend({ - name='mine-excavator-red-2', - products = { - presets.missions.supply.convoy:extend({ name='mine-supply-red'}), - presets.missions.supply.transfer:extend({name='mine-transfer-red'}) - } - }), - presets.upgrades.supply.excavator:extend({ - name='mine-excavator-red-3', - products = { - presets.missions.supply.convoy:extend({ name='mine-supply-red'}), - presets.missions.supply.transfer:extend({name='mine-transfer-red'}) - } - }) - }, - [2] = - { - presets.upgrades.basic.tent:extend({ - name='mine-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='mine-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='mine-garrison-blue'}) - } - }), - presets.upgrades.supply.excavator:extend({ - name='mine-excavator-blue-1', - products = { - presets.missions.supply.convoy:extend({ name='mine-supply-blue'}), - presets.missions.supply.transfer:extend({name='mine-transfer-blue'}) - } - }), - presets.upgrades.supply.excavator:extend({ - name='mine-excavator-blue-2', - products = { - presets.missions.supply.convoy:extend({ name='mine-supply-blue'}), - presets.missions.supply.transfer:extend({name='mine-transfer-blue'}) - } - }), - presets.upgrades.supply.excavator:extend({ - name='mine-excavator-blue-3', - products = { - presets.missions.supply.convoy:extend({ name='mine-supply-blue'}), - presets.missions.supply.transfer:extend({name='mine-transfer-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Mine.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Papa.lua ]]----------------- - -zones.papa = ZoneCommand:new("Papa") -zones.papa.initialState = { side=1 } -zones.papa.keepActive = true -zones.papa:defineUpgrades({ - [1] = - { - presets.upgrades.basic.tent:extend({ - name='papa-tent-red', - products = { - presets.special.red.infantry:extend({ name='papa-defense-red'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='papa-comcenter-red', - products = { - presets.defenses.red.sam:extend({ name='papa-sam-red'}) - } - }) - }, - [2] = - { - presets.upgrades.basic.tent:extend({ - name='papa-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='papa-defense-blue'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='papa-comcenter-blue', - products = { - presets.defenses.blue.sam:extend({ name='papa-sam-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Papa.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Sukhumi.lua ]]----------------- - -zones.sukhumi = ZoneCommand:new("Sukhumi") -zones.sukhumi.initialState = { side=1 } -zones.sukhumi.keepActive = true -zones.sukhumi.maxResource = 50000 -zones.sukhumi:defineUpgrades({ - [1] = - { - presets.upgrades.basic.comPost:extend({ - name='sukhumi-compost-red', - products = { - presets.special.red.infantry:extend({ name='sukhumi-defense-red'}), - presets.defenses.red.infantry:extend({ name='sukhumi-garrison-red'}) - } - }), - presets.upgrades.supply.fuelTank:extend({ - name='sukhumi-fuel-red', - products = { - presets.missions.supply.helo:extend({name='sukhumi-supply-red-1'}), - presets.missions.supply.helo:extend({name='sukhumi-supply-red-2'}), - presets.missions.supply.transfer:extend({name='sukhumi-transfer-red'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='sukhumi-comcenter-red', - products = { - presets.defenses.red.sa11:extend({ name='sukhumi-airdef-red'}), - presets.missions.attack.sead:extend({name='sukhumi-sead-red', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.attack.cas:extend({name='sukhumi-cas-red', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.bai:extend({name='sukhumi-cas-red', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.strike:extend({name='sukhumi-strike-red', altitude=20000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.patrol.aircraft:extend({name='sukhumi-patrol-red', altitude=25000, range=25}), - presets.missions.patrol.aircraft:extend({name='sukhumi-patrol-red-2', altitude=25000, range=25}) - } - }) - }, - [2] = - { - presets.upgrades.basic.comPost:extend({ - name='sukhumi-compost-blue', - products = { - presets.special.blue.infantry:extend({ name='sukhumi-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='sukhumi-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelTank:extend({ - name='sukhumi-fuel-blue', - products = { - presets.missions.supply.helo:extend({name='sukhumi-supply-blue-1'}), - presets.missions.supply.helo:extend({name='sukhumi-supply-blue-2'}), - presets.missions.supply.transfer:extend({name='sukhumi-transfer-blue'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='sukhumi-comcenter-blue', - products = { - presets.defenses.blue.nasams:extend({ name='sukhumi-airdef-blue'}), - presets.missions.attack.sead:extend({name='sukhumi-sead-blue', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.attack.cas:extend({name='sukhumi-cas-blue', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.bai:extend({name='sukhumi-cas-blue', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.strike:extend({name='sukhumi-strike-blue', altitude=20000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.patrol.aircraft:extend({name='sukhumi-patrol-blue', altitude=25000, range=25}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Sukhumi.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Farm.lua ]]----------------- - -zones.farm = ZoneCommand:new("Farm") -zones.farm.initialState = { side=1 } -zones.farm:defineUpgrades({ - [1] = - { - presets.upgrades.basic.tent:extend({ - name='farm-tent-red', - products = { - presets.special.red.infantry:extend({ name='farm-defense-red'}), - presets.defenses.red.infantry:extend({ name='farm-garrison-red'}) - } - }), - presets.upgrades.supply.farm1:extend({ - name='farm-prod-red-1', - products = { - presets.missions.supply.convoy:extend({ name='farm-supply-red'}), - presets.missions.supply.transfer:extend({name='farm-transfer-red'}) - } - }), - presets.upgrades.supply.farm2:extend({ - name='farm-prod-red-2', - products = { - presets.missions.supply.convoy:extend({ name='farm-supply-red'}), - presets.missions.supply.transfer:extend({name='farm-transfer-red'}) - } - }) - }, - [2] = - { - presets.upgrades.basic.tent:extend({ - name='farm-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='farm-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='farm-garrison-blue'}) - } - }), - presets.upgrades.supply.farm1:extend({ - name='farm-prod-blue-1', - products = { - presets.missions.supply.convoy:extend({ name='farm-supply-blue'}), - presets.missions.supply.transfer:extend({name='farm-transfer-blue'}) - } - }), - presets.upgrades.supply.farm2:extend({ - name='farm-prod-blue-2', - products = { - presets.missions.supply.convoy:extend({ name='farm-supply-blue'}), - presets.missions.supply.transfer:extend({name='farm-transfer-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Farm.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Romeo.lua ]]----------------- - -zones.romeo = ZoneCommand:new("Romeo") -zones.romeo.initialState = { side=1 } -zones.romeo:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='romeo-tent-red', - products = { - presets.special.red.infantry:extend({ name='romeo-defense-red'}), - presets.defenses.red.infantry:extend({ name='romeo-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='romeo-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='romeo-supply-red'}), - presets.missions.supply.transfer:extend({name='romeo-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='romeo-ammo-red', - products = { - presets.missions.attack.surface:extend({name='romeo-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='romeo-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='romeo-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='romeo-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='romeo-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='romeo-supply-blue'}), - presets.missions.supply.transfer:extend({name='romeo-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='romeo-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='romeo-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Romeo.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Zulu.lua ]]----------------- - -zones.zulu = ZoneCommand:new("Zulu") -zones.zulu.initialState = { side=1 } -zones.zulu:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='zulu-tent-red', - products = { - presets.special.red.infantry:extend({ name='zulu-defense-red'}), - presets.defenses.red.infantry:extend({ name='zulu-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='zulu-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='zulu-supply-red'}), - presets.missions.supply.transfer:extend({name='zulu-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='zulu-ammo-red', - products = { - presets.missions.attack.surface:extend({name='zulu-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='zulu-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='zulu-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='zulu-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='zulu-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='zulu-supply-blue'}), - presets.missions.supply.transfer:extend({name='zulu-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='zulu-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='zulu-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Zulu.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Yankee.lua ]]----------------- - -zones.yankee = ZoneCommand:new("Yankee") -zones.yankee.initialState = { side=1 } -zones.yankee:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='yankee-tent-red', - products = { - presets.special.red.infantry:extend({ name='yankee-defense-red'}), - presets.defenses.red.infantry:extend({ name='yankee-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='yankee-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='yankee-supply-red'}), - presets.missions.supply.transfer:extend({name='yankee-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='yankee-ammo-red', - products = { - presets.missions.attack.surface:extend({name='yankee-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='yankee-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='yankee-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='yankee-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='yankee-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='yankee-supply-blue'}), - presets.missions.supply.transfer:extend({name='yankee-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='yankee-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='yankee-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Yankee.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Malgobek.lua ]]----------------- - -zones.malgobek = ZoneCommand:new("Malgobek") -zones.malgobek.initialState = { side=1 } -zones.malgobek:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='malgobek-tent-red', - products = { - presets.special.red.infantry:extend({ name='malgobek-defense-red'}), - presets.defenses.red.infantry:extend({ name='malgobek-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='malgobek-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='malgobek-supply-red'}), - presets.missions.supply.transfer:extend({name='malgobek-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='malgobek-ammo-red', - products = { - presets.missions.attack.surface:extend({name='malgobek-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='malgobek-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='malgobek-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='malgobek-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='malgobek-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='malgobek-supply-blue'}), - presets.missions.supply.transfer:extend({name='malgobek-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='malgobek-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='malgobek-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Malgobek.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Kilo.lua ]]----------------- - -zones.kilo = ZoneCommand:new("Kilo") -zones.kilo.initialState = { side=1 } -zones.kilo:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='kilo-tent-red', - products = { - presets.special.red.infantry:extend({ name='kilo-defense-red'}), - presets.defenses.red.infantry:extend({ name='kilo-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='kilo-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='kilo-supply-red'}), - presets.missions.supply.transfer:extend({name='kilo-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='kilo-ammo-red', - products = { - presets.missions.attack.surface:extend({name='kilo-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='kilo-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='kilo-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='kilo-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='kilo-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='kilo-supply-blue'}), - presets.missions.supply.transfer:extend({name='kilo-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='kilo-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='kilo-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Kilo.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Quebec.lua ]]----------------- - -zones.quebec = ZoneCommand:new("Quebec") -zones.quebec.initialState = { side=1 } -zones.quebec.keepActive = true -zones.quebec:defineUpgrades({ - [1] = - { - presets.upgrades.basic.tent:extend({ - name='quebec-tent-red', - products = { - presets.special.red.infantry:extend({ name='quebec-defense-red'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='quebec-comcenter-red', - products = { - presets.defenses.red.sam:extend({ name='quebec-sam-red'}) - } - }) - }, - [2] = - { - presets.upgrades.basic.tent:extend({ - name='quebec-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='quebec-defense-blue'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='quebec-comcenter-blue', - products = { - presets.defenses.blue.sam:extend({ name='quebec-sam-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Quebec.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/OilFields.lua ]]----------------- - -zones.oilfields = ZoneCommand:new("Oil Fields") -zones.oilfields.initialState = { side=1 } -zones.oilfields:defineUpgrades({ - [1] = { - presets.upgrades.basic.outpost:extend({ - name='oilfields-outpost-red', - products = { - presets.special.red.infantry:extend({ name='oilfields-defense-red'}), - presets.defenses.red.infantry:extend({ name='oilfields-garrison-red'}) - } - }), - presets.upgrades.supply.oilPump:extend({ - name='oilfields-pump-red-1', - products = { - presets.missions.supply.transfer:extend({name='oilfields-transfer-red1'}) - } - }), - presets.upgrades.supply.oilPump:extend({ - name='oilfields-pump-red-2', - products = { - presets.missions.supply.convoy:extend({name='oilfields-supply-red-1'}) - } - }), - presets.upgrades.supply.oilPump:extend({ - name='oilfields-pump-red-3', - products = { - presets.missions.supply.transfer:extend({name='oilfields-transfer-red2'}) - } - }), - presets.upgrades.supply.oilPump:extend({ - name='oilfields-pump-red-4', - products = { - presets.missions.supply.convoy:extend({name='oilfields-supply-red-2'}) - } - }) - }, - [2] = { - presets.upgrades.basic.outpost:extend({ - name='oilfields-outpost-blue', - products = { - presets.special.blue.infantry:extend({ name='oilfields-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='oilfields-garrison-blue'}) - } - }), - presets.upgrades.supply.oilPump:extend({ - name='oilfields-pump-blue-1', - products = { - presets.missions.supply.transfer:extend({name='oilfields-transfer-blue1'}) - } - }), - presets.upgrades.supply.oilPump:extend({ - name='oilfields-pump-blue-2', - products = { - presets.missions.supply.convoy:extend({name='oilfields-supply-blue-1'}) - } - }), - presets.upgrades.supply.oilPump:extend({ - name='oilfields-pump-blue-3', - products = { - presets.missions.supply.transfer:extend({name='oilfields-transfer-blue2'}) - } - }), - presets.upgrades.supply.oilPump:extend({ - name='oilfields-pump-blue-4', - products = { - presets.missions.supply.convoy:extend({name='oilfields-supply-blue-2'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/OilFields.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Echo.lua ]]----------------- - -zones.echo = ZoneCommand:new("Echo") -zones.echo.initialState = { side=2 } -zones.echo:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='echo-tent-red', - products = { - presets.special.red.infantry:extend({ name='echo-defense-red'}), - presets.defenses.red.infantry:extend({ name='echo-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='echo-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='echo-supply-red'}), - presets.missions.supply.transfer:extend({name='echo-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='echo-ammo-red', - products = { - presets.missions.attack.surface:extend({name='echo-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='echo-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='echo-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='echo-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='echo-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='echo-supply-blue'}), - presets.missions.supply.transfer:extend({name='echo-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='echo-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='echo-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Echo.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Kobuleti.lua ]]----------------- - -zones.kobuleti = ZoneCommand:new("Kobuleti") -zones.kobuleti.initialState = { side=2 } -zones.kobuleti.keepActive = true -zones.kobuleti.isHeloSpawn = true -zones.kobuleti.isPlaneSpawn = true -zones.kobuleti.maxResource = 50000 -zones.kobuleti:defineUpgrades({ - [1] = - { - presets.upgrades.basic.comPost:extend({ - name='kobuleti-compost-red', - products = { - presets.special.red.infantry:extend({ name='kobuleti-defense-red'}), - presets.defenses.red.infantry:extend({ name='kobuleti-garrison-red'}), - presets.missions.attack.surface:extend({ name='kobuleti-assault-red'}) - } - }), - presets.upgrades.supply.fuelTank:extend({ - name='kobuleti-fuel-red', - products = { - presets.missions.supply.helo:extend({name='kobuleti-supply-red-1'}), - presets.missions.supply.helo:extend({name='kobuleti-supply-red-2'}), - presets.missions.supply.transfer:extend({name='kobuleti-transfer-red'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='kobuleti-comcenter-red', - products = { - presets.defenses.red.shorad:extend({ name='kobuleti-airdef-red'}), - presets.missions.attack.sead:extend({name='kobuleti-sead-red', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.attack.cas:extend({name='kobuleti-cas-red', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.bai:extend({name='kobuleti-cas-red', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.strike:extend({name='kobuleti-strike-red', altitude=20000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.patrol.aircraft:extend({name='kobuleti-patrol-red', altitude=25000, range=25}) - } - }) - }, - [2] = - { - presets.upgrades.basic.comPost:extend({ - name='kobuleti-compost-blue', - products = { - presets.special.blue.infantry:extend({ name='kobuleti-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='kobuleti-garrison-blue'}), - presets.missions.attack.surface:extend({ name='kobuleti-assault-blue'}) - } - }), - presets.upgrades.supply.fuelTank:extend({ - name='kobuleti-fuel-blue', - products = { - presets.missions.supply.helo:extend({name='kobuleti-supply-blue-1'}), - presets.missions.supply.helo:extend({name='kobuleti-supply-blue-2'}), - presets.missions.supply.transfer:extend({name='kobuleti-transfer-blue'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='kobuleti-comcenter-blue', - products = { - presets.defenses.blue.shorad:extend({ name='kobuleti-airdef-blue'}), - presets.missions.attack.sead:extend({name='kobuleti-sead-blue', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.attack.cas:extend({name='kobuleti-cas-blue', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.bai:extend({name='kobuleti-cas-blue', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.strike:extend({name='kobuleti-strike-blue', altitude=20000, expend=AI.Task.WeaponExpend.TWO}), - presets.missions.patrol.aircraft:extend({name='kobuleti-patrol-blue', altitude=25000, range=25}), - presets.missions.support.awacs:extend({name='kobuleti-awacs-blue', altitude=30000, freq=258.5}), - presets.missions.support.tanker:extend({name='kobuleti-tanker-blue', altitude=23000, freq=258, tacan='38', variant='Boom'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Kobuleti.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Alpha.lua ]]----------------- - -zones.alpha = ZoneCommand:new('Alpha') -zones.alpha.initialState = { side=2 } -zones.alpha:defineUpgrades({ - [1] = --red side - { - presets.upgrades.basic.tent:extend({ - name = 'alpha-tent-red', - products = { - presets.special.red.infantry:extend({ name='alpha-defense-red'}), - presets.defenses.red.infantry:extend({ name='alpha-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name = 'alpha-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({ name='alpha-supply-red'}), - presets.missions.supply.transfer:extend({name='alpha-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name = 'alpha-ammo-red', - products = { - presets.missions.attack.surface:extend({ name='alpha-assault-red'}) - } - }) - }, - [2] = --blue side - { - presets.upgrades.basic.tent:extend({ - name = 'alpha-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='alpha-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='alpha-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name = 'alpha-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({ name='alpha-supply-blue'}), - presets.missions.supply.transfer:extend({name='alpha-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name = 'alpha-ammo-blue', - products = { - presets.missions.attack.surface:extend({ name='alpha-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Alpha.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Foxtrot.lua ]]----------------- - -zones.foxtrot = ZoneCommand:new("Foxtrot") -zones.foxtrot.initialState = { side=2 } -zones.foxtrot:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='foxtrot-tent-red', - products = { - presets.special.red.infantry:extend({ name='foxtrot-defense-red'}), - presets.defenses.red.infantry:extend({ name='foxtrot-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='foxtrot-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='foxtrot-supply-red'}), - presets.missions.supply.transfer:extend({name='foxtrot-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='foxtrot-ammo-red', - products = { - presets.missions.attack.surface:extend({name='foxtrot-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='foxtrot-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='foxtrot-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='foxtrot-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='foxtrot-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='foxtrot-supply-blue'}), - presets.missions.supply.transfer:extend({name='foxtrot-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='foxtrot-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='foxtrot-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Foxtrot.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Sierra.lua ]]----------------- - -zones.sierra = ZoneCommand:new("Sierra") -zones.sierra.initialState = { side=1 } -zones.sierra.isHeloSpawn = true -zones.sierra:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='sierra-tent-red', - products = { - presets.special.red.infantry:extend({ name='sierra-defense-red'}), - presets.defenses.red.infantry:extend({ name='sierra-garrison-red'}) - } - }), - presets.upgrades.supply.fuelTankFarp:extend({ - name='sierra-fuel-red', - products = { - presets.missions.supply.helo:extend({name='sierra-supply-red'}), - presets.missions.supply.helo:extend({name='sierra-supply-red-1'}), - presets.missions.supply.transfer:extend({name='sierra-transfer-red'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='sierra-comcenter-red', - products = { - presets.defenses.red.shorad:extend({name='sierra-sam-red'}), - presets.missions.attack.helo:extend({name='sierra-cas-red', altitude=200, expend=AI.Task.WeaponExpend.HALF }) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='sierra-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='sierra-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='sierra-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelTankFarp:extend({ - name='sierra-fuel-blue', - products = { - presets.missions.supply.helo:extend({name='sierra-supply-blue'}), - presets.missions.supply.helo:extend({name='sierra-supply-blue-1'}), - presets.missions.supply.transfer:extend({name='sierra-transfer-blue'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='sierra-comcenter-blue', - products = { - presets.defenses.blue.shorad:extend({name='sierra-sam-blue'}), - presets.missions.attack.helo:extend({name='sierra-cas-blue', altitude=200, expend=AI.Task.WeaponExpend.HALF }) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Sierra.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Oni.lua ]]----------------- - -zones.oni = ZoneCommand:new("Oni") -zones.oni.initialState = { side=1 } -zones.oni:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='oni-tent-red', - products = { - presets.special.red.infantry:extend({ name='oni-defense-red'}), - presets.defenses.red.infantry:extend({ name='oni-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='oni-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='oni-supply-red'}), - presets.missions.supply.helo:extend({name='oni-supply-red-2'}), - presets.missions.supply.transfer:extend({name='oni-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='oni-ammo-red', - products = { - presets.missions.attack.surface:extend({name='oni-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='oni-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='oni-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='oni-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='oni-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='oni-supply-blue'}), - presets.missions.supply.helo:extend({name='oni-supply-blue-2'}), - presets.missions.supply.transfer:extend({name='oni-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='oni-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='oni-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Oni.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Hotel.lua ]]----------------- - -zones.hotel = ZoneCommand:new("Hotel") -zones.hotel.initialState = nil -zones.hotel:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='hotel-tent-red', - products = { - presets.special.red.infantry:extend({ name='hotel-defense-red'}), - presets.defenses.red.infantry:extend({ name='hotel-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='hotel-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='hotel-supply-red'}), - presets.missions.supply.transfer:extend({name='hotel-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='hotel-ammo-red', - products = { - presets.missions.attack.surface:extend({name='hotel-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='hotel-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='hotel-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='hotel-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='hotel-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='hotel-supply-blue'}), - presets.missions.supply.transfer:extend({name='hotel-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='hotel-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='hotel-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Hotel.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Victor.lua ]]----------------- - -zones.victor = ZoneCommand:new("Victor") -zones.victor.initialState = { side=1 } -zones.victor:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='victor-tent-red', - products = { - presets.special.red.infantry:extend({ name='victor-defense-red'}), - presets.defenses.red.infantry:extend({ name='victor-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='victor-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='victor-supply-red'}), - presets.missions.supply.helo:extend({name='victor-supply-red-2'}), - presets.missions.supply.transfer:extend({name='victor-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='victor-ammo-red', - products = { - presets.missions.attack.surface:extend({name='victor-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='victor-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='victor-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='victor-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='victor-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='victor-supply-blue'}), - presets.missions.supply.helo:extend({name='victor-supply-blue-2'}), - presets.missions.supply.transfer:extend({name='victor-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='victor-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='victor-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Victor.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Tango.lua ]]----------------- - -zones.tango = ZoneCommand:new("Tango") -zones.tango.initialState = { side=1 } -zones.tango.isHeloSpawn = true -zones.tango:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='tango-tent-red', - products = { - presets.special.red.infantry:extend({ name='tango-defense-red'}), - presets.defenses.red.infantry:extend({ name='tango-garrison-red'}) - } - }), - presets.upgrades.supply.fuelTankFarp:extend({ - name='tango-fuel-red', - products = { - presets.missions.supply.helo:extend({name='tango-supply-red'}), - presets.missions.supply.helo:extend({name='tango-supply-red-1'}), - presets.missions.supply.transfer:extend({name='tango-transfer-red'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='tango-comcenter-red', - products = { - presets.defenses.red.shorad:extend({name='tango-sam-red'}), - presets.missions.attack.helo:extend({name='tango-cas-red', altitude=200, expend=AI.Task.WeaponExpend.HALF }) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='tango-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='tango-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='tango-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelTankFarp:extend({ - name='tango-fuel-blue', - products = { - presets.missions.supply.helo:extend({name='tango-supply-blue'}), - presets.missions.supply.helo:extend({name='tango-supply-blue-1'}), - presets.missions.supply.transfer:extend({name='tango-transfer-blue'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='tango-comcenter-blue', - products = { - presets.defenses.blue.shorad:extend({name='tango-sam-blue'}), - presets.missions.attack.helo:extend({name='tango-cas-blue', altitude=200, expend=AI.Task.WeaponExpend.HALF }) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Tango.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Unal.lua ]]----------------- - -zones.unal = ZoneCommand:new("Unal") -zones.unal.initialState = { side=1 } -zones.unal.isHeloSpawn = true -zones.unal:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='unal-tent-red', - products = { - presets.special.red.infantry:extend({ name='unal-defense-red'}), - presets.defenses.red.infantry:extend({ name='unal-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='unal-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='unal-supply-red'}), - presets.missions.supply.helo:extend({name='unal-supply-red-2'}), - presets.missions.supply.transfer:extend({name='unal-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='unal-ammo-red', - products = { - presets.missions.attack.surface:extend({name='unal-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='unal-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='unal-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='unal-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='unal-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='unal-supply-blue'}), - presets.missions.supply.helo:extend({name='unal-supply-blue-2'}), - presets.missions.supply.transfer:extend({name='unal-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='unal-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='unal-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Unal.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Beslan.lua ]]----------------- - -zones.beslan = ZoneCommand:new("Beslan") -zones.beslan.initialState = { side=1 } -zones.beslan.keepActive = true -zones.beslan.maxResource = 50000 -zones.beslan:defineUpgrades({ - [1] = - { - presets.upgrades.basic.comPost:extend({ - name='beslan-compost-red', - products = { - presets.special.red.infantry:extend({ name='beslan-defense-red'}), - presets.defenses.red.infantry:extend({ name='beslan-garrison-red'}) - } - }), - presets.upgrades.supply.fuelTank:extend({ - name='beslan-fuel-red', - products = { - presets.missions.supply.helo:extend({name='beslan-supply-red-1'}), - presets.missions.supply.helo:extend({name='beslan-supply-red-2'}), - presets.missions.supply.convoy_escorted:extend({name='beslan-supply-red-3'}), - presets.missions.supply.transfer:extend({name='beslan-transfer-red'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='beslan-comcenter-red', - products = { - presets.defenses.red.sa5:extend({ name='beslan-airdef-red'}), - presets.missions.attack.sead:extend({name='beslan-sead-red', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.attack.strike:extend({name='beslan-strike-red', altitude=30000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.attack.strike:extend({name='beslan-strike-red-1', altitude=30000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.patrol.aircraft:extend({name='beslan-patrol-red', altitude=25000, range=25}) - } - }) - }, - [2] = - { - presets.upgrades.basic.comPost:extend({ - name='beslan-compost-blue', - products = { - presets.special.blue.infantry:extend({ name='beslan-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='beslan-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelTank:extend({ - name='beslan-fuel-blue', - products = { - presets.missions.supply.helo:extend({name='beslan-supply-blue-1'}), - presets.missions.supply.helo:extend({name='beslan-supply-blue-2'}), - presets.missions.supply.convoy_escorted:extend({name='beslan-supply-blue-3'}), - presets.missions.supply.transfer:extend({name='beslan-transfer-blue'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='beslan-comcenter-blue', - products = { - presets.defenses.blue.patriot:extend({ name='beslan-airdef-blue'}), - presets.missions.attack.sead:extend({name='beslan-sead-blue', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.attack.strike:extend({name='beslan-strike-blue', altitude=30000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.attack.strike:extend({name='beslan-strike-blue-1', altitude=30000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.patrol.aircraft:extend({name='beslan-patrol-blue', altitude=25000, range=25}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Beslan.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Bravo.lua ]]----------------- - -zones.bravo = ZoneCommand:new("Bravo") -zones.bravo.initialState = { side=2 } -zones.bravo:defineUpgrades({ - [1] = - { - presets.upgrades.basic.comPost:extend({ - name='bravo-compost-red', - products = { - presets.special.red.infantry:extend({ name='bravo-defense-red'}), - presets.defenses.red.infantry:extend({ name='bravo-garrison-red'}) - } - }), - presets.upgrades.supply.fuelTankFarp:extend({ - name='bravo-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({ name='bravo-supply-red'}), - presets.missions.supply.transfer:extend({name='bravo-transfer-red'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='bravo-comcenter-red', - products = { - presets.defenses.red.shorad:extend({ name='bravo-airdef-red'}), - presets.missions.attack.helo:extend({name='bravo-attack-red', altitude=200, expend=AI.Task.WeaponExpend.HALF}) - } - }) - }, - [2] = - { - presets.upgrades.basic.comPost:extend({ - name='bravo-compost-blue', - products = { - presets.special.blue.infantry:extend({ name='bravo-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='bravo-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelTankFarp:extend({ - name='bravo-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({ name='bravo-supply-blue'}), - presets.missions.supply.transfer:extend({name='bravo-transfer-blue'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='bravo-comcenter-blue', - products = { - presets.defenses.blue.shorad:extend({ name='bravo-airdef-blue'}), - presets.missions.attack.helo:extend({name='bravo-attack-blue', altitude=200, expend=AI.Task.WeaponExpend.HALF }) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Bravo.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/WeaponDepot.lua ]]----------------- - -zones.weapondepot = ZoneCommand:new("Weapon Depot") -zones.weapondepot.initialState = { side=1 } -zones.weapondepot:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='weapons-tent-red', - products = { - presets.special.red.infantry:extend({ name='weapons-defense-red'}), - presets.defenses.red.infantry:extend({ name='weapons-garrison-red'}) - } - }), - presets.upgrades.supply.ammoDepot:extend({ - name='weapons-ammo-red-1', - products = { - presets.missions.supply.convoy:extend({name='weapons-supply-red-1'}), - presets.missions.supply.transfer:extend({name='weapons-transfer-red-1'}) - } - }), - presets.upgrades.supply.ammoDepot:extend({ - name='weapons-ammo-red-2', - products = { - presets.missions.supply.convoy:extend({name='weapons-supply-red-2'}), - presets.missions.supply.transfer:extend({name='weapons-transfer-red-2'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='weapons-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='weapons-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='weapons-garrison-blue'}) - } - }), - presets.upgrades.supply.ammoDepot:extend({ - name='weapons-ammo-blue-1', - products = { - presets.missions.supply.convoy:extend({name='weapons-supply-blue-1'}), - presets.missions.supply.transfer:extend({name='weapons-transfer-blue-1'}) - } - }), - presets.upgrades.supply.ammoDepot:extend({ - name='weapons-ammo-blue-2', - products = { - presets.missions.supply.convoy:extend({name='weapons-supply-blue-2'}), - presets.missions.supply.transfer:extend({name='weapons-transfer-blue-2'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/WeaponDepot.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Delta.lua ]]----------------- - -zones.delta = ZoneCommand:new("Delta") -zones.delta.initialState = { side=2 } -zones.delta:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='delta-tent-red', - products = { - presets.special.red.infantry:extend({ name='delta-defense-red'}), - presets.defenses.red.infantry:extend({ name='delta-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='delta-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='delta-supply-red'}), - presets.missions.supply.transfer:extend({name='delta-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='delta-ammo-red', - products = { - presets.missions.attack.surface:extend({name='delta-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='delta-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='delta-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='delta-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='delta-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='delta-supply-blue'}), - presets.missions.supply.transfer:extend({name='delta-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='delta-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='delta-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Delta.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Cherkessk.lua ]]----------------- - -zones.cherkessk = ZoneCommand:new("Cherkessk") -zones.cherkessk.initialState = { side=1 } -zones.cherkessk.isHeloSpawn = true -zones.cherkessk:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='cherkessk-tent-red', - products = { - presets.special.red.infantry:extend({ name='cherkessk-defense-red'}), - presets.defenses.red.infantry:extend({ name='cherkessk-garrison-red'}) - } - }), - presets.upgrades.supply.fuelTankFarp:extend({ - name='cherkessk-fuel-red', - products = { - presets.missions.supply.helo:extend({name='cherkessk-supply-red'}), - presets.missions.supply.helo:extend({name='cherkessk-supply-red-1'}), - presets.missions.supply.transfer:extend({name='cherkessk-transfer-red'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='cherkessk-comcenter-red', - products = { - presets.defenses.red.shorad:extend({name='cherkessk-sam-red'}), - presets.missions.attack.helo:extend({name='cherkessk-cas-red', altitude=200, expend=AI.Task.WeaponExpend.HALF }), - presets.missions.attack.helo:extend({name='cherkessk-cas-red-1', altitude=200, expend=AI.Task.WeaponExpend.HALF }), - presets.missions.attack.surface:extend({name='cherkessk-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='cherkessk-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='cherkessk-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='cherkessk-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelTankFarp:extend({ - name='cherkessk-fuel-blue', - products = { - presets.missions.supply.helo:extend({name='cherkessk-supply-blue'}), - presets.missions.supply.helo:extend({name='cherkessk-supply-blue-1'}), - presets.missions.supply.transfer:extend({name='cherkessk-transfer-blue'}), - presets.missions.attack.surface:extend({name='cherkessk-assault-blue'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='cherkessk-comcenter-blue', - products = { - presets.defenses.blue.shorad:extend({name='cherkessk-sam-blue'}), - presets.missions.attack.helo:extend({name='cherkessk-cas-blue', altitude=200, expend=AI.Task.WeaponExpend.HALF }), - presets.missions.attack.helo:extend({name='cherkessk-cas-blue-1', altitude=200, expend=AI.Task.WeaponExpend.HALF }) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Cherkessk.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Juliett.lua ]]----------------- - -zones.juliett = ZoneCommand:new("Juliett") -zones.initialState = nil -zones.juliett:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='juliett-tent-red', - products = { - presets.special.red.infantry:extend({ name='juliett-defense-red'}), - presets.defenses.red.infantry:extend({ name='juliett-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='juliett-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='juliett-supply-red'}), - presets.missions.supply.transfer:extend({name='juliett-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='juliett-ammo-red', - products = { - presets.missions.attack.surface:extend({name='juliett-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='juliett-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='juliett-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='juliett-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='juliett-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='juliett-supply-blue'}), - presets.missions.supply.transfer:extend({name='juliett-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='juliett-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='juliett-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Juliett.lua ]]----------------- - - - - cm = ConnectionManager:new() - cm:addConnection('Batumi', 'Alpha') - cm:addConnection('Alpha', 'Bravo') - cm:addConnection('Bravo', 'Kobuleti') - cm:addConnection('Bravo', 'Factory') - cm:addConnection('Kobuleti', 'Factory') - cm:addConnection('Kobuleti', 'Charlie') - cm:addConnection('Foxtrot', 'Charlie') - cm:addConnection('Foxtrot', 'Kobuleti') - cm:addConnection('Delta','Foxtrot') - cm:addConnection('Delta','Kobuleti') - cm:addConnection('Delta','Factory') - cm:addConnection('Echo','Charlie') - cm:addConnection('Golf','Echo') - cm:addConnection('Golf','Foxtrot') - cm:addConnection('India','Delta') - cm:addConnection('Hotel','Golf') - cm:addConnection('Hotel','Foxtrot') - cm:addConnection('Hotel','Delta') - cm:addConnection('Hotel','India') - cm:addConnection('Juliett','Echo') - cm:addConnection('Juliett','Golf') - cm:addConnection('Senaki','Juliett') - cm:addConnection('Senaki','Golf') - cm:addConnection('Senaki','Hotel') - cm:addConnection('Kutaisi','Hotel') - cm:addConnection('Kutaisi','India') - cm:addConnection('Kilo','Juliett') - cm:addConnection('Mike','Kutaisi') - cm:addConnection('Mike','Senaki') - cm:addConnection('Romeo','Mike') - cm:addConnection('Romeo','Kutaisi') - cm:addConnection('Weapon Depot','Juliett') - cm:addConnection('Weapon Depot','Senaki') - cm:addConnection('Weapon Depot','Kilo') - cm:addConnection('November','Weapon Depot') - cm:addConnection('November','Senaki') - cm:addConnection('November','Mike') - cm:addConnection('Oil Fields','Romeo') - cm:addConnection('Quebec','Kilo') - cm:addConnection('Zugdidi','Weapon Depot') - cm:addConnection('Zugdidi','Quebec') - cm:addConnection('Zugdidi','November') - cm:addConnection('Zugdidi','Kilo') - cm:addConnection('Distillery','November') - cm:addConnection('Distillery','Mike') - cm:addConnection('Zugdidi','Papa') - cm:addConnection('November','Papa') - cm:addConnection('Sierra','Papa') - cm:addConnection('Sierra','Zugdidi') - cm:addConnection('Sierra','Uniform') - cm:addConnection('Mine','Uniform') - cm:addConnection('Tango','Quebec') - cm:addConnection('Tango','Zugdidi') - cm:addConnection('Sierra','Tango') - cm:addConnection('Whiskey','Tango') - cm:addConnection('Ochamchira','Tango') - cm:addConnection('Ochamchira','Whiskey') - cm:addConnection('Ochamchira','Farm') - cm:addConnection('Ochamchira','Zulu') - cm:addConnection('Farm','Zulu') - cm:addConnection('Sukhumi','Zulu') - cm:addConnection('Lentehi','Distillery', true, 3000) - cm:addConnection('Lentehi','Babugent', true, 5000) - cm:addConnection('Nalchik','Babugent') - cm:addConnection('Victor','Distillery', true, 2000) - cm:addConnection('Victor','Romeo') - cm:addConnection('Victor','Lentehi') - cm:addConnection('Victor','Oil Fields', true, 2000) - cm:addConnection('Victor','Oni') - cm:addConnection('Unal','Oni', true, 4500) - cm:addConnection('Beslan','Unal') - cm:addConnection('Digora','Beslan') - cm:addConnection('Digora','Unal') - cm:addConnection('Digora','Babugent') - cm:addConnection('Terek','Digora') - cm:addConnection('Terek','Nalchik') - cm:addConnection('Terek','Beslan') - cm:addConnection('Prohladniy','Terek') - cm:addConnection('Prohladniy','Nalchik') - cm:addConnection('Malgobek','Terek') - cm:addConnection('Malgobek','Beslan') - cm:addConnection('Lima','Mine') - cm:addConnection('Lima','Lentehi', true, 4000) - cm:addConnection('Tyrnyauz','Lima', true, 4000) - cm:addConnection('Tyrnyauz','Nalchik') - cm:addConnection('XRay','Sukhumi') - cm:addConnection('Oscar','Sukhumi') - cm:addConnection('Oscar','XRay') - cm:addConnection('Mozdok','Malgobek') - cm:addConnection('Mozdok','Prohladniy') - cm:addConnection('Gudauta','Oscar') - cm:addConnection('Yankee','Gudauta') - cm:addConnection('Sochi','Yankee') - cm:addConnection('Refinery','XRay', true, 4000) - cm:addConnection('Refinery','Humara') - cm:addConnection('Intel Center','Tyrnyauz') - cm:addConnection('Intel Center','Nalchik') - cm:addConnection('Intel Center','Prohladniy') - cm:addConnection('Intel Center','Kislovodsk') - cm:addConnection('Mineralnye','Intel Center') - cm:addConnection('Kislovodsk','Mineralnye') - cm:addConnection('Tallyk','Mineralnye') - cm:addConnection('Tallyk','Kislovodsk') - cm:addConnection('Power Plant','Mineralnye') - cm:addConnection('Power Plant','Tallyk') - cm:addConnection('Cherkessk','Tallyk') - cm:addConnection('Cherkessk','Power Plant') - cm:addConnection('Cherkessk','Humara') -end - ZoneCommand.setNeighbours(cm) bm = BattlefieldManager:new() @@ -4527,144 +104,3 @@ local offmapZones = { -- zones.gudauta, -- zones.kobuleti, } - -supplyPointRegistry = { - blue = {}, - red = {} -} - -for i,v in ipairs(blueSupply) do - local g = Group.getByName(v) - if g then - supplyPointRegistry.blue[v] = g:getUnit(1):getPoint() - end -end - -for i,v in ipairs(redSupply) do - local g = Group.getByName(v) - if g then - supplyPointRegistry.red[v] = g:getUnit(1):getPoint() - end -end - -offmapSupplyRegistry = {} -timer.scheduleFunction(function(param, time) - local availableBlue = {} - for i,v in ipairs(param.blue) do - if offmapSupplyRegistry[v] == nil then - table.insert(availableBlue, v) - end - end - - local availableRed = {} - for i,v in ipairs(param.red) do - if offmapSupplyRegistry[v] == nil then - table.insert(availableRed, v) - end - end - - local redtargets = {} - local bluetargets = {} - for _, zn in ipairs(param.offmapZones) do - if zn:needsSupplies(3000) then - local isOnRoute = false - for _,data in pairs(offmapSupplyRegistry) do - if data.zone.name == zn.name then - isOnRoute = true - break - end - end - if not isOnRoute then - if zn.side == 1 then - table.insert(redtargets, zn) - elseif zn.side == 2 then - table.insert(bluetargets, zn) - end - end - end - end - - if #availableRed > 0 and #redtargets > 0 then - local zn = redtargets[math.random(1,#redtargets)] - - local red = nil - local minD = 999999999 - for i,v in ipairs(availableRed) do - local d = mist.utils.get2DDist(zn.zone.point, supplyPointRegistry.red[v]) - if d < minD then - red = v - minD = d - end - end - - if not red then red = availableRed[math.random(1,#availableRed)] end - - local gr = red - red = nil - mist.respawnGroup(gr, true) - offmapSupplyRegistry[gr] = {zone = zn, assigned = timer.getAbsTime()} - env.info(gr..' was deployed') - timer.scheduleFunction(function(param,time) - local g = Group.getByName(param.group) - TaskExtensions.landAtAirfield(g, param.target.zone.point) - env.info(param.group..' going to '..param.target.name) - end, {group=gr, target=zn}, timer.getTime()+2) - end - - if #availableBlue > 0 and #bluetargets>0 then - local zn = bluetargets[math.random(1,#bluetargets)] - - local blue = nil - local minD = 999999999 - for i,v in ipairs(availableBlue) do - local d = mist.utils.get2DDist(zn.zone.point, supplyPointRegistry.blue[v]) - if d < minD then - blue = v - minD = d - end - end - - if not blue then blue = availableBlue[math.random(1,#availableBlue)] end - - local gr = blue - blue = nil - mist.respawnGroup(gr, true) - offmapSupplyRegistry[gr] = {zone = zn, assigned = timer.getAbsTime()} - env.info(gr..' was deployed') - timer.scheduleFunction(function(param,time) - local g = Group.getByName(param.group) - TaskExtensions.landAtAirfield(g, param.target.zone.point) - env.info(param.group..' going to '..param.target.name) - end, {group=gr, target=zn}, timer.getTime()+2) - end - - return time+(60*5) -end, {blue = blueSupply, red = redSupply, offmapZones = offmapZones}, timer.getTime()+60) - - - -timer.scheduleFunction(function(param, time) - - for groupname,data in pairs(offmapSupplyRegistry) do - local gr = Group.getByName(groupname) - if not gr then - offmapSupplyRegistry[groupname] = nil - env.info(groupname..' was destroyed') - end - - if gr and ((timer.getAbsTime() - data.assigned) > (60*60)) then - gr:destroy() - offmapSupplyRegistry[groupname] = nil - env.info(groupname..' despawned due to being alive for too long') - end - - if gr and Utils.allGroupIsLanded(gr) and Utils.someOfGroupInZone(gr, data.zone.name) then - data.zone:addResource(15000) - gr:destroy() - offmapSupplyRegistry[groupname] = nil - env.info(groupname..' landed at '..data.zone.name..' and delivered 15000 resources') - end - end - - return time+180 -end, {}, timer.getTime()+180) \ No newline at end of file diff --git a/resources/plugins/pretense/init_footer.lua b/resources/plugins/pretense/init_footer.lua index 9df8452d..2559cb93 100644 --- a/resources/plugins/pretense/init_footer.lua +++ b/resources/plugins/pretense/init_footer.lua @@ -1,4533 +1,3 @@ - - -local savefile = 'pretense_1.1.json' -if lfs then - local dir = lfs.writedir()..'Missions/Saves/' - lfs.mkdir(dir) - savefile = dir..savefile - env.info('Pretense - Save file path: '..savefile) -end - - -do - TemplateDB.templates["infantry-red"] = { - units = { - "BTR_D", - "T-90", - "T-90", - "Infantry AK ver2", - "Infantry AK", - "Infantry AK", - "Paratrooper RPG-16", - "Infantry AK ver3", - "SA-18 Igla manpad" - }, - skill = "Excellent", - dataCategory= TemplateDB.type.group - } - - TemplateDB.templates["infantry-blue"] = { - units = { - "M1045 HMMWV TOW", - "Soldier stinger", - "Soldier M4 GRG", - "Soldier M4 GRG", - "Soldier M4 GRG", - "Soldier M4 GRG", - "Soldier M4 GRG", - "M1043 HMMWV Armament" - }, - skill = "Excellent", - dataCategory= TemplateDB.type.group - } - - TemplateDB.templates["defense-red"] = { - units = { - "Infantry AK ver2", - "Infantry AK", - "Infantry AK ver3", - "Paratrooper RPG-16", - "SA-18 Igla manpad" - }, - skill = "Excellent", - dataCategory= TemplateDB.type.group - } - - TemplateDB.templates["defense-blue"] = { - units = { - "Soldier M4 GRG", - "Soldier M4 GRG", - "Soldier M4 GRG", - "Soldier RPG", - "Soldier stinger", - }, - skill = "Excellent", - dataCategory= TemplateDB.type.group - } - - TemplateDB.templates["shorad-red"] = { - units = { - "Strela-10M3", - "Strela-10M3", - "Ural-4320T", - "2S6 Tunguska" - }, - maxDist = 300, - skill = "Excellent", - dataCategory= TemplateDB.type.group - } - - TemplateDB.templates["shorad-blue"] = { - units = { - "Roland ADS", - "M48 Chaparral", - "M 818", - "Gepard", - "Gepard" - }, - maxDist = 300, - skill = "Excellent", - dataCategory= TemplateDB.type.group - } - - TemplateDB.templates["sam-red"] = { - units = { - "p-19 s-125 sr", - "Ural-4320T", - "Ural-4320T", - "S_75M_Volhov", - "S_75M_Volhov", - "S_75M_Volhov", - "S_75M_Volhov", - "S_75M_Volhov", - "Tor 9A331", - "SNR_75V" - }, - maxDist = 300, - skill = "Excellent", - dataCategory= TemplateDB.type.group - } - - TemplateDB.templates["sam-blue"] = { - units = { - "Hawk pcp", - "Hawk cwar", - "Hawk ln", - "Hawk ln", - "Hawk ln", - "Hawk ln", - "Hawk ln", - "Hawk tr", - "M 818", - "Hawk sr" - }, - maxDist = 300, - skill = "Good", - dataCategory= TemplateDB.type.group - } - - TemplateDB.templates["patriot"] = { - units = { - "Patriot cp", - "Patriot str", - "M 818", - "M 818", - "Patriot ln", - "Patriot ln", - "Patriot ln", - "Patriot ln", - "Patriot str", - "Patriot str", - "Patriot str", - "Patriot EPP", - "Patriot ECS", - "Patriot AMG" - }, - maxDist = 300, - skill = "Good", - dataCategory= TemplateDB.type.group - } - - TemplateDB.templates["sa3"] = { - units = { - "p-19 s-125 sr", - "snr s-125 tr", - "5p73 s-125 ln", - "5p73 s-125 ln", - "Ural-4320T", - "5p73 s-125 ln", - "5p73 s-125 ln" - }, - maxDist = 300, - skill = "Excellent", - dataCategory= TemplateDB.type.group - } - - TemplateDB.templates["sa6"] = { - units = { - "Kub 1S91 str", - "Kub 2P25 ln", - "Kub 2P25 ln", - "Kub 2P25 ln", - "Kub 2P25 ln", - "2S6 Tunguska", - "Ural-4320T", - "2S6 Tunguska", - "Kub 2P25 ln" - }, - maxDist = 300, - skill = "Excellent", - dataCategory= TemplateDB.type.group - } - - TemplateDB.templates["sa10"] = { - units = { - "S-300PS 54K6 cp", - "S-300PS 5P85C ln", - "S-300PS 5P85C ln", - "S-300PS 5P85C ln", - "GAZ-66", - "GAZ-66", - "GAZ-66", - "S-300PS 5P85C ln", - "S-300PS 5P85C ln", - "S-300PS 5P85C ln", - "S-300PS 40B6MD sr", - "S-300PS 40B6M tr", - "S-300PS 64H6E sr" - }, - maxDist = 300, - skill = "Excellent", - dataCategory= TemplateDB.type.group - } - - TemplateDB.templates["sa5"] = { - units = { - "RLS_19J6", - "Ural-4320T", - "Ural-4320T", - "RPC_5N62V", - "S-200_Launcher", - "S-200_Launcher", - "S-200_Launcher", - "S-200_Launcher", - "S-200_Launcher", - "S-200_Launcher" - }, - maxDist = 300, - skill = "Excellent", - dataCategory= TemplateDB.type.group - } - - TemplateDB.templates["sa11"] = { - units = { - "SA-11 Buk SR 9S18M1", - "SA-11 Buk LN 9A310M1", - "SA-11 Buk LN 9A310M1", - "SA-11 Buk LN 9A310M1", - "SA-11 Buk LN 9A310M1", - "SA-11 Buk LN 9A310M1", - "2S6 Tunguska", - "SA-11 Buk SR 9S18M1", - "GAZ-66", - "GAZ-66", - "SA-11 Buk CC 9S470M1" - }, - maxDist = 300, - skill = "Excellent", - dataCategory= TemplateDB.type.group - } - - TemplateDB.templates["nasams"] = { - units = { - "NASAMS_Command_Post", - "NASAMS_Radar_MPQ64F1", - "Vulcan", - "M 818", - "M 818", - "Roland ADS", - "Roland ADS", - "NASAMS_LN_C", - "NASAMS_LN_C", - "NASAMS_LN_C", - "NASAMS_LN_C", - "NASAMS_Radar_MPQ64F1", - "NASAMS_Radar_MPQ64F1", - "NASAMS_Radar_MPQ64F1" - }, - maxDist = 300, - skill = "Excellent", - dataCategory= TemplateDB.type.group - } -end - -presets = { - upgrades = { - basic = { - tent = Preset:new({ - display = 'Tent', - cost = 1500, - type = 'upgrade', - template = "tent" - }), - comPost = Preset:new({ - display = 'Barracks', - cost = 1500, - type = 'upgrade', - template = "barracks" - }), - outpost = Preset:new({ - display = 'Outpost', - cost = 1500, - type = 'upgrade', - template = "outpost" - }) - }, - attack = { - ammoCache = Preset:new({ - display = 'Ammo Cache', - cost = 1500, - type = 'upgrade', - template = "ammo-cache" - }), - ammoDepot = Preset:new({ - display = 'Ammo Depot', - cost = 2000, - type = 'upgrade', - template = "ammo-depot" - }) - }, - supply = { - fuelCache = Preset:new({ - display = 'Fuel Cache', - cost = 1500, - type = 'upgrade', - template = "fuel-cache" - }), - fuelTank = Preset:new({ - display = 'Fuel Tank', - cost = 1500, - type = 'upgrade', - template = "fuel-tank-big" - }), - fuelTankFarp = Preset:new({ - display = 'Fuel Tank', - cost = 1500, - type = 'upgrade', - template = "fuel-tank-small" - }), - factory1 = Preset:new({ - display='Factory', - cost = 2000, - type ='upgrade', - income = 20, - template = "factory-1" - }), - factory2 = Preset:new({ - display='Factory', - cost = 2000, - type ='upgrade', - income = 20, - template = "factory-2" - }), - factoryTank = Preset:new({ - display='Storage Tank', - cost = 1500, - type ='upgrade', - income = 10, - template = "chem-tank" - }), - ammoDepot = Preset:new({ - display = 'Ammo Depot', - cost = 2000, - type = 'upgrade', - income = 40, - template = "ammo-depot" - }), - oilPump = Preset:new({ - display = 'Oil Pump', - cost = 1500, - type = 'upgrade', - income = 20, - template = "oil-pump" - }), - hangar = Preset:new({ - display = 'Hangar', - cost = 2000, - type = 'upgrade', - income = 30, - template = "hangar" - }), - excavator = Preset:new({ - display = 'Excavator', - cost = 2000, - type = 'upgrade', - income = 20, - template = "excavator" - }), - farm1 = Preset:new({ - display = 'Farm House', - cost = 2000, - type = 'upgrade', - income = 40, - template = "farm-house-1" - }), - farm2 = Preset:new({ - display = 'Farm House', - cost = 2000, - type = 'upgrade', - income = 40, - template = "farm-house-2" - }), - refinery1 = Preset:new({ - display='Refinery', - cost = 2000, - type ='upgrade', - income = 100, - template = "factory-1" - }), - powerplant1 = Preset:new({ - display='Power Plant', - cost = 1500, - type ='upgrade', - income = 25, - template = "factory-1" - }), - powerplant2 = Preset:new({ - display='Power Plant', - cost = 1500, - type ='upgrade', - income = 25, - template = "factory-2" - }), - antenna = Preset:new({ - display='Antenna', - cost = 1000, - type ='upgrade', - income = 10, - template = "antenna" - }), - hq = Preset:new({ - display='HQ Building', - cost = 2000, - type ='upgrade', - income = 50, - template = "tv-tower" - }) - }, - airdef = { - comCenter = Preset:new({ - display = 'Command Center', - cost = 2500, - type = 'upgrade', - template = "command-center" - }) - } - }, - defenses = { - red = { - infantry = Preset:new({ - display = 'Infantry', - cost=2000, - type='defense', - template='infantry-red', - }), - shorad = Preset:new({ - display = 'SAM', - cost=2500, - type='defense', - template='shorad-red', - }), - sam = Preset:new({ - display = 'SAM', - cost=3000, - type='defense', - template='sam-red', - }), - sa10 = Preset:new({ - display = 'SAM', - cost=3000, - type='defense', - template='sa10', - }), - sa5 = Preset:new({ - display = 'SAM', - cost=3000, - type='defense', - template='sa5', - }), - sa3 = Preset:new({ - display = 'SAM', - cost=3000, - type='defense', - template='sa3', - }), - sa6 = Preset:new({ - display = 'SAM', - cost=3000, - type='defense', - template='sa6', - }), - sa11 = Preset:new({ - display = 'SAM', - cost=3000, - type='defense', - template='sa11', - }) - }, - blue = { - infantry = Preset:new({ - display = 'Infantry', - cost=2000, - type='defense', - template='infantry-blue', - }), - shorad = Preset:new({ - display = 'SAM', - cost=2500, - type='defense', - template='shorad-blue', - }), - sam = Preset:new({ - display = 'SAM', - cost=3000, - type='defense', - template='sam-blue', - }), - patriot = Preset:new({ - display = 'SAM', - cost=3000, - type='defense', - template='patriot', - }), - nasams = Preset:new({ - display = 'SAM', - cost=3000, - type='defense', - template='nasams', - }) - } - }, - missions = { - supply = { - convoy = Preset:new({ - display = 'Supply convoy', - cost = 4000, - type = 'mission', - missionType = ZoneCommand.missionTypes.supply_convoy - }), - convoy_escorted = Preset:new({ - display = 'Supply convoy', - cost = 3000, - type = 'mission', - missionType = ZoneCommand.missionTypes.supply_convoy - }), - helo = Preset:new({ - display = 'Supply helicopter', - cost = 2500, - type='mission', - missionType = ZoneCommand.missionTypes.supply_air - }), - transfer = Preset:new({ - display = 'Supply transfer', - cost = 1000, - type='mission', - missionType = ZoneCommand.missionTypes.supply_transfer - }) - }, - attack = { - surface = Preset:new({ - display = 'Ground assault', - cost = 100, - type = 'mission', - missionType = ZoneCommand.missionTypes.assault, - }), - cas = Preset:new({ - display = 'CAS', - cost = 200, - type='mission', - missionType = ZoneCommand.missionTypes.cas - }), - bai = Preset:new({ - display = 'BAI', - cost = 200, - type='mission', - missionType = ZoneCommand.missionTypes.bai - }), - strike = Preset:new({ - display = 'Strike', - cost = 300, - type='mission', - missionType = ZoneCommand.missionTypes.strike - }), - sead = Preset:new({ - display = 'SEAD', - cost = 200, - type='mission', - missionType = ZoneCommand.missionTypes.sead - }), - helo = Preset:new({ - display = 'CAS', - cost = 100, - type='mission', - missionType = ZoneCommand.missionTypes.cas_helo - }) - }, - patrol={ - aircraft = Preset:new({ - display= "Patrol", - cost = 100, - type='mission', - missionType = ZoneCommand.missionTypes.patrol - }) - }, - support ={ - awacs = Preset:new({ - display= "AWACS", - cost = 300, - type='mission', - bias='5', - missionType = ZoneCommand.missionTypes.awacs - }), - tanker = Preset:new({ - display= "Tanker", - cost = 200, - type='mission', - bias='2', - missionType = ZoneCommand.missionTypes.tanker - }) - } - }, - special = { - red = { - infantry = Preset:new({ - display = 'Infantry', - cost=-1, - type='defense', - template='defense-red', - }), - }, - blue = { - infantry = Preset:new({ - display = 'Infantry', - cost=-1, - type='defense', - template='defense-blue', - }) - } - } -} - -zones = {} -do - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Batumi.lua ]]----------------- - -zones.batumi = ZoneCommand:new('Batumi') -zones.batumi.initialState = { side=2 } -zones.batumi.keepActive = true -zones.batumi.isHeloSpawn = true -zones.batumi.isPlaneSpawn = true -zones.batumi.maxResource = 50000 -zones.batumi:defineUpgrades({ - [1] = { --red side - presets.upgrades.basic.comPost:extend({ - name = 'batumi-com-red', - products = { - presets.special.red.infantry:extend({ name='batumi-defense-red'}), - presets.defenses.red.infantry:extend({ name='batumi-garrison-red' }) - } - }), - }, - [2] = --blue side - { - presets.upgrades.basic.comPost:extend({ - name = 'batumi-com-blue', - products = { - presets.special.blue.infantry:extend({ name='batumi-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='batumi-garrison-blue' }) - } - }), - presets.upgrades.supply.fuelTank:extend({ - name = 'batumi-fueltank-blue', - products = { - presets.missions.supply.convoy_escorted:extend({ name='batumi-supply-convoy-1'}), - presets.missions.supply.helo:extend({ name='batumi-supply-blue-1' }), - presets.missions.supply.transfer:extend({name='batumi-transfer-blue'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name = 'batumi-mission-command-blue', - products = { - presets.defenses.blue.shorad:extend({ name='batumi-sam-blue' }), - presets.missions.attack.sead:extend({name='batumi-sead-blue-1', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.attack.cas:extend({name='batumi-cas-blue-1', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.bai:extend({name='batumi-cas-blue-1', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.strike:extend({name='batumi-strike-blue-1', altitude=20000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.patrol.aircraft:extend({name='batumi-patrol-blue-1', altitude=25000, range=25}), - presets.missions.support.awacs:extend({name='batumi-awacs-blue', altitude=30000, freq=257.5}), - presets.missions.support.tanker:extend({name='batumi-tanker-blue', altitude=25000, freq=257, tacan='37', variant="Drogue"}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Batumi.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Mike.lua ]]----------------- - -zones.mike = ZoneCommand:new("Mike") -zones.mike.initialState = { side=1 } -zones.mike.keepActive = true -zones.mike:defineUpgrades({ - [1] = - { - presets.upgrades.basic.tent:extend({ - name='mike-tent-red', - products = { - presets.special.red.infantry:extend({ name='mike-defense-red'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='mike-comcenter-red', - products = { - presets.defenses.red.sam:extend({ name='mike-sam-red'}) - } - }) - }, - [2] = - { - presets.upgrades.basic.tent:extend({ - name='mike-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='mike-defense-blue'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='mike-comcenter-blue', - products = { - presets.defenses.blue.sam:extend({ name='mike-sam-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Mike.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Tyrnyauz.lua ]]----------------- - -zones.tyrnyauz = ZoneCommand:new("Tyrnyauz") -zones.tyrnyauz.initialState = { side=1 } -zones.tyrnyauz.isHeloSpawn = true -zones.tyrnyauz:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='tyrnyauz-tent-red', - products = { - presets.special.red.infantry:extend({ name='tyrnyauz-defense-red'}), - presets.defenses.red.infantry:extend({ name='tyrnyauz-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='tyrnyauz-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='tyrnyauz-supply-red'}), - presets.missions.supply.helo:extend({name='tyrnyauz-supply-red-2'}), - presets.missions.supply.transfer:extend({name='tyrnyauz-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='tyrnyauz-ammo-red', - products = { - presets.missions.attack.surface:extend({name='tyrnyauz-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='tyrnyauz-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='tyrnyauz-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='tyrnyauz-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='tyrnyauz-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='tyrnyauz-supply-blue'}), - presets.missions.supply.helo:extend({name='tyrnyauz-supply-blue-2'}), - presets.missions.supply.transfer:extend({name='tyrnyauz-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='tyrnyauz-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='tyrnyauz-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Tyrnyauz.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/India.lua ]]----------------- - -zones.india = ZoneCommand:new("India") -zones.india.initialState = nil -zones.india:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='india-tent-red', - products = { - presets.special.red.infantry:extend({ name='india-defense-red'}), - presets.defenses.red.infantry:extend({ name='india-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='india-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='india-supply-red'}), - presets.missions.supply.transfer:extend({name='india-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='india-ammo-red', - products = { - presets.missions.attack.surface:extend({name='india-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='india-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='india-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='india-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='india-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='india-supply-blue'}), - presets.missions.supply.transfer:extend({name='india-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='india-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='india-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/India.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/IntelCenter.lua ]]----------------- - -zones.intelcenter = ZoneCommand:new("Intel Center") -zones.intelcenter.initialState = { side=1 } -zones.intelcenter:defineUpgrades({ - [1] = - { - presets.upgrades.basic.tent:extend({ - name='intelcenter-tent-red', - products = { - presets.special.red.infantry:extend({ name='intelcenter-defense-red'}), - presets.defenses.red.infantry:extend({ name='intelcenter-garrison-red'}) - } - }), - presets.upgrades.supply.hq:extend({ - name='intelcenter-hq-red', - products = { - presets.missions.supply.convoy:extend({ name='intelcenter-supply-red'}), - presets.missions.supply.convoy:extend({ name='intelcenter-supply-red-1'}), - presets.missions.supply.transfer:extend({name='intelcenter-transfer-red'}) - } - }), - presets.upgrades.supply.antenna:extend({ - name='intelcenter-antenna-red', - products = { - } - }), - presets.upgrades.supply.antenna:extend({ - name='intelcenter-antenna-red-1', - products = { - } - }), - presets.upgrades.supply.antenna:extend({ - name='intelcenter-antenna-red-2', - products = { - } - }) - }, - [2] = - { - presets.upgrades.basic.tent:extend({ - name='intelcenter-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='intelcenter-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='intelcenter-garrison-blue'}) - } - }), - presets.upgrades.supply.hq:extend({ - name='intelcenter-hq-blue', - products = { - presets.missions.supply.convoy:extend({ name='intelcenter-supply-blue'}), - presets.missions.supply.convoy:extend({ name='intelcenter-supply-blue-1'}), - presets.missions.supply.transfer:extend({name='intelcenter-transfer-blue'}) - } - }), - presets.upgrades.supply.antenna:extend({ - name='intelcenter-antenna-blue', - products = { - } - }), - presets.upgrades.supply.antenna:extend({ - name='intelcenter-antenna-blue-1', - products = { - } - }), - presets.upgrades.supply.antenna:extend({ - name='intelcenter-antenna-blue-2', - products = { - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/IntelCenter.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Mineralnye.lua ]]----------------- - -zones.mineralnye = ZoneCommand:new("Mineralnye") -zones.mineralnye.initialState = { side=1 } -zones.mineralnye.keepActive = true -zones.mineralnye.isHeloSpawn = true -zones.mineralnye.isPlaneSpawn = true -zones.mineralnye:defineUpgrades({ - [1] = - { - presets.upgrades.basic.comPost:extend({ - name='mineralnye-compost-red', - products = { - presets.special.red.infantry:extend({ name='mineralnye-defense-red'}), - presets.defenses.red.infantry:extend({ name='mineralnye-garrison-red'}) - } - }), - presets.upgrades.supply.fuelTank:extend({ - name='mineralnye-fuel-red', - products = { - presets.missions.supply.helo:extend({name='mineralnye-supply-red'}), - presets.missions.supply.helo:extend({name='mineralnye-supply-red-1'}), - presets.missions.supply.transfer:extend({name='mineralnye-transfer-red'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='mineralnye-comcenter-red', - products = { - presets.defenses.red.sa11:extend({ name='mineralnye-airdef-red'}), - presets.missions.attack.cas:extend({name='mineralnye-cas-red', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.bai:extend({name='mineralnye-cas-red', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.strike:extend({name='mineralnye-strike-red', altitude=30000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.attack.strike:extend({name='mineralnye-strike-red-1', altitude=30000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.patrol.aircraft:extend({name='mineralnye-patrol-red', altitude=25000, range=25}) - } - }) - }, - [2] = - { - presets.upgrades.basic.comPost:extend({ - name='mineralnye-compost-blue', - products = { - presets.special.blue.infantry:extend({ name='mineralnye-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='mineralnye-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelTank:extend({ - name='mineralnye-fuel-blue', - products = { - presets.missions.supply.helo:extend({name='mineralnye-supply-blue'}), - presets.missions.supply.helo:extend({name='mineralnye-supply-blue-1'}), - presets.missions.supply.transfer:extend({name='mineralnye-transfer-blue'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='mineralnye-comcenter-blue', - products = { - presets.defenses.blue.nasams:extend({ name='mineralnye-airdef-blue'}), - presets.missions.attack.cas:extend({name='mineralnye-cas-blue', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.bai:extend({name='mineralnye-cas-blue', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.strike:extend({name='mineralnye-strike-blue', altitude=30000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.attack.strike:extend({name='mineralnye-strike-blue-1', altitude=30000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.patrol.aircraft:extend({name='mineralnye-patrol-blue', altitude=25000, range=25}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Mineralnye.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/PowerPlant.lua ]]----------------- - -zones.powerplant = ZoneCommand:new("Power Plant") -zones.powerplant.initialState = { side=1 } -zones.powerplant:defineUpgrades({ - [1] = - { - presets.upgrades.basic.tent:extend({ - name='powerplant-tent-red', - products = { - presets.special.red.infantry:extend({ name='powerplant-defense-red'}), - presets.defenses.red.infantry:extend({ name='powerplant-garrison-red'}) - } - }), - presets.upgrades.supply.powerplant1:extend({ - name='powerplant-building-red-1', - products = { - presets.missions.supply.convoy:extend({ name='powerplant-supply-red'}), - presets.missions.supply.transfer:extend({name='powerplant-transfer-red'}) - } - }), - presets.upgrades.supply.powerplant2:extend({ - name='powerplant-building-red-2', - products = { - presets.missions.supply.convoy:extend({ name='powerplant-supply-red-1'}), - presets.missions.supply.transfer:extend({name='powerplant-transfer-red'}) - } - }) - }, - [2] = - { - presets.upgrades.basic.tent:extend({ - name='powerplant-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='powerplant-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='powerplant-garrison-blue'}) - } - }), - presets.upgrades.supply.powerplant1:extend({ - name='powerplant-building-blue-1', - products = { - presets.missions.supply.convoy:extend({ name='powerplant-supply-blue'}), - presets.missions.supply.transfer:extend({name='powerplant-transfer-blue'}) - } - }), - presets.upgrades.supply.powerplant2:extend({ - name='powerplant-building-blue-2', - products = { - presets.missions.supply.convoy:extend({ name='powerplant-supply-blue-1'}), - presets.missions.supply.transfer:extend({name='powerplant-transfer-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/PowerPlant.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Zugdidi.lua ]]----------------- - -zones.zugdidi = ZoneCommand:new("Zugdidi") -zones.zugdidi.initialState = { side=1 } -zones.zugdidi:defineUpgrades({ - [1] = - { - presets.upgrades.basic.comPost:extend({ - name='zugdidi-compost-red', - products = { - presets.missions.supply.transfer:extend({name='zugdidi-transfer-red'}), - presets.special.red.infantry:extend({ name='zugdidi-defense-red'}), - presets.defenses.red.infantry:extend({ name='zugdidi-garrison-red'}), - presets.missions.attack.surface:extend({name='zugdidi-attack-red'}), - presets.missions.supply.convoy:extend({name='zugdidi-supply-red'}) - } - }), - presets.upgrades.supply.hangar:extend({ - name='zugdidi-hangar-red-1', - products = { - presets.missions.supply.helo:extend({name='zugdidi-supply-red-1'}) - } - }), - presets.upgrades.supply.hangar:extend({ - name='zugdidi-hangar-red-2', - products = { - presets.missions.supply.helo:extend({name='zugdidi-supply-red-2'}) - } - }), - presets.upgrades.supply.hangar:extend({ - name='zugdidi-hangar-red-3', - products = { - presets.missions.supply.helo:extend({name='zugdidi-supply-red-3'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='zugdidi-comcenter-red', - products = { - presets.defenses.red.sa6:extend({ name='zugdidi-sam-red'}) - } - }) - }, - [2] = - { - presets.upgrades.basic.tent:extend({ - name='zugdidi-compost-blue', - products = { - presets.missions.supply.transfer:extend({name='zugdidi-transfer-blue'}), - presets.special.blue.infantry:extend({ name='zugdidi-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='zugdidi-garrison-blue'}), - presets.missions.attack.surface:extend({name='zugdidi-attack-blue'}), - presets.missions.supply.convoy:extend({name='zugdidi-supply-blue'}) - } - }), - presets.upgrades.supply.hangar:extend({ - name='zugdidi-hangar-blue-1', - products = { - presets.missions.supply.helo:extend({name='zugdidi-supply-blue-1'}) - } - }), - presets.upgrades.supply.hangar:extend({ - name='zugdidi-hangar-blue-2', - products = { - presets.missions.supply.helo:extend({name='zugdidi-supply-blue-2'}) - } - }), - presets.upgrades.supply.hangar:extend({ - name='zugdidi-hangar-blue-3', - products = { - presets.missions.supply.helo:extend({name='zugdidi-supply-blue-3'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='zugdidi-comcenter-blue', - products = { - presets.defenses.blue.shorad:extend({ name='zugdidi-sam-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Zugdidi.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Babugent.lua ]]----------------- - -zones.babugent = ZoneCommand:new("Babugent") -zones.babugent.initialState = { side=1 } -zones.babugent:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='babugent-tent-red', - products = { - presets.special.red.infantry:extend({ name='babugent-defense-red'}), - presets.defenses.red.infantry:extend({ name='babugent-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='babugent-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='babugent-supply-red'}), - presets.missions.supply.helo:extend({name='babugent-supply-red-2'}), - presets.missions.supply.transfer:extend({name='babugent-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='babugent-ammo-red', - products = { - presets.missions.attack.surface:extend({name='babugent-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='babugent-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='babugent-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='babugent-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='babugent-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='babugent-supply-blue'}), - presets.missions.supply.helo:extend({name='babugent-supply-blue-2'}), - presets.missions.supply.transfer:extend({name='babugent-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='babugent-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='babugent-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Babugent.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Kislovodsk.lua ]]----------------- - -zones.kislovodsk = ZoneCommand:new("Kislovodsk") -zones.kislovodsk.initialState = { side=1 } -zones.kislovodsk:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='kislovodsk-tent-red', - products = { - presets.special.red.infantry:extend({ name='kislovodsk-defense-red'}), - presets.defenses.red.infantry:extend({ name='kislovodsk-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='kislovodsk-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='kislovodsk-supply-red'}), - presets.missions.supply.transfer:extend({name='kislovodsk-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='kislovodsk-ammo-red', - products = { - presets.missions.attack.surface:extend({name='kislovodsk-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='kislovodsk-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='kislovodsk-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='kislovodsk-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='kislovodsk-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='kislovodsk-supply-blue'}), - presets.missions.supply.transfer:extend({name='kislovodsk-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='kislovodsk-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='kislovodsk-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Kislovodsk.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Gudauta.lua ]]----------------- - -zones.gudauta = ZoneCommand:new("Gudauta") -zones.gudauta.initialState = { side=1 } -zones.gudauta.keepActive = true -zones.gudauta.maxResource = 50000 -zones.gudauta:defineUpgrades({ - [1] = - { - presets.upgrades.basic.comPost:extend({ - name='gudauta-compost-red', - products = { - presets.special.red.infantry:extend({ name='gudauta-defense-red'}), - presets.defenses.red.infantry:extend({ name='gudauta-garrison-red'}) - } - }), - presets.upgrades.supply.fuelTank:extend({ - name='gudauta-fuel-red', - products = { - presets.missions.supply.helo:extend({name='gudauta-supply-red'}), - presets.missions.supply.helo:extend({name='gudauta-supply-red-1'}), - presets.missions.supply.transfer:extend({name='gudauta-transfer-red'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='gudauta-comcenter-red', - products = { - presets.defenses.red.sam:extend({ name='gudauta-airdef-red'}), - presets.missions.attack.sead:extend({name='gudauta-sead-red', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.attack.sead:extend({name='gudauta-sead-red-1', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.attack.cas:extend({name='gudauta-cas-red', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.bai:extend({name='gudauta-cas-red', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.patrol.aircraft:extend({name='gudauta-patrol-red', altitude=25000, range=25}) - } - }) - }, - [2] = - { - presets.upgrades.basic.comPost:extend({ - name='gudauta-compost-blue', - products = { - presets.special.blue.infantry:extend({ name='gudauta-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='gudauta-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelTank:extend({ - name='gudauta-fuel-blue', - products = { - presets.missions.supply.helo:extend({name='gudauta-supply-blue'}), - presets.missions.supply.helo:extend({name='gudauta-supply-blue-1'}), - presets.missions.supply.transfer:extend({name='gudauta-transfer-blue'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='gudauta-comcenter-blue', - products = { - presets.defenses.blue.sam:extend({ name='gudauta-airdef-blue'}), - presets.missions.attack.sead:extend({name='gudauta-sead-blue', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.attack.sead:extend({name='gudauta-sead-blue-1', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.attack.cas:extend({name='gudauta-cas-blue', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.bai:extend({name='gudauta-cas-blue', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.patrol.aircraft:extend({name='gudauta-patrol-blue', altitude=25000, range=25}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Gudauta.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Distillery.lua ]]----------------- - -zones.distillery = ZoneCommand:new("Distillery") -zones.distillery.initialState = { side=1 } -zones.distillery:defineUpgrades({ - [1] = - { - presets.upgrades.basic.tent:extend({ - name='distillery-tent-red', - products = { - presets.special.red.infantry:extend({ name='distillery-defense-red'}), - presets.defenses.red.infantry:extend({ name='distillery-garrison-red'}) - } - }), - presets.upgrades.supply.factory1:extend({ - name='distillery-prod-red-1', - products = { - presets.missions.supply.convoy:extend({ name='distillery-supply-red-1'}), - presets.missions.supply.transfer:extend({name='distillery-transfer-red'}) - } - }), - presets.upgrades.supply.factory2:extend({ - name='distillery-prod-red-2', - products = { - presets.missions.supply.convoy:extend({ name='distillery-supply-red-2', cost=2000}), - presets.missions.supply.transfer:extend({name='distillery-transfer-red2'}) - } - }), - presets.upgrades.supply.factoryTank:extend({ - name='distillery-tank-red-1', - products = { - } - }), - presets.upgrades.supply.factoryTank:extend({ - name='distillery-tank-red-2', - products = { - } - }), - presets.upgrades.supply.factoryTank:extend({ - name='distillery-tank-red-3', - products = { - } - }) - }, - [2] = - { - presets.upgrades.basic.tent:extend({ - name='distillery-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='distillery-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='distillery-garrison-blue'}) - } - }), - presets.upgrades.supply.factory1:extend({ - name='distillery-prod-blue-1', - products = { - presets.missions.supply.convoy:extend({ name='distillery-supply-blue-1'}), - presets.missions.supply.transfer:extend({name='distillery-transfer-blue'}) - } - }), - presets.upgrades.supply.factory2:extend({ - name='distillery-prod-blue-2', - products = { - presets.missions.supply.convoy:extend({ name='distillery-supply-blue-2', cost=2000}), - presets.missions.supply.transfer:extend({name='distillery-transfer-blue2'}) - } - }), - presets.upgrades.supply.factoryTank:extend({ - name='distillery-tank-blue-1', - products = { - } - }), - presets.upgrades.supply.factoryTank:extend({ - name='distillery-tank-blue-2', - products = { - } - }), - presets.upgrades.supply.factoryTank:extend({ - name='distillery-tank-blue-3', - products = { - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Distillery.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Sochi.lua ]]----------------- - -zones.sochi = ZoneCommand:new("Sochi") -zones.sochi.initialState = { side=1 } -zones.sochi.keepActive = true -zones.sochi.maxResource = 50000 -zones.sochi:defineUpgrades({ - [1] = - { - presets.upgrades.basic.comPost:extend({ - name='sochi-compost-red', - products = { - presets.special.red.infantry:extend({ name='sochi-defense-red'}), - presets.defenses.red.infantry:extend({ name='sochi-garrison-red'}) - } - }), - presets.upgrades.supply.fuelTank:extend({ - name='sochi-fuel-red', - products = { - presets.missions.supply.helo:extend({name='sochi-supply-red-1'}), - presets.missions.supply.helo:extend({name='sochi-supply-red-2'}), - presets.missions.supply.convoy_escorted:extend({name='sochi-supply-red-3'}), - presets.missions.supply.transfer:extend({name='sochi-transfer-red'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='sochi-comcenter-red', - products = { - presets.defenses.red.sa10:extend({ name='sochi-airdef-red'}), - presets.missions.attack.sead:extend({name='sochi-sead-red', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.attack.strike:extend({name='sochi-strike-red', altitude=30000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.patrol.aircraft:extend({name='sochi-patrol-red', altitude=25000, range=25}), - presets.missions.patrol.aircraft:extend({name='sochi-patrol-red-1', altitude=25000, range=25}), - presets.missions.attack.cas:extend({name='sochi-cas-red', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.bai:extend({name='sochi-cas-red', altitude=10000, expend=AI.Task.WeaponExpend.ONE}) - } - }) - }, - [2] = - { - presets.upgrades.basic.comPost:extend({ - name='sochi-compost-blue', - products = { - presets.special.blue.infantry:extend({ name='sochi-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='sochi-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelTank:extend({ - name='sochi-fuel-blue', - products = { - presets.missions.supply.helo:extend({name='sochi-supply-blue-1'}), - presets.missions.supply.helo:extend({name='sochi-supply-blue-2'}), - presets.missions.supply.convoy_escorted:extend({name='sochi-supply-blue-3'}), - presets.missions.supply.transfer:extend({name='sochi-transfer-blue'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='sochi-comcenter-blue', - products = { - presets.defenses.blue.patriot:extend({ name='sochi-airdef-blue'}), - presets.missions.attack.sead:extend({name='sochi-sead-blue', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.attack.strike:extend({name='sochi-strike-blue', altitude=30000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.patrol.aircraft:extend({name='sochi-patrol-blue', altitude=25000, range=25}), - presets.missions.attack.cas:extend({name='sochi-cas-blue', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.bai:extend({name='sochi-cas-blue', altitude=10000, expend=AI.Task.WeaponExpend.ONE}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Sochi.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Golf.lua ]]----------------- - -zones.golf = ZoneCommand:new("Golf") -zones.golf.initialState = nil -zones.golf.isHeloSpawn = true -zones.golf:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='golf-tent-red', - products = { - presets.special.red.infantry:extend({ name='golf-defense-red'}), - presets.defenses.red.infantry:extend({ name='golf-garrison-red'}) - } - }), - presets.upgrades.supply.fuelTank:extend({ - name='golf-fuel-red', - products = { - presets.missions.supply.helo:extend({name='golf-supply-red'}), - presets.missions.supply.helo:extend({name='golf-supply-red-1'}), - presets.missions.supply.transfer:extend({name='golf-transfer-red'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='golf-comcenter-red', - products = { - presets.defenses.red.shorad:extend({name='golf-sam-red'}), - presets.missions.attack.helo:extend({name='golf-cas-red', altitude=200, expend=AI.Task.WeaponExpend.HALF }) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='golf-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='golf-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='golf-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelTankFarp:extend({ - name='golf-fuel-blue', - products = { - presets.missions.supply.helo:extend({name='golf-supply-blue'}), - presets.missions.supply.helo:extend({name='golf-supply-blue-1'}), - presets.missions.supply.transfer:extend({name='golf-transfer-blue'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='golf-comcenter-blue', - products = { - presets.defenses.blue.shorad:extend({name='golf-sam-blue'}), - presets.missions.attack.helo:extend({name='golf-cas-blue', altitude=200, expend=AI.Task.WeaponExpend.HALF }) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Golf.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Charlie.lua ]]----------------- - -zones.charlie = ZoneCommand:new("Charlie") -zones.charlie.initialState = { side=2 } -zones.charlie.keepActive = true -zones.charlie:defineUpgrades({ - [1] = - { - presets.upgrades.basic.tent:extend({ - name='charlie-tent-red', - products = { - presets.special.red.infantry:extend({ name='charlie-defense-red'}), - presets.defenses.red.infantry:extend({ name='charlie-garrison-red'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='charlie-comcenter-red', - products = { - presets.defenses.red.sam:extend({ name='charlie-sam-red'}) - } - }) - }, - [2] = - { - presets.upgrades.basic.tent:extend({ - name='charlie-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='charlie-defense-red'}), - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='charlie-comcenter-blue', - products = { - presets.defenses.blue.sam:extend({ name='charlie-sam-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Charlie.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Lentehi.lua ]]----------------- - -zones.lentehi = ZoneCommand:new("Lentehi") -zones.lentehi.initialState = { side=1 } -zones.lentehi:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='lentehi-tent-red', - products = { - presets.special.red.infantry:extend({ name='lentehi-defense-red'}), - presets.defenses.red.infantry:extend({ name='lentehi-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='lentehi-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='lentehi-supply-red'}), - presets.missions.supply.helo:extend({name='lentehi-supply-red-2'}), - presets.missions.supply.transfer:extend({name='lentehi-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='lentehi-ammo-red', - products = { - presets.missions.attack.surface:extend({name='lentehi-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='lentehi-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='lentehi-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='lentehi-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='lentehi-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='lentehi-supply-blue'}), - presets.missions.supply.helo:extend({name='lentehi-supply-blue-2'}), - presets.missions.supply.transfer:extend({name='lentehi-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='lentehi-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='lentehi-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Lentehi.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Refinery.lua ]]----------------- - -zones.refinery = ZoneCommand:new("Refinery") -zones.refinery.initialState = { side=1 } -zones.refinery:defineUpgrades({ - [1] = - { - presets.upgrades.basic.tent:extend({ - name='refinery-tent-red', - products = { - presets.special.red.infantry:extend({ name='refinery-defense-red'}), - presets.defenses.red.infantry:extend({ name='refinery-garrison-red'}) - } - }), - presets.upgrades.supply.refinery1:extend({ - name='refinery-building-red', - products = { - presets.missions.supply.convoy:extend({ name='refinery-supply-red'}), - presets.missions.supply.convoy:extend({ name='refinery-supply-red-1'}), - presets.missions.supply.helo:extend({ name='refinery-supply-red-2'}), - presets.missions.supply.transfer:extend({name='refinery-transfer-red'}) - } - }) - }, - [2] = - { - presets.upgrades.basic.tent:extend({ - name='refinery-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='refinery-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='refinery-garrison-blue'}) - } - }), - presets.upgrades.supply.refinery1:extend({ - name='refinery-building-blue', - products = { - presets.missions.supply.convoy:extend({ name='refinery-supply-blue'}), - presets.missions.supply.convoy:extend({ name='refinery-supply-blue-1'}), - presets.missions.supply.helo:extend({ name='refinery-supply-blue-2'}), - presets.missions.supply.transfer:extend({name='refinery-transfer-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Refinery.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Mozdok.lua ]]----------------- - -zones.mozdok = ZoneCommand:new("Mozdok") -zones.mozdok.initialState = { side=1 } -zones.mozdok.keepActive = true -zones.mozdok.maxResource = 50000 -zones.mozdok:defineUpgrades({ - [1] = - { - presets.upgrades.basic.comPost:extend({ - name='mozdok-compost-red', - products = { - presets.special.red.infantry:extend({ name='mozdok-defense-red'}), - presets.defenses.red.infantry:extend({ name='mozdok-garrison-red'}) - } - }), - presets.upgrades.supply.fuelTank:extend({ - name='mozdok-fuel-red', - products = { - presets.missions.supply.helo:extend({name='mozdok-supply-red-1'}), - presets.missions.supply.helo:extend({name='mozdok-supply-red-2'}), - presets.missions.supply.convoy_escorted:extend({name='mozdok-supply-red-3'}), - presets.missions.supply.transfer:extend({name='mozdok-transfer-red'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='mozdok-comcenter-red', - products = { - presets.defenses.red.sa10:extend({ name='mozdok-airdef-red'}), - presets.missions.patrol.aircraft:extend({name='mozdok-patrol-red', altitude=25000, range=25}), - presets.missions.attack.cas:extend({name='mozdok-cas-red', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.bai:extend({name='mozdok-cas-red', altitude=10000, expend=AI.Task.WeaponExpend.ONE}) - } - }) - }, - [2] = - { - presets.upgrades.basic.comPost:extend({ - name='mozdok-compost-blue', - products = { - presets.special.blue.infantry:extend({ name='mozdok-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='mozdok-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelTank:extend({ - name='mozdok-fuel-blue', - products = { - presets.missions.supply.helo:extend({name='mozdok-supply-blue-1'}), - presets.missions.supply.helo:extend({name='mozdok-supply-blue-2'}), - presets.missions.supply.convoy_escorted:extend({name='mozdok-supply-blue-3'}), - presets.missions.supply.transfer:extend({name='mozdok-transfer-blue'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='mozdok-comcenter-blue', - products = { - presets.defenses.blue.shorad:extend({ name='mozdok-airdef-blue'}), - presets.missions.patrol.aircraft:extend({name='mozdok-patrol-blue', altitude=25000, range=25}), - presets.missions.attack.cas:extend({name='mozdok-cas-blue', altitude=15000, cost=50, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.cas:extend({name='mozdok-cas-blue-1', altitude=15000, cost=50, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.bai:extend({name='mozdok-cas-blue', altitude=15000, cost=50, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.bai:extend({name='mozdok-cas-blue-1', altitude=15000, cost=50, expend=AI.Task.WeaponExpend.ONE}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Mozdok.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Lima.lua ]]----------------- - -zones.lima = ZoneCommand:new("Lima") -zones.lima.initialState = { side=1 } -zones.lima:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='lima-tent-red', - products = { - presets.special.red.infantry:extend({ name='lima-defense-red'}), - presets.defenses.red.infantry:extend({ name='lima-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='lima-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='lima-supply-red'}), - presets.missions.supply.helo:extend({name='lima-supply-red-1'}), - presets.missions.supply.transfer:extend({name='lima-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='lima-ammo-red', - products = { - presets.missions.attack.surface:extend({name='lima-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='lima-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='lima-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='lima-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='lima-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='lima-supply-blue'}), - presets.missions.supply.helo:extend({name='lima-supply-blue-1'}), - presets.missions.supply.transfer:extend({name='lima-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='lima-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='lima-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Lima.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Oscar.lua ]]----------------- - -zones.oscar = ZoneCommand:new("Oscar") -zones.oscar.initialState = { side=1 } -zones.oscar:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='oscar-tent-red', - products = { - presets.special.red.infantry:extend({ name='oscar-defense-red'}), - presets.defenses.red.infantry:extend({ name='oscar-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='oscar-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='oscar-supply-red'}), - presets.missions.supply.transfer:extend({name='oscar-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='oscar-ammo-red', - products = { - presets.missions.attack.surface:extend({name='oscar-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='oscar-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='oscar-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='oscar-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='oscar-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='oscar-supply-blue'}), - presets.missions.supply.transfer:extend({name='oscar-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='oscar-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='oscar-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Oscar.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Nalchik.lua ]]----------------- - -zones.nalchik = ZoneCommand:new("Nalchik") -zones.nalchik.initialState = { side=1 } -zones.nalchik.keepActive = true -zones.nalchik.isHeloSpawn = true -zones.nalchik.isPlaneSpawn = true -zones.nalchik:defineUpgrades({ - [1] = - { - presets.upgrades.basic.comPost:extend({ - name='nalchik-compost-red', - products = { - presets.special.red.infantry:extend({ name='nalchik-defense-red'}), - presets.defenses.red.infantry:extend({ name='nalchik-garrison-red'}) - } - }), - presets.upgrades.supply.fuelTank:extend({ - name='nalchik-fuel-red', - products = { - presets.missions.supply.helo:extend({name='nalchik-supply-red-1'}), - presets.missions.supply.helo:extend({name='nalchik-supply-red-2'}), - presets.missions.supply.transfer:extend({name='nalchik-transfer-red'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='nalchik-comcenter-red', - products = { - presets.defenses.red.sa3:extend({ name='nalchik-airdef-red'}), - presets.missions.attack.sead:extend({name='nalchik-sead-red', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.attack.cas:extend({name='nalchik-cas-red', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.bai:extend({name='nalchik-cas-red', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.strike:extend({name='nalchik-strike-red', altitude=30000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.patrol.aircraft:extend({name='nalchik-patrol-red', altitude=25000, range=25}), - presets.missions.patrol.aircraft:extend({name='nalchik-patrol-red-2', altitude=25000, range=25}), - presets.missions.support.awacs:extend({name='nalchik-awacs-red', altitude=30000, freq=251.2}), - presets.missions.support.tanker:extend({name='nalchik-tanker-red', altitude=30000, freq=252.2, tacan='40', variant='Drogue'}) - } - }) - }, - [2] = - { - presets.upgrades.basic.comPost:extend({ - name='nalchik-compost-blue', - products = { - presets.special.blue.infantry:extend({ name='nalchik-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='nalchik-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelTank:extend({ - name='nalchik-fuel-blue', - products = { - presets.missions.supply.helo:extend({name='nalchik-supply-blue-1'}), - presets.missions.supply.helo:extend({name='nalchik-supply-blue-2'}), - presets.missions.supply.transfer:extend({name='nalchik-transfer-blue'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='nalchik-comcenter-blue', - products = { - presets.defenses.blue.nasams:extend({ name='nalchik-airdef-blue'}), - presets.missions.support.awacs:extend({name='nalchik-awacs-blue', altitude=30000, freq=259.5}), - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Nalchik.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Digora.lua ]]----------------- - -zones.digora = ZoneCommand:new("Digora") -zones.digora.initialState = { side=1 } -zones.digora:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='digora-tent-red', - products = { - presets.special.red.infantry:extend({ name='digora-defense-red'}), - presets.defenses.red.infantry:extend({ name='digora-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='digora-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='digora-supply-red'}), - presets.missions.supply.transfer:extend({name='digora-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='digora-ammo-red', - products = { - presets.missions.attack.surface:extend({name='digora-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='digora-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='digora-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='digora-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='digora-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='digora-supply-blue'}), - presets.missions.supply.transfer:extend({name='digora-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='digora-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='digora-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Digora.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Uniform.lua ]]----------------- - -zones.uniform = ZoneCommand:new("Uniform") -zones.uniform.initialState = { side=1 } -zones.uniform:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='uniform-tent-red', - products = { - presets.special.red.infantry:extend({ name='uniform-defense-red'}), - presets.defenses.red.infantry:extend({ name='uniform-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='uniform-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='uniform-supply-red'}), - presets.missions.supply.transfer:extend({name='uniform-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='uniform-ammo-red', - products = { - presets.missions.attack.surface:extend({name='uniform-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='uniform-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='uniform-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='uniform-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='uniform-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='uniform-supply-blue'}), - presets.missions.supply.transfer:extend({name='uniform-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='uniform-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='uniform-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Uniform.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Factory.lua ]]----------------- - -zones.factory = ZoneCommand:new("Factory") -zones.factory.initialState = { side=2 } -zones.factory:defineUpgrades({ - [1] = - { - presets.upgrades.basic.tent:extend({ - name='factory-tent-red', - products = { - presets.special.red.infantry:extend({ name='factory-defense-red'}), - presets.defenses.red.infantry:extend({ name='factory-garrison-red'}) - } - }), - presets.upgrades.supply.factory1:extend({ - name='factory-prod-red-1', - products = { - presets.missions.supply.convoy:extend({ name='factory-supply-red-1'}), - presets.missions.supply.transfer:extend({name='factory-transfer-red'}) - } - }), - presets.upgrades.supply.factory2:extend({ - name='factory-prod-red-2', - products = { - presets.missions.supply.convoy:extend({ name='factory-supply-red-2', cost=2000}), - presets.missions.supply.transfer:extend({name='factory-transfer-red2'}) - } - }), - presets.upgrades.supply.factoryTank:extend({ - name='factory-tank-red-1', - products = { - } - }), - presets.upgrades.supply.factoryTank:extend({ - name='factory-tank-red-2', - products = { - } - }), - presets.upgrades.supply.factoryTank:extend({ - name='factory-tank-red-3', - products = { - } - }) - }, - [2] = - { - presets.upgrades.basic.tent:extend({ - name='factory-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='factory-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='factory-garrison-blue'}) - } - }), - presets.upgrades.supply.factory1:extend({ - name='factory-prod-blue-1', - products = { - presets.missions.supply.convoy:extend({ name='factory-supply-blue-1'}), - presets.missions.supply.transfer:extend({name='factory-transfer-blue'}) - } - }), - presets.upgrades.supply.factory2:extend({ - name='factory-prod-blue-2', - products = { - presets.missions.supply.convoy:extend({ name='factory-supply-blue-2', cost=2000}), - presets.missions.supply.transfer:extend({name='factory-transfer-blue2'}) - } - }), - presets.upgrades.supply.factoryTank:extend({ - name='factory-tank-blue-1', - products = { - } - }), - presets.upgrades.supply.factoryTank:extend({ - name='factory-tank-blue-2', - products = { - } - }), - presets.upgrades.supply.factoryTank:extend({ - name='factory-tank-blue-3', - products = { - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Factory.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Senaki.lua ]]----------------- - -zones.senaki = ZoneCommand:new("Senaki") -zones.senaki.initialState = { side=1 } -zones.senaki.keepActive = true -zones.senaki.isHeloSpawn = true -zones.senaki.isPlaneSpawn = true -zones.senaki.maxResource = 50000 -zones.senaki:defineUpgrades({ - [1] = - { - presets.upgrades.basic.comPost:extend({ - name='senaki-compost-red', - products = { - presets.special.red.infantry:extend({ name='senaki-defense-red'}), - presets.defenses.red.infantry:extend({ name='senaki-garrison-red'}) - } - }), - presets.upgrades.supply.fuelTank:extend({ - name='senaki-fuel-red', - products = { - presets.missions.supply.helo:extend({name='senaki-supply-red-1'}), - presets.missions.supply.helo:extend({name='senaki-supply-red-2'}), - presets.missions.supply.transfer:extend({name='senaki-transfer-red'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='senaki-comcenter-red', - products = { - presets.defenses.red.sa3:extend({ name='senaki-airdef-red'}), - presets.missions.attack.sead:extend({name='senaki-sead-red', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.attack.cas:extend({name='senaki-cas-red', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.bai:extend({name='senaki-cas-red', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.strike:extend({name='senaki-strike-red', altitude=20000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.patrol.aircraft:extend({name='senaki-patrol-red', altitude=25000, range=25}), - presets.missions.patrol.aircraft:extend({name='senaki-patrol-red-2', altitude=20000, range=25}) - } - }) - }, - [2] = - { - presets.upgrades.basic.comPost:extend({ - name='senaki-compost-blue', - products = { - presets.special.blue.infantry:extend({ name='senaki-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='senaki-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelTank:extend({ - name='senaki-fuel-blue', - products = { - presets.missions.supply.helo:extend({name='senaki-supply-blue-1'}), - presets.missions.supply.helo:extend({name='senaki-supply-blue-2'}), - presets.missions.supply.transfer:extend({name='senaki-transfer-blue'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='senaki-comcenter-blue', - products = { - presets.defenses.blue.shorad:extend({ name='senaki-airdef-blue'}), - presets.missions.attack.sead:extend({name='senaki-sead-blue', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.attack.cas:extend({name='senaki-cas-blue', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.bai:extend({name='senaki-cas-blue', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.strike:extend({name='senaki-strike-blue', altitude=20000, expend=AI.Task.WeaponExpend.TWO}), - presets.missions.patrol.aircraft:extend({name='senaki-patrol-blue', altitude=25000, range=25}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Senaki.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Kutaisi.lua ]]----------------- - -zones.kutaisi = ZoneCommand:new("Kutaisi") -zones.kutaisi.initialState = { side=1 } -zones.kutaisi.keepActive = true -zones.kutaisi.maxResource = 50000 -zones.kutaisi:defineUpgrades({ - [1] = - { - presets.upgrades.basic.comPost:extend({ - name='kutaisi-compost-red', - products = { - presets.special.red.infantry:extend({ name='kutaisi-defense-red'}), - presets.defenses.red.infantry:extend({ name='kutaisi-garrison-red'}) - } - }), - presets.upgrades.supply.fuelTank:extend({ - name='kutaisi-fuel-red', - products = { - presets.missions.supply.helo:extend({name='kutaisi-supply-red-1'}), - presets.missions.supply.helo:extend({name='kutaisi-supply-red-2'}), - presets.missions.supply.transfer:extend({name='kutaisi-transfer-red'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='kutaisi-comcenter-red', - products = { - presets.defenses.red.shorad:extend({ name='kutaisi-airdef-red'}), - presets.missions.attack.sead:extend({name='kutaisi-sead-red', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.attack.cas:extend({name='kutaisi-cas-red', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.bai:extend({name='kutaisi-cas-red', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.strike:extend({name='kutaisi-strike-red', altitude=20000, expend=AI.Task.WeaponExpend.HALF}), - presets.missions.patrol.aircraft:extend({name='kutaisi-patrol-red', altitude=25000, range=25}), - presets.missions.patrol.aircraft:extend({name='kutaisi-patrol-red-2', altitude=25000, range=25}) - } - }) - }, - [2] = - { - presets.upgrades.basic.comPost:extend({ - name='kutaisi-compost-blue', - products = { - presets.special.blue.infantry:extend({ name='kutaisi-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='kutaisi-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelTank:extend({ - name='kutaisi-fuel-blue', - products = { - presets.missions.supply.helo:extend({name='kutaisi-supply-blue-1'}), - presets.missions.supply.helo:extend({name='kutaisi-supply-blue-2'}), - presets.missions.supply.transfer:extend({name='kutaisi-transfer-blue'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='kutaisi-comcenter-blue', - products = { - presets.defenses.blue.shorad:extend({ name='kutaisi-airdef-blue'}), - presets.missions.attack.sead:extend({name='kutaisi-sead-blue', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.attack.cas:extend({name='kutaisi-cas-blue', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.bai:extend({name='kutaisi-cas-blue', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.strike:extend({name='kutaisi-strike-blue', altitude=20000, expend=AI.Task.WeaponExpend.TWO}), - presets.missions.patrol.aircraft:extend({name='kutaisi-patrol-blue', altitude=25000, range=25}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Kutaisi.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Prohladniy.lua ]]----------------- - -zones.prohladniy = ZoneCommand:new("Prohladniy") -zones.prohladniy.initialState = { side=1 } -zones.prohladniy:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='prohladniy-tent-red', - products = { - presets.special.red.infantry:extend({ name='prohladniy-defense-red'}), - presets.defenses.red.infantry:extend({ name='prohladniy-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='prohladniy-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='prohladniy-supply-red'}), - presets.missions.supply.transfer:extend({name='prohladniy-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='prohladniy-ammo-red', - products = { - presets.missions.attack.surface:extend({name='prohladniy-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='prohladniy-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='prohladniy-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='prohladniy-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='prohladniy-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='prohladniy-supply-blue'}), - presets.missions.supply.transfer:extend({name='prohladniy-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='prohladniy-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='prohladniy-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Prohladniy.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Tallyk.lua ]]----------------- - -zones.tallyk = ZoneCommand:new("Tallyk") -zones.tallyk.initialState = { side=1 } -zones.tallyk.keepActive = true -zones.tallyk:defineUpgrades({ - [1] = - { - presets.upgrades.basic.tent:extend({ - name='tallyk-tent-red', - products = { - presets.special.red.infantry:extend({ name='tallyk-defense-red'}), - presets.defenses.red.infantry:extend({ name='tallyk-garrison-red'}), - presets.missions.attack.surface:extend({name='tallyk-assault-red'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='tallyk-comcenter-red', - products = { - presets.defenses.red.sam:extend({ name='tallyk-sam-red'}) - } - }) - }, - [2] = - { - presets.upgrades.basic.tent:extend({ - name='tallyk-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='tallyk-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='tallyk-garrison-blue'}), - presets.missions.attack.surface:extend({name='tallyk-assault-blue'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='tallyk-comcenter-blue', - products = { - presets.defenses.blue.sam:extend({ name='tallyk-sam-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Tallyk.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Terek.lua ]]----------------- - -zones.terek = ZoneCommand:new("Terek") -zones.terek.initialState = { side=1 } -zones.terek:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='terek-tent-red', - products = { - presets.special.red.infantry:extend({ name='terek-defense-red'}), - presets.defenses.red.infantry:extend({ name='terek-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='terek-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='terek-supply-red'}), - presets.missions.supply.transfer:extend({name='terek-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='terek-ammo-red', - products = { - presets.missions.attack.surface:extend({name='terek-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='terek-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='terek-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='terek-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='terek-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='terek-supply-blue'}), - presets.missions.supply.transfer:extend({name='terek-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='terek-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='terek-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Terek.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Humara.lua ]]----------------- - -zones.humara = ZoneCommand:new("Humara") -zones.humara.initialState = { side=1 } -zones.humara:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='humara-tent-red', - products = { - presets.special.red.infantry:extend({ name='humara-defense-red'}), - presets.defenses.red.infantry:extend({ name='humara-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='humara-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='humara-supply-red'}), - presets.missions.supply.transfer:extend({name='humara-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='humara-ammo-red', - products = { - presets.missions.attack.surface:extend({name='humara-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='humara-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='humara-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='humara-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='humara-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='humara-supply-blue'}), - presets.missions.supply.transfer:extend({name='humara-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='humara-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='humara-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Humara.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Ochamchira.lua ]]----------------- - -zones.ochamchira = ZoneCommand:new("Ochamchira") -zones.ochamchira.initialState = { side=1 } -zones.ochamchira:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='ochamchira-tent-red', - products = { - presets.special.red.infantry:extend({ name='ochamchira-defense-red'}), - presets.defenses.red.infantry:extend({ name='ochamchira-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='ochamchira-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='ochamchira-supply-red'}), - presets.missions.supply.transfer:extend({name='ochamchira-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='ochamchira-ammo-red', - products = { - presets.missions.attack.surface:extend({name='ochamchira-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='ochamchira-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='ochamchira-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='ochamchira-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='ochamchira-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='ochamchira-supply-blue'}), - presets.missions.supply.transfer:extend({name='ochamchira-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='ochamchira-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='ochamchira-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Ochamchira.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/November.lua ]]----------------- - -zones.november = ZoneCommand:new("November") -zones.november.initialState = { side=1 } -zones.november.isHeloSpawn = true -zones.november:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='november-tent-red', - products = { - presets.special.red.infantry:extend({ name='november-defense-red'}), - presets.defenses.red.infantry:extend({ name='november-garrison-red'}) - } - }), - presets.upgrades.supply.fuelTankFarp:extend({ - name='november-fuel-red', - products = { - presets.missions.supply.helo:extend({name='november-supply-red'}), - presets.missions.supply.helo:extend({name='november-supply-red-1'}), - presets.missions.supply.transfer:extend({name='november-transfer-red'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='november-comcenter-red', - products = { - presets.defenses.red.shorad:extend({name='november-sam-red'}), - presets.missions.attack.helo:extend({name='november-cas-red', altitude=200, expend=AI.Task.WeaponExpend.HALF }) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='november-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='november-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='november-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelTankFarp:extend({ - name='november-fuel-blue', - products = { - presets.missions.supply.helo:extend({name='november-supply-blue'}), - presets.missions.supply.helo:extend({name='november-supply-blue-1'}), - presets.missions.supply.transfer:extend({name='november-transfer-blue'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='november-comcenter-blue', - products = { - presets.defenses.blue.shorad:extend({name='november-sam-blue'}), - presets.missions.attack.helo:extend({name='november-cas-blue', altitude=200, expend=AI.Task.WeaponExpend.HALF }) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/November.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/XRay.lua ]]----------------- - -zones.xray = ZoneCommand:new("XRay") -zones.xray.initialState = { side=1 } -zones.xray:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='xray-tent-red', - products = { - presets.special.red.infantry:extend({ name='xray-defense-red'}), - presets.defenses.red.infantry:extend({ name='xray-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='xray-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='xray-supply-red'}), - presets.missions.supply.helo:extend({name='xray-supply-red-2'}), - presets.missions.supply.transfer:extend({name='xray-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='xray-ammo-red', - products = { - presets.missions.attack.surface:extend({name='xray-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='xray-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='xray-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='xray-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='xray-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='xray-supply-blue'}), - presets.missions.supply.helo:extend({name='xray-supply-blue-2'}), - presets.missions.supply.transfer:extend({name='xray-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='xray-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='xray-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/XRay.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Whiskey.lua ]]----------------- - -zones.whiskey = ZoneCommand:new("Whiskey") -zones.whiskey.initialState = { side=1 } -zones.whiskey:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='whiskey-tent-red', - products = { - presets.special.red.infantry:extend({ name='whiskey-defense-red'}), - presets.defenses.red.infantry:extend({ name='whiskey-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='whiskey-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='whiskey-supply-red'}), - presets.missions.supply.transfer:extend({name='whiskey-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='whiskey-ammo-red', - products = { - presets.missions.attack.surface:extend({name='whiskey-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='whiskey-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='whiskey-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='whiskey-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='whiskey-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='whiskey-supply-blue'}), - presets.missions.supply.transfer:extend({name='whiskey-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='whiskey-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='whiskey-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Whiskey.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Mine.lua ]]----------------- - -zones.mine = ZoneCommand:new("Mine") -zones.mine.initialState = { side=1 } -zones.mine:defineUpgrades({ - [1] = - { - presets.upgrades.basic.tent:extend({ - name='mine-tent-red', - products = { - presets.special.red.infantry:extend({ name='mine-defense-red'}), - presets.defenses.red.infantry:extend({ name='mine-garrison-red'}) - } - }), - presets.upgrades.supply.excavator:extend({ - name='mine-excavator-red-1', - products = { - presets.missions.supply.convoy:extend({ name='mine-supply-red'}), - presets.missions.supply.transfer:extend({name='mine-transfer-red'}) - } - }), - presets.upgrades.supply.excavator:extend({ - name='mine-excavator-red-2', - products = { - presets.missions.supply.convoy:extend({ name='mine-supply-red'}), - presets.missions.supply.transfer:extend({name='mine-transfer-red'}) - } - }), - presets.upgrades.supply.excavator:extend({ - name='mine-excavator-red-3', - products = { - presets.missions.supply.convoy:extend({ name='mine-supply-red'}), - presets.missions.supply.transfer:extend({name='mine-transfer-red'}) - } - }) - }, - [2] = - { - presets.upgrades.basic.tent:extend({ - name='mine-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='mine-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='mine-garrison-blue'}) - } - }), - presets.upgrades.supply.excavator:extend({ - name='mine-excavator-blue-1', - products = { - presets.missions.supply.convoy:extend({ name='mine-supply-blue'}), - presets.missions.supply.transfer:extend({name='mine-transfer-blue'}) - } - }), - presets.upgrades.supply.excavator:extend({ - name='mine-excavator-blue-2', - products = { - presets.missions.supply.convoy:extend({ name='mine-supply-blue'}), - presets.missions.supply.transfer:extend({name='mine-transfer-blue'}) - } - }), - presets.upgrades.supply.excavator:extend({ - name='mine-excavator-blue-3', - products = { - presets.missions.supply.convoy:extend({ name='mine-supply-blue'}), - presets.missions.supply.transfer:extend({name='mine-transfer-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Mine.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Papa.lua ]]----------------- - -zones.papa = ZoneCommand:new("Papa") -zones.papa.initialState = { side=1 } -zones.papa.keepActive = true -zones.papa:defineUpgrades({ - [1] = - { - presets.upgrades.basic.tent:extend({ - name='papa-tent-red', - products = { - presets.special.red.infantry:extend({ name='papa-defense-red'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='papa-comcenter-red', - products = { - presets.defenses.red.sam:extend({ name='papa-sam-red'}) - } - }) - }, - [2] = - { - presets.upgrades.basic.tent:extend({ - name='papa-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='papa-defense-blue'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='papa-comcenter-blue', - products = { - presets.defenses.blue.sam:extend({ name='papa-sam-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Papa.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Sukhumi.lua ]]----------------- - -zones.sukhumi = ZoneCommand:new("Sukhumi") -zones.sukhumi.initialState = { side=1 } -zones.sukhumi.keepActive = true -zones.sukhumi.maxResource = 50000 -zones.sukhumi:defineUpgrades({ - [1] = - { - presets.upgrades.basic.comPost:extend({ - name='sukhumi-compost-red', - products = { - presets.special.red.infantry:extend({ name='sukhumi-defense-red'}), - presets.defenses.red.infantry:extend({ name='sukhumi-garrison-red'}) - } - }), - presets.upgrades.supply.fuelTank:extend({ - name='sukhumi-fuel-red', - products = { - presets.missions.supply.helo:extend({name='sukhumi-supply-red-1'}), - presets.missions.supply.helo:extend({name='sukhumi-supply-red-2'}), - presets.missions.supply.transfer:extend({name='sukhumi-transfer-red'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='sukhumi-comcenter-red', - products = { - presets.defenses.red.sa11:extend({ name='sukhumi-airdef-red'}), - presets.missions.attack.sead:extend({name='sukhumi-sead-red', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.attack.cas:extend({name='sukhumi-cas-red', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.bai:extend({name='sukhumi-cas-red', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.strike:extend({name='sukhumi-strike-red', altitude=20000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.patrol.aircraft:extend({name='sukhumi-patrol-red', altitude=25000, range=25}), - presets.missions.patrol.aircraft:extend({name='sukhumi-patrol-red-2', altitude=25000, range=25}) - } - }) - }, - [2] = - { - presets.upgrades.basic.comPost:extend({ - name='sukhumi-compost-blue', - products = { - presets.special.blue.infantry:extend({ name='sukhumi-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='sukhumi-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelTank:extend({ - name='sukhumi-fuel-blue', - products = { - presets.missions.supply.helo:extend({name='sukhumi-supply-blue-1'}), - presets.missions.supply.helo:extend({name='sukhumi-supply-blue-2'}), - presets.missions.supply.transfer:extend({name='sukhumi-transfer-blue'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='sukhumi-comcenter-blue', - products = { - presets.defenses.blue.nasams:extend({ name='sukhumi-airdef-blue'}), - presets.missions.attack.sead:extend({name='sukhumi-sead-blue', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.attack.cas:extend({name='sukhumi-cas-blue', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.bai:extend({name='sukhumi-cas-blue', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.strike:extend({name='sukhumi-strike-blue', altitude=20000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.patrol.aircraft:extend({name='sukhumi-patrol-blue', altitude=25000, range=25}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Sukhumi.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Farm.lua ]]----------------- - -zones.farm = ZoneCommand:new("Farm") -zones.farm.initialState = { side=1 } -zones.farm:defineUpgrades({ - [1] = - { - presets.upgrades.basic.tent:extend({ - name='farm-tent-red', - products = { - presets.special.red.infantry:extend({ name='farm-defense-red'}), - presets.defenses.red.infantry:extend({ name='farm-garrison-red'}) - } - }), - presets.upgrades.supply.farm1:extend({ - name='farm-prod-red-1', - products = { - presets.missions.supply.convoy:extend({ name='farm-supply-red'}), - presets.missions.supply.transfer:extend({name='farm-transfer-red'}) - } - }), - presets.upgrades.supply.farm2:extend({ - name='farm-prod-red-2', - products = { - presets.missions.supply.convoy:extend({ name='farm-supply-red'}), - presets.missions.supply.transfer:extend({name='farm-transfer-red'}) - } - }) - }, - [2] = - { - presets.upgrades.basic.tent:extend({ - name='farm-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='farm-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='farm-garrison-blue'}) - } - }), - presets.upgrades.supply.farm1:extend({ - name='farm-prod-blue-1', - products = { - presets.missions.supply.convoy:extend({ name='farm-supply-blue'}), - presets.missions.supply.transfer:extend({name='farm-transfer-blue'}) - } - }), - presets.upgrades.supply.farm2:extend({ - name='farm-prod-blue-2', - products = { - presets.missions.supply.convoy:extend({ name='farm-supply-blue'}), - presets.missions.supply.transfer:extend({name='farm-transfer-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Farm.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Romeo.lua ]]----------------- - -zones.romeo = ZoneCommand:new("Romeo") -zones.romeo.initialState = { side=1 } -zones.romeo:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='romeo-tent-red', - products = { - presets.special.red.infantry:extend({ name='romeo-defense-red'}), - presets.defenses.red.infantry:extend({ name='romeo-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='romeo-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='romeo-supply-red'}), - presets.missions.supply.transfer:extend({name='romeo-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='romeo-ammo-red', - products = { - presets.missions.attack.surface:extend({name='romeo-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='romeo-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='romeo-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='romeo-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='romeo-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='romeo-supply-blue'}), - presets.missions.supply.transfer:extend({name='romeo-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='romeo-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='romeo-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Romeo.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Zulu.lua ]]----------------- - -zones.zulu = ZoneCommand:new("Zulu") -zones.zulu.initialState = { side=1 } -zones.zulu:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='zulu-tent-red', - products = { - presets.special.red.infantry:extend({ name='zulu-defense-red'}), - presets.defenses.red.infantry:extend({ name='zulu-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='zulu-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='zulu-supply-red'}), - presets.missions.supply.transfer:extend({name='zulu-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='zulu-ammo-red', - products = { - presets.missions.attack.surface:extend({name='zulu-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='zulu-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='zulu-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='zulu-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='zulu-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='zulu-supply-blue'}), - presets.missions.supply.transfer:extend({name='zulu-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='zulu-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='zulu-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Zulu.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Yankee.lua ]]----------------- - -zones.yankee = ZoneCommand:new("Yankee") -zones.yankee.initialState = { side=1 } -zones.yankee:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='yankee-tent-red', - products = { - presets.special.red.infantry:extend({ name='yankee-defense-red'}), - presets.defenses.red.infantry:extend({ name='yankee-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='yankee-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='yankee-supply-red'}), - presets.missions.supply.transfer:extend({name='yankee-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='yankee-ammo-red', - products = { - presets.missions.attack.surface:extend({name='yankee-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='yankee-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='yankee-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='yankee-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='yankee-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='yankee-supply-blue'}), - presets.missions.supply.transfer:extend({name='yankee-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='yankee-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='yankee-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Yankee.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Malgobek.lua ]]----------------- - -zones.malgobek = ZoneCommand:new("Malgobek") -zones.malgobek.initialState = { side=1 } -zones.malgobek:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='malgobek-tent-red', - products = { - presets.special.red.infantry:extend({ name='malgobek-defense-red'}), - presets.defenses.red.infantry:extend({ name='malgobek-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='malgobek-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='malgobek-supply-red'}), - presets.missions.supply.transfer:extend({name='malgobek-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='malgobek-ammo-red', - products = { - presets.missions.attack.surface:extend({name='malgobek-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='malgobek-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='malgobek-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='malgobek-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='malgobek-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='malgobek-supply-blue'}), - presets.missions.supply.transfer:extend({name='malgobek-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='malgobek-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='malgobek-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Malgobek.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Kilo.lua ]]----------------- - -zones.kilo = ZoneCommand:new("Kilo") -zones.kilo.initialState = { side=1 } -zones.kilo:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='kilo-tent-red', - products = { - presets.special.red.infantry:extend({ name='kilo-defense-red'}), - presets.defenses.red.infantry:extend({ name='kilo-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='kilo-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='kilo-supply-red'}), - presets.missions.supply.transfer:extend({name='kilo-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='kilo-ammo-red', - products = { - presets.missions.attack.surface:extend({name='kilo-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='kilo-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='kilo-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='kilo-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='kilo-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='kilo-supply-blue'}), - presets.missions.supply.transfer:extend({name='kilo-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='kilo-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='kilo-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Kilo.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Quebec.lua ]]----------------- - -zones.quebec = ZoneCommand:new("Quebec") -zones.quebec.initialState = { side=1 } -zones.quebec.keepActive = true -zones.quebec:defineUpgrades({ - [1] = - { - presets.upgrades.basic.tent:extend({ - name='quebec-tent-red', - products = { - presets.special.red.infantry:extend({ name='quebec-defense-red'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='quebec-comcenter-red', - products = { - presets.defenses.red.sam:extend({ name='quebec-sam-red'}) - } - }) - }, - [2] = - { - presets.upgrades.basic.tent:extend({ - name='quebec-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='quebec-defense-blue'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='quebec-comcenter-blue', - products = { - presets.defenses.blue.sam:extend({ name='quebec-sam-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Quebec.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/OilFields.lua ]]----------------- - -zones.oilfields = ZoneCommand:new("Oil Fields") -zones.oilfields.initialState = { side=1 } -zones.oilfields:defineUpgrades({ - [1] = { - presets.upgrades.basic.outpost:extend({ - name='oilfields-outpost-red', - products = { - presets.special.red.infantry:extend({ name='oilfields-defense-red'}), - presets.defenses.red.infantry:extend({ name='oilfields-garrison-red'}) - } - }), - presets.upgrades.supply.oilPump:extend({ - name='oilfields-pump-red-1', - products = { - presets.missions.supply.transfer:extend({name='oilfields-transfer-red1'}) - } - }), - presets.upgrades.supply.oilPump:extend({ - name='oilfields-pump-red-2', - products = { - presets.missions.supply.convoy:extend({name='oilfields-supply-red-1'}) - } - }), - presets.upgrades.supply.oilPump:extend({ - name='oilfields-pump-red-3', - products = { - presets.missions.supply.transfer:extend({name='oilfields-transfer-red2'}) - } - }), - presets.upgrades.supply.oilPump:extend({ - name='oilfields-pump-red-4', - products = { - presets.missions.supply.convoy:extend({name='oilfields-supply-red-2'}) - } - }) - }, - [2] = { - presets.upgrades.basic.outpost:extend({ - name='oilfields-outpost-blue', - products = { - presets.special.blue.infantry:extend({ name='oilfields-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='oilfields-garrison-blue'}) - } - }), - presets.upgrades.supply.oilPump:extend({ - name='oilfields-pump-blue-1', - products = { - presets.missions.supply.transfer:extend({name='oilfields-transfer-blue1'}) - } - }), - presets.upgrades.supply.oilPump:extend({ - name='oilfields-pump-blue-2', - products = { - presets.missions.supply.convoy:extend({name='oilfields-supply-blue-1'}) - } - }), - presets.upgrades.supply.oilPump:extend({ - name='oilfields-pump-blue-3', - products = { - presets.missions.supply.transfer:extend({name='oilfields-transfer-blue2'}) - } - }), - presets.upgrades.supply.oilPump:extend({ - name='oilfields-pump-blue-4', - products = { - presets.missions.supply.convoy:extend({name='oilfields-supply-blue-2'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/OilFields.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Echo.lua ]]----------------- - -zones.echo = ZoneCommand:new("Echo") -zones.echo.initialState = { side=2 } -zones.echo:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='echo-tent-red', - products = { - presets.special.red.infantry:extend({ name='echo-defense-red'}), - presets.defenses.red.infantry:extend({ name='echo-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='echo-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='echo-supply-red'}), - presets.missions.supply.transfer:extend({name='echo-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='echo-ammo-red', - products = { - presets.missions.attack.surface:extend({name='echo-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='echo-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='echo-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='echo-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='echo-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='echo-supply-blue'}), - presets.missions.supply.transfer:extend({name='echo-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='echo-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='echo-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Echo.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Kobuleti.lua ]]----------------- - -zones.kobuleti = ZoneCommand:new("Kobuleti") -zones.kobuleti.initialState = { side=2 } -zones.kobuleti.keepActive = true -zones.kobuleti.isHeloSpawn = true -zones.kobuleti.isPlaneSpawn = true -zones.kobuleti.maxResource = 50000 -zones.kobuleti:defineUpgrades({ - [1] = - { - presets.upgrades.basic.comPost:extend({ - name='kobuleti-compost-red', - products = { - presets.special.red.infantry:extend({ name='kobuleti-defense-red'}), - presets.defenses.red.infantry:extend({ name='kobuleti-garrison-red'}), - presets.missions.attack.surface:extend({ name='kobuleti-assault-red'}) - } - }), - presets.upgrades.supply.fuelTank:extend({ - name='kobuleti-fuel-red', - products = { - presets.missions.supply.helo:extend({name='kobuleti-supply-red-1'}), - presets.missions.supply.helo:extend({name='kobuleti-supply-red-2'}), - presets.missions.supply.transfer:extend({name='kobuleti-transfer-red'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='kobuleti-comcenter-red', - products = { - presets.defenses.red.shorad:extend({ name='kobuleti-airdef-red'}), - presets.missions.attack.sead:extend({name='kobuleti-sead-red', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.attack.cas:extend({name='kobuleti-cas-red', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.bai:extend({name='kobuleti-cas-red', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.strike:extend({name='kobuleti-strike-red', altitude=20000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.patrol.aircraft:extend({name='kobuleti-patrol-red', altitude=25000, range=25}) - } - }) - }, - [2] = - { - presets.upgrades.basic.comPost:extend({ - name='kobuleti-compost-blue', - products = { - presets.special.blue.infantry:extend({ name='kobuleti-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='kobuleti-garrison-blue'}), - presets.missions.attack.surface:extend({ name='kobuleti-assault-blue'}) - } - }), - presets.upgrades.supply.fuelTank:extend({ - name='kobuleti-fuel-blue', - products = { - presets.missions.supply.helo:extend({name='kobuleti-supply-blue-1'}), - presets.missions.supply.helo:extend({name='kobuleti-supply-blue-2'}), - presets.missions.supply.transfer:extend({name='kobuleti-transfer-blue'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='kobuleti-comcenter-blue', - products = { - presets.defenses.blue.shorad:extend({ name='kobuleti-airdef-blue'}), - presets.missions.attack.sead:extend({name='kobuleti-sead-blue', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.attack.cas:extend({name='kobuleti-cas-blue', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.bai:extend({name='kobuleti-cas-blue', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.strike:extend({name='kobuleti-strike-blue', altitude=20000, expend=AI.Task.WeaponExpend.TWO}), - presets.missions.patrol.aircraft:extend({name='kobuleti-patrol-blue', altitude=25000, range=25}), - presets.missions.support.awacs:extend({name='kobuleti-awacs-blue', altitude=30000, freq=258.5}), - presets.missions.support.tanker:extend({name='kobuleti-tanker-blue', altitude=23000, freq=258, tacan='38', variant='Boom'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Kobuleti.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Alpha.lua ]]----------------- - -zones.alpha = ZoneCommand:new('Alpha') -zones.alpha.initialState = { side=2 } -zones.alpha:defineUpgrades({ - [1] = --red side - { - presets.upgrades.basic.tent:extend({ - name = 'alpha-tent-red', - products = { - presets.special.red.infantry:extend({ name='alpha-defense-red'}), - presets.defenses.red.infantry:extend({ name='alpha-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name = 'alpha-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({ name='alpha-supply-red'}), - presets.missions.supply.transfer:extend({name='alpha-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name = 'alpha-ammo-red', - products = { - presets.missions.attack.surface:extend({ name='alpha-assault-red'}) - } - }) - }, - [2] = --blue side - { - presets.upgrades.basic.tent:extend({ - name = 'alpha-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='alpha-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='alpha-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name = 'alpha-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({ name='alpha-supply-blue'}), - presets.missions.supply.transfer:extend({name='alpha-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name = 'alpha-ammo-blue', - products = { - presets.missions.attack.surface:extend({ name='alpha-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Alpha.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Foxtrot.lua ]]----------------- - -zones.foxtrot = ZoneCommand:new("Foxtrot") -zones.foxtrot.initialState = { side=2 } -zones.foxtrot:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='foxtrot-tent-red', - products = { - presets.special.red.infantry:extend({ name='foxtrot-defense-red'}), - presets.defenses.red.infantry:extend({ name='foxtrot-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='foxtrot-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='foxtrot-supply-red'}), - presets.missions.supply.transfer:extend({name='foxtrot-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='foxtrot-ammo-red', - products = { - presets.missions.attack.surface:extend({name='foxtrot-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='foxtrot-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='foxtrot-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='foxtrot-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='foxtrot-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='foxtrot-supply-blue'}), - presets.missions.supply.transfer:extend({name='foxtrot-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='foxtrot-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='foxtrot-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Foxtrot.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Sierra.lua ]]----------------- - -zones.sierra = ZoneCommand:new("Sierra") -zones.sierra.initialState = { side=1 } -zones.sierra.isHeloSpawn = true -zones.sierra:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='sierra-tent-red', - products = { - presets.special.red.infantry:extend({ name='sierra-defense-red'}), - presets.defenses.red.infantry:extend({ name='sierra-garrison-red'}) - } - }), - presets.upgrades.supply.fuelTankFarp:extend({ - name='sierra-fuel-red', - products = { - presets.missions.supply.helo:extend({name='sierra-supply-red'}), - presets.missions.supply.helo:extend({name='sierra-supply-red-1'}), - presets.missions.supply.transfer:extend({name='sierra-transfer-red'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='sierra-comcenter-red', - products = { - presets.defenses.red.shorad:extend({name='sierra-sam-red'}), - presets.missions.attack.helo:extend({name='sierra-cas-red', altitude=200, expend=AI.Task.WeaponExpend.HALF }) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='sierra-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='sierra-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='sierra-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelTankFarp:extend({ - name='sierra-fuel-blue', - products = { - presets.missions.supply.helo:extend({name='sierra-supply-blue'}), - presets.missions.supply.helo:extend({name='sierra-supply-blue-1'}), - presets.missions.supply.transfer:extend({name='sierra-transfer-blue'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='sierra-comcenter-blue', - products = { - presets.defenses.blue.shorad:extend({name='sierra-sam-blue'}), - presets.missions.attack.helo:extend({name='sierra-cas-blue', altitude=200, expend=AI.Task.WeaponExpend.HALF }) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Sierra.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Oni.lua ]]----------------- - -zones.oni = ZoneCommand:new("Oni") -zones.oni.initialState = { side=1 } -zones.oni:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='oni-tent-red', - products = { - presets.special.red.infantry:extend({ name='oni-defense-red'}), - presets.defenses.red.infantry:extend({ name='oni-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='oni-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='oni-supply-red'}), - presets.missions.supply.helo:extend({name='oni-supply-red-2'}), - presets.missions.supply.transfer:extend({name='oni-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='oni-ammo-red', - products = { - presets.missions.attack.surface:extend({name='oni-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='oni-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='oni-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='oni-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='oni-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='oni-supply-blue'}), - presets.missions.supply.helo:extend({name='oni-supply-blue-2'}), - presets.missions.supply.transfer:extend({name='oni-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='oni-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='oni-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Oni.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Hotel.lua ]]----------------- - -zones.hotel = ZoneCommand:new("Hotel") -zones.hotel.initialState = nil -zones.hotel:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='hotel-tent-red', - products = { - presets.special.red.infantry:extend({ name='hotel-defense-red'}), - presets.defenses.red.infantry:extend({ name='hotel-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='hotel-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='hotel-supply-red'}), - presets.missions.supply.transfer:extend({name='hotel-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='hotel-ammo-red', - products = { - presets.missions.attack.surface:extend({name='hotel-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='hotel-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='hotel-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='hotel-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='hotel-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='hotel-supply-blue'}), - presets.missions.supply.transfer:extend({name='hotel-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='hotel-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='hotel-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Hotel.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Victor.lua ]]----------------- - -zones.victor = ZoneCommand:new("Victor") -zones.victor.initialState = { side=1 } -zones.victor:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='victor-tent-red', - products = { - presets.special.red.infantry:extend({ name='victor-defense-red'}), - presets.defenses.red.infantry:extend({ name='victor-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='victor-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='victor-supply-red'}), - presets.missions.supply.helo:extend({name='victor-supply-red-2'}), - presets.missions.supply.transfer:extend({name='victor-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='victor-ammo-red', - products = { - presets.missions.attack.surface:extend({name='victor-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='victor-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='victor-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='victor-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='victor-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='victor-supply-blue'}), - presets.missions.supply.helo:extend({name='victor-supply-blue-2'}), - presets.missions.supply.transfer:extend({name='victor-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='victor-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='victor-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Victor.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Tango.lua ]]----------------- - -zones.tango = ZoneCommand:new("Tango") -zones.tango.initialState = { side=1 } -zones.tango.isHeloSpawn = true -zones.tango:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='tango-tent-red', - products = { - presets.special.red.infantry:extend({ name='tango-defense-red'}), - presets.defenses.red.infantry:extend({ name='tango-garrison-red'}) - } - }), - presets.upgrades.supply.fuelTankFarp:extend({ - name='tango-fuel-red', - products = { - presets.missions.supply.helo:extend({name='tango-supply-red'}), - presets.missions.supply.helo:extend({name='tango-supply-red-1'}), - presets.missions.supply.transfer:extend({name='tango-transfer-red'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='tango-comcenter-red', - products = { - presets.defenses.red.shorad:extend({name='tango-sam-red'}), - presets.missions.attack.helo:extend({name='tango-cas-red', altitude=200, expend=AI.Task.WeaponExpend.HALF }) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='tango-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='tango-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='tango-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelTankFarp:extend({ - name='tango-fuel-blue', - products = { - presets.missions.supply.helo:extend({name='tango-supply-blue'}), - presets.missions.supply.helo:extend({name='tango-supply-blue-1'}), - presets.missions.supply.transfer:extend({name='tango-transfer-blue'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='tango-comcenter-blue', - products = { - presets.defenses.blue.shorad:extend({name='tango-sam-blue'}), - presets.missions.attack.helo:extend({name='tango-cas-blue', altitude=200, expend=AI.Task.WeaponExpend.HALF }) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Tango.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Unal.lua ]]----------------- - -zones.unal = ZoneCommand:new("Unal") -zones.unal.initialState = { side=1 } -zones.unal.isHeloSpawn = true -zones.unal:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='unal-tent-red', - products = { - presets.special.red.infantry:extend({ name='unal-defense-red'}), - presets.defenses.red.infantry:extend({ name='unal-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='unal-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='unal-supply-red'}), - presets.missions.supply.helo:extend({name='unal-supply-red-2'}), - presets.missions.supply.transfer:extend({name='unal-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='unal-ammo-red', - products = { - presets.missions.attack.surface:extend({name='unal-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='unal-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='unal-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='unal-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='unal-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='unal-supply-blue'}), - presets.missions.supply.helo:extend({name='unal-supply-blue-2'}), - presets.missions.supply.transfer:extend({name='unal-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='unal-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='unal-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Unal.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Beslan.lua ]]----------------- - -zones.beslan = ZoneCommand:new("Beslan") -zones.beslan.initialState = { side=1 } -zones.beslan.keepActive = true -zones.beslan.maxResource = 50000 -zones.beslan:defineUpgrades({ - [1] = - { - presets.upgrades.basic.comPost:extend({ - name='beslan-compost-red', - products = { - presets.special.red.infantry:extend({ name='beslan-defense-red'}), - presets.defenses.red.infantry:extend({ name='beslan-garrison-red'}) - } - }), - presets.upgrades.supply.fuelTank:extend({ - name='beslan-fuel-red', - products = { - presets.missions.supply.helo:extend({name='beslan-supply-red-1'}), - presets.missions.supply.helo:extend({name='beslan-supply-red-2'}), - presets.missions.supply.convoy_escorted:extend({name='beslan-supply-red-3'}), - presets.missions.supply.transfer:extend({name='beslan-transfer-red'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='beslan-comcenter-red', - products = { - presets.defenses.red.sa5:extend({ name='beslan-airdef-red'}), - presets.missions.attack.sead:extend({name='beslan-sead-red', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.attack.strike:extend({name='beslan-strike-red', altitude=30000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.attack.strike:extend({name='beslan-strike-red-1', altitude=30000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.patrol.aircraft:extend({name='beslan-patrol-red', altitude=25000, range=25}) - } - }) - }, - [2] = - { - presets.upgrades.basic.comPost:extend({ - name='beslan-compost-blue', - products = { - presets.special.blue.infantry:extend({ name='beslan-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='beslan-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelTank:extend({ - name='beslan-fuel-blue', - products = { - presets.missions.supply.helo:extend({name='beslan-supply-blue-1'}), - presets.missions.supply.helo:extend({name='beslan-supply-blue-2'}), - presets.missions.supply.convoy_escorted:extend({name='beslan-supply-blue-3'}), - presets.missions.supply.transfer:extend({name='beslan-transfer-blue'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='beslan-comcenter-blue', - products = { - presets.defenses.blue.patriot:extend({ name='beslan-airdef-blue'}), - presets.missions.attack.sead:extend({name='beslan-sead-blue', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.attack.strike:extend({name='beslan-strike-blue', altitude=30000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.attack.strike:extend({name='beslan-strike-blue-1', altitude=30000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.patrol.aircraft:extend({name='beslan-patrol-blue', altitude=25000, range=25}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Beslan.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Bravo.lua ]]----------------- - -zones.bravo = ZoneCommand:new("Bravo") -zones.bravo.initialState = { side=2 } -zones.bravo:defineUpgrades({ - [1] = - { - presets.upgrades.basic.comPost:extend({ - name='bravo-compost-red', - products = { - presets.special.red.infantry:extend({ name='bravo-defense-red'}), - presets.defenses.red.infantry:extend({ name='bravo-garrison-red'}) - } - }), - presets.upgrades.supply.fuelTankFarp:extend({ - name='bravo-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({ name='bravo-supply-red'}), - presets.missions.supply.transfer:extend({name='bravo-transfer-red'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='bravo-comcenter-red', - products = { - presets.defenses.red.shorad:extend({ name='bravo-airdef-red'}), - presets.missions.attack.helo:extend({name='bravo-attack-red', altitude=200, expend=AI.Task.WeaponExpend.HALF}) - } - }) - }, - [2] = - { - presets.upgrades.basic.comPost:extend({ - name='bravo-compost-blue', - products = { - presets.special.blue.infantry:extend({ name='bravo-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='bravo-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelTankFarp:extend({ - name='bravo-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({ name='bravo-supply-blue'}), - presets.missions.supply.transfer:extend({name='bravo-transfer-blue'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='bravo-comcenter-blue', - products = { - presets.defenses.blue.shorad:extend({ name='bravo-airdef-blue'}), - presets.missions.attack.helo:extend({name='bravo-attack-blue', altitude=200, expend=AI.Task.WeaponExpend.HALF }) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Bravo.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/WeaponDepot.lua ]]----------------- - -zones.weapondepot = ZoneCommand:new("Weapon Depot") -zones.weapondepot.initialState = { side=1 } -zones.weapondepot:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='weapons-tent-red', - products = { - presets.special.red.infantry:extend({ name='weapons-defense-red'}), - presets.defenses.red.infantry:extend({ name='weapons-garrison-red'}) - } - }), - presets.upgrades.supply.ammoDepot:extend({ - name='weapons-ammo-red-1', - products = { - presets.missions.supply.convoy:extend({name='weapons-supply-red-1'}), - presets.missions.supply.transfer:extend({name='weapons-transfer-red-1'}) - } - }), - presets.upgrades.supply.ammoDepot:extend({ - name='weapons-ammo-red-2', - products = { - presets.missions.supply.convoy:extend({name='weapons-supply-red-2'}), - presets.missions.supply.transfer:extend({name='weapons-transfer-red-2'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='weapons-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='weapons-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='weapons-garrison-blue'}) - } - }), - presets.upgrades.supply.ammoDepot:extend({ - name='weapons-ammo-blue-1', - products = { - presets.missions.supply.convoy:extend({name='weapons-supply-blue-1'}), - presets.missions.supply.transfer:extend({name='weapons-transfer-blue-1'}) - } - }), - presets.upgrades.supply.ammoDepot:extend({ - name='weapons-ammo-blue-2', - products = { - presets.missions.supply.convoy:extend({name='weapons-supply-blue-2'}), - presets.missions.supply.transfer:extend({name='weapons-transfer-blue-2'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/WeaponDepot.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Delta.lua ]]----------------- - -zones.delta = ZoneCommand:new("Delta") -zones.delta.initialState = { side=2 } -zones.delta:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='delta-tent-red', - products = { - presets.special.red.infantry:extend({ name='delta-defense-red'}), - presets.defenses.red.infantry:extend({ name='delta-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='delta-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='delta-supply-red'}), - presets.missions.supply.transfer:extend({name='delta-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='delta-ammo-red', - products = { - presets.missions.attack.surface:extend({name='delta-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='delta-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='delta-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='delta-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='delta-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='delta-supply-blue'}), - presets.missions.supply.transfer:extend({name='delta-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='delta-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='delta-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Delta.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Cherkessk.lua ]]----------------- - -zones.cherkessk = ZoneCommand:new("Cherkessk") -zones.cherkessk.initialState = { side=1 } -zones.cherkessk.isHeloSpawn = true -zones.cherkessk:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='cherkessk-tent-red', - products = { - presets.special.red.infantry:extend({ name='cherkessk-defense-red'}), - presets.defenses.red.infantry:extend({ name='cherkessk-garrison-red'}) - } - }), - presets.upgrades.supply.fuelTankFarp:extend({ - name='cherkessk-fuel-red', - products = { - presets.missions.supply.helo:extend({name='cherkessk-supply-red'}), - presets.missions.supply.helo:extend({name='cherkessk-supply-red-1'}), - presets.missions.supply.transfer:extend({name='cherkessk-transfer-red'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='cherkessk-comcenter-red', - products = { - presets.defenses.red.shorad:extend({name='cherkessk-sam-red'}), - presets.missions.attack.helo:extend({name='cherkessk-cas-red', altitude=200, expend=AI.Task.WeaponExpend.HALF }), - presets.missions.attack.helo:extend({name='cherkessk-cas-red-1', altitude=200, expend=AI.Task.WeaponExpend.HALF }), - presets.missions.attack.surface:extend({name='cherkessk-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='cherkessk-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='cherkessk-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='cherkessk-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelTankFarp:extend({ - name='cherkessk-fuel-blue', - products = { - presets.missions.supply.helo:extend({name='cherkessk-supply-blue'}), - presets.missions.supply.helo:extend({name='cherkessk-supply-blue-1'}), - presets.missions.supply.transfer:extend({name='cherkessk-transfer-blue'}), - presets.missions.attack.surface:extend({name='cherkessk-assault-blue'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='cherkessk-comcenter-blue', - products = { - presets.defenses.blue.shorad:extend({name='cherkessk-sam-blue'}), - presets.missions.attack.helo:extend({name='cherkessk-cas-blue', altitude=200, expend=AI.Task.WeaponExpend.HALF }), - presets.missions.attack.helo:extend({name='cherkessk-cas-blue-1', altitude=200, expend=AI.Task.WeaponExpend.HALF }) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Cherkessk.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Juliett.lua ]]----------------- - -zones.juliett = ZoneCommand:new("Juliett") -zones.initialState = nil -zones.juliett:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='juliett-tent-red', - products = { - presets.special.red.infantry:extend({ name='juliett-defense-red'}), - presets.defenses.red.infantry:extend({ name='juliett-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='juliett-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='juliett-supply-red'}), - presets.missions.supply.transfer:extend({name='juliett-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='juliett-ammo-red', - products = { - presets.missions.attack.surface:extend({name='juliett-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='juliett-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='juliett-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='juliett-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='juliett-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='juliett-supply-blue'}), - presets.missions.supply.transfer:extend({name='juliett-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='juliett-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='juliett-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Juliett.lua ]]----------------- - - - - cm = ConnectionManager:new() - cm:addConnection('Batumi', 'Alpha') - cm:addConnection('Alpha', 'Bravo') - cm:addConnection('Bravo', 'Kobuleti') - cm:addConnection('Bravo', 'Factory') - cm:addConnection('Kobuleti', 'Factory') - cm:addConnection('Kobuleti', 'Charlie') - cm:addConnection('Foxtrot', 'Charlie') - cm:addConnection('Foxtrot', 'Kobuleti') - cm:addConnection('Delta','Foxtrot') - cm:addConnection('Delta','Kobuleti') - cm:addConnection('Delta','Factory') - cm:addConnection('Echo','Charlie') - cm:addConnection('Golf','Echo') - cm:addConnection('Golf','Foxtrot') - cm:addConnection('India','Delta') - cm:addConnection('Hotel','Golf') - cm:addConnection('Hotel','Foxtrot') - cm:addConnection('Hotel','Delta') - cm:addConnection('Hotel','India') - cm:addConnection('Juliett','Echo') - cm:addConnection('Juliett','Golf') - cm:addConnection('Senaki','Juliett') - cm:addConnection('Senaki','Golf') - cm:addConnection('Senaki','Hotel') - cm:addConnection('Kutaisi','Hotel') - cm:addConnection('Kutaisi','India') - cm:addConnection('Kilo','Juliett') - cm:addConnection('Mike','Kutaisi') - cm:addConnection('Mike','Senaki') - cm:addConnection('Romeo','Mike') - cm:addConnection('Romeo','Kutaisi') - cm:addConnection('Weapon Depot','Juliett') - cm:addConnection('Weapon Depot','Senaki') - cm:addConnection('Weapon Depot','Kilo') - cm:addConnection('November','Weapon Depot') - cm:addConnection('November','Senaki') - cm:addConnection('November','Mike') - cm:addConnection('Oil Fields','Romeo') - cm:addConnection('Quebec','Kilo') - cm:addConnection('Zugdidi','Weapon Depot') - cm:addConnection('Zugdidi','Quebec') - cm:addConnection('Zugdidi','November') - cm:addConnection('Zugdidi','Kilo') - cm:addConnection('Distillery','November') - cm:addConnection('Distillery','Mike') - cm:addConnection('Zugdidi','Papa') - cm:addConnection('November','Papa') - cm:addConnection('Sierra','Papa') - cm:addConnection('Sierra','Zugdidi') - cm:addConnection('Sierra','Uniform') - cm:addConnection('Mine','Uniform') - cm:addConnection('Tango','Quebec') - cm:addConnection('Tango','Zugdidi') - cm:addConnection('Sierra','Tango') - cm:addConnection('Whiskey','Tango') - cm:addConnection('Ochamchira','Tango') - cm:addConnection('Ochamchira','Whiskey') - cm:addConnection('Ochamchira','Farm') - cm:addConnection('Ochamchira','Zulu') - cm:addConnection('Farm','Zulu') - cm:addConnection('Sukhumi','Zulu') - cm:addConnection('Lentehi','Distillery', true, 3000) - cm:addConnection('Lentehi','Babugent', true, 5000) - cm:addConnection('Nalchik','Babugent') - cm:addConnection('Victor','Distillery', true, 2000) - cm:addConnection('Victor','Romeo') - cm:addConnection('Victor','Lentehi') - cm:addConnection('Victor','Oil Fields', true, 2000) - cm:addConnection('Victor','Oni') - cm:addConnection('Unal','Oni', true, 4500) - cm:addConnection('Beslan','Unal') - cm:addConnection('Digora','Beslan') - cm:addConnection('Digora','Unal') - cm:addConnection('Digora','Babugent') - cm:addConnection('Terek','Digora') - cm:addConnection('Terek','Nalchik') - cm:addConnection('Terek','Beslan') - cm:addConnection('Prohladniy','Terek') - cm:addConnection('Prohladniy','Nalchik') - cm:addConnection('Malgobek','Terek') - cm:addConnection('Malgobek','Beslan') - cm:addConnection('Lima','Mine') - cm:addConnection('Lima','Lentehi', true, 4000) - cm:addConnection('Tyrnyauz','Lima', true, 4000) - cm:addConnection('Tyrnyauz','Nalchik') - cm:addConnection('XRay','Sukhumi') - cm:addConnection('Oscar','Sukhumi') - cm:addConnection('Oscar','XRay') - cm:addConnection('Mozdok','Malgobek') - cm:addConnection('Mozdok','Prohladniy') - cm:addConnection('Gudauta','Oscar') - cm:addConnection('Yankee','Gudauta') - cm:addConnection('Sochi','Yankee') - cm:addConnection('Refinery','XRay', true, 4000) - cm:addConnection('Refinery','Humara') - cm:addConnection('Intel Center','Tyrnyauz') - cm:addConnection('Intel Center','Nalchik') - cm:addConnection('Intel Center','Prohladniy') - cm:addConnection('Intel Center','Kislovodsk') - cm:addConnection('Mineralnye','Intel Center') - cm:addConnection('Kislovodsk','Mineralnye') - cm:addConnection('Tallyk','Mineralnye') - cm:addConnection('Tallyk','Kislovodsk') - cm:addConnection('Power Plant','Mineralnye') - cm:addConnection('Power Plant','Tallyk') - cm:addConnection('Cherkessk','Tallyk') - cm:addConnection('Cherkessk','Power Plant') - cm:addConnection('Cherkessk','Humara') -end - -ZoneCommand.setNeighbours(cm) - -bm = BattlefieldManager:new() - -mc = MarkerCommands:new() - -pt = PlayerTracker:new(mc) - -mt = MissionTracker:new(pt, mc) - -st = SquadTracker:new() - -ct = CSARTracker:new() - -pl = PlayerLogistics:new(mt, pt, st, ct) - -gci = GCI:new(2) - -gm = GroupMonitor:new(cm) -ZoneCommand.groupMonitor = gm - --- PlayerLogistics:registerSquadGroup(squadType, groupname, weight,cost,jobtime,extracttime, squadSize) -pl:registerSquadGroup(PlayerLogistics.infantryTypes.capture, 'capture-squad', 700, 200, 60, 60*30, 4) -pl:registerSquadGroup(PlayerLogistics.infantryTypes.sabotage, 'sabotage-squad', 800, 500, 60*5, 60*30, 4) -pl:registerSquadGroup(PlayerLogistics.infantryTypes.ambush, 'ambush-squad', 900, 300, 60*20, 60*30, 5) -pl:registerSquadGroup(PlayerLogistics.infantryTypes.engineer, 'engineer-squad', 200, 1000,60, 60*30, 2) -pl:registerSquadGroup(PlayerLogistics.infantryTypes.manpads, 'manpads-squad', 900, 500, 60*20, 60*30, 5) -pl:registerSquadGroup(PlayerLogistics.infantryTypes.spy, 'spy-squad', 100, 300, 60*10, 60*30, 1) -pl:registerSquadGroup(PlayerLogistics.infantryTypes.rapier, 'rapier-squad', 1200,2000,60*60, 60*30, 8) - -Group.getByName('jtacDrone'):destroy() -CommandFunctions.jtac = JTAC:new({name = 'jtacDrone'}) - -pm = PersistenceManager:new(savefile, gm, st, ct, pl) -pm:load() - -if pm:canRestore() then - pm:restoreZones() - pm:restoreAIMissions() - pm:restoreBattlefield() - pm:restoreCsar() - pm:restoreSquads() -else - --initial states - Starter.start(zones) -end - -timer.scheduleFunction(function(param, time) - pm:save() - env.info("Mission state saved") - return time+60 -end, zones, timer.getTime()+60) - - ---make sure support units are present where needed -ensureSpawn = { - ['golf-farp-suport'] = zones.golf, - ['november-farp-suport'] = zones.november, - ['tango-farp-suport'] = zones.tango, - ['sierra-farp-suport'] = zones.sierra, - ['cherkessk-farp-suport'] = zones.cherkessk, - ['unal-farp-suport'] = zones.unal, - ['tyrnyauz-farp-suport'] = zones.tyrnyauz -} - -for grname, zn in pairs(ensureSpawn) do - local g = Group.getByName(grname) - if g then g:destroy() end -end - -timer.scheduleFunction(function(param, time) - - for grname, zn in pairs(ensureSpawn) do - local g = Group.getByName(grname) - if zn.side == 2 then - if not g then - local err, msg = pcall(mist.respawnGroup,grname,true) - if not err then - env.info("ERROR spawning "..grname) - env.info(msg) - end - end - else - if g then g:destroy() end - end - end - - return time+30 -end, {}, timer.getTime()+30) - - ---supply injection -local blueSupply = {'offmap-supply-blue-1','offmap-supply-blue-2','offmap-supply-blue-3','offmap-supply-blue-4','offmap-supply-blue-5'} -local redSupply = {'offmap-supply-red-1','offmap-supply-red-2','offmap-supply-red-3','offmap-supply-red-4','offmap-supply-red-5'} -local offmapZones = { - zones.batumi, - zones.sochi, - zones.nalchik, - zones.beslan, - zones.mozdok, - zones.mineralnye, --- zones.senaki, --- zones.sukhumi, --- zones.gudauta, --- zones.kobuleti, -} - supplyPointRegistry = { blue = {}, red = {} diff --git a/resources/plugins/pretense/init_header.lua b/resources/plugins/pretense/init_header.lua index 9df8452d..d569e9bb 100644 --- a/resources/plugins/pretense/init_header.lua +++ b/resources/plugins/pretense/init_header.lua @@ -619,4052 +619,4 @@ presets = { } zones = {} -do - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Batumi.lua ]]----------------- - -zones.batumi = ZoneCommand:new('Batumi') -zones.batumi.initialState = { side=2 } -zones.batumi.keepActive = true -zones.batumi.isHeloSpawn = true -zones.batumi.isPlaneSpawn = true -zones.batumi.maxResource = 50000 -zones.batumi:defineUpgrades({ - [1] = { --red side - presets.upgrades.basic.comPost:extend({ - name = 'batumi-com-red', - products = { - presets.special.red.infantry:extend({ name='batumi-defense-red'}), - presets.defenses.red.infantry:extend({ name='batumi-garrison-red' }) - } - }), - }, - [2] = --blue side - { - presets.upgrades.basic.comPost:extend({ - name = 'batumi-com-blue', - products = { - presets.special.blue.infantry:extend({ name='batumi-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='batumi-garrison-blue' }) - } - }), - presets.upgrades.supply.fuelTank:extend({ - name = 'batumi-fueltank-blue', - products = { - presets.missions.supply.convoy_escorted:extend({ name='batumi-supply-convoy-1'}), - presets.missions.supply.helo:extend({ name='batumi-supply-blue-1' }), - presets.missions.supply.transfer:extend({name='batumi-transfer-blue'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name = 'batumi-mission-command-blue', - products = { - presets.defenses.blue.shorad:extend({ name='batumi-sam-blue' }), - presets.missions.attack.sead:extend({name='batumi-sead-blue-1', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.attack.cas:extend({name='batumi-cas-blue-1', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.bai:extend({name='batumi-cas-blue-1', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.strike:extend({name='batumi-strike-blue-1', altitude=20000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.patrol.aircraft:extend({name='batumi-patrol-blue-1', altitude=25000, range=25}), - presets.missions.support.awacs:extend({name='batumi-awacs-blue', altitude=30000, freq=257.5}), - presets.missions.support.tanker:extend({name='batumi-tanker-blue', altitude=25000, freq=257, tacan='37', variant="Drogue"}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Batumi.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Mike.lua ]]----------------- - -zones.mike = ZoneCommand:new("Mike") -zones.mike.initialState = { side=1 } -zones.mike.keepActive = true -zones.mike:defineUpgrades({ - [1] = - { - presets.upgrades.basic.tent:extend({ - name='mike-tent-red', - products = { - presets.special.red.infantry:extend({ name='mike-defense-red'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='mike-comcenter-red', - products = { - presets.defenses.red.sam:extend({ name='mike-sam-red'}) - } - }) - }, - [2] = - { - presets.upgrades.basic.tent:extend({ - name='mike-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='mike-defense-blue'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='mike-comcenter-blue', - products = { - presets.defenses.blue.sam:extend({ name='mike-sam-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Mike.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Tyrnyauz.lua ]]----------------- - -zones.tyrnyauz = ZoneCommand:new("Tyrnyauz") -zones.tyrnyauz.initialState = { side=1 } -zones.tyrnyauz.isHeloSpawn = true -zones.tyrnyauz:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='tyrnyauz-tent-red', - products = { - presets.special.red.infantry:extend({ name='tyrnyauz-defense-red'}), - presets.defenses.red.infantry:extend({ name='tyrnyauz-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='tyrnyauz-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='tyrnyauz-supply-red'}), - presets.missions.supply.helo:extend({name='tyrnyauz-supply-red-2'}), - presets.missions.supply.transfer:extend({name='tyrnyauz-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='tyrnyauz-ammo-red', - products = { - presets.missions.attack.surface:extend({name='tyrnyauz-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='tyrnyauz-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='tyrnyauz-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='tyrnyauz-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='tyrnyauz-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='tyrnyauz-supply-blue'}), - presets.missions.supply.helo:extend({name='tyrnyauz-supply-blue-2'}), - presets.missions.supply.transfer:extend({name='tyrnyauz-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='tyrnyauz-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='tyrnyauz-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Tyrnyauz.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/India.lua ]]----------------- - -zones.india = ZoneCommand:new("India") -zones.india.initialState = nil -zones.india:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='india-tent-red', - products = { - presets.special.red.infantry:extend({ name='india-defense-red'}), - presets.defenses.red.infantry:extend({ name='india-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='india-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='india-supply-red'}), - presets.missions.supply.transfer:extend({name='india-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='india-ammo-red', - products = { - presets.missions.attack.surface:extend({name='india-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='india-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='india-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='india-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='india-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='india-supply-blue'}), - presets.missions.supply.transfer:extend({name='india-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='india-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='india-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/India.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/IntelCenter.lua ]]----------------- - -zones.intelcenter = ZoneCommand:new("Intel Center") -zones.intelcenter.initialState = { side=1 } -zones.intelcenter:defineUpgrades({ - [1] = - { - presets.upgrades.basic.tent:extend({ - name='intelcenter-tent-red', - products = { - presets.special.red.infantry:extend({ name='intelcenter-defense-red'}), - presets.defenses.red.infantry:extend({ name='intelcenter-garrison-red'}) - } - }), - presets.upgrades.supply.hq:extend({ - name='intelcenter-hq-red', - products = { - presets.missions.supply.convoy:extend({ name='intelcenter-supply-red'}), - presets.missions.supply.convoy:extend({ name='intelcenter-supply-red-1'}), - presets.missions.supply.transfer:extend({name='intelcenter-transfer-red'}) - } - }), - presets.upgrades.supply.antenna:extend({ - name='intelcenter-antenna-red', - products = { - } - }), - presets.upgrades.supply.antenna:extend({ - name='intelcenter-antenna-red-1', - products = { - } - }), - presets.upgrades.supply.antenna:extend({ - name='intelcenter-antenna-red-2', - products = { - } - }) - }, - [2] = - { - presets.upgrades.basic.tent:extend({ - name='intelcenter-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='intelcenter-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='intelcenter-garrison-blue'}) - } - }), - presets.upgrades.supply.hq:extend({ - name='intelcenter-hq-blue', - products = { - presets.missions.supply.convoy:extend({ name='intelcenter-supply-blue'}), - presets.missions.supply.convoy:extend({ name='intelcenter-supply-blue-1'}), - presets.missions.supply.transfer:extend({name='intelcenter-transfer-blue'}) - } - }), - presets.upgrades.supply.antenna:extend({ - name='intelcenter-antenna-blue', - products = { - } - }), - presets.upgrades.supply.antenna:extend({ - name='intelcenter-antenna-blue-1', - products = { - } - }), - presets.upgrades.supply.antenna:extend({ - name='intelcenter-antenna-blue-2', - products = { - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/IntelCenter.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Mineralnye.lua ]]----------------- - -zones.mineralnye = ZoneCommand:new("Mineralnye") -zones.mineralnye.initialState = { side=1 } -zones.mineralnye.keepActive = true -zones.mineralnye.isHeloSpawn = true -zones.mineralnye.isPlaneSpawn = true -zones.mineralnye:defineUpgrades({ - [1] = - { - presets.upgrades.basic.comPost:extend({ - name='mineralnye-compost-red', - products = { - presets.special.red.infantry:extend({ name='mineralnye-defense-red'}), - presets.defenses.red.infantry:extend({ name='mineralnye-garrison-red'}) - } - }), - presets.upgrades.supply.fuelTank:extend({ - name='mineralnye-fuel-red', - products = { - presets.missions.supply.helo:extend({name='mineralnye-supply-red'}), - presets.missions.supply.helo:extend({name='mineralnye-supply-red-1'}), - presets.missions.supply.transfer:extend({name='mineralnye-transfer-red'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='mineralnye-comcenter-red', - products = { - presets.defenses.red.sa11:extend({ name='mineralnye-airdef-red'}), - presets.missions.attack.cas:extend({name='mineralnye-cas-red', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.bai:extend({name='mineralnye-cas-red', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.strike:extend({name='mineralnye-strike-red', altitude=30000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.attack.strike:extend({name='mineralnye-strike-red-1', altitude=30000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.patrol.aircraft:extend({name='mineralnye-patrol-red', altitude=25000, range=25}) - } - }) - }, - [2] = - { - presets.upgrades.basic.comPost:extend({ - name='mineralnye-compost-blue', - products = { - presets.special.blue.infantry:extend({ name='mineralnye-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='mineralnye-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelTank:extend({ - name='mineralnye-fuel-blue', - products = { - presets.missions.supply.helo:extend({name='mineralnye-supply-blue'}), - presets.missions.supply.helo:extend({name='mineralnye-supply-blue-1'}), - presets.missions.supply.transfer:extend({name='mineralnye-transfer-blue'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='mineralnye-comcenter-blue', - products = { - presets.defenses.blue.nasams:extend({ name='mineralnye-airdef-blue'}), - presets.missions.attack.cas:extend({name='mineralnye-cas-blue', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.bai:extend({name='mineralnye-cas-blue', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.strike:extend({name='mineralnye-strike-blue', altitude=30000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.attack.strike:extend({name='mineralnye-strike-blue-1', altitude=30000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.patrol.aircraft:extend({name='mineralnye-patrol-blue', altitude=25000, range=25}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Mineralnye.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/PowerPlant.lua ]]----------------- - -zones.powerplant = ZoneCommand:new("Power Plant") -zones.powerplant.initialState = { side=1 } -zones.powerplant:defineUpgrades({ - [1] = - { - presets.upgrades.basic.tent:extend({ - name='powerplant-tent-red', - products = { - presets.special.red.infantry:extend({ name='powerplant-defense-red'}), - presets.defenses.red.infantry:extend({ name='powerplant-garrison-red'}) - } - }), - presets.upgrades.supply.powerplant1:extend({ - name='powerplant-building-red-1', - products = { - presets.missions.supply.convoy:extend({ name='powerplant-supply-red'}), - presets.missions.supply.transfer:extend({name='powerplant-transfer-red'}) - } - }), - presets.upgrades.supply.powerplant2:extend({ - name='powerplant-building-red-2', - products = { - presets.missions.supply.convoy:extend({ name='powerplant-supply-red-1'}), - presets.missions.supply.transfer:extend({name='powerplant-transfer-red'}) - } - }) - }, - [2] = - { - presets.upgrades.basic.tent:extend({ - name='powerplant-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='powerplant-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='powerplant-garrison-blue'}) - } - }), - presets.upgrades.supply.powerplant1:extend({ - name='powerplant-building-blue-1', - products = { - presets.missions.supply.convoy:extend({ name='powerplant-supply-blue'}), - presets.missions.supply.transfer:extend({name='powerplant-transfer-blue'}) - } - }), - presets.upgrades.supply.powerplant2:extend({ - name='powerplant-building-blue-2', - products = { - presets.missions.supply.convoy:extend({ name='powerplant-supply-blue-1'}), - presets.missions.supply.transfer:extend({name='powerplant-transfer-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/PowerPlant.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Zugdidi.lua ]]----------------- - -zones.zugdidi = ZoneCommand:new("Zugdidi") -zones.zugdidi.initialState = { side=1 } -zones.zugdidi:defineUpgrades({ - [1] = - { - presets.upgrades.basic.comPost:extend({ - name='zugdidi-compost-red', - products = { - presets.missions.supply.transfer:extend({name='zugdidi-transfer-red'}), - presets.special.red.infantry:extend({ name='zugdidi-defense-red'}), - presets.defenses.red.infantry:extend({ name='zugdidi-garrison-red'}), - presets.missions.attack.surface:extend({name='zugdidi-attack-red'}), - presets.missions.supply.convoy:extend({name='zugdidi-supply-red'}) - } - }), - presets.upgrades.supply.hangar:extend({ - name='zugdidi-hangar-red-1', - products = { - presets.missions.supply.helo:extend({name='zugdidi-supply-red-1'}) - } - }), - presets.upgrades.supply.hangar:extend({ - name='zugdidi-hangar-red-2', - products = { - presets.missions.supply.helo:extend({name='zugdidi-supply-red-2'}) - } - }), - presets.upgrades.supply.hangar:extend({ - name='zugdidi-hangar-red-3', - products = { - presets.missions.supply.helo:extend({name='zugdidi-supply-red-3'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='zugdidi-comcenter-red', - products = { - presets.defenses.red.sa6:extend({ name='zugdidi-sam-red'}) - } - }) - }, - [2] = - { - presets.upgrades.basic.tent:extend({ - name='zugdidi-compost-blue', - products = { - presets.missions.supply.transfer:extend({name='zugdidi-transfer-blue'}), - presets.special.blue.infantry:extend({ name='zugdidi-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='zugdidi-garrison-blue'}), - presets.missions.attack.surface:extend({name='zugdidi-attack-blue'}), - presets.missions.supply.convoy:extend({name='zugdidi-supply-blue'}) - } - }), - presets.upgrades.supply.hangar:extend({ - name='zugdidi-hangar-blue-1', - products = { - presets.missions.supply.helo:extend({name='zugdidi-supply-blue-1'}) - } - }), - presets.upgrades.supply.hangar:extend({ - name='zugdidi-hangar-blue-2', - products = { - presets.missions.supply.helo:extend({name='zugdidi-supply-blue-2'}) - } - }), - presets.upgrades.supply.hangar:extend({ - name='zugdidi-hangar-blue-3', - products = { - presets.missions.supply.helo:extend({name='zugdidi-supply-blue-3'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='zugdidi-comcenter-blue', - products = { - presets.defenses.blue.shorad:extend({ name='zugdidi-sam-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Zugdidi.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Babugent.lua ]]----------------- - -zones.babugent = ZoneCommand:new("Babugent") -zones.babugent.initialState = { side=1 } -zones.babugent:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='babugent-tent-red', - products = { - presets.special.red.infantry:extend({ name='babugent-defense-red'}), - presets.defenses.red.infantry:extend({ name='babugent-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='babugent-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='babugent-supply-red'}), - presets.missions.supply.helo:extend({name='babugent-supply-red-2'}), - presets.missions.supply.transfer:extend({name='babugent-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='babugent-ammo-red', - products = { - presets.missions.attack.surface:extend({name='babugent-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='babugent-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='babugent-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='babugent-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='babugent-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='babugent-supply-blue'}), - presets.missions.supply.helo:extend({name='babugent-supply-blue-2'}), - presets.missions.supply.transfer:extend({name='babugent-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='babugent-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='babugent-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Babugent.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Kislovodsk.lua ]]----------------- - -zones.kislovodsk = ZoneCommand:new("Kislovodsk") -zones.kislovodsk.initialState = { side=1 } -zones.kislovodsk:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='kislovodsk-tent-red', - products = { - presets.special.red.infantry:extend({ name='kislovodsk-defense-red'}), - presets.defenses.red.infantry:extend({ name='kislovodsk-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='kislovodsk-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='kislovodsk-supply-red'}), - presets.missions.supply.transfer:extend({name='kislovodsk-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='kislovodsk-ammo-red', - products = { - presets.missions.attack.surface:extend({name='kislovodsk-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='kislovodsk-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='kislovodsk-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='kislovodsk-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='kislovodsk-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='kislovodsk-supply-blue'}), - presets.missions.supply.transfer:extend({name='kislovodsk-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='kislovodsk-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='kislovodsk-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Kislovodsk.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Gudauta.lua ]]----------------- - -zones.gudauta = ZoneCommand:new("Gudauta") -zones.gudauta.initialState = { side=1 } -zones.gudauta.keepActive = true -zones.gudauta.maxResource = 50000 -zones.gudauta:defineUpgrades({ - [1] = - { - presets.upgrades.basic.comPost:extend({ - name='gudauta-compost-red', - products = { - presets.special.red.infantry:extend({ name='gudauta-defense-red'}), - presets.defenses.red.infantry:extend({ name='gudauta-garrison-red'}) - } - }), - presets.upgrades.supply.fuelTank:extend({ - name='gudauta-fuel-red', - products = { - presets.missions.supply.helo:extend({name='gudauta-supply-red'}), - presets.missions.supply.helo:extend({name='gudauta-supply-red-1'}), - presets.missions.supply.transfer:extend({name='gudauta-transfer-red'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='gudauta-comcenter-red', - products = { - presets.defenses.red.sam:extend({ name='gudauta-airdef-red'}), - presets.missions.attack.sead:extend({name='gudauta-sead-red', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.attack.sead:extend({name='gudauta-sead-red-1', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.attack.cas:extend({name='gudauta-cas-red', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.bai:extend({name='gudauta-cas-red', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.patrol.aircraft:extend({name='gudauta-patrol-red', altitude=25000, range=25}) - } - }) - }, - [2] = - { - presets.upgrades.basic.comPost:extend({ - name='gudauta-compost-blue', - products = { - presets.special.blue.infantry:extend({ name='gudauta-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='gudauta-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelTank:extend({ - name='gudauta-fuel-blue', - products = { - presets.missions.supply.helo:extend({name='gudauta-supply-blue'}), - presets.missions.supply.helo:extend({name='gudauta-supply-blue-1'}), - presets.missions.supply.transfer:extend({name='gudauta-transfer-blue'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='gudauta-comcenter-blue', - products = { - presets.defenses.blue.sam:extend({ name='gudauta-airdef-blue'}), - presets.missions.attack.sead:extend({name='gudauta-sead-blue', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.attack.sead:extend({name='gudauta-sead-blue-1', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.attack.cas:extend({name='gudauta-cas-blue', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.bai:extend({name='gudauta-cas-blue', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.patrol.aircraft:extend({name='gudauta-patrol-blue', altitude=25000, range=25}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Gudauta.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Distillery.lua ]]----------------- - -zones.distillery = ZoneCommand:new("Distillery") -zones.distillery.initialState = { side=1 } -zones.distillery:defineUpgrades({ - [1] = - { - presets.upgrades.basic.tent:extend({ - name='distillery-tent-red', - products = { - presets.special.red.infantry:extend({ name='distillery-defense-red'}), - presets.defenses.red.infantry:extend({ name='distillery-garrison-red'}) - } - }), - presets.upgrades.supply.factory1:extend({ - name='distillery-prod-red-1', - products = { - presets.missions.supply.convoy:extend({ name='distillery-supply-red-1'}), - presets.missions.supply.transfer:extend({name='distillery-transfer-red'}) - } - }), - presets.upgrades.supply.factory2:extend({ - name='distillery-prod-red-2', - products = { - presets.missions.supply.convoy:extend({ name='distillery-supply-red-2', cost=2000}), - presets.missions.supply.transfer:extend({name='distillery-transfer-red2'}) - } - }), - presets.upgrades.supply.factoryTank:extend({ - name='distillery-tank-red-1', - products = { - } - }), - presets.upgrades.supply.factoryTank:extend({ - name='distillery-tank-red-2', - products = { - } - }), - presets.upgrades.supply.factoryTank:extend({ - name='distillery-tank-red-3', - products = { - } - }) - }, - [2] = - { - presets.upgrades.basic.tent:extend({ - name='distillery-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='distillery-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='distillery-garrison-blue'}) - } - }), - presets.upgrades.supply.factory1:extend({ - name='distillery-prod-blue-1', - products = { - presets.missions.supply.convoy:extend({ name='distillery-supply-blue-1'}), - presets.missions.supply.transfer:extend({name='distillery-transfer-blue'}) - } - }), - presets.upgrades.supply.factory2:extend({ - name='distillery-prod-blue-2', - products = { - presets.missions.supply.convoy:extend({ name='distillery-supply-blue-2', cost=2000}), - presets.missions.supply.transfer:extend({name='distillery-transfer-blue2'}) - } - }), - presets.upgrades.supply.factoryTank:extend({ - name='distillery-tank-blue-1', - products = { - } - }), - presets.upgrades.supply.factoryTank:extend({ - name='distillery-tank-blue-2', - products = { - } - }), - presets.upgrades.supply.factoryTank:extend({ - name='distillery-tank-blue-3', - products = { - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Distillery.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Sochi.lua ]]----------------- - -zones.sochi = ZoneCommand:new("Sochi") -zones.sochi.initialState = { side=1 } -zones.sochi.keepActive = true -zones.sochi.maxResource = 50000 -zones.sochi:defineUpgrades({ - [1] = - { - presets.upgrades.basic.comPost:extend({ - name='sochi-compost-red', - products = { - presets.special.red.infantry:extend({ name='sochi-defense-red'}), - presets.defenses.red.infantry:extend({ name='sochi-garrison-red'}) - } - }), - presets.upgrades.supply.fuelTank:extend({ - name='sochi-fuel-red', - products = { - presets.missions.supply.helo:extend({name='sochi-supply-red-1'}), - presets.missions.supply.helo:extend({name='sochi-supply-red-2'}), - presets.missions.supply.convoy_escorted:extend({name='sochi-supply-red-3'}), - presets.missions.supply.transfer:extend({name='sochi-transfer-red'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='sochi-comcenter-red', - products = { - presets.defenses.red.sa10:extend({ name='sochi-airdef-red'}), - presets.missions.attack.sead:extend({name='sochi-sead-red', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.attack.strike:extend({name='sochi-strike-red', altitude=30000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.patrol.aircraft:extend({name='sochi-patrol-red', altitude=25000, range=25}), - presets.missions.patrol.aircraft:extend({name='sochi-patrol-red-1', altitude=25000, range=25}), - presets.missions.attack.cas:extend({name='sochi-cas-red', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.bai:extend({name='sochi-cas-red', altitude=10000, expend=AI.Task.WeaponExpend.ONE}) - } - }) - }, - [2] = - { - presets.upgrades.basic.comPost:extend({ - name='sochi-compost-blue', - products = { - presets.special.blue.infantry:extend({ name='sochi-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='sochi-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelTank:extend({ - name='sochi-fuel-blue', - products = { - presets.missions.supply.helo:extend({name='sochi-supply-blue-1'}), - presets.missions.supply.helo:extend({name='sochi-supply-blue-2'}), - presets.missions.supply.convoy_escorted:extend({name='sochi-supply-blue-3'}), - presets.missions.supply.transfer:extend({name='sochi-transfer-blue'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='sochi-comcenter-blue', - products = { - presets.defenses.blue.patriot:extend({ name='sochi-airdef-blue'}), - presets.missions.attack.sead:extend({name='sochi-sead-blue', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.attack.strike:extend({name='sochi-strike-blue', altitude=30000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.patrol.aircraft:extend({name='sochi-patrol-blue', altitude=25000, range=25}), - presets.missions.attack.cas:extend({name='sochi-cas-blue', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.bai:extend({name='sochi-cas-blue', altitude=10000, expend=AI.Task.WeaponExpend.ONE}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Sochi.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Golf.lua ]]----------------- - -zones.golf = ZoneCommand:new("Golf") -zones.golf.initialState = nil -zones.golf.isHeloSpawn = true -zones.golf:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='golf-tent-red', - products = { - presets.special.red.infantry:extend({ name='golf-defense-red'}), - presets.defenses.red.infantry:extend({ name='golf-garrison-red'}) - } - }), - presets.upgrades.supply.fuelTank:extend({ - name='golf-fuel-red', - products = { - presets.missions.supply.helo:extend({name='golf-supply-red'}), - presets.missions.supply.helo:extend({name='golf-supply-red-1'}), - presets.missions.supply.transfer:extend({name='golf-transfer-red'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='golf-comcenter-red', - products = { - presets.defenses.red.shorad:extend({name='golf-sam-red'}), - presets.missions.attack.helo:extend({name='golf-cas-red', altitude=200, expend=AI.Task.WeaponExpend.HALF }) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='golf-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='golf-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='golf-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelTankFarp:extend({ - name='golf-fuel-blue', - products = { - presets.missions.supply.helo:extend({name='golf-supply-blue'}), - presets.missions.supply.helo:extend({name='golf-supply-blue-1'}), - presets.missions.supply.transfer:extend({name='golf-transfer-blue'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='golf-comcenter-blue', - products = { - presets.defenses.blue.shorad:extend({name='golf-sam-blue'}), - presets.missions.attack.helo:extend({name='golf-cas-blue', altitude=200, expend=AI.Task.WeaponExpend.HALF }) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Golf.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Charlie.lua ]]----------------- - -zones.charlie = ZoneCommand:new("Charlie") -zones.charlie.initialState = { side=2 } -zones.charlie.keepActive = true -zones.charlie:defineUpgrades({ - [1] = - { - presets.upgrades.basic.tent:extend({ - name='charlie-tent-red', - products = { - presets.special.red.infantry:extend({ name='charlie-defense-red'}), - presets.defenses.red.infantry:extend({ name='charlie-garrison-red'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='charlie-comcenter-red', - products = { - presets.defenses.red.sam:extend({ name='charlie-sam-red'}) - } - }) - }, - [2] = - { - presets.upgrades.basic.tent:extend({ - name='charlie-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='charlie-defense-red'}), - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='charlie-comcenter-blue', - products = { - presets.defenses.blue.sam:extend({ name='charlie-sam-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Charlie.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Lentehi.lua ]]----------------- - -zones.lentehi = ZoneCommand:new("Lentehi") -zones.lentehi.initialState = { side=1 } -zones.lentehi:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='lentehi-tent-red', - products = { - presets.special.red.infantry:extend({ name='lentehi-defense-red'}), - presets.defenses.red.infantry:extend({ name='lentehi-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='lentehi-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='lentehi-supply-red'}), - presets.missions.supply.helo:extend({name='lentehi-supply-red-2'}), - presets.missions.supply.transfer:extend({name='lentehi-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='lentehi-ammo-red', - products = { - presets.missions.attack.surface:extend({name='lentehi-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='lentehi-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='lentehi-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='lentehi-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='lentehi-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='lentehi-supply-blue'}), - presets.missions.supply.helo:extend({name='lentehi-supply-blue-2'}), - presets.missions.supply.transfer:extend({name='lentehi-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='lentehi-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='lentehi-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Lentehi.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Refinery.lua ]]----------------- - -zones.refinery = ZoneCommand:new("Refinery") -zones.refinery.initialState = { side=1 } -zones.refinery:defineUpgrades({ - [1] = - { - presets.upgrades.basic.tent:extend({ - name='refinery-tent-red', - products = { - presets.special.red.infantry:extend({ name='refinery-defense-red'}), - presets.defenses.red.infantry:extend({ name='refinery-garrison-red'}) - } - }), - presets.upgrades.supply.refinery1:extend({ - name='refinery-building-red', - products = { - presets.missions.supply.convoy:extend({ name='refinery-supply-red'}), - presets.missions.supply.convoy:extend({ name='refinery-supply-red-1'}), - presets.missions.supply.helo:extend({ name='refinery-supply-red-2'}), - presets.missions.supply.transfer:extend({name='refinery-transfer-red'}) - } - }) - }, - [2] = - { - presets.upgrades.basic.tent:extend({ - name='refinery-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='refinery-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='refinery-garrison-blue'}) - } - }), - presets.upgrades.supply.refinery1:extend({ - name='refinery-building-blue', - products = { - presets.missions.supply.convoy:extend({ name='refinery-supply-blue'}), - presets.missions.supply.convoy:extend({ name='refinery-supply-blue-1'}), - presets.missions.supply.helo:extend({ name='refinery-supply-blue-2'}), - presets.missions.supply.transfer:extend({name='refinery-transfer-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Refinery.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Mozdok.lua ]]----------------- - -zones.mozdok = ZoneCommand:new("Mozdok") -zones.mozdok.initialState = { side=1 } -zones.mozdok.keepActive = true -zones.mozdok.maxResource = 50000 -zones.mozdok:defineUpgrades({ - [1] = - { - presets.upgrades.basic.comPost:extend({ - name='mozdok-compost-red', - products = { - presets.special.red.infantry:extend({ name='mozdok-defense-red'}), - presets.defenses.red.infantry:extend({ name='mozdok-garrison-red'}) - } - }), - presets.upgrades.supply.fuelTank:extend({ - name='mozdok-fuel-red', - products = { - presets.missions.supply.helo:extend({name='mozdok-supply-red-1'}), - presets.missions.supply.helo:extend({name='mozdok-supply-red-2'}), - presets.missions.supply.convoy_escorted:extend({name='mozdok-supply-red-3'}), - presets.missions.supply.transfer:extend({name='mozdok-transfer-red'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='mozdok-comcenter-red', - products = { - presets.defenses.red.sa10:extend({ name='mozdok-airdef-red'}), - presets.missions.patrol.aircraft:extend({name='mozdok-patrol-red', altitude=25000, range=25}), - presets.missions.attack.cas:extend({name='mozdok-cas-red', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.bai:extend({name='mozdok-cas-red', altitude=10000, expend=AI.Task.WeaponExpend.ONE}) - } - }) - }, - [2] = - { - presets.upgrades.basic.comPost:extend({ - name='mozdok-compost-blue', - products = { - presets.special.blue.infantry:extend({ name='mozdok-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='mozdok-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelTank:extend({ - name='mozdok-fuel-blue', - products = { - presets.missions.supply.helo:extend({name='mozdok-supply-blue-1'}), - presets.missions.supply.helo:extend({name='mozdok-supply-blue-2'}), - presets.missions.supply.convoy_escorted:extend({name='mozdok-supply-blue-3'}), - presets.missions.supply.transfer:extend({name='mozdok-transfer-blue'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='mozdok-comcenter-blue', - products = { - presets.defenses.blue.shorad:extend({ name='mozdok-airdef-blue'}), - presets.missions.patrol.aircraft:extend({name='mozdok-patrol-blue', altitude=25000, range=25}), - presets.missions.attack.cas:extend({name='mozdok-cas-blue', altitude=15000, cost=50, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.cas:extend({name='mozdok-cas-blue-1', altitude=15000, cost=50, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.bai:extend({name='mozdok-cas-blue', altitude=15000, cost=50, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.bai:extend({name='mozdok-cas-blue-1', altitude=15000, cost=50, expend=AI.Task.WeaponExpend.ONE}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Mozdok.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Lima.lua ]]----------------- - -zones.lima = ZoneCommand:new("Lima") -zones.lima.initialState = { side=1 } -zones.lima:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='lima-tent-red', - products = { - presets.special.red.infantry:extend({ name='lima-defense-red'}), - presets.defenses.red.infantry:extend({ name='lima-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='lima-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='lima-supply-red'}), - presets.missions.supply.helo:extend({name='lima-supply-red-1'}), - presets.missions.supply.transfer:extend({name='lima-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='lima-ammo-red', - products = { - presets.missions.attack.surface:extend({name='lima-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='lima-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='lima-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='lima-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='lima-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='lima-supply-blue'}), - presets.missions.supply.helo:extend({name='lima-supply-blue-1'}), - presets.missions.supply.transfer:extend({name='lima-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='lima-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='lima-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Lima.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Oscar.lua ]]----------------- - -zones.oscar = ZoneCommand:new("Oscar") -zones.oscar.initialState = { side=1 } -zones.oscar:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='oscar-tent-red', - products = { - presets.special.red.infantry:extend({ name='oscar-defense-red'}), - presets.defenses.red.infantry:extend({ name='oscar-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='oscar-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='oscar-supply-red'}), - presets.missions.supply.transfer:extend({name='oscar-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='oscar-ammo-red', - products = { - presets.missions.attack.surface:extend({name='oscar-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='oscar-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='oscar-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='oscar-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='oscar-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='oscar-supply-blue'}), - presets.missions.supply.transfer:extend({name='oscar-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='oscar-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='oscar-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Oscar.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Nalchik.lua ]]----------------- - -zones.nalchik = ZoneCommand:new("Nalchik") -zones.nalchik.initialState = { side=1 } -zones.nalchik.keepActive = true -zones.nalchik.isHeloSpawn = true -zones.nalchik.isPlaneSpawn = true -zones.nalchik:defineUpgrades({ - [1] = - { - presets.upgrades.basic.comPost:extend({ - name='nalchik-compost-red', - products = { - presets.special.red.infantry:extend({ name='nalchik-defense-red'}), - presets.defenses.red.infantry:extend({ name='nalchik-garrison-red'}) - } - }), - presets.upgrades.supply.fuelTank:extend({ - name='nalchik-fuel-red', - products = { - presets.missions.supply.helo:extend({name='nalchik-supply-red-1'}), - presets.missions.supply.helo:extend({name='nalchik-supply-red-2'}), - presets.missions.supply.transfer:extend({name='nalchik-transfer-red'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='nalchik-comcenter-red', - products = { - presets.defenses.red.sa3:extend({ name='nalchik-airdef-red'}), - presets.missions.attack.sead:extend({name='nalchik-sead-red', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.attack.cas:extend({name='nalchik-cas-red', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.bai:extend({name='nalchik-cas-red', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.strike:extend({name='nalchik-strike-red', altitude=30000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.patrol.aircraft:extend({name='nalchik-patrol-red', altitude=25000, range=25}), - presets.missions.patrol.aircraft:extend({name='nalchik-patrol-red-2', altitude=25000, range=25}), - presets.missions.support.awacs:extend({name='nalchik-awacs-red', altitude=30000, freq=251.2}), - presets.missions.support.tanker:extend({name='nalchik-tanker-red', altitude=30000, freq=252.2, tacan='40', variant='Drogue'}) - } - }) - }, - [2] = - { - presets.upgrades.basic.comPost:extend({ - name='nalchik-compost-blue', - products = { - presets.special.blue.infantry:extend({ name='nalchik-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='nalchik-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelTank:extend({ - name='nalchik-fuel-blue', - products = { - presets.missions.supply.helo:extend({name='nalchik-supply-blue-1'}), - presets.missions.supply.helo:extend({name='nalchik-supply-blue-2'}), - presets.missions.supply.transfer:extend({name='nalchik-transfer-blue'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='nalchik-comcenter-blue', - products = { - presets.defenses.blue.nasams:extend({ name='nalchik-airdef-blue'}), - presets.missions.support.awacs:extend({name='nalchik-awacs-blue', altitude=30000, freq=259.5}), - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Nalchik.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Digora.lua ]]----------------- - -zones.digora = ZoneCommand:new("Digora") -zones.digora.initialState = { side=1 } -zones.digora:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='digora-tent-red', - products = { - presets.special.red.infantry:extend({ name='digora-defense-red'}), - presets.defenses.red.infantry:extend({ name='digora-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='digora-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='digora-supply-red'}), - presets.missions.supply.transfer:extend({name='digora-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='digora-ammo-red', - products = { - presets.missions.attack.surface:extend({name='digora-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='digora-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='digora-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='digora-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='digora-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='digora-supply-blue'}), - presets.missions.supply.transfer:extend({name='digora-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='digora-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='digora-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Digora.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Uniform.lua ]]----------------- - -zones.uniform = ZoneCommand:new("Uniform") -zones.uniform.initialState = { side=1 } -zones.uniform:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='uniform-tent-red', - products = { - presets.special.red.infantry:extend({ name='uniform-defense-red'}), - presets.defenses.red.infantry:extend({ name='uniform-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='uniform-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='uniform-supply-red'}), - presets.missions.supply.transfer:extend({name='uniform-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='uniform-ammo-red', - products = { - presets.missions.attack.surface:extend({name='uniform-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='uniform-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='uniform-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='uniform-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='uniform-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='uniform-supply-blue'}), - presets.missions.supply.transfer:extend({name='uniform-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='uniform-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='uniform-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Uniform.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Factory.lua ]]----------------- - -zones.factory = ZoneCommand:new("Factory") -zones.factory.initialState = { side=2 } -zones.factory:defineUpgrades({ - [1] = - { - presets.upgrades.basic.tent:extend({ - name='factory-tent-red', - products = { - presets.special.red.infantry:extend({ name='factory-defense-red'}), - presets.defenses.red.infantry:extend({ name='factory-garrison-red'}) - } - }), - presets.upgrades.supply.factory1:extend({ - name='factory-prod-red-1', - products = { - presets.missions.supply.convoy:extend({ name='factory-supply-red-1'}), - presets.missions.supply.transfer:extend({name='factory-transfer-red'}) - } - }), - presets.upgrades.supply.factory2:extend({ - name='factory-prod-red-2', - products = { - presets.missions.supply.convoy:extend({ name='factory-supply-red-2', cost=2000}), - presets.missions.supply.transfer:extend({name='factory-transfer-red2'}) - } - }), - presets.upgrades.supply.factoryTank:extend({ - name='factory-tank-red-1', - products = { - } - }), - presets.upgrades.supply.factoryTank:extend({ - name='factory-tank-red-2', - products = { - } - }), - presets.upgrades.supply.factoryTank:extend({ - name='factory-tank-red-3', - products = { - } - }) - }, - [2] = - { - presets.upgrades.basic.tent:extend({ - name='factory-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='factory-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='factory-garrison-blue'}) - } - }), - presets.upgrades.supply.factory1:extend({ - name='factory-prod-blue-1', - products = { - presets.missions.supply.convoy:extend({ name='factory-supply-blue-1'}), - presets.missions.supply.transfer:extend({name='factory-transfer-blue'}) - } - }), - presets.upgrades.supply.factory2:extend({ - name='factory-prod-blue-2', - products = { - presets.missions.supply.convoy:extend({ name='factory-supply-blue-2', cost=2000}), - presets.missions.supply.transfer:extend({name='factory-transfer-blue2'}) - } - }), - presets.upgrades.supply.factoryTank:extend({ - name='factory-tank-blue-1', - products = { - } - }), - presets.upgrades.supply.factoryTank:extend({ - name='factory-tank-blue-2', - products = { - } - }), - presets.upgrades.supply.factoryTank:extend({ - name='factory-tank-blue-3', - products = { - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Factory.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Senaki.lua ]]----------------- - -zones.senaki = ZoneCommand:new("Senaki") -zones.senaki.initialState = { side=1 } -zones.senaki.keepActive = true -zones.senaki.isHeloSpawn = true -zones.senaki.isPlaneSpawn = true -zones.senaki.maxResource = 50000 -zones.senaki:defineUpgrades({ - [1] = - { - presets.upgrades.basic.comPost:extend({ - name='senaki-compost-red', - products = { - presets.special.red.infantry:extend({ name='senaki-defense-red'}), - presets.defenses.red.infantry:extend({ name='senaki-garrison-red'}) - } - }), - presets.upgrades.supply.fuelTank:extend({ - name='senaki-fuel-red', - products = { - presets.missions.supply.helo:extend({name='senaki-supply-red-1'}), - presets.missions.supply.helo:extend({name='senaki-supply-red-2'}), - presets.missions.supply.transfer:extend({name='senaki-transfer-red'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='senaki-comcenter-red', - products = { - presets.defenses.red.sa3:extend({ name='senaki-airdef-red'}), - presets.missions.attack.sead:extend({name='senaki-sead-red', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.attack.cas:extend({name='senaki-cas-red', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.bai:extend({name='senaki-cas-red', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.strike:extend({name='senaki-strike-red', altitude=20000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.patrol.aircraft:extend({name='senaki-patrol-red', altitude=25000, range=25}), - presets.missions.patrol.aircraft:extend({name='senaki-patrol-red-2', altitude=20000, range=25}) - } - }) - }, - [2] = - { - presets.upgrades.basic.comPost:extend({ - name='senaki-compost-blue', - products = { - presets.special.blue.infantry:extend({ name='senaki-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='senaki-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelTank:extend({ - name='senaki-fuel-blue', - products = { - presets.missions.supply.helo:extend({name='senaki-supply-blue-1'}), - presets.missions.supply.helo:extend({name='senaki-supply-blue-2'}), - presets.missions.supply.transfer:extend({name='senaki-transfer-blue'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='senaki-comcenter-blue', - products = { - presets.defenses.blue.shorad:extend({ name='senaki-airdef-blue'}), - presets.missions.attack.sead:extend({name='senaki-sead-blue', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.attack.cas:extend({name='senaki-cas-blue', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.bai:extend({name='senaki-cas-blue', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.strike:extend({name='senaki-strike-blue', altitude=20000, expend=AI.Task.WeaponExpend.TWO}), - presets.missions.patrol.aircraft:extend({name='senaki-patrol-blue', altitude=25000, range=25}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Senaki.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Kutaisi.lua ]]----------------- - -zones.kutaisi = ZoneCommand:new("Kutaisi") -zones.kutaisi.initialState = { side=1 } -zones.kutaisi.keepActive = true -zones.kutaisi.maxResource = 50000 -zones.kutaisi:defineUpgrades({ - [1] = - { - presets.upgrades.basic.comPost:extend({ - name='kutaisi-compost-red', - products = { - presets.special.red.infantry:extend({ name='kutaisi-defense-red'}), - presets.defenses.red.infantry:extend({ name='kutaisi-garrison-red'}) - } - }), - presets.upgrades.supply.fuelTank:extend({ - name='kutaisi-fuel-red', - products = { - presets.missions.supply.helo:extend({name='kutaisi-supply-red-1'}), - presets.missions.supply.helo:extend({name='kutaisi-supply-red-2'}), - presets.missions.supply.transfer:extend({name='kutaisi-transfer-red'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='kutaisi-comcenter-red', - products = { - presets.defenses.red.shorad:extend({ name='kutaisi-airdef-red'}), - presets.missions.attack.sead:extend({name='kutaisi-sead-red', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.attack.cas:extend({name='kutaisi-cas-red', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.bai:extend({name='kutaisi-cas-red', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.strike:extend({name='kutaisi-strike-red', altitude=20000, expend=AI.Task.WeaponExpend.HALF}), - presets.missions.patrol.aircraft:extend({name='kutaisi-patrol-red', altitude=25000, range=25}), - presets.missions.patrol.aircraft:extend({name='kutaisi-patrol-red-2', altitude=25000, range=25}) - } - }) - }, - [2] = - { - presets.upgrades.basic.comPost:extend({ - name='kutaisi-compost-blue', - products = { - presets.special.blue.infantry:extend({ name='kutaisi-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='kutaisi-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelTank:extend({ - name='kutaisi-fuel-blue', - products = { - presets.missions.supply.helo:extend({name='kutaisi-supply-blue-1'}), - presets.missions.supply.helo:extend({name='kutaisi-supply-blue-2'}), - presets.missions.supply.transfer:extend({name='kutaisi-transfer-blue'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='kutaisi-comcenter-blue', - products = { - presets.defenses.blue.shorad:extend({ name='kutaisi-airdef-blue'}), - presets.missions.attack.sead:extend({name='kutaisi-sead-blue', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.attack.cas:extend({name='kutaisi-cas-blue', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.bai:extend({name='kutaisi-cas-blue', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.strike:extend({name='kutaisi-strike-blue', altitude=20000, expend=AI.Task.WeaponExpend.TWO}), - presets.missions.patrol.aircraft:extend({name='kutaisi-patrol-blue', altitude=25000, range=25}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Kutaisi.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Prohladniy.lua ]]----------------- - -zones.prohladniy = ZoneCommand:new("Prohladniy") -zones.prohladniy.initialState = { side=1 } -zones.prohladniy:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='prohladniy-tent-red', - products = { - presets.special.red.infantry:extend({ name='prohladniy-defense-red'}), - presets.defenses.red.infantry:extend({ name='prohladniy-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='prohladniy-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='prohladniy-supply-red'}), - presets.missions.supply.transfer:extend({name='prohladniy-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='prohladniy-ammo-red', - products = { - presets.missions.attack.surface:extend({name='prohladniy-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='prohladniy-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='prohladniy-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='prohladniy-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='prohladniy-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='prohladniy-supply-blue'}), - presets.missions.supply.transfer:extend({name='prohladniy-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='prohladniy-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='prohladniy-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Prohladniy.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Tallyk.lua ]]----------------- - -zones.tallyk = ZoneCommand:new("Tallyk") -zones.tallyk.initialState = { side=1 } -zones.tallyk.keepActive = true -zones.tallyk:defineUpgrades({ - [1] = - { - presets.upgrades.basic.tent:extend({ - name='tallyk-tent-red', - products = { - presets.special.red.infantry:extend({ name='tallyk-defense-red'}), - presets.defenses.red.infantry:extend({ name='tallyk-garrison-red'}), - presets.missions.attack.surface:extend({name='tallyk-assault-red'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='tallyk-comcenter-red', - products = { - presets.defenses.red.sam:extend({ name='tallyk-sam-red'}) - } - }) - }, - [2] = - { - presets.upgrades.basic.tent:extend({ - name='tallyk-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='tallyk-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='tallyk-garrison-blue'}), - presets.missions.attack.surface:extend({name='tallyk-assault-blue'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='tallyk-comcenter-blue', - products = { - presets.defenses.blue.sam:extend({ name='tallyk-sam-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Tallyk.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Terek.lua ]]----------------- - -zones.terek = ZoneCommand:new("Terek") -zones.terek.initialState = { side=1 } -zones.terek:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='terek-tent-red', - products = { - presets.special.red.infantry:extend({ name='terek-defense-red'}), - presets.defenses.red.infantry:extend({ name='terek-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='terek-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='terek-supply-red'}), - presets.missions.supply.transfer:extend({name='terek-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='terek-ammo-red', - products = { - presets.missions.attack.surface:extend({name='terek-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='terek-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='terek-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='terek-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='terek-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='terek-supply-blue'}), - presets.missions.supply.transfer:extend({name='terek-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='terek-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='terek-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Terek.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Humara.lua ]]----------------- - -zones.humara = ZoneCommand:new("Humara") -zones.humara.initialState = { side=1 } -zones.humara:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='humara-tent-red', - products = { - presets.special.red.infantry:extend({ name='humara-defense-red'}), - presets.defenses.red.infantry:extend({ name='humara-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='humara-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='humara-supply-red'}), - presets.missions.supply.transfer:extend({name='humara-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='humara-ammo-red', - products = { - presets.missions.attack.surface:extend({name='humara-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='humara-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='humara-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='humara-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='humara-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='humara-supply-blue'}), - presets.missions.supply.transfer:extend({name='humara-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='humara-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='humara-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Humara.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Ochamchira.lua ]]----------------- - -zones.ochamchira = ZoneCommand:new("Ochamchira") -zones.ochamchira.initialState = { side=1 } -zones.ochamchira:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='ochamchira-tent-red', - products = { - presets.special.red.infantry:extend({ name='ochamchira-defense-red'}), - presets.defenses.red.infantry:extend({ name='ochamchira-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='ochamchira-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='ochamchira-supply-red'}), - presets.missions.supply.transfer:extend({name='ochamchira-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='ochamchira-ammo-red', - products = { - presets.missions.attack.surface:extend({name='ochamchira-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='ochamchira-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='ochamchira-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='ochamchira-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='ochamchira-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='ochamchira-supply-blue'}), - presets.missions.supply.transfer:extend({name='ochamchira-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='ochamchira-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='ochamchira-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Ochamchira.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/November.lua ]]----------------- - -zones.november = ZoneCommand:new("November") -zones.november.initialState = { side=1 } -zones.november.isHeloSpawn = true -zones.november:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='november-tent-red', - products = { - presets.special.red.infantry:extend({ name='november-defense-red'}), - presets.defenses.red.infantry:extend({ name='november-garrison-red'}) - } - }), - presets.upgrades.supply.fuelTankFarp:extend({ - name='november-fuel-red', - products = { - presets.missions.supply.helo:extend({name='november-supply-red'}), - presets.missions.supply.helo:extend({name='november-supply-red-1'}), - presets.missions.supply.transfer:extend({name='november-transfer-red'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='november-comcenter-red', - products = { - presets.defenses.red.shorad:extend({name='november-sam-red'}), - presets.missions.attack.helo:extend({name='november-cas-red', altitude=200, expend=AI.Task.WeaponExpend.HALF }) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='november-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='november-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='november-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelTankFarp:extend({ - name='november-fuel-blue', - products = { - presets.missions.supply.helo:extend({name='november-supply-blue'}), - presets.missions.supply.helo:extend({name='november-supply-blue-1'}), - presets.missions.supply.transfer:extend({name='november-transfer-blue'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='november-comcenter-blue', - products = { - presets.defenses.blue.shorad:extend({name='november-sam-blue'}), - presets.missions.attack.helo:extend({name='november-cas-blue', altitude=200, expend=AI.Task.WeaponExpend.HALF }) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/November.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/XRay.lua ]]----------------- - -zones.xray = ZoneCommand:new("XRay") -zones.xray.initialState = { side=1 } -zones.xray:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='xray-tent-red', - products = { - presets.special.red.infantry:extend({ name='xray-defense-red'}), - presets.defenses.red.infantry:extend({ name='xray-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='xray-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='xray-supply-red'}), - presets.missions.supply.helo:extend({name='xray-supply-red-2'}), - presets.missions.supply.transfer:extend({name='xray-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='xray-ammo-red', - products = { - presets.missions.attack.surface:extend({name='xray-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='xray-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='xray-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='xray-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='xray-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='xray-supply-blue'}), - presets.missions.supply.helo:extend({name='xray-supply-blue-2'}), - presets.missions.supply.transfer:extend({name='xray-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='xray-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='xray-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/XRay.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Whiskey.lua ]]----------------- - -zones.whiskey = ZoneCommand:new("Whiskey") -zones.whiskey.initialState = { side=1 } -zones.whiskey:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='whiskey-tent-red', - products = { - presets.special.red.infantry:extend({ name='whiskey-defense-red'}), - presets.defenses.red.infantry:extend({ name='whiskey-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='whiskey-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='whiskey-supply-red'}), - presets.missions.supply.transfer:extend({name='whiskey-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='whiskey-ammo-red', - products = { - presets.missions.attack.surface:extend({name='whiskey-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='whiskey-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='whiskey-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='whiskey-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='whiskey-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='whiskey-supply-blue'}), - presets.missions.supply.transfer:extend({name='whiskey-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='whiskey-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='whiskey-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Whiskey.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Mine.lua ]]----------------- - -zones.mine = ZoneCommand:new("Mine") -zones.mine.initialState = { side=1 } -zones.mine:defineUpgrades({ - [1] = - { - presets.upgrades.basic.tent:extend({ - name='mine-tent-red', - products = { - presets.special.red.infantry:extend({ name='mine-defense-red'}), - presets.defenses.red.infantry:extend({ name='mine-garrison-red'}) - } - }), - presets.upgrades.supply.excavator:extend({ - name='mine-excavator-red-1', - products = { - presets.missions.supply.convoy:extend({ name='mine-supply-red'}), - presets.missions.supply.transfer:extend({name='mine-transfer-red'}) - } - }), - presets.upgrades.supply.excavator:extend({ - name='mine-excavator-red-2', - products = { - presets.missions.supply.convoy:extend({ name='mine-supply-red'}), - presets.missions.supply.transfer:extend({name='mine-transfer-red'}) - } - }), - presets.upgrades.supply.excavator:extend({ - name='mine-excavator-red-3', - products = { - presets.missions.supply.convoy:extend({ name='mine-supply-red'}), - presets.missions.supply.transfer:extend({name='mine-transfer-red'}) - } - }) - }, - [2] = - { - presets.upgrades.basic.tent:extend({ - name='mine-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='mine-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='mine-garrison-blue'}) - } - }), - presets.upgrades.supply.excavator:extend({ - name='mine-excavator-blue-1', - products = { - presets.missions.supply.convoy:extend({ name='mine-supply-blue'}), - presets.missions.supply.transfer:extend({name='mine-transfer-blue'}) - } - }), - presets.upgrades.supply.excavator:extend({ - name='mine-excavator-blue-2', - products = { - presets.missions.supply.convoy:extend({ name='mine-supply-blue'}), - presets.missions.supply.transfer:extend({name='mine-transfer-blue'}) - } - }), - presets.upgrades.supply.excavator:extend({ - name='mine-excavator-blue-3', - products = { - presets.missions.supply.convoy:extend({ name='mine-supply-blue'}), - presets.missions.supply.transfer:extend({name='mine-transfer-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Mine.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Papa.lua ]]----------------- - -zones.papa = ZoneCommand:new("Papa") -zones.papa.initialState = { side=1 } -zones.papa.keepActive = true -zones.papa:defineUpgrades({ - [1] = - { - presets.upgrades.basic.tent:extend({ - name='papa-tent-red', - products = { - presets.special.red.infantry:extend({ name='papa-defense-red'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='papa-comcenter-red', - products = { - presets.defenses.red.sam:extend({ name='papa-sam-red'}) - } - }) - }, - [2] = - { - presets.upgrades.basic.tent:extend({ - name='papa-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='papa-defense-blue'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='papa-comcenter-blue', - products = { - presets.defenses.blue.sam:extend({ name='papa-sam-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Papa.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Sukhumi.lua ]]----------------- - -zones.sukhumi = ZoneCommand:new("Sukhumi") -zones.sukhumi.initialState = { side=1 } -zones.sukhumi.keepActive = true -zones.sukhumi.maxResource = 50000 -zones.sukhumi:defineUpgrades({ - [1] = - { - presets.upgrades.basic.comPost:extend({ - name='sukhumi-compost-red', - products = { - presets.special.red.infantry:extend({ name='sukhumi-defense-red'}), - presets.defenses.red.infantry:extend({ name='sukhumi-garrison-red'}) - } - }), - presets.upgrades.supply.fuelTank:extend({ - name='sukhumi-fuel-red', - products = { - presets.missions.supply.helo:extend({name='sukhumi-supply-red-1'}), - presets.missions.supply.helo:extend({name='sukhumi-supply-red-2'}), - presets.missions.supply.transfer:extend({name='sukhumi-transfer-red'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='sukhumi-comcenter-red', - products = { - presets.defenses.red.sa11:extend({ name='sukhumi-airdef-red'}), - presets.missions.attack.sead:extend({name='sukhumi-sead-red', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.attack.cas:extend({name='sukhumi-cas-red', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.bai:extend({name='sukhumi-cas-red', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.strike:extend({name='sukhumi-strike-red', altitude=20000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.patrol.aircraft:extend({name='sukhumi-patrol-red', altitude=25000, range=25}), - presets.missions.patrol.aircraft:extend({name='sukhumi-patrol-red-2', altitude=25000, range=25}) - } - }) - }, - [2] = - { - presets.upgrades.basic.comPost:extend({ - name='sukhumi-compost-blue', - products = { - presets.special.blue.infantry:extend({ name='sukhumi-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='sukhumi-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelTank:extend({ - name='sukhumi-fuel-blue', - products = { - presets.missions.supply.helo:extend({name='sukhumi-supply-blue-1'}), - presets.missions.supply.helo:extend({name='sukhumi-supply-blue-2'}), - presets.missions.supply.transfer:extend({name='sukhumi-transfer-blue'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='sukhumi-comcenter-blue', - products = { - presets.defenses.blue.nasams:extend({ name='sukhumi-airdef-blue'}), - presets.missions.attack.sead:extend({name='sukhumi-sead-blue', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.attack.cas:extend({name='sukhumi-cas-blue', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.bai:extend({name='sukhumi-cas-blue', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.strike:extend({name='sukhumi-strike-blue', altitude=20000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.patrol.aircraft:extend({name='sukhumi-patrol-blue', altitude=25000, range=25}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Sukhumi.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Farm.lua ]]----------------- - -zones.farm = ZoneCommand:new("Farm") -zones.farm.initialState = { side=1 } -zones.farm:defineUpgrades({ - [1] = - { - presets.upgrades.basic.tent:extend({ - name='farm-tent-red', - products = { - presets.special.red.infantry:extend({ name='farm-defense-red'}), - presets.defenses.red.infantry:extend({ name='farm-garrison-red'}) - } - }), - presets.upgrades.supply.farm1:extend({ - name='farm-prod-red-1', - products = { - presets.missions.supply.convoy:extend({ name='farm-supply-red'}), - presets.missions.supply.transfer:extend({name='farm-transfer-red'}) - } - }), - presets.upgrades.supply.farm2:extend({ - name='farm-prod-red-2', - products = { - presets.missions.supply.convoy:extend({ name='farm-supply-red'}), - presets.missions.supply.transfer:extend({name='farm-transfer-red'}) - } - }) - }, - [2] = - { - presets.upgrades.basic.tent:extend({ - name='farm-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='farm-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='farm-garrison-blue'}) - } - }), - presets.upgrades.supply.farm1:extend({ - name='farm-prod-blue-1', - products = { - presets.missions.supply.convoy:extend({ name='farm-supply-blue'}), - presets.missions.supply.transfer:extend({name='farm-transfer-blue'}) - } - }), - presets.upgrades.supply.farm2:extend({ - name='farm-prod-blue-2', - products = { - presets.missions.supply.convoy:extend({ name='farm-supply-blue'}), - presets.missions.supply.transfer:extend({name='farm-transfer-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Farm.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Romeo.lua ]]----------------- - -zones.romeo = ZoneCommand:new("Romeo") -zones.romeo.initialState = { side=1 } -zones.romeo:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='romeo-tent-red', - products = { - presets.special.red.infantry:extend({ name='romeo-defense-red'}), - presets.defenses.red.infantry:extend({ name='romeo-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='romeo-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='romeo-supply-red'}), - presets.missions.supply.transfer:extend({name='romeo-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='romeo-ammo-red', - products = { - presets.missions.attack.surface:extend({name='romeo-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='romeo-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='romeo-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='romeo-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='romeo-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='romeo-supply-blue'}), - presets.missions.supply.transfer:extend({name='romeo-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='romeo-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='romeo-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Romeo.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Zulu.lua ]]----------------- - -zones.zulu = ZoneCommand:new("Zulu") -zones.zulu.initialState = { side=1 } -zones.zulu:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='zulu-tent-red', - products = { - presets.special.red.infantry:extend({ name='zulu-defense-red'}), - presets.defenses.red.infantry:extend({ name='zulu-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='zulu-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='zulu-supply-red'}), - presets.missions.supply.transfer:extend({name='zulu-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='zulu-ammo-red', - products = { - presets.missions.attack.surface:extend({name='zulu-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='zulu-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='zulu-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='zulu-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='zulu-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='zulu-supply-blue'}), - presets.missions.supply.transfer:extend({name='zulu-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='zulu-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='zulu-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Zulu.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Yankee.lua ]]----------------- - -zones.yankee = ZoneCommand:new("Yankee") -zones.yankee.initialState = { side=1 } -zones.yankee:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='yankee-tent-red', - products = { - presets.special.red.infantry:extend({ name='yankee-defense-red'}), - presets.defenses.red.infantry:extend({ name='yankee-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='yankee-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='yankee-supply-red'}), - presets.missions.supply.transfer:extend({name='yankee-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='yankee-ammo-red', - products = { - presets.missions.attack.surface:extend({name='yankee-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='yankee-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='yankee-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='yankee-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='yankee-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='yankee-supply-blue'}), - presets.missions.supply.transfer:extend({name='yankee-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='yankee-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='yankee-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Yankee.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Malgobek.lua ]]----------------- - -zones.malgobek = ZoneCommand:new("Malgobek") -zones.malgobek.initialState = { side=1 } -zones.malgobek:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='malgobek-tent-red', - products = { - presets.special.red.infantry:extend({ name='malgobek-defense-red'}), - presets.defenses.red.infantry:extend({ name='malgobek-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='malgobek-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='malgobek-supply-red'}), - presets.missions.supply.transfer:extend({name='malgobek-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='malgobek-ammo-red', - products = { - presets.missions.attack.surface:extend({name='malgobek-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='malgobek-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='malgobek-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='malgobek-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='malgobek-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='malgobek-supply-blue'}), - presets.missions.supply.transfer:extend({name='malgobek-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='malgobek-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='malgobek-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Malgobek.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Kilo.lua ]]----------------- - -zones.kilo = ZoneCommand:new("Kilo") -zones.kilo.initialState = { side=1 } -zones.kilo:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='kilo-tent-red', - products = { - presets.special.red.infantry:extend({ name='kilo-defense-red'}), - presets.defenses.red.infantry:extend({ name='kilo-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='kilo-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='kilo-supply-red'}), - presets.missions.supply.transfer:extend({name='kilo-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='kilo-ammo-red', - products = { - presets.missions.attack.surface:extend({name='kilo-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='kilo-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='kilo-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='kilo-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='kilo-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='kilo-supply-blue'}), - presets.missions.supply.transfer:extend({name='kilo-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='kilo-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='kilo-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Kilo.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Quebec.lua ]]----------------- - -zones.quebec = ZoneCommand:new("Quebec") -zones.quebec.initialState = { side=1 } -zones.quebec.keepActive = true -zones.quebec:defineUpgrades({ - [1] = - { - presets.upgrades.basic.tent:extend({ - name='quebec-tent-red', - products = { - presets.special.red.infantry:extend({ name='quebec-defense-red'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='quebec-comcenter-red', - products = { - presets.defenses.red.sam:extend({ name='quebec-sam-red'}) - } - }) - }, - [2] = - { - presets.upgrades.basic.tent:extend({ - name='quebec-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='quebec-defense-blue'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='quebec-comcenter-blue', - products = { - presets.defenses.blue.sam:extend({ name='quebec-sam-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Quebec.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/OilFields.lua ]]----------------- - -zones.oilfields = ZoneCommand:new("Oil Fields") -zones.oilfields.initialState = { side=1 } -zones.oilfields:defineUpgrades({ - [1] = { - presets.upgrades.basic.outpost:extend({ - name='oilfields-outpost-red', - products = { - presets.special.red.infantry:extend({ name='oilfields-defense-red'}), - presets.defenses.red.infantry:extend({ name='oilfields-garrison-red'}) - } - }), - presets.upgrades.supply.oilPump:extend({ - name='oilfields-pump-red-1', - products = { - presets.missions.supply.transfer:extend({name='oilfields-transfer-red1'}) - } - }), - presets.upgrades.supply.oilPump:extend({ - name='oilfields-pump-red-2', - products = { - presets.missions.supply.convoy:extend({name='oilfields-supply-red-1'}) - } - }), - presets.upgrades.supply.oilPump:extend({ - name='oilfields-pump-red-3', - products = { - presets.missions.supply.transfer:extend({name='oilfields-transfer-red2'}) - } - }), - presets.upgrades.supply.oilPump:extend({ - name='oilfields-pump-red-4', - products = { - presets.missions.supply.convoy:extend({name='oilfields-supply-red-2'}) - } - }) - }, - [2] = { - presets.upgrades.basic.outpost:extend({ - name='oilfields-outpost-blue', - products = { - presets.special.blue.infantry:extend({ name='oilfields-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='oilfields-garrison-blue'}) - } - }), - presets.upgrades.supply.oilPump:extend({ - name='oilfields-pump-blue-1', - products = { - presets.missions.supply.transfer:extend({name='oilfields-transfer-blue1'}) - } - }), - presets.upgrades.supply.oilPump:extend({ - name='oilfields-pump-blue-2', - products = { - presets.missions.supply.convoy:extend({name='oilfields-supply-blue-1'}) - } - }), - presets.upgrades.supply.oilPump:extend({ - name='oilfields-pump-blue-3', - products = { - presets.missions.supply.transfer:extend({name='oilfields-transfer-blue2'}) - } - }), - presets.upgrades.supply.oilPump:extend({ - name='oilfields-pump-blue-4', - products = { - presets.missions.supply.convoy:extend({name='oilfields-supply-blue-2'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/OilFields.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Echo.lua ]]----------------- - -zones.echo = ZoneCommand:new("Echo") -zones.echo.initialState = { side=2 } -zones.echo:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='echo-tent-red', - products = { - presets.special.red.infantry:extend({ name='echo-defense-red'}), - presets.defenses.red.infantry:extend({ name='echo-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='echo-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='echo-supply-red'}), - presets.missions.supply.transfer:extend({name='echo-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='echo-ammo-red', - products = { - presets.missions.attack.surface:extend({name='echo-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='echo-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='echo-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='echo-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='echo-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='echo-supply-blue'}), - presets.missions.supply.transfer:extend({name='echo-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='echo-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='echo-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Echo.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Kobuleti.lua ]]----------------- - -zones.kobuleti = ZoneCommand:new("Kobuleti") -zones.kobuleti.initialState = { side=2 } -zones.kobuleti.keepActive = true -zones.kobuleti.isHeloSpawn = true -zones.kobuleti.isPlaneSpawn = true -zones.kobuleti.maxResource = 50000 -zones.kobuleti:defineUpgrades({ - [1] = - { - presets.upgrades.basic.comPost:extend({ - name='kobuleti-compost-red', - products = { - presets.special.red.infantry:extend({ name='kobuleti-defense-red'}), - presets.defenses.red.infantry:extend({ name='kobuleti-garrison-red'}), - presets.missions.attack.surface:extend({ name='kobuleti-assault-red'}) - } - }), - presets.upgrades.supply.fuelTank:extend({ - name='kobuleti-fuel-red', - products = { - presets.missions.supply.helo:extend({name='kobuleti-supply-red-1'}), - presets.missions.supply.helo:extend({name='kobuleti-supply-red-2'}), - presets.missions.supply.transfer:extend({name='kobuleti-transfer-red'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='kobuleti-comcenter-red', - products = { - presets.defenses.red.shorad:extend({ name='kobuleti-airdef-red'}), - presets.missions.attack.sead:extend({name='kobuleti-sead-red', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.attack.cas:extend({name='kobuleti-cas-red', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.bai:extend({name='kobuleti-cas-red', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.strike:extend({name='kobuleti-strike-red', altitude=20000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.patrol.aircraft:extend({name='kobuleti-patrol-red', altitude=25000, range=25}) - } - }) - }, - [2] = - { - presets.upgrades.basic.comPost:extend({ - name='kobuleti-compost-blue', - products = { - presets.special.blue.infantry:extend({ name='kobuleti-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='kobuleti-garrison-blue'}), - presets.missions.attack.surface:extend({ name='kobuleti-assault-blue'}) - } - }), - presets.upgrades.supply.fuelTank:extend({ - name='kobuleti-fuel-blue', - products = { - presets.missions.supply.helo:extend({name='kobuleti-supply-blue-1'}), - presets.missions.supply.helo:extend({name='kobuleti-supply-blue-2'}), - presets.missions.supply.transfer:extend({name='kobuleti-transfer-blue'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='kobuleti-comcenter-blue', - products = { - presets.defenses.blue.shorad:extend({ name='kobuleti-airdef-blue'}), - presets.missions.attack.sead:extend({name='kobuleti-sead-blue', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.attack.cas:extend({name='kobuleti-cas-blue', altitude=15000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.bai:extend({name='kobuleti-cas-blue', altitude=10000, expend=AI.Task.WeaponExpend.ONE}), - presets.missions.attack.strike:extend({name='kobuleti-strike-blue', altitude=20000, expend=AI.Task.WeaponExpend.TWO}), - presets.missions.patrol.aircraft:extend({name='kobuleti-patrol-blue', altitude=25000, range=25}), - presets.missions.support.awacs:extend({name='kobuleti-awacs-blue', altitude=30000, freq=258.5}), - presets.missions.support.tanker:extend({name='kobuleti-tanker-blue', altitude=23000, freq=258, tacan='38', variant='Boom'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Kobuleti.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Alpha.lua ]]----------------- - -zones.alpha = ZoneCommand:new('Alpha') -zones.alpha.initialState = { side=2 } -zones.alpha:defineUpgrades({ - [1] = --red side - { - presets.upgrades.basic.tent:extend({ - name = 'alpha-tent-red', - products = { - presets.special.red.infantry:extend({ name='alpha-defense-red'}), - presets.defenses.red.infantry:extend({ name='alpha-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name = 'alpha-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({ name='alpha-supply-red'}), - presets.missions.supply.transfer:extend({name='alpha-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name = 'alpha-ammo-red', - products = { - presets.missions.attack.surface:extend({ name='alpha-assault-red'}) - } - }) - }, - [2] = --blue side - { - presets.upgrades.basic.tent:extend({ - name = 'alpha-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='alpha-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='alpha-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name = 'alpha-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({ name='alpha-supply-blue'}), - presets.missions.supply.transfer:extend({name='alpha-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name = 'alpha-ammo-blue', - products = { - presets.missions.attack.surface:extend({ name='alpha-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Alpha.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Foxtrot.lua ]]----------------- - -zones.foxtrot = ZoneCommand:new("Foxtrot") -zones.foxtrot.initialState = { side=2 } -zones.foxtrot:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='foxtrot-tent-red', - products = { - presets.special.red.infantry:extend({ name='foxtrot-defense-red'}), - presets.defenses.red.infantry:extend({ name='foxtrot-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='foxtrot-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='foxtrot-supply-red'}), - presets.missions.supply.transfer:extend({name='foxtrot-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='foxtrot-ammo-red', - products = { - presets.missions.attack.surface:extend({name='foxtrot-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='foxtrot-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='foxtrot-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='foxtrot-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='foxtrot-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='foxtrot-supply-blue'}), - presets.missions.supply.transfer:extend({name='foxtrot-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='foxtrot-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='foxtrot-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Foxtrot.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Sierra.lua ]]----------------- - -zones.sierra = ZoneCommand:new("Sierra") -zones.sierra.initialState = { side=1 } -zones.sierra.isHeloSpawn = true -zones.sierra:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='sierra-tent-red', - products = { - presets.special.red.infantry:extend({ name='sierra-defense-red'}), - presets.defenses.red.infantry:extend({ name='sierra-garrison-red'}) - } - }), - presets.upgrades.supply.fuelTankFarp:extend({ - name='sierra-fuel-red', - products = { - presets.missions.supply.helo:extend({name='sierra-supply-red'}), - presets.missions.supply.helo:extend({name='sierra-supply-red-1'}), - presets.missions.supply.transfer:extend({name='sierra-transfer-red'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='sierra-comcenter-red', - products = { - presets.defenses.red.shorad:extend({name='sierra-sam-red'}), - presets.missions.attack.helo:extend({name='sierra-cas-red', altitude=200, expend=AI.Task.WeaponExpend.HALF }) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='sierra-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='sierra-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='sierra-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelTankFarp:extend({ - name='sierra-fuel-blue', - products = { - presets.missions.supply.helo:extend({name='sierra-supply-blue'}), - presets.missions.supply.helo:extend({name='sierra-supply-blue-1'}), - presets.missions.supply.transfer:extend({name='sierra-transfer-blue'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='sierra-comcenter-blue', - products = { - presets.defenses.blue.shorad:extend({name='sierra-sam-blue'}), - presets.missions.attack.helo:extend({name='sierra-cas-blue', altitude=200, expend=AI.Task.WeaponExpend.HALF }) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Sierra.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Oni.lua ]]----------------- - -zones.oni = ZoneCommand:new("Oni") -zones.oni.initialState = { side=1 } -zones.oni:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='oni-tent-red', - products = { - presets.special.red.infantry:extend({ name='oni-defense-red'}), - presets.defenses.red.infantry:extend({ name='oni-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='oni-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='oni-supply-red'}), - presets.missions.supply.helo:extend({name='oni-supply-red-2'}), - presets.missions.supply.transfer:extend({name='oni-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='oni-ammo-red', - products = { - presets.missions.attack.surface:extend({name='oni-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='oni-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='oni-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='oni-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='oni-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='oni-supply-blue'}), - presets.missions.supply.helo:extend({name='oni-supply-blue-2'}), - presets.missions.supply.transfer:extend({name='oni-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='oni-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='oni-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Oni.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Hotel.lua ]]----------------- - -zones.hotel = ZoneCommand:new("Hotel") -zones.hotel.initialState = nil -zones.hotel:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='hotel-tent-red', - products = { - presets.special.red.infantry:extend({ name='hotel-defense-red'}), - presets.defenses.red.infantry:extend({ name='hotel-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='hotel-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='hotel-supply-red'}), - presets.missions.supply.transfer:extend({name='hotel-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='hotel-ammo-red', - products = { - presets.missions.attack.surface:extend({name='hotel-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='hotel-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='hotel-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='hotel-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='hotel-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='hotel-supply-blue'}), - presets.missions.supply.transfer:extend({name='hotel-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='hotel-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='hotel-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Hotel.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Victor.lua ]]----------------- - -zones.victor = ZoneCommand:new("Victor") -zones.victor.initialState = { side=1 } -zones.victor:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='victor-tent-red', - products = { - presets.special.red.infantry:extend({ name='victor-defense-red'}), - presets.defenses.red.infantry:extend({ name='victor-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='victor-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='victor-supply-red'}), - presets.missions.supply.helo:extend({name='victor-supply-red-2'}), - presets.missions.supply.transfer:extend({name='victor-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='victor-ammo-red', - products = { - presets.missions.attack.surface:extend({name='victor-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='victor-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='victor-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='victor-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='victor-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='victor-supply-blue'}), - presets.missions.supply.helo:extend({name='victor-supply-blue-2'}), - presets.missions.supply.transfer:extend({name='victor-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='victor-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='victor-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Victor.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Tango.lua ]]----------------- - -zones.tango = ZoneCommand:new("Tango") -zones.tango.initialState = { side=1 } -zones.tango.isHeloSpawn = true -zones.tango:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='tango-tent-red', - products = { - presets.special.red.infantry:extend({ name='tango-defense-red'}), - presets.defenses.red.infantry:extend({ name='tango-garrison-red'}) - } - }), - presets.upgrades.supply.fuelTankFarp:extend({ - name='tango-fuel-red', - products = { - presets.missions.supply.helo:extend({name='tango-supply-red'}), - presets.missions.supply.helo:extend({name='tango-supply-red-1'}), - presets.missions.supply.transfer:extend({name='tango-transfer-red'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='tango-comcenter-red', - products = { - presets.defenses.red.shorad:extend({name='tango-sam-red'}), - presets.missions.attack.helo:extend({name='tango-cas-red', altitude=200, expend=AI.Task.WeaponExpend.HALF }) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='tango-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='tango-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='tango-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelTankFarp:extend({ - name='tango-fuel-blue', - products = { - presets.missions.supply.helo:extend({name='tango-supply-blue'}), - presets.missions.supply.helo:extend({name='tango-supply-blue-1'}), - presets.missions.supply.transfer:extend({name='tango-transfer-blue'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='tango-comcenter-blue', - products = { - presets.defenses.blue.shorad:extend({name='tango-sam-blue'}), - presets.missions.attack.helo:extend({name='tango-cas-blue', altitude=200, expend=AI.Task.WeaponExpend.HALF }) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Tango.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Unal.lua ]]----------------- - -zones.unal = ZoneCommand:new("Unal") -zones.unal.initialState = { side=1 } -zones.unal.isHeloSpawn = true -zones.unal:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='unal-tent-red', - products = { - presets.special.red.infantry:extend({ name='unal-defense-red'}), - presets.defenses.red.infantry:extend({ name='unal-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='unal-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='unal-supply-red'}), - presets.missions.supply.helo:extend({name='unal-supply-red-2'}), - presets.missions.supply.transfer:extend({name='unal-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='unal-ammo-red', - products = { - presets.missions.attack.surface:extend({name='unal-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='unal-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='unal-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='unal-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='unal-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='unal-supply-blue'}), - presets.missions.supply.helo:extend({name='unal-supply-blue-2'}), - presets.missions.supply.transfer:extend({name='unal-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='unal-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='unal-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Unal.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Beslan.lua ]]----------------- - -zones.beslan = ZoneCommand:new("Beslan") -zones.beslan.initialState = { side=1 } -zones.beslan.keepActive = true -zones.beslan.maxResource = 50000 -zones.beslan:defineUpgrades({ - [1] = - { - presets.upgrades.basic.comPost:extend({ - name='beslan-compost-red', - products = { - presets.special.red.infantry:extend({ name='beslan-defense-red'}), - presets.defenses.red.infantry:extend({ name='beslan-garrison-red'}) - } - }), - presets.upgrades.supply.fuelTank:extend({ - name='beslan-fuel-red', - products = { - presets.missions.supply.helo:extend({name='beslan-supply-red-1'}), - presets.missions.supply.helo:extend({name='beslan-supply-red-2'}), - presets.missions.supply.convoy_escorted:extend({name='beslan-supply-red-3'}), - presets.missions.supply.transfer:extend({name='beslan-transfer-red'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='beslan-comcenter-red', - products = { - presets.defenses.red.sa5:extend({ name='beslan-airdef-red'}), - presets.missions.attack.sead:extend({name='beslan-sead-red', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.attack.strike:extend({name='beslan-strike-red', altitude=30000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.attack.strike:extend({name='beslan-strike-red-1', altitude=30000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.patrol.aircraft:extend({name='beslan-patrol-red', altitude=25000, range=25}) - } - }) - }, - [2] = - { - presets.upgrades.basic.comPost:extend({ - name='beslan-compost-blue', - products = { - presets.special.blue.infantry:extend({ name='beslan-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='beslan-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelTank:extend({ - name='beslan-fuel-blue', - products = { - presets.missions.supply.helo:extend({name='beslan-supply-blue-1'}), - presets.missions.supply.helo:extend({name='beslan-supply-blue-2'}), - presets.missions.supply.convoy_escorted:extend({name='beslan-supply-blue-3'}), - presets.missions.supply.transfer:extend({name='beslan-transfer-blue'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='beslan-comcenter-blue', - products = { - presets.defenses.blue.patriot:extend({ name='beslan-airdef-blue'}), - presets.missions.attack.sead:extend({name='beslan-sead-blue', altitude=25000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.attack.strike:extend({name='beslan-strike-blue', altitude=30000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.attack.strike:extend({name='beslan-strike-blue-1', altitude=30000, expend=AI.Task.WeaponExpend.ALL}), - presets.missions.patrol.aircraft:extend({name='beslan-patrol-blue', altitude=25000, range=25}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Beslan.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Bravo.lua ]]----------------- - -zones.bravo = ZoneCommand:new("Bravo") -zones.bravo.initialState = { side=2 } -zones.bravo:defineUpgrades({ - [1] = - { - presets.upgrades.basic.comPost:extend({ - name='bravo-compost-red', - products = { - presets.special.red.infantry:extend({ name='bravo-defense-red'}), - presets.defenses.red.infantry:extend({ name='bravo-garrison-red'}) - } - }), - presets.upgrades.supply.fuelTankFarp:extend({ - name='bravo-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({ name='bravo-supply-red'}), - presets.missions.supply.transfer:extend({name='bravo-transfer-red'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='bravo-comcenter-red', - products = { - presets.defenses.red.shorad:extend({ name='bravo-airdef-red'}), - presets.missions.attack.helo:extend({name='bravo-attack-red', altitude=200, expend=AI.Task.WeaponExpend.HALF}) - } - }) - }, - [2] = - { - presets.upgrades.basic.comPost:extend({ - name='bravo-compost-blue', - products = { - presets.special.blue.infantry:extend({ name='bravo-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='bravo-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelTankFarp:extend({ - name='bravo-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({ name='bravo-supply-blue'}), - presets.missions.supply.transfer:extend({name='bravo-transfer-blue'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='bravo-comcenter-blue', - products = { - presets.defenses.blue.shorad:extend({ name='bravo-airdef-blue'}), - presets.missions.attack.helo:extend({name='bravo-attack-blue', altitude=200, expend=AI.Task.WeaponExpend.HALF }) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Bravo.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/WeaponDepot.lua ]]----------------- - -zones.weapondepot = ZoneCommand:new("Weapon Depot") -zones.weapondepot.initialState = { side=1 } -zones.weapondepot:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='weapons-tent-red', - products = { - presets.special.red.infantry:extend({ name='weapons-defense-red'}), - presets.defenses.red.infantry:extend({ name='weapons-garrison-red'}) - } - }), - presets.upgrades.supply.ammoDepot:extend({ - name='weapons-ammo-red-1', - products = { - presets.missions.supply.convoy:extend({name='weapons-supply-red-1'}), - presets.missions.supply.transfer:extend({name='weapons-transfer-red-1'}) - } - }), - presets.upgrades.supply.ammoDepot:extend({ - name='weapons-ammo-red-2', - products = { - presets.missions.supply.convoy:extend({name='weapons-supply-red-2'}), - presets.missions.supply.transfer:extend({name='weapons-transfer-red-2'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='weapons-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='weapons-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='weapons-garrison-blue'}) - } - }), - presets.upgrades.supply.ammoDepot:extend({ - name='weapons-ammo-blue-1', - products = { - presets.missions.supply.convoy:extend({name='weapons-supply-blue-1'}), - presets.missions.supply.transfer:extend({name='weapons-transfer-blue-1'}) - } - }), - presets.upgrades.supply.ammoDepot:extend({ - name='weapons-ammo-blue-2', - products = { - presets.missions.supply.convoy:extend({name='weapons-supply-blue-2'}), - presets.missions.supply.transfer:extend({name='weapons-transfer-blue-2'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/WeaponDepot.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Delta.lua ]]----------------- - -zones.delta = ZoneCommand:new("Delta") -zones.delta.initialState = { side=2 } -zones.delta:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='delta-tent-red', - products = { - presets.special.red.infantry:extend({ name='delta-defense-red'}), - presets.defenses.red.infantry:extend({ name='delta-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='delta-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='delta-supply-red'}), - presets.missions.supply.transfer:extend({name='delta-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='delta-ammo-red', - products = { - presets.missions.attack.surface:extend({name='delta-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='delta-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='delta-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='delta-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='delta-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='delta-supply-blue'}), - presets.missions.supply.transfer:extend({name='delta-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='delta-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='delta-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Delta.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Cherkessk.lua ]]----------------- - -zones.cherkessk = ZoneCommand:new("Cherkessk") -zones.cherkessk.initialState = { side=1 } -zones.cherkessk.isHeloSpawn = true -zones.cherkessk:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='cherkessk-tent-red', - products = { - presets.special.red.infantry:extend({ name='cherkessk-defense-red'}), - presets.defenses.red.infantry:extend({ name='cherkessk-garrison-red'}) - } - }), - presets.upgrades.supply.fuelTankFarp:extend({ - name='cherkessk-fuel-red', - products = { - presets.missions.supply.helo:extend({name='cherkessk-supply-red'}), - presets.missions.supply.helo:extend({name='cherkessk-supply-red-1'}), - presets.missions.supply.transfer:extend({name='cherkessk-transfer-red'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='cherkessk-comcenter-red', - products = { - presets.defenses.red.shorad:extend({name='cherkessk-sam-red'}), - presets.missions.attack.helo:extend({name='cherkessk-cas-red', altitude=200, expend=AI.Task.WeaponExpend.HALF }), - presets.missions.attack.helo:extend({name='cherkessk-cas-red-1', altitude=200, expend=AI.Task.WeaponExpend.HALF }), - presets.missions.attack.surface:extend({name='cherkessk-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='cherkessk-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='cherkessk-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='cherkessk-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelTankFarp:extend({ - name='cherkessk-fuel-blue', - products = { - presets.missions.supply.helo:extend({name='cherkessk-supply-blue'}), - presets.missions.supply.helo:extend({name='cherkessk-supply-blue-1'}), - presets.missions.supply.transfer:extend({name='cherkessk-transfer-blue'}), - presets.missions.attack.surface:extend({name='cherkessk-assault-blue'}) - } - }), - presets.upgrades.airdef.comCenter:extend({ - name='cherkessk-comcenter-blue', - products = { - presets.defenses.blue.shorad:extend({name='cherkessk-sam-blue'}), - presets.missions.attack.helo:extend({name='cherkessk-cas-blue', altitude=200, expend=AI.Task.WeaponExpend.HALF }), - presets.missions.attack.helo:extend({name='cherkessk-cas-blue-1', altitude=200, expend=AI.Task.WeaponExpend.HALF }) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Cherkessk.lua ]]----------------- - - - ------------------[[ MissionSpecific/PretenseCaucasus/ZoneDefinitions/Juliett.lua ]]----------------- - -zones.juliett = ZoneCommand:new("Juliett") -zones.initialState = nil -zones.juliett:defineUpgrades({ - [1] = { - presets.upgrades.basic.tent:extend({ - name='juliett-tent-red', - products = { - presets.special.red.infantry:extend({ name='juliett-defense-red'}), - presets.defenses.red.infantry:extend({ name='juliett-garrison-red'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='juliett-fuel-red', - products = { - presets.missions.supply.convoy_escorted:extend({name='juliett-supply-red'}), - presets.missions.supply.transfer:extend({name='juliett-transfer-red'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='juliett-ammo-red', - products = { - presets.missions.attack.surface:extend({name='juliett-assault-red'}) - } - }) - }, - [2] = { - presets.upgrades.basic.tent:extend({ - name='juliett-tent-blue', - products = { - presets.special.blue.infantry:extend({ name='juliett-defense-blue'}), - presets.defenses.blue.infantry:extend({ name='juliett-garrison-blue'}) - } - }), - presets.upgrades.supply.fuelCache:extend({ - name='juliett-fuel-blue', - products = { - presets.missions.supply.convoy_escorted:extend({name='juliett-supply-blue'}), - presets.missions.supply.transfer:extend({name='juliett-transfer-blue'}) - } - }), - presets.upgrades.attack.ammoCache:extend({ - name='juliett-ammo-blue', - products = { - presets.missions.attack.surface:extend({name='juliett-assault-blue'}) - } - }) - } -}) - ------------------[[ END OF MissionSpecific/PretenseCaucasus/ZoneDefinitions/Juliett.lua ]]----------------- - - - - cm = ConnectionManager:new() - cm:addConnection('Batumi', 'Alpha') - cm:addConnection('Alpha', 'Bravo') - cm:addConnection('Bravo', 'Kobuleti') - cm:addConnection('Bravo', 'Factory') - cm:addConnection('Kobuleti', 'Factory') - cm:addConnection('Kobuleti', 'Charlie') - cm:addConnection('Foxtrot', 'Charlie') - cm:addConnection('Foxtrot', 'Kobuleti') - cm:addConnection('Delta','Foxtrot') - cm:addConnection('Delta','Kobuleti') - cm:addConnection('Delta','Factory') - cm:addConnection('Echo','Charlie') - cm:addConnection('Golf','Echo') - cm:addConnection('Golf','Foxtrot') - cm:addConnection('India','Delta') - cm:addConnection('Hotel','Golf') - cm:addConnection('Hotel','Foxtrot') - cm:addConnection('Hotel','Delta') - cm:addConnection('Hotel','India') - cm:addConnection('Juliett','Echo') - cm:addConnection('Juliett','Golf') - cm:addConnection('Senaki','Juliett') - cm:addConnection('Senaki','Golf') - cm:addConnection('Senaki','Hotel') - cm:addConnection('Kutaisi','Hotel') - cm:addConnection('Kutaisi','India') - cm:addConnection('Kilo','Juliett') - cm:addConnection('Mike','Kutaisi') - cm:addConnection('Mike','Senaki') - cm:addConnection('Romeo','Mike') - cm:addConnection('Romeo','Kutaisi') - cm:addConnection('Weapon Depot','Juliett') - cm:addConnection('Weapon Depot','Senaki') - cm:addConnection('Weapon Depot','Kilo') - cm:addConnection('November','Weapon Depot') - cm:addConnection('November','Senaki') - cm:addConnection('November','Mike') - cm:addConnection('Oil Fields','Romeo') - cm:addConnection('Quebec','Kilo') - cm:addConnection('Zugdidi','Weapon Depot') - cm:addConnection('Zugdidi','Quebec') - cm:addConnection('Zugdidi','November') - cm:addConnection('Zugdidi','Kilo') - cm:addConnection('Distillery','November') - cm:addConnection('Distillery','Mike') - cm:addConnection('Zugdidi','Papa') - cm:addConnection('November','Papa') - cm:addConnection('Sierra','Papa') - cm:addConnection('Sierra','Zugdidi') - cm:addConnection('Sierra','Uniform') - cm:addConnection('Mine','Uniform') - cm:addConnection('Tango','Quebec') - cm:addConnection('Tango','Zugdidi') - cm:addConnection('Sierra','Tango') - cm:addConnection('Whiskey','Tango') - cm:addConnection('Ochamchira','Tango') - cm:addConnection('Ochamchira','Whiskey') - cm:addConnection('Ochamchira','Farm') - cm:addConnection('Ochamchira','Zulu') - cm:addConnection('Farm','Zulu') - cm:addConnection('Sukhumi','Zulu') - cm:addConnection('Lentehi','Distillery', true, 3000) - cm:addConnection('Lentehi','Babugent', true, 5000) - cm:addConnection('Nalchik','Babugent') - cm:addConnection('Victor','Distillery', true, 2000) - cm:addConnection('Victor','Romeo') - cm:addConnection('Victor','Lentehi') - cm:addConnection('Victor','Oil Fields', true, 2000) - cm:addConnection('Victor','Oni') - cm:addConnection('Unal','Oni', true, 4500) - cm:addConnection('Beslan','Unal') - cm:addConnection('Digora','Beslan') - cm:addConnection('Digora','Unal') - cm:addConnection('Digora','Babugent') - cm:addConnection('Terek','Digora') - cm:addConnection('Terek','Nalchik') - cm:addConnection('Terek','Beslan') - cm:addConnection('Prohladniy','Terek') - cm:addConnection('Prohladniy','Nalchik') - cm:addConnection('Malgobek','Terek') - cm:addConnection('Malgobek','Beslan') - cm:addConnection('Lima','Mine') - cm:addConnection('Lima','Lentehi', true, 4000) - cm:addConnection('Tyrnyauz','Lima', true, 4000) - cm:addConnection('Tyrnyauz','Nalchik') - cm:addConnection('XRay','Sukhumi') - cm:addConnection('Oscar','Sukhumi') - cm:addConnection('Oscar','XRay') - cm:addConnection('Mozdok','Malgobek') - cm:addConnection('Mozdok','Prohladniy') - cm:addConnection('Gudauta','Oscar') - cm:addConnection('Yankee','Gudauta') - cm:addConnection('Sochi','Yankee') - cm:addConnection('Refinery','XRay', true, 4000) - cm:addConnection('Refinery','Humara') - cm:addConnection('Intel Center','Tyrnyauz') - cm:addConnection('Intel Center','Nalchik') - cm:addConnection('Intel Center','Prohladniy') - cm:addConnection('Intel Center','Kislovodsk') - cm:addConnection('Mineralnye','Intel Center') - cm:addConnection('Kislovodsk','Mineralnye') - cm:addConnection('Tallyk','Mineralnye') - cm:addConnection('Tallyk','Kislovodsk') - cm:addConnection('Power Plant','Mineralnye') - cm:addConnection('Power Plant','Tallyk') - cm:addConnection('Cherkessk','Tallyk') - cm:addConnection('Cherkessk','Power Plant') - cm:addConnection('Cherkessk','Humara') -end - -ZoneCommand.setNeighbours(cm) - -bm = BattlefieldManager:new() - -mc = MarkerCommands:new() - -pt = PlayerTracker:new(mc) - -mt = MissionTracker:new(pt, mc) - -st = SquadTracker:new() - -ct = CSARTracker:new() - -pl = PlayerLogistics:new(mt, pt, st, ct) - -gci = GCI:new(2) - -gm = GroupMonitor:new(cm) -ZoneCommand.groupMonitor = gm - --- PlayerLogistics:registerSquadGroup(squadType, groupname, weight,cost,jobtime,extracttime, squadSize) -pl:registerSquadGroup(PlayerLogistics.infantryTypes.capture, 'capture-squad', 700, 200, 60, 60*30, 4) -pl:registerSquadGroup(PlayerLogistics.infantryTypes.sabotage, 'sabotage-squad', 800, 500, 60*5, 60*30, 4) -pl:registerSquadGroup(PlayerLogistics.infantryTypes.ambush, 'ambush-squad', 900, 300, 60*20, 60*30, 5) -pl:registerSquadGroup(PlayerLogistics.infantryTypes.engineer, 'engineer-squad', 200, 1000,60, 60*30, 2) -pl:registerSquadGroup(PlayerLogistics.infantryTypes.manpads, 'manpads-squad', 900, 500, 60*20, 60*30, 5) -pl:registerSquadGroup(PlayerLogistics.infantryTypes.spy, 'spy-squad', 100, 300, 60*10, 60*30, 1) -pl:registerSquadGroup(PlayerLogistics.infantryTypes.rapier, 'rapier-squad', 1200,2000,60*60, 60*30, 8) - -Group.getByName('jtacDrone'):destroy() -CommandFunctions.jtac = JTAC:new({name = 'jtacDrone'}) - -pm = PersistenceManager:new(savefile, gm, st, ct, pl) -pm:load() - -if pm:canRestore() then - pm:restoreZones() - pm:restoreAIMissions() - pm:restoreBattlefield() - pm:restoreCsar() - pm:restoreSquads() -else - --initial states - Starter.start(zones) -end - -timer.scheduleFunction(function(param, time) - pm:save() - env.info("Mission state saved") - return time+60 -end, zones, timer.getTime()+60) - - ---make sure support units are present where needed -ensureSpawn = { - ['golf-farp-suport'] = zones.golf, - ['november-farp-suport'] = zones.november, - ['tango-farp-suport'] = zones.tango, - ['sierra-farp-suport'] = zones.sierra, - ['cherkessk-farp-suport'] = zones.cherkessk, - ['unal-farp-suport'] = zones.unal, - ['tyrnyauz-farp-suport'] = zones.tyrnyauz -} - -for grname, zn in pairs(ensureSpawn) do - local g = Group.getByName(grname) - if g then g:destroy() end -end - -timer.scheduleFunction(function(param, time) - - for grname, zn in pairs(ensureSpawn) do - local g = Group.getByName(grname) - if zn.side == 2 then - if not g then - local err, msg = pcall(mist.respawnGroup,grname,true) - if not err then - env.info("ERROR spawning "..grname) - env.info(msg) - end - end - else - if g then g:destroy() end - end - end - - return time+30 -end, {}, timer.getTime()+30) - - ---supply injection -local blueSupply = {'offmap-supply-blue-1','offmap-supply-blue-2','offmap-supply-blue-3','offmap-supply-blue-4','offmap-supply-blue-5'} -local redSupply = {'offmap-supply-red-1','offmap-supply-red-2','offmap-supply-red-3','offmap-supply-red-4','offmap-supply-red-5'} -local offmapZones = { - zones.batumi, - zones.sochi, - zones.nalchik, - zones.beslan, - zones.mozdok, - zones.mineralnye, --- zones.senaki, --- zones.sukhumi, --- zones.gudauta, --- zones.kobuleti, -} - -supplyPointRegistry = { - blue = {}, - red = {} -} - -for i,v in ipairs(blueSupply) do - local g = Group.getByName(v) - if g then - supplyPointRegistry.blue[v] = g:getUnit(1):getPoint() - end -end - -for i,v in ipairs(redSupply) do - local g = Group.getByName(v) - if g then - supplyPointRegistry.red[v] = g:getUnit(1):getPoint() - end -end - -offmapSupplyRegistry = {} -timer.scheduleFunction(function(param, time) - local availableBlue = {} - for i,v in ipairs(param.blue) do - if offmapSupplyRegistry[v] == nil then - table.insert(availableBlue, v) - end - end - - local availableRed = {} - for i,v in ipairs(param.red) do - if offmapSupplyRegistry[v] == nil then - table.insert(availableRed, v) - end - end - - local redtargets = {} - local bluetargets = {} - for _, zn in ipairs(param.offmapZones) do - if zn:needsSupplies(3000) then - local isOnRoute = false - for _,data in pairs(offmapSupplyRegistry) do - if data.zone.name == zn.name then - isOnRoute = true - break - end - end - if not isOnRoute then - if zn.side == 1 then - table.insert(redtargets, zn) - elseif zn.side == 2 then - table.insert(bluetargets, zn) - end - end - end - end - - if #availableRed > 0 and #redtargets > 0 then - local zn = redtargets[math.random(1,#redtargets)] - - local red = nil - local minD = 999999999 - for i,v in ipairs(availableRed) do - local d = mist.utils.get2DDist(zn.zone.point, supplyPointRegistry.red[v]) - if d < minD then - red = v - minD = d - end - end - - if not red then red = availableRed[math.random(1,#availableRed)] end - - local gr = red - red = nil - mist.respawnGroup(gr, true) - offmapSupplyRegistry[gr] = {zone = zn, assigned = timer.getAbsTime()} - env.info(gr..' was deployed') - timer.scheduleFunction(function(param,time) - local g = Group.getByName(param.group) - TaskExtensions.landAtAirfield(g, param.target.zone.point) - env.info(param.group..' going to '..param.target.name) - end, {group=gr, target=zn}, timer.getTime()+2) - end - - if #availableBlue > 0 and #bluetargets>0 then - local zn = bluetargets[math.random(1,#bluetargets)] - - local blue = nil - local minD = 999999999 - for i,v in ipairs(availableBlue) do - local d = mist.utils.get2DDist(zn.zone.point, supplyPointRegistry.blue[v]) - if d < minD then - blue = v - minD = d - end - end - - if not blue then blue = availableBlue[math.random(1,#availableBlue)] end - - local gr = blue - blue = nil - mist.respawnGroup(gr, true) - offmapSupplyRegistry[gr] = {zone = zn, assigned = timer.getAbsTime()} - env.info(gr..' was deployed') - timer.scheduleFunction(function(param,time) - local g = Group.getByName(param.group) - TaskExtensions.landAtAirfield(g, param.target.zone.point) - env.info(param.group..' going to '..param.target.name) - end, {group=gr, target=zn}, timer.getTime()+2) - end - - return time+(60*5) -end, {blue = blueSupply, red = redSupply, offmapZones = offmapZones}, timer.getTime()+60) - - - -timer.scheduleFunction(function(param, time) - - for groupname,data in pairs(offmapSupplyRegistry) do - local gr = Group.getByName(groupname) - if not gr then - offmapSupplyRegistry[groupname] = nil - env.info(groupname..' was destroyed') - end - - if gr and ((timer.getAbsTime() - data.assigned) > (60*60)) then - gr:destroy() - offmapSupplyRegistry[groupname] = nil - env.info(groupname..' despawned due to being alive for too long') - end - - if gr and Utils.allGroupIsLanded(gr) and Utils.someOfGroupInZone(gr, data.zone.name) then - data.zone:addResource(15000) - gr:destroy() - offmapSupplyRegistry[groupname] = nil - env.info(groupname..' landed at '..data.zone.name..' and delivered 15000 resources') - end - end - - return time+180 -end, {}, timer.getTime()+180) \ No newline at end of file +do \ No newline at end of file From 7b2424c746a5ae59489161ee6f53320249cbdca1 Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Mon, 11 Sep 2023 21:42:02 +0300 Subject: [PATCH 114/243] Added newlines to Pretense init scripts. --- resources/plugins/pretense/init_body.lua | 2 ++ resources/plugins/pretense/init_footer.lua | 1 + resources/plugins/pretense/init_header.lua | 3 ++- 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/resources/plugins/pretense/init_body.lua b/resources/plugins/pretense/init_body.lua index a4452456..33a7f627 100644 --- a/resources/plugins/pretense/init_body.lua +++ b/resources/plugins/pretense/init_body.lua @@ -1,3 +1,4 @@ + ZoneCommand.setNeighbours(cm) bm = BattlefieldManager:new() @@ -104,3 +105,4 @@ local offmapZones = { -- zones.gudauta, -- zones.kobuleti, } + diff --git a/resources/plugins/pretense/init_footer.lua b/resources/plugins/pretense/init_footer.lua index 2559cb93..455520b2 100644 --- a/resources/plugins/pretense/init_footer.lua +++ b/resources/plugins/pretense/init_footer.lua @@ -1,3 +1,4 @@ + supplyPointRegistry = { blue = {}, red = {} diff --git a/resources/plugins/pretense/init_header.lua b/resources/plugins/pretense/init_header.lua index d569e9bb..b5d49d0e 100644 --- a/resources/plugins/pretense/init_header.lua +++ b/resources/plugins/pretense/init_header.lua @@ -619,4 +619,5 @@ presets = { } zones = {} -do \ No newline at end of file +do + From 9f271cdb91c5c1bbce503b2f7e11a472f2b0c117 Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Mon, 11 Sep 2023 21:42:42 +0300 Subject: [PATCH 115/243] ntentionally don't spawn anything at OffMapSpawns in Pretense --- game/pretense/pretenseaircraftgenerator.py | 5 +++++ game/pretense/pretenseflightgroupspawner.py | 6 ++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/game/pretense/pretenseaircraftgenerator.py b/game/pretense/pretenseaircraftgenerator.py index 5abec403..2437bab3 100644 --- a/game/pretense/pretenseaircraftgenerator.py +++ b/game/pretense/pretenseaircraftgenerator.py @@ -32,6 +32,7 @@ from game.theater.controlpoint import ( Airfield, ControlPoint, Fob, + OffMapSpawn, ) from game.unitmap import UnitMap from game.missiongenerator.aircraft.aircraftpainter import AircraftPainter @@ -121,6 +122,10 @@ class PretenseAircraftGenerator: num_of_strike = 0 num_of_cap = 0 for squadron in cp.squadrons: + # Intentionally don't spawn anything at OffMapSpawns in Pretense + if isinstance(squadron.location, OffMapSpawn): + continue + squadron.owned_aircraft += 1 squadron.untasked_aircraft += 1 package = Package(cp, squadron.flight_db, auto_asap=False) diff --git a/game/pretense/pretenseflightgroupspawner.py b/game/pretense/pretenseflightgroupspawner.py index 908edcc0..880ad88d 100644 --- a/game/pretense/pretenseflightgroupspawner.py +++ b/game/pretense/pretenseflightgroupspawner.py @@ -17,7 +17,7 @@ from game.ato.starttype import StartType from game.missiongenerator.aircraft.flightgroupspawner import FlightGroupSpawner from game.missiongenerator.missiondata import MissionData from game.naming import NameGenerator -from game.theater import Airfield, ControlPoint, Fob, NavalControlPoint +from game.theater import Airfield, ControlPoint, Fob, NavalControlPoint, OffMapSpawn from game.utils import feet, meters @@ -67,11 +67,13 @@ class PretenseFlightGroupSpawner(FlightGroupSpawner): cp = self.flight.departure name = namegen.next_pretense_aircraft_name(cp, self.flight) - print(name) try: if self.start_type is StartType.IN_FLIGHT: group = self._generate_over_departure(name, cp) return group + elif isinstance(cp, OffMapSpawn): + # Intentionally don't spawn anything at OffMapSpawns in Pretense + logging.info(f"Skipping flight generation for off-map spawn {cp}.") elif isinstance(cp, NavalControlPoint): group_name = cp.get_carrier_group_name() carrier_group = self.mission.find_group(group_name) From 7d0234820cdfc68c55d490d2c59752de785d4441 Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Mon, 11 Sep 2023 21:43:29 +0300 Subject: [PATCH 116/243] First version of PretenseLuaGenerator, inherited from LuaGenerator --- game/pretense/pretenseluagenerator.py | 104 ++++++++++++++++++++-- game/pretense/pretensemissiongenerator.py | 4 +- 2 files changed, 101 insertions(+), 7 deletions(-) diff --git a/game/pretense/pretenseluagenerator.py b/game/pretense/pretenseluagenerator.py index f8698ec7..d860abd3 100644 --- a/game/pretense/pretenseluagenerator.py +++ b/game/pretense/pretenseluagenerator.py @@ -13,23 +13,30 @@ from dcs.triggers import TriggerStart from game.ato import FlightType from game.dcs.aircrafttype import AircraftType +from game.missiongenerator.luagenerator import LuaGenerator from game.plugins import LuaPluginManager -from game.theater import TheaterGroundObject +from game.theater import TheaterGroundObject, Airfield from game.theater.iadsnetwork.iadsrole import IadsRole from game.utils import escape_string_for_lua -from .missiondata import MissionData +from game.missiongenerator.missiondata import MissionData if TYPE_CHECKING: from game import Game -class LuaGenerator: +class PretenseLuaGenerator(LuaGenerator): def __init__( self, game: Game, mission: Mission, mission_data: MissionData, ) -> None: + super().__init__( + game, + mission, + mission_data, + ) + self.game = game self.mission = mission self.mission_data = mission_data @@ -207,8 +214,95 @@ class LuaGenerator: 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()))) + trigger = TriggerStart(comment="Pretense init") + + init_header_file = open("./resources/plugins/pretense/init_header.lua", "r") + init_header = init_header_file.read() + + lua_string = "" + lua_data = LuaData("products") + + for cp in self.game.theater.controlpoints: + cp_name_trimmed = "".join([i for i in cp.name.lower() if i.isalnum()]) + cp_side = 2 if cp.captured else 1 + + lua_string += f"zones.{cp_name_trimmed} = ZoneCommand:new('{cp.name}')\n" + lua_string += ( + f"zones.{cp_name_trimmed}.initialState = " + + "{ side=" + + str(cp_side) + + " }\n" + ) + lua_string += f"zones.{cp_name_trimmed}.keepActive = true\n" + if cp.has_helipads: + lua_string += f"zones.{cp_name_trimmed}.isHeloSpawn = true\n" + if isinstance(cp, Airfield) or cp.has_ground_spawns: + lua_string += f"zones.{cp_name_trimmed}.isPlaneSpawn = true\n" + lua_string += f"zones.{cp_name_trimmed}.maxResource = 50000\n" + lua_string += f"zones.{cp_name_trimmed}:defineUpgrades(" + "{\n" + lua_string += " [1] = { --red side\n" + lua_string += " presets.upgrades.basic.tent:extend({\n" + lua_string += f" name='{cp_name_trimmed}-tent-red',\n" + lua_string += " products = {\n" + lua_string += " presets.special.red.infantry:extend({ name='mike-defense-red'})\n" + lua_string += " }\n" + lua_string += " }),\n" + lua_string += " presets.upgrades.basic.comPost:extend({\n" + lua_string += " name = 'batumi-com-red',\n" + lua_string += " products = {" + lua_string += " presets.special.red.infantry:extend({ name='batumi-defense-red'}),\n" + lua_string += " presets.defenses.red.infantry:extend({ name='batumi-garrison-red' })\n" + lua_string += " }\n" + lua_string += " }),\n" + lua_string += " },\n" + lua_string += " [2] = --blue side\n" + lua_string += " {\n" + lua_string += " presets.upgrades.basic.tent:extend({\n" + lua_string += " name='mike-tent-blue',\n" + lua_string += " products = {\n" + lua_string += " presets.special.blue.infantry:extend({ name='mike-defense-blue'})\n" + lua_string += " }\n" + lua_string += " }),\n" + lua_string += " presets.upgrades.basic.comPost:extend({\n" + lua_string += " name = 'batumi-com-blue',\n" + lua_string += " products = {" + lua_string += " presets.special.blue.infantry:extend({ name='batumi-defense-blue'}),\n" + lua_string += " presets.defenses.blue.infantry:extend({ name='batumi-garrison-blue' })\n" + lua_string += " }\n" + lua_string += " }),\n" + lua_string += " presets.upgrades.supply.fuelTank:extend({\n" + lua_string += " name = 'batumi-fueltank-blue',\n" + lua_string += " products = {\n" + lua_string += " presets.missions.supply.convoy_escorted:extend({ name='batumi-supply-convoy-1'}),\n" + lua_string += " presets.missions.supply.helo:extend({ name='batumi-supply-blue-1' }),\n" + lua_string += " presets.missions.supply.transfer:extend({name='batumi-transfer-blue'})\n" + lua_string += " }\n" + lua_string += " }),\n" + lua_string += " presets.upgrades.airdef.comCenter:extend({\n" + lua_string += " name = 'batumi-mission-command-blue',\n" + lua_string += " products = {\n" + lua_string += " presets.defenses.blue.shorad:extend({ name='batumi-sam-blue' }),\n" + lua_string += " presets.missions.attack.sead:extend({name='batumi-sead-blue-1', altitude=25000, expend=AI.Task.WeaponExpend.ALL}),\n" + lua_string += " presets.missions.attack.cas:extend({name='batumi-cas-blue-1', altitude=15000, expend=AI.Task.WeaponExpend.ONE}),\n" + lua_string += " presets.missions.attack.bai:extend({name='batumi-cas-blue-1', altitude=10000, expend=AI.Task.WeaponExpend.ONE}),\n" + lua_string += " presets.missions.attack.strike:extend({name='batumi-strike-blue-1', altitude=20000, expend=AI.Task.WeaponExpend.ALL}),\n" + lua_string += " presets.missions.patrol.aircraft:extend({name='batumi-patrol-blue-1', altitude=25000, range=25}),\n" + lua_string += " presets.missions.support.awacs:extend({name='batumi-awacs-blue', altitude=30000, freq=257.5}),\n" + lua_string += " presets.missions.support.tanker:extend({name='batumi-tanker-blue', altitude=25000, freq=257, tacan='37', variant=\"Drogue\"})\n" + lua_string += " }\n" + lua_string += " })\n" + lua_string += " }\n" + lua_string += "})\n" + + init_body_file = open("./resources/plugins/pretense/init_body.lua", "r") + init_body = init_body_file.read() + + init_footer_file = open("./resources/plugins/pretense/init_footer.lua", "r") + init_footer = init_footer_file.read() + + lua_string = init_header + lua_string + init_body + init_footer + + trigger.add_action(DoScript(String(lua_string))) self.mission.triggerrules.triggers.append(trigger) def inject_lua_trigger(self, contents: str, comment: str) -> None: diff --git a/game/pretense/pretensemissiongenerator.py b/game/pretense/pretensemissiongenerator.py index d39b338c..ed220130 100644 --- a/game/pretense/pretensemissiongenerator.py +++ b/game/pretense/pretensemissiongenerator.py @@ -35,9 +35,9 @@ from game.missiongenerator.frontlineconflictdescription import ( ) from game.missiongenerator.kneeboard import KneeboardGenerator from game.missiongenerator.lasercoderegistry import LaserCodeRegistry -from game.missiongenerator.luagenerator import LuaGenerator from game.missiongenerator.missiondata import MissionData from game.missiongenerator.tgogenerator import TgoGenerator +from .pretenseluagenerator import PretenseLuaGenerator from .pretensetgogenerator import PretenseTgoGenerator from .pretensetriggergenerator import PretenseTriggerGenerator from game.missiongenerator.visualsgenerator import VisualsGenerator @@ -107,7 +107,7 @@ class PretenseMissionGenerator: PretenseTriggerGenerator(self.mission, self.game).generate() ForcedOptionsGenerator(self.mission, self.game).generate() VisualsGenerator(self.mission, self.game).generate() - LuaGenerator(self.game, self.mission, self.mission_data).generate() + PretenseLuaGenerator(self.game, self.mission, self.mission_data).generate() self.setup_combined_arms() From 15fafc20df9cd7081d1d381fb46803fa61477aa4 Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Mon, 11 Sep 2023 21:43:53 +0300 Subject: [PATCH 117/243] Removed the reference to (nonexistent) NewPretenseWizard. --- qt_ui/windows/QLiberationWindow.py | 1 - 1 file changed, 1 deletion(-) diff --git a/qt_ui/windows/QLiberationWindow.py b/qt_ui/windows/QLiberationWindow.py index c6c32c48..0551f375 100644 --- a/qt_ui/windows/QLiberationWindow.py +++ b/qt_ui/windows/QLiberationWindow.py @@ -42,7 +42,6 @@ from qt_ui.windows.groundobject.QGroundObjectMenu import QGroundObjectMenu from qt_ui.windows.infos.QInfoPanel import QInfoPanel from qt_ui.windows.logs.QLogsWindow import QLogsWindow from qt_ui.windows.newgame.QNewGameWizard import NewGameWizard -from qt_ui.windows.newgame.QNewPretenseWizard import NewPretenseWizard from qt_ui.windows.notes.QNotesWindow import QNotesWindow from qt_ui.windows.preferences.QLiberationPreferencesWindow import ( QLiberationPreferencesWindow, From ecf659097ab71b3a83200178e748997e7440037b Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Wed, 13 Sep 2023 21:01:49 +0300 Subject: [PATCH 118/243] Copied flightgroupconfigurator.py as a template/inheritance for generating Pretense campaigns from Retribution campaigns. --- .../pretenseflightgroupconfigurator.py | 288 ++++++++++++++++++ 1 file changed, 288 insertions(+) create mode 100644 game/pretense/pretenseflightgroupconfigurator.py diff --git a/game/pretense/pretenseflightgroupconfigurator.py b/game/pretense/pretenseflightgroupconfigurator.py new file mode 100644 index 00000000..ec20f532 --- /dev/null +++ b/game/pretense/pretenseflightgroupconfigurator.py @@ -0,0 +1,288 @@ +from __future__ import annotations + +import logging +from datetime import datetime +from typing import Any, Optional, TYPE_CHECKING + +from dcs import Mission +from dcs.action import DoScript +from dcs.flyingunit import FlyingUnit +from dcs.task import OptReactOnThreat +from dcs.translation import String +from dcs.triggers import TriggerStart +from dcs.unit import Skill +from dcs.unitgroup import FlyingGroup + +from game.ato import Flight, FlightType +from game.callsigns import callsign_for_support_unit +from game.data.weapons import Pylon, WeaponType as WeaponTypeEnum +from game.missiongenerator.lasercoderegistry import LaserCodeRegistry +from game.missiongenerator.logisticsgenerator import LogisticsGenerator +from game.missiongenerator.missiondata import MissionData, AwacsInfo, TankerInfo +from game.radio.radios import RadioFrequency, RadioRegistry +from game.radio.tacan import TacanBand, TacanRegistry, TacanUsage +from game.runways import RunwayData +from game.squadrons import Pilot +from .aircraftbehavior import AircraftBehavior +from .aircraftpainter import AircraftPainter +from .flightdata import FlightData +from .waypoints import WaypointGenerator +from ...ato.flightplans.aewc import AewcFlightPlan +from ...ato.flightplans.packagerefueling import PackageRefuelingFlightPlan +from ...ato.flightplans.theaterrefueling import TheaterRefuelingFlightPlan +from ...theater import Fob + +if TYPE_CHECKING: + from game import Game + + +class FlightGroupConfigurator: + def __init__( + self, + flight: Flight, + group: FlyingGroup[Any], + game: Game, + mission: Mission, + time: datetime, + radio_registry: RadioRegistry, + tacan_registry: TacanRegistry, + laser_code_registry: LaserCodeRegistry, + mission_data: MissionData, + dynamic_runways: dict[str, RunwayData], + use_client: bool, + ) -> None: + self.flight = flight + self.group = group + self.game = game + self.mission = mission + self.time = time + self.radio_registry = radio_registry + self.tacan_registry = tacan_registry + self.laser_code_registry = laser_code_registry + self.mission_data = mission_data + self.dynamic_runways = dynamic_runways + self.use_client = use_client + + def configure(self) -> FlightData: + AircraftBehavior(self.flight.flight_type).apply_to(self.flight, self.group) + AircraftPainter(self.flight, self.group).apply_livery() + self.setup_props() + self.setup_payload() + self.setup_fuel() + flight_channel = self.setup_radios() + + laser_codes: list[Optional[int]] = [] + for unit, pilot in zip(self.group.units, self.flight.roster.pilots): + self.configure_flight_member(unit, pilot, laser_codes) + + divert = None + if self.flight.divert is not None: + divert = self.flight.divert.active_runway( + self.game.theater, self.game.conditions, self.dynamic_runways + ) + + if self.flight.flight_type in [ + FlightType.TRANSPORT, + FlightType.AIR_ASSAULT, + ] and self.game.settings.plugin_option("ctld"): + transfer = None + if self.flight.flight_type == FlightType.TRANSPORT: + coalition = self.game.coalition_for(player=self.flight.blue) + transfer = coalition.transfers.transfer_for_flight(self.flight) + self.mission_data.logistics.append( + LogisticsGenerator( + self.flight, self.group, self.mission, self.game.settings, transfer + ).generate_logistics() + ) + + mission_start_time, waypoints = WaypointGenerator( + self.flight, + self.group, + self.mission, + self.game.conditions.start_time, + self.time, + self.game.settings, + self.mission_data, + ).create_waypoints() + + # Special handling for landing waypoints when: + # 1. It's an AI-only flight + # 2. Aircraft are not helicopters/VTOL + # 3. Landing waypoint does not point to an airfield + if ( + self.flight.client_count < 1 + and not self.flight.unit_type.helicopter + and not self.flight.unit_type.lha_capable + and isinstance(self.flight.squadron.location, Fob) + ): + # Need to set uncontrolled to false, otherwise the AI will skip the mission and just land + self.group.uncontrolled = False + + return FlightData( + package=self.flight.package, + aircraft_type=self.flight.unit_type, + squadron=self.flight.squadron, + flight_type=self.flight.flight_type, + units=self.group.units, + size=len(self.group.units), + friendly=self.flight.from_cp.captured, + departure_delay=mission_start_time, + departure=self.flight.departure.active_runway( + self.game.theater, self.game.conditions, self.dynamic_runways + ), + arrival=self.flight.arrival.active_runway( + self.game.theater, self.game.conditions, self.dynamic_runways + ), + divert=divert, + waypoints=waypoints, + intra_flight_channel=flight_channel, + bingo_fuel=self.flight.flight_plan.bingo_fuel, + joker_fuel=self.flight.flight_plan.joker_fuel, + custom_name=self.flight.custom_name, + laser_codes=laser_codes, + ) + + def configure_flight_member( + self, unit: FlyingUnit, pilot: Optional[Pilot], laser_codes: list[Optional[int]] + ) -> None: + player = pilot is not None and pilot.player + self.set_skill(unit, pilot) + if self.flight.loadout.has_weapon_of_type(WeaponTypeEnum.TGP) and player: + laser_codes.append(self.laser_code_registry.get_next_laser_code()) + else: + laser_codes.append(None) + settings = self.flight.coalition.game.settings + if not player or not settings.plugins.get("ewrj"): + return + jammer_required = settings.plugin_option("ewrj.ecm_required") + if jammer_required: + ecm = WeaponTypeEnum.JAMMER + if not self.flight.loadout.has_weapon_of_type(ecm): + return + ewrj_menu_trigger = TriggerStart(comment=f"EWRJ-{unit.name}") + ewrj_menu_trigger.add_action(DoScript(String(f'EWJamming("{unit.name}")'))) + self.mission.triggerrules.triggers.append(ewrj_menu_trigger) + self.group.points[0].tasks[0] = OptReactOnThreat( + OptReactOnThreat.Values.PassiveDefense + ) + + def setup_radios(self) -> RadioFrequency: + freq = self.flight.frequency + if freq is None and (freq := self.flight.package.frequency) is None: + freq = self.radio_registry.alloc_uhf() + self.flight.package.frequency = freq + if freq not in self.radio_registry.allocated_channels: + self.radio_registry.reserve(freq) + + if self.flight.flight_type in {FlightType.AEWC, FlightType.REFUELING}: + self.register_air_support(freq) + elif self.flight.frequency is None and self.flight.client_count: + freq = self.flight.unit_type.alloc_flight_radio(self.radio_registry) + + self.group.set_frequency(freq.mhz) + return freq + + def register_air_support(self, channel: RadioFrequency) -> None: + callsign = callsign_for_support_unit(self.group) + if isinstance(self.flight.flight_plan, AewcFlightPlan): + self.mission_data.awacs.append( + AwacsInfo( + group_name=str(self.group.name), + callsign=callsign, + freq=channel, + depature_location=self.flight.departure.name, + start_time=self.flight.flight_plan.patrol_start_time, + end_time=self.flight.flight_plan.patrol_end_time, + blue=self.flight.departure.captured, + ) + ) + elif isinstance( + self.flight.flight_plan, TheaterRefuelingFlightPlan + ) or isinstance(self.flight.flight_plan, PackageRefuelingFlightPlan): + if self.flight.tacan is None: + tacan = self.tacan_registry.alloc_for_band( + TacanBand.Y, TacanUsage.AirToAir + ) + else: + tacan = self.flight.tacan + self.mission_data.tankers.append( + TankerInfo( + group_name=str(self.group.name), + callsign=callsign, + variant=self.flight.unit_type.name, + freq=channel, + tacan=tacan, + start_time=self.flight.flight_plan.patrol_start_time, + end_time=self.flight.flight_plan.patrol_end_time, + blue=self.flight.departure.captured, + ) + ) + + def set_skill(self, unit: FlyingUnit, pilot: Optional[Pilot]) -> None: + if pilot is None or not pilot.player: + unit.skill = self.skill_level_for(unit, pilot) + return + + if self.use_client or "Pilot #1" not in unit.name: + unit.set_client() + else: + unit.set_player() + + def skill_level_for(self, unit: FlyingUnit, pilot: Optional[Pilot]) -> Skill: + if self.flight.squadron.player: + base_skill = Skill(self.game.settings.player_skill) + else: + base_skill = Skill(self.game.settings.enemy_skill) + + if pilot is None: + logging.error(f"Cannot determine skill level: {unit.name} has not pilot") + return base_skill + + levels = [ + Skill.Average, + Skill.Good, + Skill.High, + Skill.Excellent, + ] + current_level = levels.index(base_skill) + missions_for_skill_increase = 4 + increase = pilot.record.missions_flown // missions_for_skill_increase + capped_increase = min(current_level + increase, len(levels) - 1) + + if self.game.settings.ai_pilot_levelling: + new_level = capped_increase + else: + new_level = current_level + + return levels[new_level] + + def setup_props(self) -> None: + for prop_id, value in self.flight.props.items(): + for unit in self.group.units: + unit.set_property(prop_id, value) + + def setup_payload(self) -> None: + for p in self.group.units: + p.pylons.clear() + + loadout = self.flight.loadout + if self.game.settings.restrict_weapons_by_date: + loadout = loadout.degrade_for_date(self.flight.unit_type, self.game.date) + + for pylon_number, weapon in loadout.pylons.items(): + if weapon is None: + continue + pylon = Pylon.for_aircraft(self.flight.unit_type, pylon_number) + pylon.equip(self.group, weapon) + + def setup_fuel(self) -> None: + fuel = self.flight.state.estimate_fuel() + if fuel < 0: + logging.warning( + f"Flight {self.flight} is estimated to have no fuel at mission start. " + "This estimate does not account for external fuel tanks. Setting " + "starting fuel to 100kg." + ) + fuel = 100 + for unit, pilot in zip(self.group.units, self.flight.roster.pilots): + unit.fuel = fuel From aad48c0b7863be8e27ace557b976f49a92d366df Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Wed, 13 Sep 2023 21:55:21 +0300 Subject: [PATCH 119/243] Dynamically generate Pretense air groups (pretense/pretenseluagenerator.py) --- game/game.py | 5 + game/pretense/pretenseaircraftgenerator.py | 37 +++--- game/pretense/pretenseflightgroupspawner.py | 24 ++++ game/pretense/pretenseluagenerator.py | 122 ++++++++++++++++++-- game/pretense/pretensemissiongenerator.py | 6 +- 5 files changed, 165 insertions(+), 29 deletions(-) diff --git a/game/game.py b/game/game.py index 761403b9..31ce38e9 100644 --- a/game/game.py +++ b/game/game.py @@ -148,6 +148,11 @@ class Game: self.blue.configure_default_air_wing(air_wing_config) self.red.configure_default_air_wing(air_wing_config) + # Side, control point, mission type + self.pretense_ground_supply: dict[int, dict[str, List[str]]] = {1: {}, 2: {}} + self.pretense_ground_assault: dict[int, dict[str, List[str]]] = {1: {}, 2: {}} + self.pretense_air: dict[int, dict[str, dict[str, List[str]]]] = {1: {}, 2: {}} + self.on_load(game_still_initializing=True) def __setstate__(self, state: dict[str, Any]) -> None: diff --git a/game/pretense/pretenseaircraftgenerator.py b/game/pretense/pretenseaircraftgenerator.py index 2437bab3..248f95d8 100644 --- a/game/pretense/pretenseaircraftgenerator.py +++ b/game/pretense/pretenseaircraftgenerator.py @@ -121,6 +121,7 @@ class PretenseAircraftGenerator: num_of_cas = 0 num_of_strike = 0 num_of_cap = 0 + for squadron in cp.squadrons: # Intentionally don't spawn anything at OffMapSpawns in Pretense if isinstance(squadron.location, OffMapSpawn): @@ -134,7 +135,7 @@ class PretenseAircraftGenerator: FlightType.TRANSPORT in mission_types or FlightType.AIR_ASSAULT in mission_types ): - flight_type = FlightType.TRANSPORT + flight_type = FlightType.AIR_ASSAULT elif ( FlightType.SEAD in mission_types or FlightType.SEAD_SWEEP in mission_types @@ -210,21 +211,25 @@ class PretenseAircraftGenerator: self.ground_spawns, self.mission_data, ).create_flight_group() - # self.flights.append( - # FlightGroupConfigurator( - # flight, - # group, - # self.game, - # self.mission, - # self.time, - # self.radio_registry, - # self.tacan_registy, - # self.laser_code_registry, - # self.mission_data, - # dynamic_runways, - # self.use_client, - # ).configure() - # ) + if flight.flight_type in [ + FlightType.REFUELING, + FlightType.AEWC, + ]: + self.flights.append( + FlightGroupConfigurator( + flight, + group, + self.game, + self.mission, + self.time, + self.radio_registry, + self.tacan_registy, + self.laser_code_registry, + self.mission_data, + dynamic_runways, + self.use_client, + ).configure() + ) if self.ewrj: self._track_ewrj_flight(flight, group) diff --git a/game/pretense/pretenseflightgroupspawner.py b/game/pretense/pretenseflightgroupspawner.py index 880ad88d..4e671c2d 100644 --- a/game/pretense/pretenseflightgroupspawner.py +++ b/game/pretense/pretenseflightgroupspawner.py @@ -66,9 +66,24 @@ class PretenseFlightGroupSpawner(FlightGroupSpawner): def generate_flight_at_departure(self) -> FlyingGroup[Any]: cp = self.flight.departure name = namegen.next_pretense_aircraft_name(cp, self.flight) + cp_side = 2 if cp.captured else 1 + cp_name_trimmed = "".join([i for i in cp.name.lower() if i.isalnum()]) + flight_type = self.flight.flight_type.name + if cp_name_trimmed not in self.flight.coalition.game.pretense_air[cp_side]: + self.flight.coalition.game.pretense_air[cp_side][cp_name_trimmed] = {} + if ( + flight_type + not in self.flight.coalition.game.pretense_air[cp_side][cp_name_trimmed] + ): + self.flight.coalition.game.pretense_air[cp_side][cp_name_trimmed][ + flight_type + ] = list() try: if self.start_type is StartType.IN_FLIGHT: + self.flight.coalition.game.pretense_air[cp_side][cp_name_trimmed][ + self.flight.flight_type.name + ].append(name) group = self._generate_over_departure(name, cp) return group elif isinstance(cp, OffMapSpawn): @@ -82,6 +97,9 @@ class PretenseFlightGroupSpawner(FlightGroupSpawner): f"Carrier group {carrier_group} is a " f"{carrier_group.__class__.__name__}, expected a ShipGroup" ) + self.flight.coalition.game.pretense_air[cp_side][cp_name_trimmed][ + self.flight.flight_type.name + ].append(name) return self._generate_at_group(name, carrier_group) elif isinstance(cp, Fob): is_heli = self.flight.squadron.aircraft.helicopter @@ -109,6 +127,9 @@ class PretenseFlightGroupSpawner(FlightGroupSpawner): pad_group = self._generate_at_cp_ground_spawn(name, cp) if pad_group is not None: return pad_group + self.flight.coalition.game.pretense_air[cp_side][cp_name_trimmed][ + self.flight.flight_type.name + ].append(name) return self._generate_over_departure(name, cp) elif isinstance(cp, Airfield): is_heli = self.flight.squadron.aircraft.helicopter @@ -126,6 +147,9 @@ class PretenseFlightGroupSpawner(FlightGroupSpawner): pad_group = self._generate_at_cp_ground_spawn(name, cp) if pad_group is not None: return pad_group + self.flight.coalition.game.pretense_air[cp_side][cp_name_trimmed][ + self.flight.flight_type.name + ].append(name) return self._generate_at_airfield(name, cp) else: raise NotImplementedError( diff --git a/game/pretense/pretenseluagenerator.py b/game/pretense/pretenseluagenerator.py index d860abd3..9961c1af 100644 --- a/game/pretense/pretenseluagenerator.py +++ b/game/pretense/pretenseluagenerator.py @@ -225,6 +225,10 @@ class PretenseLuaGenerator(LuaGenerator): for cp in self.game.theater.controlpoints: cp_name_trimmed = "".join([i for i in cp.name.lower() if i.isalnum()]) cp_side = 2 if cp.captured else 1 + if cp_name_trimmed not in self.game.pretense_air[cp_side]: + self.game.pretense_air[cp_side][cp_name_trimmed] = {} + # if flight_type not in self.flight.coalition.game.pretense_air[cp_side][cp_name_trimmed]: + # self.flight.coalition.game.pretense_air[cp_side][cp_name_trimmed][flight_type] = list() lua_string += f"zones.{cp_name_trimmed} = ZoneCommand:new('{cp.name}')\n" lua_string += ( @@ -248,7 +252,7 @@ class PretenseLuaGenerator(LuaGenerator): lua_string += " }\n" lua_string += " }),\n" lua_string += " presets.upgrades.basic.comPost:extend({\n" - lua_string += " name = 'batumi-com-red',\n" + lua_string += f" name = '{cp_name_trimmed}-com-red',\n" lua_string += " products = {" lua_string += " presets.special.red.infantry:extend({ name='batumi-defense-red'}),\n" lua_string += " presets.defenses.red.infantry:extend({ name='batumi-garrison-red' })\n" @@ -258,13 +262,13 @@ class PretenseLuaGenerator(LuaGenerator): lua_string += " [2] = --blue side\n" lua_string += " {\n" lua_string += " presets.upgrades.basic.tent:extend({\n" - lua_string += " name='mike-tent-blue',\n" + lua_string += f" name='{cp_name_trimmed}-tent-blue',\n" lua_string += " products = {\n" lua_string += " presets.special.blue.infantry:extend({ name='mike-defense-blue'})\n" lua_string += " }\n" lua_string += " }),\n" lua_string += " presets.upgrades.basic.comPost:extend({\n" - lua_string += " name = 'batumi-com-blue',\n" + lua_string += f" name = '{cp_name_trimmed}-com-blue',\n" lua_string += " products = {" lua_string += " presets.special.blue.infantry:extend({ name='batumi-defense-blue'}),\n" lua_string += " presets.defenses.blue.infantry:extend({ name='batumi-garrison-blue' })\n" @@ -279,16 +283,110 @@ class PretenseLuaGenerator(LuaGenerator): lua_string += " }\n" lua_string += " }),\n" lua_string += " presets.upgrades.airdef.comCenter:extend({\n" - lua_string += " name = 'batumi-mission-command-blue',\n" + lua_string += ( + f" name = '{cp_name_trimmed}-mission-command-blue',\n" + ) lua_string += " products = {\n" - lua_string += " presets.defenses.blue.shorad:extend({ name='batumi-sam-blue' }),\n" - lua_string += " presets.missions.attack.sead:extend({name='batumi-sead-blue-1', altitude=25000, expend=AI.Task.WeaponExpend.ALL}),\n" - lua_string += " presets.missions.attack.cas:extend({name='batumi-cas-blue-1', altitude=15000, expend=AI.Task.WeaponExpend.ONE}),\n" - lua_string += " presets.missions.attack.bai:extend({name='batumi-cas-blue-1', altitude=10000, expend=AI.Task.WeaponExpend.ONE}),\n" - lua_string += " presets.missions.attack.strike:extend({name='batumi-strike-blue-1', altitude=20000, expend=AI.Task.WeaponExpend.ALL}),\n" - lua_string += " presets.missions.patrol.aircraft:extend({name='batumi-patrol-blue-1', altitude=25000, range=25}),\n" - lua_string += " presets.missions.support.awacs:extend({name='batumi-awacs-blue', altitude=30000, freq=257.5}),\n" - lua_string += " presets.missions.support.tanker:extend({name='batumi-tanker-blue', altitude=25000, freq=257, tacan='37', variant=\"Drogue\"})\n" + lua_string += ( + " presets.defenses.blue.shorad:extend({ name='" + + cp_name_trimmed + + "-sam-blue' }),\n" + ) + for mission_type in self.game.pretense_air[cp_side][cp_name_trimmed]: + if mission_type == FlightType.SEAD.name: + mission_name = "attack.sead" + for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ + mission_type + ]: + lua_string += ( + f" presets.missions.{mission_name}:extend" + + "({name='" + + air_group + + "', altitude=25000, expend=AI.Task.WeaponExpend.ALL}),\n" + ) + elif mission_type == FlightType.CAS.name: + mission_name = "attack.cas" + for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ + mission_type + ]: + lua_string += ( + f" presets.missions.{mission_name}:extend" + + "({name='" + + air_group + + "', altitude=15000, expend=AI.Task.WeaponExpend.ONE}),\n" + ) + elif mission_type == FlightType.BAI.name: + mission_name = "attack.bai" + for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ + mission_type + ]: + lua_string += ( + f" presets.missions.{mission_name}:extend" + + "({name='" + + air_group + + "', altitude=10000, expend=AI.Task.WeaponExpend.ONE}),\n" + ) + elif mission_type == FlightType.STRIKE.name: + mission_name = "attack.strike" + for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ + mission_type + ]: + lua_string += ( + f" presets.missions.{mission_name}:extend" + + "({name='" + + air_group + + "', altitude=20000, expend=AI.Task.WeaponExpend.ALL}),\n" + ) + elif mission_type == FlightType.BARCAP.name: + mission_name = "patrol.aircraft" + for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ + mission_type + ]: + lua_string += ( + f" presets.missions.{mission_name}:extend" + + "({name='" + + air_group + + "', altitude=25000, range=25}),\n" + ) + elif mission_type == FlightType.AIR_ASSAULT.name: + mission_name = "supply.helo" + for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ + mission_type + ]: + lua_string += ( + f" presets.missions.{mission_name}:extend" + + "({name='" + + air_group + + "'}),\n" + ) + elif mission_type == FlightType.REFUELING.name: + mission_name = "support.tanker" + for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ + mission_type + ]: + lua_string += ( + f" presets.missions.{mission_name}:extend" + + "({name='" + + air_group + + "', freq=257, tacan='37', variant=\"Drogue\"}),\n" + ) + for tanker in self.mission_data.tankers: + if tanker.group_name == air_group: + print(tanker) + elif mission_type == FlightType.AEWC.name: + mission_name = "support.awacs" + for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ + mission_type + ]: + lua_string += ( + f" presets.missions.{mission_name}:extend" + + "({name='" + + air_group + + "', freq=257.5}),\n" + ) + for awacs in self.mission_data.awacs: + if awacs.group_name == air_group: + print(awacs) lua_string += " }\n" lua_string += " })\n" lua_string += " }\n" diff --git a/game/pretense/pretensemissiongenerator.py b/game/pretense/pretensemissiongenerator.py index ed220130..9daaab9b 100644 --- a/game/pretense/pretensemissiongenerator.py +++ b/game/pretense/pretensemissiongenerator.py @@ -3,7 +3,7 @@ from __future__ import annotations import logging from datetime import datetime from pathlib import Path -from typing import TYPE_CHECKING, cast +from typing import TYPE_CHECKING, cast, List import dcs.lua from dataclasses import field @@ -81,6 +81,10 @@ class PretenseMissionGenerator: ) self.generation_started = True + self.game.pretense_ground_supply = {1: {}, 2: {}} + self.game.pretense_ground_assault = {1: {}, 2: {}} + self.game.pretense_air = {1: {}, 2: {}} + self.setup_mission_coalitions() self.add_airfields_to_unit_map() self.initialize_registries() From 8dfb05b0d1a7309aa632e3a78a18430d8e3cdbae Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Wed, 13 Sep 2023 22:09:08 +0300 Subject: [PATCH 120/243] Tanker and AWACS frequency, TACAN and variant handling. --- game/pretense/pretenseaircraftgenerator.py | 3 ++ game/pretense/pretenseluagenerator.py | 32 ++++++++++++++++------ 2 files changed, 27 insertions(+), 8 deletions(-) diff --git a/game/pretense/pretenseaircraftgenerator.py b/game/pretense/pretenseaircraftgenerator.py index 248f95d8..82135e57 100644 --- a/game/pretense/pretenseaircraftgenerator.py +++ b/game/pretense/pretenseaircraftgenerator.py @@ -22,6 +22,9 @@ from game.ato.flightstate import Completed, WaitingForStart from game.ato.flighttype import FlightType from game.ato.package import Package from game.ato.starttype import StartType +from game.missiongenerator.aircraft.flightgroupconfigurator import ( + FlightGroupConfigurator, +) from game.missiongenerator.lasercoderegistry import LaserCodeRegistry from game.missiongenerator.missiondata import MissionData from game.radio.radios import RadioRegistry diff --git a/game/pretense/pretenseluagenerator.py b/game/pretense/pretenseluagenerator.py index 9961c1af..9ac9beea 100644 --- a/game/pretense/pretenseluagenerator.py +++ b/game/pretense/pretenseluagenerator.py @@ -364,29 +364,45 @@ class PretenseLuaGenerator(LuaGenerator): for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ mission_type ]: + tanker_freq = 257 + tanker_tacan = 37 + for tanker in self.mission_data.tankers: + if tanker.group_name == air_group: + tanker_freq = tanker.freq.hertz / 1000000 + tanker_tacan = tanker.tacan.number + if tanker.variant == "KC-135 Stratotanker": + tanker_variant = "Boom" + else: + tanker_variant = "Drogue" lua_string += ( f" presets.missions.{mission_name}:extend" + "({name='" + air_group - + "', freq=257, tacan='37', variant=\"Drogue\"}),\n" + + "', freq='" + + str(tanker_freq) + + "', tacan='" + + str(tanker_tacan) + + "', variant='" + + tanker_variant + + "'}),\n" ) - for tanker in self.mission_data.tankers: - if tanker.group_name == air_group: - print(tanker) elif mission_type == FlightType.AEWC.name: mission_name = "support.awacs" for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ mission_type ]: + awacs_freq = 257.5 + for awacs in self.mission_data.awacs: + if awacs.group_name == air_group: + awacs_freq = awacs.freq.hertz / 1000000 lua_string += ( f" presets.missions.{mission_name}:extend" + "({name='" + air_group - + "', freq=257.5}),\n" + + "', freq=" + + str(awacs_freq) + + "}),\n" ) - for awacs in self.mission_data.awacs: - if awacs.group_name == air_group: - print(awacs) lua_string += " }\n" lua_string += " })\n" lua_string += " }\n" From d8b0283efef2352657e40b226d8d375512a42b95 Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Wed, 13 Sep 2023 22:15:23 +0300 Subject: [PATCH 121/243] Deleted pretense/pretenseflightgroupconfigurator.py since it looks like it's not needed (at least for the time being). --- .../pretenseflightgroupconfigurator.py | 288 ------------------ 1 file changed, 288 deletions(-) delete mode 100644 game/pretense/pretenseflightgroupconfigurator.py diff --git a/game/pretense/pretenseflightgroupconfigurator.py b/game/pretense/pretenseflightgroupconfigurator.py deleted file mode 100644 index ec20f532..00000000 --- a/game/pretense/pretenseflightgroupconfigurator.py +++ /dev/null @@ -1,288 +0,0 @@ -from __future__ import annotations - -import logging -from datetime import datetime -from typing import Any, Optional, TYPE_CHECKING - -from dcs import Mission -from dcs.action import DoScript -from dcs.flyingunit import FlyingUnit -from dcs.task import OptReactOnThreat -from dcs.translation import String -from dcs.triggers import TriggerStart -from dcs.unit import Skill -from dcs.unitgroup import FlyingGroup - -from game.ato import Flight, FlightType -from game.callsigns import callsign_for_support_unit -from game.data.weapons import Pylon, WeaponType as WeaponTypeEnum -from game.missiongenerator.lasercoderegistry import LaserCodeRegistry -from game.missiongenerator.logisticsgenerator import LogisticsGenerator -from game.missiongenerator.missiondata import MissionData, AwacsInfo, TankerInfo -from game.radio.radios import RadioFrequency, RadioRegistry -from game.radio.tacan import TacanBand, TacanRegistry, TacanUsage -from game.runways import RunwayData -from game.squadrons import Pilot -from .aircraftbehavior import AircraftBehavior -from .aircraftpainter import AircraftPainter -from .flightdata import FlightData -from .waypoints import WaypointGenerator -from ...ato.flightplans.aewc import AewcFlightPlan -from ...ato.flightplans.packagerefueling import PackageRefuelingFlightPlan -from ...ato.flightplans.theaterrefueling import TheaterRefuelingFlightPlan -from ...theater import Fob - -if TYPE_CHECKING: - from game import Game - - -class FlightGroupConfigurator: - def __init__( - self, - flight: Flight, - group: FlyingGroup[Any], - game: Game, - mission: Mission, - time: datetime, - radio_registry: RadioRegistry, - tacan_registry: TacanRegistry, - laser_code_registry: LaserCodeRegistry, - mission_data: MissionData, - dynamic_runways: dict[str, RunwayData], - use_client: bool, - ) -> None: - self.flight = flight - self.group = group - self.game = game - self.mission = mission - self.time = time - self.radio_registry = radio_registry - self.tacan_registry = tacan_registry - self.laser_code_registry = laser_code_registry - self.mission_data = mission_data - self.dynamic_runways = dynamic_runways - self.use_client = use_client - - def configure(self) -> FlightData: - AircraftBehavior(self.flight.flight_type).apply_to(self.flight, self.group) - AircraftPainter(self.flight, self.group).apply_livery() - self.setup_props() - self.setup_payload() - self.setup_fuel() - flight_channel = self.setup_radios() - - laser_codes: list[Optional[int]] = [] - for unit, pilot in zip(self.group.units, self.flight.roster.pilots): - self.configure_flight_member(unit, pilot, laser_codes) - - divert = None - if self.flight.divert is not None: - divert = self.flight.divert.active_runway( - self.game.theater, self.game.conditions, self.dynamic_runways - ) - - if self.flight.flight_type in [ - FlightType.TRANSPORT, - FlightType.AIR_ASSAULT, - ] and self.game.settings.plugin_option("ctld"): - transfer = None - if self.flight.flight_type == FlightType.TRANSPORT: - coalition = self.game.coalition_for(player=self.flight.blue) - transfer = coalition.transfers.transfer_for_flight(self.flight) - self.mission_data.logistics.append( - LogisticsGenerator( - self.flight, self.group, self.mission, self.game.settings, transfer - ).generate_logistics() - ) - - mission_start_time, waypoints = WaypointGenerator( - self.flight, - self.group, - self.mission, - self.game.conditions.start_time, - self.time, - self.game.settings, - self.mission_data, - ).create_waypoints() - - # Special handling for landing waypoints when: - # 1. It's an AI-only flight - # 2. Aircraft are not helicopters/VTOL - # 3. Landing waypoint does not point to an airfield - if ( - self.flight.client_count < 1 - and not self.flight.unit_type.helicopter - and not self.flight.unit_type.lha_capable - and isinstance(self.flight.squadron.location, Fob) - ): - # Need to set uncontrolled to false, otherwise the AI will skip the mission and just land - self.group.uncontrolled = False - - return FlightData( - package=self.flight.package, - aircraft_type=self.flight.unit_type, - squadron=self.flight.squadron, - flight_type=self.flight.flight_type, - units=self.group.units, - size=len(self.group.units), - friendly=self.flight.from_cp.captured, - departure_delay=mission_start_time, - departure=self.flight.departure.active_runway( - self.game.theater, self.game.conditions, self.dynamic_runways - ), - arrival=self.flight.arrival.active_runway( - self.game.theater, self.game.conditions, self.dynamic_runways - ), - divert=divert, - waypoints=waypoints, - intra_flight_channel=flight_channel, - bingo_fuel=self.flight.flight_plan.bingo_fuel, - joker_fuel=self.flight.flight_plan.joker_fuel, - custom_name=self.flight.custom_name, - laser_codes=laser_codes, - ) - - def configure_flight_member( - self, unit: FlyingUnit, pilot: Optional[Pilot], laser_codes: list[Optional[int]] - ) -> None: - player = pilot is not None and pilot.player - self.set_skill(unit, pilot) - if self.flight.loadout.has_weapon_of_type(WeaponTypeEnum.TGP) and player: - laser_codes.append(self.laser_code_registry.get_next_laser_code()) - else: - laser_codes.append(None) - settings = self.flight.coalition.game.settings - if not player or not settings.plugins.get("ewrj"): - return - jammer_required = settings.plugin_option("ewrj.ecm_required") - if jammer_required: - ecm = WeaponTypeEnum.JAMMER - if not self.flight.loadout.has_weapon_of_type(ecm): - return - ewrj_menu_trigger = TriggerStart(comment=f"EWRJ-{unit.name}") - ewrj_menu_trigger.add_action(DoScript(String(f'EWJamming("{unit.name}")'))) - self.mission.triggerrules.triggers.append(ewrj_menu_trigger) - self.group.points[0].tasks[0] = OptReactOnThreat( - OptReactOnThreat.Values.PassiveDefense - ) - - def setup_radios(self) -> RadioFrequency: - freq = self.flight.frequency - if freq is None and (freq := self.flight.package.frequency) is None: - freq = self.radio_registry.alloc_uhf() - self.flight.package.frequency = freq - if freq not in self.radio_registry.allocated_channels: - self.radio_registry.reserve(freq) - - if self.flight.flight_type in {FlightType.AEWC, FlightType.REFUELING}: - self.register_air_support(freq) - elif self.flight.frequency is None and self.flight.client_count: - freq = self.flight.unit_type.alloc_flight_radio(self.radio_registry) - - self.group.set_frequency(freq.mhz) - return freq - - def register_air_support(self, channel: RadioFrequency) -> None: - callsign = callsign_for_support_unit(self.group) - if isinstance(self.flight.flight_plan, AewcFlightPlan): - self.mission_data.awacs.append( - AwacsInfo( - group_name=str(self.group.name), - callsign=callsign, - freq=channel, - depature_location=self.flight.departure.name, - start_time=self.flight.flight_plan.patrol_start_time, - end_time=self.flight.flight_plan.patrol_end_time, - blue=self.flight.departure.captured, - ) - ) - elif isinstance( - self.flight.flight_plan, TheaterRefuelingFlightPlan - ) or isinstance(self.flight.flight_plan, PackageRefuelingFlightPlan): - if self.flight.tacan is None: - tacan = self.tacan_registry.alloc_for_band( - TacanBand.Y, TacanUsage.AirToAir - ) - else: - tacan = self.flight.tacan - self.mission_data.tankers.append( - TankerInfo( - group_name=str(self.group.name), - callsign=callsign, - variant=self.flight.unit_type.name, - freq=channel, - tacan=tacan, - start_time=self.flight.flight_plan.patrol_start_time, - end_time=self.flight.flight_plan.patrol_end_time, - blue=self.flight.departure.captured, - ) - ) - - def set_skill(self, unit: FlyingUnit, pilot: Optional[Pilot]) -> None: - if pilot is None or not pilot.player: - unit.skill = self.skill_level_for(unit, pilot) - return - - if self.use_client or "Pilot #1" not in unit.name: - unit.set_client() - else: - unit.set_player() - - def skill_level_for(self, unit: FlyingUnit, pilot: Optional[Pilot]) -> Skill: - if self.flight.squadron.player: - base_skill = Skill(self.game.settings.player_skill) - else: - base_skill = Skill(self.game.settings.enemy_skill) - - if pilot is None: - logging.error(f"Cannot determine skill level: {unit.name} has not pilot") - return base_skill - - levels = [ - Skill.Average, - Skill.Good, - Skill.High, - Skill.Excellent, - ] - current_level = levels.index(base_skill) - missions_for_skill_increase = 4 - increase = pilot.record.missions_flown // missions_for_skill_increase - capped_increase = min(current_level + increase, len(levels) - 1) - - if self.game.settings.ai_pilot_levelling: - new_level = capped_increase - else: - new_level = current_level - - return levels[new_level] - - def setup_props(self) -> None: - for prop_id, value in self.flight.props.items(): - for unit in self.group.units: - unit.set_property(prop_id, value) - - def setup_payload(self) -> None: - for p in self.group.units: - p.pylons.clear() - - loadout = self.flight.loadout - if self.game.settings.restrict_weapons_by_date: - loadout = loadout.degrade_for_date(self.flight.unit_type, self.game.date) - - for pylon_number, weapon in loadout.pylons.items(): - if weapon is None: - continue - pylon = Pylon.for_aircraft(self.flight.unit_type, pylon_number) - pylon.equip(self.group, weapon) - - def setup_fuel(self) -> None: - fuel = self.flight.state.estimate_fuel() - if fuel < 0: - logging.warning( - f"Flight {self.flight} is estimated to have no fuel at mission start. " - "This estimate does not account for external fuel tanks. Setting " - "starting fuel to 100kg." - ) - fuel = 100 - for unit, pilot in zip(self.group.units, self.flight.roster.pilots): - unit.fuel = fuel From 8253d41d376249b6fbccc04fe15ffdfd8825880d Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Thu, 14 Sep 2023 08:11:38 +0300 Subject: [PATCH 122/243] Implemented adding ground unit groups to pretense data containers. --- game/pretense/pretenseluagenerator.py | 47 ++++++++++++++++++------ game/pretense/pretensetgogenerator.py | 51 ++++++++++++++++++--------- 2 files changed, 71 insertions(+), 27 deletions(-) diff --git a/game/pretense/pretenseluagenerator.py b/game/pretense/pretenseluagenerator.py index 9ac9beea..823c97cc 100644 --- a/game/pretense/pretenseluagenerator.py +++ b/game/pretense/pretenseluagenerator.py @@ -225,8 +225,9 @@ class PretenseLuaGenerator(LuaGenerator): for cp in self.game.theater.controlpoints: cp_name_trimmed = "".join([i for i in cp.name.lower() if i.isalnum()]) cp_side = 2 if cp.captured else 1 - if cp_name_trimmed not in self.game.pretense_air[cp_side]: - self.game.pretense_air[cp_side][cp_name_trimmed] = {} + for side in range(1, 3): + if cp_name_trimmed not in self.game.pretense_air[cp_side]: + self.game.pretense_air[side][cp_name_trimmed] = {} # if flight_type not in self.flight.coalition.game.pretense_air[cp_side][cp_name_trimmed]: # self.flight.coalition.game.pretense_air[cp_side][cp_name_trimmed][flight_type] = list() @@ -248,14 +249,26 @@ class PretenseLuaGenerator(LuaGenerator): lua_string += " presets.upgrades.basic.tent:extend({\n" lua_string += f" name='{cp_name_trimmed}-tent-red',\n" lua_string += " products = {\n" - lua_string += " presets.special.red.infantry:extend({ name='mike-defense-red'})\n" + lua_string += ( + " presets.special.red.infantry:extend({ name='" + + cp_name_trimmed + + "-defense-red'})\n" + ) lua_string += " }\n" lua_string += " }),\n" lua_string += " presets.upgrades.basic.comPost:extend({\n" lua_string += f" name = '{cp_name_trimmed}-com-red',\n" lua_string += " products = {" - lua_string += " presets.special.red.infantry:extend({ name='batumi-defense-red'}),\n" - lua_string += " presets.defenses.red.infantry:extend({ name='batumi-garrison-red' })\n" + lua_string += ( + " presets.special.red.infantry:extend({ name='" + + cp_name_trimmed + + "-defense-red'}),\n" + ) + lua_string += ( + " presets.defenses.red.infantry:extend({ name='" + + cp_name_trimmed + + "-garrison-red' })\n" + ) lua_string += " }\n" lua_string += " }),\n" lua_string += " },\n" @@ -264,18 +277,32 @@ class PretenseLuaGenerator(LuaGenerator): lua_string += " presets.upgrades.basic.tent:extend({\n" lua_string += f" name='{cp_name_trimmed}-tent-blue',\n" lua_string += " products = {\n" - lua_string += " presets.special.blue.infantry:extend({ name='mike-defense-blue'})\n" + lua_string += ( + " presets.special.blue.infantry:extend({ name='" + + cp_name_trimmed + + "-defense-blue'})\n" + ) lua_string += " }\n" lua_string += " }),\n" lua_string += " presets.upgrades.basic.comPost:extend({\n" lua_string += f" name = '{cp_name_trimmed}-com-blue',\n" - lua_string += " products = {" - lua_string += " presets.special.blue.infantry:extend({ name='batumi-defense-blue'}),\n" - lua_string += " presets.defenses.blue.infantry:extend({ name='batumi-garrison-blue' })\n" + lua_string += " products = {\n" + lua_string += ( + " presets.special.blue.infantry:extend({ name='" + + cp_name_trimmed + + "-defense-blue'}),\n" + ) + lua_string += ( + " presets.defenses.blue.infantry:extend({ name='" + + cp_name_trimmed + + "-garrison-blue' })\n" + ) lua_string += " }\n" lua_string += " }),\n" lua_string += " presets.upgrades.supply.fuelTank:extend({\n" - lua_string += " name = 'batumi-fueltank-blue',\n" + lua_string += ( + " name = '" + cp_name_trimmed + "-fueltank-blue',\n" + ) lua_string += " products = {\n" lua_string += " presets.missions.supply.convoy_escorted:extend({ name='batumi-supply-convoy-1'}),\n" lua_string += " presets.missions.supply.helo:extend({ name='batumi-supply-blue-1' }),\n" diff --git a/game/pretense/pretensetgogenerator.py b/game/pretense/pretensetgogenerator.py index 56d84f54..00e0477b 100644 --- a/game/pretense/pretensetgogenerator.py +++ b/game/pretense/pretensetgogenerator.py @@ -168,37 +168,55 @@ class PretenseGroundObjectGenerator(GroundObjectGenerator): def generate(self) -> None: if self.culled: return + cp_name_trimmed = "".join( + [i for i in self.ground_object.control_point.name.lower() if i.isalnum()] + ) + cp_side = 2 if self.ground_object.control_point.captured else 1 + for side in range(1, 3): + if cp_name_trimmed not in self.game.pretense_ground_supply[cp_side]: + self.game.pretense_ground_supply[side][cp_name_trimmed] = list() + if cp_name_trimmed not in self.game.pretense_ground_assault[cp_side]: + self.game.pretense_ground_assault[side][cp_name_trimmed] = list() + print(self.game.pretense_ground_supply[cp_side][cp_name_trimmed]) for group in self.ground_object.groups: vehicle_units: list[TheaterUnit] = [] ship_units: list[TheaterUnit] = [] # Split the different unit types to be compliant to dcs limitation for unit in group.units: - cp_name_trimmed = "".join( - [ - i - for i in self.ground_object.control_point.name.lower() - if i.isalnum() - ] - ) - if unit.is_static: # Add supply convoy + group_id = self.game.next_group_id() + group_role = "supply" + group_name = f"{cp_name_trimmed}-{group_role}-{group_id}" + group.name = group_name + self.game.pretense_ground_supply[cp_side][cp_name_trimmed].append( + group_name + ) + self.generate_ground_unit_of_class( UnitClass.LOGISTICS, group, vehicle_units, cp_name_trimmed, - "supply", + group_role, PRETENSE_GROUND_UNIT_GROUP_SIZE, ) elif unit.is_vehicle and unit.alive: # Add armor group + group_id = self.game.next_group_id() + group_role = "assault" + group_name = f"{cp_name_trimmed}-{group_role}-{group_id}" + group.name = group_name + self.game.pretense_ground_supply[cp_side][cp_name_trimmed].append( + group_name + ) + self.generate_ground_unit_of_class( UnitClass.TANK, group, vehicle_units, cp_name_trimmed, - "assault", + group_role, PRETENSE_GROUND_UNIT_GROUP_SIZE - 3, ) self.generate_ground_unit_of_class( @@ -206,7 +224,7 @@ class PretenseGroundObjectGenerator(GroundObjectGenerator): group, vehicle_units, cp_name_trimmed, - "assault", + group_role, PRETENSE_GROUND_UNIT_GROUP_SIZE - 2, ) self.generate_ground_unit_of_class( @@ -214,7 +232,7 @@ class PretenseGroundObjectGenerator(GroundObjectGenerator): group, vehicle_units, cp_name_trimmed, - "assault", + group_role, PRETENSE_GROUND_UNIT_GROUP_SIZE - 1, ) self.generate_ground_unit_of_class( @@ -222,7 +240,7 @@ class PretenseGroundObjectGenerator(GroundObjectGenerator): group, vehicle_units, cp_name_trimmed, - "assault", + group_role, PRETENSE_GROUND_UNIT_GROUP_SIZE, ) self.generate_ground_unit_of_class( @@ -230,7 +248,7 @@ class PretenseGroundObjectGenerator(GroundObjectGenerator): group, vehicle_units, cp_name_trimmed, - "assault", + group_role, PRETENSE_GROUND_UNIT_GROUP_SIZE, ) self.generate_ground_unit_of_class( @@ -238,7 +256,7 @@ class PretenseGroundObjectGenerator(GroundObjectGenerator): group, vehicle_units, cp_name_trimmed, - "assault", + group_role, PRETENSE_GROUND_UNIT_GROUP_SIZE, ) if random.randrange(0, 100) > 75: @@ -247,14 +265,13 @@ class PretenseGroundObjectGenerator(GroundObjectGenerator): group, vehicle_units, cp_name_trimmed, - "assault", + group_role, PRETENSE_GROUND_UNIT_GROUP_SIZE, ) elif unit.is_ship and unit.alive: # All alive Ships ship_units.append(unit) if vehicle_units: - print(f"Generating vehicle group {vehicle_units}") self.create_vehicle_group(group.group_name, vehicle_units) if ship_units: self.create_ship_group(group.group_name, ship_units) From 1db232417fa6bee3322aab8b74995bb6c230697a Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Thu, 14 Sep 2023 08:43:43 +0300 Subject: [PATCH 123/243] Fixed missing return statement with isinstance(cp, OffMapSpawn). --- game/pretense/pretenseflightgroupspawner.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/game/pretense/pretenseflightgroupspawner.py b/game/pretense/pretenseflightgroupspawner.py index 4e671c2d..839c2b19 100644 --- a/game/pretense/pretenseflightgroupspawner.py +++ b/game/pretense/pretenseflightgroupspawner.py @@ -86,9 +86,6 @@ class PretenseFlightGroupSpawner(FlightGroupSpawner): ].append(name) group = self._generate_over_departure(name, cp) return group - elif isinstance(cp, OffMapSpawn): - # Intentionally don't spawn anything at OffMapSpawns in Pretense - logging.info(f"Skipping flight generation for off-map spawn {cp}.") elif isinstance(cp, NavalControlPoint): group_name = cp.get_carrier_group_name() carrier_group = self.mission.find_group(group_name) From 0faa27dca58df4a3d255cfa66d0378d91432f8fe Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Thu, 14 Sep 2023 08:44:23 +0300 Subject: [PATCH 124/243] Now clears Retribution triggers when generating a Pretense campaign. --- game/pretense/pretenseluagenerator.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/game/pretense/pretenseluagenerator.py b/game/pretense/pretenseluagenerator.py index 823c97cc..cb1e34c7 100644 --- a/game/pretense/pretenseluagenerator.py +++ b/game/pretense/pretenseluagenerator.py @@ -53,6 +53,8 @@ class PretenseLuaGenerator(LuaGenerator): self.mission.triggerrules.triggers.append(t) def generate_plugin_data(self) -> None: + self.mission.triggerrules.triggers.clear() + lua_data = LuaData("dcsRetribution") install_path = lua_data.add_item("installPath") @@ -391,8 +393,8 @@ class PretenseLuaGenerator(LuaGenerator): for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ mission_type ]: - tanker_freq = 257 - tanker_tacan = 37 + tanker_freq = 257.0 + tanker_tacan = 37.0 for tanker in self.mission_data.tankers: if tanker.group_name == air_group: tanker_freq = tanker.freq.hertz / 1000000 From 1dd9089477c5bfc590756750b863fff387a4402d Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Thu, 14 Sep 2023 21:10:50 +0300 Subject: [PATCH 125/243] First version of the generated Pretense campaign running in-game. --- game/pretense/pretenseluagenerator.py | 356 ++++++++-------------- game/pretense/pretensetriggergenerator.py | 5 +- resources/plugins/pretense/init_body.lua | 2 + 3 files changed, 123 insertions(+), 240 deletions(-) diff --git a/game/pretense/pretenseluagenerator.py b/game/pretense/pretenseluagenerator.py index cb1e34c7..2d0e2dd3 100644 --- a/game/pretense/pretenseluagenerator.py +++ b/game/pretense/pretenseluagenerator.py @@ -55,174 +55,17 @@ class PretenseLuaGenerator(LuaGenerator): def generate_plugin_data(self) -> None: self.mission.triggerrules.triggers.clear() - 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)) - 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) + self.inject_plugin_script("base", "mist_4_5_107.lua", "mist_4_5_107") + self.inject_plugin_script( + "pretense", "pretense_compiled.lua", "pretense_compiled" + ) trigger = TriggerStart(comment="Pretense init") init_header_file = open("./resources/plugins/pretense/init_header.lua", "r") init_header = init_header_file.read() - lua_string = "" - lua_data = LuaData("products") + lua_string_zones = "" for cp in self.game.theater.controlpoints: cp_name_trimmed = "".join([i for i in cp.name.lower() if i.isalnum()]) @@ -230,93 +73,129 @@ class PretenseLuaGenerator(LuaGenerator): for side in range(1, 3): if cp_name_trimmed not in self.game.pretense_air[cp_side]: self.game.pretense_air[side][cp_name_trimmed] = {} - # if flight_type not in self.flight.coalition.game.pretense_air[cp_side][cp_name_trimmed]: - # self.flight.coalition.game.pretense_air[cp_side][cp_name_trimmed][flight_type] = list() - - lua_string += f"zones.{cp_name_trimmed} = ZoneCommand:new('{cp.name}')\n" - lua_string += ( + if cp_name_trimmed not in self.game.pretense_ground_supply[cp_side]: + self.game.pretense_ground_supply[side][cp_name_trimmed] = list() + if cp_name_trimmed not in self.game.pretense_ground_assault[cp_side]: + self.game.pretense_ground_assault[side][cp_name_trimmed] = list() + lua_string_zones += ( + f"zones.{cp_name_trimmed} = ZoneCommand:new('{cp.name}')\n" + ) + lua_string_zones += ( f"zones.{cp_name_trimmed}.initialState = " + "{ side=" + str(cp_side) + " }\n" ) - lua_string += f"zones.{cp_name_trimmed}.keepActive = true\n" + lua_string_zones += f"zones.{cp_name_trimmed}.keepActive = true\n" + max_resource = 20000 if cp.has_helipads: - lua_string += f"zones.{cp_name_trimmed}.isHeloSpawn = true\n" + lua_string_zones += f"zones.{cp_name_trimmed}.isHeloSpawn = true\n" + max_resource = 30000 if isinstance(cp, Airfield) or cp.has_ground_spawns: - lua_string += f"zones.{cp_name_trimmed}.isPlaneSpawn = true\n" - lua_string += f"zones.{cp_name_trimmed}.maxResource = 50000\n" - lua_string += f"zones.{cp_name_trimmed}:defineUpgrades(" + "{\n" - lua_string += " [1] = { --red side\n" - lua_string += " presets.upgrades.basic.tent:extend({\n" - lua_string += f" name='{cp_name_trimmed}-tent-red',\n" - lua_string += " products = {\n" - lua_string += ( + lua_string_zones += f"zones.{cp_name_trimmed}.isPlaneSpawn = true\n" + if cp.has_ground_spawns: + max_resource = 40000 + if isinstance(cp, Airfield): + max_resource = 50000 + lua_string_zones += ( + f"zones.{cp_name_trimmed}.maxResource = {max_resource}\n" + ) + lua_string_zones += f"zones.{cp_name_trimmed}:defineUpgrades(" + "{\n" + lua_string_zones += " [1] = { --red side\n" + lua_string_zones += " presets.upgrades.basic.tent:extend({\n" + lua_string_zones += f" name='{cp_name_trimmed}-tent-red',\n" + lua_string_zones += " products = {\n" + lua_string_zones += ( " presets.special.red.infantry:extend({ name='" + cp_name_trimmed + "-defense-red'})\n" ) - lua_string += " }\n" - lua_string += " }),\n" - lua_string += " presets.upgrades.basic.comPost:extend({\n" - lua_string += f" name = '{cp_name_trimmed}-com-red',\n" - lua_string += " products = {" - lua_string += ( + lua_string_zones += " }\n" + lua_string_zones += " }),\n" + lua_string_zones += " presets.upgrades.basic.comPost:extend({\n" + lua_string_zones += f" name = '{cp_name_trimmed}-com-red',\n" + lua_string_zones += " products = {" + lua_string_zones += ( " presets.special.red.infantry:extend({ name='" + cp_name_trimmed + "-defense-red'}),\n" ) - lua_string += ( + lua_string_zones += ( " presets.defenses.red.infantry:extend({ name='" + cp_name_trimmed + "-garrison-red' })\n" ) - lua_string += " }\n" - lua_string += " }),\n" - lua_string += " },\n" - lua_string += " [2] = --blue side\n" - lua_string += " {\n" - lua_string += " presets.upgrades.basic.tent:extend({\n" - lua_string += f" name='{cp_name_trimmed}-tent-blue',\n" - lua_string += " products = {\n" - lua_string += ( + lua_string_zones += " }\n" + lua_string_zones += " }),\n" + lua_string_zones += " },\n" + lua_string_zones += " [2] = --blue side\n" + lua_string_zones += " {\n" + lua_string_zones += " presets.upgrades.basic.tent:extend({\n" + lua_string_zones += f" name='{cp_name_trimmed}-tent-blue',\n" + lua_string_zones += " products = {\n" + lua_string_zones += ( " presets.special.blue.infantry:extend({ name='" + cp_name_trimmed + "-defense-blue'})\n" ) - lua_string += " }\n" - lua_string += " }),\n" - lua_string += " presets.upgrades.basic.comPost:extend({\n" - lua_string += f" name = '{cp_name_trimmed}-com-blue',\n" - lua_string += " products = {\n" - lua_string += ( + lua_string_zones += " }\n" + lua_string_zones += " }),\n" + lua_string_zones += " presets.upgrades.basic.comPost:extend({\n" + lua_string_zones += f" name = '{cp_name_trimmed}-com-blue',\n" + lua_string_zones += " products = {\n" + lua_string_zones += ( " presets.special.blue.infantry:extend({ name='" + cp_name_trimmed + "-defense-blue'}),\n" ) - lua_string += ( + lua_string_zones += ( " presets.defenses.blue.infantry:extend({ name='" + cp_name_trimmed + "-garrison-blue' })\n" ) - lua_string += " }\n" - lua_string += " }),\n" - lua_string += " presets.upgrades.supply.fuelTank:extend({\n" - lua_string += ( + lua_string_zones += " }\n" + lua_string_zones += " }),\n" + lua_string_zones += " presets.upgrades.supply.fuelTank:extend({\n" + lua_string_zones += ( " name = '" + cp_name_trimmed + "-fueltank-blue',\n" ) - lua_string += " products = {\n" - lua_string += " presets.missions.supply.convoy_escorted:extend({ name='batumi-supply-convoy-1'}),\n" - lua_string += " presets.missions.supply.helo:extend({ name='batumi-supply-blue-1' }),\n" - lua_string += " presets.missions.supply.transfer:extend({name='batumi-transfer-blue'})\n" - lua_string += " }\n" - lua_string += " }),\n" - lua_string += " presets.upgrades.airdef.comCenter:extend({\n" - lua_string += ( + lua_string_zones += " products = {\n" + for ground_group in self.game.pretense_ground_supply[cp_side][ + cp_name_trimmed + ]: + lua_string_zones += ( + " presets.missions.supply.convoy:extend({ name='" + + ground_group + + "'}),\n" + ) + for ground_group in self.game.pretense_ground_assault[cp_side][ + cp_name_trimmed + ]: + lua_string_zones += ( + " presets.missions.attack.surface:extend({ name='" + + ground_group + + "'}),\n" + ) + for mission_type in self.game.pretense_air[cp_side][cp_name_trimmed]: + if mission_type == FlightType.AIR_ASSAULT.name: + mission_name = "supply.helo" + for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ + mission_type + ]: + lua_string_zones += ( + f" presets.missions.{mission_name}:extend" + + "({name='" + + air_group + + "'}),\n" + ) + lua_string_zones += " }\n" + lua_string_zones += " }),\n" + lua_string_zones += " presets.upgrades.airdef.comCenter:extend({\n" + lua_string_zones += ( f" name = '{cp_name_trimmed}-mission-command-blue',\n" ) - lua_string += " products = {\n" - lua_string += ( + lua_string_zones += " products = {\n" + lua_string_zones += ( " presets.defenses.blue.shorad:extend({ name='" + cp_name_trimmed + "-sam-blue' }),\n" @@ -327,7 +206,7 @@ class PretenseLuaGenerator(LuaGenerator): for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ mission_type ]: - lua_string += ( + lua_string_zones += ( f" presets.missions.{mission_name}:extend" + "({name='" + air_group @@ -338,7 +217,7 @@ class PretenseLuaGenerator(LuaGenerator): for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ mission_type ]: - lua_string += ( + lua_string_zones += ( f" presets.missions.{mission_name}:extend" + "({name='" + air_group @@ -349,7 +228,7 @@ class PretenseLuaGenerator(LuaGenerator): for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ mission_type ]: - lua_string += ( + lua_string_zones += ( f" presets.missions.{mission_name}:extend" + "({name='" + air_group @@ -360,7 +239,7 @@ class PretenseLuaGenerator(LuaGenerator): for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ mission_type ]: - lua_string += ( + lua_string_zones += ( f" presets.missions.{mission_name}:extend" + "({name='" + air_group @@ -371,23 +250,12 @@ class PretenseLuaGenerator(LuaGenerator): for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ mission_type ]: - lua_string += ( + lua_string_zones += ( f" presets.missions.{mission_name}:extend" + "({name='" + air_group + "', altitude=25000, range=25}),\n" ) - elif mission_type == FlightType.AIR_ASSAULT.name: - mission_name = "supply.helo" - for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ - mission_type - ]: - lua_string += ( - f" presets.missions.{mission_name}:extend" - + "({name='" - + air_group - + "'}),\n" - ) elif mission_type == FlightType.REFUELING.name: mission_name = "support.tanker" for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ @@ -403,7 +271,7 @@ class PretenseLuaGenerator(LuaGenerator): tanker_variant = "Boom" else: tanker_variant = "Drogue" - lua_string += ( + lua_string_zones += ( f" presets.missions.{mission_name}:extend" + "({name='" + air_group @@ -424,7 +292,7 @@ class PretenseLuaGenerator(LuaGenerator): for awacs in self.mission_data.awacs: if awacs.group_name == air_group: awacs_freq = awacs.freq.hertz / 1000000 - lua_string += ( + lua_string_zones += ( f" presets.missions.{mission_name}:extend" + "({name='" + air_group @@ -432,18 +300,32 @@ class PretenseLuaGenerator(LuaGenerator): + str(awacs_freq) + "}),\n" ) - lua_string += " }\n" - lua_string += " })\n" - lua_string += " }\n" - lua_string += "})\n" + lua_string_zones += " }\n" + lua_string_zones += " })\n" + lua_string_zones += " }\n" + lua_string_zones += "})\n" init_body_file = open("./resources/plugins/pretense/init_body.lua", "r") init_body = init_body_file.read() + lua_string_connman = " cm = ConnectionManager:new()" + + for cp in self.game.theater.controlpoints: + for other_cp in cp.connected_points: + lua_string_connman += ( + f" cm: addConnection('{cp.name}', '{other_cp.name}')" + ) + init_footer_file = open("./resources/plugins/pretense/init_footer.lua", "r") init_footer = init_footer_file.read() - lua_string = init_header + lua_string + init_body + init_footer + lua_string = ( + init_header + + lua_string_zones + + lua_string_connman + + init_body + + init_footer + ) trigger.add_action(DoScript(String(lua_string))) self.mission.triggerrules.triggers.append(trigger) diff --git a/game/pretense/pretensetriggergenerator.py b/game/pretense/pretensetriggergenerator.py index 43fbb1de..352654b9 100644 --- a/game/pretense/pretensetriggergenerator.py +++ b/game/pretense/pretensetriggergenerator.py @@ -29,7 +29,7 @@ from dcs.triggers import Event, TriggerCondition, TriggerOnce from dcs.unit import Skill from game.theater import Airfield -from game.theater.controlpoint import Fob, TRIGGER_RADIUS_CAPTURE +from game.theater.controlpoint import Fob, TRIGGER_RADIUS_CAPTURE, OffMapSpawn if TYPE_CHECKING: from game.game import Game @@ -157,7 +157,7 @@ class PretenseTriggerGenerator: 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_fleet: + 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( @@ -215,7 +215,6 @@ class PretenseTriggerGenerator: self._set_skill(player_coalition, enemy_coalition) self._set_allegiances(player_coalition, enemy_coalition) - self._gen_markers() self._generate_pretense_zone_triggers(player_coalition, enemy_coalition) @classmethod diff --git a/resources/plugins/pretense/init_body.lua b/resources/plugins/pretense/init_body.lua index 33a7f627..429b8c01 100644 --- a/resources/plugins/pretense/init_body.lua +++ b/resources/plugins/pretense/init_body.lua @@ -1,4 +1,6 @@ +end + ZoneCommand.setNeighbours(cm) bm = BattlefieldManager:new() From 545210b35dda3046b28bec222c00031f60f21af4 Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Fri, 15 Sep 2023 07:45:04 +0300 Subject: [PATCH 126/243] Split the init_body.lua script in two parts. --- .../{init_body.lua => init_body_1.lua} | 0 resources/plugins/pretense/init_body_2.lua | 110 ++++++++++++++++++ 2 files changed, 110 insertions(+) rename resources/plugins/pretense/{init_body.lua => init_body_1.lua} (100%) create mode 100644 resources/plugins/pretense/init_body_2.lua diff --git a/resources/plugins/pretense/init_body.lua b/resources/plugins/pretense/init_body_1.lua similarity index 100% rename from resources/plugins/pretense/init_body.lua rename to resources/plugins/pretense/init_body_1.lua diff --git a/resources/plugins/pretense/init_body_2.lua b/resources/plugins/pretense/init_body_2.lua new file mode 100644 index 00000000..429b8c01 --- /dev/null +++ b/resources/plugins/pretense/init_body_2.lua @@ -0,0 +1,110 @@ + +end + +ZoneCommand.setNeighbours(cm) + +bm = BattlefieldManager:new() + +mc = MarkerCommands:new() + +pt = PlayerTracker:new(mc) + +mt = MissionTracker:new(pt, mc) + +st = SquadTracker:new() + +ct = CSARTracker:new() + +pl = PlayerLogistics:new(mt, pt, st, ct) + +gci = GCI:new(2) + +gm = GroupMonitor:new(cm) +ZoneCommand.groupMonitor = gm + +-- PlayerLogistics:registerSquadGroup(squadType, groupname, weight,cost,jobtime,extracttime, squadSize) +pl:registerSquadGroup(PlayerLogistics.infantryTypes.capture, 'capture-squad', 700, 200, 60, 60*30, 4) +pl:registerSquadGroup(PlayerLogistics.infantryTypes.sabotage, 'sabotage-squad', 800, 500, 60*5, 60*30, 4) +pl:registerSquadGroup(PlayerLogistics.infantryTypes.ambush, 'ambush-squad', 900, 300, 60*20, 60*30, 5) +pl:registerSquadGroup(PlayerLogistics.infantryTypes.engineer, 'engineer-squad', 200, 1000,60, 60*30, 2) +pl:registerSquadGroup(PlayerLogistics.infantryTypes.manpads, 'manpads-squad', 900, 500, 60*20, 60*30, 5) +pl:registerSquadGroup(PlayerLogistics.infantryTypes.spy, 'spy-squad', 100, 300, 60*10, 60*30, 1) +pl:registerSquadGroup(PlayerLogistics.infantryTypes.rapier, 'rapier-squad', 1200,2000,60*60, 60*30, 8) + +Group.getByName('jtacDrone'):destroy() +CommandFunctions.jtac = JTAC:new({name = 'jtacDrone'}) + +pm = PersistenceManager:new(savefile, gm, st, ct, pl) +pm:load() + +if pm:canRestore() then + pm:restoreZones() + pm:restoreAIMissions() + pm:restoreBattlefield() + pm:restoreCsar() + pm:restoreSquads() +else + --initial states + Starter.start(zones) +end + +timer.scheduleFunction(function(param, time) + pm:save() + env.info("Mission state saved") + return time+60 +end, zones, timer.getTime()+60) + + +--make sure support units are present where needed +ensureSpawn = { + ['golf-farp-suport'] = zones.golf, + ['november-farp-suport'] = zones.november, + ['tango-farp-suport'] = zones.tango, + ['sierra-farp-suport'] = zones.sierra, + ['cherkessk-farp-suport'] = zones.cherkessk, + ['unal-farp-suport'] = zones.unal, + ['tyrnyauz-farp-suport'] = zones.tyrnyauz +} + +for grname, zn in pairs(ensureSpawn) do + local g = Group.getByName(grname) + if g then g:destroy() end +end + +timer.scheduleFunction(function(param, time) + + for grname, zn in pairs(ensureSpawn) do + local g = Group.getByName(grname) + if zn.side == 2 then + if not g then + local err, msg = pcall(mist.respawnGroup,grname,true) + if not err then + env.info("ERROR spawning "..grname) + env.info(msg) + end + end + else + if g then g:destroy() end + end + end + + return time+30 +end, {}, timer.getTime()+30) + + +--supply injection +local blueSupply = {'offmap-supply-blue-1','offmap-supply-blue-2','offmap-supply-blue-3','offmap-supply-blue-4','offmap-supply-blue-5'} +local redSupply = {'offmap-supply-red-1','offmap-supply-red-2','offmap-supply-red-3','offmap-supply-red-4','offmap-supply-red-5'} +local offmapZones = { + zones.batumi, + zones.sochi, + zones.nalchik, + zones.beslan, + zones.mozdok, + zones.mineralnye, +-- zones.senaki, +-- zones.sukhumi, +-- zones.gudauta, +-- zones.kobuleti, +} + From daa3259e59f5872927d04d36692fa61a1707865d Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Fri, 15 Sep 2023 21:02:56 +0300 Subject: [PATCH 127/243] Implemented dynamic outputting of JTAC units to the Pretense init script. --- game/pretense/pretenseluagenerator.py | 20 +++++++-- game/pretense/pretensemissiongenerator.py | 54 +++-------------------- 2 files changed, 23 insertions(+), 51 deletions(-) diff --git a/game/pretense/pretenseluagenerator.py b/game/pretense/pretenseluagenerator.py index 2d0e2dd3..414b0a74 100644 --- a/game/pretense/pretenseluagenerator.py +++ b/game/pretense/pretenseluagenerator.py @@ -305,9 +305,6 @@ class PretenseLuaGenerator(LuaGenerator): lua_string_zones += " }\n" lua_string_zones += "})\n" - init_body_file = open("./resources/plugins/pretense/init_body.lua", "r") - init_body = init_body_file.read() - lua_string_connman = " cm = ConnectionManager:new()" for cp in self.game.theater.controlpoints: @@ -316,6 +313,19 @@ class PretenseLuaGenerator(LuaGenerator): f" cm: addConnection('{cp.name}', '{other_cp.name}')" ) + init_body_1_file = open("./resources/plugins/pretense/init_body_1.lua", "r") + init_body_1 = init_body_1_file.read() + + lua_string_jtac = "" + for jtac in self.mission_data.jtacs: + lua_string_jtac = f"Group.getByName('{jtac.group_name}'): destroy()" + lua_string_jtac += ( + "CommandFunctions.jtac = JTAC:new({name = '" + jtac.group_name + "'})" + ) + + init_body_2_file = open("./resources/plugins/pretense/init_body_2.lua", "r") + init_body_2 = init_body_2_file.read() + init_footer_file = open("./resources/plugins/pretense/init_footer.lua", "r") init_footer = init_footer_file.read() @@ -323,7 +333,9 @@ class PretenseLuaGenerator(LuaGenerator): init_header + lua_string_zones + lua_string_connman - + init_body + + init_body_1 + + lua_string_jtac + + init_body_2 + init_footer ) diff --git a/game/pretense/pretensemissiongenerator.py b/game/pretense/pretensemissiongenerator.py index 9daaab9b..033faca4 100644 --- a/game/pretense/pretensemissiongenerator.py +++ b/game/pretense/pretensemissiongenerator.py @@ -42,14 +42,20 @@ from .pretensetgogenerator import PretenseTgoGenerator from .pretensetriggergenerator import PretenseTriggerGenerator from game.missiongenerator.visualsgenerator import VisualsGenerator from ..ato import Flight +from ..missiongenerator import MissionGenerator from ..radio.TacanContainer import TacanContainer if TYPE_CHECKING: from game import Game -class PretenseMissionGenerator: +class PretenseMissionGenerator(MissionGenerator): def __init__(self, game: Game, time: datetime) -> None: + super().__init__( + game, + time, + ) + self.game = game self.time = time self.mission = Mission(game.theater.terrain) @@ -143,51 +149,6 @@ class PretenseMissionGenerator: c = country_dict[country_id]() self.mission.coalition["neutrals"].add_country(c) - def add_airfields_to_unit_map(self) -> None: - for control_point in self.game.theater.controlpoints: - if isinstance(control_point, Airfield): - self.unit_map.add_airfield(control_point) - - def initialize_registries(self) -> None: - unique_map_frequencies: set[RadioFrequency] = set() - self.initialize_tacan_registry(unique_map_frequencies) - self.initialize_radio_registry(unique_map_frequencies) - # Allocate UHF/VHF Guard Freq first! - unique_map_frequencies.add(MHz(243)) - unique_map_frequencies.add(MHz(121, 500)) - for frequency in unique_map_frequencies: - self.radio_registry.reserve(frequency) - - def initialize_tacan_registry( - self, unique_map_frequencies: set[RadioFrequency] - ) -> None: - """ - Dedup beacon/radio frequencies, since some maps have some frequencies - used multiple times. - """ - for beacon in Beacons.iter_theater(self.game.theater): - unique_map_frequencies.add(beacon.frequency) - if beacon.is_tacan: - if beacon.channel is None: - logging.warning(f"TACAN beacon has no channel: {beacon.callsign}") - else: - self.tacan_registry.mark_unavailable(beacon.tacan_channel) - for cp in self.game.theater.controlpoints: - if isinstance(cp, TacanContainer) and cp.tacan is not None: - self.tacan_registry.mark_unavailable(cp.tacan) - - def initialize_radio_registry( - self, unique_map_frequencies: set[RadioFrequency] - ) -> None: - for airport in self.game.theater.terrain.airport_list(): - if (atc := AtcData.from_pydcs(airport)) is not None: - unique_map_frequencies.add(atc.hf) - unique_map_frequencies.add(atc.vhf_fm) - unique_map_frequencies.add(atc.vhf_am) - unique_map_frequencies.add(atc.uhf) - # No need to reserve ILS or TACAN because those are in the - # beacon list. - def generate_ground_conflicts(self) -> None: """Generate FLOTs and JTACs for each active front line.""" for front_line in self.game.theater.conflicts(): @@ -247,7 +208,6 @@ class PretenseMissionGenerator: else: ato = self.game.red.ato cp_country = self.e_country - print(f"Generating flights for {cp_country.name} at {cp}") aircraft_generator.generate_flights( cp_country, cp, From 4ad87aef3e4a055048cca47efc47db122e854ab1 Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Fri, 15 Sep 2023 22:24:17 +0300 Subject: [PATCH 128/243] Add CJTF factions to the coalitions in Pretense, if they're not being used in the campaign. --- game/pretense/pretensemissiongenerator.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/game/pretense/pretensemissiongenerator.py b/game/pretense/pretensemissiongenerator.py index 033faca4..79f18fd4 100644 --- a/game/pretense/pretensemissiongenerator.py +++ b/game/pretense/pretensemissiongenerator.py @@ -9,7 +9,11 @@ import dcs.lua from dataclasses import field from dcs import Mission, Point from dcs.coalition import Coalition -from dcs.countries import country_dict +from dcs.countries import ( + country_dict, + CombinedJointTaskForcesBlue, + CombinedJointTaskForcesRed, +) from dcs.task import OptReactOnThreat from game.atcdata import AtcData @@ -143,6 +147,12 @@ class PretenseMissionGenerator(MissionGenerator): self.mission.coalition["blue"].add_country(self.p_country) self.mission.coalition["red"].add_country(self.e_country) + # Add CJTF factions to the coalitions, if they're not being used in the campaign + # if CombinedJointTaskForcesBlue not in {self.p_country, self.e_country}: + # self.mission.coalition["blue"].add_country(CombinedJointTaskForcesBlue()) + # if CombinedJointTaskForcesRed not in {self.p_country, self.e_country}: + # self.mission.coalition["red"].add_country(CombinedJointTaskForcesRed()) + belligerents = {self.p_country.id, self.e_country.id} for country_id in country_dict.keys(): if country_id not in belligerents: From d965f90bb4fb89edbe1c8b359e4d2f6b76190ddb Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Sat, 16 Sep 2023 14:36:59 +0300 Subject: [PATCH 129/243] Implemented spawning of Pretense cargo aircraft. To support that, implemented a separate flight plan called PretenseCargoFlightPlan. Also, will now automatically generate transport squadrons for factions which don't have pre-defined squadrons for it, but have access to transport aircraft. --- .../ato/flightplans/flightplanbuildertypes.py | 2 + game/ato/flightplans/pretensecargo.py | 99 +++++++++++++ game/ato/flightstate/navigating.py | 7 +- game/ato/flighttype.py | 4 + game/pretense/pretenseaircraftgenerator.py | 135 +++++++++++++++--- 5 files changed, 225 insertions(+), 22 deletions(-) create mode 100644 game/ato/flightplans/pretensecargo.py diff --git a/game/ato/flightplans/flightplanbuildertypes.py b/game/ato/flightplans/flightplanbuildertypes.py index 5047e45c..4561eeec 100644 --- a/game/ato/flightplans/flightplanbuildertypes.py +++ b/game/ato/flightplans/flightplanbuildertypes.py @@ -18,6 +18,7 @@ from .ocaaircraft import OcaAircraftFlightPlan from .ocarunway import OcaRunwayFlightPlan from .packagerefueling import PackageRefuelingFlightPlan from .planningerror import PlanningError +from .pretensecargo import PretenseCargoFlightPlan from .sead import SeadFlightPlan from .seadsweep import SeadSweepFlightPlan from .strike import StrikeFlightPlan @@ -60,6 +61,7 @@ class FlightPlanBuilderTypes: FlightType.TRANSPORT: AirliftFlightPlan.builder_type(), FlightType.FERRY: FerryFlightPlan.builder_type(), FlightType.AIR_ASSAULT: AirAssaultFlightPlan.builder_type(), + FlightType.PRETENSE_CARGO: PretenseCargoFlightPlan.builder_type(), } try: return builder_dict[flight.flight_type] diff --git a/game/ato/flightplans/pretensecargo.py b/game/ato/flightplans/pretensecargo.py new file mode 100644 index 00000000..f980b5f7 --- /dev/null +++ b/game/ato/flightplans/pretensecargo.py @@ -0,0 +1,99 @@ +from __future__ import annotations + +from collections.abc import Iterator +from dataclasses import dataclass +from datetime import timedelta +from typing import TYPE_CHECKING, Type + +from game.utils import feet +from .ferry import FerryLayout +from .ibuilder import IBuilder +from .planningerror import PlanningError +from .standard import StandardFlightPlan, StandardLayout +from .waypointbuilder import WaypointBuilder + +if TYPE_CHECKING: + from ..flightwaypoint import FlightWaypoint + + +PRETENSE_CARGO_FLIGHT_DISTANCE = 50000 + + +class PretenseCargoFlightPlan(StandardFlightPlan[FerryLayout]): + @staticmethod + def builder_type() -> Type[Builder]: + return Builder + + @property + def tot_waypoint(self) -> FlightWaypoint: + return self.layout.arrival + + def tot_for_waypoint(self, waypoint: FlightWaypoint) -> timedelta | None: + # TOT planning isn't really useful for ferries. They're behind the front + # lines so no need to wait for escorts or for other missions to complete. + return None + + def depart_time_for_waypoint(self, waypoint: FlightWaypoint) -> timedelta | None: + return None + + @property + def mission_departure_time(self) -> timedelta: + return self.package.time_over_target + + +class Builder(IBuilder[PretenseCargoFlightPlan, FerryLayout]): + def layout(self) -> FerryLayout: + # Find the spawn location for off-map transport planes + distance_to_flot = 0 + heading_from_flot = 0.0 + offmap_transport_cp_id = self.flight.departure.id + for front_line_cp in self.coalition.game.theater.controlpoints: + for front_line in self.coalition.game.theater.conflicts(): + if front_line_cp.captured == self.flight.coalition.player: + if ( + front_line_cp.position.distance_to_point(front_line.position) + > distance_to_flot + ): + distance_to_flot = front_line_cp.position.distance_to_point( + front_line.position + ) + heading_from_flot = front_line.position.heading_between_point( + front_line_cp.position + ) + offmap_transport_cp_id = front_line_cp.id + offmap_transport_cp = self.coalition.game.theater.find_control_point_by_id( + offmap_transport_cp_id + ) + offmap_transport_spawn = offmap_transport_cp.position.point_from_heading( + heading_from_flot, PRETENSE_CARGO_FLIGHT_DISTANCE + ) + + altitude_is_agl = self.flight.unit_type.dcs_unit_type.helicopter + altitude = ( + feet(1500) + if altitude_is_agl + else self.flight.unit_type.preferred_patrol_altitude + ) + + builder = WaypointBuilder(self.flight, self.coalition) + ferry_layout = FerryLayout( + departure=builder.join(offmap_transport_spawn), + nav_to=builder.nav_path( + offmap_transport_spawn, + self.flight.arrival.position, + altitude, + altitude_is_agl, + ), + arrival=builder.land(self.flight.arrival), + divert=builder.divert(self.flight.divert), + bullseye=builder.bullseye(), + nav_from=[], + ) + ferry_layout.departure = builder.join(offmap_transport_spawn) + ferry_layout.nav_to.append(builder.join(offmap_transport_spawn)) + ferry_layout.nav_from.append(builder.join(offmap_transport_spawn)) + print(ferry_layout) + return ferry_layout + + def build(self) -> PretenseCargoFlightPlan: + return PretenseCargoFlightPlan(self.flight, self.layout()) diff --git a/game/ato/flightstate/navigating.py b/game/ato/flightstate/navigating.py index 3932dbf3..39acfb06 100644 --- a/game/ato/flightstate/navigating.py +++ b/game/ato/flightstate/navigating.py @@ -29,11 +29,8 @@ class Navigating(InFlight): events.update_flight_position(self.flight, self.estimate_position()) def progress(self) -> float: - # if next waypoint is very close, assume we reach it immediately to avoid divide - # by zero error - if self.total_time_to_next_waypoint.total_seconds() < 1: - return 1.0 - + if self.total_time_to_next_waypoint.total_seconds() == 0.0: + return 99.9 return ( self.elapsed_time.total_seconds() / self.total_time_to_next_waypoint.total_seconds() diff --git a/game/ato/flighttype.py b/game/ato/flighttype.py index 615aaa5b..b8eeb1c9 100644 --- a/game/ato/flighttype.py +++ b/game/ato/flighttype.py @@ -58,6 +58,9 @@ class FlightType(Enum): FERRY = "Ferry" AIR_ASSAULT = "Air Assault" SEAD_SWEEP = "SEAD Sweep" # Reintroduce legacy "engage-whatever-you-can-find" SEAD + PRETENSE_CARGO = ( + "Cargo Transport" # Flight type for Pretense campaign AI cargo planes + ) def __str__(self) -> str: return self.value @@ -121,5 +124,6 @@ class FlightType(Enum): FlightType.SWEEP: AirEntity.FIGHTER, FlightType.TARCAP: AirEntity.FIGHTER, FlightType.TRANSPORT: AirEntity.UTILITY, + FlightType.PRETENSE_CARGO: AirEntity.UTILITY, FlightType.AIR_ASSAULT: AirEntity.ROTARY_WING, }.get(self, AirEntity.UNSPECIFIED) diff --git a/game/pretense/pretenseaircraftgenerator.py b/game/pretense/pretenseaircraftgenerator.py index 82135e57..60dc0c3a 100644 --- a/game/pretense/pretenseaircraftgenerator.py +++ b/game/pretense/pretenseaircraftgenerator.py @@ -5,20 +5,16 @@ import random from datetime import datetime from functools import cached_property from typing import Any, Dict, List, TYPE_CHECKING, Tuple +from uuid import UUID from dcs import Point -from dcs.action import AITaskPush -from dcs.condition import FlagIsTrue, GroupDead, Or, FlagIsFalse from dcs.country import Country from dcs.mission import Mission -from dcs.terrain.terrain import NoParkingSlotError -from dcs.triggers import TriggerOnce, Event -from dcs.unit import Skill from dcs.unitgroup import FlyingGroup, StaticGroup from game.ato.airtaaskingorder import AirTaskingOrder from game.ato.flight import Flight -from game.ato.flightstate import Completed, WaitingForStart +from game.ato.flightstate import Completed, WaitingForStart, Navigating from game.ato.flighttype import FlightType from game.ato.package import Package from game.ato.starttype import StartType @@ -32,10 +28,9 @@ from game.radio.tacan import TacanRegistry from game.runways import RunwayData from game.settings import Settings from game.theater.controlpoint import ( - Airfield, ControlPoint, - Fob, OffMapSpawn, + ParkingType, ) from game.unitmap import UnitMap from game.missiongenerator.aircraft.aircraftpainter import AircraftPainter @@ -44,7 +39,9 @@ from game.data.weapons import WeaponType if TYPE_CHECKING: from game import Game - from game.squadrons import Squadron + + +PRETENSE_SQUADRON_DEF_RETRIES = 100 class PretenseAircraftGenerator: @@ -108,6 +105,8 @@ class PretenseAircraftGenerator: ato: AirTaskingOrder, dynamic_runways: Dict[str, RunwayData], ) -> None: + from game.squadrons import Squadron + """Adds aircraft to the mission for every flight in the ATO. Aircraft generation is done by walking the ATO and spawning each flight in turn. @@ -125,6 +124,89 @@ class PretenseAircraftGenerator: num_of_strike = 0 num_of_cap = 0 + # Find locations for off-map transport planes + distance_to_flot = 0 + offmap_transport_cp_id = cp.id + parking_type = ParkingType( + fixed_wing=True, fixed_wing_stol=True, rotary_wing=False + ) + for front_line_cp in self.game.theater.controlpoints: + for front_line in self.game.theater.conflicts(): + if front_line_cp.captured == cp.captured: + if ( + front_line_cp.total_aircraft_parking(parking_type) > 0 + and front_line_cp.position.distance_to_point( + front_line.position + ) + > distance_to_flot + ): + distance_to_flot = front_line_cp.position.distance_to_point( + front_line.position + ) + offmap_transport_cp_id = front_line_cp.id + offmap_transport_cp = self.game.theater.find_control_point_by_id( + offmap_transport_cp_id + ) + + # Ensure that the faction has at least one transport helicopter and one cargo plane squadron + autogenerate_transport_helicopter_squadron = True + autogenerate_cargo_plane_squadron = True + for aircraft_type in cp.coalition.air_wing.squadrons: + for squadron in cp.coalition.air_wing.squadrons[aircraft_type]: + mission_types = squadron.auto_assignable_mission_types + if squadron.aircraft.helicopter and ( + FlightType.TRANSPORT in mission_types + or FlightType.AIR_ASSAULT in mission_types + ): + autogenerate_transport_helicopter_squadron = False + elif not squadron.aircraft.helicopter and ( + FlightType.TRANSPORT in mission_types + or FlightType.AIR_ASSAULT in mission_types + ): + autogenerate_cargo_plane_squadron = False + + if autogenerate_transport_helicopter_squadron: + flight_type = FlightType.AIR_ASSAULT + squadron_def = ( + cp.coalition.air_wing.squadron_def_generator.generate_for_task( + flight_type, offmap_transport_cp + ) + ) + squadron = Squadron.create_from( + squadron_def, + flight_type, + 2, + offmap_transport_cp, + cp.coalition, + self.game, + ) + cp.coalition.air_wing.squadrons[squadron.aircraft] = list() + cp.coalition.air_wing.add_squadron(squadron) + if autogenerate_cargo_plane_squadron: + flight_type = FlightType.TRANSPORT + squadron_def = ( + cp.coalition.air_wing.squadron_def_generator.generate_for_task( + flight_type, offmap_transport_cp + ) + ) + for retries in range(PRETENSE_SQUADRON_DEF_RETRIES): + if squadron_def.aircraft.helicopter: + squadron_def = ( + cp.coalition.air_wing.squadron_def_generator.generate_for_task( + flight_type, offmap_transport_cp + ) + ) + squadron = Squadron.create_from( + squadron_def, + flight_type, + 2, + offmap_transport_cp, + cp.coalition, + self.game, + ) + cp.coalition.air_wing.squadrons[squadron.aircraft] = list() + cp.coalition.air_wing.add_squadron(squadron) + for squadron in cp.squadrons: # Intentionally don't spawn anything at OffMapSpawns in Pretense if isinstance(squadron.location, OffMapSpawn): @@ -134,11 +216,16 @@ class PretenseAircraftGenerator: squadron.untasked_aircraft += 1 package = Package(cp, squadron.flight_db, auto_asap=False) mission_types = squadron.auto_assignable_mission_types - if ( + if squadron.aircraft.helicopter and ( FlightType.TRANSPORT in mission_types or FlightType.AIR_ASSAULT in mission_types ): flight_type = FlightType.AIR_ASSAULT + elif not squadron.aircraft.helicopter and ( + FlightType.TRANSPORT in mission_types + or FlightType.AIR_ASSAULT in mission_types + ): + flight_type = FlightType.TRANSPORT elif ( FlightType.SEAD in mission_types or FlightType.SEAD_SWEEP in mission_types @@ -170,13 +257,27 @@ class PretenseAircraftGenerator: num_of_cap += 1 else: flight_type = random.choice(list(mission_types)) - flight = Flight( - package, squadron, 1, flight_type, StartType.COLD, divert=cp - ) - flight.state = WaitingForStart( - flight, self.game.settings, self.game.conditions.start_time - ) - package.add_flight(flight) + + if flight_type == FlightType.TRANSPORT: + flight = Flight( + package, + squadron, + 1, + FlightType.PRETENSE_CARGO, + StartType.IN_FLIGHT, + divert=cp, + ) + package.add_flight(flight) + flight.state = Navigating(flight, self.game.settings, waypoint_index=1) + else: + flight = Flight( + package, squadron, 1, flight_type, StartType.COLD, divert=cp + ) + package.add_flight(flight) + flight.state = WaitingForStart( + flight, self.game.settings, self.game.conditions.start_time + ) + ato.add_package(package) self._reserve_frequencies_and_tacan(ato) From 27b1c92815725975c21059f379cd78ac1787666b Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Sat, 16 Sep 2023 15:02:49 +0300 Subject: [PATCH 130/243] PretenseFlightGroupSpawner method for spawning mid-mission flights and setting the appropriate Pretense names to them. --- game/pretense/pretenseflightgroupspawner.py | 49 ++++++++++++++++++++- 1 file changed, 48 insertions(+), 1 deletion(-) diff --git a/game/pretense/pretenseflightgroupspawner.py b/game/pretense/pretenseflightgroupspawner.py index 839c2b19..ea990924 100644 --- a/game/pretense/pretenseflightgroupspawner.py +++ b/game/pretense/pretenseflightgroupspawner.py @@ -1,4 +1,5 @@ import logging +import random import re from typing import Any, Tuple @@ -13,8 +14,14 @@ from dcs.unitgroup import ( ) from game.ato import Flight +from game.ato.flightstate import InFlight from game.ato.starttype import StartType -from game.missiongenerator.aircraft.flightgroupspawner import FlightGroupSpawner +from game.missiongenerator.aircraft.flightgroupspawner import ( + FlightGroupSpawner, + MINIMUM_MID_MISSION_SPAWN_ALTITUDE_AGL, + MINIMUM_MID_MISSION_SPAWN_ALTITUDE_MSL, + STACK_SEPARATION, +) from game.missiongenerator.missiondata import MissionData from game.naming import NameGenerator from game.theater import Airfield, ControlPoint, Fob, NavalControlPoint, OffMapSpawn @@ -160,3 +167,43 @@ class PretenseFlightGroupSpawner(FlightGroupSpawner): self.flight.start_type = StartType.IN_FLIGHT group = self._generate_over_departure(name, cp) return group + + def generate_mid_mission(self) -> FlyingGroup[Any]: + assert isinstance(self.flight.state, InFlight) + cp = self.flight.departure + name = namegen.next_pretense_aircraft_name(cp, self.flight) + speed = self.flight.state.estimate_speed() + pos = self.flight.state.estimate_position() + pos += Vector2(random.randint(100, 1000), random.randint(100, 1000)) + alt, alt_type = self.flight.state.estimate_altitude() + cp = self.flight.squadron.location.id + + if cp not in self.mission_data.cp_stack: + self.mission_data.cp_stack[cp] = MINIMUM_MID_MISSION_SPAWN_ALTITUDE_AGL + + # We don't know where the ground is, so just make sure that any aircraft + # spawning at an MSL altitude is spawned at some minimum altitude. + # https://github.com/dcs-liberation/dcs_liberation/issues/1941 + if alt_type == "BARO" and alt < MINIMUM_MID_MISSION_SPAWN_ALTITUDE_MSL: + alt = MINIMUM_MID_MISSION_SPAWN_ALTITUDE_MSL + + # Set a minimum AGL value for 'alt' if needed, + # otherwise planes might crash in trees and stuff. + if alt_type == "RADIO" and alt < self.mission_data.cp_stack[cp]: + alt = self.mission_data.cp_stack[cp] + self.mission_data.cp_stack[cp] += STACK_SEPARATION + + group = self.mission.flight_group( + country=self.country, + name=name, + aircraft_type=self.flight.unit_type.dcs_unit_type, + airport=None, + position=pos, + altitude=alt.meters, + speed=speed.kph, + maintask=None, + group_size=self.flight.count, + ) + + group.points[0].alt_type = alt_type + return group From 60fde4624954a7810ec7202c07152422c394b9fa Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Sat, 16 Sep 2023 15:04:03 +0300 Subject: [PATCH 131/243] Split the init_body.lua script in two parts to facilitate dynamic JTAC spawning. --- resources/plugins/pretense/init_body_1.lua | 78 ---------------------- resources/plugins/pretense/init_body_2.lua | 35 ---------- 2 files changed, 113 deletions(-) diff --git a/resources/plugins/pretense/init_body_1.lua b/resources/plugins/pretense/init_body_1.lua index 429b8c01..3f5068a8 100644 --- a/resources/plugins/pretense/init_body_1.lua +++ b/resources/plugins/pretense/init_body_1.lua @@ -30,81 +30,3 @@ pl:registerSquadGroup(PlayerLogistics.infantryTypes.engineer, 'engineer-squad', pl:registerSquadGroup(PlayerLogistics.infantryTypes.manpads, 'manpads-squad', 900, 500, 60*20, 60*30, 5) pl:registerSquadGroup(PlayerLogistics.infantryTypes.spy, 'spy-squad', 100, 300, 60*10, 60*30, 1) pl:registerSquadGroup(PlayerLogistics.infantryTypes.rapier, 'rapier-squad', 1200,2000,60*60, 60*30, 8) - -Group.getByName('jtacDrone'):destroy() -CommandFunctions.jtac = JTAC:new({name = 'jtacDrone'}) - -pm = PersistenceManager:new(savefile, gm, st, ct, pl) -pm:load() - -if pm:canRestore() then - pm:restoreZones() - pm:restoreAIMissions() - pm:restoreBattlefield() - pm:restoreCsar() - pm:restoreSquads() -else - --initial states - Starter.start(zones) -end - -timer.scheduleFunction(function(param, time) - pm:save() - env.info("Mission state saved") - return time+60 -end, zones, timer.getTime()+60) - - ---make sure support units are present where needed -ensureSpawn = { - ['golf-farp-suport'] = zones.golf, - ['november-farp-suport'] = zones.november, - ['tango-farp-suport'] = zones.tango, - ['sierra-farp-suport'] = zones.sierra, - ['cherkessk-farp-suport'] = zones.cherkessk, - ['unal-farp-suport'] = zones.unal, - ['tyrnyauz-farp-suport'] = zones.tyrnyauz -} - -for grname, zn in pairs(ensureSpawn) do - local g = Group.getByName(grname) - if g then g:destroy() end -end - -timer.scheduleFunction(function(param, time) - - for grname, zn in pairs(ensureSpawn) do - local g = Group.getByName(grname) - if zn.side == 2 then - if not g then - local err, msg = pcall(mist.respawnGroup,grname,true) - if not err then - env.info("ERROR spawning "..grname) - env.info(msg) - end - end - else - if g then g:destroy() end - end - end - - return time+30 -end, {}, timer.getTime()+30) - - ---supply injection -local blueSupply = {'offmap-supply-blue-1','offmap-supply-blue-2','offmap-supply-blue-3','offmap-supply-blue-4','offmap-supply-blue-5'} -local redSupply = {'offmap-supply-red-1','offmap-supply-red-2','offmap-supply-red-3','offmap-supply-red-4','offmap-supply-red-5'} -local offmapZones = { - zones.batumi, - zones.sochi, - zones.nalchik, - zones.beslan, - zones.mozdok, - zones.mineralnye, --- zones.senaki, --- zones.sukhumi, --- zones.gudauta, --- zones.kobuleti, -} - diff --git a/resources/plugins/pretense/init_body_2.lua b/resources/plugins/pretense/init_body_2.lua index 429b8c01..9d2798d8 100644 --- a/resources/plugins/pretense/init_body_2.lua +++ b/resources/plugins/pretense/init_body_2.lua @@ -1,39 +1,4 @@ -end - -ZoneCommand.setNeighbours(cm) - -bm = BattlefieldManager:new() - -mc = MarkerCommands:new() - -pt = PlayerTracker:new(mc) - -mt = MissionTracker:new(pt, mc) - -st = SquadTracker:new() - -ct = CSARTracker:new() - -pl = PlayerLogistics:new(mt, pt, st, ct) - -gci = GCI:new(2) - -gm = GroupMonitor:new(cm) -ZoneCommand.groupMonitor = gm - --- PlayerLogistics:registerSquadGroup(squadType, groupname, weight,cost,jobtime,extracttime, squadSize) -pl:registerSquadGroup(PlayerLogistics.infantryTypes.capture, 'capture-squad', 700, 200, 60, 60*30, 4) -pl:registerSquadGroup(PlayerLogistics.infantryTypes.sabotage, 'sabotage-squad', 800, 500, 60*5, 60*30, 4) -pl:registerSquadGroup(PlayerLogistics.infantryTypes.ambush, 'ambush-squad', 900, 300, 60*20, 60*30, 5) -pl:registerSquadGroup(PlayerLogistics.infantryTypes.engineer, 'engineer-squad', 200, 1000,60, 60*30, 2) -pl:registerSquadGroup(PlayerLogistics.infantryTypes.manpads, 'manpads-squad', 900, 500, 60*20, 60*30, 5) -pl:registerSquadGroup(PlayerLogistics.infantryTypes.spy, 'spy-squad', 100, 300, 60*10, 60*30, 1) -pl:registerSquadGroup(PlayerLogistics.infantryTypes.rapier, 'rapier-squad', 1200,2000,60*60, 60*30, 8) - -Group.getByName('jtacDrone'):destroy() -CommandFunctions.jtac = JTAC:new({name = 'jtacDrone'}) - pm = PersistenceManager:new(savefile, gm, st, ct, pl) pm:load() From a869c2a7580b36f4e1999aa2484997500bee7961 Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Sat, 16 Sep 2023 15:06:14 +0300 Subject: [PATCH 132/243] Also connect carrier and LHA control points to adjacent friendly points in Pretense. Enlarged the carrier trigger zones. --- game/pretense/pretenseluagenerator.py | 17 ++++++++++---- game/pretense/pretensetriggergenerator.py | 7 +++++- game/theater/conflicttheater.py | 28 +++++++++++++++++++++++ 3 files changed, 47 insertions(+), 5 deletions(-) diff --git a/game/pretense/pretenseluagenerator.py b/game/pretense/pretenseluagenerator.py index 414b0a74..99500892 100644 --- a/game/pretense/pretenseluagenerator.py +++ b/game/pretense/pretenseluagenerator.py @@ -93,9 +93,9 @@ class PretenseLuaGenerator(LuaGenerator): max_resource = 30000 if isinstance(cp, Airfield) or cp.has_ground_spawns: lua_string_zones += f"zones.{cp_name_trimmed}.isPlaneSpawn = true\n" - if cp.has_ground_spawns: + if cp.has_ground_spawns or cp.is_lha: max_resource = 40000 - if isinstance(cp, Airfield): + if isinstance(cp, Airfield) or cp.is_carrier: max_resource = 50000 lua_string_zones += ( f"zones.{cp_name_trimmed}.maxResource = {max_resource}\n" @@ -114,7 +114,7 @@ class PretenseLuaGenerator(LuaGenerator): lua_string_zones += " }),\n" lua_string_zones += " presets.upgrades.basic.comPost:extend({\n" lua_string_zones += f" name = '{cp_name_trimmed}-com-red',\n" - lua_string_zones += " products = {" + lua_string_zones += " products = {\n" lua_string_zones += ( " presets.special.red.infantry:extend({ name='" + cp_name_trimmed @@ -307,11 +307,20 @@ class PretenseLuaGenerator(LuaGenerator): lua_string_connman = " cm = ConnectionManager:new()" + # Generate ConnectionManager connections for cp in self.game.theater.controlpoints: for other_cp in cp.connected_points: lua_string_connman += ( - f" cm: addConnection('{cp.name}', '{other_cp.name}')" + f" cm: addConnection('{cp.name}', '{other_cp.name}')\n" ) + # Also connect carrier and LHA control points to adjacent friendly points + if cp.is_fleet and len(cp.connected_points) == 0: + for other_cp in self.game.theater.closest_friendly_control_points_to( + cp + ): + lua_string_connman += ( + f" cm: addConnection('{cp.name}', '{other_cp.name}')\n" + ) init_body_1_file = open("./resources/plugins/pretense/init_body_1.lua", "r") init_body_1 = init_body_1_file.read() diff --git a/game/pretense/pretensetriggergenerator.py b/game/pretense/pretensetriggergenerator.py index 352654b9..604abdfc 100644 --- a/game/pretense/pretensetriggergenerator.py +++ b/game/pretense/pretensetriggergenerator.py @@ -53,6 +53,7 @@ TRIGGER_RADIUS_CLEAR_SCENERY = 1000 TRIGGER_RADIUS_PRETENSE_TGO = 500 TRIGGER_RADIUS_PRETENSE_SUPPLY = 500 TRIGGER_RADIUS_PRETENSE_HELI = 1000 +TRIGGER_RADIUS_PRETENSE_CARRIER = 50000 class Silence(Option): @@ -157,12 +158,16 @@ class PretenseTriggerGenerator: Directly appends to the global `base_capture_events` var declared by `dcs_libaration.lua` """ for cp in self.game.theater.controlpoints: + if cp.is_fleet: + trigger_radius = TRIGGER_RADIUS_PRETENSE_CARRIER + else: + 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( cp.position, - radius=TRIGGER_RADIUS_CAPTURE, + radius=trigger_radius, hidden=False, name=cp.name, color=zone_color, diff --git a/game/theater/conflicttheater.py b/game/theater/conflicttheater.py index 1c67d1e0..f11bfabf 100644 --- a/game/theater/conflicttheater.py +++ b/game/theater/conflicttheater.py @@ -202,6 +202,34 @@ class ConflictTheater: assert closest_red is not None return closest_blue, closest_red + def closest_friendly_control_points_to( + self, cp: ControlPoint + ) -> Tuple[ControlPoint, ControlPoint]: + """ + Returns a tuple of the two nearest friendly ControlPoints in theater to ControlPoint cp. + (closest_cp, second_closest_cp) + """ + seen = set() + min_distance = math.inf + closest_cp = None + second_closest_cp = None + if cp.captured: + control_points = self.player_points() + else: + control_points = self.enemy_points() + for other_cp in control_points: + if cp == other_cp: + continue + dist = other_cp.position.distance_to_point(cp.position) + if dist < min_distance: + second_closest_cp = closest_cp + closest_cp = other_cp + min_distance = dist + + assert closest_cp is not None + assert second_closest_cp is not None + return closest_cp, second_closest_cp + def find_control_point_by_id(self, cp_id: UUID) -> ControlPoint: for i in self.controlpoints: if i.id == cp_id: From c0e26b3b7fbb7426bce59ee7f24b93f4bc2bf2e4 Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Sat, 16 Sep 2023 16:30:49 +0300 Subject: [PATCH 133/243] Fixed Pretense ground unit group id/name handling. --- game/pretense/pretensetgogenerator.py | 31 ++++++++++++++------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/game/pretense/pretensetgogenerator.py b/game/pretense/pretensetgogenerator.py index 00e0477b..639e74e5 100644 --- a/game/pretense/pretensetgogenerator.py +++ b/game/pretense/pretensetgogenerator.py @@ -145,8 +145,8 @@ class PretenseGroundObjectGenerator(GroundObjectGenerator): if self.ground_object.coalition.faction.has_access_to_unit_class(unit_class): unit_type = self.ground_unit_of_class(unit_class) if unit_type is not None and len(vehicle_units) < max_num: - group_id = self.game.next_group_id() - group_name = f"{cp_name}-{group_role}-{group_id}" + unit_id = self.game.next_unit_id() + unit_name = f"{cp_name}-{group_role}-{unit_id}" spread_out_heading = random.randrange(1, 360) spread_out_position = group.position.point_from_heading( @@ -157,8 +157,8 @@ class PretenseGroundObjectGenerator(GroundObjectGenerator): ) theater_unit = TheaterUnit( - group_id, - group_name, + unit_id, + unit_name, unit_type.dcs_unit_type, ground_unit_pos, group.ground_object, @@ -177,7 +177,6 @@ class PretenseGroundObjectGenerator(GroundObjectGenerator): self.game.pretense_ground_supply[side][cp_name_trimmed] = list() if cp_name_trimmed not in self.game.pretense_ground_assault[cp_side]: self.game.pretense_ground_assault[side][cp_name_trimmed] = list() - print(self.game.pretense_ground_supply[cp_side][cp_name_trimmed]) for group in self.ground_object.groups: vehicle_units: list[TheaterUnit] = [] ship_units: list[TheaterUnit] = [] @@ -185,13 +184,9 @@ class PretenseGroundObjectGenerator(GroundObjectGenerator): for unit in group.units: if unit.is_static: # Add supply convoy - group_id = self.game.next_group_id() group_role = "supply" - group_name = f"{cp_name_trimmed}-{group_role}-{group_id}" + group_name = f"{cp_name_trimmed}-{group_role}-{group.id}" group.name = group_name - self.game.pretense_ground_supply[cp_side][cp_name_trimmed].append( - group_name - ) self.generate_ground_unit_of_class( UnitClass.LOGISTICS, @@ -203,13 +198,9 @@ class PretenseGroundObjectGenerator(GroundObjectGenerator): ) elif unit.is_vehicle and unit.alive: # Add armor group - group_id = self.game.next_group_id() group_role = "assault" - group_name = f"{cp_name_trimmed}-{group_role}-{group_id}" + group_name = f"{cp_name_trimmed}-{group_role}-{group.id}" group.name = group_name - self.game.pretense_ground_supply[cp_side][cp_name_trimmed].append( - group_name - ) self.generate_ground_unit_of_class( UnitClass.TANK, @@ -280,6 +271,12 @@ class PretenseGroundObjectGenerator(GroundObjectGenerator): self, group_name: str, units: list[TheaterUnit] ) -> VehicleGroup: vehicle_group: Optional[VehicleGroup] = None + + cp_name_trimmed = "".join( + [i for i in self.ground_object.control_point.name.lower() if i.isalnum()] + ) + cp_side = 2 if self.ground_object.control_point.captured else 1 + for unit in units: assert issubclass(unit.type, VehicleType) faction = unit.ground_object.control_point.coalition.faction @@ -296,6 +293,10 @@ class PretenseGroundObjectGenerator(GroundObjectGenerator): vehicle_group.units[0].name = unit.unit_name self.set_alarm_state(vehicle_group) GroundForcePainter(faction, vehicle_group.units[0]).apply_livery() + + self.game.pretense_ground_supply[cp_side][cp_name_trimmed].append( + f"{vehicle_group.name}" + ) else: vehicle_unit = self.m.vehicle(unit.unit_name, unit.type) vehicle_unit.player_can_drive = True From 529841bfe4346692e53ad190848543b7c3a92502 Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Sat, 16 Sep 2023 17:20:38 +0300 Subject: [PATCH 134/243] Add CJTF factions to the coalitions in Pretense, if they're not being used in the Retribution campaign. --- game/pretense/pretensemissiongenerator.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/game/pretense/pretensemissiongenerator.py b/game/pretense/pretensemissiongenerator.py index 79f18fd4..ee626082 100644 --- a/game/pretense/pretensemissiongenerator.py +++ b/game/pretense/pretensemissiongenerator.py @@ -148,10 +148,10 @@ class PretenseMissionGenerator(MissionGenerator): self.mission.coalition["red"].add_country(self.e_country) # Add CJTF factions to the coalitions, if they're not being used in the campaign - # if CombinedJointTaskForcesBlue not in {self.p_country, self.e_country}: - # self.mission.coalition["blue"].add_country(CombinedJointTaskForcesBlue()) - # if CombinedJointTaskForcesRed not in {self.p_country, self.e_country}: - # self.mission.coalition["red"].add_country(CombinedJointTaskForcesRed()) + if CombinedJointTaskForcesBlue not in {self.p_country, self.e_country}: + self.mission.coalition["blue"].add_country(CombinedJointTaskForcesBlue()) + if CombinedJointTaskForcesRed not in {self.p_country, self.e_country}: + self.mission.coalition["red"].add_country(CombinedJointTaskForcesRed()) belligerents = {self.p_country.id, self.e_country.id} for country_id in country_dict.keys(): From 13e7e976c39aa72e7f30aa60e3b76f3d58ad6254 Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Sat, 16 Sep 2023 17:21:28 +0300 Subject: [PATCH 135/243] Now connects more isolated zones in Pretense. --- game/pretense/pretenseluagenerator.py | 32 +++++++++++++++++++++------ game/theater/conflicttheater.py | 19 +++++++++++----- 2 files changed, 39 insertions(+), 12 deletions(-) diff --git a/game/pretense/pretenseluagenerator.py b/game/pretense/pretenseluagenerator.py index 99500892..fa71e08b 100644 --- a/game/pretense/pretenseluagenerator.py +++ b/game/pretense/pretenseluagenerator.py @@ -305,7 +305,7 @@ class PretenseLuaGenerator(LuaGenerator): lua_string_zones += " }\n" lua_string_zones += "})\n" - lua_string_connman = " cm = ConnectionManager:new()" + lua_string_connman = " cm = ConnectionManager:new()\n" # Generate ConnectionManager connections for cp in self.game.theater.controlpoints: @@ -313,14 +313,32 @@ class PretenseLuaGenerator(LuaGenerator): lua_string_connman += ( f" cm: addConnection('{cp.name}', '{other_cp.name}')\n" ) - # Also connect carrier and LHA control points to adjacent friendly points - if cp.is_fleet and len(cp.connected_points) == 0: - for other_cp in self.game.theater.closest_friendly_control_points_to( - cp - ): + for sea_connection in cp.shipping_lanes: + if sea_connection.is_friendly_to(cp): lua_string_connman += ( - f" cm: addConnection('{cp.name}', '{other_cp.name}')\n" + f" cm: addConnection('{cp.name}', '{sea_connection.name}')\n" ) + if len(cp.connected_points) == 0 and len(cp.shipping_lanes) == 0: + # Also connect carrier and LHA control points to adjacent friendly points + if cp.is_fleet: + for ( + other_cp + ) in self.game.theater.closest_friendly_control_points_to(cp): + lua_string_connman += ( + f" cm: addConnection('{cp.name}', '{other_cp.name}')\n" + ) + else: + # Finally, connect remaining non-connected points + ( + closest_cp, + second_closest_cp, + ) = self.game.theater.closest_friendly_control_points_to(cp) + lua_string_connman += ( + f" cm: addConnection('{cp.name}', '{closest_cp.name}')\n" + ) + lua_string_connman += ( + f" cm: addConnection('{cp.name}', '{second_closest_cp.name}')\n" + ) init_body_1_file = open("./resources/plugins/pretense/init_body_1.lua", "r") init_body_1 = init_body_1_file.read() diff --git a/game/theater/conflicttheater.py b/game/theater/conflicttheater.py index f11bfabf..2f2f9498 100644 --- a/game/theater/conflicttheater.py +++ b/game/theater/conflicttheater.py @@ -209,10 +209,9 @@ class ConflictTheater: Returns a tuple of the two nearest friendly ControlPoints in theater to ControlPoint cp. (closest_cp, second_closest_cp) """ - seen = set() - min_distance = math.inf closest_cp = None second_closest_cp = None + distances_to_cp = dict() if cp.captured: control_points = self.player_points() else: @@ -220,11 +219,21 @@ class ConflictTheater: for other_cp in control_points: if cp == other_cp: continue + print(f"{cp}: {other_cp} being evaluated...") + dist = other_cp.position.distance_to_point(cp.position) - if dist < min_distance: - second_closest_cp = closest_cp + print(f" {other_cp} is at {dist} meters") + distances_to_cp[dist] = other_cp + for i in sorted(distances_to_cp.keys()): + other_cp = distances_to_cp[i] + print(f" {other_cp} is at {i} meters") + if closest_cp is None: closest_cp = other_cp - min_distance = dist + continue + elif second_closest_cp is None: + second_closest_cp = other_cp + break + break assert closest_cp is not None assert second_closest_cp is not None From 0b8fa9099cdb268061ee3ba1b7d248ac872af3ea Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Sat, 16 Sep 2023 20:12:00 +0300 Subject: [PATCH 136/243] Disabled the base Retribution plugin in pretenseluagenerator.py and disabled adding zones for OffMapSpawns. --- game/pretense/pretenseluagenerator.py | 7 +++++-- game/theater/conflicttheater.py | 3 --- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/game/pretense/pretenseluagenerator.py b/game/pretense/pretenseluagenerator.py index fa71e08b..776d2b79 100644 --- a/game/pretense/pretenseluagenerator.py +++ b/game/pretense/pretenseluagenerator.py @@ -15,7 +15,7 @@ from game.ato import FlightType from game.dcs.aircrafttype import AircraftType from game.missiongenerator.luagenerator import LuaGenerator from game.plugins import LuaPluginManager -from game.theater import TheaterGroundObject, Airfield +from game.theater import TheaterGroundObject, Airfield, OffMapSpawn from game.theater.iadsnetwork.iadsrole import IadsRole from game.utils import escape_string_for_lua from game.missiongenerator.missiondata import MissionData @@ -68,6 +68,9 @@ class PretenseLuaGenerator(LuaGenerator): lua_string_zones = "" for cp in self.game.theater.controlpoints: + if isinstance(cp, OffMapSpawn): + continue + cp_name_trimmed = "".join([i for i in cp.name.lower() if i.isalnum()]) cp_side = 2 if cp.captured else 1 for side in range(1, 3): @@ -401,7 +404,7 @@ class PretenseLuaGenerator(LuaGenerator): def inject_plugins(self) -> None: for plugin in LuaPluginManager.plugins(): - if plugin.enabled: + if plugin.enabled and plugin.identifier not in ("base"): plugin.inject_scripts(self) plugin.inject_configuration(self) diff --git a/game/theater/conflicttheater.py b/game/theater/conflicttheater.py index 2f2f9498..25f43158 100644 --- a/game/theater/conflicttheater.py +++ b/game/theater/conflicttheater.py @@ -219,14 +219,11 @@ class ConflictTheater: for other_cp in control_points: if cp == other_cp: continue - print(f"{cp}: {other_cp} being evaluated...") dist = other_cp.position.distance_to_point(cp.position) - print(f" {other_cp} is at {dist} meters") distances_to_cp[dist] = other_cp for i in sorted(distances_to_cp.keys()): other_cp = distances_to_cp[i] - print(f" {other_cp} is at {i} meters") if closest_cp is None: closest_cp = other_cp continue From ae13a422c359744ba0222f862e3005b6105f5b58 Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Sat, 16 Sep 2023 20:14:50 +0300 Subject: [PATCH 137/243] Calling the constructor of MissionGenerator in PretenseMissionGenerator in unnecessary, so leaving it out. --- game/pretense/pretensemissiongenerator.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/game/pretense/pretensemissiongenerator.py b/game/pretense/pretensemissiongenerator.py index ee626082..52e7349c 100644 --- a/game/pretense/pretensemissiongenerator.py +++ b/game/pretense/pretensemissiongenerator.py @@ -55,11 +55,6 @@ if TYPE_CHECKING: class PretenseMissionGenerator(MissionGenerator): def __init__(self, game: Game, time: datetime) -> None: - super().__init__( - game, - time, - ) - self.game = game self.time = time self.mission = Mission(game.theater.terrain) From 992beb5f4524fedaa76420c7e7d2499adfa953ae Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Sat, 16 Sep 2023 20:57:11 +0300 Subject: [PATCH 138/243] Fixed a type assignment in pretensecargo.py --- game/ato/flightplans/pretensecargo.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/game/ato/flightplans/pretensecargo.py b/game/ato/flightplans/pretensecargo.py index f980b5f7..f5d86170 100644 --- a/game/ato/flightplans/pretensecargo.py +++ b/game/ato/flightplans/pretensecargo.py @@ -44,7 +44,7 @@ class PretenseCargoFlightPlan(StandardFlightPlan[FerryLayout]): class Builder(IBuilder[PretenseCargoFlightPlan, FerryLayout]): def layout(self) -> FerryLayout: # Find the spawn location for off-map transport planes - distance_to_flot = 0 + distance_to_flot = 0.0 heading_from_flot = 0.0 offmap_transport_cp_id = self.flight.departure.id for front_line_cp in self.coalition.game.theater.controlpoints: From b99a719d1ca1547978bd7172aa57f899fab413ac Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Sat, 16 Sep 2023 21:08:06 +0300 Subject: [PATCH 139/243] Converted closest_friendly_control_points_to from returning a tuple of the two closest control points to returning a list of all in sorted order. --- game/pretense/pretenseluagenerator.py | 19 +++++++++++++------ game/theater/conflicttheater.py | 21 +++++---------------- 2 files changed, 18 insertions(+), 22 deletions(-) diff --git a/game/pretense/pretenseluagenerator.py b/game/pretense/pretenseluagenerator.py index 776d2b79..cb80b397 100644 --- a/game/pretense/pretenseluagenerator.py +++ b/game/pretense/pretenseluagenerator.py @@ -23,6 +23,8 @@ from game.missiongenerator.missiondata import MissionData if TYPE_CHECKING: from game import Game +PRETENSE_NUMBER_OF_ZONES_TO_CONNECT_CARRIERS_TO = 2 + class PretenseLuaGenerator(LuaGenerator): def __init__( @@ -324,23 +326,28 @@ class PretenseLuaGenerator(LuaGenerator): if len(cp.connected_points) == 0 and len(cp.shipping_lanes) == 0: # Also connect carrier and LHA control points to adjacent friendly points if cp.is_fleet: + num_of_carrier_connections = 0 for ( other_cp ) in self.game.theater.closest_friendly_control_points_to(cp): + num_of_carrier_connections += 1 + if ( + num_of_carrier_connections + > PRETENSE_NUMBER_OF_ZONES_TO_CONNECT_CARRIERS_TO + ): + break + lua_string_connman += ( f" cm: addConnection('{cp.name}', '{other_cp.name}')\n" ) else: # Finally, connect remaining non-connected points - ( - closest_cp, - second_closest_cp, - ) = self.game.theater.closest_friendly_control_points_to(cp) + closest_cps = self.game.theater.closest_friendly_control_points_to(cp) lua_string_connman += ( - f" cm: addConnection('{cp.name}', '{closest_cp.name}')\n" + f" cm: addConnection('{cp.name}', '{closest_cps[0].name}')\n" ) lua_string_connman += ( - f" cm: addConnection('{cp.name}', '{second_closest_cp.name}')\n" + f" cm: addConnection('{cp.name}', '{closest_cps[1].name}')\n" ) init_body_1_file = open("./resources/plugins/pretense/init_body_1.lua", "r") diff --git a/game/theater/conflicttheater.py b/game/theater/conflicttheater.py index 25f43158..875a2659 100644 --- a/game/theater/conflicttheater.py +++ b/game/theater/conflicttheater.py @@ -204,13 +204,11 @@ class ConflictTheater: def closest_friendly_control_points_to( self, cp: ControlPoint - ) -> Tuple[ControlPoint, ControlPoint]: + ) -> List[ControlPoint]: """ - Returns a tuple of the two nearest friendly ControlPoints in theater to ControlPoint cp. - (closest_cp, second_closest_cp) + Returns a list of the friendly ControlPoints in theater to ControlPoint cp, sorted closest to farthest. """ - closest_cp = None - second_closest_cp = None + closest_cps = list() distances_to_cp = dict() if cp.captured: control_points = self.player_points() @@ -223,18 +221,9 @@ class ConflictTheater: dist = other_cp.position.distance_to_point(cp.position) distances_to_cp[dist] = other_cp for i in sorted(distances_to_cp.keys()): - other_cp = distances_to_cp[i] - if closest_cp is None: - closest_cp = other_cp - continue - elif second_closest_cp is None: - second_closest_cp = other_cp - break - break + closest_cps.append(distances_to_cp[i]) - assert closest_cp is not None - assert second_closest_cp is not None - return closest_cp, second_closest_cp + return closest_cps def find_control_point_by_id(self, cp_id: UUID) -> ControlPoint: for i in self.controlpoints: From 3c139d803859453952ae4277b9d6519dddb38439 Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Sat, 16 Sep 2023 21:11:43 +0300 Subject: [PATCH 140/243] Fixed conflicting types for cp in generate_mid_mission(). --- game/pretense/pretenseflightgroupspawner.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/game/pretense/pretenseflightgroupspawner.py b/game/pretense/pretenseflightgroupspawner.py index ea990924..d3b20b7e 100644 --- a/game/pretense/pretenseflightgroupspawner.py +++ b/game/pretense/pretenseflightgroupspawner.py @@ -170,8 +170,7 @@ class PretenseFlightGroupSpawner(FlightGroupSpawner): def generate_mid_mission(self) -> FlyingGroup[Any]: assert isinstance(self.flight.state, InFlight) - cp = self.flight.departure - name = namegen.next_pretense_aircraft_name(cp, self.flight) + name = namegen.next_pretense_aircraft_name(self.flight.departure, self.flight) speed = self.flight.state.estimate_speed() pos = self.flight.state.estimate_position() pos += Vector2(random.randint(100, 1000), random.randint(100, 1000)) From 51462673a288c244a80b48daf079a1308668265f Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Sat, 16 Sep 2023 21:11:55 +0300 Subject: [PATCH 141/243] Cleaned up pretense/pretenseaircraftgenerator.py Moved a lot of logic into proper methods and added comments for all of those methods. --- game/pretense/pretenseaircraftgenerator.py | 200 +++++++++++++-------- 1 file changed, 129 insertions(+), 71 deletions(-) diff --git a/game/pretense/pretenseaircraftgenerator.py b/game/pretense/pretenseaircraftgenerator.py index 60dc0c3a..1d208128 100644 --- a/game/pretense/pretenseaircraftgenerator.py +++ b/game/pretense/pretenseaircraftgenerator.py @@ -98,34 +98,11 @@ class PretenseAircraftGenerator: for parking_slot in cp.parking_slots: parking_slot.unit_id = None - def generate_flights( - self, - country: Country, - cp: ControlPoint, - ato: AirTaskingOrder, - dynamic_runways: Dict[str, RunwayData], - ) -> None: - from game.squadrons import Squadron - - """Adds aircraft to the mission for every flight in the ATO. - - Aircraft generation is done by walking the ATO and spawning each flight in turn. - After the flight is generated the group is added to the UnitMap so aircraft - deaths can be tracked. - - Args: - country: The country from the mission to use for this ATO. - ato: The ATO to spawn aircraft for. - dynamic_runways: Runway data for carriers and FARPs. + def find_pretense_cargo_plane_cp(self, cp: ControlPoint) -> ControlPoint: """ - - num_of_sead = 0 - num_of_cas = 0 - num_of_strike = 0 - num_of_cap = 0 - - # Find locations for off-map transport planes - distance_to_flot = 0 + Finds the location (ControlPoint) for Pretense off-map transport planes. + """ + distance_to_flot = 0.0 offmap_transport_cp_id = cp.id parking_type = ParkingType( fixed_wing=True, fixed_wing_stol=True, rotary_wing=False @@ -144,11 +121,19 @@ class PretenseAircraftGenerator: front_line.position ) offmap_transport_cp_id = front_line_cp.id - offmap_transport_cp = self.game.theater.find_control_point_by_id( - offmap_transport_cp_id - ) + return self.game.theater.find_control_point_by_id(offmap_transport_cp_id) - # Ensure that the faction has at least one transport helicopter and one cargo plane squadron + def should_generate_pretense_transports( + self, cp: ControlPoint + ) -> tuple[bool, bool]: + """ + Returns a tuple of booleans, telling whether transport helicopter and aircraft + squadrons should be generated from the faction squadron definitions. + + This helps to ensure that the faction has at least one transport helicopter and one cargo plane squadron. + + (autogenerate_transport_helicopter_squadron, autogenerate_cargo_plane_squadron) + """ autogenerate_transport_helicopter_squadron = True autogenerate_cargo_plane_squadron = True for aircraft_type in cp.coalition.air_wing.squadrons: @@ -164,48 +149,80 @@ class PretenseAircraftGenerator: or FlightType.AIR_ASSAULT in mission_types ): autogenerate_cargo_plane_squadron = False + return ( + autogenerate_transport_helicopter_squadron, + autogenerate_cargo_plane_squadron, + ) - if autogenerate_transport_helicopter_squadron: - flight_type = FlightType.AIR_ASSAULT - squadron_def = ( - cp.coalition.air_wing.squadron_def_generator.generate_for_task( - flight_type, offmap_transport_cp - ) - ) - squadron = Squadron.create_from( - squadron_def, - flight_type, - 2, - offmap_transport_cp, - cp.coalition, - self.game, - ) - cp.coalition.air_wing.squadrons[squadron.aircraft] = list() - cp.coalition.air_wing.add_squadron(squadron) - if autogenerate_cargo_plane_squadron: - flight_type = FlightType.TRANSPORT - squadron_def = ( - cp.coalition.air_wing.squadron_def_generator.generate_for_task( - flight_type, offmap_transport_cp - ) - ) - for retries in range(PRETENSE_SQUADRON_DEF_RETRIES): - if squadron_def.aircraft.helicopter: - squadron_def = ( - cp.coalition.air_wing.squadron_def_generator.generate_for_task( - flight_type, offmap_transport_cp - ) + def generate_pretense_transport_squadron( + self, + cp: ControlPoint, + flight_type: FlightType, + fixed_wing: bool, + num_retries: int, + ) -> None: + from game.squadrons import Squadron + + """ + Generates a Pretense transport squadron from the faction squadron definitions. Use FlightType AIR_ASSAULT + for Pretense supply helicopters and TRANSPORT for off-map cargo plane squadrons. + + Retribution does not differentiate between fixed wing and rotary wing transport squadron definitions, which + is why there is a retry mechanism in case the wrong type is returned. Use fixed_wing False + for Pretense supply helicopters and fixed_wing True for off-map cargo plane squadrons. + + TODO: Find out if Pretense can handle rotary wing "cargo planes". + """ + + squadron_def = cp.coalition.air_wing.squadron_def_generator.generate_for_task( + flight_type, cp + ) + print( + f"Generating a squadron definition for fixed-wing {fixed_wing} squadron at {cp}" + ) + for retries in range(num_retries): + if squadron_def is None or fixed_wing != squadron_def.aircraft.helicopter: + squadron_def = ( + cp.coalition.air_wing.squadron_def_generator.generate_for_task( + flight_type, cp ) - squadron = Squadron.create_from( - squadron_def, - flight_type, - 2, - offmap_transport_cp, - cp.coalition, - self.game, - ) - cp.coalition.air_wing.squadrons[squadron.aircraft] = list() - cp.coalition.air_wing.add_squadron(squadron) + ) + + # Failed, stop here + if squadron_def is None: + return + + squadron = Squadron.create_from( + squadron_def, + flight_type, + 2, + cp, + cp.coalition, + self.game, + ) + cp.coalition.air_wing.squadrons[squadron.aircraft] = list() + cp.coalition.air_wing.add_squadron(squadron) + return + + def generate_pretense_aircraft( + self, cp: ControlPoint, ato: AirTaskingOrder + ) -> None: + """ + Plans and generates AI aircraft groups/packages for Pretense. + + Aircraft generation is done by walking the control points which will be made into + Pretense "zones" and spawning flights for different missions. + After the flight is generated the package is added to the ATO so the flights + can be configured. + + Args: + cp: Control point to generate aircraft for. + ato: The ATO to generate aircraft for. + """ + num_of_sead = 0 + num_of_cas = 0 + num_of_strike = 0 + num_of_cap = 0 for squadron in cp.squadrons: # Intentionally don't spawn anything at OffMapSpawns in Pretense @@ -279,6 +296,47 @@ class PretenseAircraftGenerator: ) ato.add_package(package) + return + + def generate_flights( + self, + country: Country, + cp: ControlPoint, + ato: AirTaskingOrder, + dynamic_runways: Dict[str, RunwayData], + ) -> None: + """Adds aircraft to the mission for every flight in the ATO. + + Args: + country: The country from the mission to use for this ATO. + cp: Control point to generate aircraft for. + ato: The ATO to generate aircraft for. + dynamic_runways: Runway data for carriers and FARPs. + """ + + offmap_transport_cp = self.find_pretense_cargo_plane_cp(cp) + + ( + autogenerate_transport_helicopter_squadron, + autogenerate_cargo_plane_squadron, + ) = self.should_generate_pretense_transports(cp) + + if autogenerate_transport_helicopter_squadron: + self.generate_pretense_transport_squadron( + offmap_transport_cp, + FlightType.AIR_ASSAULT, + False, + PRETENSE_SQUADRON_DEF_RETRIES, + ) + if autogenerate_cargo_plane_squadron: + self.generate_pretense_transport_squadron( + offmap_transport_cp, + FlightType.TRANSPORT, + True, + PRETENSE_SQUADRON_DEF_RETRIES, + ) + + self.generate_pretense_aircraft(cp, ato) self._reserve_frequencies_and_tacan(ato) From a97b3fee99dc25b5961ee616589365a5582f66ae Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Sat, 16 Sep 2023 23:34:05 +0300 Subject: [PATCH 142/243] Will now dynamically generate offmapZones for supply cargo aircraft. --- game/pretense/pretenseluagenerator.py | 27 ++++++++++++++++++++++ resources/plugins/pretense/init_body_2.lua | 15 +----------- 2 files changed, 28 insertions(+), 14 deletions(-) diff --git a/game/pretense/pretenseluagenerator.py b/game/pretense/pretenseluagenerator.py index cb80b397..d4005d08 100644 --- a/game/pretense/pretenseluagenerator.py +++ b/game/pretense/pretenseluagenerator.py @@ -350,6 +350,32 @@ class PretenseLuaGenerator(LuaGenerator): f" cm: addConnection('{cp.name}', '{closest_cps[1].name}')\n" ) + lua_string_supply = "local redSupply = {\n" + # Generate ConnectionManager connections + for cp_side in range(1, 3): + for cp in self.game.theater.controlpoints: + if isinstance(cp, OffMapSpawn): + continue + cp_side_captured = cp_side == 2 + if cp_side_captured != cp.captured: + continue + cp_name_trimmed = "".join([i for i in cp.name.lower() if i.isalnum()]) + for mission_type in self.game.pretense_air[cp_side][cp_name_trimmed]: + if mission_type == FlightType.TRANSPORT.name: + for air_group in self.game.pretense_air[cp_side][ + cp_name_trimmed + ][mission_type]: + lua_string_supply += f"'{air_group}'," + lua_string_supply += "}\n" + if cp_side < 2: + lua_string_supply += "local blueSupply = {\n" + lua_string_supply += "local offmapZones = {\n" + for cp in self.game.theater.controlpoints: + if isinstance(cp, Airfield): + cp_name_trimmed = "".join([i for i in cp.name.lower() if i.isalnum()]) + lua_string_supply += f" zones.{cp_name_trimmed},\n" + lua_string_supply += "}\n" + init_body_1_file = open("./resources/plugins/pretense/init_body_1.lua", "r") init_body_1 = init_body_1_file.read() @@ -373,6 +399,7 @@ class PretenseLuaGenerator(LuaGenerator): + init_body_1 + lua_string_jtac + init_body_2 + + lua_string_supply + init_footer ) diff --git a/resources/plugins/pretense/init_body_2.lua b/resources/plugins/pretense/init_body_2.lua index 9d2798d8..9994dce9 100644 --- a/resources/plugins/pretense/init_body_2.lua +++ b/resources/plugins/pretense/init_body_2.lua @@ -58,18 +58,5 @@ end, {}, timer.getTime()+30) --supply injection -local blueSupply = {'offmap-supply-blue-1','offmap-supply-blue-2','offmap-supply-blue-3','offmap-supply-blue-4','offmap-supply-blue-5'} -local redSupply = {'offmap-supply-red-1','offmap-supply-red-2','offmap-supply-red-3','offmap-supply-red-4','offmap-supply-red-5'} -local offmapZones = { - zones.batumi, - zones.sochi, - zones.nalchik, - zones.beslan, - zones.mozdok, - zones.mineralnye, --- zones.senaki, --- zones.sukhumi, --- zones.gudauta, --- zones.kobuleti, -} + From d0ff363bb52f164ae565119d5de19f5109b4a557 Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Sun, 17 Sep 2023 11:16:18 +0300 Subject: [PATCH 143/243] Now splits the generated Pretense group role properly in create_vehicle_group(). --- game/pretense/pretensetgogenerator.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/game/pretense/pretensetgogenerator.py b/game/pretense/pretensetgogenerator.py index 639e74e5..ed86f157 100644 --- a/game/pretense/pretensetgogenerator.py +++ b/game/pretense/pretensetgogenerator.py @@ -294,9 +294,15 @@ class PretenseGroundObjectGenerator(GroundObjectGenerator): self.set_alarm_state(vehicle_group) GroundForcePainter(faction, vehicle_group.units[0]).apply_livery() - self.game.pretense_ground_supply[cp_side][cp_name_trimmed].append( - f"{vehicle_group.name}" - ) + group_role = group_name.split("-")[1] + if group_role == "supply": + self.game.pretense_ground_supply[cp_side][cp_name_trimmed].append( + f"{vehicle_group.name}" + ) + elif group_role == "assault": + self.game.pretense_ground_assault[cp_side][cp_name_trimmed].append( + f"{vehicle_group.name}" + ) else: vehicle_unit = self.m.vehicle(unit.unit_name, unit.type) vehicle_unit.player_can_drive = True From aa641e1ff62ed26ddcf3fed0aaa62f71d4a6dee3 Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Sun, 17 Sep 2023 11:21:43 +0300 Subject: [PATCH 144/243] Added configuration constants for flights generated for Pretense. Fixed a bug which caused only one squadron per CP to be generated. Will now not generate Pretense cargo flights from Retribution off-map spawns, but instead will generate own air spawn points for them. Added a helper function initialize_pretense_data_structures(). --- game/ato/flightplans/pretensecargo.py | 3 + game/pretense/pretenseaircraftgenerator.py | 102 ++++++++++++++++---- game/pretense/pretenseflightgroupspawner.py | 17 ++-- game/pretense/pretenseluagenerator.py | 2 +- 4 files changed, 92 insertions(+), 32 deletions(-) diff --git a/game/ato/flightplans/pretensecargo.py b/game/ato/flightplans/pretensecargo.py index f5d86170..9aa3472f 100644 --- a/game/ato/flightplans/pretensecargo.py +++ b/game/ato/flightplans/pretensecargo.py @@ -11,6 +11,7 @@ from .ibuilder import IBuilder from .planningerror import PlanningError from .standard import StandardFlightPlan, StandardLayout from .waypointbuilder import WaypointBuilder +from ...theater import OffMapSpawn if TYPE_CHECKING: from ..flightwaypoint import FlightWaypoint @@ -48,6 +49,8 @@ class Builder(IBuilder[PretenseCargoFlightPlan, FerryLayout]): heading_from_flot = 0.0 offmap_transport_cp_id = self.flight.departure.id for front_line_cp in self.coalition.game.theater.controlpoints: + if isinstance(front_line_cp, OffMapSpawn): + continue for front_line in self.coalition.game.theater.conflicts(): if front_line_cp.captured == self.flight.coalition.player: if ( diff --git a/game/pretense/pretenseaircraftgenerator.py b/game/pretense/pretenseaircraftgenerator.py index 1d208128..35900a06 100644 --- a/game/pretense/pretenseaircraftgenerator.py +++ b/game/pretense/pretenseaircraftgenerator.py @@ -27,6 +27,7 @@ from game.radio.radios import RadioRegistry from game.radio.tacan import TacanRegistry from game.runways import RunwayData from game.settings import Settings +from game.squadrons import AirWing from game.theater.controlpoint import ( ControlPoint, OffMapSpawn, @@ -42,6 +43,13 @@ if TYPE_CHECKING: PRETENSE_SQUADRON_DEF_RETRIES = 100 +PRETENSE_SEAD_FLIGHTS_PER_CP = 1 +PRETENSE_CAS_FLIGHTS_PER_CP = 1 +PRETENSE_STRIKE_FLIGHTS_PER_CP = 1 +PRETENSE_BARCAP_FLIGHTS_PER_CP = 1 +PRETENSE_AI_AIRCRAFT_PER_FLIGHT = 2 +PRETENSE_AI_AWACS_PER_FLIGHT = 1 +PRETENSE_AI_TANKERS_PER_FLIGHT = 1 class PretenseAircraftGenerator: @@ -108,6 +116,8 @@ class PretenseAircraftGenerator: fixed_wing=True, fixed_wing_stol=True, rotary_wing=False ) for front_line_cp in self.game.theater.controlpoints: + if isinstance(front_line_cp, OffMapSpawn): + continue for front_line in self.game.theater.conflicts(): if front_line_cp.captured == cp.captured: if ( @@ -124,7 +134,7 @@ class PretenseAircraftGenerator: return self.game.theater.find_control_point_by_id(offmap_transport_cp_id) def should_generate_pretense_transports( - self, cp: ControlPoint + self, air_wing: AirWing ) -> tuple[bool, bool]: """ Returns a tuple of booleans, telling whether transport helicopter and aircraft @@ -136,17 +146,16 @@ class PretenseAircraftGenerator: """ autogenerate_transport_helicopter_squadron = True autogenerate_cargo_plane_squadron = True - for aircraft_type in cp.coalition.air_wing.squadrons: - for squadron in cp.coalition.air_wing.squadrons[aircraft_type]: - mission_types = squadron.auto_assignable_mission_types + for aircraft_type in air_wing.squadrons: + for squadron in air_wing.squadrons[aircraft_type]: if squadron.aircraft.helicopter and ( - FlightType.TRANSPORT in mission_types - or FlightType.AIR_ASSAULT in mission_types + squadron.aircraft.capable_of(FlightType.TRANSPORT) + or squadron.aircraft.capable_of(FlightType.AIR_ASSAULT) ): autogenerate_transport_helicopter_squadron = False elif not squadron.aircraft.helicopter and ( - FlightType.TRANSPORT in mission_types - or FlightType.AIR_ASSAULT in mission_types + squadron.aircraft.capable_of(FlightType.TRANSPORT) + or squadron.aircraft.capable_of(FlightType.AIR_ASSAULT) ): autogenerate_cargo_plane_squadron = False return ( @@ -181,7 +190,7 @@ class PretenseAircraftGenerator: f"Generating a squadron definition for fixed-wing {fixed_wing} squadron at {cp}" ) for retries in range(num_retries): - if squadron_def is None or fixed_wing != squadron_def.aircraft.helicopter: + if squadron_def is None or fixed_wing == squadron_def.aircraft.helicopter: squadron_def = ( cp.coalition.air_wing.squadron_def_generator.generate_for_task( flight_type, cp @@ -200,7 +209,8 @@ class PretenseAircraftGenerator: cp.coalition, self.game, ) - cp.coalition.air_wing.squadrons[squadron.aircraft] = list() + if squadron.aircraft not in cp.coalition.air_wing.squadrons: + cp.coalition.air_wing.squadrons[squadron.aircraft] = list() cp.coalition.air_wing.add_squadron(squadron) return @@ -229,10 +239,11 @@ class PretenseAircraftGenerator: if isinstance(squadron.location, OffMapSpawn): continue - squadron.owned_aircraft += 1 - squadron.untasked_aircraft += 1 + squadron.owned_aircraft += PRETENSE_AI_AIRCRAFT_PER_FLIGHT + squadron.untasked_aircraft += PRETENSE_AI_AIRCRAFT_PER_FLIGHT package = Package(cp, squadron.flight_db, auto_asap=False) mission_types = squadron.auto_assignable_mission_types + aircraft_per_flight = 1 if squadron.aircraft.helicopter and ( FlightType.TRANSPORT in mission_types or FlightType.AIR_ASSAULT in mission_types @@ -247,39 +258,54 @@ class PretenseAircraftGenerator: FlightType.SEAD in mission_types or FlightType.SEAD_SWEEP in mission_types or FlightType.SEAD_ESCORT in mission_types - ) and num_of_sead < 2: + ) and num_of_sead < PRETENSE_SEAD_FLIGHTS_PER_CP: flight_type = FlightType.SEAD num_of_sead += 1 - elif FlightType.DEAD in mission_types and num_of_sead < 2: + aircraft_per_flight = PRETENSE_AI_AIRCRAFT_PER_FLIGHT + elif ( + FlightType.DEAD in mission_types + and num_of_sead < PRETENSE_SEAD_FLIGHTS_PER_CP + ): flight_type = FlightType.DEAD num_of_sead += 1 + aircraft_per_flight = PRETENSE_AI_AIRCRAFT_PER_FLIGHT elif ( FlightType.CAS in mission_types or FlightType.BAI in mission_types - ) and num_of_cas < 2: + ) and num_of_cas < PRETENSE_CAS_FLIGHTS_PER_CP: flight_type = FlightType.CAS num_of_cas += 1 + aircraft_per_flight = PRETENSE_AI_AIRCRAFT_PER_FLIGHT elif ( FlightType.STRIKE in mission_types or FlightType.OCA_RUNWAY in mission_types or FlightType.OCA_AIRCRAFT in mission_types - ) and num_of_strike < 2: + ) and num_of_strike < PRETENSE_STRIKE_FLIGHTS_PER_CP: flight_type = FlightType.STRIKE num_of_strike += 1 + aircraft_per_flight = PRETENSE_AI_AIRCRAFT_PER_FLIGHT elif ( FlightType.BARCAP in mission_types or FlightType.TARCAP in mission_types or FlightType.ESCORT in mission_types - ) and num_of_cap < 2: + ) and num_of_cap < PRETENSE_BARCAP_FLIGHTS_PER_CP: flight_type = FlightType.BARCAP num_of_cap += 1 + aircraft_per_flight = PRETENSE_AI_AIRCRAFT_PER_FLIGHT + elif FlightType.AEWC in mission_types: + aircraft_per_flight = PRETENSE_AI_AWACS_PER_FLIGHT + elif FlightType.REFUELING in mission_types: + aircraft_per_flight = PRETENSE_AI_TANKERS_PER_FLIGHT else: flight_type = random.choice(list(mission_types)) + print( + f"Generating flight of {aircraft_per_flight} for {flight_type}: {squadron.aircraft}" + ) if flight_type == FlightType.TRANSPORT: flight = Flight( package, squadron, - 1, + aircraft_per_flight, FlightType.PRETENSE_CARGO, StartType.IN_FLIGHT, divert=cp, @@ -288,7 +314,12 @@ class PretenseAircraftGenerator: flight.state = Navigating(flight, self.game.settings, waypoint_index=1) else: flight = Flight( - package, squadron, 1, flight_type, StartType.COLD, divert=cp + package, + squadron, + aircraft_per_flight, + flight_type, + StartType.COLD, + divert=cp, ) package.add_flight(flight) flight.state = WaitingForStart( @@ -296,7 +327,34 @@ class PretenseAircraftGenerator: ) ato.add_package(package) - return + return + + def initialize_pretense_data_structures( + self, cp: ControlPoint, flight: Flight + ) -> None: + """ + Ensures that the data structures used to pass flight group information + to the Pretense init script lua are initialized for use in + PretenseFlightGroupSpawner and PretenseLuaGenerator. + + Args: + cp: Control point to generate aircraft for. + flight: The current flight being generated. + """ + flight_type = flight.flight_type.name + cp_side = 2 if cp.captured else 1 + cp_name_trimmed = "".join([i for i in cp.name.lower() if i.isalnum()]) + + if cp_name_trimmed not in flight.coalition.game.pretense_air[cp_side]: + flight.coalition.game.pretense_air[cp_side][cp_name_trimmed] = {} + if ( + flight_type + not in flight.coalition.game.pretense_air[cp_side][cp_name_trimmed] + ): + flight.coalition.game.pretense_air[cp_side][cp_name_trimmed][ + flight_type + ] = list() + return def generate_flights( self, @@ -319,7 +377,7 @@ class PretenseAircraftGenerator: ( autogenerate_transport_helicopter_squadron, autogenerate_cargo_plane_squadron, - ) = self.should_generate_pretense_transports(cp) + ) = self.should_generate_pretense_transports(cp.coalition.air_wing) if autogenerate_transport_helicopter_squadron: self.generate_pretense_transport_squadron( @@ -345,6 +403,8 @@ class PretenseAircraftGenerator: if not package.flights: continue for flight in package.flights: + self.initialize_pretense_data_structures(cp, flight) + if flight.alive: if not flight.squadron.location.runway_is_operational(): logging.warning( diff --git a/game/pretense/pretenseflightgroupspawner.py b/game/pretense/pretenseflightgroupspawner.py index d3b20b7e..6f785ceb 100644 --- a/game/pretense/pretenseflightgroupspawner.py +++ b/game/pretense/pretenseflightgroupspawner.py @@ -75,16 +75,6 @@ class PretenseFlightGroupSpawner(FlightGroupSpawner): name = namegen.next_pretense_aircraft_name(cp, self.flight) cp_side = 2 if cp.captured else 1 cp_name_trimmed = "".join([i for i in cp.name.lower() if i.isalnum()]) - flight_type = self.flight.flight_type.name - if cp_name_trimmed not in self.flight.coalition.game.pretense_air[cp_side]: - self.flight.coalition.game.pretense_air[cp_side][cp_name_trimmed] = {} - if ( - flight_type - not in self.flight.coalition.game.pretense_air[cp_side][cp_name_trimmed] - ): - self.flight.coalition.game.pretense_air[cp_side][cp_name_trimmed][ - flight_type - ] = list() try: if self.start_type is StartType.IN_FLIGHT: @@ -171,6 +161,10 @@ class PretenseFlightGroupSpawner(FlightGroupSpawner): def generate_mid_mission(self) -> FlyingGroup[Any]: assert isinstance(self.flight.state, InFlight) name = namegen.next_pretense_aircraft_name(self.flight.departure, self.flight) + cp_side = 2 if self.flight.departure.captured else 1 + cp_name_trimmed = "".join( + [i for i in self.flight.departure.name.lower() if i.isalnum()] + ) speed = self.flight.state.estimate_speed() pos = self.flight.state.estimate_position() pos += Vector2(random.randint(100, 1000), random.randint(100, 1000)) @@ -192,6 +186,9 @@ class PretenseFlightGroupSpawner(FlightGroupSpawner): alt = self.mission_data.cp_stack[cp] self.mission_data.cp_stack[cp] += STACK_SEPARATION + self.flight.coalition.game.pretense_air[cp_side][cp_name_trimmed][ + self.flight.flight_type.name + ].append(name) group = self.mission.flight_group( country=self.country, name=name, diff --git a/game/pretense/pretenseluagenerator.py b/game/pretense/pretenseluagenerator.py index d4005d08..a5e6b86e 100644 --- a/game/pretense/pretenseluagenerator.py +++ b/game/pretense/pretenseluagenerator.py @@ -361,7 +361,7 @@ class PretenseLuaGenerator(LuaGenerator): continue cp_name_trimmed = "".join([i for i in cp.name.lower() if i.isalnum()]) for mission_type in self.game.pretense_air[cp_side][cp_name_trimmed]: - if mission_type == FlightType.TRANSPORT.name: + if mission_type == FlightType.PRETENSE_CARGO.name: for air_group in self.game.pretense_air[cp_side][ cp_name_trimmed ][mission_type]: From 4b092e076313bf15fc4e931c3caf94818d6a3caf Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Sun, 17 Sep 2023 12:29:21 +0300 Subject: [PATCH 145/243] Fixed FlightType.AEWC and FlightType.REFUELING handling in generate_pretense_aircraft(). --- game/pretense/pretenseaircraftgenerator.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/game/pretense/pretenseaircraftgenerator.py b/game/pretense/pretenseaircraftgenerator.py index 35900a06..aebf990b 100644 --- a/game/pretense/pretenseaircraftgenerator.py +++ b/game/pretense/pretenseaircraftgenerator.py @@ -292,15 +292,16 @@ class PretenseAircraftGenerator: num_of_cap += 1 aircraft_per_flight = PRETENSE_AI_AIRCRAFT_PER_FLIGHT elif FlightType.AEWC in mission_types: + flight_type = FlightType.AEWC aircraft_per_flight = PRETENSE_AI_AWACS_PER_FLIGHT elif FlightType.REFUELING in mission_types: + flight_type = FlightType.REFUELING aircraft_per_flight = PRETENSE_AI_TANKERS_PER_FLIGHT else: + if len(list(mission_types)) == 0: + continue flight_type = random.choice(list(mission_types)) - print( - f"Generating flight of {aircraft_per_flight} for {flight_type}: {squadron.aircraft}" - ) if flight_type == FlightType.TRANSPORT: flight = Flight( package, From 4e4eeff9ecc387e370f1afcaf214c28d8a5ed32d Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Sun, 17 Sep 2023 13:09:02 +0300 Subject: [PATCH 146/243] Split the Pretense zone definition generation into separate methods: generate_pretense_zone_land() for land control points and generate_pretense_zone_sea() for carriers/LHAs. --- game/pretense/pretenseluagenerator.py | 622 +++++++++++++++++--------- 1 file changed, 418 insertions(+), 204 deletions(-) diff --git a/game/pretense/pretenseluagenerator.py b/game/pretense/pretenseluagenerator.py index a5e6b86e..966724ef 100644 --- a/game/pretense/pretenseluagenerator.py +++ b/game/pretense/pretenseluagenerator.py @@ -54,6 +54,420 @@ class PretenseLuaGenerator(LuaGenerator): self.mission.triggerrules.triggers.remove(t) self.mission.triggerrules.triggers.append(t) + def generate_pretense_zone_land(self, cp_name: str, cp_side: int) -> str: + lua_string_zones = "" + cp_name_trimmed = "".join([i for i in cp_name.lower() if i.isalnum()]) + + lua_string_zones += f"zones.{cp_name_trimmed}:defineUpgrades(" + "{\n" + lua_string_zones += " [1] = { --red side\n" + lua_string_zones += " presets.upgrades.basic.tent:extend({\n" + lua_string_zones += f" name='{cp_name_trimmed}-tent-red',\n" + lua_string_zones += " products = {\n" + lua_string_zones += ( + " presets.special.red.infantry:extend({ name='" + + cp_name_trimmed + + "-defense-red'})\n" + ) + lua_string_zones += " }\n" + lua_string_zones += " }),\n" + lua_string_zones += " presets.upgrades.basic.comPost:extend({\n" + lua_string_zones += f" name = '{cp_name_trimmed}-com-red',\n" + lua_string_zones += " products = {\n" + lua_string_zones += ( + " presets.special.red.infantry:extend({ name='" + + cp_name_trimmed + + "-defense-red'}),\n" + ) + lua_string_zones += ( + " presets.defenses.red.infantry:extend({ name='" + + cp_name_trimmed + + "-garrison-red' })\n" + ) + lua_string_zones += " }\n" + lua_string_zones += " }),\n" + lua_string_zones += " },\n" + lua_string_zones += " [2] = --blue side\n" + lua_string_zones += " {\n" + lua_string_zones += " presets.upgrades.basic.tent:extend({\n" + lua_string_zones += f" name='{cp_name_trimmed}-tent-blue',\n" + lua_string_zones += " products = {\n" + lua_string_zones += ( + " presets.special.blue.infantry:extend({ name='" + + cp_name_trimmed + + "-defense-blue'})\n" + ) + lua_string_zones += " }\n" + lua_string_zones += " }),\n" + lua_string_zones += " presets.upgrades.basic.comPost:extend({\n" + lua_string_zones += f" name = '{cp_name_trimmed}-com-blue',\n" + lua_string_zones += " products = {\n" + lua_string_zones += ( + " presets.special.blue.infantry:extend({ name='" + + cp_name_trimmed + + "-defense-blue'}),\n" + ) + lua_string_zones += ( + " presets.defenses.blue.infantry:extend({ name='" + + cp_name_trimmed + + "-garrison-blue' })\n" + ) + lua_string_zones += " }\n" + lua_string_zones += " }),\n" + lua_string_zones += " presets.upgrades.supply.fuelTank:extend({\n" + lua_string_zones += ( + " name = '" + cp_name_trimmed + "-fueltank-blue',\n" + ) + lua_string_zones += " products = {\n" + for ground_group in self.game.pretense_ground_supply[cp_side][cp_name_trimmed]: + lua_string_zones += ( + " presets.missions.supply.convoy:extend({ name='" + + ground_group + + "'}),\n" + ) + for ground_group in self.game.pretense_ground_assault[cp_side][cp_name_trimmed]: + lua_string_zones += ( + " presets.missions.attack.surface:extend({ name='" + + ground_group + + "'}),\n" + ) + for mission_type in self.game.pretense_air[cp_side][cp_name_trimmed]: + if mission_type == FlightType.AIR_ASSAULT.name: + mission_name = "supply.helo" + for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ + mission_type + ]: + lua_string_zones += ( + f" presets.missions.{mission_name}:extend" + + "({name='" + + air_group + + "'}),\n" + ) + lua_string_zones += " }\n" + lua_string_zones += " }),\n" + lua_string_zones += " presets.upgrades.airdef.comCenter:extend({\n" + lua_string_zones += ( + f" name = '{cp_name_trimmed}-mission-command-blue',\n" + ) + lua_string_zones += " products = {\n" + lua_string_zones += ( + " presets.defenses.blue.shorad:extend({ name='" + + cp_name_trimmed + + "-sam-blue' }),\n" + ) + for mission_type in self.game.pretense_air[cp_side][cp_name_trimmed]: + if mission_type == FlightType.SEAD.name: + mission_name = "attack.sead" + for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ + mission_type + ]: + lua_string_zones += ( + f" presets.missions.{mission_name}:extend" + + "({name='" + + air_group + + "', altitude=25000, expend=AI.Task.WeaponExpend.ALL}),\n" + ) + elif mission_type == FlightType.CAS.name: + mission_name = "attack.cas" + for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ + mission_type + ]: + lua_string_zones += ( + f" presets.missions.{mission_name}:extend" + + "({name='" + + air_group + + "', altitude=15000, expend=AI.Task.WeaponExpend.ONE}),\n" + ) + elif mission_type == FlightType.BAI.name: + mission_name = "attack.bai" + for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ + mission_type + ]: + lua_string_zones += ( + f" presets.missions.{mission_name}:extend" + + "({name='" + + air_group + + "', altitude=10000, expend=AI.Task.WeaponExpend.ONE}),\n" + ) + elif mission_type == FlightType.STRIKE.name: + mission_name = "attack.strike" + for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ + mission_type + ]: + lua_string_zones += ( + f" presets.missions.{mission_name}:extend" + + "({name='" + + air_group + + "', altitude=20000, expend=AI.Task.WeaponExpend.ALL}),\n" + ) + elif mission_type == FlightType.BARCAP.name: + mission_name = "patrol.aircraft" + for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ + mission_type + ]: + lua_string_zones += ( + f" presets.missions.{mission_name}:extend" + + "({name='" + + air_group + + "', altitude=25000, range=25}),\n" + ) + elif mission_type == FlightType.REFUELING.name: + mission_name = "support.tanker" + for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ + mission_type + ]: + tanker_freq = 257.0 + tanker_tacan = 37.0 + for tanker in self.mission_data.tankers: + if tanker.group_name == air_group: + tanker_freq = tanker.freq.hertz / 1000000 + tanker_tacan = tanker.tacan.number + if tanker.variant == "KC-135 Stratotanker": + tanker_variant = "Boom" + else: + tanker_variant = "Drogue" + lua_string_zones += ( + f" presets.missions.{mission_name}:extend" + + "({name='" + + air_group + + "', freq='" + + str(tanker_freq) + + "', tacan='" + + str(tanker_tacan) + + "', variant='" + + tanker_variant + + "'}),\n" + ) + elif mission_type == FlightType.AEWC.name: + mission_name = "support.awacs" + for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ + mission_type + ]: + awacs_freq = 257.5 + for awacs in self.mission_data.awacs: + if awacs.group_name == air_group: + awacs_freq = awacs.freq.hertz / 1000000 + lua_string_zones += ( + f" presets.missions.{mission_name}:extend" + + "({name='" + + air_group + + "', freq=" + + str(awacs_freq) + + "}),\n" + ) + lua_string_zones += " }\n" + lua_string_zones += " })\n" + lua_string_zones += " }\n" + lua_string_zones += "})\n" + + return lua_string_zones + + def generate_pretense_zone_sea(self, cp_name: str, cp_side: int) -> str: + lua_string_zones = "" + cp_name_trimmed = "".join([i for i in cp_name.lower() if i.isalnum()]) + + lua_string_zones += f"zones.{cp_name_trimmed}:defineUpgrades(" + "{\n" + lua_string_zones += " [1] = { --red side\n" + lua_string_zones += " presets.upgrades.basic.tent:extend({\n" + lua_string_zones += f" name='{cp_name_trimmed}-tent-red',\n" + lua_string_zones += " products = {\n" + lua_string_zones += ( + " presets.special.red.infantry:extend({ name='" + + cp_name_trimmed + + "-defense-red'})\n" + ) + lua_string_zones += " }\n" + lua_string_zones += " }),\n" + lua_string_zones += " presets.upgrades.basic.comPost:extend({\n" + lua_string_zones += f" name = '{cp_name_trimmed}-com-red',\n" + lua_string_zones += " products = {\n" + lua_string_zones += ( + " presets.special.red.infantry:extend({ name='" + + cp_name_trimmed + + "-defense-red'}),\n" + ) + lua_string_zones += ( + " presets.defenses.red.infantry:extend({ name='" + + cp_name_trimmed + + "-garrison-red' })\n" + ) + lua_string_zones += " }\n" + lua_string_zones += " }),\n" + lua_string_zones += " },\n" + lua_string_zones += " [2] = --blue side\n" + lua_string_zones += " {\n" + lua_string_zones += " presets.upgrades.basic.tent:extend({\n" + lua_string_zones += f" name='{cp_name_trimmed}-tent-blue',\n" + lua_string_zones += " products = {\n" + lua_string_zones += ( + " presets.special.blue.infantry:extend({ name='" + + cp_name_trimmed + + "-defense-blue'})\n" + ) + lua_string_zones += " }\n" + lua_string_zones += " }),\n" + lua_string_zones += " presets.upgrades.basic.comPost:extend({\n" + lua_string_zones += f" name = '{cp_name_trimmed}-com-blue',\n" + lua_string_zones += " products = {\n" + lua_string_zones += ( + " presets.special.blue.infantry:extend({ name='" + + cp_name_trimmed + + "-defense-blue'}),\n" + ) + lua_string_zones += ( + " presets.defenses.blue.infantry:extend({ name='" + + cp_name_trimmed + + "-garrison-blue' })\n" + ) + lua_string_zones += " }\n" + lua_string_zones += " }),\n" + lua_string_zones += " presets.upgrades.supply.fuelTank:extend({\n" + lua_string_zones += ( + " name = '" + cp_name_trimmed + "-fueltank-blue',\n" + ) + lua_string_zones += " products = {\n" + for ground_group in self.game.pretense_ground_supply[cp_side][cp_name_trimmed]: + lua_string_zones += ( + " presets.missions.supply.convoy:extend({ name='" + + ground_group + + "'}),\n" + ) + for ground_group in self.game.pretense_ground_assault[cp_side][cp_name_trimmed]: + lua_string_zones += ( + " presets.missions.attack.surface:extend({ name='" + + ground_group + + "'}),\n" + ) + for mission_type in self.game.pretense_air[cp_side][cp_name_trimmed]: + if mission_type == FlightType.AIR_ASSAULT.name: + mission_name = "supply.helo" + for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ + mission_type + ]: + lua_string_zones += ( + f" presets.missions.{mission_name}:extend" + + "({name='" + + air_group + + "'}),\n" + ) + lua_string_zones += " }\n" + lua_string_zones += " }),\n" + lua_string_zones += " presets.upgrades.airdef.comCenter:extend({\n" + lua_string_zones += ( + f" name = '{cp_name_trimmed}-mission-command-blue',\n" + ) + lua_string_zones += " products = {\n" + lua_string_zones += ( + " presets.defenses.blue.shorad:extend({ name='" + + cp_name_trimmed + + "-sam-blue' }),\n" + ) + for mission_type in self.game.pretense_air[cp_side][cp_name_trimmed]: + if mission_type == FlightType.SEAD.name: + mission_name = "attack.sead" + for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ + mission_type + ]: + lua_string_zones += ( + f" presets.missions.{mission_name}:extend" + + "({name='" + + air_group + + "', altitude=25000, expend=AI.Task.WeaponExpend.ALL}),\n" + ) + elif mission_type == FlightType.CAS.name: + mission_name = "attack.cas" + for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ + mission_type + ]: + lua_string_zones += ( + f" presets.missions.{mission_name}:extend" + + "({name='" + + air_group + + "', altitude=15000, expend=AI.Task.WeaponExpend.ONE}),\n" + ) + elif mission_type == FlightType.BAI.name: + mission_name = "attack.bai" + for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ + mission_type + ]: + lua_string_zones += ( + f" presets.missions.{mission_name}:extend" + + "({name='" + + air_group + + "', altitude=10000, expend=AI.Task.WeaponExpend.ONE}),\n" + ) + elif mission_type == FlightType.STRIKE.name: + mission_name = "attack.strike" + for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ + mission_type + ]: + lua_string_zones += ( + f" presets.missions.{mission_name}:extend" + + "({name='" + + air_group + + "', altitude=20000, expend=AI.Task.WeaponExpend.ALL}),\n" + ) + elif mission_type == FlightType.BARCAP.name: + mission_name = "patrol.aircraft" + for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ + mission_type + ]: + lua_string_zones += ( + f" presets.missions.{mission_name}:extend" + + "({name='" + + air_group + + "', altitude=25000, range=25}),\n" + ) + elif mission_type == FlightType.REFUELING.name: + mission_name = "support.tanker" + for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ + mission_type + ]: + tanker_freq = 257.0 + tanker_tacan = 37.0 + for tanker in self.mission_data.tankers: + if tanker.group_name == air_group: + tanker_freq = tanker.freq.hertz / 1000000 + tanker_tacan = tanker.tacan.number + if tanker.variant == "KC-135 Stratotanker": + tanker_variant = "Boom" + else: + tanker_variant = "Drogue" + lua_string_zones += ( + f" presets.missions.{mission_name}:extend" + + "({name='" + + air_group + + "', freq='" + + str(tanker_freq) + + "', tacan='" + + str(tanker_tacan) + + "', variant='" + + tanker_variant + + "'}),\n" + ) + elif mission_type == FlightType.AEWC.name: + mission_name = "support.awacs" + for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ + mission_type + ]: + awacs_freq = 257.5 + for awacs in self.mission_data.awacs: + if awacs.group_name == air_group: + awacs_freq = awacs.freq.hertz / 1000000 + lua_string_zones += ( + f" presets.missions.{mission_name}:extend" + + "({name='" + + air_group + + "', freq=" + + str(awacs_freq) + + "}),\n" + ) + lua_string_zones += " }\n" + lua_string_zones += " })\n" + lua_string_zones += " }\n" + lua_string_zones += "})\n" + + return lua_string_zones + def generate_plugin_data(self) -> None: self.mission.triggerrules.triggers.clear() @@ -105,210 +519,10 @@ class PretenseLuaGenerator(LuaGenerator): lua_string_zones += ( f"zones.{cp_name_trimmed}.maxResource = {max_resource}\n" ) - lua_string_zones += f"zones.{cp_name_trimmed}:defineUpgrades(" + "{\n" - lua_string_zones += " [1] = { --red side\n" - lua_string_zones += " presets.upgrades.basic.tent:extend({\n" - lua_string_zones += f" name='{cp_name_trimmed}-tent-red',\n" - lua_string_zones += " products = {\n" - lua_string_zones += ( - " presets.special.red.infantry:extend({ name='" - + cp_name_trimmed - + "-defense-red'})\n" - ) - lua_string_zones += " }\n" - lua_string_zones += " }),\n" - lua_string_zones += " presets.upgrades.basic.comPost:extend({\n" - lua_string_zones += f" name = '{cp_name_trimmed}-com-red',\n" - lua_string_zones += " products = {\n" - lua_string_zones += ( - " presets.special.red.infantry:extend({ name='" - + cp_name_trimmed - + "-defense-red'}),\n" - ) - lua_string_zones += ( - " presets.defenses.red.infantry:extend({ name='" - + cp_name_trimmed - + "-garrison-red' })\n" - ) - lua_string_zones += " }\n" - lua_string_zones += " }),\n" - lua_string_zones += " },\n" - lua_string_zones += " [2] = --blue side\n" - lua_string_zones += " {\n" - lua_string_zones += " presets.upgrades.basic.tent:extend({\n" - lua_string_zones += f" name='{cp_name_trimmed}-tent-blue',\n" - lua_string_zones += " products = {\n" - lua_string_zones += ( - " presets.special.blue.infantry:extend({ name='" - + cp_name_trimmed - + "-defense-blue'})\n" - ) - lua_string_zones += " }\n" - lua_string_zones += " }),\n" - lua_string_zones += " presets.upgrades.basic.comPost:extend({\n" - lua_string_zones += f" name = '{cp_name_trimmed}-com-blue',\n" - lua_string_zones += " products = {\n" - lua_string_zones += ( - " presets.special.blue.infantry:extend({ name='" - + cp_name_trimmed - + "-defense-blue'}),\n" - ) - lua_string_zones += ( - " presets.defenses.blue.infantry:extend({ name='" - + cp_name_trimmed - + "-garrison-blue' })\n" - ) - lua_string_zones += " }\n" - lua_string_zones += " }),\n" - lua_string_zones += " presets.upgrades.supply.fuelTank:extend({\n" - lua_string_zones += ( - " name = '" + cp_name_trimmed + "-fueltank-blue',\n" - ) - lua_string_zones += " products = {\n" - for ground_group in self.game.pretense_ground_supply[cp_side][ - cp_name_trimmed - ]: - lua_string_zones += ( - " presets.missions.supply.convoy:extend({ name='" - + ground_group - + "'}),\n" - ) - for ground_group in self.game.pretense_ground_assault[cp_side][ - cp_name_trimmed - ]: - lua_string_zones += ( - " presets.missions.attack.surface:extend({ name='" - + ground_group - + "'}),\n" - ) - for mission_type in self.game.pretense_air[cp_side][cp_name_trimmed]: - if mission_type == FlightType.AIR_ASSAULT.name: - mission_name = "supply.helo" - for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ - mission_type - ]: - lua_string_zones += ( - f" presets.missions.{mission_name}:extend" - + "({name='" - + air_group - + "'}),\n" - ) - lua_string_zones += " }\n" - lua_string_zones += " }),\n" - lua_string_zones += " presets.upgrades.airdef.comCenter:extend({\n" - lua_string_zones += ( - f" name = '{cp_name_trimmed}-mission-command-blue',\n" - ) - lua_string_zones += " products = {\n" - lua_string_zones += ( - " presets.defenses.blue.shorad:extend({ name='" - + cp_name_trimmed - + "-sam-blue' }),\n" - ) - for mission_type in self.game.pretense_air[cp_side][cp_name_trimmed]: - if mission_type == FlightType.SEAD.name: - mission_name = "attack.sead" - for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ - mission_type - ]: - lua_string_zones += ( - f" presets.missions.{mission_name}:extend" - + "({name='" - + air_group - + "', altitude=25000, expend=AI.Task.WeaponExpend.ALL}),\n" - ) - elif mission_type == FlightType.CAS.name: - mission_name = "attack.cas" - for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ - mission_type - ]: - lua_string_zones += ( - f" presets.missions.{mission_name}:extend" - + "({name='" - + air_group - + "', altitude=15000, expend=AI.Task.WeaponExpend.ONE}),\n" - ) - elif mission_type == FlightType.BAI.name: - mission_name = "attack.bai" - for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ - mission_type - ]: - lua_string_zones += ( - f" presets.missions.{mission_name}:extend" - + "({name='" - + air_group - + "', altitude=10000, expend=AI.Task.WeaponExpend.ONE}),\n" - ) - elif mission_type == FlightType.STRIKE.name: - mission_name = "attack.strike" - for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ - mission_type - ]: - lua_string_zones += ( - f" presets.missions.{mission_name}:extend" - + "({name='" - + air_group - + "', altitude=20000, expend=AI.Task.WeaponExpend.ALL}),\n" - ) - elif mission_type == FlightType.BARCAP.name: - mission_name = "patrol.aircraft" - for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ - mission_type - ]: - lua_string_zones += ( - f" presets.missions.{mission_name}:extend" - + "({name='" - + air_group - + "', altitude=25000, range=25}),\n" - ) - elif mission_type == FlightType.REFUELING.name: - mission_name = "support.tanker" - for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ - mission_type - ]: - tanker_freq = 257.0 - tanker_tacan = 37.0 - for tanker in self.mission_data.tankers: - if tanker.group_name == air_group: - tanker_freq = tanker.freq.hertz / 1000000 - tanker_tacan = tanker.tacan.number - if tanker.variant == "KC-135 Stratotanker": - tanker_variant = "Boom" - else: - tanker_variant = "Drogue" - lua_string_zones += ( - f" presets.missions.{mission_name}:extend" - + "({name='" - + air_group - + "', freq='" - + str(tanker_freq) - + "', tacan='" - + str(tanker_tacan) - + "', variant='" - + tanker_variant - + "'}),\n" - ) - elif mission_type == FlightType.AEWC.name: - mission_name = "support.awacs" - for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ - mission_type - ]: - awacs_freq = 257.5 - for awacs in self.mission_data.awacs: - if awacs.group_name == air_group: - awacs_freq = awacs.freq.hertz / 1000000 - lua_string_zones += ( - f" presets.missions.{mission_name}:extend" - + "({name='" - + air_group - + "', freq=" - + str(awacs_freq) - + "}),\n" - ) - lua_string_zones += " }\n" - lua_string_zones += " })\n" - lua_string_zones += " }\n" - lua_string_zones += "})\n" + if cp.is_fleet: + lua_string_zones += self.generate_pretense_zone_sea(cp.name, cp_side) + else: + lua_string_zones += self.generate_pretense_zone_land(cp.name, cp_side) lua_string_connman = " cm = ConnectionManager:new()\n" From 51a4b6603da499ced736df88700f40f9e93e7b0a Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Sun, 17 Sep 2023 15:58:06 +0300 Subject: [PATCH 147/243] Now generates air units for both sides at airfields. --- game/pretense/pretenseaircraftgenerator.py | 228 ++++++++++++++++---- game/pretense/pretenseflightgroupspawner.py | 16 +- game/pretense/pretenseluagenerator.py | 152 +++++++------ game/pretense/pretensemissiongenerator.py | 25 +-- game/pretense/pretensetgogenerator.py | 74 ++++++- 5 files changed, 373 insertions(+), 122 deletions(-) diff --git a/game/pretense/pretenseaircraftgenerator.py b/game/pretense/pretenseaircraftgenerator.py index aebf990b..3a956879 100644 --- a/game/pretense/pretenseaircraftgenerator.py +++ b/game/pretense/pretenseaircraftgenerator.py @@ -4,7 +4,7 @@ import logging import random from datetime import datetime from functools import cached_property -from typing import Any, Dict, List, TYPE_CHECKING, Tuple +from typing import Any, Dict, List, TYPE_CHECKING, Tuple, Optional from uuid import UUID from dcs import Point @@ -18,6 +18,7 @@ from game.ato.flightstate import Completed, WaitingForStart, Navigating from game.ato.flighttype import FlightType from game.ato.package import Package from game.ato.starttype import StartType +from game.coalition import Coalition from game.missiongenerator.aircraft.flightgroupconfigurator import ( FlightGroupConfigurator, ) @@ -32,11 +33,13 @@ from game.theater.controlpoint import ( ControlPoint, OffMapSpawn, ParkingType, + Airfield, ) from game.unitmap import UnitMap from game.missiongenerator.aircraft.aircraftpainter import AircraftPainter from game.missiongenerator.aircraft.flightdata import FlightData from game.data.weapons import WeaponType +from game.squadrons import Squadron if TYPE_CHECKING: from game import Game @@ -163,27 +166,26 @@ class PretenseAircraftGenerator: autogenerate_cargo_plane_squadron, ) - def generate_pretense_transport_squadron( + def generate_pretense_squadron( self, cp: ControlPoint, + coalition: Coalition, flight_type: FlightType, fixed_wing: bool, num_retries: int, - ) -> None: - from game.squadrons import Squadron - + ) -> Optional[Squadron]: """ - Generates a Pretense transport squadron from the faction squadron definitions. Use FlightType AIR_ASSAULT + Generates a Pretense squadron from the faction squadron definitions. Use FlightType AIR_ASSAULT for Pretense supply helicopters and TRANSPORT for off-map cargo plane squadrons. - + Retribution does not differentiate between fixed wing and rotary wing transport squadron definitions, which is why there is a retry mechanism in case the wrong type is returned. Use fixed_wing False for Pretense supply helicopters and fixed_wing True for off-map cargo plane squadrons. - - TODO: Find out if Pretense can handle rotary wing "cargo planes". + + TODO: Find out if Pretense can handle rotary wing "cargo planes". """ - squadron_def = cp.coalition.air_wing.squadron_def_generator.generate_for_task( + squadron_def = coalition.air_wing.squadron_def_generator.generate_for_task( flight_type, cp ) print( @@ -192,27 +194,27 @@ class PretenseAircraftGenerator: for retries in range(num_retries): if squadron_def is None or fixed_wing == squadron_def.aircraft.helicopter: squadron_def = ( - cp.coalition.air_wing.squadron_def_generator.generate_for_task( + coalition.air_wing.squadron_def_generator.generate_for_task( flight_type, cp ) ) # Failed, stop here if squadron_def is None: - return + return None squadron = Squadron.create_from( squadron_def, flight_type, 2, cp, - cp.coalition, + coalition, self.game, ) - if squadron.aircraft not in cp.coalition.air_wing.squadrons: - cp.coalition.air_wing.squadrons[squadron.aircraft] = list() - cp.coalition.air_wing.add_squadron(squadron) - return + if squadron.aircraft not in coalition.air_wing.squadrons: + coalition.air_wing.squadrons[squadron.aircraft] = list() + coalition.air_wing.add_squadron(squadron) + return squadron def generate_pretense_aircraft( self, cp: ControlPoint, ato: AirTaskingOrder @@ -287,6 +289,7 @@ class PretenseAircraftGenerator: FlightType.BARCAP in mission_types or FlightType.TARCAP in mission_types or FlightType.ESCORT in mission_types + or FlightType.INTERCEPTION in mission_types ) and num_of_cap < PRETENSE_BARCAP_FLIGHTS_PER_CP: flight_type = FlightType.BARCAP num_of_cap += 1 @@ -330,6 +333,137 @@ class PretenseAircraftGenerator: ato.add_package(package) return + def generate_pretense_aircraft_for_other_side( + self, cp: ControlPoint, coalition: Coalition, ato: AirTaskingOrder + ) -> None: + """ + Plans and generates AI aircraft groups/packages for Pretense + for the other side, which doesn't initially hold this control point. + + Aircraft generation is done by walking the control points which will be made into + Pretense "zones" and spawning flights for different missions. + After the flight is generated the package is added to the ATO so the flights + can be configured. + + Args: + cp: Control point to generate aircraft for. + coalition: Coalition to generate aircraft for. + ato: The ATO to generate aircraft for. + """ + + aircraft_per_flight = 1 + if cp.has_helipads and not cp.is_fleet: + flight_type = FlightType.AIR_ASSAULT + squadron = self.generate_pretense_squadron( + cp, + coalition, + flight_type, + False, + PRETENSE_SQUADRON_DEF_RETRIES, + ) + if squadron is not None: + squadron.owned_aircraft += PRETENSE_AI_AIRCRAFT_PER_FLIGHT + squadron.untasked_aircraft += PRETENSE_AI_AIRCRAFT_PER_FLIGHT + package = Package(cp, squadron.flight_db, auto_asap=False) + flight = Flight( + package, + squadron, + aircraft_per_flight, + flight_type, + StartType.COLD, + divert=cp, + ) + print( + f"Generated flight for {flight_type} flying {squadron.aircraft.name} at {squadron.location.name}" + ) + + package.add_flight(flight) + flight.state = WaitingForStart( + flight, self.game.settings, self.game.conditions.start_time + ) + ato.add_package(package) + if isinstance(cp, Airfield): + # Generate SEAD flight + flight_type = FlightType.SEAD + aircraft_per_flight = PRETENSE_AI_AIRCRAFT_PER_FLIGHT + squadron = self.generate_pretense_squadron( + cp, + coalition, + flight_type, + True, + PRETENSE_SQUADRON_DEF_RETRIES, + ) + if squadron is None: + squadron = self.generate_pretense_squadron( + cp, + coalition, + FlightType.DEAD, + True, + PRETENSE_SQUADRON_DEF_RETRIES, + ) + if squadron is not None: + squadron.owned_aircraft += PRETENSE_AI_AIRCRAFT_PER_FLIGHT + squadron.untasked_aircraft += PRETENSE_AI_AIRCRAFT_PER_FLIGHT + package = Package(cp, squadron.flight_db, auto_asap=False) + flight = Flight( + package, + squadron, + aircraft_per_flight, + flight_type, + StartType.COLD, + divert=cp, + ) + print( + f"Generated flight for {flight_type} flying {squadron.aircraft.name} at {squadron.location.name}" + ) + + package.add_flight(flight) + flight.state = WaitingForStart( + flight, self.game.settings, self.game.conditions.start_time + ) + ato.add_package(package) + + # Generate CAS flight + flight_type = FlightType.CAS + aircraft_per_flight = PRETENSE_AI_AIRCRAFT_PER_FLIGHT + squadron = self.generate_pretense_squadron( + cp, + coalition, + flight_type, + True, + PRETENSE_SQUADRON_DEF_RETRIES, + ) + if squadron is None: + squadron = self.generate_pretense_squadron( + cp, + coalition, + FlightType.BAI, + True, + PRETENSE_SQUADRON_DEF_RETRIES, + ) + if squadron is not None: + squadron.owned_aircraft += PRETENSE_AI_AIRCRAFT_PER_FLIGHT + squadron.untasked_aircraft += PRETENSE_AI_AIRCRAFT_PER_FLIGHT + package = Package(cp, squadron.flight_db, auto_asap=False) + flight = Flight( + package, + squadron, + aircraft_per_flight, + flight_type, + StartType.COLD, + divert=cp, + ) + print( + f"Generated flight for {flight_type} flying {squadron.aircraft.name} at {squadron.location.name}" + ) + + package.add_flight(flight) + flight.state = WaitingForStart( + flight, self.game.settings, self.game.conditions.start_time + ) + ato.add_package(package) + return + def initialize_pretense_data_structures( self, cp: ControlPoint, flight: Flight ) -> None: @@ -343,11 +477,15 @@ class PretenseAircraftGenerator: flight: The current flight being generated. """ flight_type = flight.flight_type.name - cp_side = 2 if cp.captured else 1 + is_player = True + cp_side = 2 if flight.coalition == self.game.coalition_for(is_player) else 1 cp_name_trimmed = "".join([i for i in cp.name.lower() if i.isalnum()]) if cp_name_trimmed not in flight.coalition.game.pretense_air[cp_side]: flight.coalition.game.pretense_air[cp_side][cp_name_trimmed] = {} + print( + f"Populated flight.coalition.game.pretense_air[{cp_side}][{cp_name_trimmed}]" + ) if ( flight_type not in flight.coalition.game.pretense_air[cp_side][cp_name_trimmed] @@ -355,6 +493,9 @@ class PretenseAircraftGenerator: flight.coalition.game.pretense_air[cp_side][cp_name_trimmed][ flight_type ] = list() + print( + f"Populated flight.coalition.game.pretense_air[{cp_side}][{cp_name_trimmed}][{flight_type}]" + ) return def generate_flights( @@ -373,29 +514,40 @@ class PretenseAircraftGenerator: dynamic_runways: Runway data for carriers and FARPs. """ - offmap_transport_cp = self.find_pretense_cargo_plane_cp(cp) + if country == cp.coalition.faction.country: + offmap_transport_cp = self.find_pretense_cargo_plane_cp(cp) - ( - autogenerate_transport_helicopter_squadron, - autogenerate_cargo_plane_squadron, - ) = self.should_generate_pretense_transports(cp.coalition.air_wing) + ( + autogenerate_transport_helicopter_squadron, + autogenerate_cargo_plane_squadron, + ) = self.should_generate_pretense_transports(cp.coalition.air_wing) - if autogenerate_transport_helicopter_squadron: - self.generate_pretense_transport_squadron( - offmap_transport_cp, - FlightType.AIR_ASSAULT, - False, - PRETENSE_SQUADRON_DEF_RETRIES, + if autogenerate_transport_helicopter_squadron: + self.generate_pretense_squadron( + offmap_transport_cp, + offmap_transport_cp.coalition, + FlightType.AIR_ASSAULT, + False, + PRETENSE_SQUADRON_DEF_RETRIES, + ) + if autogenerate_cargo_plane_squadron: + self.generate_pretense_squadron( + offmap_transport_cp, + offmap_transport_cp.coalition, + FlightType.TRANSPORT, + True, + PRETENSE_SQUADRON_DEF_RETRIES, + ) + + self.generate_pretense_aircraft(cp, ato) + else: + is_player = True + coalition = ( + self.game.coalition_for(is_player) + if country == self.game.coalition_for(is_player).faction.country + else self.game.coalition_for(False) ) - if autogenerate_cargo_plane_squadron: - self.generate_pretense_transport_squadron( - offmap_transport_cp, - FlightType.TRANSPORT, - True, - PRETENSE_SQUADRON_DEF_RETRIES, - ) - - self.generate_pretense_aircraft(cp, ato) + self.generate_pretense_aircraft_for_other_side(cp, coalition, ato) self._reserve_frequencies_and_tacan(ato) diff --git a/game/pretense/pretenseflightgroupspawner.py b/game/pretense/pretenseflightgroupspawner.py index 6f785ceb..51732d57 100644 --- a/game/pretense/pretenseflightgroupspawner.py +++ b/game/pretense/pretenseflightgroupspawner.py @@ -73,7 +73,13 @@ class PretenseFlightGroupSpawner(FlightGroupSpawner): def generate_flight_at_departure(self) -> FlyingGroup[Any]: cp = self.flight.departure name = namegen.next_pretense_aircraft_name(cp, self.flight) - cp_side = 2 if cp.captured else 1 + is_player = True + cp_side = ( + 2 + if self.flight.coalition + == self.flight.coalition.game.coalition_for(is_player) + else 1 + ) cp_name_trimmed = "".join([i for i in cp.name.lower() if i.isalnum()]) try: @@ -161,7 +167,13 @@ class PretenseFlightGroupSpawner(FlightGroupSpawner): def generate_mid_mission(self) -> FlyingGroup[Any]: assert isinstance(self.flight.state, InFlight) name = namegen.next_pretense_aircraft_name(self.flight.departure, self.flight) - cp_side = 2 if self.flight.departure.captured else 1 + is_player = True + cp_side = ( + 2 + if self.flight.coalition + == self.flight.coalition.game.coalition_for(is_player) + else 1 + ) cp_name_trimmed = "".join( [i for i in self.flight.departure.name.lower() if i.isalnum()] ) diff --git a/game/pretense/pretenseluagenerator.py b/game/pretense/pretenseluagenerator.py index 966724ef..7c5dc964 100644 --- a/game/pretense/pretenseluagenerator.py +++ b/game/pretense/pretenseluagenerator.py @@ -23,6 +23,8 @@ from game.missiongenerator.missiondata import MissionData if TYPE_CHECKING: from game import Game +PRETENSE_RED_SIDE = 1 +PRETENSE_BLUE_SIDE = 2 PRETENSE_NUMBER_OF_ZONES_TO_CONNECT_CARRIERS_TO = 2 @@ -54,68 +56,18 @@ class PretenseLuaGenerator(LuaGenerator): self.mission.triggerrules.triggers.remove(t) self.mission.triggerrules.triggers.append(t) - def generate_pretense_zone_land(self, cp_name: str, cp_side: int) -> str: + def generate_pretense_land_upgrade_supply(self, cp_name: str, cp_side: int): lua_string_zones = "" cp_name_trimmed = "".join([i for i in cp_name.lower() if i.isalnum()]) + cp_side_str = "blue" if cp_side == PRETENSE_BLUE_SIDE else "red" - lua_string_zones += f"zones.{cp_name_trimmed}:defineUpgrades(" + "{\n" - lua_string_zones += " [1] = { --red side\n" - lua_string_zones += " presets.upgrades.basic.tent:extend({\n" - lua_string_zones += f" name='{cp_name_trimmed}-tent-red',\n" - lua_string_zones += " products = {\n" - lua_string_zones += ( - " presets.special.red.infantry:extend({ name='" - + cp_name_trimmed - + "-defense-red'})\n" - ) - lua_string_zones += " }\n" - lua_string_zones += " }),\n" - lua_string_zones += " presets.upgrades.basic.comPost:extend({\n" - lua_string_zones += f" name = '{cp_name_trimmed}-com-red',\n" - lua_string_zones += " products = {\n" - lua_string_zones += ( - " presets.special.red.infantry:extend({ name='" - + cp_name_trimmed - + "-defense-red'}),\n" - ) - lua_string_zones += ( - " presets.defenses.red.infantry:extend({ name='" - + cp_name_trimmed - + "-garrison-red' })\n" - ) - lua_string_zones += " }\n" - lua_string_zones += " }),\n" - lua_string_zones += " },\n" - lua_string_zones += " [2] = --blue side\n" - lua_string_zones += " {\n" - lua_string_zones += " presets.upgrades.basic.tent:extend({\n" - lua_string_zones += f" name='{cp_name_trimmed}-tent-blue',\n" - lua_string_zones += " products = {\n" - lua_string_zones += ( - " presets.special.blue.infantry:extend({ name='" - + cp_name_trimmed - + "-defense-blue'})\n" - ) - lua_string_zones += " }\n" - lua_string_zones += " }),\n" - lua_string_zones += " presets.upgrades.basic.comPost:extend({\n" - lua_string_zones += f" name = '{cp_name_trimmed}-com-blue',\n" - lua_string_zones += " products = {\n" - lua_string_zones += ( - " presets.special.blue.infantry:extend({ name='" - + cp_name_trimmed - + "-defense-blue'}),\n" - ) - lua_string_zones += ( - " presets.defenses.blue.infantry:extend({ name='" - + cp_name_trimmed - + "-garrison-blue' })\n" - ) - lua_string_zones += " }\n" - lua_string_zones += " }),\n" lua_string_zones += " presets.upgrades.supply.fuelTank:extend({\n" lua_string_zones += ( - " name = '" + cp_name_trimmed + "-fueltank-blue',\n" + " name = '" + + cp_name_trimmed + + "-fueltank-" + + cp_side_str + + "',\n" ) lua_string_zones += " products = {\n" for ground_group in self.game.pretense_ground_supply[cp_side][cp_name_trimmed]: @@ -146,13 +98,19 @@ class PretenseLuaGenerator(LuaGenerator): lua_string_zones += " }),\n" lua_string_zones += " presets.upgrades.airdef.comCenter:extend({\n" lua_string_zones += ( - f" name = '{cp_name_trimmed}-mission-command-blue',\n" + f" name = '{cp_name_trimmed}-mission-command-" + + cp_side_str + + "',\n" ) lua_string_zones += " products = {\n" lua_string_zones += ( - " presets.defenses.blue.shorad:extend({ name='" + " presets.defenses." + + cp_side_str + + ".shorad:extend({ name='" + cp_name_trimmed - + "-sam-blue' }),\n" + + "-sam-" + + cp_side_str + + "' }),\n" ) for mission_type in self.game.pretense_air[cp_side][cp_name_trimmed]: if mission_type == FlightType.SEAD.name: @@ -256,6 +214,78 @@ class PretenseLuaGenerator(LuaGenerator): ) lua_string_zones += " }\n" lua_string_zones += " })\n" + + return lua_string_zones + + def generate_pretense_zone_land(self, cp_name: str) -> str: + lua_string_zones = "" + cp_name_trimmed = "".join([i for i in cp_name.lower() if i.isalnum()]) + + lua_string_zones += f"zones.{cp_name_trimmed}:defineUpgrades(" + "{\n" + lua_string_zones += " [1] = { --red side\n" + lua_string_zones += " presets.upgrades.basic.tent:extend({\n" + lua_string_zones += f" name='{cp_name_trimmed}-tent-red',\n" + lua_string_zones += " products = {\n" + lua_string_zones += ( + " presets.special.red.infantry:extend({ name='" + + cp_name_trimmed + + "-defense-red'})\n" + ) + lua_string_zones += " }\n" + lua_string_zones += " }),\n" + lua_string_zones += " presets.upgrades.basic.comPost:extend({\n" + lua_string_zones += f" name = '{cp_name_trimmed}-com-red',\n" + lua_string_zones += " products = {\n" + lua_string_zones += ( + " presets.special.red.infantry:extend({ name='" + + cp_name_trimmed + + "-defense-red'}),\n" + ) + lua_string_zones += ( + " presets.defenses.red.infantry:extend({ name='" + + cp_name_trimmed + + "-garrison-red' })\n" + ) + lua_string_zones += " }\n" + lua_string_zones += " }),\n" + + lua_string_zones += self.generate_pretense_land_upgrade_supply( + cp_name, PRETENSE_RED_SIDE + ) + + lua_string_zones += " },\n" + lua_string_zones += " [2] = --blue side\n" + lua_string_zones += " {\n" + lua_string_zones += " presets.upgrades.basic.tent:extend({\n" + lua_string_zones += f" name='{cp_name_trimmed}-tent-blue',\n" + lua_string_zones += " products = {\n" + lua_string_zones += ( + " presets.special.blue.infantry:extend({ name='" + + cp_name_trimmed + + "-defense-blue'})\n" + ) + lua_string_zones += " }\n" + lua_string_zones += " }),\n" + lua_string_zones += " presets.upgrades.basic.comPost:extend({\n" + lua_string_zones += f" name = '{cp_name_trimmed}-com-blue',\n" + lua_string_zones += " products = {\n" + lua_string_zones += ( + " presets.special.blue.infantry:extend({ name='" + + cp_name_trimmed + + "-defense-blue'}),\n" + ) + lua_string_zones += ( + " presets.defenses.blue.infantry:extend({ name='" + + cp_name_trimmed + + "-garrison-blue' })\n" + ) + lua_string_zones += " }\n" + lua_string_zones += " }),\n" + + lua_string_zones += self.generate_pretense_land_upgrade_supply( + cp_name, PRETENSE_BLUE_SIDE + ) + lua_string_zones += " }\n" lua_string_zones += "})\n" @@ -522,7 +552,7 @@ class PretenseLuaGenerator(LuaGenerator): if cp.is_fleet: lua_string_zones += self.generate_pretense_zone_sea(cp.name, cp_side) else: - lua_string_zones += self.generate_pretense_zone_land(cp.name, cp_side) + lua_string_zones += self.generate_pretense_zone_land(cp.name) lua_string_connman = " cm = ConnectionManager:new()\n" diff --git a/game/pretense/pretensemissiongenerator.py b/game/pretense/pretensemissiongenerator.py index 52e7349c..5d64622b 100644 --- a/game/pretense/pretensemissiongenerator.py +++ b/game/pretense/pretensemissiongenerator.py @@ -207,18 +207,19 @@ class PretenseMissionGenerator(MissionGenerator): self.game.red.ato.clear() for cp in self.game.theater.controlpoints: - if cp.captured: - ato = self.game.blue.ato - cp_country = self.p_country - else: - ato = self.game.red.ato - cp_country = self.e_country - aircraft_generator.generate_flights( - cp_country, - cp, - ato, - tgo_generator.runways, - ) + for country in (self.p_country, self.e_country): + + if country == self.p_country: + ato = self.game.blue.ato + else: + ato = self.game.red.ato + print(f"Running generate_flights for {country.name} at {cp.name}") + aircraft_generator.generate_flights( + country, + cp, + ato, + tgo_generator.runways, + ) self.mission_data.flights = aircraft_generator.flights diff --git a/game/pretense/pretensetgogenerator.py b/game/pretense/pretensetgogenerator.py index ed86f157..bcb9a0c2 100644 --- a/game/pretense/pretensetgogenerator.py +++ b/game/pretense/pretensetgogenerator.py @@ -72,12 +72,15 @@ from game.theater import ( TheaterGroundObject, TheaterUnit, NavalControlPoint, + PresetLocation, ) from game.theater.theatergroundobject import ( CarrierGroundObject, GenericCarrierGroundObject, LhaGroundObject, MissileSiteGroundObject, + BuildingGroundObject, + VehicleGroupGroundObject, ) from game.theater.theatergroup import SceneryUnit, IadsGroundGroup, TheaterGroup from game.unitmap import UnitMap @@ -171,12 +174,7 @@ class PretenseGroundObjectGenerator(GroundObjectGenerator): cp_name_trimmed = "".join( [i for i in self.ground_object.control_point.name.lower() if i.isalnum()] ) - cp_side = 2 if self.ground_object.control_point.captured else 1 - for side in range(1, 3): - if cp_name_trimmed not in self.game.pretense_ground_supply[cp_side]: - self.game.pretense_ground_supply[side][cp_name_trimmed] = list() - if cp_name_trimmed not in self.game.pretense_ground_assault[cp_side]: - self.game.pretense_ground_assault[side][cp_name_trimmed] = list() + for group in self.ground_object.groups: vehicle_units: list[TheaterUnit] = [] ship_units: list[TheaterUnit] = [] @@ -275,7 +273,12 @@ class PretenseGroundObjectGenerator(GroundObjectGenerator): cp_name_trimmed = "".join( [i for i in self.ground_object.control_point.name.lower() if i.isalnum()] ) - cp_side = 2 if self.ground_object.control_point.captured else 1 + is_player = True + side = ( + 2 + if self.country == self.game.coalition_for(is_player).faction.country + else 1 + ) for unit in units: assert issubclass(unit.type, VehicleType) @@ -296,11 +299,11 @@ class PretenseGroundObjectGenerator(GroundObjectGenerator): group_role = group_name.split("-")[1] if group_role == "supply": - self.game.pretense_ground_supply[cp_side][cp_name_trimmed].append( + self.game.pretense_ground_supply[side][cp_name_trimmed].append( f"{vehicle_group.name}" ) elif group_role == "assault": - self.game.pretense_ground_assault[cp_side][cp_name_trimmed].append( + self.game.pretense_ground_assault[side][cp_name_trimmed].append( f"{vehicle_group.name}" ) else: @@ -361,6 +364,14 @@ class PretenseTgoGenerator(TgoGenerator): def generate(self) -> None: for cp in self.game.theater.controlpoints: + cp_name_trimmed = "".join([i for i in cp.name.lower() if i.isalnum()]) + for side in range(1, 3): + if cp_name_trimmed not in self.game.pretense_ground_supply[side]: + self.game.pretense_ground_supply[side][cp_name_trimmed] = list() + if cp_name_trimmed not in self.game.pretense_ground_assault[side]: + self.game.pretense_ground_assault[side][cp_name_trimmed] = list() + + # First generate units for the coalition, which initially holds this CP country = self.m.country(cp.coalition.faction.country.name) # Generate helipads @@ -431,4 +442,49 @@ class PretenseTgoGenerator(TgoGenerator): ground_object, country, self.game, self.m, self.unit_map ) generator.generate() + # Then generate ground supply and assault groups for the other coalition + other_coalition = cp.coalition + for coalition in cp.coalition.game.coalitions: + if coalition == cp.coalition: + continue + else: + other_coalition = coalition + country = self.m.country(other_coalition.faction.country.name) + for ground_object in cp.ground_objects: + generator: GroundObjectGenerator + if isinstance(ground_object, BuildingGroundObject): + new_ground_object = BuildingGroundObject( + name=ground_object.name, + category=ground_object.category, + location=PresetLocation( + f"{ground_object.name} {ground_object.id}", + ground_object.position, + ground_object.heading, + ), + control_point=ground_object.control_point, + is_fob_structure=ground_object.is_fob_structure, + task=ground_object.task, + ) + generator = PretenseGroundObjectGenerator( + new_ground_object, country, self.game, self.m, self.unit_map + ) + elif isinstance(ground_object, VehicleGroupGroundObject): + new_ground_object = VehicleGroupGroundObject( + name=ground_object.name, + location=PresetLocation( + f"{ground_object.name} {ground_object.id}", + ground_object.position, + ground_object.heading, + ), + control_point=ground_object.control_point, + task=ground_object.task, + ) + generator = PretenseGroundObjectGenerator( + new_ground_object, country, self.game, self.m, self.unit_map + ) + else: + continue + + generator.generate() + self.mission_data.runways = list(self.runways.values()) From 77060031da7f9dc2093790a1e5292587f1aaf698 Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Sun, 17 Sep 2023 16:06:17 +0300 Subject: [PATCH 148/243] Cleaned up some code. --- game/pretense/pretenseluagenerator.py | 2 +- game/pretense/pretensetgogenerator.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/game/pretense/pretenseluagenerator.py b/game/pretense/pretenseluagenerator.py index 7c5dc964..b10f6ab6 100644 --- a/game/pretense/pretenseluagenerator.py +++ b/game/pretense/pretenseluagenerator.py @@ -56,7 +56,7 @@ class PretenseLuaGenerator(LuaGenerator): self.mission.triggerrules.triggers.remove(t) self.mission.triggerrules.triggers.append(t) - def generate_pretense_land_upgrade_supply(self, cp_name: str, cp_side: int): + def generate_pretense_land_upgrade_supply(self, cp_name: str, cp_side: int) -> str: lua_string_zones = "" cp_name_trimmed = "".join([i for i in cp_name.lower() if i.isalnum()]) cp_side_str = "blue" if cp_side == PRETENSE_BLUE_SIDE else "red" diff --git a/game/pretense/pretensetgogenerator.py b/game/pretense/pretensetgogenerator.py index bcb9a0c2..74f99a6e 100644 --- a/game/pretense/pretensetgogenerator.py +++ b/game/pretense/pretensetgogenerator.py @@ -450,8 +450,8 @@ class PretenseTgoGenerator(TgoGenerator): else: other_coalition = coalition country = self.m.country(other_coalition.faction.country.name) + new_ground_object: TheaterGroundObject for ground_object in cp.ground_objects: - generator: GroundObjectGenerator if isinstance(ground_object, BuildingGroundObject): new_ground_object = BuildingGroundObject( name=ground_object.name, From a6104968e898a08f86ce0db6b92ebda4d46279ee Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Sun, 17 Sep 2023 18:55:52 +0300 Subject: [PATCH 149/243] Don't spawn flights for the other squadron in generate_pretense_aircraft(). --- game/pretense/pretenseaircraftgenerator.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/game/pretense/pretenseaircraftgenerator.py b/game/pretense/pretenseaircraftgenerator.py index 3a956879..25408106 100644 --- a/game/pretense/pretenseaircraftgenerator.py +++ b/game/pretense/pretenseaircraftgenerator.py @@ -240,6 +240,8 @@ class PretenseAircraftGenerator: # Intentionally don't spawn anything at OffMapSpawns in Pretense if isinstance(squadron.location, OffMapSpawn): continue + if cp.coalition != squadron.coalition: + continue squadron.owned_aircraft += PRETENSE_AI_AIRCRAFT_PER_FLIGHT squadron.untasked_aircraft += PRETENSE_AI_AIRCRAFT_PER_FLIGHT From 231a8609a17f47459d5118ed8aca144d5d702204 Mon Sep 17 00:00:00 2001 From: Raffson Date: Sun, 17 Sep 2023 15:24:13 +0200 Subject: [PATCH 150/243] imports --- game/pretense/pretenseaircraftgenerator.py | 8 +++----- game/pretense/pretenseflightgroupspawner.py | 4 +--- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/game/pretense/pretenseaircraftgenerator.py b/game/pretense/pretenseaircraftgenerator.py index 25408106..9a7c36f4 100644 --- a/game/pretense/pretenseaircraftgenerator.py +++ b/game/pretense/pretenseaircraftgenerator.py @@ -5,7 +5,6 @@ import random from datetime import datetime from functools import cached_property from typing import Any, Dict, List, TYPE_CHECKING, Tuple, Optional -from uuid import UUID from dcs import Point from dcs.country import Country @@ -14,11 +13,13 @@ from dcs.unitgroup import FlyingGroup, StaticGroup from game.ato.airtaaskingorder import AirTaskingOrder from game.ato.flight import Flight -from game.ato.flightstate import Completed, WaitingForStart, Navigating +from game.ato.flightstate import WaitingForStart, Navigating from game.ato.flighttype import FlightType from game.ato.package import Package from game.ato.starttype import StartType from game.coalition import Coalition +from game.data.weapons import WeaponType +from game.missiongenerator.aircraft.flightdata import FlightData from game.missiongenerator.aircraft.flightgroupconfigurator import ( FlightGroupConfigurator, ) @@ -36,9 +37,6 @@ from game.theater.controlpoint import ( Airfield, ) from game.unitmap import UnitMap -from game.missiongenerator.aircraft.aircraftpainter import AircraftPainter -from game.missiongenerator.aircraft.flightdata import FlightData -from game.data.weapons import WeaponType from game.squadrons import Squadron if TYPE_CHECKING: diff --git a/game/pretense/pretenseflightgroupspawner.py b/game/pretense/pretenseflightgroupspawner.py index 51732d57..18deecbe 100644 --- a/game/pretense/pretenseflightgroupspawner.py +++ b/game/pretense/pretenseflightgroupspawner.py @@ -1,6 +1,5 @@ import logging import random -import re from typing import Any, Tuple from dcs import Mission @@ -24,8 +23,7 @@ from game.missiongenerator.aircraft.flightgroupspawner import ( ) from game.missiongenerator.missiondata import MissionData from game.naming import NameGenerator -from game.theater import Airfield, ControlPoint, Fob, NavalControlPoint, OffMapSpawn -from game.utils import feet, meters +from game.theater import Airfield, ControlPoint, Fob, NavalControlPoint class PretenseNameGenerator(NameGenerator): From 4ff31ea86d4e621b156f687a8b8c1c87d02f1492 Mon Sep 17 00:00:00 2001 From: Raffson Date: Sun, 17 Sep 2023 16:34:08 +0200 Subject: [PATCH 151/243] Blast through errors --- .../aircraft/flightgroupconfigurator.py | 17 +++++++++++++---- game/pretense/pretenseaircraftgenerator.py | 2 ++ game/pretense/pretenseluagenerator.py | 14 +++++--------- 3 files changed, 20 insertions(+), 13 deletions(-) diff --git a/game/missiongenerator/aircraft/flightgroupconfigurator.py b/game/missiongenerator/aircraft/flightgroupconfigurator.py index ec42092c..4e815d28 100644 --- a/game/missiongenerator/aircraft/flightgroupconfigurator.py +++ b/game/missiongenerator/aircraft/flightgroupconfigurator.py @@ -1,6 +1,7 @@ from __future__ import annotations import logging +import random from datetime import datetime from typing import Any, Optional, TYPE_CHECKING @@ -19,7 +20,12 @@ from game.data.weapons import Pylon, WeaponType from game.missiongenerator.logisticsgenerator import LogisticsGenerator from game.missiongenerator.missiondata import MissionData, AwacsInfo, TankerInfo from game.radio.radios import RadioFrequency, RadioRegistry -from game.radio.tacan import TacanBand, TacanRegistry, TacanUsage +from game.radio.tacan import ( + TacanBand, + TacanRegistry, + TacanUsage, + OutOfTacanChannelsError, +) from game.runways import RunwayData from game.squadrons import Pilot from .aircraftbehavior import AircraftBehavior @@ -210,9 +216,12 @@ class FlightGroupConfigurator: ) or isinstance(self.flight.flight_plan, PackageRefuelingFlightPlan): tacan = self.flight.tacan if tacan is None and self.flight.squadron.aircraft.dcs_unit_type.tacan: - tacan = self.tacan_registry.alloc_for_band( - TacanBand.Y, TacanUsage.AirToAir - ) + try: + tacan = self.tacan_registry.alloc_for_band( + TacanBand.Y, TacanUsage.AirToAir + ) + except OutOfTacanChannelsError: + tacan = random.choice(list(self.tacan_registry.allocated_channels)) else: tacan = self.flight.tacan self.mission_data.tankers.append( diff --git a/game/pretense/pretenseaircraftgenerator.py b/game/pretense/pretenseaircraftgenerator.py index 9a7c36f4..59f1340a 100644 --- a/game/pretense/pretenseaircraftgenerator.py +++ b/game/pretense/pretenseaircraftgenerator.py @@ -577,6 +577,8 @@ class PretenseAircraftGenerator: from game.pretense.pretenseflightgroupspawner import PretenseFlightGroupSpawner """Creates and configures the flight group in the mission.""" + if not country.unused_onboard_numbers: + country.reset_onboard_numbers() group = PretenseFlightGroupSpawner( flight, country, diff --git a/game/pretense/pretenseluagenerator.py b/game/pretense/pretenseluagenerator.py index b10f6ab6..acc2798c 100644 --- a/game/pretense/pretenseluagenerator.py +++ b/game/pretense/pretenseluagenerator.py @@ -1,7 +1,6 @@ from __future__ import annotations import logging -import os from abc import ABC, abstractmethod from pathlib import Path from typing import TYPE_CHECKING, Optional @@ -12,13 +11,11 @@ from dcs.translation import String from dcs.triggers import TriggerStart from game.ato import FlightType -from game.dcs.aircrafttype import AircraftType from game.missiongenerator.luagenerator import LuaGenerator -from game.plugins import LuaPluginManager -from game.theater import TheaterGroundObject, Airfield, OffMapSpawn -from game.theater.iadsnetwork.iadsrole import IadsRole -from game.utils import escape_string_for_lua from game.missiongenerator.missiondata import MissionData +from game.plugins import LuaPluginManager +from game.theater import Airfield, OffMapSpawn +from game.utils import escape_string_for_lua if TYPE_CHECKING: from game import Game @@ -175,14 +172,13 @@ class PretenseLuaGenerator(LuaGenerator): ]: tanker_freq = 257.0 tanker_tacan = 37.0 + tanker_variant = "Drogue" for tanker in self.mission_data.tankers: if tanker.group_name == air_group: tanker_freq = tanker.freq.hertz / 1000000 - tanker_tacan = tanker.tacan.number + tanker_tacan = tanker.tacan if tanker.tacan else "N/A" if tanker.variant == "KC-135 Stratotanker": tanker_variant = "Boom" - else: - tanker_variant = "Drogue" lua_string_zones += ( f" presets.missions.{mission_name}:extend" + "({name='" From 585b3e74a09fef2890aed70c3381a7a0f060ca0f Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Sun, 17 Sep 2023 20:31:57 +0300 Subject: [PATCH 152/243] Implemented a separate generate_packages() method in PretenseAircraftGenerator to prevent generating the same ATO multiple times over. --- game/pretense/pretenseaircraftgenerator.py | 69 +++++++++++++++------- game/pretense/pretensemissiongenerator.py | 56 ++++++------------ 2 files changed, 64 insertions(+), 61 deletions(-) diff --git a/game/pretense/pretenseaircraftgenerator.py b/game/pretense/pretenseaircraftgenerator.py index 59f1340a..0a1f704e 100644 --- a/game/pretense/pretenseaircraftgenerator.py +++ b/game/pretense/pretenseaircraftgenerator.py @@ -301,9 +301,7 @@ class PretenseAircraftGenerator: flight_type = FlightType.REFUELING aircraft_per_flight = PRETENSE_AI_TANKERS_PER_FLIGHT else: - if len(list(mission_types)) == 0: - continue - flight_type = random.choice(list(mission_types)) + continue if flight_type == FlightType.TRANSPORT: flight = Flight( @@ -464,7 +462,27 @@ class PretenseAircraftGenerator: ato.add_package(package) return - def initialize_pretense_data_structures( + def initialize_pretense_data_structures(self, cp: ControlPoint) -> None: + """ + Ensures that the data structures used to pass flight group information + to the Pretense init script lua are initialized for use in + PretenseFlightGroupSpawner and PretenseLuaGenerator. + + Args: + cp: Control point to generate aircraft for. + flight: The current flight being generated. + """ + cp_name_trimmed = "".join([i for i in cp.name.lower() if i.isalnum()]) + + for side in range(1, 3): + if cp_name_trimmed not in cp.coalition.game.pretense_air[side]: + cp.coalition.game.pretense_air[side][cp_name_trimmed] = {} + print( + f"Populated flight.coalition.game.pretense_air[{side}][{cp_name_trimmed}]" + ) + return + + def initialize_pretense_data_structures_for_flight( self, cp: ControlPoint, flight: Flight ) -> None: """ @@ -477,25 +495,24 @@ class PretenseAircraftGenerator: flight: The current flight being generated. """ flight_type = flight.flight_type.name - is_player = True - cp_side = 2 if flight.coalition == self.game.coalition_for(is_player) else 1 cp_name_trimmed = "".join([i for i in cp.name.lower() if i.isalnum()]) - if cp_name_trimmed not in flight.coalition.game.pretense_air[cp_side]: - flight.coalition.game.pretense_air[cp_side][cp_name_trimmed] = {} - print( - f"Populated flight.coalition.game.pretense_air[{cp_side}][{cp_name_trimmed}]" - ) - if ( - flight_type - not in flight.coalition.game.pretense_air[cp_side][cp_name_trimmed] - ): - flight.coalition.game.pretense_air[cp_side][cp_name_trimmed][ + for side in range(1, 3): + if cp_name_trimmed not in flight.coalition.game.pretense_air[side]: + flight.coalition.game.pretense_air[side][cp_name_trimmed] = {} + print( + f"Populated flight.coalition.game.pretense_air[{side}][{cp_name_trimmed}]" + ) + if ( flight_type - ] = list() - print( - f"Populated flight.coalition.game.pretense_air[{cp_side}][{cp_name_trimmed}][{flight_type}]" - ) + not in flight.coalition.game.pretense_air[side][cp_name_trimmed] + ): + flight.coalition.game.pretense_air[side][cp_name_trimmed][ + flight_type + ] = list() + print( + f"Populated flight.coalition.game.pretense_air[{side}][{cp_name_trimmed}][{flight_type}]" + ) return def generate_flights( @@ -503,7 +520,6 @@ class PretenseAircraftGenerator: country: Country, cp: ControlPoint, ato: AirTaskingOrder, - dynamic_runways: Dict[str, RunwayData], ) -> None: """Adds aircraft to the mission for every flight in the ATO. @@ -513,6 +529,7 @@ class PretenseAircraftGenerator: ato: The ATO to generate aircraft for. dynamic_runways: Runway data for carriers and FARPs. """ + self.initialize_pretense_data_structures(cp) if country == cp.coalition.faction.country: offmap_transport_cp = self.find_pretense_cargo_plane_cp(cp) @@ -551,12 +568,20 @@ class PretenseAircraftGenerator: self._reserve_frequencies_and_tacan(ato) + def generate_packages( + self, + country: Country, + ato: AirTaskingOrder, + dynamic_runways: Dict[str, RunwayData], + ) -> None: for package in reversed(sorted(ato.packages, key=lambda x: x.time_over_target)): logging.info(f"Generating package for target: {package.target.name}") if not package.flights: continue for flight in package.flights: - self.initialize_pretense_data_structures(cp, flight) + self.initialize_pretense_data_structures_for_flight( + flight.departure, flight + ) if flight.alive: if not flight.squadron.location.runway_is_operational(): diff --git a/game/pretense/pretensemissiongenerator.py b/game/pretense/pretensemissiongenerator.py index 5d64622b..8c8368bd 100644 --- a/game/pretense/pretensemissiongenerator.py +++ b/game/pretense/pretensemissiongenerator.py @@ -55,6 +55,7 @@ if TYPE_CHECKING: class PretenseMissionGenerator(MissionGenerator): def __init__(self, game: Game, time: datetime) -> None: + super().__init__(game, time) self.game = game self.time = time self.mission = Mission(game.theater.terrain) @@ -218,9 +219,22 @@ class PretenseMissionGenerator(MissionGenerator): country, cp, ato, - tgo_generator.runways, ) + for cp in self.game.theater.controlpoints: + if cp.captured: + ato = self.game.blue.ato + country = self.p_country + else: + ato = self.game.red.ato + country = self.e_country + + aircraft_generator.generate_packages( + country, + ato, + tgo_generator.runways, + ) + self.mission_data.flights = aircraft_generator.flights for flight in aircraft_generator.flights: @@ -228,41 +242,5 @@ class PretenseMissionGenerator(MissionGenerator): continue flight.aircraft_type.assign_channels_for_flight(flight, self.mission_data) - def notify_info_generators( - self, - ) -> None: - """Generates subscribed MissionInfoGenerator objects.""" - mission_data = self.mission_data - gens: list[MissionInfoGenerator] = [ - KneeboardGenerator(self.mission, self.game), - BriefingGenerator(self.mission, self.game), - ] - for gen in gens: - for dynamic_runway in mission_data.runways: - gen.add_dynamic_runway(dynamic_runway) - - for tanker in mission_data.tankers: - if tanker.blue: - gen.add_tanker(tanker) - - for aewc in mission_data.awacs: - if aewc.blue: - gen.add_awacs(aewc) - - for jtac in mission_data.jtacs: - if jtac.blue: - gen.add_jtac(jtac) - - for flight in mission_data.flights: - gen.add_flight(flight) - gen.generate() - - def setup_combined_arms(self) -> None: - settings = self.game.settings - commanders = settings.tactical_commander_count - self.mission.groundControl.pilot_can_control_vehicles = commanders > 0 - - self.mission.groundControl.blue_game_masters = settings.game_masters_count - self.mission.groundControl.blue_tactical_commander = commanders - self.mission.groundControl.blue_jtac = settings.jtac_count - self.mission.groundControl.blue_observer = settings.observer_count + if self.game.settings.plugins.get("ewrj"): + self._configure_ewrj(aircraft_generator) From 91cfff58d9ad467a9f5f9995b03d8d4f4f479cff Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Sun, 17 Sep 2023 20:37:29 +0300 Subject: [PATCH 153/243] Changed tanker_tacan from "N/A" to 0.0 when the tanker is not compatible with TACAN, in order to avoid mission script errors. --- game/pretense/pretenseluagenerator.py | 4 ++-- game/pretense/pretensemissiongenerator.py | 3 --- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/game/pretense/pretenseluagenerator.py b/game/pretense/pretenseluagenerator.py index acc2798c..ec4aff92 100644 --- a/game/pretense/pretenseluagenerator.py +++ b/game/pretense/pretenseluagenerator.py @@ -176,7 +176,7 @@ class PretenseLuaGenerator(LuaGenerator): for tanker in self.mission_data.tankers: if tanker.group_name == air_group: tanker_freq = tanker.freq.hertz / 1000000 - tanker_tacan = tanker.tacan if tanker.tacan else "N/A" + tanker_tacan = tanker.tacan.number if tanker.tacan else 0.0 if tanker.variant == "KC-135 Stratotanker": tanker_variant = "Boom" lua_string_zones += ( @@ -453,7 +453,7 @@ class PretenseLuaGenerator(LuaGenerator): for tanker in self.mission_data.tankers: if tanker.group_name == air_group: tanker_freq = tanker.freq.hertz / 1000000 - tanker_tacan = tanker.tacan.number + tanker_tacan = tanker.tacan.number if tanker.tacan else 0.0 if tanker.variant == "KC-135 Stratotanker": tanker_variant = "Boom" else: diff --git a/game/pretense/pretensemissiongenerator.py b/game/pretense/pretensemissiongenerator.py index 8c8368bd..c724c471 100644 --- a/game/pretense/pretensemissiongenerator.py +++ b/game/pretense/pretensemissiongenerator.py @@ -241,6 +241,3 @@ class PretenseMissionGenerator(MissionGenerator): if not flight.client_units: continue flight.aircraft_type.assign_channels_for_flight(flight, self.mission_data) - - if self.game.settings.plugins.get("ewrj"): - self._configure_ewrj(aircraft_generator) From 95bac8ec17bd04e9eb198688e5fc7cdad229cbde Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Mon, 18 Sep 2023 18:23:03 +0300 Subject: [PATCH 154/243] Run aircraft_generator.generate_packages() only once per ATO, instead of multiple times. Fixed the duplicated flights issue. --- game/pretense/pretensemissiongenerator.py | 26 ++++++++++++----------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/game/pretense/pretensemissiongenerator.py b/game/pretense/pretensemissiongenerator.py index c724c471..2f8942fe 100644 --- a/game/pretense/pretensemissiongenerator.py +++ b/game/pretense/pretensemissiongenerator.py @@ -221,19 +221,21 @@ class PretenseMissionGenerator(MissionGenerator): ato, ) - for cp in self.game.theater.controlpoints: - if cp.captured: - ato = self.game.blue.ato - country = self.p_country - else: - ato = self.game.red.ato - country = self.e_country + ato = self.game.blue.ato + country = self.p_country + aircraft_generator.generate_packages( + country, + ato, + tgo_generator.runways, + ) - aircraft_generator.generate_packages( - country, - ato, - tgo_generator.runways, - ) + ato = self.game.red.ato + country = self.e_country + aircraft_generator.generate_packages( + country, + ato, + tgo_generator.runways, + ) self.mission_data.flights = aircraft_generator.flights From 946d578ffb2b55877820f4c8ade0097334fa4856 Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Tue, 19 Sep 2023 01:05:52 +0300 Subject: [PATCH 155/243] Implemented generating player slots in the Pretense campaign. --- game/pretense/pretenseaircraftgenerator.py | 188 +++++++++++++++++++- game/pretense/pretenseflightgroupspawner.py | 47 +++-- 2 files changed, 213 insertions(+), 22 deletions(-) diff --git a/game/pretense/pretenseaircraftgenerator.py b/game/pretense/pretenseaircraftgenerator.py index 0a1f704e..6e78054d 100644 --- a/game/pretense/pretenseaircraftgenerator.py +++ b/game/pretense/pretenseaircraftgenerator.py @@ -9,6 +9,7 @@ from typing import Any, Dict, List, TYPE_CHECKING, Tuple, Optional from dcs import Point from dcs.country import Country from dcs.mission import Mission +from dcs.terrain import NoParkingSlotError from dcs.unitgroup import FlyingGroup, StaticGroup from game.ato.airtaaskingorder import AirTaskingOrder @@ -19,6 +20,7 @@ from game.ato.package import Package from game.ato.starttype import StartType from game.coalition import Coalition from game.data.weapons import WeaponType +from game.dcs.aircrafttype import AircraftType from game.missiongenerator.aircraft.flightdata import FlightData from game.missiongenerator.aircraft.flightgroupconfigurator import ( FlightGroupConfigurator, @@ -36,6 +38,7 @@ from game.theater.controlpoint import ( ParkingType, Airfield, ) +from game.theater.theatergroundobject import EwrGroundObject, SamGroundObject from game.unitmap import UnitMap from game.squadrons import Squadron @@ -51,6 +54,7 @@ PRETENSE_BARCAP_FLIGHTS_PER_CP = 1 PRETENSE_AI_AIRCRAFT_PER_FLIGHT = 2 PRETENSE_AI_AWACS_PER_FLIGHT = 1 PRETENSE_AI_TANKERS_PER_FLIGHT = 1 +PRETENSE_PLAYER_AIRCRAFT_PER_FLIGHT = 2 class PretenseAircraftGenerator: @@ -214,6 +218,49 @@ class PretenseAircraftGenerator: coalition.air_wing.add_squadron(squadron) return squadron + def generate_pretense_squadron_for( + self, + aircraft_type: AircraftType, + cp: ControlPoint, + coalition: Coalition, + ) -> Optional[Squadron]: + """ + Generates a Pretense squadron from the faction squadron definitions for the designated + AircraftType. Use FlightType AIR_ASSAULT + for Pretense supply helicopters and TRANSPORT for off-map cargo plane squadrons. + """ + + squadron_def = coalition.air_wing.squadron_def_generator.generate_for_aircraft( + aircraft_type + ) + flight_type = random.choice(list(squadron_def.auto_assignable_mission_types)) + if flight_type == FlightType.ESCORT and aircraft_type.helicopter: + flight_type = FlightType.CAS + if flight_type in ( + FlightType.INTERCEPTION, + FlightType.ESCORT, + FlightType.SWEEP, + ): + flight_type = FlightType.BARCAP + if flight_type in (FlightType.SEAD_ESCORT, FlightType.SEAD_SWEEP): + flight_type = FlightType.SEAD + if flight_type == FlightType.ANTISHIP: + flight_type = FlightType.STRIKE + if flight_type == FlightType.TRANSPORT: + flight_type = FlightType.AIR_ASSAULT + squadron = Squadron.create_from( + squadron_def, + flight_type, + 2, + cp, + coalition, + self.game, + ) + if squadron.aircraft not in coalition.air_wing.squadrons: + coalition.air_wing.squadrons[squadron.aircraft] = list() + coalition.air_wing.add_squadron(squadron) + return squadron + def generate_pretense_aircraft( self, cp: ControlPoint, ato: AirTaskingOrder ) -> None: @@ -462,6 +509,68 @@ class PretenseAircraftGenerator: ato.add_package(package) return + def generate_pretense_aircraft_for_players( + self, cp: ControlPoint, coalition: Coalition, ato: AirTaskingOrder + ) -> None: + """ + Plans and generates player piloted aircraft groups/packages for Pretense. + + Aircraft generation is done by walking the control points which will be made into + Pretense "zones" and spawning flights for different missions. + After the flight is generated the package is added to the ATO so the flights + can be configured. + + Args: + cp: Control point to generate aircraft for. + coalition: Coalition to generate aircraft for. + ato: The ATO to generate aircraft for. + """ + + aircraft_per_flight = PRETENSE_PLAYER_AIRCRAFT_PER_FLIGHT + random_aircraft_list = list(coalition.faction.aircraft) + random.shuffle(random_aircraft_list) + for aircraft_type in random_aircraft_list: + # Don't generate any player flights for non-flyable types (obviously) + if not aircraft_type.flyable: + continue + if not cp.can_operate(aircraft_type): + continue + + squadron = self.generate_pretense_squadron_for( + aircraft_type, + cp, + coalition, + ) + if squadron is not None: + squadron.owned_aircraft += PRETENSE_PLAYER_AIRCRAFT_PER_FLIGHT + squadron.untasked_aircraft += PRETENSE_PLAYER_AIRCRAFT_PER_FLIGHT + squadron.populate_for_turn_0(False) + for pilot in squadron.pilot_pool: + pilot.player = True + package = Package(cp, squadron.flight_db, auto_asap=False) + flight = Flight( + package, + squadron, + aircraft_per_flight, + squadron.primary_task, + StartType.COLD, + divert=cp, + ) + for pilot in flight.roster.pilots: + if pilot is not None: + pilot.player = True + print( + f"Generated flight for {squadron.primary_task} flying {squadron.aircraft.name} at {squadron.location.name}. Pilot client count: {flight.client_count}" + ) + + package.add_flight(flight) + flight.state = WaitingForStart( + flight, self.game.settings, self.game.conditions.start_time + ) + ato.add_package(package) + + return + def initialize_pretense_data_structures(self, cp: ControlPoint) -> None: """ Ensures that the data structures used to pass flight group information @@ -531,6 +640,7 @@ class PretenseAircraftGenerator: """ self.initialize_pretense_data_structures(cp) + is_player = True if country == cp.coalition.faction.country: offmap_transport_cp = self.find_pretense_cargo_plane_cp(cp) @@ -558,7 +668,6 @@ class PretenseAircraftGenerator: self.generate_pretense_aircraft(cp, ato) else: - is_player = True coalition = ( self.game.coalition_for(is_player) if country == self.game.coalition_for(is_player).faction.country @@ -566,6 +675,11 @@ class PretenseAircraftGenerator: ) self.generate_pretense_aircraft_for_other_side(cp, coalition, ato) + if country == self.game.coalition_for(is_player).faction.country: + if not isinstance(cp, OffMapSpawn): + coalition = self.game.coalition_for(is_player) + self.generate_pretense_aircraft_for_players(cp, coalition, ato) + self._reserve_frequencies_and_tacan(ato) def generate_packages( @@ -590,10 +704,15 @@ class PretenseAircraftGenerator: ) flight.return_pilots_and_aircraft() continue - logging.info(f"Generating flight: {flight.unit_type}") - group = self.create_and_configure_flight( - flight, country, dynamic_runways + logging.info( + f"Generating flight: {flight.unit_type} for {flight.flight_type.name}" ) + try: + group = self.create_and_configure_flight( + flight, country, dynamic_runways + ) + except NoParkingSlotError: + return self.unit_map.add_aircraft(group, flight) def create_and_configure_flight( @@ -632,6 +751,67 @@ class PretenseAircraftGenerator: self.use_client, ).configure() ) + else: + if flight.client_count > 0: + if flight.flight_type == FlightType.CAS: + for conflict in self.game.theater.conflicts(): + flight.package.target = conflict + break + elif ( + flight.flight_type == FlightType.STRIKE + or flight.flight_type == FlightType.BAI + ): + for cp in self.game.theater.closest_opposing_control_points(): + if cp.coalition == flight.coalition: + continue + for mission_target in cp.ground_objects: + flight.package.target = mission_target + elif ( + flight.flight_type == FlightType.OCA_RUNWAY + or flight.flight_type == FlightType.OCA_AIRCRAFT + ): + for cp in self.game.theater.controlpoints: + if cp.coalition == flight.coalition or not isinstance( + cp, Airfield + ): + continue + flight.package.target = cp + elif flight.flight_type == FlightType.DEAD: + for cp in self.game.theater.controlpoints: + if cp.coalition == flight.coalition: + continue + for ground_object in cp.ground_objects: + is_ewr = isinstance(ground_object, EwrGroundObject) + is_sam = isinstance(ground_object, SamGroundObject) + + if is_ewr or is_sam: + flight.package.target = ground_object + elif flight.flight_type == FlightType.AIR_ASSAULT: + for cp in self.game.theater.closest_opposing_control_points(): + if cp.coalition == flight.coalition: + continue + if flight.is_hercules: + if cp.coalition == flight.coalition or not isinstance( + cp, Airfield + ): + continue + flight.package.target = cp + + FlightGroupConfigurator( + flight, + group, + self.game, + self.mission, + self.time, + self.radio_registry, + self.tacan_registy, + self.laser_code_registry, + self.mission_data, + dynamic_runways, + self.use_client, + ).configure() + # for unit in group.units: + # unit.set_client() if self.ewrj: self._track_ewrj_flight(flight, group) diff --git a/game/pretense/pretenseflightgroupspawner.py b/game/pretense/pretenseflightgroupspawner.py index 18deecbe..5622905b 100644 --- a/game/pretense/pretenseflightgroupspawner.py +++ b/game/pretense/pretenseflightgroupspawner.py @@ -82,9 +82,10 @@ class PretenseFlightGroupSpawner(FlightGroupSpawner): try: if self.start_type is StartType.IN_FLIGHT: - self.flight.coalition.game.pretense_air[cp_side][cp_name_trimmed][ - self.flight.flight_type.name - ].append(name) + if self.flight.client_count == 0: + self.flight.coalition.game.pretense_air[cp_side][cp_name_trimmed][ + self.flight.flight_type.name + ].append(name) group = self._generate_over_departure(name, cp) return group elif isinstance(cp, NavalControlPoint): @@ -95,9 +96,10 @@ class PretenseFlightGroupSpawner(FlightGroupSpawner): f"Carrier group {carrier_group} is a " f"{carrier_group.__class__.__name__}, expected a ShipGroup" ) - self.flight.coalition.game.pretense_air[cp_side][cp_name_trimmed][ - self.flight.flight_type.name - ].append(name) + if self.flight.client_count == 0: + self.flight.coalition.game.pretense_air[cp_side][cp_name_trimmed][ + self.flight.flight_type.name + ].append(name) return self._generate_at_group(name, carrier_group) elif isinstance(cp, Fob): is_heli = self.flight.squadron.aircraft.helicopter @@ -125,9 +127,10 @@ class PretenseFlightGroupSpawner(FlightGroupSpawner): pad_group = self._generate_at_cp_ground_spawn(name, cp) if pad_group is not None: return pad_group - self.flight.coalition.game.pretense_air[cp_side][cp_name_trimmed][ - self.flight.flight_type.name - ].append(name) + if self.flight.client_count == 0: + self.flight.coalition.game.pretense_air[cp_side][cp_name_trimmed][ + self.flight.flight_type.name + ].append(name) return self._generate_over_departure(name, cp) elif isinstance(cp, Airfield): is_heli = self.flight.squadron.aircraft.helicopter @@ -145,9 +148,10 @@ class PretenseFlightGroupSpawner(FlightGroupSpawner): pad_group = self._generate_at_cp_ground_spawn(name, cp) if pad_group is not None: return pad_group - self.flight.coalition.game.pretense_air[cp_side][cp_name_trimmed][ - self.flight.flight_type.name - ].append(name) + if self.flight.client_count == 0: + self.flight.coalition.game.pretense_air[cp_side][cp_name_trimmed][ + self.flight.flight_type.name + ].append(name) return self._generate_at_airfield(name, cp) else: raise NotImplementedError( @@ -155,12 +159,19 @@ class PretenseFlightGroupSpawner(FlightGroupSpawner): ) except NoParkingSlotError: # Generated when there is no place on Runway or on Parking Slots - logging.warning( - "No room on runway or parking slots. Starting from the air." - ) - self.flight.start_type = StartType.IN_FLIGHT - group = self._generate_over_departure(name, cp) - return group + if self.flight.client_count > 0: + # Don't generate player airstarts + logging.warning( + "No room on runway or parking slots. Not generating a player air-start." + ) + raise NoParkingSlotError + else: + logging.warning( + "No room on runway or parking slots. Starting from the air." + ) + self.flight.start_type = StartType.IN_FLIGHT + group = self._generate_over_departure(name, cp) + return group def generate_mid_mission(self) -> FlyingGroup[Any]: assert isinstance(self.flight.state, InFlight) From 6fd60d150e070dd2363d7cded83537515d60fba9 Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Tue, 19 Sep 2023 11:11:37 +0300 Subject: [PATCH 156/243] Raise NoParkingSlotError instead of RuntimeError when running out of ground spawns. --- game/missiongenerator/aircraft/flightgroupspawner.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/game/missiongenerator/aircraft/flightgroupspawner.py b/game/missiongenerator/aircraft/flightgroupspawner.py index 6514f253..bec3f522 100644 --- a/game/missiongenerator/aircraft/flightgroupspawner.py +++ b/game/missiongenerator/aircraft/flightgroupspawner.py @@ -436,7 +436,9 @@ class FlightGroupSpawner: ) group.units[1 + i].heading = ground_spawn[0].units[0].heading except IndexError as ex: - raise RuntimeError(f"Not enough STOL slots available at {cp}") from ex + raise NoParkingSlotError( + f"Not enough STOL slots available at {cp}" + ) from ex return group def dcs_start_type(self) -> DcsStartType: From 47ee0e534056407893d918a1a309bd340c2a6267 Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Tue, 19 Sep 2023 11:12:39 +0300 Subject: [PATCH 157/243] Added naval units (supply/cargo/landing ships) to Pretense scripts. --- resources/plugins/pretense/init_header.lua | 52 ++++++++++++++++++- .../plugins/pretense/pretense_compiled.lua | 18 +++++++ 2 files changed, 68 insertions(+), 2 deletions(-) diff --git a/resources/plugins/pretense/init_header.lua b/resources/plugins/pretense/init_header.lua index b5d49d0e..850bfbc7 100644 --- a/resources/plugins/pretense/init_header.lua +++ b/resources/plugins/pretense/init_header.lua @@ -295,7 +295,31 @@ presets = { cost = 2000, type = 'upgrade', template = "ammo-depot" - }) + }), + shipTankerSeawisegiant = Preset:new({ + display = 'Tanker Seawise Giant', + cost = 1500, + type = 'upgrade', + template = "ship-tanker-seawisegiant" + }), + shipLandingShipSamuelChase = Preset:new({ + display = 'LST USS Samuel Chase', + cost = 1500, + type = 'upgrade', + template = "ship-landingship-samuelchase" + }), + shipLandingShipRopucha = Preset:new({ + display = 'LS Ropucha', + cost = 1500, + type = 'upgrade', + template = "ship-landingship-ropucha" + }), + shipTankerElnya = Preset:new({ + display = 'Tanker Elnya', + cost = 1500, + type = 'upgrade', + template = "ship-tanker-elnya" + }) }, supply = { fuelCache = Preset:new({ @@ -413,7 +437,31 @@ presets = { type ='upgrade', income = 50, template = "tv-tower" - }) + }), + shipSupplyTilde = Preset:new({ + display = 'Ship_Tilde_Supply', + cost = 1500, + type = 'upgrade', + template = "ship-supply-tilde" + }), + shipLandingShipLstMk2 = Preset:new({ + display = 'LST Mk.II', + cost = 1500, + type = 'upgrade', + template = "ship-landingship-lstmk2" + }), + shipBulkerYakushev = Preset:new({ + display = 'Bulker Yakushev', + cost = 1500, + type = 'upgrade', + template = "ship-bulker-yakushev" + }), + shipCargoIvanov = Preset:new({ + display = 'Cargo Ivanov', + cost = 1500, + type = 'upgrade', + template = "ship-cargo-ivanov" + }) }, airdef = { comCenter = Preset:new({ diff --git a/resources/plugins/pretense/pretense_compiled.lua b/resources/plugins/pretense/pretense_compiled.lua index 4d3a04f2..0a8d7be1 100644 --- a/resources/plugins/pretense/pretense_compiled.lua +++ b/resources/plugins/pretense/pretense_compiled.lua @@ -7527,6 +7527,24 @@ do TemplateDB.templates["command-center"] = { type=".Command Center", category="Fortifications", shape="ComCenter", dataCategory=TemplateDB.type.static } TemplateDB.templates["military-staff"] = { type="Military staff", category="Fortifications", shape="aviashtab", dataCategory=TemplateDB.type.static } + + TemplateDB.templates["ship-tanker-seawisegiant"] = { type="Seawise_Giant", category="Ships", dataCategory=TemplateDB.type.static } + + TemplateDB.templates["ship-supply-tilde"] = { type="Ship_Tilde_Supply", category="Ships", dataCategory=TemplateDB.type.static } + + TemplateDB.templates["ship-landingship-samuelchase"] = { type="USS_Samuel_Chase", category="Ships", dataCategory=TemplateDB.type.static } + + TemplateDB.templates["ship-landingship-ropucha"] = { type="BDK-775", category="Ships", dataCategory=TemplateDB.type.static } + + TemplateDB.templates["ship-tanker-elnya"] = { type="ELNYA", category="Ships", dataCategory=TemplateDB.type.static } + + TemplateDB.templates["ship-landingship-lstmk2"] = { type="LST_Mk2", category="Ships", dataCategory=TemplateDB.type.static } + + TemplateDB.templates["ship-bulker-yakushev"] = { type="Dry-cargo ship-1", category="Ships", dataCategory=TemplateDB.type.static } + + TemplateDB.templates["ship-cargo-ivanov"] = { type="Dry-cargo ship-2", category="Ships", dataCategory=TemplateDB.type.static } + + end -----------------[[ END OF TemplateDB.lua ]]----------------- From 064d17aab087fca97dc96bf2f3e0a948bcb536bb Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Tue, 19 Sep 2023 18:20:55 +0300 Subject: [PATCH 158/243] Improved the typing of game.pretense_air --- game/game.py | 5 ++- game/pretense/pretenseaircraftgenerator.py | 2 +- game/pretense/pretenseflightgroupspawner.py | 14 ++++++--- game/pretense/pretenseluagenerator.py | 34 ++++++++++----------- 4 files changed, 31 insertions(+), 24 deletions(-) diff --git a/game/game.py b/game/game.py index 31ce38e9..a5ecbd51 100644 --- a/game/game.py +++ b/game/game.py @@ -151,7 +151,10 @@ class Game: # Side, control point, mission type self.pretense_ground_supply: dict[int, dict[str, List[str]]] = {1: {}, 2: {}} self.pretense_ground_assault: dict[int, dict[str, List[str]]] = {1: {}, 2: {}} - self.pretense_air: dict[int, dict[str, dict[str, List[str]]]] = {1: {}, 2: {}} + self.pretense_air: dict[int, dict[str, dict[FlightType, List[str]]]] = { + 1: {}, + 2: {}, + } self.on_load(game_still_initializing=True) diff --git a/game/pretense/pretenseaircraftgenerator.py b/game/pretense/pretenseaircraftgenerator.py index 6e78054d..2d4e98ce 100644 --- a/game/pretense/pretenseaircraftgenerator.py +++ b/game/pretense/pretenseaircraftgenerator.py @@ -603,7 +603,7 @@ class PretenseAircraftGenerator: cp: Control point to generate aircraft for. flight: The current flight being generated. """ - flight_type = flight.flight_type.name + flight_type = flight.flight_type cp_name_trimmed = "".join([i for i in cp.name.lower() if i.isalnum()]) for side in range(1, 3): diff --git a/game/pretense/pretenseflightgroupspawner.py b/game/pretense/pretenseflightgroupspawner.py index 5622905b..35e010f8 100644 --- a/game/pretense/pretenseflightgroupspawner.py +++ b/game/pretense/pretenseflightgroupspawner.py @@ -84,7 +84,7 @@ class PretenseFlightGroupSpawner(FlightGroupSpawner): if self.start_type is StartType.IN_FLIGHT: if self.flight.client_count == 0: self.flight.coalition.game.pretense_air[cp_side][cp_name_trimmed][ - self.flight.flight_type.name + self.flight.flight_type ].append(name) group = self._generate_over_departure(name, cp) return group @@ -98,7 +98,7 @@ class PretenseFlightGroupSpawner(FlightGroupSpawner): ) if self.flight.client_count == 0: self.flight.coalition.game.pretense_air[cp_side][cp_name_trimmed][ - self.flight.flight_type.name + self.flight.flight_type ].append(name) return self._generate_at_group(name, carrier_group) elif isinstance(cp, Fob): @@ -120,6 +120,10 @@ class PretenseFlightGroupSpawner(FlightGroupSpawner): f' spawns" setting is currently disabled.' ) if cp.has_helipads and (is_heli or is_vtol): + if self.flight.client_count == 0: + self.flight.coalition.game.pretense_air[cp_side][ + cp_name_trimmed + ][self.flight.flight_type].append(name) pad_group = self._generate_at_cp_helipad(name, cp) if pad_group is not None: return pad_group @@ -129,7 +133,7 @@ class PretenseFlightGroupSpawner(FlightGroupSpawner): return pad_group if self.flight.client_count == 0: self.flight.coalition.game.pretense_air[cp_side][cp_name_trimmed][ - self.flight.flight_type.name + self.flight.flight_type ].append(name) return self._generate_over_departure(name, cp) elif isinstance(cp, Airfield): @@ -150,7 +154,7 @@ class PretenseFlightGroupSpawner(FlightGroupSpawner): return pad_group if self.flight.client_count == 0: self.flight.coalition.game.pretense_air[cp_side][cp_name_trimmed][ - self.flight.flight_type.name + self.flight.flight_type ].append(name) return self._generate_at_airfield(name, cp) else: @@ -208,7 +212,7 @@ class PretenseFlightGroupSpawner(FlightGroupSpawner): self.mission_data.cp_stack[cp] += STACK_SEPARATION self.flight.coalition.game.pretense_air[cp_side][cp_name_trimmed][ - self.flight.flight_type.name + self.flight.flight_type ].append(name) group = self.mission.flight_group( country=self.country, diff --git a/game/pretense/pretenseluagenerator.py b/game/pretense/pretenseluagenerator.py index ec4aff92..336ec5b1 100644 --- a/game/pretense/pretenseluagenerator.py +++ b/game/pretense/pretenseluagenerator.py @@ -80,7 +80,7 @@ class PretenseLuaGenerator(LuaGenerator): + "'}),\n" ) for mission_type in self.game.pretense_air[cp_side][cp_name_trimmed]: - if mission_type == FlightType.AIR_ASSAULT.name: + if mission_type == FlightType.AIR_ASSAULT: mission_name = "supply.helo" for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ mission_type @@ -110,7 +110,7 @@ class PretenseLuaGenerator(LuaGenerator): + "' }),\n" ) for mission_type in self.game.pretense_air[cp_side][cp_name_trimmed]: - if mission_type == FlightType.SEAD.name: + if mission_type == FlightType.SEAD: mission_name = "attack.sead" for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ mission_type @@ -121,7 +121,7 @@ class PretenseLuaGenerator(LuaGenerator): + air_group + "', altitude=25000, expend=AI.Task.WeaponExpend.ALL}),\n" ) - elif mission_type == FlightType.CAS.name: + elif mission_type == FlightType.CAS: mission_name = "attack.cas" for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ mission_type @@ -132,7 +132,7 @@ class PretenseLuaGenerator(LuaGenerator): + air_group + "', altitude=15000, expend=AI.Task.WeaponExpend.ONE}),\n" ) - elif mission_type == FlightType.BAI.name: + elif mission_type == FlightType.BAI: mission_name = "attack.bai" for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ mission_type @@ -143,7 +143,7 @@ class PretenseLuaGenerator(LuaGenerator): + air_group + "', altitude=10000, expend=AI.Task.WeaponExpend.ONE}),\n" ) - elif mission_type == FlightType.STRIKE.name: + elif mission_type == FlightType.STRIKE: mission_name = "attack.strike" for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ mission_type @@ -154,7 +154,7 @@ class PretenseLuaGenerator(LuaGenerator): + air_group + "', altitude=20000, expend=AI.Task.WeaponExpend.ALL}),\n" ) - elif mission_type == FlightType.BARCAP.name: + elif mission_type == FlightType.BARCAP: mission_name = "patrol.aircraft" for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ mission_type @@ -165,7 +165,7 @@ class PretenseLuaGenerator(LuaGenerator): + air_group + "', altitude=25000, range=25}),\n" ) - elif mission_type == FlightType.REFUELING.name: + elif mission_type == FlightType.REFUELING: mission_name = "support.tanker" for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ mission_type @@ -191,7 +191,7 @@ class PretenseLuaGenerator(LuaGenerator): + tanker_variant + "'}),\n" ) - elif mission_type == FlightType.AEWC.name: + elif mission_type == FlightType.AEWC: mission_name = "support.awacs" for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ mission_type @@ -364,7 +364,7 @@ class PretenseLuaGenerator(LuaGenerator): + "'}),\n" ) for mission_type in self.game.pretense_air[cp_side][cp_name_trimmed]: - if mission_type == FlightType.AIR_ASSAULT.name: + if mission_type == FlightType.AIR_ASSAULT: mission_name = "supply.helo" for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ mission_type @@ -388,7 +388,7 @@ class PretenseLuaGenerator(LuaGenerator): + "-sam-blue' }),\n" ) for mission_type in self.game.pretense_air[cp_side][cp_name_trimmed]: - if mission_type == FlightType.SEAD.name: + if mission_type == FlightType.SEAD: mission_name = "attack.sead" for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ mission_type @@ -399,7 +399,7 @@ class PretenseLuaGenerator(LuaGenerator): + air_group + "', altitude=25000, expend=AI.Task.WeaponExpend.ALL}),\n" ) - elif mission_type == FlightType.CAS.name: + elif mission_type == FlightType.CAS: mission_name = "attack.cas" for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ mission_type @@ -410,7 +410,7 @@ class PretenseLuaGenerator(LuaGenerator): + air_group + "', altitude=15000, expend=AI.Task.WeaponExpend.ONE}),\n" ) - elif mission_type == FlightType.BAI.name: + elif mission_type == FlightType.BAI: mission_name = "attack.bai" for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ mission_type @@ -421,7 +421,7 @@ class PretenseLuaGenerator(LuaGenerator): + air_group + "', altitude=10000, expend=AI.Task.WeaponExpend.ONE}),\n" ) - elif mission_type == FlightType.STRIKE.name: + elif mission_type == FlightType.STRIKE: mission_name = "attack.strike" for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ mission_type @@ -432,7 +432,7 @@ class PretenseLuaGenerator(LuaGenerator): + air_group + "', altitude=20000, expend=AI.Task.WeaponExpend.ALL}),\n" ) - elif mission_type == FlightType.BARCAP.name: + elif mission_type == FlightType.BARCAP: mission_name = "patrol.aircraft" for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ mission_type @@ -443,7 +443,7 @@ class PretenseLuaGenerator(LuaGenerator): + air_group + "', altitude=25000, range=25}),\n" ) - elif mission_type == FlightType.REFUELING.name: + elif mission_type == FlightType.REFUELING: mission_name = "support.tanker" for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ mission_type @@ -470,7 +470,7 @@ class PretenseLuaGenerator(LuaGenerator): + tanker_variant + "'}),\n" ) - elif mission_type == FlightType.AEWC.name: + elif mission_type == FlightType.AEWC: mission_name = "support.awacs" for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ mission_type @@ -601,7 +601,7 @@ class PretenseLuaGenerator(LuaGenerator): continue cp_name_trimmed = "".join([i for i in cp.name.lower() if i.isalnum()]) for mission_type in self.game.pretense_air[cp_side][cp_name_trimmed]: - if mission_type == FlightType.PRETENSE_CARGO.name: + if mission_type == FlightType.PRETENSE_CARGO: for air_group in self.game.pretense_air[cp_side][ cp_name_trimmed ][mission_type]: From 5fa7757ae1e83329a593c81121eb92d7bab62f9b Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Tue, 19 Sep 2023 19:10:36 +0300 Subject: [PATCH 159/243] Fixed not all generated flights getting inserted into Pretense data structures. --- game/pretense/pretenseaircraftgenerator.py | 6 +++--- game/pretense/pretenseflightgroupspawner.py | 12 ++++++++++++ 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/game/pretense/pretenseaircraftgenerator.py b/game/pretense/pretenseaircraftgenerator.py index 2d4e98ce..133c5501 100644 --- a/game/pretense/pretenseaircraftgenerator.py +++ b/game/pretense/pretenseaircraftgenerator.py @@ -556,9 +556,9 @@ class PretenseAircraftGenerator: StartType.COLD, divert=cp, ) - for pilot in flight.roster.pilots: - if pilot is not None: - pilot.player = True + for roster_pilot in flight.roster.pilots: + if roster_pilot is not None: + roster_pilot.player = True print( f"Generated flight for {squadron.primary_task} flying {squadron.aircraft.name} at {squadron.location.name}. Pilot client count: {flight.client_count}" ) diff --git a/game/pretense/pretenseflightgroupspawner.py b/game/pretense/pretenseflightgroupspawner.py index 35e010f8..9ee1a6fb 100644 --- a/game/pretense/pretenseflightgroupspawner.py +++ b/game/pretense/pretenseflightgroupspawner.py @@ -128,6 +128,10 @@ class PretenseFlightGroupSpawner(FlightGroupSpawner): if pad_group is not None: return pad_group if cp.has_ground_spawns and (self.flight.client_count > 0 or is_heli): + if self.flight.client_count == 0: + self.flight.coalition.game.pretense_air[cp_side][ + cp_name_trimmed + ][self.flight.flight_type].append(name) pad_group = self._generate_at_cp_ground_spawn(name, cp) if pad_group is not None: return pad_group @@ -139,6 +143,10 @@ class PretenseFlightGroupSpawner(FlightGroupSpawner): elif isinstance(cp, Airfield): is_heli = self.flight.squadron.aircraft.helicopter if cp.has_helipads and is_heli: + if self.flight.client_count == 0: + self.flight.coalition.game.pretense_air[cp_side][ + cp_name_trimmed + ][self.flight.flight_type].append(name) pad_group = self._generate_at_cp_helipad(name, cp) if pad_group is not None: return pad_group @@ -149,6 +157,10 @@ class PretenseFlightGroupSpawner(FlightGroupSpawner): >= self.flight.count and (self.flight.client_count > 0 or is_heli) ): + if self.flight.client_count == 0: + self.flight.coalition.game.pretense_air[cp_side][ + cp_name_trimmed + ][self.flight.flight_type].append(name) pad_group = self._generate_at_cp_ground_spawn(name, cp) if pad_group is not None: return pad_group From 7b9ddb1ece254a2c232cb6942306f2709ccaeba9 Mon Sep 17 00:00:00 2001 From: Raffson Date: Sat, 23 Sep 2023 19:44:01 +0200 Subject: [PATCH 160/243] Formatting --- game/pretense/pretensemissiongenerator.py | 1 - game/pretense/pretensetriggergenerator.py | 1 - 2 files changed, 2 deletions(-) diff --git a/game/pretense/pretensemissiongenerator.py b/game/pretense/pretensemissiongenerator.py index 2f8942fe..4bb5fe16 100644 --- a/game/pretense/pretensemissiongenerator.py +++ b/game/pretense/pretensemissiongenerator.py @@ -209,7 +209,6 @@ class PretenseMissionGenerator(MissionGenerator): for cp in self.game.theater.controlpoints: for country in (self.p_country, self.e_country): - if country == self.p_country: ato = self.game.blue.ato else: diff --git a/game/pretense/pretensetriggergenerator.py b/game/pretense/pretensetriggergenerator.py index 604abdfc..d01fdf2a 100644 --- a/game/pretense/pretensetriggergenerator.py +++ b/game/pretense/pretensetriggergenerator.py @@ -163,7 +163,6 @@ class PretenseTriggerGenerator: else: 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( cp.position, From 765c85b6390dbe87867e729abf3d3bc131506909 Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Sat, 23 Sep 2023 12:59:50 +0300 Subject: [PATCH 161/243] Now generates one transport helicopter squadron for each control point which can operate one. Also implemented generating several Pretense cargo plane squadrons, defined by PRETENSE_AI_CARGO_PLANES_PER_SIDE. Cleaned up PretenseMissionGenerator.generate_air_units() a bit. --- game/ato/flightplans/pretensecargo.py | 10 +- game/pretense/pretenseaircraftgenerator.py | 105 ++++++++++++--------- game/pretense/pretensemissiongenerator.py | 28 ++---- 3 files changed, 75 insertions(+), 68 deletions(-) diff --git a/game/ato/flightplans/pretensecargo.py b/game/ato/flightplans/pretensecargo.py index 9aa3472f..44c1ba02 100644 --- a/game/ato/flightplans/pretensecargo.py +++ b/game/ato/flightplans/pretensecargo.py @@ -1,5 +1,6 @@ from __future__ import annotations +import random from collections.abc import Iterator from dataclasses import dataclass from datetime import timedelta @@ -17,7 +18,8 @@ if TYPE_CHECKING: from ..flightwaypoint import FlightWaypoint -PRETENSE_CARGO_FLIGHT_DISTANCE = 50000 +PRETENSE_CARGO_FLIGHT_DISTANCE = 100000 +PRETENSE_CARGO_FLIGHT_HEADING_RANGE = 20 class PretenseCargoFlightPlan(StandardFlightPlan[FerryLayout]): @@ -67,8 +69,12 @@ class Builder(IBuilder[PretenseCargoFlightPlan, FerryLayout]): offmap_transport_cp = self.coalition.game.theater.find_control_point_by_id( offmap_transport_cp_id ) + offmap_heading = random.randrange( + int(heading_from_flot - PRETENSE_CARGO_FLIGHT_HEADING_RANGE), + int(heading_from_flot + PRETENSE_CARGO_FLIGHT_HEADING_RANGE), + ) offmap_transport_spawn = offmap_transport_cp.position.point_from_heading( - heading_from_flot, PRETENSE_CARGO_FLIGHT_DISTANCE + offmap_heading, PRETENSE_CARGO_FLIGHT_DISTANCE ) altitude_is_agl = self.flight.unit_type.dcs_unit_type.helicopter diff --git a/game/pretense/pretenseaircraftgenerator.py b/game/pretense/pretenseaircraftgenerator.py index 133c5501..89a078f2 100644 --- a/game/pretense/pretenseaircraftgenerator.py +++ b/game/pretense/pretenseaircraftgenerator.py @@ -37,6 +37,8 @@ from game.theater.controlpoint import ( OffMapSpawn, ParkingType, Airfield, + Carrier, + Lha, ) from game.theater.theatergroundobject import EwrGroundObject, SamGroundObject from game.unitmap import UnitMap @@ -47,13 +49,14 @@ if TYPE_CHECKING: PRETENSE_SQUADRON_DEF_RETRIES = 100 -PRETENSE_SEAD_FLIGHTS_PER_CP = 1 -PRETENSE_CAS_FLIGHTS_PER_CP = 1 -PRETENSE_STRIKE_FLIGHTS_PER_CP = 1 -PRETENSE_BARCAP_FLIGHTS_PER_CP = 1 +PRETENSE_SEAD_FLIGHTS_PER_CP = 2 +PRETENSE_CAS_FLIGHTS_PER_CP = 2 +PRETENSE_STRIKE_FLIGHTS_PER_CP = 2 +PRETENSE_BARCAP_FLIGHTS_PER_CP = 2 PRETENSE_AI_AIRCRAFT_PER_FLIGHT = 2 PRETENSE_AI_AWACS_PER_FLIGHT = 1 PRETENSE_AI_TANKERS_PER_FLIGHT = 1 +PRETENSE_AI_CARGO_PLANES_PER_SIDE = 8 PRETENSE_PLAYER_AIRCRAFT_PER_FLIGHT = 2 @@ -138,35 +141,41 @@ class PretenseAircraftGenerator: offmap_transport_cp_id = front_line_cp.id return self.game.theater.find_control_point_by_id(offmap_transport_cp_id) - def should_generate_pretense_transports( - self, air_wing: AirWing - ) -> tuple[bool, bool]: + def should_generate_pretense_transports_at( + self, control_point: ControlPoint + ) -> bool: """ - Returns a tuple of booleans, telling whether transport helicopter and aircraft - squadrons should be generated from the faction squadron definitions. + Returns a boolean, telling whether a transport helicopter squadron + should be generated at control point from the faction squadron definitions. - This helps to ensure that the faction has at least one transport helicopter and one cargo plane squadron. - - (autogenerate_transport_helicopter_squadron, autogenerate_cargo_plane_squadron) + This helps to ensure that the faction has at least one transport helicopter at each control point. """ autogenerate_transport_helicopter_squadron = True - autogenerate_cargo_plane_squadron = True + for squadron in control_point.squadrons: + if squadron.aircraft.helicopter and ( + squadron.aircraft.capable_of(FlightType.TRANSPORT) + or squadron.aircraft.capable_of(FlightType.AIR_ASSAULT) + ): + autogenerate_transport_helicopter_squadron = False + return autogenerate_transport_helicopter_squadron + + def number_of_pretense_cargo_plane_sq_for(self, air_wing: AirWing) -> int: + """ + Returns how many Pretense cargo plane squadrons a specific coalition has. + This is used to define how many such squadrons should be generated from + the faction squadron definitions. + + This helps to ensure that the faction has enough cargo plane squadrons. + """ + number_of_pretense_cargo_plane_squadrons = 0 for aircraft_type in air_wing.squadrons: for squadron in air_wing.squadrons[aircraft_type]: - if squadron.aircraft.helicopter and ( + if not squadron.aircraft.helicopter and ( squadron.aircraft.capable_of(FlightType.TRANSPORT) or squadron.aircraft.capable_of(FlightType.AIR_ASSAULT) ): - autogenerate_transport_helicopter_squadron = False - elif not squadron.aircraft.helicopter and ( - squadron.aircraft.capable_of(FlightType.TRANSPORT) - or squadron.aircraft.capable_of(FlightType.AIR_ASSAULT) - ): - autogenerate_cargo_plane_squadron = False - return ( - autogenerate_transport_helicopter_squadron, - autogenerate_cargo_plane_squadron, - ) + number_of_pretense_cargo_plane_squadrons += 1 + return number_of_pretense_cargo_plane_squadrons def generate_pretense_squadron( self, @@ -375,6 +384,9 @@ class PretenseAircraftGenerator: flight, self.game.settings, self.game.conditions.start_time ) + print( + f"Generated flight for {flight_type} flying {squadron.aircraft.name} at {squadron.location.name}" + ) ato.add_package(package) return @@ -397,7 +409,7 @@ class PretenseAircraftGenerator: """ aircraft_per_flight = 1 - if cp.has_helipads and not cp.is_fleet: + if (cp.has_helipads or isinstance(cp, Airfield)) and not cp.is_fleet: flight_type = FlightType.AIR_ASSAULT squadron = self.generate_pretense_squadron( cp, @@ -586,9 +598,6 @@ class PretenseAircraftGenerator: for side in range(1, 3): if cp_name_trimmed not in cp.coalition.game.pretense_air[side]: cp.coalition.game.pretense_air[side][cp_name_trimmed] = {} - print( - f"Populated flight.coalition.game.pretense_air[{side}][{cp_name_trimmed}]" - ) return def initialize_pretense_data_structures_for_flight( @@ -609,9 +618,6 @@ class PretenseAircraftGenerator: for side in range(1, 3): if cp_name_trimmed not in flight.coalition.game.pretense_air[side]: flight.coalition.game.pretense_air[side][cp_name_trimmed] = {} - print( - f"Populated flight.coalition.game.pretense_air[{side}][{cp_name_trimmed}]" - ) if ( flight_type not in flight.coalition.game.pretense_air[side][cp_name_trimmed] @@ -619,9 +625,6 @@ class PretenseAircraftGenerator: flight.coalition.game.pretense_air[side][cp_name_trimmed][ flight_type ] = list() - print( - f"Populated flight.coalition.game.pretense_air[{side}][{cp_name_trimmed}][{flight_type}]" - ) return def generate_flights( @@ -644,20 +647,24 @@ class PretenseAircraftGenerator: if country == cp.coalition.faction.country: offmap_transport_cp = self.find_pretense_cargo_plane_cp(cp) - ( - autogenerate_transport_helicopter_squadron, - autogenerate_cargo_plane_squadron, - ) = self.should_generate_pretense_transports(cp.coalition.air_wing) - - if autogenerate_transport_helicopter_squadron: + if ( + cp.has_helipads + or isinstance(cp, Airfield) + or isinstance(cp, Carrier) + or isinstance(cp, Lha) + ) and self.should_generate_pretense_transports_at(cp): self.generate_pretense_squadron( - offmap_transport_cp, - offmap_transport_cp.coalition, + cp, + cp.coalition, FlightType.AIR_ASSAULT, False, PRETENSE_SQUADRON_DEF_RETRIES, ) - if autogenerate_cargo_plane_squadron: + num_of_cargo_sq_to_generate = ( + PRETENSE_AI_CARGO_PLANES_PER_SIDE + - self.number_of_pretense_cargo_plane_sq_for(cp.coalition.air_wing) + ) + for i in range(num_of_cargo_sq_to_generate): self.generate_pretense_squadron( offmap_transport_cp, offmap_transport_cp.coalition, @@ -688,14 +695,19 @@ class PretenseAircraftGenerator: ato: AirTaskingOrder, dynamic_runways: Dict[str, RunwayData], ) -> None: - for package in reversed(sorted(ato.packages, key=lambda x: x.time_over_target)): - logging.info(f"Generating package for target: {package.target.name}") + for package in ato.packages: + logging.info( + f"Generating package for target: {package.target.name}, has_players: {package.has_players}" + ) if not package.flights: continue for flight in package.flights: self.initialize_pretense_data_structures_for_flight( flight.departure, flight ) + logging.info( + f"Generating flight in {flight.coalition.faction.name} package {flight.squadron.aircraft} {flight.flight_type} for target: {package.target.name}, departure: {flight.from_cp.name}" + ) if flight.alive: if not flight.squadron.location.runway_is_operational(): @@ -712,6 +724,9 @@ class PretenseAircraftGenerator: flight, country, dynamic_runways ) except NoParkingSlotError: + logging.warning( + f"No room on runway or parking slots for {flight.squadron.aircraft} {flight.flight_type} for target: {package.target.name}. Not generating flight." + ) return self.unit_map.add_aircraft(group, flight) diff --git a/game/pretense/pretensemissiongenerator.py b/game/pretense/pretensemissiongenerator.py index 4bb5fe16..8894aaf3 100644 --- a/game/pretense/pretensemissiongenerator.py +++ b/game/pretense/pretensemissiongenerator.py @@ -46,6 +46,7 @@ from .pretensetgogenerator import PretenseTgoGenerator from .pretensetriggergenerator import PretenseTriggerGenerator from game.missiongenerator.visualsgenerator import VisualsGenerator from ..ato import Flight +from ..ato.airtaaskingorder import AirTaskingOrder from ..missiongenerator import MissionGenerator from ..radio.TacanContainer import TacanContainer @@ -209,32 +210,17 @@ class PretenseMissionGenerator(MissionGenerator): for cp in self.game.theater.controlpoints: for country in (self.p_country, self.e_country): - if country == self.p_country: - ato = self.game.blue.ato - else: - ato = self.game.red.ato - print(f"Running generate_flights for {country.name} at {cp.name}") + ato = AirTaskingOrder() aircraft_generator.generate_flights( country, cp, ato, ) - - ato = self.game.blue.ato - country = self.p_country - aircraft_generator.generate_packages( - country, - ato, - tgo_generator.runways, - ) - - ato = self.game.red.ato - country = self.e_country - aircraft_generator.generate_packages( - country, - ato, - tgo_generator.runways, - ) + aircraft_generator.generate_packages( + country, + ato, + tgo_generator.runways, + ) self.mission_data.flights = aircraft_generator.flights From 0bdb989a2c2888b430393e05389813cb5453291b Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Sat, 23 Sep 2023 13:00:44 +0300 Subject: [PATCH 162/243] Aircraft squadrons are now products of the Hangar, instead of the air defence Command Center. --- game/pretense/pretenseluagenerator.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/game/pretense/pretenseluagenerator.py b/game/pretense/pretenseluagenerator.py index 336ec5b1..6732da43 100644 --- a/game/pretense/pretenseluagenerator.py +++ b/game/pretense/pretenseluagenerator.py @@ -109,6 +109,15 @@ class PretenseLuaGenerator(LuaGenerator): + cp_side_str + "' }),\n" ) + lua_string_zones += " }\n" + lua_string_zones += " }),\n" + lua_string_zones += " presets.upgrades.supply.hangar:extend({\n" + lua_string_zones += ( + f" name = '{cp_name_trimmed}-aircraft-command-" + + cp_side_str + + "',\n" + ) + lua_string_zones += " products = {\n" for mission_type in self.game.pretense_air[cp_side][cp_name_trimmed]: if mission_type == FlightType.SEAD: mission_name = "attack.sead" From cc3bef7937ce87cb17048bde153c5d45ade1b890 Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Sat, 23 Sep 2023 14:28:48 +0300 Subject: [PATCH 163/243] Copied flightgroupconfigurator.py as a template/inheritance for generating Pretense campaigns from Retribution campaigns. --- .../pretenseflightgroupconfigurator.py | 300 ++++++++++++++++++ 1 file changed, 300 insertions(+) create mode 100644 game/pretense/pretenseflightgroupconfigurator.py diff --git a/game/pretense/pretenseflightgroupconfigurator.py b/game/pretense/pretenseflightgroupconfigurator.py new file mode 100644 index 00000000..aa781cc8 --- /dev/null +++ b/game/pretense/pretenseflightgroupconfigurator.py @@ -0,0 +1,300 @@ +from __future__ import annotations + +import logging +import random +from datetime import datetime +from typing import Any, Optional, TYPE_CHECKING + +from dcs import Mission +from dcs.action import DoScript +from dcs.flyingunit import FlyingUnit +from dcs.task import OptReactOnThreat +from dcs.translation import String +from dcs.triggers import TriggerStart +from dcs.unit import Skill +from dcs.unitgroup import FlyingGroup + +from game.ato import Flight, FlightType +from game.callsigns import callsign_for_support_unit +from game.data.weapons import Pylon, WeaponType as WeaponTypeEnum +from game.missiongenerator.lasercoderegistry import LaserCodeRegistry +from game.missiongenerator.logisticsgenerator import LogisticsGenerator +from game.missiongenerator.missiondata import MissionData, AwacsInfo, TankerInfo +from game.radio.radios import RadioFrequency, RadioRegistry +from game.radio.tacan import ( + TacanBand, + TacanRegistry, + TacanUsage, + OutOfTacanChannelsError, +) +from game.runways import RunwayData +from game.squadrons import Pilot +from .aircraftbehavior import AircraftBehavior +from .aircraftpainter import AircraftPainter +from .flightdata import FlightData +from .waypoints import WaypointGenerator +from ...ato.flightplans.aewc import AewcFlightPlan +from ...ato.flightplans.packagerefueling import PackageRefuelingFlightPlan +from ...ato.flightplans.theaterrefueling import TheaterRefuelingFlightPlan +from ...theater import Fob + +if TYPE_CHECKING: + from game import Game + + +class FlightGroupConfigurator: + def __init__( + self, + flight: Flight, + group: FlyingGroup[Any], + game: Game, + mission: Mission, + time: datetime, + radio_registry: RadioRegistry, + tacan_registry: TacanRegistry, + laser_code_registry: LaserCodeRegistry, + mission_data: MissionData, + dynamic_runways: dict[str, RunwayData], + use_client: bool, + ) -> None: + self.flight = flight + self.group = group + self.game = game + self.mission = mission + self.time = time + self.radio_registry = radio_registry + self.tacan_registry = tacan_registry + self.laser_code_registry = laser_code_registry + self.mission_data = mission_data + self.dynamic_runways = dynamic_runways + self.use_client = use_client + + def configure(self) -> FlightData: + AircraftBehavior(self.flight.flight_type).apply_to(self.flight, self.group) + AircraftPainter(self.flight, self.group).apply_livery() + self.setup_props() + self.setup_payload() + self.setup_fuel() + flight_channel = self.setup_radios() + + laser_codes: list[Optional[int]] = [] + for unit, pilot in zip(self.group.units, self.flight.roster.pilots): + self.configure_flight_member(unit, pilot, laser_codes) + + divert = None + if self.flight.divert is not None: + divert = self.flight.divert.active_runway( + self.game.theater, self.game.conditions, self.dynamic_runways + ) + + if self.flight.flight_type in [ + FlightType.TRANSPORT, + FlightType.AIR_ASSAULT, + ] and self.game.settings.plugin_option("ctld"): + transfer = None + if self.flight.flight_type == FlightType.TRANSPORT: + coalition = self.game.coalition_for(player=self.flight.blue) + transfer = coalition.transfers.transfer_for_flight(self.flight) + self.mission_data.logistics.append( + LogisticsGenerator( + self.flight, self.group, self.mission, self.game.settings, transfer + ).generate_logistics() + ) + + mission_start_time, waypoints = WaypointGenerator( + self.flight, + self.group, + self.mission, + self.game.conditions.start_time, + self.time, + self.game.settings, + self.mission_data, + ).create_waypoints() + + # Special handling for landing waypoints when: + # 1. It's an AI-only flight + # 2. Aircraft are not helicopters/VTOL + # 3. Landing waypoint does not point to an airfield + if ( + self.flight.client_count < 1 + and not self.flight.unit_type.helicopter + and not self.flight.unit_type.lha_capable + and isinstance(self.flight.squadron.location, Fob) + ): + # Need to set uncontrolled to false, otherwise the AI will skip the mission and just land + self.group.uncontrolled = False + + return FlightData( + package=self.flight.package, + aircraft_type=self.flight.unit_type, + squadron=self.flight.squadron, + flight_type=self.flight.flight_type, + units=self.group.units, + size=len(self.group.units), + friendly=self.flight.from_cp.captured, + departure_delay=mission_start_time, + departure=self.flight.departure.active_runway( + self.game.theater, self.game.conditions, self.dynamic_runways + ), + arrival=self.flight.arrival.active_runway( + self.game.theater, self.game.conditions, self.dynamic_runways + ), + divert=divert, + waypoints=waypoints, + intra_flight_channel=flight_channel, + bingo_fuel=self.flight.flight_plan.bingo_fuel, + joker_fuel=self.flight.flight_plan.joker_fuel, + custom_name=self.flight.custom_name, + laser_codes=laser_codes, + ) + + def configure_flight_member( + self, unit: FlyingUnit, pilot: Optional[Pilot], laser_codes: list[Optional[int]] + ) -> None: + player = pilot is not None and pilot.player + self.set_skill(unit, pilot) + if self.flight.loadout.has_weapon_of_type(WeaponTypeEnum.TGP) and player: + laser_codes.append(self.laser_code_registry.get_next_laser_code()) + else: + laser_codes.append(None) + settings = self.flight.coalition.game.settings + if not player or not settings.plugins.get("ewrj"): + return + jammer_required = settings.plugin_option("ewrj.ecm_required") + if jammer_required: + ecm = WeaponTypeEnum.JAMMER + if not self.flight.loadout.has_weapon_of_type(ecm): + return + ewrj_menu_trigger = TriggerStart(comment=f"EWRJ-{unit.name}") + ewrj_menu_trigger.add_action(DoScript(String(f'EWJamming("{unit.name}")'))) + self.mission.triggerrules.triggers.append(ewrj_menu_trigger) + self.group.points[0].tasks[0] = OptReactOnThreat( + OptReactOnThreat.Values.PassiveDefense + ) + + def setup_radios(self) -> RadioFrequency: + freq = self.flight.frequency + if freq is None and (freq := self.flight.package.frequency) is None: + freq = self.radio_registry.alloc_uhf() + self.flight.package.frequency = freq + if freq not in self.radio_registry.allocated_channels: + self.radio_registry.reserve(freq) + + if self.flight.flight_type in {FlightType.AEWC, FlightType.REFUELING}: + self.register_air_support(freq) + elif self.flight.client_count and self.flight.squadron.radio_presets: + freq = self.flight.squadron.radio_presets["intra_flight"][0] + elif self.flight.frequency is None and self.flight.client_count: + freq = self.flight.unit_type.alloc_flight_radio(self.radio_registry) + + self.group.set_frequency(freq.mhz) + return freq + + def register_air_support(self, channel: RadioFrequency) -> None: + callsign = callsign_for_support_unit(self.group) + if isinstance(self.flight.flight_plan, AewcFlightPlan): + self.mission_data.awacs.append( + AwacsInfo( + group_name=str(self.group.name), + callsign=callsign, + freq=channel, + depature_location=self.flight.departure.name, + start_time=self.flight.flight_plan.patrol_start_time, + end_time=self.flight.flight_plan.patrol_end_time, + blue=self.flight.departure.captured, + ) + ) + elif isinstance( + self.flight.flight_plan, TheaterRefuelingFlightPlan + ) or isinstance(self.flight.flight_plan, PackageRefuelingFlightPlan): + tacan = self.flight.tacan + if tacan is None and self.flight.squadron.aircraft.dcs_unit_type.tacan: + try: + tacan = self.tacan_registry.alloc_for_band( + TacanBand.Y, TacanUsage.AirToAir + ) + except OutOfTacanChannelsError: + tacan = random.choice(list(self.tacan_registry.allocated_channels)) + else: + tacan = self.flight.tacan + self.mission_data.tankers.append( + TankerInfo( + group_name=str(self.group.name), + callsign=callsign, + variant=self.flight.unit_type.name, + freq=channel, + tacan=tacan, + start_time=self.flight.flight_plan.patrol_start_time, + end_time=self.flight.flight_plan.patrol_end_time, + blue=self.flight.departure.captured, + ) + ) + + def set_skill(self, unit: FlyingUnit, pilot: Optional[Pilot]) -> None: + if pilot is None or not pilot.player: + unit.skill = self.skill_level_for(unit, pilot) + return + + if self.use_client or "Pilot #1" not in unit.name: + unit.set_client() + else: + unit.set_player() + + def skill_level_for(self, unit: FlyingUnit, pilot: Optional[Pilot]) -> Skill: + if self.flight.squadron.player: + base_skill = Skill(self.game.settings.player_skill) + else: + base_skill = Skill(self.game.settings.enemy_skill) + + if pilot is None: + logging.error(f"Cannot determine skill level: {unit.name} has not pilot") + return base_skill + + levels = [ + Skill.Average, + Skill.Good, + Skill.High, + Skill.Excellent, + ] + current_level = levels.index(base_skill) + missions_for_skill_increase = 4 + increase = pilot.record.missions_flown // missions_for_skill_increase + capped_increase = min(current_level + increase, len(levels) - 1) + + if self.game.settings.ai_pilot_levelling: + new_level = capped_increase + else: + new_level = current_level + + return levels[new_level] + + def setup_props(self) -> None: + for prop_id, value in self.flight.props.items(): + for unit in self.group.units: + unit.set_property(prop_id, value) + + def setup_payload(self) -> None: + for p in self.group.units: + p.pylons.clear() + + loadout = self.flight.loadout + if self.game.settings.restrict_weapons_by_date: + loadout = loadout.degrade_for_date(self.flight.unit_type, self.game.date) + + for pylon_number, weapon in loadout.pylons.items(): + if weapon is None: + continue + pylon = Pylon.for_aircraft(self.flight.unit_type, pylon_number) + pylon.equip(self.group, weapon) + + def setup_fuel(self) -> None: + fuel = self.flight.state.estimate_fuel() + if fuel < 0: + logging.warning( + f"Flight {self.flight} is estimated to have no fuel at mission start. " + "This estimate does not account for external fuel tanks. Setting " + "starting fuel to 100kg." + ) + fuel = 100 + for unit, pilot in zip(self.group.units, self.flight.roster.pilots): + unit.fuel = fuel From c6a8673b5e465084b4e7e5d64b3a64036097d589 Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Sat, 23 Sep 2023 16:17:36 +0300 Subject: [PATCH 164/243] Added missing generation of AI BAI flights. Player flight groups are now generated as single-ship groups to support the Pretense missions for all players. Moved repetitive code in PretenseFlightGroupSpawner to insert_into_pretense() method. Disabled spawning of air assault statics by PretenseFlightGroupConfigurator since they are unnecessary in Pretense. --- game/pretense/pretenseaircraftgenerator.py | 216 +++++++++--------- .../pretenseflightgroupconfigurator.py | 214 +++-------------- game/pretense/pretenseflightgroupspawner.py | 72 +++--- 3 files changed, 156 insertions(+), 346 deletions(-) diff --git a/game/pretense/pretenseaircraftgenerator.py b/game/pretense/pretenseaircraftgenerator.py index 89a078f2..5157e3c6 100644 --- a/game/pretense/pretenseaircraftgenerator.py +++ b/game/pretense/pretenseaircraftgenerator.py @@ -22,11 +22,11 @@ from game.coalition import Coalition from game.data.weapons import WeaponType from game.dcs.aircrafttype import AircraftType from game.missiongenerator.aircraft.flightdata import FlightData -from game.missiongenerator.aircraft.flightgroupconfigurator import ( - FlightGroupConfigurator, -) from game.missiongenerator.lasercoderegistry import LaserCodeRegistry from game.missiongenerator.missiondata import MissionData +from game.pretense.pretenseflightgroupconfigurator import ( + PretenseFlightGroupConfigurator, +) from game.radio.radios import RadioRegistry from game.radio.tacan import TacanRegistry from game.runways import RunwayData @@ -51,13 +51,15 @@ if TYPE_CHECKING: PRETENSE_SQUADRON_DEF_RETRIES = 100 PRETENSE_SEAD_FLIGHTS_PER_CP = 2 PRETENSE_CAS_FLIGHTS_PER_CP = 2 +PRETENSE_BAI_FLIGHTS_PER_CP = 2 PRETENSE_STRIKE_FLIGHTS_PER_CP = 2 PRETENSE_BARCAP_FLIGHTS_PER_CP = 2 PRETENSE_AI_AIRCRAFT_PER_FLIGHT = 2 PRETENSE_AI_AWACS_PER_FLIGHT = 1 PRETENSE_AI_TANKERS_PER_FLIGHT = 1 PRETENSE_AI_CARGO_PLANES_PER_SIDE = 8 -PRETENSE_PLAYER_AIRCRAFT_PER_FLIGHT = 2 +PRETENSE_PLAYER_AIRCRAFT_PER_FLIGHT = 1 +PRETENSE_PLAYER_FLIGHTS_PER_TYPE = 2 class PretenseAircraftGenerator: @@ -287,6 +289,7 @@ class PretenseAircraftGenerator: """ num_of_sead = 0 num_of_cas = 0 + num_of_bai = 0 num_of_strike = 0 num_of_cap = 0 @@ -299,6 +302,7 @@ class PretenseAircraftGenerator: squadron.owned_aircraft += PRETENSE_AI_AIRCRAFT_PER_FLIGHT squadron.untasked_aircraft += PRETENSE_AI_AIRCRAFT_PER_FLIGHT + squadron.populate_for_turn_0(False) package = Package(cp, squadron.flight_db, auto_asap=False) mission_types = squadron.auto_assignable_mission_types aircraft_per_flight = 1 @@ -328,11 +332,17 @@ class PretenseAircraftGenerator: num_of_sead += 1 aircraft_per_flight = PRETENSE_AI_AIRCRAFT_PER_FLIGHT elif ( - FlightType.CAS in mission_types or FlightType.BAI in mission_types + FlightType.CAS in mission_types ) and num_of_cas < PRETENSE_CAS_FLIGHTS_PER_CP: flight_type = FlightType.CAS num_of_cas += 1 aircraft_per_flight = PRETENSE_AI_AIRCRAFT_PER_FLIGHT + elif ( + FlightType.BAI in mission_types + ) and num_of_bai < PRETENSE_BAI_FLIGHTS_PER_CP: + flight_type = FlightType.BAI + num_of_bai += 1 + aircraft_per_flight = PRETENSE_AI_AIRCRAFT_PER_FLIGHT elif ( FlightType.STRIKE in mission_types or FlightType.OCA_RUNWAY in mission_types @@ -421,6 +431,7 @@ class PretenseAircraftGenerator: if squadron is not None: squadron.owned_aircraft += PRETENSE_AI_AIRCRAFT_PER_FLIGHT squadron.untasked_aircraft += PRETENSE_AI_AIRCRAFT_PER_FLIGHT + squadron.populate_for_turn_0(False) package = Package(cp, squadron.flight_db, auto_asap=False) flight = Flight( package, @@ -461,6 +472,7 @@ class PretenseAircraftGenerator: if squadron is not None: squadron.owned_aircraft += PRETENSE_AI_AIRCRAFT_PER_FLIGHT squadron.untasked_aircraft += PRETENSE_AI_AIRCRAFT_PER_FLIGHT + squadron.populate_for_turn_0(False) package = Package(cp, squadron.flight_db, auto_asap=False) flight = Flight( package, @@ -501,6 +513,7 @@ class PretenseAircraftGenerator: if squadron is not None: squadron.owned_aircraft += PRETENSE_AI_AIRCRAFT_PER_FLIGHT squadron.untasked_aircraft += PRETENSE_AI_AIRCRAFT_PER_FLIGHT + squadron.populate_for_turn_0(False) package = Package(cp, squadron.flight_db, auto_asap=False) flight = Flight( package, @@ -548,38 +561,39 @@ class PretenseAircraftGenerator: if not cp.can_operate(aircraft_type): continue - squadron = self.generate_pretense_squadron_for( - aircraft_type, - cp, - coalition, - ) - if squadron is not None: - squadron.owned_aircraft += PRETENSE_PLAYER_AIRCRAFT_PER_FLIGHT - squadron.untasked_aircraft += PRETENSE_PLAYER_AIRCRAFT_PER_FLIGHT - squadron.populate_for_turn_0(False) - for pilot in squadron.pilot_pool: - pilot.player = True - package = Package(cp, squadron.flight_db, auto_asap=False) - flight = Flight( - package, - squadron, - aircraft_per_flight, - squadron.primary_task, - StartType.COLD, - divert=cp, - ) - for roster_pilot in flight.roster.pilots: - if roster_pilot is not None: - roster_pilot.player = True - print( - f"Generated flight for {squadron.primary_task} flying {squadron.aircraft.name} at {squadron.location.name}. Pilot client count: {flight.client_count}" + for i in range(PRETENSE_PLAYER_FLIGHTS_PER_TYPE): + squadron = self.generate_pretense_squadron_for( + aircraft_type, + cp, + coalition, ) + if squadron is not None: + squadron.owned_aircraft += PRETENSE_PLAYER_AIRCRAFT_PER_FLIGHT + squadron.untasked_aircraft += PRETENSE_PLAYER_AIRCRAFT_PER_FLIGHT + squadron.populate_for_turn_0(False) + for pilot in squadron.pilot_pool: + pilot.player = True + package = Package(cp, squadron.flight_db, auto_asap=False) + flight = Flight( + package, + squadron, + aircraft_per_flight, + squadron.primary_task, + StartType.COLD, + divert=cp, + ) + for roster_pilot in flight.roster.pilots: + if roster_pilot is not None: + roster_pilot.player = True + print( + f"Generated flight for {squadron.primary_task} flying {squadron.aircraft.name} at {squadron.location.name}. Pilot client count: {flight.client_count}" + ) - package.add_flight(flight) - flight.state = WaitingForStart( - flight, self.game.settings, self.game.conditions.start_time - ) - ato.add_package(package) + package.add_flight(flight) + flight.state = WaitingForStart( + flight, self.game.settings, self.game.conditions.start_time + ) + ato.add_package(package) return @@ -747,86 +761,62 @@ class PretenseAircraftGenerator: self.ground_spawns, self.mission_data, ).create_flight_group() - if flight.flight_type in [ - FlightType.REFUELING, - FlightType.AEWC, - ]: - self.flights.append( - FlightGroupConfigurator( - flight, - group, - self.game, - self.mission, - self.time, - self.radio_registry, - self.tacan_registy, - self.laser_code_registry, - self.mission_data, - dynamic_runways, - self.use_client, - ).configure() - ) - else: - if flight.client_count > 0: - if flight.flight_type == FlightType.CAS: - for conflict in self.game.theater.conflicts(): - flight.package.target = conflict - break - elif ( - flight.flight_type == FlightType.STRIKE - or flight.flight_type == FlightType.BAI - ): - for cp in self.game.theater.closest_opposing_control_points(): - if cp.coalition == flight.coalition: - continue - for mission_target in cp.ground_objects: - flight.package.target = mission_target - elif ( - flight.flight_type == FlightType.OCA_RUNWAY - or flight.flight_type == FlightType.OCA_AIRCRAFT - ): - for cp in self.game.theater.controlpoints: - if cp.coalition == flight.coalition or not isinstance( - cp, Airfield - ): - continue - flight.package.target = cp - elif flight.flight_type == FlightType.DEAD: - for cp in self.game.theater.controlpoints: - if cp.coalition == flight.coalition: - continue - for ground_object in cp.ground_objects: - is_ewr = isinstance(ground_object, EwrGroundObject) - is_sam = isinstance(ground_object, SamGroundObject) + if flight.flight_type == FlightType.CAS: + for conflict in self.game.theater.conflicts(): + flight.package.target = conflict + break + elif ( + flight.flight_type == FlightType.STRIKE + or flight.flight_type == FlightType.BAI + ): + for cp in self.game.theater.closest_opposing_control_points(): + if cp.coalition == flight.coalition: + continue + for mission_target in cp.ground_objects: + flight.package.target = mission_target + elif ( + flight.flight_type == FlightType.OCA_RUNWAY + or flight.flight_type == FlightType.OCA_AIRCRAFT + ): + for cp in self.game.theater.controlpoints: + if cp.coalition == flight.coalition or not isinstance(cp, Airfield): + continue + flight.package.target = cp + elif flight.flight_type == FlightType.DEAD: + for cp in self.game.theater.controlpoints: + if cp.coalition == flight.coalition: + continue + for ground_object in cp.ground_objects: + is_ewr = isinstance(ground_object, EwrGroundObject) + is_sam = isinstance(ground_object, SamGroundObject) - if is_ewr or is_sam: - flight.package.target = ground_object - elif flight.flight_type == FlightType.AIR_ASSAULT: - for cp in self.game.theater.closest_opposing_control_points(): - if cp.coalition == flight.coalition: - continue - if flight.is_hercules: - if cp.coalition == flight.coalition or not isinstance( - cp, Airfield - ): - continue - flight.package.target = cp + if is_ewr or is_sam: + flight.package.target = ground_object + elif flight.flight_type == FlightType.AIR_ASSAULT: + for cp in self.game.theater.closest_opposing_control_points(): + if cp.coalition == flight.coalition: + continue + if flight.is_hercules: + if cp.coalition == flight.coalition or not isinstance(cp, Airfield): + continue + flight.package.target = cp - FlightGroupConfigurator( - flight, - group, - self.game, - self.mission, - self.time, - self.radio_registry, - self.tacan_registy, - self.laser_code_registry, - self.mission_data, - dynamic_runways, - self.use_client, - ).configure() - # for unit in group.units: - # unit.set_client() + logging.info( + f"Configuring flight {group.name} {flight.squadron.aircraft} {flight.flight_type}, number of players: {flight.client_count}" + ) + PretenseFlightGroupConfigurator( + flight, + group, + self.game, + self.mission, + self.time, + self.radio_registry, + self.tacan_registy, + self.laser_code_registry, + self.mission_data, + dynamic_runways, + self.use_client, + ).configure() if self.ewrj: self._track_ewrj_flight(flight, group) diff --git a/game/pretense/pretenseflightgroupconfigurator.py b/game/pretense/pretenseflightgroupconfigurator.py index aa781cc8..a0f4616c 100644 --- a/game/pretense/pretenseflightgroupconfigurator.py +++ b/game/pretense/pretenseflightgroupconfigurator.py @@ -1,48 +1,37 @@ from __future__ import annotations import logging -import random from datetime import datetime from typing import Any, Optional, TYPE_CHECKING from dcs import Mission -from dcs.action import DoScript from dcs.flyingunit import FlyingUnit -from dcs.task import OptReactOnThreat -from dcs.translation import String -from dcs.triggers import TriggerStart from dcs.unit import Skill from dcs.unitgroup import FlyingGroup -from game.ato import Flight, FlightType -from game.callsigns import callsign_for_support_unit -from game.data.weapons import Pylon, WeaponType as WeaponTypeEnum +from game.ato import Flight +from game.missiongenerator.aircraft.flightgroupconfigurator import ( + FlightGroupConfigurator, +) from game.missiongenerator.lasercoderegistry import LaserCodeRegistry -from game.missiongenerator.logisticsgenerator import LogisticsGenerator -from game.missiongenerator.missiondata import MissionData, AwacsInfo, TankerInfo -from game.radio.radios import RadioFrequency, RadioRegistry +from game.missiongenerator.missiondata import MissionData +from game.radio.radios import RadioRegistry from game.radio.tacan import ( - TacanBand, TacanRegistry, - TacanUsage, - OutOfTacanChannelsError, ) from game.runways import RunwayData from game.squadrons import Pilot -from .aircraftbehavior import AircraftBehavior -from .aircraftpainter import AircraftPainter -from .flightdata import FlightData -from .waypoints import WaypointGenerator -from ...ato.flightplans.aewc import AewcFlightPlan -from ...ato.flightplans.packagerefueling import PackageRefuelingFlightPlan -from ...ato.flightplans.theaterrefueling import TheaterRefuelingFlightPlan -from ...theater import Fob +from game.missiongenerator.aircraft.aircraftbehavior import AircraftBehavior +from game.missiongenerator.aircraft.aircraftpainter import AircraftPainter +from game.missiongenerator.aircraft.flightdata import FlightData +from game.missiongenerator.aircraft.waypoints import WaypointGenerator +from game.theater import Fob if TYPE_CHECKING: from game import Game -class FlightGroupConfigurator: +class PretenseFlightGroupConfigurator(FlightGroupConfigurator): def __init__( self, flight: Flight, @@ -57,6 +46,20 @@ class FlightGroupConfigurator: dynamic_runways: dict[str, RunwayData], use_client: bool, ) -> None: + super().__init__( + flight, + group, + game, + mission, + time, + radio_registry, + tacan_registry, + laser_code_registry, + mission_data, + dynamic_runways, + use_client, + ) + self.flight = flight self.group = group self.game = game @@ -87,20 +90,6 @@ class FlightGroupConfigurator: self.game.theater, self.game.conditions, self.dynamic_runways ) - if self.flight.flight_type in [ - FlightType.TRANSPORT, - FlightType.AIR_ASSAULT, - ] and self.game.settings.plugin_option("ctld"): - transfer = None - if self.flight.flight_type == FlightType.TRANSPORT: - coalition = self.game.coalition_for(player=self.flight.blue) - transfer = coalition.transfers.transfer_for_flight(self.flight) - self.mission_data.logistics.append( - LogisticsGenerator( - self.flight, self.group, self.mission, self.game.settings, transfer - ).generate_logistics() - ) - mission_start_time, waypoints = WaypointGenerator( self.flight, self.group, @@ -147,154 +136,3 @@ class FlightGroupConfigurator: custom_name=self.flight.custom_name, laser_codes=laser_codes, ) - - def configure_flight_member( - self, unit: FlyingUnit, pilot: Optional[Pilot], laser_codes: list[Optional[int]] - ) -> None: - player = pilot is not None and pilot.player - self.set_skill(unit, pilot) - if self.flight.loadout.has_weapon_of_type(WeaponTypeEnum.TGP) and player: - laser_codes.append(self.laser_code_registry.get_next_laser_code()) - else: - laser_codes.append(None) - settings = self.flight.coalition.game.settings - if not player or not settings.plugins.get("ewrj"): - return - jammer_required = settings.plugin_option("ewrj.ecm_required") - if jammer_required: - ecm = WeaponTypeEnum.JAMMER - if not self.flight.loadout.has_weapon_of_type(ecm): - return - ewrj_menu_trigger = TriggerStart(comment=f"EWRJ-{unit.name}") - ewrj_menu_trigger.add_action(DoScript(String(f'EWJamming("{unit.name}")'))) - self.mission.triggerrules.triggers.append(ewrj_menu_trigger) - self.group.points[0].tasks[0] = OptReactOnThreat( - OptReactOnThreat.Values.PassiveDefense - ) - - def setup_radios(self) -> RadioFrequency: - freq = self.flight.frequency - if freq is None and (freq := self.flight.package.frequency) is None: - freq = self.radio_registry.alloc_uhf() - self.flight.package.frequency = freq - if freq not in self.radio_registry.allocated_channels: - self.radio_registry.reserve(freq) - - if self.flight.flight_type in {FlightType.AEWC, FlightType.REFUELING}: - self.register_air_support(freq) - elif self.flight.client_count and self.flight.squadron.radio_presets: - freq = self.flight.squadron.radio_presets["intra_flight"][0] - elif self.flight.frequency is None and self.flight.client_count: - freq = self.flight.unit_type.alloc_flight_radio(self.radio_registry) - - self.group.set_frequency(freq.mhz) - return freq - - def register_air_support(self, channel: RadioFrequency) -> None: - callsign = callsign_for_support_unit(self.group) - if isinstance(self.flight.flight_plan, AewcFlightPlan): - self.mission_data.awacs.append( - AwacsInfo( - group_name=str(self.group.name), - callsign=callsign, - freq=channel, - depature_location=self.flight.departure.name, - start_time=self.flight.flight_plan.patrol_start_time, - end_time=self.flight.flight_plan.patrol_end_time, - blue=self.flight.departure.captured, - ) - ) - elif isinstance( - self.flight.flight_plan, TheaterRefuelingFlightPlan - ) or isinstance(self.flight.flight_plan, PackageRefuelingFlightPlan): - tacan = self.flight.tacan - if tacan is None and self.flight.squadron.aircraft.dcs_unit_type.tacan: - try: - tacan = self.tacan_registry.alloc_for_band( - TacanBand.Y, TacanUsage.AirToAir - ) - except OutOfTacanChannelsError: - tacan = random.choice(list(self.tacan_registry.allocated_channels)) - else: - tacan = self.flight.tacan - self.mission_data.tankers.append( - TankerInfo( - group_name=str(self.group.name), - callsign=callsign, - variant=self.flight.unit_type.name, - freq=channel, - tacan=tacan, - start_time=self.flight.flight_plan.patrol_start_time, - end_time=self.flight.flight_plan.patrol_end_time, - blue=self.flight.departure.captured, - ) - ) - - def set_skill(self, unit: FlyingUnit, pilot: Optional[Pilot]) -> None: - if pilot is None or not pilot.player: - unit.skill = self.skill_level_for(unit, pilot) - return - - if self.use_client or "Pilot #1" not in unit.name: - unit.set_client() - else: - unit.set_player() - - def skill_level_for(self, unit: FlyingUnit, pilot: Optional[Pilot]) -> Skill: - if self.flight.squadron.player: - base_skill = Skill(self.game.settings.player_skill) - else: - base_skill = Skill(self.game.settings.enemy_skill) - - if pilot is None: - logging.error(f"Cannot determine skill level: {unit.name} has not pilot") - return base_skill - - levels = [ - Skill.Average, - Skill.Good, - Skill.High, - Skill.Excellent, - ] - current_level = levels.index(base_skill) - missions_for_skill_increase = 4 - increase = pilot.record.missions_flown // missions_for_skill_increase - capped_increase = min(current_level + increase, len(levels) - 1) - - if self.game.settings.ai_pilot_levelling: - new_level = capped_increase - else: - new_level = current_level - - return levels[new_level] - - def setup_props(self) -> None: - for prop_id, value in self.flight.props.items(): - for unit in self.group.units: - unit.set_property(prop_id, value) - - def setup_payload(self) -> None: - for p in self.group.units: - p.pylons.clear() - - loadout = self.flight.loadout - if self.game.settings.restrict_weapons_by_date: - loadout = loadout.degrade_for_date(self.flight.unit_type, self.game.date) - - for pylon_number, weapon in loadout.pylons.items(): - if weapon is None: - continue - pylon = Pylon.for_aircraft(self.flight.unit_type, pylon_number) - pylon.equip(self.group, weapon) - - def setup_fuel(self) -> None: - fuel = self.flight.state.estimate_fuel() - if fuel < 0: - logging.warning( - f"Flight {self.flight} is estimated to have no fuel at mission start. " - "This estimate does not account for external fuel tanks. Setting " - "starting fuel to 100kg." - ) - fuel = 100 - for unit, pilot in zip(self.group.units, self.flight.roster.pilots): - unit.fuel = fuel diff --git a/game/pretense/pretenseflightgroupspawner.py b/game/pretense/pretenseflightgroupspawner.py index 9ee1a6fb..4ddbb6e8 100644 --- a/game/pretense/pretenseflightgroupspawner.py +++ b/game/pretense/pretenseflightgroupspawner.py @@ -68,6 +68,22 @@ class PretenseFlightGroupSpawner(FlightGroupSpawner): self.ground_spawns = ground_spawns self.mission_data = mission_data + def insert_into_pretense(self, name: str) -> None: + cp = self.flight.departure + is_player = True + cp_side = ( + 2 + if self.flight.coalition + == self.flight.coalition.game.coalition_for(is_player) + else 1 + ) + cp_name_trimmed = "".join([i for i in cp.name.lower() if i.isalnum()]) + + if self.flight.client_count == 0: + self.flight.coalition.game.pretense_air[cp_side][cp_name_trimmed][ + self.flight.flight_type + ].append(name) + def generate_flight_at_departure(self) -> FlyingGroup[Any]: cp = self.flight.departure name = namegen.next_pretense_aircraft_name(cp, self.flight) @@ -82,10 +98,7 @@ class PretenseFlightGroupSpawner(FlightGroupSpawner): try: if self.start_type is StartType.IN_FLIGHT: - if self.flight.client_count == 0: - self.flight.coalition.game.pretense_air[cp_side][cp_name_trimmed][ - self.flight.flight_type - ].append(name) + self.insert_into_pretense(name) group = self._generate_over_departure(name, cp) return group elif isinstance(cp, NavalControlPoint): @@ -96,10 +109,7 @@ class PretenseFlightGroupSpawner(FlightGroupSpawner): f"Carrier group {carrier_group} is a " f"{carrier_group.__class__.__name__}, expected a ShipGroup" ) - if self.flight.client_count == 0: - self.flight.coalition.game.pretense_air[cp_side][cp_name_trimmed][ - self.flight.flight_type - ].append(name) + self.insert_into_pretense(name) return self._generate_at_group(name, carrier_group) elif isinstance(cp, Fob): is_heli = self.flight.squadron.aircraft.helicopter @@ -120,33 +130,21 @@ class PretenseFlightGroupSpawner(FlightGroupSpawner): f' spawns" setting is currently disabled.' ) if cp.has_helipads and (is_heli or is_vtol): - if self.flight.client_count == 0: - self.flight.coalition.game.pretense_air[cp_side][ - cp_name_trimmed - ][self.flight.flight_type].append(name) + self.insert_into_pretense(name) pad_group = self._generate_at_cp_helipad(name, cp) if pad_group is not None: return pad_group if cp.has_ground_spawns and (self.flight.client_count > 0 or is_heli): - if self.flight.client_count == 0: - self.flight.coalition.game.pretense_air[cp_side][ - cp_name_trimmed - ][self.flight.flight_type].append(name) + self.insert_into_pretense(name) pad_group = self._generate_at_cp_ground_spawn(name, cp) if pad_group is not None: return pad_group - if self.flight.client_count == 0: - self.flight.coalition.game.pretense_air[cp_side][cp_name_trimmed][ - self.flight.flight_type - ].append(name) + self.insert_into_pretense(name) return self._generate_over_departure(name, cp) elif isinstance(cp, Airfield): is_heli = self.flight.squadron.aircraft.helicopter if cp.has_helipads and is_heli: - if self.flight.client_count == 0: - self.flight.coalition.game.pretense_air[cp_side][ - cp_name_trimmed - ][self.flight.flight_type].append(name) + self.insert_into_pretense(name) pad_group = self._generate_at_cp_helipad(name, cp) if pad_group is not None: return pad_group @@ -157,17 +155,11 @@ class PretenseFlightGroupSpawner(FlightGroupSpawner): >= self.flight.count and (self.flight.client_count > 0 or is_heli) ): - if self.flight.client_count == 0: - self.flight.coalition.game.pretense_air[cp_side][ - cp_name_trimmed - ][self.flight.flight_type].append(name) + self.insert_into_pretense(name) pad_group = self._generate_at_cp_ground_spawn(name, cp) if pad_group is not None: return pad_group - if self.flight.client_count == 0: - self.flight.coalition.game.pretense_air[cp_side][cp_name_trimmed][ - self.flight.flight_type - ].append(name) + self.insert_into_pretense(name) return self._generate_at_airfield(name, cp) else: raise NotImplementedError( @@ -186,22 +178,14 @@ class PretenseFlightGroupSpawner(FlightGroupSpawner): "No room on runway or parking slots. Starting from the air." ) self.flight.start_type = StartType.IN_FLIGHT + self.insert_into_pretense(name) group = self._generate_over_departure(name, cp) return group def generate_mid_mission(self) -> FlyingGroup[Any]: assert isinstance(self.flight.state, InFlight) name = namegen.next_pretense_aircraft_name(self.flight.departure, self.flight) - is_player = True - cp_side = ( - 2 - if self.flight.coalition - == self.flight.coalition.game.coalition_for(is_player) - else 1 - ) - cp_name_trimmed = "".join( - [i for i in self.flight.departure.name.lower() if i.isalnum()] - ) + speed = self.flight.state.estimate_speed() pos = self.flight.state.estimate_position() pos += Vector2(random.randint(100, 1000), random.randint(100, 1000)) @@ -223,9 +207,7 @@ class PretenseFlightGroupSpawner(FlightGroupSpawner): alt = self.mission_data.cp_stack[cp] self.mission_data.cp_stack[cp] += STACK_SEPARATION - self.flight.coalition.game.pretense_air[cp_side][cp_name_trimmed][ - self.flight.flight_type - ].append(name) + self.insert_into_pretense(name) group = self.mission.flight_group( country=self.country, name=name, From d870099f414eba8761352529c2e69758b836bc4f Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Sat, 23 Sep 2023 16:20:49 +0300 Subject: [PATCH 165/243] Replaced the air defence Command Center with a bunker for SHORADs. Will use the Command Center in the future for medium/long range SAMs. --- game/pretense/pretenseluagenerator.py | 2 +- resources/plugins/pretense/init_header.lua | 6 ++++++ resources/plugins/pretense/pretense_compiled.lua | 2 ++ 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/game/pretense/pretenseluagenerator.py b/game/pretense/pretenseluagenerator.py index 6732da43..3e616357 100644 --- a/game/pretense/pretenseluagenerator.py +++ b/game/pretense/pretenseluagenerator.py @@ -93,7 +93,7 @@ class PretenseLuaGenerator(LuaGenerator): ) lua_string_zones += " }\n" lua_string_zones += " }),\n" - lua_string_zones += " presets.upgrades.airdef.comCenter:extend({\n" + lua_string_zones += " presets.upgrades.airdef.bunker:extend({\n" lua_string_zones += ( f" name = '{cp_name_trimmed}-mission-command-" + cp_side_str diff --git a/resources/plugins/pretense/init_header.lua b/resources/plugins/pretense/init_header.lua index 850bfbc7..12f84597 100644 --- a/resources/plugins/pretense/init_header.lua +++ b/resources/plugins/pretense/init_header.lua @@ -464,6 +464,12 @@ presets = { }) }, airdef = { + bunker = Preset:new({ + display = 'Bunker', + cost = 1500, + type = 'upgrade', + template = "bunker-1" + }), comCenter = Preset:new({ display = 'Command Center', cost = 2500, diff --git a/resources/plugins/pretense/pretense_compiled.lua b/resources/plugins/pretense/pretense_compiled.lua index 0a8d7be1..11a3a8b1 100644 --- a/resources/plugins/pretense/pretense_compiled.lua +++ b/resources/plugins/pretense/pretense_compiled.lua @@ -7524,6 +7524,8 @@ do TemplateDB.templates["tv-tower"] = { type="TV tower", category="Fortifications", shape="tele_bash", dataCategory=TemplateDB.type.static } + TemplateDB.templates["bunker-1"] = { type="Sandbox", category="Fortifications", dataCategory=TemplateDB.type.static } + TemplateDB.templates["command-center"] = { type=".Command Center", category="Fortifications", shape="ComCenter", dataCategory=TemplateDB.type.static } TemplateDB.templates["military-staff"] = { type="Military staff", category="Fortifications", shape="aviashtab", dataCategory=TemplateDB.type.static } From dd0217a220d1c2bd27085fe980b8df1e3ad06e16 Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Sat, 23 Sep 2023 18:15:11 +0300 Subject: [PATCH 166/243] Pretense SEAD flights will use the SEAD Sweep loadout, when available. --- .../pretenseflightgroupconfigurator.py | 34 ++++++++++++------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/game/pretense/pretenseflightgroupconfigurator.py b/game/pretense/pretenseflightgroupconfigurator.py index a0f4616c..b6a6c7a6 100644 --- a/game/pretense/pretenseflightgroupconfigurator.py +++ b/game/pretense/pretenseflightgroupconfigurator.py @@ -9,7 +9,8 @@ from dcs.flyingunit import FlyingUnit from dcs.unit import Skill from dcs.unitgroup import FlyingGroup -from game.ato import Flight +from game.ato import Flight, FlightType +from game.data.weapons import Pylon from game.missiongenerator.aircraft.flightgroupconfigurator import ( FlightGroupConfigurator, ) @@ -100,18 +101,7 @@ class PretenseFlightGroupConfigurator(FlightGroupConfigurator): self.mission_data, ).create_waypoints() - # Special handling for landing waypoints when: - # 1. It's an AI-only flight - # 2. Aircraft are not helicopters/VTOL - # 3. Landing waypoint does not point to an airfield - if ( - self.flight.client_count < 1 - and not self.flight.unit_type.helicopter - and not self.flight.unit_type.lha_capable - and isinstance(self.flight.squadron.location, Fob) - ): - # Need to set uncontrolled to false, otherwise the AI will skip the mission and just land - self.group.uncontrolled = False + self.group.uncontrolled = False return FlightData( package=self.flight.package, @@ -136,3 +126,21 @@ class PretenseFlightGroupConfigurator(FlightGroupConfigurator): custom_name=self.flight.custom_name, laser_codes=laser_codes, ) + + def setup_payload(self) -> None: + for p in self.group.units: + p.pylons.clear() + + if self.flight.flight_type == FlightType.SEAD: + self.flight.loadout = self.flight.loadout.default_for_task_and_aircraft( + FlightType.SEAD_SWEEP, self.flight.unit_type.dcs_unit_type + ) + loadout = self.flight.loadout + if self.game.settings.restrict_weapons_by_date: + loadout = loadout.degrade_for_date(self.flight.unit_type, self.game.date) + + for pylon_number, weapon in loadout.pylons.items(): + if weapon is None: + continue + pylon = Pylon.for_aircraft(self.flight.unit_type, pylon_number) + pylon.equip(self.group, weapon) From 11a0713e506227e7e52b2d7547b82e598a06295a Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Sun, 24 Sep 2023 02:05:18 +0300 Subject: [PATCH 167/243] =?UTF-8?q?Pretense=20SEAD=20missions=20will=20now?= =?UTF-8?q?=20also=20target=20AAA.=20Also=20incorporates=20the=20fix=20to?= =?UTF-8?q?=20fullBuild():=20Dzsekeb=20=E2=80=94=2003/09/2023=2011:50=20ad?= =?UTF-8?q?d=20the=20highlighted=20line=20to=20the=20fullbuild=20function?= =?UTF-8?q?=20https://discord.com/channels/959044877470027848/103145972131?= =?UTF-8?q?3517578/1147815809075392604?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- resources/plugins/pretense/pretense_compiled.lua | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/resources/plugins/pretense/pretense_compiled.lua b/resources/plugins/pretense/pretense_compiled.lua index 11a3a8b1..a3aaf78c 100644 --- a/resources/plugins/pretense/pretense_compiled.lua +++ b/resources/plugins/pretense/pretense_compiled.lua @@ -1221,7 +1221,7 @@ do if v.type == 'defense' and v.side ~= group:getCoalition() then local gr = Group.getByName(v.name) for _,unit in ipairs(gr:getUnits()) do - if unit:hasAttribute('SAM SR') or unit:hasAttribute('SAM TR') then + if unit:hasAttribute('SAM SR') or unit:hasAttribute('SAM TR') or unit:hasAttribute('AAA') or unit:hasAttribute('IR Guided SAM') or unit:hasAttribute('SAM LL') then table.insert(viable, unit:getName()) end end @@ -1235,7 +1235,7 @@ do { id = 'EngageTargets', params = { - targetTypes = {'SAM SR', 'SAM TR'} + targetTypes = {'SAM SR', 'SAM TR', 'AAA', 'IR Guided SAM', 'SAM LL'} } } } @@ -4064,6 +4064,8 @@ do end function ZoneCommand:fullBuild(useCost) + if self.side ~= 1 and self.side ~= 2 then return end + for i,v in ipairs(self.upgrades[self.side]) do if useCost then local cost = v.cost * useCost From 3d155053477739c53d885504c02aab15d6c4ad9a Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Mon, 2 Oct 2023 10:32:17 +0300 Subject: [PATCH 168/243] Implemented a static method for creating the Pretense zone connections and to avoid duplicate connections. --- game/pretense/pretenseluagenerator.py | 54 +++++++++++++++++++++------ 1 file changed, 43 insertions(+), 11 deletions(-) diff --git a/game/pretense/pretenseluagenerator.py b/game/pretense/pretenseluagenerator.py index 3e616357..6c44d547 100644 --- a/game/pretense/pretenseluagenerator.py +++ b/game/pretense/pretenseluagenerator.py @@ -503,6 +503,34 @@ class PretenseLuaGenerator(LuaGenerator): return lua_string_zones + @staticmethod + def generate_pretense_zone_connection( + lua_string_connman: str, + connected_points: dict[str, list[str]], + cp_name: str, + other_cp_name: str, + ) -> str: + try: + connected_points[cp_name] + except KeyError: + connected_points[cp_name] = list() + try: + connected_points[other_cp_name] + except KeyError: + connected_points[other_cp_name] = list() + + if ( + other_cp_name not in connected_points[cp_name] + and cp_name not in connected_points[other_cp_name] + ): + lua_string_connman += ( + f" cm: addConnection('{cp_name}', '{other_cp_name}')\n" + ) + connected_points[cp_name].append(other_cp_name) + connected_points[other_cp_name].append(cp_name) + + return lua_string_connman + def generate_plugin_data(self) -> None: self.mission.triggerrules.triggers.clear() @@ -562,15 +590,19 @@ class PretenseLuaGenerator(LuaGenerator): lua_string_connman = " cm = ConnectionManager:new()\n" # Generate ConnectionManager connections + connected_points: dict[str, list[str]] = {} for cp in self.game.theater.controlpoints: for other_cp in cp.connected_points: - lua_string_connman += ( - f" cm: addConnection('{cp.name}', '{other_cp.name}')\n" + self.generate_pretense_zone_connection( + lua_string_connman, connected_points, cp.name, other_cp.name ) for sea_connection in cp.shipping_lanes: if sea_connection.is_friendly_to(cp): - lua_string_connman += ( - f" cm: addConnection('{cp.name}', '{sea_connection.name}')\n" + self.generate_pretense_zone_connection( + lua_string_connman, + connected_points, + cp.name, + sea_connection.name, ) if len(cp.connected_points) == 0 and len(cp.shipping_lanes) == 0: # Also connect carrier and LHA control points to adjacent friendly points @@ -586,21 +618,21 @@ class PretenseLuaGenerator(LuaGenerator): ): break - lua_string_connman += ( - f" cm: addConnection('{cp.name}', '{other_cp.name}')\n" + self.generate_pretense_zone_connection( + lua_string_connman, connected_points, cp.name, other_cp.name ) else: # Finally, connect remaining non-connected points closest_cps = self.game.theater.closest_friendly_control_points_to(cp) - lua_string_connman += ( - f" cm: addConnection('{cp.name}', '{closest_cps[0].name}')\n" + self.generate_pretense_zone_connection( + lua_string_connman, connected_points, cp.name, closest_cps[0].name ) - lua_string_connman += ( - f" cm: addConnection('{cp.name}', '{closest_cps[1].name}')\n" + self.generate_pretense_zone_connection( + lua_string_connman, connected_points, cp.name, closest_cps[1].name ) lua_string_supply = "local redSupply = {\n" - # Generate ConnectionManager connections + # Generate supply for cp_side in range(1, 3): for cp in self.game.theater.controlpoints: if isinstance(cp, OffMapSpawn): From cf9538f7dfaf6bb1eeff9e8620845bbafbdee4e2 Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Tue, 3 Oct 2023 13:55:20 +0300 Subject: [PATCH 169/243] Implemented spawning of ship statics/units at naval control points instead of land structures or SHORAD sites. --- game/pretense/pretenseluagenerator.py | 419 +++++++++++---------- resources/plugins/pretense/init_header.lua | 36 +- 2 files changed, 259 insertions(+), 196 deletions(-) diff --git a/game/pretense/pretenseluagenerator.py b/game/pretense/pretenseluagenerator.py index 6c44d547..597e1681 100644 --- a/game/pretense/pretenseluagenerator.py +++ b/game/pretense/pretenseluagenerator.py @@ -1,6 +1,7 @@ from __future__ import annotations import logging +import random from abc import ABC, abstractmethod from pathlib import Path from typing import TYPE_CHECKING, Optional @@ -222,6 +223,200 @@ class PretenseLuaGenerator(LuaGenerator): return lua_string_zones + def generate_pretense_sea_upgrade_supply(self, cp_name: str, cp_side: int) -> str: + lua_string_zones = "" + cp_name_trimmed = "".join([i for i in cp_name.lower() if i.isalnum()]) + cp_side_str = "blue" if cp_side == PRETENSE_BLUE_SIDE else "red" + + if cp_side == PRETENSE_BLUE_SIDE: + if random.randint(0, 1): + supply_ship = "shipSupplyTilde" + else: + supply_ship = "shipLandingShipLstMk2" + tanker_ship = "shipTankerSeawisegiant" + command_ship = "shipLandingShipSamuelChase" + ship_group = "blueShipGroup" + else: + if random.randint(0, 1): + supply_ship = "shipBulkerYakushev" + else: + supply_ship = "shipCargoIvanov" + tanker_ship = "shipTankerElnya" + command_ship = "shipLandingShipRopucha" + ship_group = "redShipGroup" + + lua_string_zones += ( + " presets.upgrades.supply." + supply_ship + ":extend({\n" + ) + lua_string_zones += ( + " name = '" + + cp_name_trimmed + + f"-{supply_ship}-" + + cp_side_str + + "',\n" + ) + lua_string_zones += " products = {\n" + for ground_group in self.game.pretense_ground_supply[cp_side][cp_name_trimmed]: + lua_string_zones += ( + " presets.missions.supply.convoy:extend({ name='" + + ground_group + + "'}),\n" + ) + for ground_group in self.game.pretense_ground_assault[cp_side][cp_name_trimmed]: + lua_string_zones += ( + " presets.missions.attack.surface:extend({ name='" + + ground_group + + "'}),\n" + ) + for mission_type in self.game.pretense_air[cp_side][cp_name_trimmed]: + if mission_type == FlightType.AIR_ASSAULT: + mission_name = "supply.helo" + for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ + mission_type + ]: + lua_string_zones += ( + f" presets.missions.{mission_name}:extend" + + "({name='" + + air_group + + "'}),\n" + ) + lua_string_zones += " }\n" + lua_string_zones += " }),\n" + lua_string_zones += ( + " presets.upgrades.attack." + command_ship + ":extend({\n" + ) + lua_string_zones += ( + f" name = '{cp_name_trimmed}-mission-command-" + + cp_side_str + + "',\n" + ) + lua_string_zones += " products = {\n" + lua_string_zones += ( + " presets.defenses." + + cp_side_str + + "." + + ship_group + + ":extend({ name='" + + cp_name_trimmed + + "-sam-" + + cp_side_str + + "' }),\n" + ) + lua_string_zones += " }\n" + lua_string_zones += " }),\n" + lua_string_zones += ( + " presets.upgrades.attack." + tanker_ship + ":extend({\n" + ) + lua_string_zones += ( + f" name = '{cp_name_trimmed}-aircraft-command-" + + cp_side_str + + "',\n" + ) + lua_string_zones += " products = {\n" + for mission_type in self.game.pretense_air[cp_side][cp_name_trimmed]: + if mission_type == FlightType.SEAD: + mission_name = "attack.sead" + for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ + mission_type + ]: + lua_string_zones += ( + f" presets.missions.{mission_name}:extend" + + "({name='" + + air_group + + "', altitude=25000, expend=AI.Task.WeaponExpend.ALL}),\n" + ) + elif mission_type == FlightType.CAS: + mission_name = "attack.cas" + for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ + mission_type + ]: + lua_string_zones += ( + f" presets.missions.{mission_name}:extend" + + "({name='" + + air_group + + "', altitude=15000, expend=AI.Task.WeaponExpend.ONE}),\n" + ) + elif mission_type == FlightType.BAI: + mission_name = "attack.bai" + for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ + mission_type + ]: + lua_string_zones += ( + f" presets.missions.{mission_name}:extend" + + "({name='" + + air_group + + "', altitude=10000, expend=AI.Task.WeaponExpend.ONE}),\n" + ) + elif mission_type == FlightType.STRIKE: + mission_name = "attack.strike" + for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ + mission_type + ]: + lua_string_zones += ( + f" presets.missions.{mission_name}:extend" + + "({name='" + + air_group + + "', altitude=20000, expend=AI.Task.WeaponExpend.ALL}),\n" + ) + elif mission_type == FlightType.BARCAP: + mission_name = "patrol.aircraft" + for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ + mission_type + ]: + lua_string_zones += ( + f" presets.missions.{mission_name}:extend" + + "({name='" + + air_group + + "', altitude=25000, range=25}),\n" + ) + elif mission_type == FlightType.REFUELING: + mission_name = "support.tanker" + for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ + mission_type + ]: + tanker_freq = 257.0 + tanker_tacan = 37.0 + tanker_variant = "Drogue" + for tanker in self.mission_data.tankers: + if tanker.group_name == air_group: + tanker_freq = tanker.freq.hertz / 1000000 + tanker_tacan = tanker.tacan.number if tanker.tacan else 0.0 + if tanker.variant == "KC-135 Stratotanker": + tanker_variant = "Boom" + lua_string_zones += ( + f" presets.missions.{mission_name}:extend" + + "({name='" + + air_group + + "', freq='" + + str(tanker_freq) + + "', tacan='" + + str(tanker_tacan) + + "', variant='" + + tanker_variant + + "'}),\n" + ) + elif mission_type == FlightType.AEWC: + mission_name = "support.awacs" + for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ + mission_type + ]: + awacs_freq = 257.5 + for awacs in self.mission_data.awacs: + if awacs.group_name == air_group: + awacs_freq = awacs.freq.hertz / 1000000 + lua_string_zones += ( + f" presets.missions.{mission_name}:extend" + + "({name='" + + air_group + + "', freq=" + + str(awacs_freq) + + "}),\n" + ) + lua_string_zones += " }\n" + lua_string_zones += " })\n" + + return lua_string_zones + def generate_pretense_zone_land(self, cp_name: str) -> str: lua_string_zones = "" cp_name_trimmed = "".join([i for i in cp_name.lower() if i.isalnum()]) @@ -296,208 +491,25 @@ class PretenseLuaGenerator(LuaGenerator): return lua_string_zones - def generate_pretense_zone_sea(self, cp_name: str, cp_side: int) -> str: + def generate_pretense_zone_sea(self, cp_name: str) -> str: lua_string_zones = "" cp_name_trimmed = "".join([i for i in cp_name.lower() if i.isalnum()]) lua_string_zones += f"zones.{cp_name_trimmed}:defineUpgrades(" + "{\n" lua_string_zones += " [1] = { --red side\n" - lua_string_zones += " presets.upgrades.basic.tent:extend({\n" - lua_string_zones += f" name='{cp_name_trimmed}-tent-red',\n" - lua_string_zones += " products = {\n" - lua_string_zones += ( - " presets.special.red.infantry:extend({ name='" - + cp_name_trimmed - + "-defense-red'})\n" + + lua_string_zones += self.generate_pretense_sea_upgrade_supply( + cp_name, PRETENSE_RED_SIDE ) - lua_string_zones += " }\n" - lua_string_zones += " }),\n" - lua_string_zones += " presets.upgrades.basic.comPost:extend({\n" - lua_string_zones += f" name = '{cp_name_trimmed}-com-red',\n" - lua_string_zones += " products = {\n" - lua_string_zones += ( - " presets.special.red.infantry:extend({ name='" - + cp_name_trimmed - + "-defense-red'}),\n" - ) - lua_string_zones += ( - " presets.defenses.red.infantry:extend({ name='" - + cp_name_trimmed - + "-garrison-red' })\n" - ) - lua_string_zones += " }\n" - lua_string_zones += " }),\n" + lua_string_zones += " },\n" lua_string_zones += " [2] = --blue side\n" lua_string_zones += " {\n" - lua_string_zones += " presets.upgrades.basic.tent:extend({\n" - lua_string_zones += f" name='{cp_name_trimmed}-tent-blue',\n" - lua_string_zones += " products = {\n" - lua_string_zones += ( - " presets.special.blue.infantry:extend({ name='" - + cp_name_trimmed - + "-defense-blue'})\n" + + lua_string_zones += self.generate_pretense_sea_upgrade_supply( + cp_name, PRETENSE_BLUE_SIDE ) - lua_string_zones += " }\n" - lua_string_zones += " }),\n" - lua_string_zones += " presets.upgrades.basic.comPost:extend({\n" - lua_string_zones += f" name = '{cp_name_trimmed}-com-blue',\n" - lua_string_zones += " products = {\n" - lua_string_zones += ( - " presets.special.blue.infantry:extend({ name='" - + cp_name_trimmed - + "-defense-blue'}),\n" - ) - lua_string_zones += ( - " presets.defenses.blue.infantry:extend({ name='" - + cp_name_trimmed - + "-garrison-blue' })\n" - ) - lua_string_zones += " }\n" - lua_string_zones += " }),\n" - lua_string_zones += " presets.upgrades.supply.fuelTank:extend({\n" - lua_string_zones += ( - " name = '" + cp_name_trimmed + "-fueltank-blue',\n" - ) - lua_string_zones += " products = {\n" - for ground_group in self.game.pretense_ground_supply[cp_side][cp_name_trimmed]: - lua_string_zones += ( - " presets.missions.supply.convoy:extend({ name='" - + ground_group - + "'}),\n" - ) - for ground_group in self.game.pretense_ground_assault[cp_side][cp_name_trimmed]: - lua_string_zones += ( - " presets.missions.attack.surface:extend({ name='" - + ground_group - + "'}),\n" - ) - for mission_type in self.game.pretense_air[cp_side][cp_name_trimmed]: - if mission_type == FlightType.AIR_ASSAULT: - mission_name = "supply.helo" - for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ - mission_type - ]: - lua_string_zones += ( - f" presets.missions.{mission_name}:extend" - + "({name='" - + air_group - + "'}),\n" - ) - lua_string_zones += " }\n" - lua_string_zones += " }),\n" - lua_string_zones += " presets.upgrades.airdef.comCenter:extend({\n" - lua_string_zones += ( - f" name = '{cp_name_trimmed}-mission-command-blue',\n" - ) - lua_string_zones += " products = {\n" - lua_string_zones += ( - " presets.defenses.blue.shorad:extend({ name='" - + cp_name_trimmed - + "-sam-blue' }),\n" - ) - for mission_type in self.game.pretense_air[cp_side][cp_name_trimmed]: - if mission_type == FlightType.SEAD: - mission_name = "attack.sead" - for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ - mission_type - ]: - lua_string_zones += ( - f" presets.missions.{mission_name}:extend" - + "({name='" - + air_group - + "', altitude=25000, expend=AI.Task.WeaponExpend.ALL}),\n" - ) - elif mission_type == FlightType.CAS: - mission_name = "attack.cas" - for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ - mission_type - ]: - lua_string_zones += ( - f" presets.missions.{mission_name}:extend" - + "({name='" - + air_group - + "', altitude=15000, expend=AI.Task.WeaponExpend.ONE}),\n" - ) - elif mission_type == FlightType.BAI: - mission_name = "attack.bai" - for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ - mission_type - ]: - lua_string_zones += ( - f" presets.missions.{mission_name}:extend" - + "({name='" - + air_group - + "', altitude=10000, expend=AI.Task.WeaponExpend.ONE}),\n" - ) - elif mission_type == FlightType.STRIKE: - mission_name = "attack.strike" - for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ - mission_type - ]: - lua_string_zones += ( - f" presets.missions.{mission_name}:extend" - + "({name='" - + air_group - + "', altitude=20000, expend=AI.Task.WeaponExpend.ALL}),\n" - ) - elif mission_type == FlightType.BARCAP: - mission_name = "patrol.aircraft" - for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ - mission_type - ]: - lua_string_zones += ( - f" presets.missions.{mission_name}:extend" - + "({name='" - + air_group - + "', altitude=25000, range=25}),\n" - ) - elif mission_type == FlightType.REFUELING: - mission_name = "support.tanker" - for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ - mission_type - ]: - tanker_freq = 257.0 - tanker_tacan = 37.0 - for tanker in self.mission_data.tankers: - if tanker.group_name == air_group: - tanker_freq = tanker.freq.hertz / 1000000 - tanker_tacan = tanker.tacan.number if tanker.tacan else 0.0 - if tanker.variant == "KC-135 Stratotanker": - tanker_variant = "Boom" - else: - tanker_variant = "Drogue" - lua_string_zones += ( - f" presets.missions.{mission_name}:extend" - + "({name='" - + air_group - + "', freq='" - + str(tanker_freq) - + "', tacan='" - + str(tanker_tacan) - + "', variant='" - + tanker_variant - + "'}),\n" - ) - elif mission_type == FlightType.AEWC: - mission_name = "support.awacs" - for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ - mission_type - ]: - awacs_freq = 257.5 - for awacs in self.mission_data.awacs: - if awacs.group_name == air_group: - awacs_freq = awacs.freq.hertz / 1000000 - lua_string_zones += ( - f" presets.missions.{mission_name}:extend" - + "({name='" - + air_group - + "', freq=" - + str(awacs_freq) - + "}),\n" - ) - lua_string_zones += " }\n" - lua_string_zones += " })\n" + lua_string_zones += " }\n" lua_string_zones += "})\n" @@ -570,20 +582,33 @@ class PretenseLuaGenerator(LuaGenerator): ) lua_string_zones += f"zones.{cp_name_trimmed}.keepActive = true\n" max_resource = 20000 + is_helo_spawn = "false" + is_plane_spawn = "false" if cp.has_helipads: - lua_string_zones += f"zones.{cp_name_trimmed}.isHeloSpawn = true\n" + is_helo_spawn = "true" max_resource = 30000 if isinstance(cp, Airfield) or cp.has_ground_spawns: - lua_string_zones += f"zones.{cp_name_trimmed}.isPlaneSpawn = true\n" + is_helo_spawn = "true" + is_plane_spawn = "true" if cp.has_ground_spawns or cp.is_lha: + is_helo_spawn = "true" + is_plane_spawn = "true" max_resource = 40000 if isinstance(cp, Airfield) or cp.is_carrier: + is_helo_spawn = "true" + is_plane_spawn = "true" max_resource = 50000 lua_string_zones += ( f"zones.{cp_name_trimmed}.maxResource = {max_resource}\n" ) + lua_string_zones += ( + f"zones.{cp_name_trimmed}.isHeloSpawn = " + is_helo_spawn + "\n" + ) + lua_string_zones += ( + f"zones.{cp_name_trimmed}.isPlaneSpawn = " + is_plane_spawn + "\n" + ) if cp.is_fleet: - lua_string_zones += self.generate_pretense_zone_sea(cp.name, cp_side) + lua_string_zones += self.generate_pretense_zone_sea(cp.name) else: lua_string_zones += self.generate_pretense_zone_land(cp.name) @@ -687,6 +712,10 @@ class PretenseLuaGenerator(LuaGenerator): trigger.add_action(DoScript(String(lua_string))) self.mission.triggerrules.triggers.append(trigger) + file1 = open(Path("./resources/plugins/pretense", "pretense_output.lua"), "w") + file1.write(lua_string) + file1.close() + def inject_lua_trigger(self, contents: str, comment: str) -> None: trigger = TriggerStart(comment=comment) trigger.add_action(DoScript(String(contents))) diff --git a/resources/plugins/pretense/init_header.lua b/resources/plugins/pretense/init_header.lua index 12f84597..dddb6087 100644 --- a/resources/plugins/pretense/init_header.lua +++ b/resources/plugins/pretense/init_header.lua @@ -259,6 +259,28 @@ do skill = "Excellent", dataCategory= TemplateDB.type.group } + + TemplateDB.templates["blueShipGroup"] = { + units = { + "PERRY", + "USS_Arleigh_Burke_IIa", + "PERRY" + }, + maxDist = 300, + skill = "Excellent", + dataCategory= TemplateDB.type.group + } + + TemplateDB.templates["redShipGroup"] = { + units = { + "ALBATROS", + "NEUSTRASH", + "ALBATROS" + }, + maxDist = 300, + skill = "Excellent", + dataCategory= TemplateDB.type.group + } end presets = { @@ -523,10 +545,16 @@ presets = { template='sa6', }), sa11 = Preset:new({ + display = 'SAM', + cost=3000, + type='defense', + template='sa11', + }), + redShipGroup = Preset:new({ display = 'SAM', cost=3000, type='defense', - template='sa11', + template='redShipGroup', }) }, blue = { @@ -559,6 +587,12 @@ presets = { cost=3000, type='defense', template='nasams', + }), + blueShipGroup = Preset:new({ + display = 'SAM', + cost=3000, + type='defense', + template='blueShipGroup', }) } }, From 39c80cb9748075d3c0a0da79b224dc8094195d43 Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Tue, 3 Oct 2023 13:56:43 +0300 Subject: [PATCH 170/243] Implemented a list of units which will be removed from Pretense ground assault groups due to pathfinding problems. The units will just remain still instead of advancing. Also added one tank to each group and increased the maximum size of the groups to 5. Removed artillery units from the groups, similarly due to pathfinding problems. --- game/pretense/pretensetgogenerator.py | 28 ++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/game/pretense/pretensetgogenerator.py b/game/pretense/pretensetgogenerator.py index 74f99a6e..aa1ee203 100644 --- a/game/pretense/pretensetgogenerator.py +++ b/game/pretense/pretensetgogenerator.py @@ -91,7 +91,11 @@ if TYPE_CHECKING: FARP_FRONTLINE_DISTANCE = 10000 AA_CP_MIN_DISTANCE = 40000 -PRETENSE_GROUND_UNIT_GROUP_SIZE = 4 +PRETENSE_GROUND_UNIT_GROUP_SIZE = 5 +PRETENSE_GROUND_UNITS_TO_REMOVE_FROM_ASSAULT = [ + vehicles.Armor.Stug_III, + vehicles.Artillery.Grad_URAL, +] class PretenseGroundObjectGenerator(GroundObjectGenerator): @@ -131,6 +135,12 @@ class PretenseGroundObjectGenerator(GroundObjectGenerator): ) of_class = list({u for u in faction_units if u.unit_class is unit_class}) + # Remove units from list with known pathfinding issues in Pretense missions + for unit_to_remove in PRETENSE_GROUND_UNITS_TO_REMOVE_FROM_ASSAULT: + for groundunittype_to_remove in GroundUnitType.for_dcs_type(unit_to_remove): + if groundunittype_to_remove in of_class: + of_class.remove(groundunittype_to_remove) + if len(of_class) > 0: return random.choice(of_class) else: @@ -200,6 +210,14 @@ class PretenseGroundObjectGenerator(GroundObjectGenerator): group_name = f"{cp_name_trimmed}-{group_role}-{group.id}" group.name = group_name + self.generate_ground_unit_of_class( + UnitClass.TANK, + group, + vehicle_units, + cp_name_trimmed, + group_role, + PRETENSE_GROUND_UNIT_GROUP_SIZE - 4, + ) self.generate_ground_unit_of_class( UnitClass.TANK, group, @@ -232,14 +250,6 @@ class PretenseGroundObjectGenerator(GroundObjectGenerator): group_role, PRETENSE_GROUND_UNIT_GROUP_SIZE, ) - self.generate_ground_unit_of_class( - UnitClass.ARTILLERY, - group, - vehicle_units, - cp_name_trimmed, - group_role, - PRETENSE_GROUND_UNIT_GROUP_SIZE, - ) self.generate_ground_unit_of_class( UnitClass.RECON, group, From cf9cc9ba33529af9ea0b62f84a411219ff3cab7b Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Tue, 3 Oct 2023 14:34:26 +0300 Subject: [PATCH 171/243] Fixed a bug in generate_pretense_zone_connection(). --- game/pretense/pretenseluagenerator.py | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/game/pretense/pretenseluagenerator.py b/game/pretense/pretenseluagenerator.py index 597e1681..f77088da 100644 --- a/game/pretense/pretenseluagenerator.py +++ b/game/pretense/pretenseluagenerator.py @@ -517,11 +517,11 @@ class PretenseLuaGenerator(LuaGenerator): @staticmethod def generate_pretense_zone_connection( - lua_string_connman: str, connected_points: dict[str, list[str]], cp_name: str, other_cp_name: str, ) -> str: + lua_string_connman = "" try: connected_points[cp_name] except KeyError: @@ -535,7 +535,7 @@ class PretenseLuaGenerator(LuaGenerator): other_cp_name not in connected_points[cp_name] and cp_name not in connected_points[other_cp_name] ): - lua_string_connman += ( + lua_string_connman = ( f" cm: addConnection('{cp_name}', '{other_cp_name}')\n" ) connected_points[cp_name].append(other_cp_name) @@ -618,13 +618,12 @@ class PretenseLuaGenerator(LuaGenerator): connected_points: dict[str, list[str]] = {} for cp in self.game.theater.controlpoints: for other_cp in cp.connected_points: - self.generate_pretense_zone_connection( - lua_string_connman, connected_points, cp.name, other_cp.name + lua_string_connman += self.generate_pretense_zone_connection( + connected_points, cp.name, other_cp.name ) for sea_connection in cp.shipping_lanes: if sea_connection.is_friendly_to(cp): - self.generate_pretense_zone_connection( - lua_string_connman, + lua_string_connman += self.generate_pretense_zone_connection( connected_points, cp.name, sea_connection.name, @@ -643,17 +642,17 @@ class PretenseLuaGenerator(LuaGenerator): ): break - self.generate_pretense_zone_connection( - lua_string_connman, connected_points, cp.name, other_cp.name + lua_string_connman += self.generate_pretense_zone_connection( + connected_points, cp.name, other_cp.name ) else: # Finally, connect remaining non-connected points closest_cps = self.game.theater.closest_friendly_control_points_to(cp) - self.generate_pretense_zone_connection( - lua_string_connman, connected_points, cp.name, closest_cps[0].name + lua_string_connman += self.generate_pretense_zone_connection( + connected_points, cp.name, closest_cps[0].name ) - self.generate_pretense_zone_connection( - lua_string_connman, connected_points, cp.name, closest_cps[1].name + lua_string_connman += self.generate_pretense_zone_connection( + connected_points, cp.name, closest_cps[1].name ) lua_string_supply = "local redSupply = {\n" From 713cd98d108a117df788ad1b8644f85449d50473 Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Tue, 3 Oct 2023 17:17:12 +0300 Subject: [PATCH 172/243] Implemented SAM sites as products of a Command Center at a control point / zone, if the Retribution campaign has the corresponding SAM site there. The SAM site presets are still static, I might make them dynamic in the future. --- game/pretense/pretenseluagenerator.py | 119 +++++++++++++++++++-- resources/plugins/pretense/init_header.lua | 4 +- 2 files changed, 112 insertions(+), 11 deletions(-) diff --git a/game/pretense/pretenseluagenerator.py b/game/pretense/pretenseluagenerator.py index f77088da..32577ef9 100644 --- a/game/pretense/pretenseluagenerator.py +++ b/game/pretense/pretenseluagenerator.py @@ -3,6 +3,7 @@ from __future__ import annotations import logging import random from abc import ABC, abstractmethod +from dataclasses import dataclass from pathlib import Path from typing import TYPE_CHECKING, Optional @@ -10,6 +11,7 @@ from dcs import Mission from dcs.action import DoScript, DoScriptFile from dcs.translation import String from dcs.triggers import TriggerStart +from dcs.vehicles import AirDefence from game.ato import FlightType from game.missiongenerator.luagenerator import LuaGenerator @@ -26,6 +28,19 @@ PRETENSE_BLUE_SIDE = 2 PRETENSE_NUMBER_OF_ZONES_TO_CONNECT_CARRIERS_TO = 2 +@dataclass +class PretenseSam: + name: str + enabled: bool + + def __init__( + self, + name: str, + ) -> None: + self.name = name + self.enabled = False + + class PretenseLuaGenerator(LuaGenerator): def __init__( self, @@ -54,10 +69,44 @@ class PretenseLuaGenerator(LuaGenerator): self.mission.triggerrules.triggers.remove(t) self.mission.triggerrules.triggers.append(t) + @staticmethod + def generate_sam_from_preset( + preset: str, cp_side_str: str, cp_name_trimmed: str + ) -> str: + lua_string_zones = ( + " presets.defenses." + + cp_side_str + + "." + + preset + + ":extend({ name='" + + cp_name_trimmed + + f"-{preset}-" + + cp_side_str + + "' }),\n" + ) + return lua_string_zones + def generate_pretense_land_upgrade_supply(self, cp_name: str, cp_side: int) -> str: lua_string_zones = "" cp_name_trimmed = "".join([i for i in cp_name.lower() if i.isalnum()]) cp_side_str = "blue" if cp_side == PRETENSE_BLUE_SIDE else "red" + cp = self.game.theater.controlpoints[0] + for loop_cp in self.game.theater.controlpoints: + if loop_cp.name == cp_name: + cp = loop_cp + sam_presets: dict[str, PretenseSam] = {} + for sam_name in [ + "sa2", + "sa3", + "sa5", + "sa6", + "sa10", + "sa11", + "hawk", + "patriot", + "nasams", + ]: + sam_presets[sam_name] = PretenseSam(sam_name) lua_string_zones += " presets.upgrades.supply.fuelTank:extend({\n" lua_string_zones += ( @@ -96,22 +145,74 @@ class PretenseLuaGenerator(LuaGenerator): lua_string_zones += " }),\n" lua_string_zones += " presets.upgrades.airdef.bunker:extend({\n" lua_string_zones += ( - f" name = '{cp_name_trimmed}-mission-command-" + f" name = '{cp_name_trimmed}-shorad-command-" + cp_side_str + "',\n" ) lua_string_zones += " products = {\n" - lua_string_zones += ( - " presets.defenses." - + cp_side_str - + ".shorad:extend({ name='" - + cp_name_trimmed - + "-sam-" - + cp_side_str - + "' }),\n" + lua_string_zones += self.generate_sam_from_preset( + "shorad", cp_side_str, cp_name_trimmed ) lua_string_zones += " }\n" lua_string_zones += " }),\n" + + for ground_object in cp.ground_objects: + for ground_unit in ground_object.units: + if ground_unit.unit_type is not None: + if ground_unit.unit_type.dcs_unit_type == AirDefence.S_75M_Volhov: + sam_presets["sa2"].enabled = True + if ( + ground_unit.unit_type.dcs_unit_type + == AirDefence.X_5p73_s_125_ln + ): + sam_presets["sa3"].enabled = True + if ground_unit.unit_type.dcs_unit_type == AirDefence.S_200_Launcher: + sam_presets["sa5"].enabled = True + if ground_unit.unit_type.dcs_unit_type == AirDefence.Kub_2P25_ln: + sam_presets["sa6"].enabled = True + if ( + ground_unit.unit_type.dcs_unit_type + == AirDefence.S_300PS_5P85C_ln + or ground_unit.unit_type.dcs_unit_type + == AirDefence.S_300PS_5P85D_ln + ): + sam_presets["sa10"].enabled = True + if ( + ground_unit.unit_type.dcs_unit_type + == AirDefence.SA_11_Buk_LN_9A310M1 + ): + sam_presets["sa11"].enabled = True + if ground_unit.unit_type.dcs_unit_type == AirDefence.Hawk_ln: + sam_presets["hawk"].enabled = True + if ground_unit.unit_type.dcs_unit_type == AirDefence.Patriot_ln: + sam_presets["patriot"].enabled = True + if ( + ground_unit.unit_type.dcs_unit_type == AirDefence.NASAMS_LN_B + or ground_unit.unit_type.dcs_unit_type == AirDefence.NASAMS_LN_C + ): + sam_presets["nasams"].enabled = True + + cp_has_sams = False + for sam_name in sam_presets: + if sam_presets[sam_name].enabled: + cp_has_sams = True + break + if cp_has_sams: + lua_string_zones += " presets.upgrades.airdef.comCenter:extend({\n" + lua_string_zones += ( + f" name = '{cp_name_trimmed}-sam-command-" + + cp_side_str + + "',\n" + ) + lua_string_zones += " products = {\n" + for sam_name in sam_presets: + if sam_presets[sam_name].enabled: + lua_string_zones += self.generate_sam_from_preset( + sam_name, cp_side_str, cp_name_trimmed + ) + lua_string_zones += " }\n" + lua_string_zones += " }),\n" + lua_string_zones += " presets.upgrades.supply.hangar:extend({\n" lua_string_zones += ( f" name = '{cp_name_trimmed}-aircraft-command-" diff --git a/resources/plugins/pretense/init_header.lua b/resources/plugins/pretense/init_header.lua index dddb6087..a8585391 100644 --- a/resources/plugins/pretense/init_header.lua +++ b/resources/plugins/pretense/init_header.lua @@ -90,7 +90,7 @@ do dataCategory= TemplateDB.type.group } - TemplateDB.templates["sam-red"] = { + TemplateDB.templates["sa2"] = { units = { "p-19 s-125 sr", "Ural-4320T", @@ -108,7 +108,7 @@ do dataCategory= TemplateDB.type.group } - TemplateDB.templates["sam-blue"] = { + TemplateDB.templates["hawk"] = { units = { "Hawk pcp", "Hawk cwar", From 921b1ee9fdef316f67b8a65b3dc75b19d630aec4 Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Tue, 3 Oct 2023 17:56:21 +0300 Subject: [PATCH 173/243] Fixed a bug in SHORAD and SAM generation. --- game/pretense/pretenseluagenerator.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/game/pretense/pretenseluagenerator.py b/game/pretense/pretenseluagenerator.py index 32577ef9..af97601b 100644 --- a/game/pretense/pretenseluagenerator.py +++ b/game/pretense/pretenseluagenerator.py @@ -74,9 +74,7 @@ class PretenseLuaGenerator(LuaGenerator): preset: str, cp_side_str: str, cp_name_trimmed: str ) -> str: lua_string_zones = ( - " presets.defenses." - + cp_side_str - + "." + " presets.defenses.sam." + preset + ":extend({ name='" + cp_name_trimmed @@ -150,8 +148,14 @@ class PretenseLuaGenerator(LuaGenerator): + "',\n" ) lua_string_zones += " products = {\n" - lua_string_zones += self.generate_sam_from_preset( - "shorad", cp_side_str, cp_name_trimmed + lua_string_zones += ( + " presets.defenses." + + cp_side_str + + ".shorad:extend({ name='" + + cp_name_trimmed + + "-shorad-" + + cp_side_str + + "' }),\n" ) lua_string_zones += " }\n" lua_string_zones += " }),\n" From 08930f4a9ae4c7b5e3b231c4d967e996ad960dad Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Tue, 3 Oct 2023 22:45:34 +0300 Subject: [PATCH 174/243] Added new icons and a separate button which will point to the Pretense/Foothold Discord. --- qt_ui/uiconstants.py | 3 ++- qt_ui/windows/QLiberationWindow.py | 15 +++++++++++++-- resources/ui/misc/pretense_discord.png | Bin 0 -> 36022 bytes resources/ui/misc/pretense_generate.png | Bin 0 -> 14287 bytes 4 files changed, 15 insertions(+), 3 deletions(-) create mode 100644 resources/ui/misc/pretense_discord.png create mode 100644 resources/ui/misc/pretense_generate.png diff --git a/qt_ui/uiconstants.py b/qt_ui/uiconstants.py index 8edc69a0..d431fc29 100644 --- a/qt_ui/uiconstants.py +++ b/qt_ui/uiconstants.py @@ -32,7 +32,8 @@ def load_icons(): "./resources/ui/misc/" + get_theme_icons() + "/github.png" ) ICONS["Ukraine"] = QPixmap("./resources/ui/misc/ukraine.png") - ICONS["Pretense"] = QPixmap("./resources/ui/misc/pretense.png") + ICONS["Pretense"] = QPixmap("./resources/ui/misc/pretense_discord.png") + ICONS["Pretense_generate"] = QPixmap("./resources/ui/misc/pretense_generate.png") ICONS["Control Points"] = QPixmap( "./resources/ui/misc/" + get_theme_icons() + "/circle.png" diff --git a/qt_ui/windows/QLiberationWindow.py b/qt_ui/windows/QLiberationWindow.py index 0551f375..3cd88f26 100644 --- a/qt_ui/windows/QLiberationWindow.py +++ b/qt_ui/windows/QLiberationWindow.py @@ -194,8 +194,18 @@ class QLiberationWindow(QMainWindow): lambda: webbrowser.open_new_tab("https://shdwp.github.io/ukraine/") ) - self.newPretenseAction = QAction("&New Pretense Campaign", self) - self.newPretenseAction.setIcon(QIcon(CONST.ICONS["Pretense"])) + self.pretenseLinkAction = QAction("&DCS: Pretense", self) + self.pretenseLinkAction.setIcon(QIcon(CONST.ICONS["Pretense"])) + self.pretenseLinkAction.triggered.connect( + lambda: webbrowser.open_new_tab( + "https://" + "discord.gg" + "/" + "PtPsb9Mpk6" + ) + ) + + self.newPretenseAction = QAction( + "&Generate a Pretense Campaign from the running campaign", self + ) + self.newPretenseAction.setIcon(QIcon(CONST.ICONS["Pretense_generate"])) self.newPretenseAction.triggered.connect(self.newPretenseCampaign) self.openLogsAction = QAction("Show &logs", self) @@ -239,6 +249,7 @@ class QLiberationWindow(QMainWindow): self.links_bar.addAction(self.openDiscordAction) self.links_bar.addAction(self.openGithubAction) self.links_bar.addAction(self.ukraineAction) + self.links_bar.addAction(self.pretenseLinkAction) self.links_bar.addAction(self.newPretenseAction) self.actions_bar = self.addToolBar("Actions") diff --git a/resources/ui/misc/pretense_discord.png b/resources/ui/misc/pretense_discord.png new file mode 100644 index 0000000000000000000000000000000000000000..c9d7f544df58f12787ecff2a9b58696631b846f0 GIT binary patch literal 36022 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 literal 0 HcmV?d00001 diff --git a/resources/ui/misc/pretense_generate.png b/resources/ui/misc/pretense_generate.png new file mode 100644 index 0000000000000000000000000000000000000000..5fcc00b6a546b72be48354f183f0e1b4b71c6cf0 GIT binary patch literal 14287 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 literal 0 HcmV?d00001 From ced7aa4d723f1fb1d78b1f88ef72ae9032524d07 Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Wed, 4 Oct 2023 18:38:41 +0300 Subject: [PATCH 175/243] Implemented generating amphibious units for Pretense unit groups. Amphibious units are selected out of a pre-defined list. Units which the faction has access to are preferred, but certain default unit types are selected as a fall-back to ensure that all the generated units can swim. --- game/pretense/pretensetgogenerator.py | 363 ++++++++++++++++++++--- resources/units/ground_units/LARC-V.yaml | 4 +- 2 files changed, 319 insertions(+), 48 deletions(-) diff --git a/game/pretense/pretensetgogenerator.py b/game/pretense/pretensetgogenerator.py index aa1ee203..d9363a29 100644 --- a/game/pretense/pretensetgogenerator.py +++ b/game/pretense/pretensetgogenerator.py @@ -7,51 +7,22 @@ create the pydcs groups and statics for those areas and add them to the mission. """ from __future__ import annotations -import logging import random from collections import defaultdict -from typing import Any, Dict, Iterator, List, Optional, TYPE_CHECKING, Type, Tuple +from typing import Dict, Optional, TYPE_CHECKING, Tuple, Type -import dcs.vehicles -from dcs import Mission, Point, unitgroup -from dcs.action import DoScript, SceneryDestructionZone -from dcs.condition import MapObjectIsDead +from dcs import Mission, Point from dcs.countries import * from dcs.country import Country -from dcs.point import StaticPoint, PointAction -from dcs.ships import ( - CVN_71, - CVN_72, - CVN_73, - CVN_75, - Stennis, - Forrestal, - LHA_Tarawa, -) -from dcs.statics import Fortification -from dcs.task import ( - ActivateBeaconCommand, - ActivateICLSCommand, - ActivateLink4Command, - ActivateACLSCommand, - EPLRS, - FireAtPoint, - OptAlarmState, -) -from dcs.translation import String -from dcs.triggers import Event, TriggerOnce, TriggerStart, TriggerZone -from dcs.unit import Unit, InvisibleFARP, BaseFARP, SingleHeliPad, FARP -from dcs.unitgroup import MovingGroup, ShipGroup, StaticGroup, VehicleGroup -from dcs.unittype import ShipType, VehicleType -from dcs.vehicles import vehicle_map, Unarmed +from dcs.unitgroup import StaticGroup, VehicleGroup +from dcs.unittype import VehicleType from game.data.units import UnitClass from game.dcs.groundunittype import GroundUnitType from game.missiongenerator.groundforcepainter import ( - NavalForcePainter, GroundForcePainter, ) -from game.missiongenerator.missiondata import CarrierInfo, MissionData +from game.missiongenerator.missiondata import MissionData from game.missiongenerator.tgogenerator import ( TgoGenerator, HelipadGenerator, @@ -63,9 +34,8 @@ from game.missiongenerator.tgogenerator import ( MissileSiteGenerator, ) from game.point_with_heading import PointWithHeading -from game.radio.RadioFrequencyContainer import RadioFrequencyContainer -from game.radio.radios import RadioFrequency, RadioRegistry -from game.radio.tacan import TacanBand, TacanChannel, TacanRegistry, TacanUsage +from game.radio.radios import RadioRegistry +from game.radio.tacan import TacanRegistry from game.runways import RunwayData from game.theater import ( ControlPoint, @@ -76,15 +46,21 @@ from game.theater import ( ) from game.theater.theatergroundobject import ( CarrierGroundObject, - GenericCarrierGroundObject, LhaGroundObject, MissileSiteGroundObject, BuildingGroundObject, VehicleGroupGroundObject, ) -from game.theater.theatergroup import SceneryUnit, IadsGroundGroup, TheaterGroup +from game.theater.theatergroup import TheaterGroup from game.unitmap import UnitMap -from game.utils import Heading, feet, knots, mps +from pydcs_extensions import ( + Char_M551_Sheridan, + BV410_RBS70, + BV410_RBS90, + BV410, + VAB__50, + VAB_T20_13, +) if TYPE_CHECKING: from game import Game @@ -96,6 +72,30 @@ PRETENSE_GROUND_UNITS_TO_REMOVE_FROM_ASSAULT = [ vehicles.Armor.Stug_III, vehicles.Artillery.Grad_URAL, ] +PRETENSE_AMPHIBIOUS_UNITS = [ + vehicles.Unarmed.LARC_V, + vehicles.Armor.AAV7, + vehicles.Armor.LAV_25, + vehicles.Armor.TPZ, + vehicles.Armor.PT_76, + vehicles.Armor.BMD_1, + vehicles.Armor.BMP_1, + vehicles.Armor.BMP_2, + vehicles.Armor.BMP_3, + vehicles.Armor.BTR_80, + vehicles.Armor.BTR_82A, + vehicles.Armor.BRDM_2, + vehicles.Armor.BTR_D, + vehicles.Armor.MTLB, + vehicles.Armor.ZBD04A, + vehicles.Armor.VAB_Mephisto, + VAB__50, + VAB_T20_13, + Char_M551_Sheridan, + BV410_RBS70, + BV410_RBS90, + BV410, +] class PretenseGroundObjectGenerator(GroundObjectGenerator): @@ -128,6 +128,16 @@ class PretenseGroundObjectGenerator(GroundObjectGenerator): return self.game.iads_considerate_culling(self.ground_object) def ground_unit_of_class(self, unit_class: UnitClass) -> Optional[GroundUnitType]: + """ + Returns a GroundUnitType of the specified class that belongs to the + TheaterGroundObject faction. + + Units, which are known to have pathfinding issues in Pretense missions + are removed based on a pre-defined list. + + Args: + unit_class: Class of unit to return. + """ faction_units = ( set(self.ground_object.coalition.faction.frontline_units) | set(self.ground_object.coalition.faction.artillery_units) @@ -155,6 +165,25 @@ class PretenseGroundObjectGenerator(GroundObjectGenerator): group_role: str, max_num: int, ) -> None: + """ + Generates a single land based TheaterUnit for a Pretense unit group + for a specific TheaterGroup, provided that the group still has room + (defined by the max_num argument). Land based groups don't have + restrictions on the unit types, other than that they must be + accessible by the faction and must be of the specified class. + + Generated units are placed 30 meters from the TheaterGroup + position in a random direction. + + Args: + unit_class: Class of unit to generate. + group: The TheaterGroup to generate the unit/group for. + vehicle_units: List of TheaterUnits. The new unit will be appended to this list. + cp_name: Name of the Control Point. + group_role: Pretense group role, "support" or "assault". + max_num: Maximum number of units to generate per group. + """ + if self.ground_object.coalition.faction.has_access_to_unit_class(unit_class): unit_type = self.ground_unit_of_class(unit_class) if unit_type is not None and len(vehicle_units) < max_num: @@ -178,6 +207,140 @@ class PretenseGroundObjectGenerator(GroundObjectGenerator): ) vehicle_units.append(theater_unit) + def generate_amphibious_unit_of_class( + self, + unit_class: UnitClass, + group: TheaterGroup, + vehicle_units: list[TheaterUnit], + cp_name: str, + group_role: str, + max_num: int, + ) -> None: + """ + Generates a single amphibious TheaterUnit for a Pretense unit group + for a specific TheaterGroup, provided that the group still has room + (defined by the max_num argument). Amphibious units are selected + out of a pre-defined list. Units which the faction has access to + are preferred, but certain default unit types are selected as + a fall-back to ensure that all the generated units can swim. + + Generated units are placed 30 meters from the TheaterGroup + position in a random direction. + + Args: + unit_class: Class of unit to generate. + group: The TheaterGroup to generate the unit/group for. + vehicle_units: List of TheaterUnits. The new unit will be appended to this list. + cp_name: Name of the Control Point. + group_role: Pretense group role, "support" or "assault". + max_num: Maximum number of units to generate per group. + """ + unit_type = None + faction = self.ground_object.coalition.faction + is_player = True + side = ( + 2 + if self.country == self.game.coalition_for(is_player).faction.country + else 1 + ) + default_amphibious_unit = unit_type + default_logistics_unit = unit_type + default_tank_unit_blue = unit_type + default_apc_unit_blue = unit_type + default_ifv_unit_blue = unit_type + default_recon_unit_blue = unit_type + default_atgm_unit_blue = unit_type + default_tank_unit_red = unit_type + default_apc_unit_red = unit_type + default_ifv_unit_red = unit_type + default_recon_unit_red = unit_type + default_atgm_unit_red = unit_type + default_ifv_unit_chinese = unit_type + pretense_amphibious_units = PRETENSE_AMPHIBIOUS_UNITS + random.shuffle(pretense_amphibious_units) + for unit in pretense_amphibious_units: + for groundunittype in GroundUnitType.for_dcs_type(unit): + if unit == vehicles.Unarmed.LARC_V: + default_logistics_unit = groundunittype + elif unit == Char_M551_Sheridan: + default_tank_unit_blue = groundunittype + elif unit == vehicles.Armor.AAV7: + default_apc_unit_blue = groundunittype + elif unit == vehicles.Armor.LAV_25: + default_ifv_unit_blue = groundunittype + elif unit == vehicles.Armor.TPZ: + default_recon_unit_blue = groundunittype + elif unit == vehicles.Armor.VAB_Mephisto: + default_atgm_unit_blue = groundunittype + elif unit == vehicles.Armor.PT_76: + default_tank_unit_red = groundunittype + elif unit == vehicles.Armor.BTR_80: + default_apc_unit_red = groundunittype + elif unit == vehicles.Armor.BMD_1: + default_ifv_unit_red = groundunittype + elif unit == vehicles.Armor.BRDM_2: + default_recon_unit_red = groundunittype + elif unit == vehicles.Armor.BTR_D: + default_atgm_unit_red = groundunittype + elif unit == vehicles.Armor.ZBD04A: + default_ifv_unit_chinese = groundunittype + elif unit == vehicles.Armor.MTLB: + default_amphibious_unit = groundunittype + if self.ground_object.coalition.faction.has_access_to_dcs_type(unit): + if groundunittype.unit_class == unit_class: + unit_type = groundunittype + break + if unit_type is None: + if unit_class == UnitClass.LOGISTICS: + unit_type = default_logistics_unit + elif faction.country.id == China.id: + unit_type = default_ifv_unit_chinese + elif side == 2 and unit_class == UnitClass.TANK: + if faction.mod_settings is not None and faction.mod_settings.frenchpack: + unit_type = default_tank_unit_blue + else: + unit_type = default_apc_unit_blue + elif side == 2 and unit_class == UnitClass.IFV: + unit_type = default_ifv_unit_blue + elif side == 2 and unit_class == UnitClass.APC: + unit_type = default_apc_unit_blue + elif side == 2 and unit_class == UnitClass.ATGM: + unit_type = default_atgm_unit_blue + elif side == 2 and unit_class == UnitClass.RECON: + unit_type = default_recon_unit_blue + elif side == 1 and unit_class == UnitClass.TANK: + unit_type = default_tank_unit_red + elif side == 1 and unit_class == UnitClass.IFV: + unit_type = default_ifv_unit_red + elif side == 1 and unit_class == UnitClass.APC: + unit_type = default_apc_unit_red + elif side == 1 and unit_class == UnitClass.ATGM: + unit_type = default_atgm_unit_red + elif side == 1 and unit_class == UnitClass.RECON: + unit_type = default_recon_unit_red + else: + unit_type = default_amphibious_unit + if unit_type is not None and len(vehicle_units) < max_num: + unit_id = self.game.next_unit_id() + unit_name = f"{cp_name}-{group_role}-{unit_id}" + + spread_out_heading = random.randrange(1, 360) + spread_out_position = group.position.point_from_heading( + spread_out_heading, 30 + ) + ground_unit_pos = PointWithHeading.from_point( + spread_out_position, group.position.heading + ) + + theater_unit = TheaterUnit( + unit_id, + unit_name, + unit_type.dcs_unit_type, + ground_unit_pos, + group.ground_object, + ) + vehicle_units.append(theater_unit) + def generate(self) -> None: if self.culled: return @@ -187,7 +350,6 @@ class PretenseGroundObjectGenerator(GroundObjectGenerator): for group in self.ground_object.groups: vehicle_units: list[TheaterUnit] = [] - ship_units: list[TheaterUnit] = [] # Split the different unit types to be compliant to dcs limitation for unit in group.units: if unit.is_static: @@ -268,20 +430,129 @@ class PretenseGroundObjectGenerator(GroundObjectGenerator): PRETENSE_GROUND_UNIT_GROUP_SIZE, ) elif unit.is_ship and unit.alive: - # All alive Ships - ship_units.append(unit) + print(f"Generating amphibious group at {unit.unit_name}") + # Attach this group to the closest naval group, if available + control_point = self.ground_object.control_point + for ( + other_cp + ) in self.game.theater.closest_friendly_control_points_to( + self.ground_object.control_point + ): + if other_cp.is_fleet: + control_point = other_cp + break + + cp_name_trimmed = "".join( + [i for i in control_point.name.lower() if i.isalnum()] + ) + is_player = True + side = ( + 2 + if self.country + == self.game.coalition_for(is_player).faction.country + else 1 + ) + + try: + number_of_supply_groups = len( + self.game.pretense_ground_supply[side][cp_name_trimmed] + ) + except KeyError: + number_of_supply_groups = 0 + self.game.pretense_ground_supply[side][cp_name_trimmed] = list() + self.game.pretense_ground_assault[side][ + cp_name_trimmed + ] = list() + + if number_of_supply_groups == 0: + # Add supply convoy + group_role = "supply" + group_name = f"{cp_name_trimmed}-{group_role}-{group.id}" + group.name = group_name + + self.generate_amphibious_unit_of_class( + UnitClass.LOGISTICS, + group, + vehicle_units, + cp_name_trimmed, + group_role, + PRETENSE_GROUND_UNIT_GROUP_SIZE, + ) + else: + # Add armor group + group_role = "assault" + group_name = f"{cp_name_trimmed}-{group_role}-{group.id}" + group.name = group_name + + self.generate_amphibious_unit_of_class( + UnitClass.TANK, + group, + vehicle_units, + cp_name_trimmed, + group_role, + PRETENSE_GROUND_UNIT_GROUP_SIZE - 4, + ) + self.generate_amphibious_unit_of_class( + UnitClass.TANK, + group, + vehicle_units, + cp_name_trimmed, + group_role, + PRETENSE_GROUND_UNIT_GROUP_SIZE - 3, + ) + self.generate_amphibious_unit_of_class( + UnitClass.ATGM, + group, + vehicle_units, + cp_name_trimmed, + group_role, + PRETENSE_GROUND_UNIT_GROUP_SIZE - 2, + ) + self.generate_amphibious_unit_of_class( + UnitClass.APC, + group, + vehicle_units, + cp_name_trimmed, + group_role, + PRETENSE_GROUND_UNIT_GROUP_SIZE - 1, + ) + self.generate_amphibious_unit_of_class( + UnitClass.IFV, + group, + vehicle_units, + cp_name_trimmed, + group_role, + PRETENSE_GROUND_UNIT_GROUP_SIZE, + ) + self.generate_amphibious_unit_of_class( + UnitClass.RECON, + group, + vehicle_units, + cp_name_trimmed, + group_role, + PRETENSE_GROUND_UNIT_GROUP_SIZE, + ) if vehicle_units: self.create_vehicle_group(group.group_name, vehicle_units) - if ship_units: - self.create_ship_group(group.group_name, ship_units) def create_vehicle_group( self, group_name: str, units: list[TheaterUnit] ) -> VehicleGroup: vehicle_group: Optional[VehicleGroup] = None + control_point = self.ground_object.control_point + for unit in self.ground_object.units: + if unit.is_ship: + # Unit is naval/amphibious. Attach this group to the closest naval group, if available. + for other_cp in self.game.theater.closest_friendly_control_points_to( + self.ground_object.control_point + ): + if other_cp.is_fleet: + control_point = other_cp + break + cp_name_trimmed = "".join( - [i for i in self.ground_object.control_point.name.lower() if i.isalnum()] + [i for i in control_point.name.lower() if i.isalnum()] ) is_player = True side = ( diff --git a/resources/units/ground_units/LARC-V.yaml b/resources/units/ground_units/LARC-V.yaml index 986ee5d8..a43c3a36 100644 --- a/resources/units/ground_units/LARC-V.yaml +++ b/resources/units/ground_units/LARC-V.yaml @@ -1,4 +1,4 @@ class: Logistics -price: 2 +price: 3 variants: - LARC-V: null + LARC-V Amphibious Cargo Vehicle: null From b36215b808065bffa87670977dab81f7b7c4605b Mon Sep 17 00:00:00 2001 From: Raffson Date: Wed, 4 Oct 2023 19:49:34 +0200 Subject: [PATCH 176/243] Fix errors due to LaserCodeRegistry move --- game/pretense/pretenseaircraftgenerator.py | 2 +- .../pretenseflightgroupconfigurator.py | 15 +++------ game/pretense/pretensemissiongenerator.py | 32 ++++++------------- 3 files changed, 15 insertions(+), 34 deletions(-) diff --git a/game/pretense/pretenseaircraftgenerator.py b/game/pretense/pretenseaircraftgenerator.py index 5157e3c6..85a29793 100644 --- a/game/pretense/pretenseaircraftgenerator.py +++ b/game/pretense/pretenseaircraftgenerator.py @@ -21,8 +21,8 @@ from game.ato.starttype import StartType from game.coalition import Coalition from game.data.weapons import WeaponType from game.dcs.aircrafttype import AircraftType +from game.lasercodes.lasercoderegistry import LaserCodeRegistry from game.missiongenerator.aircraft.flightdata import FlightData -from game.missiongenerator.lasercoderegistry import LaserCodeRegistry from game.missiongenerator.missiondata import MissionData from game.pretense.pretenseflightgroupconfigurator import ( PretenseFlightGroupConfigurator, diff --git a/game/pretense/pretenseflightgroupconfigurator.py b/game/pretense/pretenseflightgroupconfigurator.py index b6a6c7a6..18a75981 100644 --- a/game/pretense/pretenseflightgroupconfigurator.py +++ b/game/pretense/pretenseflightgroupconfigurator.py @@ -1,32 +1,27 @@ from __future__ import annotations -import logging from datetime import datetime from typing import Any, Optional, TYPE_CHECKING from dcs import Mission -from dcs.flyingunit import FlyingUnit -from dcs.unit import Skill from dcs.unitgroup import FlyingGroup from game.ato import Flight, FlightType from game.data.weapons import Pylon +from game.lasercodes.lasercoderegistry import LaserCodeRegistry +from game.missiongenerator.aircraft.aircraftbehavior import AircraftBehavior +from game.missiongenerator.aircraft.aircraftpainter import AircraftPainter +from game.missiongenerator.aircraft.flightdata import FlightData from game.missiongenerator.aircraft.flightgroupconfigurator import ( FlightGroupConfigurator, ) -from game.missiongenerator.lasercoderegistry import LaserCodeRegistry +from game.missiongenerator.aircraft.waypoints import WaypointGenerator from game.missiongenerator.missiondata import MissionData from game.radio.radios import RadioRegistry from game.radio.tacan import ( TacanRegistry, ) from game.runways import RunwayData -from game.squadrons import Pilot -from game.missiongenerator.aircraft.aircraftbehavior import AircraftBehavior -from game.missiongenerator.aircraft.aircraftpainter import AircraftPainter -from game.missiongenerator.aircraft.flightdata import FlightData -from game.missiongenerator.aircraft.waypoints import WaypointGenerator -from game.theater import Fob if TYPE_CHECKING: from game import Game diff --git a/game/pretense/pretensemissiongenerator.py b/game/pretense/pretensemissiongenerator.py index 8894aaf3..79bf3eec 100644 --- a/game/pretense/pretensemissiongenerator.py +++ b/game/pretense/pretensemissiongenerator.py @@ -1,12 +1,10 @@ from __future__ import annotations -import logging from datetime import datetime from pathlib import Path -from typing import TYPE_CHECKING, cast, List +from typing import TYPE_CHECKING import dcs.lua -from dataclasses import field from dcs import Mission, Point from dcs.coalition import Coalition from dcs.countries import ( @@ -14,22 +12,8 @@ from dcs.countries import ( CombinedJointTaskForcesBlue, CombinedJointTaskForcesRed, ) -from dcs.task import OptReactOnThreat -from game.atcdata import AtcData -from game.dcs.beacons import Beacons - -from game.naming import namegen -from game.radio.radios import RadioFrequency, RadioRegistry, MHz -from game.radio.tacan import TacanRegistry -from game.theater import Airfield -from game.theater.bullseye import Bullseye -from game.unitmap import UnitMap -from game.pretense.pretenseaircraftgenerator import PretenseAircraftGenerator -from game.missiongenerator.briefinggenerator import ( - BriefingGenerator, - MissionInfoGenerator, -) +from game.lasercodes.lasercoderegistry import LaserCodeRegistry from game.missiongenerator.convoygenerator import ConvoyGenerator from game.missiongenerator.environmentgenerator import EnvironmentGenerator from game.missiongenerator.flotgenerator import FlotGenerator @@ -37,18 +21,20 @@ from game.missiongenerator.forcedoptionsgenerator import ForcedOptionsGenerator from game.missiongenerator.frontlineconflictdescription import ( FrontLineConflictDescription, ) -from game.missiongenerator.kneeboard import KneeboardGenerator -from game.missiongenerator.lasercoderegistry import LaserCodeRegistry from game.missiongenerator.missiondata import MissionData from game.missiongenerator.tgogenerator import TgoGenerator +from game.missiongenerator.visualsgenerator import VisualsGenerator +from game.naming import namegen +from game.pretense.pretenseaircraftgenerator import PretenseAircraftGenerator +from game.radio.radios import RadioRegistry +from game.radio.tacan import TacanRegistry +from game.theater.bullseye import Bullseye +from game.unitmap import UnitMap from .pretenseluagenerator import PretenseLuaGenerator from .pretensetgogenerator import PretenseTgoGenerator from .pretensetriggergenerator import PretenseTriggerGenerator -from game.missiongenerator.visualsgenerator import VisualsGenerator -from ..ato import Flight from ..ato.airtaaskingorder import AirTaskingOrder from ..missiongenerator import MissionGenerator -from ..radio.TacanContainer import TacanContainer if TYPE_CHECKING: from game import Game From 91a9a9b88fca4678fbcc8f017cc5194a33374278 Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Mon, 9 Oct 2023 10:43:25 +0300 Subject: [PATCH 177/243] Moved SAM site presets from presets.defenses.sam to presets.defenses.red/blue --- game/pretense/pretenseluagenerator.py | 4 +- resources/plugins/pretense/init_header.lua | 128 +++++++++++++++------ 2 files changed, 94 insertions(+), 38 deletions(-) diff --git a/game/pretense/pretenseluagenerator.py b/game/pretense/pretenseluagenerator.py index af97601b..d29880f0 100644 --- a/game/pretense/pretenseluagenerator.py +++ b/game/pretense/pretenseluagenerator.py @@ -74,7 +74,9 @@ class PretenseLuaGenerator(LuaGenerator): preset: str, cp_side_str: str, cp_name_trimmed: str ) -> str: lua_string_zones = ( - " presets.defenses.sam." + " presets.defenses." + + cp_side_str + + "." + preset + ":extend({ name='" + cp_name_trimmed diff --git a/resources/plugins/pretense/init_header.lua b/resources/plugins/pretense/init_header.lua index a8585391..c73f5444 100644 --- a/resources/plugins/pretense/init_header.lua +++ b/resources/plugins/pretense/init_header.lua @@ -514,34 +514,34 @@ presets = { type='defense', template='shorad-red', }), - sam = Preset:new({ - display = 'SAM', - cost=3000, - type='defense', - template='sam-red', + sa2 = Preset:new({ + display = 'SAM', + cost=3000, + type='defense', + template='sa2', }), sa10 = Preset:new({ - display = 'SAM', - cost=3000, - type='defense', + display = 'SAM', + cost=3000, + type='defense', template='sa10', }), sa5 = Preset:new({ - display = 'SAM', - cost=3000, - type='defense', + display = 'SAM', + cost=3000, + type='defense', template='sa5', }), sa3 = Preset:new({ - display = 'SAM', - cost=3000, - type='defense', + display = 'SAM', + cost=3000, + type='defense', template='sa3', }), sa6 = Preset:new({ - display = 'SAM', - cost=3000, - type='defense', + display = 'SAM', + cost=3000, + type='defense', template='sa6', }), sa11 = Preset:new({ @@ -550,42 +550,96 @@ presets = { type='defense', template='sa11', }), + hawk = Preset:new({ + display = 'SAM', + cost=3000, + type='defense', + template='hawk', + }), + patriot = Preset:new({ + display = 'SAM', + cost=3000, + type='defense', + template='patriot', + }), + nasams = Preset:new({ + display = 'SAM', + cost=3000, + type='defense', + template='nasams', + }), redShipGroup = Preset:new({ - display = 'SAM', - cost=3000, - type='defense', + display = 'SAM', + cost=3000, + type='defense', template='redShipGroup', }) }, blue = { infantry = Preset:new({ - display = 'Infantry', - cost=2000, - type='defense', + display = 'Infantry', + cost=2000, + type='defense', template='infantry-blue', }), shorad = Preset:new({ - display = 'SAM', - cost=2500, - type='defense', + display = 'SAM', + cost=2500, + type='defense', template='shorad-blue', }), - sam = Preset:new({ - display = 'SAM', - cost=3000, - type='defense', - template='sam-blue', + sa2 = Preset:new({ + display = 'SAM', + cost=3000, + type='defense', + template='sa2', + }), + sa10 = Preset:new({ + display = 'SAM', + cost=3000, + type='defense', + template='sa10', + }), + sa5 = Preset:new({ + display = 'SAM', + cost=3000, + type='defense', + template='sa5', + }), + sa3 = Preset:new({ + display = 'SAM', + cost=3000, + type='defense', + template='sa3', + }), + sa6 = Preset:new({ + display = 'SAM', + cost=3000, + type='defense', + template='sa6', + }), + sa11 = Preset:new({ + display = 'SAM', + cost=3000, + type='defense', + template='sa11', + }), + hawk = Preset:new({ + display = 'SAM', + cost=3000, + type='defense', + template='hawk', }), patriot = Preset:new({ - display = 'SAM', - cost=3000, - type='defense', + display = 'SAM', + cost=3000, + type='defense', template='patriot', }), nasams = Preset:new({ - display = 'SAM', - cost=3000, - type='defense', + display = 'SAM', + cost=3000, + type='defense', template='nasams', }), blueShipGroup = Preset:new({ From b6449a7056d0fb0941b6af2faf44eedd0657f34b Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Mon, 9 Oct 2023 10:51:13 +0300 Subject: [PATCH 178/243] In the event of too many channel users, fail gracefully by reusing a random channel instead of always the previous one. --- game/radio/radios.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/game/radio/radios.py b/game/radio/radios.py index cf02f040..85d67d0f 100644 --- a/game/radio/radios.py +++ b/game/radio/radios.py @@ -405,15 +405,19 @@ class RadioRegistry: already allocated. """ try: + while_count = 0 while (channel := random_frequency(radio)) in self.allocated_channels: + while_count += 1 + if while_count > 1000: + raise StopIteration pass self.reserve(channel) return channel except StopIteration: # In the event of too many channel users, fail gracefully by reusing - # the last channel. + # a channel. # https://github.com/dcs-liberation/dcs_liberation/issues/598 - channel = radio.last_channel + channel = random_frequency(radio) logging.warning( f"No more free channels for {radio.name}. Reusing {channel}." ) From 85db8c908e2aa5d6033d1664ea4195e7eebf6bbe Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Mon, 9 Oct 2023 20:17:31 +0300 Subject: [PATCH 179/243] Restored compatibility of the Pretense generator after the timedelta -> datetime change and other changes caused by the Liberation merge/sync. --- game/ato/flightplans/pretensecargo.py | 21 ++--- game/pretense/pretenseaircraftgenerator.py | 79 ++++++++++++++----- .../pretenseflightgroupconfigurator.py | 46 +++++++---- game/pretense/pretenseflightgroupspawner.py | 2 +- game/pretense/pretensemissiongenerator.py | 3 +- 5 files changed, 102 insertions(+), 49 deletions(-) diff --git a/game/ato/flightplans/pretensecargo.py b/game/ato/flightplans/pretensecargo.py index 44c1ba02..4022139e 100644 --- a/game/ato/flightplans/pretensecargo.py +++ b/game/ato/flightplans/pretensecargo.py @@ -3,7 +3,7 @@ from __future__ import annotations import random from collections.abc import Iterator from dataclasses import dataclass -from datetime import timedelta +from datetime import datetime from typing import TYPE_CHECKING, Type from game.utils import feet @@ -31,16 +31,20 @@ class PretenseCargoFlightPlan(StandardFlightPlan[FerryLayout]): def tot_waypoint(self) -> FlightWaypoint: return self.layout.arrival - def tot_for_waypoint(self, waypoint: FlightWaypoint) -> timedelta | None: + def tot_for_waypoint(self, waypoint: FlightWaypoint) -> datetime | None: # TOT planning isn't really useful for ferries. They're behind the front # lines so no need to wait for escorts or for other missions to complete. return None - def depart_time_for_waypoint(self, waypoint: FlightWaypoint) -> timedelta | None: + def depart_time_for_waypoint(self, waypoint: FlightWaypoint) -> datetime | None: return None @property - def mission_departure_time(self) -> timedelta: + def mission_begin_on_station_time(self) -> datetime | None: + return None + + @property + def mission_departure_time(self) -> datetime: return self.package.time_over_target @@ -77,14 +81,14 @@ class Builder(IBuilder[PretenseCargoFlightPlan, FerryLayout]): offmap_heading, PRETENSE_CARGO_FLIGHT_DISTANCE ) - altitude_is_agl = self.flight.unit_type.dcs_unit_type.helicopter + altitude_is_agl = self.flight.is_helo altitude = ( - feet(1500) + feet(self.coalition.game.settings.heli_cruise_alt_agl) if altitude_is_agl else self.flight.unit_type.preferred_patrol_altitude ) - builder = WaypointBuilder(self.flight, self.coalition) + builder = WaypointBuilder(self.flight) ferry_layout = FerryLayout( departure=builder.join(offmap_transport_spawn), nav_to=builder.nav_path( @@ -101,8 +105,7 @@ class Builder(IBuilder[PretenseCargoFlightPlan, FerryLayout]): ferry_layout.departure = builder.join(offmap_transport_spawn) ferry_layout.nav_to.append(builder.join(offmap_transport_spawn)) ferry_layout.nav_from.append(builder.join(offmap_transport_spawn)) - print(ferry_layout) return ferry_layout - def build(self) -> PretenseCargoFlightPlan: + def build(self, dump_debug_info: bool = False) -> PretenseCargoFlightPlan: return PretenseCargoFlightPlan(self.flight, self.layout()) diff --git a/game/pretense/pretenseaircraftgenerator.py b/game/pretense/pretenseaircraftgenerator.py index 85a29793..67efe309 100644 --- a/game/pretense/pretenseaircraftgenerator.py +++ b/game/pretense/pretenseaircraftgenerator.py @@ -395,7 +395,7 @@ class PretenseAircraftGenerator: ) print( - f"Generated flight for {flight_type} flying {squadron.aircraft.name} at {squadron.location.name}" + f"Generated flight for {flight_type} flying {squadron.aircraft.display_name} at {squadron.location.name}" ) ato.add_package(package) return @@ -442,7 +442,7 @@ class PretenseAircraftGenerator: divert=cp, ) print( - f"Generated flight for {flight_type} flying {squadron.aircraft.name} at {squadron.location.name}" + f"Generated flight for {flight_type} flying {squadron.aircraft.display_name} at {squadron.location.name}" ) package.add_flight(flight) @@ -483,7 +483,7 @@ class PretenseAircraftGenerator: divert=cp, ) print( - f"Generated flight for {flight_type} flying {squadron.aircraft.name} at {squadron.location.name}" + f"Generated flight for {flight_type} flying {squadron.aircraft.display_name} at {squadron.location.name}" ) package.add_flight(flight) @@ -524,7 +524,7 @@ class PretenseAircraftGenerator: divert=cp, ) print( - f"Generated flight for {flight_type} flying {squadron.aircraft.name} at {squadron.location.name}" + f"Generated flight for {flight_type} flying {squadron.aircraft.display_name} at {squadron.location.name}" ) package.add_flight(flight) @@ -582,11 +582,11 @@ class PretenseAircraftGenerator: StartType.COLD, divert=cp, ) - for roster_pilot in flight.roster.pilots: - if roster_pilot is not None: - roster_pilot.player = True + for roster_pilot in flight.roster.members: + if roster_pilot.pilot is not None: + roster_pilot.pilot.player = True print( - f"Generated flight for {squadron.primary_task} flying {squadron.aircraft.name} at {squadron.location.name}. Pilot client count: {flight.client_count}" + f"Generated flight for {squadron.primary_task} flying {squadron.aircraft.display_name} at {squadron.location.name}. Pilot client count: {flight.client_count}" ) package.add_flight(flight) @@ -720,7 +720,9 @@ class PretenseAircraftGenerator: flight.departure, flight ) logging.info( - f"Generating flight in {flight.coalition.faction.name} package {flight.squadron.aircraft} {flight.flight_type} for target: {package.target.name}, departure: {flight.from_cp.name}" + f"Generating flight in {flight.coalition.faction.name} package" + f" {flight.squadron.aircraft} {flight.flight_type} for target: {package.target.name}," + f" departure: {flight.departure.name}" ) if flight.alive: @@ -761,45 +763,81 @@ class PretenseAircraftGenerator: self.ground_spawns, self.mission_data, ).create_flight_group() - if flight.flight_type == FlightType.CAS: + + control_points_to_scan = ( + list(self.game.theater.closest_opposing_control_points()) + + self.game.theater.controlpoints + ) + + if ( + flight.flight_type == FlightType.CAS + or flight.flight_type == FlightType.TARCAP + ): for conflict in self.game.theater.conflicts(): flight.package.target = conflict break + elif flight.flight_type == FlightType.BARCAP: + for cp in control_points_to_scan: + if cp.coalition != flight.coalition or cp == flight.departure: + continue + if flight.package.target != flight.departure: + break + for mission_target in cp.ground_objects: + flight.package.target = mission_target + break elif ( flight.flight_type == FlightType.STRIKE or flight.flight_type == FlightType.BAI ): - for cp in self.game.theater.closest_opposing_control_points(): - if cp.coalition == flight.coalition: + for cp in control_points_to_scan: + if cp.coalition == flight.coalition or cp == flight.departure: continue + if flight.package.target != flight.departure: + break for mission_target in cp.ground_objects: flight.package.target = mission_target + break elif ( flight.flight_type == FlightType.OCA_RUNWAY or flight.flight_type == FlightType.OCA_AIRCRAFT ): - for cp in self.game.theater.controlpoints: - if cp.coalition == flight.coalition or not isinstance(cp, Airfield): + for cp in control_points_to_scan: + if ( + cp.coalition == flight.coalition + or not isinstance(cp, Airfield) + or cp == flight.departure + ): continue flight.package.target = cp - elif flight.flight_type == FlightType.DEAD: - for cp in self.game.theater.controlpoints: - if cp.coalition == flight.coalition: + break + elif ( + flight.flight_type == FlightType.DEAD + or flight.flight_type == FlightType.SEAD + ): + for cp in control_points_to_scan: + if cp.coalition == flight.coalition or cp == flight.departure: continue + if flight.package.target != flight.departure: + break for ground_object in cp.ground_objects: is_ewr = isinstance(ground_object, EwrGroundObject) is_sam = isinstance(ground_object, SamGroundObject) if is_ewr or is_sam: flight.package.target = ground_object + break elif flight.flight_type == FlightType.AIR_ASSAULT: - for cp in self.game.theater.closest_opposing_control_points(): - if cp.coalition == flight.coalition: + for cp in control_points_to_scan: + if cp.coalition == flight.coalition or cp == flight.departure: continue if flight.is_hercules: if cp.coalition == flight.coalition or not isinstance(cp, Airfield): continue flight.package.target = cp + break + + now = self.game.conditions.start_time + flight.package.set_tot_asap(now) logging.info( f"Configuring flight {group.name} {flight.squadron.aircraft} {flight.flight_type}, number of players: {flight.client_count}" @@ -812,7 +850,6 @@ class PretenseAircraftGenerator: self.time, self.radio_registry, self.tacan_registy, - self.laser_code_registry, self.mission_data, dynamic_runways, self.use_client, @@ -832,7 +869,7 @@ class PretenseAircraftGenerator: or flight.client_count and ( not self.need_ecm - or flight.loadout.has_weapon_of_type(WeaponType.JAMMER) + or flight.any_member_has_weapon_of_type(WeaponType.JAMMER) ) ): self.ewrj_package_dict[id(flight.package)].append(group) diff --git a/game/pretense/pretenseflightgroupconfigurator.py b/game/pretense/pretenseflightgroupconfigurator.py index 18a75981..10bc45a4 100644 --- a/game/pretense/pretenseflightgroupconfigurator.py +++ b/game/pretense/pretenseflightgroupconfigurator.py @@ -3,14 +3,17 @@ from __future__ import annotations from datetime import datetime from typing import Any, Optional, TYPE_CHECKING -from dcs import Mission +from dcs import Mission, Point +from dcs.flyingunit import FlyingUnit from dcs.unitgroup import FlyingGroup from game.ato import Flight, FlightType +from game.ato.flightmember import FlightMember from game.data.weapons import Pylon from game.lasercodes.lasercoderegistry import LaserCodeRegistry from game.missiongenerator.aircraft.aircraftbehavior import AircraftBehavior from game.missiongenerator.aircraft.aircraftpainter import AircraftPainter +from game.missiongenerator.aircraft.bingoestimator import BingoEstimator from game.missiongenerator.aircraft.flightdata import FlightData from game.missiongenerator.aircraft.flightgroupconfigurator import ( FlightGroupConfigurator, @@ -37,7 +40,6 @@ class PretenseFlightGroupConfigurator(FlightGroupConfigurator): time: datetime, radio_registry: RadioRegistry, tacan_registry: TacanRegistry, - laser_code_registry: LaserCodeRegistry, mission_data: MissionData, dynamic_runways: dict[str, RunwayData], use_client: bool, @@ -50,7 +52,6 @@ class PretenseFlightGroupConfigurator(FlightGroupConfigurator): time, radio_registry, tacan_registry, - laser_code_registry, mission_data, dynamic_runways, use_client, @@ -63,7 +64,6 @@ class PretenseFlightGroupConfigurator(FlightGroupConfigurator): self.time = time self.radio_registry = radio_registry self.tacan_registry = tacan_registry - self.laser_code_registry = laser_code_registry self.mission_data = mission_data self.dynamic_runways = dynamic_runways self.use_client = use_client @@ -72,12 +72,12 @@ class PretenseFlightGroupConfigurator(FlightGroupConfigurator): AircraftBehavior(self.flight.flight_type).apply_to(self.flight, self.group) AircraftPainter(self.flight, self.group).apply_livery() self.setup_props() - self.setup_payload() + self.setup_payloads() self.setup_fuel() flight_channel = self.setup_radios() laser_codes: list[Optional[int]] = [] - for unit, pilot in zip(self.group.units, self.flight.roster.pilots): + for unit, pilot in zip(self.group.units, self.flight.roster.members): self.configure_flight_member(unit, pilot, laser_codes) divert = None @@ -90,12 +90,21 @@ class PretenseFlightGroupConfigurator(FlightGroupConfigurator): self.flight, self.group, self.mission, - self.game.conditions.start_time, self.time, self.game.settings, self.mission_data, ).create_waypoints() + divert_position: Point | None = None + if self.flight.divert is not None: + divert_position = self.flight.divert.position + bingo_estimator = BingoEstimator( + self.flight.unit_type.fuel_consumption, + self.flight.arrival.position, + divert_position, + self.flight.flight_plan.waypoints, + ) + self.group.uncontrolled = False return FlightData( @@ -105,7 +114,7 @@ class PretenseFlightGroupConfigurator(FlightGroupConfigurator): flight_type=self.flight.flight_type, units=self.group.units, size=len(self.group.units), - friendly=self.flight.from_cp.captured, + friendly=self.flight.departure.captured, departure_delay=mission_start_time, departure=self.flight.departure.active_runway( self.game.theater, self.game.conditions, self.dynamic_runways @@ -116,21 +125,26 @@ class PretenseFlightGroupConfigurator(FlightGroupConfigurator): divert=divert, waypoints=waypoints, intra_flight_channel=flight_channel, - bingo_fuel=self.flight.flight_plan.bingo_fuel, - joker_fuel=self.flight.flight_plan.joker_fuel, + bingo_fuel=bingo_estimator.estimate_bingo(), + joker_fuel=bingo_estimator.estimate_joker(), custom_name=self.flight.custom_name, laser_codes=laser_codes, ) - def setup_payload(self) -> None: - for p in self.group.units: - p.pylons.clear() + def setup_payloads(self) -> None: + for unit, member in zip(self.group.units, self.flight.iter_members()): + self.setup_payload(unit, member) + + def setup_payload(self, unit: FlyingUnit, member: FlightMember) -> None: + unit.pylons.clear() + + loadout = member.loadout if self.flight.flight_type == FlightType.SEAD: - self.flight.loadout = self.flight.loadout.default_for_task_and_aircraft( + loadout = member.loadout.default_for_task_and_aircraft( FlightType.SEAD_SWEEP, self.flight.unit_type.dcs_unit_type ) - loadout = self.flight.loadout + if self.game.settings.restrict_weapons_by_date: loadout = loadout.degrade_for_date(self.flight.unit_type, self.game.date) @@ -138,4 +152,4 @@ class PretenseFlightGroupConfigurator(FlightGroupConfigurator): if weapon is None: continue pylon = Pylon.for_aircraft(self.flight.unit_type, pylon_number) - pylon.equip(self.group, weapon) + pylon.equip(unit, weapon) diff --git a/game/pretense/pretenseflightgroupspawner.py b/game/pretense/pretenseflightgroupspawner.py index 4ddbb6e8..cdb10a23 100644 --- a/game/pretense/pretenseflightgroupspawner.py +++ b/game/pretense/pretenseflightgroupspawner.py @@ -118,7 +118,7 @@ class PretenseFlightGroupSpawner(FlightGroupSpawner): raise RuntimeError( f"Cannot spawn fixed-wing aircraft at {cp} because of insufficient ground spawn slots." ) - pilot_count = len(self.flight.roster.pilots) + pilot_count = len(self.flight.roster.members) if ( not is_heli and self.flight.roster.player_count != pilot_count diff --git a/game/pretense/pretensemissiongenerator.py b/game/pretense/pretensemissiongenerator.py index 79bf3eec..ae08b766 100644 --- a/game/pretense/pretensemissiongenerator.py +++ b/game/pretense/pretensemissiongenerator.py @@ -148,7 +148,7 @@ class PretenseMissionGenerator(MissionGenerator): player_cp = front_line.blue_cp enemy_cp = front_line.red_cp conflict = FrontLineConflictDescription.frontline_cas_conflict( - front_line, self.game.theater, self.game.settings + front_line, self.game.theater ) # Generate frontline ops player_gp = self.game.ground_planners[player_cp.id].units_per_cp[ @@ -166,7 +166,6 @@ class PretenseMissionGenerator(MissionGenerator): self.unit_map, self.radio_registry, self.mission_data, - self.laser_code_registry, ) ground_conflict_gen.generate() From 64e425012c9ce8bc908206178eaeaf757b2ac46d Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Sun, 15 Oct 2023 18:16:58 +0300 Subject: [PATCH 180/243] Implemented generating runway zones at airports in Pretense. --- game/pretense/pretensetriggergenerator.py | 44 +++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/game/pretense/pretensetriggergenerator.py b/game/pretense/pretensetriggergenerator.py index d01fdf2a..4eeae332 100644 --- a/game/pretense/pretensetriggergenerator.py +++ b/game/pretense/pretensetriggergenerator.py @@ -54,6 +54,8 @@ TRIGGER_RADIUS_PRETENSE_TGO = 500 TRIGGER_RADIUS_PRETENSE_SUPPLY = 500 TRIGGER_RADIUS_PRETENSE_HELI = 1000 TRIGGER_RADIUS_PRETENSE_CARRIER = 50000 +TRIGGER_RUNWAY_LENGTH_PRETENSE = 2500 +TRIGGER_RUNWAY_WIDTH_PRETENSE = 400 class Silence(Option): @@ -212,6 +214,48 @@ class PretenseTriggerGenerator: color=zone_color, ) break + airfields = [ + cp for cp in self.game.theater.controlpoints if isinstance(cp, Airfield) + ] + for airfield in airfields: + cp_airport = self.mission.terrain.airport_by_id(airfield.airport.id) + cp_name_trimmed = "".join( + [i for i in cp_airport.name.lower() if i.isalnum()] + ) + zone_color = {1: 0.0, 2: 1.0, 3: 0.5, 4: 0.15} + if cp_airport is None: + raise RuntimeError( + f"Could not find {airfield.airport.name} in the mission" + ) + for runway in cp_airport.runways: + runway_end_1 = cp_airport.position.point_from_heading( + runway.heading, TRIGGER_RUNWAY_LENGTH_PRETENSE / 2 + ) + runway_end_2 = cp_airport.position.point_from_heading( + runway.heading + 180, TRIGGER_RUNWAY_LENGTH_PRETENSE / 2 + ) + runway_verticies = [ + runway_end_1.point_from_heading( + runway.heading - 90, TRIGGER_RUNWAY_WIDTH_PRETENSE / 2 + ), + runway_end_1.point_from_heading( + runway.heading + 90, TRIGGER_RUNWAY_WIDTH_PRETENSE / 2 + ), + runway_end_2.point_from_heading( + runway.heading + 90, TRIGGER_RUNWAY_WIDTH_PRETENSE / 2 + ), + runway_end_2.point_from_heading( + runway.heading - 90, TRIGGER_RUNWAY_WIDTH_PRETENSE / 2 + ), + ] + trigger_zone = self.mission.triggers.add_triggerzone_quad( + cp_airport.position, + runway_verticies, + hidden=False, + name=f"{cp_name_trimmed}-runway-{runway.id}", + color=zone_color, + ) + break def generate(self) -> None: player_coalition = "blue" From 2808d0f72cc642bd62cb1a44ac50a22e8d894455 Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Sun, 22 Oct 2023 21:45:36 +0300 Subject: [PATCH 181/243] Fixed the bug of not generating opposing force ground unit groups at Pretense zones. Added the country name to ground vehicle group names to avoid duplicates. --- game/pretense/pretensetgogenerator.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/game/pretense/pretensetgogenerator.py b/game/pretense/pretensetgogenerator.py index d9363a29..bab1f089 100644 --- a/game/pretense/pretensetgogenerator.py +++ b/game/pretense/pretensetgogenerator.py @@ -347,6 +347,9 @@ class PretenseGroundObjectGenerator(GroundObjectGenerator): cp_name_trimmed = "".join( [i for i in self.ground_object.control_point.name.lower() if i.isalnum()] ) + country_name_trimmed = "".join( + [i for i in self.country.shortname.lower() if i.isalnum()] + ) for group in self.ground_object.groups: vehicle_units: list[TheaterUnit] = [] @@ -355,7 +358,7 @@ class PretenseGroundObjectGenerator(GroundObjectGenerator): if unit.is_static: # Add supply convoy group_role = "supply" - group_name = f"{cp_name_trimmed}-{group_role}-{group.id}" + group_name = f"{cp_name_trimmed}-{country_name_trimmed}-{group_role}-{group.id}" group.name = group_name self.generate_ground_unit_of_class( @@ -369,7 +372,7 @@ class PretenseGroundObjectGenerator(GroundObjectGenerator): elif unit.is_vehicle and unit.alive: # Add armor group group_role = "assault" - group_name = f"{cp_name_trimmed}-{group_role}-{group.id}" + group_name = f"{cp_name_trimmed}-{country_name_trimmed}-{group_role}-{group.id}" group.name = group_name self.generate_ground_unit_of_class( @@ -430,7 +433,6 @@ class PretenseGroundObjectGenerator(GroundObjectGenerator): PRETENSE_GROUND_UNIT_GROUP_SIZE, ) elif unit.is_ship and unit.alive: - print(f"Generating amphibious group at {unit.unit_name}") # Attach this group to the closest naval group, if available control_point = self.ground_object.control_point for ( @@ -467,7 +469,7 @@ class PretenseGroundObjectGenerator(GroundObjectGenerator): if number_of_supply_groups == 0: # Add supply convoy group_role = "supply" - group_name = f"{cp_name_trimmed}-{group_role}-{group.id}" + group_name = f"{cp_name_trimmed}-{country_name_trimmed}-{group_role}-{group.id}" group.name = group_name self.generate_amphibious_unit_of_class( @@ -481,7 +483,7 @@ class PretenseGroundObjectGenerator(GroundObjectGenerator): else: # Add armor group group_role = "assault" - group_name = f"{cp_name_trimmed}-{group_role}-{group.id}" + group_name = f"{cp_name_trimmed}-{country_name_trimmed}-{group_role}-{group.id}" group.name = group_name self.generate_amphibious_unit_of_class( @@ -578,7 +580,7 @@ class PretenseGroundObjectGenerator(GroundObjectGenerator): self.set_alarm_state(vehicle_group) GroundForcePainter(faction, vehicle_group.units[0]).apply_livery() - group_role = group_name.split("-")[1] + group_role = group_name.split("-")[2] if group_role == "supply": self.game.pretense_ground_supply[side][cp_name_trimmed].append( f"{vehicle_group.name}" @@ -746,6 +748,7 @@ class PretenseTgoGenerator(TgoGenerator): is_fob_structure=ground_object.is_fob_structure, task=ground_object.task, ) + new_ground_object.groups = ground_object.groups generator = PretenseGroundObjectGenerator( new_ground_object, country, self.game, self.m, self.unit_map ) @@ -760,6 +763,7 @@ class PretenseTgoGenerator(TgoGenerator): control_point=ground_object.control_point, task=ground_object.task, ) + new_ground_object.groups = ground_object.groups generator = PretenseGroundObjectGenerator( new_ground_object, country, self.game, self.m, self.unit_map ) From 5b60d64437953d19881b8edb8b9302130af74ebb Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Sun, 22 Oct 2023 21:46:30 +0300 Subject: [PATCH 182/243] Reduced the number of cargo planes to 2 per side. --- game/pretense/pretenseaircraftgenerator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/game/pretense/pretenseaircraftgenerator.py b/game/pretense/pretenseaircraftgenerator.py index 67efe309..1bfe7b8b 100644 --- a/game/pretense/pretenseaircraftgenerator.py +++ b/game/pretense/pretenseaircraftgenerator.py @@ -57,7 +57,7 @@ PRETENSE_BARCAP_FLIGHTS_PER_CP = 2 PRETENSE_AI_AIRCRAFT_PER_FLIGHT = 2 PRETENSE_AI_AWACS_PER_FLIGHT = 1 PRETENSE_AI_TANKERS_PER_FLIGHT = 1 -PRETENSE_AI_CARGO_PLANES_PER_SIDE = 8 +PRETENSE_AI_CARGO_PLANES_PER_SIDE = 2 PRETENSE_PLAYER_AIRCRAFT_PER_FLIGHT = 1 PRETENSE_PLAYER_FLIGHTS_PER_TYPE = 2 From 72d1c105a7b690e45689c25a0b074e68114d591b Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Sun, 22 Oct 2023 21:59:21 +0300 Subject: [PATCH 183/243] Implemented a Pretense settings page. --- game/settings/settings.py | 24 ++++++++++++++++++++++++ qt_ui/uiconstants.py | 3 ++- qt_ui/windows/QLiberationWindow.py | 2 +- 3 files changed, 27 insertions(+), 2 deletions(-) diff --git a/game/settings/settings.py b/game/settings/settings.py index 4a9ac57e..10ea5eee 100644 --- a/game/settings/settings.py +++ b/game/settings/settings.py @@ -49,6 +49,8 @@ FLIGHT_PLANNER_AUTOMATION = "Flight Planner Automation" CAMPAIGN_DOCTRINE_PAGE = "Campaign Doctrine" DOCTRINE_DISTANCES_SECTION = "Doctrine distances" +PRETENSE_PAGE = "Pretense" + MISSION_GENERATOR_PAGE = "Mission Generator" GAMEPLAY_SECTION = "Gameplay" @@ -979,6 +981,28 @@ class Settings: "if the start-up type was manually changed to 'In-Flight'." ), ) + pretense_maxdistfromfront_distance: int = bounded_int_option( + "Max distance from front (km)", + page=PRETENSE_PAGE, + section=GENERAL_SECTION, + default=130, + min=10, + max=10000, + ) + pretense_closeoverride_distance: int = bounded_int_option( + "Close override distance (km)", + page=PRETENSE_PAGE, + section=GENERAL_SECTION, + default=28, + min=5, + max=10000, + ) + pretense_do_not_generate_sead_missions: bool = boolean_option( + "Do not generate player SEAD missions", + page=PRETENSE_PAGE, + section=PERFORMANCE_SECTION, + default=False, + ) # Cheating. Not using auto settings because the same page also has buttons which do # not alter settings. diff --git a/qt_ui/uiconstants.py b/qt_ui/uiconstants.py index d431fc29..99d84386 100644 --- a/qt_ui/uiconstants.py +++ b/qt_ui/uiconstants.py @@ -32,7 +32,8 @@ def load_icons(): "./resources/ui/misc/" + get_theme_icons() + "/github.png" ) ICONS["Ukraine"] = QPixmap("./resources/ui/misc/ukraine.png") - ICONS["Pretense"] = QPixmap("./resources/ui/misc/pretense_discord.png") + ICONS["Pretense"] = QPixmap("./resources/ui/misc/pretense.png") + ICONS["Pretense_discord"] = QPixmap("./resources/ui/misc/pretense_discord.png") ICONS["Pretense_generate"] = QPixmap("./resources/ui/misc/pretense_generate.png") ICONS["Control Points"] = QPixmap( diff --git a/qt_ui/windows/QLiberationWindow.py b/qt_ui/windows/QLiberationWindow.py index 3cd88f26..e4de9ba0 100644 --- a/qt_ui/windows/QLiberationWindow.py +++ b/qt_ui/windows/QLiberationWindow.py @@ -195,7 +195,7 @@ class QLiberationWindow(QMainWindow): ) self.pretenseLinkAction = QAction("&DCS: Pretense", self) - self.pretenseLinkAction.setIcon(QIcon(CONST.ICONS["Pretense"])) + self.pretenseLinkAction.setIcon(QIcon(CONST.ICONS["Pretense_discord"])) self.pretenseLinkAction.triggered.connect( lambda: webbrowser.open_new_tab( "https://" + "discord.gg" + "/" + "PtPsb9Mpk6" From 93ab146cf537435695b3dac6dd984e55b5f65e8f Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Sun, 22 Oct 2023 22:01:00 +0300 Subject: [PATCH 184/243] Restored Retribution scripts and triggers to Pretense campaigns. Pretense progress can now be translated back to Retribution. Moved the trigger clearing from pretenseluagenerator.py to pretensemissiongenerator.py keepActive is now only enabled for airbases and carriers/LHAs, for performance reasons. --- game/pretense/pretenseluagenerator.py | 207 +++++++++++++++++++++- game/pretense/pretensemissiongenerator.py | 1 + game/pretense/pretensetriggergenerator.py | 78 +++++++- 3 files changed, 273 insertions(+), 13 deletions(-) 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: From e15aca8c5459e9cfc97bb24861174549c7e84c42 Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Sun, 22 Oct 2023 22:03:29 +0300 Subject: [PATCH 185/243] Updated pretense_compiled.lua to version 1.3.6. Implemented Pretense settings in pretense_compiled.lua Added new function moveOffRoadToPointAndAssault() to enable assault groups to drive off-road and thus avoid some of the bridges where they might get stuck. --- .../plugins/pretense/pretense_compiled.lua | 139 +++++++++++++++--- 1 file changed, 121 insertions(+), 18 deletions(-) diff --git a/resources/plugins/pretense/pretense_compiled.lua b/resources/plugins/pretense/pretense_compiled.lua index a3aaf78c..cf8bb881 100644 --- a/resources/plugins/pretense/pretense_compiled.lua +++ b/resources/plugins/pretense/pretense_compiled.lua @@ -32,6 +32,8 @@ Config.buildSpeed = Config.buildSpeed or 10 -- structure and defense build speed Config.supplyBuildSpeed = Config.supplyBuildSpeed or 85 -- supply helicopters and convoys build speed Config.missionBuildSpeedReduction = Config.missionBuildSpeedReduction or 0.12 -- reduction of build speed in case of ai missions Config.maxDistFromFront = Config.maxDistFromFront or 129640 -- max distance in meters from front after which zone is forced into low activity state (export mode) +Config.closeOverride = Config.closeOverride or 27780 -- close override distance in meters from front within which zone is never forced into low activity state +Config.disablePlayerSead = Config.disablePlayerSead or false Config.missions = Config.missions or {} @@ -503,6 +505,8 @@ end GroupMonitor = {} do GroupMonitor.blockedDespawnTime = 10*60 --used to despawn aircraft that are stuck taxiing for some reason + GroupMonitor.blockedDespawnTimeGround = 30*60 --used to despawn ground units that are stuck en route for some reason + GroupMonitor.blockedDespawnTimeGroundAssault = 90*60 --used to despawn assault units that are stuck en route for some reason GroupMonitor.landedDespawnTime = 10 GroupMonitor.atDestinationDespawnTime = 2*60 GroupMonitor.recoveryReduction = 0.8 -- reduce recovered resource from landed missions by this amount to account for maintenance @@ -638,7 +642,13 @@ do group.state = 'enroute' group.lastStateTime = timer.getAbsTime() MissionTargetRegistry.addBaiTarget(group) - elseif timer.getAbsTime() - group.lastStateTime > GroupMonitor.blockedDespawnTime then + elseif group.product.missionType == 'assault' and timer.getAbsTime() - group.lastStateTime > GroupMonitor.blockedDespawnTimeGroundAssault then + env.info('GroupMonitor: processSurface ['..group.name..'] despawned due to blockage') + gr:destroy() + local todeliver = math.floor(group.product.cost) + z:addResource(todeliver) + return true + elseif timer.getAbsTime() - group.lastStateTime > GroupMonitor.blockedDespawnTimeGround then env.info('GroupMonitor: processSurface ['..group.name..'] despawned due to blockage') gr:destroy() local todeliver = math.floor(group.product.cost) @@ -734,7 +744,7 @@ do y = group.target.zone.point.z } - TaskExtensions.moveOnRoadToPointAndAssault(gr, tp, group.target.built) + TaskExtensions.moveOffRoadToPointAndAssault(gr, tp, group.target.built) group.isstopped = false end end @@ -2135,7 +2145,68 @@ do } group:getController():setTask(mis) end - + + function TaskExtensions.moveOffRoadToPointAndAssault(group, point, targets) + if not group or not point then return end + if not group:isExist() or group:getSize()==0 then return end + local startPos = group:getUnit(1):getPoint() + + local srx, sry = land.getClosestPointOnRoads('roads', startPos.x, startPos.z) + local erx, ery = land.getClosestPointOnRoads('roads', point.x, point.y) + + local mis = { + id='Mission', + params = { + route = { + points = { + [1] = { + type= AI.Task.WaypointType.TURNING_POINT, + x = srx, + y = sry, + speed = 1000, + action = AI.Task.VehicleFormation.DIAMOND + }, + [2] = { + type= AI.Task.WaypointType.TURNING_POINT, + x = erx, + y = ery, + speed = 1000, + action = AI.Task.VehicleFormation.DIAMOND + }, + [3] = { + type= AI.Task.WaypointType.TURNING_POINT, + x = point.x, + y = point.y, + speed = 1000, + action = AI.Task.VehicleFormation.DIAMOND + } + } + } + } + } + + for i,v in pairs(targets) do + if v.type == 'defense' then + local group = Group.getByName(v.name) + if group then + for i,v in ipairs(group:getUnits()) do + local unpos = v:getPoint() + local pnt = {x=unpos.x, y = unpos.z} + + table.insert(mis.params.route.points, { + type= AI.Task.WaypointType.TURNING_POINT, + x = pnt.x, + y = pnt.y, + speed = 10, + action = AI.Task.VehicleFormation.DIAMOND + }) + end + end + end + end + group:getController():setTask(mis) + end + function TaskExtensions.landAtPointFromAir(group, point, alt) if not group or not point then return end if not group:isExist() or group:getSize()==0 then return end @@ -2542,7 +2613,7 @@ do unit = event.initiator }) - env.info('PlayerLogistics - Hercules - '..unitName..'deployed crate with '..amount..' supplies') + env.info('PlayerLogistics - Hercules - '..unitName..' deployed crate with '..amount..' supplies') self.context:processHercCargos(unitName) self.context.hercPreparedDrops[groupId] = nil trigger.action.outTextForUnit(event.initiator:getID(), 'Crate with '..amount..' supplies deployed', 10) @@ -2577,7 +2648,7 @@ do unit = event.initiator }) - env.info('PlayerLogistics - Hercules - '..unitName..'deployed crate with '..toDrop.type) + env.info('PlayerLogistics - Hercules - '..unitName..' deployed crate with '..toDrop.type) self.context:processHercCargos(unitName) self.context.hercPreparedDrops[groupId] = nil @@ -2607,6 +2678,7 @@ do local reschedule = params.context:checkHercCargo(params.unitName, time) if not reschedule then params.context.hercTracker.cargoCheckFunctions[params.unitName] = nil + env.info('PlayerLogistics - Hercules - stopped tracking cargos of '..unitName) end return reschedule @@ -2627,9 +2699,14 @@ do table.insert(remaining, cargo) end else - env.info('PlayerLogistics - Hercules - cargo crashed') + env.info('PlayerLogistics - Hercules - cargo crashed '..tostring(cargo.supply)..' '..tostring(cargo.squad)) + if cargo.squad then + env.info('PlayerLogistics - Hercules - squad crashed '..tostring(cargo.squad.type)) + end + if cargo.unit and cargo.unit:isExist() then - trigger.action.outTextForUnit(cargo.unit:getID(), 'Cargo drop of '..cargo.unit:getPlayerName()..' crashed', 10) + local squadName = PlayerLogistics.getInfantryName(cargo.squad.type) + trigger.action.outTextForUnit(cargo.unit:getID(), 'Cargo drop of '..cargo.unit:getPlayerName()..' with '..squadName..' crashed', 10) end end end @@ -2647,13 +2724,14 @@ do local zone = ZoneCommand.getZoneOfWeapon(cargo.object) if zone then zone:addResource(cargo.supply) - cargo.object:destroy() env.info('PlayerLogistics - Hercules - '..cargo.supply..' delivered to '..zone.name) self:awardSupplyXP(cargo.lastLoaded, zone, cargo.unit, cargo.supply) end elseif cargo.squad then local pos = Utils.getPointOnSurface(cargo.object:getPoint()) + pos.y = pos.z + pos.z = nil local surface = land.getSurfaceType(pos) if surface == land.SurfaceType.LAND or surface == land.SurfaceType.ROAD or surface == land.SurfaceType.RUNWAY then local zn = ZoneCommand.getZoneOfPoint(pos) @@ -2672,13 +2750,12 @@ do else local error = self.squadTracker:spawnInfantry(self.registeredSquadGroups[cargo.squad.type], pos) if not error then - cargo.object:destroy() env.info('PlayerLogistics - Hercules - '..cargo.squad.type..' deployed') local squadName = PlayerLogistics.getInfantryName(cargo.squad.type) - trigger.action.outTextForUnit(cargo.unit:getID(), squadName..' deployed', 10) if cargo.unit and cargo.unit:isExist() and cargo.unit.getPlayerName then + trigger.action.outTextForUnit(cargo.unit:getID(), squadName..' deployed', 10) local player = cargo.unit:getPlayerName() local xp = RewardDefinitions.actions.squadDeploy @@ -2693,8 +2770,17 @@ do end end end + else + env.info('PlayerLogistics - Hercules - '..cargo.squad.type..' dropped on invalid surface '..tostring(surface)) + local cpos = cargo.object:getPoint() + env.info('PlayerLogistics - Hercules - cargo spot X:'..cpos.x..' Y:'..cpos.y..' Z:'..cpos.z) + env.info('PlayerLogistics - Hercules - surface spot X:'..pos.x..' Y:'..pos.y..' Z:'..pos.z) + local squadName = PlayerLogistics.getInfantryName(cargo.squad.type) + trigger.action.outTextForUnit(cargo.unit:getID(), 'Cargo drop of '..cargo.unit:getPlayerName()..' with '..squadName..' crashed', 10) end end + + cargo.object:destroy() end end @@ -3989,6 +4075,22 @@ do end self:refreshText() + + if self.airbaseName then + local ab = Airbase.getByName(self.airbaseName) + if ab then + if ab:autoCaptureIsOn() then ab:autoCapture(false) end + ab:setCoalition(self.side) + else + for i=1,10,1 do + local ab = Airbase.getByName(self.airbaseName..'-'..i) + if ab then + if ab:autoCaptureIsOn() then ab:autoCapture(false) end + ab:setCoalition(self.side) + end + end + end + end end function ZoneCommand:addResource(amount) @@ -4780,7 +4882,7 @@ do product.lastMission = {zoneName = zone.name} timer.scheduleFunction(function(param) local gr = Group.getByName(param.name) - TaskExtensions.moveOnRoadToPointAndAssault(gr, param.point, param.targets) + TaskExtensions.moveOffRoadToPointAndAssault(gr, param.point, param.targets) end, {name=product.name, point={ x=tgtPoint.point.x, y = tgtPoint.point.z}, targets=zone.built}, timer.getTime()+1) end end @@ -5311,7 +5413,7 @@ do product.lastMission = {zoneName = v.name} timer.scheduleFunction(function(param) local gr = Group.getByName(param.name) - TaskExtensions.moveOnRoadToPointAndAssault(gr, param.point, param.targets) + TaskExtensions.moveOffRoadToPointAndAssault(gr, param.point, param.targets) end, {name=product.name, point={ x=tgtPoint.point.x, y = tgtPoint.point.z}, targets=v.built}, timer.getTime()+1) env.info("ZoneCommand - "..product.name.." targeting "..v.name) @@ -5873,7 +5975,7 @@ end BattlefieldManager = {} do - BattlefieldManager.closeOverride = 27780 -- 15nm + BattlefieldManager.closeOverride = Config.closeOverride -- default 15nm BattlefieldManager.farOverride = Config.maxDistFromFront -- default 100nm BattlefieldManager.boostScale = {[0] = 1.0, [1]=1.0, [2]=1.0} BattlefieldManager.noRedZones = false @@ -10452,6 +10554,7 @@ do end end end + return false end function SEAD:getMissionName() @@ -12105,7 +12208,7 @@ do if toGen > 0 then local validMissions = {} for _,v in pairs(Mission.types) do - if self:canCreateMission(v) then + if timer.getAbsTime() - timer.getTime0() > 120 and self:canCreateMission(v) then table.insert(validMissions,v) end end @@ -12565,7 +12668,7 @@ do return CAS_Easy.canCreate() elseif misType == Mission.types.cas_medium then return CAS_Medium.canCreate() - elseif misType == Mission.types.sead then + elseif Config.disablePlayerSead == false and misType == Mission.types.sead then return SEAD.canCreate() elseif misType == Mission.types.dead then return DEAD.canCreate() @@ -12659,7 +12762,7 @@ do function SquadTracker:restoreInfantry(save) - Spawner.createObject(save.name, save.data.name, save.position, 2, 10, 20,{ + Spawner.createObject(save.name, save.data.name, save.position, 2, 20, 30,{ [land.SurfaceType.LAND] = true, [land.SurfaceType.ROAD] = true, [land.SurfaceType.RUNWAY] = true, @@ -12683,7 +12786,7 @@ do function SquadTracker:spawnInfantry(infantryData, position) local callsign = self:generateCallsign() if callsign then - Spawner.createObject(callsign, infantryData.name, position, 2, 10, 20,{ + Spawner.createObject(callsign, infantryData.name, position, 2, 20, 30,{ [land.SurfaceType.LAND] = true, [land.SurfaceType.ROAD] = true, [land.SurfaceType.RUNWAY] = true, @@ -12851,7 +12954,7 @@ do local p = Utils.getPointOnSurface(unPos) p.x = p.x + math.random(-5,5) p.z = p.z + math.random(-5,5) - trigger.action.smoke(p, trigger.smokeColor.Green) + trigger.action.smoke(p, trigger.smokeColor.Blue) squad.lastMarkerDeployedTime = timer.getAbsTime() end end From f4e8e30cb1be15b6eff2cee13f20f801323b3780 Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Wed, 22 Nov 2023 19:46:44 +0200 Subject: [PATCH 186/243] Implemented new options in settings: - Pretense: Extra friendly zone connections - Add connections from each zone to this many closest friendly zones, which don't have an existing supply route defined in the campaign. - Number of cargo planes per side - Number of AI SEAD flights per control point / zone - Number of AI CAS flights per control point / zone - Number of AI BAI flights per control point / zone - Number of AI Strike flights per control point / zone - Number of AI BARCAP flights per control point / zone - Number of AI aircraft per flight - Number of player flights per aircraft type at each base - Number of AI cargo planes per side Implemented CAS helo mission handling for Pretense. Implemented separate pretense_air_groups container for storing/referencing Flight objects. Tweaked the supply costs of SAM sites and Command centers. Will no longer generate player air starts at roadbases either. Restored the missing DEAD flights to Pretense. Removed spawning of frontline units and moved the JTAC spawning to pretensemissiongenerator.py --- game/game.py | 2 + game/missiongenerator/tgogenerator.py | 315 ++++++++++-------- game/pretense/pretenseaircraftgenerator.py | 112 ++++--- game/pretense/pretenseflightgroupspawner.py | 14 +- game/pretense/pretenseluagenerator.py | 64 ++-- game/pretense/pretensemissiongenerator.py | 81 +++-- game/settings/settings.py | 93 +++++- resources/plugins/pretense/init_header.lua | 30 +- .../plugins/pretense/pretense_compiled.lua | 89 +---- 9 files changed, 461 insertions(+), 339 deletions(-) diff --git a/game/game.py b/game/game.py index a5ecbd51..dfb0c2f7 100644 --- a/game/game.py +++ b/game/game.py @@ -22,6 +22,7 @@ from game.models.game_stats import GameStats from game.plugins import LuaPluginManager from game.utils import Distance from . import naming, persistency +from .ato import Flight from .ato.flighttype import FlightType from .campaignloader import CampaignAirWingConfig from .coalition import Coalition @@ -155,6 +156,7 @@ class Game: 1: {}, 2: {}, } + self.pretense_air_groups: dict[str, Flight] = {} self.on_load(game_still_initializing=True) diff --git a/game/missiongenerator/tgogenerator.py b/game/missiongenerator/tgogenerator.py index a01f33e1..ed83823e 100644 --- a/game/missiongenerator/tgogenerator.py +++ b/game/missiongenerator/tgogenerator.py @@ -290,11 +290,9 @@ class GroundObjectGenerator: # All alive Ships ship_units.append(unit) if vehicle_units: - vg = self.create_vehicle_group(group.group_name, vehicle_units) - vg.hidden_on_mfd = self.ground_object.hide_on_mfd + self.create_vehicle_group(group.group_name, vehicle_units) if ship_units: - sg = self.create_ship_group(group.group_name, ship_units) - sg.hidden_on_mfd = self.ground_object.hide_on_mfd + self.create_ship_group(group.group_name, ship_units) def create_vehicle_group( self, group_name: str, units: list[TheaterUnit] @@ -827,30 +825,45 @@ class HelipadGenerator: else: self.helipads.append(sg) - # Generate a FARP Ammo and Fuel stack for each pad - self.m.static_group( - country=country, - name=(name + "_fuel"), - _type=Fortification.FARP_Fuel_Depot, - position=pad.position.point_from_heading(helipad.heading.degrees, 35), - heading=pad.heading + 180, - ) - self.m.static_group( - country=country, - name=(name + "_ammo"), - _type=Fortification.FARP_Ammo_Dump_Coating, - position=pad.position.point_from_heading( - helipad.heading.degrees, 35 - ).point_from_heading(helipad.heading.degrees + 90, 10), - heading=pad.heading + 90, - ) - self.m.static_group( - country=country, - name=(name + "_ws"), - _type=Fortification.Windsock, - position=helipad.point_from_heading(helipad.heading.degrees + 45, 35), - heading=pad.heading, - ) + if self.game.position_culled(helipad): + cull_farp_statics = True + if self.cp.coalition.player: + for package in self.cp.coalition.ato.packages: + for flight in package.flights: + if flight.squadron.location == self.cp: + cull_farp_statics = False + break + elif flight.divert and flight.divert == self.cp: + cull_farp_statics = False + break + else: + cull_farp_statics = False + + if not cull_farp_statics: + # Generate a FARP Ammo and Fuel stack for each pad + self.m.static_group( + country=country, + name=(name + "_fuel"), + _type=Fortification.FARP_Fuel_Depot, + position=pad.position.point_from_heading(helipad.heading.degrees, 35), + heading=pad.heading + 180, + ) + self.m.static_group( + country=country, + name=(name + "_ammo"), + _type=Fortification.FARP_Ammo_Dump_Coating, + position=pad.position.point_from_heading( + helipad.heading.degrees, 35 + ).point_from_heading(helipad.heading.degrees + 90, 10), + heading=pad.heading + 90, + ) + self.m.static_group( + country=country, + name=(name + "_ws"), + _type=Fortification.Windsock, + position=helipad.point_from_heading(helipad.heading.degrees + 45, 35), + heading=pad.heading, + ) def append_helipad( self, @@ -927,61 +940,76 @@ class GroundSpawnRoadbaseGenerator: country.id ) - # Generate ammo truck/farp and fuel truck/stack for each pad - if self.game.settings.ground_start_trucks_roadbase: - self.m.vehicle_group( - country=country, - name=(name + "_fuel"), - _type=tanker_type, - position=pad.position.point_from_heading( - ground_spawn[0].heading.degrees + 90, 35 - ), - group_size=1, - heading=pad.heading + 315, - move_formation=PointAction.OffRoad, - ) - self.m.vehicle_group( - country=country, - name=(name + "_ammo"), - _type=ammo_truck_type, - position=pad.position.point_from_heading( - ground_spawn[0].heading.degrees + 90, 35 - ).point_from_heading(ground_spawn[0].heading.degrees + 180, 10), - group_size=1, - heading=pad.heading + 315, - move_formation=PointAction.OffRoad, - ) + if self.game.position_culled(ground_spawn[0]): + cull_farp_statics = True + if self.cp.coalition.player: + for package in self.cp.coalition.ato.packages: + for flight in package.flights: + if flight.squadron.location == self.cp: + cull_farp_statics = False + break + elif flight.divert and flight.divert == self.cp: + cull_farp_statics = False + break else: - self.m.static_group( - country=country, - name=(name + "_fuel"), - _type=Fortification.FARP_Fuel_Depot, - position=pad.position.point_from_heading( - ground_spawn[0].heading.degrees + 90, 35 - ), - heading=pad.heading + 270, - ) - self.m.static_group( - country=country, - name=(name + "_ammo"), - _type=Fortification.FARP_Ammo_Dump_Coating, - position=pad.position.point_from_heading( - ground_spawn[0].heading.degrees + 90, 35 - ).point_from_heading(ground_spawn[0].heading.degrees + 180, 10), - heading=pad.heading + 180, - ) - if self.game.settings.ground_start_ground_power_trucks_roadbase: - self.m.vehicle_group( - country=country, - name=(name + "_power"), - _type=power_truck_type, - position=pad.position.point_from_heading( - ground_spawn[0].heading.degrees + 90, 35 - ).point_from_heading(ground_spawn[0].heading.degrees + 180, 20), - group_size=1, - heading=pad.heading + 315, - move_formation=PointAction.OffRoad, - ) + cull_farp_statics = False + + if not cull_farp_statics: + # Generate ammo truck/farp and fuel truck/stack for each pad + if self.game.settings.ground_start_trucks_roadbase: + self.m.vehicle_group( + country=country, + name=(name + "_fuel"), + _type=tanker_type, + position=pad.position.point_from_heading( + ground_spawn[0].heading.degrees + 90, 35 + ), + group_size=1, + heading=pad.heading + 315, + move_formation=PointAction.OffRoad, + ) + self.m.vehicle_group( + country=country, + name=(name + "_ammo"), + _type=ammo_truck_type, + position=pad.position.point_from_heading( + ground_spawn[0].heading.degrees + 90, 35 + ).point_from_heading(ground_spawn[0].heading.degrees + 180, 10), + group_size=1, + heading=pad.heading + 315, + move_formation=PointAction.OffRoad, + ) + else: + self.m.static_group( + country=country, + name=(name + "_fuel"), + _type=Fortification.FARP_Fuel_Depot, + position=pad.position.point_from_heading( + ground_spawn[0].heading.degrees + 90, 35 + ), + heading=pad.heading + 270, + ) + self.m.static_group( + country=country, + name=(name + "_ammo"), + _type=Fortification.FARP_Ammo_Dump_Coating, + position=pad.position.point_from_heading( + ground_spawn[0].heading.degrees + 90, 35 + ).point_from_heading(ground_spawn[0].heading.degrees + 180, 10), + heading=pad.heading + 180, + ) + if self.game.settings.ground_start_ground_power_trucks_roadbase: + self.m.vehicle_group( + country=country, + name=(name + "_power"), + _type=power_truck_type, + position=pad.position.point_from_heading( + ground_spawn[0].heading.degrees + 90, 35 + ).point_from_heading(ground_spawn[0].heading.degrees + 180, 20), + group_size=1, + heading=pad.heading + 315, + move_formation=PointAction.OffRoad, + ) def generate(self) -> None: try: @@ -1044,61 +1072,76 @@ class GroundSpawnGenerator: country.id ) - # Generate a FARP Ammo and Fuel stack for each pad - if self.game.settings.ground_start_trucks: - self.m.vehicle_group( - country=country, - name=(name + "_fuel"), - _type=tanker_type, - position=pad.position.point_from_heading( - vtol_pad[0].heading.degrees - 175, 35 - ), - group_size=1, - heading=pad.heading + 45, - move_formation=PointAction.OffRoad, - ) - self.m.vehicle_group( - country=country, - name=(name + "_ammo"), - _type=ammo_truck_type, - position=pad.position.point_from_heading( - vtol_pad[0].heading.degrees - 185, 35 - ), - group_size=1, - heading=pad.heading + 45, - move_formation=PointAction.OffRoad, - ) + if self.game.position_culled(vtol_pad[0]): + cull_farp_statics = True + if self.cp.coalition.player: + for package in self.cp.coalition.ato.packages: + for flight in package.flights: + if flight.squadron.location == self.cp: + cull_farp_statics = False + break + elif flight.divert and flight.divert == self.cp: + cull_farp_statics = False + break else: - self.m.static_group( - country=country, - name=(name + "_fuel"), - _type=Fortification.FARP_Fuel_Depot, - position=pad.position.point_from_heading( - vtol_pad[0].heading.degrees - 180, 45 - ), - heading=pad.heading, - ) - self.m.static_group( - country=country, - name=(name + "_ammo"), - _type=Fortification.FARP_Ammo_Dump_Coating, - position=pad.position.point_from_heading( - vtol_pad[0].heading.degrees - 180, 35 - ), - heading=pad.heading + 270, - ) - if self.game.settings.ground_start_ground_power_trucks: - self.m.vehicle_group( - country=country, - name=(name + "_power"), - _type=power_truck_type, - position=pad.position.point_from_heading( - vtol_pad[0].heading.degrees - 185, 35 - ), - group_size=1, - heading=pad.heading + 45, - move_formation=PointAction.OffRoad, - ) + cull_farp_statics = False + + if not cull_farp_statics: + # Generate a FARP Ammo and Fuel stack for each pad + if self.game.settings.ground_start_trucks: + self.m.vehicle_group( + country=country, + name=(name + "_fuel"), + _type=tanker_type, + position=pad.position.point_from_heading( + vtol_pad[0].heading.degrees - 175, 35 + ), + group_size=1, + heading=pad.heading + 45, + move_formation=PointAction.OffRoad, + ) + self.m.vehicle_group( + country=country, + name=(name + "_ammo"), + _type=ammo_truck_type, + position=pad.position.point_from_heading( + vtol_pad[0].heading.degrees - 185, 35 + ), + group_size=1, + heading=pad.heading + 45, + move_formation=PointAction.OffRoad, + ) + else: + self.m.static_group( + country=country, + name=(name + "_fuel"), + _type=Fortification.FARP_Fuel_Depot, + position=pad.position.point_from_heading( + vtol_pad[0].heading.degrees - 180, 45 + ), + heading=pad.heading, + ) + self.m.static_group( + country=country, + name=(name + "_ammo"), + _type=Fortification.FARP_Ammo_Dump_Coating, + position=pad.position.point_from_heading( + vtol_pad[0].heading.degrees - 180, 35 + ), + heading=pad.heading + 270, + ) + if self.game.settings.ground_start_ground_power_trucks: + self.m.vehicle_group( + country=country, + name=(name + "_power"), + _type=power_truck_type, + position=pad.position.point_from_heading( + vtol_pad[0].heading.degrees - 185, 35 + ), + group_size=1, + heading=pad.heading + 45, + move_formation=PointAction.OffRoad, + ) def generate(self) -> None: try: diff --git a/game/pretense/pretenseaircraftgenerator.py b/game/pretense/pretenseaircraftgenerator.py index 1bfe7b8b..6b0a316c 100644 --- a/game/pretense/pretenseaircraftgenerator.py +++ b/game/pretense/pretenseaircraftgenerator.py @@ -49,17 +49,9 @@ if TYPE_CHECKING: PRETENSE_SQUADRON_DEF_RETRIES = 100 -PRETENSE_SEAD_FLIGHTS_PER_CP = 2 -PRETENSE_CAS_FLIGHTS_PER_CP = 2 -PRETENSE_BAI_FLIGHTS_PER_CP = 2 -PRETENSE_STRIKE_FLIGHTS_PER_CP = 2 -PRETENSE_BARCAP_FLIGHTS_PER_CP = 2 -PRETENSE_AI_AIRCRAFT_PER_FLIGHT = 2 PRETENSE_AI_AWACS_PER_FLIGHT = 1 PRETENSE_AI_TANKERS_PER_FLIGHT = 1 -PRETENSE_AI_CARGO_PLANES_PER_SIDE = 2 PRETENSE_PLAYER_AIRCRAFT_PER_FLIGHT = 1 -PRETENSE_PLAYER_FLIGHTS_PER_TYPE = 2 class PretenseAircraftGenerator: @@ -300,8 +292,12 @@ class PretenseAircraftGenerator: if cp.coalition != squadron.coalition: continue - squadron.owned_aircraft += PRETENSE_AI_AIRCRAFT_PER_FLIGHT - squadron.untasked_aircraft += PRETENSE_AI_AIRCRAFT_PER_FLIGHT + squadron.owned_aircraft += ( + self.game.settings.pretense_ai_aircraft_per_flight + ) + squadron.untasked_aircraft += ( + self.game.settings.pretense_ai_aircraft_per_flight + ) squadron.populate_for_turn_0(False) package = Package(cp, squadron.flight_db, auto_asap=False) mission_types = squadron.auto_assignable_mission_types @@ -320,46 +316,46 @@ class PretenseAircraftGenerator: FlightType.SEAD in mission_types or FlightType.SEAD_SWEEP in mission_types or FlightType.SEAD_ESCORT in mission_types - ) and num_of_sead < PRETENSE_SEAD_FLIGHTS_PER_CP: + ) and num_of_sead < self.game.settings.pretense_sead_flights_per_cp: flight_type = FlightType.SEAD num_of_sead += 1 - aircraft_per_flight = PRETENSE_AI_AIRCRAFT_PER_FLIGHT + aircraft_per_flight = self.game.settings.pretense_ai_aircraft_per_flight elif ( FlightType.DEAD in mission_types - and num_of_sead < PRETENSE_SEAD_FLIGHTS_PER_CP + and num_of_sead < self.game.settings.pretense_sead_flights_per_cp ): flight_type = FlightType.DEAD num_of_sead += 1 - aircraft_per_flight = PRETENSE_AI_AIRCRAFT_PER_FLIGHT + aircraft_per_flight = self.game.settings.pretense_ai_aircraft_per_flight elif ( FlightType.CAS in mission_types - ) and num_of_cas < PRETENSE_CAS_FLIGHTS_PER_CP: + ) and num_of_cas < self.game.settings.pretense_cas_flights_per_cp: flight_type = FlightType.CAS num_of_cas += 1 - aircraft_per_flight = PRETENSE_AI_AIRCRAFT_PER_FLIGHT + aircraft_per_flight = self.game.settings.pretense_ai_aircraft_per_flight elif ( FlightType.BAI in mission_types - ) and num_of_bai < PRETENSE_BAI_FLIGHTS_PER_CP: + ) and num_of_bai < self.game.settings.pretense_bai_flights_per_cp: flight_type = FlightType.BAI num_of_bai += 1 - aircraft_per_flight = PRETENSE_AI_AIRCRAFT_PER_FLIGHT + aircraft_per_flight = self.game.settings.pretense_ai_aircraft_per_flight elif ( FlightType.STRIKE in mission_types or FlightType.OCA_RUNWAY in mission_types or FlightType.OCA_AIRCRAFT in mission_types - ) and num_of_strike < PRETENSE_STRIKE_FLIGHTS_PER_CP: + ) and num_of_strike < self.game.settings.pretense_strike_flights_per_cp: flight_type = FlightType.STRIKE num_of_strike += 1 - aircraft_per_flight = PRETENSE_AI_AIRCRAFT_PER_FLIGHT + aircraft_per_flight = self.game.settings.pretense_ai_aircraft_per_flight elif ( FlightType.BARCAP in mission_types or FlightType.TARCAP in mission_types or FlightType.ESCORT in mission_types or FlightType.INTERCEPTION in mission_types - ) and num_of_cap < PRETENSE_BARCAP_FLIGHTS_PER_CP: + ) and num_of_cap < self.game.settings.pretense_barcap_flights_per_cp: flight_type = FlightType.BARCAP num_of_cap += 1 - aircraft_per_flight = PRETENSE_AI_AIRCRAFT_PER_FLIGHT + aircraft_per_flight = self.game.settings.pretense_ai_aircraft_per_flight elif FlightType.AEWC in mission_types: flight_type = FlightType.AEWC aircraft_per_flight = PRETENSE_AI_AWACS_PER_FLIGHT @@ -429,8 +425,12 @@ class PretenseAircraftGenerator: PRETENSE_SQUADRON_DEF_RETRIES, ) if squadron is not None: - squadron.owned_aircraft += PRETENSE_AI_AIRCRAFT_PER_FLIGHT - squadron.untasked_aircraft += PRETENSE_AI_AIRCRAFT_PER_FLIGHT + squadron.owned_aircraft += ( + self.game.settings.pretense_ai_aircraft_per_flight + ) + squadron.untasked_aircraft += ( + self.game.settings.pretense_ai_aircraft_per_flight + ) squadron.populate_for_turn_0(False) package = Package(cp, squadron.flight_db, auto_asap=False) flight = Flight( @@ -453,7 +453,7 @@ class PretenseAircraftGenerator: if isinstance(cp, Airfield): # Generate SEAD flight flight_type = FlightType.SEAD - aircraft_per_flight = PRETENSE_AI_AIRCRAFT_PER_FLIGHT + aircraft_per_flight = self.game.settings.pretense_ai_aircraft_per_flight squadron = self.generate_pretense_squadron( cp, coalition, @@ -470,8 +470,12 @@ class PretenseAircraftGenerator: PRETENSE_SQUADRON_DEF_RETRIES, ) if squadron is not None: - squadron.owned_aircraft += PRETENSE_AI_AIRCRAFT_PER_FLIGHT - squadron.untasked_aircraft += PRETENSE_AI_AIRCRAFT_PER_FLIGHT + squadron.owned_aircraft += ( + self.game.settings.pretense_ai_aircraft_per_flight + ) + squadron.untasked_aircraft += ( + self.game.settings.pretense_ai_aircraft_per_flight + ) squadron.populate_for_turn_0(False) package = Package(cp, squadron.flight_db, auto_asap=False) flight = Flight( @@ -494,7 +498,7 @@ class PretenseAircraftGenerator: # Generate CAS flight flight_type = FlightType.CAS - aircraft_per_flight = PRETENSE_AI_AIRCRAFT_PER_FLIGHT + aircraft_per_flight = self.game.settings.pretense_ai_aircraft_per_flight squadron = self.generate_pretense_squadron( cp, coalition, @@ -511,8 +515,12 @@ class PretenseAircraftGenerator: PRETENSE_SQUADRON_DEF_RETRIES, ) if squadron is not None: - squadron.owned_aircraft += PRETENSE_AI_AIRCRAFT_PER_FLIGHT - squadron.untasked_aircraft += PRETENSE_AI_AIRCRAFT_PER_FLIGHT + squadron.owned_aircraft += ( + self.game.settings.pretense_ai_aircraft_per_flight + ) + squadron.untasked_aircraft += ( + self.game.settings.pretense_ai_aircraft_per_flight + ) squadron.populate_for_turn_0(False) package = Package(cp, squadron.flight_db, auto_asap=False) flight = Flight( @@ -561,7 +569,7 @@ class PretenseAircraftGenerator: if not cp.can_operate(aircraft_type): continue - for i in range(PRETENSE_PLAYER_FLIGHTS_PER_TYPE): + for i in range(self.game.settings.pretense_player_flights_per_type): squadron = self.generate_pretense_squadron_for( aircraft_type, cp, @@ -607,7 +615,7 @@ class PretenseAircraftGenerator: cp: Control point to generate aircraft for. flight: The current flight being generated. """ - cp_name_trimmed = "".join([i for i in cp.name.lower() if i.isalnum()]) + cp_name_trimmed = "".join([i for i in cp.name.lower() if i.isalpha()]) for side in range(1, 3): if cp_name_trimmed not in cp.coalition.game.pretense_air[side]: @@ -627,7 +635,7 @@ class PretenseAircraftGenerator: flight: The current flight being generated. """ flight_type = flight.flight_type - cp_name_trimmed = "".join([i for i in cp.name.lower() if i.isalnum()]) + cp_name_trimmed = "".join([i for i in cp.name.lower() if i.isalpha()]) for side in range(1, 3): if cp_name_trimmed not in flight.coalition.game.pretense_air[side]: @@ -675,7 +683,7 @@ class PretenseAircraftGenerator: PRETENSE_SQUADRON_DEF_RETRIES, ) num_of_cargo_sq_to_generate = ( - PRETENSE_AI_CARGO_PLANES_PER_SIDE + self.game.settings.pretense_ai_cargo_planes_per_side - self.number_of_pretense_cargo_plane_sq_for(cp.coalition.air_wing) ) for i in range(num_of_cargo_sq_to_generate): @@ -795,7 +803,8 @@ class PretenseAircraftGenerator: if flight.package.target != flight.departure: break for mission_target in cp.ground_objects: - flight.package.target = mission_target + if mission_target.alive_unit_count > 0: + flight.package.target = mission_target break elif ( flight.flight_type == FlightType.OCA_RUNWAY @@ -837,23 +846,30 @@ class PretenseAircraftGenerator: break now = self.game.conditions.start_time - flight.package.set_tot_asap(now) + try: + flight.package.set_tot_asap(now) + except: + raise RuntimeError( + f"Pretense flight group {group.name} {flight.squadron.aircraft} {flight.flight_type} for target {flight.package.target} configuration failed. Please check if your Retribution campaign is compatible with Pretense." + ) logging.info( f"Configuring flight {group.name} {flight.squadron.aircraft} {flight.flight_type}, number of players: {flight.client_count}" ) - PretenseFlightGroupConfigurator( - flight, - group, - self.game, - self.mission, - self.time, - self.radio_registry, - self.tacan_registy, - self.mission_data, - dynamic_runways, - self.use_client, - ).configure() + self.mission_data.flights.append( + PretenseFlightGroupConfigurator( + flight, + group, + self.game, + self.mission, + self.time, + self.radio_registry, + self.tacan_registy, + self.mission_data, + dynamic_runways, + self.use_client, + ).configure() + ) if self.ewrj: self._track_ewrj_flight(flight, group) diff --git a/game/pretense/pretenseflightgroupspawner.py b/game/pretense/pretenseflightgroupspawner.py index cdb10a23..dad732c8 100644 --- a/game/pretense/pretenseflightgroupspawner.py +++ b/game/pretense/pretenseflightgroupspawner.py @@ -30,7 +30,7 @@ class PretenseNameGenerator(NameGenerator): @classmethod def next_pretense_aircraft_name(cls, cp: ControlPoint, flight: Flight) -> str: cls.aircraft_number += 1 - cp_name_trimmed = "".join([i for i in cp.name.lower() if i.isalnum()]) + cp_name_trimmed = "".join([i for i in cp.name.lower() if i.isalpha()]) return "{}-{}-{}".format( cp_name_trimmed, str(flight.flight_type).lower(), cls.aircraft_number ) @@ -77,12 +77,17 @@ class PretenseFlightGroupSpawner(FlightGroupSpawner): == self.flight.coalition.game.coalition_for(is_player) else 1 ) - cp_name_trimmed = "".join([i for i in cp.name.lower() if i.isalnum()]) + cp_name_trimmed = "".join([i for i in cp.name.lower() if i.isalpha()]) if self.flight.client_count == 0: self.flight.coalition.game.pretense_air[cp_side][cp_name_trimmed][ self.flight.flight_type ].append(name) + try: + self.flight.coalition.game.pretense_air_groups[name] = self.flight + except AttributeError: + self.flight.coalition.game.pretense_air_groups = {} + self.flight.coalition.game.pretense_air_groups[name] = self.flight def generate_flight_at_departure(self) -> FlyingGroup[Any]: cp = self.flight.departure @@ -94,7 +99,7 @@ class PretenseFlightGroupSpawner(FlightGroupSpawner): == self.flight.coalition.game.coalition_for(is_player) else 1 ) - cp_name_trimmed = "".join([i for i in cp.name.lower() if i.isalnum()]) + cp_name_trimmed = "".join([i for i in cp.name.lower() if i.isalpha()]) try: if self.start_type is StartType.IN_FLIGHT: @@ -139,8 +144,7 @@ class PretenseFlightGroupSpawner(FlightGroupSpawner): pad_group = self._generate_at_cp_ground_spawn(name, cp) if pad_group is not None: return pad_group - self.insert_into_pretense(name) - return self._generate_over_departure(name, cp) + raise NoParkingSlotError elif isinstance(cp, Airfield): is_heli = self.flight.squadron.aircraft.helicopter if cp.has_helipads and is_heli: diff --git a/game/pretense/pretenseluagenerator.py b/game/pretense/pretenseluagenerator.py index 605e257f..2a56ba8c 100644 --- a/game/pretense/pretenseluagenerator.py +++ b/game/pretense/pretenseluagenerator.py @@ -259,7 +259,7 @@ class PretenseLuaGenerator(LuaGenerator): def generate_pretense_land_upgrade_supply(self, cp_name: str, cp_side: int) -> str: lua_string_zones = "" - cp_name_trimmed = "".join([i for i in cp_name.lower() if i.isalnum()]) + cp_name_trimmed = "".join([i for i in cp_name.lower() if i.isalpha()]) cp_side_str = "blue" if cp_side == PRETENSE_BLUE_SIDE else "red" cp = self.game.theater.controlpoints[0] for loop_cp in self.game.theater.controlpoints: @@ -398,7 +398,7 @@ class PretenseLuaGenerator(LuaGenerator): ) lua_string_zones += " products = {\n" for mission_type in self.game.pretense_air[cp_side][cp_name_trimmed]: - if mission_type == FlightType.SEAD: + if mission_type in (FlightType.SEAD, FlightType.DEAD): mission_name = "attack.sead" for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ mission_type @@ -414,6 +414,9 @@ class PretenseLuaGenerator(LuaGenerator): for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ mission_type ]: + flight = self.game.pretense_air_groups[air_group] + if flight.is_helo: + mission_name = "attack.helo" lua_string_zones += ( f" presets.missions.{mission_name}:extend" + "({name='" @@ -503,7 +506,7 @@ class PretenseLuaGenerator(LuaGenerator): def generate_pretense_sea_upgrade_supply(self, cp_name: str, cp_side: int) -> str: lua_string_zones = "" - cp_name_trimmed = "".join([i for i in cp_name.lower() if i.isalnum()]) + cp_name_trimmed = "".join([i for i in cp_name.lower() if i.isalpha()]) cp_side_str = "blue" if cp_side == PRETENSE_BLUE_SIDE else "red" if cp_side == PRETENSE_BLUE_SIDE: @@ -608,6 +611,9 @@ class PretenseLuaGenerator(LuaGenerator): for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ mission_type ]: + flight = self.game.pretense_air_groups[air_group] + if flight.is_helo: + mission_name = "attack.helo" lua_string_zones += ( f" presets.missions.{mission_name}:extend" + "({name='" @@ -697,7 +703,7 @@ class PretenseLuaGenerator(LuaGenerator): def generate_pretense_zone_land(self, cp_name: str) -> str: lua_string_zones = "" - cp_name_trimmed = "".join([i for i in cp_name.lower() if i.isalnum()]) + cp_name_trimmed = "".join([i for i in cp_name.lower() if i.isalpha()]) lua_string_zones += f"zones.{cp_name_trimmed}:defineUpgrades(" + "{\n" lua_string_zones += " [1] = { --red side\n" @@ -771,7 +777,7 @@ class PretenseLuaGenerator(LuaGenerator): def generate_pretense_zone_sea(self, cp_name: str) -> str: lua_string_zones = "" - cp_name_trimmed = "".join([i for i in cp_name.lower() if i.isalnum()]) + cp_name_trimmed = "".join([i for i in cp_name.lower() if i.isalpha()]) lua_string_zones += f"zones.{cp_name_trimmed}:defineUpgrades(" + "{\n" lua_string_zones += " [1] = { --red side\n" @@ -823,29 +829,29 @@ class PretenseLuaGenerator(LuaGenerator): 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" - ) - trigger = TriggerStart(comment="Pretense init") - - lua_string_config = "" + lua_string_config = "Config = Config or {}\n" 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" + trigger = TriggerStart(comment="Pretense config") + trigger.add_action(DoScript(String(lua_string_config))) + self.mission.triggerrules.triggers.append(trigger) + + self.inject_plugin_script( + "pretense", "pretense_compiled.lua", "pretense_compiled" + ) + + trigger = TriggerStart(comment="Pretense init") + init_header_file = open("./resources/plugins/pretense/init_header.lua", "r") init_header = init_header_file.read() @@ -855,7 +861,7 @@ class PretenseLuaGenerator(LuaGenerator): if isinstance(cp, OffMapSpawn): continue - cp_name_trimmed = "".join([i for i in cp.name.lower() if i.isalnum()]) + cp_name_trimmed = "".join([i for i in cp.name.lower() if i.isalpha()]) cp_side = 2 if cp.captured else 1 for side in range(1, 3): if cp_name_trimmed not in self.game.pretense_air[cp_side]: @@ -947,12 +953,17 @@ class PretenseLuaGenerator(LuaGenerator): else: # Finally, connect remaining non-connected points closest_cps = self.game.theater.closest_friendly_control_points_to(cp) - lua_string_connman += self.generate_pretense_zone_connection( - connected_points, cp.name, closest_cps[0].name - ) - lua_string_connman += self.generate_pretense_zone_connection( - connected_points, cp.name, closest_cps[1].name - ) + for extra_connection in range( + self.game.settings.pretense_extra_zone_connections + ): + if len(closest_cps) > extra_connection: + lua_string_connman += self.generate_pretense_zone_connection( + connected_points, + cp.name, + closest_cps[extra_connection].name, + ) + else: + break lua_string_supply = "local redSupply = {\n" # Generate supply @@ -963,7 +974,7 @@ class PretenseLuaGenerator(LuaGenerator): cp_side_captured = cp_side == 2 if cp_side_captured != cp.captured: continue - cp_name_trimmed = "".join([i for i in cp.name.lower() if i.isalnum()]) + cp_name_trimmed = "".join([i for i in cp.name.lower() if i.isalpha()]) for mission_type in self.game.pretense_air[cp_side][cp_name_trimmed]: if mission_type == FlightType.PRETENSE_CARGO: for air_group in self.game.pretense_air[cp_side][ @@ -976,7 +987,7 @@ class PretenseLuaGenerator(LuaGenerator): lua_string_supply += "local offmapZones = {\n" for cp in self.game.theater.controlpoints: if isinstance(cp, Airfield): - cp_name_trimmed = "".join([i for i in cp.name.lower() if i.isalnum()]) + cp_name_trimmed = "".join([i for i in cp.name.lower() if i.isalpha()]) lua_string_supply += f" zones.{cp_name_trimmed},\n" lua_string_supply += "}\n" @@ -997,8 +1008,7 @@ class PretenseLuaGenerator(LuaGenerator): init_footer = init_footer_file.read() lua_string = ( - lua_string_config - + init_header + init_header + lua_string_zones + lua_string_connman + init_body_1 diff --git a/game/pretense/pretensemissiongenerator.py b/game/pretense/pretensemissiongenerator.py index e67ece34..a6dc8013 100644 --- a/game/pretense/pretensemissiongenerator.py +++ b/game/pretense/pretensemissiongenerator.py @@ -12,6 +12,7 @@ from dcs.countries import ( CombinedJointTaskForcesBlue, CombinedJointTaskForcesRed, ) +from dcs.task import AFAC, FAC, SetInvisibleCommand, SetImmortalCommand, OrbitAction from game.lasercodes.lasercoderegistry import LaserCodeRegistry from game.missiongenerator.convoygenerator import ConvoyGenerator @@ -21,7 +22,7 @@ from game.missiongenerator.forcedoptionsgenerator import ForcedOptionsGenerator from game.missiongenerator.frontlineconflictdescription import ( FrontLineConflictDescription, ) -from game.missiongenerator.missiondata import MissionData +from game.missiongenerator.missiondata import MissionData, JtacInfo from game.missiongenerator.tgogenerator import TgoGenerator from game.missiongenerator.visualsgenerator import VisualsGenerator from game.naming import namegen @@ -34,6 +35,8 @@ from .pretenseluagenerator import PretenseLuaGenerator from .pretensetgogenerator import PretenseTgoGenerator from .pretensetriggergenerator import PretenseTriggerGenerator from ..ato.airtaaskingorder import AirTaskingOrder +from ..callsigns import callsign_for_support_unit +from ..dcs.aircrafttype import AircraftType from ..missiongenerator import MissionGenerator if TYPE_CHECKING: @@ -148,27 +151,61 @@ class PretenseMissionGenerator(MissionGenerator): for front_line in self.game.theater.conflicts(): player_cp = front_line.blue_cp enemy_cp = front_line.red_cp - conflict = FrontLineConflictDescription.frontline_cas_conflict( - front_line, self.game.theater - ) - # Generate frontline ops - player_gp = self.game.ground_planners[player_cp.id].units_per_cp[ - enemy_cp.id - ] - enemy_gp = self.game.ground_planners[enemy_cp.id].units_per_cp[player_cp.id] - ground_conflict_gen = FlotGenerator( - self.mission, - conflict, - self.game, - player_gp, - enemy_gp, - player_cp.stances[enemy_cp.id], - enemy_cp.stances[player_cp.id], - self.unit_map, - self.radio_registry, - self.mission_data, - ) - ground_conflict_gen.generate() + + # Add JTAC + if self.game.blue.faction.has_jtac: + freq = self.radio_registry.alloc_uhf() + # If the option fc3LaserCode is enabled, force all JTAC + # laser codes to 1113 to allow lasing for Su-25 Frogfoots and A-10A Warthogs. + # Otherwise use 1688 for the first JTAC, 1687 for the second etc. + if self.game.settings.plugins.get("ctld.fc3LaserCode"): + code = self.game.laser_code_registry.fc3_code + else: + code = front_line.laser_code + + utype = self.game.blue.faction.jtac_unit + if utype is None: + utype = AircraftType.named("MQ-9 Reaper") + + country = self.mission.country(self.game.blue.faction.country.name) + position = FrontLineConflictDescription.frontline_position( + front_line, self.game.theater, self.game.settings + ) + jtac = self.mission.flight_group( + country=country, + name=namegen.next_jtac_name(), + aircraft_type=utype.dcs_unit_type, + position=position[0], + airport=None, + altitude=5000, + maintask=AFAC, + ) + jtac.points[0].tasks.append( + FAC( + callsign=len(self.mission_data.jtacs) + 1, + frequency=int(freq.mhz), + modulation=freq.modulation, + ) + ) + jtac.points[0].tasks.append(SetInvisibleCommand(True)) + jtac.points[0].tasks.append(SetImmortalCommand(True)) + jtac.points[0].tasks.append( + OrbitAction(5000, 300, OrbitAction.OrbitPattern.Circle) + ) + frontline = f"Frontline {player_cp.name}/{enemy_cp.name}" + # Note: Will need to change if we ever add ground based JTAC. + callsign = callsign_for_support_unit(jtac) + self.mission_data.jtacs.append( + JtacInfo( + group_name=jtac.name, + unit_name=jtac.units[0].name, + callsign=callsign, + region=frontline, + code=str(code), + blue=True, + freq=freq, + ) + ) def generate_air_units(self, tgo_generator: TgoGenerator) -> None: """Generate the air units for the Operation""" diff --git a/game/settings/settings.py b/game/settings/settings.py index 10ea5eee..918b28f1 100644 --- a/game/settings/settings.py +++ b/game/settings/settings.py @@ -988,21 +988,102 @@ class Settings: default=130, min=10, max=10000, + detail=( + "Zones farther away than this from the front line are switched " + "into low activity state, but will still be there as functional " + "parts of the economy. Use this to adjust performance." + ), ) - pretense_closeoverride_distance: int = bounded_int_option( - "Close override distance (km)", + pretense_extra_zone_connections: int = bounded_int_option( + "Extra friendly zone connections", page=PRETENSE_PAGE, section=GENERAL_SECTION, - default=28, - min=5, - max=10000, + default=2, + min=0, + max=10, + detail=( + "Add connections from each zone to this many closest friendly zones," + "which don't have an existing supply route defined in the campaign." + ), ) pretense_do_not_generate_sead_missions: bool = boolean_option( "Do not generate player SEAD missions", page=PRETENSE_PAGE, - section=PERFORMANCE_SECTION, + section=GENERAL_SECTION, default=False, ) + pretense_num_of_cargo_planes: int = bounded_int_option( + "Number of cargo planes per side", + page=PRETENSE_PAGE, + section=GENERAL_SECTION, + default=2, + min=1, + max=100, + ) + pretense_sead_flights_per_cp: int = bounded_int_option( + "Number of AI SEAD flights per control point / zone", + page=PRETENSE_PAGE, + section=GENERAL_SECTION, + default=2, + min=1, + max=10, + ) + pretense_cas_flights_per_cp: int = bounded_int_option( + "Number of AI CAS flights per control point / zone", + page=PRETENSE_PAGE, + section=GENERAL_SECTION, + default=2, + min=1, + max=10, + ) + pretense_bai_flights_per_cp: int = bounded_int_option( + "Number of AI BAI flights per control point / zone", + page=PRETENSE_PAGE, + section=GENERAL_SECTION, + default=2, + min=1, + max=10, + ) + pretense_strike_flights_per_cp: int = bounded_int_option( + "Number of AI Strike flights per control point / zone", + page=PRETENSE_PAGE, + section=GENERAL_SECTION, + default=2, + min=1, + max=10, + ) + pretense_barcap_flights_per_cp: int = bounded_int_option( + "Number of AI BARCAP flights per control point / zone", + page=PRETENSE_PAGE, + section=GENERAL_SECTION, + default=2, + min=1, + max=10, + ) + pretense_ai_aircraft_per_flight: int = bounded_int_option( + "Number of AI aircraft per flight", + page=PRETENSE_PAGE, + section=GENERAL_SECTION, + default=2, + min=1, + max=4, + ) + pretense_player_flights_per_type: int = bounded_int_option( + "Number of player flights per aircraft type at each base", + page=PRETENSE_PAGE, + section=GENERAL_SECTION, + default=2, + min=1, + max=10, + ) + pretense_ai_cargo_planes_per_side: int = bounded_int_option( + "Number of AI cargo planes per side", + page=PRETENSE_PAGE, + section=GENERAL_SECTION, + default=2, + min=1, + max=20, + ) # Cheating. Not using auto settings because the same page also has buttons which do # not alter settings. diff --git a/resources/plugins/pretense/init_header.lua b/resources/plugins/pretense/init_header.lua index c73f5444..d7481f31 100644 --- a/resources/plugins/pretense/init_header.lua +++ b/resources/plugins/pretense/init_header.lua @@ -494,7 +494,7 @@ presets = { }), comCenter = Preset:new({ display = 'Command Center', - cost = 2500, + cost = 12500, type = 'upgrade', template = "command-center" }) @@ -522,43 +522,43 @@ presets = { }), sa10 = Preset:new({ display = 'SAM', - cost=3000, + cost=30000, type='defense', template='sa10', }), sa5 = Preset:new({ display = 'SAM', - cost=3000, + cost=20000, type='defense', template='sa5', }), sa3 = Preset:new({ display = 'SAM', - cost=3000, + cost=4000, type='defense', template='sa3', }), sa6 = Preset:new({ display = 'SAM', - cost=3000, + cost=6000, type='defense', template='sa6', }), sa11 = Preset:new({ display = 'SAM', - cost=3000, + cost=10000, type='defense', template='sa11', }), hawk = Preset:new({ display = 'SAM', - cost=3000, + cost=6000, type='defense', template='hawk', }), patriot = Preset:new({ display = 'SAM', - cost=3000, + cost=30000, type='defense', template='patriot', }), @@ -596,43 +596,43 @@ presets = { }), sa10 = Preset:new({ display = 'SAM', - cost=3000, + cost=30000, type='defense', template='sa10', }), sa5 = Preset:new({ display = 'SAM', - cost=3000, + cost=20000, type='defense', template='sa5', }), sa3 = Preset:new({ display = 'SAM', - cost=3000, + cost=4000, type='defense', template='sa3', }), sa6 = Preset:new({ display = 'SAM', - cost=3000, + cost=6000, type='defense', template='sa6', }), sa11 = Preset:new({ display = 'SAM', - cost=3000, + cost=10000, type='defense', template='sa11', }), hawk = Preset:new({ display = 'SAM', - cost=3000, + cost=6000, type='defense', template='hawk', }), patriot = Preset:new({ display = 'SAM', - cost=3000, + cost=30000, type='defense', template='patriot', }), diff --git a/resources/plugins/pretense/pretense_compiled.lua b/resources/plugins/pretense/pretense_compiled.lua index cf8bb881..5dbece1a 100644 --- a/resources/plugins/pretense/pretense_compiled.lua +++ b/resources/plugins/pretense/pretense_compiled.lua @@ -32,7 +32,6 @@ Config.buildSpeed = Config.buildSpeed or 10 -- structure and defense build speed Config.supplyBuildSpeed = Config.supplyBuildSpeed or 85 -- supply helicopters and convoys build speed Config.missionBuildSpeedReduction = Config.missionBuildSpeedReduction or 0.12 -- reduction of build speed in case of ai missions Config.maxDistFromFront = Config.maxDistFromFront or 129640 -- max distance in meters from front after which zone is forced into low activity state (export mode) -Config.closeOverride = Config.closeOverride or 27780 -- close override distance in meters from front within which zone is never forced into low activity state Config.disablePlayerSead = Config.disablePlayerSead or false Config.missions = Config.missions or {} @@ -505,8 +504,6 @@ end GroupMonitor = {} do GroupMonitor.blockedDespawnTime = 10*60 --used to despawn aircraft that are stuck taxiing for some reason - GroupMonitor.blockedDespawnTimeGround = 30*60 --used to despawn ground units that are stuck en route for some reason - GroupMonitor.blockedDespawnTimeGroundAssault = 90*60 --used to despawn assault units that are stuck en route for some reason GroupMonitor.landedDespawnTime = 10 GroupMonitor.atDestinationDespawnTime = 2*60 GroupMonitor.recoveryReduction = 0.8 -- reduce recovered resource from landed missions by this amount to account for maintenance @@ -642,13 +639,7 @@ do group.state = 'enroute' group.lastStateTime = timer.getAbsTime() MissionTargetRegistry.addBaiTarget(group) - elseif group.product.missionType == 'assault' and timer.getAbsTime() - group.lastStateTime > GroupMonitor.blockedDespawnTimeGroundAssault then - env.info('GroupMonitor: processSurface ['..group.name..'] despawned due to blockage') - gr:destroy() - local todeliver = math.floor(group.product.cost) - z:addResource(todeliver) - return true - elseif timer.getAbsTime() - group.lastStateTime > GroupMonitor.blockedDespawnTimeGround then + elseif timer.getAbsTime() - group.lastStateTime > GroupMonitor.blockedDespawnTime then env.info('GroupMonitor: processSurface ['..group.name..'] despawned due to blockage') gr:destroy() local todeliver = math.floor(group.product.cost) @@ -744,7 +735,7 @@ do y = group.target.zone.point.z } - TaskExtensions.moveOffRoadToPointAndAssault(gr, tp, group.target.built) + TaskExtensions.moveOnRoadToPointAndAssault(gr, tp, group.target.built) group.isstopped = false end end @@ -1231,7 +1222,7 @@ do if v.type == 'defense' and v.side ~= group:getCoalition() then local gr = Group.getByName(v.name) for _,unit in ipairs(gr:getUnits()) do - if unit:hasAttribute('SAM SR') or unit:hasAttribute('SAM TR') or unit:hasAttribute('AAA') or unit:hasAttribute('IR Guided SAM') or unit:hasAttribute('SAM LL') then + if unit:hasAttribute('SAM SR') or unit:hasAttribute('SAM TR') then table.insert(viable, unit:getName()) end end @@ -1245,7 +1236,7 @@ do { id = 'EngageTargets', params = { - targetTypes = {'SAM SR', 'SAM TR', 'AAA', 'IR Guided SAM', 'SAM LL'} + targetTypes = {'SAM SR', 'SAM TR'} } } } @@ -2145,68 +2136,7 @@ do } group:getController():setTask(mis) end - - function TaskExtensions.moveOffRoadToPointAndAssault(group, point, targets) - if not group or not point then return end - if not group:isExist() or group:getSize()==0 then return end - local startPos = group:getUnit(1):getPoint() - - local srx, sry = land.getClosestPointOnRoads('roads', startPos.x, startPos.z) - local erx, ery = land.getClosestPointOnRoads('roads', point.x, point.y) - - local mis = { - id='Mission', - params = { - route = { - points = { - [1] = { - type= AI.Task.WaypointType.TURNING_POINT, - x = srx, - y = sry, - speed = 1000, - action = AI.Task.VehicleFormation.DIAMOND - }, - [2] = { - type= AI.Task.WaypointType.TURNING_POINT, - x = erx, - y = ery, - speed = 1000, - action = AI.Task.VehicleFormation.DIAMOND - }, - [3] = { - type= AI.Task.WaypointType.TURNING_POINT, - x = point.x, - y = point.y, - speed = 1000, - action = AI.Task.VehicleFormation.DIAMOND - } - } - } - } - } - - for i,v in pairs(targets) do - if v.type == 'defense' then - local group = Group.getByName(v.name) - if group then - for i,v in ipairs(group:getUnits()) do - local unpos = v:getPoint() - local pnt = {x=unpos.x, y = unpos.z} - - table.insert(mis.params.route.points, { - type= AI.Task.WaypointType.TURNING_POINT, - x = pnt.x, - y = pnt.y, - speed = 10, - action = AI.Task.VehicleFormation.DIAMOND - }) - end - end - end - end - group:getController():setTask(mis) - end - + function TaskExtensions.landAtPointFromAir(group, point, alt) if not group or not point then return end if not group:isExist() or group:getSize()==0 then return end @@ -4882,7 +4812,7 @@ do product.lastMission = {zoneName = zone.name} timer.scheduleFunction(function(param) local gr = Group.getByName(param.name) - TaskExtensions.moveOffRoadToPointAndAssault(gr, param.point, param.targets) + TaskExtensions.moveOnRoadToPointAndAssault(gr, param.point, param.targets) end, {name=product.name, point={ x=tgtPoint.point.x, y = tgtPoint.point.z}, targets=zone.built}, timer.getTime()+1) end end @@ -5413,7 +5343,7 @@ do product.lastMission = {zoneName = v.name} timer.scheduleFunction(function(param) local gr = Group.getByName(param.name) - TaskExtensions.moveOffRoadToPointAndAssault(gr, param.point, param.targets) + TaskExtensions.moveOnRoadToPointAndAssault(gr, param.point, param.targets) end, {name=product.name, point={ x=tgtPoint.point.x, y = tgtPoint.point.z}, targets=v.built}, timer.getTime()+1) env.info("ZoneCommand - "..product.name.." targeting "..v.name) @@ -5975,7 +5905,7 @@ end BattlefieldManager = {} do - BattlefieldManager.closeOverride = Config.closeOverride -- default 15nm + BattlefieldManager.closeOverride = 27780 -- 15nm BattlefieldManager.farOverride = Config.maxDistFromFront -- default 100nm BattlefieldManager.boostScale = {[0] = 1.0, [1]=1.0, [2]=1.0} BattlefieldManager.noRedZones = false @@ -10554,7 +10484,6 @@ do end end end - return false end function SEAD:getMissionName() @@ -12208,7 +12137,7 @@ do if toGen > 0 then local validMissions = {} for _,v in pairs(Mission.types) do - if timer.getAbsTime() - timer.getTime0() > 120 and self:canCreateMission(v) then + if self:canCreateMission(v) then table.insert(validMissions,v) end end From 00760b831e30f781ee034b864fed4fb0b4b6f6bf Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Wed, 22 Nov 2023 20:13:57 +0200 Subject: [PATCH 187/243] Will now correctly generate Pretense campaigns with CJTF factions. --- game/pretense/pretensemissiongenerator.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/game/pretense/pretensemissiongenerator.py b/game/pretense/pretensemissiongenerator.py index a6dc8013..7b18b35b 100644 --- a/game/pretense/pretensemissiongenerator.py +++ b/game/pretense/pretensemissiongenerator.py @@ -135,9 +135,9 @@ class PretenseMissionGenerator(MissionGenerator): self.mission.coalition["red"].add_country(self.e_country) # Add CJTF factions to the coalitions, if they're not being used in the campaign - if CombinedJointTaskForcesBlue not in {self.p_country, self.e_country}: + if CombinedJointTaskForcesBlue.id not in {self.p_country.id, self.e_country.id}: self.mission.coalition["blue"].add_country(CombinedJointTaskForcesBlue()) - if CombinedJointTaskForcesRed not in {self.p_country, self.e_country}: + if CombinedJointTaskForcesRed.id not in {self.p_country.id, self.e_country.id}: self.mission.coalition["red"].add_country(CombinedJointTaskForcesRed()) belligerents = {self.p_country.id, self.e_country.id} From 76bab86e67cb642d9b0d9bccb59970106656e135 Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Wed, 6 Dec 2023 16:38:25 +0200 Subject: [PATCH 188/243] Updated the Pretense script to version 1.3.8 - 3 Dec 2023 and MIST to version 4.5.122. --- game/pretense/pretenseluagenerator.py | 7 ++-- resources/plugins/pretense/init_header.lua | 4 +-- .../plugins/pretense/pretense_compiled.lua | 36 +++++-------------- 3 files changed, 12 insertions(+), 35 deletions(-) diff --git a/game/pretense/pretenseluagenerator.py b/game/pretense/pretenseluagenerator.py index 2a56ba8c..17ac01a4 100644 --- a/game/pretense/pretenseluagenerator.py +++ b/game/pretense/pretenseluagenerator.py @@ -828,7 +828,7 @@ class PretenseLuaGenerator(LuaGenerator): return lua_string_connman 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("base", "mist_4_5_122.lua", "mist_4_5_122") lua_string_config = "Config = Config or {}\n" @@ -912,10 +912,7 @@ class PretenseLuaGenerator(LuaGenerator): 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: - lua_string_zones += self.generate_pretense_zone_land(cp.name) + lua_string_zones += self.generate_pretense_zone_land(cp.name) lua_string_connman = " cm = ConnectionManager:new()\n" diff --git a/resources/plugins/pretense/init_header.lua b/resources/plugins/pretense/init_header.lua index d7481f31..fce64047 100644 --- a/resources/plugins/pretense/init_header.lua +++ b/resources/plugins/pretense/init_header.lua @@ -487,10 +487,10 @@ presets = { }, airdef = { bunker = Preset:new({ - display = 'Bunker', + display = 'Excavator', cost = 1500, type = 'upgrade', - template = "bunker-1" + template = "excavator" }), comCenter = Preset:new({ display = 'Command Center', diff --git a/resources/plugins/pretense/pretense_compiled.lua b/resources/plugins/pretense/pretense_compiled.lua index 5dbece1a..ac48acce 100644 --- a/resources/plugins/pretense/pretense_compiled.lua +++ b/resources/plugins/pretense/pretense_compiled.lua @@ -709,8 +709,8 @@ do if #targets > 0 then for _,tgt in ipairs(targets) do if tgt.visible and tgt.object then - if tgt.object.getCoalition and tgt.object:getCoalition()~=frUnit:getCoalition() and - tgt.object.getCategory and tgt.object:getCategory() == 1 then + if tgt.object.isExist and tgt.object:isExist() and tgt.object.getCoalition and tgt.object:getCoalition()~=frUnit:getCoalition() and + Object.getCategory(tgt.object) == 1 then local dist = mist.utils.get3DDist(frUnit:getPoint(), tgt.object:getPoint()) if dist < 1000 then if not group.isstopped then @@ -886,9 +886,9 @@ do if #targets > 0 then for _,tgt in ipairs(targets) do if tgt.visible and tgt.object and tgt.object.isExist and tgt.object:isExist() then - if tgt.object.getCategory and tgt.object:getCategory() == Object.Category.UNIT and + if Object.getCategory(tgt.object) == Object.Category.UNIT and tgt.object.getCoalition and tgt.object:getCoalition()~=frUnit:getCoalition() and - tgt.object:getDesc().category == Unit.Category.GROUND_UNIT then + Unit.getCategory(tgt.object) == Unit.Category.GROUND_UNIT then local dist = mist.utils.get3DDist(frUnit:getPoint(), tgt.object:getPoint()) if dist < 2000 then @@ -6274,8 +6274,8 @@ do if not player then return end if event.id==world.event.S_EVENT_PLAYER_ENTER_UNIT then - if event.initiator and event.initiator:getCategory() == Object.Category.UNIT and - (event.initiator:getDesc().category == Unit.Category.AIRPLANE or event.initiator:getDesc().category == Unit.Category.HELICOPTER) then + if event.initiator and Object.getCategory(event.initiator) == Object.Category.UNIT and + (Unit.getCategory(event.initiator) == Unit.Category.AIRPLANE or Unit.getCategory(event.initiator) == Unit.Category.HELICOPTER) then local pname = event.initiator:getPlayerName() if pname then @@ -7558,29 +7558,9 @@ do TemplateDB.templates["tv-tower"] = { type="TV tower", category="Fortifications", shape="tele_bash", dataCategory=TemplateDB.type.static } - TemplateDB.templates["bunker-1"] = { type="Sandbox", category="Fortifications", dataCategory=TemplateDB.type.static } - TemplateDB.templates["command-center"] = { type=".Command Center", category="Fortifications", shape="ComCenter", dataCategory=TemplateDB.type.static } TemplateDB.templates["military-staff"] = { type="Military staff", category="Fortifications", shape="aviashtab", dataCategory=TemplateDB.type.static } - - TemplateDB.templates["ship-tanker-seawisegiant"] = { type="Seawise_Giant", category="Ships", dataCategory=TemplateDB.type.static } - - TemplateDB.templates["ship-supply-tilde"] = { type="Ship_Tilde_Supply", category="Ships", dataCategory=TemplateDB.type.static } - - TemplateDB.templates["ship-landingship-samuelchase"] = { type="USS_Samuel_Chase", category="Ships", dataCategory=TemplateDB.type.static } - - TemplateDB.templates["ship-landingship-ropucha"] = { type="BDK-775", category="Ships", dataCategory=TemplateDB.type.static } - - TemplateDB.templates["ship-tanker-elnya"] = { type="ELNYA", category="Ships", dataCategory=TemplateDB.type.static } - - TemplateDB.templates["ship-landingship-lstmk2"] = { type="LST_Mk2", category="Ships", dataCategory=TemplateDB.type.static } - - TemplateDB.templates["ship-bulker-yakushev"] = { type="Dry-cargo ship-1", category="Ships", dataCategory=TemplateDB.type.static } - - TemplateDB.templates["ship-cargo-ivanov"] = { type="Dry-cargo ship-2", category="Ships", dataCategory=TemplateDB.type.static } - - end -----------------[[ END OF TemplateDB.lua ]]----------------- @@ -12357,7 +12337,7 @@ do for _,m in pairs(self.activeMissions) do if m.players[player] then if m.state == Mission.states.active then - if weapon:getDesc().category == Weapon.Category.BOMB then + if Weapon.getCategory(weapon) == Weapon.Category.BOMB then timer.scheduleFunction(function (params, time) if not params.weapon:isExist() then return nil -- weapon despawned @@ -13209,7 +13189,7 @@ do local detected = u:getController():getDetectedTargets(Controller.Detection.RADAR) for _,d in ipairs(detected) do if d and d.object and d.object.isExist and d.object:isExist() and - d.object:getCategory() == Object.Category.UNIT and + Object.getCategory(d.object) == Object.Category.UNIT and d.object.getCoalition and d.object:getCoalition() == self.tgtSide then From 3d435c78216f9e09e94857d43a6d517210261cb4 Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Sat, 9 Dec 2023 11:24:47 +0200 Subject: [PATCH 189/243] Removed a non-functional option from the settings. --- game/pretense/pretenseluagenerator.py | 4 ---- game/settings/settings.py | 6 ------ 2 files changed, 10 deletions(-) diff --git a/game/pretense/pretenseluagenerator.py b/game/pretense/pretenseluagenerator.py index 17ac01a4..e30f53d2 100644 --- a/game/pretense/pretenseluagenerator.py +++ b/game/pretense/pretenseluagenerator.py @@ -837,10 +837,6 @@ class PretenseLuaGenerator(LuaGenerator): + str(self.game.settings.pretense_maxdistfromfront_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" trigger = TriggerStart(comment="Pretense config") trigger.add_action(DoScript(String(lua_string_config))) diff --git a/game/settings/settings.py b/game/settings/settings.py index 918b28f1..6cfc0b3b 100644 --- a/game/settings/settings.py +++ b/game/settings/settings.py @@ -1006,12 +1006,6 @@ class Settings: "which don't have an existing supply route defined in the campaign." ), ) - pretense_do_not_generate_sead_missions: bool = boolean_option( - "Do not generate player SEAD missions", - page=PRETENSE_PAGE, - section=GENERAL_SECTION, - default=False, - ) pretense_num_of_cargo_planes: int = bounded_int_option( "Number of cargo planes per side", page=PRETENSE_PAGE, From 3d8956434c35bfc52956fc07ed14cee4ec4ebc39 Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Sat, 9 Dec 2023 12:24:06 +0200 Subject: [PATCH 190/243] Will now append the date and time in the Pretense savefile, mitigating risks of missions script errors when trying to load a savefile from a previously generated mission. --- game/pretense/pretenseluagenerator.py | 10 +++++++++- resources/plugins/pretense/init_header.lua | 3 +-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/game/pretense/pretenseluagenerator.py b/game/pretense/pretenseluagenerator.py index e30f53d2..63cd22dd 100644 --- a/game/pretense/pretenseluagenerator.py +++ b/game/pretense/pretenseluagenerator.py @@ -5,6 +5,7 @@ import os import random from abc import ABC, abstractmethod from dataclasses import dataclass +from datetime import datetime from pathlib import Path from typing import TYPE_CHECKING, Optional @@ -848,6 +849,12 @@ class PretenseLuaGenerator(LuaGenerator): trigger = TriggerStart(comment="Pretense init") + now = datetime.now() + date_time = now.strftime("%Y-%d-%mT%H_%M_%S") + lua_string_savefile = ( + f"local savefile = 'pretense_retribution_{date_time}.json'" + ) + init_header_file = open("./resources/plugins/pretense/init_header.lua", "r") init_header = init_header_file.read() @@ -1001,7 +1008,8 @@ class PretenseLuaGenerator(LuaGenerator): init_footer = init_footer_file.read() lua_string = ( - init_header + lua_string_savefile + + init_header + lua_string_zones + lua_string_connman + init_body_1 diff --git a/resources/plugins/pretense/init_header.lua b/resources/plugins/pretense/init_header.lua index fce64047..5854907d 100644 --- a/resources/plugins/pretense/init_header.lua +++ b/resources/plugins/pretense/init_header.lua @@ -1,7 +1,6 @@ -local savefile = 'pretense_1.1.json' -if lfs then +if lfs then local dir = lfs.writedir()..'Missions/Saves/' lfs.mkdir(dir) savefile = dir..savefile From 3ab0f2a5547743e808bd31769c9d53bc7dcd6222 Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Mon, 22 Jan 2024 18:28:13 +0200 Subject: [PATCH 191/243] Rename resources/plugins/pretense/init_body_3.lua (from resources/plugins/pretense/init_body_2.lua) --- resources/plugins/pretense/{init_body_2.lua => init_body_3.lua} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename resources/plugins/pretense/{init_body_2.lua => init_body_3.lua} (100%) diff --git a/resources/plugins/pretense/init_body_2.lua b/resources/plugins/pretense/init_body_3.lua similarity index 100% rename from resources/plugins/pretense/init_body_2.lua rename to resources/plugins/pretense/init_body_3.lua From cafc6b5d30f5514e92ed9066e9624b7f5c9a47b1 Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Mon, 22 Jan 2024 18:28:31 +0200 Subject: [PATCH 192/243] Rename resources/plugins/pretense/init_body_2.lua (from resources/plugins/pretense/init_body_1.lua) --- resources/plugins/pretense/{init_body_1.lua => init_body_2.lua} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename resources/plugins/pretense/{init_body_1.lua => init_body_2.lua} (100%) diff --git a/resources/plugins/pretense/init_body_1.lua b/resources/plugins/pretense/init_body_2.lua similarity index 100% rename from resources/plugins/pretense/init_body_1.lua rename to resources/plugins/pretense/init_body_2.lua From c0a887b258c6e40adc7f162f8c7c0c358383cd82 Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Mon, 22 Jan 2024 18:29:18 +0200 Subject: [PATCH 193/243] Copied init_header.lua to init_body_1.lua --- resources/plugins/pretense/init_body_1.lua | 764 +++++++++++++++++++++ 1 file changed, 764 insertions(+) create mode 100644 resources/plugins/pretense/init_body_1.lua diff --git a/resources/plugins/pretense/init_body_1.lua b/resources/plugins/pretense/init_body_1.lua new file mode 100644 index 00000000..5854907d --- /dev/null +++ b/resources/plugins/pretense/init_body_1.lua @@ -0,0 +1,764 @@ + + +if lfs then + local dir = lfs.writedir()..'Missions/Saves/' + lfs.mkdir(dir) + savefile = dir..savefile + env.info('Pretense - Save file path: '..savefile) +end + + +do + TemplateDB.templates["infantry-red"] = { + units = { + "BTR_D", + "T-90", + "T-90", + "Infantry AK ver2", + "Infantry AK", + "Infantry AK", + "Paratrooper RPG-16", + "Infantry AK ver3", + "SA-18 Igla manpad" + }, + skill = "Excellent", + dataCategory= TemplateDB.type.group + } + + TemplateDB.templates["infantry-blue"] = { + units = { + "M1045 HMMWV TOW", + "Soldier stinger", + "Soldier M4 GRG", + "Soldier M4 GRG", + "Soldier M4 GRG", + "Soldier M4 GRG", + "Soldier M4 GRG", + "M1043 HMMWV Armament" + }, + skill = "Excellent", + dataCategory= TemplateDB.type.group + } + + TemplateDB.templates["defense-red"] = { + units = { + "Infantry AK ver2", + "Infantry AK", + "Infantry AK ver3", + "Paratrooper RPG-16", + "SA-18 Igla manpad" + }, + skill = "Excellent", + dataCategory= TemplateDB.type.group + } + + TemplateDB.templates["defense-blue"] = { + units = { + "Soldier M4 GRG", + "Soldier M4 GRG", + "Soldier M4 GRG", + "Soldier RPG", + "Soldier stinger", + }, + skill = "Excellent", + dataCategory= TemplateDB.type.group + } + + TemplateDB.templates["shorad-red"] = { + units = { + "Strela-10M3", + "Strela-10M3", + "Ural-4320T", + "2S6 Tunguska" + }, + maxDist = 300, + skill = "Excellent", + dataCategory= TemplateDB.type.group + } + + TemplateDB.templates["shorad-blue"] = { + units = { + "Roland ADS", + "M48 Chaparral", + "M 818", + "Gepard", + "Gepard" + }, + maxDist = 300, + skill = "Excellent", + dataCategory= TemplateDB.type.group + } + + TemplateDB.templates["sa2"] = { + units = { + "p-19 s-125 sr", + "Ural-4320T", + "Ural-4320T", + "S_75M_Volhov", + "S_75M_Volhov", + "S_75M_Volhov", + "S_75M_Volhov", + "S_75M_Volhov", + "Tor 9A331", + "SNR_75V" + }, + maxDist = 300, + skill = "Excellent", + dataCategory= TemplateDB.type.group + } + + TemplateDB.templates["hawk"] = { + units = { + "Hawk pcp", + "Hawk cwar", + "Hawk ln", + "Hawk ln", + "Hawk ln", + "Hawk ln", + "Hawk ln", + "Hawk tr", + "M 818", + "Hawk sr" + }, + maxDist = 300, + skill = "Good", + dataCategory= TemplateDB.type.group + } + + TemplateDB.templates["patriot"] = { + units = { + "Patriot cp", + "Patriot str", + "M 818", + "M 818", + "Patriot ln", + "Patriot ln", + "Patriot ln", + "Patriot ln", + "Patriot str", + "Patriot str", + "Patriot str", + "Patriot EPP", + "Patriot ECS", + "Patriot AMG" + }, + maxDist = 300, + skill = "Good", + dataCategory= TemplateDB.type.group + } + + TemplateDB.templates["sa3"] = { + units = { + "p-19 s-125 sr", + "snr s-125 tr", + "5p73 s-125 ln", + "5p73 s-125 ln", + "Ural-4320T", + "5p73 s-125 ln", + "5p73 s-125 ln" + }, + maxDist = 300, + skill = "Excellent", + dataCategory= TemplateDB.type.group + } + + TemplateDB.templates["sa6"] = { + units = { + "Kub 1S91 str", + "Kub 2P25 ln", + "Kub 2P25 ln", + "Kub 2P25 ln", + "Kub 2P25 ln", + "2S6 Tunguska", + "Ural-4320T", + "2S6 Tunguska", + "Kub 2P25 ln" + }, + maxDist = 300, + skill = "Excellent", + dataCategory= TemplateDB.type.group + } + + TemplateDB.templates["sa10"] = { + units = { + "S-300PS 54K6 cp", + "S-300PS 5P85C ln", + "S-300PS 5P85C ln", + "S-300PS 5P85C ln", + "GAZ-66", + "GAZ-66", + "GAZ-66", + "S-300PS 5P85C ln", + "S-300PS 5P85C ln", + "S-300PS 5P85C ln", + "S-300PS 40B6MD sr", + "S-300PS 40B6M tr", + "S-300PS 64H6E sr" + }, + maxDist = 300, + skill = "Excellent", + dataCategory= TemplateDB.type.group + } + + TemplateDB.templates["sa5"] = { + units = { + "RLS_19J6", + "Ural-4320T", + "Ural-4320T", + "RPC_5N62V", + "S-200_Launcher", + "S-200_Launcher", + "S-200_Launcher", + "S-200_Launcher", + "S-200_Launcher", + "S-200_Launcher" + }, + maxDist = 300, + skill = "Excellent", + dataCategory= TemplateDB.type.group + } + + TemplateDB.templates["sa11"] = { + units = { + "SA-11 Buk SR 9S18M1", + "SA-11 Buk LN 9A310M1", + "SA-11 Buk LN 9A310M1", + "SA-11 Buk LN 9A310M1", + "SA-11 Buk LN 9A310M1", + "SA-11 Buk LN 9A310M1", + "2S6 Tunguska", + "SA-11 Buk SR 9S18M1", + "GAZ-66", + "GAZ-66", + "SA-11 Buk CC 9S470M1" + }, + maxDist = 300, + skill = "Excellent", + dataCategory= TemplateDB.type.group + } + + TemplateDB.templates["nasams"] = { + units = { + "NASAMS_Command_Post", + "NASAMS_Radar_MPQ64F1", + "Vulcan", + "M 818", + "M 818", + "Roland ADS", + "Roland ADS", + "NASAMS_LN_C", + "NASAMS_LN_C", + "NASAMS_LN_C", + "NASAMS_LN_C", + "NASAMS_Radar_MPQ64F1", + "NASAMS_Radar_MPQ64F1", + "NASAMS_Radar_MPQ64F1" + }, + maxDist = 300, + skill = "Excellent", + dataCategory= TemplateDB.type.group + } + + TemplateDB.templates["blueShipGroup"] = { + units = { + "PERRY", + "USS_Arleigh_Burke_IIa", + "PERRY" + }, + maxDist = 300, + skill = "Excellent", + dataCategory= TemplateDB.type.group + } + + TemplateDB.templates["redShipGroup"] = { + units = { + "ALBATROS", + "NEUSTRASH", + "ALBATROS" + }, + maxDist = 300, + skill = "Excellent", + dataCategory= TemplateDB.type.group + } +end + +presets = { + upgrades = { + basic = { + tent = Preset:new({ + display = 'Tent', + cost = 1500, + type = 'upgrade', + template = "tent" + }), + comPost = Preset:new({ + display = 'Barracks', + cost = 1500, + type = 'upgrade', + template = "barracks" + }), + outpost = Preset:new({ + display = 'Outpost', + cost = 1500, + type = 'upgrade', + template = "outpost" + }) + }, + attack = { + ammoCache = Preset:new({ + display = 'Ammo Cache', + cost = 1500, + type = 'upgrade', + template = "ammo-cache" + }), + ammoDepot = Preset:new({ + display = 'Ammo Depot', + cost = 2000, + type = 'upgrade', + template = "ammo-depot" + }), + shipTankerSeawisegiant = Preset:new({ + display = 'Tanker Seawise Giant', + cost = 1500, + type = 'upgrade', + template = "ship-tanker-seawisegiant" + }), + shipLandingShipSamuelChase = Preset:new({ + display = 'LST USS Samuel Chase', + cost = 1500, + type = 'upgrade', + template = "ship-landingship-samuelchase" + }), + shipLandingShipRopucha = Preset:new({ + display = 'LS Ropucha', + cost = 1500, + type = 'upgrade', + template = "ship-landingship-ropucha" + }), + shipTankerElnya = Preset:new({ + display = 'Tanker Elnya', + cost = 1500, + type = 'upgrade', + template = "ship-tanker-elnya" + }) + }, + supply = { + fuelCache = Preset:new({ + display = 'Fuel Cache', + cost = 1500, + type = 'upgrade', + template = "fuel-cache" + }), + fuelTank = Preset:new({ + display = 'Fuel Tank', + cost = 1500, + type = 'upgrade', + template = "fuel-tank-big" + }), + fuelTankFarp = Preset:new({ + display = 'Fuel Tank', + cost = 1500, + type = 'upgrade', + template = "fuel-tank-small" + }), + factory1 = Preset:new({ + display='Factory', + cost = 2000, + type ='upgrade', + income = 20, + template = "factory-1" + }), + factory2 = Preset:new({ + display='Factory', + cost = 2000, + type ='upgrade', + income = 20, + template = "factory-2" + }), + factoryTank = Preset:new({ + display='Storage Tank', + cost = 1500, + type ='upgrade', + income = 10, + template = "chem-tank" + }), + ammoDepot = Preset:new({ + display = 'Ammo Depot', + cost = 2000, + type = 'upgrade', + income = 40, + template = "ammo-depot" + }), + oilPump = Preset:new({ + display = 'Oil Pump', + cost = 1500, + type = 'upgrade', + income = 20, + template = "oil-pump" + }), + hangar = Preset:new({ + display = 'Hangar', + cost = 2000, + type = 'upgrade', + income = 30, + template = "hangar" + }), + excavator = Preset:new({ + display = 'Excavator', + cost = 2000, + type = 'upgrade', + income = 20, + template = "excavator" + }), + farm1 = Preset:new({ + display = 'Farm House', + cost = 2000, + type = 'upgrade', + income = 40, + template = "farm-house-1" + }), + farm2 = Preset:new({ + display = 'Farm House', + cost = 2000, + type = 'upgrade', + income = 40, + template = "farm-house-2" + }), + refinery1 = Preset:new({ + display='Refinery', + cost = 2000, + type ='upgrade', + income = 100, + template = "factory-1" + }), + powerplant1 = Preset:new({ + display='Power Plant', + cost = 1500, + type ='upgrade', + income = 25, + template = "factory-1" + }), + powerplant2 = Preset:new({ + display='Power Plant', + cost = 1500, + type ='upgrade', + income = 25, + template = "factory-2" + }), + antenna = Preset:new({ + display='Antenna', + cost = 1000, + type ='upgrade', + income = 10, + template = "antenna" + }), + hq = Preset:new({ + display='HQ Building', + cost = 2000, + type ='upgrade', + income = 50, + template = "tv-tower" + }), + shipSupplyTilde = Preset:new({ + display = 'Ship_Tilde_Supply', + cost = 1500, + type = 'upgrade', + template = "ship-supply-tilde" + }), + shipLandingShipLstMk2 = Preset:new({ + display = 'LST Mk.II', + cost = 1500, + type = 'upgrade', + template = "ship-landingship-lstmk2" + }), + shipBulkerYakushev = Preset:new({ + display = 'Bulker Yakushev', + cost = 1500, + type = 'upgrade', + template = "ship-bulker-yakushev" + }), + shipCargoIvanov = Preset:new({ + display = 'Cargo Ivanov', + cost = 1500, + type = 'upgrade', + template = "ship-cargo-ivanov" + }) + }, + airdef = { + bunker = Preset:new({ + display = 'Excavator', + cost = 1500, + type = 'upgrade', + template = "excavator" + }), + comCenter = Preset:new({ + display = 'Command Center', + cost = 12500, + type = 'upgrade', + template = "command-center" + }) + } + }, + defenses = { + red = { + infantry = Preset:new({ + display = 'Infantry', + cost=2000, + type='defense', + template='infantry-red', + }), + shorad = Preset:new({ + display = 'SAM', + cost=2500, + type='defense', + template='shorad-red', + }), + sa2 = Preset:new({ + display = 'SAM', + cost=3000, + type='defense', + template='sa2', + }), + sa10 = Preset:new({ + display = 'SAM', + cost=30000, + type='defense', + template='sa10', + }), + sa5 = Preset:new({ + display = 'SAM', + cost=20000, + type='defense', + template='sa5', + }), + sa3 = Preset:new({ + display = 'SAM', + cost=4000, + type='defense', + template='sa3', + }), + sa6 = Preset:new({ + display = 'SAM', + cost=6000, + type='defense', + template='sa6', + }), + sa11 = Preset:new({ + display = 'SAM', + cost=10000, + type='defense', + template='sa11', + }), + hawk = Preset:new({ + display = 'SAM', + cost=6000, + type='defense', + template='hawk', + }), + patriot = Preset:new({ + display = 'SAM', + cost=30000, + type='defense', + template='patriot', + }), + nasams = Preset:new({ + display = 'SAM', + cost=3000, + type='defense', + template='nasams', + }), + redShipGroup = Preset:new({ + display = 'SAM', + cost=3000, + type='defense', + template='redShipGroup', + }) + }, + blue = { + infantry = Preset:new({ + display = 'Infantry', + cost=2000, + type='defense', + template='infantry-blue', + }), + shorad = Preset:new({ + display = 'SAM', + cost=2500, + type='defense', + template='shorad-blue', + }), + sa2 = Preset:new({ + display = 'SAM', + cost=3000, + type='defense', + template='sa2', + }), + sa10 = Preset:new({ + display = 'SAM', + cost=30000, + type='defense', + template='sa10', + }), + sa5 = Preset:new({ + display = 'SAM', + cost=20000, + type='defense', + template='sa5', + }), + sa3 = Preset:new({ + display = 'SAM', + cost=4000, + type='defense', + template='sa3', + }), + sa6 = Preset:new({ + display = 'SAM', + cost=6000, + type='defense', + template='sa6', + }), + sa11 = Preset:new({ + display = 'SAM', + cost=10000, + type='defense', + template='sa11', + }), + hawk = Preset:new({ + display = 'SAM', + cost=6000, + type='defense', + template='hawk', + }), + patriot = Preset:new({ + display = 'SAM', + cost=30000, + type='defense', + template='patriot', + }), + nasams = Preset:new({ + display = 'SAM', + cost=3000, + type='defense', + template='nasams', + }), + blueShipGroup = Preset:new({ + display = 'SAM', + cost=3000, + type='defense', + template='blueShipGroup', + }) + } + }, + missions = { + supply = { + convoy = Preset:new({ + display = 'Supply convoy', + cost = 4000, + type = 'mission', + missionType = ZoneCommand.missionTypes.supply_convoy + }), + convoy_escorted = Preset:new({ + display = 'Supply convoy', + cost = 3000, + type = 'mission', + missionType = ZoneCommand.missionTypes.supply_convoy + }), + helo = Preset:new({ + display = 'Supply helicopter', + cost = 2500, + type='mission', + missionType = ZoneCommand.missionTypes.supply_air + }), + transfer = Preset:new({ + display = 'Supply transfer', + cost = 1000, + type='mission', + missionType = ZoneCommand.missionTypes.supply_transfer + }) + }, + attack = { + surface = Preset:new({ + display = 'Ground assault', + cost = 100, + type = 'mission', + missionType = ZoneCommand.missionTypes.assault, + }), + cas = Preset:new({ + display = 'CAS', + cost = 200, + type='mission', + missionType = ZoneCommand.missionTypes.cas + }), + bai = Preset:new({ + display = 'BAI', + cost = 200, + type='mission', + missionType = ZoneCommand.missionTypes.bai + }), + strike = Preset:new({ + display = 'Strike', + cost = 300, + type='mission', + missionType = ZoneCommand.missionTypes.strike + }), + sead = Preset:new({ + display = 'SEAD', + cost = 200, + type='mission', + missionType = ZoneCommand.missionTypes.sead + }), + helo = Preset:new({ + display = 'CAS', + cost = 100, + type='mission', + missionType = ZoneCommand.missionTypes.cas_helo + }) + }, + patrol={ + aircraft = Preset:new({ + display= "Patrol", + cost = 100, + type='mission', + missionType = ZoneCommand.missionTypes.patrol + }) + }, + support ={ + awacs = Preset:new({ + display= "AWACS", + cost = 300, + type='mission', + bias='5', + missionType = ZoneCommand.missionTypes.awacs + }), + tanker = Preset:new({ + display= "Tanker", + cost = 200, + type='mission', + bias='2', + missionType = ZoneCommand.missionTypes.tanker + }) + } + }, + special = { + red = { + infantry = Preset:new({ + display = 'Infantry', + cost=-1, + type='defense', + template='defense-red', + }), + }, + blue = { + infantry = Preset:new({ + display = 'Infantry', + cost=-1, + type='defense', + template='defense-blue', + }) + } + } +} + +zones = {} +do + From 100eafcffdb79488fc8c3e66399ff3208ea1ae43 Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Mon, 22 Jan 2024 18:30:09 +0200 Subject: [PATCH 194/243] Separated init_header.lua into init_body_1.lua and init_header.lua. Deleted the ground unit groups since they will eventually be generated dynamically. --- resources/plugins/pretense/init_body_1.lua | 281 -------- resources/plugins/pretense/init_header.lua | 752 --------------------- 2 files changed, 1033 deletions(-) diff --git a/resources/plugins/pretense/init_body_1.lua b/resources/plugins/pretense/init_body_1.lua index 5854907d..0d528ddf 100644 --- a/resources/plugins/pretense/init_body_1.lua +++ b/resources/plugins/pretense/init_body_1.lua @@ -1,285 +1,4 @@ - -if lfs then - local dir = lfs.writedir()..'Missions/Saves/' - lfs.mkdir(dir) - savefile = dir..savefile - env.info('Pretense - Save file path: '..savefile) -end - - -do - TemplateDB.templates["infantry-red"] = { - units = { - "BTR_D", - "T-90", - "T-90", - "Infantry AK ver2", - "Infantry AK", - "Infantry AK", - "Paratrooper RPG-16", - "Infantry AK ver3", - "SA-18 Igla manpad" - }, - skill = "Excellent", - dataCategory= TemplateDB.type.group - } - - TemplateDB.templates["infantry-blue"] = { - units = { - "M1045 HMMWV TOW", - "Soldier stinger", - "Soldier M4 GRG", - "Soldier M4 GRG", - "Soldier M4 GRG", - "Soldier M4 GRG", - "Soldier M4 GRG", - "M1043 HMMWV Armament" - }, - skill = "Excellent", - dataCategory= TemplateDB.type.group - } - - TemplateDB.templates["defense-red"] = { - units = { - "Infantry AK ver2", - "Infantry AK", - "Infantry AK ver3", - "Paratrooper RPG-16", - "SA-18 Igla manpad" - }, - skill = "Excellent", - dataCategory= TemplateDB.type.group - } - - TemplateDB.templates["defense-blue"] = { - units = { - "Soldier M4 GRG", - "Soldier M4 GRG", - "Soldier M4 GRG", - "Soldier RPG", - "Soldier stinger", - }, - skill = "Excellent", - dataCategory= TemplateDB.type.group - } - - TemplateDB.templates["shorad-red"] = { - units = { - "Strela-10M3", - "Strela-10M3", - "Ural-4320T", - "2S6 Tunguska" - }, - maxDist = 300, - skill = "Excellent", - dataCategory= TemplateDB.type.group - } - - TemplateDB.templates["shorad-blue"] = { - units = { - "Roland ADS", - "M48 Chaparral", - "M 818", - "Gepard", - "Gepard" - }, - maxDist = 300, - skill = "Excellent", - dataCategory= TemplateDB.type.group - } - - TemplateDB.templates["sa2"] = { - units = { - "p-19 s-125 sr", - "Ural-4320T", - "Ural-4320T", - "S_75M_Volhov", - "S_75M_Volhov", - "S_75M_Volhov", - "S_75M_Volhov", - "S_75M_Volhov", - "Tor 9A331", - "SNR_75V" - }, - maxDist = 300, - skill = "Excellent", - dataCategory= TemplateDB.type.group - } - - TemplateDB.templates["hawk"] = { - units = { - "Hawk pcp", - "Hawk cwar", - "Hawk ln", - "Hawk ln", - "Hawk ln", - "Hawk ln", - "Hawk ln", - "Hawk tr", - "M 818", - "Hawk sr" - }, - maxDist = 300, - skill = "Good", - dataCategory= TemplateDB.type.group - } - - TemplateDB.templates["patriot"] = { - units = { - "Patriot cp", - "Patriot str", - "M 818", - "M 818", - "Patriot ln", - "Patriot ln", - "Patriot ln", - "Patriot ln", - "Patriot str", - "Patriot str", - "Patriot str", - "Patriot EPP", - "Patriot ECS", - "Patriot AMG" - }, - maxDist = 300, - skill = "Good", - dataCategory= TemplateDB.type.group - } - - TemplateDB.templates["sa3"] = { - units = { - "p-19 s-125 sr", - "snr s-125 tr", - "5p73 s-125 ln", - "5p73 s-125 ln", - "Ural-4320T", - "5p73 s-125 ln", - "5p73 s-125 ln" - }, - maxDist = 300, - skill = "Excellent", - dataCategory= TemplateDB.type.group - } - - TemplateDB.templates["sa6"] = { - units = { - "Kub 1S91 str", - "Kub 2P25 ln", - "Kub 2P25 ln", - "Kub 2P25 ln", - "Kub 2P25 ln", - "2S6 Tunguska", - "Ural-4320T", - "2S6 Tunguska", - "Kub 2P25 ln" - }, - maxDist = 300, - skill = "Excellent", - dataCategory= TemplateDB.type.group - } - - TemplateDB.templates["sa10"] = { - units = { - "S-300PS 54K6 cp", - "S-300PS 5P85C ln", - "S-300PS 5P85C ln", - "S-300PS 5P85C ln", - "GAZ-66", - "GAZ-66", - "GAZ-66", - "S-300PS 5P85C ln", - "S-300PS 5P85C ln", - "S-300PS 5P85C ln", - "S-300PS 40B6MD sr", - "S-300PS 40B6M tr", - "S-300PS 64H6E sr" - }, - maxDist = 300, - skill = "Excellent", - dataCategory= TemplateDB.type.group - } - - TemplateDB.templates["sa5"] = { - units = { - "RLS_19J6", - "Ural-4320T", - "Ural-4320T", - "RPC_5N62V", - "S-200_Launcher", - "S-200_Launcher", - "S-200_Launcher", - "S-200_Launcher", - "S-200_Launcher", - "S-200_Launcher" - }, - maxDist = 300, - skill = "Excellent", - dataCategory= TemplateDB.type.group - } - - TemplateDB.templates["sa11"] = { - units = { - "SA-11 Buk SR 9S18M1", - "SA-11 Buk LN 9A310M1", - "SA-11 Buk LN 9A310M1", - "SA-11 Buk LN 9A310M1", - "SA-11 Buk LN 9A310M1", - "SA-11 Buk LN 9A310M1", - "2S6 Tunguska", - "SA-11 Buk SR 9S18M1", - "GAZ-66", - "GAZ-66", - "SA-11 Buk CC 9S470M1" - }, - maxDist = 300, - skill = "Excellent", - dataCategory= TemplateDB.type.group - } - - TemplateDB.templates["nasams"] = { - units = { - "NASAMS_Command_Post", - "NASAMS_Radar_MPQ64F1", - "Vulcan", - "M 818", - "M 818", - "Roland ADS", - "Roland ADS", - "NASAMS_LN_C", - "NASAMS_LN_C", - "NASAMS_LN_C", - "NASAMS_LN_C", - "NASAMS_Radar_MPQ64F1", - "NASAMS_Radar_MPQ64F1", - "NASAMS_Radar_MPQ64F1" - }, - maxDist = 300, - skill = "Excellent", - dataCategory= TemplateDB.type.group - } - - TemplateDB.templates["blueShipGroup"] = { - units = { - "PERRY", - "USS_Arleigh_Burke_IIa", - "PERRY" - }, - maxDist = 300, - skill = "Excellent", - dataCategory= TemplateDB.type.group - } - - TemplateDB.templates["redShipGroup"] = { - units = { - "ALBATROS", - "NEUSTRASH", - "ALBATROS" - }, - maxDist = 300, - skill = "Excellent", - dataCategory= TemplateDB.type.group - } end presets = { diff --git a/resources/plugins/pretense/init_header.lua b/resources/plugins/pretense/init_header.lua index 5854907d..3b9cbbf5 100644 --- a/resources/plugins/pretense/init_header.lua +++ b/resources/plugins/pretense/init_header.lua @@ -8,757 +8,5 @@ if lfs then end -do - TemplateDB.templates["infantry-red"] = { - units = { - "BTR_D", - "T-90", - "T-90", - "Infantry AK ver2", - "Infantry AK", - "Infantry AK", - "Paratrooper RPG-16", - "Infantry AK ver3", - "SA-18 Igla manpad" - }, - skill = "Excellent", - dataCategory= TemplateDB.type.group - } - - TemplateDB.templates["infantry-blue"] = { - units = { - "M1045 HMMWV TOW", - "Soldier stinger", - "Soldier M4 GRG", - "Soldier M4 GRG", - "Soldier M4 GRG", - "Soldier M4 GRG", - "Soldier M4 GRG", - "M1043 HMMWV Armament" - }, - skill = "Excellent", - dataCategory= TemplateDB.type.group - } - - TemplateDB.templates["defense-red"] = { - units = { - "Infantry AK ver2", - "Infantry AK", - "Infantry AK ver3", - "Paratrooper RPG-16", - "SA-18 Igla manpad" - }, - skill = "Excellent", - dataCategory= TemplateDB.type.group - } - - TemplateDB.templates["defense-blue"] = { - units = { - "Soldier M4 GRG", - "Soldier M4 GRG", - "Soldier M4 GRG", - "Soldier RPG", - "Soldier stinger", - }, - skill = "Excellent", - dataCategory= TemplateDB.type.group - } - - TemplateDB.templates["shorad-red"] = { - units = { - "Strela-10M3", - "Strela-10M3", - "Ural-4320T", - "2S6 Tunguska" - }, - maxDist = 300, - skill = "Excellent", - dataCategory= TemplateDB.type.group - } - - TemplateDB.templates["shorad-blue"] = { - units = { - "Roland ADS", - "M48 Chaparral", - "M 818", - "Gepard", - "Gepard" - }, - maxDist = 300, - skill = "Excellent", - dataCategory= TemplateDB.type.group - } - - TemplateDB.templates["sa2"] = { - units = { - "p-19 s-125 sr", - "Ural-4320T", - "Ural-4320T", - "S_75M_Volhov", - "S_75M_Volhov", - "S_75M_Volhov", - "S_75M_Volhov", - "S_75M_Volhov", - "Tor 9A331", - "SNR_75V" - }, - maxDist = 300, - skill = "Excellent", - dataCategory= TemplateDB.type.group - } - - TemplateDB.templates["hawk"] = { - units = { - "Hawk pcp", - "Hawk cwar", - "Hawk ln", - "Hawk ln", - "Hawk ln", - "Hawk ln", - "Hawk ln", - "Hawk tr", - "M 818", - "Hawk sr" - }, - maxDist = 300, - skill = "Good", - dataCategory= TemplateDB.type.group - } - - TemplateDB.templates["patriot"] = { - units = { - "Patriot cp", - "Patriot str", - "M 818", - "M 818", - "Patriot ln", - "Patriot ln", - "Patriot ln", - "Patriot ln", - "Patriot str", - "Patriot str", - "Patriot str", - "Patriot EPP", - "Patriot ECS", - "Patriot AMG" - }, - maxDist = 300, - skill = "Good", - dataCategory= TemplateDB.type.group - } - - TemplateDB.templates["sa3"] = { - units = { - "p-19 s-125 sr", - "snr s-125 tr", - "5p73 s-125 ln", - "5p73 s-125 ln", - "Ural-4320T", - "5p73 s-125 ln", - "5p73 s-125 ln" - }, - maxDist = 300, - skill = "Excellent", - dataCategory= TemplateDB.type.group - } - - TemplateDB.templates["sa6"] = { - units = { - "Kub 1S91 str", - "Kub 2P25 ln", - "Kub 2P25 ln", - "Kub 2P25 ln", - "Kub 2P25 ln", - "2S6 Tunguska", - "Ural-4320T", - "2S6 Tunguska", - "Kub 2P25 ln" - }, - maxDist = 300, - skill = "Excellent", - dataCategory= TemplateDB.type.group - } - - TemplateDB.templates["sa10"] = { - units = { - "S-300PS 54K6 cp", - "S-300PS 5P85C ln", - "S-300PS 5P85C ln", - "S-300PS 5P85C ln", - "GAZ-66", - "GAZ-66", - "GAZ-66", - "S-300PS 5P85C ln", - "S-300PS 5P85C ln", - "S-300PS 5P85C ln", - "S-300PS 40B6MD sr", - "S-300PS 40B6M tr", - "S-300PS 64H6E sr" - }, - maxDist = 300, - skill = "Excellent", - dataCategory= TemplateDB.type.group - } - - TemplateDB.templates["sa5"] = { - units = { - "RLS_19J6", - "Ural-4320T", - "Ural-4320T", - "RPC_5N62V", - "S-200_Launcher", - "S-200_Launcher", - "S-200_Launcher", - "S-200_Launcher", - "S-200_Launcher", - "S-200_Launcher" - }, - maxDist = 300, - skill = "Excellent", - dataCategory= TemplateDB.type.group - } - - TemplateDB.templates["sa11"] = { - units = { - "SA-11 Buk SR 9S18M1", - "SA-11 Buk LN 9A310M1", - "SA-11 Buk LN 9A310M1", - "SA-11 Buk LN 9A310M1", - "SA-11 Buk LN 9A310M1", - "SA-11 Buk LN 9A310M1", - "2S6 Tunguska", - "SA-11 Buk SR 9S18M1", - "GAZ-66", - "GAZ-66", - "SA-11 Buk CC 9S470M1" - }, - maxDist = 300, - skill = "Excellent", - dataCategory= TemplateDB.type.group - } - - TemplateDB.templates["nasams"] = { - units = { - "NASAMS_Command_Post", - "NASAMS_Radar_MPQ64F1", - "Vulcan", - "M 818", - "M 818", - "Roland ADS", - "Roland ADS", - "NASAMS_LN_C", - "NASAMS_LN_C", - "NASAMS_LN_C", - "NASAMS_LN_C", - "NASAMS_Radar_MPQ64F1", - "NASAMS_Radar_MPQ64F1", - "NASAMS_Radar_MPQ64F1" - }, - maxDist = 300, - skill = "Excellent", - dataCategory= TemplateDB.type.group - } - - TemplateDB.templates["blueShipGroup"] = { - units = { - "PERRY", - "USS_Arleigh_Burke_IIa", - "PERRY" - }, - maxDist = 300, - skill = "Excellent", - dataCategory= TemplateDB.type.group - } - - TemplateDB.templates["redShipGroup"] = { - units = { - "ALBATROS", - "NEUSTRASH", - "ALBATROS" - }, - maxDist = 300, - skill = "Excellent", - dataCategory= TemplateDB.type.group - } -end - -presets = { - upgrades = { - basic = { - tent = Preset:new({ - display = 'Tent', - cost = 1500, - type = 'upgrade', - template = "tent" - }), - comPost = Preset:new({ - display = 'Barracks', - cost = 1500, - type = 'upgrade', - template = "barracks" - }), - outpost = Preset:new({ - display = 'Outpost', - cost = 1500, - type = 'upgrade', - template = "outpost" - }) - }, - attack = { - ammoCache = Preset:new({ - display = 'Ammo Cache', - cost = 1500, - type = 'upgrade', - template = "ammo-cache" - }), - ammoDepot = Preset:new({ - display = 'Ammo Depot', - cost = 2000, - type = 'upgrade', - template = "ammo-depot" - }), - shipTankerSeawisegiant = Preset:new({ - display = 'Tanker Seawise Giant', - cost = 1500, - type = 'upgrade', - template = "ship-tanker-seawisegiant" - }), - shipLandingShipSamuelChase = Preset:new({ - display = 'LST USS Samuel Chase', - cost = 1500, - type = 'upgrade', - template = "ship-landingship-samuelchase" - }), - shipLandingShipRopucha = Preset:new({ - display = 'LS Ropucha', - cost = 1500, - type = 'upgrade', - template = "ship-landingship-ropucha" - }), - shipTankerElnya = Preset:new({ - display = 'Tanker Elnya', - cost = 1500, - type = 'upgrade', - template = "ship-tanker-elnya" - }) - }, - supply = { - fuelCache = Preset:new({ - display = 'Fuel Cache', - cost = 1500, - type = 'upgrade', - template = "fuel-cache" - }), - fuelTank = Preset:new({ - display = 'Fuel Tank', - cost = 1500, - type = 'upgrade', - template = "fuel-tank-big" - }), - fuelTankFarp = Preset:new({ - display = 'Fuel Tank', - cost = 1500, - type = 'upgrade', - template = "fuel-tank-small" - }), - factory1 = Preset:new({ - display='Factory', - cost = 2000, - type ='upgrade', - income = 20, - template = "factory-1" - }), - factory2 = Preset:new({ - display='Factory', - cost = 2000, - type ='upgrade', - income = 20, - template = "factory-2" - }), - factoryTank = Preset:new({ - display='Storage Tank', - cost = 1500, - type ='upgrade', - income = 10, - template = "chem-tank" - }), - ammoDepot = Preset:new({ - display = 'Ammo Depot', - cost = 2000, - type = 'upgrade', - income = 40, - template = "ammo-depot" - }), - oilPump = Preset:new({ - display = 'Oil Pump', - cost = 1500, - type = 'upgrade', - income = 20, - template = "oil-pump" - }), - hangar = Preset:new({ - display = 'Hangar', - cost = 2000, - type = 'upgrade', - income = 30, - template = "hangar" - }), - excavator = Preset:new({ - display = 'Excavator', - cost = 2000, - type = 'upgrade', - income = 20, - template = "excavator" - }), - farm1 = Preset:new({ - display = 'Farm House', - cost = 2000, - type = 'upgrade', - income = 40, - template = "farm-house-1" - }), - farm2 = Preset:new({ - display = 'Farm House', - cost = 2000, - type = 'upgrade', - income = 40, - template = "farm-house-2" - }), - refinery1 = Preset:new({ - display='Refinery', - cost = 2000, - type ='upgrade', - income = 100, - template = "factory-1" - }), - powerplant1 = Preset:new({ - display='Power Plant', - cost = 1500, - type ='upgrade', - income = 25, - template = "factory-1" - }), - powerplant2 = Preset:new({ - display='Power Plant', - cost = 1500, - type ='upgrade', - income = 25, - template = "factory-2" - }), - antenna = Preset:new({ - display='Antenna', - cost = 1000, - type ='upgrade', - income = 10, - template = "antenna" - }), - hq = Preset:new({ - display='HQ Building', - cost = 2000, - type ='upgrade', - income = 50, - template = "tv-tower" - }), - shipSupplyTilde = Preset:new({ - display = 'Ship_Tilde_Supply', - cost = 1500, - type = 'upgrade', - template = "ship-supply-tilde" - }), - shipLandingShipLstMk2 = Preset:new({ - display = 'LST Mk.II', - cost = 1500, - type = 'upgrade', - template = "ship-landingship-lstmk2" - }), - shipBulkerYakushev = Preset:new({ - display = 'Bulker Yakushev', - cost = 1500, - type = 'upgrade', - template = "ship-bulker-yakushev" - }), - shipCargoIvanov = Preset:new({ - display = 'Cargo Ivanov', - cost = 1500, - type = 'upgrade', - template = "ship-cargo-ivanov" - }) - }, - airdef = { - bunker = Preset:new({ - display = 'Excavator', - cost = 1500, - type = 'upgrade', - template = "excavator" - }), - comCenter = Preset:new({ - display = 'Command Center', - cost = 12500, - type = 'upgrade', - template = "command-center" - }) - } - }, - defenses = { - red = { - infantry = Preset:new({ - display = 'Infantry', - cost=2000, - type='defense', - template='infantry-red', - }), - shorad = Preset:new({ - display = 'SAM', - cost=2500, - type='defense', - template='shorad-red', - }), - sa2 = Preset:new({ - display = 'SAM', - cost=3000, - type='defense', - template='sa2', - }), - sa10 = Preset:new({ - display = 'SAM', - cost=30000, - type='defense', - template='sa10', - }), - sa5 = Preset:new({ - display = 'SAM', - cost=20000, - type='defense', - template='sa5', - }), - sa3 = Preset:new({ - display = 'SAM', - cost=4000, - type='defense', - template='sa3', - }), - sa6 = Preset:new({ - display = 'SAM', - cost=6000, - type='defense', - template='sa6', - }), - sa11 = Preset:new({ - display = 'SAM', - cost=10000, - type='defense', - template='sa11', - }), - hawk = Preset:new({ - display = 'SAM', - cost=6000, - type='defense', - template='hawk', - }), - patriot = Preset:new({ - display = 'SAM', - cost=30000, - type='defense', - template='patriot', - }), - nasams = Preset:new({ - display = 'SAM', - cost=3000, - type='defense', - template='nasams', - }), - redShipGroup = Preset:new({ - display = 'SAM', - cost=3000, - type='defense', - template='redShipGroup', - }) - }, - blue = { - infantry = Preset:new({ - display = 'Infantry', - cost=2000, - type='defense', - template='infantry-blue', - }), - shorad = Preset:new({ - display = 'SAM', - cost=2500, - type='defense', - template='shorad-blue', - }), - sa2 = Preset:new({ - display = 'SAM', - cost=3000, - type='defense', - template='sa2', - }), - sa10 = Preset:new({ - display = 'SAM', - cost=30000, - type='defense', - template='sa10', - }), - sa5 = Preset:new({ - display = 'SAM', - cost=20000, - type='defense', - template='sa5', - }), - sa3 = Preset:new({ - display = 'SAM', - cost=4000, - type='defense', - template='sa3', - }), - sa6 = Preset:new({ - display = 'SAM', - cost=6000, - type='defense', - template='sa6', - }), - sa11 = Preset:new({ - display = 'SAM', - cost=10000, - type='defense', - template='sa11', - }), - hawk = Preset:new({ - display = 'SAM', - cost=6000, - type='defense', - template='hawk', - }), - patriot = Preset:new({ - display = 'SAM', - cost=30000, - type='defense', - template='patriot', - }), - nasams = Preset:new({ - display = 'SAM', - cost=3000, - type='defense', - template='nasams', - }), - blueShipGroup = Preset:new({ - display = 'SAM', - cost=3000, - type='defense', - template='blueShipGroup', - }) - } - }, - missions = { - supply = { - convoy = Preset:new({ - display = 'Supply convoy', - cost = 4000, - type = 'mission', - missionType = ZoneCommand.missionTypes.supply_convoy - }), - convoy_escorted = Preset:new({ - display = 'Supply convoy', - cost = 3000, - type = 'mission', - missionType = ZoneCommand.missionTypes.supply_convoy - }), - helo = Preset:new({ - display = 'Supply helicopter', - cost = 2500, - type='mission', - missionType = ZoneCommand.missionTypes.supply_air - }), - transfer = Preset:new({ - display = 'Supply transfer', - cost = 1000, - type='mission', - missionType = ZoneCommand.missionTypes.supply_transfer - }) - }, - attack = { - surface = Preset:new({ - display = 'Ground assault', - cost = 100, - type = 'mission', - missionType = ZoneCommand.missionTypes.assault, - }), - cas = Preset:new({ - display = 'CAS', - cost = 200, - type='mission', - missionType = ZoneCommand.missionTypes.cas - }), - bai = Preset:new({ - display = 'BAI', - cost = 200, - type='mission', - missionType = ZoneCommand.missionTypes.bai - }), - strike = Preset:new({ - display = 'Strike', - cost = 300, - type='mission', - missionType = ZoneCommand.missionTypes.strike - }), - sead = Preset:new({ - display = 'SEAD', - cost = 200, - type='mission', - missionType = ZoneCommand.missionTypes.sead - }), - helo = Preset:new({ - display = 'CAS', - cost = 100, - type='mission', - missionType = ZoneCommand.missionTypes.cas_helo - }) - }, - patrol={ - aircraft = Preset:new({ - display= "Patrol", - cost = 100, - type='mission', - missionType = ZoneCommand.missionTypes.patrol - }) - }, - support ={ - awacs = Preset:new({ - display= "AWACS", - cost = 300, - type='mission', - bias='5', - missionType = ZoneCommand.missionTypes.awacs - }), - tanker = Preset:new({ - display= "Tanker", - cost = 200, - type='mission', - bias='2', - missionType = ZoneCommand.missionTypes.tanker - }) - } - }, - special = { - red = { - infantry = Preset:new({ - display = 'Infantry', - cost=-1, - type='defense', - template='defense-red', - }), - }, - blue = { - infantry = Preset:new({ - display = 'Infantry', - cost=-1, - type='defense', - template='defense-blue', - }) - } - } -} - -zones = {} do From 5cfeee7b2a3b77a2b23b629d0da5455447986f59 Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Mon, 22 Jan 2024 18:31:54 +0200 Subject: [PATCH 195/243] Helicopter escort squadrons no longer cause an error on Pretense campaign generation. --- game/pretense/pretenseaircraftgenerator.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/game/pretense/pretenseaircraftgenerator.py b/game/pretense/pretenseaircraftgenerator.py index 6b0a316c..e9b4484f 100644 --- a/game/pretense/pretenseaircraftgenerator.py +++ b/game/pretense/pretenseaircraftgenerator.py @@ -193,9 +193,6 @@ class PretenseAircraftGenerator: squadron_def = coalition.air_wing.squadron_def_generator.generate_for_task( flight_type, cp ) - print( - f"Generating a squadron definition for fixed-wing {fixed_wing} squadron at {cp}" - ) for retries in range(num_retries): if squadron_def is None or fixed_wing == squadron_def.aircraft.helicopter: squadron_def = ( @@ -328,8 +325,10 @@ class PretenseAircraftGenerator: num_of_sead += 1 aircraft_per_flight = self.game.settings.pretense_ai_aircraft_per_flight elif ( - FlightType.CAS in mission_types - ) and num_of_cas < self.game.settings.pretense_cas_flights_per_cp: + (squadron.aircraft.helicopter and (FlightType.ESCORT in mission_types)) + or (FlightType.CAS in mission_types) + and num_of_cas < self.game.settings.pretense_cas_flights_per_cp + ): flight_type = FlightType.CAS num_of_cas += 1 aircraft_per_flight = self.game.settings.pretense_ai_aircraft_per_flight From 49ba40aaf37316984fe25db5c65f1b7fdcc75c4c Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Mon, 22 Jan 2024 18:32:32 +0200 Subject: [PATCH 196/243] Now randomly shuffles the Pretense squadrons when generating a Pretense campaign. --- game/pretense/pretenseaircraftgenerator.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/game/pretense/pretenseaircraftgenerator.py b/game/pretense/pretenseaircraftgenerator.py index e9b4484f..a8c45dde 100644 --- a/game/pretense/pretenseaircraftgenerator.py +++ b/game/pretense/pretenseaircraftgenerator.py @@ -282,7 +282,9 @@ class PretenseAircraftGenerator: num_of_strike = 0 num_of_cap = 0 - for squadron in cp.squadrons: + random_squadron_list = list(cp.squadrons) + random.shuffle(random_squadron_list) + for squadron in random_squadron_list: # Intentionally don't spawn anything at OffMapSpawns in Pretense if isinstance(squadron.location, OffMapSpawn): continue From 969f0e26c7cfd37e29a9d0a5d2162fffb909b227 Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Mon, 22 Jan 2024 21:14:13 +0200 Subject: [PATCH 197/243] Pretense zone radius (radii) for FOBs with FARPs will now be dynamically adjusted. Increased the size of Pretense zones at Damascus, Khalkhalah and Krasnodar-Pashkovsky (which are quite spread out) so the zone would encompass the entire airfield. --- game/pretense/pretensetriggergenerator.py | 36 +++++++++++++++++++---- 1 file changed, 31 insertions(+), 5 deletions(-) diff --git a/game/pretense/pretensetriggergenerator.py b/game/pretense/pretensetriggergenerator.py index 740c17c6..0f33f68a 100644 --- a/game/pretense/pretensetriggergenerator.py +++ b/game/pretense/pretensetriggergenerator.py @@ -24,6 +24,8 @@ from dcs.condition import ( ) from dcs.mission import Mission from dcs.task import Option +from dcs.terrain.caucasus.airports import Krasnodar_Pashkovsky +from dcs.terrain.syria.airports import Damascus, Khalkhalah from dcs.translation import String from dcs.triggers import Event, TriggerCondition, TriggerOnce from dcs.unit import Skill @@ -53,6 +55,7 @@ TRIGGER_RADIUS_CLEAR_SCENERY = 1000 TRIGGER_RADIUS_PRETENSE_TGO = 500 TRIGGER_RADIUS_PRETENSE_SUPPLY = 500 TRIGGER_RADIUS_PRETENSE_HELI = 1000 +TRIGGER_RADIUS_PRETENSE_HELI_BUFFER = 500 TRIGGER_RADIUS_PRETENSE_CARRIER = 50000 TRIGGER_RUNWAY_LENGTH_PRETENSE = 2500 TRIGGER_RUNWAY_WIDTH_PRETENSE = 400 @@ -224,19 +227,42 @@ class PretenseTriggerGenerator: """ for cp in self.game.theater.controlpoints: if cp.is_fleet: - trigger_radius = TRIGGER_RADIUS_PRETENSE_CARRIER + trigger_radius = float(TRIGGER_RADIUS_PRETENSE_CARRIER) + elif isinstance(cp, Fob) and cp.has_helipads: + trigger_radius = TRIGGER_RADIUS_PRETENSE_HELI + for helipad in list( + cp.helipads + cp.helipads_quad + cp.helipads_invisible + ): + if cp.position.distance_to_point(helipad) > trigger_radius: + trigger_radius = cp.position.distance_to_point(helipad) + for ground_spawn, ground_spawn_wp in list( + cp.ground_spawns + cp.ground_spawns_roadbase + ): + if cp.position.distance_to_point(ground_spawn) > trigger_radius: + trigger_radius = cp.position.distance_to_point(ground_spawn) + trigger_radius += TRIGGER_RADIUS_PRETENSE_HELI_BUFFER else: - trigger_radius = TRIGGER_RADIUS_CAPTURE + if cp.dcs_airport is not None and ( + isinstance(cp.dcs_airport, Damascus) + or isinstance(cp.dcs_airport, Khalkhalah) + or isinstance(cp.dcs_airport, Krasnodar_Pashkovsky) + ): + trigger_radius = int(TRIGGER_RADIUS_CAPTURE * 1.8) + else: + trigger_radius = TRIGGER_RADIUS_CAPTURE + cp_name = "".join( + [i for i in cp.name if i.isalnum() or i.isspace() or i == "-"] + ) if not isinstance(cp, OffMapSpawn): zone_color = {1: 0.0, 2: 0.0, 3: 0.0, 4: 0.15} self.mission.triggers.add_triggerzone( cp.position, radius=trigger_radius, hidden=False, - name=cp.name, + name=cp_name, color=zone_color, ) - cp_name_trimmed = "".join([i for i in cp.name.lower() if i.isalnum()]) + cp_name_trimmed = "".join([i for i in cp.name.lower() if i.isalpha()]) tgo_num = 0 for tgo in cp.ground_objects: if cp.is_fleet or tgo.sea_object: @@ -285,7 +311,7 @@ class PretenseTriggerGenerator: if cp_airport is None: continue cp_name_trimmed = "".join( - [i for i in cp_airport.name.lower() if i.isalnum()] + [i for i in cp_airport.name.lower() if i.isalpha()] ) zone_color = {1: 0.0, 2: 1.0, 3: 0.5, 4: 0.15} if cp_airport is None: From a4d8c14579fbadc5d956e1f19334e20dd503f43d Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Mon, 22 Jan 2024 21:15:44 +0200 Subject: [PATCH 198/243] Added Iron Dome, David's Slinh, NASAMS-B and Rapier sites to Pretense. Fixed the timestamp in the save file name. --- game/pretense/pretenseluagenerator.py | 415 ++++++++++++++++++++- resources/plugins/pretense/init_body_1.lua | 92 +++-- 2 files changed, 476 insertions(+), 31 deletions(-) diff --git a/game/pretense/pretenseluagenerator.py b/game/pretense/pretenseluagenerator.py index 63cd22dd..971588a7 100644 --- a/game/pretense/pretenseluagenerator.py +++ b/game/pretense/pretenseluagenerator.py @@ -16,13 +16,17 @@ from dcs.triggers import TriggerStart from dcs.vehicles import AirDefence from game.ato import FlightType +from game.coalition import Coalition +from game.data.units import UnitClass 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.pretense.pretensetgogenerator import PretenseGroundObjectGenerator from game.theater import Airfield, OffMapSpawn, TheaterGroundObject from game.theater.iadsnetwork.iadsrole import IadsRole from game.utils import escape_string_for_lua +from pydcs_extensions import IRON_DOME_LN, DAVID_SLING_LN if TYPE_CHECKING: from game import Game @@ -276,7 +280,11 @@ class PretenseLuaGenerator(LuaGenerator): "sa11", "hawk", "patriot", - "nasams", + "nasamsb", + "nasamsc", + "rapier", + "irondome", + "davidsling", ]: sam_presets[sam_name] = PretenseSam(sam_name) @@ -364,11 +372,19 @@ class PretenseLuaGenerator(LuaGenerator): sam_presets["hawk"].enabled = True if ground_unit.unit_type.dcs_unit_type == AirDefence.Patriot_ln: sam_presets["patriot"].enabled = True + if ground_unit.unit_type.dcs_unit_type == AirDefence.NASAMS_LN_B: + sam_presets["nasamsb"].enabled = True + if ground_unit.unit_type.dcs_unit_type == AirDefence.NASAMS_LN_C: + sam_presets["nasamsc"].enabled = True if ( - ground_unit.unit_type.dcs_unit_type == AirDefence.NASAMS_LN_B - or ground_unit.unit_type.dcs_unit_type == AirDefence.NASAMS_LN_C + ground_unit.unit_type.dcs_unit_type + == AirDefence.Rapier_fsa_launcher ): - sam_presets["nasams"].enabled = True + sam_presets["rapier"].enabled = True + if ground_unit.unit_type.dcs_unit_type == IRON_DOME_LN: + sam_presets["irondome"].enabled = True + if ground_unit.unit_type.dcs_unit_type == DAVID_SLING_LN: + sam_presets["davidsling"].enabled = True cp_has_sams = False for sam_name in sam_presets: @@ -800,6 +816,365 @@ class PretenseLuaGenerator(LuaGenerator): return lua_string_zones + def get_ground_unit( + self, coalition: Coalition, side: int, desired_unit_classes: list[UnitClass] + ) -> str: + for unit_class in desired_unit_classes: + if coalition.faction.has_access_to_unit_class(unit_class): + dcs_unit_type = PretenseGroundObjectGenerator.ground_unit_of_class( + coalition=coalition, unit_class=unit_class + ) + if dcs_unit_type is not None: + return dcs_unit_type.dcs_id + + # Faction did not contain any of the desired unit classes. + # Fall back to defaults. + if desired_unit_classes[0] == UnitClass.TANK: + if side == PRETENSE_BLUE_SIDE: + return "M-1 Abrams" + else: + return "T-90" + elif desired_unit_classes[0] == UnitClass.ATGM: + if side == PRETENSE_BLUE_SIDE: + return "M1134 Stryker ATGM" + else: + return "BTR_D" + elif desired_unit_classes[0] == UnitClass.IFV: + if side == PRETENSE_BLUE_SIDE: + return "M1128 Stryker MGS" + else: + return "BMP-3" + elif desired_unit_classes[0] == UnitClass.APC: + if side == PRETENSE_BLUE_SIDE: + return "LAV-25" + else: + return "BTR-80" + elif desired_unit_classes[0] == UnitClass.RECON: + if side == PRETENSE_BLUE_SIDE: + return "M1043 HMMWV Armament" + else: + return "BRDM-2" + elif desired_unit_classes[0] == UnitClass.SHORAD: + if side == PRETENSE_BLUE_SIDE: + return "Roland ADS" + else: + return "2S6 Tunguska" + elif desired_unit_classes[0] == UnitClass.AAA: + if side == PRETENSE_BLUE_SIDE: + return "bofors40" + else: + return "KS-19" + elif desired_unit_classes[0] == UnitClass.MANPAD: + if side == PRETENSE_BLUE_SIDE: + return "Soldier stinger" + else: + return "SA-18 Igla manpad" + elif desired_unit_classes[0] == UnitClass.LOGISTICS: + if side == PRETENSE_BLUE_SIDE: + return "M 818" + else: + return "Ural-4320T" + else: + if side == PRETENSE_BLUE_SIDE: + return "Soldier M4" + else: + return "Infantry AK" + + def generate_pretense_ground_groups(self, side: int) -> str: + if side == PRETENSE_BLUE_SIDE: + side_str = "blue" + skill_str = self.game.settings.player_skill + coalition = self.game.blue + else: + side_str = "red" + skill_str = self.game.settings.enemy_vehicle_skill + coalition = self.game.red + + lua_string_ground_groups = "" + + lua_string_ground_groups += ( + 'TemplateDB.templates["infantry-' + side_str + '"] = {\n' + ) + lua_string_ground_groups += " units = {\n" + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.IFV, UnitClass.APC, UnitClass.RECON])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.TANK, UnitClass.ATGM, UnitClass.IFV, UnitClass.APC, UnitClass.RECON])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.TANK, UnitClass.ATGM, UnitClass.IFV, UnitClass.APC, UnitClass.RECON])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.INFANTRY])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.INFANTRY])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.INFANTRY])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.INFANTRY])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.INFANTRY])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.MANPAD, UnitClass.INFANTRY])}"\n' + lua_string_ground_groups += " },\n" + lua_string_ground_groups += f' skill = "{skill_str}",\n' + lua_string_ground_groups += " dataCategory = TemplateDB.type.group\n" + lua_string_ground_groups += "}\n" + + lua_string_ground_groups += ( + 'TemplateDB.templates["defense-' + side_str + '"] = {\n' + ) + lua_string_ground_groups += " units = {\n" + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.INFANTRY])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.INFANTRY])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.INFANTRY])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.INFANTRY])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.MANPAD, UnitClass.INFANTRY])}"\n' + lua_string_ground_groups += " },\n" + lua_string_ground_groups += f' skill = "{skill_str}",\n' + lua_string_ground_groups += " dataCategory = TemplateDB.type.group\n" + lua_string_ground_groups += "}\n" + + lua_string_ground_groups += ( + 'TemplateDB.templates["shorad-' + side_str + '"] = {\n' + ) + lua_string_ground_groups += " units = {\n" + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.SHORAD, UnitClass.AAA, UnitClass.MANPAD])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.SHORAD, UnitClass.AAA, UnitClass.MANPAD])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.LOGISTICS])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.AAA, UnitClass.SHORAD, UnitClass.MANPAD])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.AAA, UnitClass.SHORAD, UnitClass.MANPAD])}"\n' + lua_string_ground_groups += " },\n" + lua_string_ground_groups += " maxDist = 300,\n" + lua_string_ground_groups += f' skill = "{skill_str}",\n' + lua_string_ground_groups += " dataCategory = TemplateDB.type.group\n" + lua_string_ground_groups += "}\n" + + lua_string_ground_groups += 'TemplateDB.templates["sa2-' + side_str + '"] = {\n' + lua_string_ground_groups += " units = {\n" + lua_string_ground_groups += ' "p-19 s-125 sr",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.LOGISTICS])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.LOGISTICS])}",\n' + lua_string_ground_groups += ' "S_75M_Volhov",\n' + lua_string_ground_groups += ' "S_75M_Volhov",\n' + lua_string_ground_groups += ' "S_75M_Volhov",\n' + lua_string_ground_groups += ' "S_75M_Volhov",\n' + lua_string_ground_groups += ' "S_75M_Volhov",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.SHORAD, UnitClass.AAA, UnitClass.MANPAD])}",\n' + lua_string_ground_groups += ' "SNR_75V"\n' + lua_string_ground_groups += " },\n" + lua_string_ground_groups += " maxDist = 300,\n" + lua_string_ground_groups += f' skill = "{skill_str}",\n' + lua_string_ground_groups += " dataCategory = TemplateDB.type.group\n" + lua_string_ground_groups += "}\n" + + lua_string_ground_groups += ( + 'TemplateDB.templates["hawk-' + side_str + '"] = {\n' + ) + lua_string_ground_groups += " units = {\n" + lua_string_ground_groups += ' "Hawk pcp",\n' + lua_string_ground_groups += ' "Hawk cwar",\n' + lua_string_ground_groups += ' "Hawk ln",\n' + lua_string_ground_groups += ' "Hawk ln",\n' + lua_string_ground_groups += ' "Hawk ln",\n' + lua_string_ground_groups += ' "Hawk ln",\n' + lua_string_ground_groups += ' "Hawk ln",\n' + lua_string_ground_groups += ' "Hawk tr",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.LOGISTICS])}",\n' + lua_string_ground_groups += ' "Hawk sr"\n' + lua_string_ground_groups += " },\n" + lua_string_ground_groups += " maxDist = 300,\n" + lua_string_ground_groups += f' skill = "{skill_str}",\n' + lua_string_ground_groups += " dataCategory = TemplateDB.type.group\n" + lua_string_ground_groups += "}\n" + + lua_string_ground_groups += ( + 'TemplateDB.templates["patriot-' + side_str + '"] = {\n' + ) + lua_string_ground_groups += " units = {\n" + lua_string_ground_groups += ' "Patriot cp",\n' + lua_string_ground_groups += ' "Patriot str",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.LOGISTICS])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.LOGISTICS])}",\n' + lua_string_ground_groups += ' "Patriot ln",\n' + lua_string_ground_groups += ' "Patriot ln",\n' + lua_string_ground_groups += ' "Patriot ln",\n' + lua_string_ground_groups += ' "Patriot ln",\n' + lua_string_ground_groups += ' "Patriot str",\n' + lua_string_ground_groups += ' "Patriot str",\n' + lua_string_ground_groups += ' "Patriot str",\n' + lua_string_ground_groups += ' "Patriot EPP",\n' + lua_string_ground_groups += ' "Patriot ECS",\n' + lua_string_ground_groups += ' "Patriot AMG"\n' + lua_string_ground_groups += " },\n" + lua_string_ground_groups += " maxDist = 300,\n" + lua_string_ground_groups += f' skill = "{skill_str}",\n' + lua_string_ground_groups += " dataCategory = TemplateDB.type.group\n" + lua_string_ground_groups += "}\n" + + lua_string_ground_groups += 'TemplateDB.templates["sa3-' + side_str + '"] = {\n' + lua_string_ground_groups += " units = {\n" + lua_string_ground_groups += ' "p-19 s-125 sr",\n' + lua_string_ground_groups += ' "snr s-125 tr",\n' + lua_string_ground_groups += ' "5p73 s-125 ln",\n' + lua_string_ground_groups += ' "5p73 s-125 ln",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.LOGISTICS])}",\n' + lua_string_ground_groups += ' "5p73 s-125 ln",\n' + lua_string_ground_groups += ' "5p73 s-125 ln"\n' + lua_string_ground_groups += " },\n" + lua_string_ground_groups += " maxDist = 300,\n" + lua_string_ground_groups += f' skill = "{skill_str}",\n' + lua_string_ground_groups += " dataCategory = TemplateDB.type.group\n" + lua_string_ground_groups += "}\n" + + lua_string_ground_groups += 'TemplateDB.templates["sa6-' + side_str + '"] = {\n' + lua_string_ground_groups += " units = {\n" + lua_string_ground_groups += ' "Kub 1S91 str",\n' + lua_string_ground_groups += ' "Kub 2P25 ln",\n' + lua_string_ground_groups += ' "Kub 2P25 ln",\n' + lua_string_ground_groups += ' "Kub 2P25 ln",\n' + lua_string_ground_groups += ' "Kub 2P25 ln",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.SHORAD, UnitClass.AAA, UnitClass.MANPAD])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.LOGISTICS])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.SHORAD, UnitClass.AAA, UnitClass.MANPAD])}",\n' + lua_string_ground_groups += ' "Kub 2P25 ln"\n' + lua_string_ground_groups += " },\n" + lua_string_ground_groups += " maxDist = 300,\n" + lua_string_ground_groups += f' skill = "{skill_str}",\n' + lua_string_ground_groups += " dataCategory = TemplateDB.type.group\n" + lua_string_ground_groups += "}" + + lua_string_ground_groups += ( + 'TemplateDB.templates["sa10-' + side_str + '"] = {\n' + ) + lua_string_ground_groups += " units = {\n" + lua_string_ground_groups += ' "S-300PS 54K6 cp",\n' + lua_string_ground_groups += ' "S-300PS 5P85C ln",\n' + lua_string_ground_groups += ' "S-300PS 5P85C ln",\n' + lua_string_ground_groups += ' "S-300PS 5P85C ln",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.LOGISTICS])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.LOGISTICS])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.LOGISTICS])}",\n' + lua_string_ground_groups += ' "S-300PS 5P85C ln",\n' + lua_string_ground_groups += ' "S-300PS 5P85C ln",\n' + lua_string_ground_groups += ' "S-300PS 5P85C ln",\n' + lua_string_ground_groups += ' "S-300PS 40B6MD sr",\n' + lua_string_ground_groups += ' "S-300PS 40B6M tr",\n' + lua_string_ground_groups += ' "S-300PS 64H6E sr"\n' + lua_string_ground_groups += " },\n" + lua_string_ground_groups += " maxDist = 300,\n" + lua_string_ground_groups += f' skill = "{skill_str}",\n' + lua_string_ground_groups += " dataCategory = TemplateDB.type.group\n" + lua_string_ground_groups += "}\n" + + lua_string_ground_groups += 'TemplateDB.templates["sa5-' + side_str + '"] = {\n' + lua_string_ground_groups += " units = {\n" + lua_string_ground_groups += ' "RLS_19J6",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.LOGISTICS])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.LOGISTICS])}",\n' + lua_string_ground_groups += ' "RPC_5N62V",\n' + lua_string_ground_groups += ' "S-200_Launcher",\n' + lua_string_ground_groups += ' "S-200_Launcher",\n' + lua_string_ground_groups += ' "S-200_Launcher",\n' + lua_string_ground_groups += ' "S-200_Launcher",\n' + lua_string_ground_groups += ' "S-200_Launcher",\n' + lua_string_ground_groups += ' "S-200_Launcher"\n' + lua_string_ground_groups += " },\n" + lua_string_ground_groups += " maxDist = 300,\n" + lua_string_ground_groups += f' skill = "{skill_str}",\n' + lua_string_ground_groups += " dataCategory = TemplateDB.type.group\n" + lua_string_ground_groups += "}\n" + + lua_string_ground_groups += ( + 'TemplateDB.templates["sa11-' + side_str + '"] = {\n' + ) + lua_string_ground_groups += " units = {\n" + lua_string_ground_groups += ' "SA-11 Buk SR 9S18M1",\n' + lua_string_ground_groups += ' "SA-11 Buk LN 9A310M1",\n' + lua_string_ground_groups += ' "SA-11 Buk LN 9A310M1",\n' + lua_string_ground_groups += ' "SA-11 Buk LN 9A310M1",\n' + lua_string_ground_groups += ' "SA-11 Buk LN 9A310M1",\n' + lua_string_ground_groups += ' "SA-11 Buk LN 9A310M1",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.SHORAD, UnitClass.AAA, UnitClass.MANPAD])}",\n' + lua_string_ground_groups += ' "SA-11 Buk SR 9S18M1",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.LOGISTICS])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.LOGISTICS])}",\n' + lua_string_ground_groups += ' "SA-11 Buk CC 9S470M1"\n' + lua_string_ground_groups += " },\n" + lua_string_ground_groups += " maxDist = 300,\n" + lua_string_ground_groups += f' skill = "{skill_str}",\n' + lua_string_ground_groups += " dataCategory = TemplateDB.type.group\n" + lua_string_ground_groups += "}\n" + + lua_string_ground_groups += ( + 'TemplateDB.templates["nasamsb-' + side_str + '"] = {\n' + ) + lua_string_ground_groups += " units = {\n" + lua_string_ground_groups += ' "NASAMS_Command_Post",\n' + lua_string_ground_groups += ' "NASAMS_Radar_MPQ64F1",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.AAA, UnitClass.SHORAD, UnitClass.MANPAD])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.LOGISTICS])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.LOGISTICS])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.SHORAD, UnitClass.AAA, UnitClass.MANPAD])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.SHORAD, UnitClass.AAA, UnitClass.MANPAD])}",\n' + lua_string_ground_groups += ' "NASAMS_LN_B",\n' + lua_string_ground_groups += ' "NASAMS_LN_B",\n' + lua_string_ground_groups += ' "NASAMS_LN_B",\n' + lua_string_ground_groups += ' "NASAMS_LN_B",\n' + lua_string_ground_groups += ' "NASAMS_Radar_MPQ64F1",\n' + lua_string_ground_groups += ' "NASAMS_Radar_MPQ64F1",\n' + lua_string_ground_groups += ' "NASAMS_Radar_MPQ64F1"\n' + lua_string_ground_groups += " },\n" + lua_string_ground_groups += " maxDist = 300,\n" + lua_string_ground_groups += f' skill = "{skill_str}",\n' + lua_string_ground_groups += " dataCategory = TemplateDB.type.group\n" + lua_string_ground_groups += "}\n" + + lua_string_ground_groups += ( + 'TemplateDB.templates["nasamsc-' + side_str + '"] = {\n' + ) + lua_string_ground_groups += " units = {\n" + lua_string_ground_groups += ' "NASAMS_Command_Post",\n' + lua_string_ground_groups += ' "NASAMS_Radar_MPQ64F1",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.AAA, UnitClass.SHORAD, UnitClass.MANPAD])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.LOGISTICS])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.LOGISTICS])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.SHORAD, UnitClass.AAA, UnitClass.MANPAD])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.SHORAD, UnitClass.AAA, UnitClass.MANPAD])}",\n' + lua_string_ground_groups += ' "NASAMS_LN_C",\n' + lua_string_ground_groups += ' "NASAMS_LN_C",\n' + lua_string_ground_groups += ' "NASAMS_LN_C",\n' + lua_string_ground_groups += ' "NASAMS_LN_C",\n' + lua_string_ground_groups += ' "NASAMS_Radar_MPQ64F1",\n' + lua_string_ground_groups += ' "NASAMS_Radar_MPQ64F1",\n' + lua_string_ground_groups += ' "NASAMS_Radar_MPQ64F1"\n' + lua_string_ground_groups += " },\n" + lua_string_ground_groups += " maxDist = 300,\n" + lua_string_ground_groups += f' skill = "{skill_str}",\n' + lua_string_ground_groups += " dataCategory = TemplateDB.type.group\n" + lua_string_ground_groups += "}\n" + + lua_string_ground_groups += ( + 'TemplateDB.templates["rapier-' + side_str + '"] = {\n' + ) + lua_string_ground_groups += " units = {\n" + lua_string_ground_groups += ' "rapier_fsa_blindfire_radar",\n' + lua_string_ground_groups += ' "rapier_fsa_blindfire_radar",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.AAA, UnitClass.SHORAD, UnitClass.MANPAD])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.LOGISTICS])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.LOGISTICS])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.SHORAD, UnitClass.AAA, UnitClass.MANPAD])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.SHORAD, UnitClass.AAA, UnitClass.MANPAD])}",\n' + lua_string_ground_groups += ' "rapier_fsa_launcher",\n' + lua_string_ground_groups += ' "rapier_fsa_launcher",\n' + lua_string_ground_groups += ' "rapier_fsa_launcher",\n' + lua_string_ground_groups += ' "rapier_fsa_launcher",\n' + lua_string_ground_groups += ( + ' "rapier_fsa_optical_tracker_unit",\n' + ) + lua_string_ground_groups += ( + ' "rapier_fsa_optical_tracker_unit",\n' + ) + lua_string_ground_groups += ( + ' "rapier_fsa_optical_tracker_unit"\n' + ) + lua_string_ground_groups += " },\n" + lua_string_ground_groups += " maxDist = 300,\n" + lua_string_ground_groups += f' skill = "{skill_str}",\n' + lua_string_ground_groups += " dataCategory = TemplateDB.type.group\n" + lua_string_ground_groups += "}\n" + + return lua_string_ground_groups + @staticmethod def generate_pretense_zone_connection( connected_points: dict[str, list[str]], @@ -820,8 +1195,14 @@ class PretenseLuaGenerator(LuaGenerator): other_cp_name not in connected_points[cp_name] and cp_name not in connected_points[other_cp_name] ): + cp_name_conn = "".join( + [i for i in cp_name if i.isalnum() or i.isspace() or i == "-"] + ) + cp_name_conn_other = "".join( + [i for i in other_cp_name if i.isalnum() or i.isspace() or i == "-"] + ) lua_string_connman = ( - f" cm: addConnection('{cp_name}', '{other_cp_name}')\n" + f" cm: addConnection('{cp_name_conn}', '{cp_name_conn_other}')\n" ) connected_points[cp_name].append(other_cp_name) connected_points[other_cp_name].append(cp_name) @@ -850,7 +1231,7 @@ class PretenseLuaGenerator(LuaGenerator): trigger = TriggerStart(comment="Pretense init") now = datetime.now() - date_time = now.strftime("%Y-%d-%mT%H_%M_%S") + date_time = now.strftime("%Y-%m-%dT%H_%M_%S") lua_string_savefile = ( f"local savefile = 'pretense_retribution_{date_time}.json'" ) @@ -858,6 +1239,13 @@ class PretenseLuaGenerator(LuaGenerator): init_header_file = open("./resources/plugins/pretense/init_header.lua", "r") init_header = init_header_file.read() + lua_string_ground_groups_blue = self.generate_pretense_ground_groups( + PRETENSE_BLUE_SIDE + ) + lua_string_ground_groups_red = self.generate_pretense_ground_groups( + PRETENSE_RED_SIDE + ) + lua_string_zones = "" for cp in self.game.theater.controlpoints: @@ -865,6 +1253,9 @@ class PretenseLuaGenerator(LuaGenerator): continue cp_name_trimmed = "".join([i for i in cp.name.lower() if i.isalpha()]) + cp_name = "".join( + [i for i in cp.name if i.isalnum() or i.isspace() or i == "-"] + ) cp_side = 2 if cp.captured else 1 for side in range(1, 3): if cp_name_trimmed not in self.game.pretense_air[cp_side]: @@ -874,7 +1265,7 @@ class PretenseLuaGenerator(LuaGenerator): if cp_name_trimmed not in self.game.pretense_ground_assault[cp_side]: self.game.pretense_ground_assault[side][cp_name_trimmed] = list() lua_string_zones += ( - f"zones.{cp_name_trimmed} = ZoneCommand:new('{cp.name}')\n" + f"zones.{cp_name_trimmed} = ZoneCommand:new('{cp_name}')\n" ) lua_string_zones += ( f"zones.{cp_name_trimmed}.initialState = " @@ -1004,17 +1395,23 @@ class PretenseLuaGenerator(LuaGenerator): init_body_2_file = open("./resources/plugins/pretense/init_body_2.lua", "r") init_body_2 = init_body_2_file.read() + init_body_3_file = open("./resources/plugins/pretense/init_body_3.lua", "r") + init_body_3 = init_body_3_file.read() + init_footer_file = open("./resources/plugins/pretense/init_footer.lua", "r") init_footer = init_footer_file.read() lua_string = ( lua_string_savefile + init_header + + lua_string_ground_groups_blue + + lua_string_ground_groups_red + + init_body_1 + lua_string_zones + lua_string_connman - + init_body_1 - + lua_string_jtac + init_body_2 + + lua_string_jtac + + init_body_3 + lua_string_supply + init_footer ) diff --git a/resources/plugins/pretense/init_body_1.lua b/resources/plugins/pretense/init_body_1.lua index 0d528ddf..ad7ae685 100644 --- a/resources/plugins/pretense/init_body_1.lua +++ b/resources/plugins/pretense/init_body_1.lua @@ -227,7 +227,7 @@ presets = { template='infantry-red', }), shorad = Preset:new({ - display = 'SAM', + display = 'SHORAD', cost=2500, type='defense', template='shorad-red', @@ -236,55 +236,79 @@ presets = { display = 'SAM', cost=3000, type='defense', - template='sa2', + template='sa2-red', }), sa10 = Preset:new({ display = 'SAM', cost=30000, type='defense', - template='sa10', + template='sa10-red', }), sa5 = Preset:new({ display = 'SAM', cost=20000, type='defense', - template='sa5', + template='sa5-red', }), sa3 = Preset:new({ display = 'SAM', cost=4000, type='defense', - template='sa3', + template='sa3-red', }), sa6 = Preset:new({ display = 'SAM', cost=6000, type='defense', - template='sa6', + template='sa6-red', }), sa11 = Preset:new({ display = 'SAM', cost=10000, type='defense', - template='sa11', + template='sa11-red', }), hawk = Preset:new({ display = 'SAM', cost=6000, type='defense', - template='hawk', + template='hawk-red', }), patriot = Preset:new({ display = 'SAM', cost=30000, type='defense', - template='patriot', + template='patriot-red', }), - nasams = Preset:new({ + nasamsb = Preset:new({ display = 'SAM', cost=3000, type='defense', - template='nasams', + template='nasamsb-red', + }), + nasamsc = Preset:new({ + display = 'SAM', + cost=3000, + type='defense', + template='nasamsc-red', + }), + rapier = Preset:new({ + display = 'SAM', + cost=3000, + type='defense', + template='rapier-red', + }), + irondome = Preset:new({ + display = 'SAM', + cost=20000, + type='defense', + template='irondome-red', + }), + davidsling = Preset:new({ + display = 'SAM', + cost=30000, + type='defense', + template='davidsling-red', }), redShipGroup = Preset:new({ display = 'SAM', @@ -301,7 +325,7 @@ presets = { template='infantry-blue', }), shorad = Preset:new({ - display = 'SAM', + display = 'SHORAD', cost=2500, type='defense', template='shorad-blue', @@ -310,55 +334,79 @@ presets = { display = 'SAM', cost=3000, type='defense', - template='sa2', + template='sa2-blue', }), sa10 = Preset:new({ display = 'SAM', cost=30000, type='defense', - template='sa10', + template='sa10-blue', }), sa5 = Preset:new({ display = 'SAM', cost=20000, type='defense', - template='sa5', + template='sa5-blue', }), sa3 = Preset:new({ display = 'SAM', cost=4000, type='defense', - template='sa3', + template='sa3-blue', }), sa6 = Preset:new({ display = 'SAM', cost=6000, type='defense', - template='sa6', + template='sa6-blue', }), sa11 = Preset:new({ display = 'SAM', cost=10000, type='defense', - template='sa11', + template='sa11-blue', }), hawk = Preset:new({ display = 'SAM', cost=6000, type='defense', - template='hawk', + template='hawk-blue', }), patriot = Preset:new({ display = 'SAM', cost=30000, type='defense', - template='patriot', + template='patriot-blue', }), - nasams = Preset:new({ + nasamsb = Preset:new({ display = 'SAM', cost=3000, type='defense', - template='nasams', + template='nasamsb-blue', + }), + nasamsc = Preset:new({ + display = 'SAM', + cost=3000, + type='defense', + template='nasamsc-blue', + }), + rapier = Preset:new({ + display = 'SAM', + cost=3000, + type='defense', + template='rapier-blue', + }), + irondome = Preset:new({ + display = 'SAM', + cost=20000, + type='defense', + template='irondome-blue', + }), + davidsling = Preset:new({ + display = 'SAM', + cost=30000, + type='defense', + template='davidsling-blue', }), blueShipGroup = Preset:new({ display = 'SAM', From 200812a39b86ff99af349895fd418272dc2e5d39 Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Tue, 26 Dec 2023 14:36:33 +0200 Subject: [PATCH 199/243] Other coalition TGO spawns are now generated with the correct faction. --- game/pretense/pretensetgogenerator.py | 28 ++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/game/pretense/pretensetgogenerator.py b/game/pretense/pretensetgogenerator.py index bab1f089..061957f8 100644 --- a/game/pretense/pretensetgogenerator.py +++ b/game/pretense/pretensetgogenerator.py @@ -122,6 +122,7 @@ class PretenseGroundObjectGenerator(GroundObjectGenerator): self.game = game self.m = mission self.unit_map = unit_map + self.coalition = ground_object.coalition @property def culled(self) -> bool: @@ -139,9 +140,9 @@ class PretenseGroundObjectGenerator(GroundObjectGenerator): unit_class: Class of unit to return. """ faction_units = ( - set(self.ground_object.coalition.faction.frontline_units) - | set(self.ground_object.coalition.faction.artillery_units) - | set(self.ground_object.coalition.faction.logistics_units) + set(self.coalition.faction.frontline_units) + | set(self.coalition.faction.artillery_units) + | set(self.coalition.faction.logistics_units) ) of_class = list({u for u in faction_units if u.unit_class is unit_class}) @@ -184,7 +185,7 @@ class PretenseGroundObjectGenerator(GroundObjectGenerator): max_num: Maximum number of units to generate per group. """ - if self.ground_object.coalition.faction.has_access_to_unit_class(unit_class): + if self.coalition.faction.has_access_to_unit_class(unit_class): unit_type = self.ground_unit_of_class(unit_class) if unit_type is not None and len(vehicle_units) < max_num: unit_id = self.game.next_unit_id() @@ -236,7 +237,7 @@ class PretenseGroundObjectGenerator(GroundObjectGenerator): max_num: Maximum number of units to generate per group. """ unit_type = None - faction = self.ground_object.coalition.faction + faction = self.coalition.faction is_player = True side = ( 2 @@ -286,7 +287,7 @@ class PretenseGroundObjectGenerator(GroundObjectGenerator): default_ifv_unit_chinese = groundunittype elif unit == vehicles.Armor.MTLB: default_amphibious_unit = groundunittype - if self.ground_object.coalition.faction.has_access_to_dcs_type(unit): + if self.coalition.faction.has_access_to_dcs_type(unit): if groundunittype.unit_class == unit_class: unit_type = groundunittype break @@ -345,15 +346,15 @@ class PretenseGroundObjectGenerator(GroundObjectGenerator): if self.culled: return cp_name_trimmed = "".join( - [i for i in self.ground_object.control_point.name.lower() if i.isalnum()] + [i for i in self.ground_object.control_point.name.lower() if i.isalpha()] ) country_name_trimmed = "".join( - [i for i in self.country.shortname.lower() if i.isalnum()] + [i for i in self.country.shortname.lower() if i.isalpha()] ) for group in self.ground_object.groups: vehicle_units: list[TheaterUnit] = [] - # Split the different unit types to be compliant to dcs limitation + for unit in group.units: if unit.is_static: # Add supply convoy @@ -445,7 +446,7 @@ class PretenseGroundObjectGenerator(GroundObjectGenerator): break cp_name_trimmed = "".join( - [i for i in control_point.name.lower() if i.isalnum()] + [i for i in control_point.name.lower() if i.isalpha()] ) is_player = True side = ( @@ -554,7 +555,7 @@ class PretenseGroundObjectGenerator(GroundObjectGenerator): break cp_name_trimmed = "".join( - [i for i in control_point.name.lower() if i.isalnum()] + [i for i in control_point.name.lower() if i.isalpha()] ) is_player = True side = ( @@ -565,7 +566,7 @@ class PretenseGroundObjectGenerator(GroundObjectGenerator): for unit in units: assert issubclass(unit.type, VehicleType) - faction = unit.ground_object.control_point.coalition.faction + faction = self.coalition.faction if vehicle_group is None: vehicle_group = self.m.vehicle_group( self.country, @@ -647,7 +648,7 @@ class PretenseTgoGenerator(TgoGenerator): def generate(self) -> None: for cp in self.game.theater.controlpoints: - cp_name_trimmed = "".join([i for i in cp.name.lower() if i.isalnum()]) + cp_name_trimmed = "".join([i for i in cp.name.lower() if i.isalpha()]) for side in range(1, 3): if cp_name_trimmed not in self.game.pretense_ground_supply[side]: self.game.pretense_ground_supply[side][cp_name_trimmed] = list() @@ -770,6 +771,7 @@ class PretenseTgoGenerator(TgoGenerator): else: continue + generator.coalition = other_coalition generator.generate() self.mission_data.runways = list(self.runways.values()) From 2edc0a73052c8017cd717822fdfcdcd4e91cd778 Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Tue, 26 Dec 2023 17:23:15 +0200 Subject: [PATCH 200/243] Ground unit presets are now generated from the coalition/faction definitions. --- game/pretense/pretensetgogenerator.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/game/pretense/pretensetgogenerator.py b/game/pretense/pretensetgogenerator.py index 061957f8..58a1cbed 100644 --- a/game/pretense/pretensetgogenerator.py +++ b/game/pretense/pretensetgogenerator.py @@ -17,6 +17,7 @@ from dcs.country import Country from dcs.unitgroup import StaticGroup, VehicleGroup from dcs.unittype import VehicleType +from game.coalition import Coalition from game.data.units import UnitClass from game.dcs.groundunittype import GroundUnitType from game.missiongenerator.groundforcepainter import ( @@ -128,7 +129,10 @@ class PretenseGroundObjectGenerator(GroundObjectGenerator): def culled(self) -> bool: return self.game.iads_considerate_culling(self.ground_object) - def ground_unit_of_class(self, unit_class: UnitClass) -> Optional[GroundUnitType]: + @staticmethod + def ground_unit_of_class( + coalition: Coalition, unit_class: UnitClass + ) -> Optional[GroundUnitType]: """ Returns a GroundUnitType of the specified class that belongs to the TheaterGroundObject faction. @@ -137,12 +141,13 @@ class PretenseGroundObjectGenerator(GroundObjectGenerator): are removed based on a pre-defined list. Args: + coalition: Coalition to return the unit for. unit_class: Class of unit to return. """ faction_units = ( - set(self.coalition.faction.frontline_units) - | set(self.coalition.faction.artillery_units) - | set(self.coalition.faction.logistics_units) + set(coalition.faction.frontline_units) + | set(coalition.faction.artillery_units) + | set(coalition.faction.logistics_units) ) of_class = list({u for u in faction_units if u.unit_class is unit_class}) @@ -186,7 +191,7 @@ class PretenseGroundObjectGenerator(GroundObjectGenerator): """ if self.coalition.faction.has_access_to_unit_class(unit_class): - unit_type = self.ground_unit_of_class(unit_class) + unit_type = self.ground_unit_of_class(self.coalition, unit_class) if unit_type is not None and len(vehicle_units) < max_num: unit_id = self.game.next_unit_id() unit_name = f"{cp_name}-{group_role}-{unit_id}" From e549a923d5c90fb0e435ab47bcd6e8334630abaf Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Tue, 26 Dec 2023 17:57:26 +0200 Subject: [PATCH 201/243] Updated the Pretense script to version 1.4.5 --- .../plugins/pretense/pretense_compiled.lua | 991 ++++++++++++------ 1 file changed, 683 insertions(+), 308 deletions(-) diff --git a/resources/plugins/pretense/pretense_compiled.lua b/resources/plugins/pretense/pretense_compiled.lua index ac48acce..a6b58409 100644 --- a/resources/plugins/pretense/pretense_compiled.lua +++ b/resources/plugins/pretense/pretense_compiled.lua @@ -23,6 +23,27 @@ Makes use of Mission scripting tools (Mist): = 5 then + env.info('GroupMonitor: processSurface ['..group.name..'] is stuck, trying to get unstuck by teleport') + group.unstuck_attempts = 0 + local frUnit = gr:getUnit(1) + local pos = frUnit:getPoint() + + mist.teleportToPoint({ + groupName = group.name, + action = 'teleport', + initTasks = false, + point = {x=pos.x+math.random(-25,25), y=pos.y, z = pos.z+math.random(-25,25)} + }) + + timer.scheduleFunction(function(params, time) + local group = params.group + local gr = Group.getByName(group.name) + local supplyPoint = trigger.misc.getZone(group.target.name..'-sp') + if not supplyPoint then + supplyPoint = trigger.misc.getZone(group.target.name) + end + + TaskExtensions.moveOnRoadToPoint(gr, {x=supplyPoint.point.x, y=supplyPoint.point.z}, true) + end, {gr = group}, timer.getTime()+2) + end + elseif group.unstuck_attempts and group.unstuck_attempts > 0 then + group.unstuck_attempts = 0 end - elseif group.product.missionType == 'assault' then + elseif group.product.missionType == 'assault' then local frUnit = gr:getUnit(1) if frUnit then - local controller = frUnit:getController() - local targets = controller:getDetectedTargets() + local skipDetection = false + if group.lastStarted and (timer.getAbsTime() - group.lastStarted) < (30) then + skipDetection = true + else + group.lastStarted = nil + end local shouldstop = false - if #targets > 0 then - for _,tgt in ipairs(targets) do - if tgt.visible and tgt.object then - if tgt.object.isExist and tgt.object:isExist() and tgt.object.getCoalition and tgt.object:getCoalition()~=frUnit:getCoalition() and - Object.getCategory(tgt.object) == 1 then - local dist = mist.utils.get3DDist(frUnit:getPoint(), tgt.object:getPoint()) - if dist < 1000 then - if not group.isstopped then - env.info('GroupMonitor: processSurface ['..group.name..'] stopping to engage targets') - --gr:getController():setCommand({id = 'StopRoute', params = { value = true}}) - TaskExtensions.stopAndDisperse(gr) - group.isstopped = true + if not skipDetection then + local controller = frUnit:getController() + local targets = controller:getDetectedTargets() + + if #targets > 0 then + for _,tgt in ipairs(targets) do + if tgt.visible and tgt.object then + if tgt.object.isExist and tgt.object:isExist() and tgt.object.getCoalition and tgt.object:getCoalition()~=frUnit:getCoalition() and + Object.getCategory(tgt.object) == 1 then + local dist = mist.utils.get3DDist(frUnit:getPoint(), tgt.object:getPoint()) + if dist < 700 then + if not group.isstopped then + env.info('GroupMonitor: processSurface ['..group.name..'] stopping to engage targets') + TaskExtensions.stopAndDisperse(gr) + group.isstopped = true + group.lastStopped = timer.getAbsTime() + end + shouldstop = true + break end - shouldstop = true - break end end end end end + if group.lastStopped then + if (timer.getAbsTime() - group.lastStopped) > (3*60) then + env.info('GroupMonitor: processSurface ['..group.name..'] override stop, waited too long') + shouldstop = false + group.lastStarted = timer.getAbsTime() + end + end + if not shouldstop and group.isstopped then env.info('GroupMonitor: processSurface ['..group.name..'] resuming mission') - --gr:getController():setCommand({id = 'StopRoute', params = { value = false}}) local tp = { x = group.target.zone.point.x, y = group.target.zone.point.z @@ -737,6 +814,48 @@ do TaskExtensions.moveOnRoadToPointAndAssault(gr, tp, group.target.built) group.isstopped = false + group.lastStopped = nil + end + + if not shouldstop and not group.isstopped then + if GroupMonitor.isStuck(group) then + env.info('GroupMonitor: processSurface ['..group.name..'] is stuck, trying to get unstuck') + local tp = { + x = group.target.zone.point.x, + y = group.target.zone.point.z + } + + TaskExtensions.moveOnRoadToPointAndAssault(gr, tp, group.target.built, true) + + group.unstuck_attempts = group.unstuck_attempts or 0 + group.unstuck_attempts = group.unstuck_attempts + 1 + + if group.unstuck_attempts >= 5 then + env.info('GroupMonitor: processSurface ['..group.name..'] is stuck, trying to get unstuck by teleport') + group.unstuck_attempts = 0 + local pos = frUnit:getPoint() + + mist.teleportToPoint({ + groupName = group.name, + action = 'teleport', + initTasks = false, + point = {x=pos.x+math.random(-25,25), y=pos.y, z = pos.z+math.random(-25,25)} + }) + + timer.scheduleFunction(function(params, time) + local group = params.group + local gr = Group.getByName(gr) + local tp = { + x = group.target.zone.point.x, + y = group.target.zone.point.z + } + + TaskExtensions.moveOnRoadToPointAndAssault(gr, tp, group.target.built, true) + end, {gr = group}, timer.getTime()+2) + end + elseif group.unstuck_attempts and group.unstuck_attempts > 0 then + group.unstuck_attempts = 0 + end end end end @@ -799,6 +918,31 @@ do end end end + + function GroupMonitor.isStuck(group) + local gr = Group.getByName(group.name) + if not gr then return false end + if gr:getSize() == 0 then return false end + + local un = gr:getUnit(1) + if un and un:isExist() and mist.vec.mag(un:getVelocity()) >= 0.01 and group.stuck_marker > 0 then + group.stuck_marker = 0 + env.info('GroupMonitor: isStuck ['..group.name..'] is moving, reseting stuck marker velocity='..mist.vec.mag(un:getVelocity())) + end + + if un and un:isExist() and mist.vec.mag(un:getVelocity()) < 0.01 then + group.stuck_marker = group.stuck_marker + 1 + env.info('GroupMonitor: isStuck ['..group.name..'] is not moving, increasing stuck marker to '..group.stuck_marker..' velocity='..mist.vec.mag(un:getVelocity())) + + if group.stuck_marker >= 3 then + group.stuck_marker = 0 + env.info('GroupMonitor: isStuck ['..group.name..'] is stuck') + return true + end + end + + return false + end function GroupMonitor:processAir(group)-- states: [takeoff, inair, landed] local gr = Group.getByName(group.name) @@ -888,7 +1032,7 @@ do if tgt.visible and tgt.object and tgt.object.isExist and tgt.object:isExist() then if Object.getCategory(tgt.object) == Object.Category.UNIT and tgt.object.getCoalition and tgt.object:getCoalition()~=frUnit:getCoalition() and - Unit.getCategory(tgt.object) == Unit.Category.GROUND_UNIT then + Unit.getCategoryEx(tgt.object) == Unit.Category.GROUND_UNIT then local dist = mist.utils.get3DDist(frUnit:getPoint(), tgt.object:getPoint()) if dist < 2000 then @@ -925,7 +1069,7 @@ do if supplyPoint then group.returning = true - local alt = self.connectionManager:getHeliAlt(group.target.name, group.home.name) + local alt = DependencyManager.get("ConnectionManager"):getHeliAlt(group.target.name, group.home.name) TaskExtensions.landAtPointFromAir(gr, {x=supplyPoint.point.x, y=supplyPoint.point.z}, alt) env.info('GroupMonitor: processAir ['..group.name..'] returning home') end @@ -961,7 +1105,8 @@ do obj.blockedRoads = {} setmetatable(obj, self) self.__index = self - + + DependencyManager.register("ConnectionManager", obj) return obj end @@ -1770,9 +1915,31 @@ do } } + local awacs = { - id = 'AWACS', - params = { + id = 'ComboTask', + params = { + tasks = { + { + id = "WrappedAction", + params = + { + action = + { + id = "EPLRS", + params = { + value = true, + groupId = group:getID(), + } + } + } + }, + { + id = 'AWACS', + params = { + } + } + } } } @@ -2035,7 +2202,7 @@ do }) end - function TaskExtensions.moveOnRoadToPointAndAssault(group, point, targets) + function TaskExtensions.moveOnRoadToPointAndAssault(group, point, targets, detour) if not group or not point then return end if not group:isExist() or group:getSize()==0 then return end local startPos = group:getUnit(1):getPoint() @@ -2047,33 +2214,72 @@ do id='Mission', params = { route = { - points = { - [1] = { - type= AI.Task.WaypointType.TURNING_POINT, - x = srx, - y = sry, - speed = 1000, - action = AI.Task.VehicleFormation.ON_ROAD - }, - [2] = { - type= AI.Task.WaypointType.TURNING_POINT, - x = erx, - y = ery, - speed = 1000, - action = AI.Task.VehicleFormation.ON_ROAD - }, - [3] = { - type= AI.Task.WaypointType.TURNING_POINT, - x = point.x, - y = point.y, - speed = 1000, - action = AI.Task.VehicleFormation.DIAMOND - } - } + points = {} } } } + if detour then + local detourPoint = {x = startPos.x, y = startPos.z} + + local direction = { + x = erx - startPos.x, + y = ery - startPos.y + } + + local magnitude = (direction.x^2 + direction.y^2) ^ 0.5 + if magnitude > 0.0 then + direction.x = direction.x / magnitude + direction.y = direction.y / magnitude + + local scale = math.random(250,500) + direction.x = direction.x * scale + direction.y = direction.y * scale + + detourPoint.x = detourPoint.x + direction.x + detourPoint.y = detourPoint.y + direction.y + else + detourPoint.x = detourPoint.x + math.random(-500,500) + detourPoint.y = detourPoint.y + math.random(-500,500) + end + + table.insert(mis.params.route.points, { + type= AI.Task.WaypointType.TURNING_POINT, + x = detourPoint.x, + y = detourPoint.y, + speed = 1000, + action = AI.Task.VehicleFormation.OFF_ROAD + }) + + srx, sry = land.getClosestPointOnRoads('roads', detourPoint.x, detourPoint.y) + end + + + table.insert(mis.params.route.points, { + type= AI.Task.WaypointType.TURNING_POINT, + x = srx, + y = sry, + speed = 1000, + action = AI.Task.VehicleFormation.ON_ROAD + }) + + table.insert(mis.params.route.points, { + type= AI.Task.WaypointType.TURNING_POINT, + x = erx, + y = ery, + speed = 1000, + action = AI.Task.VehicleFormation.ON_ROAD + }) + + table.insert(mis.params.route.points, { + type= AI.Task.WaypointType.TURNING_POINT, + x = point.x, + y = point.y, + speed = 1000, + action = AI.Task.VehicleFormation.DIAMOND + }) + + for i,v in pairs(targets) do if v.type == 'defense' then local group = Group.getByName(v.name) @@ -2093,10 +2299,11 @@ do end end end + group:getController():setTask(mis) end - function TaskExtensions.moveOnRoadToPoint(group, point) -- point = {x,y} + function TaskExtensions.moveOnRoadToPoint(group, point, detour) -- point = {x,y} if not group or not point then return end if not group:isExist() or group:getSize()==0 then return end local startPos = group:getUnit(1):getPoint() @@ -2109,31 +2316,70 @@ do params = { route = { points = { - [1] = { - type= AI.Task.WaypointType.TURNING_POINT, - x = srx, - y = sry, - speed = 1000, - action = AI.Task.VehicleFormation.ON_ROAD - }, - [2] = { - type= AI.Task.WaypointType.TURNING_POINT, - x = erx, - y = ery, - speed = 1000, - action = AI.Task.VehicleFormation.ON_ROAD - }, - [3] = { - type= AI.Task.WaypointType.TURNING_POINT, - x = point.x, - y = point.y, - speed = 1000, - action = AI.Task.VehicleFormation.OFF_ROAD - } } } } } + + if detour then + local detourPoint = {x = startPos.x, y = startPos.z} + + local direction = { + x = erx - startPos.x, + y = ery - startPos.y + } + + local magnitude = (direction.x^2 + direction.y^2) ^ 0.5 + if magnitude > 0.0 then + direction.x = direction.x / magnitude + direction.y = direction.y / magnitude + + local scale = math.random(250,1000) + direction.x = direction.x * scale + direction.y = direction.y * scale + + detourPoint.x = detourPoint.x + direction.x + detourPoint.y = detourPoint.y + direction.y + else + detourPoint.x = detourPoint.x + math.random(-500,500) + detourPoint.y = detourPoint.y + math.random(-500,500) + end + + table.insert(mis.params.route.points, { + type= AI.Task.WaypointType.TURNING_POINT, + x = detourPoint.x, + y = detourPoint.y, + speed = 1000, + action = AI.Task.VehicleFormation.OFF_ROAD + }) + + srx, sry = land.getClosestPointOnRoads('roads', detourPoint.x, detourPoint.y) + end + + table.insert(mis.params.route.points, { + type= AI.Task.WaypointType.TURNING_POINT, + x = srx, + y = sry, + speed = 1000, + action = AI.Task.VehicleFormation.ON_ROAD + }) + + table.insert(mis.params.route.points, { + type= AI.Task.WaypointType.TURNING_POINT, + x = erx, + y = ery, + speed = 1000, + action = AI.Task.VehicleFormation.ON_ROAD + }) + + table.insert(mis.params.route.points, { + type= AI.Task.WaypointType.TURNING_POINT, + x = point.x, + y = point.y, + speed = 1000, + action = AI.Task.VehicleFormation.OFF_ROAD + }) + group:getController():setTask(mis) end @@ -2332,7 +2578,7 @@ do return "INVALID SQUAD" end - function PlayerLogistics:new(misTracker, plyTracker, squadTracker, csarTracker) + function PlayerLogistics:new() local obj = {} obj.groupMenus = {} -- groupid = path obj.carriedCargo = {} -- groupid = source @@ -2340,10 +2586,6 @@ do obj.carriedPilots = {} --groupid = source obj.registeredSquadGroups = {} obj.lastLoaded = {} -- groupid = zonename - obj.missionTracker = misTracker - obj.playerTracker = plyTracker - obj.squadTracker = squadTracker - obj.csarTracker = csarTracker obj.hercTracker = { cargos = {}, @@ -2357,6 +2599,7 @@ do obj:start() + DependencyManager.register("PlayerLogistics", obj) return obj end @@ -2635,8 +2878,12 @@ do end if cargo.unit and cargo.unit:isExist() then - local squadName = PlayerLogistics.getInfantryName(cargo.squad.type) - trigger.action.outTextForUnit(cargo.unit:getID(), 'Cargo drop of '..cargo.unit:getPlayerName()..' with '..squadName..' crashed', 10) + if cargo.squad then + local squadName = PlayerLogistics.getInfantryName(cargo.squad.type) + trigger.action.outTextForUnit(cargo.unit:getID(), 'Cargo drop of '..cargo.unit:getPlayerName()..' with '..squadName..' crashed', 10) + elseif cargo.supply then + trigger.action.outTextForUnit(cargo.unit:getID(), 'Cargo drop of '..cargo.unit:getPlayerName()..' with '..cargo.supply..' supplies crashed', 10) + end end end end @@ -2678,7 +2925,7 @@ do end end else - local error = self.squadTracker:spawnInfantry(self.registeredSquadGroups[cargo.squad.type], pos) + local error = DependencyManager.get("SquadTracker"):spawnInfantry(self.registeredSquadGroups[cargo.squad.type], pos) if not error then env.info('PlayerLogistics - Hercules - '..cargo.squad.type..' deployed') @@ -2687,14 +2934,14 @@ do if cargo.unit and cargo.unit:isExist() and cargo.unit.getPlayerName then trigger.action.outTextForUnit(cargo.unit:getID(), squadName..' deployed', 10) local player = cargo.unit:getPlayerName() - local xp = RewardDefinitions.actions.squadDeploy + local xp = RewardDefinitions.actions.squadDeploy * DependencyManager.get("PlayerTracker"):getPlayerMultiplier(player) - self.playerTracker:addStat(player, math.floor(xp), PlayerTracker.statTypes.xp) + DependencyManager.get("PlayerTracker"):addStat(player, math.floor(xp), PlayerTracker.statTypes.xp) if zn then - self.missionTracker:tallyUnloadSquad(player, zn.name, cargo.squad.type) + DependencyManager.get("MissionTracker"):tallyUnloadSquad(player, zn.name, cargo.squad.type) else - self.missionTracker:tallyUnloadSquad(player, '', cargo.squad.type) + DependencyManager.get("MissionTracker"):tallyUnloadSquad(player, '', cargo.squad.type) end trigger.action.outTextForUnit(cargo.unit:getID(), '+'..math.floor(xp)..' XP', 10) end @@ -2793,8 +3040,10 @@ do xp = xp * 0.25 end - self.playerTracker:addStat(player, math.floor(xp), PlayerTracker.statTypes.xp) - self.missionTracker:tallySupplies(player, amount, zone.name) + xp = xp * DependencyManager.get("PlayerTracker"):getPlayerMultiplier(player) + + DependencyManager.get("PlayerTracker"):addStat(player, math.floor(xp), PlayerTracker.statTypes.xp) + DependencyManager.get("MissionTracker"):tallySupplies(player, amount, zone.name) trigger.action.outTextForUnit(unit:getID(), '+'..math.floor(xp)..' XP', 10) end end @@ -2908,7 +3157,7 @@ do if gr then local un = gr:getUnit(1) if un then - local data = self.csarTracker:getClosestPilot(un:getPoint()) + local data = DependencyManager.get("CSARTracker"):getClosestPilot(un:getPoint()) if not data then trigger.action.outTextForUnit(un:getID(), 'No pilots in need of extraction', 10) @@ -2944,14 +3193,14 @@ do if gr then local un = gr:getUnit(1) if un then - local data = self.csarTracker:getClosestPilot(un:getPoint()) + local data = DependencyManager.get("CSARTracker"):getClosestPilot(un:getPoint()) if not data or data.dist >= 5000 then trigger.action.outTextForUnit(un:getID(), 'No pilots nearby', 10) return end - self.csarTracker:markPilot(data) + DependencyManager.get("CSARTracker"):markPilot(data) trigger.action.outTextForUnit(un:getID(), 'Location of '..data.name..' marked with green smoke.', 10) end end @@ -2962,14 +3211,14 @@ do if gr then local un = gr:getUnit(1) if un then - local data = self.csarTracker:getClosestPilot(un:getPoint()) + local data = DependencyManager.get("CSARTracker"):getClosestPilot(un:getPoint()) if not data or data.dist >= 5000 then trigger.action.outTextForUnit(un:getID(), 'No pilots nearby', 10) return end - self.csarTracker:flarePilot(data) + DependencyManager.get("CSARTracker"):flarePilot(data) trigger.action.outTextForUnit(un:getID(), data.name..' has deployed a green flare', 10) end end @@ -3015,8 +3264,10 @@ do local xp = #pilots*RewardDefinitions.actions.pilotExtract - self.playerTracker:addStat(player, math.floor(xp), PlayerTracker.statTypes.xp) - self.missionTracker:tallyUnloadPilot(player, zn.name) + xp = xp * DependencyManager.get("PlayerTracker"):getPlayerMultiplier(player) + + DependencyManager.get("PlayerTracker"):addStat(player, math.floor(xp), PlayerTracker.statTypes.xp) + DependencyManager.get("MissionTracker"):tallyUnloadPilot(player, zn.name) trigger.action.outTextForUnit(un:getID(), '+'..math.floor(xp)..' XP', 10) end @@ -3044,7 +3295,7 @@ do if not un:isExist() then return end local gr = un:getGroup() - local data = self.csarTracker:getClosestPilot(un:getPoint()) + local data = DependencyManager.get("CSARTracker"):getClosestPilot(un:getPoint()) if not data or data.dist > 500 then trigger.action.outTextForUnit(un:getID(), 'There is no pilot nearby that needs extraction', 10) @@ -3063,8 +3314,8 @@ do if not self.carriedPilots[gr:getID()] then self.carriedPilots[gr:getID()] = {} end table.insert(self.carriedPilots[gr:getID()], data.name) local player = un:getPlayerName() - self.missionTracker:tallyLoadPilot(player, data) - self.csarTracker:removePilot(data.name) + DependencyManager.get("MissionTracker"):tallyLoadPilot(player, data) + DependencyManager.get("CSARTracker"):removePilot(data.name) local weight = self:getCarriedPersonWeight(gr:getName()) trigger.action.setUnitInternalCargo(un:getName(), weight) trigger.action.outTextForUnit(un:getID(), data.name..' onboard. ('..weight..' kg)', 10) @@ -3098,7 +3349,7 @@ do return end - local squad, distance = self.squadTracker:getClosestExtractableSquad(un:getPoint()) + local squad, distance = DependencyManager.get("SquadTracker"):getClosestExtractableSquad(un:getPoint()) if squad and distance < 50 then local squadgr = Group.getByName(squad.name) if squadgr and squadgr:isExist() then @@ -3119,8 +3370,8 @@ do trigger.action.outTextForUnit(un:getID(), loadedInfName..' onboard. ('..weight..' kg)', 10) local player = un:getPlayerName() - self.missionTracker:tallyLoadSquad(player, squad) - self.squadTracker:removeSquad(squad.name) + DependencyManager.get("MissionTracker"):tallyLoadSquad(player, squad) + DependencyManager.get("SquadTracker"):removeSquad(squad.name) squadgr:destroy() end @@ -3280,31 +3531,31 @@ do if un.getPlayerName then local player = un:getPlayerName() - local xp = RewardDefinitions.actions.squadExtract + local xp = RewardDefinitions.actions.squadExtract * DependencyManager.get("PlayerTracker"):getPlayerMultiplier(player) - self.playerTracker:addStat(player, math.floor(xp), PlayerTracker.statTypes.xp) - self.missionTracker:tallyUnloadSquad(player, zn.name, sq.type) + DependencyManager.get("PlayerTracker"):addStat(player, math.floor(xp), PlayerTracker.statTypes.xp) + DependencyManager.get("MissionTracker"):tallyUnloadSquad(player, zn.name, sq.type) trigger.action.outTextForUnit(un:getID(), '+'..math.floor(xp)..' XP', 10) end end elseif self.registeredSquadGroups[sq.type] then local pos = Utils.getPointOnSurface(un:getPoint()) - local error = self.squadTracker:spawnInfantry(self.registeredSquadGroups[sq.type], pos) + local error = DependencyManager.get("SquadTracker"):spawnInfantry(self.registeredSquadGroups[sq.type], pos) if not error then trigger.action.outTextForUnit(un:getID(), squadName..' deployed', 10) if un.getPlayerName then local player = un:getPlayerName() - local xp = RewardDefinitions.actions.squadDeploy + local xp = RewardDefinitions.actions.squadDeploy * DependencyManager.get("PlayerTracker"):getPlayerMultiplier(player) - self.playerTracker:addStat(player, math.floor(xp), PlayerTracker.statTypes.xp) + DependencyManager.get("PlayerTracker"):addStat(player, math.floor(xp), PlayerTracker.statTypes.xp) if zn then - self.missionTracker:tallyUnloadSquad(player, zn.name, sq.type) + DependencyManager.get("MissionTracker"):tallyUnloadSquad(player, zn.name, sq.type) else - self.missionTracker:tallyUnloadSquad(player, '', sq.type) + DependencyManager.get("MissionTracker"):tallyUnloadSquad(player, '', sq.type) end trigger.action.outTextForUnit(un:getID(), '+'..math.floor(xp)..' XP', 10) end @@ -3500,9 +3751,11 @@ do if un:getDesc().typeName == "Hercules" then local loadedInCrates = 0 local ammo = un:getAmmo() - for _,load in ipairs(ammo) do - if load.desc.typeName == 'weapons.bombs.Generic Crate [20000lb]' then - loadedInCrates = 9000 * load.count + if ammo then + for _,load in ipairs(ammo) do + if load.desc.typeName == 'weapons.bombs.Generic Crate [20000lb]' then + loadedInCrates = 9000 * load.count + end end end @@ -3616,6 +3869,7 @@ do obj:start() + DependencyManager.register("MarkerCommands", obj) return obj end @@ -3711,8 +3965,6 @@ do obj.reservedMissions = {} obj.isHeloSpawn = false obj.isPlaneSpawn = false - - obj.connectionManager = nil obj.zone = CustomZone:getByName(zonename) obj.products = {} @@ -3806,9 +4058,9 @@ do end end - function ZoneCommand.setNeighbours(conManager) + function ZoneCommand.setNeighbours() + local conManager = DependencyManager.get("ConnectionManager") for name,zone in pairs(ZoneCommand.allZones) do - zone.connectionManager = conManager local neighbours = conManager:getConnectionsOfZone(name) zone.neighbours = {} for _,zname in ipairs(neighbours) do @@ -4637,16 +4889,14 @@ do end end elseif product.missionType == ZoneCommand.missionTypes.bai then - if not ZoneCommand.groupMonitor then return false end if self.mode ~= ZoneCommand.modes.normal and not self.keepActive then return false end - for _,tgt in pairs(ZoneCommand.groupMonitor.groups) do + for _,tgt in pairs(DependencyManager.get("GroupMonitor").groups) do if self:isBaiMissionValid(product, tgt) then return true end end elseif product.missionType == ZoneCommand.missionTypes.awacs then - if not ZoneCommand.groupMonitor then return false end if self.mode ~= ZoneCommand.modes.normal and not self.keepActive then return false end for _,tgt in pairs(ZoneCommand.getAllZones()) do if self:isAwacsMissionValid(product, tgt) then @@ -4654,7 +4904,6 @@ do end end elseif product.missionType == ZoneCommand.missionTypes.tanker then - if not ZoneCommand.groupMonitor then return false end if self.mode ~= ZoneCommand.modes.normal and not self.keepActive then return false end if not self.distToFront or self.distToFront == 0 then return false end for _,tgt in pairs(ZoneCommand.getAllZones()) do @@ -4781,9 +5030,7 @@ do end if supplyPoint then mist.teleportToPoint(getDefaultPos(savedData, false)) - if ZoneCommand.groupMonitor then - ZoneCommand.groupMonitor:registerGroup(product, zone, self, savedData) - end + DependencyManager.get("GroupMonitor"):registerGroup(product, zone, self, savedData) product.lastMission = {zoneName = zone.name} timer.scheduleFunction(function(param) @@ -4802,9 +5049,7 @@ do end if supplyPoint then mist.teleportToPoint(getDefaultPos(savedData, false)) - if ZoneCommand.groupMonitor then - ZoneCommand.groupMonitor:registerGroup(product, zone, self, savedData) - end + DependencyManager.get("GroupMonitor"):registerGroup(product, zone, self, savedData) local tgtPoint = trigger.misc.getZone(zone.name) @@ -4822,9 +5067,7 @@ do local zone = ZoneCommand.getZoneByName(savedData.lastMission.zoneName) mist.teleportToPoint(getDefaultPos(savedData, true)) - if ZoneCommand.groupMonitor then - ZoneCommand.groupMonitor:registerGroup(product, zone, self, savedData) - end + DependencyManager.get("GroupMonitor"):registerGroup(product, zone, self, savedData) local supplyPoint = trigger.misc.getZone(zone.name..'-hsp') if not supplyPoint then @@ -4833,7 +5076,7 @@ do if supplyPoint then product.lastMission = {zoneName = zone.name} - local alt = self.connectionManager:getHeliAlt(self.name, zone.name) + local alt = DependencyManager.get("ConnectionManager"):getHeliAlt(self.name, zone.name) timer.scheduleFunction(function(param) local gr = Group.getByName(param.name) TaskExtensions.landAtPoint(gr, param.point, param.alt) @@ -4849,9 +5092,7 @@ do local zone = ZoneCommand.getZoneByName(savedData.lastMission.zoneName) mist.teleportToPoint(getDefaultPos(savedData, true)) - if ZoneCommand.groupMonitor then - ZoneCommand.groupMonitor:registerGroup(product, zone, self, savedData) - end + DependencyManager.get("GroupMonitor"):registerGroup(product, zone, self, savedData) local homePos = trigger.misc.getZone(savedData.homeName).point @@ -4873,9 +5114,7 @@ do mist.teleportToPoint(getDefaultPos(savedData, true)) - if ZoneCommand.groupMonitor then - ZoneCommand.groupMonitor:registerGroup(product, zone, self, savedData) - end + DependencyManager.get("GroupMonitor"):registerGroup(product, zone, self, savedData) local homePos = trigger.misc.getZone(savedData.homeName).point @@ -4892,9 +5131,7 @@ do local zone = ZoneCommand.getZoneByName(savedData.lastMission.zoneName) mist.teleportToPoint(getDefaultPos(savedData, true)) - if ZoneCommand.groupMonitor then - ZoneCommand.groupMonitor:registerGroup(product, zone, self, savedData) - end + DependencyManager.get("GroupMonitor"):registerGroup(product, zone, self, savedData) local homePos = trigger.misc.getZone(savedData.homeName).point @@ -4912,9 +5149,7 @@ do local zn1 = ZoneCommand.getZoneByName(savedData.lastMission.zone1name) mist.teleportToPoint(getDefaultPos(savedData, true)) - if ZoneCommand.groupMonitor then - ZoneCommand.groupMonitor:registerGroup(product, zn1, self, savedData) - end + DependencyManager.get("GroupMonitor"):registerGroup(product, zn1, self, savedData) local homePos = trigger.misc.getZone(savedData.homeName).point @@ -4933,7 +5168,7 @@ do function ZoneCommand:reActivateBaiMission(product, savedData) local targets = {} local hasTarget = false - for _,tgt in pairs(ZoneCommand.groupMonitor.groups) do + for _,tgt in pairs(DependencyManager.get("GroupMonitor").groups) do if self:isBaiMissionValid(product, tgt) then targets[tgt.product.name] = tgt.product hasTarget = true @@ -4944,9 +5179,7 @@ do if hasTarget then mist.teleportToPoint(getDefaultPos(savedData, true)) - if ZoneCommand.groupMonitor then - ZoneCommand.groupMonitor:registerGroup(product, nil, self, savedData) - end + DependencyManager.get("GroupMonitor"):registerGroup(product, nil, self, savedData) product.lastMission = { active = true } timer.scheduleFunction(function(param) @@ -4962,9 +5195,7 @@ do local homePos = trigger.misc.getZone(savedData.homeName).point mist.teleportToPoint(getDefaultPos(savedData, true)) - if ZoneCommand.groupMonitor then - ZoneCommand.groupMonitor:registerGroup(product, nil, self, savedData) - end + DependencyManager.get("GroupMonitor"):registerGroup(product, nil, self, savedData) timer.scheduleFunction(function(param) local gr = Group.getByName(param.prod.name) if gr then @@ -4987,9 +5218,7 @@ do local homePos = trigger.misc.getZone(savedData.homeName).point mist.teleportToPoint(getDefaultPos(savedData, true)) - if ZoneCommand.groupMonitor then - ZoneCommand.groupMonitor:registerGroup(product, zone, self, savedData) - end + DependencyManager.get("GroupMonitor"):registerGroup(product, zone, self, savedData) timer.scheduleFunction(function(param) local gr = Group.getByName(param.prod.name) @@ -5018,7 +5247,7 @@ do --{name = product.name, lastStateTime = timer.getAbsTime(), product = product, target = target} local targets = {} local hasTarget = false - for _,tgt in pairs(ZoneCommand.groupMonitor.groups) do + for _,tgt in pairs(DependencyManager.get("GroupMonitor").groups) do if self:isBaiMissionValid(product, tgt) then targets[tgt.product.name] = tgt.product hasTarget = true @@ -5035,9 +5264,7 @@ do env.info("ZoneCommand - activateBaiMission fallback to respawnGroup") end - if ZoneCommand.groupMonitor then - ZoneCommand.groupMonitor:registerGroup(product, nil, self) - end + DependencyManager.get("GroupMonitor"):registerGroup(product, nil, self) product.lastMission = { active = true } timer.scheduleFunction(function(param) @@ -5136,9 +5363,7 @@ do env.info("ZoneCommand - activateAirSupplyMission fallback to respawnGroup") end - if ZoneCommand.groupMonitor then - ZoneCommand.groupMonitor:registerGroup(product, v, self) - end + DependencyManager.get("GroupMonitor"):registerGroup(product, v, self) local supplyPoint = trigger.misc.getZone(v.name..'-hsp') if not supplyPoint then @@ -5148,7 +5373,7 @@ do if supplyPoint then product.lastMission = {zoneName = v.name} - local alt = self.connectionManager:getHeliAlt(self.name, v.name) + local alt = DependencyManager.get("ConnectionManager"):getHeliAlt(self.name, v.name) timer.scheduleFunction(function(param) local gr = Group.getByName(param.name) TaskExtensions.landAtPoint(gr, param.point, param.alt ) @@ -5189,12 +5414,8 @@ do end function ZoneCommand:isSupplyMissionValid(product, target) - if not self.connectionManager then - env.info("ZoneCommand - ERROR missing connection manager") - end - if product.missionType == ZoneCommand.missionTypes.supply_convoy then - if self.connectionManager:isRoadBlocked(self.name, target.name) then + if DependencyManager.get("ConnectionManager"):isRoadBlocked(self.name, target.name) then return false end end @@ -5266,9 +5487,7 @@ do env.info("ZoneCommand - activateSupplyConvoyMission fallback to respawnGroup") end - if ZoneCommand.groupMonitor then - ZoneCommand.groupMonitor:registerGroup(product, v, self) - end + DependencyManager.get("GroupMonitor"):registerGroup(product, v, self) product.lastMission = {zoneName = v.name} timer.scheduleFunction(function(param) @@ -5285,12 +5504,9 @@ do end function ZoneCommand:isAssaultMissionValid(product, target) - if not self.connectionManager then - env.info("ZoneCommand - ERROR missing connection manager") - end if product.missionType == ZoneCommand.missionTypes.assault then - if self.connectionManager:isRoadBlocked(self.name, target.name) then + if DependencyManager.get("ConnectionManager"):isRoadBlocked(self.name, target.name) then return false end end @@ -5333,9 +5549,7 @@ do env.info("ZoneCommand - activateAssaultMission fallback to respawnGroup") end - if ZoneCommand.groupMonitor then - ZoneCommand.groupMonitor:registerGroup(product, v, self) - end + DependencyManager.get("GroupMonitor"):registerGroup(product, v, self) local tgtPoint = trigger.misc.getZone(v.name) @@ -5382,9 +5596,8 @@ do env.info("ZoneCommand - activateAwacsMission fallback to respawnGroup") end - if ZoneCommand.groupMonitor then - ZoneCommand.groupMonitor:registerGroup(product, zn, self) - end + DependencyManager.get("GroupMonitor"):registerGroup(product, zn, self) + timer.scheduleFunction(function(param) local gr = Group.getByName(param.prod.name) if gr then @@ -5434,9 +5647,7 @@ do env.info("ZoneCommand - activateTankerMission fallback to respawnGroup") end - if ZoneCommand.groupMonitor then - ZoneCommand.groupMonitor:registerGroup(product, zn, self) - end + DependencyManager.get("GroupMonitor"):registerGroup(product, zn, self) timer.scheduleFunction(function(param) local gr = Group.getByName(param.prod.name) @@ -5494,9 +5705,7 @@ do env.info("ZoneCommand - activatePatrolMission fallback to respawnGroup") end - if ZoneCommand.groupMonitor then - ZoneCommand.groupMonitor:registerGroup(product, zn1, self) - end + DependencyManager.get("GroupMonitor"):registerGroup(product, zn1, self) if zn1 then product.lastMission = {zone1name = zn1.name} @@ -5601,7 +5810,7 @@ do for i,v in pairs(self.built) do if v.type == ZoneCommand.productTypes.upgrade and v.side == side then local st = StaticObject.getByName(v.name) - if st then + if st and st:isExist() then for _,a in ipairs(attributes) do if a == "Buildings" and ZoneCommand.staticRegistry[v.name] then -- dcs does not consider all statics buildings so we compensate if amount==nil then @@ -5617,13 +5826,15 @@ do local gr = Group.getByName(v.name) if gr then for _,unit in ipairs(gr:getUnits()) do - for _,a in ipairs(attributes) do - if unit:hasAttribute(a) then - if amount==nil then - return true - else - count = count + 1 - if count >= amount then return true end + if unit:isExist() then + for _,a in ipairs(attributes) do + if unit:hasAttribute(a) then + if amount==nil then + return true + else + count = count + 1 + if count >= amount then return true end + end end end end @@ -5692,9 +5903,7 @@ do env.info("ZoneCommand - activateSeadMission fallback to respawnGroup") end - if ZoneCommand.groupMonitor then - ZoneCommand.groupMonitor:registerGroup(product, target, self) - end + DependencyManager.get("GroupMonitor"):registerGroup(product, target, self) if target then product.lastMission = {zoneName = target.name} @@ -5750,9 +5959,7 @@ do env.info("ZoneCommand - activateStrikeMission fallback to respawnGroup") end - if ZoneCommand.groupMonitor then - ZoneCommand.groupMonitor:registerGroup(product, target, self) - end + DependencyManager.get("GroupMonitor"):registerGroup(product, target, self) if target then product.lastMission = {zoneName = target.name} @@ -5814,9 +6021,7 @@ do env.info("ZoneCommand - activateCasMission fallback to respawnGroup") end - if ZoneCommand.groupMonitor then - ZoneCommand.groupMonitor:registerGroup(product, target, self) - end + DependencyManager.get("GroupMonitor"):registerGroup(product, target, self) if target then product.lastMission = {zoneName = target.name} @@ -6230,16 +6435,17 @@ do [PlayerTracker.cmdShopTypes.bribe2] = 3, } - function PlayerTracker:new(markerCommands) + function PlayerTracker:new() local obj = {} - obj.markerCommands = markerCommands obj.stats = {} + obj.config = {} obj.tempStats = {} obj.groupMenus = {} obj.groupShopMenus = {} + obj.groupConfigMenus = {} obj.groupTgtMenus = {} obj.playerAircraft = {} - obj.playerWeaponStock = {} + obj.playerEarningMultiplier = {} if lfs then local dir = lfs.writedir()..'Missions/Saves/' @@ -6251,7 +6457,7 @@ do local save = Utils.loadTable(PlayerTracker.savefile) if save then obj.stats = save.stats or {} - obj.playerWeaponStock = save.playerWeaponStock or {} + obj.config = save.config or {} end setmetatable(obj, self) @@ -6259,6 +6465,7 @@ do obj:init() + DependencyManager.register("PlayerTracker", obj) return obj end @@ -6275,7 +6482,7 @@ do if event.id==world.event.S_EVENT_PLAYER_ENTER_UNIT then if event.initiator and Object.getCategory(event.initiator) == Object.Category.UNIT and - (Unit.getCategory(event.initiator) == Unit.Category.AIRPLANE or Unit.getCategory(event.initiator) == Unit.Category.HELICOPTER) then + (Unit.getCategoryEx(event.initiator) == Unit.Category.AIRPLANE or Unit.getCategoryEx(event.initiator) == Unit.Category.HELICOPTER) then local pname = event.initiator:getPlayerName() if pname then @@ -6298,6 +6505,14 @@ do self.context.tempStats[player] = nil -- reset playeraircraft self.context.playerAircraft[player] = nil + + self.context.playerEarningMultiplier[player] = { spawnTime = timer.getAbsTime(), unit = event.initiator, multiplier = 1.0, minutes = 0 } + + local config = self.context:getPlayerConfig(player) + if config.gci_warning_radius then + local gci = DependencyManager.get("GCI") + gci:registerPlayer(player, event.initiator, config.gci_warning_radius, config.gci_metric) + end end if event.id == world.event.S_EVENT_KILL then @@ -6311,6 +6526,8 @@ do local xpkey = PlayerTracker.statTypes.xp local award = PlayerTracker.getXP(target) + award = math.floor(award * self.context:getPlayerMultiplier(player)) + local instantxp = math.floor(award*0.25) local tempxp = award - instantxp @@ -6333,7 +6550,7 @@ do local key = PlayerTracker.statTypes.xp local xp = self.context.tempStats[player][key] if xp then - local isFree = event.initiator:getGroup():getName():find("(FREE)") + xp = xp * self.context:getPlayerMultiplier(player) trigger.action.outTextForUnit(un:getID(), 'Ejection. 30\% XP claimed', 5) self.context:addStat(player, math.floor(xp*0.3), PlayerTracker.statTypes.xp) trigger.action.outTextForUnit(un:getID(), '[XP] '..self.context.stats[player][key]..' (+'..math.floor(xp*0.3)..')', 5) @@ -6364,53 +6581,79 @@ do end if event.id==world.event.S_EVENT_LAND then - local un = event.initiator - local zn = ZoneCommand.getZoneOfUnit(event.initiator:getName()) - local aircraft = self.context.playerAircraft[player] - env.info('PlayerTracker - '..player..' landed in '..tostring(un:getID())..' '..un:getName()) - if aircraft and un and zn and zn.side == un:getCoalition() then - trigger.action.outTextForUnit(event.initiator:getID(), "Wait 10 seconds to validate landing...", 10) - timer.scheduleFunction(function(param, time) - local un = param.unit - if not un or not un:isExist() then return end - - local player = param.player - local isLanded = Utils.isLanded(un, true) - local zn = ZoneCommand.getZoneOfUnit(un:getName()) - - env.info('PlayerTracker - '..player..' checking if landed: '..tostring(isLanded)) - - if isLanded then - if self.context.tempStats[player] then - if zn and zn.side == un:getCoalition() then - self.context.stats[player] = self.context.stats[player] or {} - - trigger.action.outTextForUnit(un:getID(), 'Rewards claimed', 5) - for _,key in pairs(PlayerTracker.statTypes) do - local value = self.context.tempStats[player][key] - env.info("PlayerTracker.landing - "..player..' redeeming '..tostring(value)..' '..key) - if value then - self.context:commitTempStat(player, key) - trigger.action.outTextForUnit(un:getID(), key..' +'..value..'', 5) - end - end - end - end - - local aircraft = param.context.playerAircraft[player] - if aircraft and aircraft.unitID == un:getID() then - param.context.playerAircraft[player] = nil - end - end - end, {player = player, unit = event.initiator, context = self.context}, timer.getTime()+10) - end - + self.context:validateLanding(event.initiator, player) end end world.addEventHandler(ev) self:periodicSave() self:menuSetup() + + timer.scheduleFunction(function(params, time) + local players = params.context.playerEarningMultiplier + for i,v in pairs(players) do + local aircraft = params.context.playerAircraft[i] + if aircraft and v.unit.isExist and v.unit:isExist() and aircraft.unitID == v.unit:getID() then + if v.multiplier < 5.0 and v.unit and v.unit:isExist() and Utils.isInAir(v.unit) then + v.minutes = v.minutes + 1 + + local multi = 1.0 + if v.minutes > 10 and v.minutes <= 60 then + multi = 1.0 + ((v.minutes-10)*0.05) + elseif v.minutes > 60 then + multi = 1.0 + (50*0.05) + ((v.minutes - 60)*0.025) + end + + v.multiplier = math.min(multi, 5.0) + end + end + end + + return time+60 + end, {context = self}, timer.getTime()+60) + end + + function PlayerTracker:validateLanding(unit, player) + local un = unit + local zn = ZoneCommand.getZoneOfUnit(unit:getName()) + local aircraft = self.playerAircraft[player] + env.info('PlayerTracker - '..player..' landed in '..tostring(un:getID())..' '..un:getName()) + if aircraft and un and zn and zn.side == un:getCoalition() then + trigger.action.outTextForUnit(unit:getID(), "Wait 10 seconds to validate landing...", 10) + timer.scheduleFunction(function(param, time) + local un = param.unit + if not un or not un:isExist() then return end + + local player = param.player + local isLanded = Utils.isLanded(un, true) + local zn = ZoneCommand.getZoneOfUnit(un:getName()) + + env.info('PlayerTracker - '..player..' checking if landed: '..tostring(isLanded)) + + if isLanded then + if param.context.tempStats[player] then + if zn and zn.side == un:getCoalition() then + param.context.stats[player] = param.context.stats[player] or {} + + trigger.action.outTextForUnit(un:getID(), 'Rewards claimed', 5) + for _,key in pairs(PlayerTracker.statTypes) do + local value = param.context.tempStats[player][key] + env.info("PlayerTracker.landing - "..player..' redeeming '..tostring(value)..' '..key) + if value then + param.context:commitTempStat(player, key) + trigger.action.outTextForUnit(un:getID(), key..' +'..value..'', 5) + end + end + end + end + + local aircraft = param.context.playerAircraft[player] + if aircraft and aircraft.unitID == un:getID() then + param.context.playerAircraft[player] = nil + end + end + end, {player = player, unit = unit, context = self}, timer.getTime()+10) + end end function PlayerTracker:addTempStat(player, amount, stattype) @@ -6512,6 +6755,7 @@ do local menu = missionCommands.addSubMenuForGroup(groupid, 'Information') missionCommands.addCommandForGroup(groupid, 'Player', menu, Utils.log(context.showGroupStats), context, groupname) missionCommands.addCommandForGroup(groupid, 'Frequencies', menu, Utils.log(context.showFrequencies), context, groupname) + missionCommands.addCommandForGroup(groupid, 'Validate Landing', menu, Utils.log(context.validateLandingMenu), context, groupname) context.groupMenus[groupid] = menu end @@ -6564,6 +6808,21 @@ do context.groupShopMenus[groupid] = menu end + + if context.groupConfigMenus[groupid] then + missionCommands.removeItemForGroup(groupid, context.groupConfigMenus[groupid]) + context.groupConfigMenus[groupid] = nil + end + + if not context.groupConfigMenus[groupid] then + + local menu = missionCommands.addSubMenuForGroup(groupid, 'Config') + local missionWarningMenu = missionCommands.addSubMenuForGroup(groupid, 'No mission warning', menu) + missionCommands.addCommandForGroup(groupid, 'Activate', missionWarningMenu, Utils.log(context.setNoMissionWarning), context, groupname, true) + missionCommands.addCommandForGroup(groupid, 'Deactivate', missionWarningMenu, Utils.log(context.setNoMissionWarning), context, groupname, false) + + context.groupConfigMenus[groupid] = menu + end end end elseif (event.id == world.event.S_EVENT_PLAYER_LEAVE_UNIT or event.id == world.event.S_EVENT_DEAD) and event.initiator and event.initiator.getPlayerName then @@ -6584,7 +6843,7 @@ do end end, self) - self.markerCommands:addCommand('stats',function(event, _, state) + DependencyManager.get("MarkerCommands"):addCommand('stats',function(event, _, state) local unit = nil if event.initiator then unit = event.initiator @@ -6598,7 +6857,7 @@ do return true end, false, self) - self.markerCommands:addCommand('freqs',function(event, _, state) + DependencyManager.get("MarkerCommands"):addCommand('freqs',function(event, _, state) local unit = nil if event.initiator then unit = event.initiator @@ -6613,6 +6872,19 @@ do end, false, self) end + function PlayerTracker:setNoMissionWarning(groupname, active) + local gr = Group.getByName(groupname) + if gr and gr:getSize()>0 then + local un = gr:getUnit(1) + if un then + local player = un:getPlayerName() + if player then + self:setPlayerConfig(player, "noMissionWarning", active) + end + end + end + end + function PlayerTracker:buyCommand(groupname, itemType) local gr = Group.getByName(groupname) if gr and gr:getSize()>0 then @@ -6717,6 +6989,17 @@ do end end + function PlayerTracker:validateLandingMenu(groupname) + local gr = Group.getByName(groupname) + if gr then + for i,v in pairs(gr:getUnits()) do + if v.getPlayerName and v:getPlayerName() then + self:validateLanding(v, v:getPlayerName()) + end + end + end + end + function PlayerTracker:showGroupStats(groupname) local gr = Group.getByName(groupname) if gr then @@ -6742,6 +7025,11 @@ do end end + local multiplier = self:getPlayerMultiplier(player) + if multiplier then + message = message..'\nSurvival XP multiplier: '..string.format("%.2f", multiplier)..'x' + end + local cmd = stats[PlayerTracker.statTypes.cmd] if cmd then message = message ..'\n\nCMD: '..cmd @@ -6763,11 +7051,28 @@ do end end + function PlayerTracker:setPlayerConfig(player, setting, value) + local cfg = self:getPlayerConfig(player) + cfg[setting] = value + end + + function PlayerTracker:getPlayerConfig(player) + if not self.config[player] then + self.config[player] = { + noMissionWarning = false, + gci_warning_radius = nil, + gci_metric = nil + } + end + + return self.config[player] + end + function PlayerTracker:periodicSave() timer.scheduleFunction(function(param, time) local tosave = {} tosave.stats = param.stats - tosave.playerWeaponStock = param.playerWeaponStock + tosave.config = param.config --temp mission stat tracking tosave.zones = {} @@ -6829,6 +7134,14 @@ do end end + function PlayerTracker:getPlayerMultiplier(playername) + if self.playerEarningMultiplier[playername] then + return self.playerEarningMultiplier[playername].multiplier + end + + return 1.0 + end + function PlayerTracker:getRank(xp) local rank = nil local nextRank = nil @@ -7102,13 +7415,9 @@ PersistenceManager = {} do - function PersistenceManager:new(path, groupManager, squadTracker, csarTracker, playerLogistics) + function PersistenceManager:new(path) local obj = { path = path, - groupManager = groupManager, - squadTracker = squadTracker, - csarTracker = csarTracker, - playerLogistics = playerLogistics, data = nil } @@ -7252,7 +7561,7 @@ do local save = self.data if save.csarTracker then for i,v in pairs(save.csarTracker) do - self.csarTracker:restorePilot(v) + DependencyManager.get("CSARTracker"):restorePilot(v) end end end @@ -7261,17 +7570,30 @@ do local save = self.data if save.squadTracker then for i,v in pairs(save.squadTracker) do - local sdata = self.playerLogistics.registeredSquadGroups[v.type] + local sdata = DependencyManager.get("PlayerLogistics").registeredSquadGroups[v.type] if sdata then v.data = sdata - self.squadTracker:restoreInfantry(v) + DependencyManager.get("SquadTracker"):restoreInfantry(v) end end end end function PersistenceManager:canRestore() - return self.data ~= nil + if self.data == nil then return false end + + local redExist = false + local blueExist = false + for _,z in pairs(self.data.zones) do + if z.side == 1 and not redExist then redExist = true end + if z.side == 2 and not blueExist then blueExist = true end + + if redExist and blueExist then break end + end + + if not redExist or not blueExist then return false end + + return true end function PersistenceManager:load() @@ -7330,7 +7652,7 @@ do end tosave.activeGroups = {} - for i,v in pairs(self.groupManager.groups) do + for i,v in pairs(DependencyManager.get("GroupMonitor").groups) do tosave.activeGroups[i] = { productName = v.product.name, type = v.product.missionType @@ -7393,7 +7715,7 @@ do tosave.csarTracker = {} - for i,v in pairs(self.csarTracker.activePilots) do + for i,v in pairs(DependencyManager.get("CSARTracker").activePilots) do if v.pilot:isExist() and v.pilot:getSize()>0 and v.remainingTime>60 then tosave.csarTracker[i] = { name = v.name, @@ -7405,7 +7727,7 @@ do tosave.squadTracker = {} - for i,v in pairs(self.squadTracker.activeInfantrySquads) do + for i,v in pairs(DependencyManager.get("SquadTracker").activeInfantrySquads) do tosave.squadTracker[i] = { state = v.state, remainingStateTime = v.remainingStateTime, @@ -9039,9 +9361,9 @@ do end if self.param.mis.state == Mission.states.completed then - if self.state == Mission.states.new or - self.state == Mission.states.preping or - self.state == Mission.states.comencing then + if self.mission.state == Mission.states.new or + self.mission.state == Mission.states.preping or + self.mission.state == Mission.states.comencing then self.isFailed = true end @@ -9774,6 +10096,19 @@ do end end + function Mission:pushMessageToPlayer(player, msg, duration) + if not duration then + duration = 10 + end + + for name,un in pairs(self.players) do + if name == player and un and un:isExist() then + trigger.action.outTextForUnit(un:getID(), msg, duration) + break + end + end + end + function Mission:pushMessageToPlayers(msg, duration) if not duration then duration = 10 @@ -10111,7 +10446,7 @@ do function Mission:getDetailedDescription() local msg = '['..self.name..']' - if self.state == Mission.states.comencing or self.state == Mission.states.preping then + if self.state == Mission.states.comencing or self.state == Mission.states.preping or (not Config.restrictMissionAcceptance) then msg = msg..'\nJoin code ['..self.missionID..']' end @@ -10988,7 +11323,7 @@ Escort = Mission:new() do function Escort.canCreate() local currentTime = timer.getAbsTime() - for _,gr in pairs(ZoneCommand.groupMonitor.groups) do + for _,gr in pairs(DependencyManager.get("GroupMonitor").groups) do if gr.product.side == 2 and gr.product.type == 'mission' and (gr.product.missionType == 'supply_convoy' or gr.product.missionType == 'assault') then local z = gr.target if z.distToFront == 0 and z.side~= 2 then @@ -11014,7 +11349,7 @@ do local currentTime = timer.getAbsTime() local viableConvoys = {} - for _,gr in pairs(ZoneCommand.groupMonitor.groups) do + for _,gr in pairs(DependencyManager.get("GroupMonitor").groups) do if gr.product.side == 2 and gr.product.type == 'mission' and (gr.product.missionType == 'supply_convoy' or gr.product.missionType == 'assault') then local z = gr.target if z.distToFront == 0 and z.side ~= 2 then @@ -11796,7 +12131,7 @@ do [Mission.types.cas_easy] = 1, [Mission.types.cas_medium] = 1, [Mission.types.cas_hard] = 1, - [Mission.types.sead] = 1, + [Mission.types.sead] = 3, [Mission.types.supply_easy] = 1, [Mission.types.supply_hard] = 1, [Mission.types.strike_veryeasy] = 1, @@ -11814,7 +12149,7 @@ do [Mission.types.anti_runway] = 1, [Mission.types.csar] = 1, [Mission.types.extraction] = 1, - [Mission.types.deploy_squad] = 1, + [Mission.types.deploy_squad] = 3, } if Config.missions then @@ -11827,10 +12162,8 @@ do MissionTracker.missionBoardSize = 10 - function MissionTracker:new(playerTracker, markerCommands) + function MissionTracker:new() local obj = {} - obj.playerTracker = playerTracker - obj.markerCommands = markerCommands obj.groupMenus = {} obj.missionIDPool = {} obj.missionBoard = {} @@ -11839,7 +12172,7 @@ do setmetatable(obj, self) self.__index = self - obj.markerCommands:addCommand('list', function(event, _, state) + DependencyManager.get("MarkerCommands"):addCommand('list', function(event, _, state) if event.initiator then state:printMissionBoard(event.initiator:getID(), nil, event.initiator:getGroup():getName()) elseif world.getPlayer() then @@ -11849,7 +12182,7 @@ do return true end, nil, obj) - obj.markerCommands:addCommand('help', function(event, _, state) + DependencyManager.get("MarkerCommands"):addCommand('help', function(event, _, state) if event.initiator then state:printHelp(event.initiator:getID()) elseif world.getPlayer() then @@ -11859,7 +12192,7 @@ do return true end, nil, obj) - obj.markerCommands:addCommand('active', function(event, _, state) + DependencyManager.get("MarkerCommands"):addCommand('active', function(event, _, state) if event.initiator then state:printActiveMission(event.initiator:getID(), nil, event.initiator:getPlayerName()) elseif world.getPlayer() then @@ -11868,7 +12201,7 @@ do return true end, nil, obj) - obj.markerCommands:addCommand('accept',function(event, code, state) + DependencyManager.get("MarkerCommands"):addCommand('accept',function(event, code, state) local numcode = tonumber(code) if not numcode or numcode<1000 or numcode > 9999 then return false end @@ -11885,7 +12218,7 @@ do return state:activateMission(numcode, player, unit) end, true, obj) - obj.markerCommands:addCommand('join',function(event, code, state) + DependencyManager.get("MarkerCommands"):addCommand('join',function(event, code, state) local numcode = tonumber(code) if not numcode or numcode<1000 or numcode > 9999 then return false end @@ -11902,7 +12235,7 @@ do return state:joinMission(numcode, player, unit) end, true, obj) - obj.markerCommands:addCommand('leave',function(event, _, state) + DependencyManager.get("MarkerCommands"):addCommand('leave',function(event, _, state) local player = '' if event.initiator then player = event.initiator:getPlayerName() @@ -11915,6 +12248,8 @@ do obj:menuSetup() obj:start() + + DependencyManager.register("MissionTracker", obj) return obj end @@ -12096,6 +12431,30 @@ do end function MissionTracker:start() + timer.scheduleFunction(function(params, time) + for i,v in ipairs(coalition.getPlayers(2)) do + if v and v:isExist() and not Utils.isInAir(v) and v.getPlayerName and v:getPlayerName() then + local player = v:getPlayerName() + local cfg = DependencyManager.get("PlayerTracker"):getPlayerConfig(player) + if cfg.noMissionWarning == true then + local hasMis = false + for _,mis in pairs(params.context.activeMissions) do + if mis.players[player] then + hasMis = true + break + end + end + + if not hasMis then + trigger.action.outTextForUnit(v:getID(), "No mission selected", 9) + end + end + end + end + + return time+10 + end, {context = self}, timer.getTime()+10) + timer.scheduleFunction(function(param, time) for code,mis in pairs(param.missionBoard) do if timer.getAbsTime() - mis.lastStateTime > mis.expireTime then @@ -12207,20 +12566,22 @@ do for _,reward in ipairs(mis.rewards) do for p,_ in pairs(mis.players) do - if isInstant then - param.playerTracker:addStat(p, reward.amount, reward.type) - else - param.playerTracker:addTempStat(p, reward.amount, reward.type) + local finalAmount = reward.amount + if reward.type == PlayerTracker.statTypes.xp then + finalAmount = math.floor(finalAmount * DependencyManager.get("PlayerTracker"):getPlayerMultiplier(p)) end - end - if isInstant then - mis:pushMessageToPlayers('+'..reward.amount..' '..reward.type) + if isInstant then + DependencyManager.get("PlayerTracker"):addStat(p, finalAmount, reward.type) + mis:pushMessageToPlayer(p, '+'..reward.amount..' '..reward.type) + else + DependencyManager.get("PlayerTracker"):addTempStat(p, finalAmount, reward.type) + end end end for p,u in pairs(mis.players) do - param.playerTracker:addRankRewards(p,u, not isInstant) + DependencyManager.get("PlayerTracker"):addRankRewards(p,u, not isInstant) end mis:pushSoundToPlayers("success.ogg") @@ -12446,15 +12807,17 @@ do end function MissionTracker:activateMission(code, player, unit) - if not unit or not unit:isExist() or not Utils.isLanded(unit, true) then - if unit and unit:isExist() then trigger.action.outTextForUnit(unit:getID(), 'Can only accept mission while landed', 5) end - return false - end + if Config.restrictMissionAcceptance then + if not unit or not unit:isExist() or not Utils.isLanded(unit, true) then + if unit and unit:isExist() then trigger.action.outTextForUnit(unit:getID(), 'Can only accept mission while landed', 5) end + return false + end - local zn = ZoneCommand.getZoneOfUnit(unit:getName()) - if not zn or zn.side ~= unit:getCoalition() then - trigger.action.outTextForUnit(unit:getID(), 'Can only accept mission while inside friendly zone', 5) - return false + local zn = ZoneCommand.getZoneOfUnit(unit:getName()) + if not zn or zn.side ~= unit:getCoalition() then + trigger.action.outTextForUnit(unit:getID(), 'Can only accept mission while inside friendly zone', 5) + return false + end end for c,m in pairs(self.activeMissions) do @@ -12496,15 +12859,17 @@ do end function MissionTracker:joinMission(code, player, unit) - if not unit or not unit:isExist() or not Utils.isLanded(unit, true) then - if unit and unit:isExist() then trigger.action.outTextForUnit(unit:getID(), 'Can only join mission while landed', 5) end - return false - end + if Config.restrictMissionAcceptance then + if not unit or not unit:isExist() or not Utils.isLanded(unit, true) then + if unit and unit:isExist() then trigger.action.outTextForUnit(unit:getID(), 'Can only join mission while landed', 5) end + return false + end - local zn = ZoneCommand.getZoneOfUnit(unit:getName()) - if not zn or zn.side ~= unit:getCoalition() then - trigger.action.outTextForUnit(unit:getID(), 'Can only join mission while inside friendly zone', 5) - return false + local zn = ZoneCommand.getZoneOfUnit(unit:getName()) + if not zn or zn.side ~= unit:getCoalition() then + trigger.action.outTextForUnit(unit:getID(), 'Can only join mission while inside friendly zone', 5) + return false + end end for c,m in pairs(self.activeMissions) do @@ -12577,7 +12942,7 @@ do return CAS_Easy.canCreate() elseif misType == Mission.types.cas_medium then return CAS_Medium.canCreate() - elseif Config.disablePlayerSead == false and misType == Mission.types.sead then + elseif misType == Mission.types.sead then return SEAD.canCreate() elseif misType == Mission.types.dead then return DEAD.canCreate() @@ -12640,6 +13005,8 @@ do self.__index = self obj:start() + + DependencyManager.register("SquadTracker", obj) return obj end @@ -12886,6 +13253,8 @@ do self.__index = self obj:start() + + DependencyManager.register("CSARTracker", obj) return obj end @@ -13065,6 +13434,7 @@ do o:start() + DependencyManager.register("GCI", o) return o end @@ -13077,22 +13447,27 @@ do msg=msg.."nm" end + local wRadius = 0 if metric then - warningRadius = warningRadius * 1000 + wRadius = warningRadius * 1000 else - warningRadius = warningRadius * 1852 + wRadius = warningRadius * 1852 end self.players[name] = { unit = unit, - warningRadius = warningRadius, + warningRadius = wRadius, metric = metric } trigger.action.outTextForUnit(unit:getID(), msg, 10) + DependencyManager.get("PlayerTracker"):setPlayerConfig(name, "gci_warning_radius", warningRadius) + DependencyManager.get("PlayerTracker"):setPlayerConfig(name, "gci_metric", metric) else self.players[name] = nil trigger.action.outTextForUnit(unit:getID(), "GCI Reports disabled", 10) + DependencyManager.get("PlayerTracker"):setPlayerConfig(name, "gci_warning_radius", nil) + DependencyManager.get("PlayerTracker"):setPlayerConfig(name, "gci_metric", nil) end end From e816e6990a21ce5f4f1ec69724479f332acf55da Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Mon, 5 Feb 2024 20:00:43 +0200 Subject: [PATCH 202/243] Updated pretense_compiled.lua to version 1.6.5: Pretense v1.6.5 - 04 Feb 2024 Fixed error on restoring strike targets Block slots from within the mission, removing the need for slotblock.lua --- .../plugins/pretense/pretense_compiled.lua | 2292 ++++++++++++++--- 1 file changed, 1951 insertions(+), 341 deletions(-) diff --git a/resources/plugins/pretense/pretense_compiled.lua b/resources/plugins/pretense/pretense_compiled.lua index a6b58409..4a458bbd 100644 --- a/resources/plugins/pretense/pretense_compiled.lua +++ b/resources/plugins/pretense/pretense_compiled.lua @@ -57,7 +57,10 @@ Config.maxDistFromFront = Config.maxDistFromFront or 129640 -- max distance in m if Config.restrictMissionAcceptance == nil then Config.restrictMissionAcceptance = true end -- if set to true, missions can only be accepted while landed inside friendly zones Config.missions = Config.missions or {} +Config.missionBoardSize = Config.missionBoardSize or 15 +Config.carrierSpawnCost = Config.carrierSpawnCost or 500 -- resource cost for carrier when players take off, set to 0 to disable restriction +Config.zoneSpawnCost = Config.zoneSpawnCost or 500 -- resource cost for zones when players take off, set to 0 to disable restriction -----------------[[ END OF Config.lua ]]----------------- @@ -79,6 +82,14 @@ do return cnt end + function Utils.isInArray(value, array) + for _,v in ipairs(array) do + if value == v then + return true + end + end + end + Utils.cache = {} Utils.cache.groups = {} function Utils.getOriginalGroup(groupName) @@ -181,6 +192,11 @@ do return false end + + function Utils.isInCircle(point, center, radius) + local dist = mist.utils.get2DDist(point, center) + return dist 0 then + local count = 0 + for i,v in ipairs(targets) do + count = count + 1 + local oname = v.zone.name..'('..v.data.display..')' + if count<10 then + missionCommands.addCommandForGroup(groupid, oname, menu, executeAction, action, {target = v, menu=menu, groupid=groupid, data=data}) + elseif count==10 then + sub1 = missionCommands.addSubMenuForGroup(groupid, "More", menu) + missionCommands.addCommandForGroup(groupid, oname, sub1, executeAction, action, {target = v, menu=menu, groupid=groupid, data=data}) + elseif count%9==1 then + sub1 = missionCommands.addSubMenuForGroup(groupid, "More", sub1) + missionCommands.addCommandForGroup(groupid, oname, sub1, executeAction, action, {target = v, menu=menu, groupid=groupid, data=data}) + else + missionCommands.addCommandForGroup(groupid, oname, sub1, executeAction, action, {target = v, menu=menu, groupid=groupid, data=data}) + end + end + else + return false + end + + return menu + end + + function MenuRegistry.showTargetZoneMenu(groupid, name, action, targetside, minDistToFront, data, includeCarriers) + local executeAction = function(act, params) + local err = act(params) + if not err then + missionCommands.removeItemForGroup(params.groupid, params.menu) + end + end + + local menu = missionCommands.addSubMenuForGroup(groupid, name) + local sub1 = nil + local zones = ZoneCommand.getAllZones() + + if targetside and type(targetside) == 'number' then + targetside = { targetside } + end + local zns = {} for i,v in pairs(zones) do - if not targetside or v.side == targetside then + if not targetside or Utils.isInArray(v.side,targetside) then if not minDistToFront or v.distToFront <= minDistToFront then table.insert(zns, v) end end end + if includeCarriers then + for i,v in pairs(CarrierCommand.getAllCarriers()) do + if not targetside or Utils.isInArray(v.side,targetside) then + table.insert(zns, v) + end + end + end + table.sort(zns, function(a,b) return a.name < b.name end) local count = 0 for i,v in ipairs(zns) do count = count + 1 if count<10 then - missionCommands.addCommandForGroup(groupid, v.name, menu, executeAction, action, {zone = v, menu=menu, groupid=groupid}) + missionCommands.addCommandForGroup(groupid, v.name, menu, executeAction, action, {zone = v, menu=menu, groupid=groupid, data=data}) elseif count==10 then sub1 = missionCommands.addSubMenuForGroup(groupid, "More", menu) - missionCommands.addCommandForGroup(groupid, v.name, sub1, executeAction, action, {zone = v, menu=menu, groupid=groupid}) + missionCommands.addCommandForGroup(groupid, v.name, sub1, executeAction, action, {zone = v, menu=menu, groupid=groupid, data=data}) elseif count%9==1 then sub1 = missionCommands.addSubMenuForGroup(groupid, "More", sub1) - missionCommands.addCommandForGroup(groupid, v.name, sub1, executeAction, action, {zone = v, menu=menu, groupid=groupid}) + missionCommands.addCommandForGroup(groupid, v.name, sub1, executeAction, action, {zone = v, menu=menu, groupid=groupid, data=data}) else - missionCommands.addCommandForGroup(groupid, v.name, sub1, executeAction, action, {zone = v, menu=menu, groupid=groupid}) + missionCommands.addCommandForGroup(groupid, v.name, sub1, executeAction, action, {zone = v, menu=menu, groupid=groupid, data=data}) end end @@ -649,7 +723,7 @@ do if not group.state then group.state = 'started' - lastStateTime = timer.getAbsTime() + group.lastStateTime = timer.getAbsTime() env.info('GroupMonitor: processSurface ['..group.name..'] starting') end @@ -657,6 +731,9 @@ do if gr then local firstUnit = gr:getUnit(1):getName() local z = ZoneCommand.getZoneOfUnit(firstUnit) + if not z then + z = CarrierCommand.getCarrierOfUnit(firstUnit) + end if not z then env.info('GroupMonitor: processSurface ['..group.name..'] is enroute') @@ -675,6 +752,9 @@ do if gr then local firstUnit = gr:getUnit(1):getName() local z = ZoneCommand.getZoneOfUnit(firstUnit) + if not z then + z = CarrierCommand.getCarrierOfUnit(firstUnit) + end if z and (z.name==group.target.name or z.name==group.home.name) then MissionTargetRegistry.removeBaiTarget(group) @@ -724,9 +804,15 @@ do end elseif GroupMonitor.isStuck(group) then env.info('GroupMonitor: processSurface ['..group.name..'] is stuck, trying to get unstuck') - local supplyPoint = trigger.misc.getZone(group.target.name..'-sp') + + local tgtname = group.target.name + if group.returning then + tgtname = group.home.name + end + + local supplyPoint = trigger.misc.getZone(tgtname..'-sp') if not supplyPoint then - supplyPoint = trigger.misc.getZone(group.target.name) + supplyPoint = trigger.misc.getZone(tgtname) end TaskExtensions.moveOnRoadToPoint(gr, {x=supplyPoint.point.x, y=supplyPoint.point.z}, true) @@ -747,18 +833,20 @@ do }) timer.scheduleFunction(function(params, time) - local group = params.group + local group = params.gr + local tgtname = group.target.name + if group.returning then + tgtname = group.home.name + end local gr = Group.getByName(group.name) - local supplyPoint = trigger.misc.getZone(group.target.name..'-sp') + local supplyPoint = trigger.misc.getZone(tgtname..'-sp') if not supplyPoint then - supplyPoint = trigger.misc.getZone(group.target.name) + supplyPoint = trigger.misc.getZone(tgtname) end TaskExtensions.moveOnRoadToPoint(gr, {x=supplyPoint.point.x, y=supplyPoint.point.z}, true) end, {gr = group}, timer.getTime()+2) end - elseif group.unstuck_attempts and group.unstuck_attempts > 0 then - group.unstuck_attempts = 0 end elseif group.product.missionType == 'assault' then local frUnit = gr:getUnit(1) @@ -867,6 +955,9 @@ do if gr then local firstUnit = gr:getUnit(1):getName() local z = ZoneCommand.getZoneOfUnit(firstUnit) + if not z then + z = CarrierCommand.getCarrierOfUnit(firstUnit) + end if z and z.side == 0 then env.info('GroupMonitor: processSurface ['..group.name..'] is at neutral zone') z:capture(gr:getCoalition()) @@ -927,6 +1018,7 @@ do local un = gr:getUnit(1) if un and un:isExist() and mist.vec.mag(un:getVelocity()) >= 0.01 and group.stuck_marker > 0 then group.stuck_marker = 0 + group.unstuck_attempts = 0 env.info('GroupMonitor: isStuck ['..group.name..'] is moving, reseting stuck marker velocity='..mist.vec.mag(un:getVelocity())) end @@ -947,7 +1039,7 @@ do function GroupMonitor:processAir(group)-- states: [takeoff, inair, landed] local gr = Group.getByName(group.name) if not gr then return true end - if gr:getSize()==0 then + if not gr:isExist() or gr:getSize()==0 then gr:destroy() return true end @@ -968,20 +1060,27 @@ do if group.state =='takeoff' then if timer.getAbsTime() - group.lastStateTime > GroupMonitor.blockedDespawnTime then - if gr and Utils.allGroupIsLanded(gr) then - env.info('GroupMonitor: processAir ['..group.name..'] is blocked, despawning') + if gr and gr:getSize()>0 and gr:getUnit(1):isExist() then local frUnit = gr:getUnit(1) - if frUnit then - local firstUnit = frUnit:getName() - local z = ZoneCommand.getZoneOfUnit(firstUnit) - if z then - z:addResource(group.product.cost) - env.info('GroupMonitor: processAir ['..z.name..'] has recovered ['..group.product.cost..'] from ['..group.name..']') + local cz = CarrierCommand.getCarrierOfUnit(frUnit:getName()) + if Utils.allGroupIsLanded(gr, cz ~= nil) then + env.info('GroupMonitor: processAir ['..group.name..'] is blocked, despawning') + local frUnit = gr:getUnit(1) + if frUnit then + local firstUnit = frUnit:getName() + local z = ZoneCommand.getZoneOfUnit(firstUnit) + if not z then + z = CarrierCommand.getCarrierOfUnit(firstUnit) + end + if z then + z:addResource(group.product.cost) + env.info('GroupMonitor: processAir ['..z.name..'] has recovered ['..group.product.cost..'] from ['..group.name..']') + end end - end - gr:destroy() - return true + gr:destroy() + return true + end end elseif gr and Utils.someOfGroupInAir(gr) then env.info('GroupMonitor: processAir ['..group.name..'] is in the air') @@ -989,89 +1088,97 @@ do group.lastStateTime = timer.getAbsTime() end elseif group.state =='inair' then - if gr and Utils.allGroupIsLanded(gr) then - env.info('GroupMonitor: processAir ['..group.name..'] has landed') - group.state = 'landed' - group.lastStateTime = timer.getAbsTime() - + if gr then local unit = gr:getUnit(1) - if unit then - local firstUnit = unit:getName() - local z = ZoneCommand.getZoneOfUnit(firstUnit) - - if group.product.missionType == 'supply_air' then - if z then - z:capture(gr:getCoalition()) - z:addResource(group.product.cost) - env.info('GroupMonitor: processAir ['..group.name..'] has supplied ['..z.name..'] with ['..group.product.cost..']') + if not unit or not unit:isExist() then return end + + local cz = CarrierCommand.getCarrierOfUnit(unit:getName()) + if Utils.allGroupIsLanded(gr, cz ~= nil) then + env.info('GroupMonitor: processAir ['..group.name..'] has landed') + group.state = 'landed' + group.lastStateTime = timer.getAbsTime() + + if unit then + local firstUnit = unit:getName() + local z = ZoneCommand.getZoneOfUnit(firstUnit) + if not z then + z = CarrierCommand.getCarrierOfUnit(firstUnit) + end + + if group.product.missionType == 'supply_air' then + if z then + z:capture(gr:getCoalition()) + z:addResource(group.product.cost) + env.info('GroupMonitor: processAir ['..group.name..'] has supplied ['..z.name..'] with ['..group.product.cost..']') + end + else + if z and z.side == gr:getCoalition() then + local percentSurvived = gr:getSize()/gr:getInitialSize() + local torecover = math.floor(group.product.cost * percentSurvived * GroupMonitor.recoveryReduction) + z:addResource(torecover) + env.info('GroupMonitor: processAir ['..z.name..'] has recovered ['..torecover..'] from ['..group.name..']') + end end else - if z and z.side == gr:getCoalition() then - local percentSurvived = gr:getSize()/gr:getInitialSize() - local torecover = math.floor(group.product.cost * percentSurvived * GroupMonitor.recoveryReduction) - z:addResource(torecover) - env.info('GroupMonitor: processAir ['..z.name..'] has recovered ['..torecover..'] from ['..group.name..']') - end + env.info('GroupMonitor: processAir ['..group.name..'] size ['..gr:getSize()..'] has no unit 1') end else - env.info('GroupMonitor: processAir ['..group.name..'] size ['..gr:getSize()..'] has no unit 1') - end - elseif gr then - if GroupMonitor.isAirAttack(group.product.missionType) and not group.returning then - if not GroupMonitor.hasWeapons(gr) then - env.info('GroupMonitor: processAir ['..group.name..'] size ['..gr:getSize()..'] has no weapons outside of shells') - self:sendHome(group) - elseif group.product.missionType == ZoneCommand.missionTypes.cas_helo then - local frUnit = gr:getUnit(1) - local controller = frUnit:getController() - local targets = controller:getDetectedTargets() + if GroupMonitor.isAirAttack(group.product.missionType) and not group.returning then + if not GroupMonitor.hasWeapons(gr) then + env.info('GroupMonitor: processAir ['..group.name..'] size ['..gr:getSize()..'] has no weapons outside of shells') + self:sendHome(group) + elseif group.product.missionType == ZoneCommand.missionTypes.cas_helo then + local frUnit = gr:getUnit(1) + local controller = frUnit:getController() + local targets = controller:getDetectedTargets() - local tgtToEngage = {} - if #targets > 0 then - for _,tgt in ipairs(targets) do - if tgt.visible and tgt.object and tgt.object.isExist and tgt.object:isExist() then - if Object.getCategory(tgt.object) == Object.Category.UNIT and - tgt.object.getCoalition and tgt.object:getCoalition()~=frUnit:getCoalition() and - Unit.getCategoryEx(tgt.object) == Unit.Category.GROUND_UNIT then + local tgtToEngage = {} + if #targets > 0 then + for _,tgt in ipairs(targets) do + if tgt.visible and tgt.object and tgt.object.isExist and tgt.object:isExist() then + if Object.getCategory(tgt.object) == Object.Category.UNIT and + tgt.object.getCoalition and tgt.object:getCoalition()~=frUnit:getCoalition() and + Unit.getCategoryEx(tgt.object) == Unit.Category.GROUND_UNIT then - local dist = mist.utils.get3DDist(frUnit:getPoint(), tgt.object:getPoint()) - if dist < 2000 then - table.insert(tgtToEngage, tgt.object) + local dist = mist.utils.get3DDist(frUnit:getPoint(), tgt.object:getPoint()) + if dist < 2000 then + table.insert(tgtToEngage, tgt.object) + end end end end end - end - if not group.isengaging and #tgtToEngage > 0 then - env.info('GroupMonitor: processAir ['..group.name..'] engaging targets') - TaskExtensions.heloEngageTargets(gr, tgtToEngage, group.product.expend) - group.isengaging = true - group.startedEngaging = timer.getAbsTime() - elseif group.isengaging and #tgtToEngage == 0 and group.startedEngaging and (timer.getAbsTime() - group.startedEngaging) > 60*5 then - env.info('GroupMonitor: processAir ['..group.name..'] resuming mission') - if group.returning then - group.returning = nil - self:sendHome(group) - else - local homePos = group.home.zone.point - TaskExtensions.executeHeloCasMission(gr, group.target.built, group.product.expend, group.product.altitude, {homePos = homePos}) + if not group.isengaging and #tgtToEngage > 0 then + env.info('GroupMonitor: processAir ['..group.name..'] engaging targets') + TaskExtensions.heloEngageTargets(gr, tgtToEngage, group.product.expend) + group.isengaging = true + group.startedEngaging = timer.getAbsTime() + elseif group.isengaging and #tgtToEngage == 0 and group.startedEngaging and (timer.getAbsTime() - group.startedEngaging) > 60*5 then + env.info('GroupMonitor: processAir ['..group.name..'] resuming mission') + if group.returning then + group.returning = nil + self:sendHome(group) + else + local homePos = group.home.zone.point + TaskExtensions.executeHeloCasMission(gr, group.target.built, group.product.expend, group.product.altitude, {homePos = homePos}) + end + group.isengaging = false end - group.isengaging = false - end - end - elseif group.product.missionType == 'supply_air' then - if not group.returning and group.target and group.target.side ~= group.product.side and group.target.side ~= 0 then - local supplyPoint = trigger.misc.getZone(group.home.name..'-hsp') - if not supplyPoint then - supplyPoint = trigger.misc.getZone(group.home.name) end + elseif group.product.missionType == 'supply_air' then + if not group.returning and group.target and group.target.side ~= group.product.side and group.target.side ~= 0 then + local supplyPoint = trigger.misc.getZone(group.home.name..'-hsp') + if not supplyPoint then + supplyPoint = trigger.misc.getZone(group.home.name) + end - if supplyPoint then - group.returning = true - local alt = DependencyManager.get("ConnectionManager"):getHeliAlt(group.target.name, group.home.name) - TaskExtensions.landAtPointFromAir(gr, {x=supplyPoint.point.x, y=supplyPoint.point.z}, alt) - env.info('GroupMonitor: processAir ['..group.name..'] returning home') + if supplyPoint then + group.returning = true + local alt = DependencyManager.get("ConnectionManager"):getHeliAlt(group.target.name, group.home.name) + TaskExtensions.landAtPointFromAir(gr, {x=supplyPoint.point.x, y=supplyPoint.point.z}, alt) + env.info('GroupMonitor: processAir ['..group.name..'] returning home') + end end end end @@ -1271,7 +1378,15 @@ do end end - function TaskExtensions.getDefaultWaypoints(startPos, task, tgpos, reactivated) + function TaskExtensions.getTargetPos(targetName) + local tgt = StaticObject.getByName(targetName) + if not tgt then tgt = Unit.getByName(targetName) end + if tgt then + return tgt:getPoint() + end + end + + function TaskExtensions.getDefaultWaypoints(startPos, task, tgpos, reactivated, landUnitID) local defwp = { id='Mission', params = { @@ -1329,15 +1444,29 @@ do }) end - table.insert(defwp.params.route.points, { - type= AI.Task.WaypointType.LAND, - x = startPos.x, - y = startPos.z, - speed = 257, - action = AI.Task.TurnMethod.FIN_POINT, - alt = 0, - alt_type = AI.Task.AltitudeType.RADIO - }) + if landUnitID then + table.insert(defwp.params.route.points, { + type= AI.Task.WaypointType.LAND, + linkUnit = landUnitID, + helipadId = landUnitID, + x = startPos.x, + y = startPos.z, + speed = 257, + action = AI.Task.TurnMethod.FIN_POINT, + alt = 0, + alt_type = AI.Task.AltitudeType.RADIO + }) + else + table.insert(defwp.params.route.points, { + type= AI.Task.WaypointType.LAND, + x = startPos.x, + y = startPos.z, + speed = 257, + action = AI.Task.TurnMethod.FIN_POINT, + alt = 0, + alt_type = AI.Task.AltitudeType.RADIO + }) + end return defwp end @@ -1405,7 +1534,7 @@ do TaskExtensions.setDefaultAG(group) end - function TaskExtensions.executeStrikeMission(group,targets, expend, altitude, reactivated) + function TaskExtensions.executeStrikeMission(group, targets, expend, altitude, reactivated, landUnitID) if not group then return end if not group:isExist() or group:getSize()==0 then return end local startPos = group:getUnit(1):getPoint() @@ -1440,7 +1569,59 @@ do end end - local mis = TaskExtensions.getDefaultWaypoints(startPos, attack, nil, reactivated) + local mis = TaskExtensions.getDefaultWaypoints(startPos, attack, nil, reactivated, landUnitID) + + group:getController():setTask(mis) + TaskExtensions.setDefaultAG(group) + end + + function TaskExtensions.executePinpointStrikeMission(group, targetPos, expend, altitude, reactivated, landUnitID) + if not group then return end + if not group:isExist() or group:getSize()==0 then return end + local startPos = group:getUnit(1):getPoint() + + if reactivated then + reactivated.currentPos = startPos + startPos = reactivated.homePos + end + + local expCount = AI.Task.WeaponExpend.ALL + if expend then + expCount = expend + end + + local alt = 4572 + if altitude then + alt = altitude/3.281 + end + + local attack = { + id = 'Bombing', + params = { + point = { + x = targetPos.x, + y = targetPos.z + }, + attackQty = 1, + weaponType = Weapon.flag.AnyBomb, + expend = expCount, + groupAttack = true, + altitude = alt, + altitudeEnabled = (altitude ~= nil), + } + } + + local diff = { + x = targetPos.x - startPos.x, + z = targetPos.z - startPos.z + } + + local tp = { + x = targetPos.x - diff.x*0.5, + z = targetPos.z - diff.z*0.5 + } + + local mis = TaskExtensions.getDefaultWaypoints(startPos, attack, tp, reactivated, landUnitID) group:getController():setTask(mis) TaskExtensions.setDefaultAG(group) @@ -1679,7 +1860,7 @@ do TaskExtensions.setDefaultAG(group) end - function TaskExtensions.executeTankerMission(group, point, altitude, frequency, tacan, reactivated) + function TaskExtensions.executeTankerMission(group, point, altitude, frequency, tacan, reactivated, landUnitID) if not group then return end if not group:isExist() or group:getSize()==0 then return end local startPos = group:getUnit(1):getPoint() @@ -1713,7 +1894,11 @@ do system = 4, -- Tanker TACAN name = 'tacan task', callsign = group:getUnit(1):getCallsign():sub(1,3), - frequency = tacan + frequency = tacan, + AA = true, + channel = tacan, + bearing = true, + modeChannel = "X" } } @@ -1833,23 +2018,37 @@ do } }) - table.insert(task.params.route.points, { - type= AI.Task.WaypointType.LAND, - x = startPos.x, - y = startPos.z, - speed = 450, - action = AI.Task.TurnMethod.FIN_POINT, - alt = 0, - alt_type = AI.Task.AltitudeType.RADIO - }) - + if landUnitID then + table.insert(task.params.route.points, { + type= AI.Task.WaypointType.LAND, + linkUnit = landUnitID, + helipadId = landUnitID, + x = startPos.x, + y = startPos.z, + speed = 450, + action = AI.Task.TurnMethod.FIN_POINT, + alt = 0, + alt_type = AI.Task.AltitudeType.RADIO + }) + else + table.insert(task.params.route.points, { + type= AI.Task.WaypointType.LAND, + x = startPos.x, + y = startPos.z, + speed = 450, + action = AI.Task.TurnMethod.FIN_POINT, + alt = 0, + alt_type = AI.Task.AltitudeType.RADIO + }) + end + group:getController():setTask(task) group:getController():setOption(AI.Option.Air.id.REACTION_ON_THREAT, AI.Option.Air.val.REACTION_ON_THREAT.PASSIVE_DEFENCE) group:getController():setCommand(setfreq) group:getController():setCommand(setbeacon) end - function TaskExtensions.executeAwacsMission(group, point, altitude, frequency, reactivated) + function TaskExtensions.executeAwacsMission(group, point, altitude, frequency, reactivated, landUnitID) if not group then return end if not group:isExist() or group:getSize()==0 then return end local startPos = group:getUnit(1):getPoint() @@ -2013,22 +2212,36 @@ do } }) - table.insert(task.params.route.points, { - type= AI.Task.WaypointType.LAND, - x = startPos.x, - y = startPos.z, - speed = 257, - action = AI.Task.TurnMethod.FIN_POINT, - alt = 0, - alt_type = AI.Task.AltitudeType.RADIO - }) + if landUnitID then + table.insert(task.params.route.points, { + type= AI.Task.WaypointType.LAND, + linkUnit = landUnitID, + helipadId = landUnitID, + x = startPos.x, + y = startPos.z, + speed = 257, + action = AI.Task.TurnMethod.FIN_POINT, + alt = 0, + alt_type = AI.Task.AltitudeType.RADIO + }) + else + table.insert(task.params.route.points, { + type= AI.Task.WaypointType.LAND, + x = startPos.x, + y = startPos.z, + speed = 257, + action = AI.Task.TurnMethod.FIN_POINT, + alt = 0, + alt_type = AI.Task.AltitudeType.RADIO + }) + end group:getController():setTask(task) group:getController():setOption(AI.Option.Air.id.REACTION_ON_THREAT, AI.Option.Air.val.REACTION_ON_THREAT.PASSIVE_DEFENCE) group:getController():setCommand(setfreq) end - function TaskExtensions.executePatrolMission(group, point, altitude, range, reactivated) + function TaskExtensions.executePatrolMission(group, point, altitude, range, reactivated, landUnitID) if not group then return end if not group:isExist() or group:getSize()==0 then return end local startPos = group:getUnit(1):getPoint() @@ -2138,15 +2351,29 @@ do task = orbit }) - table.insert(task.params.route.points, { - type= AI.Task.WaypointType.LAND, - x = startPos.x, - y = startPos.z, - speed = 257, - action = AI.Task.TurnMethod.FIN_POINT, - alt = 0, - alt_type = AI.Task.AltitudeType.RADIO - }) + if landUnitID then + table.insert(task.params.route.points, { + type= AI.Task.WaypointType.LAND, + linkUnit = landUnitID, + helipadId = landUnitID, + x = startPos.x, + y = startPos.z, + speed = 257, + action = AI.Task.TurnMethod.FIN_POINT, + alt = 0, + alt_type = AI.Task.AltitudeType.RADIO + }) + else + table.insert(task.params.route.points, { + type= AI.Task.WaypointType.LAND, + x = startPos.x, + y = startPos.z, + speed = 257, + action = AI.Task.TurnMethod.FIN_POINT, + alt = 0, + alt_type = AI.Task.AltitudeType.RADIO + }) + end group:getController():setTask(task) TaskExtensions.setDefaultAA(group) @@ -2435,7 +2662,7 @@ do group:getController():setTask(mis) end - function TaskExtensions.landAtPoint(group, point, alt) -- point = {x,y} + function TaskExtensions.landAtPoint(group, point, alt, skiptakeoff) -- point = {x,y} if not group or not point then return end if not group:isExist() or group:getSize()==0 then return end local startPos = group:getUnit(1):getPoint() @@ -2459,30 +2686,33 @@ do params = { route = { airborne = true, - points = { - [1] = { - type = AI.Task.WaypointType.TAKEOFF, - x = startPos.x, - y = startPos.z, - speed = 0, - action = AI.Task.TurnMethod.FIN_POINT, - alt = alt, - alt_type = atype - }, - [2] = { - type = AI.Task.WaypointType.TURNING_POINT, - x = point.x, - y = point.y, - speed = 257, - action = AI.Task.TurnMethod.FIN_POINT, - alt = alt, - alt_type = atype, - task = land - } - } + points = {} } } } + + if not skiptakeoff then + table.insert(mis.params.route.points,{ + type = AI.Task.WaypointType.TAKEOFF, + x = startPos.x, + y = startPos.z, + speed = 0, + action = AI.Task.TurnMethod.FIN_POINT, + alt = alt, + alt_type = atype + }) + end + + table.insert(mis.params.route.points,{ + type = AI.Task.WaypointType.TURNING_POINT, + x = point.x, + y = point.y, + speed = 257, + action = AI.Task.TurnMethod.FIN_POINT, + alt = alt, + alt_type = atype, + task = land + }) group:getController():setTask(mis) end @@ -2522,6 +2752,127 @@ do group:getController():setTask(mis) end + + function TaskExtensions.carrierGoToPos(group, point) + if not group or not point then return end + if not group:isExist() or group:getSize()==0 then return end + + local mis = { + id='Mission', + params = { + route = { + airborne = true, + points = { + [1] = { + type= AI.Task.WaypointType.TURNING_POINT, + x = point.x, + y = point.z, + speed = 50, + action = AI.Task.TurnMethod.FIN_POINT + } + } + } + } + } + + group:getController():setTask(mis) + end + + function TaskExtensions.stopCarrier(group) + if not group then return end + if not group:isExist() or group:getSize()==0 then return end + local point = group:getUnit(1):getPoint() + + group:getController():setTask({ + id='Mission', + params = { + route = { + airborne = false, + points = { + [1] = { + type= AI.Task.WaypointType.TURNING_POINT, + x = point.x, + y = point.z, + speed = 0, + action = AI.Task.TurnMethod.FIN_POINT + } + } + } + } + }) + end + + function TaskExtensions.setupCarrier(unit, icls, acls, tacan, link4, radio) + if not unit then return end + if not unit:isExist() then return end + + local commands = {} + if icls then + table.insert(commands, { + id = 'ActivateICLS', + params = { + type = 131584, + channel = icls, + unitId = unit:getID(), + name = "ICLS "..icls, + } + }) + end + + if acls then + table.insert(commands, { + id = 'ActivateACLS', + params = { + unitId = unit:getID(), + name = "ACLS", + } + }) + end + + if tacan then + table.insert(commands, { + id = 'ActivateBeacon', + params = { + type = 4, + system = 4, + name = "TACAN "..tacan.channel, + callsign = tacan.callsign, + frequency = tacan.channel, + channel = tacan.channel, + bearing = true, + modeChannel = "X" + } + }) + end + + if link4 then + table.insert(commands, { + id = 'ActivateLink4', + params = { + unitId = unit:getID(), + frequency = link4, + name = "Link4 "..link4, + } + }) + end + + if radio then + table.insert(commands, { + id = "SetFrequency", + params = { + power = 100, + modulation = 0, + frequency = radio, + } + }) + end + + for i,v in ipairs(commands) do + unit:getController():setCommand(v) + end + + unit:getGroup():getController():setOption(AI.Option.Ground.id.AC_ENGAGEMENT_RANGE_RESTRICTION, 30) + end end -----------------[[ END OF TaskExtensions.lua ]]----------------- @@ -2951,7 +3302,7 @@ do env.info('PlayerLogistics - Hercules - '..cargo.squad.type..' dropped on invalid surface '..tostring(surface)) local cpos = cargo.object:getPoint() env.info('PlayerLogistics - Hercules - cargo spot X:'..cpos.x..' Y:'..cpos.y..' Z:'..cpos.z) - env.info('PlayerLogistics - Hercules - surface spot X:'..pos.x..' Y:'..pos.y..' Z:'..pos.z) + env.info('PlayerLogistics - Hercules - surface spot X:'..pos.x..' Y:'..pos.y) local squadName = PlayerLogistics.getInfantryName(cargo.squad.type) trigger.action.outTextForUnit(cargo.unit:getID(), 'Cargo drop of '..cargo.unit:getPlayerName()..' with '..squadName..' crashed', 10) end @@ -3000,7 +3351,7 @@ do end function PlayerLogistics:awardSupplyXP(lastLoad, zone, unit, amount) - if lastLoad and zone.name~=lastLoad.name then + if lastLoad and zone.name~=lastLoad.name and not zone.isCarrier and not lastLoad.isCarrier then if unit and unit.isExist and unit:isExist() and unit.getPlayerName then local player = unit:getPlayerName() local xp = amount*RewardDefinitions.actions.supplyRatio @@ -3246,6 +3597,10 @@ do end local zn = ZoneCommand.getZoneOfUnit(un:getName()) + if not zn then + zn = CarrierCommand.getCarrierOfUnit(un:getName()) + end + if not zn then trigger.action.outTextForUnit(un:getID(), 'Can only unload extracted pilots while within a friendly zone', 10) return @@ -3407,6 +3762,10 @@ do end local zn = ZoneCommand.getZoneOfUnit(un:getName()) + if not zn then + zn = CarrierCommand.getCarrierOfUnit(un:getName()) + end + if not zn then trigger.action.outTextForUnit(un:getID(), 'Can only load infantry while within a friendly zone', 10) return @@ -3505,6 +3864,9 @@ do end local zn = ZoneCommand.getZoneOfUnit(un:getName()) + if not zn then + zn = CarrierCommand.getCarrierOfUnit(un:getName()) + end for _, sq in ipairs(toUnload) do local squadName = PlayerLogistics.getInfantryName(sq.type) @@ -3711,6 +4073,10 @@ do end local zn = ZoneCommand.getZoneOfUnit(un:getName()) + if not zn then + zn = CarrierCommand.getCarrierOfUnit(un:getName()) + end + if not zn then trigger.action.outTextForUnit(un:getID(), 'Can only load supplies while within a friendly zone', 10) return @@ -3791,6 +4157,10 @@ do end local zn = ZoneCommand.getZoneOfUnit(un:getName()) + if not zn then + zn = CarrierCommand.getCarrierOfUnit(un:getName()) + end + if not zn then trigger.action.outTextForUnit(un:getID(), 'Can only unload supplies while within a friendly zone', 10) return @@ -4054,7 +4424,10 @@ do function ZoneCommand:refreshSpawnBlocking() for _,v in ipairs(self.spawns) do - trigger.action.setUserFlag(v.name, v.side ~= self.side) + local isDifferentSide = v.side ~= self.side + local noResources = self.resource < Config.zoneSpawnCost + + trigger.action.setUserFlag(v.name, isDifferentSide or noResources) end end @@ -4278,11 +4651,13 @@ do function ZoneCommand:addResource(amount) self.resource = self.resource+amount self.resource = math.floor(math.min(self.resource, self.maxResource)) + self:refreshSpawnBlocking() end function ZoneCommand:removeResource(amount) self.resource = self.resource-amount self.resource = math.floor(math.max(self.resource, 0)) + self:refreshSpawnBlocking() end function ZoneCommand:reveal() @@ -6416,7 +6791,8 @@ do PlayerTracker.savefile = 'player_stats.json' PlayerTracker.statTypes = { xp = 'XP', - cmd = "CMD" + cmd = "CMD", + survivalBonus = "SB" } PlayerTracker.cmdShopTypes = { @@ -6425,14 +6801,18 @@ do jtac = 'jtac', bribe1 = 'bribe1', bribe2 = 'bribe2', + artillery = 'artillery', + sabotage1 = 'sabotage1', } PlayerTracker.cmdShopPrices = { [PlayerTracker.cmdShopTypes.smoke] = 1, - [PlayerTracker.cmdShopTypes.prio] = 2, - [PlayerTracker.cmdShopTypes.jtac] = 3, - [PlayerTracker.cmdShopTypes.bribe1] = 1, - [PlayerTracker.cmdShopTypes.bribe2] = 3, + [PlayerTracker.cmdShopTypes.prio] = 10, + [PlayerTracker.cmdShopTypes.jtac] = 5, + [PlayerTracker.cmdShopTypes.bribe1] = 5, + [PlayerTracker.cmdShopTypes.bribe2] = 10, + [PlayerTracker.cmdShopTypes.artillery] = 15, + [PlayerTracker.cmdShopTypes.sabotage1] = 20, } function PlayerTracker:new() @@ -6444,7 +6824,6 @@ do obj.groupShopMenus = {} obj.groupConfigMenus = {} obj.groupTgtMenus = {} - obj.playerAircraft = {} obj.playerEarningMultiplier = {} if lfs then @@ -6480,7 +6859,8 @@ do local player = event.initiator:getPlayerName() if not player then return end - if event.id==world.event.S_EVENT_PLAYER_ENTER_UNIT then + local blocked = false + if event.id==world.event.S_EVENT_BIRTH then if event.initiator and Object.getCategory(event.initiator) == Object.Category.UNIT and (Unit.getCategoryEx(event.initiator) == Unit.Category.AIRPLANE or Unit.getCategoryEx(event.initiator) == Unit.Category.HELICOPTER) then @@ -6488,14 +6868,23 @@ do if pname then local gr = event.initiator:getGroup() if trigger.misc.getUserFlag(gr:getName())==1 then - trigger.action.outTextForGroup(gr:getID(), 'Can not spawn as '..gr:getName()..' in enemy/neutral zone',5) + blocked = true + trigger.action.outTextForGroup(gr:getID(), 'Can not spawn as '..gr:getName()..' in enemy/neutral zone or zone without enough resources',5) event.initiator:destroy() + + for i,v in pairs(net.get_player_list()) do + if net.get_name(v) == pname then + net.send_chat_to('Can not spawn as '..gr:getName()..' in enemy/neutral zone or zone without enough resources' , v) + net.force_player_slot(v, 0, '') + break + end + end end end end end - if event.id == world.event.S_EVENT_BIRTH then + if event.id == world.event.S_EVENT_BIRTH and not blocked then -- init stats for player if not exist if not self.context.stats[player] then self.context.stats[player] = {} @@ -6503,10 +6892,15 @@ do -- reset temp track for player self.context.tempStats[player] = nil - -- reset playeraircraft - self.context.playerAircraft[player] = nil - self.context.playerEarningMultiplier[player] = { spawnTime = timer.getAbsTime(), unit = event.initiator, multiplier = 1.0, minutes = 0 } + local minutes = 0 + local multiplier = 1.0 + if self.context.stats[player][PlayerTracker.statTypes.survivalBonus] ~= nil then + minutes = self.context.stats[player][PlayerTracker.statTypes.survivalBonus] + multiplier = PlayerTracker.minutesToMultiplier(minutes) + end + + self.context.playerEarningMultiplier[player] = { spawnTime = timer.getAbsTime(), unit = event.initiator, multiplier = multiplier, minutes = minutes } local config = self.context:getPlayerConfig(player) if config.gci_warning_radius then @@ -6562,21 +6956,35 @@ do if event.id==world.event.S_EVENT_TAKEOFF then local un = event.initiator - local zn = ZoneCommand.getZoneOfUnit(event.initiator:getName()) env.info('PlayerTracker - '..player..' took off in '..tostring(un:getID())..' '..un:getName()) - if un and zn and zn.side == un:getCoalition() then - timer.scheduleFunction(function(param, time) - local un = param.unit - if not un or not un:isExist() then return end - local player = param.player - local inAir = Utils.isInAir(un) - env.info('PlayerTracker - '..player..' checking if in air: '..tostring(inAir)) - if inAir and param.context.playerAircraft[player] == nil then - if param.context.playerAircraft[player] == nil then - param.context.playerAircraft[player] = { unitID = un:getID() } - end - end - end, {player = player, unit = event.initiator, context = self.context}, timer.getTime()+10) + if self.context.stats[player][PlayerTracker.statTypes.survivalBonus] ~= nil then + self.context.stats[player][PlayerTracker.statTypes.survivalBonus] = nil + trigger.action.outTextForUnit(un:getID(), 'Taken off, survival bonus no longer secure.', 10) + end + + local zn = CarrierCommand.getCarrierOfUnit(un:getName()) + if zn then + zn:removeResource(Config.carrierSpawnCost) + else + zn = ZoneCommand.getZoneOfUnit(un:getName()) + if zn then + zn:removeResource(Config.zoneSpawnCost) + end + end + end + + if event.id==world.event.S_EVENT_ENGINE_SHUTDOWN then + local un = event.initiator + local zn = ZoneCommand.getZoneOfUnit(un:getName()) + if not zn then + zn = CarrierCommand.getCarrierOfUnit(un:getName()) + end + if un and un:isExist() and zn and zn.side == un:getCoalition() then + env.info('PlayerTracker - '..player..' has shut down engine of '..tostring(un:getID())..' '..un:getName()..' at '..zn.name) + self.context.stats[player][PlayerTracker.statTypes.survivalBonus] = self.context:getPlayerMinutes(player) + self.context:save() + trigger.action.outTextForUnit(un:getID(), 'Engines shut down. Survival bonus secured.', 10) + env.info('PlayerTracker - '..player..' secured survival bonus of '..self.context.stats[player][PlayerTracker.statTypes.survivalBonus]..' minutes') end end @@ -6592,19 +7000,10 @@ do timer.scheduleFunction(function(params, time) local players = params.context.playerEarningMultiplier for i,v in pairs(players) do - local aircraft = params.context.playerAircraft[i] - if aircraft and v.unit.isExist and v.unit:isExist() and aircraft.unitID == v.unit:getID() then + if v.unit.isExist and v.unit:isExist() then if v.multiplier < 5.0 and v.unit and v.unit:isExist() and Utils.isInAir(v.unit) then v.minutes = v.minutes + 1 - - local multi = 1.0 - if v.minutes > 10 and v.minutes <= 60 then - multi = 1.0 + ((v.minutes-10)*0.05) - elseif v.minutes > 60 then - multi = 1.0 + (50*0.05) + ((v.minutes - 60)*0.025) - end - - v.multiplier = math.min(multi, 5.0) + v.multiplier = PlayerTracker.minutesToMultiplier(v.minutes) end end end @@ -6616,9 +7015,12 @@ do function PlayerTracker:validateLanding(unit, player) local un = unit local zn = ZoneCommand.getZoneOfUnit(unit:getName()) - local aircraft = self.playerAircraft[player] + if not zn then + zn = CarrierCommand.getCarrierOfUnit(unit:getName()) + end + env.info('PlayerTracker - '..player..' landed in '..tostring(un:getID())..' '..un:getName()) - if aircraft and un and zn and zn.side == un:getCoalition() then + if un and zn and zn.side == un:getCoalition() then trigger.action.outTextForUnit(unit:getID(), "Wait 10 seconds to validate landing...", 10) timer.scheduleFunction(function(param, time) local un = param.unit @@ -6627,10 +7029,19 @@ do local player = param.player local isLanded = Utils.isLanded(un, true) local zn = ZoneCommand.getZoneOfUnit(un:getName()) + if not zn then + zn = CarrierCommand.getCarrierOfUnit(un:getName()) + end env.info('PlayerTracker - '..player..' checking if landed: '..tostring(isLanded)) if isLanded then + if zn.isCarrier then + zn:addResource(Config.carrierSpawnCost) + else + zn:addResource(Config.zoneSpawnCost) + end + if param.context.tempStats[player] then if zn and zn.side == un:getCoalition() then param.context.stats[player] = param.context.stats[player] or {} @@ -6644,12 +7055,9 @@ do trigger.action.outTextForUnit(un:getID(), key..' +'..value..'', 5) end end - end - end - local aircraft = param.context.playerAircraft[player] - if aircraft and aircraft.unitID == un:getID() then - param.context.playerAircraft[player] = nil + param.context:save() + end end end end, {player = player, unit = unit, context = self}, timer.getTime()+10) @@ -6699,23 +7107,31 @@ do local cmdChance = rank.cmdChance if cmdChance > 0 then - local die = math.random() - if die <= cmdChance then + + local tkns = 0 + for i=1,rank.cmdTrys,1 do + local die = math.random() + if die <= cmdChance then + tkns = tkns + 1 + end + end + + if tkns > 0 then if isTemp then - self:addTempStat(player, 1, PlayerTracker.statTypes.cmd) + self:addTempStat(player, tkns, PlayerTracker.statTypes.cmd) else - self:addStat(player, 1, PlayerTracker.statTypes.cmd) + self:addStat(player, tkns, PlayerTracker.statTypes.cmd) end local msg = "" if isTemp then - msg = '+1 CMD (unclaimed)' + msg = '+'..tkns..' CMD (unclaimed)' else - msg = '[CMD] '..self.stats[player][PlayerTracker.statTypes.cmd]..' (+1)' + msg = '[CMD] '..self.stats[player][PlayerTracker.statTypes.cmd]..' (+'..tkns..')' end trigger.action.outTextForUnit(unit:getID(), msg, 5) - env.info("PlayerTracker.addRankRewards - Awarded "..player.." a CMD token with chance "..cmdChance.." die roll "..die) + env.info("PlayerTracker.addRankRewards - Awarded "..player.." "..tkns.." CMD tokens with chance "..cmdChance) end end end @@ -6773,56 +7189,56 @@ do end end, self) - MenuRegistry:register(4, function(event, context) + MenuRegistry:register(5, function(event, context) if event.id == world.event.S_EVENT_BIRTH and event.initiator and event.initiator.getPlayerName then local player = event.initiator:getPlayerName() if player then local rank = context:getPlayerRank(player) if not rank then return end - if rank.cmdChance > 0 then - local groupid = event.initiator:getGroup():getID() - local groupname = event.initiator:getGroup():getName() + local groupid = event.initiator:getGroup():getID() + local groupname = event.initiator:getGroup():getName() + + if context.groupConfigMenus[groupid] then + missionCommands.removeItemForGroup(groupid, context.groupConfigMenus[groupid]) + context.groupConfigMenus[groupid] = nil + end + + if not context.groupConfigMenus[groupid] then - if context.groupShopMenus[groupid] then - missionCommands.removeItemForGroup(groupid, context.groupShopMenus[groupid]) - context.groupShopMenus[groupid] = nil - end + local menu = missionCommands.addSubMenuForGroup(groupid, 'Config') + local missionWarningMenu = missionCommands.addSubMenuForGroup(groupid, 'No mission warning', menu) + missionCommands.addCommandForGroup(groupid, 'Activate', missionWarningMenu, Utils.log(context.setNoMissionWarning), context, groupname, true) + missionCommands.addCommandForGroup(groupid, 'Deactivate', missionWarningMenu, Utils.log(context.setNoMissionWarning), context, groupname, false) + + context.groupConfigMenus[groupid] = menu + end + + if context.groupShopMenus[groupid] then + missionCommands.removeItemForGroup(groupid, context.groupShopMenus[groupid]) + context.groupShopMenus[groupid] = nil + end + + if context.groupTgtMenus[groupid] then + missionCommands.removeItemForGroup(groupid, context.groupTgtMenus[groupid]) + context.groupTgtMenus[groupid] = nil + end + + if not context.groupShopMenus[groupid] then - if context.groupTgtMenus[groupid] then - missionCommands.removeItemForGroup(groupid, context.groupTgtMenus[groupid]) - context.groupTgtMenus[groupid] = nil + local menu = missionCommands.addSubMenuForGroup(groupid, 'Command & Control') + missionCommands.addCommandForGroup(groupid, 'Deploy Smoke ['..PlayerTracker.cmdShopPrices[PlayerTracker.cmdShopTypes.smoke]..' CMD]', menu, Utils.log(context.buyCommand), context, groupname, PlayerTracker.cmdShopTypes.smoke) + missionCommands.addCommandForGroup(groupid, 'Hack enemy comms ['..PlayerTracker.cmdShopPrices[PlayerTracker.cmdShopTypes.bribe1]..' CMD]', menu, Utils.log(context.buyCommand), context, groupname, PlayerTracker.cmdShopTypes.bribe1) + missionCommands.addCommandForGroup(groupid, 'Prioritize zone ['..PlayerTracker.cmdShopPrices[PlayerTracker.cmdShopTypes.prio]..' CMD]', menu, Utils.log(context.buyCommand), context, groupname, PlayerTracker.cmdShopTypes.prio) + missionCommands.addCommandForGroup(groupid, 'Bribe enemy officer ['..PlayerTracker.cmdShopPrices[PlayerTracker.cmdShopTypes.bribe2]..' CMD]', menu, Utils.log(context.buyCommand), context, groupname, PlayerTracker.cmdShopTypes.bribe2) + missionCommands.addCommandForGroup(groupid, 'Shell zone with artillery ['..PlayerTracker.cmdShopPrices[PlayerTracker.cmdShopTypes.artillery]..' CMD]', menu, Utils.log(context.buyCommand), context, groupname, PlayerTracker.cmdShopTypes.artillery) + missionCommands.addCommandForGroup(groupid, 'Sabotage enemy zone ['..PlayerTracker.cmdShopPrices[PlayerTracker.cmdShopTypes.sabotage1]..' CMD]', menu, Utils.log(context.buyCommand), context, groupname, PlayerTracker.cmdShopTypes.sabotage1) + + if CommandFunctions.jtac then + missionCommands.addCommandForGroup(groupid, 'Deploy JTAC ['..PlayerTracker.cmdShopPrices[PlayerTracker.cmdShopTypes.jtac]..' CMD]', menu, Utils.log(context.buyCommand), context, groupname, PlayerTracker.cmdShopTypes.jtac) end - if not context.groupShopMenus[groupid] then - - local menu = missionCommands.addSubMenuForGroup(groupid, 'Command & Control') - missionCommands.addCommandForGroup(groupid, 'Deploy Smoke ['..PlayerTracker.cmdShopPrices[PlayerTracker.cmdShopTypes.smoke]..' CMD]', menu, Utils.log(context.buyCommand), context, groupname, PlayerTracker.cmdShopTypes.smoke) - missionCommands.addCommandForGroup(groupid, 'Hack enemy comms ['..PlayerTracker.cmdShopPrices[PlayerTracker.cmdShopTypes.bribe1]..' CMD]', menu, Utils.log(context.buyCommand), context, groupname, PlayerTracker.cmdShopTypes.bribe1) - missionCommands.addCommandForGroup(groupid, 'Prioritize zone ['..PlayerTracker.cmdShopPrices[PlayerTracker.cmdShopTypes.prio]..' CMD]', menu, Utils.log(context.buyCommand), context, groupname, PlayerTracker.cmdShopTypes.prio) - missionCommands.addCommandForGroup(groupid, 'Bribe enemy officer ['..PlayerTracker.cmdShopPrices[PlayerTracker.cmdShopTypes.bribe2]..' CMD]', menu, Utils.log(context.buyCommand), context, groupname, PlayerTracker.cmdShopTypes.bribe2) - - if CommandFunctions.jtac then - missionCommands.addCommandForGroup(groupid, 'Deploy JTAC ['..PlayerTracker.cmdShopPrices[PlayerTracker.cmdShopTypes.jtac]..' CMD]', menu, Utils.log(context.buyCommand), context, groupname, PlayerTracker.cmdShopTypes.jtac) - end - - context.groupShopMenus[groupid] = menu - end - - if context.groupConfigMenus[groupid] then - missionCommands.removeItemForGroup(groupid, context.groupConfigMenus[groupid]) - context.groupConfigMenus[groupid] = nil - end - - if not context.groupConfigMenus[groupid] then - - local menu = missionCommands.addSubMenuForGroup(groupid, 'Config') - local missionWarningMenu = missionCommands.addSubMenuForGroup(groupid, 'No mission warning', menu) - missionCommands.addCommandForGroup(groupid, 'Activate', missionWarningMenu, Utils.log(context.setNoMissionWarning), context, groupname, true) - missionCommands.addCommandForGroup(groupid, 'Deactivate', missionWarningMenu, Utils.log(context.setNoMissionWarning), context, groupname, false) - - context.groupConfigMenus[groupid] = menu - end + context.groupShopMenus[groupid] = menu end end elseif (event.id == world.event.S_EVENT_PLAYER_LEAVE_UNIT or event.id == world.event.S_EVENT_DEAD) and event.initiator and event.initiator.getPlayerName then @@ -6921,8 +7337,8 @@ do elseif itemType== PlayerTracker.cmdShopTypes.prio then self.groupTgtMenus[gr:getID()] = MenuRegistry.showTargetZoneMenu(gr:getID(), "Priority zone", function(params) - BattlefieldManager.overridePriority(2, params.zone, 2) - trigger.action.outTextForGroup(params.groupid, "Blue is concentrating efforts on "..params.zone.name.." for the next hour", 5) + BattlefieldManager.overridePriority(2, params.zone, 4) + trigger.action.outTextForGroup(params.groupid, "Blue is concentrating efforts on "..params.zone.name.." for the next two hours", 5) end, nil, 1) trigger.action.outTextForGroup(gr:getID(), "Select target from radio menu",10) @@ -6967,6 +7383,18 @@ do end, {groupid=gr:getID()}, timer.getTime()+(60*5)) trigger.action.outTextForGroup(gr:getID(), "Bribe has been transfered to enemy officer. Waiting for contact...",20) + elseif itemType == PlayerTracker.cmdShopTypes.artillery then + self.groupTgtMenus[gr:getID()] = MenuRegistry.showTargetZoneMenu(gr:getID(), "Artillery target", function(params) + CommandFunctions.shellZone(params.zone, 50) + end, 1, 1) + + trigger.action.outTextForGroup(gr:getID(), "Select target zone from radio menu",10) + elseif itemType == PlayerTracker.cmdShopTypes.sabotage1 then + self.groupTgtMenus[gr:getID()] = MenuRegistry.showTargetZoneMenu(gr:getID(), "Sabotage target", function(params) + CommandFunctions.sabotageZone(params.zone) + end, 1, 1) + + trigger.action.outTextForGroup(gr:getID(), "Select target zone from radio menu",10) end self.stats[player][PlayerTracker.statTypes.cmd] = self.stats[player][PlayerTracker.statTypes.cmd] - cost @@ -7028,6 +7456,10 @@ do local multiplier = self:getPlayerMultiplier(player) if multiplier then message = message..'\nSurvival XP multiplier: '..string.format("%.2f", multiplier)..'x' + + if stats[PlayerTracker.statTypes.survivalBonus] ~= nil then + message = message..' [SECURED]' + end end local cmd = stats[PlayerTracker.statTypes.cmd] @@ -7043,6 +7475,11 @@ do if tempxp and tempxp > 0 then message = message .. '\nUnclaimed XP: '..tempxp end + + local tempcmd = tstats[PlayerTracker.statTypes.cmd] + if tempcmd and tempcmd > 0 then + message = message .. '\nUnclaimed CMD: '..tempcmd + end end trigger.action.outTextForUnit(v:getID(), message, 10) @@ -7070,60 +7507,61 @@ do function PlayerTracker:periodicSave() timer.scheduleFunction(function(param, time) - local tosave = {} - tosave.stats = param.stats - tosave.config = param.config - - --temp mission stat tracking - tosave.zones = {} - tosave.zones.red = {} - tosave.zones.blue = {} - tosave.zones.neutral = {} - for i,v in pairs(ZoneCommand.getAllZones()) do - if v.side == 1 then - table.insert(tosave.zones.red,v.name) - elseif v.side == 2 then - table.insert(tosave.zones.blue,v.name) - elseif v.side == 0 then - table.insert(tosave.zones.neutral,v.name) - end - end - - tosave.players = {} - for i,v in ipairs(coalition.getPlayers(2)) do - if v and v:isExist() and v.getPlayerName then - table.insert(tosave.players, {name=v:getPlayerName(), unit=v:getDesc().typeName}) - end - end - - --end mission stat tracking - - Utils.saveTable(PlayerTracker.savefile, tosave) - env.info("PlayerTracker - state saved") + param:save() return time+60 end, self, timer.getTime()+60) end + function PlayerTracker:save() + local tosave = {} + tosave.stats = self.stats + tosave.config = self.config + + tosave.zones = {} + tosave.zones.red = {} + tosave.zones.blue = {} + tosave.zones.neutral = {} + for i,v in pairs(ZoneCommand.getAllZones()) do + if v.side == 1 then + table.insert(tosave.zones.red,v.name) + elseif v.side == 2 then + table.insert(tosave.zones.blue,v.name) + elseif v.side == 0 then + table.insert(tosave.zones.neutral,v.name) + end + end + + tosave.players = {} + for i,v in ipairs(coalition.getPlayers(2)) do + if v and v:isExist() and v.getPlayerName then + table.insert(tosave.players, {name=v:getPlayerName(), unit=v:getDesc().typeName}) + end + end + + Utils.saveTable(PlayerTracker.savefile, tosave) + env.info("PlayerTracker - state saved") + end + PlayerTracker.ranks = {} - PlayerTracker.ranks[1] = { rank=1, name='E-1 Airman basic', requiredXP = 0, cmdChance = 0, cmdAward=0} - PlayerTracker.ranks[2] = { rank=2, name='E-2 Airman', requiredXP = 2000, cmdChance = 0, cmdAward=0} - PlayerTracker.ranks[3] = { rank=3, name='E-3 Airman first class', requiredXP = 4500, cmdChance = 0, cmdAward=0} - PlayerTracker.ranks[4] = { rank=4, name='E-4 Senior airman', requiredXP = 7700, cmdChance = 0, cmdAward=0} - PlayerTracker.ranks[5] = { rank=5, name='E-5 Staff sergeant', requiredXP = 11800, cmdChance = 0, cmdAward=0} - PlayerTracker.ranks[6] = { rank=6, name='E-6 Technical sergeant', requiredXP = 17000, cmdChance = 0.01, cmdAward=1} - PlayerTracker.ranks[7] = { rank=7, name='E-7 Master sergeant', requiredXP = 23500, cmdChance = 0.02, cmdAward=1} - PlayerTracker.ranks[8] = { rank=8, name='E-8 Senior master sergeant', requiredXP = 31500, cmdChance = 0.03, cmdAward=1} - PlayerTracker.ranks[9] = { rank=9, name='E-9 Chief master sergeant', requiredXP = 42000, cmdChance = 0.05, cmdAward=1} - PlayerTracker.ranks[10] = { rank=10, name='O-1 Second lieutenant', requiredXP = 52800, cmdChance = 0.08, cmdAward=2} - PlayerTracker.ranks[11] = { rank=11, name='O-2 First lieutenant', requiredXP = 66500, cmdChance = 0.10, cmdAward=2} - PlayerTracker.ranks[12] = { rank=12, name='O-3 Captain', requiredXP = 82500, cmdChance = 0.14, cmdAward=2} - PlayerTracker.ranks[13] = { rank=13, name='O-4 Major', requiredXP = 101000, cmdChance = 0.17, cmdAward=2} - PlayerTracker.ranks[14] = { rank=14, name='O-5 Lieutenant colonel', requiredXP = 122200, cmdChance = 0.22, cmdAward=3} - PlayerTracker.ranks[15] = { rank=15, name='O-6 Colonel', requiredXP = 146300, cmdChance = 0.26, cmdAward=3} - PlayerTracker.ranks[16] = { rank=16, name='O-7 Brigadier general', requiredXP = 173500, cmdChance = 0.32, cmdAward=3} - PlayerTracker.ranks[17] = { rank=17, name='O-8 Major general', requiredXP = 204000, cmdChance = 0.37, cmdAward=4} - PlayerTracker.ranks[18] = { rank=18, name='O-9 Lieutenant general', requiredXP = 238000, cmdChance = 0.43, cmdAward=4} - PlayerTracker.ranks[19] = { rank=19, name='O-10 General', requiredXP = 275700, cmdChance = 0.50, cmdAward=5} + PlayerTracker.ranks[1] = { rank=1, name='E-1 Airman basic', requiredXP = 0, cmdChance = 0, cmdAward=0, cmdTrys=0} + PlayerTracker.ranks[2] = { rank=2, name='E-2 Airman', requiredXP = 2000, cmdChance = 0, cmdAward=0, cmdTrys=0} + PlayerTracker.ranks[3] = { rank=3, name='E-3 Airman first class', requiredXP = 4500, cmdChance = 0, cmdAward=0, cmdTrys=0} + PlayerTracker.ranks[4] = { rank=4, name='E-4 Senior airman', requiredXP = 7700, cmdChance = 0, cmdAward=0, cmdTrys=0} + PlayerTracker.ranks[5] = { rank=5, name='E-5 Staff sergeant', requiredXP = 11800, cmdChance = 0.01, cmdAward=1, cmdTrys=1} + PlayerTracker.ranks[6] = { rank=6, name='E-6 Technical sergeant', requiredXP = 17000, cmdChance = 0.01, cmdAward=5, cmdTrys=10} + PlayerTracker.ranks[7] = { rank=7, name='E-7 Master sergeant', requiredXP = 23500, cmdChance = 0.03, cmdAward=5, cmdTrys=10} + PlayerTracker.ranks[8] = { rank=8, name='E-8 Senior master sergeant', requiredXP = 31500, cmdChance = 0.06, cmdAward=10, cmdTrys=10} + PlayerTracker.ranks[9] = { rank=9, name='E-9 Chief master sergeant', requiredXP = 42000, cmdChance = 0.10, cmdAward=10, cmdTrys=10} + PlayerTracker.ranks[10] = { rank=10, name='O-1 Second lieutenant', requiredXP = 52800, cmdChance = 0.14, cmdAward=20, cmdTrys=15} + PlayerTracker.ranks[11] = { rank=11, name='O-2 First lieutenant', requiredXP = 66500, cmdChance = 0.20, cmdAward=20, cmdTrys=15} + PlayerTracker.ranks[12] = { rank=12, name='O-3 Captain', requiredXP = 82500, cmdChance = 0.27, cmdAward=25, cmdTrys=15, allowCarrierSupport=true} + PlayerTracker.ranks[13] = { rank=13, name='O-4 Major', requiredXP = 101000, cmdChance = 0.34, cmdAward=25, cmdTrys=20, allowCarrierSupport=true} + PlayerTracker.ranks[14] = { rank=14, name='O-5 Lieutenant colonel', requiredXP = 122200, cmdChance = 0.43, cmdAward=25, cmdTrys=20, allowCarrierSupport=true} + PlayerTracker.ranks[15] = { rank=15, name='O-6 Colonel', requiredXP = 146300, cmdChance = 0.52, cmdAward=30, cmdTrys=20, allowCarrierSupport=true} + PlayerTracker.ranks[16] = { rank=16, name='O-7 Brigadier general', requiredXP = 173500, cmdChance = 0.63, cmdAward=35, cmdTrys=25, allowCarrierSupport=true, allowCarrierCommand=true} + PlayerTracker.ranks[17] = { rank=17, name='O-8 Major general', requiredXP = 204000, cmdChance = 0.74, cmdAward=40, cmdTrys=25, allowCarrierSupport=true, allowCarrierCommand=true} + PlayerTracker.ranks[18] = { rank=18, name='O-9 Lieutenant general', requiredXP = 238000, cmdChance = 0.87, cmdAward=45, cmdTrys=25, allowCarrierSupport=true, allowCarrierCommand=true} + PlayerTracker.ranks[19] = { rank=19, name='O-10 General', requiredXP = 275700, cmdChance = 0.95, cmdAward=50, cmdTrys=30, allowCarrierSupport=true, allowCarrierCommand=true} function PlayerTracker:getPlayerRank(playername) if self.stats[playername] then @@ -7142,6 +7580,25 @@ do return 1.0 end + function PlayerTracker:getPlayerMinutes(playername) + if self.playerEarningMultiplier[playername] then + return self.playerEarningMultiplier[playername].minutes + end + + return 0 + end + + function PlayerTracker.minutesToMultiplier(minutes) + local multi = 1.0 + if minutes > 10 and minutes <= 60 then + multi = 1.0 + ((minutes-10)*0.05) + elseif minutes > 60 then + multi = 1.0 + (50*0.05) + ((minutes - 60)*0.025) + end + + return math.min(multi, 5.0) + end + function PlayerTracker:getRank(xp) local rank = nil local nextRank = nil @@ -7234,7 +7691,7 @@ do env.info('MissionTargetRegistry - bai target removed '..target.name) end - MissionTargetRegistry.strikeTargetExpireTime = 30*60 + MissionTargetRegistry.strikeTargetExpireTime = 60*60 MissionTargetRegistry.strikeTargets = {} function MissionTargetRegistry.addStrikeTarget(target, zone, isDeep) @@ -7285,6 +7742,26 @@ do return targets[dice] end + function MissionTargetRegistry.getAllStrikeTargets(coalition) + local targets = {} + for i,v in pairs(MissionTargetRegistry.strikeTargets) do + if v.data.side == coalition then + local tgt = StaticObject.getByName(v.data.name) + if not tgt then tgt = Group.getByName(v.data.name) end + + if not tgt or not tgt:isExist() then + MissionTargetRegistry.removeStrikeTarget(v) + elseif timer.getAbsTime() - v.addedTime > MissionTargetRegistry.strikeTargetExpireTime then + MissionTargetRegistry.removeStrikeTarget(v) + elseif v.isDeep == isDeep then + table.insert(targets, v) + end + end + end + + return targets + end + function MissionTargetRegistry.removeStrikeTarget(target) MissionTargetRegistry.strikeTargets[target.data.name] = nil env.info('MissionTargetRegistry - strike target removed '..target.data.name) @@ -7426,6 +7903,19 @@ do return obj end + function PersistenceManager:restore() + self:restoreZones() + self:restoreAIMissions() + self:restoreBattlefield() + self:restoreCsar() + self:restoreSquads() + self:restoreCarriers() + + timer.scheduleFunction(function(param) + param:restoreStrikeTargets() + end, self, timer.getTime()+5) + end + function PersistenceManager:restoreZones() local save = self.data for i,v in pairs(save.zones) do @@ -7484,6 +7974,7 @@ do end z:refreshText() + z:refreshSpawnBlocking() end end @@ -7579,6 +8070,90 @@ do end end + function PersistenceManager:restoreStrikeTargets() + local save = self.data + if save.strikeTargets then + for i,v in pairs(save.strikeTargets) do + local zone = ZoneCommand.getZoneByName(v.zname) + local product = zone:getProductByName(v.pname) + + MissionTargetRegistry.strikeTargets[i] = { + data = product, + zone = zone, + addedTime = timer.getAbsTime() - v.elapsedTime, + isDeep = isDeep + } + end + end + end + + function PersistenceManager:restoreCarriers() + local save = self.data + if save.carriers then + for i,v in pairs(save.carriers) do + local carrier = CarrierCommand.getCarrierByName(v.name) + if carrier then + carrier.resource = math.min(v.resource, carrier.maxResource) + carrier:refreshSpawnBlocking() + + local unit = Unit.getByName(v.name) + if unit then + if not v.isAlive then + unit:destroy() + else + local vars = { + groupName = unit:getGroup():getName(), + point = v.position.p, + action = 'teleport', + heading = math.atan2(v.position.x.z, v.position.x.x), + initTasks = false, + route = {} + } + + mist.teleportToPoint(vars) + + timer.scheduleFunction(function(param, time) + param:setupRadios() + end, carrier, timer.getTime()+3) + + carrier.navigation.waypoints = v.navigation.waypoints + carrier.navigation.currentWaypoint = nil + carrier.navigation.nextWaypoint = v.navigation.currentWaypoint + carrier.navigation.loop = v.navigation.loop + + if v.supportFlightStates then + for sfsName, sfsData in pairs(v.supportFlightStates) do + local sflight = carrier.supportFlights[sfsName] + if sflight then + if sfsData.state == CarrierCommand.supportStates.inair and sfsData.targetName and sfsData.position then + local zn = ZoneCommand.getZoneByName(sfsData.targetName) + if not zn then + zn = CarrierCommand.getCarrierByName(sfsData.targetName) + end + + if zn then + CarrierCommand.spawnSupport(sflight, zn, sfsData) + end + elseif sfsData.state == CarrierCommand.supportStates.takeoff and sfsData.targetName then + local zn = ZoneCommand.getZoneByName(sfsData.targetName) + if not zn then + zn = CarrierCommand.getCarrierByName(sfsData.targetName) + end + + if zn then + CarrierCommand.spawnSupport(sflight, zn) + end + end + end + end + end + end + end + end + end + end + end + function PersistenceManager:canRestore() if self.data == nil then return false end @@ -7737,6 +8312,49 @@ do } end + tosave.carriers = {} + for cname,cdata in pairs(CarrierCommand.getAllCarriers()) do + local unit = Unit.getByName(cdata.name) + + tosave.carriers[cname] = { + name = cdata.name, + resource = cdata.resource, + isAlive = unit ~= nil, + position = unit:getPosition(), + navigation = cdata.navigation, + supportFlightStates = {} + } + + for spname, spdata in pairs(cdata.supportFlights) do + tosave.carriers[cname].supportFlightStates[spname] = { + name = spdata.name, + state = spdata.state, + lastStateDuration = timer.getAbsTime() - spdata.lastStateTime, + returning = spdata.returning + } + + if spdata.target then + tosave.carriers[cname].supportFlightStates[spname].targetName = spdata.target.name + end + + if spdata.state == CarrierCommand.supportStates.inair then + local spgr = Group.getByName(spname) + if spgr and spgr:isExist() and spgr:getSize()>0 then + local spun = spgr:getUnit(1) + if spun and spun:isExist() then + tosave.carriers[cname].supportFlightStates[spname].position = spun:getPoint() + tosave.carriers[cname].supportFlightStates[spname].heading = math.atan2(spun:getPosition().x.z, spun:getPosition().x.x) + end + end + end + end + end + + tosave.strikeTargets = {} + for i,v in pairs(MissionTargetRegistry.strikeTargets) do + tosave.strikeTargets[i] = { pname = v.data.name, zname = v.zone.name, elapsedTime = timer.getAbsTime() - v.addedTime, isDeep = v.isDeep } + end + Utils.saveTable(self.path, tosave) end end @@ -8130,13 +8748,15 @@ do local units = {} for i,v in pairs(zone.built) do local g = Group.getByName(v.name) - if g then + if g and g:isExist() then for i2,v2 in ipairs(g:getUnits()) do - table.insert(units, v2) + if v2:isExist() then + table.insert(units, v2) + end end else local s = StaticObject.getByName(v.name) - if s then + if s and s:isExist() then table.insert(units, s) end end @@ -8156,6 +8776,100 @@ do trigger.action.smoke(pos, 1) end end + + function CommandFunctions.sabotageZone(zone) + trigger.action.outText("Saboteurs have been dispatched to "..zone.name, 10) + local delay = math.random(5*60, 7*60) + timer.scheduleFunction(function(param, time) + if math.random() < 0.1 then + trigger.action.outText("Saboteurs have been caught by the enemy before they could complete their mission", 10) + return + end + + local zone = param.zone + local units = {} + for i,v in pairs(zone.built) do + if v.type == 'upgrade' then + local s = StaticObject.getByName(v.name) + if s and s:isExist() then + table.insert(units, s) + end + end + end + + if #units > 0 then + local selected = units[math.random(1,#units)] + + timer.scheduleFunction(function(p2, t2) + if p2.count > 0 then + p2.count = p2.count - 1 + local offsetPos = { + x = p2.pos.x + math.random(-25,25), + y = p2.pos.y, + z = p2.pos.z + math.random(-25,25) + } + + offsetPos.y = land.getHeight({x = offsetPos.x, y = offsetPos.z}) + trigger.action.explosion(offsetPos, 30) + return t2 + 0.05 + (math.random()) + else + trigger.action.explosion(p2.pos, 2000) + end + end, {count = 3, pos = selected:getPoint()}, timer.getTime()+0.5) + + trigger.action.outText("Saboteurs have succesfully triggered explosions at "..zone.name, 10) + end + end, { zone = zone }, timer.getTime()+delay) + end + + function CommandFunctions.shellZone(zone, count) + local minutes = math.random(3,7) + local seconds = math.random(-30,30) + local delay = (minutes*60)+seconds + trigger.action.outText("Artillery preparing to fire on "..zone.name.." ETA: "..minutes.." minutes", 10) + + local positions = {} + for i,v in pairs(zone.built) do + local g = Group.getByName(v.name) + if g and g:isExist() then + for i2,v2 in ipairs(g:getUnits()) do + if v2:isExist() then + table.insert(positions, v2:getPoint()) + end + end + else + local s = StaticObject.getByName(v.name) + if s and s:isExist() then + table.insert(positions, s:getPoint()) + end + end + end + + timer.scheduleFunction(function(param, time) + trigger.action.outText("Artillery firing on "..param.zone.name.." ETA: 30 seconds", 10) + end, {zone = zone}, timer.getTime()+delay-30) + + timer.scheduleFunction(function(param, time) + param.count = param.count - 1 + + local selected = param.positions[math.random(1,#param.positions)] + local offsetPos = { + x = selected.x + math.random(-50,50), + y = selected.y, + z = selected.z + math.random(-50,50) + } + + offsetPos.y = land.getHeight({x = offsetPos.x, y = offsetPos.z}) + + trigger.action.explosion(offsetPos, 20) + + if param.count > 0 then + return time+0.05+(math.random()*2) + else + trigger.action.outText("Artillery finished firing on "..param.zone.name, 10) + end + end, { positions = positions, count = count, zone = zone}, timer.getTime()+delay) + end end -----------------[[ END OF CommandFunctions.lua ]]----------------- @@ -8362,7 +9076,7 @@ do isStructure = true end - if tgtunit then + if tgtunit and tgtunit:isExist() then local pnt = tgtunit:getPoint() local tgttype = "Unidentified" if isStructure then @@ -8413,7 +9127,7 @@ do end if self.timerReference then - mist.removeFunction(self.timerReference) + timer.removeFunction(self.timerReference) self.timerReference = nil end @@ -8427,20 +9141,20 @@ do function JTAC:searchTarget() local gr = Group.getByName(self.name) - if gr then + if gr and gr:isExist() then if self.tgtzone and self.tgtzone.side~=0 and self.tgtzone.side~=gr:getCoalition() then local viabletgts = {} for i,v in pairs(self.tgtzone.built) do local tgtgr = Group.getByName(v.name) - if tgtgr and tgtgr:getSize()>0 then + if tgtgr and tgtgr:isExist() and tgtgr:getSize()>0 then for i2,v2 in ipairs(tgtgr:getUnits()) do - if v2:getLife()>=1 then + if v2:isExist() and v2:getLife()>=1 then table.insert(viabletgts, v2) end end else tgtgr = StaticObject.getByName(v.name) - if tgtgr then + if tgtgr and tgtgr:isExist() then table.insert(viabletgts, tgtgr) end end @@ -8483,11 +9197,11 @@ do function JTAC:searchIfNoTarget() if Group.getByName(self.name) then - if not self.target or (not Unit.getByName(self.target) and not StaticObject.getByName(self.target)) then + if not self.target then self:searchTarget() - elseif self.target then + else local un = Unit.getByName(self.target) - if un then + if un and un:isExist() then if un:getLife()>=1 then self:setTarget(un) else @@ -8495,9 +9209,11 @@ do end else local st = StaticObject.getByName(self.target) - if st then + if st and st:isExist() then self:setTarget(st) - end + else + self:searchTarget() + end end end else @@ -8515,10 +9231,15 @@ do vars.point = {x=p.x, y=5000, z = p.z} mist.teleportToPoint(vars) - mist.scheduleFunction(self.setOrbit, {self, self.tgtzone.zone, p}, timer.getTime()+1) + timer.scheduleFunction(function(param,time) + param.context:setOrbit(param.target, param.point) + end, {context = self, target = self.tgtzone.zone, point = p}, timer.getTime()+1) if not self.timerReference then - self.timerReference = mist.scheduleFunction(self.searchIfNoTarget, {self}, timer.getTime()+5, 5) + self.timerReference = timer.scheduleFunction(function(param, time) + param:searchIfNoTarget() + return time+5 + end, self, timer.getTime()+5) end end @@ -8553,6 +9274,880 @@ end +-----------------[[ CarrierMap.lua ]]----------------- + +CarrierMap = {} +do + CarrierMap.currentIndex = 15000 + function CarrierMap:new(zoneList) + + local obj = {} + obj.zones = {} + + for i,v in ipairs(zoneList) do + local zn = CustomZone:getByName(v) + + local id = CarrierMap.currentIndex + CarrierMap.currentIndex = CarrierMap.currentIndex + 1 + + zn:draw(id, {1,1,1,0.2}, {1,1,1,0.2}) + obj.zones[v] = {zone = zn, waypoints = {}} + + for subi=1,1000,1 do + local subname = v..'-'..subi + if CustomZone:getByName(subname) then + table.insert(obj.zones[v].waypoints, subname) + else + break + end + end + + id = CarrierMap.currentIndex + CarrierMap.currentIndex = CarrierMap.currentIndex + 1 + + trigger.action.textToAll(-1, id , zn.point, {0,0,0,0.8}, {1,1,1,0}, 15, true, v) + for i,wps in ipairs(obj.zones[v].waypoints) do + id = CarrierMap.currentIndex + CarrierMap.currentIndex = CarrierMap.currentIndex + 1 + local point = CustomZone:getByName(wps).point + trigger.action.textToAll(-1, id, point, {0,0,0,0.8}, {1,1,1,0}, 10, true, wps) + end + end + + setmetatable(obj, self) + self.__index = self + + return obj + end + + function CarrierMap:getNavMap() + local map = {} + for nm, zn in pairs(self.zones) do + table.insert(map, {name = zn.zone.name, waypoints = zn.waypoints}) + end + + table.sort(map, function(a,b) return a.name < b.name end) + return map + end +end + +-----------------[[ END OF CarrierMap.lua ]]----------------- + + + +-----------------[[ CarrierCommand.lua ]]----------------- + +CarrierCommand = {} +do + CarrierCommand.allCarriers = {} + CarrierCommand.currentIndex = 6000 + CarrierCommand.isCarrier = true + + CarrierCommand.supportTypes = { + strike = 'Strike', + cap = 'CAP', + awacs = 'AWACS', + tanker = 'Tanker', + transport = 'Transport' + } + + CarrierCommand.supportStates = { + takeoff = 'takeoff', + inair = 'inair', + landed = 'landed', + none = 'none' + } + + CarrierCommand.blockedDespawnTime = 10*60 + CarrierCommand.recoveryReduction = 0.8 + CarrierCommand.landedDespawnTime = 10 + + function CarrierCommand:new(name, range, navmap, radioConfig, maxResource) + local unit = Unit.getByName(name) + if not unit then return end + + local obj = {} + obj.name = name + obj.range = range + obj.side = unit:getCoalition() + obj.resource = maxResource or 30000 + obj.maxResource = maxResource or 30000 + obj.spendTreshold = 500 + obj.revealTime = 0 + obj.isHeloSpawn = true + obj.isPlaneSpawn = true + obj.supportFlights = {} + + obj.navigation = { + currentWaypoint = nil, + waypoints = {}, + loop = true + } + + obj.navmap = navmap + + obj.tacan = radioConfig.tacan + obj.icls = radioConfig.icls + obj.acls = radioConfig.acls + obj.link4 = radioConfig.link4 + obj.radio = radioConfig.radio + + obj.spawns = {} + for i,v in pairs(mist.DBs.groupsByName) do + if v.units[1].skill == 'Client' then + local pos3d = { + x = v.units[1].point.x, + y = 0, + z = v.units[1].point.y + } + + if Utils.isInCircle(pos3d, unit:getPoint(), obj.range)then + table.insert(obj.spawns, {name=i}) + end + end + end + + obj.index = CarrierCommand.currentIndex + CarrierCommand.currentIndex = CarrierCommand.currentIndex + 1 + + local point = unit:getPoint() + + local color = {0.7,0.7,0.7,0.3} + if obj.side == 1 then + color = {1,0,0,0.3} + elseif obj.side == 2 then + color = {0,0,1,0.3} + end + + trigger.action.circleToAll(-1,3000+obj.index,point, obj.range, color, color, 1) + + point.z = point.z + obj.range + trigger.action.textToAll(-1,2000+obj.index, point, {0,0,0,0.8}, {1,1,1,0.5}, 15, true, '') + + setmetatable(obj, self) + self.__index = self + + obj:start() + obj:refreshText() + obj:refreshSpawnBlocking() + CarrierCommand.allCarriers[obj.name] = obj + return obj + end + + function CarrierCommand:setupRadios() + local unit = Unit.getByName(self.name) + TaskExtensions.setupCarrier(unit, self.icls, self.acls, self.tacan, self.link4, self.radio) + end + + function CarrierCommand:start() + self:setupRadios() + + timer.scheduleFunction(function(param, time) + local self = param.context + local unit = Unit.getByName(self.name) + if not unit then + self:clearDrawings() + return + end + + self:updateNavigation() + self:updateSupports() + self:refreshText() + return time+10 + end, {context = self}, timer.getTime()+1) + end + + function CarrierCommand:clearDrawings() + if not self.cleared then + trigger.action.removeMark(2000+self.index) + trigger.action.removeMark(3000+self.index) + self.cleared = true + end + end + + function CarrierCommand:updateSupports() + for _, data in pairs(self.supportFlights) do + self:processAir(data) + end + end + + local function setState(group, state) + group.state = state + group.lastStateTime = timer.getAbsTime() + end + + local function isAttack(group) + if group.type == CarrierCommand.supportTypes.cap then return true end + if group.type == CarrierCommand.supportTypes.strike then return true end + end + + local function hasWeapons(group) + for _,un in ipairs(group:getUnits()) do + local wps = un:getAmmo() + if wps then + for _,w in ipairs(wps) do + if w.desc.category ~= 0 and w.count > 0 then + return true + end + end + end + end + end + + function CarrierCommand:processAir(group) + local carrier = Unit.getByName(self.name) + if not carrier or not carrier:isExist() then return end + + local gr = Group.getByName(group.name) + if not gr or not gr:isExist() then + if group.state ~= CarrierCommand.supportStates.none then + setState(group, CarrierCommand.supportStates.none) + group.returning = false + env.info('CarrierCommand: processAir ['..group.name..'] does not exist state=none') + end + return + end + + if gr:getSize() == 0 then + gr:destroy() + setState(group, CarrierCommand.supportStates.none) + group.returning = false + env.info('CarrierCommand: processAir ['..group.name..'] has no members state=none') + return + end + + if group.state == CarrierCommand.supportStates.none then + setState(group, CarrierCommand.supportStates.takeoff) + env.info('CarrierCommand: processAir ['..group.name..'] started existing state=takeoff') + elseif group.state == CarrierCommand.supportStates.takeoff then + if timer.getAbsTime() - group.lastStateTime > CarrierCommand.blockedDespawnTime then + if gr and gr:getSize()>0 and gr:getUnit(1):isExist() then + local frUnit = gr:getUnit(1) + local cz = CarrierCommand.getCarrierOfUnit(frUnit:getName()) + if Utils.allGroupIsLanded(gr, cz ~= nil) then + env.info('CarrierCommand: processAir ['..group.name..'] is blocked, despawning') + local frUnit = gr:getUnit(1) + if frUnit then + local firstUnit = frUnit:getName() + local z = ZoneCommand.getZoneOfUnit(firstUnit) + if not z then + z = CarrierCommand.getCarrierOfUnit(firstUnit) + end + if z then + z:addResource(group.cost) + env.info('CarrierCommand: processAir ['..z.name..'] has recovered ['..group.cost..'] from ['..group.name..']') + end + end + + gr:destroy() + setState(group, CarrierCommand.supportStates.none) + group.returning = false + env.info('CarrierCommand: processAir ['..group.name..'] has been removed due to being blocked state=none') + return + end + end + elseif gr and Utils.someOfGroupInAir(gr) then + env.info('CarrierCommand: processAir ['..group.name..'] is in the air state=inair') + setState(group, CarrierCommand.supportStates.inair) + end + elseif group.state == CarrierCommand.supportStates.inair then + if gr and gr:getSize()>0 and gr:getUnit(1) and gr:getUnit(1):isExist() then + local frUnit = gr:getUnit(1) + local cz = CarrierCommand.getCarrierOfUnit(frUnit:getName()) + if Utils.allGroupIsLanded(gr, cz ~= nil) then + env.info('CarrierCommand: processAir ['..group.name..'] has landed state=landed') + setState(group, CarrierCommand.supportStates.landed) + + local unit = gr:getUnit(1) + if unit then + local firstUnit = unit:getName() + local z = ZoneCommand.getZoneOfUnit(firstUnit) + if not z then + z = CarrierCommand.getCarrierOfUnit(firstUnit) + end + + if group.type == CarrierCommand.supportTypes.transport then + if z then + z:capture(gr:getCoalition()) + z:addResource(group.cost) + env.info('CarrierCommand: processAir ['..group.name..'] has supplied ['..z.name..'] with ['..group.cost..']') + end + else + if z and z.side == gr:getCoalition() then + local percentSurvived = gr:getSize()/gr:getInitialSize() + local torecover = math.floor(group.cost * percentSurvived * CarrierCommand.recoveryReduction) + z:addResource(torecover) + env.info('CarrierCommand: processAir ['..z.name..'] has recovered ['..torecover..'] from ['..group.name..']') + end + end + else + env.info('CarrierCommand: processAir ['..group.name..'] size ['..gr:getSize()..'] has no unit 1') + end + else + if isAttack(group) and not group.returning then + if not hasWeapons(gr) then + env.info('CarrierCommand: processAir ['..group.name..'] size ['..gr:getSize()..'] has no weapons outside of shells') + group.returning = true + + local point = carrier:getPoint() + TaskExtensions.landAtAirfield(gr, {x=point.x, y=point.z}) + local cnt = gr:getController() + cnt:setOption(0,4) -- force ai hold fire + cnt:setOption(1, 4) -- force reaction on threat to allow abort + end + elseif group.type == CarrierCommand.supportTypes.transport then + if not group.returning and group.target and group.target.side ~= self.side and group.target.side ~= 0 then + group.returning = true + local point = carrier:getPoint() + TaskExtensions.landAtPointFromAir(gr, {x=point.x, y=point.z}, group.altitude) + env.info('CarrierCommand: processAir ['..group.name..'] returning home due to invalid target') + end + end + end + end + elseif group.state == CarrierCommand.supportStates.landed then + if timer.getAbsTime() - group.lastStateTime > CarrierCommand.landedDespawnTime then + if gr then + gr:destroy() + setState(group, CarrierCommand.supportStates.none) + group.returning = false + env.info('CarrierCommand: processAir ['..group.name..'] despawned after landing state=none') + return true + end + end + end + end + + function CarrierCommand:setWaypoints(wplist) + self.navigation.waypoints = wplist + self.navigation.currentWaypoint = nil + self.navigation.nextWaypoint = 1 + self.navigation.loop = #wplist > 1 + end + + function CarrierCommand:updateNavigation() + local unit = Unit.getByName(self.name) + + if self.navigation.nextWaypoint then + local dist = 0 + if self.navigation.currentWaypoint then + local tgzn = self.navigation.waypoints[self.navigation.currentWaypoint] + local point = CustomZone:getByName(tgzn).point + dist = mist.utils.get2DDist(unit:getPoint(), point) + end + + if dist<2000 then + self.navigation.currentWaypoint = self.navigation.nextWaypoint + + local tgzn = self.navigation.waypoints[self.navigation.currentWaypoint] + local point = CustomZone:getByName(tgzn).point + env.info("CarrierCommand - sending "..self.name.." to "..tgzn.." x"..point.x.." z"..point.z) + TaskExtensions.carrierGoToPos(unit:getGroup(), point) + + if self.navigation.loop then + self.navigation.nextWaypoint = self.navigation.nextWaypoint + 1 + if self.navigation.nextWaypoint > #self.navigation.waypoints then + self.navigation.nextWaypoint = 1 + end + else + self.navigation.nextWaypoint = nil + end + end + else + local dist = 9999999 + if self.navigation.currentWaypoint then + local tgzn = self.navigation.waypoints[self.navigation.currentWaypoint] + local point = CustomZone:getByName(tgzn).point + dist = mist.utils.get2DDist(unit:getPoint(), point) + end + + if dist<2000 then + env.info("CarrierCommand - "..self.name.." stopping after reached waypoint") + TaskExtensions.stopCarrier(unit:getGroup()) + self.navigation.currentWaypoint = nil + end + end + end + + function CarrierCommand:addSupportFlight(name, cost, type, data) + self.supportFlights[name] = { + name = name, + cost = cost, + type = type, + target = nil, + state = CarrierCommand.supportStates.none, + lastStateTime = timer.getAbsTime(), + carrier = self + } + + for i,v in pairs(data) do + self.supportFlights[name][i] = v + end + + local gr = Group.getByName(name) + if gr then gr:destroy() end + end + + function CarrierCommand:callSupport(data, groupname) + local playerGroup = Group.getByName(groupname) + if not playerGroup then return end + + if Group.getByName(data.name) and (timer.getAbsTime() - data.lastStateTime < 60*60) then + trigger.action.outTextForGroup(playerGroup:getID(), data.name..' tasking is not available at this time.', 10) + return + end + + if self.resource <= data.cost then + trigger.action.outTextForGroup(playerGroup:getID(), self.name..' does not have enough resources to deploy '..data.name, 10) + return + end + + local targetCoalition = nil + local minDistToFront = nil + local includeCarriers = nil + + if data.type == CarrierCommand.supportTypes.strike then + targetCoalition = 1 + minDistToFront = 1 + elseif data.type == CarrierCommand.supportTypes.cap then + minDistToFront = 1 + includeCarriers = true + elseif data.type == CarrierCommand.supportTypes.awacs then + targetCoalition = 2 + includeCarriers = true + elseif data.type == CarrierCommand.supportTypes.tanker then + targetCoalition = 2 + includeCarriers = true + elseif data.type == CarrierCommand.supportTypes.transport then + targetCoalition = {0,2} + end + + MenuRegistry.showTargetZoneMenu(playerGroup:getID(), "Select "..data.name..'('..data.type..") target", function(params) + CarrierCommand.spawnSupport(params.data, params.zone) + trigger.action.outTextForGroup(params.groupid, params.data.name..'('..params.data.type..') heading to '..params.zone.name, 10) + end, targetCoalition, minDistToFront, data, includeCarriers) + + self:removeResource(data.cost) + trigger.action.outTextForGroup(playerGroup:getID(), 'Select target for '..data.name..' ('..data.type..') from radio menu.', 20) + end + + local function getDefaultPos(savedData) + local action = 'Turning Point' + local speed = 250 + + local vars = { + groupName = savedData.name, + point = savedData.position, + action = 'respawn', + heading = savedData.heading, + initTasks = false, + route = { + [1] = { + alt = savedData.position.y, + type = 'Turning Point', + action = action, + alt_type = 'BARO', + x = savedData.position.x, + y = savedData.position.z, + speed = speed + } + } + } + + return vars + end + + function CarrierCommand.spawnSupport(data, target, saveData) + data.target = target + + if saveData then + mist.teleportToPoint(getDefaultPos(saveData)) + data.state = saveData.state + data.lastStateTime = timer.getAbsTime() - saveData.lastStateDuration + data.returning = saveData.returning + else + mist.respawnGroup(data.name, true) + end + + if data.type == CarrierCommand.supportTypes.strike then + CarrierCommand.dispatchStrike(data, saveData~=nil) + elseif data.type == CarrierCommand.supportTypes.cap then + CarrierCommand.dispatchCap(data, saveData~=nil) + elseif data.type == CarrierCommand.supportTypes.awacs then + CarrierCommand.dispatchAwacs(data, saveData~=nil) + elseif data.type == CarrierCommand.supportTypes.tanker then + CarrierCommand.dispatchTanker(data, saveData~=nil) + elseif data.type == CarrierCommand.supportTypes.transport then + CarrierCommand.dispatchTransport(data, saveData~=nil) + end + end + + function CarrierCommand.dispatchStrike(data, isReactivated) + timer.scheduleFunction(function(param) + local gr = Group.getByName(param.data.name) + local homePos = nil + local carrier = Unit.getByName(param.data.carrier.name) + if carrier and isReactivated then + homePos = { homePos = carrier:getPoint() } + end + env.info('CarrierCommand - sending '..param.data.name..' to '..param.data.target.name) + + local targets = {} + for i,v in pairs(param.data.target.built) do + if v.type == 'upgrade' and v.side ~= gr:getCoalition() then + local tg = TaskExtensions.getTargetPos(v.name) + table.insert(targets, tg) + end + end + + if #targets == 0 then + gr:destroy() + return + end + + local choice = targets[math.random(1, #targets)] + TaskExtensions.executePinpointStrikeMission(gr, choice, AI.Task.WeaponExpend.ALL, param.data.altitude, homePos, carrier:getID()) + end, {data = data}, timer.getTime()+1) + end + + function CarrierCommand.dispatchCap(data, isReactivated) + timer.scheduleFunction(function(param) + local gr = Group.getByName(param.data.name) + + local homePos = nil + local carrier = Unit.getByName(param.data.carrier.name) + if carrier and isReactivated then + homePos = { homePos = carrier:getPoint() } + end + + local point = nil + if param.data.target.isCarrier then + point = Unit.getByName(param.data.target.name):getPoint() + else + point = trigger.misc.getZone(param.data.target.name).point + end + + TaskExtensions.executePatrolMission(gr, point, param.data.altitude, param.data.range, homePos, carrier:getID()) + end, {data = data}, timer.getTime()+1) + end + + function CarrierCommand.dispatchAwacs(data, isReactivated) + timer.scheduleFunction(function(param) + local gr = Group.getByName(param.data.name) + + local homePos = nil + local carrier = Unit.getByName(param.data.carrier.name) + if carrier and isReactivated then + homePos = { homePos = carrier:getPoint() } + end + + local un = gr:getUnit(1) + if un then + local callsign = un:getCallsign() + RadioFrequencyTracker.registerRadio(param.data.name, '[AWACS] '..callsign, param.data.freq..' AM') + end + + local point = nil + if param.data.target.isCarrier then + point = Unit.getByName(param.data.target.name):getPoint() + else + point = trigger.misc.getZone(param.data.target.name).point + end + + TaskExtensions.executeAwacsMission(gr, point, param.data.altitude, param.data.freq, homePos, carrier:getID()) + end, {data = data}, timer.getTime()+1) + end + + function CarrierCommand.dispatchTanker(data, isReactivated) + timer.scheduleFunction(function(param) + local gr = Group.getByName(param.data.name) + + local homePos = nil + local carrier = Unit.getByName(param.data.carrier.name) + if carrier and isReactivated then + homePos = { homePos = carrier:getPoint() } + end + + local un = gr:getUnit(1) + if un then + local callsign = un:getCallsign() + RadioFrequencyTracker.registerRadio(param.data.name, '[Tanker(Drogue)] '..callsign, param.data.freq..' AM | TCN '..param.data.tacan..'X') + end + + local point = nil + if param.data.target.isCarrier then + point = Unit.getByName(param.data.target.name):getPoint() + else + point = trigger.misc.getZone(param.data.target.name).point + end + + TaskExtensions.executeTankerMission(gr, point, param.data.altitude, param.data.freq, param.data.tacan, homePos, carrier:getID()) + end, {data = data}, timer.getTime()+1) + end + + function CarrierCommand.dispatchTransport(data, isReactivated) + timer.scheduleFunction(function(param) + local gr = Group.getByName(param.data.name) + + local supplyPoint = trigger.misc.getZone(param.data.target.name..'-hsp') + if not supplyPoint then + supplyPoint = trigger.misc.getZone(param.data.target.name) + end + + local point = { x=supplyPoint.point.x, y = supplyPoint.point.z} + TaskExtensions.landAtPoint(gr, point, param.data.altitude, true) + end, {data = data}, timer.getTime()+1) + end + + function CarrierCommand:showInformation(groupname) + local gr = Group.getByName(groupname) + if gr then + local msg = '['..self.name..']' + if self.radio then msg = msg..'\n Radio: '..string.format('%.3f',self.radio/1000000)..' AM' end + if self.tacan then msg = msg..'\n TACAN: '..self.tacan.channel..'X ('..self.tacan.callsign..')' end + if self.link4 then msg = msg..'\n Link4: '..string.format('%.3f',self.link4/1000000) end + if self.icls then msg = msg..'\n ICLS: '..self.icls end + + if Utils.getTableSize(self.supportFlights) > 0 then + local flights = {} + for _, data in pairs(self.supportFlights) do + if (data.state == CarrierCommand.supportStates.none or (timer.getAbsTime()-data.lastStateTime >= 60*60)) and data.cost <= self.resource then + table.insert(flights, data) + end + end + + table.sort(flights, function(a,b) return a.name 0 then + msg = msg..'\n\n Available for tasking:' + for _,data in ipairs(flights) do + msg = msg..'\n '..data.name..' ('..data.type..') ['..data.cost..']' + end + end + end + + trigger.action.outTextForGroup(gr:getID(), msg, 20) + end + end + + function CarrierCommand:addResource(amount) + self.resource = self.resource+amount + self.resource = math.floor(math.min(self.resource, self.maxResource)) + self:refreshSpawnBlocking() + self:refreshText() + end + + function CarrierCommand:removeResource(amount) + self.resource = self.resource-amount + self.resource = math.floor(math.max(self.resource, 0)) + self:refreshSpawnBlocking() + self:refreshText() + end + + function CarrierCommand:refreshSpawnBlocking() + for _,v in ipairs(self.spawns) do + trigger.action.setUserFlag(v.name, self.resource < Config.carrierSpawnCost) + end + end + + function CarrierCommand:refreshText() + local build = '' + local mBuild = '' + + local status='' + if self:criticalOnSupplies() then + status = '(!)' + end + + local color = {0.3,0.3,0.3,1} + if self.side == 1 then + color = {0.7,0,0,1} + elseif self.side == 2 then + color = {0,0,0.7,1} + end + + trigger.action.setMarkupColor(2000+self.index, color) + + local label = '['..self.resource..'/'..self.maxResource..']'..status..build..mBuild + + if self.side == 1 then + if self.revealTime > 0 then + trigger.action.setMarkupText(2000+self.index, self.name..label) + else + trigger.action.setMarkupText(2000+self.index, self.name) + end + elseif self.side == 2 then + trigger.action.setMarkupText(2000+self.index, self.name..label) + elseif self.side == 0 then + trigger.action.setMarkupText(2000+self.index, ' '..self.name..' ') + end + + if self.side == 2 and (self.isHeloSpawn or self.isPlaneSpawn) then + trigger.action.setMarkupTypeLine(3000+self.index, 2) + trigger.action.setMarkupColor(3000+self.index, {0,1,0,1}) + end + + local unit = Unit.getByName(self.name) + local point = unit:getPoint() + trigger.action.setMarkupPositionStart(3000+self.index, point) + + point.z = point.z + self.range + trigger.action.setMarkupPositionStart(2000+self.index, point) + end + + function CarrierCommand:capture(side) + end + + function CarrierCommand:criticalOnSupplies() + return self.resource<=self.spendTreshold + end + + function CarrierCommand.getCarrierByName(name) + if not name then return nil end + return CarrierCommand.allCarriers[name] + end + + function CarrierCommand.getAllCarriers() + return CarrierCommand.allCarriers + end + + function CarrierCommand.getCarrierOfUnit(unitname) + local un = Unit.getByName(unitname) + + if not un then + return nil + end + + for i,v in pairs(CarrierCommand.allCarriers) do + local carrier = Unit.getByName(v.name) + if carrier then + if Utils.isInCircle(un:getPoint(), carrier:getPoint(), v.range) then + return v + end + end + end + + return nil + end + + function CarrierCommand.getClosestCarrierToPoint(point) + local minDist = 9999999 + local closest = nil + for i,v in pairs(CarrierCommand.allCarriers) do + local carrier = Unit.getByName(v.name) + if carrier then + local d = mist.utils.get2DDist(carrier:getPoint(), point) + if d < minDist then + minDist = d + closest = v + end + end + end + + return closest, minDist + end + + function CarrierCommand.getCarrierOfPoint(point) + for i,v in pairs(CarrierCommand.allCarriers) do + local carrier = Unit.getByName(v.name) + if carrier then + if Utils.isInCircle(point, carrier:getPoint(), v.range) then + return v + end + end + end + + return nil + end + + CarrierCommand.groupMenus = {} + MenuRegistry:register(6, function(event, context) + if event.id == world.event.S_EVENT_BIRTH and event.initiator and event.initiator.getPlayerName then + local player = event.initiator:getPlayerName() + if player then + local groupid = event.initiator:getGroup():getID() + local groupname = event.initiator:getGroup():getName() + + if CarrierCommand.groupMenus[groupid] then + missionCommands.removeItemForGroup(groupid, CarrierCommand.groupMenus[groupid]) + CarrierCommand.groupMenus[groupid] = nil + end + + if not CarrierCommand.groupMenus[groupid] then + + local menu = missionCommands.addSubMenuForGroup(groupid, 'Naval Command') + + local sorted = {} + for cname, carrier in pairs(CarrierCommand.getAllCarriers()) do + local cr = Unit.getByName(carrier.name) + if cr then + table.insert(sorted, carrier) + end + end + + table.sort(sorted, function(a,b) return a.name < b.name end) + + for _,carrier in ipairs(sorted) do + local crunit = Unit.getByName(carrier.name) + if crunit and crunit:isExist() then + local subm = missionCommands.addSubMenuForGroup(groupid, carrier.name, menu) + missionCommands.addCommandForGroup(groupid, 'Information', subm, Utils.log(carrier.showInformation), carrier, groupname) + + local rank = DependencyManager.get("PlayerTracker"):getPlayerRank(player) + + if rank and rank.allowCarrierSupport and Utils.getTableSize(carrier.supportFlights) > 0 then + local supm = missionCommands.addSubMenuForGroup(groupid, "Support", subm) + local flights = {} + for _, data in pairs(carrier.supportFlights) do + table.insert(flights, data) + end + + table.sort(flights, function(a,b) return a.name 1 then + missionCommands.addCommandForGroup(groupid, 'Patrol Area', wpm, Utils.log(carrier.setWaypoints), carrier, wp.waypoints, groupname) + end + + missionCommands.addCommandForGroup(groupid, 'Go to '..wp.name, wpm, Utils.log(carrier.setWaypoints), carrier, {wp.name}, groupname) + for _,subwp in ipairs(wp.waypoints) do + missionCommands.addCommandForGroup(groupid, 'Go to '..subwp, wpm, Utils.log(carrier.setWaypoints), carrier, {subwp}, groupname) + end + end + end + end + end + + CarrierCommand.groupMenus[groupid] = menu + end + end + elseif (event.id == world.event.S_EVENT_PLAYER_LEAVE_UNIT or event.id == world.event.S_EVENT_DEAD) and event.initiator and event.initiator.getPlayerName then + local player = event.initiator:getPlayerName() + if player then + local groupid = event.initiator:getGroup():getID() + + if CarrierCommand.groupMenus[groupid] then + missionCommands.removeItemForGroup(groupid, CarrierCommand.groupMenus[groupid]) + CarrierCommand.groupMenus[groupid] = nil + end + end + end + end, nil) +end + +-----------------[[ END OF CarrierCommand.lua ]]----------------- + + + -----------------[[ Objectives/Objective.lua ]]----------------- Objective = {} @@ -9708,7 +11303,10 @@ do if not self.param.loadedBy then - if self.param.target.pilot:isExist() then + if self.param.target.pilot:isExist() and + self.param.target.pilot:getSize() > 0 and + self.param.target.pilot:getUnit(1):isExist() then + local point = self.param.target.pilot:getUnit(1):getPoint() local lat,lon,alt = coord.LOtoLL(point) @@ -11187,7 +12785,6 @@ do targetzone = zn } - MissionTargetRegistry.removeStrikeTarget(tgt) end self.description = self.description..description end @@ -11307,7 +12904,6 @@ do targetzone = zn } - MissionTargetRegistry.removeStrikeTarget(tgt) end self.description = self.description..description end @@ -11554,7 +13150,7 @@ do if firstWP and firstWP.zone:hasUnitWithAttributeOnSide({'Buildings'}, 1) then local tgt = firstWP.zone:getRandomUnitWithAttributeOnSide({'Buildings'}, 1) if tgt then - MissionTargetRegistry.addStrikeTarget(tgt, firstWP.zone, true) + MissionTargetRegistry.addStrikeTarget(tgt, firstWP.zone, false) self:pushMessageToPlayers(tgt.display..' discovered at '..firstWP.zone.name) firstWP.zone:reveal() end @@ -12126,27 +13722,27 @@ end MissionTracker = {} do MissionTracker.maxMissionCount = { - [Mission.types.cap_easy] = 1, + [Mission.types.cap_easy] = 2, [Mission.types.cap_medium] = 1, - [Mission.types.cas_easy] = 1, + [Mission.types.cas_easy] = 2, [Mission.types.cas_medium] = 1, [Mission.types.cas_hard] = 1, [Mission.types.sead] = 3, - [Mission.types.supply_easy] = 1, + [Mission.types.supply_easy] = 3, [Mission.types.supply_hard] = 1, - [Mission.types.strike_veryeasy] = 1, + [Mission.types.strike_veryeasy] = 2, [Mission.types.strike_easy] = 1, [Mission.types.strike_medium] = 3, [Mission.types.strike_hard] = 1, [Mission.types.dead] = 1, - [Mission.types.escort] = 1, + [Mission.types.escort] = 2, [Mission.types.tarcap] = 1, - [Mission.types.recon_plane] = 1, - [Mission.types.recon_plane_deep] = 1, + [Mission.types.recon_plane] = 3, + [Mission.types.recon_plane_deep] = 3, [Mission.types.deep_strike] = 3, - [Mission.types.scout_helo] = 1, + [Mission.types.scout_helo] = 3, [Mission.types.bai] = 1, - [Mission.types.anti_runway] = 1, + [Mission.types.anti_runway] = 2, [Mission.types.csar] = 1, [Mission.types.extraction] = 1, [Mission.types.deploy_squad] = 3, @@ -12160,7 +13756,7 @@ do end end - MissionTracker.missionBoardSize = 10 + MissionTracker.missionBoardSize = Config.missionBoardSize or 15 function MissionTracker:new() local obj = {} @@ -12698,7 +14294,7 @@ do for _,m in pairs(self.activeMissions) do if m.players[player] then if m.state == Mission.states.active then - if Weapon.getCategory(weapon) == Weapon.Category.BOMB then + if Weapon.getCategoryEx(weapon) == Weapon.Category.BOMB then timer.scheduleFunction(function (params, time) if not params.weapon:isExist() then return nil -- weapon despawned @@ -12814,6 +14410,10 @@ do end local zn = ZoneCommand.getZoneOfUnit(unit:getName()) + if not zn then + zn = CarrierCommand.getCarrierOfUnit(unit:getName()) + end + if not zn or zn.side ~= unit:getCoalition() then trigger.action.outTextForUnit(unit:getID(), 'Can only accept mission while inside friendly zone', 5) return false @@ -12866,6 +14466,10 @@ do end local zn = ZoneCommand.getZoneOfUnit(unit:getName()) + if not zn then + zn = CarrierCommand.getCarrierOfUnit(unit:getName()) + end + if not zn or zn.side ~= unit:getCoalition() then trigger.action.outTextForUnit(unit:getID(), 'Can only join mission while inside friendly zone', 5) return false @@ -13196,6 +14800,12 @@ do for _,v in pairs(zn.neighbours) do if v.side ~= gr:getCoalition() and v.side ~= 0 then v:reveal() + if v:hasUnitWithAttributeOnSide({'Buildings'}, v.side) then + local tgt = v:getRandomUnitWithAttributeOnSide({'Buildings'}, v.side) + if tgt then + MissionTargetRegistry.addStrikeTarget(tgt, v, v.distToFront >= 2) + end + end end end end @@ -13357,7 +14967,7 @@ do local name = nil for i,v in pairs(self.activePilots) do - if v.pilot:isExist() and v.remainingTime > 0 then + if v.pilot:isExist() and v.pilot:getSize()>0 and v.pilot:getUnit(1):isExist() and v.remainingTime > 0 then local dist = mist.utils.get2DDist(toPosition, v.pilot:getUnit(1):getPoint()) if dist Date: Sun, 25 Feb 2024 12:27:58 +0200 Subject: [PATCH 203/243] Updated pretense_compiled.lua to version 1.7.2: Pretense v1.7.2 - 23 Feb 2024 Fixed occasional script error when saving zone groups --- .../plugins/pretense/pretense_compiled.lua | 4225 +++++++++-------- 1 file changed, 2359 insertions(+), 1866 deletions(-) diff --git a/resources/plugins/pretense/pretense_compiled.lua b/resources/plugins/pretense/pretense_compiled.lua index 4a458bbd..34e2529c 100644 --- a/resources/plugins/pretense/pretense_compiled.lua +++ b/resources/plugins/pretense/pretense_compiled.lua @@ -23,6 +23,21 @@ Makes use of Mission scripting tools (Mist): 0 and group:getController():hasTask() then @@ -325,6 +345,22 @@ do end end end + + function Utils.getAmmo(group, type) + local count = 0 + for _, u in ipairs(group:getUnits()) do + if u:isExist() then + local ammo = u:getAmmo() + for i,v in pairs(ammo) do + if v.desc.typeName == type then + count = count + v.count + end + end + end + end + + return count + end end @@ -366,63 +402,7 @@ do world.addEventHandler(ev) - function MenuRegistry.showStrikeTargetMenu(groupid, name, action, targetside, data) - local executeAction = function(act, params) - local err = act(params) - if not err then - missionCommands.removeItemForGroup(params.groupid, params.menu) - end - end - - local menu = missionCommands.addSubMenuForGroup(groupid, name) - local sub1 = nil - local zones = ZoneCommand.getAllZones() - - local targets = {} - for i,v in pairs(MissionTargetRegistry.getAllStrikeTargets(targetside)) do - table.insert(targets, v) - end - - table.sort(targets, function(a,b) - local aname = a.zone.name..'('..a.data.display..')' - local bname = b.zone.name..'('..b.data.display..')' - return aname < bname - end) - - if #targets > 0 then - local count = 0 - for i,v in ipairs(targets) do - count = count + 1 - local oname = v.zone.name..'('..v.data.display..')' - if count<10 then - missionCommands.addCommandForGroup(groupid, oname, menu, executeAction, action, {target = v, menu=menu, groupid=groupid, data=data}) - elseif count==10 then - sub1 = missionCommands.addSubMenuForGroup(groupid, "More", menu) - missionCommands.addCommandForGroup(groupid, oname, sub1, executeAction, action, {target = v, menu=menu, groupid=groupid, data=data}) - elseif count%9==1 then - sub1 = missionCommands.addSubMenuForGroup(groupid, "More", sub1) - missionCommands.addCommandForGroup(groupid, oname, sub1, executeAction, action, {target = v, menu=menu, groupid=groupid, data=data}) - else - missionCommands.addCommandForGroup(groupid, oname, sub1, executeAction, action, {target = v, menu=menu, groupid=groupid, data=data}) - end - end - else - return false - end - - return menu - end - - function MenuRegistry.showTargetZoneMenu(groupid, name, action, targetside, minDistToFront, data, includeCarriers) - local executeAction = function(act, params) - local err = act(params) - if not err then - missionCommands.removeItemForGroup(params.groupid, params.menu) - end - end - - local menu = missionCommands.addSubMenuForGroup(groupid, name) - local sub1 = nil + function MenuRegistry.showTargetZoneMenu(groupid, name, action, targetside, minDistToFront, data, includeCarriers, onlyRevealed) local zones = ZoneCommand.getAllZones() if targetside and type(targetside) == 'number' then @@ -433,7 +413,9 @@ do for i,v in pairs(zones) do if not targetside or Utils.isInArray(v.side,targetside) then if not minDistToFront or v.distToFront <= minDistToFront then - table.insert(zns, v) + if not onlyRevealed or v.revealTime>0 then + table.insert(zns, v) + end end end end @@ -446,8 +428,20 @@ do end end + if #zns == 0 then return false end + table.sort(zns, function(a,b) return a.name < b.name end) + local executeAction = function(act, params) + local err = act(params) + if not err then + missionCommands.removeItemForGroup(params.groupid, params.menu) + end + end + + local menu = missionCommands.addSubMenuForGroup(groupid, name) + local sub1 = nil + local count = 0 for i,v in ipairs(zns) do count = count + 1 @@ -560,7 +554,7 @@ do return spawnZones[choice] end - function CustomZone:spawnGroup(product) + function CustomZone:spawnGroup(product, acceptedSurface) local spname = self.name local spawnzone = nil @@ -571,17 +565,23 @@ do if spawnzone then spname = spawnzone end + + if not acceptedSurface then + acceptedSurface = { + [land.SurfaceType.LAND] = true + } + end local pnt = mist.getRandomPointInZone(spname) for i=1,500,1 do - if land.getSurfaceType(pnt) == land.SurfaceType.LAND then + if acceptedSurface[land.getSurfaceType(pnt)] then break end pnt = mist.getRandomPointInZone(spname) end - local newgr = Spawner.createObject(product.name, product.template, pnt, product.side, nil, nil, nil, spname) + local newgr = Spawner.createObject(product.name, product.template, pnt, product.side, nil, nil, acceptedSurface, spname) return newgr end @@ -596,6 +596,1353 @@ end +-----------------[[ PlayerLogistics.lua ]]----------------- + +PlayerLogistics = {} +do + PlayerLogistics.allowedTypes = {} + PlayerLogistics.allowedTypes['Mi-24P'] = { supplies = true, personCapacity = 8 } + PlayerLogistics.allowedTypes['Mi-8MT'] = { supplies = true, personCapacity = 24 } + PlayerLogistics.allowedTypes['UH-1H'] = { supplies = true, personCapacity = 12} + PlayerLogistics.allowedTypes['Hercules'] = { supplies = true, personCapacity = 92 } + PlayerLogistics.allowedTypes['UH-60L'] = { supplies = true, personCapacity = 12 } + PlayerLogistics.allowedTypes['Ka-50'] = { supplies = false } + PlayerLogistics.allowedTypes['Ka-50_3'] = { supplies = false } + PlayerLogistics.allowedTypes['SA342L'] = { supplies = false, personCapacity = 2} + PlayerLogistics.allowedTypes['SA342M'] = { supplies = false, personCapacity = 2} + PlayerLogistics.allowedTypes['SA342Minigun'] = { supplies = false, personCapacity = 2} + PlayerLogistics.allowedTypes['AH-64D_BLK_II'] = { supplies = false } + + PlayerLogistics.infantryTypes = { + capture = 'capture', + sabotage = 'sabotage', + ambush = 'ambush', + engineer = 'engineer', + manpads = 'manpads', + spy = 'spy', + rapier = 'rapier', + extractable = 'extractable' + } + + function PlayerLogistics.getInfantryName(infType) + if infType==PlayerLogistics.infantryTypes.capture then + return "Capture Squad" + elseif infType==PlayerLogistics.infantryTypes.sabotage then + return "Sabotage Squad" + elseif infType==PlayerLogistics.infantryTypes.ambush then + return "Ambush Squad" + elseif infType==PlayerLogistics.infantryTypes.engineer then + return "Engineer" + elseif infType==PlayerLogistics.infantryTypes.manpads then + return "MANPADS" + elseif infType==PlayerLogistics.infantryTypes.spy then + return "Spy" + elseif infType==PlayerLogistics.infantryTypes.rapier then + return "Rapier SAM" + elseif infType==PlayerLogistics.infantryTypes.extractable then + return "Extracted infantry" + end + + return "INVALID SQUAD" + end + + function PlayerLogistics:new() + local obj = {} + obj.groupMenus = {} -- groupid = path + obj.carriedCargo = {} -- groupid = source + obj.carriedInfantry = {} -- groupid = source + obj.carriedPilots = {} --groupid = source + obj.registeredSquadGroups = {} + obj.lastLoaded = {} -- groupid = zonename + + obj.hercTracker = { + cargos = {}, + cargoCheckFunctions = {} + } + + obj.hercPreparedDrops = {} + + setmetatable(obj, self) + self.__index = self + + obj:start() + + DependencyManager.register("PlayerLogistics", obj) + return obj + end + + function PlayerLogistics:registerSquadGroup(squadType, groupname, weight, cost, jobtime, extracttime, squadSize, side) + self.registeredSquadGroups[squadType] = { name=groupname, type=squadType, weight=weight, cost=cost, jobtime=jobtime, extracttime=extracttime, size = squadSize, side=side} + end + + function PlayerLogistics:start() + if not ZoneCommand then return end + + MenuRegistry:register(3, function(event, context) + if event.id == world.event.S_EVENT_BIRTH and event.initiator and event.initiator.getPlayerName then + local player = event.initiator:getPlayerName() + if player then + local unitType = event.initiator:getDesc()['typeName'] + local groupid = event.initiator:getGroup():getID() + local groupname = event.initiator:getGroup():getName() + + local logistics = context.allowedTypes[unitType] + if logistics and (logistics.supplies or logistics.personCapacity)then + + if context.groupMenus[groupid] then + missionCommands.removeItemForGroup(groupid, context.groupMenus[groupid]) + context.groupMenus[groupid] = nil + end + + if not context.groupMenus[groupid] then + local size = event.initiator:getGroup():getSize() + if size > 1 then + trigger.action.outText('WARNING: group '..groupname..' has '..size..' units. Logistics will only function for group leader', 10) + end + + local cargomenu = missionCommands.addSubMenuForGroup(groupid, 'Logistics') + if logistics.supplies then + local supplyMenu = missionCommands.addSubMenuForGroup(groupid, 'Supplies', cargomenu) + local loadMenu = missionCommands.addSubMenuForGroup(groupid, 'Load', supplyMenu) + missionCommands.addCommandForGroup(groupid, 'Load 100 supplies', loadMenu, Utils.log(context.loadSupplies), context, {group=groupname, amount=100}) + missionCommands.addCommandForGroup(groupid, 'Load 500 supplies', loadMenu, Utils.log(context.loadSupplies), context, {group=groupname, amount=500}) + missionCommands.addCommandForGroup(groupid, 'Load 1000 supplies', loadMenu, Utils.log(context.loadSupplies), context, {group=groupname, amount=1000}) + missionCommands.addCommandForGroup(groupid, 'Load 2000 supplies', loadMenu, Utils.log(context.loadSupplies), context, {group=groupname, amount=2000}) + missionCommands.addCommandForGroup(groupid, 'Load 5000 supplies', loadMenu, Utils.log(context.loadSupplies), context, {group=groupname, amount=5000}) + + local unloadMenu = missionCommands.addSubMenuForGroup(groupid, 'Unload', supplyMenu) + missionCommands.addCommandForGroup(groupid, 'Unload 100 supplies', unloadMenu, Utils.log(context.unloadSupplies), context, {group=groupname, amount=100}) + missionCommands.addCommandForGroup(groupid, 'Unload 500 supplies', unloadMenu, Utils.log(context.unloadSupplies), context, {group=groupname, amount=500}) + missionCommands.addCommandForGroup(groupid, 'Unload 1000 supplies', unloadMenu, Utils.log(context.unloadSupplies), context, {group=groupname, amount=1000}) + missionCommands.addCommandForGroup(groupid, 'Unload 2000 supplies', unloadMenu, Utils.log(context.unloadSupplies), context, {group=groupname, amount=2000}) + missionCommands.addCommandForGroup(groupid, 'Unload 5000 supplies', unloadMenu, Utils.log(context.unloadSupplies), context, {group=groupname, amount=5000}) + missionCommands.addCommandForGroup(groupid, 'Unload all supplies', unloadMenu, Utils.log(context.unloadSupplies), context, {group=groupname, amount=9999999}) + end + + local sqs = {} + for sqType,_ in pairs(context.registeredSquadGroups) do + table.insert(sqs,sqType) + end + table.sort(sqs) + + if logistics.personCapacity then + local infMenu = missionCommands.addSubMenuForGroup(groupid, 'Infantry', cargomenu) + + local loadInfMenu = missionCommands.addSubMenuForGroup(groupid, 'Load', infMenu) + for _,sqType in ipairs(sqs) do + local menuName = 'Load '..PlayerLogistics.getInfantryName(sqType) + missionCommands.addCommandForGroup(groupid, menuName, loadInfMenu, Utils.log(context.loadInfantry), context, {group=groupname, type=sqType}) + end + + local unloadInfMenu = missionCommands.addSubMenuForGroup(groupid, 'Unload', infMenu) + for _,sqType in ipairs(sqs) do + local menuName = 'Unload '..PlayerLogistics.getInfantryName(sqType) + missionCommands.addCommandForGroup(groupid, menuName, unloadInfMenu, Utils.log(context.unloadInfantry), context, {group=groupname, type=sqType}) + end + missionCommands.addCommandForGroup(groupid, 'Unload Extracted squad', unloadInfMenu, Utils.log(context.unloadInfantry), context, {group=groupname, type=PlayerLogistics.infantryTypes.extractable}) + + missionCommands.addCommandForGroup(groupid, 'Extract squad', infMenu, Utils.log(context.extractSquad), context, groupname) + missionCommands.addCommandForGroup(groupid, 'Unload all', infMenu, Utils.log(context.unloadInfantry), context, {group=groupname}) + + local csarMenu = missionCommands.addSubMenuForGroup(groupid, 'CSAR', cargomenu) + missionCommands.addCommandForGroup(groupid, 'Show info (closest)', csarMenu, Utils.log(context.showPilot), context, groupname) + missionCommands.addCommandForGroup(groupid, 'Smoke marker (closest)', csarMenu, Utils.log(context.smokePilot), context, groupname) + missionCommands.addCommandForGroup(groupid, 'Flare (closest)', csarMenu, Utils.log(context.flarePilot), context, groupname) + missionCommands.addCommandForGroup(groupid, 'Extract pilot', csarMenu, Utils.log(context.extractPilot), context, groupname) + missionCommands.addCommandForGroup(groupid, 'Unload pilots', csarMenu, Utils.log(context.unloadPilots), context, groupname) + end + + missionCommands.addCommandForGroup(groupid, 'Cargo status', cargomenu, Utils.log(context.cargoStatus), context, groupname) + missionCommands.addCommandForGroup(groupid, 'Unload Everything', cargomenu, Utils.log(context.unloadAll), context, groupname) + + if unitType == 'Hercules' then + local loadmasterMenu = missionCommands.addSubMenuForGroup(groupid, 'Loadmaster', cargomenu) + + for _,sqType in ipairs(sqs) do + local menuName = 'Prepare '..PlayerLogistics.getInfantryName(sqType) + missionCommands.addCommandForGroup(groupid, menuName, loadmasterMenu, Utils.log(context.hercPrepareDrop), context, {group=groupname, type=sqType}) + end + + missionCommands.addCommandForGroup(groupid, 'Prepare Supplies', loadmasterMenu, Utils.log(context.hercPrepareDrop), context, {group=groupname, type='supplies'}) + end + + + context.groupMenus[groupid] = cargomenu + end + + if context.carriedCargo[groupid] then + context.carriedCargo[groupid] = 0 + end + + if context.carriedInfantry[groupid] then + context.carriedInfantry[groupid] = {} + end + + if context.carriedPilots[groupid] then + context.carriedPilots[groupid] = {} + end + + if context.lastLoaded[groupid] then + context.lastLoaded[groupid] = nil + end + + if context.hercPreparedDrops[groupid] then + context.hercPreparedDrops[groupid] = nil + end + end + end + elseif (event.id == world.event.S_EVENT_PLAYER_LEAVE_UNIT or event.id == world.event.S_EVENT_DEAD) and event.initiator and event.initiator.getPlayerName then + local player = event.initiator:getPlayerName() + if player then + local groupid = event.initiator:getGroup():getID() + + if context.groupMenus[groupid] then + missionCommands.removeItemForGroup(groupid, context.groupMenus[groupid]) + context.groupMenus[groupid] = nil + end + end + end + end, self) + + local ev = {} + ev.context = self + function ev:onEvent(event) + if event.id == world.event.S_EVENT_SHOT and event.initiator and event.initiator:isExist() then + local unitName = event.initiator:getName() + local groupId = event.initiator:getGroup():getID() + local name = event.weapon:getDesc().typeName + if name == 'weapons.bombs.Generic Crate [20000lb]' then + local prepared = self.context.hercPreparedDrops[groupId] + + if not prepared then + prepared = 'supplies' + + if self.context.carriedInfantry[groupId] then + for _,v in ipairs(self.context.carriedInfantry[groupId]) do + if v.type ~= PlayerLogistics.infantryTypes.extractable then + prepared = v.type + break + end + end + end + + env.info('PlayerLogistics - Hercules - auto preparing '..prepared) + end + + if prepared then + if prepared == 'supplies' then + env.info('PlayerLogistics - Hercules - supplies getting dropped') + local carried = self.context.carriedCargo[groupId] + local amount = 0 + if carried and carried > 0 then + amount = 9000 + if carried < amount then + amount = carried + end + end + + if amount > 0 then + self.context.carriedCargo[groupId] = math.max(0,self.context.carriedCargo[groupId] - amount) + if not self.context.hercTracker.cargos[unitName] then + self.context.hercTracker.cargos[unitName] = {} + end + + table.insert(self.context.hercTracker.cargos[unitName],{ + object = event.weapon, + supply = amount, + lastLoaded = self.context.lastLoaded[groupId], + unit = event.initiator + }) + + env.info('PlayerLogistics - Hercules - '..unitName..' deployed crate with '..amount..' supplies') + self.context:processHercCargos(unitName) + self.context.hercPreparedDrops[groupId] = nil + trigger.action.outTextForUnit(event.initiator:getID(), 'Crate with '..amount..' supplies deployed', 10) + else + trigger.action.outTextForUnit(event.initiator:getID(), 'Empty crate deployed', 10) + end + else + env.info('PlayerLogistics - Hercules - searching for prepared infantry') + local toDrop = nil + local remaining = {} + if self.context.carriedInfantry[groupId] then + for _,v in ipairs(self.context.carriedInfantry[groupId]) do + if v.type == prepared and toDrop == nil then + toDrop = v + else + table.insert(remaining, v) + end + end + end + + + if toDrop then + env.info('PlayerLogistics - Hercules - dropping '..toDrop.type) + if not self.context.hercTracker.cargos[unitName] then + self.context.hercTracker.cargos[unitName] = {} + end + + table.insert(self.context.hercTracker.cargos[unitName],{ + object = event.weapon, + squad = toDrop, + lastLoaded = self.context.lastLoaded[groupId], + unit = event.initiator + }) + + env.info('PlayerLogistics - Hercules - '..unitName..' deployed crate with '..toDrop.type) + self.context:processHercCargos(unitName) + self.context.hercPreparedDrops[groupId] = nil + + local squadName = PlayerLogistics.getInfantryName(prepared) + trigger.action.outTextForUnit(event.initiator:getID(), squadName..' crate deployed.', 10) + self.context.carriedInfantry[groupId] = remaining + local weight = self.context:getCarriedPersonWeight(event.initiator:getGroup():getName()) + trigger.action.setUnitInternalCargo(event.initiator:getName(), weight) + else + trigger.action.outTextForUnit(event.initiator:getID(), 'Empty crate deployed', 10) + end + end + else + trigger.action.outTextForUnit(event.initiator:getID(), 'Empty crate deployed', 10) + end + end + end + end + + world.addEventHandler(ev) + end + + function PlayerLogistics:processHercCargos(unitName) + if not self.hercTracker.cargoCheckFunctions[unitName] then + env.info('PlayerLogistics - Hercules - start tracking cargos of '..unitName) + self.hercTracker.cargoCheckFunctions[unitName] = timer.scheduleFunction(function(params, time) + local reschedule = params.context:checkHercCargo(params.unitName, time) + if not reschedule then + params.context.hercTracker.cargoCheckFunctions[params.unitName] = nil + env.info('PlayerLogistics - Hercules - stopped tracking cargos of '..unitName) + end + + return reschedule + end, {unitName=unitName, context = self}, timer.getTime() + 0.1) + end + end + + function PlayerLogistics:checkHercCargo(unitName, time) + local cargos = self.hercTracker.cargos[unitName] + if cargos and #cargos > 0 then + local remaining = {} + for _,cargo in ipairs(cargos) do + if cargo.object and cargo.object:isExist() then + local alt = Utils.getAGL(cargo.object) + if alt < 5 then + self:deliverHercCargo(cargo) + else + table.insert(remaining, cargo) + end + else + env.info('PlayerLogistics - Hercules - cargo crashed '..tostring(cargo.supply)..' '..tostring(cargo.squad)) + if cargo.squad then + env.info('PlayerLogistics - Hercules - squad crashed '..tostring(cargo.squad.type)) + end + + if cargo.unit and cargo.unit:isExist() then + if cargo.squad then + local squadName = PlayerLogistics.getInfantryName(cargo.squad.type) + trigger.action.outTextForUnit(cargo.unit:getID(), 'Cargo drop of '..cargo.unit:getPlayerName()..' with '..squadName..' crashed', 10) + elseif cargo.supply then + trigger.action.outTextForUnit(cargo.unit:getID(), 'Cargo drop of '..cargo.unit:getPlayerName()..' with '..cargo.supply..' supplies crashed', 10) + end + end + end + end + + if #remaining > 0 then + self.hercTracker.cargos[unitName] = remaining + return time + 0.1 + end + end + end + + function PlayerLogistics:deliverHercCargo(cargo) + if cargo.object and cargo.object:isExist() then + if cargo.supply then + local zone = ZoneCommand.getZoneOfWeapon(cargo.object) + if zone then + zone:addResource(cargo.supply) + env.info('PlayerLogistics - Hercules - '..cargo.supply..' delivered to '..zone.name) + + self:awardSupplyXP(cargo.lastLoaded, zone, cargo.unit, cargo.supply) + end + elseif cargo.squad then + local pos = Utils.getPointOnSurface(cargo.object:getPoint()) + pos.y = pos.z + pos.z = nil + local surface = land.getSurfaceType(pos) + if surface == land.SurfaceType.LAND or surface == land.SurfaceType.ROAD or surface == land.SurfaceType.RUNWAY then + local zn = ZoneCommand.getZoneOfPoint(pos) + + local lastLoad = cargo.squad.loadedAt + if lastLoad and zn and zn.side == cargo.object:getCoalition() and zn.name==lastLoad.name then + if self.registeredSquadGroups[cargo.squad.type] then + local cost = self.registeredSquadGroups[cargo.squad.type].cost + zn:addResource(cost) + zn:refreshText() + if cargo.unit and cargo.unit:isExist() then + local squadName = PlayerLogistics.getInfantryName(cargo.squad.type) + trigger.action.outTextForUnit(cargo.unit:getID(), squadName..' unloaded', 10) + end + end + else + local error = DependencyManager.get("SquadTracker"):spawnInfantry(self.registeredSquadGroups[cargo.squad.type], pos) + if not error then + env.info('PlayerLogistics - Hercules - '..cargo.squad.type..' deployed') + + local squadName = PlayerLogistics.getInfantryName(cargo.squad.type) + + if cargo.unit and cargo.unit:isExist() and cargo.unit.getPlayerName then + trigger.action.outTextForUnit(cargo.unit:getID(), squadName..' deployed', 10) + local player = cargo.unit:getPlayerName() + local xp = RewardDefinitions.actions.squadDeploy * DependencyManager.get("PlayerTracker"):getPlayerMultiplier(player) + + DependencyManager.get("PlayerTracker"):addStat(player, math.floor(xp), PlayerTracker.statTypes.xp) + + if zn then + DependencyManager.get("MissionTracker"):tallyUnloadSquad(player, zn.name, cargo.squad.type) + else + DependencyManager.get("MissionTracker"):tallyUnloadSquad(player, '', cargo.squad.type) + end + trigger.action.outTextForUnit(cargo.unit:getID(), '+'..math.floor(xp)..' XP', 10) + end + end + end + else + env.info('PlayerLogistics - Hercules - '..cargo.squad.type..' dropped on invalid surface '..tostring(surface)) + local cpos = cargo.object:getPoint() + env.info('PlayerLogistics - Hercules - cargo spot X:'..cpos.x..' Y:'..cpos.y..' Z:'..cpos.z) + env.info('PlayerLogistics - Hercules - surface spot X:'..pos.x..' Y:'..pos.y) + local squadName = PlayerLogistics.getInfantryName(cargo.squad.type) + trigger.action.outTextForUnit(cargo.unit:getID(), 'Cargo drop of '..cargo.unit:getPlayerName()..' with '..squadName..' crashed', 10) + end + end + + cargo.object:destroy() + end + end + + function PlayerLogistics:hercPrepareDrop(params) + local groupname = params.group + local type = params.type + local gr = Group.getByName(groupname) + if gr then + local un = gr:getUnit(1) + + if type == 'supplies' then + local cargo = self.carriedCargo[gr:getID()] + if cargo and cargo > 0 then + self.hercPreparedDrops[gr:getID()] = type + trigger.action.outTextForUnit(un:getID(), 'Supply drop prepared', 10) + else + trigger.action.outTextForUnit(un:getID(), 'No supplies onboard the aircraft', 10) + end + else + local exists = false + if self.carriedInfantry[gr:getID()] then + for i,v in ipairs(self.carriedInfantry[gr:getID()]) do + if v.type == type then + exists = true + break + end + end + end + + if exists then + self.hercPreparedDrops[gr:getID()] = type + local squadName = PlayerLogistics.getInfantryName(type) + trigger.action.outTextForUnit(un:getID(), squadName..' drop prepared', 10) + else + local squadName = PlayerLogistics.getInfantryName(type) + trigger.action.outTextForUnit(un:getID(), 'No '..squadName..' onboard the aircraft', 10) + end + end + end + end + + function PlayerLogistics:awardSupplyXP(lastLoad, zone, unit, amount) + if lastLoad and zone.name~=lastLoad.name and not zone.isCarrier and not lastLoad.isCarrier then + if unit and unit.isExist and unit:isExist() and unit.getPlayerName then + local player = unit:getPlayerName() + local xp = amount*RewardDefinitions.actions.supplyRatio + + local totalboost = 0 + local dist = mist.utils.get2DDist(lastLoad.zone.point, zone.zone.point) + if dist > 15000 then + local extradist = math.max(dist - 15000, 85000) + local kmboost = extradist/85000 + local actualboost = xp * kmboost * 1 + totalboost = totalboost + actualboost + end + + local both = true + if zone:criticalOnSupplies() then + local actualboost = xp * RewardDefinitions.actions.supplyBoost + totalboost = totalboost + actualboost + else + both = false + end + + if zone.distToFront == 0 then + local actualboost = xp * RewardDefinitions.actions.supplyBoost + totalboost = totalboost + actualboost + else + both = false + end + + if both then + local actualboost = xp * 1 + totalboost = totalboost + actualboost + end + + xp = xp + totalboost + + if lastLoad.distToFront >= zone.distToFront then + xp = xp * 0.25 + end + + xp = xp * DependencyManager.get("PlayerTracker"):getPlayerMultiplier(player) + + DependencyManager.get("PlayerTracker"):addStat(player, math.floor(xp), PlayerTracker.statTypes.xp) + DependencyManager.get("MissionTracker"):tallySupplies(player, amount, zone.name) + trigger.action.outTextForUnit(unit:getID(), '+'..math.floor(xp)..' XP', 10) + end + end + end + + function PlayerLogistics.markWithSmoke(zonename) + local zone = CustomZone:getByName(zonename) + local p = Utils.getPointOnSurface(zone.point) + trigger.action.smoke(p, 0) + end + + function PlayerLogistics.getWeight(supplies) + return math.floor(supplies) + end + + function PlayerLogistics:getCarriedPersonWeight(groupname) + local gr = Group.getByName(groupname) + local un = gr:getUnit(1) + if un then + if not PlayerLogistics.allowedTypes[un:getDesc().typeName] then return 0 end + + local max = PlayerLogistics.allowedTypes[un:getDesc().typeName].personCapacity + + local pilotWeight = 0 + local squadWeight = 0 + if not self.carriedPilots[gr:getID()] then self.carriedPilots[gr:getID()] = {} end + local pilots = self.carriedPilots[gr:getID()] + if pilots then + pilotWeight = 100 * #pilots + end + + if not self.carriedInfantry[gr:getID()] then self.carriedInfantry[gr:getID()] = {} end + local squads = self.carriedInfantry[gr:getID()] + if squads then + for _,squad in ipairs(squads) do + squadWeight = squadWeight + squad.weight + end + end + + return pilotWeight + squadWeight + end + end + + function PlayerLogistics:getOccupiedPersonCapacity(groupname) + local gr = Group.getByName(groupname) + local un = gr:getUnit(1) + if un then + if not PlayerLogistics.allowedTypes[un:getDesc().typeName] then return 0 end + if self.carriedCargo[gr:getID()] and self.carriedCargo[gr:getID()] > 0 then return 0 end + + local max = PlayerLogistics.allowedTypes[un:getDesc().typeName].personCapacity + + local pilotCount = 0 + local squadCount = 0 + if not self.carriedPilots[gr:getID()] then self.carriedPilots[gr:getID()] = {} end + local pilots = self.carriedPilots[gr:getID()] + if pilots then + pilotCount = #pilots + end + + if not self.carriedInfantry[gr:getID()] then self.carriedInfantry[gr:getID()] = {} end + local squads = self.carriedInfantry[gr:getID()] + if squads then + for _,squad in ipairs(squads) do + squadCount = squadCount + squad.size + end + end + + local total = pilotCount + squadCount + + return total + end + end + + function PlayerLogistics:getRemainingPersonCapacity(groupname) + local gr = Group.getByName(groupname) + local un = gr:getUnit(1) + if un then + if not PlayerLogistics.allowedTypes[un:getDesc().typeName] then return 0 end + if self.carriedCargo[gr:getID()] and self.carriedCargo[gr:getID()] > 0 then return 0 end + + local max = PlayerLogistics.allowedTypes[un:getDesc().typeName].personCapacity + + local total = self:getOccupiedPersonCapacity(groupname) + + return max - total + end + end + + function PlayerLogistics:canFitCargo(groupname) + local gr = Group.getByName(groupname) + local un = gr:getUnit(1) + if un then + if not PlayerLogistics.allowedTypes[un:getDesc().typeName] then return false end + return self:getOccupiedPersonCapacity(groupname) == 0 + end + end + + function PlayerLogistics:canFitPersonnel(groupname, toFit) + local gr = Group.getByName(groupname) + local un = gr:getUnit(1) + if un then + if not PlayerLogistics.allowedTypes[un:getDesc().typeName] then return false end + + return self:getRemainingPersonCapacity(groupname) >= toFit + end + end + + function PlayerLogistics:showPilot(groupname) + local gr = Group.getByName(groupname) + if gr then + local un = gr:getUnit(1) + if un then + local data = DependencyManager.get("CSARTracker"):getClosestPilot(un:getPoint()) + + if not data then + trigger.action.outTextForUnit(un:getID(), 'No pilots in need of extraction', 10) + return + end + + local pos = data.pilot:getUnit(1):getPoint() + local brg = math.floor(Utils.getBearing(un:getPoint(), data.pilot:getUnit(1):getPoint())) + local dist = data.dist + local dstft = math.floor(dist/0.3048) + + local msg = data.name..' requesting extraction' + msg = msg..'\n\n Distance: ' + if dist>1000 then + local dstkm = string.format('%.2f',dist/1000) + local dstnm = string.format('%.2f',dist/1852) + + msg = msg..dstkm..'km | '..dstnm..'nm' + else + local dstft = math.floor(dist/0.3048) + msg = msg..math.floor(dist)..'m | '..dstft..'ft' + end + + msg = msg..'\n Bearing: '..brg + + trigger.action.outTextForUnit(un:getID(), msg, 10) + end + end + end + + function PlayerLogistics:smokePilot(groupname) + local gr = Group.getByName(groupname) + if gr then + local un = gr:getUnit(1) + if un then + local data = DependencyManager.get("CSARTracker"):getClosestPilot(un:getPoint()) + + if not data or data.dist >= 5000 then + trigger.action.outTextForUnit(un:getID(), 'No pilots nearby', 10) + return + end + + DependencyManager.get("CSARTracker"):markPilot(data) + trigger.action.outTextForUnit(un:getID(), 'Location of '..data.name..' marked with green smoke.', 10) + end + end + end + + function PlayerLogistics:flarePilot(groupname) + local gr = Group.getByName(groupname) + if gr then + local un = gr:getUnit(1) + if un then + local data = DependencyManager.get("CSARTracker"):getClosestPilot(un:getPoint()) + + if not data or data.dist >= 5000 then + trigger.action.outTextForUnit(un:getID(), 'No pilots nearby', 10) + return + end + + DependencyManager.get("CSARTracker"):flarePilot(data) + trigger.action.outTextForUnit(un:getID(), data.name..' has deployed a green flare', 10) + end + end + end + + function PlayerLogistics:unloadPilots(groupname) + local gr = Group.getByName(groupname) + if gr then + local un = gr:getUnit(1) + if un then + local pilots = self.carriedPilots[gr:getID()] + if not pilots or #pilots==0 then + trigger.action.outTextForUnit(un:getID(), 'No pilots onboard', 10) + return + end + + if Utils.isInAir(un) then + trigger.action.outTextForUnit(un:getID(), 'Can not unload pilot while in air', 10) + return + end + + if not self:isCargoDoorOpen(un) then + trigger.action.outTextForUnit(un:getID(), 'Can not unload pilot while cargo door closed', 10) + return + end + + local zn = ZoneCommand.getZoneOfUnit(un:getName()) + if not zn then + zn = CarrierCommand.getCarrierOfUnit(un:getName()) + end + + if not zn then + trigger.action.outTextForUnit(un:getID(), 'Can only unload extracted pilots while within a friendly zone', 10) + return + end + + if zn.side ~= 0 and zn.side ~= un:getCoalition()then + trigger.action.outTextForUnit(un:getID(), 'Can only unload extracted pilots while within a friendly zone', 10) + return + end + + zn:addResource(200*#pilots) + zn:refreshText() + + if un.getPlayerName then + local player = un:getPlayerName() + + local xp = #pilots*RewardDefinitions.actions.pilotExtract + + xp = xp * DependencyManager.get("PlayerTracker"):getPlayerMultiplier(player) + + DependencyManager.get("PlayerTracker"):addStat(player, math.floor(xp), PlayerTracker.statTypes.xp) + DependencyManager.get("MissionTracker"):tallyUnloadPilot(player, zn.name) + trigger.action.outTextForUnit(un:getID(), '+'..math.floor(xp)..' XP', 10) + end + + self.carriedPilots[gr:getID()] = {} + trigger.action.setUnitInternalCargo(un:getName(), 0) + trigger.action.outTextForUnit(un:getID(), 'Pilots unloaded', 10) + end + end + end + + function PlayerLogistics:extractPilot(groupname) + local gr = Group.getByName(groupname) + if gr then + local un = gr:getUnit(1) + if un then + if not self:canFitPersonnel(groupname, 1) then + trigger.action.outTextForUnit(un:getID(), 'Not enough free space onboard. (Need 1)', 10) + return + end + + timer.scheduleFunction(function(param,time) + local self = param.context + local un = param.unit + if not un then return end + if not un:isExist() then return end + local gr = un:getGroup() + + local data = DependencyManager.get("CSARTracker"):getClosestPilot(un:getPoint()) + + if not data or data.dist > 500 then + trigger.action.outTextForUnit(un:getID(), 'There is no pilot nearby that needs extraction', 10) + return + else + if not self:isCargoDoorOpen(un) then + trigger.action.outTextForUnit(un:getID(), 'Cargo door closed', 1) + elseif Utils.getAGL(un) > 70 then + trigger.action.outTextForUnit(un:getID(), 'Altitude too high (< 70 m). Current: '..string.format('%.2f',Utils.getAGL(un))..' m', 1) + elseif mist.vec.mag(un:getVelocity())>5 then + trigger.action.outTextForUnit(un:getID(), 'Moving too fast (< 5 m/s). Current: '..string.format('%.2f',mist.vec.mag(un:getVelocity()))..' m/s', 1) + else + if data.dist > 100 then + trigger.action.outTextForUnit(un:getID(), 'Too far (< 100m). Current: '..string.format('%.2f',data.dist)..' m', 1) + else + if not self.carriedPilots[gr:getID()] then self.carriedPilots[gr:getID()] = {} end + table.insert(self.carriedPilots[gr:getID()], data.name) + local player = un:getPlayerName() + DependencyManager.get("MissionTracker"):tallyLoadPilot(player, data) + DependencyManager.get("CSARTracker"):removePilot(data.name) + local weight = self:getCarriedPersonWeight(gr:getName()) + trigger.action.setUnitInternalCargo(un:getName(), weight) + trigger.action.outTextForUnit(un:getID(), data.name..' onboard. ('..weight..' kg)', 10) + return + end + end + end + + param.trys = param.trys - 1 + if param.trys > 0 then + return time+1 + end + end, {context = self, unit = un, trys = 60}, timer.getTime()+0.1) + end + end + end + + function PlayerLogistics:extractSquad(groupname) + local gr = Group.getByName(groupname) + if gr then + local un = gr:getUnit(1) + if un then + + if Utils.isInAir(un) then + trigger.action.outTextForUnit(un:getID(), 'Can not load infantry while in air', 10) + return + end + + if not self:isCargoDoorOpen(un) then + trigger.action.outTextForUnit(un:getID(), 'Can not load infantry while cargo door closed', 10) + return + end + + local squad, distance = DependencyManager.get("SquadTracker"):getClosestExtractableSquad(un:getPoint(), un:getCoalition()) + if squad and distance < 50 then + local squadgr = Group.getByName(squad.name) + if squadgr and squadgr:isExist() then + local sqsize = squadgr:getSize() + if not self:canFitPersonnel(groupname, sqsize) then + trigger.action.outTextForUnit(un:getID(), 'Not enough free space onboard. (Need '..sqsize..')', 10) + return + end + + if not self.carriedInfantry[gr:getID()] then self.carriedInfantry[gr:getID()] = {} end + table.insert(self.carriedInfantry[gr:getID()],{type = PlayerLogistics.infantryTypes.extractable, size = sqsize, weight = sqsize * 100}) + + local weight = self:getCarriedPersonWeight(gr:getName()) + + trigger.action.setUnitInternalCargo(un:getName(), weight) + + local loadedInfName = PlayerLogistics.getInfantryName(PlayerLogistics.infantryTypes.extractable) + trigger.action.outTextForUnit(un:getID(), loadedInfName..' onboard. ('..weight..' kg)', 10) + + local player = un:getPlayerName() + DependencyManager.get("MissionTracker"):tallyLoadSquad(player, squad) + DependencyManager.get("SquadTracker"):removeSquad(squad.name) + + squadgr:destroy() + end + else + trigger.action.outTextForUnit(un:getID(), 'There is no infantry nearby that is ready to be extracted.', 10) + end + end + end + end + + function PlayerLogistics:loadInfantry(params) + if not ZoneCommand then return end + + local gr = Group.getByName(params.group) + local squadType = params.type + local squadName = PlayerLogistics.getInfantryName(squadType) + + local squadCost = 0 + local squadSize = 999999 + local squadWeight = 0 + if self.registeredSquadGroups[squadType] then + squadCost = self.registeredSquadGroups[squadType].cost + squadSize = self.registeredSquadGroups[squadType].size + squadWeight = self.registeredSquadGroups[squadType].weight + end + + if gr then + local un = gr:getUnit(1) + if un then + if Utils.isInAir(un) then + trigger.action.outTextForUnit(un:getID(), 'Can not load infantry while in air', 10) + return + end + + local zn = ZoneCommand.getZoneOfUnit(un:getName()) + if not zn then + zn = CarrierCommand.getCarrierOfUnit(un:getName()) + end + + if not zn then + trigger.action.outTextForUnit(un:getID(), 'Can only load infantry while within a friendly zone', 10) + return + end + + if zn.side ~= un:getCoalition() then + trigger.action.outTextForUnit(un:getID(), 'Can only load infantry while within a friendly zone', 10) + return + end + + if not self:isCargoDoorOpen(un) then + trigger.action.outTextForUnit(un:getID(), 'Can not load infantry while cargo door closed', 10) + return + end + + if zn:criticalOnSupplies() then + trigger.action.outTextForUnit(un:getID(), 'Can not load infantry, zone is low on resources', 10) + return + end + + if zn.resource < zn.spendTreshold + squadCost then + trigger.action.outTextForUnit(un:getID(), 'Can not afford to load '..squadName..' (Cost: '..squadCost..'). Resources would fall to a critical level.', 10) + return + end + + if not self:canFitPersonnel(params.group, squadSize) then + trigger.action.outTextForUnit(un:getID(), 'Not enough free space on board. (Need '..squadSize..')', 10) + return + end + + zn:removeResource(squadCost) + zn:refreshText() + if not self.carriedInfantry[gr:getID()] then self.carriedInfantry[gr:getID()] = {} end + table.insert(self.carriedInfantry[gr:getID()],{ type = squadType, size = squadSize, weight = squadWeight, loadedAt = zn }) + self.lastLoaded[gr:getID()] = zn + + local weight = self:getCarriedPersonWeight(gr:getName()) + trigger.action.setUnitInternalCargo(un:getName(), weight) + + local loadedInfName = PlayerLogistics.getInfantryName(squadType) + trigger.action.outTextForUnit(un:getID(), loadedInfName..' onboard. ('..weight..' kg)', 10) + end + end + end + + function PlayerLogistics:unloadInfantry(params) + if not ZoneCommand then return end + local groupname = params.group + local sqtype = params.type + + local gr = Group.getByName(groupname) + if gr then + local un = gr:getUnit(1) + if un then + if Utils.isInAir(un) then + trigger.action.outTextForUnit(un:getID(), 'Can not unload infantry while in air', 10) + return + end + + if not self:isCargoDoorOpen(un) then + trigger.action.outTextForUnit(un:getID(), 'Can not unload infantry while cargo door closed', 10) + return + end + + local carriedSquads = self.carriedInfantry[gr:getID()] + if not carriedSquads or #carriedSquads == 0 then + trigger.action.outTextForUnit(un:getID(), 'No infantry onboard', 10) + return + end + + local toUnload = carriedSquads + local remaining = {} + if sqtype then + toUnload = {} + local sqToUnload = nil + for _,sq in ipairs(carriedSquads) do + if sq.type == sqtype and not sqToUnload then + sqToUnload = sq + else + table.insert(remaining, sq) + end + end + + if sqToUnload then toUnload = { sqToUnload } end + end + + if #toUnload == 0 then + if sqtype then + local squadName = PlayerLogistics.getInfantryName(sqtype) + trigger.action.outTextForUnit(un:getID(), 'No '..squadName..' onboard.', 10) + else + trigger.action.outTextForUnit(un:getID(), 'No infantry onboard.', 10) + end + + return + end + + local zn = ZoneCommand.getZoneOfUnit(un:getName()) + if not zn then + zn = CarrierCommand.getCarrierOfUnit(un:getName()) + end + + for _, sq in ipairs(toUnload) do + local squadName = PlayerLogistics.getInfantryName(sq.type) + local lastLoad = sq.loadedAt + if lastLoad and zn and zn.side == un:getCoalition() and zn.name==lastLoad.name then + if self.registeredSquadGroups[sq.type] then + local cost = self.registeredSquadGroups[sq.type].cost + zn:addResource(cost) + zn:refreshText() + trigger.action.outTextForUnit(un:getID(), squadName..' unloaded', 10) + end + else + if sq.type == PlayerLogistics.infantryTypes.extractable then + if not zn then + trigger.action.outTextForUnit(un:getID(), 'Can only unload extracted infantry while within a friendly zone', 10) + table.insert(remaining, sq) + elseif zn.side ~= un:getCoalition() then + trigger.action.outTextForUnit(un:getID(), 'Can only unload extracted infantry while within a friendly zone', 10) + table.insert(remaining, sq) + else + trigger.action.outTextForUnit(un:getID(), 'Infantry recovered', 10) + zn:addResource(200) + zn:refreshText() + + if un.getPlayerName then + local player = un:getPlayerName() + local xp = RewardDefinitions.actions.squadExtract * DependencyManager.get("PlayerTracker"):getPlayerMultiplier(player) + + DependencyManager.get("PlayerTracker"):addStat(player, math.floor(xp), PlayerTracker.statTypes.xp) + DependencyManager.get("MissionTracker"):tallyUnloadSquad(player, zn.name, sq.type) + trigger.action.outTextForUnit(un:getID(), '+'..math.floor(xp)..' XP', 10) + end + end + elseif self.registeredSquadGroups[sq.type] then + local pos = Utils.getPointOnSurface(un:getPoint()) + + local error = DependencyManager.get("SquadTracker"):spawnInfantry(self.registeredSquadGroups[sq.type], pos) + + if not error then + trigger.action.outTextForUnit(un:getID(), squadName..' deployed', 10) + + if un.getPlayerName then + local player = un:getPlayerName() + local xp = RewardDefinitions.actions.squadDeploy * DependencyManager.get("PlayerTracker"):getPlayerMultiplier(player) + + DependencyManager.get("PlayerTracker"):addStat(player, math.floor(xp), PlayerTracker.statTypes.xp) + + if zn then + DependencyManager.get("MissionTracker"):tallyUnloadSquad(player, zn.name, sq.type) + else + DependencyManager.get("MissionTracker"):tallyUnloadSquad(player, '', sq.type) + end + trigger.action.outTextForUnit(un:getID(), '+'..math.floor(xp)..' XP', 10) + end + else + trigger.action.outTextForUnit(un:getID(), 'Failed to deploy squad, no suitable location nearby', 10) + table.insert(remaining, sq) + end + else + trigger.action.outText("ERROR: SQUAD TYPE NOT REGISTERED", 60) + end + end + end + + self.carriedInfantry[gr:getID()] = remaining + local weight = self:getCarriedPersonWeight(groupname) + trigger.action.setUnitInternalCargo(un:getName(), weight) + end + end + end + + function PlayerLogistics:unloadAll(groupname) + local gr = Group.getByName(groupname) + if gr then + local un = gr:getUnit(1) + if un then + local cargo = self.carriedCargo[gr:getID()] + local squad = self.carriedInfantry[gr:getID()] + local pilot = self.carriedPilots[gr:getID()] + + if cargo and cargo>0 then + self:unloadSupplies({group=groupname, amount=9999999}) + end + + if squad and #squad>0 then + self:unloadInfantry({group=groupname}) + end + + if pilot and #pilot>0 then + self:unloadPilots(groupname) + end + end + end + end + + function PlayerLogistics:cargoStatus(groupName) + local gr = Group.getByName(groupName) + if gr then + local un = gr:getUnit(1) + if un then + local onboard = self.carriedCargo[gr:getID()] + if onboard and onboard > 0 then + local weight = self.getWeight(onboard) + trigger.action.outTextForUnit(un:getID(), onboard..' supplies onboard. ('..weight..' kg)', 10) + else + local msg = '' + local squads = self.carriedInfantry[gr:getID()] + if squads and #squads>0 then + msg = msg..'Squads:\n' + + for _,squad in ipairs(squads) do + local infName = PlayerLogistics.getInfantryName(squad.type) + msg = msg..' \n'..infName..' (Size: '..squad.size..')' + end + end + + local pilots = self.carriedPilots[gr:getID()] + if pilots and #pilots>0 then + msg = msg.."\n\nPilots:\n" + for i,v in ipairs(pilots) do + msg = msg..'\n '..v + end + + end + + local max = PlayerLogistics.allowedTypes[un:getDesc().typeName].personCapacity + local occupied = self:getOccupiedPersonCapacity(groupName) + + msg = msg..'\n\nCapacity: '..occupied..'/'..max + + msg = msg..'\n('..self:getCarriedPersonWeight(groupName)..' kg)' + + if un:getDesc().typeName == 'Hercules' then + local preped = self.hercPreparedDrops[gr:getID()] + if preped then + if preped == 'supplies' then + msg = msg..'\nSupplies prepared for next drop.' + else + local squadName = PlayerLogistics.getInfantryName(preped) + msg = msg..'\n'..squadName..' prepared for next drop.' + end + end + end + + trigger.action.outTextForUnit(un:getID(), msg, 10) + end + end + end + end + + function PlayerLogistics:isCargoDoorOpen(unit) + if unit then + local tp = unit:getDesc().typeName + if tp == "Mi-8MT" then + if unit:getDrawArgumentValue(86) == 1 then return true end + if unit:getDrawArgumentValue(38) > 0.85 then return true end + elseif tp == "UH-1H" then + if unit:getDrawArgumentValue(43) == 1 then return true end + if unit:getDrawArgumentValue(44) == 1 then return true end + elseif tp == "Mi-24P" then + if unit:getDrawArgumentValue(38) == 1 then return true end + if unit:getDrawArgumentValue(86) == 1 then return true end + elseif tp == "Hercules" then + if unit:getDrawArgumentValue(1215) == 1 and unit:getDrawArgumentValue(1216) == 1 then return true end + elseif tp == "UH-60L" then + if unit:getDrawArgumentValue(401) == 1 then return true end + if unit:getDrawArgumentValue(402) == 1 then return true end + elseif tp == "SA342Mistral" then + if unit:getDrawArgumentValue(34) == 1 then return true end + if unit:getDrawArgumentValue(38) == 1 then return true end + elseif tp == "SA342L" then + if unit:getDrawArgumentValue(34) == 1 then return true end + if unit:getDrawArgumentValue(38) == 1 then return true end + elseif tp == "SA342M" then + if unit:getDrawArgumentValue(34) == 1 then return true end + if unit:getDrawArgumentValue(38) == 1 then return true end + elseif tp == "SA342Minigun" then + if unit:getDrawArgumentValue(34) == 1 then return true end + if unit:getDrawArgumentValue(38) == 1 then return true end + else + return true + end + end + end + + function PlayerLogistics:loadSupplies(params) + if not ZoneCommand then return end + + local groupName = params.group + local amount = params.amount + + local gr = Group.getByName(groupName) + if gr then + local un = gr:getUnit(1) + if un then + if not self:canFitCargo(groupName) then + trigger.action.outTextForUnit(un:getID(), 'Can not load cargo. Personnel onboard.', 10) + return + end + + if Utils.isInAir(un) then + trigger.action.outTextForUnit(un:getID(), 'Can not load supplies while in air', 10) + return + end + + local zn = ZoneCommand.getZoneOfUnit(un:getName()) + if not zn then + zn = CarrierCommand.getCarrierOfUnit(un:getName()) + end + + if not zn then + trigger.action.outTextForUnit(un:getID(), 'Can only load supplies while within a friendly zone', 10) + return + end + + if zn.side ~= un:getCoalition() then + trigger.action.outTextForUnit(un:getID(), 'Can only load supplies while within a friendly zone', 10) + return + end + + if not self:isCargoDoorOpen(un) then + trigger.action.outTextForUnit(un:getID(), 'Can not load supplies while cargo door closed', 10) + return + end + + if zn:criticalOnSupplies() then + trigger.action.outTextForUnit(un:getID(), 'Can not load supplies, zone is low on resources', 10) + return + end + + if zn.resource < zn.spendTreshold + amount then + trigger.action.outTextForUnit(un:getID(), 'Can not load supplies if resources would fall to a critical level.', 10) + return + end + + local carried = self.carriedCargo[gr:getID()] or 0 + if amount > zn.resource then + amount = zn.resource + end + + zn:removeResource(amount) + zn:refreshText() + self.carriedCargo[gr:getID()] = carried + amount + self.lastLoaded[gr:getID()] = zn + local onboard = self.carriedCargo[gr:getID()] + local weight = self.getWeight(onboard) + + if un:getDesc().typeName == "Hercules" then + local loadedInCrates = 0 + local ammo = un:getAmmo() + if ammo then + for _,load in ipairs(ammo) do + if load.desc.typeName == 'weapons.bombs.Generic Crate [20000lb]' then + loadedInCrates = 9000 * load.count + end + end + end + + local internal = 0 + if weight > loadedInCrates then + internal = weight - loadedInCrates + end + + trigger.action.setUnitInternalCargo(un:getName(), internal) + else + trigger.action.setUnitInternalCargo(un:getName(), weight) + end + + trigger.action.outTextForUnit(un:getID(), amount..' supplies loaded', 10) + trigger.action.outTextForUnit(un:getID(), onboard..' supplies onboard. ('..weight..' kg)', 10) + end + end + end + + function PlayerLogistics:unloadSupplies(params) + if not ZoneCommand then return end + + local groupName = params.group + local amount = params.amount + + local gr = Group.getByName(groupName) + if gr then + local un = gr:getUnit(1) + if un then + if Utils.isInAir(un) then + trigger.action.outTextForUnit(un:getID(), 'Can not unload supplies while in air', 10) + return + end + + local zn = ZoneCommand.getZoneOfUnit(un:getName()) + if not zn then + zn = CarrierCommand.getCarrierOfUnit(un:getName()) + end + + if not zn then + trigger.action.outTextForUnit(un:getID(), 'Can only unload supplies while within a friendly zone', 10) + return + end + + if zn.side ~= 0 and zn.side ~= un:getCoalition()then + trigger.action.outTextForUnit(un:getID(), 'Can only unload supplies while within a friendly zone', 10) + return + end + + if not self:isCargoDoorOpen(un) then + trigger.action.outTextForUnit(un:getID(), 'Can not unload supplies while cargo door closed', 10) + return + end + + if not self.carriedCargo[gr:getID()] or self.carriedCargo[gr:getID()] == 0 then + trigger.action.outTextForUnit(un:getID(), 'No supplies loaded', 10) + return + end + + local carried = self.carriedCargo[gr:getID()] + if amount > carried then + amount = carried + end + + self.carriedCargo[gr:getID()] = carried-amount + zn:addResource(amount) + + local lastLoad = self.lastLoaded[gr:getID()] + self:awardSupplyXP(lastLoad, zn, un, amount) + + zn:refreshText() + local onboard = self.carriedCargo[gr:getID()] + local weight = self.getWeight(onboard) + + if un:getDesc().typeName == "Hercules" then + local loadedInCrates = 0 + local ammo = un:getAmmo() + for _,load in ipairs(ammo) do + if load.desc.typeName == 'weapons.bombs.Generic Crate [20000lb]' then + loadedInCrates = 9000 * load.count + end + end + + local internal = 0 + if weight > loadedInCrates then + internal = weight - loadedInCrates + end + + trigger.action.setUnitInternalCargo(un:getName(), internal) + else + trigger.action.setUnitInternalCargo(un:getName(), weight) + end + + trigger.action.outTextForUnit(un:getID(), amount..' supplies unloaded', 10) + trigger.action.outTextForUnit(un:getID(), onboard..' supplies remaining onboard. ('..weight..' kg)', 10) + end + end + end +end + +-----------------[[ END OF PlayerLogistics.lua ]]----------------- + + + -----------------[[ GroupMonitor.lua ]]----------------- GroupMonitor = {} @@ -608,6 +1955,61 @@ do GroupMonitor.siegeExplosiveTime = 5*60 -- how long until random upgrade is detonated in zone GroupMonitor.siegeExplosiveStrength = 1000 -- detonation strength + GroupMonitor.timeBeforeSquadDeploy = 10*60 + GroupMonitor.squadChance = 0.001 + GroupMonitor.ambushChance = 0.7 + + GroupMonitor.aiSquads = { + ambush = { + [1] = { + name='ambush-squad-red', + type=PlayerLogistics.infantryTypes.ambush, + weight = 900, + cost= 300, + jobtime= 60*60*2, + extracttime= 0, + size = 5, + side = 1, + isAISpawned = true + }, + [2] = { + name='ambush-squad', + type=PlayerLogistics.infantryTypes.ambush, + weight = 900, + cost= 300, + jobtime= 60*60, + extracttime= 60*30, + size = 5, + side = 2, + isAISpawned = true + }, + }, + manpads = { + [1] = { + name='manpads-squad-red', + type=PlayerLogistics.infantryTypes.manpads, + weight = 900, + cost= 500, + jobtime= 60*60*2, + extracttime= 0, + size = 5, + side= 1, + isAISpawned = true + }, + [2] = { + name='manpads-squad', + type=PlayerLogistics.infantryTypes.manpads, + weight = 900, + cost= 500, + jobtime= 60*60, + extracttime= 60*30, + size = 5, + side= 2, + isAISpawned = true + } + } + } + function GroupMonitor:new() local obj = {} obj.groups = {} @@ -682,6 +2084,7 @@ do env.info('GroupMonitor - registerGroup ['..product.name..'] restored state '..savedData.state..' dur:'..savedData.lastStateDuration) self.groups[product.name].state = savedData.state self.groups[product.name].lastStateTime = timer.getAbsTime() - savedData.lastStateDuration + self.groups[product.name].spawnedSquad = savedData.spawnedSquad end end @@ -945,6 +2348,25 @@ do group.unstuck_attempts = 0 end end + + local timeElapsed = timer.getAbsTime() - group.lastStateTime + if not group.spawnedSquad and timeElapsed > GroupMonitor.timeBeforeSquadDeploy then + local die = math.random() + if die < GroupMonitor.squadChance then + local pos = gr:getUnit(1):getPoint() + + local squadData = nil + if math.random() > GroupMonitor.ambushChance then + squadData = GroupMonitor.aiSquads.manpads[gr:getCoalition()] + else + squadData = GroupMonitor.aiSquads.ambush[gr:getCoalition()] + end + + DependencyManager.get("SquadTracker"):spawnInfantry(squadData, pos) + env.info('GroupMonitor: processSurface ['..group.name..'] has deployed '..squadData.type..' squad') + group.spawnedSquad = true + end + end end end end @@ -1918,13 +3340,16 @@ do y = point.z - dy } + local orbit_speed = 97 + local travel_speed = 450 + local orbit = { id = 'Orbit', params = { pattern = AI.Task.OrbitPattern.RACE_TRACK, point = pos1, point2 = pos2, - speed = 195, + speed = orbit_speed, altitude = alt } } @@ -1963,7 +3388,7 @@ do type= AI.Task.WaypointType.TURNING_POINT, x = pos1.x, y = pos1.y, - speed = 450, + speed = travel_speed, action = AI.Task.TurnMethod.FLY_OVER_POINT, alt = alt, alt_type = AI.Task.AltitudeType.BARO, @@ -1986,7 +3411,7 @@ do type= AI.Task.WaypointType.TURNING_POINT, x = pos1.x, y = pos1.y, - speed = 195, + speed = orbit_speed, action = AI.Task.TurnMethod.FLY_OVER_POINT, alt = alt, alt_type = AI.Task.AltitudeType.BARO, @@ -2004,7 +3429,7 @@ do type= AI.Task.WaypointType.TURNING_POINT, x = pos2.x, y = pos2.y, - speed = 195, + speed = orbit_speed, action = AI.Task.TurnMethod.FLY_OVER_POINT, alt = alt, alt_type = AI.Task.AltitudeType.BARO, @@ -2025,7 +3450,7 @@ do helipadId = landUnitID, x = startPos.x, y = startPos.z, - speed = 450, + speed = travel_speed, action = AI.Task.TurnMethod.FIN_POINT, alt = 0, alt_type = AI.Task.AltitudeType.RADIO @@ -2035,7 +3460,7 @@ do type= AI.Task.WaypointType.LAND, x = startPos.x, y = startPos.z, - speed = 450, + speed = travel_speed, action = AI.Task.TurnMethod.FIN_POINT, alt = 0, alt_type = AI.Task.AltitudeType.RADIO @@ -2753,6 +4178,62 @@ do group:getController():setTask(mis) end + function TaskExtensions.fireAtTargets(group, targets, amount) + if not group then return end + if not group:isExist() or group:getSize() == 0 then return end + + local units = {} + for i,v in pairs(targets) do + local g = Group.getByName(v.name) + if g then + for i2,v2 in ipairs(g:getUnits()) do + table.insert(units, v2) + end + else + local s = StaticObject.getByName(v.name) + if s then + table.insert(units, s) + end + end + end + + if #units == 0 then + return + end + + local selected = {} + for i=1,amount,1 do + if #units == 0 then + break + end + + local tgt = math.random(1,#units) + + table.insert(selected, units[tgt]) + table.remove(units, tgt) + end + + while #selected < amount do + local ind = math.random(1,#selected) + table.insert(selected, selected[ind]) + end + + for i,v in ipairs(selected) do + local unt = v + if unt then + local target = {} + target.x = unt:getPosition().p.x + target.y = unt:getPosition().p.z + target.radius = 100 + target.expendQty = 1 + target.expendQtyEnabled = true + local fire = {id = 'FireAtPoint', params = target} + + group:getController():pushTask(fire) + end + end + end + function TaskExtensions.carrierGoToPos(group, point) if not group or not point then return end if not group:isExist() or group:getSize()==0 then return end @@ -2879,1353 +4360,6 @@ end ------------------[[ PlayerLogistics.lua ]]----------------- - -PlayerLogistics = {} -do - PlayerLogistics.allowedTypes = {} - PlayerLogistics.allowedTypes['Mi-24P'] = { supplies = true, personCapacity = 8 } - PlayerLogistics.allowedTypes['Mi-8MT'] = { supplies = true, personCapacity = 24 } - PlayerLogistics.allowedTypes['UH-1H'] = { supplies = true, personCapacity = 12} - PlayerLogistics.allowedTypes['Hercules'] = { supplies = true, personCapacity = 92 } - PlayerLogistics.allowedTypes['UH-60L'] = { supplies = true, personCapacity = 12 } - PlayerLogistics.allowedTypes['Ka-50'] = { supplies = false } - PlayerLogistics.allowedTypes['Ka-50_3'] = { supplies = false } - PlayerLogistics.allowedTypes['SA342L'] = { supplies = false, personCapacity = 2} - PlayerLogistics.allowedTypes['SA342M'] = { supplies = false, personCapacity = 2} - PlayerLogistics.allowedTypes['SA342Minigun'] = { supplies = false, personCapacity = 2} - PlayerLogistics.allowedTypes['AH-64D_BLK_II'] = { supplies = false } - - PlayerLogistics.infantryTypes = { - capture = 'capture', - sabotage = 'sabotage', - ambush = 'ambush', - engineer = 'engineer', - manpads = 'manpads', - spy = 'spy', - rapier = 'rapier', - extractable = 'extractable' - } - - function PlayerLogistics.getInfantryName(infType) - if infType==PlayerLogistics.infantryTypes.capture then - return "Capture Squad" - elseif infType==PlayerLogistics.infantryTypes.sabotage then - return "Sabotage Squad" - elseif infType==PlayerLogistics.infantryTypes.ambush then - return "Ambush Squad" - elseif infType==PlayerLogistics.infantryTypes.engineer then - return "Engineer" - elseif infType==PlayerLogistics.infantryTypes.manpads then - return "MANPADS" - elseif infType==PlayerLogistics.infantryTypes.spy then - return "Spy" - elseif infType==PlayerLogistics.infantryTypes.rapier then - return "Rapier SAM" - elseif infType==PlayerLogistics.infantryTypes.extractable then - return "Extracted infantry" - end - - return "INVALID SQUAD" - end - - function PlayerLogistics:new() - local obj = {} - obj.groupMenus = {} -- groupid = path - obj.carriedCargo = {} -- groupid = source - obj.carriedInfantry = {} -- groupid = source - obj.carriedPilots = {} --groupid = source - obj.registeredSquadGroups = {} - obj.lastLoaded = {} -- groupid = zonename - - obj.hercTracker = { - cargos = {}, - cargoCheckFunctions = {} - } - - obj.hercPreparedDrops = {} - - setmetatable(obj, self) - self.__index = self - - obj:start() - - DependencyManager.register("PlayerLogistics", obj) - return obj - end - - function PlayerLogistics:registerSquadGroup(squadType, groupname, weight, cost, jobtime, extracttime, squadSize) - self.registeredSquadGroups[squadType] = { name=groupname, type=squadType, weight=weight, cost=cost, jobtime=jobtime, extracttime=extracttime, size = squadSize} - end - - function PlayerLogistics:start() - if not ZoneCommand then return end - - MenuRegistry:register(3, function(event, context) - if event.id == world.event.S_EVENT_BIRTH and event.initiator and event.initiator.getPlayerName then - local player = event.initiator:getPlayerName() - if player then - local unitType = event.initiator:getDesc()['typeName'] - local groupid = event.initiator:getGroup():getID() - local groupname = event.initiator:getGroup():getName() - - local logistics = context.allowedTypes[unitType] - if logistics and (logistics.supplies or logistics.personCapacity)then - - if context.groupMenus[groupid] then - missionCommands.removeItemForGroup(groupid, context.groupMenus[groupid]) - context.groupMenus[groupid] = nil - end - - if not context.groupMenus[groupid] then - local size = event.initiator:getGroup():getSize() - if size > 1 then - trigger.action.outText('WARNING: group '..groupname..' has '..size..' units. Logistics will only function for group leader', 10) - end - - local cargomenu = missionCommands.addSubMenuForGroup(groupid, 'Logistics') - if logistics.supplies then - local supplyMenu = missionCommands.addSubMenuForGroup(groupid, 'Supplies', cargomenu) - local loadMenu = missionCommands.addSubMenuForGroup(groupid, 'Load', supplyMenu) - missionCommands.addCommandForGroup(groupid, 'Load 100 supplies', loadMenu, Utils.log(context.loadSupplies), context, {group=groupname, amount=100}) - missionCommands.addCommandForGroup(groupid, 'Load 500 supplies', loadMenu, Utils.log(context.loadSupplies), context, {group=groupname, amount=500}) - missionCommands.addCommandForGroup(groupid, 'Load 1000 supplies', loadMenu, Utils.log(context.loadSupplies), context, {group=groupname, amount=1000}) - missionCommands.addCommandForGroup(groupid, 'Load 2000 supplies', loadMenu, Utils.log(context.loadSupplies), context, {group=groupname, amount=2000}) - missionCommands.addCommandForGroup(groupid, 'Load 5000 supplies', loadMenu, Utils.log(context.loadSupplies), context, {group=groupname, amount=5000}) - - local unloadMenu = missionCommands.addSubMenuForGroup(groupid, 'Unload', supplyMenu) - missionCommands.addCommandForGroup(groupid, 'Unload 100 supplies', unloadMenu, Utils.log(context.unloadSupplies), context, {group=groupname, amount=100}) - missionCommands.addCommandForGroup(groupid, 'Unload 500 supplies', unloadMenu, Utils.log(context.unloadSupplies), context, {group=groupname, amount=500}) - missionCommands.addCommandForGroup(groupid, 'Unload 1000 supplies', unloadMenu, Utils.log(context.unloadSupplies), context, {group=groupname, amount=1000}) - missionCommands.addCommandForGroup(groupid, 'Unload 2000 supplies', unloadMenu, Utils.log(context.unloadSupplies), context, {group=groupname, amount=2000}) - missionCommands.addCommandForGroup(groupid, 'Unload 5000 supplies', unloadMenu, Utils.log(context.unloadSupplies), context, {group=groupname, amount=5000}) - missionCommands.addCommandForGroup(groupid, 'Unload all supplies', unloadMenu, Utils.log(context.unloadSupplies), context, {group=groupname, amount=9999999}) - end - - local sqs = {} - for sqType,_ in pairs(context.registeredSquadGroups) do - table.insert(sqs,sqType) - end - table.sort(sqs) - - if logistics.personCapacity then - local infMenu = missionCommands.addSubMenuForGroup(groupid, 'Infantry', cargomenu) - - local loadInfMenu = missionCommands.addSubMenuForGroup(groupid, 'Load', infMenu) - for _,sqType in ipairs(sqs) do - local menuName = 'Load '..PlayerLogistics.getInfantryName(sqType) - missionCommands.addCommandForGroup(groupid, menuName, loadInfMenu, Utils.log(context.loadInfantry), context, {group=groupname, type=sqType}) - end - - local unloadInfMenu = missionCommands.addSubMenuForGroup(groupid, 'Unload', infMenu) - for _,sqType in ipairs(sqs) do - local menuName = 'Unload '..PlayerLogistics.getInfantryName(sqType) - missionCommands.addCommandForGroup(groupid, menuName, unloadInfMenu, Utils.log(context.unloadInfantry), context, {group=groupname, type=sqType}) - end - missionCommands.addCommandForGroup(groupid, 'Unload Extracted squad', unloadInfMenu, Utils.log(context.unloadInfantry), context, {group=groupname, type=PlayerLogistics.infantryTypes.extractable}) - - missionCommands.addCommandForGroup(groupid, 'Extract squad', infMenu, Utils.log(context.extractSquad), context, groupname) - missionCommands.addCommandForGroup(groupid, 'Unload all', infMenu, Utils.log(context.unloadInfantry), context, {group=groupname}) - - local csarMenu = missionCommands.addSubMenuForGroup(groupid, 'CSAR', cargomenu) - missionCommands.addCommandForGroup(groupid, 'Show info (closest)', csarMenu, Utils.log(context.showPilot), context, groupname) - missionCommands.addCommandForGroup(groupid, 'Smoke marker (closest)', csarMenu, Utils.log(context.smokePilot), context, groupname) - missionCommands.addCommandForGroup(groupid, 'Flare (closest)', csarMenu, Utils.log(context.flarePilot), context, groupname) - missionCommands.addCommandForGroup(groupid, 'Extract pilot', csarMenu, Utils.log(context.extractPilot), context, groupname) - missionCommands.addCommandForGroup(groupid, 'Unload pilots', csarMenu, Utils.log(context.unloadPilots), context, groupname) - end - - missionCommands.addCommandForGroup(groupid, 'Cargo status', cargomenu, Utils.log(context.cargoStatus), context, groupname) - missionCommands.addCommandForGroup(groupid, 'Unload Everything', cargomenu, Utils.log(context.unloadAll), context, groupname) - - if unitType == 'Hercules' then - local loadmasterMenu = missionCommands.addSubMenuForGroup(groupid, 'Loadmaster', cargomenu) - - for _,sqType in ipairs(sqs) do - local menuName = 'Prepare '..PlayerLogistics.getInfantryName(sqType) - missionCommands.addCommandForGroup(groupid, menuName, loadmasterMenu, Utils.log(context.hercPrepareDrop), context, {group=groupname, type=sqType}) - end - - missionCommands.addCommandForGroup(groupid, 'Prepare Supplies', loadmasterMenu, Utils.log(context.hercPrepareDrop), context, {group=groupname, type='supplies'}) - end - - - context.groupMenus[groupid] = cargomenu - end - - if context.carriedCargo[groupid] then - context.carriedCargo[groupid] = 0 - end - - if context.carriedInfantry[groupid] then - context.carriedInfantry[groupid] = {} - end - - if context.carriedPilots[groupid] then - context.carriedPilots[groupid] = {} - end - - if context.lastLoaded[groupid] then - context.lastLoaded[groupid] = nil - end - - if context.hercPreparedDrops[groupid] then - context.hercPreparedDrops[groupid] = nil - end - end - end - elseif (event.id == world.event.S_EVENT_PLAYER_LEAVE_UNIT or event.id == world.event.S_EVENT_DEAD) and event.initiator and event.initiator.getPlayerName then - local player = event.initiator:getPlayerName() - if player then - local groupid = event.initiator:getGroup():getID() - - if context.groupMenus[groupid] then - missionCommands.removeItemForGroup(groupid, context.groupMenus[groupid]) - context.groupMenus[groupid] = nil - end - end - end - end, self) - - local ev = {} - ev.context = self - function ev:onEvent(event) - if event.id == world.event.S_EVENT_SHOT and event.initiator and event.initiator:isExist() then - local unitName = event.initiator:getName() - local groupId = event.initiator:getGroup():getID() - local name = event.weapon:getDesc().typeName - if name == 'weapons.bombs.Generic Crate [20000lb]' then - local prepared = self.context.hercPreparedDrops[groupId] - - if not prepared then - prepared = 'supplies' - - if self.context.carriedInfantry[groupId] then - for _,v in ipairs(self.context.carriedInfantry[groupId]) do - if v.type ~= PlayerLogistics.infantryTypes.extractable then - prepared = v.type - break - end - end - end - - env.info('PlayerLogistics - Hercules - auto preparing '..prepared) - end - - if prepared then - if prepared == 'supplies' then - env.info('PlayerLogistics - Hercules - supplies getting dropped') - local carried = self.context.carriedCargo[groupId] - local amount = 0 - if carried and carried > 0 then - amount = 9000 - if carried < amount then - amount = carried - end - end - - if amount > 0 then - self.context.carriedCargo[groupId] = math.max(0,self.context.carriedCargo[groupId] - amount) - if not self.context.hercTracker.cargos[unitName] then - self.context.hercTracker.cargos[unitName] = {} - end - - table.insert(self.context.hercTracker.cargos[unitName],{ - object = event.weapon, - supply = amount, - lastLoaded = self.context.lastLoaded[groupId], - unit = event.initiator - }) - - env.info('PlayerLogistics - Hercules - '..unitName..' deployed crate with '..amount..' supplies') - self.context:processHercCargos(unitName) - self.context.hercPreparedDrops[groupId] = nil - trigger.action.outTextForUnit(event.initiator:getID(), 'Crate with '..amount..' supplies deployed', 10) - else - trigger.action.outTextForUnit(event.initiator:getID(), 'Empty crate deployed', 10) - end - else - env.info('PlayerLogistics - Hercules - searching for prepared infantry') - local toDrop = nil - local remaining = {} - if self.context.carriedInfantry[groupId] then - for _,v in ipairs(self.context.carriedInfantry[groupId]) do - if v.type == prepared and toDrop == nil then - toDrop = v - else - table.insert(remaining, v) - end - end - end - - - if toDrop then - env.info('PlayerLogistics - Hercules - dropping '..toDrop.type) - if not self.context.hercTracker.cargos[unitName] then - self.context.hercTracker.cargos[unitName] = {} - end - - table.insert(self.context.hercTracker.cargos[unitName],{ - object = event.weapon, - squad = toDrop, - lastLoaded = self.context.lastLoaded[groupId], - unit = event.initiator - }) - - env.info('PlayerLogistics - Hercules - '..unitName..' deployed crate with '..toDrop.type) - self.context:processHercCargos(unitName) - self.context.hercPreparedDrops[groupId] = nil - - local squadName = PlayerLogistics.getInfantryName(prepared) - trigger.action.outTextForUnit(event.initiator:getID(), squadName..' crate deployed.', 10) - self.context.carriedInfantry[groupId] = remaining - local weight = self.context:getCarriedPersonWeight(event.initiator:getGroup():getName()) - trigger.action.setUnitInternalCargo(event.initiator:getName(), weight) - else - trigger.action.outTextForUnit(event.initiator:getID(), 'Empty crate deployed', 10) - end - end - else - trigger.action.outTextForUnit(event.initiator:getID(), 'Empty crate deployed', 10) - end - end - end - end - - world.addEventHandler(ev) - end - - function PlayerLogistics:processHercCargos(unitName) - if not self.hercTracker.cargoCheckFunctions[unitName] then - env.info('PlayerLogistics - Hercules - start tracking cargos of '..unitName) - self.hercTracker.cargoCheckFunctions[unitName] = timer.scheduleFunction(function(params, time) - local reschedule = params.context:checkHercCargo(params.unitName, time) - if not reschedule then - params.context.hercTracker.cargoCheckFunctions[params.unitName] = nil - env.info('PlayerLogistics - Hercules - stopped tracking cargos of '..unitName) - end - - return reschedule - end, {unitName=unitName, context = self}, timer.getTime() + 0.1) - end - end - - function PlayerLogistics:checkHercCargo(unitName, time) - local cargos = self.hercTracker.cargos[unitName] - if cargos and #cargos > 0 then - local remaining = {} - for _,cargo in ipairs(cargos) do - if cargo.object and cargo.object:isExist() then - local alt = Utils.getAGL(cargo.object) - if alt < 5 then - self:deliverHercCargo(cargo) - else - table.insert(remaining, cargo) - end - else - env.info('PlayerLogistics - Hercules - cargo crashed '..tostring(cargo.supply)..' '..tostring(cargo.squad)) - if cargo.squad then - env.info('PlayerLogistics - Hercules - squad crashed '..tostring(cargo.squad.type)) - end - - if cargo.unit and cargo.unit:isExist() then - if cargo.squad then - local squadName = PlayerLogistics.getInfantryName(cargo.squad.type) - trigger.action.outTextForUnit(cargo.unit:getID(), 'Cargo drop of '..cargo.unit:getPlayerName()..' with '..squadName..' crashed', 10) - elseif cargo.supply then - trigger.action.outTextForUnit(cargo.unit:getID(), 'Cargo drop of '..cargo.unit:getPlayerName()..' with '..cargo.supply..' supplies crashed', 10) - end - end - end - end - - if #remaining > 0 then - self.hercTracker.cargos[unitName] = remaining - return time + 0.1 - end - end - end - - function PlayerLogistics:deliverHercCargo(cargo) - if cargo.object and cargo.object:isExist() then - if cargo.supply then - local zone = ZoneCommand.getZoneOfWeapon(cargo.object) - if zone then - zone:addResource(cargo.supply) - env.info('PlayerLogistics - Hercules - '..cargo.supply..' delivered to '..zone.name) - - self:awardSupplyXP(cargo.lastLoaded, zone, cargo.unit, cargo.supply) - end - elseif cargo.squad then - local pos = Utils.getPointOnSurface(cargo.object:getPoint()) - pos.y = pos.z - pos.z = nil - local surface = land.getSurfaceType(pos) - if surface == land.SurfaceType.LAND or surface == land.SurfaceType.ROAD or surface == land.SurfaceType.RUNWAY then - local zn = ZoneCommand.getZoneOfPoint(pos) - - local lastLoad = cargo.squad.loadedAt - if lastLoad and zn and zn.side == cargo.object:getCoalition() and zn.name==lastLoad.name then - if self.registeredSquadGroups[cargo.squad.type] then - local cost = self.registeredSquadGroups[cargo.squad.type].cost - zn:addResource(cost) - zn:refreshText() - if cargo.unit and cargo.unit:isExist() then - local squadName = PlayerLogistics.getInfantryName(cargo.squad.type) - trigger.action.outTextForUnit(cargo.unit:getID(), squadName..' unloaded', 10) - end - end - else - local error = DependencyManager.get("SquadTracker"):spawnInfantry(self.registeredSquadGroups[cargo.squad.type], pos) - if not error then - env.info('PlayerLogistics - Hercules - '..cargo.squad.type..' deployed') - - local squadName = PlayerLogistics.getInfantryName(cargo.squad.type) - - if cargo.unit and cargo.unit:isExist() and cargo.unit.getPlayerName then - trigger.action.outTextForUnit(cargo.unit:getID(), squadName..' deployed', 10) - local player = cargo.unit:getPlayerName() - local xp = RewardDefinitions.actions.squadDeploy * DependencyManager.get("PlayerTracker"):getPlayerMultiplier(player) - - DependencyManager.get("PlayerTracker"):addStat(player, math.floor(xp), PlayerTracker.statTypes.xp) - - if zn then - DependencyManager.get("MissionTracker"):tallyUnloadSquad(player, zn.name, cargo.squad.type) - else - DependencyManager.get("MissionTracker"):tallyUnloadSquad(player, '', cargo.squad.type) - end - trigger.action.outTextForUnit(cargo.unit:getID(), '+'..math.floor(xp)..' XP', 10) - end - end - end - else - env.info('PlayerLogistics - Hercules - '..cargo.squad.type..' dropped on invalid surface '..tostring(surface)) - local cpos = cargo.object:getPoint() - env.info('PlayerLogistics - Hercules - cargo spot X:'..cpos.x..' Y:'..cpos.y..' Z:'..cpos.z) - env.info('PlayerLogistics - Hercules - surface spot X:'..pos.x..' Y:'..pos.y) - local squadName = PlayerLogistics.getInfantryName(cargo.squad.type) - trigger.action.outTextForUnit(cargo.unit:getID(), 'Cargo drop of '..cargo.unit:getPlayerName()..' with '..squadName..' crashed', 10) - end - end - - cargo.object:destroy() - end - end - - function PlayerLogistics:hercPrepareDrop(params) - local groupname = params.group - local type = params.type - local gr = Group.getByName(groupname) - if gr then - local un = gr:getUnit(1) - - if type == 'supplies' then - local cargo = self.carriedCargo[gr:getID()] - if cargo and cargo > 0 then - self.hercPreparedDrops[gr:getID()] = type - trigger.action.outTextForUnit(un:getID(), 'Supply drop prepared', 10) - else - trigger.action.outTextForUnit(un:getID(), 'No supplies onboard the aircraft', 10) - end - else - local exists = false - if self.carriedInfantry[gr:getID()] then - for i,v in ipairs(self.carriedInfantry[gr:getID()]) do - if v.type == type then - exists = true - break - end - end - end - - if exists then - self.hercPreparedDrops[gr:getID()] = type - local squadName = PlayerLogistics.getInfantryName(type) - trigger.action.outTextForUnit(un:getID(), squadName..' drop prepared', 10) - else - local squadName = PlayerLogistics.getInfantryName(type) - trigger.action.outTextForUnit(un:getID(), 'No '..squadName..' onboard the aircraft', 10) - end - end - end - end - - function PlayerLogistics:awardSupplyXP(lastLoad, zone, unit, amount) - if lastLoad and zone.name~=lastLoad.name and not zone.isCarrier and not lastLoad.isCarrier then - if unit and unit.isExist and unit:isExist() and unit.getPlayerName then - local player = unit:getPlayerName() - local xp = amount*RewardDefinitions.actions.supplyRatio - - local totalboost = 0 - local dist = mist.utils.get2DDist(lastLoad.zone.point, zone.zone.point) - if dist > 15000 then - local extradist = math.max(dist - 15000, 85000) - local kmboost = extradist/85000 - local actualboost = xp * kmboost * 1 - totalboost = totalboost + actualboost - end - - local both = true - if zone:criticalOnSupplies() then - local actualboost = xp * RewardDefinitions.actions.supplyBoost - totalboost = totalboost + actualboost - else - both = false - end - - if zone.distToFront == 0 then - local actualboost = xp * RewardDefinitions.actions.supplyBoost - totalboost = totalboost + actualboost - else - both = false - end - - if both then - local actualboost = xp * 1 - totalboost = totalboost + actualboost - end - - xp = xp + totalboost - - if lastLoad.distToFront >= zone.distToFront then - xp = xp * 0.25 - end - - xp = xp * DependencyManager.get("PlayerTracker"):getPlayerMultiplier(player) - - DependencyManager.get("PlayerTracker"):addStat(player, math.floor(xp), PlayerTracker.statTypes.xp) - DependencyManager.get("MissionTracker"):tallySupplies(player, amount, zone.name) - trigger.action.outTextForUnit(unit:getID(), '+'..math.floor(xp)..' XP', 10) - end - end - end - - function PlayerLogistics.markWithSmoke(zonename) - local zone = CustomZone:getByName(zonename) - local p = Utils.getPointOnSurface(zone.point) - trigger.action.smoke(p, 0) - end - - function PlayerLogistics.getWeight(supplies) - return math.floor(supplies) - end - - function PlayerLogistics:getCarriedPersonWeight(groupname) - local gr = Group.getByName(groupname) - local un = gr:getUnit(1) - if un then - if not PlayerLogistics.allowedTypes[un:getDesc().typeName] then return 0 end - - local max = PlayerLogistics.allowedTypes[un:getDesc().typeName].personCapacity - - local pilotWeight = 0 - local squadWeight = 0 - if not self.carriedPilots[gr:getID()] then self.carriedPilots[gr:getID()] = {} end - local pilots = self.carriedPilots[gr:getID()] - if pilots then - pilotWeight = 100 * #pilots - end - - if not self.carriedInfantry[gr:getID()] then self.carriedInfantry[gr:getID()] = {} end - local squads = self.carriedInfantry[gr:getID()] - if squads then - for _,squad in ipairs(squads) do - squadWeight = squadWeight + squad.weight - end - end - - return pilotWeight + squadWeight - end - end - - function PlayerLogistics:getOccupiedPersonCapacity(groupname) - local gr = Group.getByName(groupname) - local un = gr:getUnit(1) - if un then - if not PlayerLogistics.allowedTypes[un:getDesc().typeName] then return 0 end - if self.carriedCargo[gr:getID()] and self.carriedCargo[gr:getID()] > 0 then return 0 end - - local max = PlayerLogistics.allowedTypes[un:getDesc().typeName].personCapacity - - local pilotCount = 0 - local squadCount = 0 - if not self.carriedPilots[gr:getID()] then self.carriedPilots[gr:getID()] = {} end - local pilots = self.carriedPilots[gr:getID()] - if pilots then - pilotCount = #pilots - end - - if not self.carriedInfantry[gr:getID()] then self.carriedInfantry[gr:getID()] = {} end - local squads = self.carriedInfantry[gr:getID()] - if squads then - for _,squad in ipairs(squads) do - squadCount = squadCount + squad.size - end - end - - local total = pilotCount + squadCount - - return total - end - end - - function PlayerLogistics:getRemainingPersonCapacity(groupname) - local gr = Group.getByName(groupname) - local un = gr:getUnit(1) - if un then - if not PlayerLogistics.allowedTypes[un:getDesc().typeName] then return 0 end - if self.carriedCargo[gr:getID()] and self.carriedCargo[gr:getID()] > 0 then return 0 end - - local max = PlayerLogistics.allowedTypes[un:getDesc().typeName].personCapacity - - local total = self:getOccupiedPersonCapacity(groupname) - - return max - total - end - end - - function PlayerLogistics:canFitCargo(groupname) - local gr = Group.getByName(groupname) - local un = gr:getUnit(1) - if un then - if not PlayerLogistics.allowedTypes[un:getDesc().typeName] then return false end - return self:getOccupiedPersonCapacity(groupname) == 0 - end - end - - function PlayerLogistics:canFitPersonnel(groupname, toFit) - local gr = Group.getByName(groupname) - local un = gr:getUnit(1) - if un then - if not PlayerLogistics.allowedTypes[un:getDesc().typeName] then return false end - - return self:getRemainingPersonCapacity(groupname) >= toFit - end - end - - function PlayerLogistics:showPilot(groupname) - local gr = Group.getByName(groupname) - if gr then - local un = gr:getUnit(1) - if un then - local data = DependencyManager.get("CSARTracker"):getClosestPilot(un:getPoint()) - - if not data then - trigger.action.outTextForUnit(un:getID(), 'No pilots in need of extraction', 10) - return - end - - local pos = data.pilot:getUnit(1):getPoint() - local brg = math.floor(Utils.getBearing(un:getPoint(), data.pilot:getUnit(1):getPoint())) - local dist = data.dist - local dstft = math.floor(dist/0.3048) - - local msg = data.name..' requesting extraction' - msg = msg..'\n\n Distance: ' - if dist>1000 then - local dstkm = string.format('%.2f',dist/1000) - local dstnm = string.format('%.2f',dist/1852) - - msg = msg..dstkm..'km | '..dstnm..'nm' - else - local dstft = math.floor(dist/0.3048) - msg = msg..math.floor(dist)..'m | '..dstft..'ft' - end - - msg = msg..'\n Bearing: '..brg - - trigger.action.outTextForUnit(un:getID(), msg, 10) - end - end - end - - function PlayerLogistics:smokePilot(groupname) - local gr = Group.getByName(groupname) - if gr then - local un = gr:getUnit(1) - if un then - local data = DependencyManager.get("CSARTracker"):getClosestPilot(un:getPoint()) - - if not data or data.dist >= 5000 then - trigger.action.outTextForUnit(un:getID(), 'No pilots nearby', 10) - return - end - - DependencyManager.get("CSARTracker"):markPilot(data) - trigger.action.outTextForUnit(un:getID(), 'Location of '..data.name..' marked with green smoke.', 10) - end - end - end - - function PlayerLogistics:flarePilot(groupname) - local gr = Group.getByName(groupname) - if gr then - local un = gr:getUnit(1) - if un then - local data = DependencyManager.get("CSARTracker"):getClosestPilot(un:getPoint()) - - if not data or data.dist >= 5000 then - trigger.action.outTextForUnit(un:getID(), 'No pilots nearby', 10) - return - end - - DependencyManager.get("CSARTracker"):flarePilot(data) - trigger.action.outTextForUnit(un:getID(), data.name..' has deployed a green flare', 10) - end - end - end - - function PlayerLogistics:unloadPilots(groupname) - local gr = Group.getByName(groupname) - if gr then - local un = gr:getUnit(1) - if un then - local pilots = self.carriedPilots[gr:getID()] - if not pilots or #pilots==0 then - trigger.action.outTextForUnit(un:getID(), 'No pilots onboard', 10) - return - end - - if Utils.isInAir(un) then - trigger.action.outTextForUnit(un:getID(), 'Can not unload pilot while in air', 10) - return - end - - if not self:isCargoDoorOpen(un) then - trigger.action.outTextForUnit(un:getID(), 'Can not unload pilot while cargo door closed', 10) - return - end - - local zn = ZoneCommand.getZoneOfUnit(un:getName()) - if not zn then - zn = CarrierCommand.getCarrierOfUnit(un:getName()) - end - - if not zn then - trigger.action.outTextForUnit(un:getID(), 'Can only unload extracted pilots while within a friendly zone', 10) - return - end - - if zn.side ~= 0 and zn.side ~= un:getCoalition()then - trigger.action.outTextForUnit(un:getID(), 'Can only unload extracted pilots while within a friendly zone', 10) - return - end - - zn:addResource(200*#pilots) - zn:refreshText() - - if un.getPlayerName then - local player = un:getPlayerName() - - local xp = #pilots*RewardDefinitions.actions.pilotExtract - - xp = xp * DependencyManager.get("PlayerTracker"):getPlayerMultiplier(player) - - DependencyManager.get("PlayerTracker"):addStat(player, math.floor(xp), PlayerTracker.statTypes.xp) - DependencyManager.get("MissionTracker"):tallyUnloadPilot(player, zn.name) - trigger.action.outTextForUnit(un:getID(), '+'..math.floor(xp)..' XP', 10) - end - - self.carriedPilots[gr:getID()] = {} - trigger.action.setUnitInternalCargo(un:getName(), 0) - trigger.action.outTextForUnit(un:getID(), 'Pilots unloaded', 10) - end - end - end - - function PlayerLogistics:extractPilot(groupname) - local gr = Group.getByName(groupname) - if gr then - local un = gr:getUnit(1) - if un then - if not self:canFitPersonnel(groupname, 1) then - trigger.action.outTextForUnit(un:getID(), 'Not enough free space onboard. (Need 1)', 10) - return - end - - timer.scheduleFunction(function(param,time) - local self = param.context - local un = param.unit - if not un then return end - if not un:isExist() then return end - local gr = un:getGroup() - - local data = DependencyManager.get("CSARTracker"):getClosestPilot(un:getPoint()) - - if not data or data.dist > 500 then - trigger.action.outTextForUnit(un:getID(), 'There is no pilot nearby that needs extraction', 10) - return - else - if not self:isCargoDoorOpen(un) then - trigger.action.outTextForUnit(un:getID(), 'Cargo door closed', 1) - elseif Utils.getAGL(un) > 70 then - trigger.action.outTextForUnit(un:getID(), 'Altitude too high (< 70 m). Current: '..string.format('%.2f',Utils.getAGL(un))..' m', 1) - elseif mist.vec.mag(un:getVelocity())>5 then - trigger.action.outTextForUnit(un:getID(), 'Moving too fast (< 5 m/s). Current: '..string.format('%.2f',mist.vec.mag(un:getVelocity()))..' m/s', 1) - else - if data.dist > 100 then - trigger.action.outTextForUnit(un:getID(), 'Too far (< 100m). Current: '..string.format('%.2f',data.dist)..' m', 1) - else - if not self.carriedPilots[gr:getID()] then self.carriedPilots[gr:getID()] = {} end - table.insert(self.carriedPilots[gr:getID()], data.name) - local player = un:getPlayerName() - DependencyManager.get("MissionTracker"):tallyLoadPilot(player, data) - DependencyManager.get("CSARTracker"):removePilot(data.name) - local weight = self:getCarriedPersonWeight(gr:getName()) - trigger.action.setUnitInternalCargo(un:getName(), weight) - trigger.action.outTextForUnit(un:getID(), data.name..' onboard. ('..weight..' kg)', 10) - return - end - end - end - - param.trys = param.trys - 1 - if param.trys > 0 then - return time+1 - end - end, {context = self, unit = un, trys = 60}, timer.getTime()+0.1) - end - end - end - - function PlayerLogistics:extractSquad(groupname) - local gr = Group.getByName(groupname) - if gr then - local un = gr:getUnit(1) - if un then - - if Utils.isInAir(un) then - trigger.action.outTextForUnit(un:getID(), 'Can not load infantry while in air', 10) - return - end - - if not self:isCargoDoorOpen(un) then - trigger.action.outTextForUnit(un:getID(), 'Can not load infantry while cargo door closed', 10) - return - end - - local squad, distance = DependencyManager.get("SquadTracker"):getClosestExtractableSquad(un:getPoint()) - if squad and distance < 50 then - local squadgr = Group.getByName(squad.name) - if squadgr and squadgr:isExist() then - local sqsize = squadgr:getSize() - if not self:canFitPersonnel(groupname, sqsize) then - trigger.action.outTextForUnit(un:getID(), 'Not enough free space onboard. (Need '..sqsize..')', 10) - return - end - - if not self.carriedInfantry[gr:getID()] then self.carriedInfantry[gr:getID()] = {} end - table.insert(self.carriedInfantry[gr:getID()],{type = PlayerLogistics.infantryTypes.extractable, size = sqsize, weight = sqsize * 100}) - - local weight = self:getCarriedPersonWeight(gr:getName()) - - trigger.action.setUnitInternalCargo(un:getName(), weight) - - local loadedInfName = PlayerLogistics.getInfantryName(PlayerLogistics.infantryTypes.extractable) - trigger.action.outTextForUnit(un:getID(), loadedInfName..' onboard. ('..weight..' kg)', 10) - - local player = un:getPlayerName() - DependencyManager.get("MissionTracker"):tallyLoadSquad(player, squad) - DependencyManager.get("SquadTracker"):removeSquad(squad.name) - - squadgr:destroy() - end - else - trigger.action.outTextForUnit(un:getID(), 'There is no infantry nearby that is ready to be extracted.', 10) - end - end - end - end - - function PlayerLogistics:loadInfantry(params) - if not ZoneCommand then return end - - local gr = Group.getByName(params.group) - local squadType = params.type - local squadName = PlayerLogistics.getInfantryName(squadType) - - local squadCost = 0 - local squadSize = 999999 - local squadWeight = 0 - if self.registeredSquadGroups[squadType] then - squadCost = self.registeredSquadGroups[squadType].cost - squadSize = self.registeredSquadGroups[squadType].size - squadWeight = self.registeredSquadGroups[squadType].weight - end - - if gr then - local un = gr:getUnit(1) - if un then - if Utils.isInAir(un) then - trigger.action.outTextForUnit(un:getID(), 'Can not load infantry while in air', 10) - return - end - - local zn = ZoneCommand.getZoneOfUnit(un:getName()) - if not zn then - zn = CarrierCommand.getCarrierOfUnit(un:getName()) - end - - if not zn then - trigger.action.outTextForUnit(un:getID(), 'Can only load infantry while within a friendly zone', 10) - return - end - - if zn.side ~= un:getCoalition() then - trigger.action.outTextForUnit(un:getID(), 'Can only load infantry while within a friendly zone', 10) - return - end - - if not self:isCargoDoorOpen(un) then - trigger.action.outTextForUnit(un:getID(), 'Can not load infantry while cargo door closed', 10) - return - end - - if zn:criticalOnSupplies() then - trigger.action.outTextForUnit(un:getID(), 'Can not load infantry, zone is low on resources', 10) - return - end - - if zn.resource < zn.spendTreshold + squadCost then - trigger.action.outTextForUnit(un:getID(), 'Can not afford to load '..squadName..' (Cost: '..squadCost..'). Resources would fall to a critical level.', 10) - return - end - - if not self:canFitPersonnel(params.group, squadSize) then - trigger.action.outTextForUnit(un:getID(), 'Not enough free space on board. (Need '..squadSize..')', 10) - return - end - - zn:removeResource(squadCost) - zn:refreshText() - if not self.carriedInfantry[gr:getID()] then self.carriedInfantry[gr:getID()] = {} end - table.insert(self.carriedInfantry[gr:getID()],{ type = squadType, size = squadSize, weight = squadWeight, loadedAt = zn }) - self.lastLoaded[gr:getID()] = zn - - local weight = self:getCarriedPersonWeight(gr:getName()) - trigger.action.setUnitInternalCargo(un:getName(), weight) - - local loadedInfName = PlayerLogistics.getInfantryName(squadType) - trigger.action.outTextForUnit(un:getID(), loadedInfName..' onboard. ('..weight..' kg)', 10) - end - end - end - - function PlayerLogistics:unloadInfantry(params) - if not ZoneCommand then return end - local groupname = params.group - local sqtype = params.type - - local gr = Group.getByName(groupname) - if gr then - local un = gr:getUnit(1) - if un then - if Utils.isInAir(un) then - trigger.action.outTextForUnit(un:getID(), 'Can not unload infantry while in air', 10) - return - end - - if not self:isCargoDoorOpen(un) then - trigger.action.outTextForUnit(un:getID(), 'Can not unload infantry while cargo door closed', 10) - return - end - - local carriedSquads = self.carriedInfantry[gr:getID()] - if not carriedSquads or #carriedSquads == 0 then - trigger.action.outTextForUnit(un:getID(), 'No infantry onboard', 10) - return - end - - local toUnload = carriedSquads - local remaining = {} - if sqtype then - toUnload = {} - local sqToUnload = nil - for _,sq in ipairs(carriedSquads) do - if sq.type == sqtype and not sqToUnload then - sqToUnload = sq - else - table.insert(remaining, sq) - end - end - - if sqToUnload then toUnload = { sqToUnload } end - end - - if #toUnload == 0 then - if sqtype then - local squadName = PlayerLogistics.getInfantryName(sqtype) - trigger.action.outTextForUnit(un:getID(), 'No '..squadName..' onboard.', 10) - else - trigger.action.outTextForUnit(un:getID(), 'No infantry onboard.', 10) - end - - return - end - - local zn = ZoneCommand.getZoneOfUnit(un:getName()) - if not zn then - zn = CarrierCommand.getCarrierOfUnit(un:getName()) - end - - for _, sq in ipairs(toUnload) do - local squadName = PlayerLogistics.getInfantryName(sq.type) - local lastLoad = sq.loadedAt - if lastLoad and zn and zn.side == un:getCoalition() and zn.name==lastLoad.name then - if self.registeredSquadGroups[sq.type] then - local cost = self.registeredSquadGroups[sq.type].cost - zn:addResource(cost) - zn:refreshText() - trigger.action.outTextForUnit(un:getID(), squadName..' unloaded', 10) - end - else - if sq.type == PlayerLogistics.infantryTypes.extractable then - if not zn then - trigger.action.outTextForUnit(un:getID(), 'Can only unload extracted infantry while within a friendly zone', 10) - table.insert(remaining, sq) - elseif zn.side ~= un:getCoalition() then - trigger.action.outTextForUnit(un:getID(), 'Can only unload extracted infantry while within a friendly zone', 10) - table.insert(remaining, sq) - else - trigger.action.outTextForUnit(un:getID(), 'Infantry recovered', 10) - zn:addResource(200) - zn:refreshText() - - if un.getPlayerName then - local player = un:getPlayerName() - local xp = RewardDefinitions.actions.squadExtract * DependencyManager.get("PlayerTracker"):getPlayerMultiplier(player) - - DependencyManager.get("PlayerTracker"):addStat(player, math.floor(xp), PlayerTracker.statTypes.xp) - DependencyManager.get("MissionTracker"):tallyUnloadSquad(player, zn.name, sq.type) - trigger.action.outTextForUnit(un:getID(), '+'..math.floor(xp)..' XP', 10) - end - end - elseif self.registeredSquadGroups[sq.type] then - local pos = Utils.getPointOnSurface(un:getPoint()) - - local error = DependencyManager.get("SquadTracker"):spawnInfantry(self.registeredSquadGroups[sq.type], pos) - - if not error then - trigger.action.outTextForUnit(un:getID(), squadName..' deployed', 10) - - if un.getPlayerName then - local player = un:getPlayerName() - local xp = RewardDefinitions.actions.squadDeploy * DependencyManager.get("PlayerTracker"):getPlayerMultiplier(player) - - DependencyManager.get("PlayerTracker"):addStat(player, math.floor(xp), PlayerTracker.statTypes.xp) - - if zn then - DependencyManager.get("MissionTracker"):tallyUnloadSquad(player, zn.name, sq.type) - else - DependencyManager.get("MissionTracker"):tallyUnloadSquad(player, '', sq.type) - end - trigger.action.outTextForUnit(un:getID(), '+'..math.floor(xp)..' XP', 10) - end - else - trigger.action.outTextForUnit(un:getID(), 'Failed to deploy squad, no suitable location nearby', 10) - table.insert(remaining, sq) - end - else - trigger.action.outText("ERROR: SQUAD TYPE NOT REGISTERED", 60) - end - end - end - - self.carriedInfantry[gr:getID()] = remaining - local weight = self:getCarriedPersonWeight(groupname) - trigger.action.setUnitInternalCargo(un:getName(), weight) - end - end - end - - function PlayerLogistics:unloadAll(groupname) - local gr = Group.getByName(groupname) - if gr then - local un = gr:getUnit(1) - if un then - local cargo = self.carriedCargo[gr:getID()] - local squad = self.carriedInfantry[gr:getID()] - local pilot = self.carriedPilots[gr:getID()] - - if cargo and cargo>0 then - self:unloadSupplies({group=groupname, amount=9999999}) - end - - if squad and #squad>0 then - self:unloadInfantry({group=groupname}) - end - - if pilot and #pilot>0 then - self:unloadPilots(groupname) - end - end - end - end - - function PlayerLogistics:cargoStatus(groupName) - local gr = Group.getByName(groupName) - if gr then - local un = gr:getUnit(1) - if un then - local onboard = self.carriedCargo[gr:getID()] - if onboard and onboard > 0 then - local weight = self.getWeight(onboard) - trigger.action.outTextForUnit(un:getID(), onboard..' supplies onboard. ('..weight..' kg)', 10) - else - local msg = '' - local squads = self.carriedInfantry[gr:getID()] - if squads and #squads>0 then - msg = msg..'Squads:\n' - - for _,squad in ipairs(squads) do - local infName = PlayerLogistics.getInfantryName(squad.type) - msg = msg..' \n'..infName..' (Size: '..squad.size..')' - end - end - - local pilots = self.carriedPilots[gr:getID()] - if pilots and #pilots>0 then - msg = msg.."\n\nPilots:\n" - for i,v in ipairs(pilots) do - msg = msg..'\n '..v - end - - end - - local max = PlayerLogistics.allowedTypes[un:getDesc().typeName].personCapacity - local occupied = self:getOccupiedPersonCapacity(groupName) - - msg = msg..'\n\nCapacity: '..occupied..'/'..max - - msg = msg..'\n('..self:getCarriedPersonWeight(groupName)..' kg)' - - if un:getDesc().typeName == 'Hercules' then - local preped = self.hercPreparedDrops[gr:getID()] - if preped then - if preped == 'supplies' then - msg = msg..'\nSupplies prepared for next drop.' - else - local squadName = PlayerLogistics.getInfantryName(preped) - msg = msg..'\n'..squadName..' prepared for next drop.' - end - end - end - - trigger.action.outTextForUnit(un:getID(), msg, 10) - end - end - end - end - - function PlayerLogistics:isCargoDoorOpen(unit) - if unit then - local tp = unit:getDesc().typeName - if tp == "Mi-8MT" then - if unit:getDrawArgumentValue(86) == 1 then return true end - if unit:getDrawArgumentValue(38) > 0.85 then return true end - elseif tp == "UH-1H" then - if unit:getDrawArgumentValue(43) == 1 then return true end - if unit:getDrawArgumentValue(44) == 1 then return true end - elseif tp == "Mi-24P" then - if unit:getDrawArgumentValue(38) == 1 then return true end - if unit:getDrawArgumentValue(86) == 1 then return true end - elseif tp == "Hercules" then - if unit:getDrawArgumentValue(1215) == 1 and unit:getDrawArgumentValue(1216) == 1 then return true end - elseif tp == "UH-60L" then - if unit:getDrawArgumentValue(401) == 1 then return true end - if unit:getDrawArgumentValue(402) == 1 then return true end - elseif tp == "SA342Mistral" then - if unit:getDrawArgumentValue(34) == 1 then return true end - if unit:getDrawArgumentValue(38) == 1 then return true end - elseif tp == "SA342L" then - if unit:getDrawArgumentValue(34) == 1 then return true end - if unit:getDrawArgumentValue(38) == 1 then return true end - elseif tp == "SA342M" then - if unit:getDrawArgumentValue(34) == 1 then return true end - if unit:getDrawArgumentValue(38) == 1 then return true end - elseif tp == "SA342Minigun" then - if unit:getDrawArgumentValue(34) == 1 then return true end - if unit:getDrawArgumentValue(38) == 1 then return true end - else - return true - end - end - end - - function PlayerLogistics:loadSupplies(params) - if not ZoneCommand then return end - - local groupName = params.group - local amount = params.amount - - local gr = Group.getByName(groupName) - if gr then - local un = gr:getUnit(1) - if un then - if not self:canFitCargo(groupName) then - trigger.action.outTextForUnit(un:getID(), 'Can not load cargo. Personnel onboard.', 10) - return - end - - if Utils.isInAir(un) then - trigger.action.outTextForUnit(un:getID(), 'Can not load supplies while in air', 10) - return - end - - local zn = ZoneCommand.getZoneOfUnit(un:getName()) - if not zn then - zn = CarrierCommand.getCarrierOfUnit(un:getName()) - end - - if not zn then - trigger.action.outTextForUnit(un:getID(), 'Can only load supplies while within a friendly zone', 10) - return - end - - if zn.side ~= un:getCoalition() then - trigger.action.outTextForUnit(un:getID(), 'Can only load supplies while within a friendly zone', 10) - return - end - - if not self:isCargoDoorOpen(un) then - trigger.action.outTextForUnit(un:getID(), 'Can not load supplies while cargo door closed', 10) - return - end - - if zn:criticalOnSupplies() then - trigger.action.outTextForUnit(un:getID(), 'Can not load supplies, zone is low on resources', 10) - return - end - - if zn.resource < zn.spendTreshold + amount then - trigger.action.outTextForUnit(un:getID(), 'Can not load supplies if resources would fall to a critical level.', 10) - return - end - - local carried = self.carriedCargo[gr:getID()] or 0 - if amount > zn.resource then - amount = zn.resource - end - - zn:removeResource(amount) - zn:refreshText() - self.carriedCargo[gr:getID()] = carried + amount - self.lastLoaded[gr:getID()] = zn - local onboard = self.carriedCargo[gr:getID()] - local weight = self.getWeight(onboard) - - if un:getDesc().typeName == "Hercules" then - local loadedInCrates = 0 - local ammo = un:getAmmo() - if ammo then - for _,load in ipairs(ammo) do - if load.desc.typeName == 'weapons.bombs.Generic Crate [20000lb]' then - loadedInCrates = 9000 * load.count - end - end - end - - local internal = 0 - if weight > loadedInCrates then - internal = weight - loadedInCrates - end - - trigger.action.setUnitInternalCargo(un:getName(), internal) - else - trigger.action.setUnitInternalCargo(un:getName(), weight) - end - - trigger.action.outTextForUnit(un:getID(), amount..' supplies loaded', 10) - trigger.action.outTextForUnit(un:getID(), onboard..' supplies onboard. ('..weight..' kg)', 10) - end - end - end - - function PlayerLogistics:unloadSupplies(params) - if not ZoneCommand then return end - - local groupName = params.group - local amount = params.amount - - local gr = Group.getByName(groupName) - if gr then - local un = gr:getUnit(1) - if un then - if Utils.isInAir(un) then - trigger.action.outTextForUnit(un:getID(), 'Can not unload supplies while in air', 10) - return - end - - local zn = ZoneCommand.getZoneOfUnit(un:getName()) - if not zn then - zn = CarrierCommand.getCarrierOfUnit(un:getName()) - end - - if not zn then - trigger.action.outTextForUnit(un:getID(), 'Can only unload supplies while within a friendly zone', 10) - return - end - - if zn.side ~= 0 and zn.side ~= un:getCoalition()then - trigger.action.outTextForUnit(un:getID(), 'Can only unload supplies while within a friendly zone', 10) - return - end - - if not self:isCargoDoorOpen(un) then - trigger.action.outTextForUnit(un:getID(), 'Can not unload supplies while cargo door closed', 10) - return - end - - if not self.carriedCargo[gr:getID()] or self.carriedCargo[gr:getID()] == 0 then - trigger.action.outTextForUnit(un:getID(), 'No supplies loaded', 10) - return - end - - local carried = self.carriedCargo[gr:getID()] - if amount > carried then - amount = carried - end - - self.carriedCargo[gr:getID()] = carried-amount - zn:addResource(amount) - - local lastLoad = self.lastLoaded[gr:getID()] - self:awardSupplyXP(lastLoad, zn, un, amount) - - zn:refreshText() - local onboard = self.carriedCargo[gr:getID()] - local weight = self.getWeight(onboard) - - if un:getDesc().typeName == "Hercules" then - local loadedInCrates = 0 - local ammo = un:getAmmo() - for _,load in ipairs(ammo) do - if load.desc.typeName == 'weapons.bombs.Generic Crate [20000lb]' then - loadedInCrates = 9000 * load.count - end - end - - local internal = 0 - if weight > loadedInCrates then - internal = weight - loadedInCrates - end - - trigger.action.setUnitInternalCargo(un:getName(), internal) - else - trigger.action.setUnitInternalCargo(un:getName(), weight) - end - - trigger.action.outTextForUnit(un:getID(), amount..' supplies unloaded', 10) - trigger.action.outTextForUnit(un:getID(), onboard..' supplies remaining onboard. ('..weight..' kg)', 10) - end - end - end -end - ------------------[[ END OF PlayerLogistics.lua ]]----------------- - - - -----------------[[ MarkerCommands.lua ]]----------------- MarkerCommands = {} @@ -4335,6 +4469,7 @@ do obj.reservedMissions = {} obj.isHeloSpawn = false obj.isPlaneSpawn = false + obj.spawnSurface = nil obj.zone = CustomZone:getByName(zonename) obj.products = {} @@ -4481,14 +4616,16 @@ do return nil end - function ZoneCommand.getClosestZoneToPoint(point) + function ZoneCommand.getClosestZoneToPoint(point, side) local minDist = 9999999 local closest = nil for i,v in pairs(ZoneCommand.allZones) do - local d = mist.utils.get2DDist(v.zone.point, point) - if d < minDist then - minDist = d - closest = v + if not side or side == v.side then + local d = mist.utils.get2DDist(v.zone.point, point) + if d < minDist then + minDist = d + closest = v + end end end @@ -4660,8 +4797,13 @@ do self:refreshSpawnBlocking() end - function ZoneCommand:reveal() - self.revealTime = 60*30 + function ZoneCommand:reveal(time) + local revtime = 30 + if time then + revtime = time + end + + self.revealTime = 60*revtime self:refreshText() end @@ -4702,7 +4844,7 @@ do if product.type == 'defense' or product.type == 'upgrade' then env.info('ZoneCommand: instantBuild ['..product.name..']') if self.built[product.name] == nil then - self.zone:spawnGroup(product) + self.zone:spawnGroup(product, self.spawnSurface) self.built[product.name] = product end elseif product.type == 'mission' then @@ -5101,10 +5243,10 @@ do elseif self.currentBuild.product.type == 'defense' or self.currentBuild.product.type=='upgrade' then if self.currentBuild.isRepair then if Group.getByName(self.currentBuild.product.name) then - self.zone:spawnGroup(self.currentBuild.product) + self.zone:spawnGroup(self.currentBuild.product, self.spawnSurface) end else - self.zone:spawnGroup(self.currentBuild.product) + self.zone:spawnGroup(self.currentBuild.product, self.spawnSurface) end self.built[self.currentBuild.product.name] = self.currentBuild.product @@ -6808,7 +6950,7 @@ do PlayerTracker.cmdShopPrices = { [PlayerTracker.cmdShopTypes.smoke] = 1, [PlayerTracker.cmdShopTypes.prio] = 10, - [PlayerTracker.cmdShopTypes.jtac] = 5, + [PlayerTracker.cmdShopTypes.jtac] = 20, [PlayerTracker.cmdShopTypes.bribe1] = 5, [PlayerTracker.cmdShopTypes.bribe2] = 10, [PlayerTracker.cmdShopTypes.artillery] = 15, @@ -7311,6 +7453,8 @@ do local cmdTokens = self.stats[player][PlayerTracker.statTypes.cmd] if cmdTokens and cost <= cmdTokens then + local canPurchase = true + if self.groupTgtMenus[gr:getID()] then missionCommands.removeItemForGroup(gr:getID(), self.groupTgtMenus[gr:getID()]) self.groupTgtMenus[gr:getID()] = nil @@ -7321,8 +7465,14 @@ do self.groupTgtMenus[gr:getID()] = MenuRegistry.showTargetZoneMenu(gr:getID(), "Smoke Marker target", function(params) CommandFunctions.smokeTargets(params.zone, 5) trigger.action.outTextForGroup(params.groupid, "Targets marked at "..params.zone.name.." with red smoke", 5) - end, 1, 1) - trigger.action.outTextForGroup(gr:getID(), "Select target from radio menu",10) + end, 1, 1, nil, nil, true) + + if self.groupTgtMenus[gr:getID()] then + trigger.action.outTextForGroup(gr:getID(), "Select target from radio menu",10) + else + trigger.action.outTextForGroup(gr:getID(), "No valid targets available",10) + canPurchase = false + end elseif itemType == PlayerTracker.cmdShopTypes.jtac then @@ -7332,7 +7482,13 @@ do trigger.action.outTextForGroup(params.groupid, "Reaper orbiting "..params.zone.name,5) end, 1, 1) - trigger.action.outTextForGroup(gr:getID(), "Select target from radio menu",10) + + if self.groupTgtMenus[gr:getID()] then + trigger.action.outTextForGroup(gr:getID(), "Select target from radio menu",10) + else + trigger.action.outTextForGroup(gr:getID(), "No valid targets available",10) + canPurchase = false + end elseif itemType== PlayerTracker.cmdShopTypes.prio then @@ -7340,7 +7496,13 @@ do BattlefieldManager.overridePriority(2, params.zone, 4) trigger.action.outTextForGroup(params.groupid, "Blue is concentrating efforts on "..params.zone.name.." for the next two hours", 5) end, nil, 1) - trigger.action.outTextForGroup(gr:getID(), "Select target from radio menu",10) + + if self.groupTgtMenus[gr:getID()] then + trigger.action.outTextForGroup(gr:getID(), "Select target from radio menu",10) + else + trigger.action.outTextForGroup(gr:getID(), "No valid targets available",10) + canPurchase = false + end elseif itemType== PlayerTracker.cmdShopTypes.bribe1 then @@ -7388,16 +7550,29 @@ do CommandFunctions.shellZone(params.zone, 50) end, 1, 1) - trigger.action.outTextForGroup(gr:getID(), "Select target zone from radio menu",10) + if self.groupTgtMenus[gr:getID()] then + trigger.action.outTextForGroup(gr:getID(), "Select target from radio menu",10) + else + trigger.action.outTextForGroup(gr:getID(), "No valid targets available",10) + canPurchase = false + end + elseif itemType == PlayerTracker.cmdShopTypes.sabotage1 then self.groupTgtMenus[gr:getID()] = MenuRegistry.showTargetZoneMenu(gr:getID(), "Sabotage target", function(params) CommandFunctions.sabotageZone(params.zone) end, 1, 1) - trigger.action.outTextForGroup(gr:getID(), "Select target zone from radio menu",10) + if self.groupTgtMenus[gr:getID()] then + trigger.action.outTextForGroup(gr:getID(), "Select target from radio menu",10) + else + trigger.action.outTextForGroup(gr:getID(), "No valid targets available",10) + canPurchase = false + end end - self.stats[player][PlayerTracker.statTypes.cmd] = self.stats[player][PlayerTracker.statTypes.cmd] - cost + if canPurchase then + self.stats[player][PlayerTracker.statTypes.cmd] = self.stats[player][PlayerTracker.statTypes.cmd] - cost + end else trigger.action.outTextForUnit(un:getID(), "Insufficient CMD to buy selected item", 5) end @@ -7619,6 +7794,350 @@ end +-----------------[[ ReconManager.lua ]]----------------- + +ReconManager = {} +do + ReconManager.groupMenus = {} + ReconManager.requiredProgress = 5*60 + ReconManager.updateFrequency = 5 + + function ReconManager:new() + local obj = {} + obj.recondata = {} + obj.cancelRequests = {} + + setmetatable(obj, self) + self.__index = self + DependencyManager.register("ReconManager", obj) + obj:init() + return obj + end + + function ReconManager:init() + MenuRegistry:register(7, function(event, context) + if event.id == world.event.S_EVENT_BIRTH and event.initiator and event.initiator.getPlayerName then + local player = event.initiator:getPlayerName() + if player then + local groupid = event.initiator:getGroup():getID() + local groupname = event.initiator:getGroup():getName() + + if ReconManager.groupMenus[groupid] then + missionCommands.removeItemForGroup(groupid, ReconManager.groupMenus[groupid]) + ReconManager.groupMenus[groupid] = nil + end + + if not ReconManager.groupMenus[groupid] then + local menu = missionCommands.addSubMenuForGroup(groupid, 'Recon') + missionCommands.addCommandForGroup(groupid, 'Start', menu, Utils.log(context.activateRecon), context, groupname) + missionCommands.addCommandForGroup(groupid, 'Cancel', menu, Utils.log(context.cancelRecon), context, groupname) + missionCommands.addCommandForGroup(groupid, 'Analyze', menu, Utils.log(context.analyzeData), context, groupname) + + ReconManager.groupMenus[groupid] = menu + end + end + elseif (event.id == world.event.S_EVENT_PLAYER_LEAVE_UNIT or event.id == world.event.S_EVENT_DEAD) and event.initiator and event.initiator.getPlayerName then + local player = event.initiator:getPlayerName() + if player then + local groupid = event.initiator:getGroup():getID() + + if ReconManager.groupMenus[groupid] then + missionCommands.removeItemForGroup(groupid, ReconManager.groupMenus[groupid]) + ReconManager.groupMenus[groupid] = nil + end + end + end + end, self) + end + + function ReconManager:activateRecon(groupname) + local gr = Group.getByName(groupname) + if gr then + local un = gr:getUnit(1) + if un and un:isExist() then + timer.scheduleFunction(function(param, time) + local cancelRequest = param.context.cancelRequests[param.groupname] + if cancelRequest and (timer.getAbsTime() - cancelRequest < 5) then + param.context.cancelRequests[param.groupname] = nil + return + end + + local shouldUpdateMsg = (timer.getAbsTime() - param.lastUpdate) > ReconManager.updateFrequency + + local withinParameters = false + + local pgr = Group.getByName(param.groupname) + if not pgr then + return + end + local pun = pgr:getUnit(1) + if not pun or not pun:isExist() then + return + end + + local closestZone = nil + if param.lastZone then + if param.lastZone.side == 0 or param.lastZone.side == pun:getCoalition() then + local msg = param.lastZone.name..' is no longer controlled by the enemy.' + msg = msg..'\n Discarding data.' + trigger.action.outTextForUnit(pun:getID(), msg, 20) + closestZone = ZoneCommand.getClosestZoneToPoint(pun:getPoint(), Utils.getEnemy(pun:getCoalition())) + else + closestZone = param.lastZone + end + else + closestZone = ZoneCommand.getClosestZoneToPoint(pun:getPoint(), Utils.getEnemy(pun:getCoalition())) + end + + if not closestZone then + return + end + + local stats = ReconManager.getAircraftStats(pun:getDesc().typeName) + local currentParameters = { + distance = 0, + deviation = 0, + percent_visible = 0 + } + + currentParameters.distance = mist.utils.get2DDist(pun:getPoint(), closestZone.zone.point) + + local unitPos = pun:getPosition() + local unitheading = math.deg(math.atan2(unitPos.x.z, unitPos.x.x)) + local bearing = Utils.getBearing(pun:getPoint(), closestZone.zone.point) + + currentParameters.deviation = math.abs(Utils.getHeadingDiff(unitheading, bearing)) + + local unitsCount = 0 + local visibleCount = 0 + for _,product in pairs(closestZone.built) do + if product.side ~= pun:getCoalition() then + local gr = Group.getByName(product.name) + if gr then + for _,enemyUnit in ipairs(gr:getUnits()) do + unitsCount = unitsCount+1 + local from = pun:getPoint() + from.y = from.y+1.5 + local to = enemyUnit:getPoint() + to.y = to.y+1.5 + if land.isVisible(from, to) then + visibleCount = visibleCount+1 + end + end + else + local st = StaticObject.getByName(product.name) + if st then + unitsCount = unitsCount+1 + local from = pun:getPoint() + from.y = from.y+1.5 + local to = st:getPoint() + to.y = to.y+1.5 + if land.isVisible(from, to) then + visibleCount = visibleCount+1 + end + end + end + end + end + + if unitsCount > 0 and visibleCount > 0 then + currentParameters.percent_visible = visibleCount/unitsCount + end + + if currentParameters.distance < (stats.minDist * 1000) and currentParameters.percent_visible >= 0.5 then + if stats.maxDeviation then + if currentParameters.deviation <= stats.maxDeviation then + withinParameters = true + end + else + withinParameters = true + end + end + + if withinParameters then + if not param.lastZone then + param.lastZone = closestZone + end + + param.timeout = 300 + + local speed = stats.recon_speed * currentParameters.percent_visible + param.progress = math.min(param.progress + speed, ReconManager.requiredProgress) + + if shouldUpdateMsg then + local msg = "[Recon: "..param.lastZone.name..']' + msg = msg.."\nProgress: "..string.format('%.1f', (param.progress/ReconManager.requiredProgress)*100)..'%\n' + msg = msg.."\nVisibility: "..string.format('%.1f', currentParameters.percent_visible*100)..'%' + trigger.action.outTextForUnit(pun:getID(), msg, ReconManager.updateFrequency) + + param.lastUpdate = timer.getAbsTime() + end + else + param.timeout = param.timeout - 1 + if shouldUpdateMsg then + + local msg = "[Nearest enemy zone: "..closestZone.name..']' + + if param.lastZone then + msg = "[Recon in progress: "..param.lastZone.name..']' + msg = msg.."\nProgress: "..string.format('%.1f', (param.progress/ReconManager.requiredProgress)*100)..'%\n' + end + + if stats.maxDeviation then + msg = msg.."\nDeviation: "..string.format('%.1f', currentParameters.deviation)..' deg (under '..stats.maxDeviation..' deg)' + end + + msg = msg.."\nDistance: "..string.format('%.2f', currentParameters.distance/1000)..'km (under '..stats.minDist..' km)' + msg = msg.."\nVisibility: "..string.format('%.1f', currentParameters.percent_visible*100)..'% (min 50%)' + msg = msg.."\n\nTime left: "..param.timeout..' sec' + trigger.action.outTextForUnit(pun:getID(), msg, ReconManager.updateFrequency) + + param.lastUpdate = timer.getAbsTime() + end + end + + if param.progress >= ReconManager.requiredProgress then + + local msg = "Data recorded for "..param.lastZone.name + msg = msg.."\nAnalyze data at a friendly zone to recover results" + trigger.action.outTextForUnit(pun:getID(), msg, 20) + + param.context.recondata[param.groupname] = param.lastZone + return + end + + if param.timeout > 0 then + return time+1 + end + + local msg = "Recon cancelled." + if param.progress > 0 then + msg = msg.." Data lost." + end + trigger.action.outTextForUnit(pun:getID(), msg, 20) + + end, {context = self, groupname = groupname, timeout = 300, progress = 0, lastZone = nil, lastUpdate = timer.getAbsTime()-5}, timer.getTime()+1) + end + end + end + + function ReconManager:cancelRecon(groupname) + self.cancelRequests[groupname] = timer.getAbsTime() + end + + function ReconManager:analyzeData(groupname) + local gr = Group.getByName(groupname) + if not gr then return end + local un = gr:getUnit(1) + if not un or not un:isExist() then return end + local player = un:getPlayerName() + + local zn = ZoneCommand.getZoneOfUnit(un:getName()) + if not zn then + zn = CarrierCommand.getCarrierOfUnit(un:getName()) + end + + if not zn or not Utils.isLanded(un, zn.isCarrier) then + trigger.action.outTextForUnit(un:getID(), "Recon data can only be analyzed while landed in a friendly zone.", 5) + return + end + + local data = self.recondata[groupname] + if data then + if data.side == 0 or data.side == un:getCoalition() then + local msg = param.lastZone.name..' is no longer controlled by the enemy.' + msg = msg..'\n Data discarded.' + trigger.action.outTextForUnit(un:getID(), msg, 20) + else + local wasRevealed = data.revealTime > 60 + data:reveal() + + if data:hasUnitWithAttributeOnSide({'Buildings'}, 1) then + local tgt = data:getRandomUnitWithAttributeOnSide({'Buildings'}, 1) + if tgt then + MissionTargetRegistry.addStrikeTarget(tgt, data) + trigger.action.outTextForUnit(un:getID(), tgt.display..' discovered at '..data.name, 20) + end + end + + local xp = RewardDefinitions.actions.recon * DependencyManager.get("PlayerTracker"):getPlayerMultiplier(player) + if wasRevealed then + xp = xp/10 + end + + DependencyManager.get("PlayerTracker"):addStat(player, math.floor(xp), PlayerTracker.statTypes.xp) + local msg = '+'..math.floor(xp)..' XP' + trigger.action.outTextForUnit(un:getID(), msg, 10) + + DependencyManager.get("MissionTracker"):tallyRecon(player, data.name, zn.name) + end + + self.recondata[groupname] = nil + else + trigger.action.outTextForUnit(un:getID(), "No data recorded.", 5) + end + end + + function ReconManager.getAircraftStats(aircraftType) + local stats = ReconManager.aircraftStats[aircraftType] + if not stats then + stats = { recon_speed = 1, minDist = 5 } + end + + return stats + end + + ReconManager.aircraftStats = { + ['A-10A'] = { recon_speed = 1, minDist = 5, }, + ['A-10C'] = { recon_speed = 2, minDist = 20, }, + ['A-10C_2'] = { recon_speed = 2, minDist = 20, }, + ['A-4E-C'] = { recon_speed = 1, minDist = 5, }, + ['AJS37'] = { recon_speed = 10, minDist = 10, }, + ['AV8BNA'] = { recon_speed = 2, minDist = 20, }, + ['C-101CC'] = { recon_speed = 1, minDist = 5, }, + ['F-14A-135-GR'] = { recon_speed = 10, minDist = 5, }, + ['F-14B'] = { recon_speed = 10, minDist = 5, }, + ['F-15C'] = { recon_speed = 1, minDist = 5, }, + ['F-16C_50'] = { recon_speed = 2, minDist = 20, }, + ['F-5E-3'] = { recon_speed = 1, minDist = 5, }, + ['F-86F Sabre'] = { recon_speed = 1, minDist = 5, }, + ['FA-18C_hornet'] = { recon_speed = 2, minDist = 20, }, + ['Hercules'] = { recon_speed = 1, minDist = 5, }, + ['J-11A'] = { recon_speed = 1, minDist = 5, }, + ['JF-17'] = { recon_speed = 2, minDist = 20, }, + ['L-39ZA'] = { recon_speed = 1, minDist = 5, }, + ['M-2000C'] = { recon_speed = 1, minDist = 5, }, + ['Mirage-F1BE'] = { recon_speed = 1, minDist = 5, }, + ['Mirage-F1CE'] = { recon_speed = 1, minDist = 5, }, + ['Mirage-F1EE'] = { recon_speed = 1, minDist = 5, }, + ['MiG-15bis'] = { recon_speed = 1, minDist = 5, }, + ['MiG-19P'] = { recon_speed = 1, minDist = 5, }, + ['MiG-21Bis'] = { recon_speed = 1, minDist = 5, }, + ['MiG-29A'] = { recon_speed = 1, minDist = 5, }, + ['MiG-29G'] = { recon_speed = 1, minDist = 5, }, + ['MiG-29S'] = { recon_speed = 1, minDist = 5, }, + ['Su-25'] = { recon_speed = 1, minDist = 5, }, + ['Su-25T'] = { recon_speed = 2, minDist = 10, }, + ['Su-27'] = { recon_speed = 1, minDist = 5, }, + ['Su-33'] = { recon_speed = 1, minDist = 5, }, + ['T-45'] = { recon_speed = 1, minDist = 5, }, + ['AH-64D_BLK_II'] = { recon_speed = 5, minDist = 15, maxDeviation = 120 }, + ['Ka-50'] = { recon_speed = 5, minDist = 15, maxDeviation = 35 }, + ['Ka-50_3'] = { recon_speed = 5, minDist = 15, maxDeviation = 35 }, + ['Mi-24P'] = { recon_speed = 5, minDist = 10, maxDeviation = 60 }, + ['Mi-8MT'] = { recon_speed = 1, minDist = 5, maxDeviation = 30 }, + ['SA342L'] = { recon_speed = 5, minDist = 10, maxDeviation = 120 }, + ['SA342M'] = { recon_speed = 10, minDist = 15, maxDeviation = 120 }, + ['SA342Minigun'] = { recon_speed = 2, minDist = 5, maxDeviation = 45 }, + ['UH-1H'] = { recon_speed = 1, minDist = 5, maxDeviation = 30 }, + ['UH-60L'] = { recon_speed = 1, minDist = 5, maxDeviation = 30 } + } +end + +-----------------[[ END OF ReconManager.lua ]]----------------- + + + -----------------[[ MissionTargetRegistry.lua ]]----------------- MissionTargetRegistry = {} @@ -7694,8 +8213,8 @@ do MissionTargetRegistry.strikeTargetExpireTime = 60*60 MissionTargetRegistry.strikeTargets = {} - function MissionTargetRegistry.addStrikeTarget(target, zone, isDeep) - MissionTargetRegistry.strikeTargets[target.name] = {data=target, zone=zone, addedTime = timer.getAbsTime(), isDeep = isDeep} + function MissionTargetRegistry.addStrikeTarget(target, zone) + MissionTargetRegistry.strikeTargets[target.name] = {data=target, zone=zone, addedTime = timer.getAbsTime()} env.info('MissionTargetRegistry - strike target added '..target.name) end @@ -7709,7 +8228,7 @@ do MissionTargetRegistry.removeStrikeTarget(v) elseif timer.getAbsTime() - v.addedTime > MissionTargetRegistry.strikeTargetExpireTime then MissionTargetRegistry.removeStrikeTarget(v) - elseif v.isDeep == isDeep then + elseif not isDeep or v.zone.distToFront >= 2 then return true end end @@ -7729,7 +8248,7 @@ do MissionTargetRegistry.removeStrikeTarget(v) elseif timer.getAbsTime() - v.addedTime > MissionTargetRegistry.strikeTargetExpireTime then MissionTargetRegistry.removeStrikeTarget(v) - elseif v.isDeep == isDeep then + elseif not isDeep or v.zone.distToFront >= 2 then table.insert(targets, v) end end @@ -7753,7 +8272,7 @@ do MissionTargetRegistry.removeStrikeTarget(v) elseif timer.getAbsTime() - v.addedTime > MissionTargetRegistry.strikeTargetExpireTime then MissionTargetRegistry.removeStrikeTarget(v) - elseif v.isDeep == isDeep then + else table.insert(targets, v) end end @@ -7774,10 +8293,10 @@ do env.info('MissionTargetRegistry - squad added '..squad.name) end - function MissionTargetRegistry.squadsReadyToExtract() + function MissionTargetRegistry.squadsReadyToExtract(onside) for i,v in pairs(MissionTargetRegistry.extractableSquads) do local gr = Group.getByName(i) - if gr and gr:isExist() and gr:getSize() > 0 then + if gr and gr:isExist() and gr:getSize() > 0 and gr:getCoalition() == onside then return true end end @@ -7785,11 +8304,11 @@ do return false end - function MissionTargetRegistry.getRandomSquad() + function MissionTargetRegistry.getRandomSquad(onside) local targets = {} for i,v in pairs(MissionTargetRegistry.extractableSquads) do local gr = Group.getByName(i) - if gr and gr:isExist() and gr:getSize() > 0 then + if gr and gr:isExist() and gr:getSize() > 0 and gr:getCoalition() == onside then table.insert(targets, v) end end @@ -8061,7 +8580,17 @@ do local save = self.data if save.squadTracker then for i,v in pairs(save.squadTracker) do - local sdata = DependencyManager.get("PlayerLogistics").registeredSquadGroups[v.type] + local sdata = nil + if v.isAISpawned then + if v.type == PlayerLogistics.infantryTypes.ambush then + sdata = GroupMonitor.aiSquads.ambush[v.side] + else + sdata = GroupMonitor.aiSquads.manpads[v.side] + end + else + sdata = DependencyManager.get("PlayerLogistics").registeredSquadGroups[v.type] + end + if sdata then v.data = sdata DependencyManager.get("SquadTracker"):restoreInfantry(v) @@ -8094,60 +8623,75 @@ do local carrier = CarrierCommand.getCarrierByName(v.name) if carrier then carrier.resource = math.min(v.resource, carrier.maxResource) + carrier.weaponStocks = v.weaponStocks or {} carrier:refreshSpawnBlocking() - local unit = Unit.getByName(v.name) - if unit then - if not v.isAlive then - unit:destroy() - else - local vars = { - groupName = unit:getGroup():getName(), - point = v.position.p, - action = 'teleport', - heading = math.atan2(v.position.x.z, v.position.x.x), - initTasks = false, - route = {} - } - - mist.teleportToPoint(vars) + local group = Group.getByName(v.name) + if group then + local vars = { + groupName = group:getName(), + point = v.position.p, + action = 'teleport', + heading = math.atan2(v.position.x.z, v.position.x.x), + initTasks = false, + route = {} + } + + mist.teleportToPoint(vars) - timer.scheduleFunction(function(param, time) - param:setupRadios() - end, carrier, timer.getTime()+3) + timer.scheduleFunction(function(param, time) + param:setupRadios() + end, carrier, timer.getTime()+3) - carrier.navigation.waypoints = v.navigation.waypoints - carrier.navigation.currentWaypoint = nil - carrier.navigation.nextWaypoint = v.navigation.currentWaypoint - carrier.navigation.loop = v.navigation.loop + carrier.navigation.waypoints = v.navigation.waypoints + carrier.navigation.currentWaypoint = nil + carrier.navigation.nextWaypoint = v.navigation.currentWaypoint + carrier.navigation.loop = v.navigation.loop - if v.supportFlightStates then - for sfsName, sfsData in pairs(v.supportFlightStates) do - local sflight = carrier.supportFlights[sfsName] - if sflight then - if sfsData.state == CarrierCommand.supportStates.inair and sfsData.targetName and sfsData.position then - local zn = ZoneCommand.getZoneByName(sfsData.targetName) - if not zn then - zn = CarrierCommand.getCarrierByName(sfsData.targetName) - end + if v.supportFlightStates then + for sfsName, sfsData in pairs(v.supportFlightStates) do + local sflight = carrier.supportFlights[sfsName] + if sflight then + if sfsData.state == CarrierCommand.supportStates.inair and sfsData.targetName and sfsData.position then + local zn = ZoneCommand.getZoneByName(sfsData.targetName) + if not zn then + zn = CarrierCommand.getCarrierByName(sfsData.targetName) + end - if zn then - CarrierCommand.spawnSupport(sflight, zn, sfsData) - end - elseif sfsData.state == CarrierCommand.supportStates.takeoff and sfsData.targetName then - local zn = ZoneCommand.getZoneByName(sfsData.targetName) - if not zn then - zn = CarrierCommand.getCarrierByName(sfsData.targetName) - end + if zn then + CarrierCommand.spawnSupport(sflight, zn, sfsData) + end + elseif sfsData.state == CarrierCommand.supportStates.takeoff and sfsData.targetName then + local zn = ZoneCommand.getZoneByName(sfsData.targetName) + if not zn then + zn = CarrierCommand.getCarrierByName(sfsData.targetName) + end - if zn then - CarrierCommand.spawnSupport(sflight, zn) - end + if zn then + CarrierCommand.spawnSupport(sflight, zn) end end end end end + + if v.aliveGroupMembers then + timer.scheduleFunction(function(param, time) + local g = Group.getByName(param.name) + if not g then return end + local grMembers = g:getUnits() + local liveMembers = {} + for _, agm in ipairs(param.aliveGroupMembers) do + liveMembers[agm] = true + end + + for _, gm in ipairs(grMembers) do + if not liveMembers[gm:getName()] then + gm:destroy() + end + end + end, v, timer.getTime()+4) + end end end end @@ -8197,11 +8741,13 @@ do if b.type == 'defense' then local typeList = {} local gr = Group.getByName(b.name) - for _,unit in ipairs(gr:getUnits()) do - table.insert(typeList, unit:getDesc().typeName) - end + if gr then + for _,unit in ipairs(gr:getUnits()) do + table.insert(typeList, unit:getDesc().typeName) + end - tosave.zones[i].built[n] = typeList + tosave.zones[i].built[n] = typeList + end else tosave.zones[i].built[n] = true end @@ -8243,6 +8789,10 @@ do end end + if v.spawnedSquad then + tosave.activeGroups[i].spawnedSquad = true + end + if v.target then tosave.activeGroups[i].targetName = v.target.name end @@ -8308,42 +8858,52 @@ do remainingStateTime = v.remainingStateTime, position = v.position, name = v.name, - type = v.data.type + type = v.data.type, + side = v.data.side, + isAISpawned = v.data.isAISpawned, + discovered = v.discovered } end tosave.carriers = {} for cname,cdata in pairs(CarrierCommand.getAllCarriers()) do - local unit = Unit.getByName(cdata.name) + local group = Group.getByName(cdata.name) + if group and group:isExist() then - tosave.carriers[cname] = { - name = cdata.name, - resource = cdata.resource, - isAlive = unit ~= nil, - position = unit:getPosition(), - navigation = cdata.navigation, - supportFlightStates = {} - } - - for spname, spdata in pairs(cdata.supportFlights) do - tosave.carriers[cname].supportFlightStates[spname] = { - name = spdata.name, - state = spdata.state, - lastStateDuration = timer.getAbsTime() - spdata.lastStateTime, - returning = spdata.returning + tosave.carriers[cname] = { + name = cdata.name, + resource = cdata.resource, + position = group:getUnit(1):getPosition(), + navigation = cdata.navigation, + supportFlightStates = {}, + weaponStocks = cdata.weaponStocks, + aliveGroupMembers = {} } - if spdata.target then - tosave.carriers[cname].supportFlightStates[spname].targetName = spdata.target.name + for _, gm in ipairs(group:getUnits()) do + table.insert(tosave.carriers[cname].aliveGroupMembers, gm:getName()) end - if spdata.state == CarrierCommand.supportStates.inair then - local spgr = Group.getByName(spname) - if spgr and spgr:isExist() and spgr:getSize()>0 then - local spun = spgr:getUnit(1) - if spun and spun:isExist() then - tosave.carriers[cname].supportFlightStates[spname].position = spun:getPoint() - tosave.carriers[cname].supportFlightStates[spname].heading = math.atan2(spun:getPosition().x.z, spun:getPosition().x.x) + for spname, spdata in pairs(cdata.supportFlights) do + tosave.carriers[cname].supportFlightStates[spname] = { + name = spdata.name, + state = spdata.state, + lastStateDuration = timer.getAbsTime() - spdata.lastStateTime, + returning = spdata.returning + } + + if spdata.target then + tosave.carriers[cname].supportFlightStates[spname].targetName = spdata.target.name + end + + if spdata.state == CarrierCommand.supportStates.inair then + local spgr = Group.getByName(spname) + if spgr and spgr:isExist() and spgr:getSize()>0 then + local spun = spgr:getUnit(1) + if spun and spun:isExist() then + tosave.carriers[cname].supportFlightStates[spname].position = spun:getPoint() + tosave.carriers[cname].supportFlightStates[spname].heading = math.atan2(spun:getPosition().x.z, spun:getPosition().x.x) + end end end end @@ -8431,6 +8991,31 @@ do dataCategory= TemplateDB.type.group } + TemplateDB.templates["ambush-squad-red"] = { + units = { + "Paratrooper RPG-16", + "Paratrooper RPG-16", + "Infantry AK ver2", + "Infantry AK", + "Infantry AK ver3" + }, + skill = "Good", + invisible = true, + dataCategory= TemplateDB.type.group + } + + TemplateDB.templates["manpads-squad-red"] = { + units = { + "Infantry AK ver3", + "Infantry AK ver2", + "SA-18 Igla manpad", + "SA-18 Igla manpad", + "Infantry AK" + }, + skill = "Good", + dataCategory= TemplateDB.type.group + } + TemplateDB.templates["engineer-squad"] = { units = { "Soldier M4 GRG", @@ -8741,6 +9326,7 @@ do CommandFunctions.jtac:deployAtZone(zone) CommandFunctions.jtac:showMenu() CommandFunctions.jtac:setLifeTime(60) + zone:reveal(60) end end @@ -8780,10 +9366,13 @@ do function CommandFunctions.sabotageZone(zone) trigger.action.outText("Saboteurs have been dispatched to "..zone.name, 10) local delay = math.random(5*60, 7*60) + local isReveled = zone.revealTime > 0 timer.scheduleFunction(function(param, time) - if math.random() < 0.1 then - trigger.action.outText("Saboteurs have been caught by the enemy before they could complete their mission", 10) - return + if not param.isRevealed then + if math.random() < 0.3 then + trigger.action.outText("Saboteurs have been caught by the enemy before they could complete their mission", 10) + return + end end local zone = param.zone @@ -8819,13 +9408,15 @@ do trigger.action.outText("Saboteurs have succesfully triggered explosions at "..zone.name, 10) end - end, { zone = zone }, timer.getTime()+delay) + end, { zone = zone , isRevealed = isReveled}, timer.getTime()+delay) end function CommandFunctions.shellZone(zone, count) local minutes = math.random(3,7) local seconds = math.random(-30,30) local delay = (minutes*60)+seconds + + local isRevealed = zone.revealTime > 0 trigger.action.outText("Artillery preparing to fire on "..zone.name.." ETA: "..minutes.." minutes", 10) local positions = {} @@ -8852,11 +9443,16 @@ do timer.scheduleFunction(function(param, time) param.count = param.count - 1 + local accuracy = 50 + if param.isRevealed then + accuracy = 10 + end + local selected = param.positions[math.random(1,#param.positions)] local offsetPos = { - x = selected.x + math.random(-50,50), + x = selected.x + math.random(-accuracy,accuracy), y = selected.y, - z = selected.z + math.random(-50,50) + z = selected.z + math.random(-accuracy,accuracy) } offsetPos.y = land.getHeight({x = offsetPos.x, y = offsetPos.z}) @@ -8868,7 +9464,7 @@ do else trigger.action.outText("Artillery finished firing on "..param.zone.name, 10) end - end, { positions = positions, count = count, zone = zone}, timer.getTime()+delay) + end, { positions = positions, count = count, zone = zone, isRevealed = isRevealed}, timer.getTime()+delay) end end @@ -9348,7 +9944,8 @@ do cap = 'CAP', awacs = 'AWACS', tanker = 'Tanker', - transport = 'Transport' + transport = 'Transport', + mslstrike = 'Cruise Missiles' } CarrierCommand.supportStates = { @@ -9377,6 +9974,8 @@ do obj.isHeloSpawn = true obj.isPlaneSpawn = true obj.supportFlights = {} + obj.extraSupports = {} + obj.weaponStocks = {} obj.navigation = { currentWaypoint = nil, @@ -9447,6 +10046,10 @@ do local unit = Unit.getByName(self.name) if not unit then self:clearDrawings() + local gr = Group.getByName(self.name) + if gr and gr:isExist() then + TaskExtensions.stopCarrier(gr) + end return end @@ -9469,6 +10072,13 @@ do for _, data in pairs(self.supportFlights) do self:processAir(data) end + + + for wep, stock in pairs(self.weaponStocks) do + local gr = Unit.getByName(self.name):getGroup() + local realstock = Utils.getAmmo(gr, wep) + self.weaponStocks[wep] = math.min(stock, realstock) + end end local function setState(group, state) @@ -9688,6 +10298,63 @@ do if gr then gr:destroy() end end + function CarrierCommand:addExtraSupport(name, cost, type, data) + self.extraSupports[name] = { + name = name, + cost = cost, + type = type, + target = nil, + carrier = self + } + + for i,v in pairs(data) do + self.extraSupports[name][i] = v + end + end + + function CarrierCommand:setWPStock(wpname, amount) + self.weaponStocks[wpname] = amount + end + + function CarrierCommand:callExtraSupport(data, groupname) + local playerGroup = Group.getByName(groupname) + if not playerGroup then return end + + if self.resource < data.cost then + trigger.action.outTextForGroup(playerGroup:getID(), self.name..' does not have enough resources for '..data.name, 10) + return + end + + local cru = Unit.getByName(self.name) + if not cru or not cru:isExist() then return end + + local crg = cru:getGroup() + + local ammo = self.weaponStocks[data.wpType] or 0 + if ammo < data.salvo then + trigger.action.outTextForGroup(playerGroup:getID(), data.name..' is not available at this time.', 10) + return + end + + local success = MenuRegistry.showTargetZoneMenu(playerGroup:getID(), "Select "..data.name..'('..data.type..") target", function(params) + local cru = Unit.getByName(params.data.carrier.name) + if not cru or not cru:isExist() then return end + local crg = cru:getGroup() + + TaskExtensions.fireAtTargets(crg, params.zone.built, params.data.salvo) + if params.data.carrier.weaponStocks[params.data.wpType] then + params.data.carrier.weaponStocks[params.data.wpType] = params.data.carrier.weaponStocks[params.data.wpType] - params.data.salvo + end + end, 1, nil, data, nil, true) + + if success then + self:removeResource(data.cost) + trigger.action.outTextForGroup(playerGroup:getID(), 'Select target for '..data.name..' ('..data.type..') from radio menu.', 10) + else + trigger.action.outTextForGroup(playerGroup:getID(), 'No valid targets for '..data.name..' ('..data.type..')', 10) + end + end + function CarrierCommand:callSupport(data, groupname) local playerGroup = Group.getByName(groupname) if not playerGroup then return end @@ -9697,7 +10364,7 @@ do return end - if self.resource <= data.cost then + if self.resource < data.cost then trigger.action.outTextForGroup(playerGroup:getID(), self.name..' does not have enough resources to deploy '..data.name, 10) return end @@ -9705,10 +10372,11 @@ do local targetCoalition = nil local minDistToFront = nil local includeCarriers = nil + local onlyRevealed = nil if data.type == CarrierCommand.supportTypes.strike then targetCoalition = 1 - minDistToFront = 1 + onlyRevealed = true elseif data.type == CarrierCommand.supportTypes.cap then minDistToFront = 1 includeCarriers = true @@ -9722,13 +10390,17 @@ do targetCoalition = {0,2} end - MenuRegistry.showTargetZoneMenu(playerGroup:getID(), "Select "..data.name..'('..data.type..") target", function(params) + local success = MenuRegistry.showTargetZoneMenu(playerGroup:getID(), "Select "..data.name..'('..data.type..") target", function(params) CarrierCommand.spawnSupport(params.data, params.zone) trigger.action.outTextForGroup(params.groupid, params.data.name..'('..params.data.type..') heading to '..params.zone.name, 10) - end, targetCoalition, minDistToFront, data, includeCarriers) + end, targetCoalition, minDistToFront, data, includeCarriers, onlyRevealed) - self:removeResource(data.cost) - trigger.action.outTextForGroup(playerGroup:getID(), 'Select target for '..data.name..' ('..data.type..') from radio menu.', 20) + if success then + self:removeResource(data.cost) + trigger.action.outTextForGroup(playerGroup:getID(), 'Select target for '..data.name..' ('..data.type..') from radio menu.', 10) + else + trigger.action.outTextForGroup(playerGroup:getID(), 'No valid targets for '..data.name..' ('..data.type..')', 10) + end end local function getDefaultPos(savedData) @@ -9926,6 +10598,43 @@ do end end + if Utils.getTableSize(self.extraSupports) > 0 then + local extras = {} + for _, data in pairs(self.extraSupports) do + if data.cost <= self.resource then + if data.type == CarrierCommand.supportTypes.mslstrike then + local cru = Unit.getByName(self.name) + if cru and cru:isExist() then + local crg = cru:getGroup() + local remaining = self.weaponStocks[data.wpType] or 0 + if remaining > data.salvo then + table.insert(extras, data) + end + end + end + end + end + + table.sort(extras, function(a,b) return a.name 0 then + msg = msg..'\n\n Other:' + for _,data in ipairs(extras) do + if data.type == CarrierCommand.supportTypes.mslstrike then + local cru = Unit.getByName(self.name) + if cru and cru:isExist() then + local crg = cru:getGroup() + local remaining = self.weaponStocks[data.wpType] or 0 + if remaining > data.salvo then + remaining = math.floor(remaining/data.salvo) + msg = msg..'\n '..data.name..' ('..data.type..') ['..data.cost..'] ('..remaining..' left)' + end + end + end + end + end + end + trigger.action.outTextForGroup(gr:getID(), msg, 20) end end @@ -10108,6 +10817,17 @@ do local name = data.name..' ('..data.type..') ['..data.cost..']' missionCommands.addCommandForGroup(groupid, name, supm, Utils.log(carrier.callSupport), carrier, data, groupname) end + + local extras = {} + for _, data in pairs(carrier.extraSupports) do + table.insert(extras, data) + end + + table.sort(extras, function(a,b) return a.name updateFrequency - - for name, unit in pairs(self.mission.players) do - if unit and unit:isExist() then - local dist = mist.utils.get2DDist(unit:getPoint(), self.param.target.zone.point) - if dist < self.param.proxDist then - local unitPos = unit:getPosition() - local unitheading = math.deg(math.atan2(unitPos.x.z, unitPos.x.x)) - local bearing = Utils.getBearing(unit:getPoint(), self.param.target.zone.point) - - local diff = Utils.getHeadingDiff(unitheading, bearing) - - if math.abs(diff) <= self.param.allowedDeviation then - local unitsCount = 0 - local visibleCount = 0 - for _,product in pairs(self.param.target.built) do - if product.side ~= unit:getCoalition() then - local gr = Group.getByName(product.name) - if gr then - for _,enemyUnit in ipairs(gr:getUnits()) do - unitsCount = unitsCount+1 - local from = unit:getPoint() - from.y = from.y+1.5 - local to = enemyUnit:getPoint() - to.y = to.y+1.5 - if land.isVisible(from, to) then - visibleCount = visibleCount+1 - end - end - else - local st = StaticObject.getByName(product.name) - if st then - unitsCount = unitsCount+1 - local from = unit:getPoint() - from.y = from.y+1.5 - local to = st:getPoint() - to.y = to.y+1.5 - if land.isVisible(from, to) then - visibleCount = visibleCount+1 - end - end - end - end - end - - local percentVisible = 0 - if unitsCount > 0 and visibleCount > 0 then - percentVisible = visibleCount/unitsCount - if percentVisible > 0.5 then - self.param.amount = self.param.amount - percentVisible - else - - end - env.info('Scout_Helo - player can see '..string.format('%.2f',percentVisible)..'%') - end - - - if shouldUpdateMsg then - if visibleCount == 0 then - local prg = string.format('%.1f',((self.param.maxAmount - self.param.amount)/self.param.maxAmount)*100) - trigger.action.outTextForUnit(unit:getID(), 'No enemy visible.\nProgress: '..prg..'%', updateFrequency) - else - local percent = string.format('%.1f',percentVisible*100) - - local prg = string.format('%.1f',((self.param.maxAmount - self.param.amount)/self.param.maxAmount)*100) - trigger.action.outTextForUnit(unit:getID(), percent..'% of enemies visible.\nProgress: '..prg..'%', updateFrequency) - end - end - else - if shouldUpdateMsg then - local m = 'Within range\nTurn heading: '..math.floor(Utils.getBearing(unit:getPoint(), self.param.target.zone.point)) - trigger.action.outTextForUnit(unit:getID(), m, updateFrequency) - end - end - else - if shouldUpdateMsg then - local dstkm = string.format('%.2f',dist/1000) - local dstnm = string.format('%.2f',dist/1852) - - local m = 'Distance: ' - m = m..dstkm..'km | '..dstnm..'nm' - - m = m..'\nBearing: '..math.floor(Utils.getBearing(unit:getPoint(), self.param.target.zone.point)) - trigger.action.outTextForUnit(unit:getID(), m, updateFrequency) - end - end - end - end - - if shouldUpdateMsg then - self.param.lastUpdate = timer.getAbsTime() - end - - if self.param.amount <= 0 then + if self.param.reconData then self.isComplete = true return true end @@ -11631,15 +12247,13 @@ do strike_hard = 'strike_hard', -- destroy all structures at zone A and turn it neutral deep_strike = 'deep_strike', -- destroy specific structure taken from strike queue - show LatLon and Alt in mission description - recon_plane ='recon_plane', -- overly target zone and survive - recon_plane_deep = 'recon_plane_deep', -- overfly zone where distance from front == 2, add target to a strike queue, stays there until building exists, or 1hr passes anti_runway = 'anti_runway', -- drop at least X anti runway bombs on runway zone (if player unit launches correct weapon, track, if agl>10m check if in zone, tally), define list of runway zones somewhere supply_easy = 'supply_easy', -- transfer resources to zone A(low supply) supply_hard = 'supply_hard', -- transfer resources to zone A(low supply), high resource number escort = 'escort', -- follow and protect friendly convoy until they get to target OR 10 minutes pass - csar = 'csar', -- extract specific pilot to friendly zone, track friendly pilots ejected - scout_helo = 'scout_helo', -- within X km, facing Y angle +-, % of enemy units in LOS progress faster + csar = 'csar', -- extract specific pilot to friendly zone, track friendly pilots ejected + recon = 'recon', -- conduct recon extraction = 'extraction', -- extract a deployed squad to friendly zone, generate mission if squad has extractionReady state deploy_squad = 'deploy_squad', -- deploy squad to zone } @@ -11990,6 +12604,18 @@ do end end + function Mission:tallyRecon(player, targetzone, analyzezonename) + for _,obj in ipairs(self.objectives) do + if not obj.isComplete and not obj.isFailed then + if obj.type == ObjReconZone:getType() then + if obj.param.target.name == targetzone then + obj.param.reconData = targetzone + end + end + end + end + end + function Mission:tallyLoadSquad(player, squad) for _,obj in ipairs(self.objectives) do if not obj.isComplete and not obj.isFailed then @@ -13081,209 +13707,42 @@ end ------------------[[ Missions/Recon_Plane.lua ]]----------------- +-----------------[[ Missions/Recon.lua ]]----------------- -Recon_Plane = Mission:new() +Recon = Mission:new() do - function Recon_Plane.canCreate() + function Recon.canCreate() local zoneNum = 0 for _,zone in pairs(ZoneCommand.getAllZones()) do - if zone.side == 1 and zone.distToFront == 0 then + if zone.side == 1 and zone.distToFront == 0 and zone.revealTime == 0 then return true end end end - function Recon_Plane:getMissionName() + function Recon:getMissionName() return 'Recon' end - function Recon_Plane:isUnitTypeAllowed(unit) - return unit:hasAttribute('Planes') + function Recon:isUnitTypeAllowed(unit) + return true end - function Recon_Plane:generateObjectives() + function Recon:isInstantReward() + return true + end + + function Recon:generateObjectives() self.completionType = Mission.completion_type.any local description = '' local viableZones = {} local secondaryViableZones = {} for _,zone in pairs(ZoneCommand.getAllZones()) do - if zone.side == 1 and zone.distToFront == 0 then - if zone.revealTime <= 60*5 then - table.insert(viableZones, zone) - else - table.insert(secondaryViableZones, zone) - end + if zone.side == 1 and zone.distToFront == 0 and zone.revealTime == 0 then + table.insert(viableZones, zone) end end - if #viableZones == 0 then - viableZones = secondaryViableZones - end - - - if #viableZones > 0 then - local choice1 = math.random(1,#viableZones) - local zn1 = viableZones[choice1] - - local recon = ObjFlyToZoneSequence:new() - recon:initialize(self,{ - waypoints = { - [1] = {zone = zn1, complete = false} - }, - failZones = { - [1] = {zn1} - } - }) - - table.insert(self.objectives, recon) - description = description..' Overfly '..zn1.name..'\n' - end - - self.description = self.description..description - end - - function Recon_Plane:objectiveCompletedCallback(objective) - if objective.type == ObjFlyToZoneSequence:getType() then - local firstWP = objective.param.waypoints[1] - - if firstWP and firstWP.zone:hasUnitWithAttributeOnSide({'Buildings'}, 1) then - local tgt = firstWP.zone:getRandomUnitWithAttributeOnSide({'Buildings'}, 1) - if tgt then - MissionTargetRegistry.addStrikeTarget(tgt, firstWP.zone, false) - self:pushMessageToPlayers(tgt.display..' discovered at '..firstWP.zone.name) - firstWP.zone:reveal() - end - end - end - end -end - ------------------[[ END OF Missions/Recon_Plane.lua ]]----------------- - - - ------------------[[ Missions/Deep_Recon_Plane.lua ]]----------------- - -Deep_Recon_Plane = Mission:new() -do - function Deep_Recon_Plane.canCreate() - local zoneNum = 0 - for _,zone in pairs(ZoneCommand.getAllZones()) do - if zone.side == 1 and zone.distToFront == 2 then - return true - end - end - end - - function Deep_Recon_Plane:getMissionName() - return 'Deep Recon' - end - - function Deep_Recon_Plane:isUnitTypeAllowed(unit) - return unit:hasAttribute('Planes') - end - - function Deep_Recon_Plane:generateObjectives() - self.completionType = Mission.completion_type.any - local description = '' - local viableZones = {} - local secondaryViableZones = {} - for _,zone in pairs(ZoneCommand.getAllZones()) do - if zone.side == 1 and zone.distToFront == 2 then - if zone.revealTime <= 60*5 then - table.insert(viableZones, zone) - else - table.insert(secondaryViableZones, zone) - end - end - end - - if #viableZones == 0 then - viableZones = secondaryViableZones - end - - if #viableZones > 0 then - local choice1 = math.random(1,#viableZones) - local zn1 = viableZones[choice1] - - local recon = ObjFlyToZoneSequence:new() - recon:initialize(self, { - waypoints = { - [1] = {zone = zn1, complete = false} - }, - failZones = { - [1] = {zn1} - } - }) - - table.insert(self.objectives, recon) - description = description..' Overfly '..zn1.name..'\n' - end - - self.description = self.description..description - end - - function Deep_Recon_Plane:objectiveCompletedCallback(objective) - if objective.type == ObjFlyToZoneSequence:getType() then - local firstWP = objective.param.waypoints[1] - - if firstWP and firstWP.zone:hasUnitWithAttributeOnSide({'Buildings'}, 1) then - local tgt = firstWP.zone:getRandomUnitWithAttributeOnSide({'Buildings'}, 1) - if tgt then - MissionTargetRegistry.addStrikeTarget(tgt, firstWP.zone, true) - self:pushMessageToPlayers(tgt.display..' discovered at '..firstWP.zone.name) - firstWP.zone:reveal() - end - end - end - end -end - ------------------[[ END OF Missions/Deep_Recon_Plane.lua ]]----------------- - - - ------------------[[ Missions/Scout_Helo.lua ]]----------------- - -Scout_Helo = Mission:new() -do - function Scout_Helo.canCreate() - local zoneNum = 0 - for _,zone in pairs(ZoneCommand.getAllZones()) do - if zone.side == 1 and zone.distToFront == 0 then - return true - end - end - end - - function Scout_Helo:getMissionName() - return 'Recon' - end - - function Scout_Helo:isUnitTypeAllowed(unit) - return unit:hasAttribute('Helicopters') - end - - function Scout_Helo:generateObjectives() - self.completionType = Mission.completion_type.any - local description = '' - local viableZones = {} - local secondaryViableZones = {} - for _,zone in pairs(ZoneCommand.getAllZones()) do - if zone.side == 1 and zone.distToFront == 0 then - if zone.revealTime <= 60*5 then - table.insert(viableZones, zone) - else - table.insert(secondaryViableZones, zone) - end - end - end - - if #viableZones == 0 then - viableZones = secondaryViableZones - end - if #viableZones > 0 then local choice1 = math.random(1,#viableZones) local zn1 = viableZones[choice1] @@ -13291,11 +13750,6 @@ do local recon = ObjReconZone:new() recon:initialize(self, { target = zn1, - maxAmount = 60*1.5, - amount = 60*1.5, - allowedDeviation = 20, - proxDist = 10000, - lastUpdate = timer.getAbsTime(), failZones = { [1] = {zn1} } @@ -13307,22 +13761,9 @@ do self.description = self.description..description end - - function Scout_Helo:objectiveCompletedCallback(objective) - if objective.type == ObjReconZone:getType() then - if objective.param.target:hasUnitWithAttributeOnSide({'Buildings'}, 1) then - local tgt = objective.param.target:getRandomUnitWithAttributeOnSide({'Buildings'}, 1) - if tgt then - MissionTargetRegistry.addStrikeTarget(tgt, objective.param.target, false) - self:pushMessageToPlayers(tgt.display..' discovered at '..objective.param.target.name) - objective.param.target:reveal() - end - end - end - end end ------------------[[ END OF Missions/Scout_Helo.lua ]]----------------- +-----------------[[ END OF Missions/Recon.lua ]]----------------- @@ -13509,7 +13950,7 @@ end Extraction = Mission:new() do function Extraction.canCreate() - return MissionTargetRegistry.squadsReadyToExtract() + return MissionTargetRegistry.squadsReadyToExtract(2) end function Extraction:getMissionName() @@ -13531,8 +13972,8 @@ do self.completionType = Mission.completion_type.all local description = '' - if MissionTargetRegistry.squadsReadyToExtract() then - local tgt = MissionTargetRegistry.getRandomSquad() + if MissionTargetRegistry.squadsReadyToExtract(2) then + local tgt = MissionTargetRegistry.getRandomSquad(2) if tgt then local extract = ObjExtractSquad:new() extract:initialize(self, { @@ -13692,13 +14133,11 @@ do [Mission.types.strike_medium] = { xp = { low = 20, high = 30, boost = 0 } }, [Mission.types.strike_hard] = { xp = { low = 30, high = 40, boost = 0 } }, [Mission.types.deep_strike] = { xp = { low = 30, high = 40, boost = 0 } }, - [Mission.types.recon_plane] = { xp = { low = 20, high = 30, boost = 0 } }, - [Mission.types.recon_plane_deep]= { xp = { low = 30, high = 40, boost = 0 } }, [Mission.types.anti_runway] = { xp = { low = 20, high = 30, boost = 25 } }, [Mission.types.supply_easy] = { xp = { low = 10, high = 20, boost = 0 } }, [Mission.types.supply_hard] = { xp = { low = 20, high = 30, boost = 0 } }, [Mission.types.escort] = { xp = { low = 20, high = 30, boost = 0 } }, - [Mission.types.scout_helo] = { xp = { low = 20, high = 30, boost = 0 } }, + [Mission.types.recon] = { xp = { low = 20, high = 30, boost = 0 } }, [Mission.types.csar] = { xp = { low = 20, high = 30, boost = 0 } }, [Mission.types.extraction] = { xp = { low = 20, high = 30, boost = 0 } }, [Mission.types.deploy_squad] = { xp = { low = 20, high = 30, boost = 0 } } @@ -13709,7 +14148,8 @@ do squadDeploy = 150, squadExtract = 150, supplyRatio = 0.06, - supplyBoost = 0.5 + supplyBoost = 0.5, + recon = 150 } end @@ -13737,10 +14177,8 @@ do [Mission.types.dead] = 1, [Mission.types.escort] = 2, [Mission.types.tarcap] = 1, - [Mission.types.recon_plane] = 3, - [Mission.types.recon_plane_deep] = 3, [Mission.types.deep_strike] = 3, - [Mission.types.scout_helo] = 3, + [Mission.types.recon] = 3, [Mission.types.bai] = 1, [Mission.types.anti_runway] = 2, [Mission.types.csar] = 1, @@ -14263,12 +14701,8 @@ do newmis = Escort:new(misid, misType) elseif misType == Mission.types.tarcap then newmis = TARCAP:new(misid, misType, self.activeMissions) - elseif misType == Mission.types.recon_plane then - newmis = Recon_Plane:new(misid, misType) - elseif misType == Mission.types.recon_plane_deep then - newmis = Deep_Recon_Plane:new(misid, misType) - elseif misType == Mission.types.scout_helo then - newmis = Scout_Helo:new(misid, misType) + elseif misType == Mission.types.recon then + newmis = Recon:new(misid, misType) elseif misType == Mission.types.bai then newmis = BAI:new(misid, misType) elseif misType == Mission.types.anti_runway then @@ -14384,6 +14818,17 @@ do end end + function MissionTracker:tallyRecon(player, targetzone, analyzezonename) + env.info("MissionTracker - tallyRecon: "..player.." analyzed "..targetzone.." recon data at "..analyzezonename) + for _,m in pairs(self.activeMissions) do + if m.players[player] then + if m.state == Mission.states.active then + m:tallyRecon(player, targetzone, analyzezonename) + end + end + end + end + function MissionTracker:activateOrJoinMissionForGroup(code, groupname) if groupname then env.info('MissionTracker - activateOrJoinMissionForGroup: '..tostring(groupname)..' requested activate or join '..code) @@ -14570,12 +15015,8 @@ do return Escort.canCreate() elseif misType == Mission.types.tarcap then return TARCAP.canCreate(self.activeMissions) - elseif misType == Mission.types.recon_plane then - return Recon_Plane.canCreate() - elseif misType == Mission.types.recon_plane_deep then - return Deep_Recon_Plane.canCreate() - elseif misType == Mission.types.scout_helo then - return Scout_Helo.canCreate() + elseif misType == Mission.types.recon then + return Recon.canCreate() elseif misType == Mission.types.bai then return BAI.canCreate() elseif misType == Mission.types.anti_runway then @@ -14642,7 +15083,7 @@ do function SquadTracker:restoreInfantry(save) - Spawner.createObject(save.name, save.data.name, save.position, 2, 20, 30,{ + Spawner.createObject(save.name, save.data.name, save.position, save.side, 20, 30,{ [land.SurfaceType.LAND] = true, [land.SurfaceType.ROAD] = true, [land.SurfaceType.RUNWAY] = true, @@ -14653,6 +15094,8 @@ do position = save.position, state = save.state, remainingStateTime=save.remainingStateTime, + shouldDiscover = save.discovered, + discovered = save.discovered, data = save.data } @@ -14666,7 +15109,7 @@ do function SquadTracker:spawnInfantry(infantryData, position) local callsign = self:generateCallsign() if callsign then - Spawner.createObject(callsign, infantryData.name, position, 2, 20, 30,{ + Spawner.createObject(callsign, infantryData.name, position, infantryData.side, 20, 30,{ [land.SurfaceType.LAND] = true, [land.SurfaceType.ROAD] = true, [land.SurfaceType.RUNWAY] = true, @@ -14710,12 +15153,12 @@ do end end - function SquadTracker:getClosestExtractableSquad(sourcePoint) + function SquadTracker:getClosestExtractableSquad(sourcePoint, onside) local minDist = 99999999 local squad = nil for i,v in pairs(self.activeInfantrySquads) do - if v.state == 'extractReady' then + if v.state == 'extractReady' and v.data.side == onside then local gr = Group.getByName(v.name) if gr and gr:getSize()>0 then local dist = mist.utils.get2DDist(sourcePoint, gr:getUnit(1):getPoint()) @@ -14803,7 +15246,7 @@ do if v:hasUnitWithAttributeOnSide({'Buildings'}, v.side) then local tgt = v:getRandomUnitWithAttributeOnSide({'Buildings'}, v.side) if tgt then - MissionTargetRegistry.addStrikeTarget(tgt, v, v.distToFront >= 2) + MissionTargetRegistry.addStrikeTarget(tgt, v) end end end @@ -14814,12 +15257,62 @@ do else env.info('SquadTracker - '..squad.name..'('..squad.data.type..') not in zone, cant infiltrate') end + elseif squad.data.type == PlayerLogistics.infantryTypes.ambush then + local cnt = gr:getController() + cnt:setCommand({ + id = 'SetInvisible', + params = { + value = false + } + }) end squad.state = 'extractReady' squad.remainingStateTime = squad.data.extracttime MissionTargetRegistry.addSquad(squad) - end + else + if squad.data.type == PlayerLogistics.infantryTypes.ambush then + if not squad.discovered then + local frcnt = gr:getUnit(1):getController() + local targets = frcnt:getDetectedTargets() + local isTargetClose = false + if #targets > 0 then + for _,tgt in ipairs(targets) do + if tgt.visible and tgt.object then + if tgt.object.isExist and tgt.object:isExist() and tgt.object.getCoalition and tgt.object:getCoalition()~=gr:getCoalition() and + Object.getCategory(tgt.object) == 1 then + local dist = mist.utils.get3DDist(gr:getUnit(1):getPoint(), tgt.object:getPoint()) + if dist < 100 then + isTargetClose = true + break + end + end + end + end + end + + if isTargetClose then + squad.discovered = true + local cnt = gr:getController() + cnt:setCommand({ + id = 'SetInvisible', + params = { + value = false + } + }) + end + elseif squad.shouldDiscover then + squad.shouldDiscover = nil + local cnt = gr:getController() + cnt:setCommand({ + id = 'SetInvisible', + params = { + value = false + } + }) + end + end + end elseif squad.state == 'extractReady' then if squad.remainingStateTime <= 0 then env.info('SquadTracker - '..squad.name..'('..squad.data.type..') extract time elapsed, group MIA') From ff096b7d8d6a4052fea05cc0722a44ed63dcd730 Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Sat, 24 Feb 2024 08:01:46 +0200 Subject: [PATCH 204/243] Will now generate multiple missions per squadron in Pretense campaigns to ensure most mission types are available. --- game/pretense/pretenseaircraftgenerator.py | 290 +++++++++++++++------ 1 file changed, 208 insertions(+), 82 deletions(-) diff --git a/game/pretense/pretenseaircraftgenerator.py b/game/pretense/pretenseaircraftgenerator.py index a8c45dde..f1b0d90c 100644 --- a/game/pretense/pretenseaircraftgenerator.py +++ b/game/pretense/pretenseaircraftgenerator.py @@ -281,9 +281,45 @@ class PretenseAircraftGenerator: num_of_bai = 0 num_of_strike = 0 num_of_cap = 0 + sead_tasks = [FlightType.SEAD, FlightType.SEAD_SWEEP, FlightType.SEAD_ESCORT] + strike_tasks = [ + FlightType.STRIKE, + FlightType.OCA_RUNWAY, + FlightType.OCA_AIRCRAFT, + ] + patrol_tasks = [ + FlightType.BARCAP, + FlightType.TARCAP, + FlightType.ESCORT, + FlightType.INTERCEPTION, + ] + sead_capable_cp = False + cas_capable_cp = False + bai_capable_cp = False + strike_capable_cp = False + patrol_capable_cp = False + + # First check what are the capabilities of the squadrons on this CP + for squadron in cp.squadrons: + for task in sead_tasks: + if task in squadron.auto_assignable_mission_types: + sead_capable_cp = True + for task in strike_tasks: + if task in squadron.auto_assignable_mission_types: + if not squadron.aircraft.helicopter: + strike_capable_cp = True + for task in patrol_tasks: + if task in squadron.auto_assignable_mission_types: + if not squadron.aircraft.helicopter: + patrol_capable_cp = True + if FlightType.CAS in squadron.auto_assignable_mission_types: + cas_capable_cp = True + if FlightType.BAI in squadron.auto_assignable_mission_types: + bai_capable_cp = True random_squadron_list = list(cp.squadrons) random.shuffle(random_squadron_list) + # Then plan transports, AEWC and tankers for squadron in random_squadron_list: # Intentionally don't spawn anything at OffMapSpawns in Pretense if isinstance(squadron.location, OffMapSpawn): @@ -291,14 +327,6 @@ class PretenseAircraftGenerator: if cp.coalition != squadron.coalition: continue - squadron.owned_aircraft += ( - self.game.settings.pretense_ai_aircraft_per_flight - ) - squadron.untasked_aircraft += ( - self.game.settings.pretense_ai_aircraft_per_flight - ) - squadron.populate_for_turn_0(False) - package = Package(cp, squadron.flight_db, auto_asap=False) mission_types = squadron.auto_assignable_mission_types aircraft_per_flight = 1 if squadron.aircraft.helicopter and ( @@ -311,52 +339,6 @@ class PretenseAircraftGenerator: or FlightType.AIR_ASSAULT in mission_types ): flight_type = FlightType.TRANSPORT - elif ( - FlightType.SEAD in mission_types - or FlightType.SEAD_SWEEP in mission_types - or FlightType.SEAD_ESCORT in mission_types - ) and num_of_sead < self.game.settings.pretense_sead_flights_per_cp: - flight_type = FlightType.SEAD - num_of_sead += 1 - aircraft_per_flight = self.game.settings.pretense_ai_aircraft_per_flight - elif ( - FlightType.DEAD in mission_types - and num_of_sead < self.game.settings.pretense_sead_flights_per_cp - ): - flight_type = FlightType.DEAD - num_of_sead += 1 - aircraft_per_flight = self.game.settings.pretense_ai_aircraft_per_flight - elif ( - (squadron.aircraft.helicopter and (FlightType.ESCORT in mission_types)) - or (FlightType.CAS in mission_types) - and num_of_cas < self.game.settings.pretense_cas_flights_per_cp - ): - flight_type = FlightType.CAS - num_of_cas += 1 - aircraft_per_flight = self.game.settings.pretense_ai_aircraft_per_flight - elif ( - FlightType.BAI in mission_types - ) and num_of_bai < self.game.settings.pretense_bai_flights_per_cp: - flight_type = FlightType.BAI - num_of_bai += 1 - aircraft_per_flight = self.game.settings.pretense_ai_aircraft_per_flight - elif ( - FlightType.STRIKE in mission_types - or FlightType.OCA_RUNWAY in mission_types - or FlightType.OCA_AIRCRAFT in mission_types - ) and num_of_strike < self.game.settings.pretense_strike_flights_per_cp: - flight_type = FlightType.STRIKE - num_of_strike += 1 - aircraft_per_flight = self.game.settings.pretense_ai_aircraft_per_flight - elif ( - FlightType.BARCAP in mission_types - or FlightType.TARCAP in mission_types - or FlightType.ESCORT in mission_types - or FlightType.INTERCEPTION in mission_types - ) and num_of_cap < self.game.settings.pretense_barcap_flights_per_cp: - flight_type = FlightType.BARCAP - num_of_cap += 1 - aircraft_per_flight = self.game.settings.pretense_ai_aircraft_per_flight elif FlightType.AEWC in mission_types: flight_type = FlightType.AEWC aircraft_per_flight = PRETENSE_AI_AWACS_PER_FLIGHT @@ -366,37 +348,181 @@ class PretenseAircraftGenerator: else: continue - if flight_type == FlightType.TRANSPORT: - flight = Flight( - package, - squadron, - aircraft_per_flight, - FlightType.PRETENSE_CARGO, - StartType.IN_FLIGHT, - divert=cp, - ) - package.add_flight(flight) - flight.state = Navigating(flight, self.game.settings, waypoint_index=1) - else: - flight = Flight( - package, - squadron, - aircraft_per_flight, - flight_type, - StartType.COLD, - divert=cp, - ) - package.add_flight(flight) - flight.state = WaitingForStart( - flight, self.game.settings, self.game.conditions.start_time - ) - - print( - f"Generated flight for {flight_type} flying {squadron.aircraft.display_name} at {squadron.location.name}" + self.generate_pretense_flight( + ato, cp, squadron, aircraft_per_flight, flight_type ) - ato.add_package(package) + # Then plan SEAD and DEAD, if capable + if sead_capable_cp: + while num_of_sead < self.game.settings.pretense_sead_flights_per_cp: + for squadron in random_squadron_list: + # Intentionally don't spawn anything at OffMapSpawns in Pretense + if isinstance(squadron.location, OffMapSpawn): + continue + if cp.coalition != squadron.coalition: + continue + + mission_types = squadron.auto_assignable_mission_types + if ( + ( + FlightType.SEAD in mission_types + or FlightType.SEAD_SWEEP in mission_types + or FlightType.SEAD_ESCORT in mission_types + ) + and num_of_sead + < self.game.settings.pretense_sead_flights_per_cp + ): + flight_type = FlightType.SEAD + num_of_sead += 1 + aircraft_per_flight = ( + self.game.settings.pretense_ai_aircraft_per_flight + ) + elif ( + FlightType.DEAD in mission_types + and num_of_sead + < self.game.settings.pretense_sead_flights_per_cp + ): + flight_type = FlightType.DEAD + num_of_sead += 1 + aircraft_per_flight = ( + self.game.settings.pretense_ai_aircraft_per_flight + ) + else: + continue + self.generate_pretense_flight( + ato, cp, squadron, aircraft_per_flight, flight_type + ) + # Then plan Strike, if capable + if strike_capable_cp: + while num_of_strike < self.game.settings.pretense_strike_flights_per_cp: + for squadron in random_squadron_list: + # Intentionally don't spawn anything at OffMapSpawns in Pretense + if isinstance(squadron.location, OffMapSpawn): + continue + if cp.coalition != squadron.coalition: + continue + + mission_types = squadron.auto_assignable_mission_types + for task in strike_tasks: + if task in mission_types and not squadron.aircraft.helicopter: + flight_type = FlightType.STRIKE + num_of_strike += 1 + aircraft_per_flight = ( + self.game.settings.pretense_ai_aircraft_per_flight + ) + self.generate_pretense_flight( + ato, cp, squadron, aircraft_per_flight, flight_type + ) + break + # Then plan air-to-air, if capable + if patrol_capable_cp: + while num_of_cap < self.game.settings.pretense_barcap_flights_per_cp: + for squadron in random_squadron_list: + # Intentionally don't spawn anything at OffMapSpawns in Pretense + if isinstance(squadron.location, OffMapSpawn): + continue + if cp.coalition != squadron.coalition: + continue + + mission_types = squadron.auto_assignable_mission_types + for task in patrol_tasks: + if task in mission_types and not squadron.aircraft.helicopter: + flight_type = FlightType.BARCAP + num_of_cap += 1 + aircraft_per_flight = ( + self.game.settings.pretense_ai_aircraft_per_flight + ) + self.generate_pretense_flight( + ato, cp, squadron, aircraft_per_flight, flight_type + ) + break + # Then plan CAS, if capable + if cas_capable_cp: + while num_of_cas < self.game.settings.pretense_cas_flights_per_cp: + for squadron in random_squadron_list: + # Intentionally don't spawn anything at OffMapSpawns in Pretense + if isinstance(squadron.location, OffMapSpawn): + continue + if cp.coalition != squadron.coalition: + continue + + mission_types = squadron.auto_assignable_mission_types + if ( + squadron.aircraft.helicopter + and (FlightType.ESCORT in mission_types) + ) or (FlightType.CAS in mission_types): + flight_type = FlightType.CAS + num_of_cas += 1 + aircraft_per_flight = ( + self.game.settings.pretense_ai_aircraft_per_flight + ) + self.generate_pretense_flight( + ato, cp, squadron, aircraft_per_flight, flight_type + ) + # And finally, plan BAI, if capable + if bai_capable_cp: + while num_of_bai < self.game.settings.pretense_bai_flights_per_cp: + for squadron in random_squadron_list: + # Intentionally don't spawn anything at OffMapSpawns in Pretense + if isinstance(squadron.location, OffMapSpawn): + continue + if cp.coalition != squadron.coalition: + continue + + mission_types = squadron.auto_assignable_mission_types + if FlightType.BAI in mission_types: + flight_type = FlightType.BAI + num_of_bai += 1 + aircraft_per_flight = ( + self.game.settings.pretense_ai_aircraft_per_flight + ) + self.generate_pretense_flight( + ato, cp, squadron, aircraft_per_flight, flight_type + ) + return + def generate_pretense_flight( + self, + ato: AirTaskingOrder, + cp: ControlPoint, + squadron: Squadron, + aircraft_per_flight: int, + flight_type: FlightType, + ) -> None: + squadron.owned_aircraft += self.game.settings.pretense_ai_aircraft_per_flight + squadron.untasked_aircraft += self.game.settings.pretense_ai_aircraft_per_flight + squadron.populate_for_turn_0(False) + package = Package(cp, squadron.flight_db, auto_asap=False) + if flight_type == FlightType.TRANSPORT: + flight = Flight( + package, + squadron, + aircraft_per_flight, + FlightType.PRETENSE_CARGO, + StartType.IN_FLIGHT, + divert=cp, + ) + package.add_flight(flight) + flight.state = Navigating(flight, self.game.settings, waypoint_index=1) + else: + flight = Flight( + package, + squadron, + aircraft_per_flight, + flight_type, + StartType.COLD, + divert=cp, + ) + package.add_flight(flight) + flight.state = WaitingForStart( + flight, self.game.settings, self.game.conditions.start_time + ) + + print( + f"Generated flight for {flight_type} flying {squadron.aircraft.display_name} at {squadron.location.name}" + ) + ato.add_package(package) + def generate_pretense_aircraft_for_other_side( self, cp: ControlPoint, coalition: Coalition, ato: AirTaskingOrder ) -> None: From 64b1410de89464d4cc053da027e7c39ab462c179 Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Sat, 6 Apr 2024 15:46:11 +0300 Subject: [PATCH 205/243] Implemented support for player controllable carriers in Pretense campaigns. This functionality can be enabled or disabled in settings, because the controllable carriers in Pretense do not build and deploy AI missions autonomously, so the old functionality is retained. Added new options in settings: - Carriers steam into wind - Navmesh to use for Pretense carrier zones - Remove ground spawn statics, including invisible FARPs, at airbases. - Percentage of randomly selected aircraft types (only for generated squadrons) intended to allow the user to increase aircraft variety. Will now store the ICLS channel and Link4 frequency in missiondata.py CarrierInfo. Implemented artillery groups as Pretense garrisons. Artillery groups are spawned by the Artillery Bunker. Will now also ensure that the logistics units spawned as part of Pretense garrisons are actually capable of ammo resupply. Fixed the Pretense generator generating a bit too many missions per squadron. Ground spawns: Also hot start aircraft which require ground crew support (ground air or chock removal) which might not be available at roadbases. Also, pretensetgogenerator.py will now correctly handle air defence units in ground_unit_of_class(). Added Roland groups in the Pretense generator. --- .../campaignloader/defaultsquadronassigner.py | 2 +- game/campaignloader/squadrondefgenerator.py | 6 +- game/game.py | 1 + .../aircraft/flightgroupspawner.py | 55 ++- game/missiongenerator/missiondata.py | 2 + game/missiongenerator/tgogenerator.py | 15 +- game/pretense/pretenseaircraftgenerator.py | 22 +- game/pretense/pretenseluagenerator.py | 405 +++++++++++++++--- game/pretense/pretensemissiongenerator.py | 21 + game/pretense/pretensetgogenerator.py | 182 +++++++- game/pretense/pretensetriggergenerator.py | 114 ++++- game/settings/settings.py | 75 +++- resources/plugins/pretense/init_body_1.lua | 84 ++-- 13 files changed, 851 insertions(+), 133 deletions(-) diff --git a/game/campaignloader/defaultsquadronassigner.py b/game/campaignloader/defaultsquadronassigner.py index adb21dd9..8702496a 100644 --- a/game/campaignloader/defaultsquadronassigner.py +++ b/game/campaignloader/defaultsquadronassigner.py @@ -73,7 +73,7 @@ class DefaultSquadronAssigner: # If we can't find any squadron matching the requirement, we should # create one. return self.air_wing.squadron_def_generator.generate_for_task( - config.primary, control_point + config.primary, control_point, self.game.settings.squadron_random_chance ) def find_preferred_squadron( diff --git a/game/campaignloader/squadrondefgenerator.py b/game/campaignloader/squadrondefgenerator.py index 6871b03d..fffcae7a 100644 --- a/game/campaignloader/squadrondefgenerator.py +++ b/game/campaignloader/squadrondefgenerator.py @@ -21,7 +21,7 @@ class SquadronDefGenerator: self.used_nicknames: set[str] = set() def generate_for_task( - self, task: FlightType, control_point: ControlPoint + self, task: FlightType, control_point: ControlPoint, squadron_random_chance: int ) -> Optional[SquadronDef]: aircraft_choice: Optional[AircraftType] = None for aircraft in AircraftType.priority_list_for_task(task): @@ -30,9 +30,9 @@ class SquadronDefGenerator: if not control_point.can_operate(aircraft): continue aircraft_choice = aircraft - # 50/50 chance to keep looking for an aircraft that isn't as far up the + # squadron_random_chance percent chance to keep looking for an aircraft that isn't as far up the # priority list to maintain some unit variety. - if random.choice([True, False]): + if squadron_random_chance >= random.randint(1, 100): break if aircraft_choice is None: diff --git a/game/game.py b/game/game.py index dfb0c2f7..43598f40 100644 --- a/game/game.py +++ b/game/game.py @@ -157,6 +157,7 @@ class Game: 2: {}, } self.pretense_air_groups: dict[str, Flight] = {} + self.pretense_carrier_zones: List[str] = [] self.on_load(game_still_initializing=True) diff --git a/game/missiongenerator/aircraft/flightgroupspawner.py b/game/missiongenerator/aircraft/flightgroupspawner.py index bec3f522..cc14db7a 100644 --- a/game/missiongenerator/aircraft/flightgroupspawner.py +++ b/game/missiongenerator/aircraft/flightgroupspawner.py @@ -15,6 +15,7 @@ from dcs.planes import ( C_101CC, Su_33, MiG_15bis, + M_2000C, ) from dcs.point import PointAction from dcs.ships import KUZNECOW @@ -35,7 +36,7 @@ from game.missiongenerator.missiondata import MissionData from game.naming import namegen from game.theater import Airfield, ControlPoint, Fob, NavalControlPoint, OffMapSpawn from game.utils import feet, meters -from pydcs_extensions import A_4E_C +from pydcs_extensions import A_4E_C, VSN_F4B, VSN_F4C WARM_START_HELI_ALT = meters(500) WARM_START_ALTITUDE = meters(3000) @@ -400,6 +401,18 @@ class FlightGroupSpawner: group.points[0].type = "TakeOffGround" group.units[0].heading = ground_spawn[0].units[0].heading + if ( + cp.coalition.game.settings.ground_start_airbase_statics_farps_remove + and isinstance(cp, Airfield) + ): + # Remove invisible FARPs from airfields because they are unnecessary + neutral_country = self.mission.country( + cp.coalition.game.neutral_country.name + ) + neutral_country.remove_static_group(ground_spawn[0]) + group.points[0].link_unit = None + group.points[0].helipad_id = None + # Hot start aircraft which require ground power to start, when ground power # trucks have been disabled for performance reasons ground_power_available = ( @@ -410,10 +423,31 @@ class FlightGroupSpawner: and self.flight.coalition.game.settings.ground_start_ground_power_trucks_roadbase ) - if self.start_type is not StartType.COLD or ( - not ground_power_available - and self.flight.unit_type.dcs_unit_type - in [A_4E_C, F_5E_3, F_86F_Sabre, MiG_15bis, F_14A_135_GR, F_14B, C_101CC] + # Also hot start aircraft which require ground crew support (ground air or chock removal) + # which might not be available at roadbases + if ( + self.start_type is not StartType.COLD + or ( + not ground_power_available + and self.flight.unit_type.dcs_unit_type + in [ + A_4E_C, + F_86F_Sabre, + MiG_15bis, + F_14A_135_GR, + F_14B, + C_101CC, + ] + ) + or ( + self.flight.unit_type.dcs_unit_type + in [ + F_5E_3, + M_2000C, + VSN_F4B, + VSN_F4C, + ] + ) ): group.points[0].action = PointAction.FromGroundAreaHot group.points[0].type = "TakeOffGroundHot" @@ -435,6 +469,17 @@ class FlightGroupSpawner: ground_spawn[0].x, ground_spawn[0].y, terrain=terrain ) group.units[1 + i].heading = ground_spawn[0].units[0].heading + + if ( + cp.coalition.game.settings.ground_start_airbase_statics_farps_remove + and isinstance(cp, Airfield) + ): + # Remove invisible FARPs from airfields because they are unnecessary + neutral_country = self.mission.country( + cp.coalition.game.neutral_country.name + ) + neutral_country.remove_static_group(ground_spawn[0]) + except IndexError as ex: raise NoParkingSlotError( f"Not enough STOL slots available at {cp}" diff --git a/game/missiongenerator/missiondata.py b/game/missiongenerator/missiondata.py index 8906feb4..062d6a56 100644 --- a/game/missiongenerator/missiondata.py +++ b/game/missiongenerator/missiondata.py @@ -52,6 +52,8 @@ class CarrierInfo(UnitInfo): """Carrier information.""" tacan: TacanChannel + icls_channel: int | None + link4_freq: RadioFrequency | None @dataclass diff --git a/game/missiongenerator/tgogenerator.py b/game/missiongenerator/tgogenerator.py index ed83823e..43f7052d 100644 --- a/game/missiongenerator/tgogenerator.py +++ b/game/missiongenerator/tgogenerator.py @@ -60,6 +60,7 @@ from game.theater import ( TheaterGroundObject, TheaterUnit, NavalControlPoint, + Airfield, ) from game.theater.theatergroundobject import ( CarrierGroundObject, @@ -626,6 +627,8 @@ class GenericCarrierGenerator(GroundObjectGenerator): callsign=tacan_callsign, freq=atc, tacan=tacan, + icls_channel=icls, + link4_freq=link4, blue=self.control_point.captured, ) ) @@ -940,7 +943,11 @@ class GroundSpawnRoadbaseGenerator: country.id ) - if self.game.position_culled(ground_spawn[0]): + if self.game.settings.ground_start_airbase_statics_farps_remove and isinstance( + self.cp, Airfield + ): + cull_farp_statics = True + elif self.game.position_culled(ground_spawn[0]): cull_farp_statics = True if self.cp.coalition.player: for package in self.cp.coalition.ato.packages: @@ -1072,7 +1079,11 @@ class GroundSpawnGenerator: country.id ) - if self.game.position_culled(vtol_pad[0]): + if self.game.settings.ground_start_airbase_statics_farps_remove and isinstance( + self.cp, Airfield + ): + cull_farp_statics = True + elif self.game.position_culled(vtol_pad[0]): cull_farp_statics = True if self.cp.coalition.player: for package in self.cp.coalition.ato.packages: diff --git a/game/pretense/pretenseaircraftgenerator.py b/game/pretense/pretenseaircraftgenerator.py index f1b0d90c..f8f13157 100644 --- a/game/pretense/pretenseaircraftgenerator.py +++ b/game/pretense/pretenseaircraftgenerator.py @@ -191,13 +191,13 @@ class PretenseAircraftGenerator: """ squadron_def = coalition.air_wing.squadron_def_generator.generate_for_task( - flight_type, cp + flight_type, cp, self.game.settings.squadron_random_chance ) for retries in range(num_retries): if squadron_def is None or fixed_wing == squadron_def.aircraft.helicopter: squadron_def = ( coalition.air_wing.squadron_def_generator.generate_for_task( - flight_type, cp + flight_type, cp, self.game.settings.squadron_random_chance ) ) @@ -302,7 +302,10 @@ class PretenseAircraftGenerator: # First check what are the capabilities of the squadrons on this CP for squadron in cp.squadrons: for task in sead_tasks: - if task in squadron.auto_assignable_mission_types: + if ( + task in squadron.auto_assignable_mission_types + or FlightType.DEAD in squadron.auto_assignable_mission_types + ): sead_capable_cp = True for task in strike_tasks: if task in squadron.auto_assignable_mission_types: @@ -360,6 +363,8 @@ class PretenseAircraftGenerator: continue if cp.coalition != squadron.coalition: continue + if num_of_sead >= self.game.settings.pretense_sead_flights_per_cp: + break mission_types = squadron.auto_assignable_mission_types if ( @@ -400,6 +405,11 @@ class PretenseAircraftGenerator: continue if cp.coalition != squadron.coalition: continue + if ( + num_of_strike + >= self.game.settings.pretense_strike_flights_per_cp + ): + break mission_types = squadron.auto_assignable_mission_types for task in strike_tasks: @@ -422,6 +432,8 @@ class PretenseAircraftGenerator: continue if cp.coalition != squadron.coalition: continue + if num_of_cap >= self.game.settings.pretense_barcap_flights_per_cp: + break mission_types = squadron.auto_assignable_mission_types for task in patrol_tasks: @@ -444,6 +456,8 @@ class PretenseAircraftGenerator: continue if cp.coalition != squadron.coalition: continue + if num_of_cas >= self.game.settings.pretense_cas_flights_per_cp: + break mission_types = squadron.auto_assignable_mission_types if ( @@ -467,6 +481,8 @@ class PretenseAircraftGenerator: continue if cp.coalition != squadron.coalition: continue + if num_of_bai >= self.game.settings.pretense_bai_flights_per_cp: + break mission_types = squadron.auto_assignable_mission_types if FlightType.BAI in mission_types: diff --git a/game/pretense/pretenseluagenerator.py b/game/pretense/pretenseluagenerator.py index 971588a7..5a13d8fb 100644 --- a/game/pretense/pretenseluagenerator.py +++ b/game/pretense/pretenseluagenerator.py @@ -7,13 +7,15 @@ from abc import ABC, abstractmethod from dataclasses import dataclass from datetime import datetime from pathlib import Path -from typing import TYPE_CHECKING, Optional +from typing import TYPE_CHECKING, Optional, List, Type from dcs import Mission from dcs.action import DoScript, DoScriptFile +from dcs.ships import Stennis, CVN_71, CVN_72, CVN_73, CVN_75, Forrestal from dcs.translation import String from dcs.triggers import TriggerStart -from dcs.vehicles import AirDefence +from dcs.unittype import VehicleType, ShipType +from dcs.vehicles import AirDefence, Unarmed from game.ato import FlightType from game.coalition import Coalition @@ -283,6 +285,7 @@ class PretenseLuaGenerator(LuaGenerator): "nasamsb", "nasamsc", "rapier", + "roland", "irondome", "davidsling", ]: @@ -381,6 +384,8 @@ class PretenseLuaGenerator(LuaGenerator): == AirDefence.Rapier_fsa_launcher ): sam_presets["rapier"].enabled = True + if ground_unit.unit_type.dcs_unit_type == AirDefence.Roland_ADS: + sam_presets["roland"].enabled = True if ground_unit.unit_type.dcs_unit_type == IRON_DOME_LN: sam_presets["irondome"].enabled = True if ground_unit.unit_type.dcs_unit_type == DAVID_SLING_LN: @@ -526,22 +531,9 @@ class PretenseLuaGenerator(LuaGenerator): cp_name_trimmed = "".join([i for i in cp_name.lower() if i.isalpha()]) cp_side_str = "blue" if cp_side == PRETENSE_BLUE_SIDE else "red" - if cp_side == PRETENSE_BLUE_SIDE: - if random.randint(0, 1): - supply_ship = "shipSupplyTilde" - else: - supply_ship = "shipLandingShipLstMk2" - tanker_ship = "shipTankerSeawisegiant" - command_ship = "shipLandingShipSamuelChase" - ship_group = "blueShipGroup" - else: - if random.randint(0, 1): - supply_ship = "shipBulkerYakushev" - else: - supply_ship = "shipCargoIvanov" - tanker_ship = "shipTankerElnya" - command_ship = "shipLandingShipRopucha" - ship_group = "redShipGroup" + supply_ship = "oilPump" + tanker_ship = "chemTank" + command_ship = "comCenter" lua_string_zones += ( " presets.upgrades.supply." + supply_ship + ":extend({\n" @@ -581,7 +573,7 @@ class PretenseLuaGenerator(LuaGenerator): lua_string_zones += " }\n" lua_string_zones += " }),\n" lua_string_zones += ( - " presets.upgrades.attack." + command_ship + ":extend({\n" + " presets.upgrades.airdef." + command_ship + ":extend({\n" ) lua_string_zones += ( f" name = '{cp_name_trimmed}-mission-command-" @@ -592,11 +584,9 @@ class PretenseLuaGenerator(LuaGenerator): lua_string_zones += ( " presets.defenses." + cp_side_str - + "." - + ship_group - + ":extend({ name='" + + ".shorad:extend({ name='" + cp_name_trimmed - + "-sam-" + + "-shorad-" + cp_side_str + "' }),\n" ) @@ -719,6 +709,8 @@ class PretenseLuaGenerator(LuaGenerator): return lua_string_zones def generate_pretense_zone_land(self, cp_name: str) -> str: + is_artillery_zone = random.choice([True, False]) + lua_string_zones = "" cp_name_trimmed = "".join([i for i in cp_name.lower() if i.isalpha()]) @@ -727,11 +719,12 @@ class PretenseLuaGenerator(LuaGenerator): lua_string_zones += " presets.upgrades.basic.tent:extend({\n" lua_string_zones += f" name='{cp_name_trimmed}-tent-red',\n" lua_string_zones += " products = {\n" - lua_string_zones += ( - " presets.special.red.infantry:extend({ name='" - + cp_name_trimmed - + "-defense-red'})\n" - ) + if not is_artillery_zone: + lua_string_zones += ( + " presets.special.red.infantry:extend({ name='" + + cp_name_trimmed + + "-defense-red'})\n" + ) lua_string_zones += " }\n" lua_string_zones += " }),\n" lua_string_zones += " presets.upgrades.basic.comPost:extend({\n" @@ -742,13 +735,25 @@ class PretenseLuaGenerator(LuaGenerator): + cp_name_trimmed + "-defense-red'}),\n" ) - lua_string_zones += ( - " presets.defenses.red.infantry:extend({ name='" - + cp_name_trimmed - + "-garrison-red' })\n" - ) + if not is_artillery_zone: + lua_string_zones += ( + " presets.defenses.red.infantry:extend({ name='" + + cp_name_trimmed + + "-garrison-red' })\n" + ) lua_string_zones += " }\n" lua_string_zones += " }),\n" + if is_artillery_zone: + lua_string_zones += " presets.upgrades.basic.artyBunker:extend({\n" + lua_string_zones += f" name='{cp_name_trimmed}-arty-red',\n" + lua_string_zones += " products = {\n" + lua_string_zones += ( + " presets.defenses.red.artillery:extend({ name='" + + cp_name_trimmed + + "-artillery-red'})\n" + ) + lua_string_zones += " }\n" + lua_string_zones += " }),\n" lua_string_zones += self.generate_pretense_land_upgrade_supply( cp_name, PRETENSE_RED_SIDE @@ -760,11 +765,12 @@ class PretenseLuaGenerator(LuaGenerator): lua_string_zones += " presets.upgrades.basic.tent:extend({\n" lua_string_zones += f" name='{cp_name_trimmed}-tent-blue',\n" lua_string_zones += " products = {\n" - lua_string_zones += ( - " presets.special.blue.infantry:extend({ name='" - + cp_name_trimmed - + "-defense-blue'})\n" - ) + if not is_artillery_zone: + lua_string_zones += ( + " presets.special.blue.infantry:extend({ name='" + + cp_name_trimmed + + "-defense-blue'})\n" + ) lua_string_zones += " }\n" lua_string_zones += " }),\n" lua_string_zones += " presets.upgrades.basic.comPost:extend({\n" @@ -775,13 +781,25 @@ class PretenseLuaGenerator(LuaGenerator): + cp_name_trimmed + "-defense-blue'}),\n" ) - lua_string_zones += ( - " presets.defenses.blue.infantry:extend({ name='" - + cp_name_trimmed - + "-garrison-blue' })\n" - ) + if not is_artillery_zone: + lua_string_zones += ( + " presets.defenses.blue.infantry:extend({ name='" + + cp_name_trimmed + + "-garrison-blue' })\n" + ) lua_string_zones += " }\n" lua_string_zones += " }),\n" + if is_artillery_zone: + lua_string_zones += " presets.upgrades.basic.artyBunker:extend({\n" + lua_string_zones += f" name='{cp_name_trimmed}-arty-blue',\n" + lua_string_zones += " products = {\n" + lua_string_zones += ( + " presets.defenses.blue.artillery:extend({ name='" + + cp_name_trimmed + + "-artillery-blue'})\n" + ) + lua_string_zones += " }\n" + lua_string_zones += " }),\n" lua_string_zones += self.generate_pretense_land_upgrade_supply( cp_name, PRETENSE_BLUE_SIDE @@ -816,14 +834,204 @@ class PretenseLuaGenerator(LuaGenerator): return lua_string_zones + def generate_pretense_carrier_zones(self) -> str: + lua_string_carrier_zones = "cmap1 = CarrierMap:new({" + for zone_name in self.game.pretense_carrier_zones: + lua_string_carrier_zones += f'"{zone_name}",' + lua_string_carrier_zones += "})\n" + + return lua_string_carrier_zones + + def generate_pretense_carriers( + self, + cp_name: str, + cp_side: int, + cp_carrier_group_type: Type[ShipType] | None, + cp_carrier_group_name: str | None, + ) -> str: + lua_string_carrier = "\n" + cp_name_trimmed = "".join([i for i in cp_name.lower() if i.isalpha()]) + + link4carriers = [Stennis, CVN_71, CVN_72, CVN_73, CVN_75, Forrestal] + is_link4carrier = False + carrier_unit_name = "" + icls_channel = 10 + link4_freq = 339000000 + tacan_channel = 44 + tacan_callsign = "" + radio = 137500000 + if cp_carrier_group_type is not None: + if cp_carrier_group_type in link4carriers: + is_link4carrier = True + else: + return lua_string_carrier + if cp_carrier_group_name is None: + return lua_string_carrier + + for carrier in self.mission_data.carriers: + if cp_carrier_group_name == carrier.group_name: + carrier_unit_name = carrier.unit_name + tacan_channel = carrier.tacan.number + tacan_callsign = carrier.callsign + radio = carrier.freq.hertz + if carrier.link4_freq is not None: + link4_freq = carrier.link4_freq.hertz + if carrier.icls_channel is not None: + icls_channel = carrier.icls_channel + break + + lua_string_carrier += ( + f'{cp_name_trimmed} = CarrierCommand:new("' + + carrier_unit_name + + '", 3000, cmap1:getNavMap(), ' + + "{\n" + ) + if is_link4carrier: + lua_string_carrier += " icls = " + str(icls_channel) + ",\n" + lua_string_carrier += " acls = true,\n" + lua_string_carrier += " link4 = " + str(link4_freq) + ",\n" + lua_string_carrier += ( + " tacan = {channel = " + + str(tacan_channel) + + ', callsign="' + + tacan_callsign + + '"},\n' + ) + lua_string_carrier += " radio = " + str(radio) + "\n" + lua_string_carrier += "}, 30000)\n" + + for mission_type in self.game.pretense_air[cp_side][cp_name_trimmed]: + if mission_type == FlightType.SEAD: + mission_name = "supportTypes.strike" + for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ + mission_type + ]: + lua_string_carrier += ( + f'{cp_name_trimmed}:addSupportFlight("{air_group}", 1000, CarrierCommand.{mission_name}, ' + + "{altitude = 25000, expend=AI.Task.WeaponExpend.ALL})\n" + ) + elif mission_type == FlightType.CAS: + mission_name = "supportTypes.strike" + for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ + mission_type + ]: + lua_string_carrier += ( + f'{cp_name_trimmed}:addSupportFlight("{air_group}", 1000, CarrierCommand.{mission_name}, ' + + "{altitude = 15000, expend=AI.Task.WeaponExpend.ONE})\n" + ) + elif mission_type == FlightType.BAI: + mission_name = "supportTypes.strike" + for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ + mission_type + ]: + lua_string_carrier += ( + f'{cp_name_trimmed}:addSupportFlight("{air_group}", 1000, CarrierCommand.{mission_name}, ' + + "{altitude = 10000, expend=AI.Task.WeaponExpend.ONE})\n" + ) + elif mission_type == FlightType.STRIKE: + mission_name = "supportTypes.strike" + for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ + mission_type + ]: + lua_string_carrier += ( + f'{cp_name_trimmed}:addSupportFlight("{air_group}", 2000, CarrierCommand.{mission_name}, ' + + "{altitude = 20000})\n" + ) + elif mission_type == FlightType.BARCAP: + mission_name = "supportTypes.cap" + for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ + mission_type + ]: + lua_string_carrier += ( + f'{cp_name_trimmed}:addSupportFlight("{air_group}", 1000, CarrierCommand.{mission_name}, ' + + "{altitude = 25000, range=25})\n" + ) + elif mission_type == FlightType.REFUELING: + mission_name = "supportTypes.tanker" + for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ + mission_type + ]: + tanker_freq = 257.0 + tanker_tacan = 37.0 + tanker_variant = "Drogue" + for tanker in self.mission_data.tankers: + if tanker.group_name == air_group: + tanker_freq = tanker.freq.hertz / 1000000 + tanker_tacan = tanker.tacan.number if tanker.tacan else 0.0 + if tanker.variant == "KC-135 Stratotanker": + tanker_variant = "Boom" + lua_string_carrier += ( + f'{cp_name_trimmed}:addSupportFlight("{air_group}", 3000, CarrierCommand.{mission_name}, ' + + "{altitude = 19000, freq=" + + str(tanker_freq) + + ", tacan=" + + str(tanker_tacan) + + "})\n" + ) + elif mission_type == FlightType.AEWC: + mission_name = "supportTypes.awacs" + for air_group in self.game.pretense_air[cp_side][cp_name_trimmed][ + mission_type + ]: + awacs_freq = 257.5 + for awacs in self.mission_data.awacs: + if awacs.group_name == air_group: + awacs_freq = awacs.freq.hertz / 1000000 + lua_string_carrier += ( + f'{cp_name_trimmed}:addSupportFlight("{air_group}", 5000, CarrierCommand.{mission_name}, ' + + "{altitude = 30000, freq=" + + str(awacs_freq) + + "})\n" + ) + + # lua_string_carrier += f'{cp_name_trimmed}:addExtraSupport("BGM-109B", 10000, CarrierCommand.supportTypes.mslstrike, ' + '{salvo = 10, wpType = \'weapons.missiles.BGM_109B\'})\n' + + return lua_string_carrier + def get_ground_unit( self, coalition: Coalition, side: int, desired_unit_classes: list[UnitClass] ) -> str: + ammo_trucks: List[Type[VehicleType]] = [ + Unarmed.S_75_ZIL, + Unarmed.GAZ_3308, + Unarmed.GAZ_66, + Unarmed.KAMAZ_Truck, + Unarmed.KrAZ6322, + Unarmed.Ural_375, + Unarmed.Ural_375_PBU, + Unarmed.Ural_4320_31, + Unarmed.Ural_4320T, + Unarmed.ZIL_135, + Unarmed.Blitz_36_6700A, + Unarmed.M_818, + Unarmed.Bedford_MWD, + ] + for unit_class in desired_unit_classes: if coalition.faction.has_access_to_unit_class(unit_class): dcs_unit_type = PretenseGroundObjectGenerator.ground_unit_of_class( coalition=coalition, unit_class=unit_class ) + if ( + dcs_unit_type is not None + and unit_class == UnitClass.LOGISTICS + and dcs_unit_type.dcs_unit_type.__class__ not in ammo_trucks + ): + # ground_unit_of_class returned a logistics unit not capable of ammo resupply + # Retry up to 10 times + for truck_retry in range(10): + dcs_unit_type = ( + PretenseGroundObjectGenerator.ground_unit_of_class( + coalition=coalition, unit_class=unit_class + ) + ) + if ( + dcs_unit_type is not None + and dcs_unit_type.dcs_unit_type in ammo_trucks + ): + break + else: + dcs_unit_type = None if dcs_unit_type is not None: return dcs_unit_type.dcs_id @@ -849,6 +1057,11 @@ class PretenseLuaGenerator(LuaGenerator): return "LAV-25" else: return "BTR-80" + elif desired_unit_classes[0] == UnitClass.ARTILLERY: + if side == PRETENSE_BLUE_SIDE: + return "M-109" + else: + return "SAU Gvozdika" elif desired_unit_classes[0] == UnitClass.RECON: if side == PRETENSE_BLUE_SIDE: return "M1043 HMMWV Armament" @@ -865,10 +1078,16 @@ class PretenseLuaGenerator(LuaGenerator): else: return "KS-19" elif desired_unit_classes[0] == UnitClass.MANPAD: - if side == PRETENSE_BLUE_SIDE: - return "Soldier stinger" + if coalition.game.date.year >= 1990: + if side == PRETENSE_BLUE_SIDE: + return "Soldier stinger" + else: + return "SA-18 Igla manpad" else: - return "SA-18 Igla manpad" + if side == PRETENSE_BLUE_SIDE: + return "Soldier M4" + else: + return "Infantry AK" elif desired_unit_classes[0] == UnitClass.LOGISTICS: if side == PRETENSE_BLUE_SIDE: return "M 818" @@ -910,6 +1129,26 @@ class PretenseLuaGenerator(LuaGenerator): lua_string_ground_groups += " dataCategory = TemplateDB.type.group\n" lua_string_ground_groups += "}\n" + lua_string_ground_groups += ( + 'TemplateDB.templates["artillery-' + side_str + '"] = {\n' + ) + lua_string_ground_groups += " units = {\n" + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.ARTILLERY])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.ARTILLERY])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.ARTILLERY])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.ARTILLERY])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.INFANTRY])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.INFANTRY])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.INFANTRY])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.INFANTRY])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.LOGISTICS])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.LOGISTICS])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.MANPAD, UnitClass.INFANTRY])}"\n' + lua_string_ground_groups += " },\n" + lua_string_ground_groups += f' skill = "{skill_str}",\n' + lua_string_ground_groups += " dataCategory = TemplateDB.type.group\n" + lua_string_ground_groups += "}\n" + lua_string_ground_groups += ( 'TemplateDB.templates["defense-' + side_str + '"] = {\n' ) @@ -1173,6 +1412,30 @@ class PretenseLuaGenerator(LuaGenerator): lua_string_ground_groups += " dataCategory = TemplateDB.type.group\n" lua_string_ground_groups += "}\n" + lua_string_ground_groups += ( + 'TemplateDB.templates["roland-' + side_str + '"] = {\n' + ) + lua_string_ground_groups += " units = {\n" + lua_string_ground_groups += ' "Roland ADS",\n' + lua_string_ground_groups += ' "Roland ADS",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.AAA, UnitClass.SHORAD, UnitClass.MANPAD])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.LOGISTICS])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.LOGISTICS])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.SHORAD, UnitClass.AAA, UnitClass.MANPAD])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.SHORAD, UnitClass.AAA, UnitClass.MANPAD])}",\n' + lua_string_ground_groups += ' "Roland ADS",\n' + lua_string_ground_groups += ' "Roland ADS",\n' + lua_string_ground_groups += ' "Roland ADS",\n' + lua_string_ground_groups += ' "Roland ADS",\n' + lua_string_ground_groups += ' "Roland Radar",\n' + lua_string_ground_groups += ' "Roland Radar",\n' + lua_string_ground_groups += ' "Roland Radar"\n' + lua_string_ground_groups += " },\n" + lua_string_ground_groups += " maxDist = 300,\n" + lua_string_ground_groups += f' skill = "{skill_str}",\n' + lua_string_ground_groups += " dataCategory = TemplateDB.type.group\n" + lua_string_ground_groups += "}\n" + return lua_string_ground_groups @staticmethod @@ -1219,7 +1482,6 @@ class PretenseLuaGenerator(LuaGenerator): + str(self.game.settings.pretense_maxdistfromfront_distance * 1000) + "\n" ) - trigger = TriggerStart(comment="Pretense config") trigger.add_action(DoScript(String(lua_string_config))) self.mission.triggerrules.triggers.append(trigger) @@ -1247,16 +1509,30 @@ class PretenseLuaGenerator(LuaGenerator): ) lua_string_zones = "" + lua_string_carriers = self.generate_pretense_carrier_zones() for cp in self.game.theater.controlpoints: - if isinstance(cp, OffMapSpawn): - continue - cp_name_trimmed = "".join([i for i in cp.name.lower() if i.isalpha()]) cp_name = "".join( [i for i in cp.name if i.isalnum() or i.isspace() or i == "-"] ) cp_side = 2 if cp.captured else 1 + + if isinstance(cp, OffMapSpawn): + continue + elif ( + cp.is_fleet + and cp.captured + and self.game.settings.pretense_controllable_carrier + ): + # Friendly carrier, generate carrier parameters + cp_carrier_group_type = cp.get_carrier_group_type() + cp_carrier_group_name = cp.get_carrier_group_name() + lua_string_carriers += self.generate_pretense_carriers( + cp_name, cp_side, cp_carrier_group_type, cp_carrier_group_name + ) + continue + for side in range(1, 3): if cp_name_trimmed not in self.game.pretense_air[cp_side]: self.game.pretense_air[side][cp_name_trimmed] = {} @@ -1306,7 +1582,10 @@ class PretenseLuaGenerator(LuaGenerator): lua_string_zones += ( f"zones.{cp_name_trimmed}.keepActive = " + is_keep_active + "\n" ) - lua_string_zones += self.generate_pretense_zone_land(cp.name) + if cp.is_fleet: + lua_string_zones += self.generate_pretense_zone_sea(cp_name) + else: + lua_string_zones += self.generate_pretense_zone_land(cp_name) lua_string_connman = " cm = ConnectionManager:new()\n" @@ -1326,7 +1605,10 @@ class PretenseLuaGenerator(LuaGenerator): ) if len(cp.connected_points) == 0 and len(cp.shipping_lanes) == 0: # Also connect carrier and LHA control points to adjacent friendly points - if cp.is_fleet: + if cp.is_fleet and ( + not self.game.settings.pretense_controllable_carrier + or not cp.captured + ): num_of_carrier_connections = 0 for ( other_cp @@ -1347,7 +1629,19 @@ class PretenseLuaGenerator(LuaGenerator): for extra_connection in range( self.game.settings.pretense_extra_zone_connections ): - if len(closest_cps) > extra_connection: + if ( + cp.is_fleet + and cp.captured + and self.game.settings.pretense_controllable_carrier + ): + break + elif ( + closest_cps[extra_connection].is_fleet + and closest_cps[extra_connection].captured + and self.game.settings.pretense_controllable_carrier + ): + break + elif len(closest_cps) > extra_connection: lua_string_connman += self.generate_pretense_zone_connection( connected_points, cp.name, @@ -1387,9 +1681,9 @@ class PretenseLuaGenerator(LuaGenerator): lua_string_jtac = "" for jtac in self.mission_data.jtacs: - lua_string_jtac = f"Group.getByName('{jtac.group_name}'): destroy()" + lua_string_jtac = f"Group.getByName('{jtac.group_name}'): destroy()\n" lua_string_jtac += ( - "CommandFunctions.jtac = JTAC:new({name = '" + jtac.group_name + "'})" + "CommandFunctions.jtac = JTAC:new({name = '" + jtac.group_name + "'})\n" ) init_body_2_file = open("./resources/plugins/pretense/init_body_2.lua", "r") @@ -1411,6 +1705,7 @@ class PretenseLuaGenerator(LuaGenerator): + lua_string_connman + init_body_2 + lua_string_jtac + + lua_string_carriers + init_body_3 + lua_string_supply + init_footer diff --git a/game/pretense/pretensemissiongenerator.py b/game/pretense/pretensemissiongenerator.py index 7b18b35b..57b53b26 100644 --- a/game/pretense/pretensemissiongenerator.py +++ b/game/pretense/pretensemissiongenerator.py @@ -38,6 +38,7 @@ from ..ato.airtaaskingorder import AirTaskingOrder from ..callsigns import callsign_for_support_unit from ..dcs.aircrafttype import AircraftType from ..missiongenerator import MissionGenerator +from ..theater import Airfield if TYPE_CHECKING: from game import Game @@ -104,6 +105,26 @@ class PretenseMissionGenerator(MissionGenerator): self.generate_ground_conflicts() self.generate_air_units(tgo_generator) + for cp in self.game.theater.controlpoints: + if ( + self.game.settings.ground_start_airbase_statics_farps_remove + and isinstance(cp, Airfield) + ): + while len(tgo_generator.ground_spawns[cp]) > 0: + ground_spawn = tgo_generator.ground_spawns[cp].pop() + # Remove invisible FARPs from airfields because they are unnecessary + neutral_country = self.mission.country( + cp.coalition.game.neutral_country.name + ) + neutral_country.remove_static_group(ground_spawn[0]) + while len(tgo_generator.ground_spawns_roadbase[cp]) > 0: + ground_spawn = tgo_generator.ground_spawns_roadbase[cp].pop() + # Remove invisible FARPs from airfields because they are unnecessary + neutral_country = self.mission.country( + cp.coalition.game.neutral_country.name + ) + neutral_country.remove_static_group(ground_spawn[0]) + self.mission.triggerrules.triggers.clear() PretenseTriggerGenerator(self.mission, self.game).generate() ForcedOptionsGenerator(self.mission, self.game).generate() diff --git a/game/pretense/pretensetgogenerator.py b/game/pretense/pretensetgogenerator.py index 58a1cbed..82217973 100644 --- a/game/pretense/pretensetgogenerator.py +++ b/game/pretense/pretensetgogenerator.py @@ -8,12 +8,14 @@ create the pydcs groups and statics for those areas and add them to the mission. from __future__ import annotations import random +import logging from collections import defaultdict -from typing import Dict, Optional, TYPE_CHECKING, Tuple, Type +from typing import Dict, Optional, TYPE_CHECKING, Tuple, Type, Iterator from dcs import Mission, Point from dcs.countries import * from dcs.country import Country +from dcs.ships import Stennis, CVN_71, CVN_72, CVN_73, CVN_75, Forrestal, LHA_Tarawa from dcs.unitgroup import StaticGroup, VehicleGroup from dcs.unittype import VehicleType @@ -23,7 +25,7 @@ from game.dcs.groundunittype import GroundUnitType from game.missiongenerator.groundforcepainter import ( GroundForcePainter, ) -from game.missiongenerator.missiondata import MissionData +from game.missiongenerator.missiondata import MissionData, CarrierInfo from game.missiongenerator.tgogenerator import ( TgoGenerator, HelipadGenerator, @@ -33,10 +35,11 @@ from game.missiongenerator.tgogenerator import ( CarrierGenerator, LhaGenerator, MissileSiteGenerator, + GenericCarrierGenerator, ) from game.point_with_heading import PointWithHeading from game.radio.radios import RadioRegistry -from game.radio.tacan import TacanRegistry +from game.radio.tacan import TacanRegistry, TacanBand, TacanUsage from game.runways import RunwayData from game.theater import ( ControlPoint, @@ -51,9 +54,11 @@ from game.theater.theatergroundobject import ( MissileSiteGroundObject, BuildingGroundObject, VehicleGroupGroundObject, + GenericCarrierGroundObject, ) from game.theater.theatergroup import TheaterGroup from game.unitmap import UnitMap +from game.utils import Heading from pydcs_extensions import ( Char_M551_Sheridan, BV410_RBS70, @@ -147,6 +152,7 @@ class PretenseGroundObjectGenerator(GroundObjectGenerator): faction_units = ( set(coalition.faction.frontline_units) | set(coalition.faction.artillery_units) + | set(coalition.faction.air_defense_units) | set(coalition.faction.logistics_units) ) of_class = list({u for u in faction_units if u.unit_class is unit_class}) @@ -608,6 +614,172 @@ class PretenseGroundObjectGenerator(GroundObjectGenerator): return vehicle_group +class PretenseGenericCarrierGenerator(GenericCarrierGenerator): + """Base type for carrier group generation. + + Used by both CV(N) groups and LHA groups. + """ + + def __init__( + self, + ground_object: GenericCarrierGroundObject, + control_point: NavalControlPoint, + country: Country, + game: Game, + mission: Mission, + radio_registry: RadioRegistry, + tacan_registry: TacanRegistry, + icls_alloc: Iterator[int], + runways: Dict[str, RunwayData], + unit_map: UnitMap, + mission_data: MissionData, + ) -> None: + super().__init__( + ground_object, + control_point, + country, + game, + mission, + radio_registry, + tacan_registry, + icls_alloc, + runways, + unit_map, + mission_data, + ) + self.ground_object = ground_object + self.control_point = control_point + self.radio_registry = radio_registry + self.tacan_registry = tacan_registry + self.icls_alloc = icls_alloc + self.runways = runways + self.mission_data = mission_data + + def generate(self) -> None: + if self.control_point.frequency is not None: + atc = self.control_point.frequency + if atc not in self.radio_registry.allocated_channels: + self.radio_registry.reserve(atc) + else: + atc = self.radio_registry.alloc_uhf() + + for g_id, group in enumerate(self.ground_object.groups): + if not group.units: + logging.warning(f"Found empty carrier group in {self.control_point}") + continue + + ship_units = [] + for unit in group.units: + if unit.alive: + # All alive Ships + print( + f"Added {unit.unit_name} to ship_units of group {group.group_name}" + ) + ship_units.append(unit) + + if not ship_units: + # Empty array (no alive units), skip this group + continue + + ship_group = self.create_ship_group(group.group_name, ship_units, atc) + + if self.game.settings.pretense_carrier_steams_into_wind: + # Always steam into the wind, even if the carrier is being moved. + # There are multiple unsimulated hours between turns, so we can + # count those as the time the carrier uses to move and the mission + # time as the recovery window. + brc = self.steam_into_wind(ship_group) + else: + brc = Heading(0) + + # Set Carrier Specific Options + if g_id == 0 and self.control_point.runway_is_operational(): + # Get Correct unit type for the carrier. + # This will upgrade to super carrier if option is enabled + carrier_type = self.carrier_type + if carrier_type is None: + raise RuntimeError( + f"Error generating carrier group for {self.control_point.name}" + ) + ship_group.units[0].type = carrier_type.id + if self.control_point.tacan is None: + tacan = self.tacan_registry.alloc_for_band( + TacanBand.X, TacanUsage.TransmitReceive + ) + else: + tacan = self.control_point.tacan + if self.control_point.tcn_name is None: + tacan_callsign = self.tacan_callsign() + else: + tacan_callsign = self.control_point.tcn_name + link4 = None + link4carriers = [Stennis, CVN_71, CVN_72, CVN_73, CVN_75, Forrestal] + if carrier_type in link4carriers: + if self.control_point.link4 is None: + link4 = self.radio_registry.alloc_uhf() + else: + link4 = self.control_point.link4 + icls = None + icls_name = self.control_point.icls_name + if carrier_type in link4carriers or carrier_type == LHA_Tarawa: + if self.control_point.icls_channel is None: + icls = next(self.icls_alloc) + else: + icls = self.control_point.icls_channel + self.activate_beacons( + ship_group, tacan, tacan_callsign, icls, icls_name, link4 + ) + self.add_runway_data( + brc or Heading.from_degrees(0), atc, tacan, tacan_callsign, icls + ) + self.mission_data.carriers.append( + CarrierInfo( + group_name=ship_group.name, + unit_name=ship_group.units[0].name, + callsign=tacan_callsign, + freq=atc, + tacan=tacan, + icls_channel=icls, + link4_freq=link4, + blue=self.control_point.captured, + ) + ) + + +class PretenseCarrierGenerator(PretenseGenericCarrierGenerator): + def tacan_callsign(self) -> str: + # TODO: Assign these properly. + return random.choice( + [ + "STE", + "CVN", + "CVH", + "CCV", + "ACC", + "ARC", + "GER", + "ABR", + "LIN", + "TRU", + ] + ) + + +class PretenseLhaGenerator(PretenseGenericCarrierGenerator): + def tacan_callsign(self) -> str: + # TODO: Assign these properly. + return random.choice( + [ + "LHD", + "LHA", + "LHB", + "LHC", + "LHD", + "LDS", + ] + ) + + class PretenseTgoGenerator(TgoGenerator): """Creates DCS groups and statics for the theater during mission generation. @@ -693,7 +865,7 @@ class PretenseTgoGenerator(TgoGenerator): if isinstance(ground_object, CarrierGroundObject) and isinstance( cp, NavalControlPoint ): - generator = CarrierGenerator( + generator = PretenseCarrierGenerator( ground_object, cp, country, @@ -709,7 +881,7 @@ class PretenseTgoGenerator(TgoGenerator): elif isinstance(ground_object, LhaGroundObject) and isinstance( cp, NavalControlPoint ): - generator = LhaGenerator( + generator = PretenseLhaGenerator( ground_object, cp, country, diff --git a/game/pretense/pretensetriggergenerator.py b/game/pretense/pretensetriggergenerator.py index 0f33f68a..8500bf47 100644 --- a/game/pretense/pretensetriggergenerator.py +++ b/game/pretense/pretensetriggergenerator.py @@ -1,6 +1,8 @@ from __future__ import annotations import logging +import math +import random from typing import TYPE_CHECKING, List from dcs import Point @@ -29,7 +31,10 @@ from dcs.terrain.syria.airports import Damascus, Khalkhalah from dcs.translation import String from dcs.triggers import Event, TriggerCondition, TriggerOnce from dcs.unit import Skill +from numpy import cross, einsum, arctan2 +from shapely import MultiPolygon, Point as ShapelyPoint +from game.naming import ALPHA_MILITARY from game.theater import Airfield from game.theater.controlpoint import Fob, TRIGGER_RADIUS_CAPTURE, OffMapSpawn @@ -56,10 +61,14 @@ TRIGGER_RADIUS_PRETENSE_TGO = 500 TRIGGER_RADIUS_PRETENSE_SUPPLY = 500 TRIGGER_RADIUS_PRETENSE_HELI = 1000 TRIGGER_RADIUS_PRETENSE_HELI_BUFFER = 500 -TRIGGER_RADIUS_PRETENSE_CARRIER = 50000 +TRIGGER_RADIUS_PRETENSE_CARRIER = 20000 +TRIGGER_RADIUS_PRETENSE_CARRIER_SMALL = 3000 +TRIGGER_RADIUS_PRETENSE_CARRIER_CORNER = 25000 TRIGGER_RUNWAY_LENGTH_PRETENSE = 2500 TRIGGER_RUNWAY_WIDTH_PRETENSE = 400 +SIMPLIFY_RUNS_PRETENSE_CARRIER = 10000 + class Silence(Option): Key = 7 @@ -221,12 +230,105 @@ class PretenseTriggerGenerator: 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` + """Creates triggger zones for the Pretense campaign. These include: + - Carrier zones for friendly forces, generated from the navmesh / sea zone intersection + - Carrier zones for opposing forces + - Airfield and FARP zones + - Airfield and FARP spawn points / helicopter spawn points / ground object positions """ + + # First generate carrier zones for friendly forces + use_blue_navmesh = ( + self.game.settings.pretense_carrier_zones_navmesh == "Blue navmesh" + ) + sea_zones_landmap = self.game.coalition_for( + player=False + ).nav_mesh.theater.landmap + if ( + self.game.settings.pretense_controllable_carrier + and sea_zones_landmap is not None + ): + navmesh_number = 0 + for navmesh_poly in self.game.coalition_for( + player=use_blue_navmesh + ).nav_mesh.polys: + navmesh_number += 1 + if sea_zones_landmap.sea_zones.intersects(navmesh_poly.poly): + # Get the intersection between the navmesh zone and the sea zone + navmesh_sea_intersection = sea_zones_landmap.sea_zones.intersection( + navmesh_poly.poly + ) + navmesh_zone_verticies = navmesh_sea_intersection + + # Simplify it to get a quadrangle + for simplify_run in range(SIMPLIFY_RUNS_PRETENSE_CARRIER): + navmesh_zone_verticies = navmesh_sea_intersection.simplify( + float(simplify_run * 10), preserve_topology=False + ) + if isinstance(navmesh_zone_verticies, MultiPolygon): + break + if len(navmesh_zone_verticies.exterior.coords) <= 4: + break + if isinstance(navmesh_zone_verticies, MultiPolygon): + continue + trigger_zone_verticies = [] + terrain = self.game.theater.terrain + alpha = random.choice(ALPHA_MILITARY) + + # Generate the quadrangle zone and four points inside it for carrier navigation + if len(navmesh_zone_verticies.exterior.coords) == 4: + zone_color = {1: 1.0, 2: 1.0, 3: 1.0, 4: 0.15} + corner_point_num = 0 + for point_coord in navmesh_zone_verticies.exterior.coords: + corner_point = Point( + x=point_coord[0], y=point_coord[1], terrain=terrain + ) + nav_point = corner_point.point_from_heading( + corner_point.heading_between_point( + navmesh_sea_intersection.centroid + ), + TRIGGER_RADIUS_PRETENSE_CARRIER_CORNER, + ) + corner_point_num += 1 + + zone_name = f"{alpha}-{navmesh_number}-{corner_point_num}" + if sea_zones_landmap.sea_zones.contains( + ShapelyPoint(nav_point.x, nav_point.y) + ): + self.mission.triggers.add_triggerzone( + nav_point, + radius=TRIGGER_RADIUS_PRETENSE_CARRIER_SMALL, + hidden=False, + name=zone_name, + color=zone_color, + ) + + trigger_zone_verticies.append(corner_point) + + zone_name = f"{alpha}-{navmesh_number}" + trigger_zone = self.mission.triggers.add_triggerzone_quad( + navmesh_sea_intersection.centroid, + trigger_zone_verticies, + hidden=False, + name=zone_name, + color=zone_color, + ) + try: + if len(self.game.pretense_carrier_zones) == 0: + self.game.pretense_carrier_zones = [] + except AttributeError: + self.game.pretense_carrier_zones = [] + self.game.pretense_carrier_zones.append(zone_name) + for cp in self.game.theater.controlpoints: - if cp.is_fleet: + if ( + cp.is_fleet + and self.game.settings.pretense_controllable_carrier + and cp.captured + ): + # Friendly carrier zones are generated above + continue + elif cp.is_fleet: trigger_radius = float(TRIGGER_RADIUS_PRETENSE_CARRIER) elif isinstance(cp, Fob) and cp.has_helipads: trigger_radius = TRIGGER_RADIUS_PRETENSE_HELI @@ -247,6 +349,8 @@ class PretenseTriggerGenerator: or isinstance(cp.dcs_airport, Khalkhalah) or isinstance(cp.dcs_airport, Krasnodar_Pashkovsky) ): + # Increase the size of Pretense zones at Damascus, Khalkhalah and Krasnodar-Pashkovsky + # (which are quite spread out) so the zone would encompass the entire airfield. trigger_radius = int(TRIGGER_RADIUS_CAPTURE * 1.8) else: trigger_radius = TRIGGER_RADIUS_CAPTURE diff --git a/game/settings/settings.py b/game/settings/settings.py index 6cfc0b3b..e2df6a2c 100644 --- a/game/settings/settings.py +++ b/game/settings/settings.py @@ -158,6 +158,7 @@ class Settings: MISSION_RESTRICTIONS_SECTION, default=True, ) + easy_communication: Optional[bool] = choices_option( "Easy Communication", page=DIFFICULTY_PAGE, @@ -176,6 +177,20 @@ class Settings: # Campaign management # General + squadron_random_chance: int = bounded_int_option( + "Percentage of randomly selected aircraft types (only for generated squadrons)", + page=CAMPAIGN_MANAGEMENT_PAGE, + section=GENERAL_SECTION, + default=50, + min=0, + max=100, + detail=( + "

Aircraft type selection is governed by the campaign and the squadron definitions available to " + "Retribution. Squadrons are generated by Retribution if the faction does not have access to the campaign " + "designer's squadron/aircraft definitions. Use the above to increase/decrease aircraft variety by making " + "some selections random instead of picking aircraft types from a priority list.

" + ), + ) restrict_weapons_by_date: bool = boolean_option( "Restrict weapons by date (WIP)", page=CAMPAIGN_MANAGEMENT_PAGE, @@ -831,6 +846,17 @@ class Settings: "Needed to cold-start some aircraft types. Might have a performance impact." ), ) + ground_start_airbase_statics_farps_remove: bool = boolean_option( + "Remove ground spawn statics, including invisible FARPs, at airbases", + MISSION_GENERATOR_PAGE, + GAMEPLAY_SECTION, + default=True, + detail=( + "Ammo and fuel statics and invisible FARPs should be unnecessary when creating " + "additional spawns for players at airbases. This setting will disable them and " + "potentially grant a marginal performance benefit." + ), + ) ai_unlimited_fuel: bool = boolean_option( "AI flights have unlimited fuel", MISSION_GENERATOR_PAGE, @@ -994,6 +1020,43 @@ class Settings: "parts of the economy. Use this to adjust performance." ), ) + pretense_controllable_carrier: bool = boolean_option( + "Controllable carrier", + page=PRETENSE_PAGE, + section=GENERAL_SECTION, + default=True, + detail=( + "This can be used to enable or disable the native carrier support in Pretense. The Pretense carrier " + "can be controlled through the communication menu (if the Pretense character has enough rank/CMD points) " + "and the player can call in AI aerial and cruise missile missions using it." + "The controllable carriers in Pretense do not build and deploy AI missions autonomously, so if you prefer " + "to have both sides deploy carrier aviation autonomously, you might want to disable this option. " + "When this option is disabled, moving the carrier can only be done with the Retribution interface." + ), + ) + pretense_carrier_steams_into_wind: bool = boolean_option( + "Carriers steam into wind", + page=PRETENSE_PAGE, + section=GENERAL_SECTION, + default=True, + detail=( + "This setting controls whether carriers and their escorts will steam into wind. Disable to " + "to ensure that the carriers stay within the carrier zone in Pretense, but note that " + "doing so might limit carrier operations, takeoff weights and landings." + ), + ) + pretense_carrier_zones_navmesh: str = choices_option( + "Navmesh to use for Pretense carrier zones", + page=PRETENSE_PAGE, + section=GENERAL_SECTION, + choices=["Blue navmesh", "Red navmesh"], + default="Blue navmesh", + detail=( + "Use the Retribution map interface options to compare the blue navmesh and the red navmesh." + "You can select which navmesh to use when generating the zones in which the controllable carrier(s) " + "move and operate." + ), + ) pretense_extra_zone_connections: int = bounded_int_option( "Extra friendly zone connections", page=PRETENSE_PAGE, @@ -1018,7 +1081,7 @@ class Settings: "Number of AI SEAD flights per control point / zone", page=PRETENSE_PAGE, section=GENERAL_SECTION, - default=2, + default=1, min=1, max=10, ) @@ -1026,7 +1089,7 @@ class Settings: "Number of AI CAS flights per control point / zone", page=PRETENSE_PAGE, section=GENERAL_SECTION, - default=2, + default=1, min=1, max=10, ) @@ -1034,7 +1097,7 @@ class Settings: "Number of AI BAI flights per control point / zone", page=PRETENSE_PAGE, section=GENERAL_SECTION, - default=2, + default=1, min=1, max=10, ) @@ -1042,7 +1105,7 @@ class Settings: "Number of AI Strike flights per control point / zone", page=PRETENSE_PAGE, section=GENERAL_SECTION, - default=2, + default=1, min=1, max=10, ) @@ -1050,7 +1113,7 @@ class Settings: "Number of AI BARCAP flights per control point / zone", page=PRETENSE_PAGE, section=GENERAL_SECTION, - default=2, + default=1, min=1, max=10, ) @@ -1066,7 +1129,7 @@ class Settings: "Number of player flights per aircraft type at each base", page=PRETENSE_PAGE, section=GENERAL_SECTION, - default=2, + default=1, min=1, max=10, ) diff --git a/resources/plugins/pretense/init_body_1.lua b/resources/plugins/pretense/init_body_1.lua index ad7ae685..2cac3514 100644 --- a/resources/plugins/pretense/init_body_1.lua +++ b/resources/plugins/pretense/init_body_1.lua @@ -21,6 +21,12 @@ presets = { cost = 1500, type = 'upgrade', template = "outpost" + }), + artyBunker = Preset:new({ + display = 'Artillery Bunker', + cost = 2000, + type = 'upgrade', + template = "ammo-depot" }) }, attack = { @@ -36,30 +42,12 @@ presets = { type = 'upgrade', template = "ammo-depot" }), - shipTankerSeawisegiant = Preset:new({ - display = 'Tanker Seawise Giant', - cost = 1500, - type = 'upgrade', - template = "ship-tanker-seawisegiant" - }), - shipLandingShipSamuelChase = Preset:new({ - display = 'LST USS Samuel Chase', - cost = 1500, - type = 'upgrade', - template = "ship-landingship-samuelchase" - }), - shipLandingShipRopucha = Preset:new({ - display = 'LS Ropucha', - cost = 1500, - type = 'upgrade', - template = "ship-landingship-ropucha" - }), - shipTankerElnya = Preset:new({ - display = 'Tanker Elnya', - cost = 1500, - type = 'upgrade', - template = "ship-tanker-elnya" - }) + chemTank = Preset:new({ + display='Chemical Tank', + cost = 2000, + type ='upgrade', + template = "chem-tank" + }), }, supply = { fuelCache = Preset:new({ @@ -178,30 +166,6 @@ presets = { income = 50, template = "tv-tower" }), - shipSupplyTilde = Preset:new({ - display = 'Ship_Tilde_Supply', - cost = 1500, - type = 'upgrade', - template = "ship-supply-tilde" - }), - shipLandingShipLstMk2 = Preset:new({ - display = 'LST Mk.II', - cost = 1500, - type = 'upgrade', - template = "ship-landingship-lstmk2" - }), - shipBulkerYakushev = Preset:new({ - display = 'Bulker Yakushev', - cost = 1500, - type = 'upgrade', - template = "ship-bulker-yakushev" - }), - shipCargoIvanov = Preset:new({ - display = 'Cargo Ivanov', - cost = 1500, - type = 'upgrade', - template = "ship-cargo-ivanov" - }) }, airdef = { bunker = Preset:new({ @@ -226,6 +190,12 @@ presets = { type='defense', template='infantry-red', }), + artillery = Preset:new({ + display = 'Artillery', + cost=2500, + type='defense', + template='artillery-red', + }), shorad = Preset:new({ display = 'SHORAD', cost=2500, @@ -298,6 +268,12 @@ presets = { type='defense', template='rapier-red', }), + roland = Preset:new({ + display = 'SAM', + cost=3000, + type='defense', + template='roland-red', + }), irondome = Preset:new({ display = 'SAM', cost=20000, @@ -324,6 +300,12 @@ presets = { type='defense', template='infantry-blue', }), + artillery = Preset:new({ + display = 'Artillery', + cost=2500, + type='defense', + template='artillery-blue', + }), shorad = Preset:new({ display = 'SHORAD', cost=2500, @@ -396,6 +378,12 @@ presets = { type='defense', template='rapier-blue', }), + roland = Preset:new({ + display = 'SAM', + cost=3000, + type='defense', + template='roland-blue', + }), irondome = Preset:new({ display = 'SAM', cost=20000, From 0a7b885626123697d846f71ec347dc8f8b942b87 Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Sat, 6 Apr 2024 20:27:39 +0300 Subject: [PATCH 206/243] Now will not call CarrierMap:new() when game.settings.pretense_controllable_carrier is disabled. --- game/pretense/pretenseluagenerator.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/game/pretense/pretenseluagenerator.py b/game/pretense/pretenseluagenerator.py index 5a13d8fb..769fd372 100644 --- a/game/pretense/pretenseluagenerator.py +++ b/game/pretense/pretenseluagenerator.py @@ -1509,7 +1509,9 @@ class PretenseLuaGenerator(LuaGenerator): ) lua_string_zones = "" - lua_string_carriers = self.generate_pretense_carrier_zones() + lua_string_carriers = "" + if self.game.settings.pretense_controllable_carrier: + lua_string_carriers += self.generate_pretense_carrier_zones() for cp in self.game.theater.controlpoints: cp_name_trimmed = "".join([i for i in cp.name.lower() if i.isalpha()]) From 40a01218dd40f1e2e93758bce091d776105c3c65 Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Thu, 28 Dec 2023 14:07:00 +0200 Subject: [PATCH 207/243] Implemented saving of a Pretense pre-generation backup save before generating a Pretense campaign. --- game/pretense/pretensemissiongenerator.py | 29 +++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/game/pretense/pretensemissiongenerator.py b/game/pretense/pretensemissiongenerator.py index 57b53b26..bf606169 100644 --- a/game/pretense/pretensemissiongenerator.py +++ b/game/pretense/pretensemissiongenerator.py @@ -1,5 +1,8 @@ from __future__ import annotations +import copy +import logging +import pickle from datetime import datetime from pathlib import Path from typing import TYPE_CHECKING @@ -71,6 +74,21 @@ class PretenseMissionGenerator(MissionGenerator): self.mission.options.load_from_dict(options) def generate_miz(self, output: Path) -> UnitMap: + now = datetime.now() + date_time = now.strftime("%Y-%d-%mT%H_%M_%S") + game_backup_pickle = pickle.dumps(self.game) + blue_coalition_backup = copy.deepcopy(self.game.blue) + red_coalition_backup = copy.deepcopy(self.game.red) + try: + with open( + self.game.savepath + ".pre-pretense-backup." + date_time, "wb" + ) as f: + pickle.dump(self.game, f) + except: + logging.error( + f"Unable to save Pretense pre-generation backup to {self.game.savepath}.pre-pretense-backup.{date_time}" + ) + if self.generation_started: raise RuntimeError( "Mission has already begun generating. To reset, create a new " @@ -139,6 +157,17 @@ class PretenseMissionGenerator(MissionGenerator): namegen.reset_numbers() self.mission.save(output) + print( + f"Loading pre-pretense save, number of BLUFOR squadrons: {len(self.game.blue.air_wing.squadrons)}" + ) + self.game = pickle.loads(game_backup_pickle) + self.game.blue = copy.deepcopy(blue_coalition_backup) + self.game.red = copy.deepcopy(red_coalition_backup) + print( + f"Loaded pre-pretense save, number of BLUFOR squadrons: {len(self.game.blue.air_wing.squadrons)}" + ) + self.game.on_load() + return self.unit_map def setup_mission_coalitions(self) -> None: From e69708ada9d823403a8823b67e5b217e642de88d Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Sat, 4 May 2024 09:24:56 +0300 Subject: [PATCH 208/243] Removed Retribution convoys from Pretense campaigns. Removed deep copies of both coalitions when saving the pre-Pretense generation backup. --- game/pretense/pretensemissiongenerator.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/game/pretense/pretensemissiongenerator.py b/game/pretense/pretensemissiongenerator.py index bf606169..7805d490 100644 --- a/game/pretense/pretensemissiongenerator.py +++ b/game/pretense/pretensemissiongenerator.py @@ -77,8 +77,6 @@ class PretenseMissionGenerator(MissionGenerator): now = datetime.now() date_time = now.strftime("%Y-%d-%mT%H_%M_%S") game_backup_pickle = pickle.dumps(self.game) - blue_coalition_backup = copy.deepcopy(self.game.blue) - red_coalition_backup = copy.deepcopy(self.game.red) try: with open( self.game.savepath + ".pre-pretense-backup." + date_time, "wb" @@ -161,8 +159,6 @@ class PretenseMissionGenerator(MissionGenerator): f"Loading pre-pretense save, number of BLUFOR squadrons: {len(self.game.blue.air_wing.squadrons)}" ) self.game = pickle.loads(game_backup_pickle) - self.game.blue = copy.deepcopy(blue_coalition_backup) - self.game.red = copy.deepcopy(red_coalition_backup) print( f"Loaded pre-pretense save, number of BLUFOR squadrons: {len(self.game.blue.air_wing.squadrons)}" ) From cbd230862fb85d98f8d16d888930e4017c41afc5 Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Sat, 4 May 2024 09:26:02 +0300 Subject: [PATCH 209/243] Air-start supersonic AI aircraft if the campaign is being flown in a WWII terrain. This will improve these terrains' use in cold war campaigns. Air-start AI fixed wing (non-VTOL) aircraft if the campaign is being flown in the South Atlantic terrain and the airfield is one of the Harrier-only ones in East Falklands. This will help avoid AI aircraft from smashing into the end of the runway and exploding. --- game/pretense/pretenseflightgroupspawner.py | 32 ++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/game/pretense/pretenseflightgroupspawner.py b/game/pretense/pretenseflightgroupspawner.py index dad732c8..124b86ec 100644 --- a/game/pretense/pretenseflightgroupspawner.py +++ b/game/pretense/pretenseflightgroupspawner.py @@ -5,7 +5,9 @@ from typing import Any, Tuple from dcs import Mission from dcs.country import Country from dcs.mapping import Vector2, Point -from dcs.terrain import NoParkingSlotError +from dcs.terrain import NoParkingSlotError, TheChannel, Falklands +from dcs.terrain.falklands.airports import San_Carlos_FOB, Goose_Green, Gull_Point +from dcs.terrain.thechannel.airports import Manston from dcs.unitgroup import ( FlyingGroup, ShipGroup, @@ -37,6 +39,10 @@ class PretenseNameGenerator(NameGenerator): namegen = PretenseNameGenerator +# Air-start AI aircraft which are faster than this on WWII terrains +# 1000 km/h is just above the max speed of the Harrier and Su-25, +# so they will still start normally from grass and dirt strips +WW2_TERRAIN_SUPERSONIC_AI_AIRSTART_SPEED = 1000 class PretenseFlightGroupSpawner(FlightGroupSpawner): @@ -147,11 +153,35 @@ class PretenseFlightGroupSpawner(FlightGroupSpawner): raise NoParkingSlotError elif isinstance(cp, Airfield): is_heli = self.flight.squadron.aircraft.helicopter + is_vtol = not is_heli and self.flight.squadron.aircraft.lha_capable if cp.has_helipads and is_heli: self.insert_into_pretense(name) pad_group = self._generate_at_cp_helipad(name, cp) if pad_group is not None: return pad_group + # Air-start supersonic AI aircraft if the campaign is being flown in a WWII terrain + # This will improve these terrains' use in cold war campaigns + if isinstance(cp.theater.terrain, TheChannel) and not isinstance( + cp.dcs_airport, Manston + ): + if ( + self.flight.client_count == 0 + and self.flight.squadron.aircraft.max_speed.speed_in_kph + > WW2_TERRAIN_SUPERSONIC_AI_AIRSTART_SPEED + ): + self.insert_into_pretense(name) + return self._generate_over_departure(name, cp) + # Air-start AI fixed wing (non-VTOL) aircraft if the campaign is being flown in the South Atlantic terrain and + # the airfield is one of the Harrier-only ones in East Falklands. + # This will help avoid AI aircraft from smashing into the end of the runway and exploding. + if isinstance(cp.theater.terrain, Falklands) and ( + isinstance(cp.dcs_airport, San_Carlos_FOB) + or isinstance(cp.dcs_airport, Goose_Green) + or isinstance(cp.dcs_airport, Gull_Point) + ): + if self.flight.client_count == 0 and is_vtol: + self.insert_into_pretense(name) + return self._generate_over_departure(name, cp) if ( cp.has_ground_spawns and len(self.ground_spawns[cp]) From c11575fcffbda288fb4a90f31a4db26604d310b3 Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Sat, 4 May 2024 09:27:17 +0300 Subject: [PATCH 210/243] Added missing custom_waypoints parameter in PretenseCargoFlightPlan. --- game/ato/flightplans/pretensecargo.py | 1 + 1 file changed, 1 insertion(+) diff --git a/game/ato/flightplans/pretensecargo.py b/game/ato/flightplans/pretensecargo.py index 4022139e..f143aaa2 100644 --- a/game/ato/flightplans/pretensecargo.py +++ b/game/ato/flightplans/pretensecargo.py @@ -101,6 +101,7 @@ class Builder(IBuilder[PretenseCargoFlightPlan, FerryLayout]): divert=builder.divert(self.flight.divert), bullseye=builder.bullseye(), nav_from=[], + custom_waypoints=list(), ) ferry_layout.departure = builder.join(offmap_transport_spawn) ferry_layout.nav_to.append(builder.join(offmap_transport_spawn)) From 557b441d135d1b23897d29071238f84e648e7e13 Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Sat, 4 May 2024 09:27:36 +0300 Subject: [PATCH 211/243] Replaced expend=AI.Task.WeaponExpend.ONE with expend=AI.Task.WeaponExpend.QUARTER on CAS and BAI flights. Added HQ7 (Crotale) sites to Pretense generator. Also, added logistics units to most SAM sites to ensure that launchers have access to missile/ammunition resupply. --- game/pretense/pretenseluagenerator.py | 96 ++++++++++++++++++++++----- 1 file changed, 81 insertions(+), 15 deletions(-) diff --git a/game/pretense/pretenseluagenerator.py b/game/pretense/pretenseluagenerator.py index 769fd372..eb505861 100644 --- a/game/pretense/pretenseluagenerator.py +++ b/game/pretense/pretenseluagenerator.py @@ -286,6 +286,7 @@ class PretenseLuaGenerator(LuaGenerator): "nasamsc", "rapier", "roland", + "hq7", "irondome", "davidsling", ]: @@ -386,6 +387,8 @@ class PretenseLuaGenerator(LuaGenerator): sam_presets["rapier"].enabled = True if ground_unit.unit_type.dcs_unit_type == AirDefence.Roland_ADS: sam_presets["roland"].enabled = True + if ground_unit.unit_type.dcs_unit_type == AirDefence.HQ_7_STR_SP: + sam_presets["hq7"].enabled = True if ground_unit.unit_type.dcs_unit_type == IRON_DOME_LN: sam_presets["irondome"].enabled = True if ground_unit.unit_type.dcs_unit_type == DAVID_SLING_LN: @@ -443,7 +446,7 @@ class PretenseLuaGenerator(LuaGenerator): f" presets.missions.{mission_name}:extend" + "({name='" + air_group - + "', altitude=15000, expend=AI.Task.WeaponExpend.ONE}),\n" + + "', altitude=15000, expend=AI.Task.WeaponExpend.QUARTER}),\n" ) elif mission_type == FlightType.BAI: mission_name = "attack.bai" @@ -454,7 +457,7 @@ class PretenseLuaGenerator(LuaGenerator): f" presets.missions.{mission_name}:extend" + "({name='" + air_group - + "', altitude=10000, expend=AI.Task.WeaponExpend.ONE}),\n" + + "', altitude=10000, expend=AI.Task.WeaponExpend.QUARTER}),\n" ) elif mission_type == FlightType.STRIKE: mission_name = "attack.strike" @@ -625,7 +628,7 @@ class PretenseLuaGenerator(LuaGenerator): f" presets.missions.{mission_name}:extend" + "({name='" + air_group - + "', altitude=15000, expend=AI.Task.WeaponExpend.ONE}),\n" + + "', altitude=15000, expend=AI.Task.WeaponExpend.QUARTER}),\n" ) elif mission_type == FlightType.BAI: mission_name = "attack.bai" @@ -636,7 +639,7 @@ class PretenseLuaGenerator(LuaGenerator): f" presets.missions.{mission_name}:extend" + "({name='" + air_group - + "', altitude=10000, expend=AI.Task.WeaponExpend.ONE}),\n" + + "', altitude=10000, expend=AI.Task.WeaponExpend.QUARTER}),\n" ) elif mission_type == FlightType.STRIKE: mission_name = "attack.strike" @@ -917,7 +920,7 @@ class PretenseLuaGenerator(LuaGenerator): ]: lua_string_carrier += ( f'{cp_name_trimmed}:addSupportFlight("{air_group}", 1000, CarrierCommand.{mission_name}, ' - + "{altitude = 15000, expend=AI.Task.WeaponExpend.ONE})\n" + + "{altitude = 15000, expend=AI.Task.WeaponExpend.QUARTER})\n" ) elif mission_type == FlightType.BAI: mission_name = "supportTypes.strike" @@ -926,7 +929,7 @@ class PretenseLuaGenerator(LuaGenerator): ]: lua_string_carrier += ( f'{cp_name_trimmed}:addSupportFlight("{air_group}", 1000, CarrierCommand.{mission_name}, ' - + "{altitude = 10000, expend=AI.Task.WeaponExpend.ONE})\n" + + "{altitude = 10000, expend=AI.Task.WeaponExpend.QUARTER})\n" ) elif mission_type == FlightType.STRIKE: mission_name = "supportTypes.strike" @@ -1169,9 +1172,11 @@ class PretenseLuaGenerator(LuaGenerator): lua_string_ground_groups += " units = {\n" lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.SHORAD, UnitClass.AAA, UnitClass.MANPAD])}",\n' lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.SHORAD, UnitClass.AAA, UnitClass.MANPAD])}",\n' - lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.LOGISTICS])}",\n' lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.AAA, UnitClass.SHORAD, UnitClass.MANPAD])}",\n' - lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.AAA, UnitClass.SHORAD, UnitClass.MANPAD])}"\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.AAA, UnitClass.SHORAD, UnitClass.MANPAD])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.LOGISTICS])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.LOGISTICS])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.LOGISTICS])}"\n' lua_string_ground_groups += " },\n" lua_string_ground_groups += " maxDist = 300,\n" lua_string_ground_groups += f' skill = "{skill_str}",\n' @@ -1183,12 +1188,16 @@ class PretenseLuaGenerator(LuaGenerator): lua_string_ground_groups += ' "p-19 s-125 sr",\n' lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.LOGISTICS])}",\n' lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.LOGISTICS])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.LOGISTICS])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.LOGISTICS])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.LOGISTICS])}",\n' lua_string_ground_groups += ' "S_75M_Volhov",\n' lua_string_ground_groups += ' "S_75M_Volhov",\n' lua_string_ground_groups += ' "S_75M_Volhov",\n' lua_string_ground_groups += ' "S_75M_Volhov",\n' lua_string_ground_groups += ' "S_75M_Volhov",\n' lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.SHORAD, UnitClass.AAA, UnitClass.MANPAD])}",\n' + lua_string_ground_groups += ' "RD_75",\n' lua_string_ground_groups += ' "SNR_75V"\n' lua_string_ground_groups += " },\n" lua_string_ground_groups += " maxDist = 300,\n" @@ -1209,6 +1218,10 @@ class PretenseLuaGenerator(LuaGenerator): lua_string_ground_groups += ' "Hawk ln",\n' lua_string_ground_groups += ' "Hawk tr",\n' lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.LOGISTICS])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.LOGISTICS])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.LOGISTICS])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.LOGISTICS])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.LOGISTICS])}",\n' lua_string_ground_groups += ' "Hawk sr"\n' lua_string_ground_groups += " },\n" lua_string_ground_groups += " maxDist = 300,\n" @@ -1224,6 +1237,9 @@ class PretenseLuaGenerator(LuaGenerator): lua_string_ground_groups += ' "Patriot str",\n' lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.LOGISTICS])}",\n' lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.LOGISTICS])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.LOGISTICS])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.LOGISTICS])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.LOGISTICS])}",\n' lua_string_ground_groups += ' "Patriot ln",\n' lua_string_ground_groups += ' "Patriot ln",\n' lua_string_ground_groups += ' "Patriot ln",\n' @@ -1247,6 +1263,9 @@ class PretenseLuaGenerator(LuaGenerator): lua_string_ground_groups += ' "5p73 s-125 ln",\n' lua_string_ground_groups += ' "5p73 s-125 ln",\n' lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.LOGISTICS])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.LOGISTICS])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.LOGISTICS])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.LOGISTICS])}",\n' lua_string_ground_groups += ' "5p73 s-125 ln",\n' lua_string_ground_groups += ' "5p73 s-125 ln"\n' lua_string_ground_groups += " },\n" @@ -1263,8 +1282,11 @@ class PretenseLuaGenerator(LuaGenerator): lua_string_ground_groups += ' "Kub 2P25 ln",\n' lua_string_ground_groups += ' "Kub 2P25 ln",\n' lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.SHORAD, UnitClass.AAA, UnitClass.MANPAD])}",\n' - lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.LOGISTICS])}",\n' lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.SHORAD, UnitClass.AAA, UnitClass.MANPAD])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.LOGISTICS])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.LOGISTICS])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.LOGISTICS])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.LOGISTICS])}",\n' lua_string_ground_groups += ' "Kub 2P25 ln"\n' lua_string_ground_groups += " },\n" lua_string_ground_groups += " maxDist = 300,\n" @@ -1283,6 +1305,8 @@ class PretenseLuaGenerator(LuaGenerator): lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.LOGISTICS])}",\n' lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.LOGISTICS])}",\n' lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.LOGISTICS])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.LOGISTICS])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.LOGISTICS])}",\n' lua_string_ground_groups += ' "S-300PS 5P85C ln",\n' lua_string_ground_groups += ' "S-300PS 5P85C ln",\n' lua_string_ground_groups += ' "S-300PS 5P85C ln",\n' @@ -1300,6 +1324,9 @@ class PretenseLuaGenerator(LuaGenerator): lua_string_ground_groups += ' "RLS_19J6",\n' lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.LOGISTICS])}",\n' lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.LOGISTICS])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.LOGISTICS])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.LOGISTICS])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.LOGISTICS])}",\n' lua_string_ground_groups += ' "RPC_5N62V",\n' lua_string_ground_groups += ' "S-200_Launcher",\n' lua_string_ground_groups += ' "S-200_Launcher",\n' @@ -1327,6 +1354,9 @@ class PretenseLuaGenerator(LuaGenerator): lua_string_ground_groups += ' "SA-11 Buk SR 9S18M1",\n' lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.LOGISTICS])}",\n' lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.LOGISTICS])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.LOGISTICS])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.LOGISTICS])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.LOGISTICS])}",\n' lua_string_ground_groups += ' "SA-11 Buk CC 9S470M1"\n' lua_string_ground_groups += " },\n" lua_string_ground_groups += " maxDist = 300,\n" @@ -1343,6 +1373,9 @@ class PretenseLuaGenerator(LuaGenerator): lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.AAA, UnitClass.SHORAD, UnitClass.MANPAD])}",\n' lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.LOGISTICS])}",\n' lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.LOGISTICS])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.LOGISTICS])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.LOGISTICS])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.LOGISTICS])}",\n' lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.SHORAD, UnitClass.AAA, UnitClass.MANPAD])}",\n' lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.SHORAD, UnitClass.AAA, UnitClass.MANPAD])}",\n' lua_string_ground_groups += ' "NASAMS_LN_B",\n' @@ -1367,6 +1400,9 @@ class PretenseLuaGenerator(LuaGenerator): lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.AAA, UnitClass.SHORAD, UnitClass.MANPAD])}",\n' lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.LOGISTICS])}",\n' lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.LOGISTICS])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.LOGISTICS])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.LOGISTICS])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.LOGISTICS])}",\n' lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.SHORAD, UnitClass.AAA, UnitClass.MANPAD])}",\n' lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.SHORAD, UnitClass.AAA, UnitClass.MANPAD])}",\n' lua_string_ground_groups += ' "NASAMS_LN_C",\n' @@ -1391,6 +1427,9 @@ class PretenseLuaGenerator(LuaGenerator): lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.AAA, UnitClass.SHORAD, UnitClass.MANPAD])}",\n' lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.LOGISTICS])}",\n' lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.LOGISTICS])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.LOGISTICS])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.LOGISTICS])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.LOGISTICS])}",\n' lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.SHORAD, UnitClass.AAA, UnitClass.MANPAD])}",\n' lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.SHORAD, UnitClass.AAA, UnitClass.MANPAD])}",\n' lua_string_ground_groups += ' "rapier_fsa_launcher",\n' @@ -1421,6 +1460,9 @@ class PretenseLuaGenerator(LuaGenerator): lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.AAA, UnitClass.SHORAD, UnitClass.MANPAD])}",\n' lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.LOGISTICS])}",\n' lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.LOGISTICS])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.LOGISTICS])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.LOGISTICS])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.LOGISTICS])}",\n' lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.SHORAD, UnitClass.AAA, UnitClass.MANPAD])}",\n' lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.SHORAD, UnitClass.AAA, UnitClass.MANPAD])}",\n' lua_string_ground_groups += ' "Roland ADS",\n' @@ -1436,6 +1478,31 @@ class PretenseLuaGenerator(LuaGenerator): lua_string_ground_groups += " dataCategory = TemplateDB.type.group\n" lua_string_ground_groups += "}\n" + lua_string_ground_groups += 'TemplateDB.templates["hq7-' + side_str + '"] = {\n' + lua_string_ground_groups += " units = {\n" + lua_string_ground_groups += ' "HQ-7_LN_EO",\n' + lua_string_ground_groups += ' "HQ-7_LN_EO",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.AAA, UnitClass.SHORAD, UnitClass.MANPAD])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.LOGISTICS])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.LOGISTICS])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.LOGISTICS])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.LOGISTICS])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.LOGISTICS])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.SHORAD, UnitClass.AAA, UnitClass.MANPAD])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.SHORAD, UnitClass.AAA, UnitClass.MANPAD])}",\n' + lua_string_ground_groups += ' "HQ-7_LN_SP",\n' + lua_string_ground_groups += ' "HQ-7_LN_SP",\n' + lua_string_ground_groups += ' "HQ-7_LN_SP",\n' + lua_string_ground_groups += ' "HQ-7_LN_SP",\n' + lua_string_ground_groups += ' "HQ-7_STR_SP",\n' + lua_string_ground_groups += ' "HQ-7_STR_SP",\n' + lua_string_ground_groups += ' "HQ-7_STR_SP"\n' + lua_string_ground_groups += " },\n" + lua_string_ground_groups += " maxDist = 300,\n" + lua_string_ground_groups += f' skill = "{skill_str}",\n' + lua_string_ground_groups += " dataCategory = TemplateDB.type.group\n" + lua_string_ground_groups += "}\n" + return lua_string_ground_groups @staticmethod @@ -1599,12 +1666,11 @@ class PretenseLuaGenerator(LuaGenerator): connected_points, cp.name, other_cp.name ) for sea_connection in cp.shipping_lanes: - if sea_connection.is_friendly_to(cp): - lua_string_connman += self.generate_pretense_zone_connection( - connected_points, - cp.name, - sea_connection.name, - ) + lua_string_connman += self.generate_pretense_zone_connection( + connected_points, + cp.name, + sea_connection.name, + ) if len(cp.connected_points) == 0 and len(cp.shipping_lanes) == 0: # Also connect carrier and LHA control points to adjacent friendly points if cp.is_fleet and ( From 3bf77042fd31497c520a55cab33bb38fbaa86372 Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Tue, 7 May 2024 15:20:20 +0300 Subject: [PATCH 212/243] Added HQ7 (Crotale) sites to Pretense generator. --- resources/plugins/pretense/init_body_1.lua | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/resources/plugins/pretense/init_body_1.lua b/resources/plugins/pretense/init_body_1.lua index 2cac3514..4b18a0fd 100644 --- a/resources/plugins/pretense/init_body_1.lua +++ b/resources/plugins/pretense/init_body_1.lua @@ -286,11 +286,11 @@ presets = { type='defense', template='davidsling-red', }), - redShipGroup = Preset:new({ + hq7 = Preset:new({ display = 'SAM', cost=3000, type='defense', - template='redShipGroup', + template='hq7-red', }) }, blue = { @@ -396,11 +396,11 @@ presets = { type='defense', template='davidsling-blue', }), - blueShipGroup = Preset:new({ + hq7 = Preset:new({ display = 'SAM', cost=3000, type='defense', - template='blueShipGroup', + template='hq7-blue', }) } }, From 16377fe5637a1536017416f898df0aefc67b1a62 Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Tue, 7 May 2024 15:20:55 +0300 Subject: [PATCH 213/243] Added Iron Dome & David's Sling sites to Pretense generator. --- game/pretense/pretenseluagenerator.py | 56 +++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/game/pretense/pretenseluagenerator.py b/game/pretense/pretenseluagenerator.py index eb505861..54922665 100644 --- a/game/pretense/pretenseluagenerator.py +++ b/game/pretense/pretenseluagenerator.py @@ -1503,6 +1503,62 @@ class PretenseLuaGenerator(LuaGenerator): lua_string_ground_groups += " dataCategory = TemplateDB.type.group\n" lua_string_ground_groups += "}\n" + lua_string_ground_groups += ( + 'TemplateDB.templates["irondome-' + side_str + '"] = {\n' + ) + lua_string_ground_groups += " units = {\n" + lua_string_ground_groups += ' "Iron_Dome_David_Sling_CP",\n' + lua_string_ground_groups += ' "ELM2084_MMR_AD_RT",\n' + lua_string_ground_groups += ' "ELM2084_MMR_AD_SC",\n' + lua_string_ground_groups += ' "ELM2084_MMR_WLR",\n' + lua_string_ground_groups += ' "IRON_DOME_LN",\n' + lua_string_ground_groups += ' "IRON_DOME_LN",\n' + lua_string_ground_groups += ' "IRON_DOME_LN",\n' + lua_string_ground_groups += ' "IRON_DOME_LN",\n' + lua_string_ground_groups += ' "IRON_DOME_LN",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.LOGISTICS])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.LOGISTICS])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.LOGISTICS])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.LOGISTICS])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.LOGISTICS])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.SHORAD, UnitClass.AAA, UnitClass.MANPAD])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.SHORAD, UnitClass.AAA, UnitClass.MANPAD])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.AAA, UnitClass.SHORAD, UnitClass.MANPAD])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.AAA, UnitClass.SHORAD, UnitClass.MANPAD])}"\n' + lua_string_ground_groups += " },\n" + lua_string_ground_groups += " maxDist = 300,\n" + lua_string_ground_groups += f' skill = "{skill_str}",\n' + lua_string_ground_groups += " dataCategory = TemplateDB.type.group\n" + lua_string_ground_groups += "}\n" + + lua_string_ground_groups += ( + 'TemplateDB.templates["davidsling-' + side_str + '"] = {\n' + ) + lua_string_ground_groups += " units = {\n" + lua_string_ground_groups += ' "Iron_Dome_David_Sling_CP",\n' + lua_string_ground_groups += ' "ELM2084_MMR_AD_RT",\n' + lua_string_ground_groups += ' "ELM2084_MMR_AD_SC",\n' + lua_string_ground_groups += ' "ELM2084_MMR_WLR",\n' + lua_string_ground_groups += ' "DAVID_SLING_LN",\n' + lua_string_ground_groups += ' "DAVID_SLING_LN",\n' + lua_string_ground_groups += ' "DAVID_SLING_LN",\n' + lua_string_ground_groups += ' "DAVID_SLING_LN",\n' + lua_string_ground_groups += ' "DAVID_SLING_LN",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.LOGISTICS])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.LOGISTICS])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.LOGISTICS])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.LOGISTICS])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.LOGISTICS])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.SHORAD, UnitClass.AAA, UnitClass.MANPAD])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.SHORAD, UnitClass.AAA, UnitClass.MANPAD])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.AAA, UnitClass.SHORAD, UnitClass.MANPAD])}",\n' + lua_string_ground_groups += f' "{self.get_ground_unit(coalition, side, [UnitClass.AAA, UnitClass.SHORAD, UnitClass.MANPAD])}"\n' + lua_string_ground_groups += " },\n" + lua_string_ground_groups += " maxDist = 300,\n" + lua_string_ground_groups += f' skill = "{skill_str}",\n' + lua_string_ground_groups += " dataCategory = TemplateDB.type.group\n" + lua_string_ground_groups += "}\n" + return lua_string_ground_groups @staticmethod From 85f4f66f21e064a2f93958ee1f5ba20c2898184c Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Thu, 9 May 2024 20:57:29 +0300 Subject: [PATCH 214/243] pretenseaircraftgenerator.py now obeys the new setting Default start type for Player flights. --- game/pretense/pretenseaircraftgenerator.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/game/pretense/pretenseaircraftgenerator.py b/game/pretense/pretenseaircraftgenerator.py index f8f13157..32cc50f7 100644 --- a/game/pretense/pretenseaircraftgenerator.py +++ b/game/pretense/pretenseaircraftgenerator.py @@ -529,6 +529,12 @@ class PretenseAircraftGenerator: StartType.COLD, divert=cp, ) + if flight.roster is not None and flight.roster.player_count > 0: + flight.start_type = ( + squadron.coalition.game.settings.default_start_type_client + ) + else: + flight.start_type = squadron.coalition.game.settings.default_start_type package.add_flight(flight) flight.state = WaitingForStart( flight, self.game.settings, self.game.conditions.start_time @@ -683,6 +689,13 @@ class PretenseAircraftGenerator: flight, self.game.settings, self.game.conditions.start_time ) ato.add_package(package) + if squadron is not None: + if flight.roster is not None and flight.roster.player_count > 0: + flight.start_type = ( + squadron.coalition.game.settings.default_start_type_client + ) + else: + flight.start_type = squadron.coalition.game.settings.default_start_type return def generate_pretense_aircraft_for_players( @@ -730,7 +743,7 @@ class PretenseAircraftGenerator: squadron, aircraft_per_flight, squadron.primary_task, - StartType.COLD, + squadron.coalition.game.settings.default_start_type_client, divert=cp, ) for roster_pilot in flight.roster.members: From 150a4dde497488ffaf525454e511cf6314c4c76d Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Fri, 10 May 2024 12:46:27 +0300 Subject: [PATCH 215/243] Added missing call squadron: Optional[Squadron] = None to help avoid reference before assignment error. --- game/pretense/pretenseaircraftgenerator.py | 1 + 1 file changed, 1 insertion(+) diff --git a/game/pretense/pretenseaircraftgenerator.py b/game/pretense/pretenseaircraftgenerator.py index 32cc50f7..46b4083d 100644 --- a/game/pretense/pretenseaircraftgenerator.py +++ b/game/pretense/pretenseaircraftgenerator.py @@ -564,6 +564,7 @@ class PretenseAircraftGenerator: """ aircraft_per_flight = 1 + squadron: Optional[Squadron] = None if (cp.has_helipads or isinstance(cp, Airfield)) and not cp.is_fleet: flight_type = FlightType.AIR_ASSAULT squadron = self.generate_pretense_squadron( From bfe008775db94d052b320be4d04c118e9e2614cf Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Tue, 21 May 2024 00:25:20 +0300 Subject: [PATCH 216/243] Implemented PretenseNameGenerator.pretense_trimmed_cp_name to remove Scandic characters from Pretense zone names. --- game/pretense/pretenseaircraftgenerator.py | 5 ++- game/pretense/pretenseflightgroupspawner.py | 20 ++++----- game/pretense/pretenseluagenerator.py | 46 +++++++++++++++++---- game/pretense/pretensetgogenerator.py | 15 +++---- game/pretense/pretensetriggergenerator.py | 13 ++++-- 5 files changed, 69 insertions(+), 30 deletions(-) diff --git a/game/pretense/pretenseaircraftgenerator.py b/game/pretense/pretenseaircraftgenerator.py index 46b4083d..dce749da 100644 --- a/game/pretense/pretenseaircraftgenerator.py +++ b/game/pretense/pretenseaircraftgenerator.py @@ -27,6 +27,7 @@ from game.missiongenerator.missiondata import MissionData from game.pretense.pretenseflightgroupconfigurator import ( PretenseFlightGroupConfigurator, ) +from game.pretense.pretenseflightgroupspawner import PretenseNameGenerator from game.radio.radios import RadioRegistry from game.radio.tacan import TacanRegistry from game.runways import RunwayData @@ -772,7 +773,7 @@ class PretenseAircraftGenerator: cp: Control point to generate aircraft for. flight: The current flight being generated. """ - cp_name_trimmed = "".join([i for i in cp.name.lower() if i.isalpha()]) + cp_name_trimmed = PretenseNameGenerator.pretense_trimmed_cp_name(cp.name) for side in range(1, 3): if cp_name_trimmed not in cp.coalition.game.pretense_air[side]: @@ -792,7 +793,7 @@ class PretenseAircraftGenerator: flight: The current flight being generated. """ flight_type = flight.flight_type - cp_name_trimmed = "".join([i for i in cp.name.lower() if i.isalpha()]) + cp_name_trimmed = PretenseNameGenerator.pretense_trimmed_cp_name(cp.name) for side in range(1, 3): if cp_name_trimmed not in flight.coalition.game.pretense_air[side]: diff --git a/game/pretense/pretenseflightgroupspawner.py b/game/pretense/pretenseflightgroupspawner.py index 124b86ec..e773144f 100644 --- a/game/pretense/pretenseflightgroupspawner.py +++ b/game/pretense/pretenseflightgroupspawner.py @@ -32,11 +32,19 @@ class PretenseNameGenerator(NameGenerator): @classmethod def next_pretense_aircraft_name(cls, cp: ControlPoint, flight: Flight) -> str: cls.aircraft_number += 1 - cp_name_trimmed = "".join([i for i in cp.name.lower() if i.isalpha()]) + cp_name_trimmed = cls.pretense_trimmed_cp_name(cp.name) return "{}-{}-{}".format( cp_name_trimmed, str(flight.flight_type).lower(), cls.aircraft_number ) + @classmethod + def pretense_trimmed_cp_name(cls, cp_name: str): + cp_name_trimmed = "".join([i for i in cp_name.lower() if i.isalpha()]) + cp_name_trimmed = cp_name_trimmed.replace("ä", "a") + cp_name_trimmed = cp_name_trimmed.replace("ö", "o") + cp_name_trimmed = cp_name_trimmed.replace("ø", "o") + return cp_name_trimmed + namegen = PretenseNameGenerator # Air-start AI aircraft which are faster than this on WWII terrains @@ -83,7 +91,7 @@ class PretenseFlightGroupSpawner(FlightGroupSpawner): == self.flight.coalition.game.coalition_for(is_player) else 1 ) - cp_name_trimmed = "".join([i for i in cp.name.lower() if i.isalpha()]) + cp_name_trimmed = PretenseNameGenerator.pretense_trimmed_cp_name(cp.name) if self.flight.client_count == 0: self.flight.coalition.game.pretense_air[cp_side][cp_name_trimmed][ @@ -98,14 +106,6 @@ class PretenseFlightGroupSpawner(FlightGroupSpawner): def generate_flight_at_departure(self) -> FlyingGroup[Any]: cp = self.flight.departure name = namegen.next_pretense_aircraft_name(cp, self.flight) - is_player = True - cp_side = ( - 2 - if self.flight.coalition - == self.flight.coalition.game.coalition_for(is_player) - else 1 - ) - cp_name_trimmed = "".join([i for i in cp.name.lower() if i.isalpha()]) try: if self.start_type is StartType.IN_FLIGHT: diff --git a/game/pretense/pretenseluagenerator.py b/game/pretense/pretenseluagenerator.py index 54922665..a56a0a84 100644 --- a/game/pretense/pretenseluagenerator.py +++ b/game/pretense/pretenseluagenerator.py @@ -24,6 +24,7 @@ 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.pretense.pretenseflightgroupspawner import PretenseNameGenerator from game.pretense.pretensetgogenerator import PretenseGroundObjectGenerator from game.theater import Airfield, OffMapSpawn, TheaterGroundObject from game.theater.iadsnetwork.iadsrole import IadsRole @@ -266,7 +267,7 @@ class PretenseLuaGenerator(LuaGenerator): def generate_pretense_land_upgrade_supply(self, cp_name: str, cp_side: int) -> str: lua_string_zones = "" - cp_name_trimmed = "".join([i for i in cp_name.lower() if i.isalpha()]) + cp_name_trimmed = PretenseNameGenerator.pretense_trimmed_cp_name(cp_name) cp_side_str = "blue" if cp_side == PRETENSE_BLUE_SIDE else "red" cp = self.game.theater.controlpoints[0] for loop_cp in self.game.theater.controlpoints: @@ -531,7 +532,7 @@ class PretenseLuaGenerator(LuaGenerator): def generate_pretense_sea_upgrade_supply(self, cp_name: str, cp_side: int) -> str: lua_string_zones = "" - cp_name_trimmed = "".join([i for i in cp_name.lower() if i.isalpha()]) + cp_name_trimmed = PretenseNameGenerator.pretense_trimmed_cp_name(cp_name) cp_side_str = "blue" if cp_side == PRETENSE_BLUE_SIDE else "red" supply_ship = "oilPump" @@ -715,7 +716,7 @@ class PretenseLuaGenerator(LuaGenerator): is_artillery_zone = random.choice([True, False]) lua_string_zones = "" - cp_name_trimmed = "".join([i for i in cp_name.lower() if i.isalpha()]) + cp_name_trimmed = PretenseNameGenerator.pretense_trimmed_cp_name(cp_name) lua_string_zones += f"zones.{cp_name_trimmed}:defineUpgrades(" + "{\n" lua_string_zones += " [1] = { --red side\n" @@ -815,7 +816,7 @@ class PretenseLuaGenerator(LuaGenerator): def generate_pretense_zone_sea(self, cp_name: str) -> str: lua_string_zones = "" - cp_name_trimmed = "".join([i for i in cp_name.lower() if i.isalpha()]) + cp_name_trimmed = PretenseNameGenerator.pretense_trimmed_cp_name(cp_name) lua_string_zones += f"zones.{cp_name_trimmed}:defineUpgrades(" + "{\n" lua_string_zones += " [1] = { --red side\n" @@ -853,7 +854,7 @@ class PretenseLuaGenerator(LuaGenerator): cp_carrier_group_name: str | None, ) -> str: lua_string_carrier = "\n" - cp_name_trimmed = "".join([i for i in cp_name.lower() if i.isalpha()]) + cp_name_trimmed = PretenseNameGenerator.pretense_trimmed_cp_name(cp_name) link4carriers = [Stennis, CVN_71, CVN_72, CVN_73, CVN_75, Forrestal] is_link4carrier = False @@ -1587,6 +1588,19 @@ class PretenseLuaGenerator(LuaGenerator): cp_name_conn_other = "".join( [i for i in other_cp_name if i.isalnum() or i.isspace() or i == "-"] ) + cp_name_conn = cp_name_conn.replace("Ä", "A") + cp_name_conn = cp_name_conn.replace("Ö", "O") + cp_name_conn = cp_name_conn.replace("Ø", "O") + cp_name_conn = cp_name_conn.replace("ä", "a") + cp_name_conn = cp_name_conn.replace("ö", "o") + cp_name_conn = cp_name_conn.replace("ø", "o") + + cp_name_conn_other = cp_name_conn_other.replace("Ä", "A") + cp_name_conn_other = cp_name_conn_other.replace("Ö", "O") + cp_name_conn_other = cp_name_conn_other.replace("Ø", "O") + cp_name_conn_other = cp_name_conn_other.replace("ä", "a") + cp_name_conn_other = cp_name_conn_other.replace("ö", "o") + cp_name_conn_other = cp_name_conn_other.replace("ø", "o") lua_string_connman = ( f" cm: addConnection('{cp_name_conn}', '{cp_name_conn_other}')\n" ) @@ -1637,10 +1651,16 @@ class PretenseLuaGenerator(LuaGenerator): lua_string_carriers += self.generate_pretense_carrier_zones() for cp in self.game.theater.controlpoints: - cp_name_trimmed = "".join([i for i in cp.name.lower() if i.isalpha()]) + cp_name_trimmed = PretenseNameGenerator.pretense_trimmed_cp_name(cp.name) cp_name = "".join( [i for i in cp.name if i.isalnum() or i.isspace() or i == "-"] ) + cp_name.replace("Ä", "A") + cp_name.replace("Ö", "O") + cp_name.replace("Ø", "O") + cp_name.replace("ä", "a") + cp_name.replace("ö", "o") + cp_name.replace("ø", "o") cp_side = 2 if cp.captured else 1 if isinstance(cp, OffMapSpawn): @@ -1665,6 +1685,12 @@ class PretenseLuaGenerator(LuaGenerator): self.game.pretense_ground_supply[side][cp_name_trimmed] = list() if cp_name_trimmed not in self.game.pretense_ground_assault[cp_side]: self.game.pretense_ground_assault[side][cp_name_trimmed] = list() + cp_name = cp_name.replace("Ä", "A") + cp_name = cp_name.replace("Ö", "O") + cp_name = cp_name.replace("Ø", "O") + cp_name = cp_name.replace("ä", "a") + cp_name = cp_name.replace("ö", "o") + cp_name = cp_name.replace("ø", "o") lua_string_zones += ( f"zones.{cp_name_trimmed} = ZoneCommand:new('{cp_name}')\n" ) @@ -1783,7 +1809,9 @@ class PretenseLuaGenerator(LuaGenerator): cp_side_captured = cp_side == 2 if cp_side_captured != cp.captured: continue - cp_name_trimmed = "".join([i for i in cp.name.lower() if i.isalpha()]) + cp_name_trimmed = PretenseNameGenerator.pretense_trimmed_cp_name( + cp.name + ) for mission_type in self.game.pretense_air[cp_side][cp_name_trimmed]: if mission_type == FlightType.PRETENSE_CARGO: for air_group in self.game.pretense_air[cp_side][ @@ -1796,7 +1824,9 @@ class PretenseLuaGenerator(LuaGenerator): lua_string_supply += "local offmapZones = {\n" for cp in self.game.theater.controlpoints: if isinstance(cp, Airfield): - cp_name_trimmed = "".join([i for i in cp.name.lower() if i.isalpha()]) + cp_name_trimmed = PretenseNameGenerator.pretense_trimmed_cp_name( + cp.name + ) lua_string_supply += f" zones.{cp_name_trimmed},\n" lua_string_supply += "}\n" diff --git a/game/pretense/pretensetgogenerator.py b/game/pretense/pretensetgogenerator.py index 82217973..3d02298c 100644 --- a/game/pretense/pretensetgogenerator.py +++ b/game/pretense/pretensetgogenerator.py @@ -38,6 +38,7 @@ from game.missiongenerator.tgogenerator import ( GenericCarrierGenerator, ) from game.point_with_heading import PointWithHeading +from game.pretense.pretenseflightgroupspawner import PretenseNameGenerator from game.radio.radios import RadioRegistry from game.radio.tacan import TacanRegistry, TacanBand, TacanUsage from game.runways import RunwayData @@ -356,8 +357,8 @@ class PretenseGroundObjectGenerator(GroundObjectGenerator): def generate(self) -> None: if self.culled: return - cp_name_trimmed = "".join( - [i for i in self.ground_object.control_point.name.lower() if i.isalpha()] + cp_name_trimmed = PretenseNameGenerator.pretense_trimmed_cp_name( + self.ground_object.control_point.name ) country_name_trimmed = "".join( [i for i in self.country.shortname.lower() if i.isalpha()] @@ -456,8 +457,8 @@ class PretenseGroundObjectGenerator(GroundObjectGenerator): control_point = other_cp break - cp_name_trimmed = "".join( - [i for i in control_point.name.lower() if i.isalpha()] + cp_name_trimmed = PretenseNameGenerator.pretense_trimmed_cp_name( + control_point.name ) is_player = True side = ( @@ -565,8 +566,8 @@ class PretenseGroundObjectGenerator(GroundObjectGenerator): control_point = other_cp break - cp_name_trimmed = "".join( - [i for i in control_point.name.lower() if i.isalpha()] + cp_name_trimmed = PretenseNameGenerator.pretense_trimmed_cp_name( + control_point.name ) is_player = True side = ( @@ -825,7 +826,7 @@ class PretenseTgoGenerator(TgoGenerator): def generate(self) -> None: for cp in self.game.theater.controlpoints: - cp_name_trimmed = "".join([i for i in cp.name.lower() if i.isalpha()]) + cp_name_trimmed = PretenseNameGenerator.pretense_trimmed_cp_name(cp.name) for side in range(1, 3): if cp_name_trimmed not in self.game.pretense_ground_supply[side]: self.game.pretense_ground_supply[side][cp_name_trimmed] = list() diff --git a/game/pretense/pretensetriggergenerator.py b/game/pretense/pretensetriggergenerator.py index 8500bf47..1f1bd829 100644 --- a/game/pretense/pretensetriggergenerator.py +++ b/game/pretense/pretensetriggergenerator.py @@ -35,6 +35,7 @@ from numpy import cross, einsum, arctan2 from shapely import MultiPolygon, Point as ShapelyPoint from game.naming import ALPHA_MILITARY +from game.pretense.pretenseflightgroupspawner import PretenseNameGenerator from game.theater import Airfield from game.theater.controlpoint import Fob, TRIGGER_RADIUS_CAPTURE, OffMapSpawn @@ -357,6 +358,12 @@ class PretenseTriggerGenerator: cp_name = "".join( [i for i in cp.name if i.isalnum() or i.isspace() or i == "-"] ) + cp_name = cp_name.replace("Ä", "A") + cp_name = cp_name.replace("Ö", "O") + cp_name = cp_name.replace("Ø", "O") + cp_name = cp_name.replace("ä", "a") + cp_name = cp_name.replace("ö", "o") + cp_name = cp_name.replace("ø", "o") if not isinstance(cp, OffMapSpawn): zone_color = {1: 0.0, 2: 0.0, 3: 0.0, 4: 0.15} self.mission.triggers.add_triggerzone( @@ -366,7 +373,7 @@ class PretenseTriggerGenerator: name=cp_name, color=zone_color, ) - cp_name_trimmed = "".join([i for i in cp.name.lower() if i.isalpha()]) + cp_name_trimmed = PretenseNameGenerator.pretense_trimmed_cp_name(cp.name) tgo_num = 0 for tgo in cp.ground_objects: if cp.is_fleet or tgo.sea_object: @@ -414,8 +421,8 @@ class PretenseTriggerGenerator: 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.isalpha()] + cp_name_trimmed = PretenseNameGenerator.pretense_trimmed_cp_name( + cp_airport.name ) zone_color = {1: 0.0, 2: 1.0, 3: 0.5, 4: 0.15} if cp_airport is None: From 0fd0e2c3c2f044731d875d53dc353a2e145234ba Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Sun, 26 May 2024 17:07:17 +0300 Subject: [PATCH 217/243] Added function return type annotation to pretense_trimmed_cp_name() --- game/pretense/pretenseflightgroupspawner.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/game/pretense/pretenseflightgroupspawner.py b/game/pretense/pretenseflightgroupspawner.py index e773144f..1a0c3572 100644 --- a/game/pretense/pretenseflightgroupspawner.py +++ b/game/pretense/pretenseflightgroupspawner.py @@ -38,7 +38,7 @@ class PretenseNameGenerator(NameGenerator): ) @classmethod - def pretense_trimmed_cp_name(cls, cp_name: str): + def pretense_trimmed_cp_name(cls, cp_name: str) -> str: cp_name_trimmed = "".join([i for i in cp_name.lower() if i.isalpha()]) cp_name_trimmed = cp_name_trimmed.replace("ä", "a") cp_name_trimmed = cp_name_trimmed.replace("ö", "o") From b8011361459b70b2eca8b47a5256113e33d4902b Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Tue, 28 May 2024 18:59:09 +0300 Subject: [PATCH 218/243] Updated pretenseluagenerator.py for the latest pydcs. --- game/pretense/pretenseluagenerator.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/game/pretense/pretenseluagenerator.py b/game/pretense/pretenseluagenerator.py index a56a0a84..afdf688c 100644 --- a/game/pretense/pretenseluagenerator.py +++ b/game/pretense/pretenseluagenerator.py @@ -354,7 +354,7 @@ class PretenseLuaGenerator(LuaGenerator): sam_presets["sa2"].enabled = True if ( ground_unit.unit_type.dcs_unit_type - == AirDefence.X_5p73_s_125_ln + == AirDefence.x_5p73_s_125_ln ): sam_presets["sa3"].enabled = True if ground_unit.unit_type.dcs_unit_type == AirDefence.S_200_Launcher: @@ -383,7 +383,7 @@ class PretenseLuaGenerator(LuaGenerator): sam_presets["nasamsc"].enabled = True if ( ground_unit.unit_type.dcs_unit_type - == AirDefence.Rapier_fsa_launcher + == AirDefence.rapier_fsa_launcher ): sam_presets["rapier"].enabled = True if ground_unit.unit_type.dcs_unit_type == AirDefence.Roland_ADS: From 25e2da2b917a70801ce43fcca9e828438a276b3d Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Sat, 1 Jun 2024 16:45:15 +0300 Subject: [PATCH 219/243] Pretense should always use Client slots. --- game/pretense/pretenseaircraftgenerator.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/game/pretense/pretenseaircraftgenerator.py b/game/pretense/pretenseaircraftgenerator.py index dce749da..82b8151d 100644 --- a/game/pretense/pretenseaircraftgenerator.py +++ b/game/pretense/pretenseaircraftgenerator.py @@ -92,9 +92,8 @@ class PretenseAircraftGenerator: @cached_property def use_client(self) -> bool: """True if Client should be used instead of Player.""" - blue_clients = self.client_slots_in_ato(self.game.blue.ato) - red_clients = self.client_slots_in_ato(self.game.red.ato) - return blue_clients + red_clients > 1 + """Pretense should always use Client slots.""" + return True @staticmethod def client_slots_in_ato(ato: AirTaskingOrder) -> int: From 8df77dec2a140f2baaf50dc9ea5dece7d90e1a37 Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Sat, 1 Jun 2024 16:59:30 +0300 Subject: [PATCH 220/243] Fixed a bug where Pretense campaign generation would hang if the campaign had off-map spawns. --- game/pretense/pretenseaircraftgenerator.py | 30 +++++++++++----------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/game/pretense/pretenseaircraftgenerator.py b/game/pretense/pretenseaircraftgenerator.py index 82b8151d..0002dc46 100644 --- a/game/pretense/pretenseaircraftgenerator.py +++ b/game/pretense/pretenseaircraftgenerator.py @@ -357,10 +357,10 @@ class PretenseAircraftGenerator: # Then plan SEAD and DEAD, if capable if sead_capable_cp: while num_of_sead < self.game.settings.pretense_sead_flights_per_cp: + # Intentionally don't spawn anything at OffMapSpawns in Pretense + if isinstance(cp, OffMapSpawn): + break for squadron in random_squadron_list: - # Intentionally don't spawn anything at OffMapSpawns in Pretense - if isinstance(squadron.location, OffMapSpawn): - continue if cp.coalition != squadron.coalition: continue if num_of_sead >= self.game.settings.pretense_sead_flights_per_cp: @@ -399,10 +399,10 @@ class PretenseAircraftGenerator: # Then plan Strike, if capable if strike_capable_cp: while num_of_strike < self.game.settings.pretense_strike_flights_per_cp: + # Intentionally don't spawn anything at OffMapSpawns in Pretense + if isinstance(cp, OffMapSpawn): + break for squadron in random_squadron_list: - # Intentionally don't spawn anything at OffMapSpawns in Pretense - if isinstance(squadron.location, OffMapSpawn): - continue if cp.coalition != squadron.coalition: continue if ( @@ -426,10 +426,10 @@ class PretenseAircraftGenerator: # Then plan air-to-air, if capable if patrol_capable_cp: while num_of_cap < self.game.settings.pretense_barcap_flights_per_cp: + # Intentionally don't spawn anything at OffMapSpawns in Pretense + if isinstance(cp, OffMapSpawn): + break for squadron in random_squadron_list: - # Intentionally don't spawn anything at OffMapSpawns in Pretense - if isinstance(squadron.location, OffMapSpawn): - continue if cp.coalition != squadron.coalition: continue if num_of_cap >= self.game.settings.pretense_barcap_flights_per_cp: @@ -450,10 +450,10 @@ class PretenseAircraftGenerator: # Then plan CAS, if capable if cas_capable_cp: while num_of_cas < self.game.settings.pretense_cas_flights_per_cp: + # Intentionally don't spawn anything at OffMapSpawns in Pretense + if isinstance(cp, OffMapSpawn): + break for squadron in random_squadron_list: - # Intentionally don't spawn anything at OffMapSpawns in Pretense - if isinstance(squadron.location, OffMapSpawn): - continue if cp.coalition != squadron.coalition: continue if num_of_cas >= self.game.settings.pretense_cas_flights_per_cp: @@ -475,10 +475,10 @@ class PretenseAircraftGenerator: # And finally, plan BAI, if capable if bai_capable_cp: while num_of_bai < self.game.settings.pretense_bai_flights_per_cp: + # Intentionally don't spawn anything at OffMapSpawns in Pretense + if isinstance(cp, OffMapSpawn): + break for squadron in random_squadron_list: - # Intentionally don't spawn anything at OffMapSpawns in Pretense - if isinstance(squadron.location, OffMapSpawn): - continue if cp.coalition != squadron.coalition: continue if num_of_bai >= self.game.settings.pretense_bai_flights_per_cp: From cc7ff3f1c2ddd095857bba4ef617ab72d7f6ebbc Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Mon, 10 Jun 2024 18:26:03 +0300 Subject: [PATCH 221/243] Added "LARC-V" as a separate variant in LARC-V.yaml --- resources/units/ground_units/LARC-V.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/resources/units/ground_units/LARC-V.yaml b/resources/units/ground_units/LARC-V.yaml index 2da32ca6..b6551bd2 100644 --- a/resources/units/ground_units/LARC-V.yaml +++ b/resources/units/ground_units/LARC-V.yaml @@ -2,3 +2,4 @@ class: Logistics price: 3 variants: LARC-V Amphibious Cargo Vehicle: null + LARC-V: null From 7f7821f8789d5affc65029da286abe5e4705707d Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Tue, 11 Jun 2024 19:35:45 +0300 Subject: [PATCH 222/243] Stop the execution of the pretense_extra_zone_connections in case no more connected points can be found in the campaign. Should fix an error which prevents campaign generation. --- game/pretense/pretenseluagenerator.py | 44 +++++++++++++++------------ 1 file changed, 25 insertions(+), 19 deletions(-) diff --git a/game/pretense/pretenseluagenerator.py b/game/pretense/pretenseluagenerator.py index afdf688c..d2ee4c85 100644 --- a/game/pretense/pretenseluagenerator.py +++ b/game/pretense/pretenseluagenerator.py @@ -1779,25 +1779,31 @@ class PretenseLuaGenerator(LuaGenerator): for extra_connection in range( self.game.settings.pretense_extra_zone_connections ): - if ( - cp.is_fleet - and cp.captured - and self.game.settings.pretense_controllable_carrier - ): - break - elif ( - closest_cps[extra_connection].is_fleet - and closest_cps[extra_connection].captured - and self.game.settings.pretense_controllable_carrier - ): - break - elif len(closest_cps) > extra_connection: - lua_string_connman += self.generate_pretense_zone_connection( - connected_points, - cp.name, - closest_cps[extra_connection].name, - ) - else: + try: + if ( + cp.is_fleet + and cp.captured + and self.game.settings.pretense_controllable_carrier + ): + break + elif ( + closest_cps[extra_connection].is_fleet + and closest_cps[extra_connection].captured + and self.game.settings.pretense_controllable_carrier + ): + break + elif len(closest_cps) > extra_connection: + lua_string_connman += ( + self.generate_pretense_zone_connection( + connected_points, + cp.name, + closest_cps[extra_connection].name, + ) + ) + else: + break + except IndexError: + # No more connected points, so no need to continue the loop break lua_string_supply = "local redSupply = {\n" From 2b62f5919f48b71226f4987567dd70e72c31ecf9 Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Fri, 5 Jul 2024 12:14:47 +0300 Subject: [PATCH 223/243] Large ground spawn support in PretenseFlightGroupSpawner. --- game/pretense/pretenseflightgroupspawner.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/game/pretense/pretenseflightgroupspawner.py b/game/pretense/pretenseflightgroupspawner.py index 1a0c3572..165de027 100644 --- a/game/pretense/pretenseflightgroupspawner.py +++ b/game/pretense/pretenseflightgroupspawner.py @@ -61,6 +61,7 @@ class PretenseFlightGroupSpawner(FlightGroupSpawner): mission: Mission, helipads: dict[ControlPoint, list[StaticGroup]], ground_spawns_roadbase: dict[ControlPoint, list[Tuple[StaticGroup, Point]]], + ground_spawns_large: dict[ControlPoint, list[Tuple[StaticGroup, Point]]], ground_spawns: dict[ControlPoint, list[Tuple[StaticGroup, Point]]], mission_data: MissionData, ) -> None: @@ -70,6 +71,7 @@ class PretenseFlightGroupSpawner(FlightGroupSpawner): mission, helipads, ground_spawns_roadbase, + ground_spawns_large, ground_spawns, mission_data, ) From 59fa77e10b2b8d6f3117788a032588a3565a1409 Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Fri, 5 Jul 2024 12:23:42 +0300 Subject: [PATCH 224/243] Large ground spawn support in PretenseAircraftGenerator. --- game/pretense/pretenseaircraftgenerator.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/game/pretense/pretenseaircraftgenerator.py b/game/pretense/pretenseaircraftgenerator.py index 0002dc46..6faee75a 100644 --- a/game/pretense/pretenseaircraftgenerator.py +++ b/game/pretense/pretenseaircraftgenerator.py @@ -69,6 +69,7 @@ class PretenseAircraftGenerator: mission_data: MissionData, helipads: dict[ControlPoint, list[StaticGroup]], ground_spawns_roadbase: dict[ControlPoint, list[Tuple[StaticGroup, Point]]], + ground_spawns_large: dict[ControlPoint, list[Tuple[StaticGroup, Point]]], ground_spawns: dict[ControlPoint, list[Tuple[StaticGroup, Point]]], ) -> None: self.mission = mission @@ -83,6 +84,7 @@ class PretenseAircraftGenerator: self.mission_data = mission_data self.helipads = helipads self.ground_spawns_roadbase = ground_spawns_roadbase + self.ground_spawns_large = ground_spawns_large self.ground_spawns = ground_spawns self.ewrj_package_dict: Dict[int, List[FlyingGroup[Any]]] = {} @@ -925,6 +927,7 @@ class PretenseAircraftGenerator: self.mission, self.helipads, self.ground_spawns_roadbase, + self.ground_spawns_large, self.ground_spawns, self.mission_data, ).create_flight_group() From e704d2aa5372c0b989eb90e5fbf2a713c9aa132e Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Fri, 5 Jul 2024 12:29:19 +0300 Subject: [PATCH 225/243] Large ground spawn support in PretenseMissionGenerator. --- game/pretense/pretensemissiongenerator.py | 1 + 1 file changed, 1 insertion(+) diff --git a/game/pretense/pretensemissiongenerator.py b/game/pretense/pretensemissiongenerator.py index 7805d490..a3922154 100644 --- a/game/pretense/pretensemissiongenerator.py +++ b/game/pretense/pretensemissiongenerator.py @@ -269,6 +269,7 @@ class PretenseMissionGenerator(MissionGenerator): mission_data=self.mission_data, helipads=tgo_generator.helipads, ground_spawns_roadbase=tgo_generator.ground_spawns_roadbase, + ground_spawns_large=tgo_generator.ground_spawns_large, ground_spawns=tgo_generator.ground_spawns, ) From 081c981c8648fcb020645d5b7158b6c456c598c4 Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Tue, 25 Jun 2024 00:16:11 +0300 Subject: [PATCH 226/243] The squadrons are now re-shuffled between different mission types in generate_pretense_aircraft. --- game/pretense/pretenseaircraftgenerator.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/game/pretense/pretenseaircraftgenerator.py b/game/pretense/pretenseaircraftgenerator.py index 6faee75a..0595a25c 100644 --- a/game/pretense/pretenseaircraftgenerator.py +++ b/game/pretense/pretenseaircraftgenerator.py @@ -356,6 +356,7 @@ class PretenseAircraftGenerator: self.generate_pretense_flight( ato, cp, squadron, aircraft_per_flight, flight_type ) + random.shuffle(random_squadron_list) # Then plan SEAD and DEAD, if capable if sead_capable_cp: while num_of_sead < self.game.settings.pretense_sead_flights_per_cp: @@ -398,6 +399,7 @@ class PretenseAircraftGenerator: self.generate_pretense_flight( ato, cp, squadron, aircraft_per_flight, flight_type ) + random.shuffle(random_squadron_list) # Then plan Strike, if capable if strike_capable_cp: while num_of_strike < self.game.settings.pretense_strike_flights_per_cp: @@ -425,6 +427,7 @@ class PretenseAircraftGenerator: ato, cp, squadron, aircraft_per_flight, flight_type ) break + random.shuffle(random_squadron_list) # Then plan air-to-air, if capable if patrol_capable_cp: while num_of_cap < self.game.settings.pretense_barcap_flights_per_cp: @@ -449,6 +452,7 @@ class PretenseAircraftGenerator: ato, cp, squadron, aircraft_per_flight, flight_type ) break + random.shuffle(random_squadron_list) # Then plan CAS, if capable if cas_capable_cp: while num_of_cas < self.game.settings.pretense_cas_flights_per_cp: @@ -474,6 +478,7 @@ class PretenseAircraftGenerator: self.generate_pretense_flight( ato, cp, squadron, aircraft_per_flight, flight_type ) + random.shuffle(random_squadron_list) # And finally, plan BAI, if capable if bai_capable_cp: while num_of_bai < self.game.settings.pretense_bai_flights_per_cp: From 81ad559ca1a373eeef29605c4bb35504f5993d62 Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Sat, 29 Jun 2024 14:14:28 +0300 Subject: [PATCH 227/243] Pretense generator now handles Severomorsk-1 and Severomorsk-3 correctly. --- game/pretense/pretenseflightgroupspawner.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/game/pretense/pretenseflightgroupspawner.py b/game/pretense/pretenseflightgroupspawner.py index 165de027..ace1b3f2 100644 --- a/game/pretense/pretenseflightgroupspawner.py +++ b/game/pretense/pretenseflightgroupspawner.py @@ -39,7 +39,8 @@ class PretenseNameGenerator(NameGenerator): @classmethod def pretense_trimmed_cp_name(cls, cp_name: str) -> str: - cp_name_trimmed = "".join([i for i in cp_name.lower() if i.isalpha()]) + cp_name_alnum = "".join([i for i in cp_name.lower() if i.isalnum()]) + cp_name_trimmed = cp_name_alnum.lstrip("1 2 3 4 5 6 7 8 9 0") cp_name_trimmed = cp_name_trimmed.replace("ä", "a") cp_name_trimmed = cp_name_trimmed.replace("ö", "o") cp_name_trimmed = cp_name_trimmed.replace("ø", "o") From ec7982f181417fdfc49deae98c20db330482d0d6 Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Mon, 1 Jul 2024 13:40:47 +0300 Subject: [PATCH 228/243] Replaced OCA/Runway and OCA/Aircraft mission with Strike missions in the Pretense generator, in order to allow for Pretense campaigns where one (or both) side only has one airbase with a runway. --- game/pretense/pretenseaircraftgenerator.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/game/pretense/pretenseaircraftgenerator.py b/game/pretense/pretenseaircraftgenerator.py index 0595a25c..a88c4a2f 100644 --- a/game/pretense/pretenseaircraftgenerator.py +++ b/game/pretense/pretenseaircraftgenerator.py @@ -286,8 +286,6 @@ class PretenseAircraftGenerator: sead_tasks = [FlightType.SEAD, FlightType.SEAD_SWEEP, FlightType.SEAD_ESCORT] strike_tasks = [ FlightType.STRIKE, - FlightType.OCA_RUNWAY, - FlightType.OCA_AIRCRAFT, ] patrol_tasks = [ FlightType.BARCAP, @@ -746,11 +744,14 @@ class PretenseAircraftGenerator: for pilot in squadron.pilot_pool: pilot.player = True package = Package(cp, squadron.flight_db, auto_asap=False) + primary_task = squadron.primary_task + if primary_task in [FlightType.OCA_AIRCRAFT, FlightType.OCA_RUNWAY]: + primary_task = FlightType.STRIKE flight = Flight( package, squadron, aircraft_per_flight, - squadron.primary_task, + primary_task, squadron.coalition.game.settings.default_start_type_client, divert=cp, ) From 655ed23d799dfa6e2e612c4da19bbb39725d05ad Mon Sep 17 00:00:00 2001 From: ColonelAkirNakesh <93631947+ColonelAkirNakesh@users.noreply.github.com> Date: Mon, 15 Jul 2024 16:02:46 -0700 Subject: [PATCH 229/243] Add files via upload Full campaigns for Marianas, Nevada, and Persian Gulf. --- resources/campaigns/marianas_full.miz | Bin 0 -> 39928 bytes resources/campaigns/marianas_full.yaml | 126 +++++++++++++++++++++ resources/campaigns/nevada_full.miz | Bin 0 -> 54271 bytes resources/campaigns/nevada_full.yaml | 104 +++++++++++++++++ resources/campaigns/persian_gulf_full.miz | Bin 0 -> 47946 bytes resources/campaigns/persian_gulf_full.yaml | 126 +++++++++++++++++++++ 6 files changed, 356 insertions(+) create mode 100644 resources/campaigns/marianas_full.miz create mode 100644 resources/campaigns/marianas_full.yaml create mode 100644 resources/campaigns/nevada_full.miz create mode 100644 resources/campaigns/nevada_full.yaml create mode 100644 resources/campaigns/persian_gulf_full.miz create mode 100644 resources/campaigns/persian_gulf_full.yaml diff --git a/resources/campaigns/marianas_full.miz b/resources/campaigns/marianas_full.miz new file mode 100644 index 0000000000000000000000000000000000000000..5fbffae2ad23711003a7144a7c0af1a8e79651a4 GIT binary patch literal 39928 zcmY(qWmsInvM!7hoIr3(f;++8-FaOZhl!1o%0s#f_2?7G*D}+|>w4lsS2#6Z!PZ01QEw+}<&X)Fer=7aa zg_1LHUIN_(g&_AYKZE%hCSb*v%v&s}#TePR^_LBE1^DWH z;QiM-H8|w`LZ8I%9W2t|@mk|!1irtgj-8bgeYbpO)Gs)qdBTyKPV)c!&Xn~g?N^;3 zKuC|1c`*{({%$m)uz#A`<(F;0WR$9)aI+k!p>mHiqwl4`Ky03=@$KaFV&6Yi3+^dw zj-?b$jkx8V_SjMZ6d9HfEB(I5G0}Z~CwypCzb1PSp7aOlncF}4x$vD$4KcsigGG{j zUp{>3OyKX^xAodplviG`Ij8s1QaRk6zpa0V=6F}FI~(SMzrQ!j#`_B{I0`$nncv`H ztbbFWqncyvfa48(p6X9K!}b88CK?h*Q|Ruzvrp6qpFIMG^?6QCR)rARXXpRk6(`}} z`-@l>uGKU9_dNH{#G1d5clPTqp3t=AjFY-Q6Shj&HNS%Fb8l=LeRO#6zK3D(S zeSZGsm?XsSQyi^89{{d(t=_jcF4FJiZRs4w$o|j5?y^toa*n(E^uuzWT|MsrQfyiY zm#g?WVUPv5CZlsJa*swFOMX!x;lQUK=e_h0$LJNx%6}~#%3or3++DdA&)VRoL#Mm{ zoiKkKluBja^Twx5_+z3Zv#Ws0-rN2p^;gvs{mk+)eWn{NQ*%tFsBa$aa`>Mrv7m+U zlzD`^iz92Y=$3)o6JBA3G5cGN*45O_2>lm|w*r5<0ybx~x6Ps4`n%H;8~)YTf_J{= zkvjE-ha(?Nx+ik>fWkR*FsJW2oq$trHvb97c}2^2V)K)6m#esQ_}9j(MFswZxQ3XL zll`4vh=m8K7Hk9eGYg#u(#w1?E?m3dh~e?<<>q(2B0@n>=F?RlDD27UbLVp4`aAbk zl$gz8@k)0tJ^S>rKFLm@ZQEnb+tmpv^~sMW&E0y-S~DM*+TA*zEWYuAA%`Ykt#eFS zg}zJIyE|9nGd=OYc&o9vNJawR^}r`ToS9#~SxcX*{1qJbkJ{aPw+s#%qR8^Fw@i?s;#23?VLY*;PH}J zWO)TV7fqH6R5rCsx6cFMqycC3PcGUL#u9*Y9^`!equ9 zUm}!W=#==pAkR!RZI4T{O*ColRQR5#!R_n8igV?;_X(S4iUMbpw1v4mk_%JCeibw$ zK}Qm0R&Dx+B$SsHf2^QDGK`vXS9gabr4Y_&MN%JgP?we!SS3gt%Il+4;eF-S#;?%0UPi>EvvoOZF3%Mx>qVRo?HzwfF9Hz?(CFsIORvA(dk;O6jA zij-oLrJ5^75;r(bJ|aQSolcuGcfw^omt?e{uBMo4Qa|m?V7GJwAGn?wiI;0;A8`xgB9t{v6_?;SknswlG=UhQl|tm4uRtrZR}K9IQT_n7cLz=VRc5^JPuIezj;EBJD`hK8T|l<>!sA| z=p0hP%e@(%r}GG~O!sy>3H2-vnBASowal+E*80a|+q`|&^KTiIp?rP4s85akQ+h*A zxV9Q7K>v0FrE2dF(G&hBouBhPmi@|3!=IPt-PSrUd+>JIUSr^7=i9#Y!R*W@qci&g z=Wk0n2w5l&E6$sb6+k`@8GxONV3L3GYagHa>kl7Ydb3D-l0gl4k}>`v#Aqv|UjV
!27S*JVR0ZOJ`pEaMSy9#gz(ig0Z381meE#W~{2! zLmmd0XN_-sA*sYQnOH2a77u9X?r&eg?L*0ARVm9A16FVH+dt+?=TT?Orl zBZI#~rE78Ki`?vA)XVVy~8_bq7iO046`J3k#_h)DBcU`b9eF)f_)i(;1c$?kWrmKe6fYn%d4`)TK z%e$QrX>nCUpEBjumLPY;3AB{3T|Ogzl3Sw%zE$9Y4tKTRtfK3~rp;1Y9Yqt1Ss&qo zG!nED<`RmqE1PG}+~KSAO4l#$yw~-_>?Ka_lGGX9(vtK_JI2VU>5u+Oa!4p^jTba{ zf#pj}51+TL(VqPLr!E>Baw^K&;GVofNokp!yX#8-+$8nytzZGC z1^2d227X!YpvSU{?x1?VDI%(>>J3}q8D#e0>)-k=pzG?oy8djNRy%OCUhf~YyRyE^ z7jiqY8d<+1>{6`R=dpnK%!PSB$$YReF%ZYgKd(GnPkWXdtaq^v$0*%hGs+lo6&M`G z5Q95t&lGBU82Hl6>@qFbE#ZllBM+)-e$MXNh>o3bGgT-pyxqMi2OgK!Ycacg(oPZ9zZ-*a~c1hOb@_I=wVM%Fs>YoAq{&rxS{nVz0^1Cj`sG2u3lXOsWa zz5aqGqXHNju7u?@CCcHW~qtBjWRu&r`943IiSc}LsTBYki{DSj4W8a3h^~dwXgAU$R^#H#ojl-(iVv(Cry2AZXW!`B$#Y24Sz)5C(5IOY)RPCYY$L1x zX9>?^+$W|#x!o76`P8fp^*c(BJLMmUvhFep9$u=NRxHt0&V)>Z^k!G90!qqt)hI|u z{%JYYZDH(#s`6q>YddA-%(^pR#UE!x671K#I+JXtuhtOZ)>)1GTd$UA%m(t9ld^ ze&rPmUmly4d<3D4SCk@j?7n;75mqbCEstt-=Ws96449vtUg>OFnb|R}+C0BEP15q> zoat*tN0aD;L6b-&mWSreGQgzAu6H$k7Y8vN7F^%C9M2~;=iRAri*;_cVi#{J&U1)# zedcYy3-WY#C0zqfKUL&~;gtsI`+N6+N@h*{X*%Izi4Bd^nI0j4Kg zNs1R2cN3KX4Q}lw6k_{X{U(t3d2L?M#h5{J4v8t%Gkik2*?y(7Gwoo#*M_<$m*g8S z{EXM~x7co>rvYf+t9c3vJ5c;P)~NEC^vpG3x(B6B zrH6g4ghK?6mce4lar5}2fj-ArJ)g}3*E*<#k#V{(-Y!%nx6Q-%4ZmupaC^=?`?@^m zg6;1_L}ISnvsaz{6h4oi!%iko_rhePn~b~c0cbki4I*f2S-JAqlh`LlaEKtM{e{`z zdPYm(npnOlMbseCG=3=h`dZ~}LJzmR0=(pJ{a|Fy#4f|QDJn41$QP;YeGX&QS&aF9 zBAuw4aeDn5M*_LW47cq41Obkc0)dzAAa;=Ax8nkhIlX6oj2~d$ZJrRiQ5exxjrmCR zdw7(2Y0vM?c}!(wKY*5Rr@)kV2Dc)7hO#u)RmeN7R{HFK1KW${f&%1Z0mI8+MHlq~ z0Y;94gQ&lKO}ObJxCN5D@8aMaF#(a^B2SQdGtiB-O4kh^(beW~L??NPk9K`zE3hEm zwzC)+PFL-sdDWB>G42_k%1dTG$*vt}?bS@Wd+|PIujqFrsNWIQ-5qjFKvg9KIHA63 zpIL>*ixd!{dkfaSr{kzZ9+WXt$Xbd>ee{GWZ82qfD>5H3cwODdLmte1+moHoKpq@3 zc%3dDHt<;xzPLNF*I7Pmc39IIDt5mxh&Y$BJJ;0F!HZ1A=2^;SoP2Rk6#YH_#*F>W zV05LGtl=ZZd}Q)H{PXd=Ji?$geHHMb{Y)OTs?vDQ6El$NW1PJCU2N`DdVtZM;8Y<1#3rw&@QbH^&)oZYZxdUJ0C-p{AZ zsK4-uDal(9U$$3UfYrNK0nZWqifni>@0)&M*FgA}V^hgDl;B*tZ~^!s_ZhHPO1oO< zHhDRvyv@Ma3vZyx8Z)y0OuY&2o2|WK=(d7CvlL~sDXw1Vb}WNf=ytt&t9rZA#zK-F z5Qy5AQSFL#N7S$)Fw)8wYfKA$HDd*b28+!>~NB+)b=k-?W=qK*sZDu$HCV#BuRjPduW%e zrvIvT67@-`Ksp&353{LmNhOJH1le}%8L3(-UR3mmfoihQg-FM*f##*}<=h(*jFUqE zcUF0KX54b8Y)q;rc_8pM$*&tk>nq3ASgshJiZDKv>Tf>3iFHCW@=YM2HYMtgoN1w= z`k1~G=(+;7>-HD!i))%WE<5uBZO*F}uls>~Truy+m#yM$T)P>Uk7rA>vg&Ln>P*Q1 z^sQDSYBJSYPV)&sI}AY8e}D341J9+apM4$Yv7Clz+wmydagniAQ)L@}a;QJa)o#R{ z`ZIc3ji~6uDXasMKMWvJ0kLY4&k(n>;z-)3&B~UXUjXhRwbU-Bi%fw&p)>~6lQMHo z1HR8&r5A8hNLV;n7cpNJNN7kYY9y;7x1^V{meS$~Z#b8p(MniIXyDIoIG37v_KjcY zq$zS0{X*dRJ=E65S-J+^IJ zRfb>BnK?tAdyv-RP|{Cmz?RYi?rVoh7d&^o{H602$CytjOA&1(s~}gR4?m{(JkKYk zDpR0^F<*)~?<=L+%~;T;pHo(SYL<0s#AGp*LskLXQ9EiMaTgklIiF1`+Q<-Y^$+p< z$TfY0k|boE=fcfro0l~e+**3#)jc1npx((c)%YN-`j5U0TUzyjJE4r<_jZZ;fBCT~ z)n^EC7ZnFbmw8s2KmpB(K0!?*b#$a}v1S8lSbt*KaaLR~2b z$v_bd!Vkd%R)`ND6>Z$TvJ;ud+W6#7nj3OfXwNY8P`jrEU8TH4$U3J}?{-S-2`JAp zoKJD3>?EB;{<+qBPZ)bShx8El(Hj%&jn$2UKfOHQ5wfaoTnHSq)zQfK3Dk>9ky*NQZdB}%fih=-zk9Y|!oJ7R65 zusnW9jwn?#te|cCXAN}ts>vVL08oEY`=KB2P}1ffTuv-mqQyL)P|okC_*(7ulC1|^ zwQFt%-&4<>TzF|)Hh+WPtcN z0=yx!?zpJEMgTHhZKz#ZYGj7=2-Z>>x$+ugO1tCVD*XWhxcZ5V-89;|h7%sH9J?+! zL1Lj+2_WpNnTl?@zcLsT8fY;eF5x@1wUnmJsFw0VLS0JI-BD6wg;kYX^J=WVGT_Xe z+~3!$sc{9bVNPD#-aSbVWVJc_;Wk`vg|ta-UpfO!K;+)^%hn@$GWS|_i#UJufC-%` zr;R2c1#{5;>(xWQ9srkLZ+V1sxyN9-z;;9z$-^{RAL|AS!Ms{+AJ?@?`dCYOF`+GG zuCb_^+|`%cl}HQvVr_Kf8@s^7xrZX)y?K&Q*(rozN=dhF0ZLDE zA28q}wxel%hWo#O{~17L;n`(DD{@msf#mk~kqt#vO?i=0Wih*JVOFc6`u5gM;Y78t z+FQ43)qX&ILh#6n=%d5`CHx6eNj1-ZpA|~0)ueR0NsqPY6?6e^%<^tb8XXn@<;DNy zFF(2eaok4wZ_55h>VLqyqq)Y4imDvuL+W)QpTc`GS6^mVK1FXnUaq!6l%%c#wLfj= zZr%|a$IUNiZW5`s|IfUo%Ico{A9}X`(9`~3^a}qE{lt%Bt~iciWr?zr zTI|N%Y1dsUbNqbEz8J9r@TQkjO4<;drd3|_EuKAZN?Ou8a{2#7{jciunO)bD|9AbY ze+a`iyN09m#p6-r{TqP0~E`BwftA4AZ<;J7r!KD*-`6^`% z397sX8g+fYDsoK)AxXQAXbvh7-2nv(FY3d%E`G_Xcck-OD$u><*XfH-KizI_%JZ&m z2=^tf3YCXTcX=Snl*{WT|D`}(1$r43vL8087OxWAu5y3nsE-XoY5Uk;ubw|}gY9gi z@J7D)`b=VLzw_bgqg^4rH|Z4TUJsq@Td{a&@R5;-a*!`AdAL>>H!&VRbzzAjPtGtzO4%ZA63N>F_1qI^TczQBa@H3ed<>!;t zd{E~5&6J!(t=a&%uOT6-t3^L!Rc_E&MpRtdu#zob zx`}tYdZ`|{GW$X=7r74anMT;gEy3~Hp7-0PXPNy$!)YTo6Xrx_oI3oU`<2B9bLw=~DKcB!&;|rPm3hIg zGIWwQ$>^^z%(#^sw3kVD_;S-XZk|~LG4LK0067O&XKrJvX*1})W7xG?BsjOCkBM1} zVDkfb;p8`lh8Py!yy<)y%KxiObs2rp_jFzV^iAnbiQ{i*V~YcDxdh!;@&F$Yc<^=X z$Va>(KN2T6HoGb%tUhe~qoO12AL>@ixyEMRxUtTZ(e!d;#`eErfrV9r6t4B8R2K z`q=*xA0)|%4ILbAsqMEcRir73h}g#TYHN=A(U#sKsTB7E-}7+PN%)I~dx|~EB&cln zcMJFOgD<^~qoo{?5=1^tpIGRwJG}ehN=g<=2~d9>XP*rz7t5#-&>8W=2_}%o}et%ktJz=ZEG{jG~ z(dQDR&i?M{yWyO$`(i@J!H`K!mUMGUs1F{8A)&K6BB6~-Rtrd&?D?72`X`T90jF%r zcwzI&;tCL?TW}6M-^*MjU_y9yR%-bRA#{E5Nr!Nwjb+YOm_=UXw-82g5Xw`awi@Vn zaL=dQ?57%yqo8d-(R4Qp4z?=&J@K_t5oAHD6d_h$U79j{U%G_+C$TrDcr#`&O()6= zv&bZ~_Y5((YlivE(+&rn=mi|ZWGAC_zYW#;IC^;|x=8AF$`S1yWnpyf?l%?s%~q2- z`R*&2U=VsPwcKxK-HYQR(#;~l5dX08%!5aQXI^wsbM5pEdG#h4{mJ*Kax#is5Hq6n zy5noZC3YR`h6o;Y9L;bqsl$85TQtH|szr!HgCL=c7CPB8p`_WD{>P!+3!fo^;B-TriHIHH<$TT@VOjtiQ|&ceWlq6 z4*_5TAjzKoYEZ09FD_z^3GI$pL?s1}V&~@HvR~yDt*w3$1~Pp@5qLsEkDi|Sjg=H6 zb7E{%QjR!rhOR4@Mf-KYVIa`@7YJfzw|{dndwXp=+tLT)&`LQ_&{r1U#4&*&rjDSA zz+Wf)hp#c-TX9iGZHiwE3}=b-C;AmTbZ??Tb{H;v0->*d-B<`AuN{az<~vkx$%Zdf zEqAXD2Wk1V2hw>%16T;hHdg_O?1`U4!#7}7B)mW8HBCWkxQ(4Qr&_O(>raq1WO&Xw z;AorHI~XK3D{C$O`ZI17qmX4ru{;w==g+ur-~@f&goIbQDGg*)><*Ig!|an{5eV8Y z>dhpPf5ZlAM)p{-59qNW{ZX5Z1WR)K*&u@gDU@(d0TWTu$TGXC&8%;I+q_=A+ah4< zeDoyV981Tyls3{n?O12nF^kfz!zktylRjPF0k!b1Zo*Lw%B1*?JC~ z1Wt7ZXra8S%x2G*Yr~U}AfON3m!NOILpXN%ZvUn%8~`-&IiaaMue!rxM|<>;E62o zIS&M;^4(wwiqV%E6{e_MmhH28JIeYjx`kPsE=y6pOmy|GlftN@IFbv~1fpdwpGtJM zmVLEP;3ul;gxwrasqkjIgcVrgb++Uo_A)nvzN?_>7jiwb`+~8nU|)W7NySawFpb#1 zA%L@db>$v(s|Jet6O{#TwpcEQOW;>w@_DsQ+RrKKQ_fd5n~G{ zDHdue-wz<(7h6#B>4%W_Q3mY(0%O;o$HB`Zra;+_+mOb``*AJYM zZkPTp2I%kd(!#w3#6in=HJr=&xabIuGdSn(^Qlwl2$R$g^qo3B5!wcGsE$YlzS3PY zAw8FhxLTV&0nh@firs0-Ow6ywmpEDwveziP!N^vpw{0r-MZf{=vLK{3)%jVA-%dNGFcJ}eHN_q=S`gzF`{UQ%CP{y_ltD6NK5~@GC0+>Brfgm}IcOziU=bsS2s@;{hI)mM;Y0=(Q{3`TW zV~N>;`(r0Jkp_J%lnA#5BRxgFDXhe$=%RHF{9C_t98fz2VY7aB`XAc-vS-Gk_?4Te z1rQWK%VpJz&fg}GUs`e|tN(rd*A`B%X|j%@tG!NLM!_`tbHPSa*0(dwG<6=Xa95X= zkv=n5fac&(^$*s}bzRfu;p26+dnU^T^n)%k=U}y-CtX>~EnY)Z20Rr4IS72W&%%Pc zmSmhXN_{f2t{vusU05)}SI&Eo-c7Q@t@sq1b;`&8F4Zi9d@d5nJG{baak}inK~NDm z;R

+Mw5^C5TtQfaJ6|XkNc!0$ET&M+ZJ|IZeF<a5wyFH zY;J3xXNSyH-nfr(u$6ere0B86c0>>fiagOW&Sd@e8KElCG3U?}t7>tH8}lT=fjn?Q z-q~>02L5Z05@Tsk@egLqBPi#<&=Rw>ii%Ch@tv{JN?TkK*z7J3-Cc%8NgwM^E)UFN zf%@GxTUi?x{_;_V681jY4QI>>$G@6_q%C&Pj9INLc$Uv*q)9h~b{eMH2nqW-SO~qT z*y)qV(Yb|^5HbqWc*d?|d0wFklRF#QRGogxq>McSAXV)JL+PfJJ>lkTlIePqk*aO} zj6qcgjSG!O(XF6p%4Dwair#QNN0>k7;p8V4#3I0*F*wK~ti3vK328J8KVMIJlGYSZ zCf!N&z|*= z2MKQsSM)C(&4l=W>G8dZb5^$7Qnvo;$Xe#WPGKC=6x4tvwbL?{2mn#ICJFkR1jzr_iamA0dUc1(~) zQ3+APg1*NL+oQt$9b>$()J(k^V+=Ug>JJ}{N&RHBMtWd}D?A|i$us1ZutC z0^Pe=nur)}cuBH!&!ifdV0l;h8AoL7Bi-$<3w~-JP76w9x^uSv+z|iR!Zo|!2(m~x z{Awx}5~3>iN3H}^$t4(G3}<&hR3sD;jqm98!%Al65Ty3c%QvTx_uX}7oA zpPTk^hL0D_WiFmwK0dK}7IDFVmccC|PBWOOnCUMA(&iBob;r?&IZP z@%lvE#)g^fGc7Il)iv{BNa2%u(sXwvBkm?CvKah=fk#H*FIBJNH)f|;0JBL>#IHo0 zK4%(7)regR{^Iw&n4aTKdqQ^Ki1%zlYZoq&jn9&hjiZwdd2_3q$EAX?SIh?lKk^`7 zxkkqcbjhnY&v}PFMH3)#P`6@nPDInAxfBt=GdChJwV$%Np%npv4bV-$C6Qi0F`?CG zlM75l@w^Syj<4tPCKHqZG7G8zw5+K4<=XXc8y%w&kkLb?kTc7_0XaAZ5_fT0RgmPc zI8tzvC=9C5Qo86cyg8Jt2x4xHM$*v5Khhi!e0eK#r#zZGABn5Y#y;i;M1o@ zDcj%j-jA&(AVk4TMEiL)?n_Z{gg^>VfXx5K~*EGu=PhH zhF7uJ;p%Wz?yY!Y&ilA?&hJE$xNqED26LQKR|1c$T5Au24S5n%`}cnJ_Nww2iIVl+D(_1jmdMj&SR0 z2g^1J4{1zt_?C%p&>wr!R4xi9h^&)v>j)Ld|EzD%auR^JO&+fN((7$3SXw~_k_&F5~Cd-k@8LLl{7yUS%U1+w-0*a4}WK7S*hmVWeMG1 zT=LtLtT(*x-s{6GO}Y2<0_d_ z(+&g)3+vLAsnhE{6fVxOP;;`V)Vi9MbJ~J?Y-W$OGJI1C^}6>8t?2dpKe;(>T`!2& zmJ5bD6AHHAJI#L19D%CC1~+_tF5nZdmY%+I-Xy2M<=kcjjhO;M{ICnP1R|BT&9AQO z>Te}xmyKA#luN_qy@$lv7#3sym_>^663rmvi4w*6T&ZG_IpS6iCyjbCx^OM_` z7w27Av>DK=rDbJo6guL@LaR{fV8g8kQ5Wdjx*O@>|K&BTd14i|QdP;mTszai zC{S4*diwqB<=O6>u@H(I9S$JF%Cq%0ipWLD#(}K}=AG*GYr8UZF%SP^xf@y8+)nVn(C#)5cH5NyizO+#(tn8(|B18(UK08^kS%v?yAW%@(&AcnR5SILe76 zOdaC}YEUp?RaE5IrC{q444QDCBSXB=gz;CRi6bkP%!z5}9IDn0*#oF2vrsxki9|@Y zhh7xIj{E2{DiV`+i&NOt#|19I;k8?fH$t`s1(&Ep$yUK$9*?RC0)1LDrPsr& ze`%TkR=jNl7}I%=yvRyeV<#4vM$9HC>qd09yd%Oxk&q`15s{NYj?nMp6Hw_}iL0Db zmM?^hNNpm0?}4PZ`Jc5h__IR%L^)fc{bcbPu3xoX&Tx%y!wmg|0f~YggBln;^Zmhc zgZx#GBv)Ut#Y6@z_VQ`R5SpXEb$;Kt(XCC);bhL2;JeB**W6sn zxjj?5*CAQ1hG$Ye`(?wF&i?|8pSL)AM0W)A1k=)LG17m zuZj1O-N17=%0d;LenINin|GCtqj}n8_HvN?4=f-?wsw1V9U!nKIy?XGRyRas+4F}(h>Jfq=0om^hPDQ|8_JgM6S28+n!Xh`$pGT7xMkTfi z;MH$!8Jqw?UA|VM9)Yi2P?8t|Zk$GaOv@=Edmbf%i;>@ulG@A#O_#boCZc~hIy{OD z?CQ{MvSsz-u-MUEVLR47a@*{YB2rf}@C65dLy|=(zgtL4xEv{X`#dpjX@=$hv6@U@ zv?Xt;WrKND^Dxt>F(67R9->-8qy`$_p(aTuA(>FFzgmXO>y}LRew$Ch0$=k*6`&JXm zn#n45wxs2GZG3#lC?xL#aM*=H=kIa^P~l3B_^b3Zwh@S3u}?cI?;#IIocWc0|%;F|U0 z!EuH}qW!jfj9h098<$Xk&Ysk)p-NhZBQIx%N25+P$L``m>fhMi5r&5bN8T?k;tG*p zZz;cJL?vFPUv8j;D4!f!^@L86aLz1A^;Np&a9fcLVG`k=_v5IbcgiM?+@te_GG8Ab z{YVPQB9OLuBwg(k0(3o$I4Ny$rJO8_2&uZb@jJ#_JSq)@L{Q;=3Prk_y1Nr&r6dP< zUn?>}ah~b6yB&HTaYi@fOCClKsi7T4!H;`ilSkuiTZQ3aSVo>KyDSJww&#t{X7-WT zn#EEuyRY@W=l7WWu{WJhrsxhDwd^wnrQRG-cy)l8=c)tsPvmZW@Z>yOkOa!b(DaGY zd&&xHKf`=vSs3gD_;NVE7n0dH)&wb~|1AGr+rXHDlbN)^+4U;B%0c;EyXebsFayS} zQ0YyW&MpJ_&vW~;DX7t}JD*@DzwHE2MylBgSk^86nVDPP8O13$H~b-QsH6S!yz@mo zx8+4lonIG?X`~}L{9@%CeYyJRiGRYrN388|f@#|Jwfqy^SI+Oa4GCLm7?QC*iyk9) zmyRqspLq50>b}nR272{hbu|0F>W^3=pSYWmEuOiV&Bp?=k z{?5h}#Wyze6i(5#CAM#qL%N;bzA`FK0?p|exY~y#i9Q0Vd!^2Xmkw<;20_y6b4cmU z&%#x4fY1c`Z7G{Lhxz&;v-oVzc{waAIKw$I@qkh%8GjmJ zlj=MDoQ?lo(PtD%@4HH}t@GnAve&;obbTY`iB~R5ZUR$ayma;q{&o=iV+4QgX$<=6 zksAsz(!%ED&@2vG&@LRJ7_|fcOlw&CQ}fS^!|MrUh2haoC|L)~NBfR$WJI)^rmX3z z0th9Um#IxvYC+wx(1?{}CQ{H1PbE`>@;dRqlaF|}iblQ=BUj{q#<#FYzkgIOJL|_nuoArrnAMN9d<0y9LR!ML zDivL9HASwRdt)Nu-ZLVwdVZ~>WzG}g)q6H*G2j!mj?gtY-BKW>avAe_q*s6cmwZMz z%1y}?&B6NgG~yuS>?qndP%ly-L zr)hYQT$?GBpElFn=g-7`B6M?x;y%_0A>68moZC3yl?>pMoK^MT?%G!TWnWU;md~3` zmM>57+Zouh1Q;cIZ5eSj^S_BYY(m{3=l1qR)qM+7RmwUD=x-?naqv=9SHa1@$->@W zy>!YC_wSy>n4}bt0_Ox#$(B#n5K;Lw{;ZnNkzg!cB$1*u0J-3q=fPHc>jC#&M#Dkp zJ_CV&s7b8s>;(OiDU(t6ouO?CAjhjrk!>Hu4Q8-Ka4i45ey9aPj5q|kmH%B*L99dvn?B`u;QXR)M1}u_(%sSxOev}j zxOZ8PsBDcrLFJrOUyp`tVTVQ;wiy3aIqo$MHHmW8FKVvL!+ssg**w|SACQfV4l^~h z{Jj101%A$7{y?Cx(EylDk9rMuhBzn)ef;|wM_522G5D34RvfurQEJhTY>j{2`@V2j zyhL!wPt_!MXDQ6^<#2Rpm1;vqB!0jK*)XrzhOS1_m`~K0XJMx4mugwLd^i;QoMoQQ zf=o@o1co}vMKZ@IW%J^vp^Q$XOEXVvdJ@o90RPUPr^|_~%}?eNlfE2JwK_C=$jps@ z@l2L2M+QA+)?#E|29g!Vm-`BwY0pt~;A-TQlmeWbIfZ*}s6DnuZaXhPnUXbp^20w> zArYd}X=}%Dal_gQ($GgRlKQ>s$Ss!k)+qZ1bPVx(knzvb9a6OXt})@6{qL)P;13A2 zuv5r?V)sr>(%d~=M|PRX?4*Q~)+jOE)Z~AD)!Y&rOa6r4`?r^ZfPI(2V>T=dusxnZ zEtD6XVHu~xXoig4O0tO8OagyO3e)rhN!x@TUeZuIWy*aPGG=hE{`VskH7sjRT!6$g zMa&%@Y)fn>zFRcA?Qxw>If6#ld$HvOp*~iD%*+*Y6xj1bE4wf~x zV0Z57G4$Ne-etEKRN44jo6OB+_&qaQp!)omU5!8m%hRRws=YIQE*K zp7pU!cGqKu2_#9To7yvY93f{N{^tfg9;K9mvX++Wd~Oyl?TyC9KCk9|Y+~k!j0KCe zV5`32!&F&YAvKt>LT5~HR2Wo0e1*bbf{a@BXmxh1r{X!t7F*_Gj&uSTTBJi>S9fHZ zxeE$Z);Up=LnfuxnBiA*yahUpqzxn7!!ldTXcp$x(e2#!}F1 zf6^RAP<97VvqQK?*SC3lO_Q1kw4K84Nypd3?RC7L#_!aN<0`#;7N4efPx{PSg(t4< z?b%5{1$WPlR&PT&l8hdgAvQz8>kfyFMbTkr8+b8%!R17wl9j-pR7jbX!mqFUOh8Kv zFDmg6-hdG>Z$O^wFW#wOqXfOme2cu=*N z2KB|p*-T)M^MTO9eI05Ka_l-fk84wQm#|}1>Wsewaz;vPBnp?92%qn>$+^{{$&uOH z10hpxK~ee6kb45g>HEI4aH@73l}vb4=mc&lBoU`W0IJ5W91T_u zI!{Ye#Ol?b=6{!hx~(yC$*gpZ@Mti_t7`=CP^K<>-}>-rvF|azvY6+S=+{AmS>OHN zlaL(m(aJ`6X7Hm4*2+>e^qvUfe~OzubjMbmcUS_$9!ojYU;oOjrHp^JFgy?o$Pn<% zOWG~YS8Iq-7x?zdD2?4fATC0ld7qh!PvE;;RppcTMpPzC-fI2WXFY_=;l6^_If#h0 z_htArQ(n3v^SNSnK+SVeTf@Xq$Yv%ciU4O@$Z)6hN6kP0_Lmw+6+S8B+^Jx^i==p% zb!S$j#DgO|GarGL7W&A8Qocr_)yAKM{4tj+ulyf3pnA6n7~PN^8S?GdWwZ90B|@F~ z9AQ7pr9z(Qtm*S^Skg?iBvKs{`I+$Mlp0!P!Z6aw-`CRbWEs;Xo zrp+w!)g#da+rn zyWp!k*lQY_3~5jH!zqib+ih6Ua&GBt*CzasQ0 z=rEX}RhFgoE}68Rd>t87zNuvRBkApxnQhZQG>mX1{q2><6Ce1v+@t@3O zLb!NfW}ahkAcyE~zyWd)%i5Hat}zdaoJCT;hK;WA7B1qj*?vBgl8MUAwc8jlA-y?F zd_k6BhlNsDK^Xu397v7uEe}OX%7G9#kk4QXJ!mx&Sv6*5NcB^3eyLHsMSb;d4dUYl zM`L#GFA3_1S=D@1NinRxNzvq-g!?IZA8d~6(_y+A;nQEiL&lq-CR;BP&cZsnj@FxN zP)d$IPmNbjur%KqJlwsbqoxP`5L0Q`S=wR=QT8PqNnWGzSHD=fO$tvakA+ci#g$)B zJUSb3{iVOAA9|cwnj~)@mDWdqO=@r$Dm*zl@d>)TG9gcWv;2Z}5`ZJD{KHjfgq`^l-&-SGG+odK%uBWPrfCAxH+`t5$2}we^0~{-U(uldF?iEb{8Q zn;rYxPs{$%F9?3lwE>C6nk7hVS3ejL8(3-4Zaq9uodfVaTo{s9er~N{^LuXeFD7~- z@J-;I(E0b){C1)^d*%8Ono+{R;`ys~BJuCZX%9Ae2pOTJ!IL}j6830#6`K$l5JEnS zx+#+W6MVj)neJx_e6J>l0k^84BNpQOmv$}G{A606^54a@u$M>oQGV_Y5_b+!wuirk zqiungp{4-qQ4dipn^Km1lp(|2^>Hw_f+H)ML3y~w>d4lJ1y@VHhEDmEbM52hNRT%( zp%y>?Os3}~NebZ2fFjS7nc&*Yc@Y%khF8^ z|KsW%gCyzNsNJ@0+qP}nwrx(^J#Aalwr$(iv~6onJ@0qoM4a!(j>?Lt$k-K?mHXc7 zzSc?`?0D{Wx1B~yUTcz|;981bNHDcpvr(XR>a(B}r`N;Oni{_dBw!*Bg?~Dl zb_4Y!MGW)>NTIn^D<4zDsOr+Do-OFe3?JbLg@2`@%ecD;P5i9hpJ0tf57v^v@Qu0f zC;sl+I1uvHZ34E`{dLQ*FZ$^nS<+-09HbkFN&DA}gVcgs{myJu%?8v3yX{U|V}KRM zE(A|QGmn%DXo?*;V02(@!v_63R^w8;Tj~j5-o%c&o*%`8-Knk-0Vq=MVw$E z(wOd;QQ5Q_1EM%R5IQPMhY+tzIo>R4oBeK4IE)@#1`dPMV`gl)`+mo70R9;-^V7+NoEZBsBv0dhgQnBn z%>bFKRpgdzGIg( z0U|%S{HTtULxuRU**eP-baj0HgoLJ^C${y|S}mh*5~QPpZ+~Y3Pd1@S8bDc4ls4Kl zp|mNnO8*2M@2~qLT`=%;2~xsx)CeM`Zb3?(tzxZovP*>Nf@02YZ`~kSh&f51xcMt# zP;6Kmw`x1gzgl%7Ur8<7c4HmGkk@mVm2~x_IS|~390e)1HddH-{ZCVY$(?Ga!a(5F* zcxPsDfq#n?;%8eNZxX7Cp3~+?sXzkk&e(1Uf%uGhkhMR#&e6Oqwyjxc9MAkv_%XOC zp_@;rA(K;;7c^45kJGd|+@f}t7)mf@4Pf2C+iW9x+iJ{Ocamk=Sq4g*1%{CFA}e zgjwPQ!Z|^oq-UvOX&j^1Xs>}&Hz>M)f8>x6XPh$K$rHXXzft_UoSf~)0&!O%ZqJu3 z7_NBy{Lk!nJj$X^hm{RRmoLlJG+pO4ORZU!oyFtEiRL40n+t4>T6=256nF4{66WhR zlJ*VK2@s(4zI>Y)oL%QNoobs7e;Jlp(-`R72qw98pd^9}HLe%Kb0RI83Xjs}KXlhkTtvw_Q%n;m2cJUA*NeIz5vk46PV{xC_9Cc5bW7Itv-q0{+K zQ{G|Gm=`ckB?%Kimkdl$6!>UI_N$Z`hxet${%;8@qP~2V_csQ8`xm?(R6ZSo%qQ95 zIzNm9zhOa44kow`UJB&ohW3qN|Minpko$EhoK>8OoFkirJyx!r`O2R-d^L%2eNP;G zm$lprV$U7w=>W(+JF7x>i@rw&oH-Z-cpiyS|3I--be7Pd)l|SbXyLa{&t`3NYkMN1 znDyWsC>uAh>v8^695Hicjlu5PX!R@gSkmM)Xi+=)U5Qz4l+2L9tJN$BCJrM;&)fLy zH$%COS=T;mYRuLCX_6OQfLRyk+2pH$wsZ~d4`ZYnL9fwUXdVc8!UF7&hiTn}t;Oz< zvHLM88l=%!SqeF?Gn7E79c?SH>!b(FJ|Xvv{Fo8L(=sVVev)x`Z^%JFi{G4hFX#5E zXo!oDNMXZuxDMUV=8{fD`O4lIG#HL zISUOhHPmck*I~@(07Te;e8#J~5cOf>kenL|6h2>GWD9NkLMKEb)=N1IunKA$BLyHf zz@j^5+EZkliMDFdS>e@a0cS!>*ZxJU!#CYtr=hlqLN6sTC$_NYFDN^v*`|6!o3H*0=>;ql0jFS!W08s}MRyv3Q+B+zwz z&v;}eXZi%s#1QZ;fP7^xSzTg`1pg=HL1i&*yd;Vm<$fAbT5lciqbMBO%a5Y?4`Uqc z^$+(v_Wg)LldKOf0J8A#^H_+$*Jas?6O!&VO7_nwj;S?(7*q$f-kvUFB$|n`7A}B* zhi`0`O-aidmPTZldLv6I^38tB>XK|gb2-ZE%!MFe+hr_`(&I$*T2d^ z2dw~u*}q4siONP;MlYLzaullPsEQq|71BgKt&EEk!tQwX0G+H1oL{xOXY$n(kj%CU z>zz$UT-Qb4c1>qQQIQ1ihklOLw@q0&B7_j)aBI+#V#ik&@`y9)WZjyG2siNpQM3LXL! zN@wM=xVl3Hgxivu4$fm61DrZPymQUoNkEQlbPK|D5`3JtDu?W5&_!*#<{_>0X=yP; zEn{U*UuJa&_akf1_Wei0vQO=oZvic7tp0CCE2~POogk^G7Iw(V{@G#12#UOA=a69E zslm84E32^mGJ zVJHn!r+Fx78c$_UydnF#u}G~(4P1#Bw`&!t+#Mn(t zV`%D%R)QLdkWU5t@{hr=LihL0RpgXi;6wy;%w)I%n;eR=(dG}7$qJG8)&y{Le_A4n zWkd^aOcM2s+8UI=$6nByC5KV;J_QcFL}MEgNryV}+DoY>BHmO$t4}!G0TnmSEV6|iYeXN2 z*H-K|SxM$GT}G{pn*D7XCURPVFBV8KP8f*J-XitM49wB84pZGyF@nHGx%05w6*1W$ zC%%%Z)d4P3{#Tbxzc6J4T`wUe8LT3d4}he`PxWC!QM$u&uxJhTvkQliiJc(S!qt_?;8>b>+G{vLYtJ;jF09AEn*9%^wf_6AtTb*Pf!KHF6RoO2b>jI z-E+YTK0CRq$w=jE$gla*J2|&Imt$Ng7^PkYFJBV?>E>c7w(8+!8H9S_?d>hH)}(%9 z8dH{NDC-gSXGI8j`gf*cX)UbGyx>->w@D&8o4Cr)EeVkw{&An=S>hmazwkn)Y2z4*F1MtF~G8Rzxk2hVc~Xj==UI)NsU!+EUY zfYM>Bkz7oS^?Dv2t`f}92_Dk5_(?^9X1=h0t(%cG6@_9_W5|HNavrr8(x(kvQmm%`Z5rxzD6m>gX7o~OTc4k$q0Nk?oyus(CF)?59w>7LN3N>v3 zd@9n}+8tOhPb=SiiSXN{bBoy0S^%I-bbG#RKBD?C#!P}rT>@qgCM!&q;H<1Idh2pY zwEjF9Mi@~b`U@RKAT$V`A3t({>1}^WUpOG*wWlM*49}oWQ-!Fb~OXqj>F+90Y_)d^CaFRx-0rlk{;Z$To z70twl?oHiUvt=<6Cns=#eL@OT3h+b)H1<}ZcT7%~aU>7ZA7X$(M?KQa7`4-xISQD2 z&aJG-G?u0LOr5@j?GWE-FL8J0|3acb3#Xm%VI(|5fm_$t=ioGiS_nPnOWZF#2I+KEX#FXD zq79IK3|MnFr(vcD)3gI)!%+sPgbo=M?BZHS+LDOERu;raKwI0p!gX$|-CLZSR#y(R zFQMhR*@Cxd5fu$P+5OgFK+F_G2F&PnDY!(Zf(+UZ*~an9R=N+H{bu!9poEo^ZlS@A z?KU;!_r}eZnF)P~7hXYnj#LbK7S^Fa#!EEA#on4VF|PE0ta+V~w?^91h(F*aqe2jo zj)9h3(@UFH)>fV*!I)jBX<_{~QNUB5!X!#<><)zob4Ambln^7qISeDL-}d>i2z?aL z&M@;JW#(E0+<@0Tv^jI?J07but7i8`q z@wQq}M1+pG!gJ`>JK(&_W1o=V8TiGj*a2O{4^nBVON)bp?Iy+6LoLz!52^&{iVTvo z673J18RhP1tLJ(@3_mL-vml%jxbjenUIos3!Eco6kXSUSCvC4-0b2XA1%HiAg=fG~ zgkeI{6fQ{CvdDN<4OT>;TwZE>0spyDHq$w&W8~v$&D29@2N6t!D_8#zcg_dk^~ovj z{G%|lNgHqz%%2P%sBiSK!{n-nK+%#_K!neV`@N$8G+mTKcBiEUK~Wger4O6i># zPe5qcyt|l14W<}CC5o$^e-C9Uw}{~X$%FRQ!vq!_z(F@3cI~@!e1Y8=c57hKWABGJ zFFk}KiMdvEZ6g9zt}P#IdQ_*CKV(0rveu5OKdVO89Os8Y@s=~;&5MvM<)`xF7j;4b zUQoa%9!9IEY)h{=p<(=%{*(7jddnRZZ|M8OuV5WAi?}}pu82&f1YEZ5dKqoPi(WdN zjHadOZ3*G#U>Eu~{toQ1d939QEf6I0&Xjb09-A6P+T%L+uNNuot2<1!;0UEOli6w_ z#X#_{;p~pkn{r3(SMBcXqMLGQF(0$`RWgB-1%RK=8N&Q4#MssJ{_0o*cGR^16-TEI zVC=f|i!*3dl>*;-25_*r@sOF-#oJ{J|Lb&I8<8oH1JIri0NPUplMLklMf|%Psk%9V z9<{qS0W=pcl+Z6tDiJjCLLFO&!p@7<^ zZ)SjPNahvd%i(0uF*c5~nY;kKX{_O!px85P5+AD(HwroURyO}PNX+7UAqdUKLcod4 z3a($90!*?y5m?d^O!8ggUtzw13JCqbTrefn0K}O7POaMFh+`6w{~3~=+&}I>F{2_w zW>KLkpc&4BDY8e8ETA^bTxsiODYALT!HJtW^q^|=ZJ~bKbT;6ln>c8=h6b#B?apg& z0_03h2vi1b#CAe#PgMG%)OBiaI^zO-ZvbRO}fSGM0J*BSdMlPsI<~5%P zpYvX`9+SUdgIc4ej_KX_0Ji!#qf}7gQgKh#`uMM2u8=`VhXA`83sj zntN(=`UnV@qVv8diWf}AS75w~{yH;Ij%7+WeC5HPw0XwtBRFV`5sGXSV9a5e2A_BB zp#Ka7_;vCdT0v8{22)}4w+1zVrFjq$`RvYc9he%O>#&11pz(qVDxmr1B}-ew4Xn5@ zS*zIfueB+f9fB+uBmd`^;zJ&IVx!_O^4wp~okRU2^w=gNXDT(*w~ww50( zN>xZjRSgUg*5VT~sD0t=b=F7X1PSn%a3Vm*DO?OG%+V8QlowS+5rzgozPlXstG+)3 zcDNr=bT;Ve+4YhW`_DOxefT>j85sWNR>U76{{8YNlx@=z+S=^;V2e?7`-|>pbvQhj znmb_+3O-SKF}}2EB(5oZ|BUtFqVTLK!CLioG-F$Sjge-O#=@d_nPghd|19+P_lNs~>?t2JYsB8FSL zULNC7Nm`PL539S^9PTigyG9TF48uh{m?z3_Kh&r99{`oHLazZN2$%eO)X6cxY9vA* z44%9~HVx37=4d(wDdL_-0Grww z7~uMqZq^bkY{2(9D4odxSfetJanqOzRMJl7q2Sq?9lqARQFC5%(ZK;eaG@se8qYl2 zLx}g_O_sdBuH*ELGB-5fth^~UzA|=S2&YWrBvuK>`uQR_?(n9buZR;=yD&AgO?`oKuBA=e3o{(jPp!q9bPZAw*nF*Wx|xSq zJ*WqS+-Cq)>v4j)F>Br4^WIW=`np3{*Dva*XT3f)yTgZy81qYe>dn*rEBpwqBmA)J31RX|Sb-%;Pn-OaW~wQ)XI$In&8&x0gE-O^CgKnp0gg#j-0&^vr{tF}+i z>QAx70IvqU641n)i5Ueq{=sc>4%z%15o9tP!%tI`M$9>$eQ6BgC1@Gli!D_O42}(5 zXh8KqF2we}LRH^D?kHUdPJCj^+X<=Xd;Y&^QZtC(wPBNWtuDf7pj`Tk`&c(fh~WdK z)F4ND2i$&MIEE&Zk+t?*BzWE9e=}HIK*K}`Pnij2ib8yO*&M~=(GH}-2x*2x=GH@2 z`Fx~!Qlac^vIYLQvPEu|WTvt;A?Z|KgWjX8&hV|4SNpZs0T;Zx zXw0haUjb^MxikJ8l=>5w25tK6oiMJh#mVnT>VCCqtOzmQ89POJx;mA1PsnTeovWMf zw^zsNb=8i#;#_>{UhMBH78q)i#33@YRy}@6T0x)n={$>tFr>-p)cjfcCUlWiM%)v7 zyVZR%u)pxVSdBv2bu0yo7Pk&5Pqk@4RYq*C+ZZ;<5b@$ak#&kJ7VFr%ClD@Qu9zt7x`3$~;g2InPXv z1oK(q(fW7=T)Ny8Dohg&on~fne;QVNTcqs=_&rfvh@im%qAi-C{wU@4&lGcSLE>LM zMLktJWf?%vV+C#K6y1PGFDiFE{(PCIfF?F)eN%?z0cJeam%m5iu>?cT=>|^~$8Q4@ z{`G8H1m4G@a5XS94Ck15eJd_@ZQ|eKycyl1q>)s}Gm4gzE44wm;2^azHL+iP8aR9F zIE4@TWVqrhjRyFsTkBl_5IT&5`HDNjZ9!7ROrN18qZKN&peI1mj(~+vdZdmGk_4E! z^OxBz5HBA7bB|`w*;%!LnD`$T&yjIeLcheUQ>-)Y%hxy6)PJ2Hx;+oX6BKRCg`*@b zqfGVb!;m=E9m*LUZNrO}4+)Ysw0`FW&Dg2MQmVw3{?n@~3J`swp%__Ns^tRBz*gcU z;Hcwb{1|E}e>!cFSaY4?Ctl8rn1%AyGj;sW{Z{_B6|8)r0b$gI7LrSAeqvXNfd#LR zx4kQHqOH3n?G|waG&i&Nn)@CcE*W(S1%lTx8#wL!!BC#F;r0`DaAPm3a%^S~bmrL{ zj_x0Wwa)swfp)dK#?`D;5Pj`0SBK<8x0$PpDZ?9Xo1K^W%%wsJP8(fu&G1`Ygw9t# zOcM?@11tXILahA`>sRX@BBw=v4J&`@S2V9U)4(zG;8n_GkV_X>mICayr*+u~?#@DU zknJUHXZP)8G^52Zg8169x<`4O(6%I-fu+qzrhG4Ae6jUSqCTQ+9gdGc`Xh^7-QkbP z0D3a62V7}{lefq?Mvte7QS0;r#2u2t0{DpN{&f8DaFCo%omaiq9U2_YJo`EVmp2WI z*0bxnu@#m5CwhR|f3HhdUp+jnC2ef=nW4MW)5PB{qPljY zi_}8dQ=^0=PDj1+X;)d1l2Lc&#O_WOLJ58k{@WA;o6>I!|4ZNdiKU7Bjor5UfxH={ zuauINu;_O}1cRyxzwm0X=_}Kc)fUf#__*217ak?x>xt&D1fFk)z45gIT5*Mr@xx8z zeu?`t%p3g`J1NU4C1J zS^=tOXUCv;fAvw+HgbAiSabJOB98`uT4+F_(plF;UW@r6bZJKI8w=LN{)WkX^ ze;tKzyF>PzI~_-Z@teH&SCr25GB+nLx-SgC0T`o`8SeU{nh-_9r+U9%1`J@koF!=U z*%Y9Rd!_3`sv}AdJF%3-%qsnzXpiJ;^Cj&<@NWHnEDA2IplJDmobuN{a|YHeMBW5U zg-Hwi11iy<7L6uEf=Dn#J8Tp)hhrbYrc-?{exzs{Q|&?&UPD+2ZZ6x8RxVh_yv*D4 zKUIaQ*c-I;f}5G}Z&u&3{!95e;qn14(WQcar{qGc%0yv>=F@iSuzKb;o5C18Vvwupbc(aDZJHh#r+e`fYrUP6Ge~5?BTk5`mxpn ze^V|mhk|q6G}_tt4cwYBvXHD=$rT|^IGoLab!&&d0ypoKO>q0d#P{5$nIX@oJAWQs zsMZFTB%vlAS0-1E4|*6pQZ#|waB{d^h_e1gzJ>YMQRreXBPk=RB9MW7I^{$Zs|W5m zs)_@44>cApbp3J&9#OYF^%1D7Yz!HOMjZw#2nv6}!vHw2z2OSDJ6=!Nnt~;8CkPD# ztK3`$FK?~)MS-<;QIo9kyBOS8mdhF&J|W87Zzg)TD6H!@Bzn4;*$3hC~y-IP0WJx$cS~MIMAU;ym?T5oOqMu)NFN$kQ*^~e%?o5 zr{LK3>y`?ivSqhh;0vh?KaNjo=8h=&wb~Wx8i9Vv)&?lQW%l8f9+s{R7gY_m6z7xS zJ{)?B!J*S3+hiPPRP1kTf7U3BrLg6JBtVq@f~S;2TbqAFZW)KUC&i&)%qm(#mS4a{ zkzar_Ut{~IO-|x^bN>TeU8675m z@ziWU;`C7oT7Fx16zg?JD4LxQJ` z*ntY17bmxKn43CmJIujj1PvN5yI{1J^ozII!DLO_jONvO;kQ&jKNZQEF>_B>!tuHT zON3}OuqqL1ZygCP59y3jm@KQBtvst)nGGd=faZc5_~vN_F;e#oGc238`uEDh{P>W) z1VFzv4{yjPyNffC2JOkoa5^7EUTSaLVe{q$qQN$f24lo92Bn+;t30>&YcbSVGoS!M z&~2obf(ui6q-zXC)56qD0?uWB*GKa)<%?wek>pc^D{_}=&@x);lL9`%b}=>V4?X{) znaaoZE-+&>;QQ_SZR_9e@8lcV((lj>kYB#NybC7eq$63CZ$%&`jMQ>ZMl$#-%XiNM=oL?@3*RO@SoU5-K8_ zb}U}E$7}0F`1K%&v>~|x23NwtwK`=|XVfWLLtts4srD#R`6qloCq&mqqn$xMH4)S) zfQt8xA|4;ih#`Ye0$~;Tv>m7=+FpQ%TR#Zk+Jx?+fC0XaoxgvfCt3+(>21BrpHS)w zOoN3T<4t9V(9*0asa}L8VuICRK&dLiZ8f(}9HG^Kh{4c!pyNydT*qGsQr=-Z4u%OxbH5D-(E_OCIfg2kO& zK=z$%ocbQF!&pg_fKc2x`0cx10f@`dioGB1JB7;gKp_*MCp>2_o9mtWfX`HX9}P%8 zbg;e`L%u6nI%952AvJ$uMU5pcZIZs_$N&kNR+6CHScu?E+1$c|I+?u`G>AE@b;5=N ztY*6zWu`#%e^$g+A>R;sgr{yZ(32JF-~P9gFi0GNp>eBpC_1{iDDZi>;2a^)YG_z7 zm5eYsK;&)lzT?la{3b!fU4V_gd~%5NO}h6Jsw9yB7Gkz0Gk^VT;(E5O`Fl?yqzsR9 zgz}jYeHW}*FpmTHJoj@$-2S3Ocy%X@Nv@yqq0I-E{RxM(rUuR}2GXh76MW)?86XZI zH{|46wyP;bWO?9ltvS5^N_=uxEN z&b^B7EmNh5%-fme=U-P><8iAq-N&wBqGCN-S+aMil#I!=IJ}C-G9y(>F;`^+d3uQ( zB|G~+0kCp#T5J0Dz!UP+X!9Lk2PryFZpwz8;tDO09c8o(k$ZYYC5 zQF41;TFNJ@{35z1&-wC&%cbz&S)c>2JHMSVESqC7kFYdp*=%(}GY}nH;AH-8;6MoU zv;wR*Ov-(6ipBJ6G`!HH3?1hQzvRZ2C;ixX+;0FGcMHF+Q=P^*hsBA_{YOcEli;KUg-IW={JQtB#MRZ`5)Y$i2%0~l3$ zH$yn(P@LR_IY3rJu&I=0I}XAF&FcwKu=xUnLm<;dNB>$rcp#Jxb| zh?X62zTVdGpuS<$_c(jl@~a7-wQR$x3X_q*y$GQmLyY?cyCHc9oxaLL z2rrdT%;UV@lhBV_B^_bETN!%haGr=b=>bi>z60N5@sKgv8PP32lz=o1by9|-6Gm>d z=_85;^gg&I$-mscNs2&Ncf!7)u6OMZL293j*OW!kyUJ0g^=mtWXV5Ss2`z9t zYr83AMbFN$IbR+ZDfxPQ{qusC$_legxzc5+AsAeNkLNq$YT`w3WP#yFb_Cl~ad_c| z-8s4ypPcTbYcBVdYDC~zLNb?^W2N@s8ch?}AaDafUunC~ud(MVaT$SqN364S;LD)Gg! z6A3Sa?)0WbsCveq(_d`A(I=guv`URYeU{yiGo|tW%pIUpRJ6S^19aDs!QCg6EXWgQ7Lu4+nMM z^fJsW2+#Oi8OJ~dnRR?0rs~NxXN$l;mtKMPVbNgiN{Q`^YGj+nqLF+_hj=QoKUS}Y zXWlPT>$7Tecnq41P3U?24kvpjO{r^->GZYvcnz%ySl*?cM`fBq>^+{(a;@i}W~!Hj zGm^)LDCOslmNVL2djz#7`m`1??%zhtqsxEoI`6;Gmwf#mvdy6a`54g-{M2Ibm&C-8 z)#uXPu}ypAoxV*#-SX}T`Muu9mHOh!9wZSjCWDGu5R%G$Qj-Xj=IU3;-f)T_ca{7g z4ox_*-L?HVm>V&nl5bPuGW>i?W#PSj@&a;mIUTLuW@)iG*u+C`4GM$gKZUJs!@9(5 z(+`gCziDZdm=Qx7Q6!;0vl6#DJY{~FBhHBR4Yr=_x^(Qu{_5`jQ0ygn_a%6ahTEMs zS_L;{y6iM#y3A_nrs)5W?H0DwF+lABSz5B|BvA`rZk?>OgL0$Og`l2hUJ!xokeu)z zz94uOT+cCH#0||gWC=Wpzeq+pjMSKrICMKYtha2^;Q{IvX2%BzgJF&8?#}H_wAYg;cp>zc zsCaHAwHXp_Lk?M$a_yA&5N^!9k?SPq-vPyRTZGV7I~hKZKQsbStNa0y^B(z!sPTRD zyXx2Wt#3!SYG0gVV~G*m4BZwkYO->uwJ9=Pr;QP^arR{G!j#=Jn=aqag*<2W#zp$S z6^)U@$LMMspd%e1)QGss{>1~R9up)UN)$>V_aKeOefHp&!0{EfXHJJJ$+{I5FY_8^ zxQC$bl1q+RYHAF8;sNe{&@~GzJ*En3sPJQfYw_K-Mis(2oo#HXAY-s%#rI_BHTrVMK1y|43`}(#m7UUQx884>~7-hDqyYoq!ODx7k6~9aGyY& zwTN&y^X1*!kyjFObn8tGiOpwN@0Zt`gl?PAs5hZW7lc8Mo^fzKd^6g`3gq{o#Ie=B zhlpT4ZqxA0PDtzRE#sj9QMs&J*U=un6>aOMsfrF4+}GdiXm8(Pv%e{PTqPq8L>^U= z1yaLOA!NRLJj6Wk%VN$9DaTP8a%cW_eT#T$A!&-tLaK**JQ52CmhDK|AgQZe z$3^7xvU(MV4!r}RR&ObG5el)0E+gS$4vO_0J;TK!+5Aq%!W2tR<+0i){RlDTeOE@Y zM#I*i^+L|hAc&Fb*QRNF_b|tb80$vmQ-yi>QDj!(@bC$b~>W?GKjKO(Fir$Z61{8J)F5-|m@qNH~6vuG-awBO;Xo`PkN*_r82cz~sR)7Co#=Lc}_Yb~F(xr3*=+i`IiU%(^Q{Ao(;)QhO zr2J`BhUp$ELvM0s*5Q!73MH)5ml*US?0I4Vs(kMHLM#cvEH(FfFLsXjt9&BU+mcOQ=jSH#VdIF$5( z1f~0H9rZW<1gU6~)l7|O9H3r7GNK-5XK>i?)rG@P(Pk%IF#;+=!Xvlt(OTO?NCB|Y z#(>Dd;F4k&IJG;$bytmxQ=&)dGF$JmdGTH9t*TNrfrkC0-1mGb$w_OH_u?y)z1wISx}^nxpf!CgOYRsIkw>9w(q~qf8z7$a84lskblid} zvfOVLB2vA&DNUIGuENl` z=WY;TVIyYP1ry9Cvkepwdfu^_)Oy!#z2MwKiK*E_TZXtMkk&t0IBq-2GeWl1Tv(rh zr+9cR@s%N{}))zo>=FdGRrY4I%a@B}Q-gcS8n?KrGK?TwT zVA(6Ih2RDIpEYaepaToZs8A$&LI0d2wOdHR{r-I_#;gd2@GBvpgPJkWybcLK-<908 zkPUo#vi$)l0Hc&6>q&B>wn*o$;n%FEMPO#{a5A_R#eDa3k>w!w{7Dc6ANarX2_ z4mXl|T9%txaK!iholUu*I;Hg9eRBCS)*Sn__E47YtdzqiP*u^-=4|L7*pg9m#dK+q z^{Mjk%6B7+-Q6BU$OTe?q=_bW-y=_>T`WGHIs&IFb(V<8sryHA%F=HI%??Eom}X7f z7i&jcJp+XexH9^JE_h9>r9KQ5|E92gGd`S~K^p1M2rr#27iIHm%$f;iGkXavudX}| zYDu_FAFHNUn#Z(2T?1ds-f-*mJm$2EJO`q>)$Qe8 zM#hqmQJLqnoIZCa;vSt3vod|m5>>t6D~dg;H=*d}Lbg2C2K+v%2G78g1qrU%`pu%{HF`dOg|ASPFguv$6%Z!na&+3%CE5bT2!cOTO$~ z;jcj+vlK`2StX-v&weAc?#B_Zyt&E0v3@>^O6z;ID=#8|3l}aDg6dzjAGJ2DoP+L` zY%}PGm=w~n+H>CWA2D;hx9p0vFFtx@`jAw|emh%}2g_opS67}dES+8uTlwIk0<9Uw0_z60`g?MHlp3yj+2=k^ahSfa_+EHcW#DzZ-G*or6 zq<-=@9&eYl`h}hR2mIh=M;8;=y4Vl85K-3&aV@jRxKcxSvBvBahel(uz8x;jgvP(M8;0s;Sp`+ z1l_-n3$+`8)MXd0CGoL~^7Y{@JmdT1*oD?o&%SF%Y_|D&#Omt0Wf}+gL!DC+BJeDY zq9?lLBcdlj+`gSOARV}>4&At9huY}mYgMd4$F+BI`6Grpea9WKmt6qx?wD6vLwD#R z8)Xj_bsPQ94`!BH{2UFpHAsIMBfx}vvNJvj{{`awcbiv$c|e$ToAe)6asA^@$g3O_ zui#uK>&*bo2CaZ?$k{t*dy`+4Lj!DnA4^1_q2@_UndhN9`^Lod1WuH$c1L5?9c{GOQ7j8i~65_$oiCk5yluiE7~v%emNpM zfl2m1hXpCnNt=7w-G37@Aqjy4WN#6QMSafwV!{? zI(z~wak(zxUQ}!Rd<@l&QqkmZwD^e0eJf<|Mnb8T)UIaACTNx%l)s)%gr;)Vt(`HlD-Hdm(M)C$X-B&Lvn8O(}CLp~ZI!1ubNO#s4N~ zFO3z~bw<^=64Azn^fu{>{gdUUJh})J5VL9_@u~d1V(JsKm#(^X6jEsZJl%wHDh>Q} zVJ+YJ_f0L(R<>k^HnacIzyzeODz|^Dx^or0x{Cp zl!g)Uupkc-W>jtk-ztnr8{CjA_U@evUM7Bn7jYM#FbK4s6DD`36Pc&$S>o(!bzR{% zlLTr8!o_AvRp~jsQj}Ae5a3x z3u@lB)ZOr{-Y@A(`Er6xM3~j8X zl}!|!rA(YXx}#L+lsb}<5VAfFzKW8jm%UW+7NthRN;Fc2aZxl*Q&h8HS{F0wHw%{0 z>QzYF7j?k(^~09C(Zasi!qD0kJAnTG^W>J)ttAD_j~Xzr{_lC(8#}3(yE?kNn3_+? z(?QA5&oa`>D5(E$rj_dRE9z1+vdZI%s^j8PF2MiSiB*nhxnMznfSd$@fM5V8_B3`e zw{moMHFv$G%`ZB~hMyZ80hN%GW22zRizH4(s4s1HCp;ka^OS`(??PJ! zqhqh@ZRx)Y9ruVmZyyqp zhefd2iex(r+29^k=cpij#h5SxT;}(*P_{Ob&}fo;WK6@Ei8D>!*>NryeZh_+X^GF; z0OM@&d?|#n3(eU>isv8M?SMEBy@RXO85@k9mit@h;hl!BbMK`%R+ia`->ZGex2IEa zn}Yh+U)MQ~jGTYIN*>iOvu#2ku7ddMAzxC=6h)iLpnZ)H0q@B-mX@!q2ME?deT@{Q zUz*u`Hvu2%CscIF zoV}mt!@Ks`AD(CL_svb**|a#Igi4H*eE$;V#6=P&F@k$_bBdpj^Up`ztK{>Y zq!rwIQ7FuM?ZeMoSy=Ex_S%a&wfk54hIX=9p##AGyA~gARg{HMqM<#tM?=GXSTYco zfBq8p3Go@X6@|q8yN^WTS!QA66QkV{A!tek*kp8Q$u0g?g#}oY@^6KOg^Nt zMkaqa4{%k9-(pMN4YR~7fyJ-~w3TC8Po18zs)d$fp51UgqifO4ekXmWRDLfaf^iN+ ze$qZ6-(8s5Z%NpHEJQ`BDAVu%hhc^N@00@ zJq2A4w=}NInTNET%*|?2VAgu@-4$5iO0Mg+_tom z06w7sl8nK}^+y9!OkxN3$>VAtbiAh-VA~x1l>5?_o=3PRuMr;jadaF2n_ve;?@;KU z{Ql9gKsBnNMH4T9s%;UZi(OB-eUWU7_(39bs zl}n9?9qE$Fxen;)a_G~t#sn+{@Oi#m3IxPGG& z{PDtd&XXc_t5;SI)dQl(dZP}$dNQfyue+xP% ze)E-!2=$MjV37+if9MaEE0*wCh`-&D^8B$h|C){|Jz(GHYZe=roGGv(wKH*r3PnsV z=8yZyWWdedoq$7c!+4Us`(m5iiT?ibv3AlS+1|cdc)F z(`2P54HTpvJkIdDWZ$5V-$|A#KR$9g(wHY+?zXaAPRV$)mEz?_Bu=jde4?W~{icRo z_tr~%b0GS$O;3S8kquo<%^-4YmRO(`WvkpO`DH`aCWN09S;c0Q zt1?%A@}@LXQxUnA+*5B74wwkJcc2g?(bY?o$;sf7LMy(H4bD;?s@;ot{-jP*+iWj7 zig5{!-Py!7T+)zr*i4Zz_KndyB21PiDr8NEc^AxYTMUwNX6KJAt+r?|9^LpfKXkx60olI<_j+TjY|8jYxuePmMLfCleqXyzWZ}2SvlKmp5Q2d5;H1Xi zJ|@a*>oHDjrdH>xA?RaWTw9UICR1$DTJyzZQ$?Of{jKzJJaSdoweP$-B`J+tb)`pbmK`#yB(85Yt4b+yLYubqKGvCZQ8e z*ug%C6Ie+sa*Ta+gAAe5daFh)UL&gZF5eBD7%NukTM=z!+%~dY)+-nLW%tz9Q*>{M zQ&|{%0v&vVZa5Pp89D#4o~CZ$0O2?)TY(EQBOi?gW=#v5!|nrw`PIt=_(owCO{-OA zHq{D3Q_nML&y}WnOb``W+ahOm+bag&1?j5iv^w-^r{?j*%m~)o>I&*j@eQ!Rl9EY2 z8B0DjuHJ-HK6y*n_FR2YMaEHP9bavTpByn@hF=RRe?qmH4x8ppEv6qvM?lB+Y+y<= zV;OZ)DfM_BM4=7505zQ3Y6~bS9Dmt9Zh!7nJWZONwkp^5?W}myP`ZWa>IQ3Q%L})_ zBPmsv_1tfucP|Rd4GKRb2(+~_nntTvmEx<9*v(Ak++1l<-GC2^kZF>uA%b@f+P<}v zdZ+;kyg8davgJpaKtd_!*8Sfq+@d%EB%>>GzNngzA^3FNY^u8Ln6b-SNrYj{yTq&f zqK-$w_nD1vQ2LT6A39}k>&vqR-e^-hhpETj#=LL@B(=nAe87nAz>tN zqnpDRm*NW+9~A&y0t8a+3)0jk%RA;J!CjNeFr1|}qCCySHXan-;T>oQ-?}8Z(IG`t zZqfge@O4i$_6lcI^Pr!lk&UnU?bSeazJF*psbfC3GUKDjFyfJ21Jv zOmPpTMyuYld_@M|P~Y-w+(>_~-n3U>I(jf75g?|QFu$m<#gW@)_qEW_PW~WBy(`98 zfSb~N_3c$A&jnc$It8{IVMH#%b`aXe=QWJd_G&Z>z=tpuauIk?^e3?~4i!FwfW4#< zbDweTo8-^DTFKH63O_rcROU)r)=@Z~v&t8N$NYkrZ80>J?+R-) zM%?4m?NvYt0>6draEKjw0jJGS&Vh1*t?D`rlWEj|K_z?2Y%$5$I_UwZzvWQBNI#7} zOHirZTDEdfQ8`7nDa+(c~IV9Zt40fCJ4FuqWJsENP)%Q z=+F(>s-f7ubjr~-u<>J`&-2a>M8+kE6tLJhm>wbEDBX-GG^MA&b*vB@z?aD|>$~`@ z^IN=@qjguVJ_RI7ghd${Q@(DOg|T-WQ^!8y969-VMSD<`Tlvpv1ofo)v1Fdl_gxN` zL!By)QPQU9?ms8a379!@*4QxETRQF{HAHlJQhkHmDyWNC3W_pc1w+ z24<3+Gc3nh^!~=!IFqzC)ycMY`eLwqH(+dRYD(x;V&J|AL9^dyCJ+DSE<9Vu4d{Rg zgn|NW-t?L6;3kI<{5FYDw=Bz)^zOC!X8=(F7j784j$Ar&MRKWIHboyd-Faivc@%sixt|JDP1 zdXQY~6;!_s*Dq;Ja+n18GD~|m7rZ z!QMvgisHL)vHK0=TkNOo!K7DG4%j3XUspG8?zw+mmtD2(eEIJ3x)?yGx17nZ7LbAT zDCjG(4zevI@k-}}*y)=J-9yNX>rz(^H5epPNAQQmhEz`X7Mcu|!47gwPX@gj z%(qNwf8rCGQs#G`_~XV2i}!FP(QagqpTFid2|(-VkD1?M9$A zo|elxP^Q*P61d20xvNozQ)?Wc-J+UX^K>qwC~fXQ7O5HSv|N;eeYkdQ;KD zMxI5>rPe z6?CrUI{Y4m78Jz}NP0VNhi$W3zs~C(ru0F?dqMh7NDi3dX7?{{r=P>Q+B~Q7AUUXl z>iqLjf3S&h4=+QuFY3^|xP30>iit!YS6!~6YWt4tbgMk3Lryf) z60V?G3>TW2rm4nht;^Ql$SCHt7XbLCYi|e4r!}nAq#dH6?^?D2v`+}|+nz${2Fm)u z8$Mw{uJX$hMue6^+Qvo`PQK1|29-nr4V@i^OF!;(BmZl-W~nf8o4@E%EyY?M%)sH6 z^*vsUl9}OowfBvFV~iU=36mt|ui_K?%#}CGbPbDTEUR5?Npabz`q^h)CeZ8Wd0+FF zAyge&20sZ&gS#OG6Io|2mdZ9n{r+#%K++2S`wjTwzmphcJ-rLg-b|U*D7c-#xlNJ| zy-9~t8BLj5WpH3v`5ZlWA9%uP6m9vh)8Xgo)DEF$WrhN+gx%)+H)gy|7)w10t**5- zW4{k_Eity)_PhMe;|sc{NzMqT1B-uV3YW+(%fJ$ZV(J!_#Nq0)j-}Op^Mt@poJr)A^v(IE z#ABQAbp|LHG?9O)hBg&-8c2|9J+gSjVqgM!`Pv}V@nO?gFp8M@qi^C+xINQSVx0g( z=0$)xX!uUP4DWmHdDwuzf_MJRknk}IAV&2Fs|5T?-=-eU<(Se-7VXEDL$GvG0X^J) z+3u=I`$IWUU3LBTELy_4*(Y=b44xAl!#2gO{pp_E>RCmX+TRJ4G*nT)*GTv+VbM;4 zfN9e)>*tNH^g6+(A?}luog(6|KT1<_GLeX+Rz74IMXu^IT6z59S@)}k2vAUjbBZBe zy^#AvxasQYxyWW$+(cER7B*{INL&eI}! zkMJp8_Y)>svDk)SE8`RwSKHz$!PS6Sdq)g3Il(F25A_9hUti!9hxuY{=iB1KH{xtB z-t);Vs#j&CwiS73_2ffjcn1I@3jwxHQqeusmg;eHaZ0uv5@S-5IkqEHt;+}0yI;$9 zMxyXg-xUujxpbtWL#clVy_WMC^(Fdzn!9X=kx>Thuz#c7-%1!xIx`~oQxck${~h{^ z`YFTavtOdaMzcE?C6^~J9WBAZm{|*M`Hh6rk1pako@C89m9lLi)O{t8UB`=qorH|> zU;Dk?Z%Cn^2zl5SEAr;rt5W7uw(%-vv&-`wZ~eg(b1HRx`0`Ig6fc}B=5_bb$;9pJ zdp5&SJ&m!<#;RuQXi?5;6QfiZ-G(~?P@1q!Cf|*83@xz89~EvOUwVVqPE?pP)bTcQ zPiXzO!kIqj=@4o1yFP)N$$@C+j5o2#jzgDJ>ZC>CRrS)(e{jZshtrKDSC};JebqYp zG^kqG6L5Q!cfYoebq5(1E;pPfSAk68>16Fwe%eL15xB&A+Q%qb+J&^aFL{?lyRgRH z5bw#-muaprv>)Hbr7*gYo1l*{iL0UNHGu?k%m%bsYQA3k#CEq`UMDzlJRSO@ySibw zvdh`EjTL`vUyJ;|~LEe_l`=(RD6V`yk$ifmlIoWJi^L zzn63(o^;l*9Ho_{#EbHo?7d23`jL7N&S|+J-Q?FORM7}Le6*52eBjHbL`g%wtC}QE zqHh@07g>!wO7dx0DkW-$K%V!KX@;`!Waz89tWB?2)5gUq$?O+v$02VliRO68fAV4B z2$WSqy4*=g4S2y+b zQAAf0)jZr9J>!i)|GcENqRS8ZVlrv<$6jKfrgFx-2WS&BNF-^r!+w+x(FV$AH zRFW0{_(UET(TU2RE6Eo!9Ui_NGImQf-N8E>(mvWoxcAF`*S&Kx@lBbk2 zmTih2f38T@cPCI9t`ks5aQHO2$yM-9TI{(v4^`;? zU=OO|Q@rg0gajWp%(Nk%+zBQXIZ@J>CObHdk6n2t$$cIh5oXIK+MP`xk%XnBy5k)< z#Pls}tmKAq?A{P{hBY~8Hng7YWe0huVMu^n$B0h+?Dz2Gx>o>*t`<5-5tpd)8e-_qMt5uuQj zaokBPjEp~x5H0=glTmmlQG5`(0=z9W6Zz1d@W z4A}F)2qWoD?l|ijn?%l5mh-#_OPW^&I(rkh7lS%O;dvxunws%D?2+@)BCH)kk!2sw zdCpFbkzQqF;Nz@{*)PW{8WFPBfh&VdoznOfDf9ESKEY6fD86GG~Pn5er=4Kmw5A9}tLbIUHlE*1xzimfZD|eeBK=5J4>~e2RhM{oPawPbviRDh zvhF%dQE<-Xsk7?~_#(8d`81YY%rZsteAKzUNl8-Y$8yE$YFhB*ozXnr4dNc-|H{;J zN7SVx5Arn8L;pvn_OJt4dboj()wu@L231eEhLs0ZIdC5A5cCK8<6lR}s0$^f8iIya zi}~n3um>a>`aiG#G$*jT`-9m1H|TF0>7Tj(4RQ(je@&&oN&fbD{v~-5`WMOna(w<~ z`Pm&NsgKT^e}n#x@xLI|%>Rk_8&zzae+FA Marianas full map campaign. The campaign is tuned for out-of-the-box Pretense generation compatibility and as such will be unbalanced for a standard campaign.

+miz: marianas_full.miz +performance: 3 +version: "10.7" +squadrons: + #Andersen + 6: + - primary: Air Assault + aircraft: + - UH-1H Iroquois + - Mi-8MTV2 Hip + - primary: CAS + aircraft: + - AH-64D Apache Longbow + - Ka-50 Hokum III + - Ka-50 Hokum + - Mi-24P Hind-F + - primary: CAS + secondary: any + aircraft: + - A-10C Thunderbolt II (Suite 7) + - A-10C Thunderbolt II (Suite 3) + - A-10A Thunderbolt II + - Su-25 Frogfoot + - L-39ZA Albatros + - primary: SEAD + aircraft: + - F-16CM Fighting Falcon (Block 50) + - F/A-18C Hornet (Lot 20) + - Su-25T Frogfoot + - primary: BARCAP + secondary: air-to-air + aircraft: + - F-15C Eagle + - J-11A Flanker-L + - MiG-29S Fulcrum-C + - Su-27 Flanker-B + - Su-33 Flanker-D + - MiG-29A Fulcrum-A + - primary: AEW&C + aircraft: + - E-3A + - A-50 + - primary: Refueling + aircraft: + - KC-135 Stratotanker + - IL-78M + - primary: Transport + aircraft: + - C-130 + - An-26B + #Saipan + 2: + - primary: Air Assault + aircraft: + - UH-1H Iroquois + - Mi-8MTV2 Hip + - primary: CAS + aircraft: + - AH-64D Apache Longbow + - Ka-50 Hokum III + - Ka-50 Hokum + - Mi-24P Hind-F + - primary: CAS + secondary: any + aircraft: + - A-10C Thunderbolt II (Suite 7) + - A-10C Thunderbolt II (Suite 3) + - A-10A Thunderbolt II + - Su-25 Frogfoot + - L-39ZA Albatros + - primary: SEAD + aircraft: + - F-16CM Fighting Falcon (Block 50) + - F/A-18C Hornet (Lot 20) + - Su-25T Frogfoot + - primary: BARCAP + secondary: air-to-air + aircraft: + - F-15C Eagle + - J-11A Flanker-L + - MiG-29S Fulcrum-C + - Su-27 Flanker-B + - Su-33 Flanker-D + - MiG-29A Fulcrum-A + - primary: AEW&C + aircraft: + - E-3A + - A-50 + - primary: Refueling + aircraft: + - KC-135 Stratotanker + - IL-78M + - primary: Transport + aircraft: + - C-130 + - An-26B + Blue CVBG: + - primary: BARCAP + aircraft: + - F/A-18C Hornet (Lot 20) + - Su-33 Flanker-D + - primary: AEW&C + aircraft: + - E-2D Advanced Hawkeye + - primary: Refueling + aircraft: + - S-3B Tanker + Red CVBG: + - primary: BARCAP + aircraft: + - F/A-18C Hornet (Lot 20) + - Su-33 Flanker-D + - primary: AEW&C + aircraft: + - E-2D Advanced Hawkeye + - primary: Refueling + aircraft: + - S-3B Tanker \ No newline at end of file diff --git a/resources/campaigns/nevada_full.miz b/resources/campaigns/nevada_full.miz new file mode 100644 index 0000000000000000000000000000000000000000..d8ec297241bb48dce4da31a80e956c657fb70384 GIT binary patch literal 54271 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 Wx%zkQFAji=$oirxQ5Q=@ME?N0uNFZ7 literal 0 HcmV?d00001 diff --git a/resources/campaigns/nevada_full.yaml b/resources/campaigns/nevada_full.yaml new file mode 100644 index 00000000..56618621 --- /dev/null +++ b/resources/campaigns/nevada_full.yaml @@ -0,0 +1,104 @@ +--- +name: Nevada - Full +theater: Nevada +authors: Colonel Akir Nakesh +recommended_player_faction: Bluefor Modern +recommended_enemy_faction: USAF Aggressors +description: +

A Nevada full map campaign. The campaign is tuned for out-of-the-box Pretense generation compatibility and as such may be unbalanced for a standard campaign.

+miz: nevada_full.miz +performance: 3 +version: "10.7" +squadrons: + # Nellis + 4: + - primary: Air Assault + aircraft: + - UH-1H Iroquois + - Mi-8MTV2 Hip + - primary: CAS + aircraft: + - AH-64D Apache Longbow + - Ka-50 Hokum III + - Ka-50 Hokum + - Mi-24P Hind-F + - primary: CAS + secondary: any + aircraft: + - A-10C Thunderbolt II (Suite 7) + - A-10C Thunderbolt II (Suite 3) + - A-10A Thunderbolt II + - Su-25 Frogfoot + - L-39ZA Albatros + - primary: SEAD + aircraft: + - F-16CM Fighting Falcon (Block 50) + - F/A-18C Hornet (Lot 20) + - Su-25T Frogfoot + - primary: BARCAP + secondary: air-to-air + aircraft: + - F-15C Eagle + - J-11A Flanker-L + - MiG-29S Fulcrum-C + - Su-27 Flanker-B + - Su-33 Flanker-D + - MiG-29A Fulcrum-A + - primary: AEW&C + aircraft: + - E-3A + - A-50 + - primary: Refueling + aircraft: + - KC-135 Stratotanker + - IL-78M + - primary: Transport + aircraft: + - C-130 + - An-26B + # Tonopah Test Range + 18: + - primary: Air Assault + aircraft: + - UH-1H Iroquois + - Mi-8MTV2 Hip + - primary: CAS + aircraft: + - AH-64D Apache Longbow + - Ka-50 Hokum III + - Ka-50 Hokum + - Mi-24P Hind-F + - primary: CAS + secondary: any + aircraft: + - A-10C Thunderbolt II (Suite 7) + - A-10C Thunderbolt II (Suite 3) + - A-10A Thunderbolt II + - Su-25 Frogfoot + - L-39ZA Albatros + - primary: SEAD + aircraft: + - F-16CM Fighting Falcon (Block 50) + - F/A-18C Hornet (Lot 20) + - Su-25T Frogfoot + - primary: BARCAP + secondary: air-to-air + aircraft: + - F-15C Eagle + - J-11A Flanker-L + - MiG-29S Fulcrum-C + - Su-27 Flanker-B + - Su-33 Flanker-D + - MiG-29A Fulcrum-A + - primary: AEW&C + aircraft: + - E-3A + - A-50 + - primary: Refueling + aircraft: + - KC-135 Stratotanker + - IL-78M + - primary: Transport + aircraft: + - C-130 + - An-26B diff --git a/resources/campaigns/persian_gulf_full.miz b/resources/campaigns/persian_gulf_full.miz new file mode 100644 index 0000000000000000000000000000000000000000..7345e5b753a769ccfb0bd05c51d8c1c43db5b53a GIT binary patch literal 47946 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
A2NvX8Ef)@XfOw=1Jcv82gQD6b$i+Zi)H{29 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 dHOg!KKZjdU1_JV*21wx77A Persian Gulf full map campaign. The campaign is tuned for out-of-the-box Pretense generation compatibility and as such will be unbalanced for a standard campaign. Recommended settings for campaign generation: disable frontline smoke, disable CTLD, disable Skynet, set CVBG navmesh to Redfor.

+miz: persian_gulf_full.miz +performance: 3 +version: "10.7" +squadrons: + #Al Dhafra AFB + 4: + - primary: Air Assault + aircraft: + - UH-1H Iroquois + - Mi-8MTV2 Hip + - primary: CAS + aircraft: + - AH-64D Apache Longbow + - Ka-50 Hokum III + - Ka-50 Hokum + - Mi-24P Hind-F + - primary: CAS + secondary: any + aircraft: + - A-10C Thunderbolt II (Suite 7) + - A-10C Thunderbolt II (Suite 3) + - A-10A Thunderbolt II + - Su-25 Frogfoot + - L-39ZA Albatros + - primary: SEAD + aircraft: + - F-16CM Fighting Falcon (Block 50) + - F/A-18C Hornet (Lot 20) + - Su-25T Frogfoot + - primary: BARCAP + secondary: air-to-air + aircraft: + - F-15C Eagle + - J-11A Flanker-L + - MiG-29S Fulcrum-C + - Su-27 Flanker-B + - Su-33 Flanker-D + - MiG-29A Fulcrum-A + - primary: AEW&C + aircraft: + - E-3A + - A-50 + - primary: Refueling + aircraft: + - KC-135 Stratotanker + - IL-78M + - primary: Transport + aircraft: + - C-130 + - An-26B + #Shiraz Intl + 19: + - primary: Air Assault + aircraft: + - UH-1H Iroquois + - Mi-8MTV2 Hip + - primary: CAS + aircraft: + - AH-64D Apache Longbow + - Ka-50 Hokum III + - Ka-50 Hokum + - Mi-24P Hind-F + - primary: CAS + secondary: any + aircraft: + - A-10C Thunderbolt II (Suite 7) + - A-10C Thunderbolt II (Suite 3) + - A-10A Thunderbolt II + - Su-25 Frogfoot + - L-39ZA Albatros + - primary: SEAD + aircraft: + - F-16CM Fighting Falcon (Block 50) + - F/A-18C Hornet (Lot 20) + - Su-25T Frogfoot + - primary: BARCAP + secondary: air-to-air + aircraft: + - F-15C Eagle + - J-11A Flanker-L + - MiG-29S Fulcrum-C + - Su-27 Flanker-B + - Su-33 Flanker-D + - MiG-29A Fulcrum-A + - primary: AEW&C + aircraft: + - E-3A + - A-50 + - primary: Refueling + aircraft: + - KC-135 Stratotanker + - IL-78M + - primary: Transport + aircraft: + - C-130 + - An-26B + Blue CVBG: + - primary: BARCAP + aircraft: + - F/A-18C Hornet (Lot 20) + - Su-33 Flanker-D + - primary: AEW&C + aircraft: + - E-2D Advanced Hawkeye + - primary: Refueling + aircraft: + - S-3B Tanker + Red CVBG: + - primary: BARCAP + aircraft: + - F/A-18C Hornet (Lot 20) + - Su-33 Flanker-D + - primary: AEW&C + aircraft: + - E-2D Advanced Hawkeye + - primary: Refueling + aircraft: + - S-3B Tanker \ No newline at end of file From 93da86eed30aae824fb779eb7f4c5ff343b43d6f Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Wed, 17 Jul 2024 19:40:29 +0300 Subject: [PATCH 230/243] Escaped the colon character in the description of the persian_gulf_full.yaml and added default settings to it. Changed the naming of the campaigns based on feedback from the Discord server. --- resources/campaigns/marianas_full.yaml | 2 +- resources/campaigns/nevada_full.yaml | 2 +- resources/campaigns/persian_gulf_full.yaml | 10 ++++++++-- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/resources/campaigns/marianas_full.yaml b/resources/campaigns/marianas_full.yaml index 06085777..d0edb340 100644 --- a/resources/campaigns/marianas_full.yaml +++ b/resources/campaigns/marianas_full.yaml @@ -1,5 +1,5 @@ --- -name: Marianas - Full +name: Marianas - [Pretense] - Full map theater: MarianaIslands authors: Colonel Akir Nakesh recommended_player_faction: Bluefor Modern diff --git a/resources/campaigns/nevada_full.yaml b/resources/campaigns/nevada_full.yaml index 56618621..a85d68fe 100644 --- a/resources/campaigns/nevada_full.yaml +++ b/resources/campaigns/nevada_full.yaml @@ -1,5 +1,5 @@ --- -name: Nevada - Full +name: Nevada - [Pretense] - Full map theater: Nevada authors: Colonel Akir Nakesh recommended_player_faction: Bluefor Modern diff --git a/resources/campaigns/persian_gulf_full.yaml b/resources/campaigns/persian_gulf_full.yaml index 78694f7a..d3f18111 100644 --- a/resources/campaigns/persian_gulf_full.yaml +++ b/resources/campaigns/persian_gulf_full.yaml @@ -1,14 +1,20 @@ --- -name: Persian Gulf - Full +name: Persian Gulf - [Pretense] - Full map theater: Persian Gulf authors: Colonel Akir Nakesh recommended_player_faction: Bluefor Modern recommended_enemy_faction: Iran 2015 description: -

A Persian Gulf full map campaign. The campaign is tuned for out-of-the-box Pretense generation compatibility and as such will be unbalanced for a standard campaign. Recommended settings for campaign generation: disable frontline smoke, disable CTLD, disable Skynet, set CVBG navmesh to Redfor.

+

A Persian Gulf full map campaign. The campaign is tuned for out-of-the-box Pretense generation compatibility and as such will be unbalanced for a standard campaign. Recommended settings for campaign generation: disable frontline smoke, disable CTLD, disable Skynet, set CVBG navmesh to Redfor.

miz: persian_gulf_full.miz performance: 3 version: "10.7" +settings: + pretense_carrier_zones_navmesh: "Red navmesh" + perf_smoke_gen: false + plugins: + ctld: false + skynetiads: false squadrons: #Al Dhafra AFB 4: From 16239d4089a43eac29f09d65f48d154f37e707fd Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Wed, 17 Jul 2024 19:48:51 +0300 Subject: [PATCH 231/243] Added Afghanistan - Pretense Full campaign by @ColonelAkirNakesh --- resources/campaigns/afghanistan_full.miz | Bin 0 -> 43520 bytes resources/campaigns/afghanistan_full.yaml | 104 ++++++++++++++++++++++ 2 files changed, 104 insertions(+) create mode 100644 resources/campaigns/afghanistan_full.miz create mode 100644 resources/campaigns/afghanistan_full.yaml diff --git a/resources/campaigns/afghanistan_full.miz b/resources/campaigns/afghanistan_full.miz new file mode 100644 index 0000000000000000000000000000000000000000..993a17414ec02eee462d9b3c54fafcf2cb9328b8 GIT binary patch literal 43520 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 lKhnp)LI3XH|A4eA|EIfEIb8q% literal 0 HcmV?d00001 diff --git a/resources/campaigns/afghanistan_full.yaml b/resources/campaigns/afghanistan_full.yaml new file mode 100644 index 00000000..ae512eb4 --- /dev/null +++ b/resources/campaigns/afghanistan_full.yaml @@ -0,0 +1,104 @@ +--- +name: Afghanistan - Pretense Full +theater: Afghanistan +authors: Colonel Akir Nakesh +recommended_player_faction: Bluefor Modern +recommended_enemy_faction: Insurgents +description: +

An Afghanistan full map campaign. The campaign is tuned for out-of-the-box Pretense generation compatibility and as such may be unbalanced for a standard campaign.

+miz: afghanistan_full.miz +performance: 3 +version: "10.7" +squadrons: + # Herat + 1: + - primary: Air Assault + aircraft: + - UH-1H Iroquois + - Mi-8MTV2 Hip + - primary: CAS + aircraft: + - AH-64D Apache Longbow + - Ka-50 Hokum III + - Ka-50 Hokum + - Mi-24P Hind-F + - primary: CAS + secondary: any + aircraft: + - A-10C Thunderbolt II (Suite 7) + - A-10C Thunderbolt II (Suite 3) + - A-10A Thunderbolt II + - Su-25 Frogfoot + - L-39ZA Albatros + - primary: SEAD + aircraft: + - F-16CM Fighting Falcon (Block 50) + - F/A-18C Hornet (Lot 20) + - Su-25T Frogfoot + - primary: BARCAP + secondary: air-to-air + aircraft: + - F-15C Eagle + - J-11A Flanker-L + - MiG-29S Fulcrum-C + - Su-27 Flanker-B + - Su-33 Flanker-D + - MiG-29A Fulcrum-A + - primary: AEW&C + aircraft: + - E-3A + - A-50 + - primary: Refueling + aircraft: + - KC-135 Stratotanker + - IL-78M + - primary: Transport + aircraft: + - C-130 + - An-26B + # Kandahar + 7: + - primary: Air Assault + aircraft: + - UH-1H Iroquois + - Mi-8MTV2 Hip + - primary: CAS + aircraft: + - AH-64D Apache Longbow + - Ka-50 Hokum III + - Ka-50 Hokum + - Mi-24P Hind-F + - primary: CAS + secondary: any + aircraft: + - A-10C Thunderbolt II (Suite 7) + - A-10C Thunderbolt II (Suite 3) + - A-10A Thunderbolt II + - Su-25 Frogfoot + - L-39ZA Albatros + - primary: SEAD + aircraft: + - F-16CM Fighting Falcon (Block 50) + - F/A-18C Hornet (Lot 20) + - Su-25T Frogfoot + - primary: BARCAP + secondary: air-to-air + aircraft: + - F-15C Eagle + - J-11A Flanker-L + - MiG-29S Fulcrum-C + - Su-27 Flanker-B + - Su-33 Flanker-D + - MiG-29A Fulcrum-A + - primary: AEW&C + aircraft: + - E-3A + - A-50 + - primary: Refueling + aircraft: + - KC-135 Stratotanker + - IL-78M + - primary: Transport + aircraft: + - C-130 + - An-26B From 4c05837b6162841123a554ea58d51de95362bb8a Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Wed, 17 Jul 2024 19:51:03 +0300 Subject: [PATCH 232/243] Changed the naming of afghanistan_full.yaml campaign based on feedback from the Discord server. --- resources/campaigns/afghanistan_full.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/campaigns/afghanistan_full.yaml b/resources/campaigns/afghanistan_full.yaml index ae512eb4..bf38482b 100644 --- a/resources/campaigns/afghanistan_full.yaml +++ b/resources/campaigns/afghanistan_full.yaml @@ -1,5 +1,5 @@ --- -name: Afghanistan - Pretense Full +name: Afghanistan - [Pretense] - Full map theater: Afghanistan authors: Colonel Akir Nakesh recommended_player_faction: Bluefor Modern From 8dcf25d9ee7dd843115bee606bfd6457a92cea02 Mon Sep 17 00:00:00 2001 From: MetalStormGhost Date: Sat, 20 Jul 2024 19:21:56 +0300 Subject: [PATCH 233/243] Updated MIST to 4.5.126 --- game/pretense/pretenseluagenerator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/game/pretense/pretenseluagenerator.py b/game/pretense/pretenseluagenerator.py index d2ee4c85..304ce93a 100644 --- a/game/pretense/pretenseluagenerator.py +++ b/game/pretense/pretenseluagenerator.py @@ -1610,7 +1610,7 @@ class PretenseLuaGenerator(LuaGenerator): return lua_string_connman def generate_pretense_plugin_data(self) -> None: - self.inject_plugin_script("base", "mist_4_5_122.lua", "mist_4_5_122") + self.inject_plugin_script("base", "mist_4_5_126.lua", "mist_4_5_126") lua_string_config = "Config = Config or {}\n" From 1b757ec54411a692bd71ffe3c929366db34e4e17 Mon Sep 17 00:00:00 2001 From: Raffson Date: Sun, 28 Jul 2024 16:53:09 +0200 Subject: [PATCH 234/243] Fixing warehouse stuff --- game/missiongenerator/tgogenerator.py | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/game/missiongenerator/tgogenerator.py b/game/missiongenerator/tgogenerator.py index 993d84ab..d1ffdc0c 100644 --- a/game/missiongenerator/tgogenerator.py +++ b/game/missiongenerator/tgogenerator.py @@ -859,15 +859,15 @@ class HelipadGenerator: else: cull_farp_statics = False - if not cull_farp_statics: - warehouse = Airport( - pad.position, - self.m.terrain, - ).dict() - warehouse["coalition"] = "blue" if self.cp.coalition.player else "red" - # configure dynamic spawn + hot start of DS, plus dynamic cargo? - self.m.warehouses.warehouses[pad.id] = warehouse + warehouse = Airport( + pad.position, + self.m.terrain, + ).dict() + warehouse["coalition"] = "blue" if self.cp.coalition.player else "red" + # configure dynamic spawn + hot start of DS, plus dynamic cargo? + self.m.warehouses.warehouses[pad.id] = warehouse + if not cull_farp_statics: # Generate a FARP Ammo and Fuel stack for each pad self.m.static_group( country=country, @@ -986,6 +986,14 @@ class GroundSpawnRoadbaseGenerator: else: cull_farp_statics = False + warehouse = Airport( + pad.position, + self.m.terrain, + ).dict() + warehouse["coalition"] = "blue" if self.cp.coalition.player else "red" + # configure dynamic spawn + hot start of DS, plus dynamic cargo? + self.m.warehouses.warehouses[pad.id] = warehouse + if not cull_farp_statics: # Generate ammo truck/farp and fuel truck/stack for each pad if self.game.settings.ground_start_trucks_roadbase: From 8eb30445c246a3f88d54644e6320e190cd22e9c5 Mon Sep 17 00:00:00 2001 From: Raffson Date: Sun, 28 Jul 2024 16:59:21 +0200 Subject: [PATCH 235/243] Restore hidden_on_mfd functionality --- game/missiongenerator/tgogenerator.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/game/missiongenerator/tgogenerator.py b/game/missiongenerator/tgogenerator.py index d1ffdc0c..73acdc64 100644 --- a/game/missiongenerator/tgogenerator.py +++ b/game/missiongenerator/tgogenerator.py @@ -332,6 +332,7 @@ class GroundObjectGenerator: self._register_theater_unit(unit, vehicle_group.units[-1]) if vehicle_group is None: raise RuntimeError(f"Error creating VehicleGroup for {group_name}") + vehicle_group.hidden_on_mfd = self.ground_object.hide_on_mfd return vehicle_group def create_ship_group( @@ -368,6 +369,7 @@ class GroundObjectGenerator: self._register_theater_unit(unit, ship_group.units[-1]) if ship_group is None: raise RuntimeError(f"Error creating ShipGroup for {group_name}") + ship_group.hidden_on_mfd = self.ground_object.hide_on_mfd return ship_group def create_static_group(self, unit: TheaterUnit) -> None: From 5bb70626d2dcbd9d698e2b8fbcc1e2d570fe1d42 Mon Sep 17 00:00:00 2001 From: Raffson Date: Sun, 28 Jul 2024 17:08:01 +0200 Subject: [PATCH 236/243] Revert unnecessary change --- game/ato/flightstate/navigating.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/game/ato/flightstate/navigating.py b/game/ato/flightstate/navigating.py index 39acfb06..26cfbbc0 100644 --- a/game/ato/flightstate/navigating.py +++ b/game/ato/flightstate/navigating.py @@ -29,8 +29,10 @@ class Navigating(InFlight): events.update_flight_position(self.flight, self.estimate_position()) def progress(self) -> float: - if self.total_time_to_next_waypoint.total_seconds() == 0.0: - return 99.9 + # if next waypoint is very close, assume we reach it immediately to avoid divide + # by zero error + if self.total_time_to_next_waypoint.total_seconds() < 1: + return 1.0 return ( self.elapsed_time.total_seconds() / self.total_time_to_next_waypoint.total_seconds() From d604905f52b3a9917ccaf608bde32ed11aff79b6 Mon Sep 17 00:00:00 2001 From: Raffson Date: Sun, 28 Jul 2024 17:13:26 +0200 Subject: [PATCH 237/243] Streamlining after merge --- game/campaignloader/defaultsquadronassigner.py | 2 +- game/campaignloader/squadrondefgenerator.py | 4 +++- game/pretense/pretenseaircraftgenerator.py | 6 +++--- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/game/campaignloader/defaultsquadronassigner.py b/game/campaignloader/defaultsquadronassigner.py index 763d31d5..624dc317 100644 --- a/game/campaignloader/defaultsquadronassigner.py +++ b/game/campaignloader/defaultsquadronassigner.py @@ -74,7 +74,7 @@ class DefaultSquadronAssigner: # If we can't find any squadron matching the requirement, we should # create one. return self.air_wing.squadron_def_generator.generate_for_task( - config.primary, control_point, self.game.settings.squadron_random_chance + config.primary, control_point ) def find_preferred_squadron( diff --git a/game/campaignloader/squadrondefgenerator.py b/game/campaignloader/squadrondefgenerator.py index f8b775db..f5e38552 100644 --- a/game/campaignloader/squadrondefgenerator.py +++ b/game/campaignloader/squadrondefgenerator.py @@ -21,8 +21,10 @@ class SquadronDefGenerator: self.used_nicknames: set[str] = set() def generate_for_task( - self, task: FlightType, control_point: ControlPoint, squadron_random_chance: int + self, task: FlightType, control_point: ControlPoint ) -> Optional[SquadronDef]: + settings = control_point.coalition.game.settings + squadron_random_chance = settings.squadron_random_chance aircraft_choice: Optional[AircraftType] = None for aircraft in AircraftType.priority_list_for_task(task): if aircraft not in self.faction.all_aircrafts: diff --git a/game/pretense/pretenseaircraftgenerator.py b/game/pretense/pretenseaircraftgenerator.py index a88c4a2f..81d9e935 100644 --- a/game/pretense/pretenseaircraftgenerator.py +++ b/game/pretense/pretenseaircraftgenerator.py @@ -33,6 +33,7 @@ from game.radio.tacan import TacanRegistry from game.runways import RunwayData from game.settings import Settings from game.squadrons import AirWing +from game.squadrons import Squadron from game.theater.controlpoint import ( ControlPoint, OffMapSpawn, @@ -43,7 +44,6 @@ from game.theater.controlpoint import ( ) from game.theater.theatergroundobject import EwrGroundObject, SamGroundObject from game.unitmap import UnitMap -from game.squadrons import Squadron if TYPE_CHECKING: from game import Game @@ -193,13 +193,13 @@ class PretenseAircraftGenerator: """ squadron_def = coalition.air_wing.squadron_def_generator.generate_for_task( - flight_type, cp, self.game.settings.squadron_random_chance + flight_type, cp ) for retries in range(num_retries): if squadron_def is None or fixed_wing == squadron_def.aircraft.helicopter: squadron_def = ( coalition.air_wing.squadron_def_generator.generate_for_task( - flight_type, cp, self.game.settings.squadron_random_chance + flight_type, cp ) ) From 3bdda3b85278cd3f17bd646d2fb782a42cdfcbc3 Mon Sep 17 00:00:00 2001 From: Raffson Date: Sun, 28 Jul 2024 17:14:08 +0200 Subject: [PATCH 238/243] Readd removed newline --- game/ato/flightstate/navigating.py | 1 + 1 file changed, 1 insertion(+) diff --git a/game/ato/flightstate/navigating.py b/game/ato/flightstate/navigating.py index 26cfbbc0..3932dbf3 100644 --- a/game/ato/flightstate/navigating.py +++ b/game/ato/flightstate/navigating.py @@ -33,6 +33,7 @@ class Navigating(InFlight): # by zero error if self.total_time_to_next_waypoint.total_seconds() < 1: return 1.0 + return ( self.elapsed_time.total_seconds() / self.total_time_to_next_waypoint.total_seconds() From d2c45c97fea3fc406bab54b79413f3d193c216f1 Mon Sep 17 00:00:00 2001 From: Raffson Date: Sun, 28 Jul 2024 17:50:02 +0200 Subject: [PATCH 239/243] Refactor invisible FARP remover --- .../aircraft/flightgroupspawner.py | 44 +++++++++---------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/game/missiongenerator/aircraft/flightgroupspawner.py b/game/missiongenerator/aircraft/flightgroupspawner.py index bcdcd1bb..58d4e9b1 100644 --- a/game/missiongenerator/aircraft/flightgroupspawner.py +++ b/game/missiongenerator/aircraft/flightgroupspawner.py @@ -497,7 +497,7 @@ class FlightGroupSpawner: ) -> Optional[FlyingGroup[Any]]: is_airbase = False is_roadbase = False - ground_spawn = None + ground_spawn: Optional[Tuple[StaticGroup, Point]] = None if not is_large and len(self.ground_spawns_roadbase[cp]) > 0: ground_spawn = self.ground_spawns_roadbase[cp].pop() @@ -520,17 +520,7 @@ class FlightGroupSpawner: group.points[0].type = "TakeOffGround" group.units[0].heading = ground_spawn[0].units[0].heading - if ( - cp.coalition.game.settings.ground_start_airbase_statics_farps_remove - and isinstance(cp, Airfield) - ): - # Remove invisible FARPs from airfields because they are unnecessary - neutral_country = self.mission.country( - cp.coalition.game.neutral_country.name - ) - neutral_country.remove_static_group(ground_spawn[0]) - group.points[0].link_unit = None - group.points[0].helipad_id = None + self._remove_invisible_farps_if_requested(cp, ground_spawn[0], group) # Hot start aircraft which require ground power to start, when ground power # trucks have been disabled for performance reasons @@ -591,22 +581,32 @@ class FlightGroupSpawner: ) group.units[1 + i].heading = ground_spawn[0].units[0].heading - if ( - cp.coalition.game.settings.ground_start_airbase_statics_farps_remove - and isinstance(cp, Airfield) - ): - # Remove invisible FARPs from airfields because they are unnecessary - neutral_country = self.mission.country( - cp.coalition.game.neutral_country.name - ) - neutral_country.remove_static_group(ground_spawn[0]) - + self._remove_invisible_farps_if_requested(cp, ground_spawn[0]) except IndexError as ex: raise NoParkingSlotError( f"Not enough STOL slots available at {cp}" ) from ex return group + def _remove_invisible_farps_if_requested( + self, + cp: ControlPoint, + ground_spawn: StaticGroup, + group: Optional[FlyingGroup[Any]] = None, + ) -> None: + if ( + cp.coalition.game.settings.ground_start_airbase_statics_farps_remove + and isinstance(cp, Airfield) + ): + # Remove invisible FARPs from airfields because they are unnecessary + neutral_country = self.mission.country( + cp.coalition.game.neutral_country.name + ) + neutral_country.remove_static_group(ground_spawn) + if group: + group.points[0].link_unit = None + group.points[0].helipad_id = None + def dcs_start_type(self) -> DcsStartType: if self.start_type is StartType.RUNWAY: return DcsStartType.Runway From f12a1350ceca468876d475fafe77c54007f697b1 Mon Sep 17 00:00:00 2001 From: Raffson Date: Sun, 28 Jul 2024 18:06:55 +0200 Subject: [PATCH 240/243] Fix exceptions thrown for Armed Recon --- game/pretense/pretenseaircraftgenerator.py | 1 + 1 file changed, 1 insertion(+) diff --git a/game/pretense/pretenseaircraftgenerator.py b/game/pretense/pretenseaircraftgenerator.py index 81d9e935..e297901f 100644 --- a/game/pretense/pretenseaircraftgenerator.py +++ b/game/pretense/pretenseaircraftgenerator.py @@ -962,6 +962,7 @@ class PretenseAircraftGenerator: elif ( flight.flight_type == FlightType.STRIKE or flight.flight_type == FlightType.BAI + or flight.flight_type == FlightType.ARMED_RECON ): for cp in control_points_to_scan: if cp.coalition == flight.coalition or cp == flight.departure: From dcc424d7e1b29396cddbab148478a8e0f335f250 Mon Sep 17 00:00:00 2001 From: Raffson Date: Sun, 28 Jul 2024 18:27:27 +0200 Subject: [PATCH 241/243] Update gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 197ba2a3..ccb1e474 100644 --- a/.gitignore +++ b/.gitignore @@ -22,5 +22,6 @@ env/ /logs/ /resources/logging.yaml +/resources/plugins/pretense/pretense_output.lua *.psd From 3d1a5bea6778a7975586885b5b841e824488232e Mon Sep 17 00:00:00 2001 From: Raffson Date: Sun, 28 Jul 2024 18:59:07 +0200 Subject: [PATCH 242/243] Adjust pre-pretense backup strategy --- game/persistency.py | 4 ++++ game/pretense/pretensemissiongenerator.py | 14 ++++++-------- qt_ui/windows/QLiberationWindow.py | 22 +++++++++++++++++++--- 3 files changed, 29 insertions(+), 11 deletions(-) diff --git a/game/persistency.py b/game/persistency.py index f196bf33..d32d2aa6 100644 --- a/game/persistency.py +++ b/game/persistency.py @@ -154,6 +154,10 @@ def save_dir() -> Path: return base_path() / "Retribution" / "Saves" +def pre_pretense_backups_dir() -> Path: + return save_dir() / "PrePretenseBackups" + + def server_port() -> int: global _server_port return _server_port diff --git a/game/pretense/pretensemissiongenerator.py b/game/pretense/pretensemissiongenerator.py index a3922154..fc427deb 100644 --- a/game/pretense/pretensemissiongenerator.py +++ b/game/pretense/pretensemissiongenerator.py @@ -1,6 +1,5 @@ from __future__ import annotations -import copy import logging import pickle from datetime import datetime @@ -20,7 +19,6 @@ from dcs.task import AFAC, FAC, SetInvisibleCommand, SetImmortalCommand, OrbitAc from game.lasercodes.lasercoderegistry import LaserCodeRegistry from game.missiongenerator.convoygenerator import ConvoyGenerator from game.missiongenerator.environmentgenerator import EnvironmentGenerator -from game.missiongenerator.flotgenerator import FlotGenerator from game.missiongenerator.forcedoptionsgenerator import ForcedOptionsGenerator from game.missiongenerator.frontlineconflictdescription import ( FrontLineConflictDescription, @@ -29,6 +27,7 @@ from game.missiongenerator.missiondata import MissionData, JtacInfo from game.missiongenerator.tgogenerator import TgoGenerator from game.missiongenerator.visualsgenerator import VisualsGenerator from game.naming import namegen +from game.persistency import pre_pretense_backups_dir from game.pretense.pretenseaircraftgenerator import PretenseAircraftGenerator from game.radio.radios import RadioRegistry from game.radio.tacan import TacanRegistry @@ -74,17 +73,16 @@ class PretenseMissionGenerator(MissionGenerator): self.mission.options.load_from_dict(options) def generate_miz(self, output: Path) -> UnitMap: - now = datetime.now() - date_time = now.strftime("%Y-%d-%mT%H_%M_%S") game_backup_pickle = pickle.dumps(self.game) + path = pre_pretense_backups_dir() + path.mkdir(parents=True, exist_ok=True) + path /= f".pre-pretense-backup.retribution" try: - with open( - self.game.savepath + ".pre-pretense-backup." + date_time, "wb" - ) as f: + with open(path, "wb") as f: pickle.dump(self.game, f) except: logging.error( - f"Unable to save Pretense pre-generation backup to {self.game.savepath}.pre-pretense-backup.{date_time}" + f"Unable to save Pretense pre-generation backup to {path}" ) if self.generation_started: diff --git a/qt_ui/windows/QLiberationWindow.py b/qt_ui/windows/QLiberationWindow.py index a4b8fbe1..dc8ac0f2 100644 --- a/qt_ui/windows/QLiberationWindow.py +++ b/qt_ui/windows/QLiberationWindow.py @@ -1,6 +1,7 @@ import logging import traceback import webbrowser +from datetime import datetime from pathlib import Path from typing import Optional @@ -21,6 +22,7 @@ from game import Game, VERSION, persistency, Migrator from game.debriefing import Debriefing from game.game import TurnState from game.layout import LAYOUTS +from game.persistency import pre_pretense_backups_dir from game.pretense.pretensemissiongenerator import PretenseMissionGenerator from game.server import EventStream, GameContext from game.server.dependencies import QtCallbacks, QtContext @@ -322,9 +324,23 @@ class QLiberationWindow(QMainWindow): def newPretenseCampaign(self): output = persistency.mission_path_for("pretense_campaign.miz") - PretenseMissionGenerator( - self.game, self.game.conditions.start_time - ).generate_miz(output) + try: + PretenseMissionGenerator( + self.game, self.game.conditions.start_time + ).generate_miz(output) + except Exception as e: + now = datetime.now() + date_time = now.strftime("%Y-%d-%mT%H_%M_%S") + path = pre_pretense_backups_dir() + path.mkdir(parents=True, exist_ok=True) + tgt = path / f"pre-pretense-backup_{date_time}.retribution" + path /= f".pre-pretense-backup.retribution" + if path.exists(): + with open(path, "rb") as source: + with open(tgt, "wb") as target: + target.write(source.read()) + raise e + title = "Pretense campaign generated" msg = f"A Pretense campaign mission has been successfully generated in {output}" QMessageBox.information(QApplication.focusWidget(), title, msg, QMessageBox.Ok) From 242ef3046991b13d7dbd62fc731a5c18d95e716d Mon Sep 17 00:00:00 2001 From: Raffson Date: Sun, 28 Jul 2024 19:09:59 +0200 Subject: [PATCH 243/243] Formatting -_- --- game/pretense/pretensemissiongenerator.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/game/pretense/pretensemissiongenerator.py b/game/pretense/pretensemissiongenerator.py index fc427deb..8cc693fc 100644 --- a/game/pretense/pretensemissiongenerator.py +++ b/game/pretense/pretensemissiongenerator.py @@ -81,9 +81,7 @@ class PretenseMissionGenerator(MissionGenerator): with open(path, "wb") as f: pickle.dump(self.game, f) except: - logging.error( - f"Unable to save Pretense pre-generation backup to {path}" - ) + logging.error(f"Unable to save Pretense pre-generation backup to {path}") if self.generation_started: raise RuntimeError(