* RotorOpsPerks, mist change, ice halo fix

* Update RotorOpsPerks.lua
- added new points conditions, revised cas bonus
- allow perks to be defined dynamically
- option to silence point scoring messages
- added red coalition support
- multicrew support!

Update MissionGenerator.exe for release
This commit is contained in:
spencershepard 2023-01-28 15:52:20 -08:00 committed by GitHub
parent f382a2e3cc
commit 76e2858c7b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 1037 additions and 22 deletions

View File

@ -4,6 +4,8 @@ on:
pull_request:
types:
- closed
branches:
- main
jobs:
if_merged:

View File

@ -549,6 +549,7 @@ class Window(QMainWindow, Ui_MainWindow):
"imports": self.imports_list,
}
# holds our generator options. We'll pull from the UI or the scenario config file
data = {
"objects": objects,
"credits": credits,
@ -591,6 +592,7 @@ class Window(QMainWindow, Ui_MainWindow):
"red_cap": self.scenario.getConfigValue("red_cap", default=True),
"blue_cap": self.scenario.getConfigValue("blue_cap", default=True),
"rotorops_server": self.scenario.getConfigValue("rotorops_server", default=False),
"perks": self.perks_checkBox.isChecked(),
}
logger.info("Generating mission with options:")

View File

@ -236,7 +236,7 @@ class Ui_MainWindow(object):
self.label_2.setFont(font)
self.label_2.setObjectName("label_2")
self.scenario_label_9 = QtWidgets.QLabel(self.centralwidget)
self.scenario_label_9.setGeometry(QtCore.QRect(480, 401, 251, 23))
self.scenario_label_9.setGeometry(QtCore.QRect(500, 401, 251, 23))
font = QtGui.QFont()
font.setPointSize(10)
self.scenario_label_9.setFont(font)
@ -258,21 +258,21 @@ class Ui_MainWindow(object):
self.tankers_checkBox.setChecked(True)
self.tankers_checkBox.setObjectName("tankers_checkBox")
self.voiceovers_checkBox = QtWidgets.QCheckBox(self.centralwidget)
self.voiceovers_checkBox.setGeometry(QtCore.QRect(500, 594, 171, 31))
self.voiceovers_checkBox.setGeometry(QtCore.QRect(500, 584, 171, 31))
font = QtGui.QFont()
font.setPointSize(9)
self.voiceovers_checkBox.setFont(font)
self.voiceovers_checkBox.setChecked(True)
self.voiceovers_checkBox.setObjectName("voiceovers_checkBox")
self.smoke_pickup_zone_checkBox = QtWidgets.QCheckBox(self.centralwidget)
self.smoke_pickup_zone_checkBox.setGeometry(QtCore.QRect(500, 541, 231, 20))
self.smoke_pickup_zone_checkBox.setGeometry(QtCore.QRect(500, 530, 231, 20))
font = QtGui.QFont()
font.setPointSize(9)
self.smoke_pickup_zone_checkBox.setFont(font)
self.smoke_pickup_zone_checkBox.setChecked(False)
self.smoke_pickup_zone_checkBox.setObjectName("smoke_pickup_zone_checkBox")
self.game_status_checkBox = QtWidgets.QCheckBox(self.centralwidget)
self.game_status_checkBox.setGeometry(QtCore.QRect(500, 570, 221, 21))
self.game_status_checkBox.setGeometry(QtCore.QRect(500, 560, 221, 21))
font = QtGui.QFont()
font.setPointSize(9)
self.game_status_checkBox.setFont(font)
@ -339,7 +339,7 @@ class Ui_MainWindow(object):
self.generateButton.setStyleSheet("")
self.generateButton.setObjectName("generateButton")
self.farp_always = QtWidgets.QRadioButton(self.centralwidget)
self.farp_always.setGeometry(QtCore.QRect(500, 431, 261, 24))
self.farp_always.setGeometry(QtCore.QRect(520, 431, 261, 24))
font = QtGui.QFont()
font.setPointSize(9)
self.farp_always.setFont(font)
@ -348,14 +348,14 @@ class Ui_MainWindow(object):
self.farp_buttonGroup.setObjectName("farp_buttonGroup")
self.farp_buttonGroup.addButton(self.farp_always)
self.farp_never = QtWidgets.QRadioButton(self.centralwidget)
self.farp_never.setGeometry(QtCore.QRect(500, 491, 271, 24))
self.farp_never.setGeometry(QtCore.QRect(520, 491, 271, 24))
font = QtGui.QFont()
font.setPointSize(9)
self.farp_never.setFont(font)
self.farp_never.setObjectName("farp_never")
self.farp_buttonGroup.addButton(self.farp_never)
self.farp_gunits = QtWidgets.QRadioButton(self.centralwidget)
self.farp_gunits.setGeometry(QtCore.QRect(500, 460, 261, 24))
self.farp_gunits.setGeometry(QtCore.QRect(520, 460, 261, 24))
font = QtGui.QFont()
font.setPointSize(9)
self.farp_gunits.setFont(font)
@ -458,6 +458,13 @@ class Ui_MainWindow(object):
self.farp_spawn_checkBox.setChecked(False)
self.farp_spawn_checkBox.setTristate(False)
self.farp_spawn_checkBox.setObjectName("farp_spawn_checkBox")
self.perks_checkBox = QtWidgets.QCheckBox(self.centralwidget)
self.perks_checkBox.setGeometry(QtCore.QRect(500, 610, 171, 31))
font = QtGui.QFont()
font.setPointSize(9)
self.perks_checkBox.setFont(font)
self.perks_checkBox.setChecked(True)
self.perks_checkBox.setObjectName("perks_checkBox")
MainWindow.setCentralWidget(self.centralwidget)
self.menubar = QtWidgets.QMenuBar(MainWindow)
self.menubar.setGeometry(QtCore.QRect(0, 0, 1280, 29))
@ -655,6 +662,8 @@ class Ui_MainWindow(object):
self.time_comboBox.setStatusTip(_translate("MainWindow", "Mission start time of day. \'Default\' is the start time as defined by the mission template designer."))
self.farp_spawn_checkBox.setStatusTip(_translate("MainWindow", "Add helicopter slots where zone FARPs will be built. Helicopters will be empty fuel, requiring the FARP to be established to refuel and rearm."))
self.farp_spawn_checkBox.setText(_translate("MainWindow", "Spawns at zone FARPs"))
self.perks_checkBox.setStatusTip(_translate("MainWindow", "Adds a rewards system with points for kills, troop drops, etc. See the F10 menu to use Perks."))
self.perks_checkBox.setText(_translate("MainWindow", "Perks"))
self.menuMap.setTitle(_translate("MainWindow", "Map"))
self.menuFilter.setTitle(_translate("MainWindow", "Filter"))
self.menuPreferences.setTitle(_translate("MainWindow", "Preferences"))

