mirror of
https://github.com/spencershepard/RotorOps.git
synced 2025-11-10 15:45:30 +00:00
Develop (#40)
For users: -Added KA-50 III and AV8BNA Harrier to slot selection -Changed message in mission generated success dialog -Zone protect SAMs now part of 'Advanced Defenses' feature -Late activated friendly/enemy CAP units are placed in mission as a template for Deployed CAP fighters (ie will not be active unless using Advanced Defenses or 'DEPLOY_FIGHTERS' name for radar ground unit) -improve idle troop behavior at bases/FARPs For Mission creators: -Updated pydcs library supports new units such as technicals -Updated pydcs library supports Falklands map -allow troop pickup from HELO_CARRIER -enemy units with radar can be designated to deploy intercept fighters on detection (see RotorOps.fighter options in RotorOps.lua for details) with options for min detection altitude and distance (min detection altitude allows helis to fly 'under the radar') -Insert RotorOpsServer.lua script and trigger actions if option set in scenario config: rotorops_server: true -scenario template triggers should now be 'untouched' after mission generation, allowing previously unsupported triggers and actions to be used, along with color coding -block adding player helicopters if slots locked in scenario config -Added RotorOps.draw_conflict_zones setting to give users the ability to disable or enable displaying of zones on the map. -allow disabling spinboxes in scenario config -mission ends 10 mins after mission success/fail -copy helicopter start type from templates Internal: -github actions workflow to automatically deploy to update server -Startup version check will ignore micro version -bypassing triggers and merging before save (to preserve unsupported triggers in pydcs). Our goal is to leave the trigrules and trig from the source mission untouched -if using random weather, set ice halo to auto and crystals to none -dont put planes at airports without ILS (to avoid putting planes at helicopter airports ie. Syria) -improved guardPosition task -refactored 'coalition' variables to 'coal' to help prevent introducing errors in RotorOps.lua
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
RotorOps = {}
|
||||
RotorOps.version = "1.3.0"
|
||||
RotorOps.version = "1.3.3"
|
||||
local debug = true
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ RotorOps.pickup_zone_smoke = "blue"
|
||||
RotorOps.ai_task_by_name = true --allow tasking all groups that include key strings in their group names eg 'Patrol'
|
||||
RotorOps.ai_task_by_name_scheduler = true --continually search active groups for key strings and ai tasking
|
||||
RotorOps.patrol_task_string = 'patrol' --default string to search group names for the patrol task. requires ai_task_by_name
|
||||
RotorOps.aggressive_task_string = 'aggressive' --default string to search group names for the patrol task. requires ai_task_by_name
|
||||
RotorOps.aggressive_task_string = 'aggressive' --default string to search group names for the aggressive task. requires ai_task_by_name
|
||||
RotorOps.move_to_active_task_string = "activezone" --default string to search group names for the move to active zone task. requires ai_task_by_name
|
||||
RotorOps.shift_task_string = "shift"
|
||||
RotorOps.guard_task_string = "guard"
|
||||
@@ -49,6 +49,10 @@ RotorOps.guard_task_string = "guard"
|
||||
RotorOps.defending_vehicles_behavior = "shift" --available options: 'none', 'patrol', 'shift'
|
||||
RotorOps.farp_pickups = true --allow ctld troop pickup at FARPs
|
||||
RotorOps.enable_staging_pickzones = true
|
||||
RotorOps.persistent_tasking = false --prevent the script from restasking in a loop --might help with odd movement patterns between zones
|
||||
|
||||
--RotorOps settings that are safe to change only in the script config option in the scenario config file
|
||||
RotorOps.draw_conflict_zones = true
|
||||
|
||||
---[[END OF OPTIONS]]---
|
||||
|
||||
@@ -71,6 +75,13 @@ RotorOps.ai_tasks = {}
|
||||
RotorOps.defending = false
|
||||
RotorOps.staged_units_flag = 111 -- shows a percentage of the units found in the staging zone when the game starts. you can also use 'ROPS_ATTACKERS' for readability
|
||||
|
||||
--fighter variables
|
||||
local fighters_by_detected_unitname = {}
|
||||
RotorOps.fighter_radar_unit_string = 'FIGHTER_DEPLOYMENT' --any unit capable of detecting aircraft by radar can be used as a detection source to spawn intercept fighters, if this string is in the unit name
|
||||
RotorOps.fighter_min_detection_alt = 609 --aircraft below this agl altitude (meters) will not be 'detected' by radar units.
|
||||
RotorOps.fighter_max_detection_dist = 7000 --default max range from radar to target in order for intercept fighters to spawn (you can also set range for individual radar sources via unit name)
|
||||
RotorOps.fighter_max_active = 2 --total maximum active deployed fighters, shared between red/blue
|
||||
|
||||
trigger.action.outText("ROTOR OPS STARTED: "..RotorOps.version, 5)
|
||||
env.info("ROTOR OPS STARTED: "..RotorOps.version)
|
||||
|
||||
@@ -88,6 +99,7 @@ local cooldown = {
|
||||
["attack_helo_msg"] = 0,
|
||||
["attack_plane_msg"] = 0,
|
||||
["trans_helo_msg"] = 0,
|
||||
["e_fighters_inbound_msg"] = 0,
|
||||
}
|
||||
local zone_defenders_flags = {
|
||||
'ROPS_A_DEFENDERS',
|
||||
@@ -202,6 +214,9 @@ RotorOps.gameMsgs = {
|
||||
transp_helos_toff = {
|
||||
{'ENEMY TRANSPORT HELICOPTERS INBOUND!', 'enemy_chopper_inbound.ogg'},
|
||||
},
|
||||
enemy_fighters_inbound = {
|
||||
{'ENEMY FIGHTERS INBOUND!', 'enemy_fighters_inbound.ogg'},
|
||||
},
|
||||
|
||||
|
||||
}
|
||||
@@ -289,16 +304,19 @@ ctld.addCallback(function(_args)
|
||||
local unit = _args.unit
|
||||
local picked_troops = _args.onboard
|
||||
local dropped_troops = _args.unloaded
|
||||
--trigger.action.outText("dbg: ".. mist.utils.tableShow(_args), 5)
|
||||
--env.info("ctld callback: ".. mist.utils.tableShow(_args))
|
||||
|
||||
|
||||
if action == "load_troops" or action == "extract_troops" then
|
||||
trigger.action.outSoundForGroup(unit:getGroup():getID() , sound_effects.troop_pickup[math.random(1, #sound_effects.troop_pickup)])
|
||||
elseif action == "unload_troops_zone" or action == "dropped_troops" then
|
||||
trigger.action.outSoundForGroup(unit:getGroup():getID() , sound_effects.troop_dropoff[math.random(1, #sound_effects.troop_dropoff)])
|
||||
if RotorOps.isUnitInZone(unit, RotorOps.active_zone) then
|
||||
local id = timer.scheduleFunction(RotorOps.gameMsgHandler, RotorOps.gameMsgs.friendly_troops_dropped, timer.getTime() + 6) --allow some extra time so we don't step on the player's troop/unload sound effects
|
||||
|
||||
end
|
||||
if dropped_troops.jtac == true then
|
||||
local id = timer.scheduleFunction(RotorOps.gameMsgHandler, RotorOps.gameMsgs.jtac, timer.getTime() + 6) --allow some extra time so we don't step on the player's troop/unload sound effects
|
||||
local id = timer.scheduleFunction(RotorOps.gameMsgHandler, RotorOps.gameMsgs.jtac, timer.getTime() + 12) --allow some extra time so we don't step on the player's troop/unload sound effects
|
||||
end
|
||||
end
|
||||
|
||||
@@ -529,9 +547,9 @@ function RotorOps.deployTroops(quantity, target_group, announce)
|
||||
debugMsg("DeployTroops on group: "..target_group_obj:getName())
|
||||
local valid_unit = RotorOps.getValidUnitFromGroup(target_group_obj)
|
||||
if not valid_unit then return end
|
||||
local coalition = valid_unit:getCoalition()
|
||||
local coal = valid_unit:getCoalition()
|
||||
local side = "red"
|
||||
if coalition == 2 then side = "blue" end
|
||||
if coal == 2 then side = "blue" end
|
||||
local point = valid_unit:getPoint()
|
||||
ctld.spawnGroupAtPoint(side, quantity, point, 1000)
|
||||
|
||||
@@ -831,11 +849,11 @@ function RotorOps.shiftPosition(vars)
|
||||
local search_radius = vars.radius or 100
|
||||
local inner_radius = 50 --minimum distance to move for randpointincircle
|
||||
local first_valid_unit
|
||||
if grp:isExist() ~= true then return end
|
||||
if grp and grp:isExist() ~= true then return end
|
||||
local start_point = vars.point
|
||||
|
||||
if not start_point then
|
||||
env.info("RotorOps: No point provided, getting current position.")
|
||||
--env.info("RotorOps: No point provided, getting current position.")
|
||||
for index, unit in pairs(grp:getUnits()) do
|
||||
if unit:isExist() == true then
|
||||
first_valid_unit = unit
|
||||
@@ -865,7 +883,7 @@ function RotorOps.shiftPosition(vars)
|
||||
|
||||
if mist.isTerrainValid(rand_point, {'LAND', 'ROAD'}) == true then
|
||||
path[#path + 1] = mist.ground.buildWP(rand_point, formation, 5)
|
||||
env.info("point is valid, adding as waypoint with formation: " .. formation)
|
||||
--env.info("point is valid, adding as waypoint with formation: " .. formation)
|
||||
break
|
||||
end
|
||||
|
||||
@@ -881,11 +899,11 @@ function RotorOps.guardPosition(vars)
|
||||
local grp = vars.grp
|
||||
local search_radius = vars.radius or 100
|
||||
local first_valid_unit
|
||||
if grp:isExist() ~= true then return end
|
||||
if not grp or grp:isExist() ~= true then return end
|
||||
local start_point = vars.point
|
||||
|
||||
if not start_point then
|
||||
env.info("RotorOps: No point provided, getting current position.")
|
||||
--env.info("RotorOps: No point provided, getting current position.")
|
||||
for index, unit in pairs(grp:getUnits()) do
|
||||
if unit:isExist() == true then
|
||||
first_valid_unit = unit
|
||||
@@ -897,13 +915,12 @@ function RotorOps.guardPosition(vars)
|
||||
start_point = first_valid_unit:getPoint()
|
||||
end
|
||||
local object_vol_thresh = 0
|
||||
local max_waypoints = 1
|
||||
local foundUnits = {}
|
||||
|
||||
local volS = {
|
||||
id = world.VolumeType.SPHERE,
|
||||
params = {
|
||||
point = grp:getUnit(1):getPoint(), --check if exists, maybe itterate through grp
|
||||
point = start_point,
|
||||
radius = search_radius
|
||||
}
|
||||
}
|
||||
@@ -927,9 +944,10 @@ function RotorOps.guardPosition(vars)
|
||||
--world.searchObjects(Object.Category.BASE, volS, ifFound)
|
||||
if #foundUnits > 0 then
|
||||
local path = {}
|
||||
path[1] = mist.ground.buildWP(start_point, '', 5)
|
||||
--path[1] = mist.ground.buildWP(RotorOps.getValidUnitFromGroup(grp):getPoint(), '', 2)
|
||||
path[1] = mist.ground.buildWP(RotorOps.getValidUnitFromGroup(grp):getPoint(), '', 2)
|
||||
local rand_index = math.random(1,#foundUnits)
|
||||
path[#path + 1] = mist.ground.buildWP(foundUnits[rand_index]:getPoint(), '', 3)
|
||||
path[#path + 1] = mist.ground.buildWP(foundUnits[rand_index]:getPoint(), '', 2)
|
||||
mist.goRoute(grp, path)
|
||||
end
|
||||
end
|
||||
@@ -963,6 +981,9 @@ function RotorOps.aiExecute(vars)
|
||||
local last_task = vars.last_task
|
||||
local last_zone = vars.last_zone
|
||||
local group_name = vars.group_name
|
||||
if not vars.group_name or not tableHasKey(RotorOps.ai_tasks, group_name) then
|
||||
return
|
||||
end
|
||||
local task = RotorOps.ai_tasks[group_name].ai_task
|
||||
local zone = RotorOps.ai_tasks[group_name].zone
|
||||
local point = RotorOps.ai_tasks[group_name].point
|
||||
@@ -999,17 +1020,13 @@ function RotorOps.aiExecute(vars)
|
||||
|
||||
local should_update = true
|
||||
|
||||
-- if task == last_task then
|
||||
-- should_update = false
|
||||
-- end
|
||||
--
|
||||
-- if same_zone then
|
||||
-- should_update = false
|
||||
-- end
|
||||
--
|
||||
-- if task == "patrol" then
|
||||
-- should_update = true
|
||||
-- end
|
||||
if RotorOps.persistent_tasking and task == last_task then
|
||||
if task == "move_to_active_zone" or task == "move_to_zone" then
|
||||
if same_zone then
|
||||
should_update = false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
if should_update then --check to make sure we don't have the same task
|
||||
@@ -1225,7 +1242,6 @@ function RotorOps.assessUnitsInZone(var)
|
||||
percent_staged_remain = math.floor((#staged_units_remaining / #RotorOps.staged_units) * 100)
|
||||
trigger.action.setUserFlag(RotorOps.staged_units_flag, percent_staged_remain)
|
||||
trigger.action.setUserFlag('ROPS_ATTACKERS', percent_staged_remain)
|
||||
debugMsg("Staged units remaining percent: "..percent_staged_remain.."%")
|
||||
|
||||
|
||||
--is the game finished?
|
||||
@@ -1316,7 +1332,7 @@ function RotorOps.assessUnitsInZone(var)
|
||||
end
|
||||
|
||||
RotorOps.inf_spawns_avail = RotorOps.inf_spawns_avail - 1
|
||||
env.info("ROTOR OPS: Spawned infantry. "..RotorOps.inf_spawns_avail.." spawns remaining in "..zone)
|
||||
env.info("ROTOR OPS: Attempting to spawn infantry. "..RotorOps.inf_spawns_avail.." spawns remaining in "..zone)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1377,7 +1393,7 @@ function RotorOps.drawZones() --this could use a lot of work, we should use tri
|
||||
do
|
||||
local point = trigger.misc.getZone(zone.name).point
|
||||
local radius = trigger.misc.getZone(zone.name).radius
|
||||
local coalition = -1
|
||||
local coal = -1
|
||||
local id = index --this must be UNIQUE!
|
||||
local color = {1, 1, 1, 0.5}
|
||||
local fill_color = {1, 1, 1, 0.1}
|
||||
@@ -1392,11 +1408,14 @@ function RotorOps.drawZones() --this could use a lot of work, we should use tri
|
||||
fill_color = {1, 0, 0, 0.05}
|
||||
end
|
||||
if previous_point ~= nill then
|
||||
--trigger.action.lineToAll(coalition, id + 200, point, previous_point, color, line_type)
|
||||
--trigger.action.lineToAll(coal, id + 200, point, previous_point, color, line_type)
|
||||
end
|
||||
previous_point = point
|
||||
trigger.action.circleToAll(coalition, id, point, radius, color, fill_color, line_type)
|
||||
trigger.action.textToAll(coalition, id + 100, point, color, text_fill_color, font_size, read_only, text)
|
||||
|
||||
if RotorOps.draw_conflict_zones == true then
|
||||
trigger.action.circleToAll(coal, id, point, radius, color, fill_color, line_type)
|
||||
trigger.action.textToAll(coal, id + 100, point, color, text_fill_color, font_size, read_only, text)
|
||||
end
|
||||
end
|
||||
|
||||
for index, cpz in pairs(ctld.pickupZones) do
|
||||
@@ -1407,14 +1426,14 @@ function RotorOps.drawZones() --this could use a lot of work, we should use tri
|
||||
local ctld_zone_status = cpz[4]
|
||||
local point = pickup_zone.point
|
||||
local radius = pickup_zone.radius
|
||||
local coalition = -1
|
||||
local coal = -1
|
||||
local id = index + 150 --this must be UNIQUE!
|
||||
local color = {1, 1, 1, 0.5}
|
||||
local fill_color = {0, 0.8, 0, 0.1}
|
||||
local line_type = 5 --1 Solid 2 Dashed 3 Dotted 4 Dot Dash 5 Long Dash 6 Two Dash
|
||||
if ctld_zone_status == 'yes' or ctld_zone_status == 1 then
|
||||
env.info("pickup zone is active, drawing it to the map")
|
||||
trigger.action.circleToAll(coalition, id, point, radius, color, fill_color, line_type)
|
||||
trigger.action.circleToAll(coal, id, point, radius, color, fill_color, line_type)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1429,14 +1448,14 @@ function RotorOps.drawZones() --this could use a lot of work, we should use tri
|
||||
-- local ctld_zone_status = c_zone[4]
|
||||
-- local point = trigger.misc.getZone(pickup_zone).point
|
||||
-- local radius = trigger.misc.getZone(pickup_zone).radius
|
||||
-- local coalition = -1
|
||||
-- local coal = -1
|
||||
-- local id = index + 150 --this must be UNIQUE!
|
||||
-- local color = {1, 1, 1, 0.5}
|
||||
-- local fill_color = {0, 0.8, 0, 0.1}
|
||||
-- local line_type = 5 --1 Solid 2 Dashed 3 Dotted 4 Dot Dash 5 Long Dash 6 Two Dash
|
||||
-- if ctld_zone_status == 'yes' or ctld_zone_status == 1 then
|
||||
-- --debugMsg("draw the pickup zone")
|
||||
-- trigger.action.circleToAll(coalition, id, point, radius, color, fill_color, line_type)
|
||||
-- trigger.action.circleToAll(coal, id, point, radius, color, fill_color, line_type)
|
||||
-- end
|
||||
-- end
|
||||
-- end
|
||||
@@ -1548,8 +1567,8 @@ function RotorOps.setupCTLD()
|
||||
ctld.pickupZones[#ctld.pickupZones + 1] = { "BRAVO_FARP", RotorOps.pickup_zone_smoke, -1, "no", 0 }
|
||||
ctld.pickupZones[#ctld.pickupZones + 1] = { "CHARLIE_FARP", RotorOps.pickup_zone_smoke, -1, "no", 0 }
|
||||
ctld.pickupZones[#ctld.pickupZones + 1] = { "DELTA_FARP", RotorOps.pickup_zone_smoke, -1, "no", 0 }
|
||||
ctld.pickupZones[#ctld.pickupZones + 1] = { "HELO_CARRIER", "none", -1, "no", 0 }
|
||||
ctld.pickupZones[#ctld.pickupZones + 1] = { "HELO_CARRIER_1", "none", -1, "no", 0 }
|
||||
ctld.pickupZones[#ctld.pickupZones + 1] = { "HELO_CARRIER", "none", -1, "yes", 0 }
|
||||
ctld.pickupZones[#ctld.pickupZones + 1] = { "HELO_CARRIER_1", "none", -1, "yes", 0 }
|
||||
ctld.pickupZones[#ctld.pickupZones + 1] = { "troops1", RotorOps.pickup_zone_smoke, -1, "yes", 0 }
|
||||
ctld.pickupZones[#ctld.pickupZones + 1] = { "troops2", RotorOps.pickup_zone_smoke, -1, "yes", 0 }
|
||||
ctld.pickupZones[#ctld.pickupZones + 1] = { "troops3", RotorOps.pickup_zone_smoke, -1, "yes", 0 }
|
||||
@@ -1742,7 +1761,7 @@ function RotorOps.taskByName()
|
||||
end
|
||||
end
|
||||
if RotorOps.ai_task_by_name_scheduler then
|
||||
local timer_id = timer.scheduleFunction(RotorOps.taskByName, nil, timer.getTime() + 120)
|
||||
local timer_id = timer.scheduleFunction(RotorOps.taskByName, nil, timer.getTime() + 500)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1874,7 +1893,7 @@ function RotorOps.spawnTranspHelos(troops, max_drops)
|
||||
gp.route.points[#gp.route.points + 1] = mist.heli.buildWP(initial_point, 'flyover', 100, 400, 'agl')
|
||||
gp.clone = true
|
||||
local new_group_data = mist.dynAdd(gp) --returns a mist group data table
|
||||
debugTable(new_group_data)
|
||||
--debugTable(new_group_data)
|
||||
-- local new_group = Group.getByName(new_group_data.groupName)
|
||||
-- local grp_controller = new_group:getController() --controller for aircraft can be group or unit level
|
||||
-- grp_controller:setOption(AI.Option.Air.id.REACTION_ON_THREAT , AI.Option.Air.val.REACTION_ON_THREAT.EVADE_FIRE)
|
||||
@@ -1885,6 +1904,273 @@ function RotorOps.spawnTranspHelos(troops, max_drops)
|
||||
|
||||
end
|
||||
|
||||
function RotorOps.spawnCapToZone(_target_zone, _spawn_zone, coal)
|
||||
local target_zone = _target_zone
|
||||
if not target_zone then
|
||||
target_zone = RotorOps.getEnemyZones()[math.random(1, #RotorOps.getEnemyZones())]
|
||||
end
|
||||
local zone_point = trigger.misc.getZone(target_zone).point
|
||||
RotorOps.spawnCap(zone_point, _spawn_zone, coal)
|
||||
end
|
||||
|
||||
RotorOps.fighter_red_source_string = "RED CAP"
|
||||
RotorOps.fighter_blue_source_string = "BLUE CAP"
|
||||
RotorOps.fighter_engagement_dist = 20
|
||||
|
||||
function RotorOps.spawnCap(destination_point, _spawn_zone, coal)
|
||||
local red_zone_string = "RED_CAP_SPAWN"
|
||||
local blue_zone_string = "BLUE_CAP_SPAWN"
|
||||
|
||||
local coal_zone_string = nil
|
||||
if not coal or coal == 0 then
|
||||
return
|
||||
end
|
||||
if coal == 1 then
|
||||
coal_zone_string = red_zone_string
|
||||
source_group_string = RotorOps.fighter_red_source_string
|
||||
end
|
||||
if coal == 2 then
|
||||
coal_zone_string = blue_zone_string
|
||||
source_group_string = RotorOps.fighter_blue_source_string
|
||||
end
|
||||
|
||||
local spawn_zone = _spawn_zone
|
||||
if not _spawn_zone then
|
||||
local spawn_zones = {}
|
||||
for zone, zoneobj in pairs(mist.DBs.zonesByName) do
|
||||
if string.find(zone, coal_zone_string) then
|
||||
spawn_zones[#spawn_zones + 1] = zone
|
||||
--env.info("found cap spawn zone: " .. zone)
|
||||
end
|
||||
end
|
||||
if #spawn_zones < 1 then
|
||||
return
|
||||
end
|
||||
spawn_zone = spawn_zones[math.random(1, #spawn_zones)]
|
||||
end
|
||||
|
||||
local spawn_point = mist.getRandomPointInZone(spawn_zone)
|
||||
|
||||
|
||||
local altitude = math.random(2000,6000)
|
||||
local speed = 300
|
||||
|
||||
|
||||
--pick a template group at random for the source
|
||||
fighter_groups = {} --stores group names of template groups
|
||||
for uName, uData in pairs(mist.DBs.groupsByName) do
|
||||
if string.find(uName, source_group_string) then
|
||||
fighter_groups[#fighter_groups + 1] = uName
|
||||
end
|
||||
end
|
||||
|
||||
if #fighter_groups < 1 then
|
||||
return
|
||||
end
|
||||
|
||||
fighter_group_name = fighter_groups[math.random(1, #fighter_groups)]
|
||||
local group = Group.getByName(fighter_group_name)
|
||||
|
||||
if not group then
|
||||
return
|
||||
end
|
||||
|
||||
local gp = mist.getGroupData(fighter_group_name)
|
||||
--debugTable(gp)
|
||||
|
||||
gp.units[1].alt = altitude
|
||||
gp.units[1].speed = speed
|
||||
gp.units[1].x = spawn_point.x
|
||||
gp.units[1].y = spawn_point.y
|
||||
gp.units[1].heading = mist.utils.getHeadingPoints(spawn_point, destination_point)
|
||||
|
||||
|
||||
|
||||
local engage = {
|
||||
id = 'EngageTargets',
|
||||
params = {
|
||||
maxDist = RotorOps.fighter_engagement_dist,
|
||||
maxDistEnabled = true,
|
||||
targetTypes = { [1] = "Air" },
|
||||
}
|
||||
}
|
||||
|
||||
local orbit = {
|
||||
id = 'Orbit',
|
||||
params = {
|
||||
pattern = 'Race-Track',
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
gp.route = {points = {}}
|
||||
-- gp.route[1] = mist.fixedWing.buildWP(random_airbase:getPoint())
|
||||
-- gp.route[1].type = "TakeOffParking"
|
||||
-- gp.route[1].action = "From Parking Area"
|
||||
-- gp.route[1].airdromeId = airbase_id
|
||||
|
||||
gp.route.points[1] = mist.fixedWing.buildWP(spawn_point, 'turning point', speed, altitude, 'baro')
|
||||
|
||||
gp.route.points[1].task = {}
|
||||
gp.route.points[1].task.id = 'ComboTask'
|
||||
gp.route.points[1].task.params = {}
|
||||
gp.route.points[1].task.params.tasks = {}
|
||||
gp.route.points[1].task.params.tasks[1] = {number = 1, id = 'ControlledTask', enabled = true, params = {task = engage}}
|
||||
gp.route.points[1].task.params.tasks[2] = {number = 2, id = 'ControlledTask', enabled = true, params = {task = orbit}}
|
||||
|
||||
gp.route.points[2] = mist.fixedWing.buildWP(destination_point, 'turning point', speed, altitude, 'baro')
|
||||
|
||||
gp.clone = true
|
||||
local new_group_data = mist.dynAdd(gp) --returns a mist group data table
|
||||
--debugTable(new_group_data)
|
||||
local new_group = Group.getByName(new_group_data.name)
|
||||
if new_group then
|
||||
env.info("RotorOps spawned CAP: "..new_group_data.name)
|
||||
else
|
||||
env.error("RotorOps tried to spawn CAP but something went wrong.")
|
||||
return
|
||||
end
|
||||
|
||||
local grp_controller = new_group:getController() --controller for aircraft can be group or unit level
|
||||
grp_controller:setOption(AI.Option.Air.id.REACTION_ON_THREAT , AI.Option.Air.val.REACTION_ON_THREAT.EVADE_FIRE)
|
||||
grp_controller:setOption(AI.Option.Air.id.FLARE_USING , AI.Option.Air.val.FLARE_USING.WHEN_FLYING_NEAR_ENEMIES)
|
||||
grp_controller:setOption(AI.Option.Air.id.ROE , AI.Option.Air.val.ROE.OPEN_FIRE_WEAPON_FREE)
|
||||
grp_controller:setOption(AI.Option.Air.id.RADAR_USING, AI.Option.Air.val.RADAR_USING.FOR_SEARCH_IF_REQUIRED)
|
||||
|
||||
return new_group_data.name
|
||||
|
||||
end
|
||||
|
||||
|
||||
|
||||
function RotorOps.deployFighters()
|
||||
local function spawn(dest_point, target_unit, coal)
|
||||
fighter = RotorOps.spawnCap(dest_point, nil, coal)
|
||||
|
||||
if fighter and #fighters_by_detected_unitname < RotorOps.fighter_max_active then
|
||||
_spawn_time = RotorOps.getTime()
|
||||
fighters_by_detected_unitname[target_unit] = {
|
||||
name = fighter,
|
||||
spawn_time = _spawn_time,
|
||||
rtb_time = math.random(_spawn_time + (15 * 60), _spawn_time + (25 * 60)),
|
||||
respawn_time = math.random(_spawn_time + (5 * 60), _spawn_time + (15 * 60)),
|
||||
}
|
||||
if ((RotorOps.getTime() - cooldown["e_fighters_inbound_msg"]) > 90) then
|
||||
RotorOps.gameMsg(RotorOps.gameMsgs.enemy_fighters_inbound)
|
||||
cooldown["e_fighters_inbound_msg"] = RotorOps.getTime()
|
||||
end
|
||||
--debugTable(fighters_by_detected_unitname)
|
||||
env.info(target_unit .. " was detected and we spawned a new fighter group: " .. fighter)
|
||||
end
|
||||
end
|
||||
|
||||
local function rtb(group_name)
|
||||
|
||||
local grp = Group.getByName(group_name)
|
||||
if grp then
|
||||
local coal_airbases = coalition.getAirbases(grp:getCoalition())
|
||||
--debugTable(coal_airbases)
|
||||
random_airbase = coal_airbases[math.random(1, #coal_airbases)]
|
||||
|
||||
local airbase_pos = mist.utils.makeVec2(random_airbase:getPoint())
|
||||
local airbase_id = random_airbase:getID()
|
||||
local rtb = {
|
||||
id = 'Mission',
|
||||
params = {
|
||||
route = {
|
||||
points = {
|
||||
[1] = {
|
||||
alt = 2000,
|
||||
alt_type = "RADIO",
|
||||
speed = 300,
|
||||
x = airbase_pos.x,
|
||||
y = airbase_pos.y,
|
||||
aerodromeId = airbase_id,
|
||||
type = "Land",
|
||||
action = "Landing",
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
grp:getController():setTask(rtb)
|
||||
env.info(group_name .. " is RTB to ".. random_airbase:getName())
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--fighter respawning and rtb
|
||||
for target_name, fighter_group_data in pairs(fighters_by_detected_unitname) do
|
||||
local group = Group.getByName(fighter_group_data.name)
|
||||
if group then --if group alive
|
||||
if fighter_group_data.rtb_time < RotorOps.getTime() then
|
||||
env.info(fighter_group_data.name .. " is RTB. Removing from table.")
|
||||
rtb(fighter_group_data.name)
|
||||
fighters_by_detected_unitname[target_name] = nil
|
||||
end
|
||||
else --if group dead
|
||||
if fighter_group_data.respawn_time < RotorOps.getTime() then
|
||||
env.info(fighter_group_data.name .. " has hit respawn_time limit. Removing from table to allow another group to spawn.")
|
||||
fighters_by_detected_unitname[target_name] = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
for uName, uData in pairs(mist.DBs.unitsByName) do
|
||||
local str_index = string.find(uName, RotorOps.fighter_radar_unit_string)
|
||||
if str_index then
|
||||
--trigger.action.outText("Found radar unit: " .. uData.unitName, 2)
|
||||
local radar_unit = Unit.getByName(uData.unitName)
|
||||
local max_distance = RotorOps.fighter_max_detection_dist
|
||||
local dist_str = string.sub(uName, str_index + #RotorOps.fighter_radar_unit_string + 1)
|
||||
if #dist_str > 3 then
|
||||
--env.info("RotorOps: Radar unit name has the max detection distance property:".. dist_str)
|
||||
local dist = tonumber(dist_str)
|
||||
if dist and dist > 0 then
|
||||
max_distance = dist
|
||||
end
|
||||
end
|
||||
|
||||
if radar_unit and radar_unit:getLife() > 0 then
|
||||
--trigger.action.outText(uData.unitName .. " is searching for targets. life=" .. radar_unit:getLife(), 2)
|
||||
|
||||
raw_detected_units = radar_unit:getController():getDetectedTargets(Controller.Detection.RADAR)
|
||||
if raw_detected_units then
|
||||
for i, target in pairs(raw_detected_units) do
|
||||
--debugTable(target)
|
||||
if target.object and target.object:getCategory() == Object.Category.UNIT then
|
||||
local detected_unitname = target.object:getName()
|
||||
local target_pos = target.object:getPosition().p
|
||||
local target_distance = mist.utils.get2DDist(radar_unit:getPosition().p, target_pos)
|
||||
local terrain_height = land.getHeight({x = target_pos.x, y = target_pos.z})
|
||||
local target_agl = target_pos.y - terrain_height
|
||||
|
||||
env.info(uData.unitName .. "detected " .. detected_unitname .. " at " .. target_distance .. " agl:" .. target_agl)
|
||||
|
||||
if target_distance <= max_distance and target_agl >= RotorOps.fighter_min_detection_alt then
|
||||
env.info('RotorOps: ' .. uData.unitName .. " has detected "..detected_unitname .. "at agl=" .. target_agl .. " distance=" .. target_distance)
|
||||
|
||||
if tableHasKey(fighters_by_detected_unitname, detected_unitname) then
|
||||
--trigger.action.outText(detected_unitname .. " already in table with " .. fighters_by_detected_unitname[detected_unitname], 2)
|
||||
|
||||
else
|
||||
spawn(target_pos, detected_unitname, radar_unit:getCoalition())
|
||||
end
|
||||
|
||||
end
|
||||
end --end if target.object
|
||||
end --end of raw_detected targets loop
|
||||
end
|
||||
|
||||
end --end of radar_unit
|
||||
end
|
||||
end --end of all units by name loop
|
||||
|
||||
end
|
||||
|
||||
--- USEFUL PUBLIC 'LUA PREDICATE' FUNCTIONS FOR MISSION EDITOR TRIGGERS (don't forget that DCS lua predicate functions should 'return' these function calls)
|
||||
|
||||
@@ -1928,3 +2214,8 @@ function RotorOps.predPlayerInZone(zone_name)
|
||||
end
|
||||
end
|
||||
|
||||
--determine if enemy CAP is needed
|
||||
function RotorOps.predSpawnRedCap()
|
||||
return true
|
||||
end
|
||||
|
||||
|
||||
68
scripts/RotorOpsServer.lua
Normal file
68
scripts/RotorOpsServer.lua
Normal file
@@ -0,0 +1,68 @@
|
||||
RotorOpsServer = {}
|
||||
RotorOpsServer.version = "0.3"
|
||||
trigger.action.outText("ROTOROPS SERVER SCRIPT: "..RotorOpsServer.version, 5)
|
||||
env.info("ROTOROPS SERVER SCRIPT STARTED: "..RotorOpsServer.version)
|
||||
|
||||
--For SpecialK's DCSServerBot
|
||||
RotorOpsServer.dcsbot = {}
|
||||
RotorOpsServer.dcsbot.enabled = true
|
||||
RotorOpsServer.dcsbot.points = {}
|
||||
RotorOpsServer.dcsbot.points.troop_drop = 6
|
||||
RotorOpsServer.dcsbot.points.unpack = 5
|
||||
RotorOpsServer.dcsbot.points.rearm_repair = 3
|
||||
|
||||
--Mission Ending
|
||||
RotorOpsServer.time_to_end = 600
|
||||
|
||||
function RotorOpsServer.endMission(secs)
|
||||
if secs then
|
||||
RotorOpsServer.time_to_end = secs
|
||||
end
|
||||
|
||||
local function countdown()
|
||||
local minutes = math.floor(RotorOpsServer.time_to_end / 60)
|
||||
local seconds = RotorOpsServer.time_to_end - (minutes * 60) --handle as string
|
||||
if seconds < 10 then
|
||||
seconds = "0" .. seconds
|
||||
end
|
||||
trigger.action.outText("RTB now. Mission will end in "..minutes..":"..seconds, 2, true)
|
||||
RotorOpsServer.time_to_end = RotorOpsServer.time_to_end - 1
|
||||
if RotorOpsServer.time_to_end <= 0 then
|
||||
trigger.action.setUserFlag('mission_end', 2)
|
||||
else
|
||||
timer.scheduleFunction(countdown, {}, timer.getTime() + 1)
|
||||
end
|
||||
end
|
||||
countdown()
|
||||
end
|
||||
|
||||
function RotorOpsServer.registerCtldCallbacks()
|
||||
ctld.addCallback(function(_args)
|
||||
local action = _args.action
|
||||
local unit = _args.unit
|
||||
local picked_troops = _args.onboard
|
||||
local dropped_troops = _args.unloaded
|
||||
--env.info("ctld callback: ".. mist.utils.tableShow(_args))
|
||||
|
||||
local playername = unit:getPlayerName()
|
||||
if RotorOpsServer.dcsbot.enabled and dcsbot and playername then
|
||||
if action == "unload_troops_zone" or action == "dropped_troops" then
|
||||
if RotorOps.isUnitInZone(unit, RotorOps.active_zone) then
|
||||
env.info('RotorOpsServer: adding points (unload troops in active zone) for ' ..playername)
|
||||
net.send_chat(playername .. " dropped troops into the active zone. [" .. RotorOpsServer.dcsbot.points.troop_drop .. " points]")
|
||||
dcsbot.addUserPoints(playername, RotorOpsServer.dcsbot.points.troop_drop)
|
||||
end
|
||||
elseif action == "rearm" or action == "repair" then
|
||||
env.info('RotorOpsServer: adding points (rearm/repair) for ' ..playername)
|
||||
net.send_chat(playername .. " repaired/rearmed our defenses. [" .. RotorOpsServer.dcsbot.points.rearm_repair .. " points]")
|
||||
dcsbot.addUserPoints(playername, RotorOpsServer.dcsbot.points.rearm_repair)
|
||||
elseif action == "unpack" then
|
||||
env.info('RotorOpsServer: adding points (unpack) for ' ..playername)
|
||||
net.send_chat(playername .. " unpacked ground units. [" .. RotorOpsServer.dcsbot.points.unpack .. " points]")
|
||||
dcsbot.addUserPoints(playername, RotorOpsServer.dcsbot.points.unpack)
|
||||
end
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
RotorOpsServer.registerCtldCallbacks()
|
||||
Reference in New Issue
Block a user