From 595b9132e846525752f96c319b954f2030db31b8 Mon Sep 17 00:00:00 2001 From: kaltokri Date: Wed, 28 Feb 2024 17:34:40 +0100 Subject: [PATCH 1/4] Added additional information to FOX:AddProtectedGroup method --- Moose Development/Moose/Functional/Fox.lua | 622 ++++++++++----------- 1 file changed, 311 insertions(+), 311 deletions(-) diff --git a/Moose Development/Moose/Functional/Fox.lua b/Moose Development/Moose/Functional/Fox.lua index 0c9c81b67..ee5854474 100644 --- a/Moose Development/Moose/Functional/Fox.lua +++ b/Moose Development/Moose/Functional/Fox.lua @@ -1,14 +1,14 @@ --- **Functional** - Yet Another Missile Trainer. --- --- +-- +-- -- Practice to evade missiles without being destroyed. --- +-- -- -- ## Main Features: --- +-- -- * Handles air-to-air and surface-to-air missiles. -- * Define your own training zones on the map. Players in this zone will be protected. --- * Define launch zones. Only missiles launched in these zones are tracked. +-- * Define launch zones. Only missiles launched in these zones are tracked. -- * Define protected AI groups. -- * F10 radio menu to adjust settings for each player. -- * Alert on missile launch (optional). @@ -16,7 +16,7 @@ -- * Adaptive update of missile-to-player distance. -- * Finite State Machine (FSM) implementation. -- * Easy to use. See examples below. --- +-- -- === -- -- ### Author: **funkyfranky** @@ -47,7 +47,7 @@ -- @field #number dt10 Time step [sec] for missile position updates if distance to target > 10 km and < 50 km. Default 1 sec. -- @field #number dt05 Time step [sec] for missile position updates if distance to target > 5 km and < 10 km. Default 0.5 sec. -- @field #number dt01 Time step [sec] for missile position updates if distance to target > 1 km and < 5 km. Default 0.1 sec. --- @field #number dt00 Time step [sec] for missile position updates if distance to target < 1 km. Default 0.01 sec. +-- @field #number dt00 Time step [sec] for missile position updates if distance to target < 1 km. Default 0.01 sec. -- @extends Core.Fsm#FSM --- Fox 3! @@ -57,66 +57,66 @@ -- ![Banner Image](..\Presentations\FOX\FOX_Main.png) -- -- # The FOX Concept --- +-- -- As you probably know [Fox](https://en.wikipedia.org/wiki/Fox_\(code_word\)) is a NATO brevity code for launching air-to-air munition. Therefore, the class name is not 100% accurate as this -- script handles air-to-air but also surface-to-air missiles. --- +-- -- # Basic Script --- +-- -- -- Create a new missile trainer object. -- fox=FOX:New() --- +-- -- -- Start missile trainer. -- fox:Start() --- +-- -- # Training Zones --- +-- -- Players are only protected if they are inside one of the training zones. --- +-- -- -- Create a new missile trainer object. -- fox=FOX:New() --- +-- -- -- Add training zones. -- fox:AddSafeZone(ZONE:New("Training Zone Alpha")) -- fox:AddSafeZone(ZONE:New("Training Zone Bravo")) --- +-- -- -- Start missile trainer. -- fox:Start() --- +-- -- # Launch Zones --- +-- -- Missile launches are only monitored if the shooter is inside the defined launch zone. --- +-- -- -- Create a new missile trainer object. -- fox=FOX:New() --- +-- -- -- Add training zones. -- fox:AddLaunchZone(ZONE:New("Launch Zone SA-10 Krim")) -- fox:AddLaunchZone(ZONE:New("Training Zone Bravo")) --- +-- -- -- Start missile trainer. -- fox:Start() --- +-- -- # Protected AI Groups --- +-- -- Define AI protected groups. These groups cannot be harmed by missiles. --- +-- -- ## Add Individual Groups --- +-- -- -- Create a new missile trainer object. -- fox=FOX:New() --- +-- -- -- Add single protected group(s). -- fox:AddProtectedGroup(GROUP:FindByName("A-10 Protected")) -- fox:AddProtectedGroup(GROUP:FindByName("Yak-40")) --- +-- -- -- Start missile trainer. -- fox:Start() --- +-- -- # Notes --- +-- -- The script needs to be running before you enter an airplane slot. If FOX is not available to you, go back to observers and then join a slot again. --- +-- -- @field #FOX FOX = { ClassName = "FOX", @@ -218,17 +218,17 @@ function FOX:New() -- Inherit everthing from FSM class. local self=BASE:Inherit(self, FSM:New()) -- #FOX - + -- Defaults: self:SetDefaultMissileDestruction(true) self:SetDefaultLaunchAlerts(true) self:SetDefaultLaunchMarks(true) - + -- Explosion/destruction defaults. self:SetExplosionDistance() self:SetExplosionDistanceBigMissiles() self:SetExplosionPower() - + -- Start State. self:SetStartState("Stopped") @@ -350,7 +350,7 @@ function FOX:New() -- @param #string To To state. -- @param #FOX.PlayerData player Player data. - + return self end @@ -368,16 +368,16 @@ function FOX:onafterStart(From, Event, To) -- Handle events: self:HandleEvent(EVENTS.Birth) self:HandleEvent(EVENTS.Shot) - + if self.Debug then self:HandleEvent(EVENTS.Hit) end - + if self.Debug then self:TraceClass(self.ClassName) self:TraceLevel(2) end - + self:__Status(-20) end @@ -395,7 +395,7 @@ function FOX:onafterStop(From, Event, To) -- Handle events: self:UnHandleEvent(EVENTS.Birth) self:UnHandleEvent(EVENTS.Shot) - + if self.Debug then self:UnhandleEvent(EVENTS.Hit) end @@ -437,18 +437,18 @@ function FOX:SetProtectedGroupSet(groupset) return self end ---- Add a group to the protected set. +--- Add a group to the protected set. Works only with AI! -- @param #FOX self -- @param Wrapper.Group#GROUP group Protected group. -- @return #FOX self function FOX:AddProtectedGroup(group) - + if not self.protectedset then self.protectedset=SET_GROUP:New() end - + self.protectedset:AddGroup(group) - + return self end @@ -483,7 +483,7 @@ end function FOX:SetExplosionDistanceBigMissiles(distance, explosivemass) self.explosiondist2=distance or 500 - + self.bigmissilemass=explosivemass or 50 return self @@ -609,18 +609,18 @@ function FOX:onafterStatus(From, Event, To) -- Get FSM state. local fsmstate=self:GetState() - + local time=timer.getAbsTime() local clock=UTILS.SecondsToClock(time) - + -- Status. if self.verbose>=1 then self:I(self.lid..string.format("Missile trainer status %s: %s", clock, fsmstate)) end - + -- Check missile status. self:_CheckMissileStatus() - + -- Check player status. self:_CheckPlayers() @@ -635,39 +635,39 @@ function FOX:_CheckPlayers() for playername,_playersettings in pairs(self.players) do local playersettings=_playersettings --#FOX.PlayerData - + local unitname=playersettings.unitname local unit=UNIT:FindByName(unitname) - + if unit and unit:IsAlive() then - + local coord=unit:GetCoordinate() - + local issafe=self:_CheckCoordSafe(coord) - - + + if issafe then - + ----------------------------- -- Player INSIDE Safe Zone -- ----------------------------- - + if not playersettings.inzone then self:EnterSafeZone(playersettings) playersettings.inzone=true end - + else - + ------------------------------ -- Player OUTSIDE Safe Zone -- - ------------------------------ - + ------------------------------ + if playersettings.inzone==true then self:ExitSafeZone(playersettings) playersettings.inzone=false end - + end end end @@ -686,7 +686,7 @@ function FOX:_RemoveMissile(missile) table.remove(self.missiles, i) return end - end + end end end @@ -699,7 +699,7 @@ function FOX:_CheckMissileStatus() local inactive={} for i,_missile in pairs(self.missiles) do local missile=_missile --#FOX.MissileData - + local targetname="unkown" if missile.targetUnit then targetname=missile.targetUnit:GetName() @@ -712,14 +712,14 @@ function FOX:_CheckMissileStatus() local mtype=missile.missileType local dtype=missile.missileType local range=UTILS.MetersToNM(missile.missileRange) - + if not active then table.insert(inactive,i) end local heading=self:_GetWeapongHeading(missile.weapon) - + text=text..string.format("\n[%d] %s: active=%s, range=%.1f NM, heading=%03d, target=%s, player=%s, missilename=%s", i, mtype, active, range, heading, targetname, playername, missile.missileName) - + end if #self.missiles==0 then text=text.." none" @@ -728,7 +728,7 @@ function FOX:_CheckMissileStatus() self:I(self.lid..text) end - -- Remove inactive missiles. + -- Remove inactive missiles. for i=#self.missiles,1,-1 do local missile=self.missiles[i] --#FOX.MissileData if missile and not missile.active then @@ -747,31 +747,31 @@ function FOX:_IsProtected(targetunit) if not self.protectedset then return false end - + if targetunit and targetunit:IsAlive() then -- Get Group. local targetgroup=targetunit:GetGroup() - + if targetgroup then local targetname=targetgroup:GetName() - + for _,_group in pairs(self.protectedset:GetSet()) do local group=_group --Wrapper.Group#GROUP - + if group then local groupname=group:GetName() - + -- Target belongs to a protected set. if targetname==groupname then return true end end - + end end end - + return false end @@ -784,22 +784,22 @@ function FOX._FuncTrack(weapon, self, missile) -- Missile coordinate. local missileCoord= missile.missileCoord:UpdateFromVec3(weapon.vec3) --COORDINATE:NewFromVec3(_lastBombPos) - + -- Missile velocity in m/s. local missileVelocity=weapon:GetSpeed() --UTILS.VecNorm(_ordnance:getVelocity()) - + -- Update missile target if necessary. self:GetMissileTarget(missile) - + -- Target unit of the missile. - local target=nil --Wrapper.Unit#UNIT - + local target=nil --Wrapper.Unit#UNIT + if missile.targetUnit then - + ----------------------------------- -- Missile has a specific target -- ----------------------------------- - + if missile.targetPlayer then -- Target is a player. if missile.targetPlayer.destroy==true then @@ -811,13 +811,13 @@ function FOX._FuncTrack(weapon, self, missile) target=missile.targetUnit end end - + else - + ------------------------------------ -- Missile has NO specific target -- - ------------------------------------ - + ------------------------------------ + -- TODO: This might cause a problem with wingman. Even if the shooter itself is excluded from the check, it's wingmen are not. -- That would trigger the distance check right after missile launch if things to wrong. -- @@ -825,165 +825,165 @@ function FOX._FuncTrack(weapon, self, missile) -- * Time check: enable this check after X seconds after missile was fired. What is X? -- * Coalition check. But would not work in training situations where blue on blue is valid! -- * At least enable it for surface-to-air missiles. - + local function _GetTarget(_unit) local unit=_unit --Wrapper.Unit#UNIT - + -- Player position. local playerCoord=unit:GetCoordinate() - - -- Distance. + + -- Distance. local dist=missileCoord:Get3DDistance(playerCoord) - + -- Update mindist if necessary. Only include players in range of missile + 50% safety margin. if dist<=self.explosiondist then return unit - end + end end - + -- Distance to closest player. local mindist=nil - + -- Loop over players. for _,_player in pairs(self.players) do local player=_player --#FOX.PlayerData - + -- Check that player was not the one who launched the missile. if player.unitname~=missile.shooterName then - + -- Player position. local playerCoord=player.unit:GetCoordinate() - - -- Distance. + + -- Distance. local dist=missileCoord:Get3DDistance(playerCoord) - + -- Distance from shooter to player. local Dshooter2player=playerCoord:Get3DDistance(missile.shotCoord) - + -- Update mindist if necessary. Only include players in range of missile + 50% safety margin. if (mindist==nil or dist=self.bigmissilemass end - + -- If missile is 150 m from target ==> destroy missile if in safe zone. if destroymissile and self:_CheckCoordSafe(targetVec3) then - + -- Destroy missile. - self:I(self.lid..string.format("Destroying missile %s(%s) fired by %s aimed at %s [player=%s] at distance %.1f m", + self:I(self.lid..string.format("Destroying missile %s(%s) fired by %s aimed at %s [player=%s] at distance %.1f m", missile.missileType, missile.missileName, missile.shooterName, target:GetName(), tostring(missile.targetPlayer~=nil), distance)) weapon:Destroy() - + -- Missile is not active any more. missile.active=false - + -- Debug smoke. if self.Debug then - missileCoord:SmokeRed() + missileCoord:SmokeRed() end - + -- Create event. self:MissileDestroyed(missile) - + -- Little explosion for the visual effect. if self.explosionpower>0 and distance>50 and (distShooter==nil or (distShooter and distShooter>50)) then missileCoord:Explosion(self.explosionpower) end - + -- Target was a player. if missile.targetPlayer then - + -- Message to target. local text=string.format("Destroying missile. %s", self:_DeadText()) MESSAGE:New(text, 10):ToGroup(target:GetGroup()) - + -- Increase dead counter. missile.targetPlayer.dead=missile.targetPlayer.dead+1 end -- We could disable the tracking here but then the impact function would not be called. --weapon.tracking=false - + else - + -- Time step. - local dt=1.0 + local dt=1.0 if distance>50000 then -- > 50 km dt=self.dt50 --=5.0 @@ -1000,17 +1000,17 @@ function FOX._FuncTrack(weapon, self, missile) -- < 1 km dt=self.dt00 --0.01 end - + -- Set time step. weapon:SetTimeStepTrack(dt) end - + else - + -- No current target. self:T(self.lid..string.format("Missile %s(%s) fired by %s has no current target. Checking back in 0.1 sec.", missile.missileType, missile.missileName, missile.shooterName)) weapon:SetTimeStepTrack(0.1) - + end end @@ -1021,25 +1021,25 @@ end -- @param #FOX.MissileData missile Fired missile. function FOX._FuncImpact(weapon, self, missile) - if missile.targetPlayer then - + if missile.targetPlayer then + -- Get human player. local player=missile.targetPlayer - + -- Check for player and distance < 10 km. if player and player.unit:IsAlive() then -- and missileCoord and player.unit:GetCoordinate():Get3DDistance(missileCoord)<10*1000 then local text=string.format("Missile defeated. Well done, %s!", player.name) MESSAGE:New(text, 10):ToClient(player.client) - + -- Increase defeated counter. player.defeated=player.defeated+1 end - + end - + -- Missile is not active any more. - missile.active=false - + missile.active=false + --Terminate the timer. self:T(FOX.lid..string.format("Terminating missile track timer.")) weapon.tracking=false @@ -1058,59 +1058,59 @@ function FOX:onafterMissileLaunch(From, Event, To, missile) local text=string.format("FOX: Tracking missile %s(%s) - target %s - shooter %s", missile.missileType, missile.missileName, tostring(missile.targetName), missile.shooterName) self:I(FOX.lid..text) MESSAGE:New(text, 10):ToAllIf(self.Debug) - + -- Loop over players. for _,_player in pairs(self.players) do local player=_player --#FOX.PlayerData - + -- Player position. local playerUnit=player.unit - + -- Check that player is alive and of the opposite coalition. if playerUnit and playerUnit:IsAlive() and player.coalition~=missile.shooterCoalition then - + -- Player missile distance. local distance=playerUnit:GetCoordinate():Get3DDistance(missile.shotCoord) - + -- Player bearing to missile. local bearing=playerUnit:GetCoordinate():HeadingTo(missile.shotCoord) - + -- Alert that missile has been launched. if player.launchalert then - + -- Alert directly targeted players or players that are within missile max range. if (missile.targetPlayer and player.unitname==missile.targetPlayer.unitname) or (distance Target=%s, fuse dist=%s, explosive=%s", tostring(missile.shooterName), tostring(missile.missileType), tostring(missile.missileName), tostring(missile.targetName), tostring(missile.fuseDist), tostring(missile.explosive))) - + -- Only track if target was a player or target is protected. Saw the 9M311 missiles have no target! if missile.targetPlayer or self:_IsProtected(missile.targetUnit) or missile.targetName=="unknown" then - + -- Add missile table. table.insert(self.missiles, missile) - + -- Trigger MissileLaunch event. self:__MissileLaunch(0.1, missile) - + end - + end --if _track - + end --- FOX event handler for event hit. @@ -1340,7 +1340,7 @@ end -- @param Core.Event#EVENTDATA EventData function FOX:OnEventHit(EventData) self:T({eventhit = EventData}) - + -- Nil checks. if EventData.Weapon==nil then return @@ -1351,10 +1351,10 @@ function FOX:OnEventHit(EventData) if EventData.TgtUnit==nil then return end - + local weapon=EventData.Weapon local weaponname=weapon:getName() - + for i,_missile in pairs(self.missiles) do local missile=_missile --#FOX.MissileData if missile.missileName==weaponname then @@ -1375,51 +1375,51 @@ end -- @param #string _unitName Name of player unit. function FOX:_AddF10Commands(_unitName) self:F(_unitName) - + -- Get player unit and name. local _unit, playername = self:_GetPlayerUnitAndName(_unitName) - + -- Check for player unit. if _unit and playername then -- Get group and ID. local group=_unit:GetGroup() local gid=group:GetID() - + if group and gid then - + if not self.menuadded[gid] then - + -- Enable switch so we don't do this twice. self.menuadded[gid]=true - + -- Set menu root path. local _rootPath=nil if FOX.MenuF10Root then ------------------------ -- MISSON LEVEL MENUE -- - ------------------------ - + ------------------------ + -- F10/FOX/... _rootPath=FOX.MenuF10Root - + else ------------------------ -- GROUP LEVEL MENUES -- ------------------------ - + -- Main F10 menu: F10/FOX/ if FOX.MenuF10[gid]==nil then FOX.MenuF10[gid]=missionCommands.addSubMenuForGroup(gid, "FOX") end - + -- F10/FOX/... _rootPath=FOX.MenuF10[gid] - + end - - - -------------------------------- + + + -------------------------------- -- F10/F FOX/F1 Help -------------------------------- --local _helpPath=missionCommands.addSubMenuForGroup(gid, "Help", _rootPath) @@ -1430,12 +1430,12 @@ function FOX:_AddF10Commands(_unitName) ------------------------- -- F10/F FOX/ ------------------------- - + missionCommands.addCommandForGroup(gid, "Destroy Missiles On/Off", _rootPath, self._ToggleDestroyMissiles, self, _unitName) -- F1 missionCommands.addCommandForGroup(gid, "Launch Alerts On/Off", _rootPath, self._ToggleLaunchAlert, self, _unitName) -- F2 missionCommands.addCommandForGroup(gid, "Mark Launch On/Off", _rootPath, self._ToggleLaunchMark, self, _unitName) -- F3 missionCommands.addCommandForGroup(gid, "My Status", _rootPath, self._MyStatus, self, _unitName) -- F4 - + end else self:E(self.lid..string.format("ERROR: Could not find group or group ID in AddF10Menu() function. Unit name: %s.", _unitName or "unknown")) @@ -1452,23 +1452,23 @@ end -- @param #string _unitname Name of the player unit. function FOX:_MyStatus(_unitname) self:F2(_unitname) - + -- Get player unit and player name. local unit, playername = self:_GetPlayerUnitAndName(_unitname) - + -- Check if we have a player. if unit and playername then - - -- Player data. + + -- Player data. local playerData=self.players[playername] --#FOX.PlayerData - + if playerData then - + local m,mtext=self:_GetTargetMissiles(playerData.name) - + local text=string.format("Status of player %s:\n", playerData.name) local safe=self:_CheckCoordSafe(playerData.unit:GetCoordinate()) - + text=text..string.format("Destroy missiles? %s\n", tostring(playerData.destroy)) text=text..string.format("Launch alert? %s\n", tostring(playerData.launchalert)) text=text..string.format("Launch marks? %s\n", tostring(playerData.marklaunch)) @@ -1476,9 +1476,9 @@ function FOX:_MyStatus(_unitname) text=text..string.format("Missiles defeated: %d\n", playerData.defeated) text=text..string.format("Missiles destroyed: %d\n", playerData.dead) text=text..string.format("Me target: %d\n%s", m, mtext) - + MESSAGE:New(text, 10, nil, true):ToClient(playerData.client) - + end end end @@ -1494,12 +1494,12 @@ function FOX:_GetTargetMissiles(playername) local n=0 for _,_missile in pairs(self.missiles) do local missile=_missile --#FOX.MissileData - + if missile.targetPlayer and missile.targetPlayer.name==playername then n=n+1 text=text..string.format("Type %s: active %s\n", missile.missileType, tostring(missile.active)) end - + end return n,text @@ -1510,21 +1510,21 @@ end -- @param #string _unitname Name of the player unit. function FOX:_ToggleLaunchAlert(_unitname) self:F2(_unitname) - + -- Get player unit and player name. local unit, playername = self:_GetPlayerUnitAndName(_unitname) - + -- Check if we have a player. if unit and playername then - - -- Player data. + + -- Player data. local playerData=self.players[playername] --#FOX.PlayerData - + if playerData then - + -- Invert state. playerData.launchalert=not playerData.launchalert - + -- Inform player. local text="" if playerData.launchalert==true then @@ -1533,7 +1533,7 @@ function FOX:_ToggleLaunchAlert(_unitname) text=string.format("%s, missile launch alerts are now DISABLED.", playerData.name) end MESSAGE:New(text, 5):ToClient(playerData.client) - + end end end @@ -1543,21 +1543,21 @@ end -- @param #string _unitname Name of the player unit. function FOX:_ToggleLaunchMark(_unitname) self:F2(_unitname) - + -- Get player unit and player name. local unit, playername = self:_GetPlayerUnitAndName(_unitname) - + -- Check if we have a player. if unit and playername then - - -- Player data. + + -- Player data. local playerData=self.players[playername] --#FOX.PlayerData - + if playerData then - + -- Invert state. playerData.marklaunch=not playerData.marklaunch - + -- Inform player. local text="" if playerData.marklaunch==true then @@ -1566,7 +1566,7 @@ function FOX:_ToggleLaunchMark(_unitname) text=string.format("%s, missile launch marks are now DISABLED.", playerData.name) end MESSAGE:New(text, 5):ToClient(playerData.client) - + end end end @@ -1577,21 +1577,21 @@ end -- @param #string _unitname Name of the player unit. function FOX:_ToggleDestroyMissiles(_unitname) self:F2(_unitname) - + -- Get player unit and player name. local unit, playername = self:_GetPlayerUnitAndName(_unitname) - + -- Check if we have a player. if unit and playername then - - -- Player data. + + -- Player data. local playerData=self.players[playername] --#FOX.PlayerData - + if playerData then - + -- Invert state. playerData.destroy=not playerData.destroy - + -- Inform player. local text="" if playerData.destroy==true then @@ -1600,7 +1600,7 @@ function FOX:_ToggleDestroyMissiles(_unitname) text=string.format("%s, incoming missiles will NOT be DESTROYED.", playerData.name) end MESSAGE:New(text, 5):ToClient(playerData.client) - + end end end @@ -1622,9 +1622,9 @@ function FOX:_DeadText() texts[4]="Well, I guess that was it!" texts[5]="Bye, bye!" texts[6]="Cheers buddy, was nice knowing you!" - + local r=math.random(#texts) - + return texts[r] end @@ -1637,9 +1637,9 @@ function FOX:_CheckCoordSafe(coord) -- No safe zones defined ==> Everything is safe. if #self.safezones==0 then - return true + return true end - + -- Loop over all zones. for _,_zone in pairs(self.safezones) do local zone=_zone --Core.Zone#ZONE @@ -1662,9 +1662,9 @@ function FOX:_CheckCoordLaunch(coord) -- No safe zones defined ==> Everything is safe. if #self.launchzones==0 then - return true + return true end - + -- Loop over all zones. for _,_zone in pairs(self.launchzones) do local zone=_zone --Core.Zone#ZONE @@ -1679,50 +1679,50 @@ function FOX:_CheckCoordLaunch(coord) return false end ---- Returns the unit of a player and the player name. If the unit does not belong to a player, nil is returned. +--- Returns the unit of a player and the player name. If the unit does not belong to a player, nil is returned. -- @param #FOX self -- @param DCS#Weapon weapon The weapon. -- @return #number Heading of weapon in degrees or -1. function FOX:_GetWeapongHeading(weapon) if weapon and weapon:isExist() then - + local wp=weapon:getPosition() - + local wph = math.atan2(wp.x.z, wp.x.x) - + if wph < 0 then wph=wph+2*math.pi end - + wph=math.deg(wph) - + return wph end return -1 end ---- Tell player notching headings. +--- Tell player notching headings. -- @param #FOX self -- @param #FOX.PlayerData playerData Player data. -- @param DCS#Weapon weapon The weapon. function FOX:_SayNotchingHeadings(playerData, weapon) if playerData and playerData.unit and playerData.unit:IsAlive() then - + local nr, nl=self:_GetNotchingHeadings(weapon) - + if nr and nl then - local text=string.format("Notching heading %03d° or %03d°", nr, nl) + local text=string.format("Notching heading %03d° or %03d°", nr, nl) MESSAGE:New(text, 5, "FOX"):ToClient(playerData.client) end - + end end ---- Returns the unit of a player and the player name. If the unit does not belong to a player, nil is returned. +--- Returns the unit of a player and the player name. If the unit does not belong to a player, nil is returned. -- @param #FOX self -- @param DCS#Weapon weapon The weapon. -- @return #number Notching heading right, i.e. missile heading +90°. @@ -1730,22 +1730,22 @@ end function FOX:_GetNotchingHeadings(weapon) if weapon then - + local hdg=self:_GetWeapongHeading(weapon) - + local hdg1=hdg+90 if hdg1>360 then hdg1=hdg1-360 end - + local hdg2=hdg-90 if hdg2<0 then hdg2=hdg2+360 end - + return hdg1, hdg2 - end - + end + return nil, nil end @@ -1755,14 +1755,14 @@ end -- @return #FOX.PlayerData Player data. function FOX:_GetPlayerFromUnitname(unitName) - for _,_player in pairs(self.players) do + for _,_player in pairs(self.players) do local player=_player --#FOX.PlayerData - + if player.unitname==unitName then return player end end - + return nil end @@ -1777,20 +1777,20 @@ function FOX:_GetPlayerFromUnit(unit) -- Name of the unit local unitname=unit:GetName() - for _,_player in pairs(self.players) do + for _,_player in pairs(self.players) do local player=_player --#FOX.PlayerData - + if player.unitname==unitname then return player end end end - + return nil end ---- Returns the unit of a player and the player name. If the unit does not belong to a player, nil is returned. +--- Returns the unit of a player and the player name. If the unit does not belong to a player, nil is returned. -- @param #FOX self -- @param #string _unitName Name of the player unit. -- @return Wrapper.Unit#UNIT Unit of player or nil. @@ -1799,31 +1799,31 @@ function FOX:_GetPlayerUnitAndName(_unitName) self:F2(_unitName) if _unitName ~= nil then - + -- Get DCS unit from its name. local DCSunit=Unit.getByName(_unitName) - + if DCSunit then - + -- Get player name if any. local playername=DCSunit:getPlayerName() - + -- Unit object. local unit=UNIT:Find(DCSunit) - + -- Debug. self:T2({DCSunit=DCSunit, unit=unit, playername=playername}) - + -- Check if enverything is there. if DCSunit and unit and playername then self:T(self.lid..string.format("Found DCS unit %s with player %s.", tostring(_unitName), tostring(playername))) return unit, playername end - + end - + end - + -- Return nil if we could not find a player. return nil,nil end From 1cbdafda65f39e9574bd020ab036667b53f66ae6 Mon Sep 17 00:00:00 2001 From: kaltokri Date: Wed, 28 Feb 2024 17:39:06 +0100 Subject: [PATCH 2/4] Added link to demo missions for Functional.Fox --- Moose Development/Moose/Functional/Fox.lua | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Moose Development/Moose/Functional/Fox.lua b/Moose Development/Moose/Functional/Fox.lua index ee5854474..5ca6d4e51 100644 --- a/Moose Development/Moose/Functional/Fox.lua +++ b/Moose Development/Moose/Functional/Fox.lua @@ -19,6 +19,10 @@ -- -- === -- +-- ### [Demo Missions](https://github.com/FlightControl-Master/MOOSE_Demos/tree/master/Functional/FOX) +-- +-- === +-- -- ### Author: **funkyfranky** -- @module Functional.Fox -- @image Functional_FOX.png From 74c19f105830399d76d270a5c0879df48bb1e2f8 Mon Sep 17 00:00:00 2001 From: kaltokri Date: Wed, 28 Feb 2024 19:43:08 +0100 Subject: [PATCH 3/4] Added new airfields in Normandy --- Moose Development/Moose/Wrapper/Airbase.lua | 458 ++++++++++---------- 1 file changed, 231 insertions(+), 227 deletions(-) diff --git a/Moose Development/Moose/Wrapper/Airbase.lua b/Moose Development/Moose/Wrapper/Airbase.lua index 490edcba7..ae2d9db16 100644 --- a/Moose Development/Moose/Wrapper/Airbase.lua +++ b/Moose Development/Moose/Wrapper/Airbase.lua @@ -169,163 +169,167 @@ AIRBASE.Nevada = { --- Airbases of the Normandy map: -- --- * AIRBASE.Normandy.Saint_Pierre_du_Mont --- * AIRBASE.Normandy.Lignerolles --- * AIRBASE.Normandy.Cretteville --- * AIRBASE.Normandy.Maupertus --- * AIRBASE.Normandy.Brucheville --- * AIRBASE.Normandy.Meautis --- * AIRBASE.Normandy.Cricqueville_en_Bessin --- * AIRBASE.Normandy.Lessay --- * AIRBASE.Normandy.Sainte_Laurent_sur_Mer --- * AIRBASE.Normandy.Biniville --- * AIRBASE.Normandy.Cardonville --- * AIRBASE.Normandy.Deux_Jumeaux --- * AIRBASE.Normandy.Chippelle --- * AIRBASE.Normandy.Beuzeville --- * AIRBASE.Normandy.Azeville --- * AIRBASE.Normandy.Picauville --- * AIRBASE.Normandy.Le_Molay --- * AIRBASE.Normandy.Longues_sur_Mer --- * AIRBASE.Normandy.Carpiquet --- * AIRBASE.Normandy.Bazenville --- * AIRBASE.Normandy.Sainte_Croix_sur_Mer --- * AIRBASE.Normandy.Beny_sur_Mer --- * AIRBASE.Normandy.Rucqueville --- * AIRBASE.Normandy.Sommervieu --- * AIRBASE.Normandy.Lantheuil --- * AIRBASE.Normandy.Evreux --- * AIRBASE.Normandy.Chailey --- * AIRBASE.Normandy.Needs_Oar_Point --- * AIRBASE.Normandy.Funtington --- * AIRBASE.Normandy.Tangmere --- * AIRBASE.Normandy.Ford --- * AIRBASE.Normandy.Argentan --- * AIRBASE.Normandy.Goulet --- * AIRBASE.Normandy.Barville --- * AIRBASE.Normandy.Essay --- * AIRBASE.Normandy.Hauterive --- * AIRBASE.Normandy.Lymington --- * AIRBASE.Normandy.Vrigny --- * AIRBASE.Normandy.Odiham --- * AIRBASE.Normandy.Conches --- * AIRBASE.Normandy.West_Malling --- * AIRBASE.Normandy.Villacoublay --- * AIRBASE.Normandy.Kenley --- * AIRBASE.Normandy.Beauvais_Tille --- * AIRBASE.Normandy.Cormeilles_en_Vexin --- * AIRBASE.Normandy.Creil --- * AIRBASE.Normandy.Guyancourt --- * AIRBASE.Normandy.Lonrai --- * AIRBASE.Normandy.Dinan_Trelivan --- * AIRBASE.Normandy.Heathrow --- * AIRBASE.Normandy.Fecamp_Benouville --- * AIRBASE.Normandy.Farnborough --- * AIRBASE.Normandy.Friston --- * AIRBASE.Normandy.Deanland --- * AIRBASE.Normandy.Triqueville --- * AIRBASE.Normandy.Poix --- * AIRBASE.Normandy.Orly --- * AIRBASE.Normandy.Stoney_Cross --- * AIRBASE.Normandy.Amiens_Glisy --- * AIRBASE.Normandy.Ronai --- * AIRBASE.Normandy.Rouen_Boos --- * AIRBASE.Normandy.Deauville --- * AIRBASE.Normandy.Saint_Aubin --- * AIRBASE.Normandy.Flers --- * AIRBASE.Normandy.Avranches_Le_Val_Saint_Pere --- * AIRBASE.Normandy.Gravesend --- * AIRBASE.Normandy.Beaumont_le_Roger --- * AIRBASE.Normandy.Broglie --- * AIRBASE.Normandy.Bernay_Saint_Martin --- * AIRBASE.Normandy.Saint_Andre_de_lEure --- * AIRBASE.Normandy.Biggin_Hill --- * AIRBASE.Normandy.Manston --- * AIRBASE.Normandy.Detling --- * AIRBASE.Normandy.Lympne --- * AIRBASE.Normandy.Abbeville_Drucat --- * AIRBASE.Normandy.Merville_Calonne --- * AIRBASE.Normandy.Saint_Omer_Wizernes +-- * AIRBASE.Normandy.Abbeville_Drucat +-- * AIRBASE.Normandy.Amiens_Glisy +-- * AIRBASE.Normandy.Argentan +-- * AIRBASE.Normandy.Avranches_Le_Val_Saint_Pere +-- * AIRBASE.Normandy.Azeville +-- * AIRBASE.Normandy.Barville +-- * AIRBASE.Normandy.Bazenville +-- * AIRBASE.Normandy.Beaumont_le_Roger +-- * AIRBASE.Normandy.Beauvais_Tille +-- * AIRBASE.Normandy.Beny_sur_Mer +-- * AIRBASE.Normandy.Bernay_Saint_Martin +-- * AIRBASE.Normandy.Beuzeville +-- * AIRBASE.Normandy.Biggin_Hill +-- * AIRBASE.Normandy.Biniville +-- * AIRBASE.Normandy.Broglie +-- * AIRBASE.Normandy.Brucheville +-- * AIRBASE.Normandy.Cardonville +-- * AIRBASE.Normandy.Carpiquet +-- * AIRBASE.Normandy.Chailey +-- * AIRBASE.Normandy.Chippelle +-- * AIRBASE.Normandy.Conches +-- * AIRBASE.Normandy.Cormeilles_en_Vexin +-- * AIRBASE.Normandy.Creil +-- * AIRBASE.Normandy.Cretteville +-- * AIRBASE.Normandy.Cricqueville_en_Bessin +-- * AIRBASE.Normandy.Deanland +-- * AIRBASE.Normandy.Deauville +-- * AIRBASE.Normandy.Detling +-- * AIRBASE.Normandy.Deux_Jumeaux +-- * AIRBASE.Normandy.Dinan_Trelivan +-- * AIRBASE.Normandy.Dunkirk_Mardyck +-- * AIRBASE.Normandy.Essay +-- * AIRBASE.Normandy.Evreux +-- * AIRBASE.Normandy.Farnborough +-- * AIRBASE.Normandy.Fecamp_Benouville +-- * AIRBASE.Normandy.Flers +-- * AIRBASE.Normandy.Ford +-- * AIRBASE.Normandy.Friston +-- * AIRBASE.Normandy.Funtington +-- * AIRBASE.Normandy.Goulet +-- * AIRBASE.Normandy.Gravesend +-- * AIRBASE.Normandy.Guyancourt +-- * AIRBASE.Normandy.Hauterive +-- * AIRBASE.Normandy.Heathrow +-- * AIRBASE.Normandy.High_Halden +-- * AIRBASE.Normandy.Kenley +-- * AIRBASE.Normandy.Lantheuil +-- * AIRBASE.Normandy.Le_Molay +-- * AIRBASE.Normandy.Lessay +-- * AIRBASE.Normandy.Lignerolles +-- * AIRBASE.Normandy.Longues_sur_Mer +-- * AIRBASE.Normandy.Lonrai +-- * AIRBASE.Normandy.Lymington +-- * AIRBASE.Normandy.Lympne +-- * AIRBASE.Normandy.Manston +-- * AIRBASE.Normandy.Maupertus +-- * AIRBASE.Normandy.Meautis +-- * AIRBASE.Normandy.Merville_Calonne +-- * AIRBASE.Normandy.Needs_Oar_Point +-- * AIRBASE.Normandy.Odiham +-- * AIRBASE.Normandy.Orly +-- * AIRBASE.Normandy.Picauville +-- * AIRBASE.Normandy.Poix +-- * AIRBASE.Normandy.Ronai +-- * AIRBASE.Normandy.Rouen_Boos +-- * AIRBASE.Normandy.Rucqueville +-- * AIRBASE.Normandy.Saint_Andre_de_lEure +-- * AIRBASE.Normandy.Saint_Aubin +-- * AIRBASE.Normandy.Saint_Omer_Wizernes +-- * AIRBASE.Normandy.Saint_Pierre_du_Mont +-- * AIRBASE.Normandy.Sainte_Croix_sur_Mer +-- * AIRBASE.Normandy.Sainte_Laurent_sur_Mer +-- * AIRBASE.Normandy.Sommervieu +-- * AIRBASE.Normandy.Stoney_Cross +-- * AIRBASE.Normandy.Tangmere +-- * AIRBASE.Normandy.Triqueville +-- * AIRBASE.Normandy.Villacoublay +-- * AIRBASE.Normandy.Vrigny +-- * AIRBASE.Normandy.West_Malling -- -- @field Normandy AIRBASE.Normandy = { - ["Saint_Pierre_du_Mont"] = "Saint Pierre du Mont", - ["Lignerolles"] = "Lignerolles", - ["Cretteville"] = "Cretteville", - ["Maupertus"] = "Maupertus", - ["Brucheville"] = "Brucheville", - ["Meautis"] = "Meautis", - ["Cricqueville_en_Bessin"] = "Cricqueville-en-Bessin", - ["Lessay"] = "Lessay", - ["Sainte_Laurent_sur_Mer"] = "Sainte-Laurent-sur-Mer", - ["Biniville"] = "Biniville", - ["Cardonville"] = "Cardonville", - ["Deux_Jumeaux"] = "Deux Jumeaux", - ["Chippelle"] = "Chippelle", - ["Beuzeville"] = "Beuzeville", - ["Azeville"] = "Azeville", - ["Picauville"] = "Picauville", - ["Le_Molay"] = "Le Molay", - ["Longues_sur_Mer"] = "Longues-sur-Mer", - ["Carpiquet"] = "Carpiquet", - ["Bazenville"] = "Bazenville", - ["Sainte_Croix_sur_Mer"] = "Sainte-Croix-sur-Mer", - ["Beny_sur_Mer"] = "Beny-sur-Mer", - ["Rucqueville"] = "Rucqueville", - ["Sommervieu"] = "Sommervieu", - ["Lantheuil"] = "Lantheuil", - ["Evreux"] = "Evreux", - ["Chailey"] = "Chailey", - ["Needs_Oar_Point"] = "Needs Oar Point", - ["Funtington"] = "Funtington", - ["Tangmere"] = "Tangmere", - ["Ford"] = "Ford", + ["Abbeville_Drucat"] = "Abbeville Drucat", + ["Amiens_Glisy"] = "Amiens-Glisy", ["Argentan"] = "Argentan", - ["Goulet"] = "Goulet", + ["Avranches_Le_Val_Saint_Pere"] = "Avranches Le Val-Saint-Pere", + ["Azeville"] = "Azeville", ["Barville"] = "Barville", - ["Essay"] = "Essay", - ["Hauterive"] = "Hauterive", - ["Lymington"] = "Lymington", - ["Vrigny"] = "Vrigny", - ["Odiham"] = "Odiham", - ["Conches"] = "Conches", - ["West_Malling"] = "West Malling", - ["Villacoublay"] = "Villacoublay", - ["Kenley"] = "Kenley", + ["Bazenville"] = "Bazenville", + ["Beaumont_le_Roger"] = "Beaumont-le-Roger", ["Beauvais_Tille"] = "Beauvais-Tille", + ["Beny_sur_Mer"] = "Beny-sur-Mer", + ["Bernay_Saint_Martin"] = "Bernay Saint Martin", + ["Beuzeville"] = "Beuzeville", + ["Biggin_Hill"] = "Biggin Hill", + ["Biniville"] = "Biniville", + ["Broglie"] = "Broglie", + ["Brucheville"] = "Brucheville", + ["Cardonville"] = "Cardonville", + ["Carpiquet"] = "Carpiquet", + ["Chailey"] = "Chailey", + ["Chippelle"] = "Chippelle", + ["Conches"] = "Conches", ["Cormeilles_en_Vexin"] = "Cormeilles-en-Vexin", ["Creil"] = "Creil", - ["Guyancourt"] = "Guyancourt", - ["Lonrai"] = "Lonrai", - ["Dinan_Trelivan"] = "Dinan-Trelivan", - ["Heathrow"] = "Heathrow", - ["Fecamp_Benouville"] = "Fecamp-Benouville", - ["Farnborough"] = "Farnborough", - ["Friston"] = "Friston", + ["Cretteville"] = "Cretteville", + ["Cricqueville_en_Bessin"] = "Cricqueville-en-Bessin", ["Deanland"] = "Deanland", - ["Triqueville"] = "Triqueville", - ["Poix"] = "Poix", + ["Deauville"] = "Deauville", + ["Detling"] = "Detling", + ["Deux_Jumeaux"] = "Deux Jumeaux", + ["Dinan_Trelivan"] = "Dinan-Trelivan", + ["Dunkirk_Mardyck"] = "Dunkirk-Mardyck", + ["Essay"] = "Essay", + ["Evreux"] = "Evreux", + ["Farnborough"] = "Farnborough", + ["Fecamp_Benouville"] = "Fecamp-Benouville", + ["Flers"] = "Flers", + ["Ford"] = "Ford", + ["Friston"] = "Friston", + ["Funtington"] = "Funtington", + ["Goulet"] = "Goulet", + ["Gravesend"] = "Gravesend", + ["Guyancourt"] = "Guyancourt", + ["Hauterive"] = "Hauterive", + ["Heathrow"] = "Heathrow", + ["High_Halden"] = "High Halden", + ["Kenley"] = "Kenley", + ["Lantheuil"] = "Lantheuil", + ["Le_Molay"] = "Le Molay", + ["Lessay"] = "Lessay", + ["Lignerolles"] = "Lignerolles", + ["Longues_sur_Mer"] = "Longues-sur-Mer", + ["Lonrai"] = "Lonrai", + ["Lymington"] = "Lymington", + ["Lympne"] = "Lympne", + ["Manston"] = "Manston", + ["Maupertus"] = "Maupertus", + ["Meautis"] = "Meautis", + ["Merville_Calonne"] = "Merville Calonne", + ["Needs_Oar_Point"] = "Needs Oar Point", + ["Odiham"] = "Odiham", ["Orly"] = "Orly", - ["Stoney_Cross"] = "Stoney Cross", - ["Amiens_Glisy"] = "Amiens-Glisy", + ["Picauville"] = "Picauville", + ["Poix"] = "Poix", ["Ronai"] = "Ronai", ["Rouen_Boos"] = "Rouen-Boos", - ["Deauville"] = "Deauville", + ["Rucqueville"] = "Rucqueville", + ["Saint_Andre_de_lEure"] = "Saint-Andre-de-lEure", ["Saint_Aubin"] = "Saint-Aubin", - ["Flers"] = "Flers", - ["Avranches_Le_Val_Saint_Pere"] = "Avranches Le Val-Saint-Pere", - ["Gravesend"] = "Gravesend", - ["Beaumont_le_Roger"] = "Beaumont-le-Roger", - ["Broglie"] = "Broglie", - ["Bernay_Saint_Martin"] = "Bernay Saint Martin", - ["Saint_Andre_de_lEure"] = "Saint-Andre-de-lEure", - ["Biggin_Hill"] = "Biggin Hill", - ["Manston"] = "Manston", - ["Detling"] = "Detling", - ["Lympne"] = "Lympne", - ["Abbeville_Drucat"] = "Abbeville Drucat", - ["Merville_Calonne"] = "Merville Calonne", ["Saint_Omer_Wizernes"] = "Saint-Omer Wizernes", + ["Saint_Pierre_du_Mont"] = "Saint Pierre du Mont", + ["Sainte_Croix_sur_Mer"] = "Sainte-Croix-sur-Mer", + ["Sainte_Laurent_sur_Mer"] = "Sainte-Laurent-sur-Mer", + ["Sommervieu"] = "Sommervieu", + ["Stoney_Cross"] = "Stoney Cross", + ["Tangmere"] = "Tangmere", + ["Triqueville"] = "Triqueville", + ["Villacoublay"] = "Villacoublay", + ["Vrigny"] = "Vrigny", + ["West_Malling"] = "West Malling", } --- Airbases of the Persion Gulf Map: @@ -359,7 +363,7 @@ AIRBASE.Normandy = { -- * AIRBASE.PersianGulf.Sirri_Island -- * AIRBASE.PersianGulf.Tunb_Island_AFB -- * AIRBASE.PersianGulf.Tunb_Kochak --- +-- -- @field PersianGulf AIRBASE.PersianGulf = { ["Abu_Dhabi_International_Airport"] = "Abu Dhabi Intl", @@ -407,7 +411,7 @@ AIRBASE.PersianGulf = { -- * AIRBASE.TheChannel.Biggin_Hill -- * AIRBASE.TheChannel.Eastchurch -- * AIRBASE.TheChannel.Headcorn --- +-- -- @field TheChannel AIRBASE.TheChannel = { ["Abbeville_Drucat"] = "Abbeville Drucat", @@ -610,7 +614,7 @@ AIRBASE.MarianaIslands = { -- * AIRBASE.SouthAtlantic.Goose_Green -- * AIRBASE.SouthAtlantic.Hipico -- * AIRBASE.SouthAtlantic.CaletaTortel --- +-- --@field MarianaIslands AIRBASE.SouthAtlantic={ ["Port_Stanley"]="Port Stanley", @@ -674,7 +678,7 @@ AIRBASE.SouthAtlantic={ -- * AIRBASE.Sinai.Ben_Gurion -- * AIRBASE.Sinai.Bir_Hasanah -- * AIRBASE.Sinai.Cairo_West --- +-- -- @field Sinai AIRBASE.Sinai = { ["Hatzerim"] = "Hatzerim", @@ -812,13 +816,13 @@ function AIRBASE:Register(AirbaseName) -- Category. self.category=self.descriptors and self.descriptors.category or Airbase.Category.AIRDROME - + -- H2 is bugged --if self.AirbaseName == "H4" and self.descriptors == nil then --self:E("***** H4 on Syria map is currently bugged!") --return nil --end - + -- Set category. if self.category==Airbase.Category.AIRDROME then self.isAirdrome=true @@ -836,10 +840,10 @@ function AIRBASE:Register(AirbaseName) else self:E("ERROR: Unknown airbase category!") end - + -- Init Runways. self:_InitRunways() - + -- Set the active runways based on wind direction. if self.isAirdrome then self:SetActiveRunway() @@ -853,7 +857,7 @@ function AIRBASE:Register(AirbaseName) -- Init coordinate. self:GetCoordinate() - + -- Storage. self.storage=_DATABASE:AddStorage(AirbaseName) @@ -869,7 +873,7 @@ function AIRBASE:Register(AirbaseName) else self:E(string.format("ERROR: Cound not get position Vec2 of airbase %s", AirbaseName)) end - + -- Debug info. self:T2(string.format("Registered airbase %s", tostring(self.AirbaseName))) @@ -955,7 +959,7 @@ function AIRBASE:GetWarehouse() return warehouse end ---- Get the warehouse storage of this airbase. The returned `STORAGE` object is the wrapper of the DCS warehouse. +--- Get the warehouse storage of this airbase. The returned `STORAGE` object is the wrapper of the DCS warehouse. -- This allows you to add and remove items such as aircraft, liquids, weapons and other equipment. -- @param #AIRBASE self -- @return Wrapper.Storage#STORAGE The storage. @@ -970,7 +974,7 @@ end function AIRBASE:SetAutoCapture(Switch) local airbase=self:GetDCSObject() - + if airbase then airbase:autoCapture(Switch) end @@ -996,11 +1000,11 @@ end --- Returns whether auto capturing of the airbase is on or off. -- @param #AIRBASE self --- @return #boolean Returns `true` if auto capturing is on, `false` if off and `nil` if the airbase object cannot be retrieved. +-- @return #boolean Returns `true` if auto capturing is on, `false` if off and `nil` if the airbase object cannot be retrieved. function AIRBASE:IsAutoCapture() local airbase=self:GetDCSObject() - + local auto=nil if airbase then auto=airbase:autoCaptureIsOn() @@ -1016,11 +1020,11 @@ end function AIRBASE:SetCoalition(Coal) local airbase=self:GetDCSObject() - + if airbase then airbase:setCoalition(Coal) end - + return self end @@ -1148,7 +1152,7 @@ function AIRBASE:SetParkingSpotBlacklist(TerminalIdBlacklist) return self end ---- Sets the ATC belonging to an airbase object to be silent and unresponsive. This is useful for disabling the award winning ATC behavior in DCS. +--- Sets the ATC belonging to an airbase object to be silent and unresponsive. This is useful for disabling the award winning ATC behavior in DCS. -- Note that this DOES NOT remove the airbase from the list. It just makes it unresponsive and silent to any radio calls to it. -- @param #AIRBASE self -- @param #boolean Silent If `true`, enable silent mode. If `false` or `nil`, disable silent mode. @@ -1157,12 +1161,12 @@ function AIRBASE:SetRadioSilentMode(Silent) -- Get DCS airbase object. local airbase=self:GetDCSObject() - + -- Set mode. if airbase then airbase:setRadioSilentMode(Silent) end - + return self end @@ -1170,18 +1174,18 @@ end -- @param #AIRBASE self -- @return #boolean If `true`, silent mode is enabled. function AIRBASE:GetRadioSilentMode() - + -- Is silent? local silent=nil -- Get DCS airbase object. local airbase=self:GetDCSObject() - + -- Set mode. if airbase then silent=airbase:getRadioSilentMode() end - + return silent end @@ -1365,7 +1369,7 @@ function AIRBASE:_InitParkingSpots() self.NparkingTerminal[terminalType]=0 end - -- Get client coordinates. + -- Get client coordinates. local function isClient(coord) local clients=_DATABASE.CLIENTS for clientname, _client in pairs(clients) do @@ -1854,7 +1858,7 @@ end -- @param #string Name Name of the runway, e.g. "31" or "21L". -- @return #AIRBASE.Runway Runway data. function AIRBASE:GetRunwayByName(Name) - + if Name==nil then return end @@ -1862,10 +1866,10 @@ function AIRBASE:GetRunwayByName(Name) if Name then for _,_runway in pairs(self.runways) do local runway=_runway --#AIRBASE.Runway - + -- Name including L or R, e.g. "31L". local name=self:GetRunwayName(runway) - + if name==Name:upper() then return runway end @@ -1894,19 +1898,19 @@ function AIRBASE:_InitRunways(IncludeInverse) self.runways={} return {} end - + --- Function to create a runway data table. local function _createRunway(name, course, width, length, center) -- Bearing in rad. local bearing=-1*course - + -- Heading in degrees. local heading=math.deg(bearing) - + -- Data table. local runway={} --#AIRBASE.Runway - + local namefromheading = math.floor(heading/10) if self.AirbaseName == AIRBASE.Syria.Beirut_Rafic_Hariri and math.abs(namefromheading-name) > 1 then @@ -1914,12 +1918,12 @@ function AIRBASE:_InitRunways(IncludeInverse) else runway.name=string.format("%02d", tonumber(name)) end - + --runway.name=string.format("%02d", tonumber(name)) runway.magheading=tonumber(runway.name)*10 runway.heading=heading runway.width=width or 0 - runway.length=length or 0 + runway.length=length or 0 runway.center=COORDINATE:NewFromVec3(center) -- Ensure heading is [0,360] @@ -1928,7 +1932,7 @@ function AIRBASE:_InitRunways(IncludeInverse) elseif runway.heading<0 then runway.heading=runway.heading+360 end - + -- For example at Nellis, DCS reports two runways, i.e. 03 and 21, BUT the "course" of both is -0.700 rad = 40 deg! -- As a workaround, I check the difference between the "magnetic" heading derived from the name and the true heading. -- If this is too large then very likely the "inverse" heading is the one we are looking for. @@ -1936,31 +1940,31 @@ function AIRBASE:_InitRunways(IncludeInverse) self:T(string.format("WARNING: Runway %s: heading=%.1f magheading=%.1f", runway.name, runway.heading, runway.magheading)) runway.heading=runway.heading-180 end - + -- Ensure heading is [0,360] if runway.heading>360 then runway.heading=runway.heading-360 elseif runway.heading<0 then runway.heading=runway.heading+360 end - + -- Start and endpoint of runway. runway.position=runway.center:Translate(-runway.length/2, runway.heading) runway.endpoint=runway.center:Translate( runway.length/2, runway.heading) - + local init=runway.center:GetVec3() local width = runway.width/2 local L2=runway.length/2 - + local offset1 = {x = init.x + (math.cos(bearing + math.pi) * L2), y = init.z + (math.sin(bearing + math.pi) * L2)} local offset2 = {x = init.x - (math.cos(bearing + math.pi) * L2), y = init.z - (math.sin(bearing + math.pi) * L2)} - + local points={} points[1] = {x = offset1.x + (math.cos(bearing + (math.pi/2)) * width), y = offset1.y + (math.sin(bearing + (math.pi/2)) * width)} points[2] = {x = offset1.x + (math.cos(bearing - (math.pi/2)) * width), y = offset1.y + (math.sin(bearing - (math.pi/2)) * width)} points[3] = {x = offset2.x + (math.cos(bearing - (math.pi/2)) * width), y = offset2.y + (math.sin(bearing - (math.pi/2)) * width)} points[4] = {x = offset2.x + (math.cos(bearing + (math.pi/2)) * width), y = offset2.y + (math.sin(bearing + (math.pi/2)) * width)} - + -- Runway zone. runway.zone=ZONE_POLYGON_BASE:New(string.format("%s Runway %s", self.AirbaseName, runway.name), points) @@ -1970,54 +1974,54 @@ function AIRBASE:_InitRunways(IncludeInverse) -- Get DCS object. local airbase=self:GetDCSObject() - + if airbase then - + -- Get DCS runways. local runways=airbase:getRunways() - + -- Debug info. self:T2(runways) - + if runways then - + -- Loop over runways. for _,rwy in pairs(runways) do - + -- Debug info. self:T(rwy) - + -- Get runway data. local runway=_createRunway(rwy.Name, rwy.course, rwy.width, rwy.length, rwy.position) --#AIRBASE.Runway - + -- Add to table. table.insert(Runways, runway) - + -- Include "inverse" runway. if IncludeInverse then - + -- Create "inverse". local idx=tonumber(runway.name) local name2=tostring(idx-18) if idx<18 then name2=tostring(idx+18) end - + -- Create "inverse" runway. local runway=_createRunway(name2, rwy.course-math.pi, rwy.width, rwy.length, rwy.position) --#AIRBASE.Runway - + -- Add inverse to table. table.insert(Runways, runway) - + end - + end - + end - + end - + -- Look for identical (parallel) runways, e.g. 03L and 03R at Nellis. local rpairs={} for i,_ri in pairs(Runways) do @@ -2031,44 +2035,44 @@ function AIRBASE:_InitRunways(IncludeInverse) end end end - + local function isLeft(a, b, c) --return ((b.x - a.x)*(c.z - a.z) - (b.z - a.z)*(c.x - a.x)) > 0 return ((b.z - a.z)*(c.x - a.x) - (b.x - a.x)*(c.z - a.z)) > 0 end - + for i,j in pairs(rpairs) do local ri=Runways[i] --#AIRBASE.Runway local rj=Runways[j] --#AIRBASE.Runway - + -- Draw arrow. --ri.center:ArrowToAll(rj.center) - + local c0=ri.center - + -- Vector in the direction of the runway. local a=UTILS.VecTranslate(c0, 1000, ri.heading) - + -- Vector from runway i to runway j. - local b=UTILS.VecSubstract(rj.center, ri.center) + local b=UTILS.VecSubstract(rj.center, ri.center) b=UTILS.VecAdd(ri.center, b) - + -- Check if rj is left of ri. local left=isLeft(c0, a, b) - + --env.info(string.format("Found pair %s: i=%d, j=%d, left==%s", ri.name, i, j, tostring(left))) - + if left then ri.isLeft=false rj.isLeft=true else ri.isLeft=true - rj.isLeft=false + rj.isLeft=false end - + --break end - + -- Set runways. self.runways=Runways @@ -2248,9 +2252,9 @@ end -- @param #string Name Name of the runway, e.g. "31" or "02L" or "90R". If not given, the runway is determined from the wind direction. -- @param #boolean PreferLeft If `true`, perfer the left runway. If `false`, prefer the right runway. If `nil` (default), do not care about left or right. function AIRBASE:SetActiveRunway(Name, PreferLeft) - + self:SetActiveRunwayTakeoff(Name, PreferLeft) - + self:SetActiveRunwayLanding(Name,PreferLeft) end @@ -2263,17 +2267,17 @@ end function AIRBASE:SetActiveRunwayLanding(Name, PreferLeft) local runway=self:GetRunwayByName(Name) - + if not runway then runway=self:GetRunwayIntoWind(PreferLeft) end - + if runway then self:T(string.format("%s: Setting active runway for landing as %s", self.AirbaseName, self:GetRunwayName(runway))) else self:E("ERROR: Could not set the runway for landing!") end - + self.runwayLanding=runway return runway @@ -2311,17 +2315,17 @@ end function AIRBASE:SetActiveRunwayTakeoff(Name, PreferLeft) local runway=self:GetRunwayByName(Name) - + if not runway then runway=self:GetRunwayIntoWind(PreferLeft) end - + if runway then self:T(string.format("%s: Setting active runway for takeoff as %s", self.AirbaseName, self:GetRunwayName(runway))) else self:E("ERROR: Could not set the runway for takeoff!") end - + self.runwayTakeoff=runway return runway @@ -2334,7 +2338,7 @@ end -- @param #boolean PreferLeft If `true`, perfer the left runway. If `false`, prefer the right runway. If `nil` (default), do not care about left or right. -- @return #AIRBASE.Runway Active runway data table. function AIRBASE:GetRunwayIntoWind(PreferLeft) - + -- Get runway data. local runways=self:GetRunways() @@ -2357,24 +2361,24 @@ function AIRBASE:GetRunwayIntoWind(PreferLeft) local dotmin=nil for i,_runway in pairs(runways) do local runway=_runway --#AIRBASE.Runway - + if PreferLeft==nil or PreferLeft==runway.isLeft then -- Angle in rad. local alpha=math.rad(runway.heading) - + -- Runway vector. local Vrunway={x=math.cos(alpha), y=0, z=math.sin(alpha)} - + -- Dot product: parallel component of the two vectors. local dot=UTILS.VecDot(Vwind, Vrunway) - + -- New min? if dotmin==nil or dot Date: Thu, 29 Feb 2024 09:54:12 +0100 Subject: [PATCH 4/4] #SCORING Additions for SETs --- .../Moose/Functional/Scoring.lua | 44 +++++++++++++++---- Moose Development/Moose/Wrapper/Unit.lua | 2 + 2 files changed, 38 insertions(+), 8 deletions(-) diff --git a/Moose Development/Moose/Functional/Scoring.lua b/Moose Development/Moose/Functional/Scoring.lua index b8c7218cf..625103d9e 100644 --- a/Moose Development/Moose/Functional/Scoring.lua +++ b/Moose Development/Moose/Functional/Scoring.lua @@ -78,7 +78,8 @@ -- ### Authors: **FlightControl** -- -- ### Contributions: --- +-- +-- * **Applevangelist**: Additional functionality, fixes. -- * **Wingthor (TAW)**: Testing & Advice. -- * **Dutch-Baron (TAW)**: Testing & Advice. -- * **Whisper**: Testing and Advice. @@ -116,11 +117,13 @@ -- Special targets can be set that will give extra scores to the players when these are destroyed. -- Use the methods @{#SCORING.AddUnitScore}() and @{#SCORING.RemoveUnitScore}() to specify a special additional score for a specific @{Wrapper.Unit}s. -- Use the methods @{#SCORING.AddStaticScore}() and @{#SCORING.RemoveStaticScore}() to specify a special additional score for a specific @{Wrapper.Static}s. --- Use the method @{#SCORING.SetGroupGroup}() to specify a special additional score for a specific @{Wrapper.Group}s. +-- Use the method @{#SCORING.AddScoreSetGroup}() to specify a special additional score for a specific @{Wrapper.Group}s gathered in a @{Core.Set#SET_GROUP}. -- -- local Scoring = SCORING:New( "Scoring File" ) -- Scoring:AddUnitScore( UNIT:FindByName( "Unit #001" ), 200 ) -- Scoring:AddStaticScore( STATIC:FindByName( "Static #1" ), 100 ) +-- local GroupSet = SET_GROUP:New():FilterPrefixes("RAT"):FilterStart() +-- Scoring:AddScoreSetGroup( GroupSet, 100) -- -- The above grants an additional score of 200 points for Unit #001 and an additional 100 points of Static #1 if these are destroyed. -- Note that later in the mission, one can remove these scores set, for example, when the a goal achievement time limit is over. @@ -226,7 +229,7 @@ SCORING = { ClassID = 0, Players = {}, AutoSave = true, - version = "1.17.1" + version = "1.18.1" } local _SCORINGCoalition = { @@ -428,6 +431,31 @@ function SCORING:AddScoreGroup( ScoreGroup, Score ) return self end +--- Specify a special additional score for a @{Core.Set#SET_GROUP}. +-- @param #SCORING self +-- @param Core.Set#SET_GROUP Set The @{Core.Set#SET_GROUP} for which each @{Wrapper.Unit} in each Group a Score is given. +-- @param #number Score The Score value. +-- @return #SCORING +function SCORING:AddScoreSetGroup(Set, Score) + local set = Set:GetSetObjects() + + for _,_group in pairs (set) do + if _group and _group:IsAlive() then + self:AddScoreGroup(_group,Score) + end + end + + local function AddScore(group) + self:AddScoreGroup(group,Score) + end + + function Set:OnAfterAdded(From,Event,To,ObjectName,Object) + AddScore(Object) + end + + return self +end + --- Add a @{Core.Zone} to define additional scoring when any object is destroyed in that zone. -- Note that if a @{Core.Zone} with the same name is already within the scoring added, the @{Core.Zone} (type) and Score will be replaced! -- This allows for a dynamic destruction zone evolution within your mission. @@ -1935,9 +1963,9 @@ end --- Handles the event when one player kill another player -- @param #SCORING self --- @param #PLAYER Player the ataching player +-- @param #Wrapper.Client#CLIENT Player the atacking player -- @param #string TargetPlayerName the name of the killed player --- @param #bool IsTeamKill true if this kill was a team kill +-- @param #boolean IsTeamKill true if this kill was a team kill -- @param #number TargetThreatLevel Thread level of the target -- @param #number PlayerThreatLevelThread level of the player -- @param #number Score The score based on both threat levels @@ -1946,12 +1974,12 @@ function SCORING:OnKillPvP(Player, TargetPlayerName, IsTeamKill, TargetThreatLev end --- Handles the event when one player kill another player -- @param #SCORING self --- @param #PLAYER Player the ataching player +-- @param #Wrapper.Client#CLIENT Player the atacking player -- @param #string TargetUnitName the name of the killed unit --- @param #bool IsTeamKill true if this kill was a team kill +-- @param #boolean IsTeamKill true if this kill was a team kill -- @param #number TargetThreatLevel Thread level of the target -- @param #number PlayerThreatLevelThread level of the player -- @param #number Score The score based on both threat levels function SCORING:OnKillPvE(Player, TargetUnitName, IsTeamKill, TargetThreatLevel, PlayerThreatLevel, Score) -end \ No newline at end of file +end diff --git a/Moose Development/Moose/Wrapper/Unit.lua b/Moose Development/Moose/Wrapper/Unit.lua index 322d23540..781c71d48 100644 --- a/Moose Development/Moose/Wrapper/Unit.lua +++ b/Moose Development/Moose/Wrapper/Unit.lua @@ -1244,7 +1244,9 @@ function UNIT:GetThreatLevel() if Attributes["Fighters"] then ThreatLevel = 10 elseif Attributes["Multirole fighters"] then ThreatLevel = 9 + elseif Attributes["Interceptors"] then ThreatLevel = 9 elseif Attributes["Battleplanes"] then ThreatLevel = 8 + elseif Attributes["Battle airplanes"] then ThreatLevel = 8 elseif Attributes["Attack helicopters"] then ThreatLevel = 7 elseif Attributes["Strategic bombers"] then ThreatLevel = 6 elseif Attributes["Bombers"] then ThreatLevel = 5