

// Script by StardustSpy
::ROOT <- getroottable()
::MAX_CLIENTS <- MaxClients().tointeger() // get all players that can spawn in a server

if (!("ConstantNamingConvention" in ROOT))
	foreach(a, b in Constants)
		foreach(k, v in b)
			ROOT[k] <- v != null ? v : 0

// see "CNetPropManager" for applicable functions
foreach(k, v in ::NetProps.getclass())
	if (k != "IsValid" && !(k in ROOT))
		ROOT[k] <- ::NetProps[k].bindenv(::NetProps)



// see "CEntities" for applicable functions
foreach(k, v in ::Entities.getclass())
	if (k != "IsValid" && !(k in ROOT))
		ROOT[k] <- ::Entities[k].bindenv(::Entities)


foreach(k, v in ::EntityOutputs.getclass())
	if (k != "IsValid" && !(k in ROOT))
		ROOT[k] <- ::EntityOutputs[k].bindenv(::EntityOutputs)


foreach(k, v in ::NavMesh.getclass())
	if (k != "IsValid" && !(k in ROOT))
		ROOT[k] <- ::NavMesh[k].bindenv(::NavMesh)

PrecacheSound("weapons/cleaver_hit_world.wav")
        
::gamerules <- FindByClassname(null, "tf_gamerules")
::player_manager <- FindByClassname(null, "tf_player_manager")
::WORLD_SPAWN <- FindByClassname(null, "worldspawn")

const EFL_USER = 1048576
const SLOT_COUNT = 7
const STRING_NETPROP_ITEMDEF = "m_AttributeManager.m_Item.m_iItemDefinitionIndex"
const DEG2RAD   = 0.0174532924
const RAD2DEG   = 57.295779513
const FLT_SMALL = 0.0000001
const FLT_MIN   = 1.175494e-38
const FLT_MAX   = 3.402823466e+38
const INT_MIN   = -2147483648
const INT_MAX   = 2147483647

MULT_DAMAGE <- 
{
    damage_mult = function(player, weapon)
    {
        printl("Player: "+player)
        printl("Weapon: "+weapon)
        local d_bonus = player.GetCustomAttribute("damage bonus", 1)
        local d_bonus_wep = weapon.GetAttribute("damage bonus", 1)
        local d_penalty = player.GetCustomAttribute("damage penalty", 1)
        local d_penalty_wep = weapon.GetAttribute("damage penalty", 1)
        local d_hidden = player.GetCustomAttribute("damage bonus HIDDEN", 1)
        local d_hidden_wep = weapon.GetAttribute("damage bonus HIDDEN", 1)

        local d_card_wep = weapon.GetAttribute("CARD: damage bonus", 1)
        local d_card = player.GetCustomAttribute("CARD: damage bonus", 1)

        local total = 
        (
            d_bonus 
            * d_bonus_wep 
            * d_penalty 
            * d_penalty_wep 
            * d_hidden 
            * d_hidden_wep 
            * d_card_wep 
            * d_card
        )
        
        return total
    }
};

//Teams
::TEAM_SPECTATOR <- 1
::TEAM_RED <- 2
::TEAM_BLU <- 3

// stun
::STUN_MOVE <- 1
::STUN_MOVE_FORWARD_ONLY <- 4

::TF_NAV_SPAWN_ROOM_RED <- 2
::TF_NAV_SPAWN_ROOM_BLUE <- 4

::TF_BOT_FAKE_CLIENT <- 1337

//weapons

// Multi
::WEAPON_BASE_JUMPER <- 1101
::WEAPON_RESERVE_SHOOTER <- 415

// Scout
::WEAPON_FORCE_A_NATURE <- 45
::WEAPON_BACK_SCATTER <- 1103
::WEAPON_BABY_FACES_BLASTER <- 772

::WEAPON_BONK_ATOMIC_PUNCH <- 46
::WEAPON_CRIT_A_COLA <- 163
::WEAPON_MAD_MILK <- 222
::WEAPON_PRETTY_BOY_POCKET_PISTOL <- 773
::WEAPON_FLYING_GUILLIOTONE <- 812

::WEAPON_FANOWAR <- 355
::WEAPON_SANDMAN <- 44
::WEAPON_WRAP_ASSASSIN <- 648

