--------------------------------------------------------------

-- L_BOSS_SPIDER_QUEEN_ENEMY_SERVER.lua

-- Server side Spider Queen Boss fight behavior script
-- created abeechler ... 5/12/11

--------------------------------------------------------------

local spiderWaveCntTable = {2, 3}             -- The number of Dark Spiderling enemies to spawn per indexed wave number

local ROFImpactCnt = 2                        -- The number of ROF impacts in each quadrant of the arena selected at random

local SpiderlingID = 16197                    -- Reference obj ID for hatched Spiderlings

local hatchCounter = 0                        -- Global counter mainting visibility over how many eggs we have prepped to hatch per wave
local hatchList = {}                          -- Global list maintaining a record of all the eggs we have prepped to hatch for a wave

local defaultFacingZone = "Zone3Vol"          -- Maintains a default facing to ensure appropriate Spider Boss positioning for teleported players
local inZoneTable = {}                        -- Keeps track through player ID index what quadrant of the zone the player is in now
local fromZoneTable = {}                      -- Keeps track through player ID index what quadrant of the zone the player was last in

local defaultAnimPause = 2.5                  -- Default period of time to pause between missing animation actions
local spiderJeerAnim = "taunt"                -- Animation Spider Boss plays to antagonize the player
local spiderROFAnim = "attack-fire"           -- Animation Spider Boss plays to telegraph the ROF attack
local spiderWithdrawAnim = "withdraw"         -- Animation Spider Boss plays to withdraw to the top of the mountain
local spiderAdvanceAnim = "advance"           -- Animation Spider Boss plays to come back down from the mountain
local spiderWithdrawIdle = "idle-withdrawn"   -- Animation Spider Boss plays to idle on the mountain
local spiderShootLeft = "attack-shoot-left"   -- Animation Spider Boss plays to RFS shoot CCW
local spiderShootRght = "attack-shoot-right"  -- Animation Spider Boss plays to RFS shoot CW
local spiderSingleShot = "attack-fire-single" -- Animation Spider Boss plays for a single shot

local bossBulletSkill = 303                   -- Generic Spider Boss long range attack
local bossSmashSkill = 322                    -- Generic Spider Boss short range attack
local bossQueueSkill = 1568                   -- Empty skill to queue for special attack timing
local bossLandingSkill = 1539                 -- Generic Spider Boss landing attack
local bossSwipeSkill = 	1573				  -- Generic Spider Boss landing attack

local smashSkillLength = 3.1                  -- Time (in seconds) the boss smash skill lasts

local s1DelayMin = 10                         -- Minimum time until calling for another Rapid Fire Shot
local s1DelayMax = 15                         -- Maximum time until calling for another Rapid Fire Shot
local s2DelayMin = 10                         -- Minimum time until calling for another Rain Of Fire
local s2DelayMax = 15                         -- Maximum time until calling for another Rain Of Fire

local instanceZoneID = 1102                   -- Zone ID for the Spider Queen fight instance
local instanceMissionID = 1941                -- Achievement to update for beating the instanced Boss
     
-- Establishes a link for the Spider to identify rapid fire targets based on zone reference
local rapidFireTargetTable = {
                                ["Zone1Vol"] = {"Zone8Targets", "Zone1Targets", "Zone2Targets"},
                                ["Zone2Vol"] = {"Zone1Targets", "Zone2Targets", "Zone3Targets"},
                                ["Zone3Vol"] = {"Zone2Targets", "Zone3Targets", "Zone4Targets"},
                                ["Zone4Vol"] = {"Zone3Targets", "Zone4Targets", "Zone5Targets"},
                                ["Zone5Vol"] = {"Zone4Targets", "Zone5Targets", "Zone6Targets"},
                                ["Zone6Vol"] = {"Zone5Targets", "Zone6Targets", "Zone7Targets"},
                                ["Zone7Vol"] = {"Zone6Targets", "Zone7Targets", "Zone8Targets"},
                                ["Zone8Vol"] = {"Zone7Targets", "Zone8Targets", "Zone1Targets"}
                             }

----------------------------------------------------------------
-- On Startup, process necessary AI events
----------------------------------------------------------------
function onStartup(self)
    -- Make immune to stuns
    self:SetStunImmunity{StateChangeType = "PUSH", bImmuneToStunAttack = true, bImmuneToStunMove = true, bImmuneToStunTurn = true, bImmuneToStunUseItem = true, bImmuneToStunEquip = true, bImmuneToStunInteract = true, bImmuneToStunJump = true} 
    -- Make immune to knockbacks and pulls
    self:SetStatusImmunity{StateChangeType = "PUSH", bImmuneToPullToPoint = true, bImmuneToKnockback = true, bImmuneToInterrupt = true}
    -- A state suspending scripted movement AI
    self:SetVar("Set.SuspendLuaMovementAI", true)   
    -- A state suspending scripted AI
    self:SetVar("Set.SuspendLuaAI", true)    
    
    -- Seed random pulls
    math.randomseed(os.time())
    
    -- Determine Spider Boss health transition thresholds
    local spiderBossHealth = self:GetMaxHealth().health
    local transitionTickHealth = spiderBossHealth / 3
    
    local Stage2HealthThreshold = spiderBossHealth - transitionTickHealth
    local Stage3HealthThreshold = spiderBossHealth - (2 * transitionTickHealth)
    local ThresholdTable = {Stage2HealthThreshold, Stage3HealthThreshold}
    
    self:SetVar("ThresholdTable", ThresholdTable)
    self:SetVar("CurrentBossStage", 1)

    -- Obtain faction and collision group to save for subsequent resets
    self:SetVar("SBFactionList", self:GetFaction().factionList)
    self:SetVar("SBCollisionGroup", self:GetCollisionGroup().colGroup)
