Skip to content

Commit

Permalink
Move menu system to Gremlin
Browse files Browse the repository at this point in the history
  • Loading branch information
danhunsaker committed Mar 24, 2024
1 parent 9320372 commit 3a547af
Show file tree
Hide file tree
Showing 7 changed files with 341 additions and 123 deletions.
5 changes: 3 additions & 2 deletions docs/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
- [Functions](./gremlin/functions.md)
- [Events](./gremlin/functions/events.md)
- [Log](./gremlin/functions/log.md)
- [Menu](./gremlin/functions/menu.md)
- [Utils](./gremlin/functions/utils.md)

# Gremlin Evac
Expand All @@ -19,9 +20,9 @@
- [Setup](./evac/setup.md)
- [Usage](./evac/usage.md)
- [Functions](./evac/functions.md)
- [Zones](./evac/functions/zones.md)
- [Units](./evac/functions/units.md)
- [Groups](./evac/functions/groups.md)
- [Units](./evac/functions/units.md)
- [Zones](./evac/functions/zones.md)

# Book-Keeping

Expand Down
51 changes: 51 additions & 0 deletions docs/gremlin/functions/menu.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<!-- markdownlint-disable MD041 -->
### Menu

Only one function in here for the moment! But it's pretty powerful, so the configuration can be a bit complex.

#### `Gremlin.menu.updateF10({ toolId, commands, getForUnits })`

> **WARNING - DO NOT CALL THIS FUNCTION MORE THAN ONCE PER SCRIPT!**
> This is a _great_ way to confuse the poor thing and break your missions.
Registers a set of menu commands to be updated automatically (every 10 seconds) by Gremlin itself. These commands will be grouped under a parent menu named after the tool itself - `Gremlin Evac` for `evac.lua`, `Gremlin Waves` for `waves.lua`, etc. Here's an example:

```lua,editable
Gremlin.menu.updateF10({ MyScript.Id, {
{
text = 'My Cool Unit Command',
func = MyScript.commands.myCoolUnitFunc,
args = { '{unit}:name' },
when = true,
},
{
text = 'My Cool Group Command',
func = MyScript.commands.myCoolGroupFunc,
args = { '{group}:name' },
when = {
func = MyScript.unit.inTheZone,
args = { '{unit}:name' },
comp = 'equal',
value = true,
},
},
}, function()
return MyScript._state.pilotedUnits
end })
```

- `toolId` - should be a string identifying your script by name for the menu. Should be the same value used for Gremlin's logging methods.