View File

@ -619,7 +619,7 @@ p, li { white-space: pre-wrap; }
<widget class="QLabel" name="scenario_label_9">
<property name="geometry">
<rect>
<x>480</x>
<x>500</x>
<y>401</y>
<width>251</width>
<height>23</height>
@ -688,7 +688,7 @@ p, li { white-space: pre-wrap; }
<property name="geometry">
<rect>
<x>500</x>
<y>594</y>
<y>584</y>
<width>171</width>
<height>31</height>
</rect>
@ -712,7 +712,7 @@ p, li { white-space: pre-wrap; }
<property name="geometry">
<rect>
<x>500</x>
<y>541</y>
<y>530</y>
<width>231</width>
<height>20</height>
</rect>
@ -736,7 +736,7 @@ p, li { white-space: pre-wrap; }
<property name="geometry">
<rect>
<x>500</x>
<y>570</y>
<y>560</y>
<width>221</width>
<height>21</height>
</rect>
@ -946,7 +946,7 @@ p, li { white-space: pre-wrap; }
<widget class="QRadioButton" name="farp_always">
<property name="geometry">
<rect>
<x>500</x>
<x>520</x>
<y>431</y>
<width>261</width>
<height>24</height>
@ -970,7 +970,7 @@ p, li { white-space: pre-wrap; }
<widget class="QRadioButton" name="farp_never">
<property name="geometry">
<rect>
<x>500</x>
<x>520</x>
<y>491</y>
<width>271</width>
<height>24</height>
@ -994,7 +994,7 @@ p, li { white-space: pre-wrap; }
<widget class="QRadioButton" name="farp_gunits">
<property name="geometry">
<rect>
<x>500</x>
<x>520</x>
<y>460</y>
<width>261</width>
<height>24</height>
@ -1317,6 +1317,30 @@ p, li { white-space: pre-wrap; }
<bool>false</bool>
</property>
</widget>
<widget class="QCheckBox" name="perks_checkBox">
<property name="geometry">
<rect>
<x>500</x>
<y>610</y>
<width>171</width>
<height>31</height>
</rect>
</property>
<property name="font">
<font>
<pointsize>9</pointsize>
</font>
</property>
<property name="statusTip">
<string>Adds a rewards system with points for kills, troop drops, etc. See the F10 menu to use Perks.</string>
</property>
<property name="text">
<string>Perks</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</widget>
<widget class="QMenuBar" name="menubar">
<property name="geometry">