end

----------------------------------------------
-- Object instantiation catch
----------------------------------------------
function onPhysicsComponentReady(self, msg) 
	-- Stop our initial ability to attack
	ToggleAttacking(self, false)
	
end

----------------------------------------------
-- Sent when it is time to procure zone script data
----------------------------------------------
function onFireEventServerSide(self, msg) 
    if msg.args == "QueryZoneScript" then
        -- Query our containing property for the IDs of various spawner networks
	    --
	    -- RESULT: This Object now has a variable set called "SpiderEggNetworkID" 
	    -- 		   that contains the spider egg spawn network ID
	    --
	    --         This Object now has a variable set called "ROFTargetGroupIDTable"
	    --         that contains the IDs of the various ROF Target Group spawners
	    --
	    --         This Object now has a variable set called "LandingTarget"
	    --         that contains a reference to the object that will cast the landing skill
	    --
	    --         This Object now has a variable set called "ScreamEmitter"
	    --         that contains a reference to the object that will project the Spider's mountain scream
	    GAMEOBJ:GetZoneControlID():FireEvent{senderID=self, args="RetrieveZoneData"}
	    
    end
    
end

----------------------------------------------
-- Catch Spiderling clean-up events
----------------------------------------------
function onFireEvent(self, msg)
	-- Receive the sending object ID and the message to parse
	local eventType = msg.args
	-- Missing a valid event type?
	if not eventType then return end
	
	if eventType == "CleanupSpiders" then
		-- The zone object is requesting we cleanup
		-- the remaining live Spiderlings
		CleanupSpiders(self)
	end
end

----------------------------------------------------------------
-- Catch Spiderling deaths in order to process when a wave ends 
----------------------------------------------------------------
function notifyDie(self, spiderlingID, msg)
    -- Get the game object of the child
	local child = spiderlingID
	
	-- Check to see if the child is Spiderling
	if(child:GetLOT().objtemplate ~= SpiderlingID) then return end
	
	-- Play the Spider Boss scream
	local screamEmitter = self:GetVar("ScreamEmitter")
	self:NotifyClientObject{name = "EmitScream", paramObj = screamEmitter}
    
    -- Grab the current wave death counter
    local deathCounter = self:GetVar("deathCounter") or 0
    
    -- Increment the counter
    deathCounter = deathCounter + 1
    self:SetVar("deathCounter", deathCounter)
    
    -- Grab the current wave count
    local currentStage = self:GetVar("CurrentBossStage") 
    local spiderWaveCnt = spiderWaveCntTable[currentStage]
    
    -- Have we finished the wave?
    if(deathCounter >= spiderWaveCnt) then
        -- Activate the Spider Boss
        WithdrawSpider(self, false)
    end
    
    -- Remove reference from the Spiderling table
    local spiderlingTable = self:GetVar("spiderlingTable") or {}
    -- Iterate through the table and find the dead Spiderling
    for i, sID in ipairs(spiderlingTable) do
        if(sID:GetID() == child:GetID()) then
            table.remove(spiderlingTable, i)
            break
        end
    end
    self:SetVar("spiderlingTable", spiderlingTable)
    
    -- Cancel the notification request
	self:SendLuaNotificationCancel{requestTarget = child, messageName = "Die"}
	
end

----------------------------------------------------------------
-- Catch Spiderling spawns to subscribe to proper notifies and de-couple
-- from parent
----------------------------------------------------------------
function onChildLoaded(self, msg)
    -- Get the game object of the child
    local child = msg.childID
    
    -- Check to see if the child is Spiderling
	if(child:GetLOT().objtemplate ~= SpiderlingID) then return end
	-- We have a Spiderling, de-couple it from the parent Spider Boss
	child:SetParentObj{bSetToSelf = true}
	-- Ensure it is the enemy of the player
    child:SetFaction{faction = 4}
    -- Receive notification on death
    self:SendLuaNotificationRequest{ requestTarget = child, messageName = "Die" }
    -- Add to a spawned Spiderling table
    local spiderlingTable = self:GetVar("spiderlingTable") or {}
    table.insert(spiderlingTable, child)
    self:SetVar("spiderlingTable", spiderlingTable)
end

