From 07de403128302fd06e04826cb595400b15a795e1 Mon Sep 17 00:00:00 2001 From: Jeremy Smitherman Date: Mon, 24 Dec 2018 20:54:30 -0600 Subject: [PATCH] Added comments, added base framework for respawning --- lib/spawner.lua | 264 +++++++++++++++++++++++++++++++++++++++--------- lib/utils.lua | 10 ++ 2 files changed, 227 insertions(+), 47 deletions(-) diff --git a/lib/spawner.lua b/lib/spawner.lua index 0b94686..a78c4c8 100644 --- a/lib/spawner.lua +++ b/lib/spawner.lua @@ -1,59 +1,229 @@ +--- spawner.lua +-- Spawning utilities for H.O.G.G.I.T. Framework + +--Groups that should be auto-respawned along with their delay, and respawn percentage +HOGGIT.zombies = {} + +-- Scheduled functions to check for respawns. This allows us to queue up functions to check for +-- dead zombies, and by keeping a reference to the scheduled functions ID we can cancel it if +-- another unit in the same group dies a short while after, and queue up another one, basically +-- batching the dead checks if you kill many units in the same group in quick succession. +HOGGIT.zombie_checks = {} + --[[ -spawner.lua - -Automatically create a table of all units in the game and create a spawner for them. +Auto generated spawner objects created from the .miz +Example usage: if you have a red coalition group in the .miz named "Bad Guys": +HOGGIT.spawners.red['Bad Guys']:Spawn() ]] +HOGGIT.spawners = {['neutral'] = {}, ['red'] = {}, ['blue'] = {}} +--[[ +Spawner object that takes a group name from the ME. +Has flexible options for spawning a group once defined. One of these are created for each group that exists +in the .miz later on (stored in HOGGIT.spawners), but you can also define your own if you'd like to have groups +that have the same template in the .miz, but have different behaviors +(like spawning in a different location, or adding respawn options) + +Example usage: With a group in your .miz with the name "Good Guys 1": +local good_guys_spawn = HOGGIT.Spawner("Good Guys 1") +local spawned_good_guys_group = good_guys_spawn:Spawn() + +@param grpName Name of a group defined in the mission editor +@return HOGGIT.Spawner +]] HOGGIT.Spawner = function(grpName) + local GroupName = grpName + + --- holds the function and args that are called when this spawner spawns a group local CallBack = {} + + --- Table/Dictionary keyed by a group object with true value if group is alive. + -- Dead groups will be set to nil and garbage collected + local SpawnedGroups = {} + + --- Respawn options + local zombie = false + local respawn_delay = 60 + local partial_death_threshold_percent = nil + local partial_death_respawn_delay = respawn_delay + return { - Spawn = function(self) - local added_grp = Group.getByName(mist.cloneGroup(grpName, true).name) - if CallBack.func then - if not CallBack.args then CallBack.args = {} end - mist.scheduleFunction(CallBack.func, {added_grp, unpack(CallBack.args)}, timer.getTime() + 1) - end - return added_grp - end, - SpawnAtPoint = function(self, point) - local vars = { - groupName = grpName, - point = point, - action = "clone" - } - - local new_group = mist.teleportToPoint(vars) - if new_group then - local name = new_group.name - if CallBack.func then + --- Spawns a copy of the "template" group in the mission editor + -- Spawns the group at the same location and with the same parameters as the 'template' in the mission editor. + -- @param self HOGGIT.Spawner object + -- @return Group + Spawn = function(self) + local added_grp = Group.getByName(mist.cloneGroup(grpName, true).name) + if CallBack.func then if not CallBack.args then CallBack.args = {} end - mist.scheduleFunction(CallBack.func, {Group.getByName(name), unpack(CallBack.args)}, timer.getTime() + 1) - end - return Group.getByName(name) - else - trigger.action.outText("Error spawning " .. grpName, 15) - end - - end, - SpawnInZone = function(self, zoneName) - local added_grp = Group.getByName(mist.cloneInZone(grpName, zoneName).name) - if CallBack.func then - if not CallBack.args then CallBack.args = {} end - mist.scheduleFunction(CallBack.func, {added_grp, unpack(CallBack.args)}, timer.getTime() + 1) - end - return added_grp - end, - OnSpawnGroup = function(self, f, args) - CallBack.func = f - CallBack.args = args - end + mist.scheduleFunction(CallBack.func, {added_grp, unpack(CallBack.args)}, timer.getTime() + 1) + end + SpawnedGroups[added_grp] = true + if zombie then HOGGIT.setZombie(HOGGIT.zombies, added_grp, self, true) end + return added_grp + end, + + --- Same as 'Spawn', but takes an SSE Vec2 (table with an X and Y keys) + -- @param self HOGGIT.Spawner object + -- @param point Vec2 + -- @return Group + SpawnAtPoint = function(self, point) + local vars = { + groupName = grpName, + point = point, + action = "clone" + } + + local new_group = mist.teleportToPoint(vars) + if new_group then + local name = new_group.name + if CallBack.func then + if not CallBack.args then CallBack.args = {} end + mist.scheduleFunction(CallBack.func, {Group.getByName(name), unpack(CallBack.args)}, timer.getTime() + 1) + end + local added_grp = Group.getByName(name) + SpawnedGroups[added_grp] = true + if zombie then HOGGIT.setZombie(HOGGIT.zombies, added_grp, self, true) end + return added_grp + else + trigger.action.outText("Error spawning " .. grpName, 15) + end + + end, + + --- Same as 'Spawn' but takes a zone name defined in the mission editor + -- @param self HOGGIT.Spawner object + -- @param zoneName Name of a zone that's defined in the mission editor + -- @return Group + SpawnInZone = function(self, zoneName) + local added_grp = Group.getByName(mist.cloneInZone(grpName, zoneName).name) + if CallBack.func then + if not CallBack.args then CallBack.args = {} end + mist.scheduleFunction(CallBack.func, {added_grp, unpack(CallBack.args)}, timer.getTime() + 1) + end + SpawnedGroups[added_grp] = true + if zombie then HOGGIT.setZombie(HOGGIT.zombies, added_grp, self, true) end + return added_grp + end, + + --- Add a function with arguments to be called when this Spawner actually spawns a unit + -- @param self HOGGIT.Spawner + -- @param f the function to be called, always called with spawned group as first param + -- @param args table of additional arguments that the callback function will be called with + OnSpawnGroup = function(self, f, args) + CallBack.func = f + CallBack.args = args + end, + + --[[ + Setup Zombies that respawn upon death! + + Adds groups to the HOGGIT.zombies table for evaulation for respawning. + @param spawner HOGGIT.Spawner object + @param respawn_delay Amount of seconds group will respawn after emitting S_EVENT_DEAD event + @param partial_death_threshold_percent Percentage of dead units in group to consider whole group dead + @param partial_death_respawn_delay Amount of time that a partially dead unit that meets the threshold percent + to be considered completely dead takes to respawn in seconds, default: respawn_delay + ]] + SetGroupRespawnOptions = function( + self, + respawn_delay, + partial_death_threshold_percent, + partial_death_respawn_delay) do + if partial_death_respawn_delay == nil and partial_death_threshold_percent ~= nil then + partial_death_respawn_delay = respawn_delay + end + + self:SetZombieOptions({ + ['zombie'] = true, + ['respawn_delay'] = respawn_delay, + ['partial_death_threshold_percent'] = respawn_delay, + ['partial_death_respawn_delay'] = partial_death_respawn_delay + }) + end + end, + + SetZombieOptions = function(self, options) + zombie = options['zombie'] + if zombie then + respawn_delay = options['respawn_delay'] + partial_death_threshold_percent = options['partial_death_threshold_percent'] + if options['partial_death_respawn_delay'] then + partial_death_respawn_delay = options['partial_death_respawn_delay'] + else + partial_death_respawn_delay = respawn_delay + end + + trigger.action.outText(GroupName .. " is now a zombie! Arggghhh!", 10) + end + end, } end -HOGGIT.spawners = {['neutral'] = {}, ['red'] = {}, ['blue'] = {}} - -for cidx, coalitionName in ipairs({'neutral', 'red', 'blue'}) do - for gidx, group in pairs(coalition.getGroups(cidx - 1)) do - HOGGIT.spawners[coalitionName][Group.getName(group)] = HOGGIT.Spawner(Group.getName(group)) +--- Setup the default table of spawners, one for every group in the .miz +HOGGIT.SetupDefaultSpawners = function() + for cidx, coalitionName in ipairs({'neutral', 'red', 'blue'}) do + for gidx, group in pairs(coalition.getGroups(cidx - 1)) do + HOGGIT.spawners[coalitionName][Group.getName(group)] = HOGGIT.Spawner(Group.getName(group)) + end end end + +HOGGIT.setZombie = function(zombie_tracker, group, spawner, state) + if state then + zombie_tracker[group] = spawner + else + zombie_tracker[group] = nil + end +end + +HOGGIT._deathHandler = function(event) + if event.id ~= world.event.S_EVENT_DEAD then return end + if not event.initiator then return end + if not event.initiator.getGroup then return end + + local grp = event.initiator:getGroup() + if grp then + if HOGGIT.zombies[grp] then + local spawner = HOGGIT.zombies[grp] + if HOGGIT.zombie_checks[spawner] then + local s_func_id = HOGGIT.zombie_checks[spawner] + mist.removeFunction(s_func_id) + trigger.action.outText("Removing dead check id ".. s_func_id .." for group: " + grp:getName()) + end + local new_func_id = mist.scheduleFunction(function() + local needs_respawn = false + local partial_respawn = false + if not HOGGIT.GroupIsAlive(grp) then + needs_respawn = true + elseif spawner.partial_death_threshold_percent then + local group_size = grp:getSize() + local initial_size = grp:getInitialSize() + local percent_alive = group_size / initial_size * 100 + + if (percent_alive - 100) >= partial_death_threshold_percent then + needs_respawn = true + partial_respawn = true + end + end + + if needs_respawn then + local delay = spawner.respawn_delay + if partial_respawn then delay = spawner.partial_death_respawn_delay end + HOGGIT.zombies[grp] = nil + + mist.scheduleFunction(function() + spawner.Spawn() + end, {}, timer.getTime() + delay) + end + HOGGIT.zombie_checks[spawner] = nil + + end, {}, timer.getTime() + 10) + + trigger.action.outText("Scheduled NEW dead check id ".. new_func_id .." for group: " .. grp:getName()) + HOGGIT.zombie_checks[spawner] = new_func_id + end + end +end + +mist.addEventHandler(HOGGIT._deathHandler) diff --git a/lib/utils.lua b/lib/utils.lua index 06d53de..5c8d126 100644 --- a/lib/utils.lua +++ b/lib/utils.lua @@ -20,3 +20,13 @@ HOGGIT.listContains = function(list, elem) return false end + +HOGGIT.GroupIsAlive = function(group) + local grp = nil + if type(group) == "string" then + grp = Group.getByName(group) + else + grp = group + end + if grp and grp:isExist() and grp:getSize() > 0 then return true else return false end +end \ No newline at end of file