View File

@ -20,7 +20,8 @@ def triggerSetup(rops, options):
trig.actions.append(dcs.action.DoScriptFile(rops.scripts["Splash_Damage_2_0.lua"]))
trig.actions.append(dcs.action.DoScriptFile(rops.scripts["CTLD.lua"]))
trig.actions.append(dcs.action.DoScriptFile(rops.scripts["RotorOps.lua"]))
script = ""
if options["perks"]:
trig.actions.append(dcs.action.DoScriptFile(rops.scripts["RotorOpsPerks.lua"]))
script = ("--OPTIONS HERE!\n\n" +
"RotorOps.CTLD_crates = " + lb("crates") + "\n\n" +
"RotorOps.CTLD_sound_effects = true\n\n" +

View File

@ -348,6 +348,11 @@ class RotorOpsMission:
# Add AI Flights
self.addFlights(options, red_forces, blue_forces)
# Add source statics
if options["perks"]:
# fat cow farps require source objects to work (can't be dynamically inserted)
self.addSourceStatics(options)
# Set the Editor Map View
self.m.map.position = self.conflict_zones["ALPHA"].position
self.m.map.zoom = 100000
@ -365,6 +370,11 @@ class RotorOpsMission:
# set the weather and time
#pydcs bug fix
if self.m.weather.halo.preset == {}:
self.m.weather.halo.preset = dcs.weather.Halo.Preset.Auto
self.m.weather.halo.crystals = None
if options["random_weather"]:
# self.m.random_weather = True
max = len(dcs.cloud_presets.CLOUD_PRESETS) - 1
@ -813,6 +823,36 @@ class RotorOpsMission:
f_cap_spawn_point = primary_f_airport.position.point_from_heading(e_airport_heading + 180, 100000)
self.m.triggers.add_triggerzone(f_cap_spawn_point, 30000, hidden=True, name="BLUE_CAP_SPAWN")
# Fat Cow
if True:
helo_type = dcs.helicopters.CH_47D
name = "FAT COW"
airport = self.getParking(primary_f_airport, helo_type, friendly_airports, 1)
if carrier:
afg = self.m.flight_group_from_unit(
combinedJointTaskForcesBlue,
name,
helo_type,
carrier,
start_type=dcs.mission.StartType.Cold,
group_size=1)
afg.set_skill(dcs.unit.Skill.Excellent)
afg.late_activation = True
elif airport:
afg = self.m.flight_group_from_airport(
combinedJointTaskForcesBlue,
name,
helo_type,
airport=airport,
start_type=dcs.mission.StartType.Cold,
group_size=1)
afg.set_skill(dcs.unit.Skill.Excellent)
afg.late_activation = True
if options["f_awacs"]:
awacs_name = "AWACS"
awacs_freq = 266
@ -966,8 +1006,6 @@ class RotorOpsMission:
group_size=group_size)
zone_attack(afg, airport)
else:
return
if source_helo and afg:
for unit in afg.units:
@ -1202,3 +1240,48 @@ class RotorOpsMission:
def addMods(self):
dcs.helicopters.helicopter_map["UH-60L"] = aircraftMods.UH_60L
self.m.country(jtf_blue).helicopters.append(aircraftMods.UH_60L)
def addSourceStatics(self, options):
insert_point = None
if self.m.terrain.name == "Caucasus":
insert_point = dcs.mapping.Point(-500000, 200000, dcs.terrain.Caucasus)
elif self.m.terrain.name == "Falklands":
insert_point = dcs.mapping.Point(216000, -990000, dcs.terrain.Falklands)
elif self.m.terrain.name == "MarianaIslands":
insert_point = dcs.mapping.Point(686200, 71200, dcs.terrain.MarianaIslands)
elif self.m.terrain.name == "PersianGulf":
insert_point = dcs.mapping.Point(-350000, -800000, dcs.terrain.PersianGulf)
elif self.m.terrain.name == "Nevada":
insert_point = dcs.mapping.Point(-140000, -300000, dcs.terrain.Nevada)
elif self.m.terrain.name == "Syria":
insert_point = dcs.mapping.Point(235000, -440000, dcs.terrain.Syria)
if insert_point:
for i in range(1, 4):
fuel = self.m.static_group(name="FAT COW FUEL " + str(i),
_type=dcs.statics.Fortification.FARP_Fuel_Depot,
country=self.m.country(jtf_blue),
position=insert_point.random_point_within(1000, 1000),
heading=0,
hidden=True,)
fuel.units[0].name = "FAT COW FUEL " + str(i)
ammo = self.m.static_group(name="FAT COW AMMO " + str(i),
_type=dcs.statics.Fortification.FARP_Ammo_Dump_Coating,
country=self.m.country(jtf_blue),
position=insert_point.random_point_within(1000, 1000),
heading=0,
hidden=True, )
ammo.units[0].name = "FAT COW AMMO " + str(i)
tent = self.m.static_group(name="FAT COW TENT " + str(i),
_type=dcs.statics.Fortification.FARP_Tent,
country=self.m.country(jtf_blue),
position=insert_point.random_point_within(1000, 1000),
heading=0,
hidden=True, )
tent.units[0].name = "FAT COW TENT " + str(i)
self.m.farp(self.m.country(jtf_blue), "FAT COW FARP " + str(i),
insert_point.random_point_within(1000, 1000), hidden=True, dead=False, farp_type=dcs.unit.InvisibleFARP)