----------------------------------------------------------------
-- Catch zone volume sensing collision events and process accordingly
----------------------------------------------------------------
function notifyCollisionPhantom(self, cVol, cMsg)
    -- Obtain the player that instigated the collision notify
    local player = cMsg.objectID
    local playerID = player:GetID()
    -- Obtain the name of the group the volume that was collided with is in
    local cVolGroup = string.sub(cVol:GetStoredConfigData{optionalKey = "groupID"}.configData["groupID"], 1, -2)
    
    if((cVolGroup == "AggroVol") or (cVolGroup == "TeleVol")) then
        -- How many targetable players are there?
        local currentPlayerCount = GAMEOBJ:GetZoneControlID():GetVar("numberOfPlayers") or 1
        
        -- How many people are in the safe zone?
        local aggroVol = self:GetVar("AggroVol")
        if(table.maxn(aggroVol:GetObjectsInPhysicsBounds().objects) == currentPlayerCount) then
            self:SetVar("Aggro", false)
            ToggleAttacking(self, false)
        end
        
        -- Have we teleported?
        if(cVolGroup == "TeleVol") then
            -- Update the zone tables with the default position of the player
            zoneTableUpdate(self, playerID, defaultFacingZone)
        end
        
    else
        -- Update the zone tables with the current position of the colliding player
        zoneTableUpdate(self, playerID, cVolGroup)
    end
end

----------------------------------------------------------------
-- Update the Spider Boss reference tables for current/previous
-- arena player positioning
----------------------------------------------------------------
function zoneTableUpdate(self, updatePlayer, updateZone)
    -- Update the zone tables with the current position of the update player
    local fromZoneBuf = inZoneTable[updatePlayer] or updateZone
    inZoneTable[updatePlayer] = updateZone
    fromZoneTable[updatePlayer] = fromZoneBuf
end

----------------------------------------------------------------
-- Catch volume sensing collision off events and process accordingly
----------------------------------------------------------------
function notifyOffCollisionPhantom(self, cVol, cMsg)
    -- Obtain the player that instigated the collision notify
    local player = cMsg.objectID
    local playerID = player:GetID()
    -- Obtain the name of the group the volume that was collided with is in
    local cVolGroup = string.sub(cVol:GetStoredConfigData{optionalKey = "groupID"}.configData["groupID"], 1, -2)
    
    if(cVolGroup == "AggroVol") then
        -- How many targetable players are there?
        local currentPlayerCount = GAMEOBJ:GetZoneControlID():GetVar("numberOfPlayers") or 1
        
        -- How many people are in the safe zone?
        if(table.maxn(cVol:GetObjectsInPhysicsBounds().objects) < currentPlayerCount) then
            -- The Spider Queen has a viable target - pop her attack stun state
            self:SetVar("Aggro", true)
            ToggleAttacking(self, true)
        end
        
    end
    
end

----------------------------------------------------------------
-- Register boss damaged events and process when necessary
----------------------------------------------------------------
function onHitOrHealResult(self, msg)
    -- It isn't the Spider Boss
    if msg.receiver:GetID() ~= self:GetID() then return end
    -- The Spider Boss has fallen!
    if(msg.diedAsResult) then 
        -- Immediately cancel all skill timers
        SpiderSkillManager(self, false)
        -- Run the property clearing events
        GAMEOBJ:GetTimer():AddTimerWithCancel(0.1, "Clear", self)
        -- If we are on the Spider Queen Instance, update the achievement
        if(LEVEL:GetCurrentZoneID() == instanceZoneID) then
            -- Obtain a current list of players in the zone
            local playerTable = GAMEOBJ:GetZoneControlID():GetVar("PlayerTable") or {}
            -- Iterate through the Player Table and update the appropriate 
            -- mission for each player
            for i, playerID in ipairs(playerTable) do
                local player = GAMEOBJ:GetObjectByID(playerID)
                
                if(player:Exists()) then
                    -- We have a valid player, update the mission
                    player:UpdateMissionTask{taskType = "complete", value = instanceMissionID, value2 = 1, target = self}
                end
            end
        end
        return 
    end
    
    local stoppedFlag = self:GetVar("stoppedFlag")
    local ThresholdTable = self:GetVar("ThresholdTable")
    local currentStage = self:GetVar("CurrentBossStage")
    local currentThreshold = ThresholdTable[currentStage] or 0
    
    -- Catch for instances of players being ready 
    -- before the Spider Boss was 
    if(stoppedFlag) then
        self:SetVar("Aggro", true)
        ToggleAttacking(self, true)
    end
    
    if(self:GetHealth().health <= currentThreshold) then
        local isWithdrawn = self:GetVar("isWithdrawn") or false
        if(not isWithdrawn) then 
            -- We have hit a stage threshold!
            GAMEOBJ:GetTimer():CancelAllTimers(self)
            self:CancelSkillCast()
        
            -- Is Special Attacking?
            local bSpecialAttacking = self:GetVar("isSpecialAttacking")
            if(bSpecialAttacking) then
                -- Pop any current stun states
                self:SetStunned{StateChangeType = "POP", 
                        bCantMove = true,
                        bCantJump = true,
                        bCantTurn = true,
                        bCantAttack = true,
                        bCantUseItem = true,
                        bCantEquip = true,
                        bCantInteract = true,
                        bIgnoreImmunity = true}
            end
            -- Mark as not special attacking
            self:SetVar("isSpecialAttacking", false)
            -- We no longer need to lock specials
            self:SetVar("bSpecialLock", false)
	    
	        -- Withdraw the Spider Boss
	        WithdrawSpider(self, true)
	    end
    end
    
