-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
a34d1e9
commit 0211050
Showing
7 changed files
with
800 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
<!-- markdownlint-disable MD041 --> | ||
## About | ||
|
||
Gremlin Waves is a reinforcements script for your missions. Running low on RedFor, but not ready for the fun to end? Call in reinforcements with Gremlin Waves! |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
<!-- markdownlint-disable MD041 --> | ||
### Setup | ||
|
||
#### Configuration | ||
|
||
```lua,editable | ||
Waves:setup({ | ||
adminPilotNames = { | ||
'Steve Jobs', | ||
'Linus Torvalds', | ||
'Bill Gates', | ||
}, | ||
waves = { | ||
['Wave 2'] = { | ||
trigger = { | ||
type = 'time', | ||
value = 12600, -- 3.5 hours | ||
}, | ||
groups = { | ||
['F-14B'] = { | ||
category = Group.Category.AIRPLANE, | ||
country = country.USA, | ||
zone = 'Reinforcement Staging', | ||
scatter = 15, | ||
orders = {}, | ||
units = { | ||
['F-14B'] = 3, | ||
}, | ||
}, | ||
['Ground A'] = { | ||
category = Group.Category.GROUND, | ||
country = country.USA, | ||
zone = 'Reinforcement Staging', | ||
scatter = 5, | ||
orders = {}, | ||
units = { | ||
['Infantry'] = 4, | ||
} | ||
}, | ||
['Ground B'] = { | ||
category = Group.Category.GROUND, | ||
country = country.USA, | ||
zone = 'Reinforcement Staging', | ||
scatter = 5, | ||
orders = {}, | ||
units = { | ||
['RPG'] = 1, | ||
['Infantry'] = 3, | ||
['JTAC'] = 1, | ||
} | ||
}, | ||
}, | ||
}, | ||
}, | ||
}) | ||
``` | ||
|
||
- `adminPilotNames` table | ||
- list of pilots who should see the menu | ||
- `waves` table | ||
- collection of reinforcement waves for this mission | ||
- wave | ||
- `trigger` table | ||
- `type` one of `time`, `event`, or `menu` | ||
- `value` a time, event id / filter, or menu item text | ||
- `groups` table | ||
- list of groups to spawn | ||
- group | ||
- `category` a member of `Groups.Category` indicating the group's category | ||
- `country` a `country` id | ||
- `zone` where to spawn the group | ||
- `scatter` how far apart, in meters, to scatter units at spawn | ||
- `orders` table | ||
- a list of [DCS AI tasks](https://www.digitalcombatsimulator.com/en/support/faq/1267/#3307680) for the group to perform | ||
- `units` table | ||
- key: the unit type to spawn | ||
- value: how many to spawn in the group |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
<!-- markdownlint-disable MD041 --> | ||
### Usage | ||
|
||
Gremlin Waves is really simple to use. Configure it once (see [the Setup page](./setup.md)), and it handles the rest! |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,279 @@ | ||
Waves = { | ||
Id = 'Gremlin Waves', | ||
Version = '202403.01', | ||
|
||
config = { | ||
adminPilotNames = {}, | ||
waves = {} | ||
}, | ||
|
||
_state = { | ||
alreadyInitialized = false, | ||
paused = false, | ||
}, | ||
_internal = {}, | ||
} | ||
|
||
Waves._internal.menu = { | ||
{ | ||
text = 'Pause Waves', | ||
func = Waves._internal.pause, | ||
args = {}, | ||
when = { | ||
func = function() return Waves._state.paused end, | ||
args = {}, | ||
comp = 'equal', | ||
value = true | ||
}, | ||
}, | ||
{ | ||
text = 'Resume Waves', | ||
func = Waves._internal.unpause, | ||
args = {}, | ||
when = { | ||
func = function() return Waves._state.paused end, | ||
args = {}, | ||
comp = 'inequal', | ||
value = true | ||
}, | ||
}, | ||
} | ||
|
||
Waves._internal.handlers = { | ||
eventTriggers = { | ||
event = -1, | ||
fn = function(_event) | ||
if not Waves._state.paused then | ||
Gremlin.log.trace(Waves.Id, string.format('Checking Event Against Countdowns : %s', Gremlin.events.idToName[_event.id])) | ||
|
||
for _name, _wave in pairs(Waves.config.waves) do | ||
if _wave.trigger.type == 'event' | ||
and ( | ||
_wave.trigger.value.id == _event.id | ||
or _wave.trigger.value.id == -1 | ||
) | ||
and _wave.trigger.value.filter(_event) | ||
then | ||
Waves.config.waves[_name].trigger.fired = true | ||
Waves._internal.spawnWave(_name, _wave) | ||
end | ||
end | ||
end | ||
end | ||
}, | ||
} | ||
|
||
Waves._internal.spawnWave = function(_name, _wave) | ||
Gremlin.log.trace(Waves.Id, string.format('Started Spawning Wave : %s', _name)) | ||
|
||
for _groupName, _groupData in pairs(_wave.groups) do | ||
local _spawnZone = trigger.misc.getZone(_groupData.zone) | ||
|
||
if _spawnZone == nil then | ||
Gremlin.log.error(Waves.Id, "Can't find zone called " .. _groupData.zone) | ||
return | ||
end | ||
|
||
local _pos2 = { | ||
x = _spawnZone.point.x, | ||
y = _spawnZone.point.z | ||
} | ||
local _alt = land.getHeight(_pos2) | ||
local _pos3 = { | ||
x = _pos2.x, | ||
y = _alt, | ||
z = _pos2.y | ||
} | ||
---@diagnostic disable-next-line: deprecated | ||
local _angle = math.atan2(_pos3.z, _pos3.x) | ||
|
||
local _units = {} | ||
for _unitType, _unitCount in pairs(_groupData.units) do | ||
for i = 1, _unitCount do | ||
local _xOffset = math.cos(_angle) * math.random(_groupData.scatter) | ||
local _yOffset = math.sin(_angle) * math.random(_groupData.scatter) | ||
|
||
table.insert(_units, { | ||
type = _unitType, | ||
name = string.format('%s: %s: %s %i', _name, _groupName, _unitType, i), | ||
skill = 'Excellent', | ||
playerCanDrive = false, | ||
point = { | ||
x = _pos3.x + _xOffset, | ||
y = _pos3.z + _yOffset, | ||
}, | ||
heading = _angle | ||
}) | ||
end | ||
end | ||
|
||
local _group = mist.dynAdd({ | ||
visible = true, | ||
hidden = false, | ||
units = _units, | ||
name = string.format('%s: %s', _name, _groupName), | ||
category = _groupData.category, | ||
country = _groupData.country, | ||
x = _pos3.x, | ||
y = _pos3.z, | ||
}) | ||
|
||
if _group ~= nil then | ||
trigger.action.activateGroup(_group.name) | ||
mist.teleportInZone(_group.name, _groupData.zone, true, _groupData.scatter) | ||
|
||
local _controller = Group.getByName(_group.name):getController() | ||
if _controller ~= nil then | ||
for _, _task in ipairs(_groupData.orders) do | ||
_controller.pushTask(_task) | ||
end | ||
end | ||
end | ||
end | ||
|
||
Gremlin.log.trace(Waves.Id, string.format('Finished Spawning Wave : %s', _name)) | ||
end | ||
|
||
Waves._internal.getAdminUnits = function() | ||
Gremlin.log.trace(Waves.Id, string.format('Scanning For Connected Admins')) | ||
|
||
local _units = {} | ||
|
||
for _name, _ in pairs(mist.DBs.unitsByName) do | ||
local _unit = Unit.getByName(_name) | ||
|
||
if _unit ~= nil and _unit.isExist ~= nil and _unit:isExist() and _unit.getPlayerName ~= nil then | ||
local _pilot = _unit:getPlayerName() | ||
if _pilot ~= nil and _pilot ~= '' then | ||
for _, _adminName in pairs(Waves.config.adminPilotNames) do | ||
if _adminName == _pilot then | ||
table.insert(_units, _name) | ||
break | ||
end | ||
end | ||
end | ||
end | ||
end | ||
|
||
Gremlin.log.trace(Waves.Id, string.format('Scan Complete : Found %i Active Admin Units', Gremlin.utils.countTableEntries(_units))) | ||
|
||
return _units | ||
end | ||
|
||
Waves._internal.initMenu = function() | ||
Gremlin.log.trace(Waves.Id, string.format('Building Menu')) | ||
|
||
for _name, _wave in pairs(Waves.config.waves) do | ||
if _wave.trigger.type == 'menu' then | ||
table.insert(Waves._internal.menu, { | ||
text = _wave.trigger.value or ('Send In Reinforcements : ' .. _name), | ||
func = Waves._internal.menuWave, | ||
args = { _name }, | ||
when = { | ||
func = function(_name) | ||
return not Waves._state.paused and not Waves.config.waves[_name].trigger.fired | ||
end, | ||
args = { _name }, | ||
comp = 'equal', | ||
value = true, | ||
} | ||
}) | ||
end | ||
end | ||
|
||
Gremlin.log.trace(Waves.Id, string.format('Menu Ready')) | ||
end | ||
|
||
Waves._internal.updateF10 = function() | ||
Gremlin.log.trace(Waves.Id, string.format('Updating Menu')) | ||
|
||
timer.scheduleFunction(Waves._internal.updateF10, nil, timer.getTime() + 5) | ||
|
||
Gremlin.menu.updateF10(Waves.Id, Waves._internal.menu, Waves._internal.getAdminUnits()) | ||
end | ||
|
||
Waves._internal.menuWave = function(_name) | ||
if not Waves._state.paused and not Waves.config.waves[_name].trigger.fired then | ||
Gremlin.log.trace(Waves.Id, string.format('Caling In Reinforcements : %s', _name)) | ||
|
||
Waves.config.waves[_name].trigger.fired = true | ||
Waves._internal.spawnWave(_name, Waves.config.waves[_name]) | ||
|
||
Gremlin.log.trace(Waves.Id, string.format('Reinforcements En Route : %s', _name)) | ||
end | ||
end | ||
|
||
Waves._internal.timeWave = function() | ||
timer.scheduleFunction(Waves._internal.timeWave, nil, timer.getTime() + 1) | ||
|
||
if not Waves._state.paused then | ||
Gremlin.log.trace(Waves.Id, string.format('Checking On Next Wave')) | ||
|
||
for _name, _wave in pairs(Waves.config.waves) do | ||
if _wave.trigger.type == 'time' and not _wave.trigger.fired and _wave.trigger.value <= timer.getTime() then | ||
Waves.config.waves[_name].trigger.fired = true | ||
Waves._internal.spawnWave(_name, _wave) | ||
end | ||
end | ||
end | ||
end | ||
|
||
Waves._internal.pause = function() | ||
Gremlin.log.trace(Waves.Id, string.format('Pausing Reinforcement Waves')) | ||
|
||
Waves._state.paused = true | ||
Gremlin.menu.updateF10(Waves.Id, Waves._internal.menu, Waves._internal.getAdminUnits()) | ||
end | ||
|
||
Waves._internal.unpause = function() | ||
Gremlin.log.trace(Waves.Id, string.format('Releasing Pending Reinforcement Waves')) | ||
|
||
Waves._state.paused = false | ||
Gremlin.menu.updateF10(Waves.Id, Waves._internal.menu, Waves._internal.getAdminUnits()) | ||
end | ||
|
||
function Waves:setup(config) | ||
if config == nil then | ||
config = {} | ||
end | ||
|
||
assert(Gremlin ~= nil, | ||
'\n\n** HEY MISSION-DESIGNER! **\n\nGremlin Script Tools has not been loaded!\n\nMake sure Gremlin Script Tools is loaded *before* running this script!\n') | ||
|
||
if not Gremlin.alreadyInitialized or config.forceReload then | ||
Gremlin:setup(config) | ||
end | ||
|
||
if Waves._state.alreadyInitialized and not config.forceReload then | ||
Gremlin.log.info(Waves.Id, string.format('Bypassing initialization because Waves._state.alreadyInitialized = true')) | ||
return | ||
end | ||
|
||
Gremlin.log.info(Waves.Id, string.format('Starting setup of %s version %s!', Waves.Id, Waves.Version)) | ||
|
||
-- start configuration | ||
if not Waves._state.alreadyInitialized or config.forceReload then | ||
Waves.config.adminPilotNames = config.adminPilotNames or {} | ||
Waves.config.waves = config.waves or {} | ||
|
||
Gremlin.log.debug(Waves.Id, string.format('Configuration Loaded : %s', mist.utils.tableShowSorted(Waves.config))) | ||
end | ||
-- end configuration | ||
|
||
Waves._internal.initMenu() | ||
|
||
timer.scheduleFunction(function() | ||
timer.scheduleFunction(Waves._internal.timeWave, nil, timer.getTime() + 1) | ||
timer.scheduleFunction(Waves._internal.updateF10, nil, timer.getTime() + 1) | ||
end, nil, timer.getTime() + 1) | ||
|
||
for _name, _def in pairs(Waves._internal.handlers) do | ||
Waves._internal.handlers[_name].id = Gremlin.events.on(_def.event, _def.fn) | ||
|
||
Gremlin.log.debug(Waves.Id, string.format('Registered %s event handler', _name)) | ||
end | ||
|
||
Gremlin.log.info(Waves.Id, string.format('Finished setting up %s version %s!', Waves.Id, Waves.Version)) | ||
|
||
Waves._state.alreadyInitialized = true | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.