Binary file not shown.

Binary file not shown.

View File

@ -1,7 +1,7 @@
# ROTOROPS VERSION
maj_version = 1
minor_version = 3
patch_version = 2
minor_version = 4
patch_version = 1
version_url = 'https://dcs-helicopters.com/app-updates/versioncheck.yaml'

Binary file not shown.

BIN
assets/Header_1.bmp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

BIN
assets/Header_2.bmp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

BIN
assets/Wizard_1.bmp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

BIN
assets/Wizard_2.bmp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

BIN
assets/ancient.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

BIN
assets/modern.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

View File

@ -1,5 +1,5 @@
RotorOps = {}
RotorOps.version = "1.3.3"
RotorOps.version = "1.3.4"
local debug = true
@ -1517,6 +1517,10 @@ end
--make some changes to the CTLD script/settings
function RotorOps.setupCTLD()
if not ctld then
trigger.action.outText("ERROR: CTLD Not Loaded!!", 90)
return
end
if type(ctld.pickupZones[1][2]) == "number" then --ctld converts its string table to integer on load, so we'll see if that's happened already
trigger.action.outText("ERROR: CTLD Loaded Too Soon!!", 90)
return

890
scripts/RotorOpsPerks.lua Normal file
View File

@ -0,0 +1,890 @@
--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)
RotorOpsPerks = {}
RotorOpsPerks.version = "1.3"
trigger.action.outText('ROTOROPS PERKS STARTED: '..RotorOpsPerks.version, 10)
RotorOpsPerks.perks = {}
RotorOpsPerks.players = {}
RotorOpsPerks.players_temp = {}
RotorOpsPerks.troops = {} --by group name
---- 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 = true
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 were in proximity of your troops killing something
dropped_troops_kill_inf=5, --your troops killed infantry
dropped_troops_kill=10, --your troops killed a vehicle
dropped_troops_kill_armor=15, --your troops killed armor
rearm=10, --ctld rearm/repair of ground units
unpack=10, --ctld unpack of ground units
}
---- END OPTIONS ----
local function debugMsg(msg)
if RotorOpsPerks.debug then
env.info("ROTOROPS PERKS:")
env.info(msg)
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.
function requestFatCowPerk(args)
local index = RotorOpsPerks.perks.fatcow.used + 1
RotorOpsPerks.spawnFatCow(args.target_point, index)
end
RotorOpsPerks.perks["fatcow"] = {
perk_name='fatcow',
display_name='FatCow FARP',
cost=100,
cooldown=60,
max_per_player=1,
max_per_mission=4, --for fatcow, you will want to ensure that you have this many sets of FARP statics
at_mark=true,
at_position=true,
enabled=true,
sides={0,1,2},
last_used=0,
used=0,
action_function=requestFatCowPerk
}
---- INSTANT STRIKE PERK ----
function requestStrikePerk(args)
--explosion at dest_point after 10 seconds
timer.scheduleFunction(function()
trigger.action.explosion(args.target_point, 1000)
end, nil, timer.getTime() + 10)
end
RotorOpsPerks.perks["strike"] = {
perk_name='strike',
display_name='Instant Strike',
cost=100,
cooldown=60,
max_per_player=2,
max_per_mission=3,
at_mark=true,
at_position=false,
enabled=true,
sides={0,1,2},
last_used=0,
used=0,
action_function=requestStrikePerk
}
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)
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
return false
end
--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 = 0
else
player.points = player.points - points_to_subtract
remainder = 0
end
end
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
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
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
--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
env.warning('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] = {
name=name,
slot=slot,
points = RotorOpsPerks.points.player_default,
groupId = groupId,
groupName = groupName,
side = side,
menu = {},
perks_used = {},
}
env.warning('ADDED ' .. identifier .. ' TO PLAYERS TABLE')
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
--update an existing player
elseif RotorOpsPerks.players[identifier].groupId ~= groupId then
env.warning('UPDATING ' .. identifier .. ' TO GROUP NAME: ' .. groupName)
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)
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 function addPerkCommand(groupId, groupName, perk, path, vars)
-- missionCommands.addCommandForGroup(groupId, 'Request '.. perk.display_name .. ' at ' .. vars.target, path , RotorOpsPerks.requestPerk, {player_group_name=groupName, perk_name=perk_name, target=vars.target})
-- end
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
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 then
if perk.at_mark then
--addPerkCommand(groupId, groupName, perk, menu_root, {target='mark'})
missionCommands.addCommandForGroup(groupId, 'Request '.. 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, 'Request '.. perk.display_name .. ' at current position', menu_root , RotorOpsPerks.requestPerk, {player_group_name=groupName, perk_name=perk.perk_name, target='position'})
end
end
end
end
function teleportStatic(source_name, dest_point)
debugMsg('teleportStatic: ' .. source_name)
local source = StaticObject.getByName(source_name)
if not source then
env.info('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
env.info('teleportStatic: ' .. source_name .. ' success')
else
env.info('teleportStatic: ' .. source_name .. ' failed')
end
end
function RotorOpsPerks.spawnFatCowFarpObjects(pt_x, pt_y, index)
env.info('spawnFatCowFarpObjects called. Looking for static group names ending in ' .. index)
local dest_point = mist.utils.makeVec3GL({x = pt_x, y = pt_y})
trigger.action.smoke(dest_point, 2)
trigger.action.outText('FatCow FARP deploying...get clear of the landing zone!', 20)
timer.scheduleFunction(function()
local fuel_point = {x = dest_point.x + 35, y = dest_point.y, z = dest_point.z}
teleportStatic('FAT COW FUEL ' .. index, fuel_point)
teleportStatic('FAT COW TENT ' .. index, fuel_point)
local ammo_point = {x = dest_point.x - 35, y = dest_point.y, z = dest_point.z}
teleportStatic('FAT COW AMMO ' .. index, ammo_point)
end, nil, timer.getTime() + 235)
end
function RotorOpsPerks.spawnFatCow(dest_point, index)
local fatcow_name = 'FAT COW'
local source_farp_name = 'FAT COW FARP ' .. index
env.info('spawnFatCow 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(approach_point), 1)
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
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.spawnFatCowFarpObjects(]] .. dest_point.x ..[[,]] .. dest_point.y .. [[,]] .. index .. [[)
env.info('FatCow FARP deployment scheduled')
]]
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)
env.info('requestPerk called for ' .. args.perk_name)
--env.info(mist.utils.tableShow(args, 'args'))
local player_group = Group.getByName(args.player_group_name)
local player_unit = player_group:getUnits()[1]
local player_pos = player_unit:getPoint()
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]
--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'))
--env.info('player group' .. mist.utils.tableShow(player_group, 'player_group'))
--env.info('player' .. mist.utils.tableShow(player_unit, 'player_unit'))
local perk_name_matches = false
--determine if mark name matches the perk name
local mark_name = mark.text
--remove whitespace and new line from mark name
mark_name = mark_name:gsub("%s+", "")
mark_name = mark_name:gsub("%n+", "")
if mark_name == args.perk_name then
perk_name_matches = true
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
debugMsg(mist.utils.tableShow(mist.DBs.markList, 'markList'))
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_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 > 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
--spend points
if RotorOpsPerks.spendPoints(args.player_group_name, perk.cost)
then
env.info(args.player_group_name.. ' spent ' .. perk.cost .. ' points for ' .. args.perk_name)
else
env.info(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
--add some useful data to pass to perk action
args.target_point = target_point
args.player_group = player_group
args.player_unit = player_unit
--call perk action
perk.action_function(args)
--update last_used
perk.last_used = timer.getTime()
perk.used = perk.used + 1
--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
env.info(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 enemy unit destroyed
if e.id == world.event.S_EVENT_KILL then
if e.initiator and e.target then
if 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
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
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))
if dropped_troops then
--env.info('dropped troops: ' .. mist.utils.tableShow(dropped_troops))
--env.info('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.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
env.warning('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 players = net.get_player_list() --empty in single player
env.warning('GET CREW ALL PLAYERS: '.. mist.utils.tableShow(players))
for _, player in pairs(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
end
RotorOpsPerks.monitorPlayers()
RotorOpsPerks.registerCtldCallbacks()

View File

@ -7371,9 +7371,9 @@ do
usedMarks[e.idx] = e.idx
if not mist.DBs.markList[e.idx] then
--log:info('create maker DB: $1', e.idx)
mist.DBs.markList[e.idx] = {time = e.time, pos = e.pos, groupId = e.groupId, mType = 'panel', text = e.text, markId = e.idx, coalition = e.coalition}
mist.DBs.markList[e.idx] = {time = e.time, pos = e.pos, groupId = e.groupId, mType = 'panel', text = e.text, markId = e.idx, coalition = e.coalition, initiator = e.initiator}
if e.unit then
mist.DBs.markList[e.idx].unit = e.initiaor:getName()
mist.DBs.markList[e.idx].unit = e.initiator:getName()
end
--log:info(mist.marker.list[e.idx])
end