end

----------------------------------------------
-- Toggle Spider for custom scripted events
----------------------------------------------
function ToggleForSpecial(self, bOn)
    
    local stateChange = "PUSH"
    
    if(bOn) then
        -- Stun the boss
        stateChange = "PUSH" 
    else
        -- Un-stun the boss
        stateChange = "POP" 
    end
    
    -- Update stun state
    self:SetStunned{StateChangeType = stateChange, 
                    bCantMove = true,
                    bCantJump = true,
                    bCantTurn = true,
                    bCantAttack = true,
                    bCantUseItem = true,
                    bCantEquip = true,
                    bCantInteract = true,
                    bIgnoreImmunity = true}
    
    -- Mark special attacking state
    self:SetVar("isSpecialAttacking", bOn)
    
end

----------------------------------------------
-- Toggle Spider Boss' ability to attack
---------------------------------------------
function ToggleAttacking(self, bOn)
    local stoppedFlag = self:GetVar("stoppedFlag") or false
    local stateChangeType = "POP"
    
    if(not bOn) then
        -- If we already have pushed an attack stop, prevent further pushes
        if(stoppedFlag) then return end
        
        stateChangeType = "PUSH"
        self:SetVar("stoppedFlag", true)
    else
		-- if we never pushed, don't allow a pop
		if(not stoppedFlag) then return end

        self:SetVar("stoppedFlag", false)
    end
    
    self:SetStunned{StateChangeType = stateChangeType, bCantAttack = true, bIgnoreImmunity = true}
end

----------------------------------------------
-- Transitions the Spider Boss between its two central
-- positions based on the value of bWithdrawn
----------------------------------------------
function WithdrawSpider(self, bWithdrawn)
    -- Check the boss withdrawn state
    local isWithdrawn = self:GetVar("isWithdrawn") or false
    -- Control duplicate Withdraw calls
    if((bWithdrawn and isWithdrawn) or ((not bWithdrawn) and (not isWithdrawn))) then return end
    
    -- Process the flag parameter
    if(bWithdrawn) then
        -- Move the Spider to its withdrawn location
        -- deactivating its AI Skills and providing invulnerability
        self:SetStunned{StateChangeType = "PUSH", 
                    bCantMove = true,
                    bCantJump = true,
                    bCantTurn = true,
                    bCantAttack = true,
                    bCantUseItem = true,
                    bCantEquip = true,
                    bCantInteract = true,
                    bIgnoreImmunity = true}
        
        self:NotifyClientObject{name = "SetColGroup", param1 = 10}
        -- Rotate for anim
        self:SetRotation{x = 0, y = -0.005077, z = 0, w = 0.999, bTeleport = true}
        
        -- Run the withdraw animation and prepare a timer for hatching post leap
        local animTime = playAnimAndReturnTime(self, spiderWithdrawAnim)
        local withdrawTime = animTime - 0.25
        
        -- Set faction to no longer pull player attention
        self:SetFaction{faction = -1}
        -- Grant current status immunity
        self:SetStatusImmunity{ StateChangeType = "PUSH", bImmuneToSpeed = true, bImmuneToBasicAttack = true, bImmuneToDOT = true}
        -- Prepare a timer for post leap
        GAMEOBJ:GetTimer():AddTimerWithCancel(withdrawTime, "WithdrawComplete", self)
        
    else
        -- Move the Spider to its ground location
        -- preparing its stage attacks, and removing invulnerability
        
        -- Run the advance animation and prepare a timer for resuming AI
        local animTime = playAnimAndReturnTime(self, spiderAdvanceAnim)
        local attackPause = animTime - 0.4
        -- Prepare a timer for post leap attack
        GAMEOBJ:GetTimer():AddTimerWithCancel(attackPause, "AdvanceAttack", self)
        -- Prepare a timer for post leap 
        GAMEOBJ:GetTimer():AddTimerWithCancel(animTime, "AdvanceComplete", self)
    end
    
    -- Mark the Boss' withdrawn state
    self:SetVar("isWithdrawn", bWithdrawn)
        
end

