diff --git a/DCS_Kola/Operation_Polar_Shield/F99th-Operation_Polar_Shield_1.0.8.miz b/DCS_Kola/Operation_Polar_Shield/F99th-Operation_Polar_Shield_1.0.8.miz index 2ca9e1e..776467a 100644 Binary files a/DCS_Kola/Operation_Polar_Shield/F99th-Operation_Polar_Shield_1.0.8.miz and b/DCS_Kola/Operation_Polar_Shield/F99th-Operation_Polar_Shield_1.0.8.miz differ diff --git a/DCS_Kola/Operation_Polar_Shield/F99th-Operation_Polar_Shield_1.0.9.miz b/DCS_Kola/Operation_Polar_Shield/F99th-Operation_Polar_Shield_1.0.9.miz index 8948fde..776467a 100644 Binary files a/DCS_Kola/Operation_Polar_Shield/F99th-Operation_Polar_Shield_1.0.9.miz and b/DCS_Kola/Operation_Polar_Shield/F99th-Operation_Polar_Shield_1.0.9.miz differ diff --git a/DCS_Kola/Operation_Polar_Shield/F99th-Operation_Polar_Shield_1.1.1.miz b/DCS_Kola/Operation_Polar_Shield/F99th-Operation_Polar_Shield_1.1.1.miz index fd3cba2..aa91e18 100644 Binary files a/DCS_Kola/Operation_Polar_Shield/F99th-Operation_Polar_Shield_1.1.1.miz and b/DCS_Kola/Operation_Polar_Shield/F99th-Operation_Polar_Shield_1.1.1.miz differ diff --git a/DCS_Kola/Operation_Polar_Shield/F99th-Operation_Polar_Shield_1.1.2.miz b/DCS_Kola/Operation_Polar_Shield/F99th-Operation_Polar_Shield_1.1.2.miz index 73fff72..aa91e18 100644 Binary files a/DCS_Kola/Operation_Polar_Shield/F99th-Operation_Polar_Shield_1.1.2.miz and b/DCS_Kola/Operation_Polar_Shield/F99th-Operation_Polar_Shield_1.1.2.miz differ diff --git a/DCS_Kola/Operation_Polar_Shield/F99th-Operation_Polar_Shield_1.1.3.miz b/DCS_Kola/Operation_Polar_Shield/F99th-Operation_Polar_Shield_1.1.3.miz new file mode 100644 index 0000000..aa91e18 Binary files /dev/null and b/DCS_Kola/Operation_Polar_Shield/F99th-Operation_Polar_Shield_1.1.3.miz differ diff --git a/Moose_.lua b/Moose_.lua index a0357ab..8bbc66d 100644 --- a/Moose_.lua +++ b/Moose_.lua @@ -1,4 +1,4 @@ -env.info('*** MOOSE GITHUB Commit Hash ID: 2025-10-01T15:26:49+02:00-d8281b01032aa44a579c14d690ca79413b671d9d ***') +env.info('*** MOOSE GITHUB Commit Hash ID: 2025-10-26T07:31:50+01:00-1965e24860936512d2670eb7d41c8440707a12ff ***') if not MOOSE_DEVELOPMENT_FOLDER then MOOSE_DEVELOPMENT_FOLDER='Scripts' end @@ -469,6 +469,7 @@ CH47={}, OH58={}, UH1H={}, AH64D={}, +UH60L={}, } } ENUMS.Storage.weapons.nurs.SNEB_TYPE253_F1B="weapons.nurs.SNEB_TYPE253_F1B" @@ -1172,6 +1173,24 @@ ENUMS.Storage.weapons.UH1H.M134_MiniGun_Right_Door={4,15,46,175} ENUMS.Storage.weapons.UH1H.M60_MG_Right_Door={4,15,46,177} ENUMS.Storage.weapons.UH1H.M134_MiniGun_Left_Door={4,15,46,174} ENUMS.Storage.weapons.UH1H.M60_MG_Left_Door={4,15,46,176} +ENUMS.Storage.weapons.UH60L.M151_HYDRA={4,7,33,147} +ENUMS.Storage.weapons.UH60L.M156_HYDRA={4,7,33,148} +ENUMS.Storage.weapons.UH60L.M229_HYDRA={4,7,33,148} +ENUMS.Storage.weapons.UH60L.M257_HYDRA={4,7,33,151} +ENUMS.Storage.weapons.UH60L.M259_HYDRA={4,7,33,151} +ENUMS.Storage.weapons.UH60L.M274_HYDRA={4,7,33,150} +ENUMS.Storage.weapons.UH60L.M134_DOOR_GUN={4,15,46,3031} +ENUMS.Storage.weapons.UH60L.M3M={4,15,46,2496} +ENUMS.Storage.weapons.UH60L.M3M_DOOR_GUN={4,15,46,3032} +ENUMS.Storage.weapons.UH60L.M60_DOOR_GUN={4,15,46,3033} +ENUMS.Storage.weapons.UH60L.FUEL_TANK_200={1,3,43,3023} +ENUMS.Storage.weapons.UH60L.FUEL_TANK_230={1,3,43,3024} +ENUMS.Storage.weapons.UH60L.FUEL_TANK_450={1,3,43,3025} +ENUMS.Storage.weapons.UH60L.FUEL_TANK_DUAL_AUX={1,3,43,3026} +ENUMS.Storage.weapons.UH60L.CARGO_SEAT_REAR_ROW={1,3,43,3030} +ENUMS.Storage.weapons.UH60L.CARGO_SEAT_THREE_ROWS={1,3,43,3029} +ENUMS.Storage.weapons.UH60L.EMPTY_GUNNER_SEAT_1={1,3,43,3027} +ENUMS.Storage.weapons.UH60L.EMPTY_GUNNER_SEAT_2={1,3,43,3028} ENUMS.Storage.weapons.OH58.FIM92={4,4,7,449} ENUMS.Storage.weapons.OH58.MG_M3P100={4,15,46,2611} ENUMS.Storage.weapons.OH58.MG_M3P200={4,15,46,2610} @@ -2659,6 +2678,14 @@ if type_name=="UH-60L"and(unit:getDrawArgumentValue(38)>0 or unit:getDrawArgumen BASE:T(unit_name.." front door(s) are open") return true end +if type_name=="UH-60L_DAP"and(unit:getDrawArgumentValue(401)==1 or unit:getDrawArgumentValue(402)==1)then +BASE:T(unit_name.." cargo door is open") +return true +end +if type_name=="UH-60L_DAP"and(unit:getDrawArgumentValue(38)>0 or unit:getDrawArgumentValue(400)==1)then +BASE:T(unit_name.." front door(s) are open") +return true +end if type_name=="AH-64D_BLK_II"then BASE:T(unit_name.." front door(s) are open") return true @@ -3860,7 +3887,55 @@ end UTILS.lcg.seed=(UTILS.lcg.a*UTILS.lcg.seed+UTILS.lcg.c)%UTILS.lcg.m return UTILS.lcg.seed/UTILS.lcg.m end -function UTILS.SpawnFARPAndFunctionalStatics(Name,Coordinate,FARPType,Coalition,Country,CallSign,Frequency,Modulation,ADF,SpawnRadius,VehicleTemplate,Liquids,Equipment,Airframes,F10Text,DynamicSpawns,HotStart) +function UTILS.GenerateGridPoints(startVec2,n,spacingX,spacingY) +local points={} +local gridSize=math.ceil(math.sqrt(n)) +local count=0 +local n=n or 1 +local spacingX=spacingX or 100 +local spacingY=spacingY or 100 +local startX=startVec2.x or 100 +local startY=startVec2.y or 100 +for row=0,gridSize-1 do +for col=0,gridSize-1 do +if count>=n then +break +end +local point={ +x=startX+(col*spacingX), +y=startY+(row*spacingY) +} +table.insert(points,point) +count=count+1 +end +if count>=n then +break +end +end +return points +end +function UTILS.SpawnFARPAndFunctionalStatics(Name,Coordinate,FARPType,Coalition,Country,CallSign,Frequency,Modulation,ADF,SpawnRadius,VehicleTemplate,Liquids,Equipment,Airframes,F10Text,DynamicSpawns,HotStart,NumberPads,SpacingX,SpacingY) +local function PopulateStorage(Name,liquids,equip,airframes) +local newWH=STORAGE:New(Name) +if liquids and liquids>0 then +newWH:SetLiquid(STORAGE.Liquid.DIESEL,liquids) +newWH:SetLiquid(STORAGE.Liquid.GASOLINE,liquids) +newWH:SetLiquid(STORAGE.Liquid.JETFUEL,liquids) +newWH:SetLiquid(STORAGE.Liquid.MW50,liquids) +end +if equip and equip>0 then +for cat,nitem in pairs(ENUMS.Storage.weapons)do +for name,item in pairs(nitem)do +newWH:SetItem(item,equip) +end +end +end +if airframes and airframes>0 then +for typename in pairs(CSAR.AircraftType)do +newWH:SetItem(typename,airframes) +end +end +end local farplocation=Coordinate local farptype=FARPType or ENUMS.FARPType.FARP local Coalition=Coalition or coalition.side.BLUE @@ -3878,11 +3953,62 @@ local STypeName=statictypes.TypeName local SShapeName=statictypes.ShapeName local Country=Country or(Coalition==coalition.side.BLUE and country.id.USA or country.id.RUSSIA) local ReturnObjects={} +local NumberPads=NumberPads or 1 +local SpacingX=SpacingX or 100 +local SpacingY=SpacingY or 100 +local FarpVec2=Coordinate:GetVec2() +if NumberPads>1 then +local Grid=UTILS.GenerateGridPoints(FarpVec2,NumberPads,SpacingX,SpacingY) +local groupData={ +["visible"]=true, +["hidden"]=false, +["units"]={}, +["y"]=0, +["x"]=0, +["name"]=Name, +} +local unitData={ +["category"]="Heliports", +["type"]=STypeName, +["y"]=0, +["x"]=0, +["name"]=Name, +["heading"]=0, +["heliport_modulation"]=mod, +["heliport_frequency"]=freq, +["heliport_callsign_id"]=callsign, +["dead"]=false, +["shape_name"]=SShapeName, +["dynamicSpawn"]=DynamicSpawns, +["allowHotStart"]=HotStart, +} +for id,gridpoint in ipairs(Grid)do +local UnitTemplate=UTILS.DeepCopy(unitData) +UnitTemplate.x=gridpoint.x +UnitTemplate.y=gridpoint.y +UnitTemplate.name=Name.."-"..id +table.insert(groupData.units,UnitTemplate) +if id==1 then +groupData.x=gridpoint.x +groupData.y=gridpoint.y +end +end +local Static=coalition.addGroup(Country,-1,groupData) +local Event={ +id=EVENTS.Birth, +time=timer.getTime(), +initiator=Static +} +world.onEvent(Event) +PopulateStorage(Name.."-1",liquids,equip,airframes) +else local newfarp=SPAWNSTATIC:NewFromType(STypeName,"Heliports",Country) newfarp:InitShape(SShapeName) newfarp:InitFARP(callsign,freq,mod,DynamicSpawns,HotStart) local spawnedfarp=newfarp:SpawnFromCoordinate(farplocation,0,Name) table.insert(ReturnObjects,spawnedfarp) +PopulateStorage(Name,liquids,equip,airframes) +end local FARPStaticObjectsNato={ ["FUEL"]={TypeName="FARP Fuel Depot",ShapeName="GSM Rus",Category="Fortifications"}, ["AMMO"]={TypeName="FARP Ammo Dump Coating",ShapeName="SetkaKP",Category="Fortifications"}, @@ -3911,25 +4037,6 @@ vehicles:InitDelayOff() local spawnedvehicle=vehicles:SpawnFromCoordinate(vcoordinate) table.insert(ReturnObjects,spawnedvehicle) end -local newWH=STORAGE:New(Name) -if liquids and liquids>0 then -newWH:SetLiquid(STORAGE.Liquid.DIESEL,liquids) -newWH:SetLiquid(STORAGE.Liquid.GASOLINE,liquids) -newWH:SetLiquid(STORAGE.Liquid.JETFUEL,liquids) -newWH:SetLiquid(STORAGE.Liquid.MW50,liquids) -end -if equip and equip>0 then -for cat,nitem in pairs(ENUMS.Storage.weapons)do -for name,item in pairs(nitem)do -newWH:SetItem(item,equip) -end -end -end -if airframes and airframes>0 then -for typename in pairs(CSAR.AircraftType)do -newWH:SetItem(typename,airframes) -end -end local ADFName if ADF and type(ADF)=="number"then local ADFFreq=ADF*1000 @@ -9919,6 +10026,16 @@ self.LastVec2=ZoneUNIT:GetVec2() _EVENTDISPATCHER:CreateEventNewZone(self) return self end +function ZONE_UNIT:UpdateFromUnit(Unit) +if Unit and Unit:IsAlive()then +local vec2=Unit:GetVec2() +self.LastVec2=vec2 +elseif self.ZoneUNIT and self.ZoneUNIT:IsAlive()then +local ZoneVec2=self.ZoneUNIT:GetVec2() +self.LastVec2=ZoneVec2 +end +return self +end function ZONE_UNIT:GetVec2() local ZoneVec2=self.ZoneUNIT:GetVec2() if ZoneVec2 then @@ -9980,6 +10097,17 @@ ZoneVec2=self._.ZoneVec2Cache end return ZoneVec2 end +function ZONE_GROUP:UpdateFromGroup(Group) +if Group and Group:IsAlive()then +local vec2=Group:GetVec2() +self.Vec2=vec2 +elseif self._.ZoneGROUP and self._.ZoneGROUP:IsAlive()then +local ZoneVec2=self._.ZoneGROUP:GetVec2() +self.Vec2=ZoneVec2 +self._.ZoneVec2Cache=ZoneVec2 +end +return self +end function ZONE_GROUP:GetRandomVec2() local Point={} local Vec2=self._.ZoneGROUP:GetVec2() @@ -21723,7 +21851,7 @@ self:ScheduleOnce(0.3,self.SpawnFunctionHook,mystatic,unpack(self.SpawnFunctionA end if self.StaticCopyFrom~=nil then mystatic.StaticCopyFrom=self.StaticCopyFrom -if not _DATABASE.Templates.Statics[Template.name]then +end local TemplateGroup={} TemplateGroup.units={} TemplateGroup.units[1]=Template @@ -21731,8 +21859,6 @@ TemplateGroup.x=Template.x TemplateGroup.y=Template.y TemplateGroup.name=Template.name _DATABASE:_RegisterStaticTemplate(TemplateGroup,self.CoalitionID,self.CategoryID,CountryID) -end -end return mystatic end TIMER={ @@ -26216,6 +26342,46 @@ return self end return nil end +function CONTROLLABLE:OptionAAAMinFiringHeightMeters(meters) +self:F2({self.ControllableName}) +local meters=meters or 20 +local DCSControllable=self:GetDCSObject() +if DCSControllable then +local Controller=self:_GetController() +if Controller then +if self:IsGround()then +self:SetOption(27,meters) +end +end +return self +end +return nil +end +function CONTROLLABLE:OptionAAAMaxFiringHeightMeters(meters) +self:F2({self.ControllableName}) +local meters=meters or 1000 +local DCSControllable=self:GetDCSObject() +if DCSControllable then +local Controller=self:_GetController() +if Controller then +if self:IsGround()then +self:SetOption(29,meters) +end +end +return self +end +return nil +end +function CONTROLLABLE:OptionAAAMinFiringHeightFeet(feet) +self:F2({self.ControllableName}) +local feet=feet or 60 +return self:OptionAAAMinFiringHeightMeters(UTILS.FeetToMeters(feet)) +end +function CONTROLLABLE:OptionAAAMaxFiringHeightfeet(feet) +self:F2({self.ControllableName}) +local feet=feet or 3000 +return self:OptionAAAMaxFiringHeightMeters(UTILS.FeetToMeters(feet)) +end function CONTROLLABLE:OptionEngageRange(EngageRange) self:F2({self.ControllableName}) EngageRange=EngageRange or 100 @@ -31125,6 +31291,13 @@ end self:T2(string.format("Registered airbase %s",tostring(self.AirbaseName))) return self end +function AIRBASE:GetVec2() +local runways=self:GetRunways() +if runways and#runways>0 then +return runways[1].center:GetVec2() +end +return self:GetCoordinate():GetVec2() +end function AIRBASE:_GetCategory() local name=self.AirbaseName local static=StaticObject.getByName(name) @@ -33652,6 +33825,8 @@ REMOVED="REMOVED", } DYNAMICCARGO.AircraftTypes={ ["CH-47Fbl1"]="CH-47Fbl1", +["Mi-8MTV2"]="CH-47Fbl1", +["Mi-8MT"]="CH-47Fbl1", } DYNAMICCARGO.AircraftDimensions={ ["CH-47Fbl1"]={ @@ -33660,8 +33835,20 @@ DYNAMICCARGO.AircraftDimensions={ ["length"]=11, ["ropelength"]=30, }, +["Mi-8MTV2"]={ +["width"]=6, +["height"]=6, +["length"]=15, +["ropelength"]=30, +}, +["Mi-8MT"]={ +["width"]=6, +["height"]=6, +["length"]=15, +["ropelength"]=30, +}, } -DYNAMICCARGO.version="0.0.7" +DYNAMICCARGO.version="0.0.9" function DYNAMICCARGO:Register(CargoName) local self=BASE:Inherit(self,POSITIONABLE:New(CargoName)) self.StaticName=CargoName @@ -35274,7 +35461,9 @@ end end) self.AutoSavePath=SavePath self.AutoSave=AutoSave or true +if self.AutoSave==true then self:OpenCSV(GameName) +end return self end function SCORING:SetDisplayMessagePrefix(DisplayMessagePrefix) @@ -36298,7 +36487,7 @@ TargetUnitCoalition=TargetUnitCoalition or"" TargetUnitCategory=TargetUnitCategory or"" TargetUnitType=TargetUnitType or"" TargetUnitName=TargetUnitName or"" -if lfs and io and os and self.AutoSave then +if lfs and io and os and self.AutoSave==true and self.CSVFile~=nil then self.CSVFile:write( '"'..self.GameName..'"'..','.. '"'..self.RunTime..'"'..','.. @@ -58882,8 +59071,8 @@ end end TIRESIAS={ ClassName="TIRESIAS", -debug=true, -version=" 0.0.7-OPT", +debug=false, +version=" 0.0.8", Interval=20, GroundSet=nil, VehicleSet=nil, @@ -58903,7 +59092,7 @@ self:SetStartState("Stopped") self:AddTransition("Stopped","Start","Running") self:AddTransition("*","Status","*") self:AddTransition("*","Stop","Stopped") -self.ExceptionSet=nil +self.ExceptionSet=SET_GROUP:New() self._cached_zones={} self:HandleEvent(EVENTS.PlayerEnterAircraft,self._EventHandler) self.lid="TIRESIAS "..self.version.." | " @@ -58934,10 +59123,7 @@ exception=true, } Set:ForEachGroupAlive( function(grp) -local inAAASet=self.AAASet:IsIncludeObject(grp) -local inVehSet=self.VehicleSet:IsIncludeObject(grp) -local inSAMSet=self.SAMSet:IsIncludeObject(grp) -if grp:IsGround()and(not grp.Tiresias)and(not inAAASet)and(not inVehSet)and(not inSAMSet)then +if grp:IsGround()and(not grp.Tiresias)then grp.Tiresias=exception_data exceptions:AddGroup(grp,true) BASE:T(" TIRESIAS: Added exception group: "..grp:GetName()) @@ -59078,19 +59264,14 @@ self:T(self.lid.." _SwitchOnGroups "..group:GetName().." Radius "..radius.." N local group_name=group:GetName() local cache_key=group_name.." _"..radius local zone=self._cached_zones[cache_key] -local ground=self._cached_groupsets[cache_key] if not zone then zone=ZONE_GROUP:New(" Zone-"..group_name,group,UTILS.NMToMeters(radius)) self._cached_zones[cache_key]=zone else zone:UpdateFromGroup(group) end -if not ground then -ground=SET_GROUP:New():FilterCategoryGround():FilterZones({zone}):FilterOnce() -self._cached_groupsets[cache_key]=ground -else -ground:FilterZones({zone},true):FilterOnce() -end +zone:Scan({Object.Category.UNIT},{Unit.Category.GROUND_UNIT}) +local ground=zone:GetScannedSetGroup() local count=ground:CountAlive() if self.debug then self:I(string.format(" There are %d groups around this plane or helo!",count)) @@ -63489,6 +63670,7 @@ brc=self:GetBRCintoWind(self.recoverywindow.SPEED) end flight.Tcharlie=self:_GetCharlieTime(flight) local Ccharlie=UTILS.SecondsToClock(flight.Tcharlie) +brc=brc%360 self:_MarshalCallArrived(flight.onboard,flight.case,brc,alt,Ccharlie,P) if self.TACANon and(not flight.ai)and flight.difficulty==AIRBOSS.Difficulty.EASY then local radial=self:GetRadial(flight.case,true,true,true) @@ -63852,7 +64034,7 @@ playerData.stable=false playerData.landed=false playerData.Tlso=timer.getTime() playerData.Tgroove=nil -playerData.TIG0=nil +playerData.TIG0=0 playerData.wire=nil playerData.flag=-100 playerData.debriefschedulerID=nil @@ -64882,77 +65064,9 @@ if self:_CheckAbort(X,Z,self.BreakEntry)then self:_AbortPattern(playerData,X,Z,self.BreakEntry,true) return end -local stern=self:_GetSternCoord() -local coord=playerData.unit:GetCoordinate() -local dist=coord:Get2DDistance(stern) -local playerCallsign=playerData.unit:GetCallsign() -local playerName=playerData.name -local unit=playerData.unit -local unitClient=Unit.getByName(unit:GetName()) -local hookArgument=unitClient:getDrawArgumentValue(25) -local hookArgument_Tomcat=unitClient:getDrawArgumentValue(1305) -local speedMPS=playerData.unit:GetVelocityMPS() -local speedKTS=UTILS.MpsToKnots(speedMPS) -local player_alt=playerData.unit:GetAltitude() -player_alt_feet=player_alt*3.28 -player_alt_feet=player_alt_feet/10 -player_alt_feet=math.floor(player_alt_feet)*10 -local player_velocity_round=speedKTS*1.00 -player_velocity_round=player_velocity_round/10 -player_velocity_round=math.floor(player_velocity_round)*10 -local player_alt_feet=player_alt*3.28 -player_alt_feet=player_alt_feet/10 -player_alt_feet=math.floor(player_alt_feet)*10 -local Play_SH_Sound=USERSOUND:New("Airboss Soundfiles/GreatBallsOfFire.ogg") -local Play_666SH_Sound=USERSOUND:New("Airboss Soundfiles/Runninwiththedevil.ogg") -local playerType=playerData.actype -if dist<1000 and clientSHBFlag==false then -if speedKTS>450 and speedKTS<590 then -if player_alt_feet<1500 then -if hookArgument>0 or hookArgument_Tomcat>0 then -playerData.shb=true -trigger.action.outText(playerName..' performing a Sierra Hotel Break in a '..playerType,10) -local sh_message_to_discord=('**'..playerName..' is performing a Sierra Hotel Break in a '..playerType..' at '..player_velocity_round..' knots and '..player_alt_feet..' feet!**') -HypeMan.sendBotMessage(sh_message_to_discord) -Play_SH_Sound:ToAll() -clientSHBFlag=true -else -playerData.shb=false -end -else -end -elseif speedKTS>589 then -if player_alt_feet<625 and player_alt_feet>575 then -if hookArgument>0 or hookArgument_Tomcat>0 then -playerData.shb=true -trigger.action.outText(playerName..' performing a 666 Sierra Hotel Break in a '..playerType,10) -local sh_message_to_discord=('**'..playerName..' is performing a 666 Sierra Hotel Break in a '..playerType..' at '..player_velocity_round..' knots and '..player_alt_feet..' feet!**') -HypeMan.sendBotMessage(sh_message_to_discord) -Play_666SH_Sound:ToAll() -clientSHBFlag=true -else -playerData.shb=false -end -else -if hookArgument>0 or hookArgument_Tomcat>0 then -playerData.shb=true -trigger.action.outText(playerName..' performing a Sierra Hotel Break in a '..playerType,10) -local sh_message_to_discord=('**'..playerName..' is performing a Sierra Hotel Break in a '..playerType..' at '..player_velocity_round..' knots and '..player_alt_feet..' feet!**') -HypeMan.sendBotMessage(sh_message_to_discord) -Play_SH_Sound:ToAll() -clientSHBFlag=true -else -playerData.shb=false -end -end -else -end -else -end if self:_CheckLimits(X,Z,self.BreakEntry)then self:_PlayerHint(playerData) self:_SetPlayerStep(playerData,AIRBOSS.PatternStep.EARLYBREAK) -clientSHBFlag=false end end function AIRBOSS:_Break(playerData,part) @@ -65240,19 +65354,19 @@ end if rho>=RAR and rho<=RIM then if gd.LUE>0.22 and lineupError<-0.22 then env.info" Drift Right across centre ==> DR-" -gd.Drift=" DR" +gd.Drift="DR" self:T(self.lid..string.format("Got Drift Right across centre step %s, d=%.3f: Max LUE=%.3f, lower LUE=%.3f",gs,d,gd.LUE,lineupError)) elseif gd.LUE<-0.22 and lineupError>0.22 then env.info" Drift Left ==> DL-" -gd.Drift=" DL" +gd.Drift="DL" self:T(self.lid..string.format("Got Drift Left across centre at step %s, d=%.3f: Min LUE=%.3f, lower LUE=%.3f",gs,d,gd.LUE,lineupError)) elseif gd.LUE>0.13 and lineupError<-0.14 then env.info" Little Drift Right across centre ==> (DR-)" -gd.Drift=" (DR)" +gd.Drift="(DR)" self:T(self.lid..string.format("Got Little Drift Right across centre at step %s, d=%.3f: Max LUE=%.3f, lower LUE=%.3f",gs,d,gd.LUE,lineupError)) elseif gd.LUE<-0.13 and lineupError>0.14 then env.info" Little Drift Left across centre ==> (DL-)" -gd.Drift=" (DL)" +gd.Drift="(DL)" self:E(self.lid..string.format("Got Little Drift Left across centre at step %s, d=%.3f: Min LUE=%.3f, lower LUE=%.3f",gs,d,gd.LUE,lineupError)) end end @@ -66051,9 +66165,7 @@ local hdg=self.carrier:GetHeading() if magnetic then hdg=hdg-self.magvar end -if hdg<0 then -hdg=hdg+360 -end +hdg=hdg%360 return hdg end function AIRBOSS:GetBRC() @@ -66152,7 +66264,7 @@ theta=math.asin(vdeck*math.sin(alpha)/vwind) v=vdeck*math.cos(alpha)-vwind*math.cos(theta) end local magvar=magnetic and self.magvar or 0 -local intowind=self:GetHeadingIntoWind_old(vdeck) +local intowind=(540+(windto-magvar+math.deg(theta)))%360 return intowind,v end function AIRBOSS:GetBRCintoWind(vdeck) @@ -66343,7 +66455,7 @@ return select(2,string.gsub(base,pattern,"")) end local TIG="" if playerData.Tgroove and playerData.Tgroove<=360 and playerData.case<3 then -TIG=self:_EvalGrooveTime(playerData) +TIG=self:_EvalGrooveTime(playerData)or"N/A" end local GXX,nXX=self:_Flightdata2Text(playerData,AIRBOSS.GroovePos.XX) local GIM,nIM=self:_Flightdata2Text(playerData,AIRBOSS.GroovePos.IM) @@ -66358,6 +66470,7 @@ local N=nXX+nIM+nIC+nAR+nIW local nL=count(G,'_')/2 local nS=count(G,'%(') local nN=N-nS-nL +if TIG=="_OK_"then nL=nL-1 end local Tgroove=playerData.Tgroove local TgrooveUnicorn=Tgroove and(Tgroove>=16.49 and Tgroove<=16.59)or false local TgrooveVstolUnicorn=Tgroove and(Tgroove>=60.0 and Tgroove<=65.0)and playerData.actype==AIRBOSS.AircraftCarrier.AV8B or false @@ -66465,14 +66578,8 @@ grade="CUT" points=0.0 end end -if playerData.wire==1 and points>1 then -if points==4 then -points=3 -grade="(OK)" -elseif points==3 then -points=2 -grade="--" -end +if playerData.wire==1 and points>=3 and N>4 then +points=points-1 end env.info("Returning: "..grade.." "..points.." "..G) return grade,points,G @@ -66520,6 +66627,7 @@ O=little("OS") end end local S=nil +local A=nil if step~=AIRBOSS.PatternStep.GROOVE_IW then if AIRBOSS.PatternStep.GROOVE_AR and playerData.waveoff==true and playerData.owo==true then else @@ -66536,7 +66644,6 @@ S="F" elseif AOAself.gle.HIGH then A=underline("H") elseif GSE>self.gle.High then @@ -72835,6 +72942,7 @@ if type(Location)=="string"then Location=ZONE:New(Location) end self.Location=Location +self.NoMoveToZone=false return self end function CTLD_CARGO:SetStaticTypeAndShape(Category,TypeName,ShapeName) @@ -73326,6 +73434,7 @@ self.dropAsCargoCrate=false self.smokedistance=2000 self.movetroopstowpzone=true self.movetroopsdistance=5000 +self.returntroopstobase=true self.troopdropzoneradius=100 self.VehicleMoveFormation=AI.Task.VehicleFormation.VEE self.enableHercules=false @@ -74698,7 +74807,7 @@ if not inzone then inzone,zonename,zone,distance=self:IsUnitInZone(Unit,CTLD.CargoZoneType.SHIP) end if inzone then -droppingatbase=true +droppingatbase=self.returntroopstobase end local hoverunload=self:IsCorrectHover(Unit) local IsHerc=self:IsFixedWing(Unit) @@ -75384,7 +75493,8 @@ subcatmenus[catName]=MENU_GROUP:New(_group,catName,cratesmenu) end for _,cargoObj in pairs(self.Cargo_Crates)do if not cargoObj.DontShowInMenu then -local txt=string.format("Crate %s (%dkg)",cargoObj.Name,cargoObj.PerCrateMass or 0) +local needed=cargoObj:GetCratesNeeded()or 1 +local txt=string.format("%d crate%s %s (%dkg)",needed,needed==1 and""or"s",cargoObj.Name,cargoObj.PerCrateMass or 0) if cargoObj.Location then txt=txt.."[R]"end local stock=cargoObj:GetStock() if stock>=0 and self.showstockinmenuitems then txt=txt.."["..stock.."]"end @@ -75395,7 +75505,8 @@ end end for _,cargoObj in pairs(self.Cargo_Statics)do if not cargoObj.DontShowInMenu then -local txt=string.format("Crate %s (%dkg)",cargoObj.Name,cargoObj.PerCrateMass or 0) +local needed=cargoObj:GetCratesNeeded()or 1 +local txt=string.format("%d crate%s %s (%dkg)",needed,needed==1 and""or"s",cargoObj.Name,cargoObj.PerCrateMass or 0) if cargoObj.Location then txt=txt.."[R]"end local stock=cargoObj:GetStock() if stock>=0 and self.showstockinmenuitems then txt=txt.."["..stock.."]"end @@ -75407,7 +75518,8 @@ end else for _,cargoObj in pairs(self.Cargo_Crates)do if not cargoObj.DontShowInMenu then -local txt=string.format("Crate %s (%dkg)",cargoObj.Name,cargoObj.PerCrateMass or 0) +local needed=cargoObj:GetCratesNeeded()or 1 +local txt=string.format("%d crate%s %s (%dkg)",needed,needed==1 and""or"s",cargoObj.Name,cargoObj.PerCrateMass or 0) if cargoObj.Location then txt=txt.."[R]"end local stock=cargoObj:GetStock() if stock>=0 and self.showstockinmenuitems then txt=txt.."["..stock.."]"end @@ -75418,7 +75530,8 @@ end end for _,cargoObj in pairs(self.Cargo_Statics)do if not cargoObj.DontShowInMenu then -local txt=string.format("Crate %s (%dkg)",cargoObj.Name,cargoObj.PerCrateMass or 0) +local needed=cargoObj:GetCratesNeeded()or 1 +local txt=string.format("%d crate%s %s (%dkg)",needed,needed==1 and""or"s",cargoObj.Name,cargoObj.PerCrateMass or 0) if cargoObj.Location then txt=txt.."[R]"end local stock=cargoObj:GetStock() if stock>=0 and self.showstockinmenuitems then txt=txt.."["..stock.."]"end @@ -75429,14 +75542,15 @@ end end end else -if self.usesubcats then +if self.usesubcats==true then local subcatmenus={} for catName,_ in pairs(self.subcats)do subcatmenus[catName]=MENU_GROUP:New(_group,catName,cratesmenu) end for _,cargoObj in pairs(self.Cargo_Crates)do if not cargoObj.DontShowInMenu then -local txt=string.format("Crate %s (%dkg)",cargoObj.Name,cargoObj.PerCrateMass or 0) +local needed=cargoObj:GetCratesNeeded()or 1 +local txt=string.format("%d crate%s %s (%dkg)",needed,needed==1 and""or"s",cargoObj.Name,cargoObj.PerCrateMass or 0) if cargoObj.Location then txt=txt.."[R]"end local stock=cargoObj:GetStock() if stock>=0 and self.showstockinmenuitems then txt=txt.."["..stock.."]"end @@ -75445,7 +75559,8 @@ end end for _,cargoObj in pairs(self.Cargo_Statics)do if not cargoObj.DontShowInMenu then -local txt=string.format("Crate %s (%dkg)",cargoObj.Name,cargoObj.PerCrateMass or 0) +local needed=cargoObj:GetCratesNeeded()or 1 +local txt=string.format("%d crate%s %s (%dkg)",needed,needed==1 and""or"s",cargoObj.Name,cargoObj.PerCrateMass or 0) if cargoObj.Location then txt=txt.."[R]"end local stock=cargoObj:GetStock() if stock>=0 and self.showstockinmenuitems then txt=txt.."["..stock.."]"end @@ -75455,7 +75570,8 @@ end else for _,cargoObj in pairs(self.Cargo_Crates)do if not cargoObj.DontShowInMenu then -local txt=string.format("Crate %s (%dkg)",cargoObj.Name,cargoObj.PerCrateMass or 0) +local needed=cargoObj:GetCratesNeeded()or 1 +local txt=string.format("%d crate%s %s (%dkg)",needed,needed==1 and""or"s",cargoObj.Name,cargoObj.PerCrateMass or 0) if cargoObj.Location then txt=txt.."[R]"end local stock=cargoObj:GetStock() if stock>=0 and self.showstockinmenuitems then txt=txt.."["..stock.."]"end @@ -75464,7 +75580,8 @@ end end for _,cargoObj in pairs(self.Cargo_Statics)do if not cargoObj.DontShowInMenu then -local txt=string.format("Crate %s (%dkg)",cargoObj.Name,cargoObj.PerCrateMass or 0) +local needed=cargoObj:GetCratesNeeded()or 1 +local txt=string.format("%d crate%s %s (%dkg)",needed,needed==1 and""or"s",cargoObj.Name,cargoObj.PerCrateMass or 0) if cargoObj.Location then txt=txt.."[R]"end local stock=cargoObj:GetStock() if stock>=0 and self.showstockinmenuitems then txt=txt.."["..stock.."]"end @@ -75888,7 +76005,7 @@ if not inzone then inzone,zonename,zone,distance=self:IsUnitInZone(Unit,CTLD.CargoZoneType.SHIP) end if inzone then -droppingatbase=true +droppingatbase=self.returntroopstobase end if self.pilotmustopendoors and not UTILS.IsLoadingDoorOpen(Unit:GetName())then self:_SendMessage("You need to open the door(s) to unload troops!",10,false,Group) @@ -76117,6 +76234,34 @@ table.insert(self.Cargo_Crates,cargo) if SubCategory and self.usesubcats~=true then self.usesubcats=true end return self end +function CTLD:AddCratesCargoNoMove(Name,Templates,Type,NoCrates,PerCrateMass,Stock,SubCategory,DontShowInMenu,Location,UnitTypes,Category,TypeName,ShapeName) +self:T(self.lid.." AddCratesCargoNoMove") +if not self:_CheckTemplates(Templates)then +self:E(self.lid.."Crates Cargo for "..Name.." has missing template(s)!") +return self +end +self.CargoCounter=self.CargoCounter+1 +local cargo=CTLD_CARGO:New(self.CargoCounter,Name,Templates,Type,false,false,NoCrates,nil,nil,PerCrateMass,Stock,SubCategory,DontShowInMenu,Location) +cargo.NoMoveToZone=true +if UnitTypes then +cargo:AddUnitTypeName(UnitTypes) +end +cargo:SetStaticTypeAndShape("Cargos",self.basetype) +if TypeName then +cargo:SetStaticTypeAndShape(Category,TypeName,ShapeName) +end +table.insert(self.Cargo_Crates,cargo) +self.templateToCargoName=self.templateToCargoName or{} +if type(Templates)=="table"then +for _,t in pairs(Templates)do self.templateToCargoName[t]=Name end +else +self.templateToCargoName[Templates]=Name +end +self.nomovetozone_names=self.nomovetozone_names or{} +self.nomovetozone_names[Name]=true +if SubCategory and self.usesubcats~=true then self.usesubcats=true end +return self +end function CTLD:AddStaticsCargo(Name,Mass,Stock,SubCategory,DontShowInMenu,Location) self:T(self.lid.." AddStaticsCargo") self.CargoCounter=self.CargoCounter+1 @@ -76269,7 +76414,12 @@ self:E(self.lid.."**** Ship does not exist: "..Name) return self end end -local ctldzone={} +local exists=true +local ctldzone=self:GetCTLDZone(Name,Type) +if not ctldzone then +exists=false +ctldzone={} +end ctldzone.active=Active or false ctldzone.color=Color or SMOKECOLOR.Red ctldzone.name=Name or"NONE" @@ -76292,9 +76442,45 @@ if Type==CTLD.CargoZoneType.SHIP then ctldzone.shiplength=Shiplength or 100 ctldzone.shipwidth=Shipwidth or 10 end +if not exists then self:AddZone(ctldzone) +end return self end +function CTLD:GetCTLDZone(Name,Type) +if Type==CTLD.CargoZoneType.LOAD then +for _,z in pairs(self.pickupZones)do +if z.name==Name then +return z +end +end +elseif Type==CTLD.CargoZoneType.DROP then +for _,z in pairs(self.dropOffZones)do +if z.name==Name then +return z +end +end +elseif Type==CTLD.CargoZoneType.SHIP then +for _,z in pairs(self.shipZones)do +if z.name==Name then +return z +end +end +elseif Type==CTLD.CargoZoneType.BEACON then +for _,z in pairs(self.droppedBeacons)do +if z.name==Name then +return z +end +end +else +for _,z in pairs(self.wpZones)do +if z.name==Name then +return z +end +end +end +return nil +end function CTLD:AddCTLDZoneFromAirbase(AirbaseName,Type,Color,Active,HasBeacon) self:T(self.lid.." AddCTLDZoneFromAirbase") local AFB=AIRBASE:FindByName(AirbaseName) @@ -77540,9 +77726,12 @@ return self end function CTLD:onafterCratesBuild(From,Event,To,Group,Unit,Vehicle) self:T({From,Event,To}) -if self.movetroopstowpzone then +if self.movetroopstowpzone and Vehicle then +local cg=self:GetGenericCargoObjectFromGroupName(Vehicle:GetName()) +if not(cg and(cg.NoMoveToZone or(self.nomovetozone_names and self.nomovetozone_names[cg:GetName()])))then self:_MoveGroupToZone(Vehicle) end +end return self end function CTLD:onbeforeTroopsRTB(From,Event,To,Group,Unit,ZoneName,ZoneObject) @@ -78295,7 +78484,7 @@ CSAR.AircraftType["MH-60R"]=10 CSAR.AircraftType["OH-6A"]=2 CSAR.AircraftType["OH58D"]=2 CSAR.AircraftType["CH-47Fbl1"]=31 -CSAR.version="1.0.33" +CSAR.version="1.0.34" function CSAR:New(Coalition,Template,Alias) local self=BASE:Inherit(self,FSM:New()) BASE:T({Coalition,Template,Alias}) @@ -78741,11 +78930,11 @@ return self end local initdcscoord=nil local initcoord=nil -if _event.id==EVENTS.Ejection then +if _event.id==EVENTS.Ejection and _event.TgtDCSUnit then initdcscoord=_event.TgtDCSUnit:getPoint() initcoord=COORDINATE:NewFromVec3(initdcscoord) self:T({initdcscoord}) -else +elseif _event.IniDCSUnit then initdcscoord=_event.IniDCSUnit:getPoint() initcoord=COORDINATE:NewFromVec3(initdcscoord) self:T({initdcscoord}) @@ -78808,7 +78997,10 @@ return self end if _place:GetCoalition()==self.coalition or _place:GetCoalition()==coalition.side.NEUTRAL then self:__Landed(2,_event.IniUnitName,_place) -self:_ScheduledSARFlight(_event.IniUnitName,_event.IniGroupName,true,true) +local IsHeloBase=false +local ABName=_place:GetName() +if ABName and string.find(ABName,"^H")then IsHeloBase=true end +self:_ScheduledSARFlight(_event.IniUnitName,_event.IniGroupName,true,true,IsHeloBase) else self:T(string.format("Airfield %d, Unit %d",_place:GetCoalition(),_unit:GetCoalition())) end @@ -79163,7 +79355,7 @@ else return false end end -function CSAR:_ScheduledSARFlight(heliname,groupname,isairport,noreschedule) +function CSAR:_ScheduledSARFlight(heliname,groupname,isairport,noreschedule,IsHeloBase) self:T(self.lid.." _ScheduledSARFlight") self:T({heliname,groupname}) local _heliUnit=self:_GetSARHeli(heliname) @@ -79181,7 +79373,7 @@ self:T(self.lid.."[Drop off debug] Check distance to MASH for "..heliname.." Dis return end self:T(self.lid.."[Drop off debug] Check distance to MASH for "..heliname.." Distance km: "..math.floor(_dist/1000)) -if(_dist=2 then local text=string.format("Updating MENU: State=%s, ATC=%s [%s]",self:GetState(), self.flightcontrol and self.flightcontrol.airbasename or"None",self.flightcontrol and self.flightcontrol:GetFlightStatus(self)or"Unknown") MESSAGE:New(text,5):ToGroup(self.group) -self:I(self.lid..text) +self:T(self.lid..text) end local position=self:GetCoordinate(nil,player.name) local fc={} @@ -98621,6 +98828,13 @@ local mission=_mission if mission:IsNotOver()and mission:IsReadyToCancel()then mission:Cancel() end +local TNow=timer.getTime() +if mission:IsOver()and mission:IsNotRepeatable()and mission.DeletionTimstamp==nil then +mission.DeletionTimstamp=TNow +end +if mission.DeletionTimstamp~=nil and TNow-mission.DeletionTimstamp>1800 then +mission=nil +end end if self:IsAirwing()then if self:IsRunwayOperational()==false then @@ -98676,7 +98890,7 @@ if EscortAvail and TransportAvail then self:MissionRequest(mission,assets) if reinforce then mission.reinforce=mission.reinforce-#assets -self:I(self.lid..string.format("Reinforced with N=%d Nreinforce=%d",#assets,mission.reinforce)) +self:T(self.lid..string.format("Reinforced with N=%d Nreinforce=%d",#assets,mission.reinforce)) end return true else @@ -103616,7 +103830,12 @@ if zoneCurr then self:T(self.lid..string.format("Current target zone=%s owner=%s",zoneCurr:GetName(),zoneCurr:GetOwnerName())) if zoneCurr:GetOwner()==self:GetCoalition()then self:T(self.lid..string.format("Zone %s captured ==> Task DONE!",zoneCurr:GetName())) +if Task.StayInZoneTime then +local stay=Task.StayInZoneTime +self:__TaskDone(stay,Task) +else self:TaskDone(Task) +end else self:T(self.lid..string.format("Zone %s NOT captured!",zoneCurr:GetName())) if Mission:GetGroupStatus(self)==AUFTRAG.GroupStatus.EXECUTING then @@ -104983,7 +105202,7 @@ self.Ndestroyed=self.Ndestroyed+1 self:ElementDead(Element) end function OPSGROUP:onafterElementDead(From,Event,To,Element) -self:I(self.lid..string.format("Element dead %s at t=%.3f",Element.name,timer.getTime())) +self:T(self.lid..string.format("Element dead %s at t=%.3f",Element.name,timer.getTime())) self:_UpdateStatus(Element,OPSGROUP.ElementStatus.DEAD) if self.spot.On and self.spot.element.name==Element.name then self:LaserOff() @@ -105256,7 +105475,7 @@ local text=string.format("WARNING: Group is still alive! Current state=%s. Life self:T(self.lid..text) end _DATABASE.FLIGHTGROUPS[self.groupname]=nil -self:I(self.lid.."STOPPED! Unhandled events, cleared scheduler and removed from _DATABASE") +self:T(self.lid.."STOPPED! Unhandled events, cleared scheduler and removed from _DATABASE") end function OPSGROUP:onafterOutOfAmmo(From,Event,To) self:T(self.lid..string.format("Group is out of ammo at t=%.3f",timer.getTime())) diff --git a/Patch-MooseMissions/Patch-MooseMissions.ps1 b/Patch-MooseMissions/Patch-MooseMissions.ps1 new file mode 100644 index 0000000..0e356d0 --- /dev/null +++ b/Patch-MooseMissions/Patch-MooseMissions.ps1 @@ -0,0 +1,269 @@ +<# +.SYNOPSIS + Patches DCS mission files (.miz) with updated Lua scripts. + +.DESCRIPTION + This script extracts a DCS mission file (which is a ZIP archive), replaces or adds + a Lua script file, and repackages the mission. This allows you to update scripts + like Moose_.lua across multiple missions without opening the DCS Mission Editor. + +.PARAMETER MissionPath + Path to the .miz mission file to patch. Can be a single file or multiple files. + +.PARAMETER LuaScriptPath + Path to the Lua script file to insert/replace in the mission. + +.PARAMETER ScriptName + Optional. The name the script should have inside the mission file. + If not specified, uses the filename from LuaScriptPath. + +.PARAMETER OutputPath + Optional. Directory where patched missions should be saved. + If not specified, saves to the same directory as the input mission. + +.PARAMETER NoVersionIncrement + If specified, does not increment the version number in the filename. + By default, the script automatically increments the patch version (e.g., 1.1.2 -> 1.1.3). + WARNING: Using this flag will OVERWRITE the original mission file! + +.EXAMPLE + .\Patch-MooseMissions.ps1 -MissionPath "C:\Missions\MyMission-1.2.3.miz" -LuaScriptPath "C:\Scripts\Moose_.lua" + Creates: MyMission-1.2.4.miz (original 1.2.3 remains untouched) + +.EXAMPLE + Get-ChildItem "C:\Missions\*.miz" | .\Patch-MooseMissions.ps1 -LuaScriptPath "C:\Scripts\Moose_.lua" + +.EXAMPLE + .\Patch-MooseMissions.ps1 -MissionPath "Mission-2.1.miz" -LuaScriptPath "MyScript.lua" -NoVersionIncrement + WARNING: Overwrites Mission-2.1.miz + +.NOTES + Author: F99th-TracerFacer + Version: 2.0 + DCS mission files are ZIP archives containing a 'l10n' folder with a 'DEFAULT' subfolder + where Lua scripts are stored. +#> + +[CmdletBinding()] +param( + [Parameter(Mandatory=$true, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true)] + [Alias("FullName", "Path")] + [string[]]$MissionPath, + + [Parameter(Mandatory=$true)] + [string]$LuaScriptPath, + + [Parameter(Mandatory=$false)] + [string]$ScriptName, + + [Parameter(Mandatory=$false)] + [string]$OutputPath, + + [Parameter(Mandatory=$false)] + [switch]$NoVersionIncrement +) + +begin { + # Verify Lua script exists + if (-not (Test-Path $LuaScriptPath)) { + throw "Lua script not found: $LuaScriptPath" + } + + # Determine script name to use inside mission + if ([string]::IsNullOrWhiteSpace($ScriptName)) { + $ScriptName = Split-Path $LuaScriptPath -Leaf + } + + Write-Host "========================================" -ForegroundColor Cyan + Write-Host "DCS Mission Patcher" -ForegroundColor Cyan + Write-Host "========================================" -ForegroundColor Cyan + Write-Host "Lua Script: $ScriptName" -ForegroundColor Yellow + Write-Host "Source: $LuaScriptPath" -ForegroundColor Gray + Write-Host "Version Increment: $(if ($NoVersionIncrement) { 'Disabled (OVERWRITES ORIGINAL!)' } else { 'Enabled' })" -ForegroundColor $(if ($NoVersionIncrement) { 'Red' } else { 'Green' }) + Write-Host "" + + # Validate output path if specified + if ($OutputPath -and -not (Test-Path $OutputPath)) { + Write-Host "Creating output directory: $OutputPath" -ForegroundColor Yellow + New-Item -Path $OutputPath -ItemType Directory -Force | Out-Null + } + + $successCount = 0 + $failCount = 0 + + # Function to increment version number in filename + function Get-IncrementedFilename { + param( + [string]$FileName + ) + + # Remove extension + $nameWithoutExt = [System.IO.Path]::GetFileNameWithoutExtension($FileName) + $extension = [System.IO.Path]::GetExtension($FileName) + + # Try to find version patterns: X.Y.Z or X.Y or just X at the end + # Patterns to match (in order of specificity): + # 1. Major.Minor.Patch (e.g., 1.2.3) + # 2. Major.Minor (e.g., 1.2) + # 3. Just version number at end (e.g., v5 or -5) + + # Pattern 1: X.Y.Z (most common) + if ($nameWithoutExt -match '^(.+?)[-_\s]?(\d+)\.(\d+)\.(\d+)$') { + $baseName = $matches[1] + $major = $matches[2] + $minor = $matches[3] + $patch = [int]$matches[4] + $newPatch = $patch + 1 + $separator = if ($nameWithoutExt -match '[-_\s](\d+\.\d+\.\d+)$') { $matches[0][0] } else { '' } + return "$baseName$separator$major.$minor.$newPatch$extension" + } + # Pattern 2: X.Y + elseif ($nameWithoutExt -match '^(.+?)[-_\s]?(\d+)\.(\d+)$') { + $baseName = $matches[1] + $major = $matches[2] + $minor = [int]$matches[3] + $newMinor = $minor + 1 + $separator = if ($nameWithoutExt -match '[-_\s](\d+\.\d+)$') { $matches[0][0] } else { '' } + return "$baseName$separator$major.$newMinor$extension" + } + # Pattern 3: Just a number at the end + elseif ($nameWithoutExt -match '^(.+?)[-_\s]?(\d+)$') { + $baseName = $matches[1] + $version = [int]$matches[2] + $newVersion = $version + 1 + $separator = if ($nameWithoutExt -match '[-_\s]\d+$') { $matches[0][0] } else { '' } + return "$baseName$separator$newVersion$extension" + } + # No version found - append .1 + else { + return "$nameWithoutExt-1.0.1$extension" + } + } +} + +process { + foreach ($mission in $MissionPath) { + try { + # Resolve full path + $missionFile = Resolve-Path $mission -ErrorAction Stop + + Write-Host "Processing: " -NoNewline -ForegroundColor White + Write-Host "$missionFile" -ForegroundColor Cyan + + # Verify mission file exists and is a .miz file + if (-not (Test-Path $missionFile)) { + throw "Mission file not found: $missionFile" + } + + if ([System.IO.Path]::GetExtension($missionFile) -ne ".miz") { + throw "File is not a .miz mission file: $missionFile" + } + + # Create temporary extraction directory + $tempDir = Join-Path $env:TEMP ("DCS_Mission_Patch_" + [System.Guid]::NewGuid().ToString()) + New-Item -Path $tempDir -ItemType Directory -Force | Out-Null + + try { + # Extract mission file (it's a ZIP archive) + # Use .NET classes instead of Expand-Archive for better .miz support + Write-Host " Extracting mission..." -ForegroundColor Gray + + Add-Type -Assembly System.IO.Compression.FileSystem + [System.IO.Compression.ZipFile]::ExtractToDirectory($missionFile, $tempDir) + + # Determine Lua script destination in mission structure + # DCS stores Lua scripts in: l10n/DEFAULT/ folder + $luaDestDir = Join-Path $tempDir "l10n\DEFAULT" + + # Create directory if it doesn't exist + if (-not (Test-Path $luaDestDir)) { + Write-Host " Creating l10n/DEFAULT directory..." -ForegroundColor Yellow + New-Item -Path $luaDestDir -ItemType Directory -Force | Out-Null + } + + $luaDestPath = Join-Path $luaDestDir $ScriptName + + # Check if script already exists + if (Test-Path $luaDestPath) { + Write-Host " Replacing existing script: $ScriptName" -ForegroundColor Yellow + } else { + Write-Host " Adding new script: $ScriptName" -ForegroundColor Green + } + + # Copy Lua script to mission + Copy-Item $LuaScriptPath $luaDestPath -Force + + # Determine output filename and location + $originalFileName = Split-Path $missionFile -Leaf + + if ($NoVersionIncrement) { + # Keep original filename + $outputFileName = $originalFileName + } else { + # Increment version number + $outputFileName = Get-IncrementedFilename -FileName $originalFileName + Write-Host " Version increment: $originalFileName -> $outputFileName" -ForegroundColor Cyan + } + + # Determine output directory + if ($OutputPath) { + $outputDir = $OutputPath + } else { + $outputDir = Split-Path $missionFile -Parent + } + + $outputMission = Join-Path $outputDir $outputFileName + + # Remove existing mission file if it exists + if (Test-Path $outputMission) { + Remove-Item $outputMission -Force + } + + # Repackage mission file + Write-Host " Repackaging mission..." -ForegroundColor Gray + + # Use .NET classes for better compatibility + Add-Type -Assembly System.IO.Compression.FileSystem + $compressionLevel = [System.IO.Compression.CompressionLevel]::Optimal + + # Create ZIP from directory (will become .miz) + [System.IO.Compression.ZipFile]::CreateFromDirectory($tempDir, $outputMission, $compressionLevel, $false) + + Write-Host " SUCCESS: Mission patched successfully!" -ForegroundColor Green + Write-Host " Output: $outputMission" -ForegroundColor Gray + Write-Host "" + + $successCount++ + } + finally { + # Clean up temporary directory + if (Test-Path $tempDir) { + Remove-Item $tempDir -Recurse -Force + } + } + } + catch { + Write-Host " ERROR: $_" -ForegroundColor Red + Write-Host "" + $failCount++ + } + } +} + +end { + Write-Host "========================================" -ForegroundColor Cyan + Write-Host "Patching Complete" -ForegroundColor Cyan + Write-Host "========================================" -ForegroundColor Cyan + Write-Host "Successful: $successCount" -ForegroundColor Green + Write-Host "Failed: $failCount" -ForegroundColor $(if ($failCount -gt 0) { "Red" } else { "Gray" }) + Write-Host "" + + if ($successCount -gt 0) { + if ($NoVersionIncrement) { + Write-Host "WARNING: Original mission files were OVERWRITTEN!" -ForegroundColor Red + } else { + Write-Host "INFO: Original mission files remain untouched. New versioned files created." -ForegroundColor Green + } + Write-Host "TIP: Test your patched missions in DCS before using them!" -ForegroundColor Yellow + } +} diff --git a/Patch-MooseMissions/Patch-MyMooseMissions.ps1 b/Patch-MooseMissions/Patch-MyMooseMissions.ps1 new file mode 100644 index 0000000..de4d038 --- /dev/null +++ b/Patch-MooseMissions/Patch-MyMooseMissions.ps1 @@ -0,0 +1,283 @@ +<# +.SYNOPSIS + Batch updates all DCS missions with the latest Moose framework. + +.DESCRIPTION + This script scans through the DCS mission development folder structure, + finds the latest version of each mission (.miz files with version numbers), + and patches them with the updated Moose_.lua script using Patch-MooseMissions.ps1. + + It processes missions in the structure: C:\DCS_MissionDev\DCS_[Terrain]\[MissionFolder]\ + For each mission folder, it identifies the latest version (by version number or file date) + and creates a new patched version. + +.PARAMETER RootPath + Root directory containing DCS terrain folders. Defaults to C:\DCS_MissionDev + +.PARAMETER MooseLuaPath + Path to the Moose_.lua file to patch into missions. Defaults to C:\DCS_MissionDev\Moose_.lua + +.PARAMETER WhatIf + Shows what would be processed without actually patching any files. + +.PARAMETER ExcludeTerrain + Array of terrain folder names to exclude from processing (e.g., @("DCS_Normandy", "DCS_Falklands")) + +.EXAMPLE + .\Patch-MyMooseMissions.ps1 + Processes all missions in C:\DCS_MissionDev using the default Moose_.lua + +.EXAMPLE + .\Patch-MyMooseMissions.ps1 -WhatIf + Shows what missions would be processed without making any changes + +.EXAMPLE + .\Patch-MyMooseMissions.ps1 -ExcludeTerrain @("DCS_Normandy", "DCS_Falklands") + Processes all missions except those in Normandy and Falklands terrains + +.NOTES + Author: F99th-TracerFacer + Version: 1.0 + Requires: Patch-MooseMissions.ps1 in the same directory +#> + +[CmdletBinding(SupportsShouldProcess)] +param( + [Parameter(Mandatory=$false)] + [string]$RootPath = "C:\DCS_MissionDev", + + [Parameter(Mandatory=$false)] + [string]$MooseLuaPath = "C:\DCS_MissionDev\Moose_.lua", + + [Parameter(Mandatory=$false)] + [string[]]$ExcludeTerrain = @() +) + +# Verify paths exist +if (-not (Test-Path $RootPath)) { + Write-Error "Root path not found: $RootPath" + exit 1 +} + +if (-not (Test-Path $MooseLuaPath)) { + Write-Error "Moose_.lua file not found: $MooseLuaPath" + exit 1 +} + +# Verify the Patch-MooseMissions.ps1 script exists +$patchScriptPath = Join-Path $PSScriptRoot "Patch-MooseMissions.ps1" +if (-not (Test-Path $patchScriptPath)) { + Write-Error "Patch-MooseMissions.ps1 not found in: $PSScriptRoot" + exit 1 +} + +Write-Host "========================================" -ForegroundColor Cyan +Write-Host "Batch Moose Mission Patcher" -ForegroundColor Cyan +Write-Host "========================================" -ForegroundColor Cyan +Write-Host "Root Path: $RootPath" -ForegroundColor Yellow +Write-Host "Moose Script: $MooseLuaPath" -ForegroundColor Yellow +Write-Host "Patch Script: $patchScriptPath" -ForegroundColor Yellow +if ($ExcludeTerrain.Count -gt 0) { + Write-Host "Excluded Terrains: $($ExcludeTerrain -join ', ')" -ForegroundColor Yellow +} +Write-Host "" + +# Function to parse version number from filename +function Get-VersionNumber { + param([string]$FileName) + + $nameWithoutExt = [System.IO.Path]::GetFileNameWithoutExtension($FileName) + + # Try X.Y.Z pattern + if ($nameWithoutExt -match '(\d+)\.(\d+)\.(\d+)') { + $major = [int]$matches[1] + $minor = [int]$matches[2] + $patch = [int]$matches[3] + return [PSCustomObject]@{ + Major = $major + Minor = $minor + Patch = $patch + Value = ($major * 1000000) + ($minor * 1000) + $patch + HasVersion = $true + } + } + # Try X.Y pattern + elseif ($nameWithoutExt -match '(\d+)\.(\d+)') { + $major = [int]$matches[1] + $minor = [int]$matches[2] + return [PSCustomObject]@{ + Major = $major + Minor = $minor + Patch = 0 + Value = ($major * 1000000) + ($minor * 1000) + HasVersion = $true + } + } + # Try single version number + elseif ($nameWithoutExt -match '(\d+)$') { + $version = [int]$matches[1] + return [PSCustomObject]@{ + Major = $version + Minor = 0 + Patch = 0 + Value = $version * 1000000 + HasVersion = $true + } + } + + # No version found + return [PSCustomObject]@{ + Major = 0 + Minor = 0 + Patch = 0 + Value = 0 + HasVersion = $false + } +} + +# Function to get the latest mission file from a directory +function Get-LatestMission { + param([string]$DirectoryPath) + + # Get all .miz files in the directory (not subdirectories) + $mizFiles = Get-ChildItem -Path $DirectoryPath -Filter "*.miz" -File -ErrorAction SilentlyContinue + + if ($mizFiles.Count -eq 0) { + return $null + } + + # Separate files with version numbers from those without + $versionedFiles = @() + $nonVersionedFiles = @() + + foreach ($file in $mizFiles) { + $version = Get-VersionNumber -FileName $file.Name + if ($version.HasVersion) { + $versionedFiles += [PSCustomObject]@{ + File = $file + Version = $version + } + } else { + $nonVersionedFiles += $file + } + } + + # If we have versioned files, return the one with the highest version + if ($versionedFiles.Count -gt 0) { + $latest = $versionedFiles | Sort-Object -Property { $_.Version.Value } -Descending | Select-Object -First 1 + return $latest.File + } + + # If no versioned files, return the most recently modified file + if ($nonVersionedFiles.Count -gt 0) { + return $nonVersionedFiles | Sort-Object -Property LastWriteTime -Descending | Select-Object -First 1 + } + + return $null +} + +# Get all DCS terrain folders (folders starting with "DCS_") +$terrainFolders = Get-ChildItem -Path $RootPath -Directory | Where-Object { $_.Name -match '^DCS_' } + +if ($terrainFolders.Count -eq 0) { + Write-Warning "No DCS terrain folders found in: $RootPath" + exit 0 +} + +Write-Host "Found $($terrainFolders.Count) terrain folder(s)" -ForegroundColor Green +Write-Host "" + +# Track statistics +$totalMissionsFound = 0 +$totalMissionsPatched = 0 +$totalMissionsFailed = 0 +$skippedTerrains = 0 + +# Process each terrain folder +foreach ($terrainFolder in $terrainFolders) { + $terrainName = $terrainFolder.Name + + # Check if this terrain should be excluded + if ($ExcludeTerrain -contains $terrainName) { + Write-Host "SKIPPING TERRAIN: $terrainName (excluded)" -ForegroundColor DarkGray + $skippedTerrains++ + continue + } + + Write-Host "TERRAIN: $terrainName" -ForegroundColor Cyan + + # Get all subdirectories (mission folders) + $missionFolders = Get-ChildItem -Path $terrainFolder.FullName -Directory -ErrorAction SilentlyContinue + + if ($missionFolders.Count -eq 0) { + Write-Host " No mission folders found" -ForegroundColor DarkGray + Write-Host "" + continue + } + + Write-Host " Found $($missionFolders.Count) mission folder(s)" -ForegroundColor Gray + + # Process each mission folder + foreach ($missionFolder in $missionFolders) { + $missionFolderName = $missionFolder.Name + + # Get the latest mission file + $latestMission = Get-LatestMission -DirectoryPath $missionFolder.FullName + + if ($null -eq $latestMission) { + Write-Host " [$missionFolderName] No .miz files found" -ForegroundColor DarkGray + continue + } + + $totalMissionsFound++ + + Write-Host " [$missionFolderName] Latest: " -NoNewline -ForegroundColor White + Write-Host "$($latestMission.Name)" -ForegroundColor Yellow + + # Execute the patch script + if ($PSCmdlet.ShouldProcess($latestMission.FullName, "Patch with Moose_.lua")) { + try { + # Call Patch-MooseMissions.ps1 + & $patchScriptPath -MissionPath $latestMission.FullName -LuaScriptPath $MooseLuaPath -ErrorAction Stop + + # Check if it succeeded (the script will output its own messages) + if ($LASTEXITCODE -eq 0 -or $null -eq $LASTEXITCODE) { + $totalMissionsPatched++ + } else { + $totalMissionsFailed++ + } + } + catch { + Write-Host " ERROR: Failed to patch mission - $_" -ForegroundColor Red + $totalMissionsFailed++ + } + } + else { + Write-Host " WHATIF: Would patch this mission" -ForegroundColor Magenta + } + } + + Write-Host "" +} + +# Final summary +Write-Host "========================================" -ForegroundColor Cyan +Write-Host "Batch Processing Complete" -ForegroundColor Cyan +Write-Host "========================================" -ForegroundColor Cyan +Write-Host "Terrains Processed: $($terrainFolders.Count - $skippedTerrains)" -ForegroundColor White +if ($skippedTerrains -gt 0) { + Write-Host "Terrains Skipped: $skippedTerrains" -ForegroundColor DarkGray +} +Write-Host "Missions Found: $totalMissionsFound" -ForegroundColor White +Write-Host "Missions Patched: $totalMissionsPatched" -ForegroundColor Green +if ($totalMissionsFailed -gt 0) { + Write-Host "Missions Failed: $totalMissionsFailed" -ForegroundColor Red +} +Write-Host "" + +if ($WhatIfPreference) { + Write-Host "INFO: This was a WhatIf run - no files were modified" -ForegroundColor Magenta +} elseif ($totalMissionsPatched -gt 0) { + Write-Host "SUCCESS: All missions have been patched with the latest Moose framework!" -ForegroundColor Green + Write-Host "TIP: Test your patched missions in DCS before deployment!" -ForegroundColor Yellow +} diff --git a/Patch-MooseMissions/README.md b/Patch-MooseMissions/README.md new file mode 100644 index 0000000..de08f42 --- /dev/null +++ b/Patch-MooseMissions/README.md @@ -0,0 +1,197 @@ +# DCS Mission Patcher + +PowerShell script to automatically patch DCS mission files (.miz) with updated Lua scripts and increment version numbers. + +## Features + +- ✅ Updates Lua scripts in .miz files without opening DCS Mission Editor +- ✅ **Automatically increments version numbers** in mission filenames (e.g., 1.2.3 → 1.2.4) +- ✅ **Preserves original missions** - creates new versioned files instead of overwriting +- ✅ Supports single or batch processing of multiple missions +- ✅ Can output to a different directory +- ✅ Handles both new script insertion and existing script replacement +- ✅ Pipeline support for processing multiple missions +- ✅ Smart version detection (supports X.Y.Z, X.Y, and X formats) + +## Version Increment Examples + +| Input Filename | Output Filename | +|----------------|-----------------| +| `Mission-1.2.3.miz` | `Mission-1.2.4.miz` | +| `Operation Black Gold 2.8.miz` | `Operation Black Gold 2.9.miz` | +| `F99th-Battle of Gori 1.3.miz` | `F99th-Battle of Gori 1.4.miz` | +| `MyMission-5.miz` | `MyMission-6.miz` | +| `Test.miz` | `Test-1.0.1.miz` | + +## Usage + +### Basic Usage + +Update a mission with automatic version increment: + +```powershell +.\Patch-MooseMissions.ps1 -MissionPath "MyMission-1.2.3.miz" -LuaScriptPath "Moose_.lua" +# Original: MyMission-1.2.3.miz (untouched) +# Creates: MyMission-1.2.4.miz +``` + +### Batch Processing + +Update all .miz files in a directory: + +```powershell +Get-ChildItem "C:\DCS_Missions\*.miz" | .\Patch-MooseMissions.ps1 -LuaScriptPath "C:\Scripts\Moose_.lua" +# Each mission gets a new version +``` + +### Output to Different Directory + +Patch missions and save versioned outputs to a different folder: + +```powershell +.\Patch-MooseMissions.ps1 -MissionPath "Mission-1.5.miz" -LuaScriptPath "Moose_.lua" -OutputPath "C:\PatchedMissions" +# Creates: C:\PatchedMissions\Mission-1.6.miz +``` + +### Disable Version Increment (CAUTION!) + +⚠️ Overwrite the original mission file without creating a new version: + +```powershell +.\Patch-MooseMissions.ps1 -MissionPath "Mission-1.2.3.miz" -LuaScriptPath "Moose_.lua" -NoVersionIncrement +# WARNING: Overwrites Mission-1.2.3.miz (original is lost!) +``` + +### Custom Script Name + +Insert a script with a different name than the source file: + +```powershell +.\Patch-MooseMissions.ps1 -MissionPath "Mission.miz" -LuaScriptPath "MyScript.lua" -ScriptName "CustomName.lua" +``` + +## Parameters + +| Parameter | Required | Description | +|-----------|----------|-------------| +| `-MissionPath` | Yes | Path to .miz mission file(s) to patch | +| `-LuaScriptPath` | Yes | Path to the Lua script to insert/replace | +| `-ScriptName` | No | Name for the script inside the mission (defaults to source filename) | +| `-OutputPath` | No | Directory for patched missions (defaults to source directory) | +| `-NoVersionIncrement` | No | **CAUTION:** Overwrites original instead of creating new version | + +## Examples + +### Example 1: Update All Caucasus Missions with Version Increment + +```powershell +Get-ChildItem "C:\DCS_MissionDev\DCS_Caucasus\*.miz" | .\Patch-MooseMissions.ps1 -LuaScriptPath "C:\Moose\Moose_.lua" +# Each mission gets a new version: 1.2.miz -> 1.3.miz +# Originals remain untouched +``` + +### Example 2: Update Specific Missions to Output Folder + +```powershell +$missions = @( + "F99th-Operation Black Gold 2.8.miz", + "F99th-Operation Ronin 1.4.miz", + "F99th-Battle of Gori 1.3.miz" +) + +$missions | .\Patch-MooseMissions.ps1 -LuaScriptPath "Moose_.lua" -OutputPath "C:\UpdatedMissions" +# Creates: Operation Black Gold 2.9.miz, Operation Ronin 1.5.miz, Battle of Gori 1.4.miz in C:\UpdatedMissions +``` + +### Example 3: Overwrite Original (Use with Caution) + +```powershell +.\Patch-MooseMissions.ps1 -MissionPath "TestMission-1.0.miz" -LuaScriptPath "Moose_.lua" -NoVersionIncrement +# WARNING: Overwrites TestMission-1.0.miz (original is lost) +``` + +## How It Works + +1. **Extraction**: The script treats .miz files as ZIP archives and extracts them to a temporary directory +2. **Script Replacement**: Locates the Lua script in `l10n/DEFAULT/` folder and replaces/adds it +3. **Version Increment**: Automatically detects version pattern and increments the patch number +4. **Repackaging**: Compresses the modified mission into a new .miz file with incremented version +5. **Cleanup**: Removes temporary files + +## Version Detection Logic + +The script intelligently detects and increments version numbers: + +- **X.Y.Z format** (e.g., `Mission-1.2.3.miz`) → Increments Z: `Mission-1.2.4.miz` +- **X.Y format** (e.g., `Mission-2.8.miz`) → Increments Y: `Mission-2.9.miz` +- **X format** (e.g., `Mission-5.miz`) → Increments X: `Mission-6.miz` +- **No version** (e.g., `Mission.miz`) → Adds version: `Mission-1.0.1.miz` + +Supports various separators: `-`, `_`, or space + +## Mission File Structure + +DCS mission files (.miz) are ZIP archives with this structure: + +``` +Mission.miz +├── mission +├── options +├── warehouses +├── l10n/ +│ └── DEFAULT/ +│ ├── dictionary +│ ├── mapResource +│ └── *.lua ← Scripts are stored here +``` + +## Important Notes + +⚠️ **Always test patched missions** before using them in production or multiplayer! + +⚠️ **Originals are safe**: By default, the script creates NEW versioned files and never modifies originals + +⚠️ **Script order matters**: If your mission has script load order dependencies, this tool only replaces the file content, not the trigger order + +⚠️ **Version increment default**: By default, versions are incremented. Use `-NoVersionIncrement` to overwrite originals (NOT recommended) + +✅ **Safe for**: Updating framework files like Moose_.lua, mist.lua, or any embedded Lua script + +✅ **Preserves originals**: Original missions remain untouched (new versioned files are created) + +✅ **No backups needed**: Since originals aren't modified, you always have your previous version + +## Troubleshooting + +### "File is not a .miz mission file" +- Ensure you're pointing to a valid .miz file, not a .lua or other file type + +### "Lua script not found" +- Verify the path to your Lua script is correct and the file exists + +### Mission doesn't work after patching +- Restore from backup +- Verify the Lua script is valid +- Check DCS.log for script errors +- Ensure you updated the correct script name + +### Permission errors +- Run PowerShell as Administrator if modifying files in protected directories +- Ensure mission files are not read-only + +## Requirements + +- Windows PowerShell 5.1 or later (or PowerShell Core 7+) +- Write permissions to mission file locations +- Valid .miz mission files +- Valid Lua script to insert + +## Author + +**F99th-TracerFacer** + +Discord: https://discord.gg/7wBVWKK3 + +## License + +Free to use and modify for the DCS community.