RotorOps/scripts/RotorOpsPerks.lua
2024-08-31 12:45:33 -07:00

1795 lines
67 KiB
Lua

--ROTOROPS PERKS by GRIMM
--Points and rewards system
--Check out RotorOps at dcs-helicopters.com
--Full documentation on Github (see the Wiki: RotorOps PERKS)
--How to use: load the script in do script trigger after the mission begins. Requires MIST, but CTLD is optional. Load scripts in this order: 1) MIST 2) CTLD 3) RotorOpsPerks
--This script will add a new menu to the F10 menu called "RotorOps Perks". This menu will allow you to select a perk to use.
--You can define the points earner per action, and the perk options below.
-- Issues:
-- - You will not get points for your troops' kills if you leave your group (ie switch aircraft)
-- - Currently requires a modified version of MIST (see rotorops repo /scripts)
--Todo:
RotorOpsPerks = {}
RotorOpsPerks.version = "1.7.1"
env.warning('ROTOROPS PERKS STARTED: '..RotorOpsPerks.version)
trigger.action.outText('ROTOROPS PERKS STARTED: '..RotorOpsPerks.version, 10)
RotorOpsPerks.perks = {}
RotorOpsPerks.players = {}
RotorOpsPerks.players_temp = {}
RotorOpsPerks.troops = {} --by group name
RotorOpsPerks.fat_cow_farps = {}
RotorOpsPerks.callbacks = {}
---- OPTIONS ----
RotorOpsPerks.silent_points = false --set to true to disable text on points scoring
RotorOpsPerks.player_update_messages = true --set to false to disable messages when players are added/updated to score keeping
RotorOpsPerks.debug = false
RotorOpsPerks.points = {
player_default=0, --how many points each player will start with
kill=10,
kill_inf=5,
kill_heli=20,
kill_plane=20,
kill_armor=15,
kill_ship=15,
cas_bonus=5, --you killed something in proximity of friendly troops
dropped_troops_kill_inf=10, --your troops killed infantry
dropped_troops_kill=20, --your troops killed a vehicle
dropped_troops_kill_armor=30, --your troops killed armor
dropped_troops_kill_plane=50, --your troops killed an plane
dropped_troops_kill_heli=50, --your troops killed a helicopter
rearm=15, --ctld rearm/repair of ground units
unpack=15, --ctld unpack of ground units
}
RotorOpsPerks.player_fatcow_types = {
"UH-60L",
"Mi-8MT",
"CH-47Fbl1"
}
RotorOpsPerks.static_helicopters_at_farp = true --spawn static helicopters at the FARP if player reslots
RotorOpsPerks.static_helicopter_types = { --mapping of player helicopters to their static equivalents
["UH-60L"] = "UH-60A",
["Mi-8MT"] = "Mi-8MT",
["CH-47Fbl1"] = "CH-47D"
}
---- END OPTIONS ----
local function log(msg, log_type)
if type(msg) == 'table' then
msg = mist.utils.tableShow(msg)
end
if not msg then
msg = "nil"
end
if log_type == "error" then
env.error("ROTOROPS PERKS ERROR: " .. msg)
else
env.info("ROTOROPS PERKS: " .. msg)
end
end
local function debugMsg(msg)
if RotorOpsPerks.debug then
if msg then
log(msg)
end
end
end
---- FATCOW PERK ----
--Fat Cow FARP requires static farp objects to work (they are teleported to the landing zone), and a late activated helicopter called 'FAT COW'. See the wiki for more details.
RotorOpsPerks.perks["fatcow"] = {
perk_name='fatcow',
display_name='FatCow FARP',
cost=150,
cooldown=60,
max_per_player=4,
max_per_mission=4,
at_mark=true,
at_position=true,
enabled=true,
sides={0,1,2},
}
RotorOpsPerks.perks.fatcow["menu_condition"] = function(group_name)
local player_unit = Group.getByName(group_name):getUnit(1)
-- if player IS a fatcow, return false
for _, unit_type in pairs(RotorOpsPerks.player_fatcow_types) do
if player_unit:getDesc().typeName == unit_type then
return false
end
end
return true
end
RotorOpsPerks.perks.fatcow["action_condition"] = function(args)
if #RotorOpsPerks.fat_cow_farps < 1 then
return {msg="No FARP resources available!", valid=false}
end
--rearming/refueling doesn't work if enemies are nearby (within 1.1nm)
--this is a DCS feature/limitation so we won't deploy the farp to avoid confusing the players
local units_in_proximity = RotorOpsPerks.findUnitsInVolume({
volume_type = world.VolumeType.SPHERE,
point = args.target_point,
radius = 2050
})
log("units_in_proximity: "..#units_in_proximity)
local enemy_coal = 1
if args.player_coalition == 1 then
enemy_coal = 2
end
for _, unit in pairs(units_in_proximity) do
if unit:getCoalition() == enemy_coal then
return {msg="Too close to enemy!", valid=false}
end
end
return {valid=true}
end
RotorOpsPerks.perks.fatcow["action_function"] = function(args)
local farp = RotorOpsPerks.getFatCowFarp()
if farp == nil then
env.error("No FARP resources available!")
return
end
RotorOpsPerks.spawnAiChinook(args.target_point, farp.index)
return true
end
---- End of FATCOW PERK ----
---- INSTANT STRIKE PERK ----
-- Here's a very simple example of how to create a Perk!
RotorOpsPerks.perks["strike"] = {
perk_name='strike',
display_name='Instant Strike',
cost=150,
cooldown=60,
max_per_player=3,
max_per_mission=10,
at_mark=true,
at_position=false,
enabled=true,
sides={0,1,2},
}
RotorOpsPerks.perks.strike["action_function"] = function(args)
--explosion at dest_point after 10 seconds
timer.scheduleFunction(function()
trigger.action.explosion(args.target_point, 1000)
end, nil, timer.getTime() + 10)
return true
end
---- End of INSTANT STRIKE PERK ----
---- JTAC DRONE PERK ----
RotorOpsPerks.perks["drone"] = {
perk_name='drone',
display_name='JTAC Drone',
cost=75,
cooldown=60,
max_per_player=3,
max_per_mission=6,
at_mark=true,
at_position=false,
enabled=true,
sides={0,1,2},
}
RotorOpsPerks.perks.drone["action_function"] = function(args)
local player_country = Unit.getByName(args.player_unit_name):getCountry()
--set a timer for one minute
timer.scheduleFunction(function()
local code = table.remove(ctld.jtacGeneratedLaserCodes, 1)
RotorOpsPerks.spawnJtacDrone(args.target_point, player_country, code)
table.insert(ctld.jtacGeneratedLaserCodes, code)
end, nil, timer.getTime() + 60)
return true
end
function RotorOpsPerks.spawnJtacDrone(dest_point, country, laser_code)
local drone = {
x = dest_point.x+1500,
y = dest_point.z,
type = "RQ-1A Predator",
speed = 70,
heading = 0,
altitude = 5000,
country = player_country,
skill = "High",
category = "plane",
livery_id = "USAF Standard",
payload = {
["pylons"] = {},
["fuel"] = 200,
["flare"] = 0,
["chaff"] = 0,
["gun"] = 0,
},
}
local drone_route = {
[1] = {
["alt"] = 5000,
["x"] = dest_point.x,
["action"] = "Turning Point",
["alt_type"] = "BARO",
["speed"] = 70,
["form"] = "Turning Point",
["type"] = "Turning Point",
["y"] = dest_point.z+1000,
},
}
local drone_group = {
units = {drone,},
country = country,
category = "airplane",
route = drone_route,
name="Reaper"
}
local orbit = {
id = 'Orbit',
params = {
pattern = 'Circle',
point = {x = dest_point.x, y = dest_point.z},
speed = 70,
altitude = 5000,
}
}
local new_group = mist.dynAdd(drone_group)
if new_group == nil then
return
end
trigger.action.outText('JTAC DRONE IS ON STATION!', 10)
--set a timer for one minute
timer.scheduleFunction(function()
local _code = table.remove(ctld.jtacGeneratedLaserCodes, 1)
log('Setting up JTAC DRONE ' .. new_group.name .. ' to lase targets on: '.._code)
Group.getByName(new_group.name):getController():setTask(orbit)
Group.getByName(new_group.name):getController():setOption(AI.Option.Air.id.REACTION_ON_THREAT, AI.Option.Air.val.REACTION_ON_THREAT.NO_REACTION)
ctld.JTACAutoLase(new_group.name, laser_code, true, "vehicle")
table.insert(ctld.jtacGeneratedLaserCodes, _code)
end, nil, timer.getTime() + 60)
end
---- End of JTAC DRONE PERK ----
---- PLAYER FAT COW ----
RotorOpsPerks.perks["player_fatcow"] = {
perk_name='player_fatcow',
display_name='Player Fat Cow',
cost=0,
cooldown=0,
at_mark=false,
at_position=true,
enabled=true,
sides={0,1,2},
active = {}, --holds a list of active fat cows
}
RotorOpsPerks.perks.player_fatcow["menu_condition"] = function(group_name)
local player_unit = Group.getByName(group_name):getUnit(1)
-- check if player unit is in list of our fat cow heli types
for _, unit_type in pairs(RotorOpsPerks.player_fatcow_types) do
if player_unit:getDesc().typeName == unit_type then
return true
end
end
end
RotorOpsPerks.perks.player_fatcow["action_condition"] = function(args)
local player_unit = Group.getByName(args.player_group_name):getUnit(1)
local player_name = player_unit:getPlayerName()
if player_name and RotorOpsPerks.perks.player_fatcow.active[player_name] then
return {msg="You already have a Fat Cow farp!", valid=false}
end
if #RotorOpsPerks.fat_cow_farps < 1 then
return {msg="No FARP resources available!", valid=false}
end
if player_unit:inAir() then
return {msg="You must be on the ground!", valid=false}
end
--rearming/refueling doesn't work if enemies are nearby (within 1.1nm)
--this is a DCS feature/limitation so we won't deploy the farp to avoid confusing the players
local units_in_proximity = RotorOpsPerks.findUnitsInVolume({
volume_type = world.VolumeType.SPHERE,
point = args.target_point,
radius = 2050
})
log("units_in_proximity: "..#units_in_proximity)
local enemy_coal = 1
if args.player_coalition == 1 then
enemy_coal = 2
end
for _, unit in pairs(units_in_proximity) do
if unit:getCoalition() == enemy_coal then
return {msg="Too close to enemy!", valid=false}
end
end
return {msg="Stay on the ground.", valid=true}
end
RotorOpsPerks.perks.player_fatcow["action_function"] = function(args)
local farp = RotorOpsPerks.getFatCowFarp()
if farp == nil then
env.error("No FARP resources available!")
return
end
local dest_point = {x=args.target_point.x, z=args.target_point.z}
RotorOpsPerks.teleportStatic('FAT COW FARP ' .. farp.index, {x=args.target_point.x, y=args.target_point.z})
local owner_player_name = Group.getByName(args.player_group_name):getUnit(1):getPlayerName()
local monitored_unit = Group.getByName(args.player_group_name):getUnit(1)
local request = {
dest_point = dest_point,
owner_player_name = owner_player_name,
monitored_unit = monitored_unit,
immediate_deploy = true,}
RotorOpsPerks.changeFarpState(farp.index, "requested", request)
RotorOpsPerks.perks.player_fatcow.active[owner_player_name] = true
farp = RotorOpsPerks.fat_cow_farps[farp.index]
return { farp = farp, request = request }
end
---- End of PLAYER FAT COW ----
---- FLARE PERK ----
RotorOpsPerks.perks["flare"] = {
perk_name='flare',
display_name='Illumination Flare',
cost=15,
cooldown=0,
at_mark=true,
at_position=false,
enabled=true,
sides={0,1,2},
}
RotorOpsPerks.perks.flare["action_function"] = function(args)
args.target_point.y = args.target_point.y + 600
trigger.action.illuminationBomb(args.target_point, 1000000)
return true
end
---- End of FLARE PERK ----
function RotorOpsPerks.getPlayerGroupSum(player_group_name, player_attribute, table_name)
--loop through RotorOpsPerks.playersByGroupName
local players = RotorOpsPerks.playersByGroupName(player_group_name)
if not players then
return false
end
local total = 0
for _, player in pairs(players) do
if table_name then
total = total + (player[table_name][player_attribute] or 0)
else
total = total + (player[player_attribute] or 0)
end
end
return total
end
function RotorOpsPerks.spendPoints(player_group_name, points, deduct_points)
local players = RotorOpsPerks.playersByGroupName(player_group_name)
local total_points = RotorOpsPerks.getPlayerGroupSum(player_group_name, "points")
--if players have enough combined points
if total_points < points then
--there was insufficient points
return false
end
--this function can be called to check points and/or deduct points
if deduct_points then
--divide points by the number of players to get an integer
local points_per_player = math.floor(points/#players)
--subtract points from each player equally. If a player doesn't have enough points, subtract the remainder from the next player
local remainder = 0
for _, player in pairs(players) do
local points_to_subtract = points_per_player + remainder
if player.points < points_to_subtract then
remainder = points_to_subtract - player.points
player.points_used = player.points_used + points_to_subtract - remainder
player.points = 0
else
player.points = player.points - points_to_subtract
player.points_used = player.points_used + points_to_subtract
remainder = 0
end
RotorOpsPerks.processCallbacks({action='onPointsSpent', player=player, points_spent=points_to_subtract})
end
RotorOpsPerks.processCallbacks({action='onGroupPointsSpent', player_group_name=player_group_name, points_spent=points})
end
--there was sufficient points
return true
end
function RotorOpsPerks.scorePoints(player_group_name, points, message)
--score points for all players in the group
local players = RotorOpsPerks.playersByGroupName(player_group_name)
if players then
for _, player in pairs(players) do
player.points = player.points + points
player.points_earned = player.points_earned + points
RotorOpsPerks.processCallbacks({action='onPointsScored', player=player, points_scored=points})
end
if message and not RotorOpsPerks.silent_points then
local total = RotorOpsPerks.getPlayerGroupSum(player_group_name, "points")
if #players > 1 then
message = message.." +"..points.." points (" .. total .. " group total)"
else
message = message.." +"..points.." points (" .. total .. ")"
end
trigger.action.outTextForGroup(Group.getByName(player_group_name):getID(), message, 10)
end
end
RotorOpsPerks.processCallbacks({action='onGroupPointsScored', player_group_name=player_group_name, points_scored=points})
end
function RotorOpsPerks.getPointsBalance(player_group_name)
local players = RotorOpsPerks.playersByGroupName(player_group_name)
if not players then
return false
end
local total_points = 0
for _, player in pairs(players) do
total_points = total_points + player.points
end
return total_points
end
function RotorOpsPerks.getPlayerMissionPointsBalance(id)
local player = RotorOpsPerks.players[id]
if not player then
return false
end
return player.points_earned - player.points_used
end
function RotorOpsPerks.checkPoints(player_group_name)
local groupId = Group.getByName(player_group_name):getID()
local players = RotorOpsPerks.playersByGroupName(player_group_name)
if not players then
return false
end
log("Checking points for "..player_group_name.."...")
log(mist.utils.tableShow(players, "players"))
--get combined points from all Players
local total_points = 0
for _, player in pairs(players) do
total_points = total_points + player.points
end
if #players == 1 then
trigger.action.outTextForGroup(groupId, 'You have ' .. total_points .. ' points.', 10)
else
trigger.action.outTextForGroup(groupId, 'Your group has ' .. total_points .. ' total points.', 10)
end
end
function RotorOpsPerks.buildPlayer(identifier, groupName, name, slot, temp_id)
-- if we're missing any of the required attributes, add to temp table until we collect all attributes
if not groupName or not name or not slot then
--create the temp player object if doesn't exist yet
if not RotorOpsPerks.players_temp[temp_id] then
RotorOpsPerks.players_temp[temp_id] = {
identifier=identifier,
name=name,
slot=slot,
groupName = groupName,
}
end
--store individual attributes if available
RotorOpsPerks.players_temp[temp_id].identifier = identifier or RotorOpsPerks.players_temp[temp_id].identifier
RotorOpsPerks.players_temp[temp_id].name = name or RotorOpsPerks.players_temp[temp_id].name
RotorOpsPerks.players_temp[temp_id].slot = slot or RotorOpsPerks.players_temp[temp_id].slot
RotorOpsPerks.players_temp[temp_id].groupName = groupName or RotorOpsPerks.players_temp[temp_id].groupName
--reassign the function args
identifier = RotorOpsPerks.players_temp[temp_id].identifier
name = RotorOpsPerks.players_temp[temp_id].name
slot = RotorOpsPerks.players_temp[temp_id].slot
groupName = RotorOpsPerks.players_temp[temp_id].groupName
--if we're still missing attributes, return
if not groupName or not name or not slot or not identifier then
-- env.warning('MISSING ATTRIBUTES FOR ' .. temp_id)
debugMsg(mist.utils.tableShow(RotorOpsPerks.players_temp[temp_id]))
return
end
--we have all we need, so add to the players table
debugMsg('BUILDPLAYER: Now adding ' .. temp_id .. ' to players table as ' .. identifier)
RotorOpsPerks.updatePlayer(identifier, groupName, name, slot)
end
end
function RotorOpsPerks.updatePlayer(identifier, groupName, name, slot)
if not Group.getByName(groupName) then
debugMsg('GROUP ' .. groupName .. ' DOES NOT EXIST')
return
end
local groupId = Group.getByName(groupName):getID()
local side = Group.getByName(groupName):getCoalition()
--add a new player
if not RotorOpsPerks.players[identifier] then
RotorOpsPerks.players[identifier] = {
identifier=identifier,
name=name,
slot=slot,
points = RotorOpsPerks.points.player_default,
groupId = groupId,
groupName = groupName,
side = side,
menu = {},
perks_used = {},
points_used = 0,
points_earned = 0,
}
env.warning('ADDED ' .. identifier .. ' TO PLAYERS TABLE')
log(mist.utils.tableShow(RotorOpsPerks.players[identifier]))
missionCommands.removeItemForGroup(groupId, {[1] = 'ROTOROPS PERKS'})
RotorOpsPerks.addRadioMenuForGroup(groupName)
if RotorOpsPerks.player_update_messages then
trigger.action.outText('PERKS: Added ' .. name .. ' to '.. groupName, 10)
end
RotorOpsPerks.processCallbacks({action='onPlayerAdded', player=RotorOpsPerks.players[identifier], perk=nil})
--update an existing player
elseif RotorOpsPerks.players[identifier].groupId ~= groupId then
env.warning('UPDATING ' .. identifier .. ' TO GROUP NAME: ' .. groupName)
log(mist.utils.tableShow(RotorOpsPerks.players[identifier]))
if RotorOpsPerks.player_update_messages then
trigger.action.outText('PERKS: ' .. name .. ' moved to '.. groupName, 10)
end
--update player
RotorOpsPerks.players[identifier].groupId = groupId
RotorOpsPerks.players[identifier].groupName = groupName
RotorOpsPerks.players[identifier].side = side
RotorOpsPerks.players[identifier].slot = slot
RotorOpsPerks.players[identifier].name = name
--REMOVE RADIO ITEMS FOR GROUP (since another player may have been in the group previously)
-- missionCommands.removeItemForGroup(groupId, RotorOpsPerks.players[identifier].menu.root)
missionCommands.removeItemForGroup(groupId, {[1] = 'ROTOROPS PERKS'})
RotorOpsPerks.addRadioMenuForGroup(groupName)
RotorOpsPerks.processCallbacks({action='onPlayerUpdated', player=RotorOpsPerks.players[identifier], perk=nil})
end
end
--returns a table of players matching the group name
function RotorOpsPerks.playersByGroupName(group_name)
local players = {}
for identifier, player in pairs(RotorOpsPerks.players) do
if player.groupName == group_name then
players[#players + 1] = player
end
end
return players
end
function RotorOpsPerks.addRadioMenuForGroup(groupName)
local groupId = Group.getByName(groupName):getID()
local group_side = Group.getByName(groupName):getCoalition()
local menu_root = missionCommands.addSubMenuForGroup(groupId, 'ROTOROPS PERKS')
missionCommands.addCommandForGroup(groupId, 'Check points balance', menu_root, RotorOpsPerks.checkPoints, groupName)
for perk_name, perk in pairs(RotorOpsPerks.perks) do
local avail_for_side = false
local avail_for_group = true
if perk.menu_condition ~= nil then
avail_for_group = perk.menu_condition(groupName)
end
for _, side in pairs(perk.sides) do
if group_side == side then
avail_for_side = true
end
end
if perk.enabled and avail_for_side and avail_for_group then
if perk.at_mark then
--addPerkCommand(groupId, groupName, perk, menu_root, {target='mark'})
missionCommands.addCommandForGroup(groupId, perk.cost .. ':' .. perk.display_name .. ' at mark (' .. perk.perk_name ..')', menu_root , RotorOpsPerks.requestPerk, {player_group_name=groupName, perk_name=perk.perk_name, target='mark'})
end
if perk.at_position then
--addPerkCommand(groupId, groupName, perk, menu_root, {target='position'})
missionCommands.addCommandForGroup(groupId, perk.cost .. ':' .. perk.display_name .. ' on me', menu_root , RotorOpsPerks.requestPerk, {player_group_name=groupName, perk_name=perk.perk_name, target='position'})
end
end
end
end
---- FATCOW FARP SUPPORTING FUNCTIONS ----
function RotorOpsPerks.monitorFarps()
--log(mist.utils.tableShow(RotorOpsPerks.fat_cow_farps))
local function farpExists(i)
local farp = StaticObject.getByName('FAT COW FARP ' .. i)
local tent = StaticObject.getByName('FAT COW TENT ' .. i)
local ammo = StaticObject.getByName('FAT COW AMMO ' .. i)
local fuel = StaticObject.getByName('FAT COW FUEL ' .. i)
if farp:isExist() and tent:isExist() and ammo:isExist() and fuel:isExist() then
return true
else
return false
end
end
--schedule the function
timer.scheduleFunction(RotorOpsPerks.monitorFarps, nil, timer.getTime() + 11)
--loop over RotorOpsPerks.fat_cow_farps
for i, farp in pairs(RotorOpsPerks.fat_cow_farps) do
--check if farp is damaged/destroyed
if not farpExists(i) then
trigger.action.outText('Some FARP resources have been destroyed', 30)
env.warning('FAT COW FARP ' .. i .. ' RESOURCES DESTROYED')
RotorOpsPerks.fat_cow_farps[i] = nil
else --farp still exists
--**** monitor player farps
local despawn_farp = false
if farp.state == "deployed_occupied" then
if not farp.monitored_unit:isExist() then
--despawn_farp = true
log("FARP monitored_unit doesn't exist, doing NOTHING!")
elseif farp.monitored_unit:inAir() then
despawn_farp = true
log("FARP monitored_unit no longer on the ground, despawning FARP!")
elseif math.abs(farp.monitored_unit:getPosition().p.x - farp.deployed_p.x) > 50 or math.abs(farp.monitored_unit:getPosition().p.z - farp.deployed_p.z) > 50 then
log("FARP monitored_unit moved from the FARP, despawning FARP!")
despawn_farp = true
end
end
if farp.expiration_time and timer.getTime() > farp.expiration_time then
log("Fat Cow FARP expired, despawning FARP!")
trigger.action.outText('Fat Cow FARP expired, despawning FARP!', 5)
despawn_farp = true
end
if despawn_farp then
RotorOpsPerks.changeFarpState(farp.index, 'not_deployed', nil)
end
--*****
end
end
end
function RotorOpsPerks.buildFatCowFarpTable()
local farp_found=true
local i = 1
while(farp_found) do
--find static invisible farps that start with name 'FAT COW FARP'
local farp = StaticObject.getByName('FAT COW FARP ' .. i)
local tent = StaticObject.getByName('FAT COW TENT ' .. i)
local ammo = StaticObject.getByName('FAT COW AMMO ' .. i)
local fuel = StaticObject.getByName('FAT COW FUEL ' .. i)
if farp and tent and ammo and fuel then
log("FAT COW FARP " .. i .. " FOUND")
RotorOpsPerks.fat_cow_farps[i] = {
index = i,
farp = farp,
farp_p = farp:getPosition().p,
tent = tent,
tent_p = tent:getPosition().p,
ammo = ammo,
ammo_p = ammo:getPosition().p,
fuel = fuel,
fuel_p = fuel:getPosition().p,
mark_id_base = 5550 + (i * 10), --arbitrary id for map drawing; allows 10 marks per FARP
state = 'uninitialized',
}
RotorOpsPerks.changeFarpState(i, 'not_deployed', nil) --initialize the farp
i = i + 1
else
farp_found = false
end
end
end
function RotorOpsPerks.getFatCowFarp()
for i, farp in pairs(RotorOpsPerks.fat_cow_farps) do
if farp.available then
RotorOpsPerks.updateFarpAttribute(farp.index, 'available', false)
farp.available = false
return farp
end
end
return nil
end
function RotorOpsPerks.changeFarpState(farp_index, new_state, request)
local farp = RotorOpsPerks.fat_cow_farps[farp_index]
local old_state = farp.state
log("Changing FARP state from " .. old_state .. " to " .. new_state)
if new_state == old_state then
return
end
local next_state = nil
if new_state == 'requested' then --set the farp attributes
if not request then
log("No request object provided for FARP state change", "error")
return
end
farp.deployed = false
farp.available = false
farp.deployed_p = request.dest_point
farp.owner_player_name = request.owner_player_name
farp.monitored_unit = request.monitored_unit
--log all vars
log("FARP REQUESTED AT: "..request.dest_point.x..", "..request.dest_point.z)
if request.immediate_deploy then --for player fat cow farps
farp.expiration_time = timer.getTime() + 3600
RotorOpsPerks.spawnFatCowFarp(farp.deployed_p.x, farp.deployed_p.z, farp.index, 15, "deployed_occupied")
end
elseif new_state == 'deployed_ai' then
farp.deployed = true
farp.expiration_time = timer.getTime() + 3600
RotorOpsPerks.spawnFatCowFarp(farp.deployed_p.x, farp.deployed_p.z, farp.index, 15, "deployed_empty")
elseif new_state == 'deployed_occupied' then
farp.deployed = true
if old_state == 'deployed_static' then
RotorOpsPerks.despawnStaticHelicopter(farp.index)
end
log("Player is on station. Now monitoring: " .. farp.monitored_unit:getName())
elseif new_state == 'deployed_static' then
local static_type = nil
if farp.monitored_unit then
local player_type = farp.monitored_unit:getTypeName()
log("Player type: " .. player_type .. " left the station. Now deploying static helicopter.")
static_type = RotorOpsPerks.static_helicopter_types[player_type]
end
if not static_type then
static_type = 'CH-47D'
end
farp.deployed = true
farp.monitored_unit = nil
RotorOpsPerks.spawnStaticFatCowHeli(farp.index, static_type)
elseif new_state == 'deployed_empty' then
farp.deployed = true
farp.monitored_unit = nil
if old_state == 'deployed_static' then
RotorOpsPerks.despawnStaticHelicopter(farp.index)
end
elseif new_state == 'not_deployed' then --reset the farp
if farp.owner_player_name then
RotorOpsPerks.perks.player_fatcow.active[farp.owner_player_name] = nil
end
RotorOpsPerks.despawnFatCowFarp(farp.index)
if old_state == 'deployed_static' then
RotorOpsPerks.despawnStaticHelicopter(farp.index)
elseif old_state == 'deployed_occupied' then
RotorOpsPerks.perks.player_fatcow.active[farp.owner_player_name] = nil --what about the AI fatcow perk??
if farp.monitored_unit and farp.monitored_unit:getGroup() then
trigger.action.outTextForGroup(farp.monitored_unit:getGroup():getID(), "FAT COW FARP despawned.", 10)
end
end
farp.deployed = false
farp.deployed_p = nil
farp.owner_player_name = nil
farp.monitored_unit = nil
farp.expiration_time = nil
farp.available = true
farp.static_heli_name = nil
end
--update F10 map
if farp.deployed then
RotorOpsPerks.updateFarpMarks(farp.index)
else
RotorOpsPerks.removeFarpMarks(farp.index)
end
--update the farp state
farp.state = new_state
RotorOpsPerks.fat_cow_farps[farp_index] = farp
if next_state then
RotorOpsPerks.changeFarpState(farp_index, next_state, nil)
end
end
function RotorOpsPerks.updateFarpAttribute(farp_index, attribute, value)
local value_str = tostring(value)
if type(value) ~= 'string' and type(value) ~= 'number' and type(value) ~= 'boolean' then
value_str = "type " .. type(value)
end
debugMsg('RotorOpsPerks.updateFarpAttribute: ' .. farp_index .. ' ' .. attribute .. ' ' .. value_str)
local farp = RotorOpsPerks.fat_cow_farps[farp_index]
farp[attribute] = value
RotorOpsPerks.fat_cow_farps[farp_index] = farp
end
function RotorOpsPerks.teleportStatic(source_name, dest_point)
debugMsg('RotorOpsPerks.teleportStatic: ' .. source_name)
local source = StaticObject.getByName(source_name)
if not source then
log('RotorOpsPerks.teleportStatic: source not found: ' .. source_name)
return
end
local vars = {}
vars.gpName = source_name
vars.action = 'teleport'
vars.point = mist.utils.makeVec3(dest_point)
local res = mist.teleportToPoint(vars)
if res then
log('RotorOpsPerks.teleportStatic: ' .. source_name .. ' success')
else
log('RotorOpsPerks.teleportStatic: ' .. source_name .. ' failed')
end
end
function RotorOpsPerks.spawnFatCowFarp(pt_x, pt_y, index, delay, new_state)
--log all vars
log("spawnFatCowFarp called with:")
log("FARP DEPLOYED AT: "..pt_x..", "..pt_y)
log("FARP INDEX: "..index)
log("DELAY: "..delay)
log("NEW STATE: "..new_state)
local farp = RotorOpsPerks.fat_cow_farps[index]
local dest_point = mist.utils.makeVec3GL({x = pt_x, y = pt_y})
trigger.action.smoke(dest_point, 2)
trigger.action.outText('Fat Cow FARP will deploy in ' ..delay .. ' seconds.', 20)
timer.scheduleFunction(function()
local fuel_point = {x = dest_point.x + 35, y = dest_point.y, z = dest_point.z}
RotorOpsPerks.teleportStatic('FAT COW FUEL ' .. index, fuel_point)
RotorOpsPerks.teleportStatic('FAT COW TENT ' .. index, fuel_point)
local ammo_point = {x = dest_point.x - 35, y = dest_point.y, z = dest_point.z}
RotorOpsPerks.teleportStatic('FAT COW AMMO ' .. index, ammo_point)
RotorOpsPerks.changeFarpState(farp.index, new_state, dest_point, nil, nil, nil)
end, nil, timer.getTime() + delay)
end
function RotorOpsPerks.despawnFatCowFarp(farp_index)
local farp = RotorOpsPerks.fat_cow_farps[farp_index]
RotorOpsPerks.teleportStatic('FAT COW FARP '..farp.index, {x=farp.farp_p.x, y=farp.farp_p.z})
RotorOpsPerks.teleportStatic('FAT COW TENT '..farp.index, {x=farp.tent_p.x, y=farp.tent_p.z})
RotorOpsPerks.teleportStatic('FAT COW AMMO '..farp.index, {x=farp.ammo_p.x, y=farp.ammo_p.z})
RotorOpsPerks.teleportStatic('FAT COW FUEL '..farp.index, {x=farp.fuel_p.x, y=farp.fuel_p.z})
end
function RotorOpsPerks.updateFarpMarks(farp_index)
local farp = RotorOpsPerks.fat_cow_farps[farp_index]
-- get the current farp position
local farp_point = farp.deployed_p
--add map marker
local coal = -1
local color = {0, 0.4, 0.95, 0.7}
local fill_color = {0, 0.4, 0.95, 0.4}
local text_fill_color = {0, 0, 0, 0}
local line_type = 3 --1 Solid 2 Dashed 3 Dotted 4 Dot Dash 5 Long Dash 6 Two Dash
local font_size = 20
local read_only = false
local text = "FAT COW FARP " .. farp.index
if farp.owner_player_name then
text = farp.owner_player_name .. "'s " .. text
end
local radius = 150 --ctld FOB pickup radius
trigger.action.circleToAll(coal, farp.mark_id_base + 0, farp_point, radius, color, fill_color, line_type)
trigger.action.textToAll(coal, farp.mark_id_base + 1, farp_point, color, text_fill_color, font_size, read_only, text)
end
function RotorOpsPerks.removeFarpMarks(farp_index)
local farp = RotorOpsPerks.fat_cow_farps[farp_index]
--for loop with 10 iterations to remove all marks
-- maybe we should store each mark in the farp object
for i = 0, 9 do
trigger.action.removeMark(farp.mark_id_base + i)
end
end
function RotorOpsPerks.spawnStaticFatCowHeli(farp_index, type)
-- this will dynamically create a static helicopter at the farp, as a placeholder for player who chose another slot
-- or to replace the AI Chinook which will eventually despawn
if not RotorOpsPerks.static_helicopters_at_farp then
return false
end
log('spawnStaticFatCowHeli called with farp_index:' .. farp_index)
local farp = RotorOpsPerks.fat_cow_farps[farp_index]
if not farp then
return false
end
local static_unit_name = 'FAT COW ' .. farp.index .. ' STATIC'
--use mist.dynAddStatic at the farp position
local heli = {
category = 'helicopters',
type = type,
country = 'USA',
x = farp.deployed_p.x,
y = farp.deployed_p.z,
name = static_unit_name,
heading = 0,
}
local new_heli = mist.dynAddStatic(heli)
if new_heli then
log('Static helicopter spawned as ' .. static_unit_name)
RotorOpsPerks.updateFarpAttribute(farp_index, 'static_heli_name', static_unit_name)
return true
end
return false
end
function RotorOpsPerks.despawnStaticHelicopter(farp_index)
local farp = RotorOpsPerks.fat_cow_farps[farp_index]
if farp.static_heli_name then
log('Despawning static helicopter ' .. farp.static_heli_name)
local unit = StaticObject.getByName(farp.static_heli_name)
if not unit then
log('Static helicopter not found: ' .. farp.static_heli_name, 'error')
return false
end
local sphere = {
point = unit:getPosition().p,
radius = 20
}
unit:destroy()
RotorOpsPerks.updateFarpAttribute(farp_index, 'static_heli_name', nil)
sphere.point.y = land.getHeight({x = sphere.point.x, y = sphere.point.z})
local volS = {
id = world.VolumeType.SPHERE,
params = {
point = sphere.point,
radius = sphere.radius
}
}
world.removeJunk(volS)
return true
end
end
function RotorOpsPerks.spawnAiChinook(dest_point, farp_index) --the AI Chinook with spawn and fly to the FARP
local farp = RotorOpsPerks.fat_cow_farps[farp_index]
local request = {
dest_point = dest_point,
farp_index = farp_index,
monitored_unit = nil,
immediate_deploy = false,
owner_player_name = nil,
}
RotorOpsPerks.changeFarpState(farp_index, 'requested', request)
local index = farp_index
local fatcow_name = 'FAT COW'
local source_farp_name = 'FAT COW FARP ' .. index
log('spawnAiChinook called with ' .. source_farp_name)
dest_point = mist.utils.makeVec2(dest_point)
local approach_point = mist.getRandPointInCircle(dest_point, 1000, 900)
trigger.action.smoke(mist.utils.makeVec3GL(dest_point), 2)
local fatcow_group = Group.getByName(fatcow_name)
if not fatcow_group then
env.warning('FatCow group not found')
return
end
RotorOpsPerks.teleportStatic(source_farp_name, dest_point)
local airbasefarp = Airbase.getByName(source_farp_name)
if not airbasefarp then
env.warning('FatCow FARP not found: ' .. source_farp_name)
return
end
local airbase_pos = mist.utils.makeVec2(airbasefarp:getPoint())
local script = [[
RotorOpsPerks.spawnFatCowFarp(]] .. dest_point.x ..[[,]] .. dest_point.y .. [[,]] .. index .. [[, 235, "deployed_ai")
]]
local myscriptaction = {
id = 'WrappedAction',
params = {
action = {
id = 'Script',
params = {
command = script,
},
},
},
}
local script_string = [[local this_grp = ...
this_grp:getController():setOption(AI.Option.Air.id.REACTION_ON_THREAT , AI.Option.Air.val.REACTION_ON_THREAT.EVADE_FIRE)
this_grp:getController():setOption(AI.Option.Air.id.FLARE_USING , AI.Option.Air.val.FLARE_USING.WHEN_FLYING_NEAR_ENEMIES)]]
local setOptions = {
id = 'WrappedAction',
params = {
action = {
id = 'Script',
params = {
command = script_string,
},
},
},
}
local group = Group.getByName(fatcow_name)
local initial_point = group:getUnits()[1]:getPoint()
local gp = mist.getGroupData(fatcow_name)
--debugTable(gp)
gp.route = {points = {}}
gp.route.points[1] = mist.heli.buildWP(initial_point, initial, 'flyover', 0, 0, 'agl')
gp.route.points[2] = mist.heli.buildWP(initial_point, initial, 'flyover', 150, 100, 'agl')
gp.route.points[2].task = setOptions
gp.route.points[3] = mist.heli.buildWP(approach_point, 'flyover', 150, 400, 'agl')
gp.route.points[4] = mist.heli.buildWP(approach_point, 'flyover', 20, 200, 'agl')
gp.route.points[5] = mist.heli.buildWP(dest_point, 'turning point', 10, 70, 'agl')
gp.route.points[5].task = myscriptaction
gp.route.points[6] = {
alt = 70,
alt_type = "RADIO",
speed = 10,
x = airbase_pos.x,
y = airbase_pos.y,
helipadId = airbasefarp:getID(),
aerodromeId = airbasefarp:getID(),
type = "Land",
action = "Landing",
}
gp.clone = true
local new_group_data = mist.dynAdd(gp)
end
function RotorOpsPerks.requestPerk(args)
log('requestPerk called for ' .. args.perk_name)
--log(mist.utils.tableShow(args, 'args'))
local player_group = Group.getByName(args.player_group_name)
local player_unit = player_group:getUnits()[1]
local player_unit_name = player_unit:getName()
local player_pos = player_unit:getPoint()
local pilot_name = player_unit:getPlayerName() --doesn't work
local players = RotorOpsPerks.playersByGroupName(args.player_group_name)
if not players then
env.warning('No players found in group ' .. args.player_group_name)
return
end
--get the perk object
local perk = RotorOpsPerks.perks[args.perk_name]
--init some essential variables
if not perk.used then
perk.used = 0
end
if not perk.last_used then
perk.last_used = 0
end
--find the intended point
local target_point = nil
if args.target == 'position' then
target_point = player_pos
elseif args.target == 'mark' then
local temp_mark = nil
for _, mark in pairs(mist.DBs.markList) do
debugMsg('mark: ' .. mist.utils.tableShow(mark, 'mark'))
local perk_name_matches = false
--determine if mark name matches the perk name
local mark_name = mark.text
--remove new line from mark name
mark_name = mark_name:gsub("\n", "")
if mark_name == args.perk_name then
perk_name_matches = true
log("mark name matches perk name")
end
if perk_name_matches then
--if MULTIPLAYER (initiator property missing in single player)
if mark.initiator then
--if mark is from player's group
if mark.initiator.id_ == player_unit.id_ then
target_point = mark.pos
if temp_mark then
--if there is already a mark from the player's group, use the most recent one
if mark.time > temp_mark.time then
temp_mark = mark
end
else
temp_mark = mark
end
end
else --we assume single player
if temp_mark then
--if there is already a mark from the player's group, use the most recent one
if mark.time > temp_mark.time then
temp_mark = mark
end
else
temp_mark = mark
end
end
end
end
-- log(mist.utils.tableShow(mist.DBs.markList, 'markList'))
-- log('player group' .. mist.utils.tableShow(player_group, 'player_group'))
-- log('player' .. mist.utils.tableShow(player_unit, 'player_unit'))
if temp_mark then
target_point = temp_mark.pos
end
end
local perk_used_count = RotorOpsPerks.getPlayerGroupSum(args.player_group_name, args.perk_name, "perks_used")
if perk.max_per_player ~= nil and perk_used_count >= (perk.max_per_player*#players) then --multiply by number of players in group
if #players > 1 then
trigger.action.outTextForGroup(player_group:getID(), 'UNABLE. You already used this perk ' .. perk_used_count .. ' times.', 10)
else
trigger.action.outTextForGroup(player_group:getID(), 'UNABLE. Your group already used this perk ' .. perk_used_count .. ' times.', 10)
end
debugMsg('max_per_group reached for ' .. args.perk_name)
return
end
-- check if the max per mission has been reached
if perk.max_per_mission ~= nil then
if perk.used >= perk.max_per_mission then
debugMsg(args.player_group_name.. ' requested ' .. args.perk_name .. ' but max per mission reached')
trigger.action.outTextForGroup(player_group:getID(), 'UNABLE. Used too many times in the mission.', 10)
return
end
end
--check if position requirements for action are met
if args.target == "mark" then
if not target_point then
debugMsg(args.player_group_name.. ' requested ' .. args.perk_name .. ' but no target was found')
trigger.action.outTextForGroup(player_group:getID(), 'UNABLE. Add a mark called "' .. args.perk_name .. '" to the F10 map first', 10)
return
end
end
--check if cooldown is over in perks object
if perk.cooldown and perk.cooldown > 0 then
if perk.last_used + perk.cooldown > timer.getTime() then
local time_remaining = perk.last_used + perk.cooldown - timer.getTime()
--round time_remaining
time_remaining = math.floor(time_remaining + 0.5)
debugMsg(args.player_group_name.. ' tried to use ' .. args.perk_name .. ' but cooldown was not over')
trigger.action.outTextForGroup(player_group:getID(), 'UNABLE. Wait for '.. time_remaining .. ' seconds.', 10)
return
end
end
--add some useful data to pass to perk condition and action functions
args.target_point = target_point
args.player_group = player_group
args.player_unit = player_unit
args.player_unit_name = player_unit_name
args.player_coalition = player_group:getCoalition()
args.pilot_name = pilot_name
--show all variables available to perk actions and conditions
--log('args: ' .. mist.utils.tableShow(args, 'args'))
--check the perk's unique prerequisite conditions
if perk.action_condition then
local r = perk.action_condition(args)
if r and not r.valid then
local message = r.msg or "UNABLE. Requirements not met."
debugMsg(args.player_group_name.. ' tried to use ' .. args.perk_name .. ' but prereq failed with message: ' .. message)
trigger.action.outTextForGroup(player_group:getID(), message, 10)
return
end
if r and r.valid and r.msg then
trigger.action.outTextForGroup(player_group:getID(), r.msg, 10)
end
end
--check points
if RotorOpsPerks.spendPoints(args.player_group_name, perk.cost, false) then
log(args.player_group_name.. ' has sufficient (' .. perk.cost .. ') points for ' .. args.perk_name)
else
log(args.player_group_name.. ' tried to spend ' .. perk.cost .. ' points for ' .. args.perk_name .. ' but did not have enough points')
if #players == 1 then
trigger.action.outTextForGroup(player_group:getID(), 'NEGATIVE. You have ' .. RotorOpsPerks.getPlayerGroupSum(args.player_group_name, "points") .. ' points. (cost '.. perk.cost .. ')', 10)
else
trigger.action.outTextForGroup(player_group:getID(), 'NEGATIVE. Your group has ' .. RotorOpsPerks.getPlayerGroupSum(args.player_group_name, "total points") .. ' points. (cost '.. perk.cost .. ')', 10)
end
return
end
--call perk action and deduct points if successful
local action_result = perk.action_function(args)
if action_result then
local data = {action_result = action_result}
RotorOpsPerks.spendPoints(args.player_group_name, perk.cost, true)
RotorOpsPerks.processCallbacks({action='onPerkUsed', perk=perk, request=args, data=data })
end
--update last_used
perk.last_used = timer.getTime()
perk.used = perk.used or 0 + 1
log('Player ' .. args.player_unit_name .. ' requested ' .. args.perk_name .. ' at ' .. args.target)
--increment player used for perk type, and initialize if it doesn't exist.
local perk_user_per_player = 1/(#players or 1)
--round perk_user_per_player to one decimal place
perk_user_per_player = math.floor(perk_user_per_player*10 + 0.5)/10
--loop through players
for _, player in pairs(players) do
if player.perks_used[args.perk_name] then
player.perks_used[args.perk_name] = player.perks_used[args.perk_name] + perk_user_per_player
else
player.perks_used[args.perk_name] = perk_user_per_player
end
end
--message players with humansByName DB
for _player_name, _player in pairs(mist.DBs.humansByName) do
--get unit object from id
local _player_unit = Unit.getByName(_player_name)
if _player_unit and _player_unit:isExist() then
local player_position = _player_unit:getPosition().p
local position_string = ' at ' .. RotorOpsPerks.BRString(target_point, player_position)
if _player.groupName == args.player_group_name then --if the player is the one who requested the perk
if args.target == 'position' then
position_string = ' at your position'
end
-- send affirmative message to the the requesting player
trigger.action.outTextForGroup(_player.groupId, 'AFFIRM. ' .. RotorOpsPerks.perks[args.perk_name].display_name .. position_string, 10)
else
-- send messages to all other players
log(player_unit:getPlayerName() .. ' requested ' .. RotorOpsPerks.perks[args.perk_name].display_name .. position_string)
trigger.action.outTextForGroup(_player.groupId, player_unit:getPlayerName() .. ' requested ' .. RotorOpsPerks.perks[args.perk_name].display_name .. position_string, 10)
end
end
end
end
function RotorOpsPerks.BRString(point_a, point_b)
point_a = mist.utils.makeVec3(point_a, 0)
point_b = mist.utils.makeVec3(point_b, 0)
local vec = {x = point_a.x - point_b.x, y = point_a.y - point_b.y, z = point_a.z - point_b.z}
local dir = mist.utils.getDir(vec, point_b)
local dist = mist.utils.get2DDist(point_a, point_b)
local bearing = mist.utils.round(mist.utils.toDegree(dir), 0)
local range_nm = mist.utils.round(mist.utils.metersToNM(dist), 0)
local range_ft = mist.utils.round(mist.utils.metersToFeet(dist), 0)
local br_string = ''
if range_nm > 0 then
br_string = bearing .. '° ' .. range_nm .. 'nm'
else
br_string = bearing .. '° ' .. range_ft .. 'ft'
end
return br_string
end
function RotorOpsPerks.findUnitsInVolume(args)
local foundUnits = {}
local volS = {
id = args.volume_type,
params = {
point = args.point,
radius = args.radius
}
}
local ifFound = function(foundObject, val)
foundUnits[#foundUnits + 1] = foundObject
end
world.searchObjects(Object.Category.UNIT, volS, ifFound)
return foundUnits
end
local handle = {}
function handle:onEvent(e)
if e.id == world.event.S_EVENT_BIRTH then
log('S_EVENT_BIRTH')
if not e.initiator then
log('BIRTH: initiator is not a unit. Returning.')
return
end
if e.initiator.getPlayerName == nil then
log('BIRTH: initiator is not a player. Returning.')
return
end
---- fat cow dynamic spawn hack: the following code relates to dynamic spawning at the fatcow site, which no longer works as intended
--
--local player_name = e.initiator:getPlayerName()
--local player_group_name = e.initiator:getGroup():getName()
--log('player_name is ' .. player_name) --nil error in single player
--log('player_group_name is ' .. player_group_name)
--
--
--for i, farp in pairs(RotorOpsPerks.fat_cow_farps) do
--
--
-- if farp.owner_player_name and player_name and farp.deployed then
-- log('farps owner_player_name is ' .. farp.owner_player_name)
-- if player_name == farp.owner_player_name and player_group_name ~= farp.chinook.groupName then
-- --if a farp owner has spawned into a slot other than the farp chinook slot, we'll spawn the static chinook
-- -- in the case where a player changes from spectator to a slot, we can't get the player from the S_EVENT_PLAYER_ENTER_UNIT event
-- -- we will try to change to 'deployed_empty' again, but it may not despawn the static chinook fast enough
-- RotorOpsPerks.changeFarpState(farp.index, 'deployed_empty', nil)
-- RotorOpsPerks.updateFarpAttribute(farp.index, 'monitored_unit', nil)
-- RotorOpsPerks.changeFarpState(farp.index, 'deployed_static', nil)
-- elseif player_name == farp.owner_player_name and player_group_name == farp.chinook.groupName then
-- --player spawned into the farp chinook slot
-- RotorOpsPerks.changeFarpState(farp.index, 'deployed_empty', nil)
-- RotorOpsPerks.updateFarpAttribute(farp.index, 'monitored_unit', e.initiator)
-- RotorOpsPerks.changeFarpState(farp.index, 'deployed_occupied', nil)
-- end
--
-- end
--end
end
if e.id == world.event.S_EVENT_PLAYER_ENTER_UNIT then
log('S_EVENT_PLAYER_ENTER_UNIT')
log('e table' .. mist.utils.tableShow(e, 'e'))
if not e.initiator then
log('ENTER: initiator is not a unit. Returning.')
return
end
end
if e.id == world.event.S_EVENT_PLAYER_LEAVE_UNIT then --getGroup will not work here
log('S_EVENT_PLAYER_LEAVE_UNIT')
log('e table' .. mist.utils.tableShow(e, 'e'))
if not e.initiator then
log('LEAVE: initiator is not a unit. Returning.')
return
end
if not e.initiator.getName then
log('LEAVE: cannot get initiator player name. Returning.')
return
end
local player_unit_name = e.initiator:getName()
log('player_unit_name is ' .. player_unit_name)
local player_name = e.initiator:getPlayerName()
log('player_name is ' .. player_name)
-- if the player is leaving the monitored_unit slot
for i, farp in pairs(RotorOpsPerks.fat_cow_farps) do
if farp.monitored_unit and player_unit_name == farp.monitored_unit:getName() then
RotorOpsPerks.changeFarpState(farp.index, 'deployed_static', nil)
end
end
---- fat cow dynamic spawn hack: the following code relates to dynamic spawning at the fatcow site, which no longer works as intended
--for i, farp in pairs(RotorOpsPerks.fat_cow_farps) do
--
-- --if the player is a farp owner, we'll despawn the static helicopter, even if we don't know what slot they'll choose
-- -- as the birth event does not let us despawn the static before the player enters the unit
--
-- if farp.owner_player_name and player_name then
-- if player_name == farp.owner_player_name then
-- RotorOpsPerks.changeFarpState(farp.index, 'deployed_static', nil)
-- end
-- end
--end
end
--if enemy unit destroyed
if e.id == world.event.S_EVENT_KILL then
if e.initiator and e.target then
if not Unit.getGroup(e.initiator) then
env.warning('KILL: initiator is not a unit')
return
end
if e.initiator:getCoalition() and e.target:getCoalition() and e.initiator:getCoalition() ~= e.target:getCoalition() then
debugMsg('KILL: initiator groupname: ' .. e.initiator:getGroup():getName())
local initiator_group_name = e.initiator:getGroup():getName()
-- if initiator is a player's dropped troops
local dropped_troops = RotorOpsPerks.troops[e.initiator:getGroup():getName()]
if dropped_troops then
if e.target:getDesc().category == Unit.Category.GROUND_UNIT == true then
if e.target:hasAttribute("Infantry") then
RotorOpsPerks.scorePoints(dropped_troops.player_group, RotorOpsPerks.points.dropped_troops_kill_inf, 'Your troops killed infantry!')
--else if target is armor
elseif e.target:hasAttribute("Tanks") then
RotorOpsPerks.scorePoints(dropped_troops.player_group, RotorOpsPerks.points.dropped_troops_kill_armor, 'Your troops killed armor!')
else
RotorOpsPerks.scorePoints(dropped_troops.player_group, RotorOpsPerks.points.dropped_troops_kill, 'Your troops killed a vehicle!')
end
elseif e.target:getDesc().category == Unit.Category.HELICOPTER == true then
RotorOpsPerks.scorePoints(dropped_troops.player_group, RotorOpsPerks.points.dropped_troops_kill_heli, 'Your troops killed a helicopter!')
elseif e.target:getDesc().category == Unit.Category.AIRPLANE == true then
RotorOpsPerks.scorePoints(dropped_troops.player_group, RotorOpsPerks.points.dropped_troops_kill_plane, 'Your troops killed a plane!')
end
end
--if the initiator is a player
if e.initiator:getPlayerName() then
--if target is a ground unit
if e.target:getDesc().category == Unit.Category.GROUND_UNIT == true then
if e.target:hasAttribute("Infantry") then
RotorOpsPerks.scorePoints(initiator_group_name, RotorOpsPerks.points.kill_inf, 'Killed infantry!')
elseif e.target:hasAttribute("Tanks") then
RotorOpsPerks.scorePoints(initiator_group_name, RotorOpsPerks.points.kill_armor, 'Killed armor!')
else
RotorOpsPerks.scorePoints(initiator_group_name, RotorOpsPerks.points.kill, 'Killed a vehicle!')
end
end
--if target is a helicopter
if e.target:getDesc().category == Unit.Category.HELICOPTER == true then
RotorOpsPerks.scorePoints(initiator_group_name, RotorOpsPerks.points.kill_heli, 'Killed a helicopter!')
end
--if target is a plane
if e.target:getDesc().category == Unit.Category.AIRPLANE == true then
RotorOpsPerks.scorePoints(initiator_group_name, RotorOpsPerks.points.kill_plane, 'Killed a plane!')
end
--if target is a ship
if e.target:getDesc().category == Unit.Category.SHIP == true then
RotorOpsPerks.scorePoints(initiator_group_name, RotorOpsPerks.points.kill_ship, 'Killed a ship!')
end
--CAS BONUS---
--we'll look for ground units in proximity to the player to apply a CAS bonus
local units_in_proximity = RotorOpsPerks.findUnitsInVolume({
volume_type = world.VolumeType.SPHERE,
point = e.initiator:getPoint(),
radius = 1852
})
local cas_bonus = false
for _, unit in pairs(units_in_proximity) do
--if we found friendly grund units near the player
if unit:getDesc().category == Unit.Category.GROUND_UNIT then
if unit:getCoalition() == e.initiator:getCoalition() then
cas_bonus = true
end
end
end
if cas_bonus then
RotorOpsPerks.scorePoints(e.initiator:getGroup():getName(), RotorOpsPerks.points.cas_bonus, '[CAS Bonus]')
end
--END CAS BONUS---
end
--end if the initiator is a player
end
end
end
end
world.addEventHandler(handle)
function RotorOpsPerks.registerCtldCallbacks()
if not ctld then
trigger.action.outText("CTLD Not Found", 10)
return
end
--if ctld.callbacks does not exist yet, loop until it does
if not ctld.callbacks then
timer.scheduleFunction(RotorOpsPerks.registerCtldCallbacks, nil, timer.getTime() + 1)
env.warning('CTLD callbacks not loaded yet, trying again in 1 second')
return
end
RotorOpsPerks.addFarpsToCtld()
ctld.addCallback(function(_args)
local action = _args.action
local unit = _args.unit
local picked_troops = _args.onboard
local dropped_troops = _args.unloaded
--log("ctld callback: ".. mist.utils.tableShow(_args))
if dropped_troops then
--log('dropped troops: ' .. mist.utils.tableShow(dropped_troops))
--log('dropped troops group name: ' .. dropped_troops:getName())
RotorOpsPerks.troops[dropped_troops:getName()] = {dropped_troops=dropped_troops:getName(), player_group=unit:getGroup():getName(), player_name=unit:getPlayerName(), player_unit=unit:getName(), side=unit:getGroup():getCoalition() , qty=#dropped_troops:getUnits()}
end
local playername = unit:getPlayerName()
if playername then
if action == "unload_troops_zone" or action == "dropped_troops" then
elseif action == "rearm" or action == "repair" then
RotorOpsPerks.scorePoints(unit:getGroup():getName(), RotorOpsPerks.points.rearm, 'Rearm/repair!')
elseif action == "unpack" then
RotorOpsPerks.scorePoints(unit:getGroup():getName(), RotorOpsPerks.points.unpack, 'Crates unpacked!')
end
end
end)
end
function RotorOpsPerks.addFarpsToCtld()
for i, farp in pairs(RotorOpsPerks.fat_cow_farps) do
if farp.tent then
log('Adding fat cow tent to ctld.buildFOBS for troop pickup: ' .. farp.tent:getName())
table.insert(ctld.builtFOBS, farp.tent:getName())
else
log('No tent found for FARP ' .. farp.index)
end
end
end
function RotorOpsPerks.monitorPlayers()
--This function, along with buildPlayer and updatePlayer, have been crafted through much trial and error in order to work with the 'nuances' of the DCS APIs in single player and multiplayer environments.
--If it's not broke, don't fix it. If it's broke... ED probably changed the behaviour of coalition.getPlayers, net.get_player_list, or net.get_player_info
timer.scheduleFunction(RotorOpsPerks.monitorPlayers, nil, timer.getTime() + 2)
-- GET PILOTS
local pilots = coalition.getPlayers(coalition.side.BLUE)
local red_pilots = coalition.getPlayers(coalition.side.RED)
-- add red pilots to pilots
for _, red_pilot in pairs(red_pilots) do
table.insert(pilots, red_pilot)
end
debugMsg('PILOTS: '.. mist.utils.tableShow(pilots))
for _, player in pairs(pilots) do
local player_group_name = player:getGroup():getName()
debugMsg('GET PILOTS Player group: ' .. player:getGroup():getName())
debugMsg('GET PILOTS PLAYER: ' .. mist.utils.tableShow(player))
--player info works in single player
local player_info = net.get_player_info(player)
if player_info then
debugMsg('GET PILOTS player info: '.. mist.utils.tableShow(player_info))
RotorOpsPerks.updatePlayer(player_info.ucid, player_group_name, player_info.name, player_info.slot)
else --player_info is nil in multiplayer, so we'll have to compile the data we need in multiple steps
--env.warning('GET PILOTS player_info for coalition.getPlayers is nil. Setting attributes to nil to be picked up by GET CREW METHODs')
RotorOpsPerks.buildPlayer(nil, player_group_name, nil, nil, player:getPlayerName()) --we don't have all the data we need to add to players yet
end
end
--GET CREW
local server_players = net.get_player_list() --empty in single player
debugMsg('GET CREW ALL PLAYERS: '.. mist.utils.tableShow(server_players))
for _, player in pairs(server_players) do
local player_info = net.get_player_info(player) --works with multicrew, but we need to find the group name
debugMsg('GET CREW player info:')
debugMsg(mist.utils.tableShow(player_info))
--find the group from slot relationship to pilots with the base slot
--client slot patterns are like 6_1, 6_2, etc where 6 is the host slot
--if the player slot is like 6_1, 6_2, etc then find the player with slot 6 and use that player's group name
if string.find(player_info.slot, '_') then --found a multicrew slot
local base_slot = string.sub(player_info.slot, 1, string.find(player_info.slot, '_')-1)
debugMsg('GET CREW found multicrew with base slot: '.. base_slot)
for _i, pilot in pairs(RotorOpsPerks.players) do
if pilot.slot == base_slot then
local player_group_name = pilot.groupName
debugMsg('GET CREW player group name: '.. player_group_name)
RotorOpsPerks.updatePlayer(player_info.ucid, player_group_name, player_info.name, player_info.slot)
end
end
else --we can't get the group name from here, so we'll have to compile the data we need in multiple steps
RotorOpsPerks.buildPlayer(player_info.ucid, nil, player_info.name, player_info.slot, player_info.name) --we don't have all the data we need to add to players yet
end
end
--IF PLAYER HAS DISCONNECTED FROM SERVER
if #server_players > 0 then
for identifier, player in pairs(RotorOpsPerks.players) do
--if the player is not in server_players, then they have disconnected
local player_still_connected = false
for _, server_player in pairs(server_players) do
if identifier == net.get_player_info(server_player).ucid then
player_still_connected = true
debugMsg(player.name .. ' is still connected')
end
end
if not player_still_connected then
log(player.name .. ' has disconnected')
RotorOpsPerks.processCallbacks({action='onPlayerDisconnected', player=player})
end
end
end
end
function RotorOpsPerks.processCallbacks(args)
log(mist.utils.tableShow(RotorOpsPerks.callbacks, 'RotorOpsPerks.callbacks'))
log('processCallbacks called with ' .. args.action)
--log(mist.utils.tableShow(args, 'args'))
for _, callback in pairs(RotorOpsPerks.callbacks) do
local success, response = pcall(function()
callback(args)
end)
if (not success) then
log(string.format("Callback Error: %s", response))
end
end
end
function RotorOpsPerks.addCallback(callback)
table.insert(RotorOpsPerks.callbacks, callback)
end
if mist.grimm_version then
log("GRIMM's version of MIST is loaded")
else
env.warning("ERROR: ROTOROPS PERKS REQUIRES A MODIFIED VERSION OF MIST TO WORK PROPERLY. PLEASE SEE THE SCRIPTS FOLDER IN THE ROTOROPS GITHUB REPO")
trigger.action.outText("ERROR: ROTOROPS PERKS REQUIRES A MODIFIED VERSION OF MIST TO WORK PROPERLY.", 30)
end
RotorOpsPerks.buildFatCowFarpTable()
log("Found " .. #RotorOpsPerks.fat_cow_farps .. " Fat Cow FARPs")
if #RotorOpsPerks.fat_cow_farps > 0 then
RotorOpsPerks.monitorFarps()
else
env.warning("NO FAT COW FARPS FOUND. PLEASE SEE THE ROTOROPS WIKI FOR INSTRUCTIONS ON HOW TO SET UP FAT COW FARPS")
trigger.action.outText("WARNING: NO FAT COW FARPS FOUND.", 30)
end
if not Group.getByName('FAT COW') then
env.warning("NO AI FAT COW HELICOPTER FOUND. PLEASE SEE THE ROTOROPS WIKI FOR INSTRUCTIONS ON HOW TO SET UP FAT COW FARPS")
trigger.action.outText("WARNING: NO AI FAT COW HELICOPTER FOUND.", 30)
end
RotorOpsPerks.registerCtldCallbacks()
-- start a 5 second timer to monitor players, to allow other scripts to load
timer.scheduleFunction(RotorOpsPerks.monitorPlayers, nil, timer.getTime() + 5)