----------------------------------------------
-- Begin processing Rain Of Fire attack event
----------------------------------------------
function RunRapidFireShooter(self)
    -- Turn off the Spider Boss
    ToggleForSpecial(self, true)
    
    -- Obtain the primary attack target
    local attackFocusID = getRandomPlayer(self)
    -- Declare the attack target table
    local attackTargetTable = {}
    
    -- Obtain the primary and secondary zone targets based on the attackTarget's
    -- current zone and a random subsequent sweep direction
    local primaryZone = inZoneTable[attackFocusID]
    local secondaryTargetGroup = rapidFireTargetTable[primaryZone][2]
    
    -- Use a random numeric flag to select CW versus CCW
    -- 1 = CW, 2 = CCW
    local dirSelect = math.random(2)
    local primaryTargetGroup = 0
    local tertiaryTargetGroup = 0
    if(dirSelect == 1) then
        -- We are shooting clockwise
        primaryTargetGroup = rapidFireTargetTable[primaryZone][1]
        tertiaryTargetGroup = rapidFireTargetTable[primaryZone][3]
    else
        -- We are shooting counter-clockwise
        primaryTargetGroup = rapidFireTargetTable[primaryZone][3]
        tertiaryTargetGroup = rapidFireTargetTable[primaryZone][1]
    end
    
    local primaryTargetObjs = self:GetObjectsInGroup{group = primaryTargetGroup, ignoreSpawners = true}.objects
    local secondaryTargetObjs = self:GetObjectsInGroup{group = secondaryTargetGroup, ignoreSpawners = true}.objects
    local tertiaryTargetObjs = self:GetObjectsInGroup{group = tertiaryTargetGroup, ignoreSpawners = true}.objects
    
    -- Prepare for sorting by handling the zone 1 overlap edge case.
    local keyOrderString = "CWOrder"
    if((primaryTargetGroup == "Zone1Targets") or (secondaryTargetGroup == "Zone1Targets") or (tertiaryTargetGroup == "Zone1Targets") or
       (primaryTargetGroup == "Zone8Targets") or (secondaryTargetGroup == "Zone8Targets") or (tertiaryTargetGroup == "Zone8Targets")) then
        keyOrderString = "CWOrder2"
    end
    
    local sortTables = {primaryTargetObjs, secondaryTargetObjs, tertiaryTargetObjs}
    for i, sTable in ipairs(sortTables) do
        -- Establish a sort list
        local toSort = {}
        
        -- Format the table to sort
        for j, tableObj in ipairs(sTable) do
            local objPriority = tableObj:GetStoredConfigData{optionalKey = keyOrderString}.configData[keyOrderString]
            table.insert(toSort, {tableObj = tableObj, objPriority = objPriority})
        end
        
        -- Sort the table
        local sortFunction = 0
        if(dirSelect == 1) then
            -- We are sorting clockwise
            sortFunction =  function(A, B) return A.objPriority < B.objPriority end
        else
            -- We are sorting counter-clockwise
            sortFunction =  function(A, B) return A.objPriority > B.objPriority end
        end
        table.sort(toSort, sortFunction)
        
        -- Append the results to the attackTargetTable
        for k, sortObj in ipairs(toSort) do
            if((i ~= 2) or ((k ~= 1) and (k ~= table.maxn(toSort)))) then
                table.insert(attackTargetTable, sortObj.tableObj)
            end
        end
        
    end
    
    -- Save the attack target list results
    self:SetVar("attackTargetTable", attackTargetTable)
    -- Initiate the RFS Manager
    -- Rotate towards the target
    local rofFireFlag = self:CastSkill{skillID = 1480, optionalTargetID = attackTargetTable[4]}
    -- Run the RFS
    RapidFireShooterManager(self)
    
    local spiderRFSShootingAnim = spiderSingleShot
    if(dirSelect == 1) then
        -- We are shooting clockwise
        spiderRFSShootingAnim = spiderShootRght
    else
        -- We are shooting counter-clockwise
        spiderRFSShootingAnim = spiderShootLeft
    end
    local animTime = playAnimAndReturnTime(self, spiderRFSShootingAnim)

end

----------------------------------------------
-- Manage Rapid Fire Shooter attack event
----------------------------------------------
function RapidFireShooterManager(self)
    -- Obtain the attack target list
    local attackTargetTable = self:GetVar("attackTargetTable") or {}
    
    if(table.maxn(attackTargetTable) > 0) then
        -- Fire the rapid fire shot skill on our first element
        local rofFireFlag = self:CastSkill{skillID = 1394, optionalTargetID = attackTargetTable[1]}
        -- Remove it from our target list
        table.remove(attackTargetTable, 1)
        -- Save the target list results
        self:SetVar("attackTargetTable", attackTargetTable)
        
        GAMEOBJ:GetTimer():AddTimerWithCancel(0.3, "PollRFSManager", self)
    else
        -- Jeer the player, telegraphing finish
        local animTime = playAnimAndReturnTime(self, spiderJeerAnim)
        GAMEOBJ:GetTimer():AddTimerWithCancel(animTime, "RFSTauntComplete", self)
       
    end

end

