import(Module_Commands)
import(Module_Control)
import(Module_DataTypes)
import(Module_Defines)
import(Module_Draw)
import(Module_Game)
import(Module_GameStates)
import(Module_Globals)
import(Module_Helpers)
import(Module_Level)
import(Module_Map)
import(Module_MapWho)
import(Module_Objects)
import(Module_Person)
import(Module_ImGui)
import(Module_Players)
import(Module_PopScript)
import(Module_System)
import(Module_Sound)
import(Module_Table)
import(Module_Math)
import(Module_Spells)
import(Module_Network)
--include("Vehicle.lua")
--include("Swampy.lua")
include("RevivalLib.lua")
--include("Tree.lua")
--include("MagicalShield.lua")

--Global needed variables for ease.
sti = spells_type_info();
pti = people_type_info();
bti = building_type_info();
vhi = vehicle_type_info();
_gsi = gsi();
_gnsi = gnsi();
_constants = constants();

local g_DrawMenu = false

--Setup variables
initializedShamanHealth = 0
treesJustSpawned = 0
treesJustSpawnedTick = 12

--Intro Text for newer players.
messageNr = 1
messageDelay = 48
messages = {
  "Welcome to the Revival Mod! Press F11 for more information about the most important Revival changes."
}

--Spell adjustments
HillCost = 145000
TreeCost = 150000
HillShots = 2
HillRange = 2048
TreeShots = 2
TreeRange = 2048

sti[M_SPELL_EROSION].EffectModels[0] = M_EFFECT_EROSION;
sti[M_SPELL_EROSION].EffectModels[1] = M_EFFECT_VALLEY;
vhi[M_VEHICLE_AIRSHIP_1].MaxNumOccupants = 3
vhi[M_VEHICLE_AIRSHIP_2].MaxNumOccupants = 3
InvisNumPeopleAffected = 4;
maximumGhosts = 60
BloodlustNumPeopleAffected = 4;
ShieldNumPeopleAffected = 4;
_constants.ShieldCount = 135;
--Just to make sure that if these do go through it won't mess up.
_constants.InvisNumPeopleAffected = 1;
_constants.ShieldNumPeopleAffected = 1;
_constants.BloodlustNumPeopleAffected = 1;
_constants.SwampNumPeopleAffected = 1;

--Unit adjustments
pti[M_PERSON_SPY].DefaultLife = 1000

--Building adjustments
bti[M_BUILDING_SPY_TRAIN].ReqdBldgWood = 500
bti[M_BUILDING_AIRSHIP_HUT_1].ReqdBldgWood = 900
bti[M_BUILDING_AIRSHIP_HUT_2].ReqdBldgWood = 900

--buildingsTable = {}
--sproggedLocationsTable = {}

--Vehicle rework settings
vehiclesTable = {}
vehicleHealthBoat = 800
vehicleHealthBalloon = 600
--Damage values and lightning table.
lightningBolts = {}
vehicleDamageFirewarriorBlast = 50 --Firewarriors seem to be doing double damage? Perhaps per fireball
vehicleDamageBlast = 100
vehicleDamageLightning = 1050
vehicleDamageTornado = 50
vehicleDamageFirestormExplosions = 100
vehicleDamageVolcanoBigRockExplosions = 500
vehicleDamageVolcanoSmallRockExplosions = 250

--Tables for tracking things
shamFindBools = {[0]= 0, 0, 0, 0, 0, 0, 0, 0}
shamAngelDeathBools = {[0]= 0, 0, 0, 0, 0, 0, 0, 0}
shamSwarmHitBools = {[0]= 0, 0, 0, 0, 0, 0, 0, 0}
shamSwampDeathBools = {[0]= 0, 0, 0, 0, 0, 0, 0, 0}
angelTribeTable = {[0]= nil, nil, nil, nil, nil, nil, nil, nil}
angelCastTable = {[0]= 0, 0, 0, 0, 0, 0, 0, 0}
ghostsTable = {}
magicShieldTable = {}
swampyTable = {}
aliveShamans = {}
deadShamans = {}
deadShamansIntArray = {[0]= 0, 0, 0, 0, 0, 0, 0, 0}
treeObjectsTable = {}
treeSpellLocationsTable = {}
treeThingTable = {}
destroyedTreeTable = {}
dormantTreeTable = {}

--Hill magic
additionalSpellsMode = 0
hillsTable = {}
blueFlattenKey = 0
redFlattenKey = 0
yellowFlattenKey = 0
greenFlattenKey = 0
cyanFlattenKey = 0
pinkFlattenKey = 0
blackFlattenKey = 0
orangeFlattenKey = 0
blueFlattenTick = 0
redFlattenTick = 0
yellowFlattenTick = 0
greenFlattenTick = 0
cyanFlattenTick = 0
pinkFlattenTick = 0
blackFlattenTick = 0
orangeFlattenTick = 0

--Locking spell panel so we can swap guest spells and add new spells through Discoveries without resorting spell panel.
_gnsi.Flags4 = _gnsi.Flags4 | GNS4_LOCK_SPELLS_ORDER
set_spell_model_for_button(1, M_SPELL_BLAST)
set_spell_model_for_button(2, M_SPELL_CONVERT_WILD)
set_spell_model_for_button(3, M_SPELL_GHOST_ARMY)
set_spell_model_for_button(4, M_SPELL_INSECT_PLAGUE)
set_spell_model_for_button(5, M_SPELL_INVISIBILITY) 
set_spell_model_for_button(6, M_SPELL_SHIELD) 
set_spell_model_for_button(7, M_SPELL_LAND_BRIDGE) 
set_spell_model_for_button(8, M_SPELL_LIGHTNING_BOLT)
set_spell_model_for_button(9, M_SPELL_HYPNOTISM)
set_spell_model_for_button(10, M_SPELL_WHIRLWIND)
set_spell_model_for_button(11, M_SPELL_SWAMP)
set_spell_model_for_button(12, M_SPELL_FLATTEN)
set_spell_model_for_button(13, M_SPELL_EARTHQUAKE)
set_spell_model_for_button(14, M_SPELL_EROSION)
set_spell_model_for_button(15, M_SPELL_FIRESTORM)
set_spell_model_for_button(16, M_SPELL_ANGEL_OF_DEATH)
set_spell_model_for_button(17, M_SPELL_VOLCANO)

--Turning on the charging of Guest Spells for Hill and Regrowth.
_gsi.Flags = _gsi.Flags | GS_GUEST_SPELLS_CHARGE

function OnKeyUp(key)
  --9 is tab
  if (key == LB_KEY_F11) then
    g_DrawMenu = not g_DrawMenu
  end

  if (key == 9) then 
    if (_gnsi.PlayerNum == 0) then
      Send(1, "bluFlat")
      blueFlattenKey = 1
    end
    if (_gnsi.PlayerNum == 1) then
      Send(1, "redFlat")
      redFlattenKey = 1
    end
    if (_gnsi.PlayerNum == 2) then
      Send(1, "yelFlat")
      yellowFlattenKey = 1
    end
    if (_gnsi.PlayerNum == 3) then
      Send(1, "greFlat")
      greenFlattenKey = 1
    end
    if (_gnsi.PlayerNum == 4) then
      Send(1, "cyaFlat")
      cyanFlattenKey = 1
    end
    if (_gnsi.PlayerNum == 5) then
      Send(1, "pinFlat")
      pinkFlattenKey = 1
    end
    if (_gnsi.PlayerNum == 6) then
      Send(1, "blaFlat")
      blackFlattenKey = 1
    end
    if (_gnsi.PlayerNum == 7) then
      Send(1, "oraFlat")
      orangeFlattenKey = 1
    end
  end
end

function OnPacket(packet_type, data)
  if (packet_type == 0) then
    blueFlattenKey = data
  end
  if (packet_type == 1) then
    redFlattenKey = data
  end
  if (packet_type == 2) then
    yellowFlattenKey = data
  end
  if (packet_type == 3) then
    greenFlattenKey = data
  end
  if (packet_type == 4) then
    cyanFlattenKey = data
  end
  if (packet_type == 5) then
    pinkFlattenKey = data
  end
  if (packet_type == 6) then
    blackFlattenKey = data
  end
  if (packet_type == 7) then
    orangeFlattenKey = data
  end
end

function OnImGuiFrame()
    if (g_DrawMenu) then
        imgui.Begin('Revival Main Changes:', nil, ImGuiWindowFlags_AlwaysAutoResize)

        imgui.TextUnformatted("Shamans can NOT cast from Balloons or Boats")
           imgui.NextColumn()
        imgui.TextUnformatted("Shamans hit by Swarm inside a Vehicle will jump out")
        imgui.NextColumn()
        imgui.TextUnformatted("You can only have ONE Angel of Death out at any time!")
           imgui.NextColumn()
        imgui.TextUnformatted("Hill and Regrowth are new spells and are in the Guest spell slot.")
           imgui.NextColumn()
        imgui.TextUnformatted("Trees from Regrowth are permanently gone after burning.")
           imgui.NextColumn()
        imgui.TextUnformatted("Guest spells can be swapped by pressing Alt+Right-Click.")
           imgui.NextColumn()
        imgui.TextUnformatted("Pressing TAB before casting Flatten on Hill will remove the Hill")
        imgui.NextColumn()
        imgui.TextUnformatted("Swamp does heavy Damage over Time, has a 3 minute timer and can NOT be removed by yourself.")
           imgui.NextColumn()
        imgui.TextUnformatted("Ghosts now only have 100 health.")
           imgui.NextColumn()
        imgui.TextUnformatted("Flatten can heal Damaged ground and remove Swamps.")
           imgui.NextColumn()
        imgui.TextUnformatted("Invisibility, Magical Shield and Bloodlust do not stack.")
        imgui.NextColumn()
        imgui.TextUnformatted("Vehicles are slower when occupied by a Shaman.")
           imgui.NextColumn()
        imgui.TextUnformatted("Vehicles are destructable via Firewarriors and other explosive effects.")
        imgui.NextColumn()
        imgui.TextUnformatted("Shamans in vehicles get ONE SHOT by towers.")
        imgui.NextColumn()
        imgui.TextUnformatted("Magical shield is no longer immune to firewarriors, but takes reduced damage.")
        imgui.NextColumn()
        imgui.TextUnformatted("")
           imgui.NextColumn()
        imgui.TextUnformatted("Many more minor changes that can be found by experimenting or exploring the Forum thread.")
        imgui.NextColumn()
        imgui.TextUnformatted("Have Fun and Good Luck from the Revival team!")
        
        imgui.End()
    end
