mirror of
https://github.com/iTracerFacer/DCS_MissionDev.git
synced 2025-12-03 04:14:46 +00:00
Too many changes to list accross to many files. Save more often dummy. :)
This commit is contained in:
parent
853a3114ee
commit
b70d208f38
@ -1,155 +0,0 @@
|
||||
# Dual Coalition Zone Capture - Full Analysis & Changes
|
||||
|
||||
## Summary
|
||||
The script has been refactored to provide **complete parity between RED and BLUE coalitions**. Both sides now have equal access to all features, information, and victory conditions.
|
||||
|
||||
---
|
||||
|
||||
## ✅ Changes Made for Full Dual-Coalition Support
|
||||
|
||||
### 1. **Tactical Markers** (Lines ~390-440)
|
||||
**BEFORE:** Only BLUE coalition received tactical markers
|
||||
**AFTER:** Both RED and BLUE receive separate tactical markers
|
||||
- Each coalition sees enemy unit positions (when ≤10 units)
|
||||
- Markers are coalition-specific and read-only
|
||||
- Uses `TacticalMarkerID_BLUE` and `TacticalMarkerID_RED` for tracking
|
||||
|
||||
### 2. **Victory Conditions** (Lines ~490-600)
|
||||
**BEFORE:** Only checked for BLUE victory
|
||||
**AFTER:** Both coalitions can win
|
||||
- BLUE Victory: All zones captured → "BLUE_VICTORY" flag set
|
||||
- RED Victory: All zones captured → "RED_VICTORY" flag set
|
||||
- Each side gets appropriate celebration effects (smoke colors, flares)
|
||||
- Proper victory/defeat messages for both sides
|
||||
|
||||
### 3. **Zone Status Reports** (Lines ~710-740)
|
||||
**BEFORE:** Only BLUE received status broadcasts
|
||||
**AFTER:** Both coalitions receive status reports
|
||||
- Each coalition sees their specific victory progress percentage
|
||||
- Same zone ownership data, customized messaging per coalition
|
||||
|
||||
### 4. **Victory Progress Monitoring** (Lines ~745-780)
|
||||
**BEFORE:** Only warned BLUE when approaching victory
|
||||
**AFTER:** Both sides get symmetric warnings
|
||||
- BLUE approaching victory (80%+) → BLUE gets encouragement, RED gets warning
|
||||
- RED approaching victory (80%+) → RED gets encouragement, BLUE gets warning
|
||||
|
||||
### 5. **F10 Radio Menu Commands** (Lines ~840-900)
|
||||
**BEFORE:** Only BLUE had F10 menu access
|
||||
**AFTER:** Both coalitions have identical F10 menus
|
||||
- "Get Zone Status Report" - Shows current zone ownership
|
||||
- "Check Victory Progress" - Shows their specific progress percentage
|
||||
- "Refresh Zone Colors" - Forces zone border redraw
|
||||
|
||||
### 6. **Zone Color Refresh Messages** (Line ~835)
|
||||
**BEFORE:** Only BLUE notified when colors refreshed
|
||||
**AFTER:** Both coalitions receive confirmation message
|
||||
|
||||
### 7. **Mission Definitions** (Lines 52-88)
|
||||
**BEFORE:** Only BLUE mission defined
|
||||
**AFTER:** Both coalitions have missions
|
||||
- BLUE: "Capture the Airfields" (offensive mission)
|
||||
- RED: "Defend the Motherland" (defensive mission)
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Features Now Available to BOTH Coalitions
|
||||
|
||||
| Feature | BLUE | RED |
|
||||
|---------|------|-----|
|
||||
| Mission Objectives | ✅ | ✅ |
|
||||
| Tactical Markers (enemy positions) | ✅ | ✅ |
|
||||
| Zone Status Reports | ✅ | ✅ |
|
||||
| Victory Progress Tracking | ✅ | ✅ |
|
||||
| Victory Conditions | ✅ | ✅ |
|
||||
| F10 Menu Commands | ✅ | ✅ |
|
||||
| Zone Color Indicators | ✅ | ✅ |
|
||||
| Capture/Attack/Guard Messages | ✅ | ✅ |
|
||||
|
||||
---
|
||||
|
||||
## 🔧 Configuration
|
||||
|
||||
Mission makers can now set up asymmetric scenarios by configuring the `ZONE_CONFIG` table:
|
||||
|
||||
```lua
|
||||
local ZONE_CONFIG = {
|
||||
RED = {
|
||||
"Kilpyavr",
|
||||
"Severomorsk-1",
|
||||
-- ... more zones
|
||||
},
|
||||
|
||||
BLUE = {
|
||||
"Banak", -- Example: BLUE starting zone
|
||||
"Kirkenes"
|
||||
},
|
||||
|
||||
NEUTRAL = {
|
||||
"Contested Valley" -- Starts empty
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎮 Gameplay Impact
|
||||
|
||||
### Balanced Competition
|
||||
- Both sides can now win by capturing all zones
|
||||
- Victory celebrations are coalition-specific (blue/red smoke & flares)
|
||||
- Mission end triggers coalition-specific flags
|
||||
|
||||
### Equal Information Access
|
||||
- Both coalitions see enemy positions in contested zones
|
||||
- Both receive periodic status updates
|
||||
- Both have access to F10 menu commands
|
||||
|
||||
### Symmetric Design
|
||||
- All event handlers work equally for both sides
|
||||
- Messages are dynamically generated based on zone ownership
|
||||
- No hardcoded coalition bias anywhere in the code
|
||||
|
||||
---
|
||||
|
||||
## 📝 Technical Notes
|
||||
|
||||
### Global Variables Used
|
||||
- `US_CC` - BLUE coalition command center
|
||||
- `RU_CC` - RED coalition command center
|
||||
- Both must be defined before loading this script
|
||||
|
||||
### User Flags Set on Victory
|
||||
- `BLUE_VICTORY` = 1 when BLUE wins
|
||||
- `RED_VICTORY` = 1 when RED wins
|
||||
|
||||
### Storage Structure
|
||||
- `zoneCaptureObjects[]` - Array of zone capture objects
|
||||
- `zoneNames[]` - Array of zone names
|
||||
- `zoneMetadata{}` - Dictionary with coalition info
|
||||
- All zones accessible via table iteration (no global zone variables)
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Migration from Old Script
|
||||
|
||||
If migrating from `Moose_CaptureZones.lua`:
|
||||
|
||||
1. **Update zone configuration** - Move zone names to `ZONE_CONFIG` table
|
||||
2. **Remove manual zone creation** - The loop handles it now
|
||||
3. **No code changes needed** for existing trigger zones in mission editor
|
||||
4. **F10 menus now available** to RED players automatically
|
||||
|
||||
---
|
||||
|
||||
## ✨ Benefits of Refactoring
|
||||
|
||||
1. **Easy to configure** - Simple table instead of repetitive code
|
||||
2. **Coalition agnostic** - Works equally for RED/BLUE/NEUTRAL
|
||||
3. **Maintainable** - Zone logic centralized in loops
|
||||
4. **Extensible** - Easy to add new features for both sides
|
||||
5. **Balanced** - True dual-coalition gameplay
|
||||
|
||||
---
|
||||
|
||||
*Last Updated: Analysis completed with full dual-coalition parity*
|
||||
Binary file not shown.
Binary file not shown.
@ -893,20 +893,30 @@ local ZoneColorVerificationScheduler = SCHEDULER:New( nil, function()
|
||||
end
|
||||
end
|
||||
|
||||
end, {}, 60, 120 ) -- Start after 60 seconds, repeat every 120 seconds (2 minutes)
|
||||
end, {}, 60, 240 ) -- Start after 60 seconds, repeat every 240 seconds (4 minutes)
|
||||
|
||||
-- Periodic tactical marker update system (every 1 minute)
|
||||
-- Periodic tactical marker update system with change detection (every 3 minutes)
|
||||
local __lastForceCountsByZone = {}
|
||||
local TacticalMarkerUpdateScheduler = SCHEDULER:New( nil, function()
|
||||
log("[TACTICAL] Running periodic tactical marker update...")
|
||||
|
||||
-- Update tactical markers for all zones
|
||||
log("[TACTICAL] Running periodic tactical marker update (change-detected)...")
|
||||
|
||||
for i, zoneCapture in ipairs(zoneCaptureObjects) do
|
||||
if zoneCapture then
|
||||
CreateTacticalInfoMarker(zoneCapture)
|
||||
local zoneName = zoneNames and zoneNames[i] or (zoneCapture.GetZoneName and zoneCapture:GetZoneName()) or ("Zone " .. i)
|
||||
local counts = GetZoneForceStrengths(zoneCapture)
|
||||
local last = __lastForceCountsByZone[zoneName]
|
||||
local changed = (not last) or (last.red ~= counts.red) or (last.blue ~= counts.blue) or (last.neutral ~= counts.neutral)
|
||||
|
||||
if changed then
|
||||
__lastForceCountsByZone[zoneName] = { red = counts.red, blue = counts.blue, neutral = counts.neutral }
|
||||
CreateTacticalInfoMarker(zoneCapture)
|
||||
else
|
||||
-- unchanged: skip marker churn
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end, {}, 30, 60 ) -- Start after 30 seconds, repeat every 60 seconds (1 minute)
|
||||
|
||||
end, {}, 30, 180 ) -- Start after 30 seconds, repeat every 180 seconds (3 minutes)
|
||||
|
||||
-- Function to refresh all zone colors based on current ownership
|
||||
local function RefreshAllZoneColors()
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
--[[
|
||||
.--[[
|
||||
Simple AFAC System v1.0
|
||||
========================
|
||||
|
||||
@ -86,8 +86,8 @@ AFAC.Config = {
|
||||
mapMarkerDuration = 120,
|
||||
smokeInterval = 300,
|
||||
|
||||
-- Target update intervals
|
||||
autoUpdateInterval = 1.0,
|
||||
-- Target update intervals (throttled to reduce per-second scanning)
|
||||
autoUpdateInterval = 2.5,
|
||||
manualScanRange = 18520,
|
||||
|
||||
-- Debug mode
|
||||
@ -962,10 +962,10 @@ timer.scheduleFunction(
|
||||
trigger.action.outText("Simple AFAC System v1.0 loaded successfully!", 10)
|
||||
AFAC.log("AFAC Script fully loaded and initialized")
|
||||
|
||||
-- Test message every 30 seconds to verify script is running
|
||||
function AFAC.heartbeat()
|
||||
AFAC.log("AFAC System heartbeat - script is running")
|
||||
timer.scheduleFunction(AFAC.heartbeat, nil, timer.getTime() + 30)
|
||||
end
|
||||
|
||||
timer.scheduleFunction(AFAC.heartbeat, nil, timer.getTime() + 30)
|
||||
-- Heartbeat disabled in production to avoid periodic UI/network churn
|
||||
-- To re-enable for debugging, uncomment lines below:
|
||||
-- function AFAC.heartbeat()
|
||||
-- AFAC.log("AFAC System heartbeat - script is running")
|
||||
-- timer.scheduleFunction(AFAC.heartbeat, nil, timer.getTime() + 30)
|
||||
-- end
|
||||
-- timer.scheduleFunction(AFAC.heartbeat, nil, timer.getTime() + 30)
|
||||
@ -31,6 +31,19 @@ local CVN_TACAN_Name = "CVN" -- put the 3-letter TACAN identifier you want to
|
||||
local CVN_RecoveryWindowTime = 20 -- time in minutes for how long recovery will be open, feel free to change the number
|
||||
|
||||
|
||||
-- Simple per-event cooldown to limit chat/sound spam (reduces network/UI churn)
|
||||
local __cvnLastMsg = {}
|
||||
local function sendThrottled(key, cooldownSec, sendFn)
|
||||
local now = timer.getTime()
|
||||
local last = __cvnLastMsg[key] or 0
|
||||
if (now - last) >= (cooldownSec or 30) then
|
||||
__cvnLastMsg[key] = now
|
||||
local ok, err = pcall(sendFn)
|
||||
if not ok then env.info("[CVN] sendThrottled error for key "..tostring(key)..": "..tostring(err)) end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- Functions to move ship to designated waypoint/zones
|
||||
local msgCVNPatrol = "Sending Carrier Group to Patrol Zone: "
|
||||
|
||||
@ -38,8 +51,10 @@ local msgCVNPatrol = "Sending Carrier Group to Patrol Zone: "
|
||||
function SetCVNPatrolZone(zoneNumber)
|
||||
if PatrolZones[zoneNumber] then
|
||||
SetCVNActivePatrolZone = zoneNumber
|
||||
MESSAGE:New(msgCVNPatrol .. SetCVNActivePatrolZone .. " (Waypoints " .. PatrolZones[zoneNumber].startWP .. "-" .. PatrolZones[zoneNumber].endWP .. ")", msgTime):ToBlue()
|
||||
USERSOUND:New("ping.ogg"):ToCoalition(coalition.side.BLUE)
|
||||
sendThrottled("CVN_SetPatrolZone", 30, function()
|
||||
MESSAGE:New(msgCVNPatrol .. SetCVNActivePatrolZone .. " (Waypoints " .. PatrolZones[zoneNumber].startWP .. "-" .. PatrolZones[zoneNumber].endWP .. ")", msgTime):ToBlue()
|
||||
USERSOUND:New("ping.ogg"):ToCoalition(coalition.side.BLUE)
|
||||
end)
|
||||
BlueCVNGroup:GotoWaypoint(PatrolZones[zoneNumber].startWP)
|
||||
else
|
||||
MESSAGE:New("Invalid patrol zone: " .. zoneNumber, msgTime):ToBlue()
|
||||
@ -93,8 +108,10 @@ function start_recovery()
|
||||
|
||||
if BlueCVNGroup:IsSteamingIntoWind() == true then
|
||||
local unitName = CVN_beacon_unit and CVN_beacon_unit:GetName() or "CVN-72 Abraham Lincoln"
|
||||
MESSAGE:New(unitName .. " is currently launching/recovering aircraft, currently active recovery window closes at time " .. timerecovery_end, msgTime, "CVNNAVINFO",false):ToBlue()
|
||||
USERSOUND:New("ping.ogg"):ToCoalition(coalition.side.BLUE)
|
||||
sendThrottled("CVN_RecoveryActive", 60, function()
|
||||
MESSAGE:New(unitName .. " is currently launching/recovering aircraft, currently active recovery window closes at time " .. timerecovery_end, msgTime, "CVNNAVINFO",false):ToBlue()
|
||||
USERSOUND:New("ping.ogg"):ToCoalition(coalition.side.BLUE)
|
||||
end)
|
||||
else
|
||||
local timenow=timer.getAbsTime( )
|
||||
local timeend=timenow+CVN_RecoveryWindowTime*60 -- this sets the recovery window to 45 minutes, you can change the numbers as you wish
|
||||
@ -102,8 +119,10 @@ function start_recovery()
|
||||
timerecovery_end = UTILS.SecondsToClock(timeend,true)
|
||||
BlueCVNGroup:AddTurnIntoWind(timerecovery_start,timerecovery_end,25,true,-9)
|
||||
local unitName = CVN_beacon_unit and CVN_beacon_unit:GetName() or "CVN-72 Abraham Lincoln"
|
||||
MESSAGE:New(unitName.." is turning into the wind to begin " .. CVN_RecoveryWindowTime .. " mins of aircraft operations.\nLaunch/Recovery Window will be open from time " .. timerecovery_start .. " until " .. timerecovery_end, msgTime, "CVNNAVINFO",false):ToBlue()
|
||||
USERSOUND:New("ping.ogg"):ToCoalition(coalition.side.BLUE)
|
||||
sendThrottled("CVN_RecoveryStart", 60, function()
|
||||
MESSAGE:New(unitName.." is turning into the wind to begin " .. CVN_RecoveryWindowTime .. " mins of aircraft operations.\nLaunch/Recovery Window will be open from time " .. timerecovery_start .. " until " .. timerecovery_end, msgTime, "CVNNAVINFO",false):ToBlue()
|
||||
USERSOUND:New("ping.ogg"):ToCoalition(coalition.side.BLUE)
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
@ -135,8 +154,10 @@ end
|
||||
function ResetAwacs()
|
||||
if BlueAwacs then
|
||||
BlueAwacs:Destroy()
|
||||
MESSAGE:New("Resetting AWACS...", msgTime, "CVNNAVINFO",false):ToBlue()
|
||||
USERSOUND:New("ping.ogg"):ToCoalition(coalition.side.BLUE)
|
||||
sendThrottled("CVN_ResetAWACS", 30, function()
|
||||
MESSAGE:New("Resetting AWACS...", msgTime, "CVNNAVINFO",false):ToBlue()
|
||||
USERSOUND:New("ping.ogg"):ToCoalition(coalition.side.BLUE)
|
||||
end)
|
||||
else
|
||||
MESSAGE:New("No AWACS to reset - AWACS functionality not available", msgTime):ToBlue()
|
||||
end
|
||||
@ -193,8 +214,10 @@ function BlueCVNGroup:OnAfterPassingWaypoint(From, Event, To, Waypoint)
|
||||
-- Debug info.
|
||||
local unitName = CVN_beacon_unit and CVN_beacon_unit:GetName() or "CVN-72 Abraham Lincoln"
|
||||
local text=string.format(unitName.." passed waypoint ID=%d (Index=%d) %d times", waypoint.uid, BlueCVNGroup:GetWaypointIndex(waypoint.uid), waypoint.npassed)
|
||||
MESSAGE:New(text, msgTime):ToBlue()
|
||||
USERSOUND:New("ping.ogg"):ToCoalition(coalition.side.BLUE)
|
||||
sendThrottled("CVN_PassingWaypoint", 20, function()
|
||||
MESSAGE:New(text, msgTime):ToBlue()
|
||||
USERSOUND:New("ping.ogg"):ToCoalition(coalition.side.BLUE)
|
||||
end)
|
||||
env.info(text)
|
||||
|
||||
-- Dynamic patrol logic based on current patrol zone
|
||||
@ -206,13 +229,13 @@ function BlueCVNGroup:OnAfterPassingWaypoint(From, Event, To, Waypoint)
|
||||
if currentWaypointID == currentZone.endWP then
|
||||
BlueCVNGroup:GotoWaypoint(currentZone.startWP)
|
||||
local patrolText = string.format("Patrolling: Going to waypoint %d (Zone %d)", currentZone.startWP, SetCVNActivePatrolZone)
|
||||
MESSAGE:New(patrolText, msgTime):ToBlue()
|
||||
sendThrottled("CVN_PatrolGoto", 10, function() MESSAGE:New(patrolText, msgTime):ToBlue() end)
|
||||
env.info(patrolText)
|
||||
-- If we reached the start waypoint of current patrol zone, go to end waypoint
|
||||
elseif currentWaypointID == currentZone.startWP then
|
||||
BlueCVNGroup:GotoWaypoint(currentZone.endWP)
|
||||
local patrolText = string.format("Patrolling: Going to waypoint %d (Zone %d)", currentZone.endWP, SetCVNActivePatrolZone)
|
||||
MESSAGE:New(patrolText, msgTime):ToBlue()
|
||||
sendThrottled("CVN_PatrolGoto", 10, function() MESSAGE:New(patrolText, msgTime):ToBlue() end)
|
||||
env.info(patrolText)
|
||||
end
|
||||
end
|
||||
@ -223,8 +246,10 @@ end
|
||||
function BlueCVNGroup:OnAfterCruise(From, Event, To)
|
||||
local unitName = CVN_beacon_unit and CVN_beacon_unit:GetName() or "CVN-72 Abraham Lincoln"
|
||||
local text=unitName.." is cruising straight and steady."
|
||||
MESSAGE:New(text, msgTime):ToBlue()
|
||||
USERSOUND:New("ping.ogg"):ToCoalition(coalition.side.BLUE)
|
||||
sendThrottled("CVN_Cruise", 30, function()
|
||||
MESSAGE:New(text, msgTime):ToBlue()
|
||||
USERSOUND:New("ping.ogg"):ToCoalition(coalition.side.BLUE)
|
||||
end)
|
||||
env.info(text)
|
||||
end
|
||||
|
||||
@ -232,8 +257,10 @@ end
|
||||
function BlueCVNGroup:OnAfterTurningStarted(From, Event, To)
|
||||
local unitName = CVN_beacon_unit and CVN_beacon_unit:GetName() or "CVN-72 Abraham Lincoln"
|
||||
local text=unitName.." has started turning!"
|
||||
MESSAGE:New(text, msgTime):ToBlue()
|
||||
USERSOUND:New("ping.ogg"):ToCoalition(coalition.side.BLUE)
|
||||
sendThrottled("CVN_TurningStarted", 30, function()
|
||||
MESSAGE:New(text, msgTime):ToBlue()
|
||||
USERSOUND:New("ping.ogg"):ToCoalition(coalition.side.BLUE)
|
||||
end)
|
||||
env.info(text)
|
||||
end
|
||||
|
||||
@ -241,8 +268,10 @@ end
|
||||
function BlueCVNGroup:OnAfterTurningStopped(From, Event, To)
|
||||
local unitName = CVN_beacon_unit and CVN_beacon_unit:GetName() or "CVN-72 Abraham Lincoln"
|
||||
local text=unitName.." has stopped turning..proceeding to next waypoint."
|
||||
MESSAGE:New(text, msgTime):ToBlue()
|
||||
USERSOUND:New("ping.ogg"):ToCoalition(coalition.side.BLUE)
|
||||
sendThrottled("CVN_TurningStopped", 30, function()
|
||||
MESSAGE:New(text, msgTime):ToBlue()
|
||||
USERSOUND:New("ping.ogg"):ToCoalition(coalition.side.BLUE)
|
||||
end)
|
||||
env.info(text)
|
||||
end
|
||||
|
||||
@ -262,8 +291,10 @@ BlueCVNGroup:SetCheckZones(ZoneSet)
|
||||
function BlueCVNGroup:OnAfterEnterZone(From, Event, To, Zone)
|
||||
local unitName = CVN_beacon_unit and CVN_beacon_unit:GetName() or "CVN-72 Abraham Lincoln"
|
||||
local text=string.format(unitName.." has entered patrol zone %s.", Zone:GetName())
|
||||
MESSAGE:New(text, msgTime):ToBlue()
|
||||
USERSOUND:New("ping.ogg"):ToCoalition(coalition.side.BLUE)
|
||||
sendThrottled("CVN_EnterZone", 20, function()
|
||||
MESSAGE:New(text, msgTime):ToBlue()
|
||||
USERSOUND:New("ping.ogg"):ToCoalition(coalition.side.BLUE)
|
||||
end)
|
||||
env.info(text)
|
||||
end
|
||||
|
||||
@ -271,7 +302,9 @@ end
|
||||
function BlueCVNGroup:OnAfterLeaveZone(From, Event, To, Zone)
|
||||
local unitName = CVN_beacon_unit and CVN_beacon_unit:GetName() or "CVN-72 Abraham Lincoln"
|
||||
local text=string.format(unitName.." left patrol zone %s.", Zone:GetName())
|
||||
MESSAGE:New(text, msgTime):ToBlue()
|
||||
USERSOUND:New("ping.ogg"):ToCoalition(coalition.side.BLUE)
|
||||
sendThrottled("CVN_LeaveZone", 20, function()
|
||||
MESSAGE:New(text, msgTime):ToBlue()
|
||||
USERSOUND:New("ping.ogg"):ToCoalition(coalition.side.BLUE)
|
||||
end)
|
||||
env.info(text)
|
||||
end
|
||||
@ -19,6 +19,13 @@ REQUIRES:
|
||||
═══════════════════════════════════════════════════════════════════════════════
|
||||
]]
|
||||
|
||||
-- Single-run guard to prevent duplicate dispatcher loops if script is reloaded
|
||||
if _G.__TDAC_DISPATCHER_RUNNING then
|
||||
env.info("[TDAC] CargoDispatcher already running; aborting duplicate load")
|
||||
return
|
||||
end
|
||||
_G.__TDAC_DISPATCHER_RUNNING = true
|
||||
|
||||
--[[
|
||||
GLOBAL STATE AND CONFIGURATION
|
||||
--------------------------------------------------------------------------
|
||||
|
||||
@ -5,6 +5,7 @@ trigger.action.outText("OnBirthMessage script is loading...", 10)
|
||||
-- Player preferences storage
|
||||
local playerWelcomeSettings = {}
|
||||
local processedPlayers = {} -- Track players to prevent double processing
|
||||
local DUP_TTL_SECONDS = 10 -- window to ignore duplicate events for the same player
|
||||
|
||||
-- F10 Menu Functions
|
||||
local function enableWelcomeMessage(playerUnitID, playerName)
|
||||
@ -77,19 +78,20 @@ function onPlayerJoin:onEvent(event)
|
||||
if (event.id == world.event.S_EVENT_BIRTH or event.id == world.event.S_EVENT_ENGINE_STARTUP) then
|
||||
env.info("OnBirthMessage: Correct event type detected")
|
||||
|
||||
if event.initiator then
|
||||
if event.initiator then
|
||||
env.info("OnBirthMessage: Initiator exists")
|
||||
local playerName = event.initiator:getPlayerName()
|
||||
if playerName then
|
||||
env.info("OnBirthMessage: Player name found: " .. playerName)
|
||||
|
||||
-- Check if we've already processed this player to prevent doubles
|
||||
local playerKey = playerName .. "_" .. event.id
|
||||
if processedPlayers[playerKey] then
|
||||
env.info("OnBirthMessage: Already processed " .. playerName .. " for event " .. event.id .. " - skipping")
|
||||
-- Check if we've already processed this player to prevent doubles (within TTL)
|
||||
local now = (event.time or timer.getTime())
|
||||
local last = processedPlayers[playerName]
|
||||
if last and (now - last) < DUP_TTL_SECONDS then
|
||||
env.info("OnBirthMessage: Duplicate event for " .. playerName .. " within TTL - skipping")
|
||||
return
|
||||
end
|
||||
processedPlayers[playerKey] = true
|
||||
processedPlayers[playerName] = now
|
||||
|
||||
-- Add error handling to prevent script crashes
|
||||
local success, errorMsg = pcall(function()
|
||||
|
||||
716
Moose_.lua
716
Moose_.lua
File diff suppressed because it is too large
Load Diff
132469
Moose_.lua.backup
Normal file
132469
Moose_.lua.backup
Normal file
File diff suppressed because it is too large
Load Diff
552
Moose_CTLD_Pure/Moose_CTLD.lua
Normal file
552
Moose_CTLD_Pure/Moose_CTLD.lua
Normal file
@ -0,0 +1,552 @@
|
||||
-- Moose_CTLD.lua
|
||||
-- Pure-MOOSE, template-free CTLD-style logistics & troop transport
|
||||
-- Drop-in script: no MIST, no mission editor templates required
|
||||
-- Dependencies: Moose.lua must be loaded before this script
|
||||
-- Author: Copilot (generated)
|
||||
|
||||
-- Contract
|
||||
-- Inputs: Config table or defaults. No ME templates needed. Zones may be named ME trigger zones or provided via coordinates in config.
|
||||
-- Outputs: F10 menus for helo/transport groups; crate spawning/building; troop load/unload; optional JTAC hookup (via FAC module);
|
||||
-- Error modes: missing Moose -> abort; unknown crate key -> message; spawn blocked in enemy airbase; zone missing -> message.
|
||||
|
||||
if not _G.Moose or not _G.BASE then
|
||||
env.info('[Moose_CTLD] Moose not detected (BASE class missing). Ensure Moose.lua is loaded before Moose_CTLD.lua')
|
||||
end
|
||||
|
||||
local CTLD = {}
|
||||
CTLD.__index = CTLD
|
||||
|
||||
-- =========================
|
||||
-- Defaults and State
|
||||
-- =========================
|
||||
CTLD.Version = '0.1.0-alpha'
|
||||
|
||||
CTLD.Config = {
|
||||
CoalitionSide = coalition.side.BLUE, -- default coalition this instance serves (menus created for this side)
|
||||
AllowedAircraft = { -- transport-capable unit type names (case-sensitive as in DCS DB)
|
||||
'UH-1H','Mi-8MTV2','Mi-24P','SA342M','SA342L','SA342Minigun','Ka-50','Ka-50_3','AH-64D_BLK_II','UH-60L','CH-47Fbl1','CH-47F','Mi-17','GazelleAI'
|
||||
},
|
||||
UseGroupMenus = true, -- if true, F10 menus per player group; otherwise coalition-wide
|
||||
UseCategorySubmenus = true, -- if true, organize crate requests by category submenu (menuCategory)
|
||||
BuildRadius = 60, -- meters around build point to collect crates
|
||||
CrateLifetime = 3600, -- seconds before crates auto-clean
|
||||
MessageDuration = 15, -- seconds for on-screen messages
|
||||
Debug = false,
|
||||
PickupZoneSmokeColor = trigger.smokeColor.Green, -- default smoke color when spawning crates at pickup zones
|
||||
|
||||
Zones = { -- Optional: supply by name (ME trigger zones) or define coordinates inline
|
||||
PickupZones = {
|
||||
-- examples:
|
||||
-- { name = 'PICKUP_BLUE_MAIN' },
|
||||
-- { name = 'Pickup-West', smoke = trigger.smokeColor.Green },
|
||||
-- { coord = { x = 12345, y = 0, z = 67890 }, radius = 150, name = 'ScriptPickup1' },
|
||||
},
|
||||
DropZones = {
|
||||
-- { name = 'DROP_BLUE_1' },
|
||||
},
|
||||
FOBZones = {
|
||||
-- optional: where FOB crates can unpack to spawn FARP/FOB assets
|
||||
},
|
||||
},
|
||||
|
||||
-- Crate catalog: key -> crate properties and build recipe
|
||||
-- No ME templates; unit compositions are defined directly here.
|
||||
CrateCatalog = {
|
||||
-- Example: MANPADS team requiring 2 crates
|
||||
MANPADS = {
|
||||
description = '2x Crates -> MANPADS team',
|
||||
weight = 120, -- affects sling/limits only informationally (no physics in script)
|
||||
dcsCargoType = 'uh1h_cargo', -- static cargo type (can adjust per DCS version); user-tunable
|
||||
required = 2, -- number of crates to assemble
|
||||
side = coalition.side.BLUE,
|
||||
category = Group.Category.GROUND,
|
||||
build = function(point, headingDeg)
|
||||
local u1 = { type = 'Soldier stinger', name = string.format('CTLD-MANPADS-%d', math.random(100000,999999)),
|
||||
x = point.x, y = point.z, heading = math.rad(headingDeg or 0) }
|
||||
local group = {
|
||||
visible = false, lateActivation = false, tasks = {}, task = 'Ground Nothing',
|
||||
units = { u1 }, route = { }, name = string.format('CTLD_MANPADS_%d', math.random(100000,999999))
|
||||
}
|
||||
return group
|
||||
end,
|
||||
},
|
||||
-- Example: AAA site needing 3 crates
|
||||
AAA = {
|
||||
description = '3x Crates -> ZU-23 site',
|
||||
weight = 400,
|
||||
dcsCargoType = 'container_cargo',
|
||||
required = 3,
|
||||
side = coalition.side.BLUE,
|
||||
category = Group.Category.GROUND,
|
||||
build = function(point, headingDeg)
|
||||
local hdg = math.rad(headingDeg or 0)
|
||||
local function offset(dx, dz) return { x = point.x + dx, z = point.z + dz } end
|
||||
local units = {
|
||||
{ type='ZU-23 Emplacement', name=string.format('CTLD-ZU23-%d', math.random(100000,999999)), x=point.x, y=point.z, heading=hdg },
|
||||
{ type='Ural-375', name=string.format('CTLD-TRK-%d', math.random(100000,999999)), x=offset(15, 12).x, y=offset(15, 12).z, heading=hdg },
|
||||
{ type='Infantry AK', name=string.format('CTLD-INF-%d', math.random(100000,999999)), x=offset(-12,-15).x, y=offset(-12,-15).z, heading=hdg },
|
||||
}
|
||||
return { visible=false, lateActivation=false, tasks={}, task='Ground Nothing', units=units, route={}, name=string.format('CTLD_AAA_%d', math.random(100000,999999)) }
|
||||
end,
|
||||
},
|
||||
-- Example: FARP/FOB build from 4 crates (spawns helipads + support statics/vehicles)
|
||||
FOB = {
|
||||
description = '4x Crates -> FARP/FOB',
|
||||
weight = 500,
|
||||
dcsCargoType = 'container_cargo',
|
||||
required = 4,
|
||||
side = coalition.side.BLUE,
|
||||
category = Group.Category.GROUND,
|
||||
build = function(point, headingDeg)
|
||||
local heading = math.rad(headingDeg or 0)
|
||||
-- Spawn statics that provide FARP services
|
||||
local function addStatic(typeName, dx, dz, nameSuffix)
|
||||
local p = { x = point.x + dx, z = point.z + dz }
|
||||
local st = {
|
||||
name = string.format('CTLD-FOB-%s-%d', nameSuffix, math.random(100000,999999)),
|
||||
type = typeName,
|
||||
x = p.x, y = p.z,
|
||||
heading = heading,
|
||||
}
|
||||
coalition.addStaticObject(coalition.side.BLUE, st)
|
||||
end
|
||||
-- Common FARP layout
|
||||
addStatic('FARP', 0, 0, 'PAD')
|
||||
addStatic('FARP Ammo Dump Coating', 15, 10, 'AMMO')
|
||||
addStatic('FARP Fuel Depot', -15, 10, 'FUEL')
|
||||
addStatic('FARP Tent', 10, -12, 'TENT1')
|
||||
addStatic('FARP Tent', -10, -12, 'TENT2')
|
||||
-- Ground support vehicles to enable rearm/refuel/repair
|
||||
local units = {
|
||||
{ type='HEMTT TFFT', name=string.format('CTLD-FOB-FUEL-%d', math.random(100000,999999)), x=point.x + 20, y=point.z + 15, heading=heading },
|
||||
{ type='Ural-375 PBU', name=string.format('CTLD-FOB-REPAIR-%d', math.random(100000,999999)), x=point.x - 20, y=point.z + 15, heading=heading },
|
||||
{ type='Ural-375', name=string.format('CTLD-FOB-AMMO-%d', math.random(100000,999999)), x=point.x, y=point.z - 18, heading=heading },
|
||||
}
|
||||
return { visible=false, lateActivation=false, tasks={}, task='Ground Nothing', units=units, route={}, name=string.format('CTLD_FOB_%d', math.random(100000,999999)) }
|
||||
end,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
-- Internal state tables
|
||||
CTLD._instances = CTLD._instances or {}
|
||||
CTLD._crates = {} -- [crateName] = { key, zone, side, spawnTime, point }
|
||||
CTLD._troopsLoaded = {} -- [groupName] = { count, typeKey }
|
||||
|
||||
-- =========================
|
||||
-- Utilities
|
||||
-- =========================
|
||||
local function _isIn(list, value)
|
||||
for _,v in ipairs(list or {}) do if v == value then return true end end
|
||||
return false
|
||||
end
|
||||
|
||||
local function _msgGroup(group, text, t)
|
||||
if not group then return end
|
||||
MESSAGE:New(text, t or CTLD.Config.MessageDuration):ToGroup(group)
|
||||
end
|
||||
|
||||
local function _msgCoalition(side, text, t)
|
||||
MESSAGE:New(text, t or CTLD.Config.MessageDuration):ToCoalition(side)
|
||||
end
|
||||
|
||||
local function _findZone(z)
|
||||
if z.name then
|
||||
local mz = ZONE:FindByName(z.name)
|
||||
if mz then return mz end
|
||||
end
|
||||
if z.coord then
|
||||
local r = z.radius or 150
|
||||
local v = VECTOR2:New(z.coord.x, z.coord.z)
|
||||
return ZONE_RADIUS:New(z.name or ('CTLD_ZONE_'..math.random(10000,99999)), v, r)
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
local function _getUnitType(unit)
|
||||
local ud = unit and unit:GetDesc() or nil
|
||||
return ud and ud.typeName or unit and unit:GetTypeName()
|
||||
end
|
||||
|
||||
local function _nearestZonePoint(unit, list)
|
||||
if not unit or not unit:IsAlive() then return nil end
|
||||
local p3 = unit:GetPointVec3()
|
||||
local best, bestd = nil, 1e12
|
||||
for _,z in ipairs(list or {}) do
|
||||
local mz = _findZone(z)
|
||||
if mz then
|
||||
local d = p3:DistanceFromPoint(mz:GetPointVec3())
|
||||
if d < bestd then best, bestd = mz, d end
|
||||
end
|
||||
end
|
||||
return best, bestd
|
||||
end
|
||||
|
||||
local function _coalitionAddGroup(side, category, groupData)
|
||||
-- Enforce side/category in groupData just to be safe
|
||||
groupData.category = category
|
||||
return coalition.addGroup(side, category, groupData)
|
||||
end
|
||||
|
||||
local function _spawnStaticCargo(side, point, cargoType, name)
|
||||
local static = {
|
||||
name = name,
|
||||
type = cargoType,
|
||||
x = point.x,
|
||||
y = point.z,
|
||||
heading = 0,
|
||||
canCargo = true,
|
||||
}
|
||||
return coalition.addStaticObject(side, static)
|
||||
end
|
||||
|
||||
local function _vec3FromUnit(unit)
|
||||
local p = unit:GetPointVec3()
|
||||
return { x = p.x, y = p.y, z = p.z }
|
||||
end
|
||||
|
||||
-- =========================
|
||||
-- Construction
|
||||
-- =========================
|
||||
function CTLD:New(cfg)
|
||||
local o = setmetatable({}, self)
|
||||
o.Config = BASE:DeepCopy(CTLD.Config)
|
||||
if cfg then o.Config = BASE:Inherit(o.Config, cfg) end
|
||||
o.Side = o.Config.CoalitionSide
|
||||
o.MenuRoots = {}
|
||||
o.MenusByGroup = {}
|
||||
o:InitZones()
|
||||
o:InitMenus()
|
||||
|
||||
-- Periodic cleanup for crates
|
||||
o.Sched = SCHEDULER:New(nil, function()
|
||||
o:CleanupCrates()
|
||||
end, {}, 60, 60)
|
||||
|
||||
table.insert(CTLD._instances, o)
|
||||
_msgCoalition(o.Side, string.format('CTLD %s initialized for coalition', CTLD.Version))
|
||||
return o
|
||||
end
|
||||
|
||||
function CTLD:InitZones()
|
||||
self.PickupZones = {}
|
||||
self.DropZones = {}
|
||||
self.FOBZones = {}
|
||||
self._ZoneDefs = { PickupZones = {}, DropZones = {}, FOBZones = {} }
|
||||
for _,z in ipairs(self.Config.Zones.PickupZones or {}) do
|
||||
local mz = _findZone(z)
|
||||
if mz then table.insert(self.PickupZones, mz); self._ZoneDefs.PickupZones[mz:GetName()] = z end
|
||||
end
|
||||
for _,z in ipairs(self.Config.Zones.DropZones or {}) do
|
||||
local mz = _findZone(z)
|
||||
if mz then table.insert(self.DropZones, mz); self._ZoneDefs.DropZones[mz:GetName()] = z end
|
||||
end
|
||||
for _,z in ipairs(self.Config.Zones.FOBZones or {}) do
|
||||
local mz = _findZone(z)
|
||||
if mz then table.insert(self.FOBZones, mz); self._ZoneDefs.FOBZones[mz:GetName()] = z end
|
||||
end
|
||||
end
|
||||
|
||||
-- =========================
|
||||
-- Menus
|
||||
-- =========================
|
||||
function CTLD:InitMenus()
|
||||
if self.Config.UseGroupMenus then
|
||||
self:WireBirthHandler()
|
||||
else
|
||||
self.MenuRoot = MENU_COALITION:New(self.Side, 'CTLD')
|
||||
self:BuildCoalitionMenus(self.MenuRoot)
|
||||
end
|
||||
end
|
||||
|
||||
function CTLD:WireBirthHandler()
|
||||
local handler = EVENTHANDLER:New()
|
||||
handler:HandleEvent(EVENTS.Birth)
|
||||
local selfref = self
|
||||
function handler:OnEventBirth(eventData)
|
||||
local unit = eventData.IniUnit
|
||||
if not unit or not unit:IsAlive() then return end
|
||||
if unit:GetCoalition() ~= selfref.Side then return end
|
||||
local typ = _getUnitType(unit)
|
||||
if not _isIn(selfref.Config.AllowedAircraft, typ) then return end
|
||||
local grp = unit:GetGroup()
|
||||
if not grp then return end
|
||||
local gname = grp:GetName()
|
||||
if selfref.MenusByGroup[gname] then return end
|
||||
selfref.MenusByGroup[gname] = selfref:BuildGroupMenus(grp)
|
||||
_msgGroup(grp, 'CTLD menu available (F10)')
|
||||
end
|
||||
self.BirthHandler = handler
|
||||
end
|
||||
|
||||
function CTLD:BuildGroupMenus(group)
|
||||
local root = MENU_GROUP:New(group, 'CTLD')
|
||||
-- Request crate submenu per catalog entry
|
||||
local reqRoot = MENU_GROUP:New(group, 'Request Crate', root)
|
||||
if self.Config.UseCategorySubmenus then
|
||||
local submenus = {}
|
||||
local function getSubmenu(catLabel)
|
||||
if not submenus[catLabel] then
|
||||
submenus[catLabel] = MENU_GROUP:New(group, catLabel, reqRoot)
|
||||
end
|
||||
return submenus[catLabel]
|
||||
end
|
||||
for key,def in pairs(self.Config.CrateCatalog) do
|
||||
local label = (def and (def.menu or def.description)) or key
|
||||
local sideOk = (not def.side) or def.side == self.Side
|
||||
if sideOk then
|
||||
local catLabel = (def and def.menuCategory) or 'Other'
|
||||
local parent = getSubmenu(catLabel)
|
||||
MENU_GROUP_COMMAND:New(group, label, parent, function()
|
||||
self:RequestCrateForGroup(group, key)
|
||||
end)
|
||||
end
|
||||
end
|
||||
else
|
||||
for key,def in pairs(self.Config.CrateCatalog) do
|
||||
local label = (def and (def.menu or def.description)) or key
|
||||
local sideOk = (not def.side) or def.side == self.Side
|
||||
if sideOk then
|
||||
MENU_GROUP_COMMAND:New(group, label, reqRoot, function()
|
||||
self:RequestCrateForGroup(group, key)
|
||||
end)
|
||||
end
|
||||
end
|
||||
end
|
||||
-- Troops
|
||||
MENU_GROUP_COMMAND:New(group, 'Load Troops', root, function() self:LoadTroops(group) end)
|
||||
MENU_GROUP_COMMAND:New(group, 'Unload Troops', root, function() self:UnloadTroops(group) end)
|
||||
|
||||
-- Build
|
||||
MENU_GROUP_COMMAND:New(group, 'Build Here', root, function()
|
||||
self:BuildAtGroup(group)
|
||||
end)
|
||||
|
||||
return root
|
||||
end
|
||||
|
||||
function CTLD:BuildCoalitionMenus(root)
|
||||
-- Optional: implement coalition-level crate spawns at pickup zones
|
||||
for key,_ in pairs(self.Config.CrateCatalog) do
|
||||
MENU_COALITION_COMMAND:New(self.Side, 'Spawn '..key..' at nearest Pickup Zone', root, function()
|
||||
-- Not group-context; skip here
|
||||
_msgCoalition(self.Side, 'Group menus recommended for crate requests')
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
-- =========================
|
||||
-- Crates
|
||||
-- =========================
|
||||
function CTLD:RequestCrateForGroup(group, crateKey)
|
||||
local cat = self.Config.CrateCatalog[crateKey]
|
||||
if not cat then _msgGroup(group, 'Unknown crate type: '..tostring(crateKey)) return end
|
||||
local unit = group:GetUnit(1)
|
||||
if not unit or not unit:IsAlive() then return end
|
||||
local zone, dist = _nearestZonePoint(unit, self.Config.Zones.PickupZones)
|
||||
local spawnPoint
|
||||
if zone and dist < 10000 then
|
||||
spawnPoint = zone:GetPointVec3()
|
||||
-- if pickup zone has smoke configured, mark it
|
||||
local zdef = self._ZoneDefs.PickupZones[zone:GetName()]
|
||||
local smokeColor = (zdef and zdef.smoke) or self.Config.PickupZoneSmokeColor
|
||||
if smokeColor then
|
||||
trigger.action.smoke({ x = spawnPoint.x, z = spawnPoint.z }, smokeColor)
|
||||
end
|
||||
else
|
||||
-- fallback: spawn near aircraft current position (safe offset)
|
||||
local p = unit:GetPointVec3()
|
||||
spawnPoint = POINT_VEC3:New(p.x + 10, p.y, p.z + 10)
|
||||
end
|
||||
local cname = string.format('CTLD_CRATE_%s_%d', crateKey, math.random(100000,999999))
|
||||
_spawnStaticCargo(self.Side, { x = spawnPoint.x, z = spawnPoint.z }, cat.dcsCargoType or 'uh1h_cargo', cname)
|
||||
CTLD._crates[cname] = {
|
||||
key = crateKey,
|
||||
side = self.Side,
|
||||
spawnTime = timer.getTime(),
|
||||
point = { x = spawnPoint.x, z = spawnPoint.z },
|
||||
}
|
||||
_msgGroup(group, string.format('Spawned crate %s at %s', crateKey, zone and zone:GetName() or 'current pos'))
|
||||
end
|
||||
|
||||
function CTLD:GetNearbyCrates(point, radius)
|
||||
local result = {}
|
||||
for name,meta in pairs(CTLD._crates) do
|
||||
local dx = (meta.point.x - point.x)
|
||||
local dz = (meta.point.z - point.z)
|
||||
local d = math.sqrt(dx*dx + dz*dz)
|
||||
if d <= radius then
|
||||
table.insert(result, { name = name, meta = meta })
|
||||
end
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
function CTLD:CleanupCrates()
|
||||
local now = timer.getTime()
|
||||
local life = self.Config.CrateLifetime
|
||||
for name,meta in pairs(CTLD._crates) do
|
||||
if now - (meta.spawnTime or now) > life then
|
||||
local obj = StaticObject.getByName(name)
|
||||
if obj then obj:destroy() end
|
||||
CTLD._crates[name] = nil
|
||||
if self.Config.Debug then env.info('[CTLD] Cleaned up crate '..name) end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- =========================
|
||||
-- Build logic
|
||||
-- =========================
|
||||
function CTLD:BuildAtGroup(group)
|
||||
local unit = group:GetUnit(1)
|
||||
if not unit or not unit:IsAlive() then return end
|
||||
local p = unit:GetPointVec3()
|
||||
local here = { x = p.x, z = p.z }
|
||||
local radius = self.Config.BuildRadius
|
||||
local nearby = self:GetNearbyCrates(here, radius)
|
||||
if #nearby == 0 then _msgGroup(group, 'No crates within '..radius..'m') return end
|
||||
|
||||
-- Count by key
|
||||
local counts = {}
|
||||
for _,c in ipairs(nearby) do
|
||||
counts[c.meta.key] = (counts[c.meta.key] or 0) + 1
|
||||
end
|
||||
|
||||
-- Helper to consume crates of a given key/qty
|
||||
local function consumeCrates(key, qty)
|
||||
local removed = 0
|
||||
for _,c in ipairs(nearby) do
|
||||
if removed >= qty then break end
|
||||
if c.meta.key == key then
|
||||
local obj = StaticObject.getByName(c.name)
|
||||
if obj then obj:destroy() end
|
||||
CTLD._crates[c.name] = nil
|
||||
removed = removed + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Try composite recipes first (requires is a map of key->qty)
|
||||
for recipeKey,cat in pairs(self.Config.CrateCatalog) do
|
||||
if type(cat.requires) == 'table' and cat.build then
|
||||
local ok = true
|
||||
for reqKey,qty in pairs(cat.requires) do
|
||||
if (counts[reqKey] or 0) < qty then ok = false; break end
|
||||
end
|
||||
if ok then
|
||||
local hdg = unit:GetHeading()
|
||||
local gdata = cat.build({ x = here.x, z = here.z }, math.deg(hdg))
|
||||
local g = _coalitionAddGroup(cat.side or self.Side, cat.category or Group.Category.GROUND, gdata)
|
||||
if g then
|
||||
for reqKey,qty in pairs(cat.requires) do consumeCrates(reqKey, qty) end
|
||||
_msgGroup(group, string.format('Built %s at your location', cat.description or recipeKey))
|
||||
return
|
||||
else
|
||||
_msgGroup(group, 'Build failed: DCS group spawn error')
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Then single-key recipes
|
||||
for key,count in pairs(counts) do
|
||||
local cat = self.Config.CrateCatalog[key]
|
||||
if cat and cat.build and (not cat.requires) and count >= (cat.required or 1) then
|
||||
local hdg = unit:GetHeading()
|
||||
local gdata = cat.build({ x = here.x, z = here.z }, math.deg(hdg))
|
||||
local g = _coalitionAddGroup(cat.side or self.Side, cat.category or Group.Category.GROUND, gdata)
|
||||
if g then
|
||||
consumeCrates(key, cat.required or 1)
|
||||
_msgGroup(group, string.format('Built %s at your location', cat.description or key))
|
||||
return
|
||||
else
|
||||
_msgGroup(group, 'Build failed: DCS group spawn error')
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
_msgGroup(group, 'Insufficient crates to build any asset here')
|
||||
end
|
||||
|
||||
-- =========================
|
||||
-- Troops
|
||||
-- =========================
|
||||
function CTLD:LoadTroops(group, opts)
|
||||
local gname = group:GetName()
|
||||
local unit = group:GetUnit(1)
|
||||
if not unit or not unit:IsAlive() then return end
|
||||
|
||||
local capacity = 6 -- simple default; can be adjusted per type later
|
||||
CTLD._troopsLoaded[gname] = {
|
||||
count = capacity,
|
||||
typeKey = 'RIFLE',
|
||||
}
|
||||
_msgGroup(group, string.format('Loaded %d troops (virtual). Use Unload Troops to deploy.', capacity))
|
||||
end
|
||||
|
||||
function CTLD:UnloadTroops(group)
|
||||
local gname = group:GetName()
|
||||
local load = CTLD._troopsLoaded[gname]
|
||||
if not load or (load.count or 0) == 0 then _msgGroup(group, 'No troops onboard') return end
|
||||
|
||||
local unit = group:GetUnit(1)
|
||||
if not unit or not unit:IsAlive() then return end
|
||||
local p = unit:GetPointVec3()
|
||||
local here = { x = p.x, z = p.z }
|
||||
local hdg = unit:GetHeading()
|
||||
|
||||
local count = load.count
|
||||
-- Spawn a simple infantry fireteam
|
||||
local units = {}
|
||||
for i=1, math.min(count, 8) do
|
||||
table.insert(units, {
|
||||
type = 'Infantry AK', name = string.format('CTLD-TROOP-%d', math.random(100000,999999)),
|
||||
x = here.x + i*1.5, y = here.z + (i%2==0 and 2 or -2), heading = hdg
|
||||
})
|
||||
end
|
||||
local groupData = {
|
||||
visible=false, lateActivation=false, tasks={}, task='Ground Nothing',
|
||||
units=units, route={}, name=string.format('CTLD_TROOPS_%d', math.random(100000,999999))
|
||||
}
|
||||
local spawned = _coalitionAddGroup(self.Side, Group.Category.GROUND, groupData)
|
||||
if spawned then
|
||||
CTLD._troopsLoaded[gname] = nil
|
||||
_msgGroup(group, string.format('Deployed %d troops', #units))
|
||||
else
|
||||
_msgGroup(group, 'Deploy failed: DCS group spawn error')
|
||||
end
|
||||
end
|
||||
|
||||
-- =========================
|
||||
-- Public helpers
|
||||
-- =========================
|
||||
function CTLD:RegisterCrate(key, def)
|
||||
self.Config.CrateCatalog[key] = def
|
||||
end
|
||||
|
||||
function CTLD:MergeCatalog(tbl)
|
||||
for k,v in pairs(tbl or {}) do self.Config.CrateCatalog[k] = v end
|
||||
end
|
||||
|
||||
function CTLD:AddPickupZone(z)
|
||||
local mz = _findZone(z)
|
||||
if mz then table.insert(self.PickupZones, mz); table.insert(self.Config.Zones.PickupZones, z) end
|
||||
end
|
||||
|
||||
function CTLD:AddDropZone(z)
|
||||
local mz = _findZone(z)
|
||||
if mz then table.insert(self.DropZones, mz); table.insert(self.Config.Zones.DropZones, z) end
|
||||
end
|
||||
|
||||
function CTLD:SetAllowedAircraft(list)
|
||||
self.Config.AllowedAircraft = BASE:DeepCopy(list)
|
||||
end
|
||||
|
||||
-- =========================
|
||||
-- Return factory
|
||||
-- =========================
|
||||
_MOOSE_CTLD = CTLD
|
||||
return CTLD
|
||||
207
Moose_CTLD_Pure/Moose_CTLD_FAC.lua
Normal file
207
Moose_CTLD_Pure/Moose_CTLD_FAC.lua
Normal file
@ -0,0 +1,207 @@
|
||||
-- Moose_CTLD_FAC.lua
|
||||
-- FAC/RECCE features integrated with pure-MOOSE CTLD
|
||||
-- Provides: recce zones, auto target marking (smoke/illum), JTAC auto-lase bootstrap, optional artillery mark tasks
|
||||
|
||||
if not _G.Moose or not _G.BASE then
|
||||
env.info('[Moose_CTLD_FAC] Moose not detected. Ensure Moose.lua is loaded before this script.')
|
||||
end
|
||||
|
||||
local FAC = {}
|
||||
FAC.__index = FAC
|
||||
FAC.Version = '0.1.0-alpha'
|
||||
|
||||
FAC.Config = {
|
||||
CoalitionSide = coalition.side.BLUE,
|
||||
ScanInterval = 20, -- seconds between scans
|
||||
MarkSmokeColor = trigger.smokeColor.Red,
|
||||
MarkIllum = false, -- drop illumination at night if true
|
||||
MarkText = true, -- place map marks with target info
|
||||
DetectionRadius = 5000, -- meters within zone
|
||||
MinReportSeparation = 400, -- meters between subsequent marks to reduce spam
|
||||
UseGroupMenus = true,
|
||||
Debug = false,
|
||||
Arty = { -- optional artillery support
|
||||
Enabled = true,
|
||||
Groups = { -- names of friendly artillery groups to use for marking
|
||||
-- 'BLUE_ARTY_1',
|
||||
},
|
||||
Rounds = 3,
|
||||
Spread = 120, -- meters randomization around mark point
|
||||
}
|
||||
}
|
||||
|
||||
FAC._lastMarks = {} -- [zoneName] = { lastPoint = {x,z} }
|
||||
|
||||
function FAC:New(ctld, cfg)
|
||||
local o = setmetatable({}, self)
|
||||
o.CTLD = ctld
|
||||
o.Config = BASE:DeepCopy(FAC.Config)
|
||||
if cfg then o.Config = BASE:Inherit(o.Config, cfg) end
|
||||
o.Side = o.Config.CoalitionSide
|
||||
o.Zones = {}
|
||||
o.MenusByGroup = {}
|
||||
|
||||
if o.Config.UseGroupMenus then o:WireBirthHandler() end
|
||||
return o
|
||||
end
|
||||
|
||||
function FAC:WireBirthHandler()
|
||||
local handler = EVENTHANDLER:New()
|
||||
handler:HandleEvent(EVENTS.Birth)
|
||||
local selfref = self
|
||||
function handler:OnEventBirth(eventData)
|
||||
local unit = eventData.IniUnit
|
||||
if not unit or not unit:IsAlive() then return end
|
||||
if unit:GetCoalition() ~= selfref.Side then return end
|
||||
local grp = unit:GetGroup()
|
||||
if not grp then return end
|
||||
local gname = grp:GetName()
|
||||
if selfref.MenusByGroup[gname] then return end
|
||||
-- Simple menu: FAC actions
|
||||
local root = MENU_GROUP:New(grp, 'FAC/RECCE')
|
||||
MENU_GROUP_COMMAND:New(grp, 'List Recce Zones', root, function() selfref:MenuListZones(grp) end)
|
||||
MENU_GROUP_COMMAND:New(grp, 'Mark Contacts (all zones)', root, function() selfref:ForceScanAll(grp) end)
|
||||
selfref.MenusByGroup[gname] = root
|
||||
MESSAGE:New('FAC/RECCE menu available (F10)', 10):ToGroup(grp)
|
||||
end
|
||||
self.BirthHandler = handler
|
||||
end
|
||||
|
||||
function FAC:AddRecceZone(def)
|
||||
-- def: { name='ZONE_NAME' } or { coord={x,y,z}, radius=NN, name='Recce1' }
|
||||
local z
|
||||
if def.name then
|
||||
z = ZONE:FindByName(def.name)
|
||||
end
|
||||
if not z and def.coord then
|
||||
local r = def.radius or 5000
|
||||
z = ZONE_RADIUS:New(def.name or ('FAC_ZONE_'..math.random(10000,99999)), VECTOR2:New(def.coord.x, def.coord.z), r)
|
||||
end
|
||||
if not z then return nil end
|
||||
local Z = {
|
||||
Zone = z,
|
||||
Name = z:GetName(),
|
||||
Detector = self:CreateDetector(z),
|
||||
LastScan = 0,
|
||||
}
|
||||
table.insert(self.Zones, Z)
|
||||
return Z
|
||||
end
|
||||
|
||||
function FAC:CreateDetector(zone)
|
||||
-- Detection in areas using Moose detection classes
|
||||
local enemySide = (self.Side == coalition.side.BLUE) and coalition.side.RED or coalition.side.BLUE
|
||||
local setEnemies = SET_GROUP:New():FilterCoalitions(enemySide):FilterCategoryGround():FilterStart()
|
||||
local det = DETECTION_AREAS:New(setEnemies, zone:GetRadius())
|
||||
det:BoundZone(zone)
|
||||
return det
|
||||
end
|
||||
|
||||
function FAC:MenuListZones(group)
|
||||
local names = {}
|
||||
for _,Z in ipairs(self.Zones) do table.insert(names, Z.Name) end
|
||||
MESSAGE:New('Recce zones: '..(table.concat(names, ', '):gsub('^%s+$','none')), 15):ToGroup(group)
|
||||
end
|
||||
|
||||
function FAC:ForceScanAll(group)
|
||||
for _,Z in ipairs(self.Zones) do self:ScanZone(Z, group) end
|
||||
end
|
||||
|
||||
function FAC:Run()
|
||||
-- schedule periodic scanning
|
||||
if self.Sched then self.Sched:Stop() end
|
||||
self.Sched = SCHEDULER:New(nil, function()
|
||||
for _,Z in ipairs(self.Zones) do self:ScanZone(Z) end
|
||||
end, {}, 5, self.Config.ScanInterval)
|
||||
end
|
||||
|
||||
local function _p3(v2)
|
||||
return { x = v2.x, y = land.getHeight({x=v2.x, y=v2.y}), z = v2.y }
|
||||
end
|
||||
|
||||
function FAC:ScanZone(Z, notifyGroup)
|
||||
local now = timer.getTime()
|
||||
local det = Z.Detector
|
||||
det:DetectionUpdate()
|
||||
local reports = det:GetDetectedItems()
|
||||
if not reports or #reports == 0 then
|
||||
if notifyGroup then MESSAGE:New('No contacts detected in '..Z.Name, 10):ToGroup(notifyGroup) end
|
||||
return
|
||||
end
|
||||
|
||||
local enemySide = (self.Side == coalition.side.BLUE) and coalition.side.RED or coalition.side.BLUE
|
||||
for _,rep in ipairs(reports) do
|
||||
local contact = rep.object -- wrapper around GROUP or UNIT
|
||||
local pos2 = rep.point -- vec2
|
||||
if pos2 then
|
||||
local markPoint = { x = pos2.x, z = pos2.y }
|
||||
local allow = true
|
||||
local last = FAC._lastMarks[Z.Name]
|
||||
if last then
|
||||
local dx = (markPoint.x - last.x); local dz = (markPoint.z - last.z)
|
||||
if math.sqrt(dx*dx+dz*dz) < self.Config.MinReportSeparation then allow = false end
|
||||
end
|
||||
if allow then
|
||||
FAC._lastMarks[Z.Name] = { x = markPoint.x, z = markPoint.z }
|
||||
self:MarkTarget(Z, markPoint, rep, enemySide)
|
||||
if notifyGroup then MESSAGE:New(string.format('Marked contact in %s', Z.Name), 10):ToGroup(notifyGroup) end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function FAC:MarkTarget(Z, point, rep, enemySide)
|
||||
-- Smoke
|
||||
trigger.action.smoke(point, self.Config.MarkSmokeColor)
|
||||
-- Map mark
|
||||
if self.Config.MarkText then
|
||||
local txt = string.format('FAC: %s at %s', rep.type or 'Contact', coord.LLtoString(coord.LOtoLL(point), 0))
|
||||
trigger.action.markToCoalition(math.random(100000,999999), txt, point, self.Side, true)
|
||||
end
|
||||
-- Optional arty marking
|
||||
if self.Config.Arty.Enabled then
|
||||
self:ArtyMark(point)
|
||||
end
|
||||
end
|
||||
|
||||
function FAC:ArtyMark(point)
|
||||
local rounds = self.Config.Arty.Rounds or 1
|
||||
for _,gname in ipairs(self.Config.Arty.Groups or {}) do
|
||||
local g = Group.getByName(gname)
|
||||
if g and g:isExist() then
|
||||
local ctrl = g:getController()
|
||||
if ctrl then
|
||||
for i=1,rounds do
|
||||
local spread = self.Config.Arty.Spread or 0
|
||||
local tgt = { x = point.x + math.random(-spread, spread), y = point.z + math.random(-spread, spread) }
|
||||
local task = {
|
||||
id = 'FireAtPoint',
|
||||
params = {
|
||||
point = { x = tgt.x, y = land.getHeight({x=tgt.x, y=tgt.y}), z = tgt.y },
|
||||
expendQty = 1,
|
||||
dispersion = 50,
|
||||
attackQty = 1,
|
||||
weaponType = 0,
|
||||
}
|
||||
}
|
||||
ctrl:setTask(task)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Bootstrap a JTAC on a spawned unit/group via MOOSE FAC_AUTO
|
||||
function FAC:StartJTACOnGroup(groupName, code, smoke)
|
||||
local grp = GROUP:FindByName(groupName)
|
||||
if not grp then return nil end
|
||||
local fac = FAC_AUTO:New(grp)
|
||||
fac:SetLaser(true, code or 1688)
|
||||
fac:SetSmoke(smoke or self.Config.MarkSmokeColor)
|
||||
fac:SetDetectVehicles()
|
||||
fac:Start()
|
||||
return fac
|
||||
end
|
||||
|
||||
_MOOSE_CTLD_FAC = FAC
|
||||
return FAC
|
||||
106
Moose_CTLD_Pure/README.md
Normal file
106
Moose_CTLD_Pure/README.md
Normal file
@ -0,0 +1,106 @@
|
||||
# Moose_CTLD_Pure
|
||||
|
||||
Pure-MOOSE CTLD-style logistics and FAC/RECCE without MIST or mission editor templates. Drop-in, config-driven.
|
||||
|
||||
## What this is
|
||||
|
||||
- Logistics and troop transport similar to popular CTLD scripts, implemented directly on MOOSE.
|
||||
- No MIST. No mission editor templates. Unit compositions are defined in config tables.
|
||||
- Optional FAC/RECCE module that auto-marks targets in zones and can drive artillery marking and JTAC auto-lase.
|
||||
|
||||
## Quick start
|
||||
|
||||
1) Load `Moose.lua` first, then include these files (order matters):
|
||||
- `Moose_CTLD_Pure/Moose_CTLD.lua`
|
||||
- `Moose_CTLD_Pure/Moose_CTLD_FAC.lua` (optional, for FAC/RECCE)
|
||||
|
||||
2) Initialize CTLD with minimal config:
|
||||
|
||||
```lua
|
||||
local CTLD = dofile(lfs.writedir()..[[Scripts\Moose_CTLD_Pure\Moose_CTLD.lua]])
|
||||
local ctld = CTLD:New({
|
||||
CoalitionSide = coalition.side.BLUE,
|
||||
Zones = {
|
||||
PickupZones = { { name = 'PICKUP_BLUE_MAIN' } },
|
||||
DropZones = { { name = 'DROP_BLUE_1' } },
|
||||
},
|
||||
})
|
||||
|
||||
-- Optionally load a larger crate catalog extracted from your CTLD.lua
|
||||
local extracted = dofile(lfs.writedir()..[[Scripts\Moose_CTLD_Pure\catalogs\CrateCatalog_CTLD_Extract.lua]])
|
||||
ctld:MergeCatalog(extracted)
|
||||
```
|
||||
|
||||
- If you don't have ME trigger zones, define by coordinates:
|
||||
|
||||
```lua
|
||||
Zones = {
|
||||
PickupZones = {
|
||||
{ coord = { x=123456, y=0, z=654321 }, radius=150, name='ScriptPickup1' },
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
3) (Optional) FAC/RECCE:
|
||||
|
||||
```lua
|
||||
local FAC = dofile(lfs.writedir()..[[Scripts\Moose_CTLD_Pure\Moose_CTLD_FAC.lua]])
|
||||
local fac = FAC:New(ctld, {
|
||||
CoalitionSide = coalition.side.BLUE,
|
||||
Arty = { Enabled = true, Groups = { 'BLUE_ARTY_1' }, Rounds = 3, Spread = 100 },
|
||||
})
|
||||
fac:AddRecceZone({ name = 'RECCE_ZONE_1' })
|
||||
fac:Run()
|
||||
```
|
||||
|
||||
4) In mission, pilots of allowed aircraft (configured in `AllowedAircraft`) will see F10 menus:
|
||||
- CTLD > Request Crate > [Type]
|
||||
- CTLD > Load Troops / Unload Troops
|
||||
- CTLD > Build Here
|
||||
- FAC/RECCE > List Recce Zones / Mark Contacts (all zones)
|
||||
|
||||
## Configuring crates and builds (no templates)
|
||||
|
||||
Edit `CrateCatalog` in `Moose_CTLD.lua`. Each entry defines:
|
||||
- `required`: how many crates to assemble
|
||||
- `weight`: informational
|
||||
- `dcsCargoType`: DCS static cargo type string (e.g., `uh1h_cargo`, `container_cargo`); tweak per map/mods
|
||||
- `build(point, headingDeg)`: function returning a DCS group table for `coalition.addGroup`
|
||||
|
||||
Example snippet:
|
||||
|
||||
```lua
|
||||
CrateCatalog = {
|
||||
MANPADS = {
|
||||
description = '2x Crates -> MANPADS team',
|
||||
weight = 120,
|
||||
dcsCargoType = 'uh1h_cargo',
|
||||
required = 2,
|
||||
side = coalition.side.BLUE,
|
||||
category = Group.Category.GROUND,
|
||||
build = function(point, headingDeg)
|
||||
return { visible=false, lateActivation=false, units={
|
||||
{ type='Soldier stinger', name='CTLD-MANPADS-1', x=point.x, y=point.z, heading=math.rad(headingDeg or 0) }
|
||||
} }
|
||||
end,
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
## Notes and limitations
|
||||
|
||||
- This avoids sling-load event dependency by using a player command "Build Here" that consumes nearby crates within a radius. You can still sling crates physically.
|
||||
- Cargo static types vary across DCS versions. If a spawned crate isn’t sling-loadable, change `dcsCargoType` strings in `CrateCatalog` (e.g., `uh1h_cargo`, `container_cargo`, `ammo_cargo`, `container_20ft` depending on map/version).
|
||||
- Troops are virtually loaded and spawned on unload. Adjust capacity logic if you want type-based capacities.
|
||||
- FAC/RECCE detection leverages Moose `DETECTION_AREAS`. Tweak `ScanInterval`, `DetectionRadius`, and `MinReportSeparation` to balance spam/performance.
|
||||
- Artillery marking uses `Controller.setTask('FireAtPoint')` on configured groups. Ensure those groups exist and are artillery-capable.
|
||||
- JTAC Auto-Lase helper provided: `fac:StartJTACOnGroup(groupName, laserCode, smokeColor)` uses `FAC_AUTO`.
|
||||
|
||||
## Extending
|
||||
|
||||
- Add radio beacons, FOB build recipes, fuel/ammo crates, and CSAR hooks by registering more `CrateCatalog` entries and/or adding helper methods.
|
||||
- To support per-airframe capacities and sling-only rules, extend `AllowedAircraft` and add a type->capacity map.
|
||||
|
||||
## Changelog
|
||||
|
||||
- 0.1.0-alpha: Initial release: CTLD crate/troops/build, FAC recce zones + arty mark + JTAC bootstrap.
|
||||
166
Moose_CTLD_Pure/catalogs/CrateCatalog_CTLD_Extract.lua
Normal file
166
Moose_CTLD_Pure/catalogs/CrateCatalog_CTLD_Extract.lua
Normal file
@ -0,0 +1,166 @@
|
||||
-- CrateCatalog_CTLD_Extract.lua
|
||||
-- Auto-generated from CTLD.lua (Operation_Polar_Shield) spawnableCrates config
|
||||
-- Returns a table of crate definitions suitable for CTLD:MergeCatalog()
|
||||
-- Notes:
|
||||
-- - Each entry has keys: description/menu, dcsCargoType, required or requires (composite), side, category, build(point, headingDeg)
|
||||
-- - Single-unit entries spawn one unit by DCS type. Composite "SITE" entries spawn a multi-unit group approximating system components.
|
||||
|
||||
local function singleUnit(unitType)
|
||||
return function(point, headingDeg)
|
||||
local name = string.format('%s-%d', unitType, math.random(100000,999999))
|
||||
local hdg = math.rad(headingDeg or 0)
|
||||
return {
|
||||
visible=false, lateActivation=false, tasks={}, task='Ground Nothing', route={},
|
||||
units={ { type=unitType, name=name, x=point.x, y=point.z, heading=hdg } },
|
||||
name = 'CTLD_'..name
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
local function multiUnits(units)
|
||||
-- units: array of { type, dx, dz }
|
||||
return function(point, headingDeg)
|
||||
local hdg = math.rad(headingDeg or 0)
|
||||
local function off(dx, dz) return { x = point.x + dx, z = point.z + dz } end
|
||||
local list = {}
|
||||
for i,u in ipairs(units) do
|
||||
local p = off(u.dx or 0, u.dz or 3*i)
|
||||
table.insert(list, {
|
||||
type = u.type, name = string.format('CTLD-%s-%d', u.type, math.random(100000,999999)),
|
||||
x = p.x, y = p.z, heading = hdg
|
||||
})
|
||||
end
|
||||
return { visible=false, lateActivation=false, tasks={}, task='Ground Nothing', route={}, units=list, name=string.format('CTLD_SITE_%d', math.random(100000,999999)) }
|
||||
end
|
||||
end
|
||||
|
||||
local BLUE = coalition.side.BLUE
|
||||
local RED = coalition.side.RED
|
||||
|
||||
local cat = {}
|
||||
|
||||
-- Combat Vehicles (BLUE)
|
||||
cat['BLUE_M1128_STRYKER_MGS'] = { menuCategory='Combat Vehicles', menu='M1128 Stryker MGS', description='M1128 Stryker MGS', dcsCargoType='container_cargo', required=1, side=BLUE, category=Group.Category.GROUND, build=singleUnit('M1128 Stryker MGS') }
|
||||
cat['BLUE_M60A3_PATTON'] = { menuCategory='Combat Vehicles', menu='M-60A3 Patton', description='M-60A3 Patton', dcsCargoType='container_cargo', required=1, side=BLUE, category=Group.Category.GROUND, build=singleUnit('M-60') }
|
||||
cat['BLUE_HMMWV_TOW'] = { menuCategory='Combat Vehicles', menu='Humvee - TOW', description='Humvee - TOW', dcsCargoType='container_cargo', required=1, side=BLUE, category=Group.Category.GROUND, build=singleUnit('M1045 HMMWV TOW') }
|
||||
cat['BLUE_M1134_STRYKER_ATGM']= { menuCategory='Combat Vehicles', menu='M1134 Stryker ATGM',description='M1134 Stryker ATGM',dcsCargoType='container_cargo', required=1, side=BLUE, category=Group.Category.GROUND, build=singleUnit('M1134 Stryker ATGM') }
|
||||
cat['BLUE_LAV25'] = { menuCategory='Combat Vehicles', menu='LAV-25', description='LAV-25', dcsCargoType='container_cargo', required=1, side=BLUE, category=Group.Category.GROUND, build=singleUnit('LAV-25') }
|
||||
cat['BLUE_M2A2_BRADLEY'] = { menuCategory='Combat Vehicles', menu='M2A2 Bradley', description='M2A2 Bradley', dcsCargoType='container_cargo', required=1, side=BLUE, category=Group.Category.GROUND, build=singleUnit('M-2 Bradley') }
|
||||
cat['BLUE_VAB_MEPHISTO'] = { menuCategory='Combat Vehicles', menu='ATGM VAB Mephisto', description='ATGM VAB Mephisto', dcsCargoType='container_cargo', required=1, side=BLUE, category=Group.Category.GROUND, build=singleUnit('VAB_Mephisto') }
|
||||
cat['BLUE_M1A2C_ABRAMS'] = { menuCategory='Combat Vehicles', menu='M1A2C Abrams', description='M1A2C Abrams', dcsCargoType='container_cargo', required=1, side=BLUE, category=Group.Category.GROUND, build=singleUnit('M1A2C_SEP_V3') }
|
||||
|
||||
-- Combat Vehicles (RED)
|
||||
cat['RED_BTR82A'] = { menuCategory='Combat Vehicles', menu='BTR-82A', description='BTR-82A', dcsCargoType='container_cargo', required=1, side=RED, category=Group.Category.GROUND, build=singleUnit('BTR-82A') }
|
||||
cat['RED_BRDM2'] = { menuCategory='Combat Vehicles', menu='BRDM-2', description='BRDM-2', dcsCargoType='container_cargo', required=1, side=RED, category=Group.Category.GROUND, build=singleUnit('BRDM-2') }
|
||||
cat['RED_BMP3'] = { menuCategory='Combat Vehicles', menu='BMP-3', description='BMP-3', dcsCargoType='container_cargo', required=1, side=RED, category=Group.Category.GROUND, build=singleUnit('BMP-3') }
|
||||
cat['RED_T55'] = { menuCategory='Combat Vehicles', menu='T-55', description='T-55', dcsCargoType='container_cargo', required=1, side=RED, category=Group.Category.GROUND, build=singleUnit('T-55') }
|
||||
cat['RED_T72B3'] = { menuCategory='Combat Vehicles', menu='T-72B3', description='T-72B3', dcsCargoType='container_cargo', required=1, side=RED, category=Group.Category.GROUND, build=singleUnit('T-72B3') }
|
||||
|
||||
-- Support (BLUE)
|
||||
cat['BLUE_MRAP_JTAC'] = { menuCategory='Support', menu='MRAP - JTAC', description='JTAC MRAP', dcsCargoType='container_cargo', required=1, side=BLUE, category=Group.Category.GROUND, build=singleUnit('MaxxPro_MRAP') }
|
||||
cat['BLUE_M818_AMMO'] = { menuCategory='Support', menu='M-818 Ammo Truck', description='M-818 Ammo Truck', dcsCargoType='container_cargo', required=1, side=BLUE, category=Group.Category.GROUND, build=singleUnit('M 818') }
|
||||
cat['BLUE_M978_TANKER'] = { menuCategory='Support', menu='M-978 Tanker', description='M-978 Tanker', dcsCargoType='container_cargo', required=1, side=BLUE, category=Group.Category.GROUND, build=singleUnit('M978 HEMTT Tanker') }
|
||||
cat['BLUE_EWR_FPS117'] = { menuCategory='Support', menu='EWR Radar FPS-117', description='EWR Radar FPS-117', dcsCargoType='container_cargo', required=1, side=BLUE, category=Group.Category.GROUND, build=singleUnit('FPS-117') }
|
||||
|
||||
-- Support (RED)
|
||||
cat['RED_TIGR_JTAC'] = { menuCategory='Support', menu='Tigr - JTAC', description='JTAC Tigr', dcsCargoType='container_cargo', required=1, side=RED, category=Group.Category.GROUND, build=singleUnit('Tigr_233036') }
|
||||
cat['RED_URAL4320_AMMO'] = { menuCategory='Support', menu='Ural-4320-31 Ammo Truck', description='Ural-4320-31 Ammo Truck', dcsCargoType='container_cargo', required=1, side=RED, category=Group.Category.GROUND, build=singleUnit('Ural-4320-31') }
|
||||
cat['RED_ATZ10_TANKER'] = { menuCategory='Support', menu='ATZ-10 Refueler', description='ATZ-10 Refueler', dcsCargoType='container_cargo', required=1, side=RED, category=Group.Category.GROUND, build=singleUnit('ATZ-10') }
|
||||
cat['RED_EWR_1L13'] = { menuCategory='Support', menu='EWR Radar 1L13', description='EWR Radar 1L13', dcsCargoType='container_cargo', required=1, side=RED, category=Group.Category.GROUND, build=singleUnit('1L13 EWR') }
|
||||
|
||||
-- Artillery (BLUE)
|
||||
cat['BLUE_MLRS'] = { menuCategory='Artillery', menu='MLRS', description='MLRS', dcsCargoType='container_cargo', required=2, side=BLUE, category=Group.Category.GROUND, build=singleUnit('MLRS') }
|
||||
cat['BLUE_SMERCH_CM'] = { menuCategory='Artillery', menu='Smerch_CM', description='Smerch (CM)', dcsCargoType='container_cargo', required=2, side=BLUE, category=Group.Category.GROUND, build=singleUnit('Smerch') }
|
||||
cat['BLUE_L118_105MM'] = { menuCategory='Artillery', menu='L118 Light Artillery 105mm', description='L118 105mm', dcsCargoType='container_cargo', required=1, side=BLUE, category=Group.Category.GROUND, build=singleUnit('L118_Unit') }
|
||||
cat['BLUE_SMERCH_HE'] = { menuCategory='Artillery', menu='Smerch_HE', description='Smerch (HE)', dcsCargoType='container_cargo', required=2, side=BLUE, category=Group.Category.GROUND, build=singleUnit('Smerch_HE') }
|
||||
cat['BLUE_M109'] = { menuCategory='Artillery', menu='M-109', description='M-109', dcsCargoType='container_cargo', required=2, side=BLUE, category=Group.Category.GROUND, build=singleUnit('M-109') }
|
||||
|
||||
-- Artillery (RED)
|
||||
cat['RED_GVOZDika'] = { menuCategory='Artillery', menu='SAU Gvozdika', description='SAU Gvozdika', dcsCargoType='container_cargo', required=2, side=RED, category=Group.Category.GROUND, build=singleUnit('SAU Gvozdika') }
|
||||
cat['RED_2S19_MSTA'] = { menuCategory='Artillery', menu='SPH 2S19 Msta', description='SPH 2S19 Msta', dcsCargoType='container_cargo', required=2, side=RED, category=Group.Category.GROUND, build=singleUnit('SAU Msta') }
|
||||
cat['RED_URAGAN_BM27'] = { menuCategory='Artillery', menu='Uragan_BM-27', description='Uragan BM-27', dcsCargoType='container_cargo', required=2, side=RED, category=Group.Category.GROUND, build=singleUnit('Uragan_BM-27') }
|
||||
cat['RED_BM21_GRAD'] = { menuCategory='Artillery', menu='BM-21 Grad Ural', description='BM-21 Grad Ural', dcsCargoType='container_cargo', required=2, side=RED, category=Group.Category.GROUND, build=singleUnit('Grad-URAL') }
|
||||
cat['RED_PLZ05'] = { menuCategory='Artillery', menu='PLZ-05 Mobile Artillery', description='PLZ-05', dcsCargoType='container_cargo', required=2, side=RED, category=Group.Category.GROUND, build=singleUnit('PLZ05') }
|
||||
|
||||
-- AAA (BLUE)
|
||||
cat['BLUE_GEPARD'] = { menuCategory='AAA', menu='Gepard AAA', description='Gepard AAA', dcsCargoType='container_cargo', required=1, side=BLUE, category=Group.Category.GROUND, build=singleUnit('Gepard') }
|
||||
cat['BLUE_CRAM'] = { menuCategory='AAA', menu='LPWS C-RAM', description='LPWS C-RAM', dcsCargoType='container_cargo', required=1, side=BLUE, category=Group.Category.GROUND, build=singleUnit('HEMTT_C-RAM_Phalanx') }
|
||||
cat['BLUE_VULCAN_M163'] = { menuCategory='AAA', menu='SPAAA Vulcan M163', description='Vulcan M163', dcsCargoType='container_cargo', required=1, side=BLUE, category=Group.Category.GROUND, build=singleUnit('Vulcan') }
|
||||
cat['BLUE_BOFORS40'] = { menuCategory='AAA', menu='Bofors 40mm', description='Bofors 40mm', dcsCargoType='container_cargo', required=1, side=BLUE, category=Group.Category.GROUND, build=singleUnit('bofors40') }
|
||||
|
||||
-- AAA (RED)
|
||||
cat['RED_URAL_ZU23'] = { menuCategory='AAA', menu='Ural-375 ZU-23', description='Ural-375 ZU-23', dcsCargoType='container_cargo', required=1, side=RED, category=Group.Category.GROUND, build=singleUnit('Ural-375 ZU-23') }
|
||||
cat['RED_SHILKA'] = { menuCategory='AAA', menu='ZSU-23-4 Shilka', description='ZSU-23-4 Shilka', dcsCargoType='container_cargo', required=1, side=RED, category=Group.Category.GROUND, build=singleUnit('ZSU-23-4 Shilka') }
|
||||
cat['RED_ZSU57_2'] = { menuCategory='AAA', menu='ZSU_57_2', description='ZSU_57_2', dcsCargoType='container_cargo', required=1, side=RED, category=Group.Category.GROUND, build=singleUnit('ZSU_57_2') }
|
||||
|
||||
-- SAM short range (BLUE)
|
||||
cat['BLUE_M1097_AVENGER'] = { menuCategory='SAM short range', menu='M1097 Avenger', description='M1097 Avenger', dcsCargoType='container_cargo', required=2, side=BLUE, category=Group.Category.GROUND, build=singleUnit('M1097 Avenger') }
|
||||
cat['BLUE_M48_CHAPARRAL'] = { menuCategory='SAM short range', menu='M48 Chaparral', description='M48 Chaparral', dcsCargoType='container_cargo', required=2, side=BLUE, category=Group.Category.GROUND, build=singleUnit('M48 Chaparral') }
|
||||
cat['BLUE_ROLAND_ADS'] = { menuCategory='SAM short range', menu='Roland ADS', description='Roland ADS', dcsCargoType='container_cargo', required=2, side=BLUE, category=Group.Category.GROUND, build=singleUnit('Roland ADS') }
|
||||
cat['BLUE_M6_LINEBACKER'] = { menuCategory='SAM short range', menu='M6 Linebacker', description='M6 Linebacker', dcsCargoType='container_cargo', required=1, side=BLUE, category=Group.Category.GROUND, build=singleUnit('M6 Linebacker') }
|
||||
-- Rapier components and site
|
||||
cat['BLUE_RAPIER_LN'] = { menuCategory='SAM short range', menu='Rapier Launcher', description='Rapier Launcher', dcsCargoType='container_cargo', required=1, side=BLUE, category=Group.Category.GROUND, build=singleUnit('rapier_fsa_launcher') }
|
||||
cat['BLUE_RAPIER_SR'] = { menuCategory='SAM short range', menu='Rapier SR', description='Rapier SR', dcsCargoType='container_cargo', required=1, side=BLUE, category=Group.Category.GROUND, build=singleUnit('rapier_fsa_blindfire_radar') }
|
||||
cat['BLUE_RAPIER_TR'] = { menuCategory='SAM short range', menu='Rapier Tracker', description='Rapier Tracker', dcsCargoType='container_cargo', required=1, side=BLUE, category=Group.Category.GROUND, build=singleUnit('rapier_fsa_optical_tracker_unit') }
|
||||
cat['BLUE_RAPIER_SITE'] = { menuCategory='SAM short range', menu='Rapier - All crates', description='Rapier Site', dcsCargoType='container_cargo', requires={ BLUE_RAPIER_LN=1, BLUE_RAPIER_SR=1, BLUE_RAPIER_TR=1 }, side=BLUE, category=Group.Category.GROUND,
|
||||
build=multiUnits({ {type='rapier_fsa_launcher'}, {type='rapier_fsa_blindfire_radar', dx=12, dz=6}, {type='rapier_fsa_optical_tracker_unit', dx=-12, dz=6} }) }
|
||||
|
||||
-- SAM short range (RED)
|
||||
cat['RED_OSA_9K33'] = { menuCategory='SAM short range', menu='9K33 Osa', description='9K33 Osa', dcsCargoType='container_cargo', required=2, side=RED, category=Group.Category.GROUND, build=singleUnit('Osa 9A33 ln') }
|
||||
cat['RED_STRELA1_9P31'] = { menuCategory='SAM short range', menu='9P31 Strela-1', description='9P31 Strela-1', dcsCargoType='container_cargo', required=2, side=RED, category=Group.Category.GROUND, build=singleUnit('Strela-1 9P31') }
|
||||
cat['RED_TUNGUSKA_2S6'] = { menuCategory='SAM short range', menu='2K22 Tunguska', description='2K22 Tunguska', dcsCargoType='container_cargo', required=2, side=RED, category=Group.Category.GROUND, build=singleUnit('2S6 Tunguska') }
|
||||
cat['RED_STRELA10M3'] = { menuCategory='SAM short range', menu='SA-13 Strela-10M3', description='SA-13 Strela-10M3', dcsCargoType='container_cargo', required=2, side=RED, category=Group.Category.GROUND, build=singleUnit('Strela-10M3') }
|
||||
-- HQ-7 components and site
|
||||
cat['RED_HQ7_LN'] = { menuCategory='SAM short range', menu='HQ-7_Launcher', description='HQ-7 Launcher', dcsCargoType='container_cargo', required=2, side=RED, category=Group.Category.GROUND, build=singleUnit('HQ-7_LN_SP') }
|
||||
cat['RED_HQ7_STR'] = { menuCategory='SAM short range', menu='HQ-7_STR_SP', description='HQ-7 STR', dcsCargoType='container_cargo', required=1, side=RED, category=Group.Category.GROUND, build=singleUnit('HQ-7_STR_SP') }
|
||||
cat['RED_HQ7_SITE'] = { menuCategory='SAM short range', menu='HQ-7 - All crates', description='HQ-7 Site', dcsCargoType='container_cargo', requires={ RED_HQ7_LN=1, RED_HQ7_STR=1 }, side=RED, category=Group.Category.GROUND,
|
||||
build=multiUnits({ {type='HQ-7_LN_SP'}, {type='HQ-7_STR_SP', dx=10, dz=8} }) }
|
||||
|
||||
-- SAM mid range (BLUE) HAWK + NASAMS
|
||||
cat['BLUE_HAWK_LN'] = { menuCategory='SAM mid range', menu='HAWK Launcher', description='HAWK Launcher', dcsCargoType='container_cargo', required=1, side=BLUE, category=Group.Category.GROUND, build=singleUnit('Hawk ln') }
|
||||
cat['BLUE_HAWK_SR'] = { menuCategory='SAM mid range', menu='HAWK Search Radar', description='HAWK SR', dcsCargoType='container_cargo', required=1, side=BLUE, category=Group.Category.GROUND, build=singleUnit('Hawk sr') }
|
||||
cat['BLUE_HAWK_TR'] = { menuCategory='SAM mid range', menu='HAWK Track Radar', description='HAWK TR', dcsCargoType='container_cargo', required=1, side=BLUE, category=Group.Category.GROUND, build=singleUnit('Hawk tr') }
|
||||
cat['BLUE_HAWK_PCP'] = { menuCategory='SAM mid range', menu='HAWK PCP', description='HAWK PCP', dcsCargoType='container_cargo', required=1, side=BLUE, category=Group.Category.GROUND, build=singleUnit('Hawk pcp') }
|
||||
cat['BLUE_HAWK_CWAR'] = { menuCategory='SAM mid range', menu='HAWK CWAR', description='HAWK CWAR', dcsCargoType='container_cargo', required=1, side=BLUE, category=Group.Category.GROUND, build=singleUnit('Hawk cwar') }
|
||||
cat['BLUE_HAWK_SITE'] = { menuCategory='SAM mid range', menu='HAWK - All crates', description='HAWK Site', dcsCargoType='container_cargo', requires={ BLUE_HAWK_LN=1, BLUE_HAWK_SR=1, BLUE_HAWK_TR=1, BLUE_HAWK_PCP=1, BLUE_HAWK_CWAR=1 }, side=BLUE, category=Group.Category.GROUND,
|
||||
build=multiUnits({ {type='Hawk ln'}, {type='Hawk sr', dx=12, dz=8}, {type='Hawk tr', dx=-12, dz=8}, {type='Hawk pcp', dx=18, dz=12}, {type='Hawk cwar', dx=-18, dz=12} }) }
|
||||
|
||||
cat['BLUE_NASAMS_LN'] = { menuCategory='SAM mid range', menu='NASAMS Launcher 120C', description='NASAMS LN 120C', dcsCargoType='container_cargo', required=1, side=BLUE, category=Group.Category.GROUND, build=singleUnit('NASAMS_LN_C') }
|
||||
cat['BLUE_NASAMS_RADAR'] = { menuCategory='SAM mid range', menu='NASAMS Search/Track Radar', description='NASAMS Radar', dcsCargoType='container_cargo', required=1, side=BLUE, category=Group.Category.GROUND, build=singleUnit('NASAMS_Radar_MPQ64F1') }
|
||||
cat['BLUE_NASAMS_CP'] = { menuCategory='SAM mid range', menu='NASAMS Command Post', description='NASAMS CP', dcsCargoType='container_cargo', required=1, side=BLUE, category=Group.Category.GROUND, build=singleUnit('NASAMS_Command_Post') }
|
||||
cat['BLUE_NASAMS_SITE'] = { menuCategory='SAM mid range', menu='NASAMS - All crates', description='NASAMS Site', dcsCargoType='container_cargo', requires={ BLUE_NASAMS_LN=1, BLUE_NASAMS_RADAR=1, BLUE_NASAMS_CP=1 }, side=BLUE, category=Group.Category.GROUND,
|
||||
build=multiUnits({ {type='NASAMS_LN_C'}, {type='NASAMS_Radar_MPQ64F1', dx=12, dz=8}, {type='NASAMS_Command_Post', dx=-12, dz=8} }) }
|
||||
|
||||
-- SAM mid range (RED) KUB
|
||||
cat['RED_KUB_LN'] = { menuCategory='SAM mid range', menu='KUB Launcher', description='KUB Launcher', dcsCargoType='container_cargo', required=1, side=RED, category=Group.Category.GROUND, build=singleUnit('Kub 2P25 ln') }
|
||||
cat['RED_KUB_RADAR'] = { menuCategory='SAM mid range', menu='KUB Radar', description='KUB Radar', dcsCargoType='container_cargo', required=1, side=RED, category=Group.Category.GROUND, build=singleUnit('Kub 1S91 str') }
|
||||
cat['RED_KUB_SITE'] = { menuCategory='SAM mid range', menu='KUB - All crates', description='KUB Site', dcsCargoType='container_cargo', requires={ RED_KUB_LN=1, RED_KUB_RADAR=1 }, side=RED, category=Group.Category.GROUND,
|
||||
build=multiUnits({ {type='Kub 2P25 ln'}, {type='Kub 1S91 str', dx=12, dz=8} }) }
|
||||
|
||||
-- SAM long range (BLUE) Patriot
|
||||
cat['BLUE_PATRIOT_LN'] = { menuCategory='SAM long range', menu='Patriot Launcher', description='Patriot Launcher', dcsCargoType='container_cargo', required=1, side=BLUE, category=Group.Category.GROUND, build=singleUnit('Patriot ln') }
|
||||
cat['BLUE_PATRIOT_RADAR'] = { menuCategory='SAM long range', menu='Patriot Radar', description='Patriot Radar', dcsCargoType='container_cargo', required=1, side=BLUE, category=Group.Category.GROUND, build=singleUnit('Patriot str') }
|
||||
cat['BLUE_PATRIOT_ECS'] = { menuCategory='SAM long range', menu='Patriot ECS', description='Patriot ECS', dcsCargoType='container_cargo', required=1, side=BLUE, category=Group.Category.GROUND, build=singleUnit('Patriot ECS') }
|
||||
cat['BLUE_PATRIOT_SITE'] = { menuCategory='SAM long range', menu='Patriot - All crates', description='Patriot Site', dcsCargoType='container_cargo', requires={ BLUE_PATRIOT_LN=1, BLUE_PATRIOT_RADAR=1, BLUE_PATRIOT_ECS=1 }, side=BLUE, category=Group.Category.GROUND,
|
||||
build=multiUnits({ {type='Patriot ln'}, {type='Patriot str', dx=14, dz=10}, {type='Patriot ECS', dx=-14, dz=10} }) }
|
||||
|
||||
-- SAM long range (RED) BUK
|
||||
cat['RED_BUK_LN'] = { menuCategory='SAM long range', menu='BUK Launcher', description='BUK Launcher', dcsCargoType='container_cargo', required=1, side=RED, category=Group.Category.GROUND, build=singleUnit('SA-11 Buk LN 9A310M1') }
|
||||
cat['RED_BUK_SR'] = { menuCategory='SAM long range', menu='BUK Search Radar', description='BUK Search Radar', dcsCargoType='container_cargo', required=1, side=RED, category=Group.Category.GROUND, build=singleUnit('SA-11 Buk SR 9S18M1') }
|
||||
cat['RED_BUK_CC'] = { menuCategory='SAM long range', menu='BUK CC Radar', description='BUK CC Radar', dcsCargoType='container_cargo', required=1, side=RED, category=Group.Category.GROUND, build=singleUnit('SA-11 Buk CC 9S470M1') }
|
||||
cat['RED_BUK_SITE'] = { menuCategory='SAM long range', menu='BUK - All crates', description='BUK Site', dcsCargoType='container_cargo', requires={ RED_BUK_LN=1, RED_BUK_SR=1, RED_BUK_CC=1 }, side=RED, category=Group.Category.GROUND,
|
||||
build=multiUnits({ {type='SA-11 Buk LN 9A310M1'}, {type='SA-11 Buk SR 9S18M1', dx=12, dz=8}, {type='SA-11 Buk CC 9S470M1', dx=-12, dz=8} }) }
|
||||
|
||||
-- Drones (JTAC)
|
||||
cat['BLUE_MQ9'] = { menuCategory='Drones', menu='MQ-9 Reaper - JTAC', description='MQ-9 JTAC', dcsCargoType='container_cargo', required=1, side=BLUE, category=Group.Category.AIRPLANE, build=singleUnit('MQ-9 Reaper') }
|
||||
cat['RED_WINGLOONG'] = { menuCategory='Drones', menu='WingLoong-I - JTAC', description='WingLoong-I JTAC', dcsCargoType='container_cargo', required=1, side=RED, category=Group.Category.AIRPLANE, build=singleUnit('WingLoong-I') }
|
||||
|
||||
-- FOB crates (Support) — three small crates build a FOB site
|
||||
cat['FOB_SMALL'] = { menuCategory='Support', menu='FOB Crate - Small', description='FOB small crate', dcsCargoType='container_cargo', required=1, side=nil, category=Group.Category.GROUND, build=function(point, headingDeg)
|
||||
-- spawns a harmless placeholder truck for visibility; consumed by FOB_SITE build
|
||||
return singleUnit('Ural-375')(point, headingDeg)
|
||||
end }
|
||||
cat['FOB_SITE'] = { menuCategory='Support', menu='FOB Crates - All', description='FOB Site', dcsCargoType='container_cargo', requires={ FOB_SMALL=3 }, side=nil, category=Group.Category.GROUND,
|
||||
build=multiUnits({ {type='HEMTT TFFT'}, {type='Ural-375 PBU', dx=10, dz=8}, {type='Ural-375', dx=-10, dz=8} }) }
|
||||
|
||||
return cat
|
||||
44
Moose_CTLD_Pure/init_example.lua
Normal file
44
Moose_CTLD_Pure/init_example.lua
Normal file
@ -0,0 +1,44 @@
|
||||
-- init_example.lua (optional dev harness snippet)
|
||||
-- Load Moose before this. Then do:
|
||||
-- dofile(lfs.writedir()..[[Scripts\Moose_CTLD_Pure\init_example.lua]])
|
||||
|
||||
local CTLD = dofile(lfs.writedir()..[[Scripts\Moose_CTLD_Pure\Moose_CTLD.lua]])
|
||||
local ctld = CTLD:New({
|
||||
CoalitionSide = coalition.side.BLUE,
|
||||
Zones = {
|
||||
PickupZones = { { name = 'PICKUP_BLUE_MAIN' } },
|
||||
DropZones = { { name = 'DROP_BLUE_1' } },
|
||||
},
|
||||
})
|
||||
|
||||
-- Load the extracted CTLD-based crate catalog
|
||||
local extracted = dofile(lfs.writedir()..[[Scripts\Moose_CTLD_Pure\catalogs\CrateCatalog_CTLD_Extract.lua]])
|
||||
ctld:MergeCatalog(extracted)
|
||||
|
||||
-- Register a SAM site built from 4 crates
|
||||
ctld:RegisterCrate('IR-SAM', {
|
||||
description = '4x crates -> IR SAM site',
|
||||
weight = 300,
|
||||
dcsCargoType = 'container_cargo',
|
||||
required = 4,
|
||||
side = coalition.side.BLUE,
|
||||
category = Group.Category.GROUND,
|
||||
build = function(point, headingDeg)
|
||||
local hdg = math.rad(headingDeg or 0)
|
||||
local function off(dx, dz) return { x=point.x+dx, z=point.z+dz } end
|
||||
local units = {
|
||||
{ type='Strela-10M3', name='CTLD-SAM-1', x=point.x, y=point.z, heading=hdg },
|
||||
{ type='Ural-375', name='CTLD-SAM-SUP', x=off(10,12).x, y=off(10,12).z, heading=hdg },
|
||||
}
|
||||
return { visible=false, lateActivation=false, units=units, name='CTLD_SAM_'..math.random(100000,999999) }
|
||||
end,
|
||||
})
|
||||
|
||||
-- FAC/RECCE setup
|
||||
local FAC = dofile(lfs.writedir()..[[Scripts\Moose_CTLD_Pure\Moose_CTLD_FAC.lua]])
|
||||
local fac = FAC:New(ctld, {
|
||||
CoalitionSide = coalition.side.BLUE,
|
||||
Arty = { Enabled=true, Groups={'BLUE_ARTY_1'}, Rounds=2, Spread=80 },
|
||||
})
|
||||
fac:AddRecceZone({ name = 'RECCE_ZONE_1' })
|
||||
fac:Run()
|
||||
83
Patch-MooseMissions/Download-MooseInclude.ps1
Normal file
83
Patch-MooseMissions/Download-MooseInclude.ps1
Normal file
@ -0,0 +1,83 @@
|
||||
# Download latest Moose_.lua from GitHub
|
||||
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
# Destination path for Moose_.lua. Defaults to repo root alongside Patch-MooseMissions folder
|
||||
[Parameter(Mandatory=$false)]
|
||||
[string]$MooseLuaPath = (Join-Path (Split-Path $PSScriptRoot -Parent) 'Moose_.lua'),
|
||||
|
||||
# Use -Force to actually download; otherwise runs in WhatIf/preview mode
|
||||
[Parameter(Mandatory=$false)]
|
||||
[switch]$Force
|
||||
)
|
||||
|
||||
# Determine WhatIf mode (preview by default)
|
||||
$runInWhatIfMode = -not $Force
|
||||
|
||||
# Ensure destination directory exists (avoid null/invalid path issues)
|
||||
if ([string]::IsNullOrWhiteSpace($MooseLuaPath)) {
|
||||
throw "Destination path for Moose_.lua is empty. Provide -MooseLuaPath."
|
||||
}
|
||||
|
||||
$destDir = Split-Path -Path $MooseLuaPath -Parent
|
||||
if (-not [string]::IsNullOrWhiteSpace($destDir) -and -not (Test-Path $destDir)) {
|
||||
New-Item -Path $destDir -ItemType Directory -Force -WhatIf:$false | Out-Null
|
||||
}
|
||||
|
||||
# Enable TLS 1.2 for GitHub downloads on older environments
|
||||
try {
|
||||
[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor [System.Net.SecurityProtocolType]::Tls12
|
||||
} catch {}
|
||||
|
||||
if (-not $runInWhatIfMode) {
|
||||
Write-Host "Downloading latest Moose_.lua from GitHub..." -ForegroundColor Yellow
|
||||
|
||||
$mooseGitHubUrl = "https://raw.githubusercontent.com/FlightControl-Master/MOOSE_INCLUDE/develop/Moose_Include_Static/Moose_.lua"
|
||||
$backupPath = "$MooseLuaPath.backup"
|
||||
|
||||
try {
|
||||
# Create a single backup of existing Moose_.lua if it exists and backup doesn't exist yet
|
||||
if ((Test-Path $MooseLuaPath) -and -not (Test-Path $backupPath)) {
|
||||
Write-Host " Creating backup: $backupPath" -ForegroundColor Gray
|
||||
Copy-Item $MooseLuaPath $backupPath -Force -WhatIf:$false
|
||||
}
|
||||
|
||||
# Download the file
|
||||
$ProgressPreference = 'SilentlyContinue' # Speeds up Invoke-WebRequest
|
||||
Invoke-WebRequest -Uri $mooseGitHubUrl -OutFile $MooseLuaPath -ErrorAction Stop
|
||||
$ProgressPreference = 'Continue'
|
||||
|
||||
# Verify the download
|
||||
if (Test-Path $MooseLuaPath) {
|
||||
$fileSize = (Get-Item $MooseLuaPath).Length
|
||||
Write-Host " SUCCESS: Downloaded Moose_.lua ($([math]::Round($fileSize/1MB, 2)) MB)" -ForegroundColor Green
|
||||
} else {
|
||||
throw "Downloaded file not found after download"
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Write-Error "Failed to download Moose_.lua from GitHub: $_"
|
||||
Write-Host " URL: $mooseGitHubUrl" -ForegroundColor Red
|
||||
|
||||
# Check if we have a backup or existing file to use
|
||||
if (Test-Path $MooseLuaPath) {
|
||||
Write-Warning "Using existing Moose_.lua file instead"
|
||||
} else {
|
||||
Write-Error "No Moose_.lua file available. Cannot proceed."
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
} else {
|
||||
Write-Host "WHATIF: Would download latest Moose_.lua from GitHub" -ForegroundColor Magenta
|
||||
Write-Host " URL: https://raw.githubusercontent.com/FlightControl-Master/MOOSE_INCLUDE/develop/Moose_Include_Static/Moose_.lua" -ForegroundColor Gray
|
||||
Write-Host " Destination: $MooseLuaPath" -ForegroundColor Gray
|
||||
|
||||
# Still need to verify file exists for WhatIf to show what would be patched
|
||||
if (-not (Test-Path $MooseLuaPath)) {
|
||||
Write-Warning "Moose_.lua not found at $MooseLuaPath - run with -Force to download it"
|
||||
Write-Warning "Continuing with WhatIf to show which missions would be processed..."
|
||||
}
|
||||
Write-Host ""
|
||||
}
|
||||
@ -25,6 +25,27 @@ PowerShell script to automatically patch DCS mission files (.miz) with updated L
|
||||
|
||||
## Usage
|
||||
|
||||
### Get the latest Moose_.lua
|
||||
|
||||
Use the helper script to fetch the latest Moose_.lua into the repo root (C:\DCS_MissionDev\Moose_.lua by default):
|
||||
|
||||
```powershell
|
||||
# Preview (no changes)
|
||||
./Download-MooseInclude.ps1
|
||||
|
||||
# Actually download
|
||||
./Download-MooseInclude.ps1 -Force
|
||||
|
||||
# Optional: choose a different destination
|
||||
./Download-MooseInclude.ps1 -Force -MooseLuaPath 'D:\Scripts\Moose_.lua'
|
||||
```
|
||||
|
||||
If your system blocks script execution, temporarily allow scripts for the current session only:
|
||||
|
||||
```powershell
|
||||
Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass
|
||||
```
|
||||
|
||||
### Basic Usage
|
||||
|
||||
Update a mission with automatic version increment:
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user