----------------------------------------------
-- Begin processing Rain Of Fire attack event
----------------------------------------------
function RunRainOfFire(self)
    -- Turn off the Spider Boss
    ToggleForSpecial(self, true)
    -- Push the ROF attack stun state
    self:SetStunned{StateChangeType = "PUSH", bCantAttack = true, bIgnoreImmunity = true}                
    
    -- The Spider Queen Boss has executed a ROF Attack
    local ROFTargetGroupIDTable = self:GetVar("ROFTargetGroupIDTable")
    if(not ROFTargetGroupIDTable) then return end
    
    -- Build a list of all the desired impact 
    -- locations for this attack
    local impactList = {}
    
    -- Iterate through the ROF Target groups, placing random elements from each
    -- in the impact list, save the first list - which selects all its targets
    for i, trgtGrp in ipairs(ROFTargetGroupIDTable) do
        -- Obtain the group targets
        local targetList = trgtGrp:SpawnerGetAllObjectIDsSpawned().objects
        
        -- Is this the first group?
        if(i == 1) then
            -- Take all its elements
            for j, trgt in ipairs(targetList) do
                table.insert(impactList, trgt)
            end
            
        else
            -- Select a random set number of impact targets from each group
            for i = 1, ROFImpactCnt do
                -- Select a random ROF target
	            local randomTrgtLoc = math.random(table.maxn(targetList))
	            local randomTrgt = targetList[randomTrgtLoc]
	            
	            --Add it to the impact list
	            table.insert(impactList, randomTrgt)
                --Remove it from our target list
	            table.remove(targetList, randomTrgtLoc)
	    
	            if((table.maxn(targetList) <= 0)) then 
                    break
                end
            end
            
        end
    
    end
    
    -- Save the impact list results
    self:SetVar("impactList", impactList)
    -- Initiate the ROF Manager
    -- project the attack with a custom anim
    local animTime = playAnimAndReturnTime(self, spiderROFAnim)
    GAMEOBJ:GetTimer():AddTimerWithCancel(animTime, "StartROF", self)
    
end

----------------------------------------------
-- Manage ROF wave event
----------------------------------------------
function RainOfFireManager(self)
    -- Obtain the impact list
    local impactList = self:GetVar("impactList") or {}
    
    if(table.maxn(impactList) > 0) then
        -- Fire the impact skill on our first element
        impactList[1]:CastSkill{skillID = 1376}
        -- Remove it from our impact list
        table.remove(impactList, 1)
        -- Save the impact list results
        self:SetVar("impactList", impactList)
        
        GAMEOBJ:GetTimer():AddTimerWithCancel(0.5, "PollROFManager", self)
    else
        -- We have finished a round of Rain Of Fire, prep the skill manager
        -- for another potential round
        -- Pop the ROF attack stun state
        self:SetStunned{StateChangeType = "POP", bCantAttack = true, bIgnoreImmunity = true}
        
        -- Determine an appropriate random time to check our manager again 
        local spiderCooldownDelay = math.random(s2DelayMin, s2DelayMax)
        -- Set a timer based on our random cooldown determination
        -- to pulse the SpiderSkillManager again
        GAMEOBJ:GetTimer():AddTimerWithCancel(spiderCooldownDelay, "PollSpiderSkillManager", self)
    end
    
end

----------------------------------------------
-- Begin processing Spiderling wave event
----------------------------------------------
function SpawnSpiderWave(self)
	
	-- The Spider Queen Boss is withdrawing and requesting the spawn 
	-- of a hatchling wave
    local SpiderEggNetworkID = self:GetVar("SpiderEggNetworkID") or 0
    if(SpiderEggNetworkID == 0) then return end
    
    local maxSpiderEggCnt = SpiderEggNetworkID:SpawnerGetNumToMaintain().uiNum
    local spiderWaveCnt = self:GetVar("SpiderWaveCount") or 0
	    
	-- Clamp invalid Spiderling number requests to the maximum amount of eggs available
	if((spiderWaveCnt > maxSpiderEggCnt) or (spiderWaveCnt < 0)) then
	    spiderWaveCnt = maxSpiderEggCnt
	end
	
	-- Reset our wave manager reference variables
    hatchCounter = spiderWaveCnt
    hatchList = {}

    -- Run the wave manager
    SpiderWaveManager(self)
	
end

----------------------------------------------
-- Manage Spiderling wave event
----------------------------------------------
function SpiderWaveManager(self)

    local spiderWaveCnt = self:GetVar("SpiderWaveCount") or 0
    local SpiderEggNetworkID = self:GetVar("SpiderEggNetworkID") or 0
    
    -- Reset the spider egg spawner network to ensure a maximum number of eggs
	SpiderEggNetworkID:SpawnerReset()
	
    -- Obtain a list of all the eggs on the egg spawner network
	local spiderEggList = SpiderEggNetworkID:SpawnerGetAllObjectIDsSpawned().objects
	if(table.maxn(spiderEggList) <= 0) then 
        GAMEOBJ:GetTimer():AddTimerWithCancel(1, "PollSpiderWaveManager", self)
        return
    end
	
	-- A check for wave mangement across multiple spawn iterations
	if(hatchCounter < spiderWaveCnt) then
	    -- We have already prepped some objects for hatching, 
	    -- remove them from our list for random egg pulls
	    for i, sVal in ipairs(spiderEggList) do
	        if(hatchList[sVal:GetID()]) then
	            -- We have found a prepped egg, remove it from the spiderEggList
	            spiderEggList[i] = nil
	        end
	    end

	end
	
	-- Select a number of random spider eggs from the list equal to the 
	-- current number needed to complete the current wave
	for i = 1, spiderWaveCnt do
	    -- Select a random spider egg
	    local randomEggLoc = math.random(table.maxn(spiderEggList))
	    local randomEgg = spiderEggList[randomEggLoc]
	    
	    if((randomEgg) and (randomEgg:Exists())) then
	        -- Prep the selected spider egg
	        randomEgg:FireEvent{senderID=self, args="prepEgg"}
	    
	        -- Add the prepped egg to our hatchList
	        hatchList[randomEgg:GetID()] = randomEgg
	        -- Decrement the hatchCounter
	        hatchCounter = hatchCounter - 1
	    end
	    
	    -- Remove it from our spider egg list
	    table.remove(spiderEggList, randomEggLoc)
	    
	    if((table.maxn(spiderEggList) <= 0) or (hatchCounter <= 0)) then 
            break
        end
	end
	
	if(hatchCounter > 0) then
	    -- We still have more eggs to hatch, poll the SpiderWaveManager again
	    GAMEOBJ:GetTimer():AddTimerWithCancel(1, "PollSpiderWaveManager", self)
	
	else
	    -- We have successfully readied a full wave
	    -- initiate hatching!
	    for key, hatchEgg in pairs(hatchList) do
	        hatchEgg:FireEvent{senderID=self, args="hatchEgg"}
	    end
	    hatchList = {}
	    
	end
	
