----------------------------------------------------------------------------------------------------------------------------------------------------------- -- __ __ ___ _ __ __ _____ ______ _____ -- / //_/ / | _____ _____ (_)_____ / /_ ____ _ ____ / /_ / ___//_ __// ___/ -- / ,< ______ / /| | / ___// ___// // ___// __// __ `// __ \ / __/ \__ \ / / \__ \ -- / /| |/_____// ___ | (__ )(__ )/ /(__ )/ /_ / /_/ // / / // /_ ___/ / / / ___/ / --/_/_|_|_ _ /_/ |_|/____//____//_//____/_\__/_\__,_//_/ /_/ \__/ /____/ /_/ /____/ __ _____ _ __ -- / ___/ (_)____ ___ ____ / /___ /_ __/_____ ____ _ ____ _____ ____ ____ _____ / /_ / ___/ _____ _____ (_)____ / /_ -- \__ \ / // __ `__ \ / __ \ / // _ \ / / / ___// __ `// __ \ / ___// __ \ / __ \ / ___// __/ \__ \ / ___// ___// // __ \ / __/ -- ___/ // // / / / / // /_/ // // __/ / / / / / /_/ // / / /(__ )/ /_/ // /_/ // / / /_ ___/ // /__ / / / // /_/ // /_ --/____//_//_/ /_/ /_// .___//_/ \___/ /_/ /_/ \__,_//_/ /_//____// .___/ \____//_/ \__/ /____/ \___//_/ /_// .___/ \__/ -- /_/ /_/ /_/ ----------------------------------------------------------------------------------------------------------------------------------------------------------- -- v0.9.9 beta ------------------------------------------------------------------------------- --------------------------------- DEV NOTES --------------------------------- ------------------------------------------------------------------------------- -- FIXES : -- -- UPGRADES : -- DONE - NEED TEST add option to disable status reports of the ARTY groups send to all coalition members. -- DONE - NEED TEST use tag to assign WPZs and PTZs to a colaition -- DONE - NEED TEST coalition tags for FLG zones modified - B_, R_, N_ are now @BLUE, @RED and @NEU -- DONE - NEED TEST Selective routing for cargo groups for CargoRouter function, cargos must consider WPZ and PTZ that belongs to theme coalition or all coalitions. -- DONE UPDATE MANUAL ITA - EN -- -- TODO FLAG ZONES VENGONO MARCATE IN MAPPA SE NON TAGGATE CON COALIZIONE - NON DOVREBBERO -- TODO MANAGE QUAD ZONES - THEY GENERATE ERROR -- -- FUTURE FEATURES : -- TODO - Crates deployment - deploy crates to give access to supplies (ammo and fuel) could spawn an invisible farp to give access to ammo and fuel for Helos and grounds ----------------------------------------------------------------------------------------------------------------------------------------------------------- -- __ __ __ ___ __ -- / / / /_____ ___ _____ / |/ /____ _ ____ __ __ ____ _ / / -- / / / // ___// _ \ / ___/ / /|_/ // __ `// __ \ / / / // __ `// / --/ /_/ /(__ )/ __// / / / / // /_/ // / / // /_/ // /_/ // / --\____//____/ \___//_/ /_/ /_/ \__,_//_/ /_/ \__,_/ \__,_//_/ -- ----------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------- ----------------------------- WHAT K-ASS-STS IS? ------------------------------ ------------------------------------------------------------------------------- -- K-Assistant-Simple Transport Script aims to Assist Mission Designers in the implementation of a Simple, Flexible and Realistic cargo management system based on MOOSE Framework, -- that allow players to transport groups and statics into their helicopters or ground vehicles. -- -- Simple - because it can be easily integrated into your missions, moreover it is also simple to use in game as the menus and functions available for the players are few and easy to use. -- -- Flessible - because it tries to give way to mission designers to build their missions without limiting too much imagination by regulating every action. -- Potentially with this script you can also load on your helicopter static objects not defined as "cargos" in DCS, for example a building or a cow... -- It is up to the mission designer to decide what should be done and what should not be done. -- -- Realistic - although the script leaves the mission designers free to use their imagination, at the same time it binds them to a realistic approach as, -- thanks to the API released in 2020 by Eagle Dynamics, for each object loaded into your helicopter it will be simulated the addition of the mass relative to the cargo itself. -- Pilots will now need to deal with cargo mass! -- -- To increase realism and immersion in the game, K-ASS-STS provides a dynamic interaction system that allows, through the use of the F10 map markers, to issue commands to the transported ground troops. -- Forget the times when troop transport was a boring thing, now you can send your troops to assault, organize patrols and request mortar attacks! -- ------------------------------------------------------------------------------- ------------------------------------ SETUP ------------------------------------ ------------------------------------------------------------------------------- -- --The MOOSE Framework MUST be loaded in your mission for the K-ASS-STS to work. --(MOOSE version 2.5.3 or higher required) -- -- 1) In ME create a trigger as follows : TYPE: MISSION START -> CONDITION : NONE -> ACTIONS : DO SCRIPT FILE (select MOOSE.lua) -- 2) In ME create a trigger as follows : TYPE: MISSION START -> CONDITION : NONE -> ACTIONS : DO SCRIPT FILE (select K-Assistant_STS.lua) -- -- That's all! -- ------------------------------------------------------------------------------- ------------------------------ SCRIPT SETTINGS -------------------------------- ------------------------------------------------------------------------------- -- The script can be configured to better meet the needs of the Mission Designer. -- The configurations must be made by modifying the parameters contained in the sts_Settings table that you can find immediately after this section of instructions. -- See sts_Settings table for more informations. -- ------------------------------------------------------------------------------- ------------------- TAGS - CARRIERS and LOADABLE OBJECTS ---------------------- ------------------------------------------------------------------------------- -- TAGS : are needed to identify elemensts within the script, must be in groups, statics and zones names defined in ME (Mission Editor) -- E.g. -- sts_Settings.CarrierTAG = "@TR" identify carriers groups that will transport cargos, all groups whose name contains "@TR" will be considered carriers. -- sts_Settings.CargoGroupsTAG = "@GR" identify groups that can be boarded to carriers so, all groups whose name contains "@GR" will be considered CARGO_GROUPs. -- sts_Settings.CargoCrateTAG = "@CR" identify static objects that can be loaded to carriers as CRATES, all static objects whose name contains "@CR" will be considered CARGO_CRATEs. -- -- TAGs can identify CARGO_GROUPS, CARGO_CRATES, CARRIERS, WAREHOUSES and ZONES. -- -- CARRIERS - are those groups that will transport the cargos in game. -- For example, your helicopter or an APC that you will control via Combined Arms. -- You must define carriers By putting the TAG that identifies Carriers ("@TR" by default) in the name of the group defined by Mission Editor. -- Carriers will have access to the in game radio menus for cargo management under F10-Other. -- -- ATTENTION: CARRIERS MUST BE GROUPS COMPOSED OF A SINGLE UNIT! -- -- LOADABLE OBJECTS : -- -- CARGO_GROUPS - By putting the TAG that identifies the loadable Groups ("@GR" by default) in the name of the group defined by Mission Editor, this will then be boardable in game. -- You simply have to land your helicopter near the group and use the radio menus under F10 - Other to board them. -- -- CARGO_CRATES - By putting the TAG that identifies the loadable static as Crates ("@CR" by default) in the name of the group defined by Mission Editor, this will then be loadable in game. -- You simply have to land your helicopter near the crate and use the radio menus under F10 - Other to load it. -- -- BE AWARE : not all static objects have a defined mass! -- For static objects belonging to the "Cargos" category, the mass set in the Mission Editor will be considered. -- Static objects belonging to other categories (such as our beloved cows) do not have a mass that can be set in the Mission Editor, -- for these objects a mass equal to the value defined in sts_Settings.DefaultStaticCargoMass (default is 666 kilograms) will always be considered. -- -- CARGO VALIDATION if sts_Settings.ValidateCargo is true the cargo will be validated. -- If the loaded weight exceeds the maximum load capacity of the carrier the cargo will not be loadable. -- Maximum load capacity is defined in sts_Settings.CargoBayLimits table. -- -- -- -- ZONES and WAREHOUSES -- Please see dedicated sections below ------------------------------------------------------------------------------- ------------------------- CARRIER ADDITIONAL SETTINGS ------------------------- ------------------------------------------------------------------------------- --To allow greater adaptability of the script to the needs of the mission designer, it is possible to determine the maximum loadable mass for each specific carrier. --To do this, simply enter in the name of the unit (not of the group) the numerical value corresponding to the desired maximum loadable mass enclosed in brackets. -- --E.g. Mi8-2 (4000) --This unit will have a maximum loadable mass of 4000Kg -- --If not specified, the default values ​​specified in the sts_Settings.CargoBayLimits table will be applied based on the type of carrier. -- ------------------------------------------------------------------------------- ----------------------------------- ZONES ------------------------------------- ------------------------------------------------------------------------------- -- The script involves the use of Zones (trigger zones defined in mission editor) to allow players to access some useful functions. -- Also in this case, to identify the various types of zone, the script uses TAGs. -- -- WAY POINT ZONES (WPZones) - WPZones are zones that can be defined by Mission Editor by inserting the TAG sts_settings.WPZoneTAG ("WPZ" by default) in the zone's name. -- When a CARGO_GROUP is unboarded inside a WPZone, after about 40 seconds it will head towards the center of the zone. -- (Ideal for an assault) -- -- PATROL ZONES (PTZones) - PTZones are zones that can be defined by Mission Editor by inserting the TAG sts_settings.PTZoneTAG ("PTZ" by default) in the zone's name. -- When a CARGO_GROUP is unboarded inside a PTZones, after about 40 seconds it will start patrolling endlessly and randomly the zone. -- (Ideal for defending an area) -- -- -- -- PATROL ZONES INSIDE WAY POINT ZONES - What if a PT Zone is inside WP Zone? -- After troops being unboarded inside WP Zone, they will head to the center of WP Zone itself. -- Once they'll reach the center of WP Zone they will search for PT Zones, if they find one or more, they will start patrolling theme. -- -- -- WAY POINT ZONE AND PATROL ZONES IN RELATION TO COALITIONS - Is it possible to assing a WPZ or a PTZ to a specific coalition? -- Yes it is. -- Since in DCS Mission Editor you can't assign a trigger zone to a specific coalition, you'll need to add the coalition TAG to -- specify to which coalition it belongs (default coalition TAGs are: @BLUE, @RED, @NEU). -- -- Zones that have no coalition TAG are visible and usable by all coalitions. -- Only one coalition TAG per zone is supported. -- -- Some Examples: -- __ -- trigger zone name: “WPZ Alpha” - will create a WPZ visible and usable by all coalitions. -- trigger zone name: “@BLUEPTZ Gamma” - will create a PTZ visible and usable by Blue coalition only. -- trigger zone name: “@REDPTZ 168” - will create a PTZ visible and usable by Red coalition only. -- trigger zone name: “@NEUWPZ Orione 1” - will create a WPZ visible and usable by Neutral coalition only. -- trigger zone name: “@BLUE@REDPTZ” - ERROR could generate unexpected behavior. -- -- -- FLAG ZONES (FLGZones) - FLAG ZONES are zones that can be defined by Mission Editor allowing you to interact with DCS Flags, -- so you can activate triggers due the presence of cargo groups or cargo crates inside a Flag Zone. -- You can define a zone by inserting the TAG "FLG" in the zone's name. -- In addition to the TAG, you MUST also enter an identifier (a TAG) in the ZONE name, this is needed to specify wich coalition the zone belogs to. -- (default coalition TAGs are: @RED, @BLUE, @NEU) -- You will also need to define wich DCS Flag will be affected by the zone, you need to spicify Flag number enclosed within brakets. -- -- Example : -- In ME create a Trigger zone and name it as follows: @BLUEFLG (100) -- This trigger zone will increment the Flag 100 for each Blue Cargo Group or Cargo Crate that goes inside the zone. -- E.g. : -- No cargos inside @BLUEFLG (100) zone , Flag 100 = FALSE -- 3 RED cargos inside @BLUEFLG (100) zone , Flag 100 = FALSE -- 1 BLUE cargos inside @BLUEFLG (100) zone , Flag 100 = 1 -- 2 BLUE cargos inside @BLUEFLG (100) zone , Flag 100 = 2 -- -- Zone Naming Examples: -- @BLUEFLG (100) = flag 100 will be affected by BLUE cargos in zone -- @REDFLG (200) = flag 200 will be affected by RED cargos in zone -- @NEUFLG (300) = flag 300 will be affected by NEUTRAL cargos in zone -- -- NOTE : if sts_Settings.ZonesMarkOnMap is true, zones boudaries will be marked on F10 map. -- -- -- SMOKE ZONES (SMKZones) - SMKZones are zones that can be defined by Mission Editor by inserting the TAG sts_settings.SMKZoneTAG ("SMK" by default) in the zone's name. -- When a Smoke Zone is defined the center of it will be smoked endlessly after mission start. -- You can specify smoke color this way : SMK (R) for red - SMK (W) for white and so on... Available colors Red-White-Orange-Green-Blue -- Blue is default coloro if color not specified. -- ------------------------------------------------------------------------------- --------------------------------- WAREHOUSES ---------------------------------- ------------------------------------------------------------------------------- -- Warehouses are UNITS defined in mission editors that allow you to generate cargo groups not currently alive in game and load theme as cargos. -- When entering the radius of a Warehouse (150m from the center of the unit defined as warehouse) -- in F10 - Other, the dedicated radio menu of the warehouse will be added for the carrier group, within warehouse menu it will be possible to choose wich group to load. -- -- Warehouses allow greater dynamism and also allow you to load troops from ships. -- Normally, given the limitations of DCS, it is not be possible to place groups on a ship's deck but you can define the ship as a warehouse! -- -- What do I need to setup a warehouse? -- You need 2 things : define a unit as warehouse and define templates for cargo groups that will be available in warehouses -- -- 1) Define a warehouse: -- By putting the sts_Settings.WarehouseTAG ("@WH" by default) in the UNIT's name enclosed in brackets you will designate that unit as warehouse. -- ATTENTION: you need to put the TAG in UNIT name, NOT in GROUP name AND IT MUST BE enclosed in brackets!! -- E.g. : Tarawa (@WH) -- -- 2) Define group templates that can be generated from warehouses: -- Create the template groups in mission editor and set them in LATE ACTIVATION, no matter where you place them, they are just templates. -- By putting the sts_Settings.WarehouseTAG ("@WH" by default) in the GROUP's name you will identify it as warehouse group template. -- ATTENTION: For group templates you need to put the TAG in GROUP name, NOT in UNIT name!! -- ------------------------------------------------------------------------------- ---------------------------- IN GAME ZONE CREATION ---------------------------- ------------------------------------------------------------------------------- -- In order to allow greater dynamism in the mission, the Zones described above can also be created directly in the game by using F10 Map Markers. -- Zones created using F10 map markers will be automatically assigned to the coalition of the player who created the marker and will not be visible or usable by other coalitions. -- -- WAY POINT ZONES (WPZones) - Add a Marker in F10 Map, edit the marker text by inserting the following pattern: "#ZoneTAG ... (RadiusInMeters) ..." -- Click outside the text field to apply, the zone will be created. -- -- PATROL ZONES (PTZones) - Add a Marker in F10 Map, edit the marker text by inserting the following pattern: "#ZoneTAG ... (RadiusInMeters) ..." -- Click outside the text field to apply, the zone will be created. -- -- Some examples: -- WPZone Mark Text : #WPZ Sochi (3000) -- will create a 3000 meter diameter WPZone at the marker position. -- PTZone Mark Text : #PTZ Defend-1 (1740) -- will create a 1750 meter diameter PTZone at the marker position. -- -- NOTE : if sts_Settings.ZonesMarkOnMap is true, zones boudaries will be marked on F10 map. -- ------------------------------------------------------------------------------- ----------------------------- SMOKES VIA MARKER ------------------------------- ------------------------------------------------------------------------------- -- SMOKE via markers - Add a Marker in F10 Map, edit the marker text by inserting the following pattern: "#SMK" -- Click outside the text field to apply, a big smoke will be create at marker position. -- You can specify smoke color this way : #SMK (R) for red - #SMK (W) for white and so on... Available colors Red-White-Orange-Green-Blue -- Blue is default if color not specified. -- -- examples: -- #SMK LZ-1 -- will create a blue smoke that will last for 5 minutes at the marker position. -- #SMK LZ-2 (R) -- will create a red smoke that will last for 5 minutes at the marker position. -- -- NOTE : if sts_Settings.SmokeFromMarker is false Smoke via Markers will not be available. -- ------------------------------------------------------------------------------- --------------------------------- ARTILLERY ----------------------------------- ------------------------------------------------------------------------------- -- K-ASS-STS integrates MOOSE Functional.Arty class developed by funkyfranky -- Thanks to this class we can request artillery attacks from mortars cargo groups. -- Yes, i mean that you can transport mortars into the battle field as CARGO_GROUPS and then request them to attack a point via F10 Map Markers! -- -- ADVICE: it is advisable to create groups of mortars consisting of a single mortar unit. -- Groups composed of several mortars or mixed-type units can give rise to unexpected or unsightly behaviors. -- -- REQUEST ATTACK - Add a Marker in F10 Map at the target location, edit the marker text by inserting the following pattern: "#ART ... (Number of Shots) ..." -- Click outside the text field to apply, all mortars that are in range will start firing at marker location. -- -- REQUEST ILLUMINATION - Add a Marker in F10 Map at the target location, edit the marker text by inserting the following pattern: "#ART ILLU (Number of Shots) ..." -- Click outside the text field to apply, all mortars that are in range will start firing illumination shells at marker location. -- -- REQUEST SMOKE - Add a Marker in F10 Map at the target location, edit the marker text by inserting the following pattern: "#ART SMOKE (Number of Shots) ..." -- Click outside the text field to apply, all mortars that are in range will start firing smoke shells at marker location. -- -- Some examples: -- Request 1 normal round shot - Mark text : "#ART (1)" -- all mortars in range will shoot 1 round at the marker location. -- Request 5 illumination round shots- Mark text : "#ART ILLU (5)" -- all mortars in range will shoot 5 illumination rounds at the marker location. -- Request 5 normal round shots- Mark text : "#ART" -- all mortars in range will shoot 5 normal rounds at the marker location. -- 5 is default for normal rounds so no need to specify number of shots -- NOTE: If no (Number of Shots) is specified in marker text default values will be applied: 5 shots for normal attack, 2 shots for illuminating, 1 for smoke. -- -- AMMO AVAILABLE and AMMO REFILL - By default, each mortar have 20 normal rounds 20 red smoke and 20 illumination rounds. Ammo can be refilled by boarding the group in a carrer. -- -- For advanced assignment via F10 Markers you can see Functional.Arty documentation at : -- https://flightcontrol-master.github.io/MOOSE_DOCS/Documentation/Functional.Arty.html -- -- ************************************************************************ -- ***************************** SETTINGS ******************************* -- ************************************************************************ sts_Settings = { ------------------------------------------------------------ GENERAL SETTINGS AND DEBUG ------------------------------------------------------------ Debug = false, -- Enable debug messages during mission DebugTrace = false, -- Enable debug tracing on dcs.log WelcomeMessage = true, RefreshTime = 20, -- Set refresh time in seconds (20 seconds default) PlayerMenu = false, -- if false disables MOOSE default player menu. Set it to true if you need it for some other MOOSE script. SmokeFromMarker = true, -- if true, creation og smoke markers via F10 map marker is enabled Warehouses = true, -- enable warehouses Artillery = true, -- if true enable functions to require cargo groups mortars fire with F10 map markers ArtilleryReports = true, -- if false disable status ARTY groups reports. MapShapes = true, -- if true will use DCS 2.7 shapes to mark zones on map, otherwise will use markers. ------------------------------------------------------------------ CARGO SETTINGS ------------------------------------------------------------------ LoadRadius = 150, -- The radius till Cargo can be loaded into the carrier (150 default) NearRadius = 25, -- The radius in meters when the cargo will board the Carrier (25m have better performance for routing of booardable groups) WarehouseLoadRadius = 150, -- The radius till Cargo can be loaded into the carrier from Warehouses (150 default) TimeToForceLoad = 120, -- if cargo not boarded within this time limit, cargo will be force boarded into carrier (if it is on ground) regarless distance RespawnOnDestroyed = true, -- if true, destroyed cargos will be respawned at the initial position. ValidateCargo = true, -- If true, will validate cargo before boarding if too heavy it will be unloaded MassOverload = 0, -- Defines the helicopter cargo overload limit in Kg - use it to add cargo capacity to carriers if you need it (applies to all carriers) DefaultStaticCargoMass = 666, -- Defines the default cargo mass for static objects that are massless (all statics exept thoose in "Cargo" category, yes, cows also...) CargoBayLimits = { -- Table where unit type cargo limits are defined as values in Kg -- keys are strings as returned by UNIT:GetTypeName() ["UH-1H"] = 1100, -- 12 seats for passengers ["Mi-8MT"] = 2300, -- 25 seats for passengers ["Ka-50"] = 1, -- no seats for passengers ["SA342L"] = 90, -- 1 seat for passengers ["SA342M"] = 90, ["SA342Minigun"] = 1, ["SA342Mistral"] = 180, -- 2 seats for passengers ["Mi-24P"] = 800 -- 8 seats for passengers + 80kg free }, ----------------------------------------------------- CARRIERS AND LOADABLE SETTNGS AND TAGS ------------------------------------------------------- CargoGroupsTAG = "@GR", -- Define TAG needed to identify CARGO_GROUPs - it must be in the group name defined in ME CargoCrateTAG = "@CR", -- Define TAG needed to identify CARGO_CRATEs - it must be in the group name defined in ME CarrierTAG = "@TR", -- Define TAG needed to identify Carrier Groups - it must be in the group name defined in ME WarehouseTAG = "@WH", -- Define TAG needed to identify Warehouse units - it must be in the group name defined in ME ------------------------------------------------------------- ZONES SETTNGS AND TAGS ---------------------------------------------------------------- BlueTAG = "@BLUE", -- TAG needed to assign coalitionless elements (like zones) to a specific coalition. RedTAG = "@RED", -- TAG needed to assign coalitionless elements (like zones) to a specific coalition. NeutralTAG = "@NEU", -- TAG needed to assign coalitionless elements (like zones) to a specific coalition. WPZoneTAG = "WPZ", -- Define TAG needed to identify WP Zones - it must be in the zone name defined in ME or in F10 Mark on Map (#WPZ) PTZoneTAG = "PTZ", -- Define TAG needed to identify PT Zones - it must be in the zone name defined in ME or in F10 Mark on Map (#PTZ) SMKZoneTAG = "SMK", -- Define TAG needed to identify SMK Zones - it must be in the zone name defined in ME or in F10 Mark on Map (#SMK) FLGZoneTAG = "FLG", -- Define TAG needed to identify FLG Zones - it must be in the zone name defined in ME (you need to identify coalition E.g. @BLUEFLG for blue, @REDFLG for red, @NEUFLG for neutrals) ZonesMarkOnMap = true, -- If true, WP zones, PT zones and FLG zones defined in ME will be marked on F10 map. } --sts_Settings END -- ************************************************************************ -- ******************* DATA COLLECTION AND UTILITY ********************* -- ************************************************************************ -- Disables MOOSE Player settings menu if sts_Settings.PlayerMenu == false then _SETTINGS:SetPlayerMenuOff() end -- LOG TRACING if sts_Settings.DebugTrace then BASE:TraceAll(true) -- BASE:TraceOn() -- BASE:TraceClass("ARTY") -- BASE:TraceClass("CARGO") end -- debug --Carrier set - this groups will transport things stsCarrierSets = { BLUE = SET_GROUP:New():FilterCoalitions("blue"):FilterActive():FilterPrefixes(sts_Settings.CarrierTAG):FilterStart(), RED = SET_GROUP:New():FilterCoalitions("red"):FilterActive():FilterPrefixes(sts_Settings.CarrierTAG):FilterStart(), NEUTRAL = SET_GROUP:New():FilterCoalitions("neutral"):FilterActive():FilterPrefixes(sts_Settings.CarrierTAG):FilterStart() } --SetS of zones used by the script stsZoneSets = { TGTZONES = SET_ZONE:New():FilterPrefixes(sts_Settings.WPZoneTAG):FilterPrefixes(sts_Settings.PTZoneTAG):FilterOnce(), -- This zones will be the route target for cargo groups that are unloaded in this zones SMKZONES = SET_ZONE:New():FilterPrefixes(sts_Settings.SMKZoneTAG):FilterOnce(), BLUE = { FLGZONES = SET_ZONE:New():FilterPrefixes(sts_Settings.BlueTAG..sts_Settings.FLGZoneTAG):FilterOnce() }, RED = { FLGZONES = SET_ZONE:New():FilterPrefixes(sts_Settings.RedTAG..sts_Settings.FLGZoneTAG):FilterOnce() }, NEUTRAL = { FLGZONES = SET_ZONE:New():FilterPrefixes(sts_Settings.NeutralTAG..sts_Settings.FLGZoneTAG):FilterOnce() } } --Set of transportable groups and statics stsTransportableSets = { BLUE = { Groups = SET_GROUP:New():FilterCoalitions("blue"):FilterActive(false):FilterCategoryGround():FilterPrefixes(sts_Settings.CargoGroupsTAG):FilterOnce() , Crates = SET_STATIC:New():FilterCoalitions("blue"):FilterPrefixes(sts_Settings.CargoCrateTAG):FilterOnce() }, RED = { Groups = SET_GROUP:New():FilterCoalitions("red"):FilterActive(false):FilterCategoryGround():FilterPrefixes(sts_Settings.CargoGroupsTAG):FilterOnce() , Crates = SET_STATIC:New():FilterCoalitions("red"):FilterPrefixes(sts_Settings.CargoCrateTAG):FilterOnce() }, NEUTRAL = { Groups = SET_GROUP:New():FilterCoalitions("neutral"):FilterActive(false):FilterCategoryGround():FilterPrefixes(sts_Settings.CargoGroupsTAG):FilterOnce() , Crates = SET_STATIC:New():FilterCoalitions("neutral"):FilterPrefixes(sts_Settings.CargoCrateTAG):FilterOnce() } } -- Set of units that will be warehouses in game if sts_Settings.Warehouses == true then stsWarehouseSet = { BLUE = SET_UNIT:New():FilterCoalitions("blue"):FilterPrefixes("%("..sts_Settings.WarehouseTAG.."%)"):FilterOnce(), RED = SET_UNIT:New():FilterCoalitions("red"):FilterPrefixes("%("..sts_Settings.WarehouseTAG.."%)"):FilterOnce(), NEUTRAL = SET_UNIT:New():FilterCoalitions("neutral"):FilterPrefixes("%("..sts_Settings.WarehouseTAG.."%)"):FilterOnce() } -- Set where templates spawnable from warehouses are defined stsWarehouseTemplatesSet = { BLUE = SET_GROUP:New():FilterCoalitions("blue"):FilterPrefixes(sts_Settings.WarehouseTAG):FilterOnce(), RED = SET_GROUP:New():FilterCoalitions("red"):FilterPrefixes(sts_Settings.WarehouseTAG):FilterOnce(), NEUTRAL = SET_GROUP:New():FilterCoalitions("neutral"):FilterPrefixes(sts_Settings.WarehouseTAG):FilterOnce() } end -- Table where CARGO_GROUPS created will be stored stsCargoGroups = { BLUE = {}, RED = {}, NEUTRAL = {} } -- Table where CARGO_CRATES created will be stored stsCargoCrates = { BLUE = {}, RED = {}, NEUTRAL = {} } -- Table where Flag Zones created will be stored stsFlagZones = { BLUE = stsZoneSets.BLUE.FLGZONES:GetSet(), RED = stsZoneSets.RED.FLGZONES:GetSet(), NEUTRAL = stsZoneSets.NEUTRAL.FLGZONES:GetSet() } -- Table where UserFlags will be stored stsUserFlags = {} -- Table where Warehouse Zones created will be stored stsWarehouseZones = { BLUE = {}, RED = {}, NEUTRAL = {} } -- Table where groups spawnable by a warehouse are stored stsWarehouseTemplates = { BLUE = stsWarehouseTemplatesSet.BLUE:GetSet(), RED = stsWarehouseTemplatesSet.RED:GetSet(), NEUTRAL = stsWarehouseTemplatesSet.NEUTRAL:GetSet() } -- Table where to store menus data avalaible via F10 - Other radio items stsMenu = {} -- Table where to store mass data for carriers - carrier mass will be added/removed when cargo is loaded/unloaded stsUnitMass = {} -- Table where to store Zones created via mark points in F10 map stsZonesFromMark = {} --Table where to store auto generated markers IDs so they can be removed in a second time stsServiceMarkers = {} -- Table where to store ARTY objects for mortars cargo groups stsMortars = {} stsSmokeColors = { B = SMOKECOLOR.Blue, R = SMOKECOLOR.Red, G = SMOKECOLOR.Green, O = SMOKECOLOR.Orange, W = SMOKECOLOR.White } function spairs(t, order) -- collect the keys local keys = {} for k in pairs(t) do keys[#keys+1] = k end -- if order function given, sort by it by passing the table and keys a, b, -- otherwise just sort the keys if order then table.sort(keys, function(a,b) return order(t, a, b) end) else table.sort(keys) end -- return the iterator function local i = 0 return function() i = i + 1 if keys[i] then return keys[i], t[keys[i]] end end end -- Returns a cargo and carrier's mass info message function CarrierMassInfo(carrierGroup) local carrierUnit = carrierGroup:GetFirstUnitAlive() local carrierUnitName = carrierUnit:GetName() local maxLoadableMass = stsUnitMass[carrierUnitName].maxMass local loadedMass = stsUnitMass[carrierUnitName].cargoMass local loadableMass = maxLoadableMass - loadedMass carrierGroup:MessageToGroup("\n".."Mass loaded in cargo bay: "..loadedMass.." Kg".."\n".."Remaining loadable mass: "..loadableMass.." Kg".."\n".."Maximum loadable mass: "..maxLoadableMass.." Kg", 15, carrierGroup, "- CARRIER MASS INFO") end -- Returns a message for each cargo in cargo bay function CargoManifestInfo(carrierGroup) local carrierUnit = carrierGroup:GetFirstUnitAlive() local carrierUnitName = carrierUnit:GetName() local cargoList = carrierUnit:GetCargo() carrierGroup:MessageToGroup("\n", 16, carrierGroup, "- CARGO MANIFEST") for k, cargo in pairs(cargoList) do local cargoName = cargo:GetName() carrierGroup:MessageToGroup("Cargo Mass : ".. stsUnitMass[carrierUnitName][cargoName].." Kg", 15, carrierGroup, string.gsub( cargoName , "@..", "")) end end -- Returns a message with a quick start guide for players function QuickStartInfo(params) local carrierGroup = params[1] local topic = params[2] if topic == 1 then carrierGroup:MessageToGroup([[Quick Start Guide : General Use Radio Menus under F10 - Other -> K-STS to interact with script funcions. Land within 150 meters from a Cargo Group or a Crate to board them via radio menu. If present in mission, Warehouses can generate infinite boardable groups. Land within 150 meters from a Warehouse, a dedicated radio menu for the warehouse will be available then. Boarded Groups and Crates can be unboarded using the - unboard groups/crates - menus. You can board and unboard troops while in hovering - speed must be less than 10 Km/h and altitude less than 25 meters from ground. ]] , 35, carrierGroup, "K-STS") elseif topic == 2 then carrierGroup:MessageToGroup([[Quick Start Guide : Routing Zones Routing Zones, as Way Point Zones and Patrol Zones, allows you to route cargo groups after unboarding. These can be created either via mission editor or by using markers in the F10 map. If a zone is created via ME it will be present since mission start. Zones are visible on the F10 map, they are highlighted by auto generated markers that delimit the boundary. ---------------------------------------------------------------------------------------------------------------- To create zones via F10 Map Markers: ---------------------------------------------------------------------------------------------------------------- WAY POINT ZONE : Create a marker on F10 map, insert #WPZ tag followed by desired radius in meters enclosed within brakets into mark text. Troops unboarded into the Way Point Zone will route to the center of it after being unboarded. Mark text Example: #WPZ (3000) or #WPZ Alpha (2000) or #WPZ Your Text (4780) ---------------------------------------------------------------------------------------------------------------- PATROL ZONE : Create a marker on F10 map, insert #PTZ tag followed by desired radius in meters enclosed within brakets into mark text. Troops unboarded into the Patrol Zone will patrol it randomly and endlessly after being unboarded. Mark text Example: #PTZ (700) or #PTZ Bravo (1000) or #PTZ Your Text (978) -------------------------------------------------------------------------------------------------------------- NOTE 1: if you put a PTZ inside a WPZ troops will reach the center of WPZ and then will start patrolling the PTZ. NOTE 2: mission designer can disable zones boundary highlight on F10 map via script settings - if so, you'll need to follow mission briefing. ]] , 40, carrierGroup, "K-STS") elseif topic == 3 then carrierGroup:MessageToGroup([[Quick Start Guide : Flag Zones FLAG ZONES are zones that can be defined only in Mission Editor allowing you to interact with DCS Flags. Mission triggers can be activated due the presence of cargos (crates and/or groups) inside a Flag Zone. Zones are visible on the F10 map, they are highlighted by auto generated markers that delimit the boundary. ---------------------------------------------------------------------------------------------------------------- Check your mission briefing to see if mission designer has modeled some tasks for you! ---------------------------------------------------------------------------------------------------------------- NOTE : mission designer can disable zones boundary highlight on F10 map via script settings - if so, you'll need to follow mission briefing. ]] , 50, carrierGroup, "K-STS") elseif topic == 4 then carrierGroup:MessageToGroup([[Quick Start Guide : Smokes Create a marker on F10 map, insert #SMK tag followed by the desired smoke color enclosed within brakets into mark text A big smoke will be generated at mark location and will last for 5 minutes. Available Colors : B = Blue, R = Red, G = Green, O = Orange, W = White If no color is specified, blue is default. ---------------------------------------------------------------------------------------------------------------- Mark text Example: #SMK - create blue smoke on marker #SMK LZ Delta (R) - create a red smoke on marker ("LZ Delta" is just an additional description) #SMK (W) - create a white smoke on marker ---------------------------------------------------------------------------------------------------------------- NOTE : mission designer can disable smoke markers via script settings. ]] , 50, carrierGroup, "K-STS") elseif topic == 5 then carrierGroup:MessageToGroup([[Quick Start Guide : Artillery - Mortars If mortars as cargo groups are present in mission you can direct their fire through the F10 map Markers. Create a marker on F10 map, insert #ART tag followed by desired type and number of shots enclosed within brakets into mark text. Mortars in range will start shooting at marker position - they have 20 normal shots and 30 illuminating shots - you'll need to board theme to reload ammo or an ammo truck (e.g. M818 o Ural-375) must be nearby for ammo refill. ---------------------------------------------------------------------------------------------------------------- Mark text Examples: #ART - each mortar in range will shot 5 normal rouds at marker position (5 default if number not specified) #ART (10) - each mortar in range will shot 10 normal rounds at marker position. #ART ILLU - each mortar in range will shot 2 illumination rounds at marker position (2 default if number not specified). #ART ILLU (5) - each mortar in range will shot 5 illumination rounds at marker position. ---------------------------------------------------------------------------------------------------------------- NOTE : mission designer can disable artilley reports via text message, in this case you'll need to monitor mortars activity directly. ]] , 50, carrierGroup, "K-STS") end end -- Search a unit type string on each unit of a group -- group object -- unitType string function SearchUnitTypeInGroup(group, unitType) local unitList = group:GetUnits() local unitTypeFounded = false -- MESSAGE:New("UNITLIST = "..tonumber(#unitList) , 3):ToAll() if tonumber(#unitList) == 0 or unitList == nil then -- MESSAGE:New("SINGLE "..group:GetTypeName() , 3):ToAll() if string.match(group:GetTypeName(), unitType) then unitTypeFounded = true end else for k, unit in pairs(unitList) do -- MESSAGE:New("FOR LOOP "..unit:GetTypeName() , 3):ToAll() if string.match(unit:GetTypeName(), unitType) then unitTypeFounded = true end end end -- MESSAGE:New("unitTypeFounded "..tostring(unitTypeFounded) , 3):ToAll() return unitTypeFounded end -- ************************************************************************ -- *************************** ZONES HANDLERS **************************** -- ************************************************************************ -- Uses MarkPoints or DCS Shapes to show WPZ, PTZ and FLG zones in F10 Map -- runs only if Settings.ZonesMarkOnMap is true function MarkZonesOnMap(zoneList) if sts_Settings.ZonesMarkOnMap then if sts_Settings.MapShapes == true then local markID = math.random(800000, 900000) -- number from wich will start assigning marks ids for k, zone in pairs(zoneList) do local color = nil local markCoalition = nil local zoneName = zone:GetName() local zoneNameClean = nil if string.match(zoneName, sts_Settings.WPZoneTAG) then color = {0.9,0,0,0.2} -- WPZ is red elseif string.match(zoneName, sts_Settings.PTZoneTAG) then color = {1, 1, 0.2, 0.2} -- PTZ is yellow elseif string.match(zoneName, sts_Settings.FLGZoneTAG) then color = {0.5,0.8,0,0.2} -- FLG is green end -- TODO TEST if string.match(zoneName, sts_Settings.BlueTAG..sts_Settings.WPZoneTAG ) or string.match(zoneName, sts_Settings.BlueTAG..sts_Settings.PTZoneTAG) or string.match(zoneName, sts_Settings.BlueTAG..sts_Settings.FLGZoneTAG) then markCoalition = 2 --coalition blue zoneNameClean = string.gsub(zoneName, sts_Settings.BlueTAG, "") elseif string.match(zoneName, sts_Settings.RedTAG..sts_Settings.WPZoneTAG ) or string.match(zoneName, sts_Settings.RedTAG..sts_Settings.PTZoneTAG) or string.match(zoneName, sts_Settings.RedTAG..sts_Settings.FLGZoneTAG) then markCoalition = 1 -- coalition red zoneNameClean = string.gsub(zoneName, sts_Settings.RedTAG, "") elseif string.match(zoneName, sts_Settings.NeutralTAG..sts_Settings.WPZoneTAG ) or string.match(zoneName, sts_Settings.NeutralTAG..sts_Settings.PTZoneTAG) or string.match(zoneName, sts_Settings.NeutralTAG..sts_Settings.FLGZoneTAG) then markCoalition = 0 -- coalition neutral zoneNameClean = string.gsub(zoneName, sts_Settings.NeutralTAG, "") else markCoalition = -1 -- no coalition - to all zoneNameClean = zoneName end markID = markID + 1 local zonePoint = zone:GetCoordinate() local zoneVec3 = zonePoint:GetVec3() --trigger.action.markToAll(markID , zoneName , zoneVec3 , true ) trigger.action.circleToAll(markCoalition , markID+2000 , zoneVec3 , zone:GetRadius() , {0, 0.1, 0.7, 0.7} , color, 1 , true) trigger.action.textToAll(markCoalition , markID+1000 , zoneVec3 , {1, 1, 1, 1} , {0, 0, 0, 0.8} , 12 , true , zoneNameClean ) end else for k, zone in pairs(zoneList) do zone:MarkZone(20) end end end end -- Smokes zones defined in ME - reapeat every 5 minutes function SmokeZones(zoneList) for k, zone in pairs(zoneList) do local zonePoint = zone:GetCoordinate() local zoneName = zone:GetName() local smokeColorID = string.match(zoneName, "%((.*)%)") if not smokeColorID == nil then smokeColorID = string.upper(smokeColorID) end local smokeColor = SMOKECOLOR.Blue for keySmokeID, valueSmokeColor in pairs(stsSmokeColors) do if keySmokeID == smokeColorID then smokeColor = valueSmokeColor end end local smokeZoneScheduler = SCHEDULER:New( nil, function() zonePoint:Smoke(smokeColor) -- zonePoint:SmokeBlue() -- TODO smoke color see COORDINATE:Smoke(SmokeColor) end, {}, 5, 310 ) end end -- Creates zones when a tagged mark is added on F10 Map function AddZoneFromMark(markCoordinate, markText, markID, markCoalition) markText = string.upper(markText) if string.match(markText, "#"..sts_Settings.WPZoneTAG) or string.match(markText, "#"..sts_Settings.PTZoneTAG) or string.match(markText, "#"..sts_Settings.SMKZoneTAG)then local markIDstring = tostring(markID) local markCoordinateV2 = markCoordinate:GetVec2() local zoneRadius = 500 if string.match(markText, "%((%d+)%)") then zoneRadius = tonumber(string.match(markText, "%((%d+)%)")) -- set zone radius if defined in markText end if string.match(markText, "#"..sts_Settings.WPZoneTAG) then -- create WPZone stsZonesFromMark[markIDstring] = ZONE_RADIUS:New(sts_Settings.WPZoneTAG.."_mk_("..markIDstring..")" , markCoordinateV2, zoneRadius) if sts_Settings.MapShapes == true then local color = {0.9,0,0,0.2} -- WPZ is red local markVec3 = markCoordinate:GetVec3() trigger.action.circleToAll(markCoalition , markID+200000 , markVec3 , zoneRadius , {0, 0, 0, 0.7} , color, 5 , true) trigger.action.textToAll(markCoalition , markID+100000 , markVec3 , {1, 1, 1, 1} , {0, 0, 0, 0.8} , 12 , true , markText ) else stsZonesFromMark[markIDstring]:MarkZone(20) end stsZoneSets.TGTZONES:AddZone(stsZonesFromMark[markIDstring]) if sts_Settings.Debug then MESSAGE:New( markText.." - WP Zone Created", 3):ToAll() end --debug elseif string.match(markText, "#"..sts_Settings.PTZoneTAG) then -- create PTZone stsZonesFromMark[markIDstring] = ZONE_RADIUS:New(sts_Settings.PTZoneTAG.."_mk_("..markIDstring..")" , markCoordinateV2, zoneRadius) if sts_Settings.MapShapes == true then local color = {1, 1, 0.2, 0.2} -- PTZ is yellow local markVec3 = markCoordinate:GetVec3() trigger.action.circleToAll(markCoalition , markID+200000 , markVec3 , zoneRadius , {0, 0, 0, 0.7} , color, 5 , true) trigger.action.textToAll(markCoalition , markID+100000 , markVec3 , {1, 1, 1, 1} , {0, 0, 0, 0.8} , 12 , true , markText ) else stsZonesFromMark[markIDstring]:MarkZone(20) end stsZoneSets.TGTZONES:AddZone(stsZonesFromMark[markIDstring]) if sts_Settings.Debug then MESSAGE:New( markText.." - PT Zone Created", 3):ToAll() end --debug elseif string.match(markText, "#"..sts_Settings.SMKZoneTAG) and sts_Settings.SmokeFromMarker == true then -- create SMKZone local smokeColorID = string.match(markText, "%((.*)%)") local smokeColor = SMOKECOLOR.Blue for keySmokeID, valueSmokeColor in pairs(stsSmokeColors) do if keySmokeID == smokeColorID then smokeColor = valueSmokeColor end end markCoordinate:Smoke(smokeColor) --markCoordinate:SmokeBlue() -- TODO enable smoke color see COORDINATE:Smoke(SmokeColor) if sts_Settings.Debug then MESSAGE:New( markText.." - Smoke Zone Created", 3):ToAll() end --debug end end end -- Removes zones created via mark on F10 Map function RemoveZoneFromMark(markText, markID) markText = string.upper(markText) if string.match(markText, "#WPZ") or string.match(markText, "#PTZ") then local markIDstring = tostring(markID) if string.match(markText, "#WPZ") then -- remove zone WPZ stsZoneSets.TGTZONES:RemoveZonesByName(sts_Settings.WPZoneTAG.."_mk_("..markIDstring..")") elseif string.match(markText, "#PTZ") then --remove zone PTZ stsZoneSets.TGTZONES:RemoveZonesByName(sts_Settings.PTZoneTAG.."_mk_("..markIDstring..")") end stsZonesFromMark[markID] = nil if sts_Settings.MapShapes == true then -- removes shapes and text on map trigger.action.removeMark( markID + 100000) trigger.action.removeMark( markID + 200000) else --removes marks generated by ZONE:MarkZone() for mID , markerIdnumber in pairs(stsServiceMarkers[markIDstring]) do trigger.action.removeMark(markerIdnumber) -- dcs api end end end end -- Scan Flag zones for cargo groups presence and set flag values function FlagZonesScan() for COALITION, zonetable in pairs (stsFlagZones) do for key, zone in pairs (zonetable) do local zoneName = zone:GetName() local zoneFlagValue = 0 local zoneFlagNumber = tostring(string.match(zoneName, "%((%d+)%)")) if sts_Settings.Debug == true then MESSAGE:New( "FLAG ZONE NAME: "..zoneName.." FLAG : "..zoneFlagNumber , 15):ToAll() end --debug if not stsUserFlags[zoneName] then stsUserFlags[zoneName] = USERFLAG:New(zoneFlagNumber) end for k, cargoGroup in pairs(stsCargoGroups[COALITION]) do if cargoGroup:IsInZone(zone) and not cargoGroup:IsLoaded() then zoneFlagValue = zoneFlagValue + 1 if sts_Settings.Debug == true then MESSAGE:New( "GROUP IN ZONE: "..cargoGroup:GetName().." FLAG VALUE: "..zoneFlagValue , 15):ToAll() end --debug end end for k, cargoCrate in pairs(stsCargoCrates[COALITION]) do if cargoCrate:IsInZone(zone) and not cargoCrate:IsLoaded() then zoneFlagValue = zoneFlagValue + 1 if sts_Settings.Debug == true then MESSAGE:New( "CRATE IN ZONE: "..cargoCrate:GetName().." FLAG VALUE: "..zoneFlagValue , 15):ToAll() end --debug end end stsUserFlags[zoneName]:Set(zoneFlagValue) end end end -- ************************************************************************ -- *************************** ARTY HANDLERS **************************** -- ************************************************************************ function CreateArty(cargoGroup, COALITION) if sts_Settings.Artillery == false then return end local group = cargoGroup:GetObject() local groupName = group:GetName() -- local COALITION = string.upper(group:GetCoalitionName()) if stsMortars[groupName] then stsMortars[groupName]:Stop() stsMortars[groupName] = nil end stsMortars[groupName] = ARTY:NewFromCargoGroup(cargoGroup) stsMortars[groupName]:SetMarkAssignmentsOn() stsMortars[groupName]:SetIlluminationShells(30) stsMortars[groupName]:SetSmokeShells(30) stsMortars[groupName]:SetIlluminationMinMaxAlt(700, 1200) stsMortars[groupName]:SetMaxFiringRange(7) if sts_Settings.ArtilleryReports == false then stsMortars[groupName]:SetReportOFF() end -- Mortars[groupName]:SetDebugON() -- DEBUG ARTY stsMortars[groupName]:Start() end function ArtyShotFromMark(markCoordinate, markText, markID) if sts_Settings.Artillery == false then return end markText = string.upper(markText) if string.match(markText, "#ART") then if string.match(markText, "ILLU") then local roundsNumber = 2 -- default illuminting rounds that will be shot per attack if string.match(markText, "%((%d+)%)") then roundsNumber = tonumber(string.match(markText, "%((%d+)%)")) end for k, v in pairs(stsMortars) do stsMortars[k]:AssignTargetCoord(markCoordinate, 50, 500, roundsNumber, nil, nil, ARTY.WeaponType.IlluminationShells) end elseif string.match(markText, "SMOKE") then local roundsNumber = 1 -- default illuminting rounds that will be shot per attack if string.match(markText, "%((%d+)%)") then roundsNumber = tonumber(string.match(markText, "%((%d+)%)")) end for k, v in pairs(stsMortars) do stsMortars[k]:AssignTargetCoord(markCoordinate, 50, 100, roundsNumber, nil, nil, ARTY.WeaponType.SmokeShells) end else local roundsNumber = 5 -- default rounds that will be shot per attack if string.match(markText, "%((%d+)%)") then roundsNumber = tonumber(string.match(markText, "%((%d+)%)")) end for k, v in pairs(stsMortars) do stsMortars[k]:AssignTargetCoord(markCoordinate, 50, 100, roundsNumber) end end end end -- ************************************************************************ -- *************************** MASS HANDLERS **************************** -- ************************************************************************ -- calculates cargo mass -- carrierUnitName (optional, needed only if cargo is loaded) function CalculateCargoMass(cargo, carrierUnitName) local cargoType = cargo:GetClassName() local cargoName = cargo:GetName() local cargoMass if cargo:IsLoaded() and carrierUnitName then -- if cargo is loaded gets the cargo mass from carrier's mass table cargoMass = stsUnitMass[carrierUnitName][cargoName] elseif cargoType == "CARGO_GROUP" and not cargo:IsLoaded() then -- if GROUP cargo isn't loaded calculates cargo mass local cargo_GroupObject = cargo:GetObject() local cargoAliveUnits = cargo_GroupObject:CountAliveUnits() local cargoInitUnitsCount = cargo:GetCount() local cargoWeight = cargo:GetWeight() cargoMass = (cargoWeight/cargoInitUnitsCount)*cargoAliveUnits elseif cargoType == "CARGO_CRATE" and not cargo:IsLoaded() then -- if CRATE cargo isn't loaded calculates cargo mass local pStatic = StaticObject.getByName( cargoName ) -- DCS API finds static object by name local iWeight = pStatic:getCargoWeight() -- DCS API gets static mass defined in ME if iWeight > 0 then cargoMass = iWeight else cargoMass = sts_Settings.DefaultStaticCargoMass -- if Static object has no mass set, applies default value end end return cargoMass end -- Add mass to a carrier function AddMass(carrierGroup, cargo, cargoAliveUnits) local cargoClass = cargo:GetClassName() local cargoName = cargo:GetName() local carrierUnit = carrierGroup:GetFirstUnitAlive() local carrierUnitName = carrierUnit:GetName() local cargoMass = 0 if cargoClass == "CARGO_GROUP" then local cargoInitUnitsCount = cargo:GetCount() local cargoWeight = cargo:GetWeight() cargoMass = (cargoWeight/cargoInitUnitsCount)*cargoAliveUnits -- calculates mass based un cargo alive units elseif cargoClass == "CARGO_CRATE" then local pStatic = StaticObject.getByName( cargoName ) -- DCS API finds static object by name local iWeight = pStatic:getCargoWeight() -- DCS API gets static mass defined in ME if iWeight > 0 then cargoMass = iWeight else cargoMass = sts_Settings.DefaultStaticCargoMass -- if Static object has no mass set, applies default value end end stsUnitMass[carrierUnitName][cargoName] = cargoMass -- record mass for specific cargo stsUnitMass[carrierUnitName].cargoMass = stsUnitMass[carrierUnitName].cargoMass + cargoMass -- record calculated total cargo mass in carrier cargo bay trigger.action.setUnitInternalCargo( carrierUnitName , stsUnitMass[carrierUnitName].cargoMass) -- DCS API that defines the cargo mass carrierGroup:MessageToGroup("\n".."Mass loaded: "..cargoMass.." Kg ".."\n".."Total Mass in cargo bay: "..stsUnitMass[carrierUnitName].cargoMass.." Kg", 10, carrierGroup, "- CARGO-INFO") end -- Remove mass from a carrier function RemoveMass(carrierGroup, cargo) local cargoName = cargo:GetName() local carrierUnit = carrierGroup:GetFirstUnitAlive() local carrierUnitName = carrierUnit:GetName() stsUnitMass[carrierUnitName].cargoMass = stsUnitMass[carrierUnitName].cargoMass - stsUnitMass[carrierUnitName][cargoName] trigger.action.setUnitInternalCargo( carrierUnitName , stsUnitMass[carrierUnitName].cargoMass) -- DCS API that defines the cargo mass carrierGroup:MessageToGroup("\n".."Mass Unloaded: "..stsUnitMass[carrierUnitName][cargoName].." Kg ".."\n".."Total Mass in cargo bay: "..stsUnitMass[carrierUnitName].cargoMass.." Kg", 10, carrierGroup, "- CARGO-INFO") end -- ************************************************************************ -- *************************** CARGO HANDLERS *************************** -- ************************************************************************ --Generate CARGO_GROUPS and store them in CargoGroups table function CargoGroupGenerator(group, COALITION) local grouObject = group local groupName = group:GetName() if string.match(groupName, sts_Settings.CargoGroupsTAG) or string.match(groupName, sts_Settings.WarehouseTAG) then if not COALITION then COALITION = string.upper(group:GetCoalitionName()) end if not stsCargoGroups[COALITION][groupName] then if sts_Settings.Debug == true then MESSAGE:New( groupName, 7):ToAll() end stsCargoGroups[COALITION][groupName] = CARGO_GROUP:New(group, "GROUP", groupName, sts_Settings.LoadRadius, sts_Settings.NearRadius) if sts_Settings.RespawnOnDestroyed == true then stsCargoGroups[COALITION][groupName]:RespawnOnDestroyed(true) end if SearchUnitTypeInGroup(group, "2B11 mortar") == true then CreateArty(stsCargoGroups[COALITION][groupName], COALITION) end end return stsCargoGroups[COALITION][groupName] end end -- Generate CARGO_CRATES and store them in CargoCrates table function CargoCrateGenerator(static, COALITION) local staticName = static:GetName() stsCargoCrates[COALITION][staticName] = CARGO_CRATE:New(static, "CRATE", staticName, 50, 50) if sts_Settings.RespawnOnDestroyed == true then stsCargoCrates[COALITION][staticName]:RespawnOnDestroyed(true) end end -- Generate Warehouse zones and bind them to Warehouse Units (ZONE_UNIT) function WarehouseZoneGenerator(unit) if not unit then return end local COALITION = string.upper(unit:GetCoalitionName()) local unitName = unit:GetName() stsWarehouseZones[COALITION][unitName] = ZONE_UNIT:New(unitName, unit, sts_Settings.WarehouseLoadRadius) if sts_Settings.Debug == true then MESSAGE:New( "Warehouse created : "..unitName, 7):ToAll() end unit:HandleEvent(EVENTS.Dead) function unit:OnEventDead(EventData) stsWarehouseZones[COALITION][unitName]:SetRadius(1) end end -- Routes cargo through WP and PT Zones function CargoRouter( cargoGroup ) local wpZone = nil local ptZones = nil local zones = stsZoneSets.TGTZONES:GetSet() --gets zones table from zone set --TODO TEST local cargo = cargoGroup local transportableGroup = cargo:GetObject() -- get the cargo's group object local transportableGroupCoalition = string.upper(transportableGroup:GetCoalitionName()) -- get cargo's group object coalition for k, zone in pairs(zones) do --removes other coalitions zones from table if transportableGroupCoalition == "BLUE" then if string.match(string.upper(zone:GetName()),sts_Settings.RedTAG) or string.match(string.upper(zone:GetName()),sts_Settings.NeutralTAG) then zones[k] = nil end elseif transportableGroupCoalition == "RED" then if string.match(string.upper(zone:GetName()),sts_Settings.BlueTAG) or string.match(string.upper(zone:GetName()),sts_Settings.NeutralTAG) then zones[k] = nil end elseif transportableGroupCoalition == "NEUTRAL" then if string.match(string.upper(zone:GetName()),sts_Settings.BlueTAG) or string.match(string.upper(zone:GetName()),sts_Settings.RedTAG) then zones[k] = nil end end end for k, zone in pairs(zones) do -- loop zones if cargo:IsInZone(zone) then local zoneName = zone:GetName() if string.match(zoneName, sts_Settings.WPZoneTAG) then wpZone = zone elseif string.match(zoneName, sts_Settings.PTZoneTAG) then if not ptZones then ptZones = {} end ptZones[#ptZones+1] = zone end end end if cargo:IsAlive() and not cargo:IsLoaded() then local transportableGroup = cargo:GetObject() if wpZone then local zonePoint = wpZone:GetCoordinate() transportableGroup:RouteGroundTo(zonePoint, 50, "Diamond", 20, AfterCargoRouter, transportableGroup ) -- route at the zone center elseif ptZones then transportableGroup:PatrolZones(ptZones, 50, "Diamond", 20, 50) -- patrol a list of zones end end end -- Routes cargo groups through PT Zones after they reach the center of a WP Zone function AfterCargoRouter(group) local ptZones = {} local zones = stsZoneSets.TGTZONES:GetSet() --TODO TEST local transportableGroupCoalition = string.upper(group:GetCoalitionName()) -- get cargo's group object coalition for k, zone in pairs(zones) do --removes other coalitions zones from table if transportableGroupCoalition == "BLUE" then if string.match(string.upper(zone:GetName()),sts_Settings.RedTAG) or string.match(string.upper(zone:GetName()),sts_Settings.NeutralTAG) then zones[k] = nil return end elseif transportableGroupCoalition == "RED" then if string.match(string.upper(zone:GetName()),sts_Settings.BlueTAG) or string.match(string.upper(zone:GetName()),sts_Settings.NeutralTAG) then zones[k] = nil return end elseif transportableGroupCoalition == "NEUTRAL" then if string.match(string.upper(zone:GetName()),sts_Settings.BlueTAG) or string.match(string.upper(zone:GetName()),sts_Settings.RedTAG) then zones[k] = nil return end end end for k, zone in pairs(zones) do if group:IsInZone(zone) then local zoneName = zone:GetName() if string.match(zoneName, sts_Settings.PTZoneTAG) then ptZones[#ptZones+1] = zone end end end if #ptZones < 1 then return end if group:IsAlive() then group:PatrolZones(ptZones, 50, "Diamond", 20, 50) end end -- Check and validate cargo before that is been loaded on carrier -- Needed to avoid people to overload carriers and transport too many troops that would not have enought room inside carrier function ValidateCargoBeforeLoad(carrierUnit, cargo) if sts_Settings.ValidateCargo == false then return true end local cargoMass = CalculateCargoMass(cargo) local carrierUnitName = carrierUnit:GetName() local maxLoadableMass = stsUnitMass[carrierUnitName].maxMass local loadedMass = stsUnitMass[carrierUnitName].cargoMass local loadableMass = maxLoadableMass - loadedMass if cargoMass <= loadableMass then return true else return false end end -- Check and validate cargo after that is been loaded on carrier -- Needed to avoid people to overload carriers and transport too many troops that would not have enought room inside carrier function ValidateCargoAfterLoad(carrierUnit) if sts_Settings.ValidateCargo == false then return true end local carrierUnitName = carrierUnit:GetName() local maxLoadableMass = stsUnitMass[carrierUnitName].maxMass local loadedMass = stsUnitMass[carrierUnitName].cargoMass if loadedMass <= maxLoadableMass then return true else return false end end -- Unboards troops and define actions to do after unboarding -- { cargoCrate, carrierGroup, optional - if NOT nil will prevent cargo to be routed in zones after unboarding} function UnBoardTroops(table) local cargoGroup = table[1] local cargoGroupName = cargoGroup:GetName() local carrierGroup = table[2] local carrierUnit = carrierGroup:GetFirstUnitAlive() local COALITION = string.upper(carrierGroup:GetCoalitionName()) local carrierPoint = carrierGroup:GetCoordinate() local RouteCargo -- if false cargo will not be routed by CargoRouter() after unboarding if table[3] == nil then RouteCargo = true else RouteCargo = false end local MenuGeneratorDelayed = TIMER:New(MenuGenerator, carrierGroup) if cargoGroup:IsUnboarding() then carrierGroup:MessageToGroup("Cargo is already unboarding", 10, carrierGroup, string.gsub( cargoGroupName , "@..", "") ) return end -- NOTE do not use that P.o.S. of :onafterUnBoard() that does not fucking work fine for me -.- function cargoGroup:onafterUnLoad(Event, From, To, Core, ToPointVec2) --delay for menu re-generation wait to group to be unboarded MenuGeneratorDelayed:Start(5) carrierGroup:MessageToGroup("Unboarded!", 10, carrierGroup, string.gsub( cargoGroupName , "@..", "")) RemoveMass(carrierGroup, cargoGroup) -- removes cargo mass from carrier carrierUnit:RemoveCargo(cargoGroup) -- need to remove cargo manually - cargo group are added to cargo automatically when boarded but not removed when unboarded local cargoGroupObject = cargoGroup:GetObject() if SearchUnitTypeInGroup(cargoGroupObject, "2B11 mortar") == true then RouteCargo = false CreateArty(cargoGroup, COALITION) end -- Schedule group routing to Zones because need to wait until all of group is unboarded, also simulates times needed to pathfinding - timer set on 40 seconds if RouteCargo == true then local unloadscheduler = SCHEDULER:New( nil, function() CargoRouter(cargoGroup) end, {}, 40 ) end end if math.floor(carrierUnit:GetVelocityKMH()) <= 10 and math.floor(carrierGroup:GetHeight(true)) <= 25 or carrierGroup:AllOnGround() == true then -- prevents from unboarding from high speed or altitude cargoGroup:UnBoard(carrierPoint:GetRandomCoordinateInRadius(30, 20)) carrierGroup:MessageToGroup("Unboarding...", 10, carrierGroup, string.gsub( cargoGroupName , "@..", "")) MenuGeneratorDelayed:Start(2) else carrierGroup:MessageToGroup("You must be under 25 meters and less then 10 Km/h to unboard!!", 10, carrierGroup, string.gsub( cargoGroupName , "@..", "")) end end -- Unboard all CARGO_GROUPS loaded in a carrier function UnBoardAllTroops(carrierGroup) local carrierUnit = carrierGroup:GetFirstUnitAlive() local cargoList = {} local unitCargo = carrierUnit:GetCargo() for k, cargo in pairs(unitCargo) do if cargo:GetClassName() == "CARGO_GROUP" then cargoList[#cargoList+1] = cargo end end if #cargoList < 1 then carrierGroup:MessageToGroup("There are no cargo groups loaded.", 10, carrierGroup, "CARGO INFO") else carrierGroup:MessageToGroup("Unboarding all cargo groups", 10, carrierGroup, "CARGO INFO") for k, cargo in pairs(cargoList) do UnBoardTroops({cargo, carrierGroup}) end end end -- Load troops to a carrier --cargoGroup = table[1] --carrierGroup = table[2] -- spawned = table[3] function LoadTroops(table) local cargoGroup = table[1] local cargoGroupName = cargoGroup:GetName() local cargo_GroupObject = cargoGroup:GetObject() local cargoAliveUnits = cargo_GroupObject:CountAliveUnits() local carrierGroup = table[2] local carrierUnit = carrierGroup:GetFirstUnitAlive() local spawned = table[3] -- set it true if cargo have been spawned so if not loadable it will be destroyed local forceLoad = table[4] -- set it true if you want to bypass if cargoGroup:IsBoarding() "Is already boarding - cannot board" if ValidateCargoBeforeLoad(carrierUnit, cargoGroup) then local COALITION = string.upper(carrierGroup:GetCoalitionName()) if cargoGroup:IsBoarding() and forceLoad == nil then -- prevent boarding from multiple carriers carrierGroup:MessageToGroup("Is already boarding - cannot board", 15, carrierGroup, string.gsub( cargoGroupName , "@..", "")) MenuGenerator(carrierGroup) return end local MenuGeneratorDelayed = TIMER:New(MenuGenerator, carrierGroup) function cargoGroup:OnEnterLoaded() --:onafterLoad(Event, From, To, CargoCarrier, Speed, LoadDistance, Angle) -- carrierGroup:MessageToGroup("Boarded!", 10, carrierGroup, string.gsub( cargoGroupName , "@..", "")) MenuGeneratorDelayed:Start(2) AddMass(carrierGroup, cargoGroup, cargoAliveUnits) -- Add cargo mass to carrier end -- END ON AFTER LOAD cargoGroup:Load(carrierUnit) MenuGenerator(carrierGroup) else if spawned == true then cargoGroup:RespawnOnDestroyed(false) cargoGroup:Destroy() end carrierGroup:MessageToGroup("\n"..string.gsub( cargoGroupName , "@..", "").."\n".."Cargo not loadable. Too big or heavy!", 15, carrierGroup, "- CARGO-WARNING") end end -- Board troops to a carrier --cargoGroup = table[1] --carrierGroup = table[2] function BoardTroops(table) local cargoGroup = table[1] local cargoGroupName = cargoGroup:GetName() local cargo_GroupObject = cargoGroup:GetObject() local cargoAliveUnits = cargo_GroupObject:CountAliveUnits() local carrierGroup = table[2] local carrierUnit = carrierGroup:GetFirstUnitAlive() if ValidateCargoBeforeLoad(carrierUnit, cargoGroup) then --local COALITION = table[3] local COALITION = string.upper(carrierGroup:GetCoalitionName()) if cargoGroup:IsBoarding() then -- prevent boarding from multiple carriers carrierGroup:MessageToGroup("Is already boarding - cannot board", 15, carrierGroup, string.gsub( cargoGroupName , "@..", "")) MenuGenerator(carrierGroup) return end local MenuGeneratorDelayed = TIMER:New(MenuGenerator, carrierGroup) -- function cargoGroup:onafterLoad(Event, From, To) -- , CargoCarrier function cargoGroup:OnEnterLoaded() carrierGroup:MessageToGroup("Boarded!", 10, carrierGroup, string.gsub( cargoGroupName , "@..", "")) MenuGeneratorDelayed:Start(2) AddMass(carrierGroup, cargoGroup, cargoAliveUnits) -- Add cargo mass to carrier if not ValidateCargoAfterLoad(carrierUnit) then -- if cargo not validated it will be unloaded UnBoardTroops({cargoGroup, carrierGroup, 1}) carrierGroup:MessageToGroup("\n"..string.gsub( cargoGroupName , "@..", "").."\n".."Cargo not loadable. Too big or heavy!\nUNLOADING NOW!", 15, carrierGroup, "- CARGO-WARNING") end end -- END ON AFTER LOAD if math.floor(carrierUnit:GetVelocityKMH()) <= 10 and math.floor(carrierGroup:GetHeight(true)) <= 25 or carrierGroup:AllOnGround() == true then -- prevents from boarding from high speed or altitude cargoGroup:Board(carrierUnit, sts_Settings.NearRadius) carrierGroup:MessageToGroup("Boarding...", 10, carrierGroup, string.gsub( cargoGroupName , "@..", "")) MenuGenerator(carrierGroup) boardScheduler = SCHEDULER:New( nil, -- if cargo not boarded within this time limit, cargo will be force boarded into carrier (if it is on ground) regarless distance function() if cargoGroup:IsLoaded() then boardScheduler:Stop() return end -- cargo now is loaded do nothing if cargoGroup:IsBoarding() then -- if cargo is still boarding cargoGroup:Respawn() cargoGroup:Load(carrierUnit) carrierGroup:MessageToGroup("FORCED TO BOARD!\nGroup was taking too much time to board...\n", 15, carrierGroup, string.gsub( cargoGroupName , "@..", "")) boardScheduler:Stop() end end, {}, sts_Settings.TimeToForceLoad ) else carrierGroup:MessageToGroup("You must be under 25 meters and less then 10 Km/h to board!!", 10, carrierGroup, string.gsub( cargoGroupName , "@..", "")) end else carrierGroup:MessageToGroup("\n"..string.gsub( cargoGroupName , "@..", "").."\n".."Cargo not loadable. Too big or heavy!", 15, carrierGroup, "- CARGO-WARNING") end end --Unload crates and define actions to do after unloading -- { cargoCrate, carrierGroup } function UnLoadCrate(table) local cargoCrate = table[1] local cargoCrateName = cargoCrate:GetName() local carrierGroup = table[2] local carrierUnit = carrierGroup:GetFirstUnitAlive() local COALITION = string.upper(carrierGroup:GetCoalitionName()) local carrierPoint = carrierUnit:GetCoordinate() local MenuGeneratorDelayed = TIMER:New(MenuGenerator, carrierGroup) function cargoCrate:onafterUnLoad(Event, From, To) -- , CargoCarrier carrierGroup:MessageToGroup("Crate Unloaded!", 10, carrierGroup, string.gsub( cargoCrateName , "@..", "")) RemoveMass(carrierGroup, cargoCrate) -- removes mass from carrier carrierUnit:RemoveCargo(cargoCrate) -- remove cargo manually-only cargo group are added to cargo automatically when boarded but not removed when unboarded crates need to be added and removed manually MenuGeneratorDelayed:Start(1) end if math.floor(carrierUnit:GetVelocityKMH()) <= 5 and math.floor(carrierGroup:GetHeight(true)) <= 10 or carrierGroup:AllOnGround() == true then -- prevents from unloading from high speed or altitude cargoCrate:UnLoad(carrierPoint:GetRandomCoordinateInRadius(16, 15)) --carrierPoint:Translate(10, cargoSpawnBearing, true) MenuGeneratorDelayed:Start(1) else carrierGroup:MessageToGroup("You must be under 10 meters and lass then 5 Km/h to unload crates!!", 10, carrierGroup, string.gsub( cargoCrateName , "@..", "")) end end -- Unload all CARGO_CRATES loaded in a carrier function UnLoadAllCrates(carrierGroup) local carrierUnit = carrierGroup:GetFirstUnitAlive() local cargoList = {} local unitCargo = carrierUnit:GetCargo() for k, cargo in pairs(unitCargo) do if cargo:GetClassName() == "CARGO_CRATE" then cargoList[#cargoList+1] = cargo end end if #cargoList < 1 then carrierGroup:MessageToGroup("There are no cargo crates loaded.", 10, carrierGroup, "CARGO INFO") else carrierGroup:MessageToGroup("Unloading all cargo crates", 10, carrierGroup, "CARGO INFO") for k, cargo in pairs(cargoList) do UnLoadCrate({cargo, carrierGroup}) end end end --Load crates to a carrier and generate menus for unboarding --cargoCrate = table[1] --carrierGroup = table[2] function LoadCrate(table) local cargoCrate = table[1] local cargoCrateName = cargoCrate:GetName() local carrierGroup = table[2] local carrierUnit = carrierGroup:GetFirstUnitAlive() if ValidateCargoBeforeLoad(carrierUnit, cargoCrate) then -- local COALITION = table[3] local COALITION = string.upper(carrierGroup:GetCoalitionName()) local MenuGeneratorDelayed = TIMER:New(MenuGenerator, carrierGroup) -- function cargoCrate:onafterLoad(Event, From, To, CargoCarrier) -- function cargoCrate:OnEnterLoaded() -- carrierGroup:MessageToGroup("Crate Loaded!", 10, carrierGroup, string.gsub( cargoCrateName , "@..", "")) AddMass(carrierGroup, cargoCrate) if not ValidateCargoAfterLoad(carrierUnit) then UnLoadCrate({cargoCrate, carrierGroup}) carrierGroup:MessageToGroup("\n"..string.gsub( cargoCrateName , "@..", "").."\n".."Cargo not loadable. Too big or heavy!\nUNLOADING NOW!", 15, carrierGroup, "- CARGO-WARNING") else carrierUnit:AddCargo(cargoCrate) end MenuGeneratorDelayed:Start(1) end -- END enterloaded if math.floor(carrierUnit:GetVelocityKMH()) <= 3 and math.floor(carrierGroup:GetHeight(true)) <= 3 or carrierGroup:AllOnGround() == true then -- prevents from loading from high speed or altitude cargoCrate:Load(carrierUnit) MenuGenerator(carrierGroup) else carrierGroup:MessageToGroup("You must be on ground to load crates!!!", 10, carrierGroup, string.gsub( cargoCrateName , "@..", "")) end else carrierGroup:MessageToGroup("\n"..string.gsub( cargoCrateName , "@..", "").."\n".."Cargo not loadable. Too big or heavy!", 15, carrierGroup, "- CARGO-WARNING") end end -- ************************************************************************ -- ************************** CARGO WAREHOUSE *************************** -- ************************************************************************ -- spawn and load into carrier -- 1.cargoName -- 2.carrierGroup function WarehouseSpawn (table) local spawnableCargoGroup = table[1] local spawnableCargoGroupName = spawnableCargoGroup:GetName() local carrierGroup = table[2] local carrierUnit = carrierGroup:GetFirstUnitAlive() local warehouseZoneObject = table[3] if carrierUnit:IsInZone(warehouseZoneObject) then local spawnGroup = SPAWN:NewWithAlias(spawnableCargoGroupName, spawnableCargoGroupName..""..math.random(999)):OnSpawnGroup( function (spawned) local COALITION = string.upper(spawned:GetCoalitionName()) local cargoGroup = CargoGroupGenerator(spawned, COALITION) cargoGroup:RespawnOnDestroyed(false) LoadTroops({cargoGroup, carrierGroup, true}) end):Spawn() else carrierGroup:MessageToGroup("\nWarehouse not available!\n It may be destroyed or out of range.", 15, carrierGroup, "- WAREHOUSE-WARNING") end end -- ************************************************************************ -- *************************** MENU HANDLERS **************************** -- ************************************************************************ -- Generates Radio Menus under F10 - Other function MenuGenerator(carrierGroup) if not carrierGroup then return end local carrierGroupName = carrierGroup:GetName() if not string.match(carrierGroupName , sts_Settings.CarrierTAG) then return end local carrierCoalition = string.upper(carrierGroup:GetCoalitionName()) local carrierUnit = carrierGroup:GetFirstUnitAlive() local carrierUnitName = carrierUnit:GetName() local carrierUnitCoord = carrierUnit:GetCoordinate() -------- GROUPS MENUS GEN/DEL -------- local distanceTableGroups = {} -- table that will contain cargo distance from carrier (format : {[cargoName] = distance} local cargoTableGroups = {} -- table that will contain cargo group objects ( format: {[cargoName] = cargoObject} for k, cargoGroup in pairs(stsCargoGroups[carrierCoalition]) do local cargoGroupName = cargoGroup:GetName() local cargoGroupObject = cargoGroup:GetObject() if cargoGroup:IsInLoadRadius(carrierUnitCoord) and not cargoGroup:IsBoarding() and not cargoGroup:IsLoaded() and cargoGroup:IsAlive() and cargoGroupObject:IsActive() then --if cargo is boardable local cargoGroupCoordinate = cargoGroup:GetCoordinate() local cargoDistanceFromCarrier = carrierUnitCoord:DistanceFromPointVec2(cargoGroupCoordinate) distanceTableGroups[cargoGroupName] = math.ceil(cargoDistanceFromCarrier) -- insert data to table cargoTableGroups[cargoGroupName] = cargoGroup -- insert data to table if stsMenu[carrierGroupName].commands.unload[cargoGroupName] then -- since the cargo is boardable (so it isn't loaded), if an unload command menu for cargo exists remove it stsMenu[carrierGroupName].commands.unload[cargoGroupName]:Remove() stsMenu[carrierGroupName].commands.unload[cargoGroupName] = nil end elseif not cargoGroup:IsInLoadRadius(carrierUnitCoord) or not cargoGroup:IsAlive() or cargoGroup:IsLoaded() or cargoGroup:IsBoarding() then -- if cargo is not boarddable removes command menu if stsMenu[carrierGroupName].commands.load[cargoGroupName] then stsMenu[carrierGroupName].commands.load[cargoGroupName]:Remove() stsMenu[carrierGroupName].commands.load[cargoGroupName] = nil end end end local cargoGroupMenuCounter = 1 for cargoname, distance in spairs(distanceTableGroups, function(t,a,b) return t[b] > t[a] end) do -- generates radio menus for group boarding sorted by distance from carrier if stsMenu[carrierGroupName].commands.load[cargoname] then stsMenu[carrierGroupName].commands.load[cargoname]:Remove() end if cargoGroupMenuCounter < 10 then stsMenu[carrierGroupName].commands.load[cargoname] = MENU_GROUP_COMMAND:New( carrierGroup, "Board "..string.gsub(cargoname, "@..", "").."("..CalculateCargoMass(cargoTableGroups[cargoname]).." Kg)", stsMenu[carrierGroupName].mainTroops, BoardTroops, {cargoTableGroups[cargoname], carrierGroup} ) cargoGroupMenuCounter = cargoGroupMenuCounter + 1 end end -------- CRATES MENUS GEN/DEL -------- local distanceTableCrates = {} -- table that will contain cargo distance from carrier (format : {[cargoName] = distance} local cargoTableCrates = {} -- table that will contain cargo group objects ( format: {[cargoName] = cargoObject} for k, cargoCrate in pairs(stsCargoCrates[carrierCoalition]) do local cargoCrateName = cargoCrate:GetName() if cargoCrate:IsInLoadRadius(carrierUnitCoord) and not cargoCrate:IsLoaded() and cargoCrate:IsAlive() then--if cargo is loadable local cargoCrateCoordinate = cargoCrate:GetCoordinate() local cargoDistanceFromCarrier = carrierUnitCoord:DistanceFromPointVec2(cargoCrateCoordinate) -- calculates cargo distance from carrier distanceTableCrates[cargoCrateName] = math.ceil(cargoDistanceFromCarrier) -- insert data to table cargoTableCrates[cargoCrateName] = cargoCrate -- insert data to table if stsMenu[carrierGroupName].commands.unload[cargoCrateName] then -- since the cargo is loadable (so it isn't loaded), if an unload command menu for cargo exists remove it stsMenu[carrierGroupName].commands.unload[cargoCrateName]:Remove() stsMenu[carrierGroupName].commands.unload[cargoCrateName] = nil end elseif not cargoCrate:IsInLoadRadius(carrierUnitCoord) or not cargoCrate:IsAlive() or cargoCrate:IsLoaded() then -- if cargo is not loadable removes command menu if stsMenu[carrierGroupName].commands.load[cargoCrateName] then stsMenu[carrierGroupName].commands.load[cargoCrateName]:Remove() stsMenu[carrierGroupName].commands.load[cargoCrateName] = nil end end end local cargoCrateMenuCounter = 1 for cargoname, distance in spairs(distanceTableCrates, function(t,a,b) return t[b] > t[a] end) do -- generates radio menus for crates loading sorted by distance from carrier if stsMenu[carrierGroupName].commands.load[cargoname] then stsMenu[carrierGroupName].commands.load[cargoname]:Remove() end if cargoCrateMenuCounter < 9 then stsMenu[carrierGroupName].commands.load[cargoname] = MENU_GROUP_COMMAND:New( carrierGroup, "Load "..string.gsub(cargoname, "@..", "").."("..CalculateCargoMass(cargoTableCrates[cargoname]).." Kg)", stsMenu[carrierGroupName].mainCrates, LoadCrate, {cargoTableCrates[cargoname], carrierGroup} ) cargoCrateMenuCounter = cargoCrateMenuCounter + 1 end end -------- UNLOAD/UNBOARD MENUS GEN/DEL -------- local unitCargo = carrierUnit:GetCargo() -- generates unload command menus based on loaded cargos local troopCounter = 1 local crateCounter = 1 for k, cargo in pairs(unitCargo) do local cargoName = cargo:GetName() if not cargo:IsUnboarding() or cargo:IsLoaded() then if cargo:GetClassName() == "CARGO_GROUP" and troopCounter < 8 then stsMenu[carrierGroupName].commands.unload[cargoName] = MENU_GROUP_COMMAND:New( carrierGroup, "Unboard "..string.gsub(cargoName, "@..", "").." ("..CalculateCargoMass(cargo,carrierUnitName).." Kg)", stsMenu[carrierGroupName].mainTroopsUnload, UnBoardTroops, {cargo, carrierGroup} ) troopCounter = troopCounter + 1 elseif cargo:GetClassName() == "CARGO_CRATE" and crateCounter < 8 then stsMenu[carrierGroupName].commands.unload[cargoName] = MENU_GROUP_COMMAND:New( carrierGroup, "Unload "..string.gsub(cargoName, "@..", "").." ("..CalculateCargoMass(cargo,carrierUnitName).." Kg)", stsMenu[carrierGroupName].mainCratesUnload, UnLoadCrate, {cargo, carrierGroup} ) crateCounter = crateCounter + 1 end else if stsMenu[carrierGroupName].commands.unload[cargoName] then stsMenu[carrierGroupName].commands.unload[cargoName]:Remove() end end end -------- WAREHOUSE MENUS GEN/DEL -------- if sts_Settings.Warehouses == true then local warehouseCargoCounter = 1 for warehouseName, warehouseZoneObject in pairs(stsWarehouseZones[carrierCoalition]) do if carrierUnit:IsInZone(warehouseZoneObject) then if not stsMenu[carrierGroupName].warehouse[warehouseName] then stsMenu[carrierGroupName].warehouse[warehouseName] = {} stsMenu[carrierGroupName].warehouse[warehouseName].main = MENU_GROUP:New( carrierGroup, "Warehouse: "..string.gsub(warehouseName, "@..", ""), stsMenu[carrierGroupName].main ) -- This Warehouse Main menu end for key, spawnableGroup in pairs(stsWarehouseTemplates[carrierCoalition]) do local spawnableGroupName = spawnableGroup:GetName() if not stsMenu[carrierGroupName].warehouse[warehouseName].commands then stsMenu[carrierGroupName].warehouse[warehouseName].commands = {} end if not stsMenu[carrierGroupName].warehouse[warehouseName].commands[spawnableGroupName] and warehouseCargoCounter < 9 then stsMenu[carrierGroupName].warehouse[warehouseName].commands[spawnableGroupName] = MENU_GROUP_COMMAND:New( carrierGroup, "Load "..string.gsub(spawnableGroupName, "@..", ""), stsMenu[carrierGroupName].warehouse[warehouseName].main, WarehouseSpawn, {spawnableGroup , carrierGroup, warehouseZoneObject}) warehouseCargoCounter = warehouseCargoCounter + 1 end end else if stsMenu[carrierGroupName].warehouse[warehouseName] then stsMenu[carrierGroupName].warehouse[warehouseName].main:Remove() stsMenu[carrierGroupName].warehouse[warehouseName] = nil end end end end end -- ************************************************************************ -- ***************************** SCHEDULERS ***************************** -- ************************************************************************ stsMainScheduler = SCHEDULER:New( nil, function() FlagZonesScan() -- scans flag zones for cargo group presence -- for each group of each carrier set check if the group is still alive: -- if group is NOT alive remove the group from set -- if group IS alive then call the function MenuGenerator for this group for skey, transportSet in pairs(stsCarrierSets) do local transportSetTable = transportSet:GetSet() for gkey, transportGroup in pairs(transportSetTable) do if not transportGroup:IsAlive() then transportSet:RemoveGroupsByName(transportGroup:GetName()) else MenuGenerator(transportGroup) end end end end, {}, 0, sts_Settings.RefreshTime ) -- ************************************************************************ -- **************************** SCRIPT INIT ***************************** -- ************************************************************************ --Generate CARGO_GROUPS and CARGO_CRATES for each coalition at the beginning of the mission stsTransportableSets.BLUE.Groups:ForEachGroup( function (group) if group:IsActive() then CargoGroupGenerator(group, "BLUE") else group:HandleEvent(EVENTS.Birth) function group:OnEventBirth(EventData) local cargoGroup = CargoGroupGenerator(EventData.IniGroup, "BLUE") local cargoGroupObject = cargoGroup:GetObject() end end end ) stsTransportableSets.BLUE.Crates:ForEachStatic( function (static) CargoCrateGenerator(static, "BLUE") end ) stsTransportableSets.RED.Groups:ForEachGroup( function (group) if group:IsActive() then CargoGroupGenerator(group, "RED") else group:HandleEvent(EVENTS.Birth) function group:OnEventBirth(EventData) local cargoGroup = CargoGroupGenerator(EventData.IniGroup, "RED") local cargoGroupObject = cargoGroup:GetObject() end end end ) stsTransportableSets.RED.Crates:ForEachStatic( function (static) CargoCrateGenerator(static, "RED") end ) stsTransportableSets.NEUTRAL.Groups:ForEachGroup( function (group) if group:IsActive() then CargoGroupGenerator(group, "NEUTRAL") else group:HandleEvent(EVENTS.Birth) function group:OnEventBirth(EventData) local cargoGroup = CargoGroupGenerator(EventData.IniGroup, "NEUTRAL") local cargoGroupObject = cargoGroup:GetObject() end end end ) stsTransportableSets.NEUTRAL.Crates:ForEachStatic( function (static) CargoCrateGenerator(static, "NEUTRAL") end ) -- ACTIVATE WAREHOUSES AT MISSION START if sts_Settings.Warehouses == true then stsWarehouseSet.BLUE:ForEachUnit( function (unit) if unit:IsActive() then WarehouseZoneGenerator(unit) else local unitGroup = unit:GetGroup() unitGroup:HandleEvent(EVENTS.Birth) function unitGroup:OnEventBirth(EventData) WarehouseZoneGenerator(unit) end end end ) stsWarehouseSet.RED:ForEachUnit( function (unit) if unit:IsActive() then WarehouseZoneGenerator(unit) else local unitGroup = unit:GetGroup() unitGroup:HandleEvent(EVENTS.Birth) function unitGroup:OnEventBirth(EventData) WarehouseZoneGenerator(unit) end end end ) stsWarehouseSet.NEUTRAL:ForEachUnit( function (unit) if unit:IsActive() then WarehouseZoneGenerator(unit) else local unitGroup = unit:GetGroup() unitGroup:HandleEvent(EVENTS.Birth) function unitGroup:OnEventBirth(EventData) WarehouseZoneGenerator(unit) end end end ) end -- mark zones boudaries at mission start MarkZonesOnMap(stsZoneSets.TGTZONES:GetSet()) MarkZonesOnMap(stsZoneSets.BLUE.FLGZONES:GetSet()) MarkZonesOnMap(stsZoneSets.RED.FLGZONES:GetSet()) MarkZonesOnMap(stsZoneSets.NEUTRAL.FLGZONES:GetSet()) -- activate smoke at smoke zones at mission start SmokeZones(stsZoneSets.SMKZONES:GetSet()) -- init carrier removing all old data if present and creating tables for menu and mass management function CarrierInit(carrierGroup) if not carrierGroup then return end local carrierGroupName = carrierGroup:GetName() if not string.match(carrierGroupName, sts_Settings.CarrierTAG) then return end local carrierUnit = carrierGroup:GetFirstUnitAlive() local carrierUnitName = carrierUnit:GetName() -- MASS TABLE SETUP FOR CARRIER stsUnitMass[carrierUnitName] = nil --reset mass data stsUnitMass[carrierUnitName] = {} stsUnitMass[carrierUnitName].cargoMass = 0 carrierUnit:ClearCargo() -- reset cargo carrierUnit:SetCargoBayWeightLimit() -- reset cargo bay limit and sets moose default limit for unit type, see function POSITIONABLE:SetCargoBayWeightLimit( WeightLimit ) in MOOSE (this set ground units weight limit) -- defining max loadable mass for carrier if this is defined in sts_Settings.CargoBayLimits table (overwrite moose default) stsUnitMass[carrierUnitName].maxMass = carrierUnit:GetCargoBayFreeWeight() for unitType, maxCargo in pairs(sts_Settings.CargoBayLimits) do -- check sts_Settings.CargoBayLimits table for unit type max mass if defined if unitType == carrierUnit:GetTypeName() then stsUnitMass[carrierUnitName].maxMass = maxCargo end end -- if in mission editor max mass is defined in units name (e.g. unitname (maxmass) ) overwrite moose default and sts_Settings.CargoBayLimits if string.match(carrierUnitName, "%((%d+)%)") then stsUnitMass[carrierUnitName].maxMass = tonumber(string.match(carrierUnitName, "%((%d+)%)")) end stsUnitMass[carrierUnitName].maxMass = math.ceil(stsUnitMass[carrierUnitName].maxMass + sts_Settings.MassOverload) if sts_Settings.Debug == true then MESSAGE:New( "CARRIER: "..carrierGroupName.." COALITION : "..carrierGroup:GetCoalitionName() , 15):ToAll() MESSAGE:New( "TYPE: "..carrierUnit:GetTypeName().." MAX LAODABLE MASS: "..stsUnitMass[carrierUnitName].maxMass , 15):ToAll() MESSAGE:New( "---------------------------------------------------------------------------------------------------------", 15):ToAll() end --debug -- MENU SETUP FOR CARRIER if stsMenu[carrierGroupName] then stsMenu[carrierGroupName].main:Remove() end -- reset menus stsMenu[carrierGroupName] = nil stsMenu[carrierGroupName] = {} stsMenu[carrierGroupName].main = MENU_GROUP:New( carrierGroup, "K-STS" ) -- STS Main menu stsMenu[carrierGroupName].warehouse = {} stsMenu[carrierGroupName].commands = {} stsMenu[carrierGroupName].commands.load = {} -- will contain MENU_GROUP_COMMAND for loading cargos stsMenu[carrierGroupName].commands.unload = {} -- will contain MENU_GROUP_COMMAND for unloading cargos stsMenu[carrierGroupName].commands.info = {} -- will contain MENU_GROUP_COMMAND for info functions stsMenu[carrierGroupName].commands.warehouse = {} -- will contain MENU_GROUP_COMMAND for warehouses functions -- TROOPS MENUS stsMenu[carrierGroupName].mainTroops = MENU_GROUP:New( carrierGroup, "Troops Transport", stsMenu[carrierGroupName].main ) -- cargo troops main menu stsMenu[carrierGroupName].mainTroopsUnload = MENU_GROUP:New( carrierGroup, "Unboard Troops", stsMenu[carrierGroupName].mainTroops ) --cargo troops unload menu stsMenu[carrierGroupName].mainTroopsUnloadAll = MENU_GROUP:New( carrierGroup, "Unboard All Troops", stsMenu[carrierGroupName].mainTroopsUnload ) -- cargo troops unload all menu stsMenu[carrierGroupName].commands.unload.allTroops = MENU_GROUP_COMMAND:New( carrierGroup, "Confirm?", stsMenu[carrierGroupName].mainTroopsUnloadAll, UnBoardAllTroops, carrierGroup) -- unload all troops command -- CRATES MENUS stsMenu[carrierGroupName].mainCrates = MENU_GROUP:New( carrierGroup, "Crates Transport", stsMenu[carrierGroupName].main ) stsMenu[carrierGroupName].mainCratesUnload = MENU_GROUP:New( carrierGroup, "Unload Crates", stsMenu[carrierGroupName].mainCrates ) stsMenu[carrierGroupName].mainCratesUnloadAll = MENU_GROUP:New( carrierGroup, "Unload All Crates", stsMenu[carrierGroupName].mainCratesUnload ) stsMenu[carrierGroupName].commands.unload.allCrates = MENU_GROUP_COMMAND:New( carrierGroup, "Confirm?", stsMenu[carrierGroupName].mainCratesUnloadAll, UnLoadAllCrates, carrierGroup) -- INFO MENUS stsMenu[carrierGroupName].mainInfo = MENU_GROUP:New( carrierGroup, "Info...", stsMenu[carrierGroupName].main ) -- info main menu - contains info functions stsMenu[carrierGroupName].commands.info.carrierMassInfo = MENU_GROUP_COMMAND:New( carrierGroup, "Mass Info", stsMenu[carrierGroupName].mainInfo, CarrierMassInfo, carrierGroup) stsMenu[carrierGroupName].commands.info.cargoManifestInfo = MENU_GROUP_COMMAND:New( carrierGroup, "Cargo Manifest", stsMenu[carrierGroupName].mainInfo, CargoManifestInfo, carrierGroup) stsMenu[carrierGroupName].mainQuickStart = MENU_GROUP:New( carrierGroup, "Quick Start Manual", stsMenu[carrierGroupName].mainInfo ) stsMenu[carrierGroupName].commands.info.quickStartGeneral = MENU_GROUP_COMMAND:New( carrierGroup, "General", stsMenu[carrierGroupName].mainQuickStart, QuickStartInfo, {carrierGroup, 1}) stsMenu[carrierGroupName].commands.info.quickStartRouting = MENU_GROUP_COMMAND:New( carrierGroup, "Routing Zones", stsMenu[carrierGroupName].mainQuickStart, QuickStartInfo, {carrierGroup, 2}) stsMenu[carrierGroupName].commands.info.quickStartFlagZones = MENU_GROUP_COMMAND:New( carrierGroup, "Flag Zones", stsMenu[carrierGroupName].mainQuickStart, QuickStartInfo, {carrierGroup, 3}) stsMenu[carrierGroupName].commands.info.quickStartSmoke = MENU_GROUP_COMMAND:New( carrierGroup, "Smokes", stsMenu[carrierGroupName].mainQuickStart, QuickStartInfo, {carrierGroup, 4}) stsMenu[carrierGroupName].commands.info.quickStartArty = MENU_GROUP_COMMAND:New( carrierGroup, "Artillery", stsMenu[carrierGroupName].mainQuickStart, QuickStartInfo, {carrierGroup, 5}) end --This is to initialize carrier that are already alive at mission start (e.g. ground carriers) stsCarrierSets.BLUE:ForEachGroup( function (carrierGroup) CarrierInit(carrierGroup) end ) stsCarrierSets.RED:ForEachGroup( function (carrierGroup) CarrierInit(carrierGroup) end ) stsCarrierSets.NEUTRAL:ForEachGroup( function (carrierGroup) CarrierInit(carrierGroup) end ) -- ************************************************************************ -- *************************** EVENT HANDLERS *************************** -- ************************************************************************ -- Event Handling Subscriptions stsEventHandler = EVENTHANDLER:New() stsEventHandler:HandleEvent(EVENTS.Birth) stsEventHandler:HandleEvent(EVENTS.Land) stsEventHandler:HandleEvent(EVENTS.Takeoff) --EventHandler:HandleEvent(EVENTS.Dead) stsEventHandler:HandleEvent(EVENTS.MarkChange) stsEventHandler:HandleEvent(EVENTS.MarkRemoved) stsEventHandler:HandleEvent(EVENTS.MarkAdded) function stsEventHandler:OnEventBirth(EventData) CarrierInit(EventData.IniGroup) MenuGenerator(EventData.IniGroup) if sts_Settings.Debug then MESSAGE:New( "DEBUG: ".."BIRTH EVENT" , 3):ToAll() end --debug end function stsEventHandler:OnEventLand(EventData) MenuGenerator(EventData.IniGroup) -- if sts_Settings.Debug then MESSAGE:New( "DEBUG: ".."LAND EVENT" , 3):ToAll() end --debug end function stsEventHandler:OnEventTakeoff(EventData) MenuGenerator(EventData.IniGroup) -- if sts_Settings.Debug then MESSAGE:New( "DEBUG: ".."TAKE-OFF EVENT" , 3):ToAll() end --debug end function stsEventHandler:OnEventMarkChange(EventData) --EVENTDATA.MarkCoalition --EVENTDATA.MarkCoordinate --EVENTDATA.MarkGroupID --EVENTDATA.MarkID --EVENTDATA.MarkText --EVENTDATA.MarkVec3 AddZoneFromMark(EventData.MarkCoordinate, EventData.MarkText, EventData.MarkID, EventData.MarkCoalition) ArtyShotFromMark(EventData.MarkCoordinate, EventData.MarkText, EventData.MarkID) -- MESSAGE:New( "DEBUG: ".."MARK CHANGE EVENT".." Coalition = "..EventData.MarkCoalition , 3):ToAll() --debug end function stsEventHandler:OnEventMarkRemoved(EventData) RemoveZoneFromMark(EventData.MarkText, EventData.MarkID) -- if sts_Settings.Debug then MESSAGE:New( "DEBUG: ".."MARK REMOVE EVENT" , 3):ToAll() end --debug end function stsEventHandler:OnEventMarkAdded(EventData) -- tracks auto generated markers so they can be removed in a second time if sts_Settings.MapShapes == false then if string.match(EventData.MarkText, "_mk_") then local fatherMarkID = string.match(EventData.MarkText, "%((%d+)%)") if not stsServiceMarkers[fatherMarkID] then stsServiceMarkers[fatherMarkID] = {} end stsServiceMarkers[fatherMarkID][tostring(EventData.MarkID)] = EventData.MarkID if sts_Settings.Debug then MESSAGE:New( "Father Mark: "..fatherMarkID.."\nMark ID: "..tostring(EventData.MarkID) , 5):ToAll() end end end end if sts_Settings.WelcomeMessage == true then MESSAGE:New( "K-Assistant: Simple Transport Script - Loaded!" , 3):ToAll() end if sts_Settings.Debug == true then MESSAGE:New( "K-Assistant: Simple Transport Script - DEBUG MODE IS ENABLED" , 10):ToAll() end if sts_Settings.DebugTrace == true then MESSAGE:New( "K-Assistant: Simple Transport Script - DEBUG TRACING IS ENABLED" , 10):ToAll() end