Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Add an automated grey goo command #4368

Draft
wants to merge 7 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions LuaRules/Configs/customcmds.lua
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ local cmds = {
ABANDON_PW = 35200,
RECALL_DRONES = 35300,
TOGGLE_DRONES = 35301,
GREYGOO = 35600,
GOO_GATHER = 35646,
PUSH_PULL = 35666, -- weapon_impulse
WANT_ONOFF = 35667,
Expand Down
233 changes: 233 additions & 0 deletions LuaRules/Gadgets/cmd_greygoo.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,233 @@
function gadget:GetInfo()
return {
name = "Area Grey Goo Handler",
desc = "Units will consume all wreckage in an area",
author = "Shaman",
date = "April 1st, 2021",
license = "CC-0",
layer = 5,
enabled = true,
}
end

if (not gadgetHandler:IsSyncedCode()) then
return
end

include("LuaRules/Configs/customcmds.h.lua")
local IterableMap = VFS.Include("LuaRules/Gadgets/Include/IterableMap.lua")

local handled = IterableMap.New()
local featurecache = {}
local needLOS = true

local areaGreyGooDesc = {
id = CMD_GREYGOO,
type = CMDTYPE.ICON_UNIT_FEATURE_OR_AREA,
name = 'Grey Goo', -- TODO: better name. Marketing was out today.
cursor = 'Reclaim',
action = 'reclaim',
tooltip = 'Marks an area or wreckage for grey goo.',
}

-- Speed ups --
local spGetUnitPosition = Spring.GetUnitPosition
local spGetFeaturePosition = Spring.GetFeaturePosition
local spGetFeaturesInCylinder = Spring.GetFeaturesInCylinder
local spGiveOrderToUnit = Spring.GiveOrderToUnit
local spSetUnitMoveGoal = Spring.SetUnitMoveGoal
local spGetFeatureDefID = Spring.GetFeatureDefID
local spValidFeatureID = Spring.ValidFeatureID
local spValidUnitID = Spring.ValidUnitID
local spGetUnitCommands = Spring.GetUnitCommands
local spInsertUnitCmdDesc = Spring.InsertUnitCmdDesc
local spIsPosInLos = Spring.IsPosInLos
local spGetUnitAllyTeam = Spring.GetUnitAllyTeam
local CommandOrder = 123456
local sqrt = math.sqrt

local validFeatures = {}
local _, GooDefs = VFS.Include("LuaRules/Configs/grey_goo_defs.lua")

for i = 1, #FeatureDefs do
local fdef = FeatureDefs[i]
if fdef.customParams and fdef.customParams.fromunit then
validFeatures[i] = true
end
end

local function Distance(x1, x2, y1, y2)
return sqrt(((x2 - x1) * (x2 - x1)) + ((y2 - y1) * (y2 - y1)))
end

local function GetEligiableWrecksInArea(x, z, radius, allyID) -- Looks for wrecks in LOS that are nearby.
local check = spGetFeaturesInCylinder(x, z, radius)
local ret = {}
for i = 1, #check do
local featureID = check[i]
if featurecache[featureID] then
if needLOS then
local x, y, z = spGetFeaturePosition(featureID)
if needLOS and spIsPosInLos(x, y, z, allyID) then
ret[#ret + 1] = featureID
end
else
ret[#ret + 1] = featureID
end
end
end
return ret
end

local function GetClosestWreck(x, z, cx, cz, radius, ally)
local wrecks = GetEligiableWrecksInArea(cx, cz, radius, ally)
if #wrecks == 0 then -- double safety.
return nil
end
local lowestDistance = math.huge
local lowestID
for i = 1, #wrecks do
local id = wrecks[i]
local x2, y2, z2 = spGetFeaturePosition(id)
local d = Distance(x, x2, z, z2)
if d < lowestDistance then
lowestID = id
lowestDistance = d
end
end
return lowestID
end

local function IsThereEligiableWreckNearby(x, z, radius, allyteam) -- stupid check. (for when we don't want the closest wreck)
local check = GetEligiableWrecksInArea(x, z, radius, allyteam)
return #check > 0 and check[1] or nil
end


function gadget:FeatureCreated(featureID, allyTeamID)
local featuredef = spGetFeatureDefID(featureID)
featurecache[featureID] = validFeatures[featuredef]
end

function gadget:FeatureDestroyed(featureID)
featurecache[featureID] = nil
end

function gadget:AllowCommand(unitID, unitDefID, unitTeam, cmdID, cmdParams, cmdOptions, cmdTag, synced)
if cmdID == CMD_GREYGOO and not GooDefs[unitDefID] then -- screen against non-grey gooers using area greygoo.
return false
end
return true
end

function gadget:UnitCreated(unitID, unitDefID)
if GooDefs[unitDefID] then
--Spring.Echo("Injecting Command to " .. unitID .. "(Cmd: " .. tostring(CMD_GREYGOO) .. ")")
spInsertUnitCmdDesc(unitID, CommandOrder, areaGreyGooDesc)
end
end

function gadget:CommandFallback(unitID, unitDefID, unitTeam, cmdID, cmdParams, cmdOptions, cmdTag) -- used for "free" command management since we have a command that isn't an engine command.
if cmdID ~= CMD_GREYGOO then
--Spring.Echo("GREYGOO: CMDFALLBACK: bad cmd")
return false, false -- don't care (not ours)
else
local data = IterableMap.Get(handled, unitID)
if data and data.done then
--Spring.Echo("GREYGOO: DONE")
IterableMap.Remove(handled, unitID)
return true, true -- we're done with this command.
elseif not data then
--Spring.Echo("GREYGOO: INITIALIZING with params: " .. tostring(cmdParams[1]) .. ", " .. tostring(cmdParams[2]) .. ", " .. tostring(cmdParams[3]) .. ", " .. tostring(cmdParams[4]))
local allyteam = spGetUnitAllyTeam(unitID)
if #cmdParams == 1 then -- this is a single feature command.
local id = cmdParams[1]
if id < Game.maxUnits then -- unitID instead of featureID
return true, true
end
id = id - Game.maxUnits
local valid = spValidFeatureID(id)
--Spring.Echo("ID: " .. id .. " (Valid: " .. tostring(valid) .. ")")
if not valid then
return true, true -- this is invalid since it's targeting a unit (wtf)
else
cmdParams = id -- we set this to just a number so our gameframe check can differentiate between a single command and an area command.
-- Note: We don't need to worry about LOS as I think it won't work out of LOS (returns a map position, probably)
end
elseif not IsThereEligiableWreckNearby(cmdParams[1], cmdParams[3], cmdParams[4], allyteam) then -- there's nothing that we can use in the radius.
--Spring.Echo("GREYGOO: No eligible wrecks nearby")
return true, true
end
local persistent = cmdOptions.alt and #cmdParams > 1
--Spring.Echo("Persistent: " .. tostring(persistent))
IterableMap.Add(handled, unitID, {def = unitDefID, done = false, params = cmdParams, goal = -9999, updates = 0, persistent = persistent}) -- we found a new unit!
end
return true, false -- we're still not done here.
end
end

function gadget:GameFrame(f)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would think that the bulk of the command handling could happen in CommandFallback.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CommandFallback calls once every long update from what I understand. I'll test it out.

if f%5 == 0 then -- 6hz
for unitID, data in IterableMap.Iterator(handled) do
if not data.done then -- don't handle things that are done.
data.updates = data.updates + 1
--Spring.Echo("GreyGoo: Update " .. unitID .. ": Goal: " .. data.goal)
local currentcmd = spGetUnitCommands(unitID, 1)
if not spValidUnitID(unitID) or #currentcmd == 0 or currentcmd[1].id ~= CMD_GREYGOO then -- safety. first we check if we have any commands, then if we're not doing grey goo anymore.
--Spring.Echo("Invalid unit or not working")
IterableMap.Remove(handled, unitID)
else
local wantedrange = 16
local wantedspeed = UnitDefs[data.def].speed
if type(data.params) ~= "table" then -- this is a single command
local featureID = data.params
if spValidFeatureID(featureID) then
if data.goal ~= featureID then -- we haven't set the move goal yet.
local x, y, z = spGetFeaturePosition(featureID) -- find the location of our target.
spSetUnitMoveGoal(unitID, x, y, z, wantedrange, wantedspeed, true)
data.goal = featureID
end
else
data.done = true
end
else -- this is an area command.
local commandRadius = data.params[4]
local commandX = data.params[1]
local commandZ = data.params[3]
local allyTeam = spGetUnitAllyTeam(unitID)
local x, y, z = spGetUnitPosition(unitID)
if data.goal == -9999 or not spValidFeatureID(data.goal) then -- haven't set a goal yet or our current greygoo task is complete.
local id = GetClosestWreck(x, z, commandX, commandZ, commandRadius, allyTeam)
if id then
local gx, gy, gz = spGetFeaturePosition(id)
spSetUnitMoveGoal(unitID, gx, gy, gz, wantedrange, wantedspeed, true)
data.goal = id
elseif not data.persistent then
spSetUnitMoveGoal(unitID, commandX, Spring.GetGroundHeight(commandX, commandZ), commandZ, wantedrange) -- don't allow them to run off to fuckall.
data.done = true
end
elseif data.updates == 3 then -- check if we're within range (fires off every 3rd update, this staggers the check)
data.updates = 0
--Spring.Echo("Check Distance. Wanted Distance: " .. wantedrange)
local px, py, pz = spGetFeaturePosition(data.goal)
if Distance(px, x, pz, z) >= wantedrange * 2 then
--Spring.Echo("Move to")
spSetUnitMoveGoal(unitID, px, py, pz, wantedrange, wantedspeed, true) -- we may have been pushed by allies or other grey gooers. prevents softlock.
end
end
end
end
end
end
end
end

function gadget:Initalize()
gadgetHandler:RegisterCMDID(CMD_GREYGOO)
--Spring.SetCustomCommandDrawData(CMD_GREYGOO, "Reclaim", {0.8, 0.3, 0.3, 0.7}, true)
-- It should be noted that Gadgets cannot set the custom command draw data for whatever reason.
-- Use gui_command_alpha widget instead.
for _, unitID in pairs(Spring.GetAllUnits()) do
gadget:UnitCreated(unitID, Spring.GetUnitDefID(unitID))
end
end
9 changes: 8 additions & 1 deletion LuaRules/Gadgets/unit_grey_goo.lua
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ local spSetUnitRulesParam = Spring.SetUnitRulesParam
local spGetUnitRulesParam = Spring.GetUnitRulesParam
local spGetFeatureDefID = Spring.GetFeatureDefID
local spSpawnCEG = Spring.SpawnCEG
local spGetUnitCommands = Spring.GetUnitCommands

local CMD_FIRE_STATE = CMD.FIRE_STATE
local CMD_MOVE_STATE = CMD.MOVE_STATE
Expand Down Expand Up @@ -275,7 +276,13 @@ function gadget:GameFrame(f)

spGiveOrderToUnit(newId, CMD_FIRE_STATE, {firestate}, 0)
spGiveOrderToUnit(newId, CMD_MOVE_STATE, {movestate}, 0)
spGiveOrderToUnit(newId, CMD_GUARD , {unitIndex[i]} , 0)
local currentcmd = spGetUnitCommands(unitIndex[i], 1)
if currentcmd[1].id == CMD_GREYGOO then
spGiveOrderToUnit(newId, CMD_GREYGOO, currentcmd[1].params, 0)
spGiveOrderToUnit(newId, CMD_GUARD, {unitIndex[i]}, CMD.OPT_SHIFT)
else
spGiveOrderToUnit(newId, CMD_GUARD , {unitIndex[i]} , 0)
end

spSpawnCEG(CEG_SPAWN,
x, y, z,
Expand Down
1 change: 1 addition & 0 deletions LuaUI/Configs/integral_menu_commands.lua
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ local cmdPosDef = {
[CMD.LOAD_UNITS] = {pos = 7, priority = 7},
[CMD.UNLOAD_UNITS] = {pos = 7, priority = 8},
[CMD_RECALL_DRONES] = {pos = 7, priority = 10},
[CMD_GREYGOO] = {pos = 7, priority = 11},

[CMD_UNIT_SET_TARGET_CIRCLE] = {pos = 13, priority = 2},
[CMD_UNIT_CANCEL_TARGET] = {pos = 13, priority = 2},
Expand Down
4 changes: 2 additions & 2 deletions LuaUI/Configs/integral_menu_config.lua
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ local commandDisplayConfig = {
[CMD_BUMPY] = {texture = imageDir .. 'bumpy.png'},

[CMD_AREA_GUARD] = { texture = imageDir .. 'Bold/guard.png', tooltip = "Guard: Protect the target and assist its production."},

[CMD_AREA_MEX] = {texture = imageDir .. 'Bold/mex.png'},

[CMD_JUMP] = {texture = imageDir .. 'Bold/jump.png'},
Expand All @@ -96,7 +96,7 @@ local commandDisplayConfig = {
[CMD_GBCANCEL] = { texture = imageDir .. 'Bold/stopbuild.png'},

[CMD_RECALL_DRONES] = {texture = imageDir .. 'Bold/recall_drones.png'},

[CMD_GREYGOO] = {texture = imageDir .. 'states/goo_on.png', tooltip = "Reclaim (Grey Goo): Consumes wreck(s) in an area (or a single wreck)."},
-- states
[CMD_WANT_ONOFF] = {
texture = {imageDir .. 'states/off.png', imageDir .. 'states/on.png'},
Expand Down
1 change: 1 addition & 0 deletions LuaUI/Configs/integral_menu_culling.lua
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ local configList = {
{cmdID = CMD.PATROL , default = true, name = "Patrol"},
{cmdID = CMD_RECALL_DRONES , default = true, name = "Recall Drones"},
{cmdID = CMD.RECLAIM , default = true, name = "Reclaim"},
{cmdID = CMD_GREYGOO , default = true, name = "Reclaim (Grey Goo)"},
{cmdID = CMD.REPAIR , default = true, name = "Repair"},
{cmdID = CMD_FIND_PAD , default = true, name = "Resupply"},
{cmdID = CMD.RESURRECT , default = true, name = "Resurrect"},
Expand Down
3 changes: 2 additions & 1 deletion LuaUI/Widgets/gui_command_alpha.lua
Original file line number Diff line number Diff line change
Expand Up @@ -38,5 +38,6 @@ function widget:Initialize()
Spring.SetCustomCommandDrawData(CMD_RESTORE, "Restore2", terraformColor, false)
Spring.SetCustomCommandDrawData(CMD_EXTENDED_LOAD, CMD.LOAD_UNITS, {0,0.6,0.6,cmdAlpha},true)
Spring.SetCustomCommandDrawData(CMD_EXTENDED_UNLOAD, CMD.UNLOAD_UNITS, {0.6,0.6,0,cmdAlpha})
Spring.SetCustomCommandDrawData(CMD_TURN, "Patrol", {0,1,0,cmdAlpha})
Spring.SetCustomCommandDrawData(CMD_TURN, "Patrol", {0, 1, 0, cmdAlpha})
Spring.SetCustomCommandDrawData(CMD_GREYGOO, "Reclaim", {0.8, 0.3, 0.3, cmdAlpha}, true)
end