end

----------------------------------------------
-- Sense for Spider Boss skill casting to process timing events
----------------------------------------------
function onCastSkill(self, msg)
    if msg.skillID == bossSmashSkill then
        -- We are melee smash attacking!
        self:SetVar("bSpecialLock", true)
        -- Prepare a timer for special attack unlocking
        GAMEOBJ:GetTimer():AddTimerWithCancel(smashSkillLength, "UnlockSpecials", self)
	
	end
end

----------------------------------------------------------------
-- Manages the processing and timing of Spider Boss stage specific
-- special skill firing
----------------------------------------------------------------
function SpiderSkillManager(self, bActive)
    -- Active state?
    if(not bActive) then
        GAMEOBJ:GetTimer():CancelTimer("PollSpiderSkillManager", self)
        return
    end
    
    -- Are we committed to a special attack locking skill?
    if(self:GetVar("bSpecialLock")) then
        -- Queue the empty skill to mark when to use a special skill
        self:SetVar("bSpecialQueued", true)
        return
    end
    
    -- Are we withdrawn?
    local isWithdrawn = self:GetVar("isWithdrawn") or false
    
    if(not isWithdrawn) then
        -- Grab the current Spider stage
        local currentStage = self:GetVar("CurrentBossStage")
        
        if(currentStage == 2) then
            -- Run the Rapid Fire Shooter skill
            RunRapidFireShooter(self)

        elseif(currentStage == 3) then
            -- Run the Rain Of Fire skill
            RunRainOfFire(self)

        end 
    end
end