// Soldier
::WEAPON_BEGGARS_BAZOOKA <- 730
::WEAPON_LIBERTY_LAUNCHER <- 414
::WEAPON_AIR_STRIKE <- 1104

::WEAPON_BUFF_BANNER <- 129
::WEAPON_BATTALIONS_BACKUP <- 226
::WEAPON_CONCHEROR <- 354
::WEAPON_BUFF_BANNER_FESTIVE <- 1001
::WEAPON_GUNBOATS <- 133
::WEAPON_MANTREADS <- 444
::WEAPON_RIGHTEOUS_BISON <- 442

// Pyro
::WEAPON_DRAGONS_FURY <- 1178
::WEAPON_RAINBLOWER <- 741
::WEAPON_PHLOGISTINATOR <- 594

::WEAPON_MANN_MELTER <- 595
::WEAPON_SHARPENED_VOLCANO_FRAGMENT <- 348

// Demo
::WEAPON_ALI_BABAS_WEE_BOOTIES <- 405
::WEAPON_BOOTLEGGER <- 608
::WEAPON_IRON_BOMBER <- 1151

::WEAPON_CHARGIN_TARGE <- 131
::WEAPON_TIDE_TURNER <- 1099
::WEAPON_SPLENDID_SCREEN <- 406
::WEAPON_CHARGIN_TARGE_FESTIVE <- 1144

::WEAPON_ULLAPOOL_CABER <- 307
::WEAPON_PERSIAN_PERSUADER <- 404

// Engineer

::WEAPON_BUILDER <- 25
::WEAPON_BUILDER_DESTROY <- 26
::WEAPON_BOX <- 28

// Sniper
::WEAPON_DARWIN_DANGER_SHIELD <- 231
::WEAPON_COZY_CAMPER <- 642 
::WEAPON_RAZORBACK <- 57

// Spy
::WEAPON_BUILDER_SAPPER <- 27
::WEAPON_INVIS_WATCH <- 30