- `commands` - an array of commands to register
- `text` - a string or a function that returns a string; used for the menu text
- `func` - the function to call when this menu command is selected
- `args` - any arguments to pass to `func` (and `text`, if it's a function)
- `{unit}:name` - placeholder value for the name of the unit whose menu is being updated
- `{group}:name` - placeholder value for the name of the group whose menu is being updated
- `when` - indicates when a command should be visible in the menu; can either be a boolean or a table
- `func` - the function to call when deciding whether to add a command to the menu
- `args` - the arguments to pass to `func`; uses the same placeholders as `args` a level up
- `comp` - one of `equal` or `inequal` (at the moment); denotes what kind of comparison to make between `func`'s return value and `value`
- `value` - the value to compare against `func`'s result

- `getForUnits` - a function that returns a table of units to add menus to; this is a function so that your list can change over time
77 changes: 10 additions & 67 deletions src/evac.lua
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,6 @@ Evac = {
['2B11'] = 0,
JTAC = 0
}},
menuAdded = {},
smoke = {},
spawns = {
alreadySpawned = {{
Expand Down Expand Up @@ -1194,71 +1193,6 @@ Evac._internal.zones = {

-- Menu
Evac._internal.menu = {
addToF10 = function()
Gremlin.log.trace(Evac.Id, string.format('Updating F10 Menu For %i Units', Gremlin.utils.countTableEntries(Evac._state.extractionUnits)))

timer.scheduleFunction(Evac._internal.menu.addToF10, nil, timer.getTime() + 10)

for _unitName, _extractorList in pairs(Evac._state.extractionUnits) do
local _unit = _extractorList[0] or Unit.getByName(_unitName)
if _unit ~= nil and _unit:isExist() then
if _extractorList[0] == nil then
Evac._state.extractionUnits[_unitName][0] = _unit
end

local _groupId = _unit:getGroup():getID()
local _groupName = _unit:getGroup():getName()

local _rootPath
if Evac._state.menuAdded[_groupId] == nil then
_rootPath = missionCommands.addSubMenuForGroup(_groupId, 'Gremlin Evac')
Evac._state.menuAdded[_groupId] = { root = _rootPath }
Gremlin.log.trace(Evac.Id, string.format('Added Root Menu'))
else
_rootPath = Evac._state.menuAdded[_groupId].root
end

for _cmdIdx, _command in pairs(Evac._internal.menu.commands) do
local _when = false
if type(_command.when) == 'boolean' then
_when = _command.when
elseif type(_command.when) == 'table' then
---@diagnostic disable-next-line: undefined-field
local _whenArgs = Gremlin.utils.parseFuncArgs(_command.when.args, {
unit = _unit,
group = _unit:getGroup()
})

---@diagnostic disable-next-line: undefined-field
if _command.when.func(table.unpack(_whenArgs)) == _command.when.value and _command.when.comp == 'equal' then
_when = true
---@diagnostic disable-next-line: undefined-field
elseif _command.when.func(table.unpack(_whenArgs)) ~= _command.when.value and _command.when.comp == 'inequal' then
_when = true
end
end

if Evac._state.menuAdded[_groupId][_cmdIdx] ~= nil then
missionCommands.removeItemForGroup(_groupId, Evac._state.menuAdded[_groupId][_cmdIdx])
end

local _args = Gremlin.utils.parseFuncArgs(_command.args, {
unit = _unit,
group = _unit:getGroup()
})
if _when then
if type(_command.text) == "string" then
Evac._state.menuAdded[_groupId][_cmdIdx] = missionCommands.addCommandForGroup(_groupId, _command.text, _rootPath, function(_args) _command.func(table.unpack(_args)) end, _args)
Gremlin.log.trace(Evac.Id, string.format('Added Menu Item To Group %s (%s) : "%s"', _groupName, _unitName, _command.text))
else
Evac._state.menuAdded[_groupId][_cmdIdx] = missionCommands.addCommandForGroup(_groupId, _command.text(table.unpack(_args)), _rootPath, function(_args) _command.func(table.unpack(_args)) end, _args)
Gremlin.log.trace(Evac.Id, string.format('Added Menu Item To Group %s (%s) : "%s"', _groupName, _unitName, _command.text(table.unpack(_args))))
end
end
end
end
end
end,
commands = {{
text = 'Scan For Evacuation Beacons',
func = Evac.units.findEvacuees,
Expand Down Expand Up @@ -1337,6 +1271,15 @@ Evac._internal.utils = {
end
end
end,
extractionUnitsToMenuUnits = function()
local _menuUnits = {}

for _unitName, _onBoard in pairs(Evac._state.extractionUnits) do
_menuUnits[_unitName] = _onBoard[0]
end

return _menuUnits
end,
getNextGroupId = function()
Gremlin.log.trace(Evac.Id, string.format('Getting Next Group ID'))

Expand Down Expand Up @@ -1850,7 +1793,7 @@ function Evac:setup(config)
timer.scheduleFunction(Evac._internal.doSpawns, nil, timer.getTime() + 5)
timer.scheduleFunction(Evac._internal.beacons.killDead, nil, timer.getTime() + 5)
timer.scheduleFunction(Evac._internal.smoke.refresh, nil, timer.getTime() + 5)
timer.scheduleFunction(Evac._internal.menu.addToF10, nil, timer.getTime() + 5)
timer.scheduleFunction(Gremlin.menu.updateF10, { Evac.Id, Evac._internal.menu.commands, Evac._internal.utils.extractionUnitsToMenuUnits }, timer.getTime() + 5)
end, nil, timer.getTime() + 1)

for _name, _def in pairs(Evac._internal.handlers) do
Expand Down
76 changes: 75 additions & 1 deletion src/gremlin.lua
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,75 @@ Gremlin = {
end
end
},
menu = {
updateF10 = function(args)
local toolId, commands, getForUnits = table.unpack(args)
local forUnits = getForUnits()

Gremlin.log.trace(Gremlin.Id, string.format('Updating F10 Menu For %i Units', Gremlin.utils.countTableEntries(forUnits)))

timer.scheduleFunction(Gremlin.menu.updateF10, {toolId, commands, forUnits}, timer.getTime() + 10)

for _unitName, _extractor in pairs(forUnits) do
local _unit = _extractor or Unit.getByName(_unitName)
if _unit ~= nil and _unit:isExist() then
local _groupId = _unit:getGroup():getID()
local _groupName = _unit:getGroup():getName()

local _rootPath
if Gremlin._state.menuAdded[toolId] == nil then
Gremlin._state.menuAdded[toolId] = {}
end
if Gremlin._state.menuAdded[toolId][_groupId] == nil then
_rootPath = missionCommands.addSubMenuForGroup(_groupId, toolId)
Gremlin._state.menuAdded[toolId][_groupId] = { root = _rootPath }
Gremlin.log.trace(Gremlin.Id, string.format('Added Root Menu For %s', toolId))
else
_rootPath = Gremlin._state.menuAdded[toolId][_groupId].root
end

for _cmdIdx, _command in pairs(commands) do
local _when = false
if type(_command.when) == 'boolean' then
_when = _command.when
elseif type(_command.when) == 'table' then
---@diagnostic disable-next-line: undefined-field
local _whenArgs = Gremlin.utils.parseFuncArgs(_command.when.args, {
unit = _unit,
group = _unit:getGroup()
})

---@diagnostic disable-next-line: undefined-field
if _command.when.func(table.unpack(_whenArgs)) == _command.when.value and _command.when.comp == 'equal' then
_when = true
---@diagnostic disable-next-line: undefined-field
elseif _command.when.func(table.unpack(_whenArgs)) ~= _command.when.value and _command.when.comp == 'inequal' then
_when = true
end
end

if Gremlin._state.menuAdded[toolId][_groupId][_cmdIdx] ~= nil then
missionCommands.removeItemForGroup(_groupId, Gremlin._state.menuAdded[toolId][_groupId][_cmdIdx])
end

local _args = Gremlin.utils.parseFuncArgs(_command.args, {
unit = _unit,
group = _unit:getGroup()
})
if _when then
if type(_command.text) == "string" then
Gremlin._state.menuAdded[toolId][_groupId][_cmdIdx] = missionCommands.addCommandForGroup(_groupId, _command.text, _rootPath, function(_args) _command.func(table.unpack(_args)) end, _args)
Gremlin.log.trace(Gremlin.Id, string.format('Added Menu Item To Group %s (%s) : "%s"', _groupName, _unitName, _command.text))
else
Gremlin._state.menuAdded[toolId][_groupId][_cmdIdx] = missionCommands.addCommandForGroup(_groupId, _command.text(table.unpack(_args)), _rootPath, function(_args) _command.func(table.unpack(_args)) end, _args)
Gremlin.log.trace(Gremlin.Id, string.format('Added Menu Item To Group %s (%s) : "%s"', _groupName, _unitName, _command.text(table.unpack(_args))))
end
end
end
end
end
end,
},
utils = {
countTableEntries = function (_tbl)
local _count = 0
Expand Down Expand Up @@ -194,7 +263,12 @@ Gremlin = {

return tbl1
end
}
},

-- Internal State
_state = {
menuAdded = {},
},
}

function Gremlin:setup(config)
Expand Down
56 changes: 3 additions & 53 deletions test/evac.lua
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
local lu = require('luaunit_3_4')
local inspect = require('inspect')
local Spy = require('lib.mock.Spy')
local ValueMatcher = require('lib.mock.ValueMatcher')

table.unpack = table.unpack or unpack
unpack = table.unpack
Expand Down Expand Up @@ -2255,56 +2254,7 @@ Test5Internal3Zones = {
tearDown = tearDown,
}

Test5Internal4Menu = {
setUp = setUp,
testAddToF10 = function()
-- INIT
local argsToMatcher = function(_args)
for _pos, _arg in pairs(_args.arguments) do
if type(_arg) == "table" then
_args.arguments[_pos] = ValueMatcher.anyTable
elseif type(_arg) == "function" then
_args.arguments[_pos] = ValueMatcher.anyFunction
end
end

return _args.arguments
end

missionCommands.addSubMenuForGroup:whenCalled({ with = { 1, 'Gremlin Evac' }, thenReturn = { { 'Gremlin Evac' } } })
missionCommands.removeItemForGroup:whenCalled({ with = { 1, ValueMatcher.anyFunction }, thenReturn = nil })
for _, _command in pairs(Evac._internal.menu.commands) do
missionCommands.addCommandForGroup:whenCalled({ with = { 1, _command.text, ValueMatcher.anyTable, ValueMatcher.anyFunction, ValueMatcher.anyTable }, thenReturn = { { 'Gremlin Evac', _command.text }, _command.text } })
end

-- TEST
lu.assertEquals(Evac._internal.menu.addToF10(), nil)

-- SIDE EFFECTS
local _status, _result = pcall(
missionCommands.addSubMenuForGroup.assertAnyCallMatches,
missionCommands.addSubMenuForGroup,
{ arguments = { 1, 'Gremlin Evac' } }
)
lu.assertEquals(_status, true, string.format('%s\n%s', inspect(_result), inspect(missionCommands.addSubMenuForGroup.spy.calls)))

for _, _command in pairs(Evac._internal.menu.commands) do
if _command.text ~= 'Unload Evacuees' then
local _args = { arguments = { 1, _command.text, { 'Gremlin Evac' }, _command.func, {} } }

_status, _result = pcall(
missionCommands.addCommandForGroup.assertAnyCallMatches,
missionCommands.addCommandForGroup,
argsToMatcher(_args)
)
lu.assertEquals(_status, true, string.format('%s\n%s\n%s', inspect(_result), inspect(_args), inspect(missionCommands.addCommandForGroup.spy.calls)))
end
end
end,
tearDown = tearDown,
}

Test5Internal5Utils = {
Test5Internal4Utils = {
setUp = setUp,
test0EndIfLossesTooHigh = function()
-- INIT
Expand Down Expand Up @@ -2413,7 +2363,7 @@ Test5Internal5Utils = {
tearDown = tearDown,
}

Test5Internal6DoSpawns = {
Test5Internal5DoSpawns = {
setUp = setUp,
test0DoSpawnsFirstPass = function()
-- INIT
Expand Down Expand Up @@ -2447,7 +2397,7 @@ Test5Internal6DoSpawns = {
tearDown = tearDown,
}

Test5Internal7Handlers = {
Test5Internal6Handlers = {
setUp = setUp,
test0FullLoss = function()
-- INIT
Expand Down
Loading

0 comments on commit 3a547af

Please sign in to comment.