----------------------------------------------------------------
-- Called when timers are done
----------------------------------------------------------------
function onTimerDone(self,msg)

	if msg.name == "PollSpiderWaveManager" then
	    -- Call the manager again to attempt to finish prepping a Spiderling wave
	    -- Run the wave manager
        SpiderWaveManager(self)
    
    elseif msg.name == "PollROFManager" then
        -- Call the manager again to attempt to initiate an impact on another random location
        -- Run the ROF Manager
        RainOfFireManager(self) 
        
    elseif msg.name == "PollRFSManager" then
        -- Call the manager again to attempt to initiate a rapid fire shot at the next sequential target
        -- Run the ROF Manager
        RapidFireShooterManager(self)
    
    elseif msg.name == "StartROF" then
        -- Re-enable Spider Boss
        ToggleForSpecial(self, false)
                    
        RainOfFireManager(self)
        
    elseif msg.name == "PollSpiderSkillManager" then
        -- Call the skill manager again to attempt to run the current Spider Boss
        -- stage's special attack again
        SpiderSkillManager(self, true)
        
    elseif msg.name == "RFS" then
        RunRapidFireShooter(self)
        
    elseif msg.name == "RFSTauntComplete" then 
        -- Determine an appropriate random time to check our manager again 
        local spiderCooldownDelay = math.random(s1DelayMin, s1DelayMax)
        -- Set a timer based on our random cooldown determination
        -- to pulse the SpiderSkillManager again
        GAMEOBJ:GetTimer():AddTimerWithCancel(spiderCooldownDelay, "PollSpiderSkillManager", self)
        -- Re-enable Spider Boss
        ToggleForSpecial(self, false)
        
    elseif msg.name == "WithdrawComplete" then
        -- Play the Spider Boss' mountain idle anim
        playAnimAndReturnTime(self, spiderWithdrawIdle)
        -- The Spider Boss has retreated, hatch a wave!
        local currentStage = self:GetVar("CurrentBossStage")
        -- Prepare a Spiderling wave and initiate egg hatch events
	    self:SetVar("SpiderWaveCount", spiderWaveCntTable[currentStage])
	    SpawnSpiderWave(self)
	    
	elseif msg.name == "AdvanceAttack" then 
	    -- Fire the melee smash skill to throw players back
	    local landingTarget = self:GetVar("LandingTarget") or false
	    
	    if((landingTarget) and (landingTarget:Exists())) then
            local advSmashFlag = landingTarget:CastSkill{skillID = bossLandingSkill}
            landingTarget:PlayEmbeddedEffectOnAllClientsNearObject{radius = 100, fromObjectID = landingTarget, effectName = "camshake-bridge"}
	    end
	    
	elseif msg.name == "AdvanceComplete" then 
	    -- Reset faction and collision
	    local SBFactionList = self:GetVar("SBFactionList")
	    local SBCollisionGroup = self:GetVar("SBCollisionGroup")
	    
	    for i, fVal in ipairs(SBFactionList) do
	        if(i == 1) then
	            -- Our first faction - flush and add
	            self:SetFaction{faction = fVal}
	        else
	            -- Add
	            self:ModifyFaction{factionID = fVal, bAddFaction = true}
	        end
	    end
        
        self:NotifyClientObject{name = "SetColGroup", param1 = SBCollisionGroup}
        
	    -- Grab the current wave count
        local currentStage = self:GetVar("CurrentBossStage") 
        -- Advance to the next Boss stage
        local advancedStage = currentStage + 1
	    self:SetVar("CurrentBossStage", advancedStage)
	    -- Reset the current wave death counter
	    self:SetVar("deathCounter", 0)
        
        -- Wind up, telegraphing next round
        local animTime = playAnimAndReturnTime(self, spiderJeerAnim)
        GAMEOBJ:GetTimer():AddTimerWithCancel(animTime, "AdvanceTauntComplete", self)
    
    elseif msg.name == "AdvanceTauntComplete" then
        -- Grab the current boss stage
        local currentStage = self:GetVar("CurrentBossStage")
        -- Declare a default special Spider Boss skill cooldown
        local spiderCooldownDelay = 10
	    if(currentStage == 2) then
	        spiderCooldownDelay = math.random(s1DelayMin, s1DelayMax)
	    elseif(currentStage == 3) then
	        spiderCooldownDelay = math.random(s2DelayMin, s2DelayMax)
	    end
	    -- Set a timer based on our random cooldown determination
        -- to pulse the SpiderSkillManager
        GAMEOBJ:GetTimer():AddTimerWithCancel(spiderCooldownDelay, "PollSpiderSkillManager", self)
        
        -- Remove current status immunity
        self:SetStatusImmunity{ StateChangeType = "POP", bImmuneToSpeed = true, bImmuneToBasicAttack = true, bImmuneToDOT = true}
        
        self:SetStunned{StateChangeType = "POP", 
                    bCantMove = true,
                    bCantJump = true,
                    bCantTurn = true,
                    bCantAttack = true,
                    bCantUseItem = true,
                    bCantEquip = true,
                    bCantInteract = true,
                    bIgnoreImmunity = true}
        
    elseif  msg.name == "Clear" then
        GAMEOBJ:GetZoneControlID():FireEvent{senderID=self, args="ClearProperty"}
        GAMEOBJ:GetTimer():CancelAllTimers(self)
	
	elseif msg.name == "UnlockSpecials" then
	    -- We no longer need to lock specials
        self:SetVar("bSpecialLock", false)
        
        -- Did we queue a spcial attack?
        if(self:GetVar("bSpecialQueued")) then
            self:SetVar("bSpecialQueued", false)
            SpiderSkillManager(self, true)
        end
	    
	end
	
end

----------------------------------------------
-- Utility function capable of playing a priority 
-- animation on a target and returning either the
-- anim time, or a desired default
----------------------------------------------
function playAnimAndReturnTime(self, animID)
	-- Get the anim time
	local animTimer = self:GetAnimationTime{animationID = animID}.time 
	
	-- If we have an animation play it
	if animTimer > 0 then 
		self:PlayAnimation{animationID = animID, fPriority = 4.0}
	end
	
	-- If the anim time is less than the the default time use default
	if animTimer < defaultAnimPause then
		animTimer = defaultAnimPause
	end
	
	return animTimer
end

----------------------------------------------
-- Obtain and return a random player ID from a list
-- of all current players in the zone
----------------------------------------------
function getRandomPlayer(self)

    -- Obtain a current list of players in the zone
    local playerTable = GAMEOBJ:GetZoneControlID():GetVar("PlayerTable") or {}
    local playerTableLen = table.maxn(playerTable)
    
    local playerTarget = 0
    -- Process the list for the first random existing player from the list
    for i = 1, playerTableLen do
        -- Grab a random player from the list
        local randomPlayerLoc = math.random(playerTableLen)
	    local randomPlayer = playerTable[randomPlayerLoc] or 0
	    
	    if(GAMEOBJ:GetObjectByID(randomPlayer):Exists()) then
	        -- We have found a valid player
	        -- set and exit list processing
	        playerTarget = randomPlayer
	        break
	    else
	        -- Remove the nonexistant reference and search again
	        table.remove(playerTable, randomPlayerLoc)
	    end
    end
    
    -- Return the results
    return playerTarget
    
end

----------------------------------------------
-- Iterate through the Spiderling table and 
-- destroy any currently alive entries
----------------------------------------------
function CleanupSpiders(self)
    local spiderlingTable = self:GetVar("spiderlingTable") or {}
    -- Iterate through the table
    for k, spiderling in ipairs(spiderlingTable) do
	    spiderling:RequestDie{killerID = self, killtype = "VIOLENT"}
    end
end
