From 194121b50e072bd5dddbdae380dc637d415fa0ef Mon Sep 17 00:00:00 2001 From: Stiofan-K <108685389+Stiofan-K@users.noreply.github.com> Date: Sun, 13 Sep 2026 23:10:13 +0200 Subject: [PATCH 1/5] Moves zombie rez queue to the api Moves the rez queue functionality to the api and makes accessible for other things via a callback function. Except I havent figured out how to get the callback funciton to work correctly. --- LuaRules/Gadgets/api_zombie_helper.lua | 175 ++++++++++++++---- .../Gadgets/unit_global_zombie_resurrect.lua | 145 ++------------- 2 files changed, 162 insertions(+), 158 deletions(-) diff --git a/LuaRules/Gadgets/api_zombie_helper.lua b/LuaRules/Gadgets/api_zombie_helper.lua index fc81f73837..e7f6a32797 100644 --- a/LuaRules/Gadgets/api_zombie_helper.lua +++ b/LuaRules/Gadgets/api_zombie_helper.lua @@ -27,16 +27,35 @@ VFS.Include("LuaRules/Configs/CAI/accessory/targetReachableTester.lua") -- unsure if these are different, differentiating between gaia and "zombie" team somehow may be good? local GaiaTeamID = Spring.GetGaiaTeamID() local GaiaAllyTeamID = select(6, Spring.GetTeamInfo(GaiaTeamID, false)) +local random = math.random +local floor = math.floor local mapWidth local mapHeight +local gameframe = 0 + +local NonZombies = { + ["asteroid"] = true, +} + +local CEG_SPAWN = [[zombie]] local REZ_SOUND = "sounds/misc/resurrect.wav" +local ZOMBIE_SOUNDS = { + "sounds/misc/zombie_1.wav", + "sounds/misc/zombie_2.wav", + "sounds/misc/zombie_3.wav", +} + +local WARNING_TIME = 5 -- could be more configurable still but seem + +local resurrectingFeatures = {} -- contains inital gameframe, base rez time, frame rez should complete and a callback function if someone wants to reuse the functionality --- Zombie resurrect -- unsure if necessary, seems like just an extra failsafe + +-- Get Feature Defname and facing local function GetFeatureResurrectData(featureID) local featureDefName, facing = Spring.GetFeatureResurrect(featureID) if featureDefName == "" then @@ -50,39 +69,45 @@ local function GetFeatureResurrectData(featureID) return featureDefName, facing end -local function TurnFeatureIntoUnit(featureID,teamID,reclaimPercentHealth) +-- Zombie resurrect +-- Turns a feature into a unit if applicable. Has a callback returning featureID and unitID for data transfer. Returns unitID. +local function TurnFeatureIntoUnit(featureID,teamID,reclaimPercentHealthBool, unitReviveCallback) local featureDefName,facing = GetFeatureResurrectData(featureID) local x, y, z = Spring.GetFeaturePosition(featureID) local unitID = Spring.CreateUnit(featureDefName, x, y, z, facing, teamID) - -- Unit and Wreck exist both - -- could let there be a function passed to this function, that returns the Unit ID for transfer of values - - if (unitID) then - gadgetHandler:NotifyUnitCreatedByMechanic(unitID, false, "zombies") - local size = UnitDefNames[featureDefName].xsize - Spring.SpawnCEG("resurrect", x, y, z, 0, 0, 0, size) - Spring.GiveOrderToUnit(unitID, CMD.FIRE_STATE, 2, 0) - GG.PlayFogHiddenSound(REZ_SOUND, 12, x, y, z) - - if reclaimPercentHealth then - local currentMetal, maxMetal = Spring.GetFeatureResources(featureID) - if currentMetal and maxMetal and (maxMetal > 0) then - local health = Spring.GetUnitHealth(unitID) - if health then - Spring.SetUnitHealth(unitID, health*(currentMetal/maxMetal)) - end + if not (unitID) then + return nil + end + + gadgetHandler:NotifyUnitCreatedByMechanic(unitID, false, "zombies") + local size = UnitDefNames[featureDefName].xsize + Spring.SpawnCEG("resurrect", x, y, z, 0, 0, 0, size) + Spring.GiveOrderToUnit(unitID, CMD.FIRE_STATE, 2, 0) + GG.PlayFogHiddenSound(REZ_SOUND, 12, x, y, z) + + if reclaimPercentHealth then + local currentMetal, maxMetal = Spring.GetFeatureResources(featureID) + if currentMetal and maxMetal and (maxMetal > 0) then + local health = Spring.GetUnitHealth(unitID) + if health then + Spring.SetUnitHealth(unitID, health*(currentMetal/maxMetal)) end end + end + + -- Unit and Wreck exist both for value transfer + if (unitReviveCallback) then + unitReviveCallback(unitID,featureID) end Spring.DestroyFeature(featureID) return unitID end - --- Works on non zombie units too. + +-- Sets the zombie specific speed multiplier. Works on non zombie units too. local function SetZombieSpeedMult(unitID,speedMult) if type(speedMult) ~= 'number' or speedMult < 0 then error("SetZombieSpeedMult: mult must be number >= 0") @@ -103,14 +128,13 @@ local function RandomFactoryOrders(unitID, unitDefID) -- give factory something return end local orders = {} - for i = 1, math.random(10, 30) do - orders[#orders + 1] = {-buildopts[math.random(1, #buildopts)], 0, 0 } + for i = 1, random(10, 30) do + orders[#orders + 1] = {-buildopts[random(1, #buildopts)], 0, 0 } end Spring.GiveOrderArrayToUnit(unitID, orders) end - local function GetUnitNearestAlly(unitID, range) local best_ally local best_dist @@ -132,12 +156,14 @@ local function GetUnitNearestAlly(unitID, range) return best_ally end +-- Applies Random Attackmove orders and Factory commands to units. local function GiveZombiesRandomOrders(unitID) local unitDefID = (not Spring.GetUnitIsDead(unitID)) and Spring.GetUnitDefID(unitID) if not unitDefID then return end + Spring.GiveOrderToUnit(unitID, CMD.MOVE_STATE, 2, 0) local rx,rz,ry local orders = {} local near_ally @@ -151,12 +177,12 @@ local function GiveZombiesRandomOrders(unitID) end end local x,y,z = Spring.GetUnitPosition(unitID) - if (near_ally) and math.random(0, 5) < 4 then -- 60% chance to guard nearest ally + if (near_ally) and random(0, 5) < 4 then -- 60% chance to guard nearest ally orders[#orders + 1] = {CMD.GUARD, {near_ally}, 0} end - for i = 1, math.random(10, 30) do - local rx = math.random(0, mapWidth) - local rz = math.random(0, mapHeight) + for i = 1, random(10, 30) do + local rx = random(0, mapWidth) + local rz = random(0, mapHeight) local ry = Spring.GetGroundHeight(rx,rz) if IsTargetReallyReachable(unitID, rx, ry, rz, x, y, z) then orders[#orders+1] = {CMD.FIGHT, {rx, ry, rz}, CMD.OPT_SHIFT} @@ -168,6 +194,87 @@ local function GiveZombiesRandomOrders(unitID) RandomFactoryOrders(unitID, unitDefID) -- give factory something to do end end + +-- Adds a wreck into the zombie countdown table. +-- Use the rezFrameCallback to repurpose the system for other effects or hook into TurnFeatureIntoUnit for a revived unit and ID. +-- If no callback is provided, resurrects the wreck on countdown completion. +-- TODO I could add another callback function that returns passes to TurnFeatureIntoUnit for their callback. while thats more straighfroward in application that feels convoluted overall. +local function AddFeatureToZombieCountdown(featureID, buildpower, minRezTime, rezFrameCallback) + local resName, face = GetFeatureResurrectData(featureID) + if resName and face and not resurrectingFeatures[featureID] then + local ud = resName and UnitDefNames[resName] + if ud and not NonZombies[resName] then + local rezTime = ud.metalCost / buildpower + if (rezTime < minRezTime) then + rezTime = minRezTime + end + resurrectingFeatures[featureID] = {rezInitFrame = gameframe, rezBaseTime = rezTime, rezFrame = (gameframe + rezTime*32), reclaimPercent = 0, rezFrameCallback = rezFrameCallback, } + end + end +end + +-- hm... getter and setter to allow modification of these? could also let it be global, but that feels like bad practice +-- could just export it under GG.zombies? +local function GetZombieResurrectData(featureID) + if(resurrectingFeatures[featureID]) then + return resurrectingFeatures[featureID] + end + return false +end + +-- The below comments are left from before -Stiofan +-- reclaiming zombies 'causes delay in rez, basically you have to have about ZOMBIES_REZ_SPEED/2 or bigger BP to reclaim faster than it resurrects... +-- TODO do more math to figure out how to perform it better? +function gadget:AllowFeatureBuildStep(builderID, builderTeam, featureID, featureDefID, part) + if (resurrectingFeatures[featureID]) then + local reclaimPercent = resurrectingFeatures[featureID].reclaimPercent + local rezBaseTime = resurrectingFeatures[featureID].rezBaseTime + local rezInitFrame = resurrectingFeatures[featureID].rezInitFrame + + reclaimPercent = reclaimPercent - part + resurrectingFeatures[featureID].rezFrame = rezInitFrame + rezBaseTime * (1 + reclaimPercent) * 32 + + resurrectingFeatures[featureID].reclaimPercent = reclaimPercent + end + return true +end + +function gadget:FeatureDestroyed(featureID, allyTeam) + if (resurrectingFeatures[featureID]) then + resurrectingFeatures[featureID] = nil + end +end + +function gadget:GameFrame(f) + gameframe = f + if f%32 ~= 0 then + return + end + local spSpawnCEG = Spring.SpawnCEG -- putting the localization here because cannot localize in global scope since spring 97 + for featureID in pairs(resurrectingFeatures) do + local rezFrame = resurrectingFeatures[featureID].rezFrame + if rezFrame <= gameframe then + if (resurrectingFeatures[featureID].rezFrameCallback) then + Spring.Echo(resurrectingFeatures[featureID].rezFrameCallback) + resurrectingFeatures[featureID].rezFrameCallback(featureID) + else + TurnFeatureIntoUnit(featureID,GaiaTeamID,true,nil) + end + else + local framesTillRezCall = floor((rezFrame - f) / 32) + if framesTillRezCall <= WARNING_TIME then + local r = Spring.GetFeatureRadius(featureID) + local x, y, z = Spring.GetFeaturePosition(featureID) + spSpawnCEG(CEG_SPAWN, x, y, z, 0, 0, 0, 10 + r, 10 + r) + + if framesTillRezCall == WARNING_TIME then + local z_sound = ZOMBIE_SOUNDS[random(#ZOMBIE_SOUNDS)] + GG.PlayFogHiddenSound(z_sound, 4, x, y, z) + end + end + end + end +end function gadget:Initialize() @@ -175,9 +282,11 @@ function gadget:Initialize() mapHeight = Game.mapSizeZ GG.Zombies = { - TurnFeatureIntoUnit = TurnFeatureIntoUnit, - SetZombieSpeedMult = SetZombieSpeedMult, - SetZombieBehavior = GiveZombiesRandomOrders, - GetFeatureResurrectData = GetFeatureResurrectData + TurnFeatureIntoUnit = TurnFeatureIntoUnit, + SetZombieSpeedMult = SetZombieSpeedMult, + SetZombieBehavior = GiveZombiesRandomOrders, + GetFeatureResurrectData = GetFeatureResurrectData, + GetZombieResurrectData = GetZombieResurrectData, -- I want to expose these for modification from outside if desired + AddFeatureToZombieCountdown = AddFeatureToZombieCountdown } -end +end \ No newline at end of file diff --git a/LuaRules/Gadgets/unit_global_zombie_resurrect.lua b/LuaRules/Gadgets/unit_global_zombie_resurrect.lua index 81ea147076..7e63252c00 100644 --- a/LuaRules/Gadgets/unit_global_zombie_resurrect.lua +++ b/LuaRules/Gadgets/unit_global_zombie_resurrect.lua @@ -36,10 +36,6 @@ local getMovetype = Spring.Utilities.getMovetype VFS.Include("LuaRules/Configs/CAI/accessory/targetReachableTester.lua") -- I'm scared... what exactly did including that file do and how do I use it? -local spGetGroundHeight = Spring.GetGroundHeight -local spGetUnitPosition = Spring.GetUnitPosition -local spGetFeaturePosition = Spring.GetFeaturePosition -local spCreateUnit = Spring.CreateUnit local spGetUnitDefID = Spring.GetUnitDefID local spGetUnitTeam = Spring.GetUnitTeam local spGetAllUnits = Spring.GetAllUnits @@ -47,20 +43,13 @@ local spGetGameFrame = Spring.GetGameFrame local spGetAllFeatures = Spring.GetAllFeatures local spGiveOrderToUnit = Spring.GiveOrderToUnit local spGetUnitCommandCount = Spring.GetUnitCommandCount -local spDestroyFeature = Spring.DestroyFeature -local spGetUnitIsDead = Spring.GetUnitIsDead -local spGiveOrderArrayToUnitArray = Spring.GiveOrderArrayToUnitArray -local spGetUnitsInCylinder = Spring.GetUnitsInCylinder local spSetTeamResource = Spring.SetTeamResource local spGetUnitHealth = Spring.GetUnitHealth -local spSetUnitRulesParam = Spring.SetUnitRulesParam local GaiaTeamID = Spring.GetGaiaTeamID() local GaiaAllyTeamID = select(6, Spring.GetTeamInfo(GaiaTeamID, false)) local gameframe = 0 -local LOS_ACCESS = {inlos = true} -local PRIVATE_ACCESS = {private = true} local random = math.random local floor = math.floor @@ -68,11 +57,6 @@ local floor = math.floor local mapWidth local mapHeight -local reclaimed_data = {} -- holds initial gameframe, initial time in seconds, % of feature reclaimed -local zombies_to_spawn = {} -local zombiesToSpawnList = {} -local zombiesToSpawnMap = {} -local zombiesToSpawnCount = 0 local zombies = {} local ZOMBIE_SOUNDS = { @@ -124,39 +108,6 @@ local CMD_GUARD = CMD.GUARD local CEG_SPAWN = [[zombie]] -local function disSQ(x1, y1, x2, y2) - return (x1 - x2)^2 + (y1 - y2)^2 -end - -local function RemoveZombieToSpawn(featureID) - if not zombiesToSpawnMap[featureID] then - return false - end - zombies_to_spawn[featureID] = nil - reclaimed_data[featureID] = nil - - local index = zombiesToSpawnMap[featureID] - - zombiesToSpawnList[index] = zombiesToSpawnList[zombiesToSpawnCount] - zombiesToSpawnMap[zombiesToSpawnList[zombiesToSpawnCount]] = index - - zombiesToSpawnList[zombiesToSpawnCount] = nil - zombiesToSpawnMap[featureID] = nil - zombiesToSpawnCount = zombiesToSpawnCount - 1 - - return true -end - --- reclaiming zombies 'causes delay in rez, basically you have to have about ZOMBIES_REZ_SPEED/2 or bigger BP to reclaim faster than it resurrects... --- TODO do more math to figure out how to perform it better? -function gadget:AllowFeatureBuildStep(builderID, builderTeam, featureID, featureDefID, part) - if (zombies_to_spawn[featureID]) then - local base_time = reclaimed_data[featureID] - base_time[3] = base_time[3] - part - zombies_to_spawn[featureID] = base_time[1] + base_time[2] * (1+base_time[3]) * 32 - end - return true -end local function CheckZombieOrders() -- i can't rely on Idle because if for example unit is unloaded it doesnt count as idle... weird for unitID, _ in pairs(zombies) do @@ -169,36 +120,6 @@ end function gadget:GameFrame(f) gameframe = f - if (f%32) == 0 then - local spSpawnCEG = Spring.SpawnCEG -- putting the localization here because cannot localize in global scope since spring 97 - local index = 1 - while index <= zombiesToSpawnCount do - local featureID = zombiesToSpawnList[index] - local time_to_spawn = zombies_to_spawn[featureID] - local x, y, z = spGetFeaturePosition(featureID) - - if time_to_spawn <= f then - if not RemoveZombieToSpawn(featureID) then - index = index + 1 - end - GG.Zombies.TurnFeatureIntoUnit(featureID,GaiaTeamID,true) - else - local steps_to_spawn = floor((time_to_spawn - f) / 32) - local resName, face = GG.Zombies.GetFeatureResurrectData(featureID) - if steps_to_spawn <= WARNING_TIME then - local r = Spring.GetFeatureRadius(featureID) - - spSpawnCEG(CEG_SPAWN, x, y, z, 0, 0, 0, 10 + r, 10 + r) - - if steps_to_spawn == WARNING_TIME then - local z_sound = ZOMBIE_SOUNDS[math.random(#ZOMBIE_SOUNDS)] - GG.PlayFogHiddenSound(z_sound, 4, x, y, z) - end - end - index = index + 1 - end - end - end if (f%640) == 1 then CheckZombieOrders() end @@ -223,65 +144,39 @@ function gadget:UnitTaken(unitID, unitDefID, teamID, newTeamID) GG.Zombies.SetZombieSpeedMult(unitID, 1) end elseif newTeamID == GaiaTeamID then - gadget:UnitFinished(unitID, unitDefID, newTeamID) + GG.Zombies.SetZombieSpeedMult(unitID, ZOMBIES_PERMA_SLOW) + GG.Zombies.SetZombieBehavior(unitID) + zombies[unitID] = true end end function gadget:UnitCreated(unitID, unitDefID, teamID, builderID) - gadget:UnitFinished(unitID, unitDefID, teamID) -end - -local UnitFinished = function(_,_,_) end - -function gadget:UnitFinished(unitID, unitDefID, teamID) - UnitFinished(unitID, unitDefID, teamID) -end - -function gadget:FeatureCreated(featureID, allyTeam) - local resName, face = GG.Zombies.GetFeatureResurrectData(featureID) - if resName and face and not zombies_to_spawn[featureID] then - local ud = resName and UnitDefNames[resName] - if ud and not NonZombies[resName] then - local rez_time = ud.metalCost / ZOMBIES_REZ_SPEED - if (rez_time < ZOMBIES_REZ_MIN) then - rez_time = ZOMBIES_REZ_MIN + if (teamID == GaiaTeamID) and (builderID == GaiaTeamID) then + GG.Zombies.SetZombieBehavior(unitID) + zombies[unitID] = true + if ZOMBIES_PERMA_SLOW then + local maxHealth = select(2, spGetUnitHealth(unitID)) -- TODO is this check something necessary? or could it be removed + if maxHealth then + GG.Zombies.SetZombieSpeedMult(unitID, ZOMBIES_PERMA_SLOW) end - reclaimed_data[featureID] = {gameframe, rez_time, 0} - zombies_to_spawn[featureID] = gameframe + (rez_time*32) - - zombiesToSpawnCount = zombiesToSpawnCount + 1 - zombiesToSpawnList[zombiesToSpawnCount] = featureID - zombiesToSpawnMap[featureID] = zombiesToSpawnCount end end end -function gadget:FeatureDestroyed(featureID, allyTeam) - if (zombies_to_spawn[featureID]) then - RemoveZombieToSpawn(featureID) - end +local function RezFrameCallback(featureID) + local unitID = GG.Zombies.TurnFeatureIntoUnit(featureID,GaiaTeamID,ZOMBIES_PARTIAL_RECLAIM, nil) + zombies[unitID] = true end +function gadget:FeatureCreated(featureID, allyTeam) + GG.Zombies.AddFeatureToZombieCountdown(featureID, GaiaTeamID, ZOMBIES_REZ_SPEED, ZOMBIES_REZ_MIN, RezFrameCallback) +end + +--TODO unsure if this does anything local function ReInit(reinit) mapWidth = Game.mapSizeX mapHeight = Game.mapSizeZ - if not (defined) then - UnitFinished = function(unitID, unitDefID, teamID, builderID) - if (teamID == GaiaTeamID) and not (zombies[unitID]) then - spGiveOrderToUnit(unitID, CMD_REPEAT, 1, 0) - spGiveOrderToUnit(unitID, CMD_MOVE_STATE, 2, 0) - GG.Zombies.SetZombieBehavior(unitID) - zombies[unitID] = true - if ZOMBIES_PERMA_SLOW then - local maxHealth = select(2, spGetUnitHealth(unitID)) - if maxHealth then - GG.Zombies.SetZombieSpeedMult(unitID, ZOMBIES_PERMA_SLOW) - end - end - end - end - defined = true - end + if (reinit) then gameframe = spGetGameFrame() local units = spGetAllUnits() @@ -311,4 +206,4 @@ end function gadget:GameStart() ReInit(true) -- anything it does doesnt mess with existing zombies -end +end \ No newline at end of file From f69c2dc3c33992cd695a4099464f081d0738ac40 Mon Sep 17 00:00:00 2001 From: Stiofan-K <108685389+Stiofan-K@users.noreply.github.com> Date: Sun, 13 Sep 2026 23:36:53 +0200 Subject: [PATCH 2/5] Whitespace fix Thx Steelblue --- LuaRules/Gadgets/api_zombie_helper.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LuaRules/Gadgets/api_zombie_helper.lua b/LuaRules/Gadgets/api_zombie_helper.lua index e7f6a32797..db8b8a09ba 100644 --- a/LuaRules/Gadgets/api_zombie_helper.lua +++ b/LuaRules/Gadgets/api_zombie_helper.lua @@ -206,7 +206,7 @@ local function AddFeatureToZombieCountdown(featureID, buildpower, minRezTime, re if ud and not NonZombies[resName] then local rezTime = ud.metalCost / buildpower if (rezTime < minRezTime) then - rezTime = minRezTime + rezTime = minRezTime end resurrectingFeatures[featureID] = {rezInitFrame = gameframe, rezBaseTime = rezTime, rezFrame = (gameframe + rezTime*32), reclaimPercent = 0, rezFrameCallback = rezFrameCallback, } end From b86a17d0d392a39ee4d028696c5d9cbf658b7c75 Mon Sep 17 00:00:00 2001 From: Stiofan-K <108685389+Stiofan-K@users.noreply.github.com> Date: Sun, 13 Sep 2026 23:40:29 +0200 Subject: [PATCH 3/5] Callbacks, now with right number of arguments who knew functions accept only as many arguments as they request! new zombies also do their thing on rez correctly --- LuaRules/Gadgets/api_zombie_helper.lua | 1 - LuaRules/Gadgets/unit_global_zombie_resurrect.lua | 4 +++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/LuaRules/Gadgets/api_zombie_helper.lua b/LuaRules/Gadgets/api_zombie_helper.lua index db8b8a09ba..43cac670c4 100644 --- a/LuaRules/Gadgets/api_zombie_helper.lua +++ b/LuaRules/Gadgets/api_zombie_helper.lua @@ -198,7 +198,6 @@ end -- Adds a wreck into the zombie countdown table. -- Use the rezFrameCallback to repurpose the system for other effects or hook into TurnFeatureIntoUnit for a revived unit and ID. -- If no callback is provided, resurrects the wreck on countdown completion. --- TODO I could add another callback function that returns passes to TurnFeatureIntoUnit for their callback. while thats more straighfroward in application that feels convoluted overall. local function AddFeatureToZombieCountdown(featureID, buildpower, minRezTime, rezFrameCallback) local resName, face = GetFeatureResurrectData(featureID) if resName and face and not resurrectingFeatures[featureID] then diff --git a/LuaRules/Gadgets/unit_global_zombie_resurrect.lua b/LuaRules/Gadgets/unit_global_zombie_resurrect.lua index 7e63252c00..4fd2fb09e2 100644 --- a/LuaRules/Gadgets/unit_global_zombie_resurrect.lua +++ b/LuaRules/Gadgets/unit_global_zombie_resurrect.lua @@ -166,10 +166,12 @@ end local function RezFrameCallback(featureID) local unitID = GG.Zombies.TurnFeatureIntoUnit(featureID,GaiaTeamID,ZOMBIES_PARTIAL_RECLAIM, nil) zombies[unitID] = true + GG.Zombies.SetZombieSpeedMult(unitID, ZOMBIES_PERMA_SLOW) + GG.Zombies.SetZombieBehavior(unitID) end function gadget:FeatureCreated(featureID, allyTeam) - GG.Zombies.AddFeatureToZombieCountdown(featureID, GaiaTeamID, ZOMBIES_REZ_SPEED, ZOMBIES_REZ_MIN, RezFrameCallback) + GG.Zombies.AddFeatureToZombieCountdown(featureID, ZOMBIES_REZ_SPEED, ZOMBIES_REZ_MIN, RezFrameCallback) end --TODO unsure if this does anything From b23494c0062f10c4f91b9d013d34f7f04bad87bb Mon Sep 17 00:00:00 2001 From: Stiofan-K <108685389+Stiofan-K@users.noreply.github.com> Date: Sun, 13 Sep 2026 23:42:33 +0200 Subject: [PATCH 4/5] He Steel _Blued all over my DM's!! thxthx anyhow. needs to be fixed at some point eh? --- LuaRules/Gadgets/api_zombie_helper.lua | 28 +++++++++++++------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/LuaRules/Gadgets/api_zombie_helper.lua b/LuaRules/Gadgets/api_zombie_helper.lua index 43cac670c4..86a3ed2da9 100644 --- a/LuaRules/Gadgets/api_zombie_helper.lua +++ b/LuaRules/Gadgets/api_zombie_helper.lua @@ -73,22 +73,22 @@ end -- Turns a feature into a unit if applicable. Has a callback returning featureID and unitID for data transfer. Returns unitID. local function TurnFeatureIntoUnit(featureID,teamID,reclaimPercentHealthBool, unitReviveCallback) - local featureDefName,facing = GetFeatureResurrectData(featureID) - local x, y, z = Spring.GetFeaturePosition(featureID) - - local unitID = Spring.CreateUnit(featureDefName, x, y, z, facing, teamID) - - if not (unitID) then + local featureDefName,facing = GetFeatureResurrectData(featureID) + local x, y, z = Spring.GetFeaturePosition(featureID) + + local unitID = Spring.CreateUnit(featureDefName, x, y, z, facing, teamID) + + if not (unitID) then return nil end - + gadgetHandler:NotifyUnitCreatedByMechanic(unitID, false, "zombies") local size = UnitDefNames[featureDefName].xsize Spring.SpawnCEG("resurrect", x, y, z, 0, 0, 0, size) Spring.GiveOrderToUnit(unitID, CMD.FIRE_STATE, 2, 0) GG.PlayFogHiddenSound(REZ_SOUND, 12, x, y, z) - - if reclaimPercentHealth then + + if reclaimPercentHealthBool then local currentMetal, maxMetal = Spring.GetFeatureResources(featureID) if currentMetal and maxMetal and (maxMetal > 0) then local health = Spring.GetUnitHealth(unitID) @@ -97,14 +97,14 @@ local function TurnFeatureIntoUnit(featureID,teamID,reclaimPercentHealthBool, un end end end - + -- Unit and Wreck exist both for value transfer if (unitReviveCallback) then unitReviveCallback(unitID,featureID) - end - - Spring.DestroyFeature(featureID) - return unitID + end + + Spring.DestroyFeature(featureID) + return unitID end -- Sets the zombie specific speed multiplier. Works on non zombie units too. From 9bdc04fee4f7d631933ad9122137d4e059ad0bbb Mon Sep 17 00:00:00 2001 From: Stiofan-K <108685389+Stiofan-K@users.noreply.github.com> Date: Tue, 15 Sep 2026 21:22:19 +0200 Subject: [PATCH 5/5] Some more cleanup Seems to be working as intended --- LuaRules/Gadgets/api_zombie_helper.lua | 70 +++++++++++-------- .../Gadgets/unit_global_zombie_resurrect.lua | 68 ++++-------------- 2 files changed, 54 insertions(+), 84 deletions(-) diff --git a/LuaRules/Gadgets/api_zombie_helper.lua b/LuaRules/Gadgets/api_zombie_helper.lua index 86a3ed2da9..aafc23bed2 100644 --- a/LuaRules/Gadgets/api_zombie_helper.lua +++ b/LuaRules/Gadgets/api_zombie_helper.lua @@ -27,6 +27,7 @@ VFS.Include("LuaRules/Configs/CAI/accessory/targetReachableTester.lua") -- unsure if these are different, differentiating between gaia and "zombie" team somehow may be good? local GaiaTeamID = Spring.GetGaiaTeamID() local GaiaAllyTeamID = select(6, Spring.GetTeamInfo(GaiaTeamID, false)) +local WARNING_TIME = 5 -- set to 5 for sounds and such to sync well. local random = math.random local floor = math.floor @@ -47,8 +48,6 @@ local ZOMBIE_SOUNDS = { "sounds/misc/zombie_3.wav", } -local WARNING_TIME = 5 -- could be more configurable still but seem - local resurrectingFeatures = {} -- contains inital gameframe, base rez time, frame rez should complete and a callback function if someone wants to reuse the functionality @@ -72,7 +71,6 @@ end -- Zombie resurrect -- Turns a feature into a unit if applicable. Has a callback returning featureID and unitID for data transfer. Returns unitID. local function TurnFeatureIntoUnit(featureID,teamID,reclaimPercentHealthBool, unitReviveCallback) - local featureDefName,facing = GetFeatureResurrectData(featureID) local x, y, z = Spring.GetFeaturePosition(featureID) @@ -157,14 +155,13 @@ local function GetUnitNearestAlly(unitID, range) end -- Applies Random Attackmove orders and Factory commands to units. -local function GiveZombiesRandomOrders(unitID) +local function SetZombieBehavior(unitID) local unitDefID = (not Spring.GetUnitIsDead(unitID)) and Spring.GetUnitDefID(unitID) if not unitDefID then return end Spring.GiveOrderToUnit(unitID, CMD.MOVE_STATE, 2, 0) - local rx,rz,ry local orders = {} local near_ally if (UnitDefs[unitDefID].canAttack) then @@ -195,43 +192,52 @@ local function GiveZombiesRandomOrders(unitID) end end --- Adds a wreck into the zombie countdown table. --- Use the rezFrameCallback to repurpose the system for other effects or hook into TurnFeatureIntoUnit for a revived unit and ID. --- If no callback is provided, resurrects the wreck on countdown completion. +-- Adds a wreck into the zombie countdown table and returns it for further modification. +-- Use the rezFrameCallback to repurpose the system for other effects or chain into TurnFeatureIntoUnit for a revived unit with ID Callback. +-- If no callback is provided, revives the wreck as a unslowed zombie unit. local function AddFeatureToZombieCountdown(featureID, buildpower, minRezTime, rezFrameCallback) local resName, face = GetFeatureResurrectData(featureID) if resName and face and not resurrectingFeatures[featureID] then local ud = resName and UnitDefNames[resName] if ud and not NonZombies[resName] then - local rezTime = ud.metalCost / buildpower + local rezBaseTime = ud.metalCost / buildpower + local rezTime = rezBaseTime + local _,_,_,_,reclaimPercent,_ = Spring.GetFeatureResources(featureID) + + rezTime = rezBaseTime + rezBaseTime * (1 - reclaimPercent) + if (rezTime < minRezTime) then rezTime = minRezTime end - resurrectingFeatures[featureID] = {rezInitFrame = gameframe, rezBaseTime = rezTime, rezFrame = (gameframe + rezTime*32), reclaimPercent = 0, rezFrameCallback = rezFrameCallback, } + resurrectingFeatures[featureID] = { + rezInitFrame = gameframe, -- frame Feature was queued + rezBaseTime = rezBaseTime, -- base resurrect time in seconds + rezFrame = gameframe + rezTime * 32, -- frame the resurrect completes or the callback is fired. + rezFrameCallback = rezFrameCallback, -- callback function fired on rezFrame + rezWarningTime = WARNING_TIME, -- warning time in seconds for base particles and sfx, set 0 to hide. + reclaimPercent = reclaimPercent, -- reclaim left in feature in % to compare and adjust reztime + } + return resurrectingFeatures[featureID] end end + return nil end --- hm... getter and setter to allow modification of these? could also let it be global, but that feels like bad practice --- could just export it under GG.zombies? -local function GetZombieResurrectData(featureID) - if(resurrectingFeatures[featureID]) then - return resurrectingFeatures[featureID] - end - return false +-- Get and modify the table as you wish. +local function GetZombieResurrectData() + return resurrectingFeatures end --- The below comments are left from before -Stiofan --- reclaiming zombies 'causes delay in rez, basically you have to have about ZOMBIES_REZ_SPEED/2 or bigger BP to reclaim faster than it resurrects... --- TODO do more math to figure out how to perform it better? +-- Reclaiming the wreck can stretch the revive time up to 2x the base time. function gadget:AllowFeatureBuildStep(builderID, builderTeam, featureID, featureDefID, part) if (resurrectingFeatures[featureID]) then - local reclaimPercent = resurrectingFeatures[featureID].reclaimPercent - local rezBaseTime = resurrectingFeatures[featureID].rezBaseTime local rezInitFrame = resurrectingFeatures[featureID].rezInitFrame + local rezBaseTime = resurrectingFeatures[featureID].rezBaseTime + local reclaimPercent = resurrectingFeatures[featureID].reclaimPercent - reclaimPercent = reclaimPercent - part - resurrectingFeatures[featureID].rezFrame = rezInitFrame + rezBaseTime * (1 + reclaimPercent) * 32 + reclaimPercent = reclaimPercent + part + local rezTime = rezBaseTime + rezBaseTime * (1 - reclaimPercent) + resurrectingFeatures[featureID].rezFrame = rezInitFrame + rezTime * 32 resurrectingFeatures[featureID].reclaimPercent = reclaimPercent end @@ -254,19 +260,21 @@ function gadget:GameFrame(f) local rezFrame = resurrectingFeatures[featureID].rezFrame if rezFrame <= gameframe then if (resurrectingFeatures[featureID].rezFrameCallback) then - Spring.Echo(resurrectingFeatures[featureID].rezFrameCallback) resurrectingFeatures[featureID].rezFrameCallback(featureID) else - TurnFeatureIntoUnit(featureID,GaiaTeamID,true,nil) + local unitID = TurnFeatureIntoUnit(featureID,GaiaTeamID,true,nil) + SetZombieBehavior(unitID) end else - local framesTillRezCall = floor((rezFrame - f) / 32) - if framesTillRezCall <= WARNING_TIME then + local secondsTillRezCall = floor((rezFrame - f) / 32) + local rezWarningTime = resurrectingFeatures[featureID].rezWarningTime + --Spring.Echo("Time left for "..featureID..": "..secondsTillRezCall) + if secondsTillRezCall <= rezWarningTime then local r = Spring.GetFeatureRadius(featureID) local x, y, z = Spring.GetFeaturePosition(featureID) spSpawnCEG(CEG_SPAWN, x, y, z, 0, 0, 0, 10 + r, 10 + r) - if framesTillRezCall == WARNING_TIME then + if secondsTillRezCall == rezWarningTime then local z_sound = ZOMBIE_SOUNDS[random(#ZOMBIE_SOUNDS)] GG.PlayFogHiddenSound(z_sound, 4, x, y, z) end @@ -283,9 +291,9 @@ function gadget:Initialize() GG.Zombies = { TurnFeatureIntoUnit = TurnFeatureIntoUnit, SetZombieSpeedMult = SetZombieSpeedMult, - SetZombieBehavior = GiveZombiesRandomOrders, + SetZombieBehavior = SetZombieBehavior, GetFeatureResurrectData = GetFeatureResurrectData, GetZombieResurrectData = GetZombieResurrectData, -- I want to expose these for modification from outside if desired AddFeatureToZombieCountdown = AddFeatureToZombieCountdown -} + } end \ No newline at end of file diff --git a/LuaRules/Gadgets/unit_global_zombie_resurrect.lua b/LuaRules/Gadgets/unit_global_zombie_resurrect.lua index 4fd2fb09e2..7c28a3edd1 100644 --- a/LuaRules/Gadgets/unit_global_zombie_resurrect.lua +++ b/LuaRules/Gadgets/unit_global_zombie_resurrect.lua @@ -49,30 +49,8 @@ local spGetUnitHealth = Spring.GetUnitHealth local GaiaTeamID = Spring.GetGaiaTeamID() local GaiaAllyTeamID = select(6, Spring.GetTeamInfo(GaiaTeamID, false)) -local gameframe = 0 - -local random = math.random -local floor = math.floor - -local mapWidth -local mapHeight - local zombies = {} -local ZOMBIE_SOUNDS = { - "sounds/misc/zombie_1.wav", - "sounds/misc/zombie_2.wav", - "sounds/misc/zombie_3.wav", -} -local REZ_SOUND = "sounds/misc/resurrect.wav" - -local defined = false -- wordaround, because i meet some kind of racing condition, if any gadget spawns gaia BEFORE this gadget can process all the stuff... - -local NonZombies = { - ["asteroid"] = true, -} - -local WARNING_TIME = 5 -- seconds to start being scary before actual reanimation event local ZOMBIES_REZ_MIN = tonumber(modOptions.zombies_delay) if (tonumber(ZOMBIES_REZ_MIN) == nil) then -- minimum of 10 seconds, max is determined by rez speed @@ -99,16 +77,6 @@ end local ZOMBIES_PARTIAL_RECLAIM = (tonumber(modOptions.zombies_partial_reclaim) == 1) -local CMD_REPEAT = CMD.REPEAT -local CMD_MOVE_STATE = CMD.MOVE_STATE -local CMD_INSERT = CMD.INSERT -local CMD_FIGHT = CMD.FIGHT -local CMD_OPT_SHIFT = CMD.OPT_SHIFT -local CMD_GUARD = CMD.GUARD - -local CEG_SPAWN = [[zombie]] - - local function CheckZombieOrders() -- i can't rely on Idle because if for example unit is unloaded it doesnt count as idle... weird for unitID, _ in pairs(zombies) do local queueSize = spGetUnitCommandCount(unitID) @@ -119,7 +87,6 @@ local function CheckZombieOrders() -- i can't rely on Idle because if for exampl end function gadget:GameFrame(f) - gameframe = f if (f%640) == 1 then CheckZombieOrders() end @@ -174,26 +141,21 @@ function gadget:FeatureCreated(featureID, allyTeam) GG.Zombies.AddFeatureToZombieCountdown(featureID, ZOMBIES_REZ_SPEED, ZOMBIES_REZ_MIN, RezFrameCallback) end ---TODO unsure if this does anything -local function ReInit(reinit) - mapWidth = Game.mapSizeX - mapHeight = Game.mapSizeZ - - if (reinit) then - gameframe = spGetGameFrame() - local units = spGetAllUnits() - for i = 1, #units do - local unitID = units[i] - local unitTeam = spGetUnitTeam(unitID) - if (unitTeam == GaiaTeamID) then - gadget:UnitFinished(unitID, spGetUnitDefID(unitID), unitTeam) - end - end - local features = spGetAllFeatures() - for i = 1, #features do - gadget:FeatureCreated(features[i], 1) -- doesnt matter who is owner of feature +local function ReInit() + local units = spGetAllUnits() + for i = 1, #units do + local unitID = units[i] + local unitTeam = spGetUnitTeam(unitID) + if (unitTeam == GaiaTeamID) then + zombies[unitID] = true + GG.Zombies.SetZombieSpeedMult(unitID,ZOMBIES_PERMA_SLOW) + GG.Zombies.SetZombieBehavior(unitID) end end + local features = spGetAllFeatures() + for i = 1, #features do + GG.Zombies.AddFeatureToZombieCountdown(features[i], ZOMBIES_REZ_SPEED, ZOMBIES_REZ_MIN, RezFrameCallback) + end end function gadget:Initialize() @@ -202,10 +164,10 @@ function gadget:Initialize() return end if (spGetGameFrame() > 1) then - ReInit(true) + ReInit() end end function gadget:GameStart() - ReInit(true) -- anything it does doesnt mess with existing zombies + ReInit() -- anything it does doesnt mess with existing zombies end \ No newline at end of file