end

--Logic
function OnTurn()
  if (everyPow(1, 1)) then
    if (blueFlattenKey == 1) then
      blueFlattenTick = blueFlattenTick + 1
      if (blueFlattenTick >= 72) then
        blueFlattenKey = 0
        blueFlattenTick = 0
      end
    end
    if (redFlattenKey == 1) then
      redFlattenTick = redFlattenTick + 1
      if (redFlattenTick >= 72) then
        redFlattenKey = 0
        redFlattenTick = 0
      end
    end
    if (yellowFlattenKey == 1) then
      yellowFlattenTick = yellowFlattenTick + 1
      if (yellowFlattenTick >= 72) then
        yellowFlattenKey = 0
        yellowFlattenTick = 0
      end
    end
    if (greenFlattenKey == 1) then
      greenFlattenTick = greenFlattenTick + 1
      if (greenFlattenTick >= 72) then
        greenFlattenKey = 0
        greenFlattenTick = 0
      end
    end
    if (cyanFlattenKey == 1) then
      cyanFlattenTick = cyanFlattenTick + 1
      if (cyanFlattenTick >= 72) then
        cyanFlattenKey = 0
        cyanFlattenTick = 0
      end
    end
    if (pinkFlattenKey == 1) then
      pinkFlattenTick = pinkFlattenTick + 1
      if (pinkFlattenTick >= 72) then
        pinkFlattenKey = 0
        pinkFlattenTick = 0
      end
    end
    if (blackFlattenKey == 1) then
      blackFlattenTick = blackFlattenTick + 1
      if (blackFlattenTick >= 72) then
        blackFlattenKey = 0
        blackFlattenTick = 0
      end
    end
    if (orangeFlattenKey == 1) then
      orangeFlattenTick = orangeFlattenTick + 1
      if (orangeFlattenTick >= 72) then
        orangeFlattenKey = 0
        orangeFlattenTick = 0
      end
    end
  end

  if (everyPow(12, 1)) then
    for i, tick in pairs(angelCastTable) do
      if (tick > 0) then 
        tick = tick - 12
        angelCastTable[i] = tick
      end
    end

    for j, ghosts in pairs(ghostsTable) do
      ghosts:killExcessGhosts()
    end
  end

  --Handle messages for new players.
  if (everyPow(messageDelay, 1)) then
    if (messageNr <= #messages) then
      log_msg(TRIBE_NEUTRAL, messages[messageNr])
      messageNr = messageNr + 1
    end
  end

  --Initialisation
  if (initializedShamanHealth == 0) then

    --Set up Hill and Regrowth in empty guest spell slots.
    additionalSpellsMode = 0

    --Set up constants for spells
    --Spellcosts && Vehicle casting
    for i=0, 7 do
      _gsi.Players[i].LimitsSpell.Cost[M_SPELL_INVISIBILITY] = 75000
      _gsi.Players[i].LimitsSpell.Cost[M_SPELL_SHIELD] = 75000
      _gsi.Players[i].LimitsSpell.Cost[M_SPELL_ANGEL_OF_DEATH] = 600000
      _gsi.Players[i].LimitsSpell.Cost[M_SPELL_FLATTEN] = 110000
      _gsi.Players[i].LimitsSpell.Cost[M_SPELL_EARTHQUAKE] = 240000
      _gsi.Players[i].LimitsSpell.Cost[M_SPELL_EROSION] = 175000
      _gsi.Players[i].LimitsSpell.WorldCoordRange[M_SPELL_EARTHQUAKE] = 3072
      _gsi.Players[i].LimitsSpell.WorldCoordRange[M_SPELL_FLATTEN] = 2560

      if (_gsi.Players[i].Flags2 & PLF2_CANNOT_CAST_FROM_VEHICLES < 1) then
        _gsi.Players[i].Flags2 = _gsi.Players[i].Flags2 | PLF2_CANNOT_CAST_FROM_VEHICLES
      end
    end

    ProcessGlobalTypeList(T_GENERAL, function(t)
      if (t.Type == T_GENERAL) then
        if (t.Model == M_GENERAL_DISCOVERY) then
          if (t.u.Discovery.DiscoveryModel == M_SPELL_BLOODLUST and t.u.Discovery.AvailabilityType == AVAILABLE_ONCE) then
            --Armageddon is Hill
            sti[M_SPELL_ARMAGEDDON].CursorSpriteNum = sti[M_SPELL_HILL].CursorSpriteNum;
            sti[M_SPELL_ARMAGEDDON].DiscoveryDrawIdx = sti[M_SPELL_HILL].DiscoveryDrawIdx;
            sti[M_SPELL_ARMAGEDDON].AvailableSpriteIdx = sti[M_SPELL_HILL].AvailableSpriteIdx;
            sti[M_SPELL_ARMAGEDDON].NotAvailableSpriteIdx = sti[M_SPELL_HILL].NotAvailableSpriteIdx;
            sti[M_SPELL_ARMAGEDDON].ClickedSpriteIdx = sti[M_SPELL_HILL].ClickedSpriteIdx;
            sti[M_SPELL_ARMAGEDDON].ToolTipStrIdx = sti[M_SPELL_HILL].ToolTipStrIdx;
            sti[M_SPELL_ARMAGEDDON].ToolTipStrIdxLSME = sti[M_SPELL_HILL].ToolTipStrIdxLSME;
            sti[M_SPELL_ARMAGEDDON].GUIButtonId = sti[M_SPELL_ARMAGEDDON].GUIButtonId;
            sti[M_SPELL_ARMAGEDDON].CreateCastMsg = 1
                
            sti[M_SPELL_ARMAGEDDON].EffectModels[0] = M_EFFECT_HILL;
            sti[M_SPELL_ARMAGEDDON].OneOffMaximum = HillShots
            sti[M_SPELL_ARMAGEDDON].Cost = HillCost;
            sti[M_SPELL_ARMAGEDDON].OptimalChargeSecs = 240
            sti[M_SPELL_ARMAGEDDON].WorldCoordRange = HillRange
            _gsi.SpellsPresentOnLevel = _gsi.SpellsPresentOnLevel | (1 << M_SPELL_ARMAGEDDON)

            --Teleport is Tree
            sti[M_SPELL_TELEPORT].CursorSpriteNum = sti[M_SPELL_PLACE_TREE].CursorSpriteNum;
            sti[M_SPELL_TELEPORT].DiscoveryDrawIdx = sti[M_SPELL_PLACE_TREE].DiscoveryDrawIdx;
            sti[M_SPELL_TELEPORT].AvailableSpriteIdx = sti[M_SPELL_PLACE_TREE].AvailableSpriteIdx;
            sti[M_SPELL_TELEPORT].NotAvailableSpriteIdx = sti[M_SPELL_PLACE_TREE].NotAvailableSpriteIdx;
            sti[M_SPELL_TELEPORT].ClickedSpriteIdx = sti[M_SPELL_PLACE_TREE].ClickedSpriteIdx;
            sti[M_SPELL_TELEPORT].ToolTipStrIdx = sti[M_SPELL_PLACE_TREE].ToolTipStrIdx;
            sti[M_SPELL_TELEPORT].ToolTipStrIdxLSME = sti[M_SPELL_PLACE_TREE].ToolTipStrIdxLSME;
            sti[M_SPELL_TELEPORT].GUIButtonId = sti[M_SPELL_TELEPORT].GUIButtonId;
            sti[M_SPELL_TELEPORT].CreateCastMsg = 1
               
            sti[M_SPELL_TELEPORT].EffectModels[0] = M_EFFECT_PLACE_TREE;
            sti[M_SPELL_TELEPORT].OneOffMaximum = TreeShots
            sti[M_SPELL_TELEPORT].Cost = TreeCost;
            sti[M_SPELL_TELEPORT].OptimalChargeSecs = 240
            sti[M_SPELL_TELEPORT].WorldCoordRange = TreeRange
            sti[M_SPELL_TELEPORT].Flags = sti[M_SPELL_TELEPORT].Flags ~ (1<<15) 
            _gsi.SpellsPresentOnLevel = _gsi.SpellsPresentOnLevel | (1 << M_SPELL_TELEPORT)

            for i=0, 7 do
              _gsi.Players[i].LimitsSpell.Cost[M_SPELL_ARMAGEDDON] = HillCost
              _gsi.Players[i].LimitsSpell.WorldCoordRange[M_SPELL_ARMAGEDDON] = HillRange
              _gsi.Players[i].LimitsSpell.MaxCharges[M_SPELL_ARMAGEDDON] = HillShots

              _gsi.Players[i].LimitsSpell.Cost[M_SPELL_TELEPORT] = TreeCost
              _gsi.Players[i].LimitsSpell.WorldCoordRange[M_SPELL_TELEPORT] = TreeRange
              _gsi.Players[i].LimitsSpell.MaxCharges[M_SPELL_TELEPORT] = TreeShots
            end

            additionalSpellsMode = 1
          elseif (t.u.Discovery.DiscoveryModel == M_SPELL_ARMAGEDDON and t.u.Discovery.AvailabilityType == AVAILABLE_ONCE) then
            --Bloodlust is Hill
            sti[M_SPELL_BLOODLUST].CursorSpriteNum = sti[M_SPELL_HILL].CursorSpriteNum;
            sti[M_SPELL_BLOODLUST].DiscoveryDrawIdx = sti[M_SPELL_HILL].DiscoveryDrawIdx;
            sti[M_SPELL_BLOODLUST].AvailableSpriteIdx = sti[M_SPELL_HILL].AvailableSpriteIdx;
            sti[M_SPELL_BLOODLUST].NotAvailableSpriteIdx = sti[M_SPELL_HILL].NotAvailableSpriteIdx;
            sti[M_SPELL_BLOODLUST].ClickedSpriteIdx = sti[M_SPELL_HILL].ClickedSpriteIdx;
            sti[M_SPELL_BLOODLUST].ToolTipStrIdx = sti[M_SPELL_HILL].ToolTipStrIdx;
            sti[M_SPELL_BLOODLUST].ToolTipStrIdxLSME = sti[M_SPELL_HILL].ToolTipStrIdxLSME;
            sti[M_SPELL_BLOODLUST].GUIButtonId = sti[M_SPELL_BLOODLUST].GUIButtonId;
            sti[M_SPELL_BLOODLUST].CreateCastMsg = 1

            sti[M_SPELL_BLOODLUST].EffectModels[0] = M_EFFECT_HILL;
            sti[M_SPELL_BLOODLUST].OneOffMaximum = HillShots
            sti[M_SPELL_BLOODLUST].Cost = HillCost;
            sti[M_SPELL_BLOODLUST].OptimalChargeSecs = 240
            sti[M_SPELL_BLOODLUST].WorldCoordRange = HillRange
            _gsi.SpellsPresentOnLevel = _gsi.SpellsPresentOnLevel | (1 << M_SPELL_BLOODLUST)

            --Teleport is Tree
            sti[M_SPELL_TELEPORT].CursorSpriteNum = sti[M_SPELL_PLACE_TREE].CursorSpriteNum;
            sti[M_SPELL_TELEPORT].DiscoveryDrawIdx = sti[M_SPELL_PLACE_TREE].DiscoveryDrawIdx;
            sti[M_SPELL_TELEPORT].AvailableSpriteIdx = sti[M_SPELL_PLACE_TREE].AvailableSpriteIdx;
            sti[M_SPELL_TELEPORT].NotAvailableSpriteIdx = sti[M_SPELL_PLACE_TREE].NotAvailableSpriteIdx;
            sti[M_SPELL_TELEPORT].ClickedSpriteIdx = sti[M_SPELL_PLACE_TREE].ClickedSpriteIdx;
            sti[M_SPELL_TELEPORT].ToolTipStrIdx = sti[M_SPELL_PLACE_TREE].ToolTipStrIdx;
            sti[M_SPELL_TELEPORT].ToolTipStrIdxLSME = sti[M_SPELL_PLACE_TREE].ToolTipStrIdxLSME;
            sti[M_SPELL_TELEPORT].GUIButtonId = sti[M_SPELL_TELEPORT].GUIButtonId;
            sti[M_SPELL_TELEPORT].CreateCastMsg = 1
               
            sti[M_SPELL_TELEPORT].EffectModels[0] = M_EFFECT_PLACE_TREE;
            sti[M_SPELL_TELEPORT].OneOffMaximum = TreeShots
            sti[M_SPELL_TELEPORT].Cost = TreeCost;
            sti[M_SPELL_TELEPORT].OptimalChargeSecs = 240
            sti[M_SPELL_TELEPORT].WorldCoordRange = TreeRange
            sti[M_SPELL_TELEPORT].Flags = sti[M_SPELL_TELEPORT].Flags ~ (1<<15) 
            _gsi.SpellsPresentOnLevel = _gsi.SpellsPresentOnLevel | (1 << M_SPELL_TELEPORT)

            for i=0, 7 do
              _gsi.Players[i].LimitsSpell.Cost[M_SPELL_BLOODLUST] = HillCost
              _gsi.Players[i].LimitsSpell.WorldCoordRange[M_SPELL_BLOODLUST] = HillRange
              _gsi.Players[i].LimitsSpell.MaxCharges[M_SPELL_BLOODLUST] = HillShots

              _gsi.Players[i].LimitsSpell.Cost[M_SPELL_TELEPORT] = TreeCost
              _gsi.Players[i].LimitsSpell.WorldCoordRange[M_SPELL_TELEPORT] = TreeRange
              _gsi.Players[i].LimitsSpell.MaxCharges[M_SPELL_TELEPORT] = TreeShots
            end

            additionalSpellsMode = 2
          elseif (t.u.Discovery.DiscoveryModel == M_SPELL_TELEPORT and t.u.Discovery.AvailabilityType == AVAILABLE_ONCE) then
            --Bloodlust is Hill
            sti[M_SPELL_BLOODLUST].CursorSpriteNum = sti[M_SPELL_HILL].CursorSpriteNum;
            sti[M_SPELL_BLOODLUST].DiscoveryDrawIdx = sti[M_SPELL_HILL].DiscoveryDrawIdx;
            sti[M_SPELL_BLOODLUST].AvailableSpriteIdx = sti[M_SPELL_HILL].AvailableSpriteIdx;
            sti[M_SPELL_BLOODLUST].NotAvailableSpriteIdx = sti[M_SPELL_HILL].NotAvailableSpriteIdx;
            sti[M_SPELL_BLOODLUST].ClickedSpriteIdx = sti[M_SPELL_HILL].ClickedSpriteIdx;
            sti[M_SPELL_BLOODLUST].ToolTipStrIdx = sti[M_SPELL_HILL].ToolTipStrIdx;
            sti[M_SPELL_BLOODLUST].ToolTipStrIdxLSME = sti[M_SPELL_HILL].ToolTipStrIdxLSME;
            sti[M_SPELL_BLOODLUST].GUIButtonId = sti[M_SPELL_BLOODLUST].GUIButtonId;
            sti[M_SPELL_BLOODLUST].CreateCastMsg = 1

            sti[M_SPELL_BLOODLUST].EffectModels[0] = M_EFFECT_HILL;
            sti[M_SPELL_BLOODLUST].OneOffMaximum = HillShots
            sti[M_SPELL_BLOODLUST].Cost = HillCost;
            sti[M_SPELL_BLOODLUST].OptimalChargeSecs = 240
            sti[M_SPELL_BLOODLUST].WorldCoordRange = HillRange
            _gsi.SpellsPresentOnLevel = _gsi.SpellsPresentOnLevel | (1 << M_SPELL_BLOODLUST)

            --Armageddon is Tree
            sti[M_SPELL_ARMAGEDDON].CursorSpriteNum = sti[M_SPELL_PLACE_TREE].CursorSpriteNum;
            sti[M_SPELL_ARMAGEDDON].DiscoveryDrawIdx = sti[M_SPELL_PLACE_TREE].DiscoveryDrawIdx;
            sti[M_SPELL_ARMAGEDDON].AvailableSpriteIdx = sti[M_SPELL_PLACE_TREE].AvailableSpriteIdx;
            sti[M_SPELL_ARMAGEDDON].NotAvailableSpriteIdx = sti[M_SPELL_PLACE_TREE].NotAvailableSpriteIdx;
            sti[M_SPELL_ARMAGEDDON].ClickedSpriteIdx = sti[M_SPELL_PLACE_TREE].ClickedSpriteIdx;
            sti[M_SPELL_ARMAGEDDON].ToolTipStrIdx = sti[M_SPELL_PLACE_TREE].ToolTipStrIdx;
            sti[M_SPELL_ARMAGEDDON].ToolTipStrIdxLSME = sti[M_SPELL_PLACE_TREE].ToolTipStrIdxLSME;
            sti[M_SPELL_ARMAGEDDON].GUIButtonId = sti[M_SPELL_ARMAGEDDON].GUIButtonId;
            sti[M_SPELL_ARMAGEDDON].CreateCastMsg = 1

            sti[M_SPELL_ARMAGEDDON].EffectModels[0] = M_EFFECT_PLACE_TREE;
            sti[M_SPELL_ARMAGEDDON].OneOffMaximum = TreeShots
            sti[M_SPELL_ARMAGEDDON].Cost = TreeCost;
            sti[M_SPELL_ARMAGEDDON].OptimalChargeSecs = 240
            sti[M_SPELL_ARMAGEDDON].WorldCoordRange = TreeRange
            _gsi.SpellsPresentOnLevel = _gsi.SpellsPresentOnLevel | (1 << M_SPELL_ARMAGEDDON)

            for i=0, 7 do
              _gsi.Players[i].LimitsSpell.Cost[M_SPELL_BLOODLUST] = HillCost
              _gsi.Players[i].LimitsSpell.WorldCoordRange[M_SPELL_BLOODLUST] = HillRange
              _gsi.Players[i].LimitsSpell.MaxCharges[M_SPELL_BLOODLUST] = HillShots

              _gsi.Players[i].LimitsSpell.Cost[M_SPELL_ARMAGEDDON] = TreeCost
              _gsi.Players[i].LimitsSpell.WorldCoordRange[M_SPELL_ARMAGEDDON] = TreeRange
              _gsi.Players[i].LimitsSpell.MaxCharges[M_SPELL_ARMAGEDDON] = TreeShots
            end

            additionalSpellsMode = 3
          end
        end
      end
    return true
    end)

    --Didn't find any discoveries OR no guest spell discoveries set it to default mode.
    if (additionalSpellsMode == 0) then
      --Bloodlust is Hill
      sti[M_SPELL_BLOODLUST].CursorSpriteNum = sti[M_SPELL_HILL].CursorSpriteNum;
      sti[M_SPELL_BLOODLUST].DiscoveryDrawIdx = sti[M_SPELL_HILL].DiscoveryDrawIdx;
      sti[M_SPELL_BLOODLUST].AvailableSpriteIdx = sti[M_SPELL_HILL].AvailableSpriteIdx;
      sti[M_SPELL_BLOODLUST].NotAvailableSpriteIdx = sti[M_SPELL_HILL].NotAvailableSpriteIdx;
      sti[M_SPELL_BLOODLUST].ClickedSpriteIdx = sti[M_SPELL_HILL].ClickedSpriteIdx;
      sti[M_SPELL_BLOODLUST].ToolTipStrIdx = sti[M_SPELL_HILL].ToolTipStrIdx;
      sti[M_SPELL_BLOODLUST].ToolTipStrIdxLSME = sti[M_SPELL_HILL].ToolTipStrIdxLSME;
      sti[M_SPELL_BLOODLUST].GUIButtonId = sti[M_SPELL_BLOODLUST].GUIButtonId;
      sti[M_SPELL_BLOODLUST].CreateCastMsg = 1

      sti[M_SPELL_BLOODLUST].EffectModels[0] = M_EFFECT_HILL;
      sti[M_SPELL_BLOODLUST].OneOffMaximum = HillShots
      sti[M_SPELL_BLOODLUST].Cost = HillCost;
      sti[M_SPELL_BLOODLUST].OptimalChargeSecs = 240
      sti[M_SPELL_BLOODLUST].WorldCoordRange = HillRange
      _gsi.SpellsPresentOnLevel = _gsi.SpellsPresentOnLevel | (1 << M_SPELL_BLOODLUST)

      --Teleport is Tree
      sti[M_SPELL_TELEPORT].CursorSpriteNum = sti[M_SPELL_PLACE_TREE].CursorSpriteNum;
      sti[M_SPELL_TELEPORT].DiscoveryDrawIdx = sti[M_SPELL_PLACE_TREE].DiscoveryDrawIdx;
      sti[M_SPELL_TELEPORT].AvailableSpriteIdx = sti[M_SPELL_PLACE_TREE].AvailableSpriteIdx;
      sti[M_SPELL_TELEPORT].NotAvailableSpriteIdx = sti[M_SPELL_PLACE_TREE].NotAvailableSpriteIdx;
      sti[M_SPELL_TELEPORT].ClickedSpriteIdx = sti[M_SPELL_PLACE_TREE].ClickedSpriteIdx;
      sti[M_SPELL_TELEPORT].ToolTipStrIdx = sti[M_SPELL_PLACE_TREE].ToolTipStrIdx;
      sti[M_SPELL_TELEPORT].ToolTipStrIdxLSME = sti[M_SPELL_PLACE_TREE].ToolTipStrIdxLSME;
      sti[M_SPELL_TELEPORT].GUIButtonId = sti[M_SPELL_TELEPORT].GUIButtonId;
      sti[M_SPELL_TELEPORT].CreateCastMsg = 1
                
      sti[M_SPELL_TELEPORT].EffectModels[0] = M_EFFECT_PLACE_TREE;
      sti[M_SPELL_TELEPORT].OneOffMaximum = TreeShots
      sti[M_SPELL_TELEPORT].Cost = TreeCost;
      sti[M_SPELL_TELEPORT].OptimalChargeSecs = 240
      sti[M_SPELL_TELEPORT].WorldCoordRange = TreeRange
      sti[M_SPELL_TELEPORT].Flags = sti[M_SPELL_TELEPORT].Flags ~ (1<<15) 
      _gsi.SpellsPresentOnLevel = _gsi.SpellsPresentOnLevel | (1 << M_SPELL_TELEPORT)

      for i=0, 7 do
        _gsi.Players[i].LimitsSpell.Cost[M_SPELL_BLOODLUST] = HillCost
        _gsi.Players[i].LimitsSpell.WorldCoordRange[M_SPELL_BLOODLUST] = HillRange
        _gsi.Players[i].LimitsSpell.MaxCharges[M_SPELL_BLOODLUST] = HillShots

        _gsi.Players[i].LimitsSpell.Cost[M_SPELL_TELEPORT] = TreeCost
        _gsi.Players[i].LimitsSpell.WorldCoordRange[M_SPELL_TELEPORT] = TreeRange
        _gsi.Players[i].LimitsSpell.MaxCharges[M_SPELL_TELEPORT] = TreeShots
      end

      additionalSpellsMode = 4
    end

    for i=0, 7 do
      if (additionalSpellsMode == 1) then
        _gsi.ThisLevelInfo.PlayerThings[i].SpellsAvailable = _gsi.ThisLevelInfo.PlayerThings[i].SpellsAvailable | (1 << M_SPELL_TELEPORT)

        if (_gsi.ThisLevelInfo.PlayerThings[i].SpellsNotCharging & (1 << M_SPELL_TELEPORT-1) < 1) then
          _gsi.ThisLevelInfo.PlayerThings[i].SpellsNotCharging = _gsi.ThisLevelInfo.PlayerThings[i].SpellsNotCharging ~ (1 << M_SPELL_TELEPORT-1)
        end
        
        _gsi.ThisLevelInfo.PlayerThings[i].SpellsAvailable = _gsi.ThisLevelInfo.PlayerThings[i].SpellsAvailable | (1 << M_SPELL_ARMAGEDDON)

        if (_gsi.ThisLevelInfo.PlayerThings[i].SpellsNotCharging & (1 << M_SPELL_ARMAGEDDON-1) < 1) then
          _gsi.ThisLevelInfo.PlayerThings[i].SpellsNotCharging = _gsi.ThisLevelInfo.PlayerThings[i].SpellsNotCharging ~ (1 << M_SPELL_ARMAGEDDON-1)
        end
      elseif (additionalSpellsMode == 2) then
        _gsi.ThisLevelInfo.PlayerThings[i].SpellsAvailable = _gsi.ThisLevelInfo.PlayerThings[i].SpellsAvailable | (1 << M_SPELL_TELEPORT)

        if (_gsi.ThisLevelInfo.PlayerThings[i].SpellsNotCharging & (1 << M_SPELL_TELEPORT-1) < 1) then
          _gsi.ThisLevelInfo.PlayerThings[i].SpellsNotCharging = _gsi.ThisLevelInfo.PlayerThings[i].SpellsNotCharging ~ (1 << M_SPELL_TELEPORT-1)
        end
        
        _gsi.ThisLevelInfo.PlayerThings[i].SpellsAvailable = _gsi.ThisLevelInfo.PlayerThings[i].SpellsAvailable | (1 << M_SPELL_BLOODLUST)

        if (_gsi.ThisLevelInfo.PlayerThings[i].SpellsNotCharging & (1 << M_SPELL_BLOODLUST-1) < 1) then
          _gsi.ThisLevelInfo.PlayerThings[i].SpellsNotCharging = _gsi.ThisLevelInfo.PlayerThings[i].SpellsNotCharging ~ (1 << M_SPELL_BLOODLUST-1)
        end
      elseif (additionalSpellsMode == 3) then
        _gsi.ThisLevelInfo.PlayerThings[i].SpellsAvailable = _gsi.ThisLevelInfo.PlayerThings[i].SpellsAvailable | (1 << M_SPELL_ARMAGEDDON)

        if (_gsi.ThisLevelInfo.PlayerThings[i].SpellsNotCharging & (1 << M_SPELL_ARMAGEDDON-1) < 1) then
          _gsi.ThisLevelInfo.PlayerThings[i].SpellsNotCharging = _gsi.ThisLevelInfo.PlayerThings[i].SpellsNotCharging ~ (1 << M_SPELL_ARMAGEDDON-1)
        end
        
        _gsi.ThisLevelInfo.PlayerThings[i].SpellsAvailable = _gsi.ThisLevelInfo.PlayerThings[i].SpellsAvailable | (1 << M_SPELL_BLOODLUST)

        if (_gsi.ThisLevelInfo.PlayerThings[i].SpellsNotCharging & (1 << M_SPELL_BLOODLUST-1) < 1) then
          _gsi.ThisLevelInfo.PlayerThings[i].SpellsNotCharging = _gsi.ThisLevelInfo.PlayerThings[i].SpellsNotCharging ~ (1 << M_SPELL_BLOODLUST-1)
        end
      elseif (additionalSpellsMode == 4) then
        _gsi.ThisLevelInfo.PlayerThings[i].SpellsAvailable = _gsi.ThisLevelInfo.PlayerThings[i].SpellsAvailable | (1 << M_SPELL_TELEPORT)

        if (_gsi.ThisLevelInfo.PlayerThings[i].SpellsNotCharging & (1 << M_SPELL_TELEPORT-1) < 1) then
          _gsi.ThisLevelInfo.PlayerThings[i].SpellsNotCharging = _gsi.ThisLevelInfo.PlayerThings[i].SpellsNotCharging ~ (1 << M_SPELL_TELEPORT-1)
        end
        
        _gsi.ThisLevelInfo.PlayerThings[i].SpellsAvailable = _gsi.ThisLevelInfo.PlayerThings[i].SpellsAvailable | (1 << M_SPELL_BLOODLUST)

        if (_gsi.ThisLevelInfo.PlayerThings[i].SpellsNotCharging & (1 << M_SPELL_BLOODLUST-1) < 1) then
          _gsi.ThisLevelInfo.PlayerThings[i].SpellsNotCharging = _gsi.ThisLevelInfo.PlayerThings[i].SpellsNotCharging ~ (1 << M_SPELL_BLOODLUST-1)
        end
      end
      
      --Set up shaman health and ghosts tables.
      local shaman = getShaman(i)

      if (shaman ~= nil) then
        HandleMaxShamanHealth(shaman)
        local tempGhost = Ghosts:new(nil, shaman.Owner, maximumGhosts)
        table.insert(ghostsTable, tempGhost)
      end
    end
    initializedShamanHealth = 1

    --Put any vehicles into the vehicles list just in case the map has some pre made vehicles
    ProcessGlobalTypeList(T_VEHICLE, function(t)
      if (t.Type == T_VEHICLE) then
        if (t.Model == M_VEHICLE_AIRSHIP_1 or t.Model == M_VEHICLE_AIRSHIP_2) then
          local maxHealth = math.floor(vehicleHealthBalloon * 0.75) + G_RANDOM(math.floor(vehicleHealthBalloon * 0.25))
          local vehicle = Vehicle:new(nil, t.Owner, maxHealth, t) --Simulate random unit health. The unit gets 75% of its max health for free and then the last 25% is a random range.
          table.insert(vehiclesTable, vehicle)
        end
        if (t.Model == M_VEHICLE_BOAT_1 or t.Model == M_VEHICLE_BOAT_2) then
          local maxHealth = math.floor(vehicleHealthBoat * 0.75) + G_RANDOM(math.floor(vehicleHealthBoat * 0.25))
          local vehicle = Vehicle:new(nil, t.Owner, maxHealth, t) --Simulate random unit health. The unit gets 75% of its max health for free and then the last 25% is a random range.
          table.insert(vehiclesTable, vehicle)
        end
      end
    return true
    end)
  end

  if (everyPow(36, 1)) then
    for i, sham in pairs(aliveShamans) do
      if (sham ~= nil) then
        shamFindBools[sham.Owner] = 1
      end
    end

    for i=0, 7 do
      if (shamFindBools[i] == 0) then
        local sham  = getShaman(i)
        if (sham ~= nil) then
          table.insert(aliveShamans, sham)
        end
      end
    end

    shamFindBools = {[0]= 0, 0, 0, 0, 0, 0, 0, 0}
  end

  if (everyPow(1, 1)) then
    for i, sham in pairs(aliveShamans) do
      if (sham ~= nil) then
        if (sham.State == S_PERSON_AOD2_VICTIM) then
          shamAngelDeathBools[sham.Owner] = 1
        end
      end
    end

    for i, sham in pairs(aliveShamans) do
      if (sham ~= nil) then
        if (sham.State == S_PERSON_DYING or sham.State == S_PERSON_DROWNING or sham.State == S_PERSON_ELECTROCUTED or sham.State == S_PERSON_SWAMP_DROWNING) then
          table.remove(aliveShamans, i)
        else
          if ((sham.Owner == TRIBE_BLUE and shamAngelDeathBools[0] == 1) or (sham.Owner == TRIBE_RED and shamAngelDeathBools[1] == 1) or (sham.Owner == TRIBE_YELLOW and shamAngelDeathBools[2] == 1) or (sham.Owner == TRIBE_GREEN and shamAngelDeathBools[3] == 1) or (sham.Owner == TRIBE_CYAN and shamAngelDeathBools[4] == 1) or (sham.Owner == TRIBE_PINK and shamAngelDeathBools[5] == 1) or (sham.Owner == TRIBE_BLACK and shamAngelDeathBools[6] == 1) or (sham.Owner == TRIBE_ORANGE and shamAngelDeathBools[7] == 1)) then
            sham.u.Pers.u.Owned.LastDamagedBy = TRIBE_NEUTRAL
          end

          if (sham.Flags2 & TF2_IN_AIRSHIP >= 1 or is_person_in_boat(sham) == 1) then
            SearchMapCells(CIRCULAR, 0, 0, 0, world_coord3d_to_map_idx(sham.Pos.D3), function(me)
					    me.MapWhoList:processList(function(p)
						    if (p.Type == T_EFFECT) then
                  if ((p.Model == M_EFFECT_INSECT_PLAGUE or p.Model == M_EFFECT_FLY_THINGUMMY) and p.Owner ~= sham.Owner) then  
                    shamSwarmHitBools[sham.Owner] = 1             
                  end
						    end
					    return true
					    end)
				    return true
				    end)
          end
        end
      end

      for i=0, 7 do
        if (getShaman(i) == nil and shamAngelDeathBools[i] == 1) then
          shamAngelDeathBools[i] = 0
        end
      end
    end 
  end

  if (everyPow(1, 1)) then
    for i=0, 7 do
      local sham = getShaman(i)
      if(sham ~= nil) then
        --[[if (sham.u ~= nil) then
          if (sham.u.Pers ~= nil) then
            sham.u.Pers.OnDamage:clear()
            sham.u.Pers.OnDamage:add(handleFWDamage)
          end
        end--]]

        if (sham.Flags2 & TF2_IN_AIRSHIP >= 1 or is_person_in_boat(sham) == 1) then
          foundVeh = FindVehicle(sham)
          if (foundVeh ~= nil) then
            foundVeh:SetShamanInsideMe(1)
          end
        end

        if (sham.Flags2 & TF2_IN_AIRSHIP <= 0 and is_person_in_boat(sham) == 0 and is_thing_on_ground(sham) == 1 and (sham.State == S_PERSON_GOTO_BASE_AND_WAIT or sham.State == S_PERSON_UNDER_COMMAND or sham.State == S_PERSON_WAIT_AT_POINT) and shamSwarmHitBools[i] == 1) then
          set_person_new_state(sham, S_PERSON_RUN_AWAY)
          shamSwarmHitBools[i] = 0
        elseif ((sham.Flags2 & TF2_IN_AIRSHIP >= 1 or is_person_in_boat(sham) == 1) and shamSwarmHitBools[i] == 1) then
          jumpOutCommand = Commands.new()
          jumpOutCommand.CommandType = CMD_GET_OUT_OF_VEHICLE
          add_persons_command(sham, jumpOutCommand, 0)
          sham.Flags2 = sham.Flags2 ~ TF2_IN_AIRSHIP
        end
      else
        shamSwarmHitBools[i] = 0
      end
    end
  end

  for k, sham in pairs(deadShamans) do
    if (sham ~= nil) then
      if (sham.Model == M_INTERNAL_SOUL_CONVERT_2) then
        if (sham.u.SoulConvert.CurrModel == M_PERSON_MEDICINE_MAN) then
          if (sham.SubState == SS_SC2_SOUL_IN_LIMBO) then
            sham.u.SoulConvert.Count = 57
            deadShamansIntArray[sham.Owner] = deadShamansIntArray[sham.Owner] + 1

            if (deadShamansIntArray[sham.Owner] == 3) then
              deadShamansIntArray[sham.Owner] = 0
              table.remove(deadShamans, k)
            end
            
          end
        end
      end
    end
  end

  if (everyPow(1, 1)) then
    for i, veh in pairs(vehiclesTable) do
      veh:handleVehicle()
    end
  end

  if (everyPow(1, 1)) then
    for i, shd in pairs(magicShieldTable) do
      shd:handleShield()
    end
  end

  --[[if (everyPow(1, 1)) then
    for i, bldg in pairs(buildingsTable) do
      bldg:handleBuilding()
    end
  end--]]

  for i, swmp in pairs(swampyTable) do
    swmp:handleSwampy()
  end 

  --Only do it once every second to reach the 330s total
  if (everyPow(12, 1)) then
    for i, tree in pairs(treeObjectsTable) do
      tree:handleTree()
    end
  end

  for i, tree in pairs(treeObjectsTable) do
    tree:showMagic()
  end
  
  if (everyPow(12, 1)) then
    if (tableLength(destroyedTreeTable) > 0 and tableLength(dormantTreeTable) > 0) then
      for j, deadTree2D in pairs(destroyedTreeTable) do
        for k, dormantTree in pairs(dormantTreeTable) do
          if (get_world_dist_xz(deadTree2D, dormantTree.Pos.D2) <= 4096) then
            DestroyThing(dormantTree)
            table.remove(destroyedTreeTable, j)
            table.remove(dormantTreeTable, k)
          end
        end
      end
    elseif (tableLength(destroyedTreeTable) == 0) then
      dormantTreeTable = {}
    end
  end

  if (everyPow(1, 1)) then
    if (tableLength(treeThingTable) > 0) then
      for i, tree in pairs(treeThingTable) do
        local treeFromSpell = 0

        for j, spellLoc in pairs(treeSpellLocationsTable) do
          local c2dSpellLoc = Coord2D.new()
          coord3D_to_coord2D(spellLoc, c2dSpellLoc)
          if (get_world_dist_xz(tree.Pos.D2, c2dSpellLoc) <= 6000) then
            treeFromSpell = 1
          end
        end

        if (tree.u.Scenery.ResourceRemaining == 400 and treeFromSpell == 1) then
          tree.u.Scenery.ResourceRemaining = 200
          local tree = Tree:new(nil, tree.Pos.D2)
          table.insert(treeObjectsTable, tree)
          table.remove(treeThingTable, i)
        else
          table.remove(treeThingTable, i)
        end
      end
    end
  end

  --Clear the tree spell location.
  if (treesJustSpawnedTick <= 0 and treesJustSpawned == 0) then
    for i, spellLoc in pairs(treeSpellLocationsTable) do
      table.remove(treeSpellLocationsTable, i)
    end

    treesJustSpawnedTick = 12
  end

  --Add a safety net so the tree spell location doesn't get cleared by a race condition.
  if (treesJustSpawned == 1 and everyPow(1, 1)) then
    treesJustSpawnedTick = treesJustSpawnedTick - 1

    if (treesJustSpawnedTick <= 0) then
      treesJustSpawned = 0
    end
  end
end

function SetShamanSwampDeath(sham)
  shamSwampDeathBools[sham.Owner] = 1
end

function DeleteMagicShieldFromList(t)
  for i, shd in pairs(magicShieldTable) do
    if (shd == t) then
      table.remove(magicShieldTable, i)
    end
  end
end

function DeleteVehicleFromList(t)
  for i, veh in pairs(vehiclesTable) do
    if (veh == t) then
      table.remove(vehiclesTable, i)
    end
  end
end

function FindVehicle(t) 
  for i, veh in pairs(vehiclesTable) do
    if (veh ~= nil) then
      local result = veh:getVehicleByPosition(t.Pos.D2)
      
      if (result ~= nil) then
        return result
      end
    end
  end
  return nil
end

function FindShield(t)
  for i, shd in pairs(magicShieldTable) do
    if (shd ~= nil) then
      local result = shd:getShieldByPosition(t.Pos.D2)

      if (result ~= nil) then
        return result
      end
    end
  end

  return nil
end

--0 is not firewarrior, 1 is grounded firewarrior, 2 is tower firewarrior
function DamageVehicle(t, dmg, firewarriorShot)
  local targetVehicle = nil
  local foundVehicle = 0

  if (t.Type == T_SHOT) then
    SearchMapCells(CIRCULAR, 0, 0, 1, world_coord3d_to_map_idx(t.u.Shot.TargetCoord), function(me)
      me.MapWhoList:processList(function(p)
          if (p.Type == T_VEHICLE and p.Owner ~= t.Owner and foundVehicle == 0) then
            targetVehicle = FindVehicle(p)
            
            if (targetVehicle ~= nil) then
              foundVehicle = 1

              if (firewarriorShot >= 1) then
                local turnWhenHit = GetTurn() + (get_world_dist_xz(targetVehicle.vehicleThing.Pos.D2, t.Pos.D2) / 342)

                if (firewarriorShot == 1) then
                  targetVehicle:AddFireball(turnWhenHit, 0, t.Owner)
                else
                  targetVehicle:AddFireball(turnWhenHit, 1, t.Owner)
                end 
              else
                targetVehicle:damageVehicle(dmg)
              end
              return false
            end
          end
      return true
      end)
    return true
    end)
  else
    SearchMapCells(CIRCULAR, 0, 0, 1, world_coord3d_to_map_idx(t.Pos.D3), function(me)
      me.MapWhoList:processList(function(p)
          if (p.Type == T_VEHICLE and foundVehicle == 0) then
            targetVehicle = FindVehicle(p)

            if (targetVehicle ~= nil) then
              c2d = Coord2D.new()
              coord3D_to_coord2D(p.Pos.D3, c2d)
              local dist = get_world_dist_xz(t.Pos.D2, c2d)

              foundVehicle = 1

              if (dist > 351) then
                dmg = math.floor(dmg * 0.25)
              end

              --Special Tornado checking
              if (t.Model == M_EFFECT_WW_DUST and (p.Model == M_VEHICLE_AIRSHIP_1 or p.Model == M_VEHICLE_AIRSHIP_2)) then
                local randomExplodeChance = G_RANDOM(101)
                if (randomExplodeChance <= 15) then
                  dmg = vehicleHealthBalloon + 1
                end

                --Don't damage if whirlwind is nearby only if it's ON the vehicle
                if (dist > 513) then
                  dmg = 0
                end
              end

              targetVehicle:damageVehicle(dmg)
              return false
            end
          end
      return true
      end)
    return true
    end)
  end
end

function OnSwampDrown(t_swamp, t_victim)
  if (t_swamp ~= nil and t_victim ~= nil) then
    if (t_swamp.Owner ~= t_victim.Owner) then
      if (t_victim.Model == M_PERSON_MEDICINE_MAN) then
        shamSwampDeathBools[t_victim.Owner] = 1
      end
    end
  end
end

function OnCreateThing(t)
  if (t.Type == T_VEHICLE) then
        if (t.Model == M_VEHICLE_AIRSHIP_1 or t.Model == M_VEHICLE_AIRSHIP_2) then
          local maxHealth = math.floor(vehicleHealthBalloon * 0.75) + G_RANDOM(math.floor(vehicleHealthBalloon * 0.25))
          local vehicle = Vehicle:new(nil, t.Owner, maxHealth, t) --Simulate random unit health. The unit gets 75% of its max health for free and then the last 25% is a random range.
          table.insert(vehiclesTable, vehicle)
        end
        if (t.Model == M_VEHICLE_BOAT_1 or t.Model == M_VEHICLE_BOAT_2) then
          local maxHealth = math.floor(vehicleHealthBoat * 0.75) + G_RANDOM(math.floor(vehicleHealthBoat * 0.25))
          local vehicle = Vehicle:new(nil, t.Owner, maxHealth, t) --Simulate random unit health. The unit gets 75% of its max health for free and then the last 25% is a random range.
          table.insert(vehiclesTable, vehicle)
        end
  end

  if (t.Type == T_SPELL) then
    local shamanOwner = getShaman(t.Owner)

    --Made by Kosjak, edited by Ralimurr to only disable S-clicks past certain distance.
    --[[if (t.Model == M_SPELL_LIGHTNING_BOLT) then
      if (not t.u.Spell.TargetThingIdx:isNull()) then

      local originShaman = getShaman(t.Owner)
        --Lightning radius is about 10 tiles away from the shaman not including her own tile in 3D to account for high ground.
        if (originShaman ~= nil) then
          if (t.u.Spell.TargetThingIdx:get().Model == M_PERSON_MEDICINE_MAN and get_world_dist_xyz(t.u.Spell.TargetThingIdx:get().Pos.D3, originShaman.Pos.D3) >= 512 * 7) then
            t.u.Spell.TargetThingIdx:set(0)
          end
        end
      end
    end--]]

    if (t.Model == M_SPELL_LIGHTNING_BOLT) then
      local c2d = Coord2D.new()
      coord3D_to_coord2D(t.u.Spell.TargetCoord, c2d)
      table.insert(lightningBolts, c2d)
    end

    --Check if the Tree spell is cast.
    if (additionalSpellsMode == 4 or additionalSpellsMode == 1 or additionalSpellsMode == 2) then
      if (t.Model == M_SPELL_TELEPORT) then
        HandleTree(t)
      end
    elseif (additionalSpellsMode == 3) then
      if (t.Model == M_SPELL_ARMAGEDDON) then
        HandleTree(t)
      end
    end


    if (t.Model == M_SPELL_ANGEL_OF_DEATH) then
      if (angelCastTable[t.Owner] == 0) then
        angelCastTable[t.Owner] = 36
        if (angelTribeTable[t.Owner] ~= nil) then
          if (angelTribeTable[t.Owner].u.Pers ~= nil) then
            angelTribeTable[t.Owner].u.Pers.Life = 0
          end
        end
      else
        t.Model = M_SPELL_NONE
      end
    end
  end

  if (t.Type == T_SCENERY) then
    if (t.Model == M_SCENERY_TREE_1 or t.Model == M_SCENERY_TREE_2 or t.Model == M_SCENERY_TREE_3 or t.Model == M_SCENERY_TREE_4 or t.Model == M_SCENERY_TREE_5 or t.Model == M_SCENERY_TREE_6) then
        treesJustSpawned = 1
        table.insert(treeThingTable, t)
    end

    if (t.Model == M_SCENERY_DORMANT_TREE) then
        table.insert(dormantTreeTable, t)
    end
  end

  if (t.Type == T_EFFECT) then

    if (t.Model == M_EFFECT_HILL) then
      myLocation = Coord2D.new()

	    local dumbassPosX = t.Pos.D3.Xpos
	    myLocation.Xpos = dumbassPosX
	    local dumbassPosZ = t.Pos.D3.Zpos
	    myLocation.Zpos = dumbassPosZ

      table.insert(hillsTable, myLocation)
    end
    
    if (t.Model == M_EFFECT_GHOST_ARMY) then
      HandleGhostArmy(t)
    elseif (t.Model == M_EFFECT_INVISIBILITY or t.Model == M_EFFECT_SHIELD or t.Model == M_EFFECT_BLOODLUST) then
      HandleBuffingSpells(t)
    elseif (t.Model == M_EFFECT_SWAMP) then
      HandleSwamp(t)
    elseif (t.Model == M_EFFECT_FLATTEN) then
      HandleFlatten(t)
    end

    if (t.Model == M_EFFECT_SPELL_BLAST) then
      DamageVehicle(t, vehicleDamageBlast, 0)
    end

    if (t.Model == M_EFFECT_SIMPLE_BLAST) then
      if (tableLength(lightningBolts) > 0) then
        for i, light in pairs(lightningBolts) do
          if (get_world_dist_xz(light, t.Pos.D2) <= 512) then
            DamageVehicle(t, vehicleDamageLightning, 0)
            break
          end
        end
        lightningBolts = {}
      end
    end
      
    if (t.Model == M_EFFECT_SPHERE_EXPLODE_AND_FIRE) then
      DamageVehicle(t, vehicleDamageFirestormExplosions, 0)
    end
    if (t.Model == M_EFFECT_WW_DUST) then
      DamageVehicle(t, vehicleDamageTornado, 0)
    end
    if (t.Model == M_EFFECT_EXPLOSION_3) then
      DamageVehicle(t, vehicleDamageVolcanoBigRockExplosions, 0)
    end
    if (t.Model == M_EFFECT_ROCK_EXPLOSION) then
      DamageVehicle(t, vehicleDamageVolcanoSmallRockExplosions, 0)
    end
  end

  if (t.Type == T_SHOT) then
    if (t.Model == M_SHOT_SUPER_WARRIOR) then
      local fwDmg = vehicleDamageFirewarriorBlast
      local tower = 0

      SearchMapCells(CIRCULAR, 0, 0, 0, world_coord3d_to_map_idx(t.Pos.D3), function(me)
        me.MapWhoList:processList(function(p)
          if (p.Type == T_BUILDING) then
            if (p.Model == M_BUILDING_DRUM_TOWER) then
              fwDmg = fwDmg * 3.5
              tower = 1
              return false
            end
          end

        return true
        end)
      return true
      end)

      if (tower == 1) then
        DamageVehicle(t, fwDmg, 2)
      else
        DamageVehicle(t, fwDmg, 1)
      end
    end
  end
  
  if (t.Type == T_PERSON) then
    if (t.Model == M_PERSON_MEDICINE_MAN) then
      HandleMaxShamanHealth(t)

      --Remove any swamps near me please.
      repeat
        local loopedWithoutFinding = 0
        for i, swmp in pairs(swampyTable) do
        local c2dLocation = Coord2D.new()
	      coord3D_to_coord2D(swmp.myLocation, c2dLocation)

          if (get_world_dist_xz(c2dLocation, t.Pos.D2) <= 2200) then
            swmp:deleteMe()
            loopedWithoutFinding = 1
          end
        end
      until (loopedWithoutFinding == 0)
    end

    if (t.Flags2 & TF2_THING_IS_A_GHOST_PERSON >= 1 and t.Model ~= M_PERSON_MEDICINE_MAN) then
      t.u.Pers.MaxLife = 100
      t.u.Pers.Life = 100

      for j, l in pairs(ghostsTable) do
        if (l.tribe == t.Owner) then
          ghostsTable[j]:addGhost(t)
        end
      end
    end

    if (t.Model == M_PERSON_ANGEL) then
      angelTribeTable[t.Owner] = t
    end
  end

  if (t.Type == T_INTERNAL) then
    if (t.Model == M_INTERNAL_SOUL_CONVERT_2) then
      if (t.u.SoulConvert.CurrModel == M_PERSON_MEDICINE_MAN) then
        for i=0, 7 do
          if (t.Owner == i and shamSwampDeathBools[i] == 1) then
            table.insert(deadShamans, t)
            shamSwampDeathBools[i] = 0
          end
        end
      end
    end
  end
end

function OnDeleteThing(t)
  if (t.Type == T_SCENERY) then
    if (t.Model == M_SCENERY_TREE_1 or t.Model == M_SCENERY_TREE_2 or t.Model == M_SCENERY_TREE_3 or t.Model == M_SCENERY_TREE_4 or t.Model == M_SCENERY_TREE_5 or t.Model == M_SCENERY_TREE_6) then
      --First check if tree is a spell Tree
      local permanentlyDestroyed = 0
      for i, tree in pairs(treeObjectsTable) do
        if (tree.myLocation == t.Pos.D2) then
          --I am a spell tree.
          --Is tree burning or sinking?
          if (t.State == S_SCENERY_ON_FIRE --[[or t.State == S_SCENERY_SINK--]] or t.State == S_SCENERY_FIRE) then
            --Save location in a dumb way and add to destroyedTreeTable please.
            local posX = t.Pos.D2.Xpos
            local posZ = t.Pos.D2.Zpos

            local c2d = Coord2D.new()
              
            c2d.Xpos = posX
            c2d.Zpos = posZ
            permanentlyDestroyed = 1

            table.insert(destroyedTreeTable, c2d)
            tree:deleteMe()
          else
            SearchMapCells(CIRCULAR, 0, 0, 0, world_coord3d_to_map_idx(t.Pos.D3), function(me)
                if (me.Flags & (1<<10) >= 1) then
                  --Was destroyed on a shape save location in a dumb way and add to destroyedTreeTable please.
                  local posX = t.Pos.D3.Xpos
                  local posZ = t.Pos.D3.Zpos

                  local c2d = Coord2D.new()

                  c2d.Xpos = posX
                  c2d.Zpos = posZ
                  permanentlyDestroyed = 1
                  table.insert(destroyedTreeTable, c2d)
                  tree:deleteMe()
                  return false
                end
            return true
            end)
          end

          if (permanentlyDestroyed == 1) then
            break
          end
          
          --If not permanently destroyed still destroy dormant trees because they shouldn't spawn on magic trees anymore.
          if (permanentlyDestroyed == 0 and t.State ~= S_SCENERY_SINK) then
            local posX = t.Pos.D3.Xpos
            local posZ = t.Pos.D3.Zpos

            local c2d = Coord2D.new()

            c2d.Xpos = posX
            c2d.Zpos = posZ
            permanentlyDestroyed = 1
            table.insert(destroyedTreeTable, c2d)
            --Start artficial regrowth for tree
            tree:startRegrowing()
            break
          end
        end
      end
    end
  end
end

function HandleMaxShamanHealth(t)
  if (t.u.Pers.MaxLife < 1600) then
    t.u.Pers.MaxLife = 1600
    t.u.Pers.Life = 1600
  end
end

function DeleteSwampFromList(t)
  for i, swmp in pairs(swampyTable) do
    if (swmp == t) then
      table.remove(swampyTable, i)
    end
  end
end

function DeleteTreeFromList(t)
  for i, tree in pairs(treeObjectsTable) do
    if (tree == t) then
      table.remove(treeObjectsTable, i)
    end
  end
end

function HandleSwamp(t)
  local swampLoc = Coord3D.new()
  swampLoc = t.Pos.D3

  local swampy = Swampy:new(nil, t.Owner, swampLoc)
  table.insert(swampyTable, swampy)
  
  DestroyThing(t)
end

--Whenever the tree spell is cast save the location.
function HandleTree(t)
  table.insert(treeSpellLocationsTable, t.u.Spell.TargetCoord)
end

function HandleFlatten(t)
  local castOnHill = 0

  if ((t.Owner == TRIBE_BLUE and blueFlattenKey == 1) or (t.Owner == TRIBE_RED and redFlattenKey == 1) or (t.Owner == TRIBE_YELLOW and yellowFlattenKey == 1) or (t.Owner == TRIBE_GREEN and greenFlattenKey == 1) or (t.Owner == TRIBE_CYAN and cyanFlattenKey == 1) or (t.Owner == TRIBE_PINK and pinkFlattenKey == 1) or (t.Owner == TRIBE_BLACK and blackFlattenKey == 1) or (t.Owner == TRIBE_ORANGE and orangeFlattenKey == 1)) then
    for i, hill in pairs(hillsTable) do
      if (get_world_dist_xz(t.Pos.D2, hill) <= 2048) then
        local c3d = Coord3D.new()
        coord2D_to_coord3D(hill, c3d)
        createThing(T_EFFECT, M_EFFECT_VALLEY, t.Owner, c3d, false, false)
        table.remove(hillsTable, i)
        DestroyThing(t)
        castOnHill = 1
        break
      end
    end
  end

  if (castOnHill == 0) then
    SearchMapCells(CIRCULAR, 0, 0, 4, world_coord3d_to_map_idx(t.Pos.D3), function(me)
      if (me.Flags & (1<<26) >= 1) then
        me.Flags = me.Flags ~ (1<<26)
        me.Cliff = 40
      end
    return true
    end)

    repeat
      local loopedWithoutFinding = 0

      for i, swmp in pairs(swampyTable) do
        local c2dLocation = Coord2D.new()
	      coord3D_to_coord2D(swmp.myLocation, c2dLocation)
    
        if (get_world_dist_xz(t.Pos.D2, c2dLocation) <= 2250) then
          swmp:deleteMe()
          loopedWithoutFinding = 1
        end
      end
    until(loopedWithoutFinding == 0)
  end
end

function HandleGhostArmy(t)
  local createdGhosts = 0
  local ghost = nil

  SearchMapCells(CIRCULAR, 0, 0, 1, world_coord3d_to_map_idx(t.Pos.D3), function(me)
  me.MapWhoList:processList(function(p)
    if (p.Type == T_PERSON and createdGhosts == 0) then
      if (p.Model == M_PERSON_MEDICINE_MAN and p.Owner == t.Owner) then
        ghost = createThing(T_PERSON, M_PERSON_MEDICINE_MAN, t.Owner, t.Pos.D3, false, true)

        createdGhosts = 1
        return false
      elseif (p.Model ~= M_PERSON_MEDICINE_MAN and p.Owner == t.Owner) then
        for i=1, 6 do
          ghost = createThing(T_PERSON, p.Model, t.Owner, t.Pos.D3, false, true)
        end

        createdGhosts = 1
        return false
      end
    end
    return true
    end)
    return true
    end)

    if (createdGhosts == 0) then
      for i=1, 6 do
        ghost = createThing(T_PERSON, M_PERSON_BRAVE, t.Owner, t.Pos.D3, false, true)

        createdGhosts = 1
      end 
    end
    
    c3d = Coord3D.new()

    for i = 0, 19 do
      c3d.Xpos = world_coord_start_of_cell(t.Pos.D3.Xpos) + G_RANDOM(512);
      c3d.Zpos = world_coord_start_of_cell(t.Pos.D3.Zpos) + G_RANDOM(512);
      createThing(T_EFFECT, M_EFFECT_SMOKE, TRIBE_NEUTRAL, c3d, false, false)
    end

    if (ghost ~= nil) then
      --Queueing the spell effect with a custom sound because the normal queue doesn't actually play it.
      queue_custom_sound_event(ghost, "43_ghost.wav", 255)
    end

    DestroyThing(t)
end

function HandleBuffingSpells(t)
  if (t.Model == M_EFFECT_INVISIBILITY) then
    spellModel = M_SPELL_INVISIBILITY
  elseif (t.Model == M_EFFECT_SHIELD) then
    spellModel = M_SPELL_SHIELD
  elseif (t.Model == M_EFFECT_BLOODLUST) then
    spellModel = M_SPELL_BLOODLUST
  end

  local unitsFound = {}
  local unitsFoundWithBuffs = {}
  local unitsFoundWithSameBuffs = {}
  local unitsFoundWithoutBuffs = {}

  local unitsAffected = {}
  local maxUnits = 0;
  local unitsFoundCount = 0

  if (spellModel == M_SPELL_INVISIBILITY) then
    maxUnits = InvisNumPeopleAffected;
  elseif (spellModel == M_SPELL_SHIELD) then
    maxUnits = ShieldNumPeopleAffected;
  elseif (spellModel == M_SPELL_BLOODLUST) then
    maxUnits = BloodlustNumPeopleAffected;
  end

  SearchMapCells(SQUARE, 0, 0, 1, world_coord3d_to_map_idx(t.Pos.D3), function(me)
  me.MapWhoList:processList(function(p)
    if (p.Type == T_PERSON) then
      if (p.Model ~= M_PERSON_MEDICINE_MAN and (p.Owner == t.Owner or are_players_allied(p.Owner, t.Owner) >= 1) and p.Flags2 & TF2_THING_IS_A_GHOST_PERSON == 0 and p.Model ~= M_PERSON_WILD) then
        unitsFound[unitsFoundCount] = p
        unitsFoundCount = unitsFoundCount + 1
      end
    end
  return true
  end)
  return true
  end)

  local unitsFoundWithBuffsIndex = 0
  local unitsFoundWithSameBuffsIndex = 0
  local unitsFoundWithoutBuffsIndex = 0
  
  for i=0, unitsFoundCount - 1 do
	  if (unitsFound[i].Flags2 & TF2_THING_IS_AN_INVISIBLE_PERSON >= 1 or unitsFound[i].Flags3 & TF3_SHIELD_ACTIVE >= 1 or unitsFound[i].Flags3 & TF3_BLOODLUST_ACTIVE >= 1) then
      if ((unitsFound[i].Flags2 & TF2_THING_IS_AN_INVISIBLE_PERSON >= 1 and spellModel == M_SPELL_INVISIBILITY) or (unitsFound[i].Flags3 & TF3_SHIELD_ACTIVE >= 1 and spellModel == M_SPELL_SHIELD) or (unitsFound[i].Flags3 & TF3_BLOODLUST_ACTIVE >= 1 and spellModel == M_SPELL_BLOODLUST)) then
        unitsFoundWithSameBuffs[unitsFoundWithSameBuffsIndex] = unitsFound[i]
        unitsFoundWithSameBuffsIndex = unitsFoundWithSameBuffsIndex + 1
      else
        unitsFoundWithBuffs[unitsFoundWithBuffsIndex] = unitsFound[i]
        unitsFoundWithBuffsIndex = unitsFoundWithBuffsIndex + 1
      end
    else
      unitsFoundWithoutBuffs[unitsFoundWithoutBuffsIndex] = unitsFound [i]
      unitsFoundWithoutBuffsIndex = unitsFoundWithoutBuffsIndex + 1
    end
  end 

  local withBuffsSpecialIndex = 0
  local withSameBuffsSpecialIndex = 0

  for i=0, maxUnits-1 do
	  if (tableLength(unitsFoundWithoutBuffs) >= i + 1) then
      unitsAffected[i] = unitsFoundWithoutBuffs[i]
    elseif (tableLength(unitsFoundWithBuffs) >= withBuffsSpecialIndex + 1) then
      unitsAffected[i] = unitsFoundWithBuffs[withBuffsSpecialIndex]
      withBuffsSpecialIndex = withBuffsSpecialIndex + 1
    elseif (tableLength(unitsFoundWithSameBuffs) >= withSameBuffsSpecialIndex + 1) then
      unitsAffected[i] = unitsFoundWithSameBuffs[withSameBuffsSpecialIndex]
      withSameBuffsSpecialIndex = withSameBuffsSpecialIndex + 1
    end
  end

  if (tableLength(unitsAffected) >= 1) then
    for i, pers in pairs(unitsAffected) do
      --First turn all other buff spells off.
      if (pers.Flags2 & TF2_THING_IS_AN_INVISIBLE_PERSON >= 1) then
        pers.u.Pers.u.Owned.InvisibleCount = 0
        pers.Flags2 = pers.Flags2 ~ TF2_THING_IS_AN_INVISIBLE_PERSON -- Invis

        --If they are standing still make them move so the source code actually applies the invisibility effect...
        if (pers.State == S_PERSON_NONE or pers.State == S_PERSON_WAIT_AT_POINT) then
          command_person_go_to_coord2d(pers, pers.Pos.D2)
          remove_all_persons_commands(pers)
        end
      end
      if (pers.Flags3 & TF3_SHIELD_ACTIVE >= 1) then
        local targetShield = FindShield(pers)
        
        if (targetShield ~= nil) then
          targetShield:removeShield()
        end
      end
      if (pers.Flags3 & TF3_BLOODLUST_ACTIVE >= 1) then
        pers.u.Pers.u.Owned.BloodlustCount = 0
        pers.Flags3 = pers.Flags3 ~ TF3_BLOODLUST_ACTIVE --Bloodlust
      end

      if (spellModel == M_SPELL_INVISIBILITY) then
        pers.Flags2 = pers.Flags2 | TF2_THING_IS_AN_INVISIBLE_PERSON
        pers.u.Pers.u.Owned.InvisibleCount = _constants.InvisibleCount
       
        --If they are standing still make them move so the source code actually applies the invisibility effect visually...
        if (pers.State == S_PERSON_NONE or pers.State == S_PERSON_WAIT_AT_POINT) then
          command_person_go_to_coord2d(pers, pers.Pos.D2)
          remove_all_persons_commands(pers)
        end
      elseif (spellModel == M_SPELL_SHIELD) then
        local magicShieldDude = MagicShield:new(nil, pers, pers.u.Pers.MaxLife, _constants.ShieldCount)
        pers.u.Pers.OnDamage:add(handleFWDamage)
        table.insert(magicShieldTable, magicShieldDude)

        --Only give buffs if they don't already have shield.
        --if (pers.Flags3 & TF3_SHIELD_ACTIVE < 1) then
          local extraHealth = math.floor(pers.u.Pers.MaxLife * 0.5)
          pers.u.Pers.MaxLife = pers.u.Pers.MaxLife + extraHealth
          pers.u.Pers.Life = pers.u.Pers.MaxLife
          pers.Flags2 = pers.Flags2 ~ TF2_EXTERNALLY_MOVEABLE
        --end

        --Give Magic Shield buffs
        pers.Flags3 = pers.Flags3 | TF3_SHIELD_ACTIVE
        pers.u.Pers.u.Owned.ShieldCount = _constants.ShieldCount

      elseif (spellModel == M_SPELL_BLOODLUST) then
        pers.Flags3 = pers.Flags3 | TF3_BLOODLUST_ACTIVE
        pers.u.Pers.u.Owned.BloodlustCount = _constants.BloodlustCount
      end
    end
  end

  DestroyThing(t)
end

function handleFWDamage(victim, damaging_player, damage_value, ignore_shield)
  --for i, shd in pairs(magicShieldTable) do
    --if (victim == shd.thing) then
      if (ignore_shield == 0 and victim.Flags3 & TF3_SHIELD_ACTIVE >= 1) then
        victim.u.Pers.Life = victim.u.Pers.Life - math.floor((damage_value * 0.5))
      end

      --[[if (ignore_shield == 0 and victim.Model == M_PERSON_MEDICINE_MAN) then
        if (victim.Flags2 & TF2_IN_AIRSHIP >= 1 or is_person_in_boat(victim) == 1) then
          --victim.u.Pers.Life = victim.u.Pers.Life - 1500
        end
      end--]]
    --end
  --end
  return 0
end

function tableLength(Table)
  local count = 0
  for _ in pairs(Table) do count = count + 1 end
  return count
end