::GiantMode_Hurricane <- 
{
    Player_Cleanup_Table = []
    Robots_Cleanup_Table = []
    MapNavAreas = {}
    Test = printl("----- Successfully Loaded Giant Scripts -----")

    TakeDamageFuncTable = {}

    // ONTAKEDAMAGE FUNCS

    BlastANatureKnockback = function()
    {
        // Blast-a-Nature Knockback fix

        if ( weapon != null && weapon.IsValid() && weapon.GetAttribute("cannot giftwrap", 0) == 1)
        {
            // if the victim is not a bot, dont run anything
            if ( !victim.IsBotOfType(TF_BOT_TYPE) )
                return 

            // if the victim has the "no_knockback" tag, dont run anything
            if ( victim.HasBotTag("no_knockback") )
                return

            local knockback_mult = 6
            
            // ANTI-GRIEF: Reduce Knockback effect if the Bomb Carrier is close to the hatch
            for ( local player; player = FindByClassname(player, "player"); )
            {
                // if player is not the bomb carrier, ignore
                if (GetPropEntity(victim, "m_hItem") == null)
                    continue
                
                local cur_nav = GetNavArea(victim.GetOrigin(), 128.0)
                local dist_to_hatch = cur_nav.GetTravelDistanceToBombTarget()
                // roughly halfway point of map
                local max_dist = 2900

                // too far away from hatch to bother
                if (dist_to_hatch > max_dist)
                    continue
                

                local knockback_value = GiantMode_Hurricane.ScaleValue(0.5, 6, dist_to_hatch, max_dist)

                knockback_mult = knockback_value

                // If the bot is deploying the bomb, allow full knockback
                local cur_sequence = victim.GetSequence()
                local sequence_name = victim.GetSequenceName(cur_sequence)
                local sequence_check = endswith(sequence_name, "_deploybomb")

                if (sequence_check == true)
                {
                    knockback_mult = 6
                }
            }

            // This is usually x6 ( 500% ) increase
            // Im 99% sure applying this attribute here doesnt do anything (tested on MvM_Hurricane, 9/07/2025)
            // as it needs to be permanetly on the FAN to do anything. But its used for our knockback calculation,
            // so leave as is.
            weapon.AddAttribute("scattergun knockback mult", knockback_mult, 0.01)

            local eye_angles = attacker.EyeAngles()
            local fwd = eye_angles.Forward()

            local scattergun_knockback = weapon.GetAttribute("scattergun knockback mult", 1)
            local knockback_resist = victim.GetCustomAttribute("damage force reduction", 1)

            local base_knockback_value = 175
            local mult = (base_knockback_value * scattergun_knockback) * knockback_resist
            local upward_mult = 80
            
            // Giants seem to be unaffected by lower z values
            if ( victim.IsMiniBoss() )
            {
                upward_mult = 270
            }

            threat.StunPlayer(0.3, 1, STUN_MOVE | STUN_MOVE_FORWARD_ONLY, self)
            victim.SetAbsVelocity( Vector(fwd.x * mult, fwd.y * mult, fwd.z + upward_mult ) )
        }
    }

    // THINKS

    // Applied to players when they spawn
    // They give their own projectiles think tables
    ApplyProjectileThink = function()
    {
        for (local projectile; projectile = FindByClassname(projectile, "tf_projectile*");) 
        {
            local owner_netprop = GetPropEntity(projectile, "m_hOwner")
            local thrower_netprop = GetPropEntity(projectile, "m_hThrower")
            local owner_launcher = GetPropEntity(projectile, "m_hLauncher")
            local owner_entity = GetPropEntity(projectile, "m_hOwnerEntity")

            projectile.ValidateScriptScope()
            local scope = projectile.GetScriptScope()

            local AddThinkBool = false
            local cur_loadout = GiantMode_Hurricane.GetLoadout(self)

            // massive if statements are confusing to look at
            // this is basically an if statement, but better to look out

            // Use this table to refer to actual values
            local entity_table =
            {
                "Owner" : owner_netprop
                "Thrower" : thrower_netprop
                "Launcher" : owner_launcher
                "Entity" : owner_entity
            }

            if ( ("HasThink" in scope ) )
            {
                return
            }

            // From entity_table, check through everything that isnt null
            // if it isnt null, check it
            foreach (string, value in entity_table)
            {
                // printl("String: "+string+" Value: "+value)

                // Dont bother with anything that isnt null
                if (value == null)
                    continue
                
                // if we find something that is ours, allow adding it
                if (value in cur_loadout || value == self)
                {
                    // printl("We found our owner: "+value)
                    AddThinkBool = true
                    break
                }  
            }

            if (AddThinkBool == false)
            {
                return
            }

            if (!("ThinkTable_Projectile" in scope)) 
            {
                scope.ThinkTable_Projectile <- {}
            }

            scope.ProjectileThink <- function() 
            { 
                foreach (name, func in scope.ThinkTable_Projectile) 
                {
                    if (func)
                    {
                        func.call(scope)
                    }
                }

                return -1 
            }
            
            scope.HasThink <- 1
            AddThinkToEnt(projectile, "ProjectileThink")

            // Add all relavent projectile thinks here
            GiantMode_Hurricane.AddProjectileThinks(projectile, self)
        }
    }

    // Helper function to convert direction vector to QAngle
    VectorToQAngle = function(direction_vector)
    {
        local pitch = asin(-direction_vector.z) * (180.0 / PI);
        local yaw = atan2(direction_vector.y, direction_vector.x) * (180.0 / PI);
        local roll = 0.0;
        return QAngle(pitch, yaw, roll);
    }

    GuillotineThink = function()
    {
        local player = owner;
        local cur_time = Time()
        local cur_origin = self.GetOrigin()
        local chest_origin = player.GetAttachmentOrigin(player.LookupAttachment("flag"))
        
        local force_return = false
        local mins = Vector(-15, -15, -150)
        local maxs = Vector(15, 15, 15)

        local sound_range = (40 + (20 * log10(4000 / 36.0))).tointeger();

        EmitSoundEx
        ({
            sound_name = "weapons/cleaver_hit_world.wav",
            origin = cur_origin,
            volume = 1
            sound_level = sound_range 
            entity = self
            filter = RECIPIENT_FILTER_GLOBAL
            flags = 4 // stop
        });
        
        if ( (timestamp + 0.5) <= cur_time )
        {

            for (local entity; entity = FindByClassnameWithin(entity, "tf_projectile*", chest_origin, 30);)
            {
                if (entity == self)
                {
                    self.Kill()
                    force_return = true
                }
            }

            // // World-space bounds of the spawnroom
            // local proj_mins = cur_origin + mins
            // local proj_maxs = cur_origin + maxs

            // // World-space bounds of the box entity
            // local ent_mins = cur_origin + player.GetBoundingMins();
            // local ent_maxs = cur_origin + player.GetBoundingMaxs();

            // // Check if all corners of the box are inside the room's bounds
            // if (ent_mins.x >= proj_mins.x && ent_maxs.x <= proj_maxs.x &&
            //     ent_mins.y >= proj_mins.y && ent_maxs.y <= proj_maxs.y &&
            //     ent_mins.z >= proj_mins.z && ent_maxs.z <= proj_maxs.z)
            // {
            //     printl("die die die")
            //     self.Kill()
            //     force_return = true
            // }
            
        }

        if (force_return == true)
        {
            return
        }


        if (!("owner" in this) || !owner.IsValid())
        {
            self.Destroy();
            return;
        }

        // projectile should be non solid but still has fringe cases
        // where it collides with stuff
        // SetCollisionGroup(0) collides with prop statics
        if (self.GetMoveType() != 8)
        {
            self.SetAbsVelocity(Vector(0, 0, 0))
            self.SetModelScale(1.5, 0)
            self.SetMoveType(8, 0)
            self.SetCollisionGroup(2)
            self.SetSolid(0)
            self.SetSolidFlags(4)
            self.SetSize(Vector(0, 0, 0), Vector(0, 0, 0))
        }
                
        local angular_speed = speed / range;
        angle += angular_speed * FrameTime();

        // Get player position and facing direction
        local player_origin = player.GetOrigin();
        local eye_angles = player.EyeAngles();
        local eye_location = player.EyePosition()

        // Convert player's yaw to radians
        local player_yaw = eye_angles.y * (PI / 180.0);

        // Calculate forward and right vectors from player's facing direction
        local forward = Vector(
            cos(player_yaw),
            sin(player_yaw),
            0
        );
        local right = Vector(
            cos(player_yaw - PI/2),
            sin(player_yaw - PI/2),
            0
        );

        // Position the circle center forward from the player
        // This makes the semicircle arc in front of the player
        local circle_center = eye_location + forward * (range / 2.0);

        // Calculate position on the arc
        // Start from angle PI/2 (left side) and go to -PI/2 (right side)
        local current_angle = (PI/2) - angle; // Start from PI/2 and decrease

        local pos = Vector(
            circle_center.x + right.x * (cos(current_angle) * range / 2.0) + forward.x * (sin(current_angle) * range / 2.0),
            circle_center.y + right.y * (cos(current_angle) * range / 2.0) + forward.y * (sin(current_angle) * range / 2.0),
            eye_location.z - 30  // Keep Z constant at eye level
        );

        self.SetAbsOrigin(pos );

        // Check if projectile has returned close to the player
        local distance_to_player = (pos - player_origin).Length();


        // Calculate the movement direction
        local current_world_angle = player_yaw + angle + (PI / 2);
        local next_angle = angle + (angular_speed * FrameTime());
        local next_world_angle = player_yaw + next_angle + (PI / 2);

        // Current and next positions
        local current_pos = Vector(
            player_origin.x + cos(current_world_angle) * range,
            player_origin.y + sin(current_world_angle) * range,
            player_origin.z + 50
        );

        local next_pos = Vector(
            player_origin.x + cos(next_world_angle) * range,
            player_origin.y + sin(next_world_angle) * range,
            player_origin.z + 50
        );

        // Calculate movement direction
        local movement_dir = next_pos - current_pos;
        movement_dir.Norm();

        // Calculate the forward direction (movement direction)
        local forward_pitch = asin(-movement_dir.z) * (180.0 / PI);
        local forward_yaw = atan2(movement_dir.y, movement_dir.x) * (180.0 / PI);

        spin_rotation += 360.0 * FrameTime() * 4.0; // 2 rotations per second
        if (spin_rotation >= 360.0)
            spin_rotation -= 360.0;

        // Apply rotation: pitch and yaw for direction, roll for spinning
        local final_angles = QAngle(
            forward_pitch,                    // Point in movement direction
            forward_yaw + spin_rotation, // Point in movement direction + left/right spin
            90                                 // No roll rotation
        );
        
        self.SetAbsAngles(final_angles);
            
        local classnames = ["player", "obj_*", "tank_boss"];

        foreach (entry in classnames)
        {
            for (local enemy; enemy = FindByClassnameWithin(enemy, entry, cur_origin, 150);)
            {
                if (target_list.find(enemy) == null && enemy.GetTeam() != self.GetTeam() && enemy.GetTeam() != TEAM_SPECTATOR)
                {
                    local damage_type = 128 // melee
                    local attack_type = 24

                    target_list.append(enemy)
                    
                    enemy.TakeDamageCustom
                    (
                        self, 
                        player, 
                        weapon, 
                        Vector(0, 0, 0), 
                        enemy.GetOrigin(), 
                        54 * MULT_DAMAGE.damage_mult(player, weapon), 
                        damage_type, 
                        attack_type 
                    )
                }
            }
        }
    }

    MissionSetup = function()
    {
        local ent = FindByName(null, "bombpath_choose_random_relay")

        ent.AcceptInput("Trigger", "", null, null)

        local TakeDamageFunctionList = 
        {
            "BlastANatureKnockback" : BlastANatureKnockback
        }

        // iterate over TakeDamageFunctionList
        foreach (name, func in TakeDamageFunctionList)
        {
            if ( !( func in TakeDamageFunctionList ) )
            {
                // printl("applying "+func+" into "+TakeDamageFunctionList)
                GiantMode_Hurricane.TakeDamageFuncTable[name] <- func
            }
        }
    }

    // Since we add individual thinks to our projectiles
    // we need to check what the id of the weapon that the projectile came from
    AddProjectileThinks = function( projectile, owner )
    {
        projectile.ValidateScriptScope()
        local scope = projectile.GetScriptScope()

        local owner_netprop = GetPropEntity(projectile, "m_hOwner")
        local thrower_netprop = GetPropEntity(projectile, "m_hThrower")
        local owner_launcher = GetPropEntity(projectile, "m_hLauncher")
        local owner_entity = GetPropEntity(projectile, "m_hOwnerEntity")

        local wep_entity = null

        // Use this table to refer to actual values
        local entity_table =
        {
            "Owner" : owner_netprop
            "Thrower" : thrower_netprop
            "Launcher" : owner_launcher
            "Entity" : owner_entity
        }

        local ProjectileThinkList = 
        [
            {"CleaverBoomerang" : [GuillotineThink, WEAPON_FLYING_GUILLIOTONE] }
        ]

        // From entity_table, check through everything that isnt null
        // if it isnt null, check it
        foreach (string, value in entity_table)
        {
            // printl("String: "+string+" Value: "+value)

            // Dont bother with anything that isnt null, or if the value is self
            if (value == null || value == owner)
                continue
            
            wep_entity = value
            break
        }

        
        local projectile_scope_table = 
        [
            {
                "CleaverBoomerang": 
                {
                    owner = owner
                    weapon = wep_entity
                    timestamp = Time()
                    range = 550.0
                    speed = 2400.0
                    spin_rotation = 0
                    target_list = []
                    angle = PI // start from side (left of the circle)
                }
            }
        ];


        // Check every think we need to potentailly add
        foreach (entry in ProjectileThinkList)
        {
            // Check if our projectile and weapon corrospond to entry
            foreach (name, data in entry)
            {
                // printl("name: " + name);             // e.g., "CleaverBoomerang"
                // printl("function: " + data[0]);      // GuillotineThink (function ref)
                // printl("weapon_id: " + data[1]);     // WEAPON_FLYING_GUILLIOTINE

                if ( GetPropInt(wep_entity, STRING_NETPROP_ITEMDEF) == data[1] )
                {
                    // Before adding the think, we need to add our values into scope
                    foreach (entry in projectile_scope_table)
                    {
                        foreach ( think_name, values in entry )
                        {
                            // We found the name corrosponding, add our values into scope
                            if (think_name == name)
                            {
                                foreach (key, val in values)
                                {
                                    // printl("key: "+key)
                                    // printl("val: "+val)
                                    scope[key] <- val;
                                }
                            }
                        }
                    }

                    // printl("Found correct weapon, adding projectile think: "+name + data[0]+"\n to entity: "+wep_entity+"\n id: "+data[1])
                    scope.ThinkTable_Projectile[name] <- data[0]
                }
            }
        }
    }

    // UTIL

    AngleToForward = function(q)
    {
        local pitch = q.x * DEG2RAD;
        local yaw = q.y * DEG2RAD;

        return Vector(
            cos(pitch) * cos(yaw),
            cos(pitch) * sin(yaw),
            -sin(pitch)
        );
    }

    GetPlayerThinkTable = function(player)
    {
        player.ValidateScriptScope();
        local scope = player.GetScriptScope();

        local bot_check = player.IsBotOfType(TF_BOT_TYPE) ? true : false;
        local key = bot_check ? "FuncTableRobots" : "FuncTablePlayers";

        if (!(key in scope))
        {
            // If the table doesn't exist yet, create it
            scope[key] <- {};
        }

        return scope[key];
    }

    GetLoadout = function(player)
    {
        local loadout = []

        for (local i = 0; i < SLOT_COUNT; i++) 
        {
            local wep = NetProps.GetPropEntityArray(player, "m_hMyWeapons", i)
            if ( wep == null ) continue

            loadout.append(wep)
            
        }

        local itemdef_getslot = 
        [
            {wep = WEAPON_MANTREADS, slot = 1},
            {wep = WEAPON_GUNBOATS, slot = 1},

            {wep = WEAPON_BOOTLEGGER, slot = 0},
            {wep = WEAPON_ALI_BABAS_WEE_BOOTIES, slot = 0},

            {wep = WEAPON_CHARGIN_TARGE, slot = 1},
            {wep = WEAPON_SPLENDID_SCREEN, slot = 1},
            {wep = WEAPON_TIDE_TURNER, slot = 1},
            {wep = WEAPON_CHARGIN_TARGE_FESTIVE, slot = 1},

            {wep = WEAPON_DARWIN_DANGER_SHIELD, slot = 1},
            {wep = WEAPON_COZY_CAMPER, slot = 1},
            {wep = WEAPON_RAZORBACK, slot = 1},

            //{wep = WEAPON_BASE_JUMPER, slot = (player.GetPlayerClass() == Demoman ? 0 : 1)}
        ];


        local children_table = []
        
        for (local child = player.FirstMoveChild(); child != null; child = child.NextMovePeer())
        {
            children_table.append(child)
        }

        foreach (child in children_table) 
        {
            //////printl("child is: "+child)

            foreach (entry in itemdef_getslot)
            {
                local id = entry.wep 

                if ( GetPropInt(child, STRING_NETPROP_ITEMDEF) == id && loadout.find(child) == null )
                {
                    loadout.append(child)
                }
                else 
                {
                    continue
                }
            }
        }
        

        return loadout
    }

    GetItemInSlot = function(player, slot) 
    {
        local item
        for (local i = 0; i < SLOT_COUNT; i++) 
        {
            local wep = NetProps.GetPropEntityArray(player, "m_hMyWeapons", i)
            if ( wep == null || wep.GetSlot() != slot) continue

            item = wep
            break
        }

        local itemdef_getslot = 
        [
            {wep = WEAPON_MANTREADS, slot = 1},
            {wep = WEAPON_GUNBOATS, slot = 1},

            {wep = WEAPON_BOOTLEGGER, slot = 0},
            {wep = WEAPON_ALI_BABAS_WEE_BOOTIES, slot = 0},

            {wep = WEAPON_CHARGIN_TARGE, slot = 1},
            {wep = WEAPON_SPLENDID_SCREEN, slot = 1},
            {wep = WEAPON_TIDE_TURNER, slot = 1},
            {wep = WEAPON_CHARGIN_TARGE_FESTIVE, slot = 1},

            {wep = WEAPON_DARWIN_DANGER_SHIELD, slot = 1},
            {wep = WEAPON_COZY_CAMPER, slot = 1},
            {wep = WEAPON_RAZORBACK, slot = 1},

            //{wep = WEAPON_BASE_JUMPER, slot = (player.GetPlayerClass() == Demoman ? 0 : 1)}
        ];

        // Didn't find anything in slot, check for passive items
        if (item == null)
        {
            local children_table = []
            
            for (local child = player.FirstMoveChild(); child != null; child = child.NextMovePeer())
            {
                children_table.append(child)
            }

            foreach (child in children_table) 
            {
                //////printl("child is: "+child)

                foreach (entry in itemdef_getslot)
                {
                    local id = entry.wep 
                    local wep_slot = entry.slot

                    // ////printl("id is: "+id)
                    // ////printl("slot: "+slot)

                    if (wep_slot != slot)
                    {
                        // ////printl("not slot")
                        continue
                    }

                    if (GetPropInt(child, STRING_NETPROP_ITEMDEF) == id)
                    {
                        // ////printl("------------- Found a passive weapon: "+child)
                        item = child
                        return item // must return item here, break doesnt return correct item 
                    }
                }
            }
        }

        return item
    }

    Cleanup = function()
    {
        for (local player; player = FindByClassname(player, "player");)
        {
            PlayerCleanupEx(player)
        }
        
        // keep this at the end of this function
        delete ::GiantMode_Hurricane
    }

    PrintTable = function(table) 
    {
        if (table == null) return;

        this.DoPrintTable(table, 0)
    }

    DoPrintTable = function(table, indent) 
    {
        local line = ""
        for (local i = 0; i < indent; i++) {
            line += " "
        }
        line += typeof table == "array" ? "[" : "{";

        ClientPrint(null, 2, line)

        indent += 2
        foreach(k, v in table) {
            line = ""
            for (local i = 0; i < indent; i++) {
                line += " "
            }
            line += k.tostring()
            line += " = "

            if (typeof v == "table" || typeof v == "array") {
                ClientPrint(null, 2, line)
                this.DoPrintTable(v, indent)
            }
            else {
                try {
                    line += v.tostring()
                }
                catch (e) {
                    line += typeof v
                }

                ClientPrint(null, 2, line)
            }
        }
        indent -= 2

        line = ""
        for (local i = 0; i < indent; i++) {
            line += " "
        }
        line += typeof table == "array" ? "]" : "}";

        ClientPrint(null, 2, line)
    }

    ScaleValue = function(min, max, given_scale, max_scale)
    {
        return min + ((max - min) * (given_scale.tofloat() / max_scale.tofloat()));
    }

    PlayerCleanupEx = function(player)
    {
        // printl("Use special cleanup")
        player.ValidateScriptScope()
        local scope = player.GetScriptScope()

        local ignore_table = 
        {
            "self"      : null,
            "__vname"   : null,
            "__vrefs"   : null
        };

        foreach (think, v in scope)
        {
            if (!(think in ignore_table))
            {
                // Delete anything found to be an entity
                if ( typeof(v) == "instance" && v.IsValid() )
                {
                    v.Kill()
                }
                
                // Delete other key values
                delete scope[think]
            }
        }

        // needs to be here else we get script issues with think table
        SetPropString(player, "m_iszScriptThinkFunction", "")
        AddThinkToEnt(player, null)

        // from this point onward we only need to worry about RED
        if ( player.IsBotOfType(TF_BOT_TYPE) )
        {
            return
        }
    }

    PlayerSpawnEx = function(player)
    {
        printl("Called PlayerSpawnEx")
        player.ValidateScriptScope()
        local bot_check = player.IsBotOfType(TF_BOT_TYPE) ? true : false
        local scope = player.GetScriptScope() 
        local cur_time = Time()

        // printl("Is Player a Bot? "+bot_check+" for: "+player)

        local key = bot_check ? 
        [ "RobotThink", "FuncTableRobots", "Robots_Cleanup_Table"] : 
        [ "PlayerThink", "FuncTablePlayers", "Player_Cleanup_Table"]

        local cleanup_map =
        {
            "Player_Cleanup_Table" : Player_Cleanup_Table,
            "Robots_Cleanup_Table" : Robots_Cleanup_Table,
        }


        // key 0 is first arg (i.e. RobotThink)
        // key 1 is second arg (i.e. FuncTableRobots)
        

        scope[key[1]] <- {}
        scope.PlayerCleanupEx <- GiantMode_Hurricane.PlayerCleanupEx
        
        scope[key[0]] <- function() 
        { 
            foreach ( name, func in this[key[1]] ) 
            {
                if (typeof func == "function")
                {
                    // printl("Running func: "+name)
                    func.call(scope);
                }
            }
            
            if ( self.IsMiniBoss() )
            {
                EntFireByHandle(self, "RunScriptCode", "SetPropBool(self, `m_Shared.m_bInUpgradeZone`, false);", -1, null, null);
            }
            
            return -1 
        }

        // append 
        cleanup_map[key[2]].append(player)

        AddThinkToEnt(player, key[0])

        scope[key[1]].ApplyProjectileThink <- GiantMode_Hurricane.ApplyProjectileThink
    }  

    // Event Hooks

    // mandatory events
    OnGameEvent_recalculate_holidays = function(_) 
    { 
        if (GetRoundState() == 3) 
        {
            Cleanup()
        } 
    }
    OnGameEvent_mvm_wave_complete = function(_) 
    {
        Cleanup() 
    }


    OnGameEvent_player_death = function(params) 
    {
        local player = GetPlayerFromUserID(params.userid)
        
        PlayerCleanupEx(player)
    }

    OnGameEvent_post_inventory_application = function(params)
    {
        local player = GetPlayerFromUserID(params.userid)
        player.ValidateScriptScope()
        local playerscope = player.GetScriptScope()
        
        player.AcceptInput("RunScriptCode", "GiantMode_Hurricane.PlayerSpawnEx(self)", player, player)
    }

    OnGameEvent_player_spawn = function(params)
    {
        local player = GetPlayerFromUserID(params.userid)
        player.ValidateScriptScope()
        local playerscope = player.GetScriptScope()

    }

    OnScriptHook_OnTakeDamage = function(params)
    {
        local victim = params.const_entity
        local weapon = params.weapon
        local attacker = params.attacker
        local entity = params.inflictor
        local damage = params.damage
        local dmgtype = params.damage_type
        local dmg_special = params.damage_stats
        local damage_force = params.damage_force
        local damage_position = params.damage_position
        local inflictor = params.inflictor
        local force_friendly_fire = params.force_friendly_fire

        local params_table = 
        {
            victim = victim,
            weapon = weapon,
            attacker = attacker,
            entity = entity,
            damage = damage,
            dmgtype = dmgtype,
            dmg_special = dmg_special
            damage_force = damage_force
            damage_position = damage_position
            inflictor = inflictor
            force_friendly_fire = force_friendly_fire
        };

        local classnames = 
        {
            "player" : 1
            "obj_teleporter" : 1
            "obj_sentrygun" : 1
            "obj_dispenser" : 1
            "tank_boss" : 1
        }

        local uber_conds = 
        [
            5, // stock
            14, // bonk
            51, // mvm spawn
            52, // canteen
            57, // wheel of fate
        ]

        local run_damage_table = true

        if (victim.GetTeam() == TEAM_SPECTATOR)
        {
            // printl("Spectator shouldnt run for")
            run_damage_table = false
        }

        foreach (cond in uber_conds)
        {
            if (victim.GetClassname() != "player")
                continue
            
            if ( victim.InCond(cond) )
            {
                run_damage_table = false
            }
        }

        foreach (name, func_table in GiantMode_Hurricane.TakeDamageFuncTable) 
        {
            if (run_damage_table == false)
                continue

            func_table.call(params_table)
        }
        
        // needed to read anything we update in our takedamagetable functions
        params.const_entity = params_table.victim;
        params.weapon = params_table.weapon;
        params.attacker = params_table.attacker;
        params.inflictor = params_table.entity;
        params.damage = params_table.damage;
        params.damage_type = params_table.dmgtype;
        params.damage_stats = params_table.dmg_special;
        params.damage_force = params_table.damage_force;
        params.damage_position = params_table.damage_position;
        params.inflictor = params_table.inflictor;
        params.force_friendly_fire = params_table.force_friendly_fire
    }
};

for (local i = 1, player; i <= MaxClients(); i++)
{
    if (player = PlayerInstanceFromIndex(i), player && player.GetTeam() == TEAM_RED)
    {
        EntFireByHandle(player, "RunScriptCode", "GiantMode_Hurricane.PlayerSpawnEx(self)", -1, player, player)
    }
}

GetAllAreas(GiantMode_Hurricane.MapNavAreas)
__CollectGameEventCallbacks(GiantMode_Hurricane) // the OnGameEvent/OnScriptHook outputs need this