// Script by StardustSpy

// todo 
// sentry can get stunned if the engie is stunned (keep in lmao)
// fix bomb curse

::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

foreach ( key, value in CEntities ) 
{
    if ( key == "IsValid" ) continue;
    // Doesn't work directly with `value` due to squirrel's broken closures
    local method = value;
    getroottable()[ key ] <- function( ... ) {
        local entity = method.acall( [ Entities ].extend( vargv ) );
        if ( entity )
            NetProps.SetPropBool( entity, "m_bForcePurgeFixedupStrings", true );
        return entity;
    }
}

foreach( _class in [  "NetProps", "Entities", "EntityOutputs", "NavMesh"  ] )
    foreach( k, v in ROOT[  _class  ].getclass() )
        if ( !( k in ROOT ) && k != "IsValid" ) 
            ROOT[  k  ] <- ROOT[  _class  ][  k  ].bindenv( ROOT[  _class  ] )

// Helpful references to important entities
::gamerules <- FindByClassname( null, "tf_gamerules" )
::player_manager <- FindByClassname( null, "tf_player_manager" )
::WORLD_SPAWN <- FindByClassname( null, "worldspawn" )
::mvm_logic_entity <- FindByClassname( null, "tf_objective_resource" )

// mdl
PrecacheModel( "models/player/heavy.mdl" )
PrecacheModel( "models/empty.mdl" )
PrecacheModel( "models/props_gameplay/cap_circle_256.mdl" )
PrecacheModel( "models/props_mvm/robot_spawnpoint_warning.mdl" )
PrecacheModel( "models/bots/headless_hatman.mdl" )
PrecacheModel( "models/bots/headless_hatman_burnt.mdl" )
::CRUMPKIN_INDEX <- PrecacheModel( "models/props_halloween/pumpkin_loot.mdl" )

// sound
PrecacheSound( "weapons/dragons_fury_impact_bonus_damage.wav" )
PrecacheSound( "items/ammo_pickup.wav" )
PrecacheSound( "ui/cyoa_ping_available.wav" )
PrecacheSound( "misc/halloween/spell_spawn_boss_disappear.wav" )
PrecacheSound( "weapons/bumper_car_hit_ghost.wav" )
PrecacheSound( "ui/halloween_boss_tagged_other_it.wav" )
PrecacheSound( "items/powerup_pickup_strength.wav" )
PrecacheSound( "ambient/medieval_dooropen.wav" )
PrecacheSound( "ambient/medieval_doorclose.wav" )
PrecacheSound( "items/powerup_pickup_reduced_damage.wav" )
PrecacheSound( "weapons/teleporter_receive.wav" )
PrecacheSound( "weapons/cguard/charging.wav" )
PrecacheSound( "vo/halloween_merasmus/sf12_bcon_headbomb36.mp3" )
PrecacheSound( "passtime/crowd_cheer.wav" )
PrecacheSound( "ui/mm_level_six_achieved.wav" )
PrecacheSound( "misc/halloween/merasmus_appear.wav" )
PrecacheSound( "weapons/stickybomblauncher_charge_up.wav" )
PrecacheSound( "misc/doomsday_missile_explosion.wav" )
PrecacheSound( "misc/halloween/spell_mirv_explode_secondary.wav" )
PrecacheSound( "misc/doomsday_cap_open_start.wav")
PrecacheSound( "misc/halloween/spell_meteor_cast.wav" )
PrecacheSound( "ui/mm_medal_spin_hit.wav" )
PrecacheSound( "ambient/fire/gascan_ignite1.wav" )
PrecacheSound( "usum_unecrozma_bosstheme.mp3" )
PrecacheSound( "music/hl2_song28.mp3" )
PrecacheSound( "vo/halloween_boss/knight_alert.mp3" )
PrecacheSound( "vo/halloween_boss/knight_dying.mp3" )
PrecacheSound( "ui/halloween_boss_defeated.wav" )

// Helpful consts 
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
const TF_BOT_FAKE_CLIENT = 1337

const DMG_CRITICAL = 1048576
const FAKE_MINI_SENTRY_MAXIMUM = 8
const SINGLE_TICK = 0.015
const REMOVE_ENTRY = false
const KEEP_ENTRY = true

const STRING_NETPROP_INIT = "m_AttributeManager.m_Item.m_bInitialized"
const STRING_NETPROP_ATTACH = "m_bValidatedAttachedEntity"

const OF_ALLOW_REPEAT_PLACEMENT      = 1
const OF_MUST_BE_BUILT_ON_ATTACHMENT = 2
const OF_DOESNT_HAVE_A_MODEL         = 4
const OF_PLAYER_DESTRUCTION          = 8

// attack
const BOSS_ATTACK_FIREBALL = 1
const BOSS_ATTACK_SCYTHE = 2

//redefine EFlags
const EFL_USER 			  = 1048576 // EFL_IS_BEING_LIFTED_BY_BARNACLE
const EFL_CUSTOM_WEARABLE = 1073741824 //EFL_NO_PHYSCANNON_INTERACTION
const EFL_PROJECTILE 	  = 2097152 //EFL_NO_ROTORWASH_PUSH
const EFL_BOT 			  = 268435456 //EFL_NO_MEGAPHYSCANNON_RAGDOLL
const EFL_SPAWNTEMPLATE   = 67108864 //EFL_DONTWALKON

// screenfade flags
const FFADE_IN = 1
const FFADE_OUT = 2
const FFADE_MODULATE = 4
const FFADE_STAYOUT = 8
const FFADE_PURGE =	16 

// trigger ent
const SF_TRIGGER_ALLOW_CLIENTS                = 1
const SF_TRIGGER_ALLOW_NPCS                   = 2
const SF_TRIGGER_ALLOW_PUSHABLES              = 4
const SF_TRIGGER_ALLOW_PHYSICS                = 8
const SF_TRIGGER_ONLY_PLAYER_ALLY_NPCS        = 16
const SF_TRIGGER_ONLY_CLIENTS_IN_VEHICLES     = 32
const SF_TRIGGER_ALLOW_ALL                    = 64
const SF_TRIG_PUSH_ONCE                       = 128
const SF_TRIG_PUSH_AFFECT_PLAYER_ON_LADDER    = 256
const SF_TRIGGER_ONLY_CLIENTS_OUT_OF_VEHICLES = 512
const SF_TRIG_TOUCH_DEBRIS                    = 1024
const SF_TRIGGER_ONLY_NPCS_IN_VEHICLES        = 2048
const SF_TRIGGER_DISALLOW_BOTS                = 4096

// screenshake

const SHAKE_START = 0
const SHAKE_STOP = 1

// restrict
::RESTRICT_MELEE <- 1
::RESTRICT_PRIMARY <- 2
::RESTRICT_SECONDARY <- 4

// curse
const CURSE_GIANT = 0
const CURSE_MEDIEVAL = 1
const CURSE_TELEPORT = 2
const CURSE_BOMBHEAD = 3
const CURSE_FEAR = 4

const CURSE_EXPIRE = true 
const CURSE_RESPAWN = false

// damage type
::TF_DMG_BURN <- 8
::TF_DMG_BLAST <- 64
::TF_DMG_BULLET <- 2
::TF_DMG_MELEE <- 128
::TF_DMG_NO_FORCE <- 2048
::TF_DMG_CRITICAL <- 1048576
::TF_DMG_FALL_OFF <- 2097152
::TF_DMG_HALF_FALLOFF <- 262144
::TF_DMG_MELEE <- 134217728 // DMG_BLAST_SURFACE

// sound
::SND_NOFLAGS <- 0
::SND_CHANGE_VOL <- 1
::SND_CHANGE_PITCH <- 2
::SND_STOP <- 4
::SND_SPAWNING <- 8
::SND_DELAY <- 16
::SND_STOP_LOOPING <- 32
::SND_SPEAKER <- 64
::SND_SHOULDPAUSE <- 128
::SND_IGNORE_PHONEMES <- 256
::SND_IGNORE_NAME <- 512
::SND_DO_NOT_OVERWRITE_EXISTING_ON_CHANNEL <- 1024

// stun
::TF_STUN_NONE <- 0
::TF_STUN_MOVEMENT <- 1
::TF_STUN_CONTROLS <- 2
::TF_STUN_MOVEMENT_FORWARD_ONLY <- 4
::TF_STUN_SPECIAL_SOUND <- 8
::TF_STUN_DODGE_COOLDOWN <- 16
::TF_STUN_NO_EFFECTS <- 32
::TF_STUN_LOSER_STATE <- 64
::TF_STUN_BY_TRIGGER <- 128
::TF_STUN_SOUND <- 256

//Teams
::TEAM_SPECTATOR <- 1
::TEAM_RED <- 2
::TEAM_BLU <- 3

::TF_NAV_SPAWN_ROOM_RED <- 2
::TF_NAV_SPAWN_ROOM_BLUE <- 4

// stun
::STUN_MOVE <- 1
::STUN_MOVE_FORWARD_ONLY <- 4

// Multi
::WEAPON_BASE_JUMPER <- 1101
::WEAPON_RESERVE_SHOOTER <- 415
::WEAPON_PAIN_TRAIN <- 154
::WEAPON_SHOTGUN_PRIMARY <- 9

// 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_GUILLOTINE <- 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_ROCKET_JUMPER <- 237

::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_DEGREASER <- 215

::WEAPON_MANN_MELTER <- 595
::WEAPON_SHARPENED_VOLCANO_FRAGMENT <- 348

// Demo
::WEAPON_ALI_BABAS_WEE_BOOTIES <- 405
::WEAPON_BOOTLEGGER <- 608
::WEAPON_IRON_BOMBER <- 1151
::WEAPON_LOCH_N_LOAD <- 308

::WEAPON_CHARGIN_TARGE <- 131
::WEAPON_TIDE_TURNER <- 1099
::WEAPON_SPLENDID_SCREEN <- 406
::WEAPON_STICKY_JUMPER <- 265
::WEAPON_CHARGIN_TARGE_FESTIVE <- 1144

::WEAPON_ULLAPOOL_CABER <- 307
::WEAPON_PERSIAN_PERSUADER <- 404
// heavy
::WEAPON_NATASCHA <- 41

// Engineer

::WEAPON_BUILDER <- 25
::WEAPON_BUILDER_DESTROY <- 26
::WEAPON_BOX <- 28
::WEAPON_WIDOWMAKER <- 527
::WEAPON_POMSON <- 588

// medic
::WEAPON_VITA_SAW <- 173
::WEAPON_SOLEMN_VOW <- 413

// Sniper
::WEAPON_DARWIN_DANGER_SHIELD <- 231
::WEAPON_COZY_CAMPER <- 642 
::WEAPON_RAZORBACK <- 57
::WEAPON_CLASSIC <- 1098
::WEAPON_CLEANERS_CARBINE <- 751

// Spy
::WEAPON_BUILDER_SAPPER <- 27
::WEAPON_INVIS_WATCH <- 30
::WEAPON_ENFORCER <- 460
::WEAPON_SPYCICLE <- 649

// FuncType
::FUNC_THINK <- 0
::FUNC_DAMAGE <- 1

::CursesScript <- 
{
    Test = printl( "----- Successfully Loaded <CursesScript> Scripts -----" )

    script1 = IncludeScript("curses/camera_fixer", getroottable())

    Player_Cleanup_Table = []
    Robots_Cleanup_Table = []
    PreservedCleanupArray = []
    CameraArray = []
    MapNavAreas = {}
    TakeDamageFuncTable = {}
    aryNoGiveExplanation = []
    bSendToHell = false
    iLastWave = 0
    iCurWave = 0
    hSuperBossPlayer = null
    iTrueWave = GetPropInt( mvm_logic_entity, "m_nMannVsMachineWaveCount" ) 
    iFinalWave = GetPropInt( mvm_logic_entity, "m_nMannVsMachineMaxWaveCount" )

    MaxAmmoTable = 
    {
		[ TF_CLASS_SCOUT ] = 
        {
			[ "tf_weapon_scattergun" ]            = 32,
			[ "tf_weapon_handgun_scout_primary" ] = 32,
			[ "tf_weapon_soda_popper" ]           = 32,
			[ "tf_weapon_pep_brawler_blaster" ]   = 32,

			[ "tf_weapon_handgun_scout_secondary" ] = 36,
			[ "tf_weapon_pistol" ]                  = 36,
		},
		[ TF_CLASS_SOLDIER ] =
        {
			[ "tf_weapon_rocketlauncher" ]           = 20,
			[ "tf_weapon_rocketlauncher_directhit" ] = 20,
			[ "tf_weapon_rocketlauncher_airstrike" ] = 20,
			[ WEAPON_ROCKET_JUMPER ] = 60,

			[ "tf_weapon_shotgun_soldier" ] = 32,
			[ "tf_weapon_shotgun" ]         = 32,
		},
		[ TF_CLASS_PYRO ] = 
        {
			[ "tf_weapon_flamethrower" ]            = 200,
			[ "tf_weapon_rocketlauncher_fireball" ] = 40,

			[ "tf_weapon_shotgun_pyro" ] = 32,
			[ "tf_weapon_shotgun" ]      = 32,
			[ "tf_weapon_flaregun" ]     = 16,
		},
		[ TF_CLASS_DEMOMAN ] = 
        {
			[ "tf_weapon_grenadelauncher" ] = 16,
			[ "tf_weapon_cannon" ]          = 16,

			[ "tf_weapon_pipebomblauncher" ] = 24,
			[ WEAPON_STICKY_JUMPER ] = 72,
		},
		[ TF_CLASS_HEAVYWEAPONS ] = 
        {
			[ "tf_weapon_minigun" ]     = 200,

			[ "tf_weapon_shotgun_hwg" ] = 32,
			[ "tf_weapon_shotgun" ]     = 32,
		},
		[ TF_CLASS_ENGINEER ] = 
        {
			[ "tf_weapon_shotgun" ]                 = 32,
			[ "tf_weapon_sentry_revenge" ]          = 32,
			[ "tf_weapon_shotgun_building_rescue" ] = 16,
			[ WEAPON_SHOTGUN_PRIMARY ] = 32,

			[ "tf_weapon_pistol" ] = 200,
		},
		[ TF_CLASS_MEDIC ] = 
        {
			[ "tf_weapon_syringegun_medic" ] = 150,
			[ "tf_weapon_crossbow" ]         = 38,
		},
		[ TF_CLASS_SNIPER ] = 
        {
			[ "tf_weapon_sniperrifle" ]         = 25,
			[ "tf_weapon_sniperrifle_decap" ]   = 25,
			[ "tf_weapon_sniperrifle_classic" ] = 25,
			[ "tf_weapon_compound_bow" ]        = 12,

			[ "tf_weapon_smg" ]         = 75,
			[ "tf_weapon_charged_smg" ] = 75,
		},
		[ TF_CLASS_SPY ] = 
        {
			[ "tf_weapon_revolver" ] = 24,
		},
	}
    Classes = [ "", "Scout", "Sniper", "Soldier", "Demo", "Medic", "Heavy", "Pyro", "Spy", "Engineer", "civilian" ] //make element 0 a dummy string instead of doing array + 1 everywhere
    
    // Entities

    CurseController = null

    // ON TAKE DAMAGE

    function DisableDamage()
    {
        // printl("test dmg")
        if ( victim.IsPlayer() && attacker.GetTeam() != victim.GetTeam() && victim.IsBotOfType(TF_BOT_TYPE))
        {
            // printl("disabling damage")
            if (victim.HasBotTag("tag_fake_boss"))
            {
                // printl("must disable damage")
                if ( victim.GetHealth() > (vicitm.GetMaxHealth() / 2 ) )
                {
                    // printl("1 dmg")
                    damage = 1
                }
                else 
                {
                    // printl("no dmg")
                    damage = 0
                }
            }
        }
    }

    function RemoveKnockback()
    {
        if (attacker.IsPlayer() && victim.IsPlayer() && attacker.GetTeam() != victim.GetTeam() && attacker.IsBotOfType(TF_BOT_TYPE))
        {
            if (attacker.HasBotTag("bot_disable_knockback"))
            {
                // printl("no kncobac")
                victim.AddCustomAttribute("damage force increase", 0, 0.1)
            }
        }
    }

    function AddFear()
    {
        if ( victim.IsPlayer() && attacker.GetTeam() != victim.GetTeam() )
        {
            victim.ValidateScriptScope()
            local scope = victim.GetScriptScope()

            if ( "SufferingCurse5" in scope )
            {
                local mult = 1

                if ( CursesScript.IsMiniCritBoosted( attacker ) )
                {
                    mult = 1.35
                }

                if ( dmgtype | DMG_CRITICAL )
                {
                    mult = 3
                }

                // For every 5 damage, add 1 fear
                local damage_per_fear = 5
                local fear_to_add = ( damage / damage_per_fear ) * mult
                // //// // printl( "fear to add: "+fear_to_add )
                scope.flFear += fear_to_add
            }
        }
    }

    function SetMedievalFireRate()
    {

        if (weapon == null || weapon == "eyeball_boss" || weapon.GetClassname() == "eyeball_boss")
        {
            return
        }

        if ( weapon && weapon.GetAttribute( "cannot giftwrap", 0 ) == 1 && victim.InCond( TF_COND_MARKEDFORDEATH ) )
        {
            // Save keys
            attacker.ValidateScriptScope()
            local scope = attacker.GetScriptScope()
            local cur_time = Time()
            
            // Initialize scope variables
            if ( !( "last_increment_time" in scope ) )
            {
                scope.last_increment_time <- cur_time
            }
            
            // Reset if more than 2 seconds since last increment
            if ( cur_time - scope.last_increment_time > 2.0 )
            {
                //// // printl( "Resetting fire rate - 2s timeout" )
                scope.last_increment_time = cur_time
            }
            
            // Get weapon timing
            local weapon_next_attack = GetPropFloat( weapon, "m_flNextPrimaryAttack" )
            local time_until_next_attack = weapon_next_attack - cur_time

            //"items/powerup_pickup_reduced_damage.wav"

            local sound_range = ( 40 + ( 20 * log10( 4000 / 36.0 ) ) ).tointeger();
            EmitSoundEx
            ({
                sound_name = "items/powerup_pickup_reduced_damage.wav",
                origin = attacker.GetOrigin(),
                volume = 1
                sound_level = sound_range 
                channel = RandomInt(40, 80)
                entity = attacker 
                filter_type = RECIPIENT_FILTER_SINGLE_PLAYER
            });
            
            // Only increment if time until next attack is greater than 0.2s
            if ( time_until_next_attack > 0.2 )
            {
                // Reduce next attack time by 5%
                local reduction = time_until_next_attack * 0.275
                SetPropFloat( weapon, "m_flNextPrimaryAttack", weapon_next_attack - reduction )
                
                // Update last increment time
                scope.last_increment_time = cur_time
                
                //// // printl( "Reduced attack time by: " + reduction + "s" )
            }
            else
            {
                //// // printl( "Attack time too low ( " + time_until_next_attack + "s ), not incrementing" )
            }
        }
    }

    function HHHHealthAttack()
    {
        if (attacker.IsPlayer() && attacker.IsBotOfType(TF_BOT_TYPE) )
        {
            if ( attacker.HasBotTag("bot_hhh") )
            {
                // printl("we are hhh")
                local max_health = victim.GetMaxHealth()
                local total_damage = max_health * 0.9
                damage = total_damage
            }
        }
    }

    // Thinks

    function PyroSuperbossThink()
    {
        local melee = CursesScript.GetItemInSlot( self, 2 )
        local cur_time = Time()
        local cur_origin = self.GetOrigin()
        local cur_scale = self.GetModelScale()
        local cur_weapon = self.GetActiveWeapon()

        if ( self.GetPlayerClass() != TF_CLASS_PYRO )
        {
            return
        }

        /*
            ####################################
            BOSS FUNCTIONALITY

            -Tracking time
            ####################################
        */

        // flTimeLeft has been inserted

        // Initiate countdown
        // Initialize timer if not already started
        if (flTimeLeft == 0)
        {
            flTimeLeft = cur_time
            // // printl("Timer started!")
        }

        // Calculate remaining time
        local elapsed_time = cur_time - flTimeLeft
        local remaining_time = flFightTime - elapsed_time

        // Ensure we don't go below 0
        if (remaining_time < 0)
            remaining_time = 0

        // Convert to minutes and seconds
        local minutes = (remaining_time / 60).tointeger()
        local seconds = (remaining_time % 60).tointeger()

        // Format seconds with leading zero if needed
        local seconds_str = seconds < 10 ? "0" + seconds : seconds.tostring()

        // Display the timer
        local timer_display = minutes + ":" + seconds_str
        // // printl("Time remaining: " + timer_display)

        // Optional: Check if timer expired
        if (remaining_time <= 0 && !( "bHasLost" in self.GetScriptScope() ) )
        {
            // printl("Timer expired!")
            self.GetScriptScope().bHasLost <- 1
            local lose_relay = FindByName(null, "boss_deploy_relay")
            lose_relay.AcceptInput("Trigger", "", null, null)

            local taunt_speed = 0.3
            local frame_begin = 0.6
            local taunt_duration = 99

            CursesScript.PlayAnimationOnPlayer( self, "taunt_flip_success_receiver", taunt_duration, taunt_speed, frame_begin )
            // Do whatever you need when timer runs out
        }
        else if ( remaining_time <= 0 )
        {
            timer_display = "You lose..."
        }

        // make it so that we display our time like this:
        // minutes:seconds
        // seconds needs to be 59 max

        foreach (i, player in aryPlayers)
        {
            CursesScript.DisplayHudElement( player, "meter_TimeLeft", function()
            {
                if ( player.IsAlive() && player.IsValid() && player != null )
                {
                    return "\nTime until you LOSE: "+timer_display
                }
            })
        }

        /*
            ####################################
            BOSS AI
            Picks the average position of every player
            Goes toward that position 
            Upon arriving, wait 5s, choose new position
            ####################################
        */

        if ( self.IsBotOfType( TF_BOT_TYPE ) )
        {
            // scope.bSuperboss <- true
            // scope.bIsMovingToNewSpot <- false 
            // scope.flNextMoveStamp <- 0
            // scope.aryAlivePlayers <- []

            if ( ( flAttackStamp + flAtkTime ) <= cur_time )
            {
                // // printl("attack")
                self.PressFireButton( 0.2 )
                self.GetActiveWeapon().PrimaryAttack()
                flAttackStamp = cur_time
            }

            // Get all living players
            if ( bIsMovingToNewSpot == false )
            {
                bIsMovingToNewSpot = true 
                flFailsafeMoveStamp = cur_time
                // // printl("Looking for new spot...")

                for ( local enemy; enemy = FindByClassname( enemy, "player" ); )
                {
                    // // // printl( "entity team: within area "+entity.GetTeam() )
                    // // // printl( "player team: within area "+hPlayer.GetTeam() )
                    if ( enemy.GetTeam() != self.GetTeam() && enemy.GetTeam() != TEAM_SPECTATOR && enemy.IsAlive() && !( enemy in aryAlivePlayers ) )
                    {
                        aryAlivePlayers.append( enemy )
                    }
                }

                // CursesScript.PrintTable( aryAlivePlayers )

                // Find the average origin 

                local total_origin = Vector(0, 0, 0)
                local player_count = aryAlivePlayers.len()

                foreach ( i, player in aryAlivePlayers )
                {
                    total_origin += player.GetOrigin()
                }

                local math = Vector
                (
                    total_origin.x / player_count,
                    total_origin.y / player_count,
                    total_origin.z / player_count
                )

                // DebugDrawCircle( math + Vector(0, 0, 25), Vector(0, 255, 0), 0, 100, true, 10 )

                // Using the average origin, find a nav 

                local nav_spot = FindNavAreaAlongRay( math + Vector(0, 0, 800), math + Vector(0, 0, -1200), null )
                // DebugDrawCircle( nav_spot.FindRandomSpot(), Vector(255, 0, 0), 0, 50, true, 10 )

                local debug_dir = 
                [ 
                    NORTH, EAST, SOUTH, WEST
                ]

                foreach ( dir in debug_dir )
                {
                    // DebugDrawLine_vCol( nav_spot.GetCenter(), nav_spot.GetCorner( dir ), Vector( 0, 255, 0 ), true, 5 )
                }

                // Get a random spot in the nav

                local spot = nav_spot.FindRandomSpot()

                // Go to the spot
                hAreaPoint.SetAbsOrigin( spot )
            }

            if ( bIsMovingToNewSpot == true)
            {
               local intersect = CursesScript.CheckEntityIntersection( self, hAreaPoint )

                // if intersect is true, wait 5s
                if (intersect == true)
                {
                    // // printl("at spot")
                    // bot will sit still when at spot
                    hAreaPoint.SetAbsOrigin( cur_origin )

                    flFailsafeMoveStamp = cur_time
                    if ( ( flNextMoveStamp + 5 ) <= cur_time )
                    {
                        // // printl("move new spot")
                        bIsMovingToNewSpot = false
                        hAreaPoint.SetAbsOrigin( cur_origin )
                    }
                }
                else 
                {
                    // // // printl("finding spot")
                    flNextMoveStamp = cur_time

                    if ( ( flFailsafeMoveStamp + 15 ) <= cur_time )
                    {
                        // // printl("triggering failsafe...")
                        hAreaPoint.SetAbsOrigin( cur_origin )
                    }
                }
            }

            // Allow bot to move
            self.GetLocomotionInterface().Approach(hAreaPoint.GetOrigin(), 1.0)
        }

        /*
            ####################################
            BOSS ATTACKS
            ####################################
        */

        if ( GetPropInt( self, "m_Shared.m_iNextMeleeCrit" ) == 0 )
        {
            local rand_atk = RandomInt( 1, tbAttackList.len() )

            foreach ( num, func in tbAttackList )
            {
                // if we have to gurantee attacks, iterate through them first

                if ( tbGuranteeAttacks.len() > 0 )
                {
                    // // printl("we must iterate")
                    foreach (k, v in tbGuranteeAttacks)
                    {
                        // // printl("k: "+k)
                        // // printl("v: "+v)

                        rand_atk = v
                        delete tbGuranteeAttacks[k]
                        break
                    }
                }


                if ( rand_atk.tostring() == num )
                {
                    func()
                    break
                }
            }

            SetPropInt( self, "m_Shared.m_iNextMeleeCrit", -2 )
        }

        /*
            ####################################
            BOSS PROJECTILE THINKS
            ####################################
        */

        for ( local projectile; projectile = FindByClassname( projectile, "tf_projectile*" ); ) 
        {
            projectile.ValidateScriptScope()
            local scope = projectile.GetScriptScope()
            local launcher = GetPropEntity( projectile, "m_hLauncher" )
            
            if (launcher && "bIsFakeWep" in launcher.GetScriptScope() ) 
            {
                GetPropEntity( projectile, "m_hLauncher" ).Kill()
                // // // printl("we have fake wep")
            }
            else 
            {
                continue
            }

            if ( projectile.GetClassname() == "tf_projectile_rocket" && !( "RandHoming" in scope ) )
            {
                // // // printl( "new rand" )
                scope.RandHoming <- RandomInt( 1, 3 )
            }

            if ( !( "HasParticle" in scope ) && "bSuperboss" in self.GetScriptScope()) 
            {
                // // // printl("add particle")
                scope.HasParticle <- 1
                
                CursesScript.DispatchParticleEffectEx
                ( 
                    "spell_cast_wheel_blue", 
                    projectile.GetOrigin(), 
                    Vector( 0, 90, 0 ), 
                    [ -1, 3 ], 
                    null, 
                    null
                )
            }

            if ( ! ( "HomingProjectile" in CursesScript.GetEntityThinkTable( projectile ) ) && "bSuperboss" in self.GetScriptScope() )
            {
                if ( !( "RandHoming" in scope ) )
                {
                    // // // printl( "wtf no homing" )
                    continue
                }

                if (  scope.RandHoming > 1 )
                    continue

                scope.hLockTarget <- CursesScript.GetRandomEnemy( self )
                scope.flBeginLifetime <- Time()
                scope.iHomingStrength <- 135
                scope.iHomingTurnSpeed <- 110
                scope.iMaxVelocity <- 525
                CursesScript.DispatchParticleEffectEx( "eyeboss_projectile", projectile.GetOrigin(), Vector( 0, 0, 0 ), [ -1, -1 ], projectile, "trail" )

                CursesScript.GetEntityThinkTable( projectile ).HomingProjectile <- CursesScript.HomingProjectile
            }
        }

        self.SetForcedTauntCam( 1 )

        /*
            ####################################
            SLAM ATTACK
            ####################################
        */

        local taunt_speed = 0.5
        local frame_begin = 0.45
        local taunt_duration = 4
        local attack_time = 2.3

        local attack_radius = 125 * cur_scale

        // if ( GetPropInt( self, "m_Shared.m_iNextMeleeCrit" ) == 0 )
        // {
        //     //// // printl( "fucker 3" )

        //     local sound_range = ( 40 + ( 20 * log10( 8000 / 36.0 ) ) ).tointeger();

        //     EmitSoundEx
        //     ({
        //         sound_name = "weapons/stickybomblauncher_charge_up.wav",
        //         origin = cur_origin,
        //         volume = 1
        //         sound_level = sound_range 
        //         channel = RandomInt(40, 80)
        //         entity = null 
        //         filter_type = RECIPIENT_FILTER_GLOBAL
        //         pitch = 175
        //     });

        //     CursesScript.PlayAnimationOnPlayer( self, "taunt_yeti", taunt_duration, taunt_speed, frame_begin )

        //     bInitiatingSlam = true
        //     flSlamAttackDelay = cur_time

        //     SetPropInt( self, "m_Shared.m_iNextMeleeCrit", -2 )
        // }

        if (bInitiatingSlam == true && ( flSlamAttackDelay + attack_time ) <= cur_time )
        {
            self.AddCustomAttribute("no_attack", 1, 0.25)
            // DebugDrawCircle(cur_origin, Vector(255, 0, 0), 0, attack_radius, true, 5)
            local aryEnemyList = CursesScript.GetEnemiesWithinArea( self, attack_radius, cur_origin )
            flAttackStamp = cur_time

            foreach (i, enemy in aryEnemyList)
            {   
                if ( enemy == null || !enemy.IsValid() || !enemy.IsAlive() || !enemy.IsPlayer() || enemy.GetClassname() != "player" )
                {
                    continue
                }
                local eye_angles = self.EyeAngles()
                local fwd = eye_angles.Forward()

                local base_knockback_value = 2000
                local mult = base_knockback_value 
                local upward_mult = 300

                enemy.StunPlayer( 0.3, 1, STUN_MOVE | STUN_MOVE_FORWARD_ONLY, null )
                enemy.SetAbsVelocity( Vector( fwd.x * mult, fwd.y * mult, fwd.z + upward_mult ) )

                enemy.TakeDamageCustom
                ( 
                    null, 
                    self, 
                    cur_weapon, 
                    Vector( 0, 0, 0 ), 
                    enemy.GetOrigin(), 
                    95, 
                    DMG_CLUB | TF_DMG_HALF_FALLOFF, 
                    TF_DMG_CUSTOM_DECAPITATION_BOSS 
                )
            }

            local sound_range = ( 40 + ( 20 * log10( 16000 / 36.0 ) ) ).tointeger();

            EmitSoundEx
            ({
                sound_name = "misc/doomsday_missile_explosion.wav",
                origin = cur_origin,
                volume = 1
                sound_level = sound_range 
                channel = RandomInt(40, 80)
                entity = null 
                filter_type = RECIPIENT_FILTER_GLOBAL
            });
            EmitSoundEx
            ({
                sound_name = "misc/doomsday_missile_explosion.wav",
                origin = cur_origin,
                volume = 0.5
                sound_level = sound_range 
                channel = RandomInt(40, 80)
                entity = null 
                filter_type = RECIPIENT_FILTER_GLOBAL
            });
            EntFireByHandle(self, "RunScriptCode", "DispatchParticleEffect(`skull_island_explosion`, self.GetOrigin(), Vector())", 0, null, null)

            // pillars of flame

            // get dirs
            local eye_pos = self.EyePosition()
            local eye_angles = self.EyeAngles()
            local fwd = eye_angles.Forward()
            fwd.z = 0
            local fwd_length = fwd.Norm()
            if (fwd_length > 0) fwd = fwd * (1.0 / fwd_length)

            local left = eye_angles.Left()
            left.z = 0
            local left_length = left.Norm()
            if (left_length > 0) left = left * (1.0 / left_length)

            local back = fwd * -1.0  // Ensure it's a float multiplication
            local right = left * -1.0

            local vec_list =
            [
                fwd,
                left,
                back,
                right
            ]

            // Debug: Print the vectors to check
            // // printl("Forward: " + fwd.x + ", " + fwd.y + ", " + fwd.z)
            // // printl("Back: " + back.x + ", " + back.y + ", " + back.z)

            // For each direction, trace in that direction by 2000 h/u
            foreach (i, direction in vec_list)
            {
                // // printl("Direction " + i + ": " + direction.x + ", " + direction.y + ", " + direction.z)
                
                for (local distance = 100; distance <= 2000; distance += 100)
                {
                    // Check if there's a wall at this distance
                    local horizontal_pos = eye_pos + (direction * distance)
                    local wall_trace_params =
                    {
                        start  = eye_pos,
                        end    = horizontal_pos,
                        mask   = ( CONTENTS_SOLID | CONTENTS_MOVEABLE | CONTENTS_WINDOW | CONTENTS_GRATE ),
                        ignore = self
                    }
                    TraceLineEx( wall_trace_params )
                    
                    // If we hit a wall BEFORE reaching our intended distance, stop
                    if (wall_trace_params.hit && wall_trace_params.fraction < 1.0)
                    {
                        // // printl("Hit wall at distance " + distance + " in direction " + i)
                        break;
                    }
                    
                    // Only if we didn't hit a wall, trace down to find ground
                    local ground_start = horizontal_pos + Vector(0, 0, 100)
                    local ground_end = horizontal_pos + Vector(0, 0, -800)
                    
                    local ground_trace_params =
                    {
                        start  = ground_start,
                        end    = ground_end,
                        mask   = ( CONTENTS_SOLID | CONTENTS_MOVEABLE | CONTENTS_WINDOW | CONTENTS_GRATE ),
                        ignore = self
                    }
                    TraceLineEx( ground_trace_params )

                    local delay = distance / 1000.0 // must be float
                    local radius = 50

                    CursesScript.SpawnPillarFlame(self, delay, ground_trace_params.endpos, radius)
                }
            }
            // For every 100 h/u, place an object that will deal damage 

            bInitiatingSlam = false
        }

        /*
            ####################################
            PROJECTILE SWARM
            ####################################
        */

        // if ( GetPropInt( self, "m_Shared.m_iNextMeleeCrit" ) == 0 )
        // {
        //     //// // printl( "fucker 3" )

        //     if ( bProjectileSwarming == false )
        //     {
        //         flBeginProjectileSwarm = cur_time 
        //     }

        //     bProjectileSwarming = true

        //     SetPropInt( self, "m_Shared.m_iNextMeleeCrit", -2 )
        // }

        if ( bProjectileSwarming == true )
        {
            local swarm_start = flBeginProjectileSwarm

            if ( ( flProjectileSwarmStamp + 0.2 ) <= cur_time )
            {
                if ( aryProjectilesToRotate.len() < 25 )
                {
                    local projectile_model = ( RandomInt( 1, 2 ) == 1 )
                    ? 
                    {
                        model = "models/weapons/w_models/w_rocket.mdl"
                        type = "rocket"
                        weapon = "tf_weapon_rocketlauncher"
                        id = 205
                    }
                    : 
                    {
                        model = "models/weapons/w_models/w_grenade_grenadelauncher.mdl"
                        type = "grenade"
                        weapon = "tf_weapon_grenadelauncher"
                        id = 206
                    }

                    local model_name = projectile_model.model
                    local model_type = projectile_model.type
                    local model_weapon = projectile_model.weapon
                    local model_id = projectile_model.id

                    // // printl( projectile_model.model )

                    local fake_projectile = SpawnEntityFromTable( "prop_dynamic",
                    {
                        model = model_name
                        origin = cur_origin
                        angles = "0 0 0"
                        targetname = model_type
                        modelscale = 1
                        skin = 1
                    })

                    CursesScript.DispatchParticleEffectEx
                    ( 
                        "ghost_appearation", 
                        fake_projectile.GetOrigin(), 
                        Vector( 0, 0, 0 ), 
                        [ -1, -1 ], 
                        fake_projectile, 
                        null
                    )

                    local sound_range = ( 40 + ( 20 * log10( 8000 / 36.0 ) ) ).tointeger();

                    EmitSoundEx
                    ({
                        sound_name = "misc/halloween/spell_mirv_explode_secondary.wav",
                        origin = fake_projectile.GetOrigin(),
                        volume = 1
                        sound_level = sound_range 
                        channel = RandomInt(40, 80)
                        entity = null 
                        filter_type = RECIPIENT_FILTER_GLOBAL
                    });

                    fake_projectile.ValidateScriptScope();
                    fake_projectile.GetScriptScope().fixed_angle_offset <- RandomFloat( 0, 360 );
                    fake_projectile.GetScriptScope().qaDownwardAngle <- RandomFloat( -75, -15 )
                    fake_projectile.GetScriptScope().stWepClassname <- model_weapon
                    fake_projectile.GetScriptScope().iWepID <- model_id
                    fake_projectile.GetScriptScope().hProjOwner <- self
                    self.GetScriptScope().vecSavedOrigin <- cur_origin
                    aryProjectilesToRotate.append( fake_projectile )
                }

                flProjectileSwarmStamp = cur_time
            }

            foreach ( i, entry in aryProjectilesToRotate )
            {
                // Get player scale
                local player_scale = self.GetModelScale()
                if ( entry == null || !entry.IsValid() )
                {
                    continue
                }

                if ( player_scale == 0 ) player_scale = 1.0;
            
                // Base orbit radius
                local base_radius = 80.0;
                local orbit_radius = base_radius * player_scale;
            
                // Use the FIXED angle offset instead of recalculating based on array position
                local fixed_offset = entry.GetScriptScope().fixed_angle_offset || 0;
                local rotation_speed = 90.0;
                local current_angle = ( cur_time * rotation_speed + fixed_offset ) % 360;
            
                // Convert to radians
                local angle_rad = current_angle * ( PI / 180.0 );
            
                // Calculate orbit position
                local orbit_x = cos( angle_rad ) * orbit_radius;
                local orbit_y = sin( angle_rad ) * orbit_radius;
            
                // Much slower random up/down movement
                local random_seed = fixed_offset; // Use fixed offset as seed instead of array index
                local random_time = cur_time + random_seed;
                local slow_frequency = 0.3 + ( ( fixed_offset / 360.0 ) * 0.5 );
                local orbit_z = sin( random_time * slow_frequency ) * 25 * player_scale;
            
                // Set position relative to player
                local player_pos = self.EyePosition();
                local new_pos = Vector
                ( 
                    player_pos.x + orbit_x,
                    player_pos.y + orbit_y,
                    player_pos.z + orbit_z
                );
            
                entry.SetAbsOrigin( new_pos );
                entry.SetAbsAngles( QAngle( -90, 0 ) );
            }

            if ( aryProjectilesToRotate.len() >= 25 )
            {
                local angle_delay = 7
                local fire_delay = 10

                if ( ( flBeginProjectileSwarm + angle_delay ) <= cur_time )
                {
                    // // // printl( "need to begin" )

                    if ( bTriggerSwarmSound == false )
                    {
                        bTriggerSwarmSound = true

                        local sound_range = ( 40 + ( 20 * log10( 8000 / 36.0 ) ) ).tointeger();

                        EmitSoundEx
                        ({
                            sound_name = "misc/doomsday_cap_open_start.wav",
                            origin = cur_origin,
                            volume = 1
                            sound_level = sound_range 
                            channel = RandomInt(40, 80)
                            entity = null 
                            filter_type = RECIPIENT_FILTER_GLOBAL
                            pitch = 145
                        });
                    }

                    foreach ( i, entry in aryProjectilesToRotate )
                    {
                        if ( entry == null || !entry.IsValid() )
                        {
                            continue
                        }

                        // Get the angle this projectile is at relative to player
                        local fixed_offset = entry.GetScriptScope().fixed_angle_offset || 0;
                        local current_rotation = ( cur_time * 90.0 + fixed_offset ) % 360;
                        
                        // Convert to radians for calculations
                        local angle_rad = current_rotation * ( PI / 180.0 );
                        
                        // Calculate horizontal direction
                        local horizontal_direction = Vector( cos( angle_rad ), sin( angle_rad ), 0 );
                        
                        // Aim outward and downward ( adjust downward_angle as needed )
                        local downward_angle = entry.GetScriptScope().qaDownwardAngle
                        local pitch_rad = downward_angle * ( PI / 180.0 );
                        
                        // Create the aimed direction
                        local aim_direction = Vector( 
                            horizontal_direction.x * cos( pitch_rad ),
                            horizontal_direction.y * cos( pitch_rad ),
                            sin( pitch_rad )
                        );
                        
                        // Convert to angles and apply
                        local aim_angles = CursesScript.VectorAngles( aim_direction );
                        entry.SetAbsAngles( aim_angles );

                        if ( !( "HasParticle2" in entry.GetScriptScope() ) ) 
                        {
                            // // printl("add particle 2")
                            entry.GetScriptScope().HasParticle2 <- 1
                            
                            CursesScript.DispatchParticleEffectEx
                            ( 
                                "duel_blue_burst", 
                                entry.GetOrigin(), 
                                Vector( 0, 0, 0 ), 
                                [ -1, -1 ], 
                                entry, 
                                null
                            )
                        }
                    }
                }

                if ( ( flBeginProjectileSwarm + fire_delay ) <= cur_time )
                {
                    // // printl( "need to fire" )
                    local vecCurEyeAngles = self.EyeAngles()

                    // Disabling this grants the boss the ability to teleport back to where the attack was initiated
                    if (self.GetScriptScope().vecSavedOrigin != cur_origin)
                    {
                        self.GetScriptScope().vecSavedOrigin = cur_origin
                    }

                    aryProjectilesToRotate = CursesScript.IterateArray( aryProjectilesToRotate, function( slot, entry )
                    {
                        local bool = KEEP_ENTRY
                        if ( entry == null || !entry.IsValid() )
                        {
                            return REMOVE_ENTRY
                        }
                        local entry_angles = entry.GetAbsAngles()
                        local wep = entry.GetScriptScope().stWepClassname
                        local id = entry.GetScriptScope().iWepID
                        local owner = entry.GetScriptScope().hProjOwner
                        local attrib_table =
                        {
                            "damage bonus" : 2
                            "fuse bonus" : 0.5
                        }

                        // Convert entry angles to player view angles format
                        // entry_angles is QAngle( pitch, yaw, roll )
                        // pl.v_angle expects Vector( pitch, yaw, roll )
                        local aim_vector = Vector( entry_angles.x, entry_angles.y, entry_angles.z )

                        owner.Teleport( true, entry.GetOrigin() + Vector( 0, 0, -68 ), false, QAngle(), false, Vector() )
                        
                        SetPropVector( owner, "pl.v_angle", aim_vector )
                        local wep = CursesScript.FireProjectile( owner, wep, id, attrib_table )
                        wep.ValidateScriptScope()
                        wep.GetScriptScope().bIsFakeWep <- true
                        SetPropVector( owner, "pl.v_angle", owner.EyeAngles().Forward() ) // Reset to original
                        
                        entry.Destroy()

                        owner.Teleport( true, owner.GetScriptScope().vecSavedOrigin, false, QAngle(), false, Vector() )
                        
                        return bool
                    })

                    bProjectileSwarming = false
                    bTriggerSwarmSound = false

                    local sound_range = ( 40 + ( 20 * log10( 8000 / 36.0 ) ) ).tointeger();

                    EmitSoundEx
                    ({
                        sound_name = "misc/halloween/spell_meteor_cast.wav",
                        origin = cur_origin,
                        volume = 1
                        sound_level = sound_range 
                        channel = RandomInt(40, 80)
                        entity = null 
                        filter_type = RECIPIENT_FILTER_GLOBAL
                    });
                }
            }
        }
        
        /*
            ####################################
            FIREBALL
            ####################################
        */
        // if ( GetPropInt( self, "m_Shared.m_iNextMeleeCrit" ) == 0 )
        // {
        //     //// // printl( "fucker" )
            
        //     CursesScript.SpawnFireball( self, 1, 20 )

        //     SetPropInt( self, "m_Shared.m_iNextMeleeCrit", -2 )
        // }

        /*
            ####################################
            WHIRLWIND SCYTHE
            ####################################
        */
        // if ( GetPropInt( self, "m_Shared.m_iNextMeleeCrit" ) == 0 )
        // {
        //     //// // printl( "fucker 2" )
            
        //     CursesScript.SpawnWhirlwindSlicer( self )

        //     SetPropInt( self, "m_Shared.m_iNextMeleeCrit", -2 )
        // }
    }

    function ApplyProjectileThink()
    {
        for ( local projectile; projectile = FindByClassname( projectile, "tf_projectile*" ); ) 
        {
            projectile.ValidateScriptScope()
            local scope = projectile.GetScriptScope()

            if ( ( "HasThink" in scope ) )
            {
                continue
            }

            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 AddThinkBool = false

            // iterating over table is slow
            if ( owner_netprop != null )
            {
                AddThinkBool = true
            }
            else if ( thrower_netprop != null )
            {
                AddThinkBool = true
            }
            else if ( owner_launcher != null )
            {
                AddThinkBool = true
            }
            else if ( owner_entity != null )
            {
                AddThinkBool = true
            }

            if ( AddThinkBool == false )
            {
                return
            }

            // // printl( "applying for: "+projectile )

            scope.ProjectileThink <- function() 
            { 
                foreach ( name, func in ThinkTable_Projectile ) 
                {
                    if ( func )
                    {
                        func.call( this )
                    }
                }

                return -1 
            }
            
            scope.HasThink <- 1
            scope.flCurLifetime <- 0
            scope.flActualLifetime <- 0
            scope.flBeginLifetime <- Time()
            scope.ThinkTable_Projectile <- {}

            AddThinkToEnt( projectile, "ProjectileThink" )
        }
    }

    function HomingProjectile()
    {
        if ( hLockTarget == null || !hLockTarget.IsValid() || !hLockTarget.IsAlive() )
        {
            return
        }

        if ( self == null || !self.IsValid() )
        {
            return 
        }

        local vecTargetOrigin = hLockTarget.GetAttachmentOrigin( hLockTarget.LookupAttachment( "flag" ) )
        local vecProjectileOrigin = self.GetOrigin()
        local vecCurrentVelocity = self.GetVelocity()
        
        // Calculate direction to target
        local vecToTarget = vecTargetOrigin - vecProjectileOrigin
        local flTargetDistance = vecToTarget.Length()
        if ( flTargetDistance > 0 )
            vecToTarget = vecToTarget * ( 1.0 / flTargetDistance ) // Normalize
        
        // Get current direction
        local vecCurrentDirection = Vector( 0, 0, 0 )
        local flCurrentSpeed = vecCurrentVelocity.Length()
        if ( flCurrentSpeed > 0 )
            vecCurrentDirection = vecCurrentVelocity * ( 1.0 / flCurrentSpeed ) // Normalize
        
        // Calculate homing factors ( 360 strength and 90 turn speed = perfect tracking )
        local flHomingFactor = iHomingStrength / 360.0  // 1.0 = perfect homing
        local flTurnSpeedFactor = iHomingTurnSpeed / 90.0  // 1.0 = perfect turning
        
        // Calculate how much we can turn this frame
        local flFrameTime = FrameTime()
        local flMaxTurnThisFrame = flTurnSpeedFactor * flFrameTime * 5.0 // Adjust multiplier as needed
        flMaxTurnThisFrame = ( flMaxTurnThisFrame > 1.0 ) ? 1.0 : flMaxTurnThisFrame
        
        // Calculate desired direction based on homing strength
        local vecDesiredDirection = vecCurrentDirection * ( 1.0 - flHomingFactor ) + vecToTarget * flHomingFactor
        local flDesiredLength = vecDesiredDirection.Length()
        if ( flDesiredLength > 0 )
            vecDesiredDirection = vecDesiredDirection * ( 1.0 / flDesiredLength ) // Normalize
        
        // Apply turn speed limitation
        local vecFinalDirection = vecCurrentDirection * ( 1.0 - flMaxTurnThisFrame ) + vecDesiredDirection * flMaxTurnThisFrame
        local flFinalLength = vecFinalDirection.Length()
        if ( flFinalLength > 0 )
            vecFinalDirection = vecFinalDirection * ( 1.0 / flFinalLength ) // Normalize
        
        // Set velocity at constant speed
        local vecNewVelocity = vecFinalDirection * iMaxVelocity
        self.SetAbsVelocity( vecNewVelocity )

        // Turn projectile toward target 

        local vecTurn = CursesScript.VectorAngles( vecFinalDirection )
        self.SetAbsAngles( vecTurn )
    }

    function CreateCurseProjectile()
    {
        if ( hLockTarget == null || !hLockTarget.IsValid() || !hLockTarget.IsAlive() || GetPropInt( mvm_logic_entity, "m_bMannVsMachineBetweenWaves" ) == true || !("CursesScript" in getroottable()))
        {
            self.Destroy()
            return
        }

        /* 
            FOLLOW TIMER
        */

        local cur_time = Time()
        local extra_time = ( iDistFromVictim / 500.0 ) * 1.5
        local ActiveTime = 2 + extra_time
        local InitialDelay = 1.5
        local FadeTime = InitialDelay / 255.0  // Time per increment ( ~0.00588 seconds )

        if ( flVisible < 255 && ( flFadeinTime + FadeTime ) <= cur_time )
        {
            flVisible += 5
            SetPropInt( hHomingModel, "m_nRenderMode", 1 )

            CursesScript.SetEntityColor( hHomingModel, 255, 255, 255, flVisible )
            flFadeinTime = cur_time
        }

        if ( ( flBeginLifetime + ActiveTime + InitialDelay ) <= cur_time )
        {
            local cur_origin = self.GetOrigin()
            DispatchParticleEffect( "eb_tp_escape_bits", self.GetOrigin(), Vector() )
            self.Destroy()

            local sound_range = ( 40 + ( 20 * log10( 8000 / 36.0 ) ) ).tointeger();

            EmitSoundEx
            ({
                sound_name = "misc/halloween/spell_spawn_boss_disappear.wav",
                origin = cur_origin,
                volume = 1
                sound_level = sound_range 
                channel = RandomInt(40, 80)
                entity = null 
                filter_type = RECIPIENT_FILTER_GLOBAL
            });

            return
        }
        
        /*
            PROJECTILE COLLISION; CAUSE CURSE
        */

        local uber_conds = 
        [ 
            5, // stock
            14, // bonk
            51, // mvm spawn
            52, // canteen
            57, // wheel of fate
        ]

        local bCantBeHurt = true

        foreach ( cond in uber_conds )
        {
            if ( hLockTarget.GetClassname() != "player" )
                continue
            
            if ( hLockTarget.InCond( cond ) )
            {
                bCantBeHurt = false
            }
        }

        foreach ( k, v in hLockTarget.GetScriptScope() )
        {
            // printl("k: "+k)
            // printl("v: "+v)

            if 
            (
                k == "SufferingCurse" 
                || k == "SufferingCurse2"
                || k == "SufferingCurse3"
                || k == "SufferingCurse4"
                || k == "SufferingCurse5"
                || k == "CurseImmunity"
            )
            {
                bCantBeHurt = false
                return
            }
        }
        
        if ( ( flBeginLifetime + InitialDelay ) <= cur_time && CursesScript.CheckEntityIntersection( self, hLockTarget ) && bCantBeHurt == true )
        {
            local sound_range = ( 40 + ( 20 * log10( 4000 / 36.0 ) ) ).tointeger();
            DispatchParticleEffect( "halloween_ghost_smoke", hLockTarget.GetOrigin(), Vector() )

            EmitSoundEx
            ({
                sound_name = "weapons/bumper_car_hit_ghost.wav",
                origin = hLockTarget.GetOrigin(),
                volume = 1
                sound_level = sound_range 
                channel = RandomInt(40, 80)
                entity = hLockTarget 
                filter_type = RECIPIENT_FILTER_SINGLE_PLAYER
            })
            self.Destroy()

            local scope = hLockTarget.GetScriptScope()

            CursesScript.CurseTarget( hLockTarget )
            
        }
    }

    function DisplayPlayerHUD()
    {
        if ( !( "player_text" in self.GetScriptScope() ) || player_text == null || !player_text.IsValid() )
		{
			return
		}

        // check if no ary, if not, return
        if ( !( "aryAbilityCooldownTable" in this ) )
        {
            //////// // printl( "No ary ability" )
            return 
        }

        //////////// // printl( "display our cooldowns" )
        local cumulative_message = ""; // Initialize the cumulative message string

        // First, run a check for duplicate entries
        local seen_meters = {};
        
        // Loop backwards to safely remove items

        for ( local i = aryAbilityCooldownTable.len() - 1; i >= 0; i-- )
        {
            local meter = aryAbilityCooldownTable[ i ].meter;

            if ( meter in seen_meters )
            {
                // Duplicate meter found, remove this entry
                aryAbilityCooldownTable.remove( i );
            }
            else
            {
                // First time we've seen this meter, record it
                seen_meters[ meter ] <- true;
            }
        }

        foreach ( index, entry in aryAbilityCooldownTable )
        {
            // Start with the base y value of the entry
            local adjusted_y = entry.y; 
            local is_newline = false; 
        
            // Add the current message to the cumulative message with a new line
            if ( index > 0 ) 
            {
                cumulative_message += "\n"; // New line before adding the next message

                if ( entry.message.find( "\n" ) != null )
                {
                    // print( "New line found in message: " + entry.message ); // Print a message when a new line is found
                    is_newline = true; 
                }
            }
            cumulative_message += entry.message; 

            player_text.KeyValueFromString( "message", cumulative_message );
            player_text.KeyValueFromString( "x", entry.x.tostring() );
            player_text.KeyValueFromString( "y", adjusted_y.tostring() ); // Use adjusted_y instead of modifying entry.y
            player_text.KeyValueFromString( "channel", entry.channel.tostring() );
            player_text.KeyValueFromString( "color", "255 0 0" );


            player_text.AcceptInput( "Display", "", self, self );
        }
    }

    // MISSION SPECIFIC FUNCTIONS

    function ResetCursedPlayer( hPlayer, iCurseType, bOnRespawn )
    {
        // printl("removing curse via function")
        local primary = CursesScript.GetItemInSlot( hPlayer, 0 )
        local secondary = CursesScript.GetItemInSlot( hPlayer, 1 )
        local melee = CursesScript.GetItemInSlot( hPlayer, 2 )
        local scope = hPlayer.GetScriptScope()
        local cur_origin = hPlayer.GetOrigin()

        scope.CurseImmunity <- 1

        EntFireByHandle(hPlayer, "RunScriptCode", "if (`CurseImmunity` in self.GetScriptScope() ) { delete self.GetScriptScope()[`CurseImmunity`] }", 5, null, null)
        
        switch (iCurseType)
        {
            case CURSE_GIANT:
                // printl("curse giant removed")
                if ( bOnRespawn == CURSE_EXPIRE )
                {
                    if ( hPlayer.GetPlayerClass() == TF_CLASS_MEDIC )
                    {
                        primary = secondary
                    }
                    local grow_time = 1.0

                    if ( primary != null && primary.IsValid() )
                    {
                        primary.Destroy()
                    }

                    hPlayer.SetHealth( hPlayer.GetMaxHealth() )
                    hPlayer.AddCondEx( TF_COND_INVULNERABLE_CARD_EFFECT, 1.5, null )
                    hPlayer.AddCondEx( TF_COND_SPEED_BOOST, 3, null )
                    EntFireByHandle( hPlayer, "RunScriptCode", "self.Regenerate( true )", SINGLE_TICK * 2, null, null )
                    EntFireByHandle( hPlayer, "RunScriptCode", "CursesScript.UnstuckEntity( self )", SINGLE_TICK + grow_time, null, null )
                    SetPropBool( hPlayer, "m_bIsMiniBoss", false )
                    hPlayer.SetModelScale( 1, 1 )
                }

                // incase we somehow have the curse on us
                if ( "SufferingCurse" in scope )
                {
                    delete scope[ "SufferingCurse" ]
                }

            break;
            
            case CURSE_MEDIEVAL:
                // printl("curse medieval removed")
                local sound = "ambient/medieval_dooropen.wav"
                local stop_script = format( "EmitSoundEx({sound_name = \"%s\", flags = SND_STOP, entity = self, filter_type = RECIPIENT_FILTER_GLOBAL})", sound )
                EntFireByHandle( hPlayer, "RunScriptCode", stop_script, 0.18, null, null )

                local sound_range = ( 40 + ( 20 * log10( 4000 / 36.0 ) ) ).tointeger();


                if ( bOnRespawn == CURSE_EXPIRE )
                {
                    if ( !( "SufferingCurse2" in scope ) )
                    {
                        return
                    }
                    
                    if ( melee != null && melee.IsValid() )
                    {
                        melee.Destroy()
                    }

                    hPlayer.Weapon_Switch( primary )
                    EntFireByHandle( hPlayer, "RunScriptCode", "self.Regenerate( true )", SINGLE_TICK * 2, null, null )

                    EmitSoundEx
                    ({
                        sound_name = "ambient/medieval_doorclose.wav",
                        origin = hPlayer.GetOrigin(),
                        volume = 1
                        delay = -0.84
                        sound_level = sound_range 
                        channel = RandomInt(40, 80)
                        entity = hPlayer 
                        filter_type = RECIPIENT_FILTER_SINGLE_PLAYER
                    });
                }

                if ( "SufferingCurse2" in scope )
                {
                    delete scope[ "SufferingCurse2" ]
                }

            break;

            case CURSE_TELEPORT:
                // printl("curse tele removed")
                if ( "SufferingCurse3" in scope )
                {
                    delete scope[ "SufferingCurse3" ]
                }

            break;

            case CURSE_BOMBHEAD:
                // printl("curse bobm removed")

                for (local cap; cap = FindByClassname(cap, "obj_teleporter");)
                {
                    local model = "models/props_gameplay/cap_circle_256.mdl"
                    local tele = cap.GetModelName()
                    cap.ValidateScriptScope()

                    if ( tele == model && cap.GetScriptScope().owner == hPlayer )
                    {
                        cap.Destroy()
                    }
                }

                if ( bOnRespawn == CURSE_EXPIRE )
                {
                    if ( !( "iCurseStage" in scope ) )
                    {
                        return
                    }
                    if (scope.iCurseStage != 2 )
                    {
                        // printl( "now we die" )
                        SetPropBool( gamerules, "m_bPlayingMannVsMachine", false )
                        local trigger = SpawnEntityFromTable( "trigger_hurt", 
                        {
                            targetname = "__bighurt_hurt",
                        })

                        if ( hPlayer.IsAlive() )
                        {
                            hPlayer.TakeDamage( 99999, DMG_BLAST, trigger )
                        }

                        SetPropBool( gamerules, "m_bPlayingMannVsMachine", true )
                        trigger.Destroy()

                        if ( "iCurseStage" in scope )
                        {
                            delete scope[ "iCurseStage" ]
                        }
                    }

                    hPlayer.AddCondEx( TF_COND_INVULNERABLE_CARD_EFFECT, 3, null )
                    hPlayer.AddCondEx( TF_COND_SPEED_BOOST, 3, null )

                    local sound_range = ( 40 + ( 20 * log10( 4000 / 36.0 ) ) ).tointeger();   

                    local explo_sound = "ambient/explosions/explode_"+RandomInt( 1, 9 )+".wav"
                    PrecacheSound( explo_sound )

                    EmitSoundEx
                    ({
                        sound_name = explo_sound,
                        origin = cur_origin,
                        volume = 1
                        sound_level = sound_range 
                        channel = RandomInt(40, 80)
                        entity = hPlayer 
                        filter_type = RECIPIENT_FILTER_SINGLE_PLAYER
                    });
                    DispatchParticleEffect( "merasmus_dazed_explosion", cur_origin, Vector() )
                }

                if ( "SufferingCurse4" in scope )
                {
                    delete scope[ "SufferingCurse4" ]
                    if ( "iCurseStage" in scope )
                    {
                        delete scope[ "iCurseStage" ]
                    }
                    hPlayer.SetForcedTauntCam( 0 )
                }

            break;

            case CURSE_FEAR:
                // printl("curse fear removed")
                if ( "SufferingCurse5" in scope )
                {
                    delete scope[ "SufferingCurse5" ]
                    delete scope[ "flFear" ]
                    delete scope[ "flMaxFear" ]

                    CursesScript.DisplayHudElement( hPlayer, "meter_GhostScare", function()
                    {
                        return ""
                    })
                }
            break;

            default:

            break
        }
        
        local curse_cntrol = FindByName(null, "curse_cntrl")

        if ( curse_cntrol != null && curse_cntrol.IsValid() )
        {
            curse_cntrol.ValidateScriptScope()
            local curse_scope = curse_cntrol.GetScriptScope()

            curse_scope.aryPlayersCursed = CursesScript.IterateArray( curse_scope.aryPlayersCursed, function( slot, entry )
            {
                if (entry.cursed == hPlayer)
                {
                    // printl("try remove entry")
                    return REMOVE_ENTRY
                }
            })
        }
    }

    // copied from seel_ins
    // the activation code is "the seel has met the spheal"

    function FindIndexOfIcon( name, entity )
    {
        local i = 0 //Check: do I start at 0 or at 1?
        local two = ""
        local i2 = 0
        for( i; i < 24; i+=1 ) //Check: and thus, do I end at 24, or 23?
        {
            if ( i >= 12 ) two = "2.",i2 = 12;

            local curIconName = GetPropStringArray( entity, "m_iszMannVsMachineWaveClassNames"+two, i-i2 )
            if ( curIconName == name )
                return i;
        }
        // printf( "Icon name '%s' not found!\n",name )
        return null;
    }

    function ChangeIconByIndex( index, name, entity )
    {
        local two = ""
        local i2 = 0
        if ( index >= 12 ) two = "2.",i2 = 12; //Was this dot ever fixed? Getting fixed? Hopefully.
        SetPropStringArray( entity, "m_iszMannVsMachineWaveClassNames"+two, name, index-i2 )
    }

    function ChangeIconByName( oldName, newName, entity )
    {
        local iconIndex = FindIndexOfIcon( oldName, entity )
        if ( iconIndex == null ) return;

        // printf( "%s found at %d! Changing to %s...\n",oldName,iconIndex,newName )
        ChangeIconByIndex( iconIndex,newName, entity )
    }

    function GetIconFlags(iconName)
	{
		local iconIndex = FindIndexOfIcon(iconName, mvm_logic_entity)
		if (iconIndex == null) return null;

		local two = ""
		local i2 = 0
		if (iconIndex >= 12) two = "2.",i2 = 12; //Was this dot ever fixed? Getting fixed? Hopefully.
		local flags = GetPropIntArray(mvm_logic_entity, "m_nMannVsMachineWaveClassFlags"+two, iconIndex)
		return flags
	}


    function ChangeIconFlags(iconName,flags)
	{
		local iconIndex = FindIndexOfIcon(iconName, mvm_logic_entity)

		if (iconIndex == null) return;

		local oldFlags = GetIconFlags(iconName)
		printf("%s found at %d! Changing its flags from %d to %d...\n",iconName,iconIndex,oldFlags,flags)

		local two = ""
		local i2 = 0
		if (iconIndex >= 12) two = "2.",i2 = 12; //Was this dot ever fixed? Getting fixed? Hopefully.

		SetPropIntArray(mvm_logic_entity, "m_nMannVsMachineWaveClassFlags"+two, flags, iconIndex-i2)
	}

    // boss

    function SpawnPillarFlame(attacker, delay, origin, radius)
    {
        // // printl("delay: "+delay)
        // DebugDrawCircle(origin, Vector(0, 0, 255), 0, radius, true, 5)
        local end_delay = 0.1
        local attack_delay = delay + 1
        local warning = SpawnEntityFromTable("prop_dynamic",
        {
            model = "models/props_mvm/robot_spawnpoint_warning.mdl"
            origin = origin
            angles = Vector(0, 0, 0)
            disableshadows = 1
            targetname = "warning"
        })
        warning.ValidateScriptScope()
        warning.GetScriptScope().hAttacker <- attacker
        warning.GetScriptScope().attack_range <- radius
        warning.GetScriptScope().vecOrigin <- origin

        CursesScript.SetDestroyCallback( warning, function()
        {
            local aryEnemyList = CursesScript.GetEnemiesWithinArea( hAttacker, attack_range, vecOrigin )

            foreach (i, enemy in aryEnemyList)
            {   
                if ( enemy == null || !enemy.IsValid() || !enemy.IsAlive() )
                {
                    continue
                }

                enemy.TakeDamageCustom
                ( 
                    null, 
                    hAttacker, 
                    hAttacker.GetActiveWeapon(), 
                    Vector( 0, 0, 0 ), 
                    enemy.GetOrigin(), 
                    60, 
                    DMG_BURN, 
                    TF_DMG_CUSTOM_BURNING
                )
                CursesScript.Ignite( enemy, 5.0, 8 ) 
            }

            local sound_range = ( 40 + ( 20 * log10( 4000 / 36.0 ) ) ).tointeger();

            EmitSoundEx
            ({
                sound_name = "ambient/fire/gascan_ignite1.wav",
                origin = vecOrigin,
                volume = 1
                sound_level = sound_range 
                channel = RandomInt(40, 80)
                entity = self 
                filter_type = RECIPIENT_FILTER_GLOBAL
            });
        })

        EntFireByHandle( warning, "Kill", "", attack_delay + end_delay, null, null )

        CursesScript.DispatchParticleEffectEx
        ( 
            "lava_fireball_01", 
            origin
            Vector( 0, 0, 0 ), 
            [ attack_delay, attack_delay + end_delay ], 
            null, 
            null
        )
    }

    function RegenBossHealth()
    {
        if ( bIsHealing == false )
            return;

        local max_health = self.GetMaxHealth();
        local cur_time = Time();

        // First run → set up tracking
        if ( !( "heal_start_time" in this ) )
        {
            this.heal_start_time <- cur_time;
            this.heal_end_time <- cur_time + 5.0; // heal_time
            this.heal_start_health <- self.GetHealth();
        }

        // Fraction of heal_time passed ( 0 → 1 )
        local t = ( cur_time - this.heal_start_time ) / ( this.heal_end_time - this.heal_start_time );
        if ( t > 1.0 ) t = 1.0;

        // Interpolate smoothly between start and max
        local new_health = this.heal_start_health + ( max_health - this.heal_start_health ) * t;
        self.SetHealth( new_health.tointeger() );

        // Done?
        if ( t >= 1.0 )
        {
            bIsHealing = false;
            delete this.heal_start_time;
            delete this.heal_end_time;
            delete this.heal_start_health;
        }
    }

    function DrainBossHealth()
    {
        local cur_health = self.GetHealth()
        self.AddCondEx( TF_COND_PREVENT_DEATH, -1, null )

        if ( cur_health == 1 )
        {
            return
        }

        if ( cur_health > 1 )
        {
            self.SetHealth( cur_health - 100 )
        }
    }

    function PrepareFinalBoss()
    {
        local sound_range = ( 40 + ( 20 * log10( 4000 / 36.0 ) ) ).tointeger();

        for ( local player; player = FindByClassname( player, "player" ); )
        {
            // theme played twice, must stop twice
            EmitSoundEx
            ({
                sound_name = "usum_unecrozma_bosstheme.mp3",
                origin = player.GetOrigin(),
                volume = 1
                sound_level = sound_range 
                channel = RandomInt(40, 80)
                entity = player 
                flags = SND_STOP
                filter_type = RECIPIENT_FILTER_GLOBAL
            });

            EmitSoundEx
            ({
                sound_name = "usum_unecrozma_bosstheme.mp3",
                origin = player.GetOrigin(),
                volume = 1
                sound_level = sound_range 
                channel = RandomInt(40, 80)
                entity = player 
                flags = SND_STOP
                filter_type = RECIPIENT_FILTER_GLOBAL
            });
        }

        function RandomWaveNum()
        {
            local path_data =
            [
                { pos = Vector( -1842, -5926, -118 ), speed = 0, dir = QAngle(30, -100), stop = 1.5, dist = -50 },
                { pos = Vector(-1984, -6016, -64), speed = 2, dir = QAngle(30, 0), stop = 0, dist = -20 },
                { pos = Vector(-1910.6, -6080, 0), speed = 2, dir = QAngle(20, 30), stop = 0, dist = -20 },
                { pos = Vector(-1792, -6182.7, 55.7205), speed = 2, dir = QAngle(30, 110), stop = 0, dist = -20 },
                { pos = Vector( -1822, -5966, 20), speed = 2, dir = QAngle(0, 270), stop = 0.25, dist = -40 },
                { pos = Vector( -1842, -5826, -175 ), speed = 1, dir = QAngle(-30, 270), stop = 3.5, dist = -20}
            ];

            // Add camera movement data to each camera
            foreach (i, cam in CursesScript.CameraArray)
            {
                cam.ValidateScriptScope();
                local scope = cam.GetScriptScope();
                if ( "isThinking" in scope )
                {
                    continue
                }
            
                scope.isThinking <- 1
                scope.path_start_time <- Time();
                scope.path_data <- path_data
                scope.total_duration <- 0;
                scope.segment_times <- [];
                scope.bNoDelete <- true
                scope.flInitiateKill <- Time() + path_data[0].stop
                
                // Calculate total path duration and segment timing
                foreach (i, point in path_data)
                {
                    scope.segment_times.append(scope.total_duration);
                    scope.total_duration += point.speed + point.stop;
                }
            
                scope.CameraPathThink <- function()
                {
                    if ( !( "AllowThink" in this) || self == null || !self.IsValid() )
                    {
                        flInitiateKill = Time() + path_data[0].stop
                        return -1
                    }

                    // taunting fucks shit up
                    if ( hPlayer.IsTaunting() )
                    {
                        for ( local scene; scene = FindByClassname( scene, "instanced_scripted_scene" ); )
                        {
                            local owner = GetPropEntity(scene, "m_hOwner" )
                            if ( owner == hPlayer )
                            {
                                scene.Kill()
                                hPlayer.RemoveCondEx( TF_COND_TAUNTING, true )
                                break
                            }
                        }
                    }

                    local cur_time = Time();
                    local elapsed = cur_time - path_start_time;
                    
                    // Loop the path
                    if (elapsed >= total_duration)
                    {
                        if (total_duration > 0) 
                        {
                            path_start_time = cur_time - (elapsed % total_duration);
                            elapsed = elapsed % total_duration;
                            if (bNoDelete == false && flInitiateKill <= Time() )
                            {
                                // // printl("now we die")
                                hPlayer.RemoveHudHideFlags(262143)
                                EntFireByHandle(self, "Disable", "", -1, hPlayer, null);
                                self.Kill()
                                CursesScript.CameraArray.clear()
                            }
                        } 
                        else 
                        {
                            return -1
                        }
                    }
                    
                    // Find current segment
                    local current_segment = 0;
                    local segment_progress = 0.0;
                    local in_stop_phase = false;

                    for (local i = 0; i < path_data.len(); i++)
                    {
                        local segment_start = segment_times[i];
                        local move_duration = path_data[i].speed;
                        local stop_duration = path_data[i].stop;

                        local segment_end = segment_start + move_duration + stop_duration;
                        
                        if (elapsed >= segment_start && elapsed < segment_end)
                        {
                            current_segment = i;
                            local time_in_segment = elapsed - segment_start;
                            
                            if (time_in_segment < move_duration && move_duration > 0)
                            {
                                // In movement phase
                                segment_progress = time_in_segment / move_duration;
                                in_stop_phase = false;
                            }
                            else if ( stop_duration > 0 )
                            {
                                // In stop phase
                                segment_progress = 1.0;
                                in_stop_phase = true;
                            }

                            break;
                        }
                    }
                    
                    if (!in_stop_phase && path_data[current_segment].speed > 0)
                    {
                        // Get current and next path points
                        local current_path = path_data[current_segment];
                        local next_segment = (current_segment + 1) % path_data.len();
                        local path_cur = current_segment;
                        local next_path = path_data[next_segment];
                        local stop_duration = path_data[current_segment].stop; // Use current segment's stop duration
                        // // // printl("next path: "+next_segment )
                        // // // printl("cur path: "+path_cur)
                        
                        // Get start position (from previous segment's end or camera start)
                        local start_pos;
                        local start_angles;
                        if (current_segment == 0)
                        {
                            start_pos = self.GetOrigin();
                            start_angles = self.GetAbsAngles();
                        }
                        else
                        {
                            local prev_segment = current_segment - 1;
                            start_pos = path_data[prev_segment].pos;
                            start_angles = path_data[prev_segment].dir;
                        }
                        
                        // Semicircular arc interpolation in X/Y plane
                        local arc_progress = segment_progress * PI; // 0 to PI for semicircle
                        local arc_multiplier = sin(arc_progress); // Creates semicircle bulge
                        
                        // Calculate the straight line between start and end
                        local line_vec = Vector
                        (
                            current_path.pos.x - start_pos.x,
                            current_path.pos.y - start_pos.y,
                            0
                        );
                        
                        // Calculate perpendicular vector for the arc offset
                        local perp_vec = Vector(-line_vec.y, line_vec.x, 0);
                        local perp_length = sqrt(perp_vec.x * perp_vec.x + perp_vec.y * perp_vec.y);
                        if (perp_length > 0) 
                        {
                            perp_vec = Vector
                            (
                                perp_vec.x / perp_length,
                                perp_vec.y / perp_length,
                                0
                            );
                        }
                        
                        // Linear interpolation for base movement
                        local base_pos = Vector
                        (
                            start_pos.x + (current_path.pos.x - start_pos.x) * segment_progress,
                            start_pos.y + (current_path.pos.y - start_pos.y) * segment_progress,
                            start_pos.z + (current_path.pos.z - start_pos.z) * segment_progress
                        );
                        
                        // Add semicircular arc offset in X/Y plane
                        local arc_offset = current_path.dist * arc_multiplier;
                        local new_pos = Vector
                        (
                            base_pos.x + perp_vec.x * arc_offset,
                            base_pos.y + perp_vec.y * arc_offset,
                            base_pos.z
                        );
                    
                        // Interpolate angles gradually
                        local angle_progress = segment_progress * segment_progress; // Slower angle change at start
                        local new_angles = QAngle
                        (
                            start_angles.x + (current_path.dir.x - start_angles.x) * angle_progress,
                            start_angles.y + (current_path.dir.y - start_angles.y) * angle_progress,
                            start_angles.z + (current_path.dir.z - start_angles.z) * angle_progress
                        );

                        if ( self != null && self.IsValid() )
                        {
                            bNoDelete = false
                            self.SetAbsOrigin(new_pos);
                            self.SetAbsAngles(new_angles);
                        }
                    }
                    else
                    {
                        // In stop phase or zero speed - hold position
                        local current_path = path_data[current_segment];

                        if ( self != null && self.IsValid() )
                        {
                            self.SetAbsOrigin(current_path.pos);
                            self.SetAbsAngles(current_path.dir);
                        }
                    }
                
                    return -1; // Continue thinking
                }


                
                AddThinkToEnt(cam, "CameraPathThink");
            }

            // taken from Seel_ins ( SINS )
            // hello seelpit :3

            local iMerasIcon = RandomInt( 1, 666 )

            local name = "boss_merasmus"
            local other_name = "random_lite"
            local bool = GetPropInt( self, "m_bMannVsMachineBetweenWaves" )

            if ( iMerasIcon > 85 )
            {
                name = "random_lite"
                other_name = "boss_merasmus"
            }

            if ( "StopRandomizing" in this )
            {
                name = "boss_merasmus"
                other_name = "random_lite"

                CursesScript.ChangeIconByName( name, other_name, self )

                SetPropInt(mvm_logic_entity, "m_nMvMEventPopfileType", 1) // halloween
            }
            else 
            {   
                CursesScript.ChangeIconByName( name, other_name, self )
                SetPropInt( self, "m_nMannVsMachineWaveCount", RandomInt( 1, 999 ) )
                SetPropInt( self, "m_nMannVsMachineMaxWaveCount", RandomInt( 1, 999 ) )
            }

            return -1
        }

        mvm_logic_entity.ValidateScriptScope()
        mvm_logic_entity.GetScriptScope().RandomWaveNum <- RandomWaveNum
        AddThinkToEnt( mvm_logic_entity, "RandomWaveNum" )

        function NoBossCheese( hPlayer )
        {
            if ( hPlayer.IsBotOfType( TF_BOT_TYPE ) )
            {
                if ( hPlayer.HasBotTag("tag_real_boss") )
                {
                    // // // printl( "Hey! No Cheesing!" )

                    CursesScript.DispatchParticleEffectEx
                    ( 
                        "utaunt_portalswirl_purple_parent", 
                        hPlayer.GetOrigin() + Vector(0, 0, 120)
                        Vector( 0, 0, 0 ), 
                        [ -1, 5 ], 
                        null, 
                        null
                    )

                    hPlayer.Teleport( true, Vector( -1772, -6074, 798 ), false, QAngle(), true, Vector(0, 0, 0) )

                    local sound_range = ( 40 + ( 20 * log10( 8000 / 36.0 ) ) ).tointeger();

                    EmitSoundEx
                    ({
                        sound_name = "misc/halloween/merasmus_appear.wav",
                        origin = hPlayer.GetOrigin(),
                        volume = 1
                        sound_level = sound_range 
                        channel = RandomInt(40, 80)
                        entity = hPlayer 
                        filter_type = RECIPIENT_FILTER_GLOBAL
                    });
                    EmitSoundEx
                    ({
                        sound_name = "misc/halloween/merasmus_appear.wav",
                        origin = hPlayer.GetOrigin(),
                        volume = 1
                        sound_level = sound_range 
                        channel = RandomInt(40, 80)
                        entity = hPlayer 
                        filter_type = RECIPIENT_FILTER_GLOBAL
                    });
                }
            }
        }

        local underworld_trig = null 
        
        for ( local trig; trig = FindByClassname( trig, "trigger_hurt" ); )
        {
            // local trig_origin = trig.GetOrigin()
            // local origin_x = CursesScript.Round( trig_origin.x, 0 )
            // local origin_y = CursesScript.Round( trig_origin.y, 0 )
            // local origin_z = CursesScript.Round( trig_origin.z, 0 )
            // // // printl("trig: "+trig)
            // // // printl("x: "+origin_x)
            // // // printl("y: "+origin_y)
            // // // printl("z: "+origin_z)

            // The underworld trigger_hurt has no name
            if ( trig.GetName() == "" )
            {
                underworld_trig = trig 
                break
            }
        }
        
        underworld_trig.ValidateScriptScope()
        underworld_trig.GetScriptScope().NoBossCheese <- NoBossCheese

        AddOutput
        (
            underworld_trig, 
            "OnHurtPlayer", 
            "!activator", 
            "RunScriptCode", 
            "!caller.GetScriptScope().NoBossCheese( activator )", 
            0, 
            -1
        )
    }

    function SpawnWhirlwindSlicer( attacker )
    {
        local eye_angles = attacker.EyeAngles();
        local eye_pos = attacker.EyePosition();
        local proj_size = attacker.GetModelScale()

        local proj_name = "fake_scythe_"+RandomFloat( -9999, 9999 )

        local scythe = SpawnEntityFromTable( "tf_projectile_stun_ball",
        {
            origin = eye_pos, 
            angles = Vector( 0, 0, 0 ),
            teamnum = attacker.GetTeam(),
            basevelocity = Vector( 0, 0, 0 )
            targetname = proj_name
        });

        scythe.SetModelScale( proj_size, 0 )
        scythe.SetModelSimple( "models/weapons/c_models/c_scythe/c_scythe.mdl" )
        scythe.SetOwner( attacker )

        local scythe_glow = SpawnEntityFromTable( "tf_glow", 
        {
            GlowColor = "0 175 200 255"
            target = proj_name
        })

        scythe.ValidateScriptScope()
        local scope = scythe.GetScriptScope()
        scope.hGlowEnt <- scythe_glow
        CursesScript.SetDestroyCallback( scythe, function()
        {
            // if wave resets with projectile active, this reports null. 
            // fix here
            if ( !hGlowEnt.IsValid() )
            {
                return
            }

            if ( hGlowEnt.IsValid() || hGlowEnt != null )
            {
                //// // printl( "destroying: "+hGlowEnt )
                hGlowEnt.Destroy()
            }
        })

        scope.owner <- attacker
        scope.weapon <- attacker.GetActiveWeapon()
        scope.timestamp <- Time()
        scope.range <- 550.0 * proj_size
        scope.speed <- 800.0 * proj_size
        scope.spin_rotation <- 0
        scope.target_list <- []
        scope.angle <- PI // start from side ( left of the circle )
        scope.flSoundTime <- 0

        scythe.SetAbsVelocity( Vector( 0, 0, 0 ) )
        scythe.SetModelScale( 1.5, 0 )
        scythe.SetMoveType( 8, 0 )
        scythe.SetCollisionGroup( 2 )
        scythe.SetSolid( 0 )
        scythe.SetSolidFlags( 4 )
        scythe.SetSize( Vector( 0, 0, 0 ), Vector( 0, 0, 0 ) )

        local func_list = 
        {
            "WhirlwindScythe" : CursesScript.WhirlwindScythe
        }
        
        // because projectiles dont immediately get their thinks for some reason
        // we need to delay this
        scope.pending_functions <- func_list
        EntFireByHandle( scythe, "RunScriptCode", "CursesScript.AddFunctionListToTable( self.GetScriptScope().pending_functions, CursesScript.GetEntityThinkTable( self ) )", SINGLE_TICK, null, null )
    }

    function WhirlwindScythe()
    {
        local player = self.GetOwner()
        local cur_time = Time()
        local cur_origin = self.GetOrigin()

        local mins = Vector( -15, -15, -150 )
        local maxs = Vector( 15, 15, 15 )
        local scope = self.GetScriptScope()

        // yes this is made with claude ( an ai )
        // cope and seethe mortal 

        // Get player scale and normalize calculations
        local player_scale = player.GetModelScale()
        local scale_factor = 1.0 / player_scale  // Normalize to scale 1.0
        
        if ( ( timestamp + 2 ) <= cur_time )
        {
            if ( CursesScript.CheckEntityIntersection( self, player ) )
            {
                local sound_range = ( 40 + ( 20 * log10( 4000 / 36.0 ) ) ).tointeger();

                EmitSoundEx
                ({
                    sound_name = "ui/mm_medal_spin_hit.wav",
                    origin = cur_origin,
                    volume = 1
                    sound_level = sound_range 
                    channel = RandomInt(40, 80)
                    entity = self 
                    delay = -0.3
                    flags = SND_STOP
                    filter_type = RECIPIENT_FILTER_GLOBAL
                });
                self.Destroy()
            }
        }

        local angular_speed = speed / range
        angle += angular_speed * FrameTime()

        // Get player position and facing direction - normalize eye position
        local player_origin = player.GetOrigin()
        local eye_angles = player.EyeAngles()
        
        // Calculate normalized eye position ( as if player was scale 1.0 )
        local raw_eye_position = player.EyePosition()
        local eye_offset = raw_eye_position - player_origin
        local normalized_eye_offset = eye_offset * scale_factor
        local eye_location = player_origin + normalized_eye_offset

        // 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 ( using normalized range )
        local normalized_range = range * scale_factor
        local circle_center = eye_location + forward * ( normalized_range / 2.0 )

        // Calculate position on the arc
        local current_angle = ( PI/2 ) - angle

        local pos = Vector
        ( 
            circle_center.x + right.x * ( cos( current_angle ) * normalized_range / 2.0 ) + forward.x * ( sin( current_angle ) * normalized_range / 2.0 ),
            circle_center.y + right.y * ( cos( current_angle ) * normalized_range / 2.0 ) + forward.y * ( sin( current_angle ) * normalized_range / 2.0 ),
            eye_location.z - ( 30 * scale_factor )  // Scale the Z offset too
        )
        
        if ( self.IsValid() )
        {
            self.SetAbsOrigin( pos )
        }
        

        if ( self.IsValid() )
        {
            local delay = 0.6
            if ( ( flSoundTime + delay ) <= cur_time )
            {
                local sound_range = ( 40 + ( 20 * log10( 4000 / 36.0 ) ) ).tointeger();
                EmitSoundEx
                ({
                    sound_name = "ui/mm_medal_spin_hit.wav",
                    origin = cur_origin,
                    volume = 1
                    sound_level = sound_range 
                    channel = RandomInt(40, 80)
                    entity = self 
                    delay = -0.3
                    filter_type = RECIPIENT_FILTER_GLOBAL
                });

                local stop_script = format( "EmitSoundEx({sound_name = \"%s\", flags = SND_STOP, entity = self, filter_type = RECIPIENT_FILTER_GLOBAL})", "ui/mm_medal_spin_hit.wav" )
                EntFireByHandle( self, "RunScriptCode", stop_script, 0.6, null, null )

                flSoundTime = cur_time
            }
        }

        // Calculate the movement direction using normalized range
        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 using normalized calculations
        local current_pos = Vector( 
            player_origin.x + cos( current_world_angle ) * normalized_range,
            player_origin.y + sin( current_world_angle ) * normalized_range,
            player_origin.z + ( 50 * scale_factor )  // Normalize Z offset
        );

        local next_pos = Vector( 
            player_origin.x + cos( next_world_angle ) * normalized_range,
            player_origin.y + sin( next_world_angle ) * normalized_range,
            player_origin.z + ( 50 * scale_factor )  // Normalize Z offset
        );

        // 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() * 2.0;
        if ( spin_rotation >= 360.0 )
            spin_rotation -= 360.0

        // Apply rotation
        local final_angles = QAngle
        ( 
            forward_pitch,
            forward_yaw + spin_rotation,
            90
        );
        
        if ( self.IsValid() )
        {
            self.SetAbsAngles( final_angles )
        }

        local classnames = [ "player", "obj_*", "tank_boss" ]

        local hitbox_size = 80

        if ( self.IsValid() )
        {
            hitbox_size = 80 * self.GetModelScale()
        }

        

        // DebugDrawCircle( cur_origin, Vector( 255, 255, 0 ), 0, hitbox_size, true, 0.1 )

        foreach ( entry in classnames )
        {
            for ( local enemy; enemy = FindByClassnameWithin( enemy, entry, cur_origin, hitbox_size ); )
            {
                if ( self.IsValid() && 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(), 
                        110 * CursesScript.MULT_DAMAGE( player, weapon ), 
                        damage_type, 
                        attack_type 
                    )

                    // weapons\blade_slice_4.wav

                    local sound = RandomInt(2, 4)
                    local string = "weapons/blade_slice_"+sound+".wav"

                    local sound_range = ( 40 + ( 20 * log10( 4000 / 36.0 ) ) ).tointeger();

                    PrecacheSound(string)

                    EmitSoundEx
                    ({
                        sound_name = string
                        origin = enemy.GetOrigin(),
                        volume = 1
                        sound_level = sound_range 
                        channel = RandomInt(40, 80)
                        entity = enemy 
                        filter_type = RECIPIENT_FILTER_SINGLE_PLAYER
                    });
                }
            }
        }
    }

    function SpawnFireball( attacker, count, spread )
    {
        local eye_angles = attacker.EyeAngles();
        local eye_pos = attacker.EyePosition();

        local speed = 800; // fireball speed

        // Center the spread so it fans out from the middle
        local startAngleOffset = -( ( count - 1 ) * spread * 0.5 );

        for ( local p = 0; p < count; p++ )
        {
            // Clone the base angles
            local fire_angle = QAngle( eye_angles.x, eye_angles.y + ( startAngleOffset + p * spread ), eye_angles.z );

            // Convert to forward vector for velocity
            local forward = fire_angle.Forward();

            // Spawn the projectile
            local projectile = SpawnEntityFromTable( "tf_projectile_spellfireball",
            {
                origin = eye_pos + forward * 5, // small forward offset
                angles = fire_angle,
                teamnum = attacker.GetTeam(),
                basevelocity = forward * speed
            });

            projectile.SetOwner( attacker );
        }
    }

    function GiantCurse( )
    {
        /*
            CURSE OF GIANTS
        */

        local player = self

        if ( Time() >= flEndTimestamp || GetPropInt(player, "m_lifeState") != 0)
        {
            CursesScript.ResetCursedPlayer( player, CURSE_GIANT, CURSE_EXPIRE )

            foreach ( k, v in self.GetScriptScope().ThinkTable_Player )
            {
                if (k == "PrepareCurse")
                {
                    delete self.GetScriptScope().ThinkTable_Player[k]
                }
            }

            return
        }

        // printl( "Curse of giants" )

        local primary = CursesScript.GetItemInSlot( player, 0 )
        local secondary = CursesScript.GetItemInSlot( player, 1 )
        local melee = CursesScript.GetItemInSlot( player, 2 )
        local scope = player.GetScriptScope()
        local grow_time = 1.0

        local AttribList = 
        {
            // "provide on active" : 1
            "fire rate penalty HIDDEN" : 0.5
            "mod max primary clip override" : -1
            "mod no reload display only" : 1
            // "disable weapon switch" : 1
            "healing received penalty" : 0.01
            "ammo regen" : 1
            "CARD: move speed bonus" : 0.5
            "can overload" : 0
            "auto fires full clip" : 0
        }

        local AttribList_NoReload = 
        {
            // "provide on active" : 1
            // "disable weapon switch" : 1
            "card: damage bonus" : 1.5
            "ammo regen" : 1
            "healing received penalty" : 0.01
            "CARD: move speed bonus" : 0.5
        }

        // sound replays if we dont check for if alive
        if ( !( "SufferingCurse" in scope ) && player.IsAlive() )
        {
            scope.SufferingCurse <- 1
            CursesScript.DispatchParticleEffectEx( "utaunt_arcane_purple_sparkle", player.GetOrigin(), Vector(), [ -1, 10 ], player, null )


            local list = AttribList

            local sound_range = ( 40 + ( 20 * log10( 4000 / 36.0 ) ) ).tointeger();
            EmitSoundEx
            ({
                sound_name = "items/powerup_pickup_strength.wav",
                origin = player.GetOrigin(),
                volume = 1
                sound_level = sound_range 
                channel = RandomInt(40, 80)
                entity = player 
                filter_type = RECIPIENT_FILTER_SINGLE_PLAYER
            });

            switch( player.GetPlayerClass() )
            {
                case TF_CLASS_SCOUT:
                
                    if ( "CARD: move speed bonus" in list )
                    {
                        delete list[ "CARD: move speed bonus" ]
                    }

                    ClientPrint( player, 4, "CURSE OF GIANTS: Become Giant; Increased Respawn time" )
                break;
                case TF_CLASS_HEAVYWEAPONS:
                    list = AttribList_NoReload
                    ClientPrint( player, 4, "CURSE OF GIANTS: Become Giant; Increased Respawn time" )
                break;

                case TF_CLASS_SNIPER:
                    list = AttribList_NoReload
                    ClientPrint( player, 4, "CURSE OF GIANTS: Become Giant; Increased Respawn time" )
                break;

                case TF_CLASS_MEDIC:
                    primary = secondary

                    delete list[ "ammo regen" ]
                    
                    player.AddCustomAttribute( "heal rate bonus", 5, 10 )
                    ClientPrint( player, 4, "CURSE OF GIANTS: Become Giant; Increased Respawn time" )
                break;

                case TF_CLASS_PYRO:
                    list = AttribList_NoReload
                    ClientPrint( player, 4, "CURSE OF GIANTS: Become Giant; Increased Respawn time" )
                break;

                case TF_CLASS_SPY:
                    // spy may be exempted from a move speed penalty
                    if ( "CARD: move speed bonus" in list )
                    {
                        delete list[ "CARD: move speed bonus" ]
                    }

                    list[ "weapon spread bonus" ] <- 0
                    list[ "card: damage bonus" ] <- 2
                    ClientPrint( player, 4, "CURSE OF GIANTS: Become Giant; Increased Respawn time" )
                break;

                case TF_CLASS_ENGINEER:

                    list[ "card: damage bonus" ] <- 2
                    list[ "engy sentry damage bonus" ] <- 2
                    ClientPrint( player, 4, "CURSE OF GIANTS: Become Giant; Double Sentry Damage, Increased Respawn time" )
                break;

                case TF_CLASS_DEMOMAN:

                    local upgrades_list = 
                    {
                        "damage bonus" : 1
                        "clip size upgrade atomic" : 0
                        "maxammo primary increased" : 1
                        "faster reload rate" : 1
                        "fire rate bonus" : 1
                        "heal on kill" : 0
                        "Projectile speed increased" : 1
                    }

                    foreach ( attrib, value in upgrades_list )
                    {
                        local attrib_value = primary.GetAttribute( attrib, value )
                        //// // printl( "attempt add: "+attrib+"return value: "+attrib_value )

                        // if we dont have the attribute, add it
                        if ( attrib_value == value )
                        {
                            //// // printl( "we dont have "+attrib+" value: "+value+" attrib value:"+attrib_value )
                            continue
                        }
                        else 
                        {
                            //// // printl( "we have "+attrib+" value: "+value+" attrib value:"+attrib_value )
                        }

                        //// // printl( "adding to list: "+attrib + value )
                        list[ attrib ] <- attrib_value
                    }

                    if ( primary.GetClassname() != "tf_weapon_grenadelauncher" )
                    {
                        //// // printl( "giving weapon" )
                        //// // printl( "player: "+player )
                        //// // printl( "primary:"+primary )
                        CursesScript.GiveWeapon( player, "tf_weapon_grenadelauncher", 206 )
                        
                        // RE-GET THE PRIMARY WEAPON AFTER GIVING NEW ONE
                        primary = CursesScript.GetItemInSlot( player, 0 )
                        //// // printl( "new primary: " + primary )
                    }
                    
                    ClientPrint( player, 4, "CURSE OF GIANTS: Become Giant; Increased Respawn time" )
                break;

                default:
                    ClientPrint( player, 4, "CURSE OF GIANTS: Become Giant; Increased Respawn time" )
                break;
            }
        
            // CursesScript.PrintTable( list )
            
            player.Weapon_Switch( primary )

            foreach ( attrib, value in list )
            {
                if ( primary == null )
                {
                    break
                }
                local attrib_value = primary.GetAttribute( attrib, value )
                //// // printl( "attempt add: "+attrib )

                // if we dont have the attribute, add it
                if ( attrib_value != value )
                {
                    //// // printl( "we have "+attrib+" value: "+value+" attrib value:"+attrib_value )
                    continue
                }

                primary.AddAttribute( attrib, value, -1 )
            }

            // fix rocket/grenade launcher not regenning ammo
            primary.SetClip1( 9999 )

            // set max health
            local max_health = player.GetMaxHealth()
            local health_gain = ( max_health * 10 ) - max_health
            primary.AddAttribute( "hidden maxhealth non buffed", health_gain, -1 )
            primary.AddAttribute( "CARD: move speed bonus", 0.5, -1 )
            primary.AddAttribute( "voice pitch scale", 0.65, -1 )
            player.SetHealth( player.GetMaxHealth() )
            player.SetModelScale( 1.75, grow_time )

            SetPropBool( player, "m_bIsMiniBoss", true )
            EntFireByHandle( player, "RunScriptCode", "CursesScript.UnstuckEntity( self )", SINGLE_TICK + grow_time, null, null )
            
            SendGlobalGameEvent( "player_healonhit", 
            {
                entindex = player.entindex(),
                amount = health_gain
            });

            /* 
                DISABLE RESISTS
            */

            local resistances = 
            {
                "dmg taken from bullets reduced": "dmg taken from bullets increased",
                "dmg taken from blast reduced": "dmg taken from blast increased", 
                "dmg taken from fire reduced": "dmg taken from fire increased",
                "dmg taken from crit reduced": "dmg taken from crit increased"
            }

            foreach ( resist_attr, counter_attr in resistances )
            {
                local resist_mult = player.GetCustomAttribute( resist_attr, 1.0 )
                // //// // printl( resist_attr + ": " + resist_mult )
                
                if ( resist_mult < 1.0 )
                {
                    local counter_mult = 1.0 / resist_mult
                    primary.AddAttribute( counter_attr, counter_mult, -1 )
                    // //// // printl( "Applied " + counter_attr + ": " + counter_mult )
                }
            }
        }

        printl("function")

    }

    function MeleeCurse( )
    {
        /*
            CURSE OF MEDIEVAL
        */
        local player = self

        if ( Time() >= flEndTimestamp || GetPropInt(player, "m_lifeState") != 0)
        {
            CursesScript.ResetCursedPlayer( player, CURSE_MEDIEVAL, CURSE_EXPIRE )

            foreach ( k, v in self.GetScriptScope().ThinkTable_Player )
            {
                if (k == "PrepareCurse")
                {
                    delete self.GetScriptScope().ThinkTable_Player[k]
                }
            }

            return
        }

        // printl("curse of medieval")

        local primary = CursesScript.GetItemInSlot( player, 0 )
        local secondary = CursesScript.GetItemInSlot( player, 1 )
        local melee = CursesScript.GetItemInSlot( player, 2 )
        local scope = player.GetScriptScope()

        // sound replays if we dont check for if alive
        if ( !( "SufferingCurse2" in scope ) && player.IsAlive() )
        {
            scope.SufferingCurse2 <- 1
            CursesScript.DispatchParticleEffectEx( "utaunt_arcane_purple_sparkle", player.GetOrigin(), Vector(), [ -1, 10 ], player, null )


            local sound_range = ( 40 + ( 20 * log10( 4000 / 36.0 ) ) ).tointeger();
            EmitSoundEx
            ({
                sound_name = "ambient/medieval_dooropen.wav",
                origin = player.GetOrigin(),
                volume = 1
                sound_level = sound_range 
                channel = RandomInt(40, 80)
                entity = player 
                filter_type = RECIPIENT_FILTER_SINGLE_PLAYER
            });

            ClientPrint( player, 4, "CURSE OF MEDIEVAL: Locked to Melee" )

            /*
                FIX: Some situations prevent weapon switching. Force here.
            */

            local CLASSES_ANTILOCK = 
            [ 
                TF_CLASS_SPY
                TF_CLASS_SOLDIER 
                TF_CLASS_DEMOMAN
                TF_CLASS_HEAVYWEAPONS
            ]

            foreach ( pclass in CLASSES_ANTILOCK )
            {
                if ( player.GetPlayerClass() == pclass )
                {
                    // sniper and heavy no longer are stuck with aim speed/sounds
                    if ( player.GetActiveWeapon() == primary )
                    {
                        player.GetActiveWeapon().Destroy()
                    }

                    SetPropEntity( player, "m_hActiveWeapon", null )
                    break
                }
            }
            player.RemoveCondEx( TF_COND_CANNOT_SWITCH_FROM_MELEE, true )
            // heavy is stuck with minigun slow if he switches while revved
            player.RemoveCondEx( TF_COND_AIMING, true )

            if ( player.InCond( TF_COND_ROCKETPACK ) )
            {
                // needed to stop air sound
                SendGlobalGameEvent( "rocketpack_landed", { userid = GetPlayerUserID( player ) })
                player.RemoveCondEx( TF_COND_ROCKETPACK, true )
            }

            player.Weapon_Switch( melee )
            melee.AddAttribute( "disable weapon switch", 1, -1 )
            melee.AddAttribute( "provide on active", 1, -1 )
            melee.AddAttribute( "drop health pack on kill", 1, -1 ) // you drop health packs in medieval
            melee.AddAttribute( "mark for death", 1, -1 )
            melee.AddAttribute( "cannot giftwrap", 1, -1 )
        }

        player.AddCondEx( TF_COND_SPEED_BOOST, 0.1, null )
        // player.AddCondEx( TF_COND_OFFENSEBUFF, 0.1, null )

    }
    function TeleCurse( )
    {
        /*
            CURSE OF TELEPORTING
        */

        local player = self

        if ( Time() >= flEndTimestamp || GetPropInt(player, "m_lifeState") != 0)
        {
            CursesScript.ResetCursedPlayer( player, CURSE_TELEPORT, CURSE_EXPIRE )

            foreach ( k, v in self.GetScriptScope().ThinkTable_Player )
            {
                if (k == "PrepareCurse")
                {
                    delete self.GetScriptScope().ThinkTable_Player[k]
                }
            }

            return
        }

        // printl("curse of tele")

        local primary = CursesScript.GetItemInSlot( player, 0 )
        local secondary = CursesScript.GetItemInSlot( player, 1 )
        local melee = CursesScript.GetItemInSlot( player, 2 )
        local scope = player.GetScriptScope()
        local cur_time = Time()

        // sound replays if we dont check for if alive
        if ( !( "SufferingCurse3" in scope ) && player.IsAlive() )
        {
            CursesScript.DispatchParticleEffectEx( "utaunt_arcane_purple_sparkle", player.GetOrigin(), Vector(), [ -1, 10 ], player, null )

            scope.SufferingCurse3 <- 1
            scope.flTeleportTimestamp <- 0
            ClientPrint( player, 4, "CURSE OF TELEPROTING: Teleport in looking direction every 1.5s" )
        }

        if ( ( scope.flTeleportTimestamp + 1.5 ) <= cur_time )
        {
            scope.flTeleportTimestamp = cur_time
            local eye_pos = player.EyePosition()
            local tele_pos = null
            
            // use abs angles so that if a player is looking up/down they teleport where there are facing rather then up
            local eye_forward = player.EyePosition()+player.GetAbsAngles().Forward() * 500
            local eye_down = player.EyePosition()+player.EyeAngles().Forward() * Vector( 0, 0, -600 )
            // eye_forward.z = 0

            local trace_eyes = 
            {
                start  = eye_pos,
                end    = eye_forward,   
                mask = ( CONTENTS_SOLID|CONTENTS_MOVEABLE|CONTENTS_WINDOW|CONTENTS_GRATE ),    
                ignore = player                      
            }
            local trace_eyes_down = 
            {
                start  = eye_pos,
                end    = eye_down,   
                mask = ( CONTENTS_SOLID|CONTENTS_MOVEABLE|CONTENTS_WINDOW|CONTENTS_GRATE ),    
                ignore = player                      
            }
            
            TraceLineEx( trace_eyes )
            TraceLineEx( trace_eyes_down )

            trace_eyes.endpos.z = trace_eyes_down.endpos.z
            tele_pos = trace_eyes.endpos

            local sound_range = ( 40 + ( 20 * log10( 4000 / 36.0 ) ) ).tointeger();

            EmitSoundEx
            ({
                sound_name = "weapons/teleporter_receive.wav",
                origin = player.GetOrigin(),
                volume = 1
                sound_level = sound_range 
                channel = RandomInt(40, 80)
                entity = player 
                filter_type = RECIPIENT_FILTER_SINGLE_PLAYER
            });

            player.Teleport( true, tele_pos, true, player.EyeAngles(), true, player.GetAbsVelocity() )

            for ( local enemy; enemy = FindByClassname( enemy, "player" ); )
            {
                if ( enemy.GetTeam() != player.GetTeam() && enemy.GetTeam() != TEAM_SPECTATOR )
                {
                    local intersect = CursesScript.CheckEntityIntersection( enemy, player )

                    if ( intersect == true )
                    {
                        enemy.TakeDamageCustom( player, player, player.GetActiveWeapon(), Vector( 0, 0, 0 ), player.GetOrigin(), 1000, DMG_CRUSH, TF_DMG_CUSTOM_TELEFRAG )
                    }
                }
            }

            CursesScript.UnstuckEntity( player )
            EntFireByHandle( player, "RunScriptCode", "DispatchParticleEffect( `teleported_red`, self.GetOrigin(), Vector() )", 0.1, null, null )
            EntFireByHandle( player, "RunScriptCode", "DispatchParticleEffect( `teleportedin_red`, self.GetOrigin(), Vector() )", 0.1, null, null )
        }
    }
    function BombCurse( )
    {
        /*
            CURSE OF BOMBING
            -Player must run to a select location
            -They then must run toward an enemy
        */

        // printl("curse of bombing")
        
        local player = self

        if ( Time() >= flEndTimestamp || GetPropInt(player, "m_lifeState") != 0)
        {
            CursesScript.ResetCursedPlayer( player, CURSE_BOMBHEAD, CURSE_EXPIRE )

            foreach ( k, v in self.GetScriptScope().ThinkTable_Player )
            {
                if (k == "PrepareCurse")
                {
                    delete self.GetScriptScope().ThinkTable_Player[k]
                }
            }

            return
        }

        local primary = CursesScript.GetItemInSlot( player, 0 )
        local secondary = CursesScript.GetItemInSlot( player, 1 )
        local melee = CursesScript.GetItemInSlot( player, 2 )
        local scope = player.GetScriptScope()
        local cur_time = Time()
        local cur_origin = player.GetOrigin()
        local hatch_location = FindByName( null, "capturezone_blue" )
        local active_bomb = FindByName( null, "Classic_Mode_Intel" )
        local bomb_origin = active_bomb.GetOrigin()

        if ( active_bomb == null )
        {
            //// // printl( "bomb might be ironman" )
            active_bomb = FindByName( null, "Ironman_Mode_Intel" )
        }

        if ( !( "SufferingCurse4" in scope ) && player.IsAlive() )
        {
            scope.SufferingCurse4 <- 1
            scope.iCurseStage <- 0
            scope.vecDestination <- Vector( 0, 0, 0 )
            scope.hDisarmModel <- null
            scope.iBaseSpeed <- 0
            scope.tpWearable <- null
            player.SetForcedTauntCam( 1 )

            local model = PrecacheModel( "models/props_lakeside_event/bomb_temp_hat.mdl" )

            scope.tpWearable = CreateByClassname( "tf_wearable" )
            SetPropInt( scope.tpWearable, "m_nModelIndex", model )
            scope.tpWearable.SetModelSimple( "models/props_lakeside_event/bomb_temp_hat.mdl" ) // script wont recognise model if we dont do this
            SetPropBool( scope.tpWearable, STRING_NETPROP_INIT, true )
            SetPropBool( scope.tpWearable, STRING_NETPROP_ATTACH, true )
            SetPropEntity( scope.tpWearable, "m_hOwnerEntity", player )
            scope.tpWearable.SetOwner( player )
            scope.tpWearable.DispatchSpawn()
            scope.tpWearable.AcceptInput( "SetParent", "!activator", player, player )
            SetPropInt( scope.tpWearable, "m_fEffects", 129 ) // EF_BONEMERGE|EF_BONEMERGE_FASTCULL
            CursesScript.PreservedCleanupArray.append( scope.tpWearable )

            local sound_range = ( 40 + ( 20 * log10( 4000 / 36.0 ) ) ).tointeger();
            EmitSoundEx
            ({
                sound_name = "vo/halloween_merasmus/sf12_bcon_headbomb36.mp3",
                origin = player.GetOrigin(),
                volume = 1
                sound_level = sound_range 
                channel = RandomInt(40, 80)
                entity = player 
                filter_type = RECIPIENT_FILTER_SINGLE_PLAYER
            });

            // Setup Objective

            // find all navs
            local navAllMeshes = CursesScript.MapNavAreas

            local navIgnored = {}
            local navValid = []

            local hatch_origin = hatch_location.GetOrigin()
            local dist_from_hatch = CursesScript.GetDist( bomb_origin, hatch_origin )

            // Get all Navs in a 750 h/u radius
            local radius = 1500
            GetNavAreasInRadius( cur_origin, radius, navIgnored )
            // // DebugDrawCircle( cur_origin, Vector( 0, 255, 0 ), 0, radius, true, 5 )

            // foreach ( id, nav in navIgnored )
            // {
            //     // DebugDrawCircle( nav.GetCenter(), Vector( 255, 0, 0 ), 0, 50, true, 5 )
            // }

            // // CursesScript.PrintTable( navIgnored )

            // Check for all navs
            // Need to ensure we ignore navs behind the bomb
            foreach ( id, nav in navAllMeshes )
            {
                local min_size = 2000
                local center = nav.GetCenter()
                // get the distance between navs center and hatch origin
                local bomb_dist = CursesScript.GetDist( center, hatch_origin )

                local debug_dir = 
                [ 
                    NORTH, EAST, SOUTH, WEST
                ]

                // if mav is too small, cant be reached by all teams or must be ignored
                // continue
                if 
                ( 
                    id in navIgnored
                    || nav.GetSizeX() * nav.GetSizeY() < min_size
                    || nav.HasAttributeTF( TF_NAV_SPAWN_ROOM_RED ) || nav.HasAttributeTF( TF_NAV_SPAWN_ROOM_BLUE ) || !nav.HasAttributeTF( TF_NAV_BOMB_CAN_DROP_HERE )
                    || bomb_dist > dist_from_hatch
                )
                {
                    if ( id in navIgnored )
                    {
                        // DebugDrawCircle( nav.GetCenter(), Vector( 255, 0, 0 ), 0, 50, true, 5 )
                    }
                    continue
                }

                // foreach ( dir in debug_dir )
                // {
                //     // DebugDrawLine_vCol( center, nav.GetCorner( dir ), Vector( 0, 255, 0 ), true, 5 )
                // }
                
                navValid.append( nav )
            }

            local nav_selected = navValid[ RandomInt( 0, navValid.len() - 1 ) ]
            local spot = nav_selected.FindRandomSpot()
            scope.vecDestination = spot

            local dist_from_target = CursesScript.GetDist( cur_origin, spot )
            local cur_class = CursesScript.Classes[ player.GetPlayerClass() ]
            //// // printl( "cur class "+cur_class )

            local base_class_speed = 
            {
                "" : 0
                "Scout"    : 400,
                "Sniper"   : 300,
                "Soldier"  : 240,
                "Demoman"  : 280,
                "Medic"    : 320,
                "Heavy"    : 230,
                "Pyro"     : 300,
                "Spy"      : 320,
                "Engineer" : 300,
                "Civilian" : 300
            }

            foreach ( player_class, speed in base_class_speed )
            {

                if ( player_class == cur_class )
                {
                    scope.iBaseSpeed = speed
                    break
                }
            }

            // amount of extra time needed to get there
            local extra_time = ( dist_from_target / scope.iBaseSpeed )

            printl("old time: "+flEndTimestamp)
            flEndTimestamp += extra_time
            printl("time: "+flEndTimestamp)
            // //// // printl( flEndTimestamp - cur_time )
            CursesScript.DispatchParticleEffectEx( "utaunt_arcane_purple_sparkle", player.GetOrigin(), Vector(), [ -1, flEndTimestamp - cur_time ], player, null )
            
            scope.hDisarmModel = CursesScript.ShowModelToPlayer( player, [ "models/props_gameplay/cap_circle_256.mdl", 0 ], spot + Vector( 0, 0, 20 ), QAngle(), INT_MAX ) 
            SendGlobalGameEvent( "show_annotation", 
            {
                text = "BOMB ACTIVE. DISARM HERE OR PERISH."
                lifetime = flEndTimestamp - cur_time
                worldPosX = spot.x
                worldPosY = spot.y
                worldPosZ = spot.z
                id = 3
                play_sound = "misc/null.wav"
                show_distance = true
                show_effect = false
                follow_entindex = 0
                visibilityBitfield = 1 << player.entindex()
            })
            scope.hDisarmModel.ValidateScriptScope()
            scope.hDisarmModel.SetSkin( 1 )
            scope.hDisarmModel.GetScriptScope().owner <- player
        }

        // printl("bomb: "+flEndTimestamp)
        // printl("math: "+ceil( flEndTimestamp - cur_time ))
        ClientPrint( player, 4, "CURSE OF BOMBINOMICON: You will EXPLODE in: "+ceil( flEndTimestamp - cur_time ) )
        local maxhealth = player.GetMaxHealth()
        local regen_amount = maxhealth / 5
        player.AddCustomAttribute( "dmg taken increased", 0.6, 0.1 )
        player.AddCustomAttribute( "SET BONUS: health regen set bonus", regen_amount, 0.1 )

        for ( local other_player; other_player = FindByClassnameWithin( other_player, "player", scope.vecDestination, 120 ); )
        {
            if ( scope.iCurseStage < 1 && other_player == player )
            {
                scope.iCurseStage = 1

                // Update players time so they can reach the front
                local dist_to_front = CursesScript.GetDist( cur_origin, bomb_origin )

                local extra_time = ( dist_to_front / scope.iBaseSpeed ) * 1.5
                SendGlobalGameEvent( "show_annotation", 
                {
                    text = "TIME BONUS! BLOW UP AN ENEMY!"
                    lifetime = flEndTimestamp - cur_time
                    worldPosX = bomb_origin.x
                    worldPosY = bomb_origin.y
                    worldPosZ = bomb_origin.z
                    id = 3
                    play_sound = "misc/null.wav"
                    show_distance = false
                    show_effect = false
                    follow_entindex = 0
                    visibilityBitfield = 1 << player.entindex()
                })


                local sound_range = ( 40 + ( 20 * log10( 4000 / 36.0 ) ) ).tointeger();
                EmitSoundEx
                ({
                    sound_name = "passtime/crowd_cheer.wav",
                    origin = player.GetOrigin(),
                    volume = 1
                    sound_level = sound_range 
                    channel = RandomInt(40, 80)
                    entity = player 
                    filter_type = RECIPIENT_FILTER_SINGLE_PLAYER
                });
                EmitSoundEx
                ({
                    sound_name = "ui/mm_level_six_achieved.wav",
                    origin = player.GetOrigin(),
                    volume = 1
                    sound_level = sound_range 
                    channel = RandomInt(40, 80)
                    entity = player 
                    filter_type = RECIPIENT_FILTER_SINGLE_PLAYER
                });
                PrecacheEntityFromTable({ classname = "info_particle_system", effect_name = "mini_fireworks" })
                CursesScript.PrecacheParticleTable.name <- "mini_fireworks"

                DispatchParticleEffect( "mini_fireworks", scope.hDisarmModel.GetOrigin(), Vector() )

                flEndTimestamp += extra_time 
                scope.hDisarmModel.Kill()
            }
        }

        if ( scope.iCurseStage == 1 )
        {
            local aryEnemyList = CursesScript.GetEnemiesWithinArea( player, 100, cur_origin + ( Vector( 0, 0, 25 ) ) )
                    
            if ( aryEnemyList.len() > 0 ) 
            {
                local classnames = 
                {
                    "player" : 1
                    "obj_teleporter" : 1
                    "obj_sentrygun" : 1
                    "obj_dispenser" : 1
                    "tank_boss" : 1
                }

                local attacked_list = []

                foreach ( string, value in classnames )
                {
                    for ( local entity; entity = FindByClassnameWithin( entity, string, cur_origin, 600 ); )
                    {
                        if ( entity.GetTeam() != player.GetTeam() && entity.GetTeam() != TEAM_SPECTATOR && attacked_list.find( entity ) == null )
                        {
                            entity.TakeDamageEx
                            ( 
                                null, 
                                player, 
                                player.GetActiveWeapon(), 
                                Vector( 0, 0, 0 ), 
                                entity.GetOrigin(), 
                                600, 
                                DMG_BLAST
                            )
                            attacked_list.append( entity )
                        }
                    }
                }

                scope.bWarningSound <- 1
                local curse_time = -30
                scope.iCurseStage = 2
                DispatchParticleEffect( "merasmus_dazed_explosion", cur_origin, Vector() )
                
                if ( scope.tpWearable != null && scope.tpWearable.IsValid() )
                {
                    scope.tpWearable.Kill()
                }

                CursesScript.ResetCursedPlayer( player, CURSE_BOMBHEAD, CURSE_EXPIRE )

                return curse_time
            }
        }

        if ( ( flEndTimestamp - 1.15 ) <= cur_time && !( "bWarningSound" in scope ) )
        {
            local sound_range = ( 40 + ( 20 * log10( 4000 / 36.0 ) ) ).tointeger();
            scope.bWarningSound <- 1

            EmitSoundEx
            ({
                sound_name = "weapons/cguard/charging.wav",
                origin = cur_origin,
                volume = 1
                sound_level = sound_range 
                channel = RandomInt(40, 80)
                entity = player 
                filter_type = RECIPIENT_FILTER_SINGLE_PLAYER
            });
        }

        // // // printl( "our bool: "+bool )
    }
    function GhostCurse( )
    {
        /*
            CURSE OF GHOSTS
        */

        // printl("curse of ghost")

        local player = self

        if ( Time() >= flEndTimestamp || GetPropInt(player, "m_lifeState") != 0)
        {
            CursesScript.ResetCursedPlayer( player, CURSE_FEAR, CURSE_EXPIRE )

            foreach ( k, v in self.GetScriptScope().ThinkTable_Player )
            {
                if (k == "PrepareCurse")
                {
                    delete self.GetScriptScope().ThinkTable_Player[k]
                }
            }

            return
        }

        local primary = CursesScript.GetItemInSlot( player, 0 )
        local secondary = CursesScript.GetItemInSlot( player, 1 )
        local melee = CursesScript.GetItemInSlot( player, 2 )
        local scope = player.GetScriptScope()
        local cur_time = Time()

        // sound replays if we dont check for if alive
        if ( !( "SufferingCurse5" in scope ) && player.IsAlive() )
        {
            CursesScript.DispatchParticleEffectEx( "utaunt_arcane_purple_sparkle", player.GetOrigin(), Vector(), [ -1, 10 ], player, null )

            scope.SufferingCurse5 <- 1
            scope.flFear <- 0
            scope.flMaxFear <- 100
            scope.bExtendDuration <- 1

            ClientPrint( player, 4, "CURSE OF GHOSTS: Taking too much damage STUNS you!" )
            CursesScript.AddHudElement( player, "meter_GhostScare", "ooOOOoohhh" )
        }

        CursesScript.DisplayHudElement( player, "meter_GhostScare", function()
        {
            local scope = player.GetScriptScope()
            local meterMax = floor( scope.flMaxFear / 10 );
            local meterFill = floor( scope.flFear / 10 );
            local meterEmpty = meterMax - meterFill
            local cur_time = Time()
            local decimal = cur_time % 1
            
            local msg = "Fear: "

            // if meter is max, flash stun meter and stun player
            if ( meterFill >= meterMax )
            {
                player.StunPlayer( 0.1, 0.0, TF_STUN_LOSER_STATE|TF_STUN_BY_TRIGGER, null )

                if ( "bExtendDuration" in scope )
                {
                    delete scope[ "bExtendDuration" ]
                    return Time() + 5
                }

                for ( local i = 0; i < meterMax; i++ )
                {
                    if ( decimal >= 0.5 ) 
                    {
                        msg += "■";
                    }
                    else
                    {
                        msg += "□"
                    }
                }

                return msg
            }

            // show player how much damage they can take
            for ( local i = 0; i < meterMax; i++ )
            {
                if ( i < meterFill )
                {
                    msg += "■";
                }
                else
                {
                    msg += "□"
                }
            }

            return msg
        })
    }

    function CurseTarget( victim )
    {
        local curse_duration = Time() + 10
        // printl("causing curse")

        local curses_list = 
        [
            GiantCurse
            MeleeCurse
            TeleCurse
            BombCurse
            GhostCurse 
        ]

        local scope = victim.GetScriptScope()
        local set_return = false

        local count = curses_list.len()
        local rand = RandomInt( 1, count - 1 )
        // rand = CURSE_BOMBHEAD
        local PrepareCurse = curses_list[rand]


        //// // printl( "picked curse: "+PickCurse )
        
        /*
            This is the absolute shittiest way to check this
            but i just spent this afternoon fixing like 2 bugs that were pissing me off 
            i dont even care if i come back and code this properly, im just not gonna do this in future
            "better done then perfect"
        */


        foreach ( k, v in scope )
        {
            // printl("k: "+k)
            // printl("v: "+v)

            if 
            (
                k == "SufferingCurse" 
                || k == "SufferingCurse2"
                || k == "SufferingCurse3"
                || k == "SufferingCurse4"
                || k == "SufferingCurse5"
                || k == "CurseImmunity"
            )
            {
                set_return = true
                return
            }
        }

        if ( set_return == true)
        {
            return
        }

        victim.GetScriptScope().flEndTimestamp <- Time() + 10
        GetEntityThinkTable( victim ).PrepareCurse <- PrepareCurse

        /*
            CLASS SPECIFIC CURSES 
            
            local curses_list = 
            {
                // player is the cursed player
                "Scout":
                {
                    pclass = TF_CLASS_SCOUT
                    "1" : function( player ) { //// // printl( "Scout Curse 1" ) },
                    "2" : function( player ) { //// // printl( "Scout Curse 2" ) }
                },
                "Soldier":
                {
                    pclass = TF_CLASS_SOLDIER
                    "1" : function( player ) { //// // printl( "Soldier Curse 1" ) },
                    "2" : function( player ) { //// // printl( "Soldier Curse 2" ) }
                }
            }

            // Find the class that matches the victim's class
            local victim_class_id = victim.GetPlayerClass()
            local get_cur_class = null
            local class_name = ""

            foreach ( name, class_data in curses_list )
            {
                if ( class_data.pclass == victim_class_id )
                {
                    get_cur_class = class_data
                    class_name = name
                    break
                }
            }

            if ( get_cur_class != null )
            {
                // Get all curse keys for this class ( excluding the pclass entry )
                local curse_keys = []
                foreach ( key, value in get_cur_class )
                {
                    if ( key != "pclass" ) // Skip the pclass property
                    {
                        curse_keys.append( key )
                    }
                }
                
                // Pick random curse from this class
                local random_curse_key = curse_keys[ RandomInt( 0, curse_keys.len() - 1 ) ]
                local PrepareCurse = get_cur_class[ random_curse_key ]
                
                //// // printl( "picked curse: " + random_curse_key + " for class: " + class_name )
            
                CurseController.GetScriptScope().aryPlayersCursed.append
                ({
                    cursed = victim,
                    duration = curse_duration,
                    func = PrepareCurse,
                    curse_type = random_curse_key
                })
            }
            else
            {
                //// // printl( "No curses found for class ID: " + victim_class_id )
            }
        */
    }

    function SendCurseToKiller( killer, victim )
    {
        //// // printl( "cursing" )
        local death_origin = victim.GetOrigin()
        local chest_origin = victim.GetAttachmentOrigin( victim.LookupAttachment( "flag" ) )
        killer.ValidateScriptScope()

        local sound_range = ( 40 + ( 20 * log10( 4000 / 36.0 ) ) ).tointeger();

        EmitSoundEx
        ({
            sound_name = "ui/halloween_boss_tagged_other_it.wav",
            origin = killer.GetOrigin(),
            volume = 1
            sound_level = sound_range 
            channel = RandomInt(40, 80)
            entity = killer 
            filter_type = RECIPIENT_FILTER_SINGLE_PLAYER
        });

        // local curse_projectile = SpawnEntityFromTable( "tf_projectile_stun_ball",
        // {
        //     origin = chest_origin, 
        //     angles = Vector( 0, 0, 0 ),
        //     teamnum = victim.GetTeam(),
        //     basevelocity = Vector( 0, 0, 0 )
        //     targetname = "__cursescript__curse_"+RandomInt( -999, 999 )
        // });

        // ShowModelToPlayer( killer, [ "models/props_mvm/mvm_human_skull_collide.mdl", 0 ], chest_origin, QAngle(), INT_MAX ) 

        local curse_projectile = DispatchParticleEffectEx( "eyeboss_projectile", chest_origin, Vector( 0, 0, 0 ), [ -1, -1 ], null, null )

        curse_projectile.SetMoveType( 8, 0 )
        // must set rendermode or it wont fade in
        SetPropInt( curse_projectile, "m_nRenderMode", 1 )
        CursesScript.SetEntityColor( curse_projectile, 255, 255, 255, 0 )
        
        curse_projectile.ValidateScriptScope()
        local scope = curse_projectile.GetScriptScope()

        scope.ProjectileThink <- function() 
        { 
            foreach ( name, func in ThinkTable_Projectile ) 
            {
                if ( func )
                {
                    func.call( this )
                }
            }

            return -1 
        }
        
        scope.HasThink <- 1
        scope.hLockTarget <- killer
        scope.flBeginLifetime <- Time()
        scope.iDistFromVictim <- GetDist( killer.GetOrigin(), victim.GetOrigin() )
        scope.iHomingStrength <- 135
        scope.iHomingTurnSpeed <- 150
        scope.iMaxVelocity <- 300
        scope.flDeathTimestamp <- 0
        scope.ThinkTable_Projectile <- {}
        scope.flVisible <- 0
        scope.flFadeinTime <- 0
        scope.last_position <- chest_origin
        // MEGA HACK: Particle handles code logic, use model for visibility
        scope.hHomingModel <- ShowModelToPlayer( killer, [ "models/props_mvm/mvm_human_skull_collide.mdl", 0 ], chest_origin, QAngle(), INT_MAX ) 
        scope.hHomingModel.AcceptInput( "SetParent", "!activator", curse_projectile, curse_projectile )

        AddThinkToEnt( curse_projectile, "ProjectileThink" )

        GetEntityThinkTable( curse_projectile ).CreateCurseProjectile <- CreateCurseProjectile
        GetEntityThinkTable( curse_projectile ).HomingProjectile <- HomingProjectile
    }
    
    // UTILITY FUNCTIONS

    function Ignite( hPlayer, duration = 10.0, damage = 1 ) 
    {
        local ignite_fix = SpawnEntityFromTable( "trigger_ignite", 
        {
            targetname = "__ignite_utilscript"
            burn_duration = duration
            damage = damage
            spawnflags = SF_TRIGGER_ALLOW_CLIENTS
        })
        EntFireByHandle( ignite_fix, "StartTouch", "", -1, hPlayer, hPlayer )
        EntFireByHandle( ignite_fix, "EndTouch", "", SINGLE_TICK, hPlayer, hPlayer )
        EntFireByHandle( ignite_fix, "Kill", "", SINGLE_TICK * 2, hPlayer, hPlayer )
    }

    function GetRandomEnemy( hPlayer )
    {
        local enemies = [];

        // // printl( "player: "+hPlayer )
        // // printl( "team: "+hPlayer.GetTeam() )

        for ( local player; player = FindByClassname( player, "player" ); )
        {
            if ( player.GetTeam() == hPlayer.GetTeam() || player.GetTeam() == TEAM_SPECTATOR )
                continue

            // // // printl( "Entity "+player+" belongs to team: "+player.GetTeam() )
            // // // printl( "caller "+hPlayer+" belongs to team: "+hPlayer.GetTeam() )

            enemies.append( player )
        }

        // // CursesScript.PrintTable( enemies )

        if ( enemies.len() == 0 )
        {
            return null; 
        }

        return enemies[  RandomInt( 0, enemies.len() - 1 )  ]
    }

    function GiveCosmetic( hPlayer, iCosmeticID, stModel = null )
    {
        local dummy = CreateByClassname( "tf_weapon_parachute" )
        SetPropInt( dummy, STRING_NETPROP_ITEMDEF, WEAPON_BASE_JUMPER )
        SetPropBool( dummy, STRING_NETPROP_INIT, true )
        dummy.SetTeam( hPlayer.GetTeam() )
        DispatchSpawn( dummy )
        hPlayer.Weapon_Equip( dummy )

        local wearable = GetPropEntity( dummy, "m_hExtraWearable" )
	    dummy.Kill()

        SetPropInt( wearable, STRING_NETPROP_ITEMDEF, iCosmeticID )
        SetPropBool( wearable, STRING_NETPROP_INIT, true )
        SetPropBool( wearable, STRING_NETPROP_ATTACH, true )

        DispatchSpawn( wearable )

        if ( stModel ) 
        {
            wearable.SetModelSimple( stModel )
        }

        // avoid infinite loop
        hPlayer.AddEFlags( EFL_CUSTOM_WEARABLE )

        SendGlobalGameEvent( "post_inventory_application",  { userid = GetPlayerUserID( hPlayer ) } )

	    hPlayer.RemoveEFlags( EFL_CUSTOM_WEARABLE )

        PreservedCleanupArray.append( wearable )

        return wearable
    }   

    function IsMiniCritBoosted( hPlayer )
    {
        local minicrit_conds = 
        [ 
            TF_COND_OFFENSEBUFF
            TF_COND_ENERGY_BUFF
            TF_COND_NOHEALINGDAMAGEBUFF
            TF_COND_MINICRITBOOSTED_ON_KILL
         ]

        foreach ( cond in minicrit_conds )
        {
            if ( hPlayer.GetClassname() != "player" )
                continue
            
            if ( hPlayer.InCond( cond ) )
            {
                return true
            }
        }

        return false
    }

    function DisplayHudElement( hPlayer, stMeterName, funcHudFunction )
    {
        hPlayer.ValidateScriptScope()
        local scope = hPlayer.GetScriptScope()
        if ( hPlayer.IsBotOfType( TF_BOT_TYPE ) || !hPlayer.IsAlive() || !("aryAbilityCooldownTable" in scope))
        {
            //// // printl( "fake client" )
            return
        }

        foreach ( entry in scope.aryAbilityCooldownTable )
        {
            if ( entry.meter == stMeterName )
            {
                entry.message = funcHudFunction()

                if ( entry.message == null )
                {
                    entry.message = "ERROR: "+stMeterName+" does contain string!"
                }
            }
            else 
            {
                continue
            }
        }
    }

    function AddHudElement( hPlayer, stMeterName, stBaseMsg )
    {
        hPlayer.ValidateScriptScope()
        local scope = hPlayer.GetScriptScope()
        if ( hPlayer.IsBotOfType( TF_BOT_TYPE ) )
        {
            // //// // printl( "fake client" )
            return
        }

        if ( !( "player_text" in scope )  )
        {
            // //// // printl( "adding new text" )
            scope.player_text <- SpawnEntityFromTable( "game_text", 
            { 
                message = stBaseMsg,
                channel = 1,
                fadein = 0,
                fadeout = 0,
                holdtime = 1.0,
                spawnflags = 0
                x = 0.2, 
                y = 0.3, 
                effect = 0, // no fade
                color = "255 255 255"
                targetname = "__gametext_hud_element"
            });
            scope.player_text.ValidateScriptScope()
            scope.player_text.GetScriptScope().owner <- hPlayer
            scope.aryAbilityCooldownTable <- []
            CursesScript.PreservedCleanupArray.append( scope.player_text )
        }
        else 
        {
            // //// // printl( "wtf player text: "+scope.player_text )
        }

        if ( !( "DisplayPlayerHUD" in GetEntityThinkTable( hPlayer ) ) )
        {
            // //// // printl( "display hud add" )
            GetEntityThinkTable( hPlayer ).DisplayPlayerHUD <- DisplayPlayerHUD
        }

        scope.aryAbilityCooldownTable.append
        ({
            x = 0.7, 
            y = 0.35, 
            channel = 3, 
            holdtime = 2, 
            spawnflags = 0, 
            message = "", 
            color = "56 123 44", 
            meter = stMeterName
        })
    }

    function ExtendRespawnTime( victim, duration, force_kill )
    {
        local SINGLE_TICK = 0.015
        local victim_origin = victim.GetOrigin()

        local trigger = SpawnEntityFromTable( "trigger_player_respawn_override", 
        {
            origin = victim_origin,
            targetname = "__giant_respawn_trigger",
            spawnflags = 1, 
            RespawnTime = duration
        })

        local victim_class = GetPropInt( victim, "m_Shared.m_iDesiredPlayerClass" )

        EntFireByHandle( trigger, "StartTouch", "", SINGLE_TICK * 3, victim, victim )
        EntFireByHandle( trigger, "EndTouch", "", SINGLE_TICK * 7, victim, victim )
        EntFireByHandle( trigger, "Kill", "", SINGLE_TICK * 8, victim, victim )
        if ( force_kill == true )
        {
            EntFireByHandle( victim, "RunScriptCode", "self.ForceRegenerateAndRespawn()", SINGLE_TICK, null, null )
            EntFireByHandle( victim, "RunScriptCode", "self.SetAbsOrigin( activator.GetOrigin() )", SINGLE_TICK * 2, trigger, null )

            SetPropInt( victim, "m_Shared.m_iDesiredPlayerClass", victim_class );

            EntFireByHandle( victim, "RunScriptCode", "self.TakeDamage( 99999, DMG_DISSOLVE, null )", SINGLE_TICK * 6, null, null )
        }
    }

    function ShowModelToPlayer( player, model = [ "models/player/heavy.mdl", 0 ], pos = Vector(), ang = QAngle(), duration = INT_MAX ) 
    {
		PrecacheModel( model[ 0 ] )
		local proxy_entity = CreateByClassname( "obj_teleporter" ) // use obj_teleporter to set bodygroups.  not using SpawnEntityFromTable as that creates spawning noises
		proxy_entity.SetAbsOrigin( pos )
		proxy_entity.SetAbsAngles( ang )
		DispatchSpawn( proxy_entity )

		proxy_entity.SetModel( model[ 0 ] )
		proxy_entity.SetSkin( model[ 1 ] )
		proxy_entity.AddEFlags( EFL_NO_THINK_FUNCTION ) // EFL_NO_THINK_function prevents the entity from disappearing
		proxy_entity.SetSolid( SOLID_NONE )

		SetPropBool( proxy_entity, "m_bPlacing", true )
		SetPropInt( proxy_entity, "m_fObjectFlags", OF_MUST_BE_BUILT_ON_ATTACHMENT ) // sets "attachment" flag, prevents entity being snapped to player feet

		// m_hBuilder is the player who the entity will be networked to only
		SetPropEntity( proxy_entity, "m_hBuilder", player )
		EntFireByHandle( proxy_entity, "Kill", "", duration, player, player )
		return proxy_entity
	}

    function GetWeaponMaxAmmo( player, wep ) 
    {
		if ( wep == null ) return

		local slot = wep.GetSlot()
		local classname = wep.GetClassname()
		local itemid =  GetPropInt( wep, STRING_NETPROP_ITEMDEF )

		local table = CursesScript.MaxAmmoTable[ player.GetPlayerClass() ]

		if ( !( itemid in table ) && !( classname in table ) )
			return -1

		local base_max = ( itemid in table ) ? table[ itemid ] : table[ classname ]

		return base_max
	}

    function CheckEntityIntersection( ent1, ent2 )
    {
        if ( !ent1.IsValid() || !ent2.IsValid() || ent1 == null || ent2 == null)
            return false;
        // Player bounds
        local p_mins = ent2.GetOrigin() + ent2.GetBoundingMins()
        local p_maxs = ent2.GetOrigin() + ent2.GetBoundingMaxs()

        // Prop bounds
        local prop_mins = ent1.GetOrigin() + ent1.GetBoundingMins()
        local prop_maxs = ent1.GetOrigin() + ent1.GetBoundingMaxs()

        // AABB intersection check
        if ( p_mins.x <= prop_maxs.x && p_maxs.x >= prop_mins.x &&
            p_mins.y <= prop_maxs.y && p_maxs.y >= prop_mins.y &&
            p_mins.z <= prop_maxs.z && p_maxs.z >= prop_mins.z )
        {
            return true;
        }

        return false;
    }

    function SendAnnotation( text, delay, lifetime, origin )
    {
        local annotate = format( "SendGlobalGameEvent( \"show_annotation\", {text = \"%s\", lifetime = %s, worldPosX = \"%s\", worldPosY = \"%s\", worldPosZ = \"%s\", id = 2, play_sound = \"\", show_distance = false, show_effect = false})", text.tostring(), lifetime.tostring(), origin.x.tostring(), origin.y.tostring(), origin.z.tostring() )
        EntFireByHandle( gamerules, "RunScriptCode", annotate, delay, null, null )
    }

    function VectorAngles( forward ) 
    {
		local yaw, pitch
		if ( forward.y == 0.0 && forward.x == 0.0 ) 
        {
			yaw = 0.0
			if ( forward.z > 0.0 )
				pitch = 270.0
			else
				pitch = 90.0
		}
		else 
        {
			yaw = ( atan2( forward.y, forward.x ) * 180.0 / Pi )
			if ( yaw < 0.0 )
				yaw += 360.0
			pitch = ( atan2( -forward.z, forward.Length2D() ) * 180.0 / Pi )
			if ( pitch < 0.0 )
				pitch += 360.0
		}

		return QAngle( pitch, yaw, 0.0 )
	}

    function NormalizeVector( vec )
    {
        // Calculate the magnitude ( length ) of the vector
        local magnitude = sqrt( vec.x * vec.x + vec.y * vec.y + vec.z * vec.z );
        
        // If the magnitude is greater than zero, normalize the vector
        if ( magnitude > 0 )
        {
            vec.x = vec.x / magnitude;
            vec.y = vec.y / magnitude;
            vec.z = vec.z / magnitude;
        }
        
        return vec;
    }

    /* 
        Iterates over array. Within array, return REMOVE_ENTRY to remove array
        aryPlayersCursed = CursesScript.IterateArray( aryPlayersCursed, function( entry )
        {
            //// // printl( entry )
            local player = entry.cursed
            local duration = entry.duration

            if ( cur_time >= duration )
            {
                //// // printl( "removing entry" )
                return REMOVE_ENTRY
            }
            else 
            {
                return KEEP_ENTRY
            }
        })
    */


    function IterateArray( array, callback )
    {
        array = array.filter( @( i, v ) callback( i, v ) == KEEP_ENTRY )
        
        return array
    }

    function GetEnemiesWithinArea( hPlayer, radius, origin )
    {
        local enemies = []

        local classnames = 
        {
            "player" : 1
            "obj_teleporter" : 1
            "obj_sentrygun" : 1
            "obj_dispenser" : 1
            "tank_boss" : 1
        }

        foreach ( string, value in classnames )
        {
            for ( local entity; entity = FindByClassnameWithin( entity, string, origin, radius ); )
            {
                // // // printl( "entity team: within area "+entity.GetTeam() )
                // // // printl( "player team: within area "+hPlayer.GetTeam() )
                if ( entity.GetTeam() != hPlayer.GetTeam() && entity.GetTeam() != TEAM_SPECTATOR )
                {
                    enemies.append( entity )
                }
            }
        }

        return enemies
    }

    // Fire a projectile 
    function FireProjectile( attacker, classname, item_id, attribute_table, bKillItem = true )
    {
        local SpecialWeaponAccommodations =
        {
            "tf_weapon_bat_wood" : "secondary",
            "tf_weapon_bat_giftwrap" : "secondary"
        }
        
        // Handle Huntsman special case - replace with Crusader's Crossbow
        local actual_classname = classname
        local actual_item_id = item_id
        if ( classname == "tf_weapon_compound_bow" )
        {
            actual_classname = "tf_weapon_crossbow"
            actual_item_id = 305 // Crusader's Crossbow item ID
            if ( attribute_table != null )
            {
                attribute_table[ "override projectile type" ] <- 8
            }
            else 
            {
                attribute_table = 
                {
                    "override projectile type" : 8
                }
            }
        }
        
        // Store original values to restore later
        local original_ammo = GetPropIntArray( attacker, "m_iAmmo", 1 )
        local original_charge = GetPropFloat( attacker, "m_Shared.m_flItemChargeMeter" )
        local original_lag_comp = GetPropBool( attacker, "m_bLagCompensation" )
        
        // Create fake weapon
        local hFakeWeapon = CreateByClassname( actual_classname )
        if ( !hFakeWeapon || !hFakeWeapon.IsValid() )
        {
            //// // printl( "Failed to create weapon: " + actual_classname )
            return null
        }
        
        // Setup weapon properties
        SetPropInt( hFakeWeapon, STRING_NETPROP_ITEMDEF, actual_item_id )
        SetPropEntity( hFakeWeapon, "m_hOwner", attacker )
        hFakeWeapon.SetOwner( attacker )
        hFakeWeapon.DispatchSpawn()
        
        // Apply attributes to the weapon
        foreach ( attribute, value in attribute_table )
        {
            hFakeWeapon.AddAttribute( attribute, value, -1 )
        }
        
        // Setup attacker for firing
        SetPropIntArray( attacker, "m_iAmmo", 99, 1 )
        SetPropFloat( attacker, "m_Shared.m_flItemChargeMeter", 100.0 )
        SetPropBool( attacker, "m_bLagCompensation", false )
        SetPropFloat( hFakeWeapon, "m_flNextPrimaryAttack", 0 )
        SetPropFloat( hFakeWeapon, "m_flNextSecondaryAttack", 0 )
        
        // Determine which attack to use
        local attack_type = "primary"
        if ( classname in SpecialWeaponAccommodations )
        {
            attack_type = SpecialWeaponAccommodations[ classname ]
        }
        
        // Fire the weapon
        if ( attack_type == "secondary" )
        {
            hFakeWeapon.SecondaryAttack()
        }
        else
        {
            hFakeWeapon.PrimaryAttack()
        }
        
        // Clean up fake weapon

        if ( bKillItem == false )
        {
            hFakeWeapon.Kill()
        }
        
        // Revert attacker changes
        SetPropBool( attacker, "m_bLagCompensation", original_lag_comp )
        SetPropIntArray( attacker, "m_iAmmo", original_ammo, 1 )
        SetPropFloat( attacker, "m_Shared.m_flItemChargeMeter", original_charge )
        
        return hFakeWeapon
    }

    function GiveWeapon( player, className, itemID )
	{
		if ( typeof itemID == "string" && className == "tf_wearable" )
		{
			CTFBot.GenerateAndWearItem.call( player, itemID )
			return
		}
		local weapon = CreateByClassname( className )
		SetPropInt( weapon, STRING_NETPROP_ITEMDEF, itemID )
		SetPropBool( weapon, "m_AttributeManager.m_Item.m_bInitialized", true )
		SetPropBool( weapon, "m_bValidatedAttachedEntity", true )
		weapon.SetTeam( player.GetTeam() )
		DispatchSpawn( weapon )

		// remove existing weapon in same slot
		for ( local i = 0; i < SLOT_COUNT; i++ )
		{
			local heldWeapon = GetPropEntityArray( player, "m_hMyWeapons", i )
			if ( heldWeapon == null || heldWeapon.GetSlot() != weapon.GetSlot() )
				continue
			heldWeapon.Destroy()
			SetPropEntityArray( player, "m_hMyWeapons", null, i )
			break
		}

		player.Weapon_Equip( weapon )
		player.Weapon_Switch( weapon )

		return weapon
	}

    function SetEntityColor( entity, r, g, b, a )
    {
        local color = r | ( g << 8 ) | ( b << 16 ) | ( a << 24 )
        SetPropInt( entity, "m_clrRender", color )
    }

    function NormalizeAngle( target ) 
    {
		target %= 360.0
		if ( target > 180.0 )
			target -= 360.0
		else if ( target < -180.0 )
			target += 360.0
		return target
	}

    function VectorAngles( forward )
    {
		local yaw, pitch
		if ( forward.y == 0.0 && forward.x == 0.0 ) 
        {
			yaw = 0.0
			if ( forward.z > 0.0 )
				pitch = 270.0
			else
				pitch = 90.0
		}
		else {
			yaw = ( atan2( forward.y, forward.x ) * 180.0 / Pi )
			if ( yaw < 0.0 )
				yaw += 360.0
			pitch = ( atan2( -forward.z, forward.Length2D() ) * 180.0 / Pi )
			if ( pitch < 0.0 )
				pitch += 360.0
		}

		return QAngle( pitch, yaw, 0.0 )
	}


	function ApproachAngle( target, value, speed ) 
    {
		target = NormalizeAngle( target )
		value = NormalizeAngle( value )
		local delta = NormalizeAngle( target - value )
		if ( delta > speed )
			return value + speed
		else if ( delta < -speed )
			return value - speed
		return target
	}

    function RemapValClamped( v, A, B, C, D ) 
    {
		if ( A == B ) {
			if ( v >= B )
				return D
			return C
		}
		local cv = ( v - A ) / ( B - A )
		if ( cv <= 0.0 )
			return C
		if ( cv >= 1.0 )
			return D
		return C + ( D - C ) * cv
	}

    function LookAt( player, target_pos, min_rate, max_rate ) 
    {
        local cur_eye_ang = player.EyeAngles()
        local cur_eye_pos = player.EyePosition()
		local cur_eye_fwd = player.EyeAngles().Forward()
		local dt  = FrameTime()
		local dir = target_pos - cur_eye_pos
		dir.Norm()
		local dot = cur_eye_fwd.Dot( dir )

		local desired_angles = CursesScript.VectorAngles( dir )

		local rate_x = CursesScript.RemapValClamped( fabs( CursesScript.NormalizeAngle( cur_eye_ang.x ) - CursesScript.NormalizeAngle( desired_angles.x ) ), 0.0, 180.0, min_rate, max_rate )
		local rate_y = CursesScript.RemapValClamped( fabs( CursesScript.NormalizeAngle( cur_eye_ang.y ) - CursesScript.NormalizeAngle( desired_angles.y ) ), 0.0, 180.0, min_rate, max_rate )

		if ( dot > 0.7 ) {
			local t = CursesScript.RemapValClamped( dot, 0.7, 1.0, 1.0, 0.05 )
			local d = sin( 1.57 * t ) // pi/2
			rate_x *= d
			rate_y *= d
		}

		cur_eye_ang.x = CursesScript.NormalizeAngle( CursesScript.ApproachAngle( desired_angles.x, cur_eye_ang.x, rate_x * dt ) )
		cur_eye_ang.y = CursesScript.NormalizeAngle( CursesScript.ApproachAngle( desired_angles.y, cur_eye_ang.y, rate_y * dt ) )

        if (player.GetClassname() == "player")
        {
            player.SnapEyeAngles( cur_eye_ang )
        }
        else 
        {
            player.SetAbsAngles( cur_eye_ang )
        }
	}


    function PlaySoundToAllEnemies( player, sound, delay, delay_stop, volume = 1, pitch = 100 )
    {
        PrecacheSound( sound )
        for ( local enemy; enemy = FindByClassname( enemy, "player" ); )
        {
            if ( enemy.GetTeam() != player.GetTeam() && enemy.GetTeam() != TEAM_SPECTATOR )
            {
                local sound_range = ( 40 + ( 20 * log10( 3000 / 36.0 ) ) ).tointeger();
                if ( delay != -1 )
                {
                    local script_code = format( "EmitSoundEx({sound_name = \"%s\", origin = self.GetOrigin(), volume = %f, pitch = %f, entity = self, filter_type = RECIPIENT_FILTER_SINGLE_PLAYER})", sound, volume, pitch )
                    EntFireByHandle( enemy, "RunScriptCode", script_code, delay, null, null )
                    
                    // Add sound stop after delay_stop
                    if ( delay_stop != -1 )
                    {
                        local stop_script = format( "EmitSoundEx({sound_name = \"%s\", flags = SND_STOP, entity = self, pitch = %f, filter_type = RECIPIENT_FILTER_GLOBAL})", sound, pitch )
                        EntFireByHandle( enemy, "RunScriptCode", stop_script, delay + delay_stop, null, null )
                    }
                }
                else
                {
                    EmitSoundEx
                    ({
                        sound_name = sound,
                        origin = enemy.GetOrigin(),
                        volume = volume,
                        sound_level = sound_range,
                        entity = enemy,
                        filter_type = RECIPIENT_FILTER_SINGLE_PLAYER
                        pitch = pitch
                    });
                    
                    // Add immediate sound stop after delay_stop
                    if ( delay_stop != -1 )
                    {
                        local stop_script = format( "EmitSoundEx({sound_name = \"%s\", flags = SND_STOP, entity = self, filter_type = RECIPIENT_FILTER_GLOBAL})", sound )
                        EntFireByHandle( enemy, "RunScriptCode", stop_script, delay_stop, null, null )
                    }
                }
            }
        }
    }

    function GetPlayerUserID( player )
    {
        return GetPropIntArray( player_manager, "m_iUserID", player.entindex() )
    }

    function PlayAnimationOnPlayer( player, sequence_name, duration, flAnimSpeed, frame_start = 0 )
    {
        local cur_model = player.GetModelName()
        local cur_loadout = CursesScript.GetLoadout( player )
        local player_scale = player.GetModelScale()
        local player_skin = player.GetSkin()
        local fake_items = []

        local cur_origin = player.GetOrigin()
        local cur_angles = player.GetAbsAngles() // use absangles since eyeposition can cause anim to turn up or down weirdly

        player.SetCustomModelWithClassAnimations( "models/empty.mdl" )

        // prevent from doing anything such as turning
        player.SetMoveType( MOVETYPE_NOCLIP + MOVETYPE_FLY, MOVECOLLIDE_DEFAULT ) // completely stops a player in place
        player.AddCondEx( TF_COND_FREEZE_INPUT, duration, null ) // prevent from doing anything such as turning
        player.AddCondEx( TF_COND_STEALTHED_USER_BUFF, duration, null )

        local anim = SpawnEntityFromTable( "prop_dynamic",
        {
            model = cur_model
            origin = cur_origin
            angles = cur_angles
            targetname = "fake_anim"
            DefaultAnim = sequence_name
            modelscale = player_scale
            skin = player_skin + 3
        })
        anim.SetPlaybackRate( flAnimSpeed )
        SetPropBool( anim, "m_bClientSideAnimation", false )
        SetPropFloat( anim, "m_flCycle", frame_start ) 

        if ( GetPropBool( player, "m_bGlowEnabled" ) == true )
        {
            SetPropBool( player, "m_bGlowEnabled", false )
            SetPropBool( anim, "m_bGlowEnabled", true )
            EntFireByHandle( player, "RunScriptCode", "SetPropBool( self, `m_bGlowEnabled`, true )", duration, null, null )
        }

        for ( local item; item = FindByClassname( item, "tf_wearable" ); )
        {
            if ( item.GetOwner() == player )
            {
                if ( item.GetModelName() == "" )
                {
                    // //// // printl( "null item" )
                    continue
                }

                PrecacheModel( item.GetModelName() )

                if ( startswith( item.GetModelName(), "models/workshop/player/items/pyro/tw_" ) )
                {
                    continue
                }

                // //// // printl( "Using: "+item.GetModelName() )

                local fake_cosmetic = SpawnEntityFromTable( "prop_dynamic_ornament",
                {
                    model = item.GetModelName()
                    origin = cur_origin
                    angles = cur_angles
                    targetname = "fake_hat"
                    // modelscale = player_scale
                    skin = player_skin
                })

                fake_cosmetic.AcceptInput( "SetAttached", "!activator", anim, anim )

                PreservedCleanupArray.append( fake_cosmetic )
            }
        }

        EntFireByHandle( anim, "Kill", "", duration, null, null )
        EntFireByHandle( player, "RunScriptCode", "self.SetMoveType( 2, MOVECOLLIDE_DEFAULT )", duration, null, null )

        local model_revert = format( "self.SetCustomModelWithClassAnimations( \"%s\" )", cur_model )
        EntFireByHandle( player, "RunScriptCode", model_revert, duration, null, null )
        EntFireByHandle( player, "RunScriptCode", "CursesScript.UnstuckEntity( self )", duration, null, null )
    }

    function PlayAnimationOnPlayerNoModelRevert( player, sequence_name, duration )
    {
        local cur_model = player.GetModelName()
        //// // printl( "model "+cur_model )
        local cur_origin = player.GetOrigin()
        local cur_angles = player.GetAbsAngles() // use absangles since eyeposition can cause anim to turn up or down weirdly

        PrecacheModel( "models/empty.mdl" )
        player.SetCustomModelWithClassAnimations( "models/empty.mdl" )

        // prevent from doing anything such as turning
        player.SetMoveType( MOVETYPE_NOCLIP + MOVETYPE_FLY, MOVECOLLIDE_DEFAULT ) // completely stops a player in place
        player.AddCondEx( TF_COND_FREEZE_INPUT, duration, null ) // prevent from doing anything such as turning

        local anim = SpawnEntityFromTable( "prop_dynamic",
        {
            model = cur_model
            origin = cur_origin
            angles = cur_angles
            targetname = "fake_anim"
            DefaultAnim = sequence_name
        })

        EntFireByHandle( anim, "Kill", "", duration, null, null )
        EntFireByHandle( player, "RunScriptCode", "self.SetMoveType( 2, MOVECOLLIDE_DEFAULT )", duration, null, null )
    }

    function DispatchParticleEffectEx( name, origin, angles, start_end, entity, attachment )
    {
        if ( !( "PrecacheParticleTable" in CursesScript ) )
        {
            CursesScript.PrecacheParticleTable <- {}
            PrecacheEntityFromTable({ classname = "info_particle_system", effect_name = name })
            CursesScript.PrecacheParticleTable.name <- name
        }

        foreach ( key, string in CursesScript.PrecacheParticleTable )
        {
            if ( string == name )
            {
                //// // printl( "already precached" )
                break
            }

            PrecacheEntityFromTable({ classname = "info_particle_system", effect_name = name })
            CursesScript.PrecacheParticleTable.name <- string
        }

        local start = start_end[ 0 ]
        local end = start_end[ 1 ]

        local particle = SpawnEntityFromTable( "info_particle_system", 
        {
            effect_name = name, 
            targetname = "_efx_temporary_"+RandomFloat( -9999, 9999 )
            origin = origin
            angles = angles
        }) 
        particle.ValidateScriptScope()
        local scope = particle.GetScriptScope()
        scope.stCurParticle <- name

        function CleanUpOnDeath()
        {
            // local parent = self.GetRootMoveParent()

            if ( !parent.IsAlive() )
            {
                SetPropString( self, "m_iszScriptThinkFunction", "" )
                AddThinkToEnt( self, null )

                EntFireByHandle( self, "Kill", "", -1, null, null )
            }

            return -1
        }

        particle.ValidateScriptScope()

        EntFireByHandle( particle, "Start", "", start, null, null );

        if ( end != -1 )
        {
            EntFireByHandle( particle, "Stop", "", end, null, null );
            EntFireByHandle( particle, "Kill", "", end + SINGLE_TICK, null, null );
        }

        if ( entity != null )
        {   
            particle.AcceptInput( "SetParent", "!activator", entity, entity )
            particle.GetScriptScope().parent <- entity

            /* 
                If we try and kill our particles in a think, we complain about null instance
                Despite my best efforts to check for null instance, it will complain anyway
                I did find a solution through using EntFireByHandle, but i for some reason
                changed the code, and attempts to restore it just caused the same issue.
                So i lost the solution...
            */

            if ( !entity.IsPlayer() )
            {
                CursesScript.SetDestroyCallback( particle, function()
                {
                    for ( local child = self.FirstMoveChild(); child != null; child = child.NextMovePeer() )
                    {
                        //// // printl( "killing: "+child )
                        child.Kill()
                    }
                })
            }
            else 
            {
                // needs to be added in scope to work
                particle.GetScriptScope().CleanUpOnDeath <- CleanUpOnDeath
                AddThinkToEnt( particle, "CleanUpOnDeath" )
            }
        } 

        if ( attachment != null )
        {   
            SetPropEntityArray( particle, "m_hControlPointEnts", attachment, 0 );
        } 

        return particle
    }

    function SetDestroyCallback( entity, callback )
    {
        entity.ValidateScriptScope()
        local scope = entity.GetScriptScope()
        scope.setdelegate({}.setdelegate({
                parent   = scope.getdelegate()
                id       = entity.GetScriptId()
                index    = entity.entindex()
                callback = callback
                _get = function( k )
                {
                    return parent[ k ]
                }
                _delslot = function( k )
                {
                    if ( k == id )
                    {
                        entity = EntIndexToHScript( index )
                        local scope = entity.GetScriptScope()
                        scope.self <- entity
                        callback.pcall( scope )
                    }
                    delete parent[ k ]
                }
            })
        )
    }

    function UnstuckEntity( entity )
    {
        ::MASK_PLAYERSOLID <- 33636363
        local origin = entity.GetOrigin();
        local trace = {
            start = origin,
            end = origin,
            hullmin = entity.GetBoundingMins(),
            hullmax = entity.GetBoundingMaxs(),
            mask = MASK_PLAYERSOLID,
            ignore = entity
        }
        TraceHull( trace );
        if ( "startsolid" in trace )
        {
            local dirs = [ Vector( 1, 0, 0 ), Vector( -1, 0, 0 ), Vector( 0, 1, 0 ), Vector( 0, -1, 0 ), Vector( 0, 0, 1 ), Vector( 0, 0, -1 ) ];
            for ( local i = 16; i <= 96; i += 16 )
            {
                foreach ( dir in dirs )
                {
                    trace.start = origin + dir * i;
                    trace.end = trace.start;
                    delete trace.startsolid;
                    TraceHull( trace );
                    if ( !( "startsolid" in trace ) )
                    {
                        entity.SetAbsOrigin( trace.end );
                        return true;
                    }
                }
            }
            return false;
        }
        return true;
    }

    function AddFunctionListToTable( table, reference )
    {
        if ( reference == null )
        {
            //// // printl( "REFERENCE IS NULL" )
            return
        }

        foreach ( name, func in table )
        {
            if ( !( func in reference ) )
            {
                reference[ name ] <- func
            }
        }
    }

    function Round( num, decimals ) 
    {
        if ( decimals <= 0 )
            return floor( num + 0.5 )

        local mod = pow( 10, decimals )
        return floor( ( num * mod ) + 0.5 ) / mod
    }

    // Return true if the dist between the 2 vecs is smaller then the given dist
    function IsWithinRange( vec1, vec2, dist )
    {
        if ( ( ( vec1 - vec2 ).Length() ) < dist )
        {
            return true
        }

        return false
    }

    function GetDist( vec1, vec2 )
    {
        return ( ( vec1 - vec2 ).Length() )
    }

    function ShowHudHint ( text = "This is a hud hint", player = null, duration = 5.0 ) 
    {
		local hudhint = FindByName( null, "__utilhudhint" )

		local flags = player == null ? 1 : 0

		if ( !hudhint ) hudhint = SpawnEntityFromTable( "env_hudhint", { targetname = "__utilhudhint", spawnflags = flags, message = text })

		hudhint.KeyValueFromString( "message", text )

		EntFireByHandle( hudhint, "ShowHudHint", "", -1, player, player )
		EntFireByHandle( hudhint, "HideHudHint", "", duration, player, player )
	}

    function MiscPrintFunc( player, color, msg ) 
    {
        ClientPrint( player,3, format( "\x07%s%s", color, msg ) )
    }

    function RunMessageBroadcast( WaveNum, player )
    {
        local scope = player.GetScriptScope()
        local bool = GetPropInt( mvm_logic_entity, "m_bMannVsMachineBetweenWaves" )

        if ( player.IsBotOfType( TF_BOT_TYPE ) )
            return 

        // //// // printl( "bool is: "+bool )

        // Check if wave is progressing
        // Check if player is not in aryNoGiveExplanation
        // If either are true, dont print
        // 0 = wave hasnt started
        if ( aryNoGiveExplanation.find( player ) != null || bool == 0 )
        {
            //// // printl( "Player has seen broadcast" )
            return
        }

        CursesScript.aryNoGiveExplanation.append( player )
        // // CursesScript.PrintTable( scope )
        
        switch( WaveNum )
        {
            case 1:

            break;

            case 2:

            break;

            case 3:

            break;

            case 4:

            break;

            case 5:

            break;

            case 6:

            break;

            case 7:
                CursesScript.MiscPrintFunc( player, "ff008b", "A thick smog fills the air;" )
                CursesScript.MiscPrintFunc( player, "ff008b", "Robots-turned-zombies roam, rip, and tear!" )

                //CursesScript.ChangeIconFlags("soldier_backup_giant",0)

                local fog_ent = FindByClassname(null, "env_fog_controller");
                local sky_fog = FindByClassname(null, "sky_camera");

                local skybox_props = 
                [
                    "m_Local.m_skybox3d.scale",
                    "m_Local.m_skybox3d.origin",
                    "m_Local.m_skybox3d.fog.start",
                    "m_Local.m_skybox3d.fog.maxdensity",
                    "m_Local.m_skybox3d.fog.end",
                    "m_Local.m_skybox3d.fog.enable",
                    "m_Local.m_skybox3d.fog.dirPrimary",
                    "m_Local.m_skybox3d.fog.colorSecondary",
                    "m_Local.m_skybox3d.fog.colorPrimary",
                    "m_Local.m_skybox3d.fog.blend",
                    "m_Local.m_skybox3d.area"
                ];

                function ColorConvert(vec)
                {
                    local r = vec.x.tointeger();
                    local g = vec.y.tointeger();
                    local b = vec.z.tointeger();

                    // pack into single int (0xRRGGBB)
                    return (r & 255) | ((g & 255) << 8) | ((b & 255) << 16);
                }
                
                local hFakeFog = SpawnEntityFromTable("env_fog_controller", 
                {
                    "targetname" : "fake_fog"
                    "origin" : "0 0 0"
                    "angles" : "0 0 0"
                    "fogblend" : "0"
                    "fogcolor" : "105 0 0"
                    "fogcolor2" : "168 39 39"
                    "fogdir" : "1 0 0"
                    "fogenable" : "1"
                    "foglerptime" : "3"
                    "fogstart" : "1"
                    "fogend" : "2500"
                    "maxdxlevel" : "0"
                    "mindxlevel" : "0"
                    "spawnflags" : "0"

                    // dont touch these 2
                    "fogmaxdensity" : "1"
                    "farz" : "-1"
                })
                hFakeFog.AcceptInput("SetStartDistLerpTo", "1", null, null)
                hFakeFog.AcceptInput("SetEndDistLerpTo", "2500", null, null)
                // hFakeFog.AcceptInput("SetStartDistLerpTo", "1", null, null)

                CursesScript.PreservedCleanupArray.append(hFakeFog)

                for (local player; player = FindByClassname(player, "player"); )
                {
                    EntFireByHandle(player, "SetFogController", hFakeFog.GetName(), 0.1, null, null)
                    EntFireByHandle(hFakeFog, "StartFogTransition", "", 0.2, null, null)

                    SetPropInt(player, "m_Local.m_skybox3d.scale", 16);
                    // SetPropVector(player, "m_Local.m_skybox3d.origin", value);
                    SetPropFloat(player, "m_Local.m_skybox3d.fog.start", 1000);
                    SetPropFloat(player, "m_Local.m_skybox3d.fog.maxdensity", 1);
                    SetPropFloat(player, "m_Local.m_skybox3d.fog.end", 12000);
                    SetPropBool(player, "m_Local.m_skybox3d.fog.enable", true);
                    SetPropVector(player, "m_Local.m_skybox3d.fog.dirPrimary", Vector(1 0 0));
                    SetPropInt(player, "m_Local.m_skybox3d.fog.colorSecondary", ColorConvert( Vector(105, 0, 0) ) );
                    SetPropInt(player, "m_Local.m_skybox3d.fog.colorPrimary", ColorConvert( Vector(105, 0, 0) ) );
                    SetPropBool(player, "m_Local.m_skybox3d.fog.blend", false);
                    // SetPropInt(player, "m_Local.m_skybox3d.area", value);
                }

                // weird cool fog effect
                // fog_ent.AcceptInput("SetColor", "112, 13, 0", null, null)
                // fog_ent.AcceptInput("SetMaxDensityLerpTo", "2500", null, null)
                // fog_ent.AcceptInput("SetStartDist", "1", null, null)
                // fog_ent.AcceptInput("SetStartDistLerpTo", "1200", null, null)
                // fog_ent.AcceptInput("SetEndDistLerpTo", "3", null, null)


                // fog_ent.AcceptInput("SetEndDistLerpTo", "2500", null, null)
                // fog_ent.AcceptInput("SetRadial", "1", null, null)
                // fog_ent.AcceptInput("SetStartDistLerpTo", "1", null, null)
                // fog_ent.AcceptInput("SetColor", "0, 0, 0", null, null)
                // fog_ent.AcceptInput("SetColorLerpTo", "112, 13, 0", null, null)

                EntFireByHandle(fog_ent, "StartFogTransition", "", SINGLE_TICK * 2, null, null)

            break;

            case 8:

            break;

            default:
                // CursesScript.MiscPrintFunc( player, "ff008b", "BROADCASTING ERROR" )
            break;
        }

        // mission specific
        if ( iTrueWave == iFinalWave )
        {
            CursesScript.MiscPrintFunc( player, "ff008b", "Custom (and potentially copyrighted) Music will play this wave! Type `#music_optout` (without quotations) if you don't want it to play! \n\nYou can type `#music_optin` if you wish to play music after typing #music_optout." )
        }
    }

    function MissionSetup()
    {
        local ent = FindByName( null, "bombpath_choose_relay" ) // may need to change name depending on map
        local ent2 = FindByName( null, "bloodrift_timer" )
        local ent3 = FindByName( null, "bloodrift_tutorial" )
        local ent4 = FindByName( null, "everyonebacktothebasepartner" )
        local cur_wave = GetPropInt( mvm_logic_entity, "m_nMannVsMachineWaveCount" )

        local start_relay = FindByName(null, "wave_start_relay")
        local curse_cntrol = FindByName(null, "curse_cntrl")
        
        CursesScript.iLastWave = GetPropInt(mvm_logic_entity, "m_nMannVsMachineMaxWaveCount")
        CursesScript.iCurWave = GetPropInt(mvm_logic_entity, "m_nMannVsMachineWaveCount")

        SetPropInt(mvm_logic_entity, "m_nMannVsMachineWaveCount", cur_wave - 1)
        SetPropInt(mvm_logic_entity, "m_nMannVsMachineMaxWaveCount", 7)

        for (local player; player = FindByClassname(player, "player");)
        {
            if ( !player in CursesScript.aryNoGiveExplanation )
            {   
                CursesScript.RunMessageBroadcast( cur_wave, player )
            }

            // HACK: Prevent error by checking that the player is not a bot
            if ( curse_cntrol != null && !player.IsBotOfType(TF_BOT_TYPE))
            {
                curse_cntrol.ValidateScriptScope()
                local curse_scope = curse_cntrol.GetScriptScope()

                curse_scope.aryPlayersCursed = CursesScript.IterateArray( curse_scope.aryPlayersCursed, function( slot, entry )
                {
                    if (entry.cursed == player)
                    {
                        // printl("try remove entry")
                        return REMOVE_ENTRY
                    }
                })
            }
        }

        AddOutput
        (
            start_relay, 
            "OnTrigger", 
            "!self", 
            "RunScriptCode", 
            "SetPropInt(mvm_logic_entity, `m_nMannVsMachineMaxWaveCount`, 7)", 
            0, 
            -1
        )

        AddOutput
        (
            start_relay, 
            "OnTrigger", 
            "!self", 
            "RunScriptCode", 
            "SetPropInt(mvm_logic_entity, `m_nMannVsMachineWaveCount`, GetPropInt( mvm_logic_entity, `m_nMannVsMachineWaveCount` ) - 1)", 
            0, 
            -1
        )
        
        ent.AcceptInput( "Trigger", "", null, null )

        local fog_ent = FindByClassname(null, "env_fog_controller")

        fog_ent.AcceptInput("SetEndDistLerpTo", "99999", null, null)
        EntFireByHandle(fog_ent, "StartFogTransition", "", -1, null, null)

        if ( ent2 != null )
        {
            ent2.Kill()
        }
        if ( ent3 != null )
        {
            ent3.Kill()
        }
        if ( ent4 != null )
        {
            ent4.Kill()
        }

        // Clear aryNoGiveExplanation so that players dont see explanation text again
        CursesScript.aryNoGiveExplanation.clear()

        local FuncList_OnTakeDamage = 
        {
            "SetMedievalFireRate" : SetMedievalFireRate
            // "AddFear" : AddFear
            "HHHHealthAttack" : HHHHealthAttack
            "RemoveKnockback" : RemoveKnockback
            // "DisableDamage" : DisableDamage
        }

        AddFunctionListToTable( FuncList_OnTakeDamage, CursesScript.TakeDamageFuncTable )
    }

    function MULT_DAMAGE( 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 d_special_wep = weapon.GetAttribute( "dmg penalty vs players", 1 )
        local d_special = player.GetCustomAttribute( "dmg penalty vs players", 1 )

        local total = 
        ( 
            d_bonus 
            * d_bonus_wep 
            * d_penalty 
            * d_penalty_wep 
            * d_hidden 
            * d_hidden_wep 
            * d_card_wep 
            * d_card
            * d_special_wep
            * d_special
        )
        
        return total
        
    }

    // Get an entity's think table
    function GetEntityThinkTable( entity )
    {
        entity.ValidateScriptScope();
        local scope = entity.GetScriptScope();
        local key = null

        foreach ( k, v in scope )
        {
            if ( startswith( k, "ThinkTable_" ) )
            {
                key = k
                break
            }
        }

        if ( key == null )
        {
            //// // printl( "WARNING: NO THINK TABLE: "+entity )
            return
        }

        if ( !( key in scope ) )
        {
            // If the table doesn't exist yet, create it
            scope[ key ] <- {};
        }

        return scope[ key ];
    }

    // Get a players loadout, and return it as an array
    function GetLoadout( player )
    {
        local loadout = []

        for ( local i = 0; i < SLOT_COUNT; i++ ) 
        {
            local wep = 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
    }

    // Get an item in a players slot
    function GetItemInSlot( player, slot ) 
    {
        local item
        for ( local i = 0; i < SLOT_COUNT; i++ ) 
        {
            local wep = 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 Cleanup()
    {
        ////// // printl( "cleaning file..." )
        if ( "StopRandomizing" in mvm_logic_entity.GetScriptScope() )
        {
            // // printl( "deletion successful" )
            delete mvm_logic_entity.GetScriptScope()[ "StopRandomizing" ]
        }
        AddThinkToEnt( mvm_logic_entity, null )
        
        for ( local player; player = FindByClassname( player, "player" ); )
        {
            PlayerCleanupEx( player )

            for (local i = 0; i < 4; i++)
            {
                CursesScript.ResetCursedPlayer( player, i, CURSE_EXPIRE )
            }

            EmitSoundEx
            ({
                sound_name = "usum_unecrozma_bosstheme.mp3",
                origin = player.GetOrigin(),
                volume = 1
                channel = RandomInt(40, 80)
                entity = player 
                flags = SND_STOP
                filter_type = RECIPIENT_FILTER_GLOBAL
            });
        }

        PreservedCleanupArray = IterateArray( PreservedCleanupArray, function( slot, entry )
        {
            local ent = entry

            if ( !ent.IsValid() || ent == null )
            {
                return REMOVE_ENTRY
            }

            ent.Kill()
            return REMOVE_ENTRY
        })

        // keep this at the end of this function
        delete ::CursesScript
    }


    function PrintTable( table ) 
    {
        if ( table == null ) return;

        this.DoPrintTable( table, 0 )
    }

    function DoPrintTable( 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 )
    }

    function PlayerCleanupEx( player )
    {
        // //////// // printl( "Use special cleanup" )
        SetPropBool( player, "m_bGlowEnabled", false )
        player.SetForcedTauntCam( 0 )
        player.ValidateScriptScope()
        local scope = player.GetScriptScope()
        local curse_cntrol = FindByName(null, "curse_cntrl")

        // HACK: Prevent error by checking that the player is not a bot
        if ( curse_cntrol != null && !player.IsBotOfType(TF_BOT_TYPE))
        {
            curse_cntrol.ValidateScriptScope()
            local curse_scope = curse_cntrol.GetScriptScope()

            curse_scope.aryPlayersCursed = CursesScript.IterateArray( curse_scope.aryPlayersCursed, function( slot, entry )
            {
                if (entry.cursed == player)
                {
                    return REMOVE_ENTRY
                }
            })
        }


        // These are in the players scope already, we must ignore them
        local ignore_table = 
        {
            "self"      : null,
            "__vname"   : null,
            "__vrefs"   : null
        };

        // Anything we put in the players scope is cleaned here
        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 ]
            }
        }

        SetPropString( player, "m_iszScriptThinkFunction", "" )
        AddThinkToEnt( player, null )
    }
    
    function PlayerSpawnEx( player )
    {
        //////// // printl( "Called PlayerSpawnEx" )
        player.ValidateScriptScope()
        // Check if the player is a bot. This ignores Source TV spectators
        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", "ThinkTable_Robots", "Robots_Cleanup_Table" ] : 
        [  "PlayerThink", "ThinkTable_Player", "Player_Cleanup_Table" ]

        // Directly reference our tables 
        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. ThinkTable_Robots )
        

        scope[ key[ 1 ] ] <- {}
        scope.PlayerCleanupEx <- CursesScript.PlayerCleanupEx
        
        scope[ key[ 0 ] ] <- function() 
        { 
            foreach ( name, func in this[ key[ 1 ] ] ) 
            {
                if ( typeof func == "function" )
                {
                    // //////// // printl( "Running func: "+name )
                    func.call( scope );
                }
            }
            
            return -1 
        }

        // append our player into the correct cleanup table
        cleanup_map[ key[ 2 ] ].append( player )

        AddThinkToEnt( player, key[ 0 ] )

        GetEntityThinkTable( player ).ApplyProjectileThink <- CursesScript.ApplyProjectileThink

        //pyro boss player test

        // EntFireByHandle( player, "RunScriptCode", "CursesScript.GetEntityThinkTable( self ).PyroSuperbossThink <- CursesScript.PyroSuperbossThink", 0, null, null )
        // SetPropInt( player, "m_Shared.m_iNextMeleeCrit", -2 )

        // // def
        // scope.bSuperboss <- true
        // scope.bIsMovingToNewSpot <- false 
        // scope.flNextMoveStamp <- 0
        // scope.flFailsafeMoveStamp <- 0
        // scope.aryAlivePlayers <- []
        // scope.flAtkTime <- 5
        // scope.flAttackStamp <- Time() + scope.flAtkTime + 10
        // scope.hAreaPoint <- SpawnEntityFromTable( "bot_action_point", 
        // {
        //     desired_distance = 1
        //     stay_time = 5
        //     targetname = "dummy"
        //     origin = Vector( 0, 0, 0 )
        //     angles = Vector( 0, 0, 0 )
        //     command = "taunt"
        // }) 

        // // atks
        // scope.flProjectileSwarmStamp <- 0
        // scope.flBeginProjectileSwarm <- 0
        // scope.bProjectileSwarming <- false
        // scope.bTriggerSwarmSound <- false
        // scope.aryProjectilesToRotate <- []
        // scope.flSlamAttackDelay <- 0
        // scope.bInitiatingSlam <- false
        // scope.tbGuranteeAttacks <- {}

        // scope.tbGuranteeAttacks[1] <- BOSS_ATTACK_FIREBALL
        // scope.tbGuranteeAttacks[2] <- BOSS_ATTACK_FIREBALL
        // scope.tbGuranteeAttacks[3] <- BOSS_ATTACK_SCYTHE
        // PrintTable(scope.tbGuranteeAttacks)
        // scope.tbAttackList <- 
        // {
        //     "1" : function()
        //     {
        //         CursesScript.SpawnFireball( self, 1, 20 )
        //     }
        //     "2" : function()
        //     {
        //         CursesScript.SpawnWhirlwindSlicer( self )
        //     }
        //     "3" : function()
        //     {
        //         if ( bProjectileSwarming == false )
        //         {
        //             flBeginProjectileSwarm = Time() 
        //         }

        //         bProjectileSwarming = true
        //     }
        //     "4" : function()
        //     {
        //         local sound_range = ( 40 + ( 20 * log10( 8000 / 36.0 ) ) ).tointeger();

        //         local taunt_speed = 0.5
        //         local frame_begin = 0.45
        //         local taunt_duration = 4
        //         local attack_time = 2.3

        //         EmitSoundEx
        //         ({
        //             sound_name = "weapons/stickybomblauncher_charge_up.wav",
        //             origin = self.GetOrigin(),
        //             volume = 1
        //             sound_level = sound_range 
        //             channel = RandomInt(40, 80)
        //             entity = null 
        //             filter_type = RECIPIENT_FILTER_GLOBAL
        //             pitch = 175
        //         });

        //         CursesScript.PlayAnimationOnPlayer( self, "taunt_yeti", taunt_duration, taunt_speed, frame_begin )

        //         bInitiatingSlam = true
        //         flSlamAttackDelay = Time()
        //     }
        // }

        if ( bot_check == false )
        {
            for (local i = 0; i < 4; i++)
            {
                CursesScript.ResetCursedPlayer( player, i, CURSE_RESPAWN )
            }
        }

        if ( bot_check == true )
        {
            // MEGA HACK: Detect if a bot has the cursed particle
            // roll 1% chance for bot to be cursed anyway

            if ( !player.HasBotTag( "bot_cursed" ) && !player.HasBotTag( "bot_nevercurse" ) )
            {
                local rand = RandomInt( 1, 100 )

                player.ValidateScriptScope()
                local scope = player.GetScriptScope()
                
                if ( rand == 1 )
                {
                    DispatchParticleEffectEx( "utaunt_arcane_purple_sparkle", player.GetOrigin(), Vector(), [ -1, -1 ], player, null )
                }
            }

            local bot_tags = {}
            player.GetAllBotTags( bot_tags )

            // get rid of shitty romevision crap

            local get_class_string = [ "", "scout", "sniper", "soldier", "demo", "medic", "heavy", "pyro", "spy", "engineer", "" ]
            local get_cur_class = get_class_string[ player.GetPlayerClass() ]

            // for ( local child = player.FirstMoveChild(); child != null; child = child.NextMovePeer() )
            // {
            //     // //// // printl( "entity "+child+" model: "+child.GetModelName() )
            //     local model_string = format( "models/workshop/player/items/%s/tw_", get_cur_class )
            //     PreservedCleanupArray.append( child ) // for some reason when iterating thru children the cosmetics retain themselves?

            //     if ( startswith( child.GetModelName(), model_string ) )
            //     {
            //         // //// // printl( "killing: "+child )
            //         EntFireByHandle( child, "Kill", "", -1, null, null )
            //     }
            // }

            foreach ( i, tag in bot_tags )
            {
                switch( tag )
                {
                    

                    case "bot_zombie":

                        local plyclass = 
                        {
                            [ 1 ] = { [ "scout" ] = 5617 },
                            [ 2 ] = { [ "sniper" ] = 5625 },
                            [ 3 ] = { [ "soldier" ] = 5618 },
                            [ 4 ] = { [ "demo" ] = 5620 },
                            [ 5 ] = { [ "medic" ] = 5622 },
                            [ 6 ] = { [ "heavy" ] = 5619 },
                            [ 7 ] = { [ "pyro" ] = 5624 },
                            [ 8 ] = { [ "spy" ] = 5623 },
                            [ 9 ] = { [ "engineer" ] = 5621 }
                        }

                        local table = plyclass[ player.GetPlayerClass() ]
                        local zombie_class = table.keys()[ 0 ]
                        local zombie_id = table[ zombie_class ]
                        local zombie_model = format( "models/player/items/%s/%s_zombie.mdl", zombie_class, zombie_class )
                        PrecacheModel( zombie_model )

                        CursesScript.GiveCosmetic( player, zombie_id, zombie_model )

                        SetPropBool( player, "m_bForcedSkin", true )
                        SetPropInt( player, "m_nForcedSkin", player.GetSkin() + 4 )
                        SetPropInt( player, "m_iPlayerSkinOverride", 1 )

                        EntFireByHandle( player, "SetCustomModelWithClassAnimations", format( "models/player/%s.mdl", zombie_class ), -1, null, null )

                    break;

                    case "bot_cursed":
                    
                        CursesScript.DispatchParticleEffectEx( "utaunt_arcane_purple_sparkle", player.GetOrigin(), Vector(), [ -1, -1 ], player, null )

                    break;

                    case "tag_alwaysfirealt":

                        function AlwaysFireAlt()
                        {
                            //// // printl( "firing alt" )
                            self.PressAltFireButton( -1 )
                        }
                        GetEntityThinkTable( player ).AlwaysFireAlt <- AlwaysFireAlt
                        
                    break;

                    case "tag_full_head":

                        SetPropInt( player, "m_Shared.m_iDecapitations", 5 )
                        
                    break;

                    case "tag_melee_closerange":

                        function UseMeleeWhenClose()
                        {
                            local cur_origin = self.GetOrigin()
                            local model_scale = self.GetModelScale()
                            local melee_range = 64 * model_scale
                            local primary = CursesScript.GetItemInSlot( self, 0 )
                            local secondary = CursesScript.GetItemInSlot( self, 1 )
                            local melee = CursesScript.GetItemInSlot( self, 2 )
                            if ( melee == null )
                            {
                                return
                            }

                            local range_mult = melee.GetAttribute( "melee range multiplier", 1 )
                            local sword_additive = melee.GetAttribute( "is_a_sword", 0 )

                            // check if we have a sword for extended range
                            if ( sword_additive != 0 )
                            {
                                melee_range += sword_additive
                            }

                            melee_range *= range_mult

                            local aryEnemyList = CursesScript.GetEnemiesWithinArea( self, melee_range, cur_origin + ( Vector( 0, 0, 25 ) * model_scale ) )
                            
                            if ( aryEnemyList.len() > 0 && bSwitchBool == true ) 
                            {
                                // //// // printl( "SWITCH MELEE" )
                                self.AddWeaponRestriction( RESTRICT_MELEE )
                                self.RemoveWeaponRestriction( RESTRICT_PRIMARY )
                                bSwitchBool = false
                            }
                            else if ( aryEnemyList.len() == 0 && bSwitchBool == false )
                            {
                                // //// // printl( "SWITCH PRIMARY" )
                                self.RemoveWeaponRestriction( RESTRICT_MELEE )
                                self.AddWeaponRestriction( RESTRICT_PRIMARY )
                                bSwitchBool = true
                            }
                        }

                        // Set up the think function
                        player.GetScriptScope().bSwitchBool <- false
                        GetEntityThinkTable( player ).UseMeleeWhenClose <- UseMeleeWhenClose
                        
                    break;

                    case "bot_hhh":
                        player.SetCustomModelWithClassAnimations("models/bots/headless_hatman_burnt.mdl")
                        local book = CursesScript.GiveWeapon( player, "tf_weapon_spellbook", 1069 )
                        SetPropString(player, "m_iName", "hhh_ent")

                        local eye_boss = SpawnEntityFromTable( "eyeball_boss",
                        {
                            origin = player.GetOrigin() + Vector(0, 0, 160)
                            angles = Vector(0, 0, 0),
                            teamnum = 3,
                            skin = 0
                            targetname = "big_eye"
                        });
                        eye_boss.ValidateScriptScope()
                        local eye_scope = eye_boss.GetScriptScope()

                        function EyebossSkinFix()
                        {
                            self.SetSkin(0)

                            SetPropBool( self, "m_bForcedSkin", true )
                            SetPropInt( self, "m_nForcedSkin", 0 )
                            return -1
                        }
                        eye_scope.EyebossSkinFix <- EyebossSkinFix
                        AddThinkToEnt(eye_boss, "EyebossSkinFix")

                        local eyename = GetPropString(eye_boss, "m_iName")
                        local hhhname = GetPropString(player, "m_iName")

                        eye_boss.SetOwner( book )
                        eye_boss.SetSolid( SOLID_NONE )
 
                        player.ValidateScriptScope()
                        local scope = player.GetScriptScope()
                        scope.hEyeBoss <- eye_boss
                        scope.hBook <- book
                        scope.flStunStamp <- Time()
                        scope.flTauntStamp <- Time()
                        scope.bIsDying <- false
                        scope.bIsScaring <- false

                        function HHHAbility()
                        {
                            local eyename = GetPropString(hEyeBoss, "m_iName")
                            local hhhname = GetPropString(self, "m_iName")
                            local cur_time = Time()
                            
                            local cur_origin = self.GetOrigin()

                            if (self.GetHealth() == 1 && bIsDying == false)
                            {
                                bIsDying = true

                                self.AddBotAttribute(IGNORE_ENEMIES)
                                self.AddBotAttribute(IGNORE_FLAG)
                                self.RemoveBotAttribute(USE_BOSS_HEALTH_BAR)
                                SetPropBool(self, "m_bUseBossHealthBar", false)
                                GetPropEntity(self, "m_hItem").AcceptInput("ForceResetSilent", "", null, null)

                                local sound_range = ( 40 + ( 20 * log10( 8000 / 36.0 ) ) ).tointeger();

                                CursesScript.PlaySoundToAllEnemies( self, "vo/halloween_boss/knight_dying.mp3", 0, -1 )
                                CursesScript.PlaySoundToAllEnemies( self, "vo/halloween_eyeball/eyeball09.mp3", 1, -1 )

                                CursesScript.PlaySoundToAllEnemies( self, "ui/halloween_boss_defeated.wav", 3, -1 )

                                //"ui/halloween_boss_defeated.wav" 

                                self.Teleport( true, Vector( -1734, -6064, -22 ), false, QAngle(), false, Vector() )

                                EntFireByHandle(self, "RunScriptCode", "CursesScript.MiscPrintFunc( null, `27F5A9`, `If you thought you were done, save your fears!` )", 5, null, null)
                                EntFireByHandle(self, "RunScriptCode", "CursesScript.MiscPrintFunc( null, `27F5A9`, `This Halloween, you'll shed BLOODY TEARS!` )", 10, null, null)
                                // EntFireByHandle(self, "RunScriptCode", "SetPropInt(self, `m_lifeState`, 1)", 10, null, null)
                                EntFireByHandle(self, "RunScriptCode", "self.Teleport( true, Vector( -1764, -3838, 110 ), false, QAngle(), false, Vector() )", 10, null, null)

                                if (hEyeBoss != null || hEyeBoss.IsValid())
                                {
                                    hEyeBoss.Kill()
                                }
                            }

                            if (bIsDying == true)
                            {
                                return
                            }

                            self.AddCondEx(TF_COND_PREVENT_DEATH, 9999, null)

                            if ( hEyeBoss == null || !hEyeBoss.IsValid() && bIsDying == false)
                            {
                                local eye_boss = SpawnEntityFromTable( "eyeball_boss",
                                {
                                    origin = self.GetOrigin() + Vector(0, 0, 160)
                                    angles = Vector(0, 0, 0),
                                    teamnum = self.GetTeam(),
                                    skin = 0
                                });

                                eye_boss.ValidateScriptScope()
                                local eye_scope = eye_boss.GetScriptScope()

                                eye_boss.SetOwner( hBook )
                                eye_boss.SetSolid( SOLID_NONE )

                                hEyeBoss = eye_boss

                                function EyebossSkinFix()
                                {
                                    self.SetSkin(0)
                                    SetPropBool( self, "m_bForcedSkin", true )
                                    SetPropInt( self, "m_nForcedSkin", 0 )
                                    return -1
                                }
                                eye_scope.EyebossSkinFix <- EyebossSkinFix
                                AddThinkToEnt(eye_boss, "EyebossSkinFix")
                            }

                            hEyeBoss.SetAbsOrigin(cur_origin + Vector(0, 0, 400))
                            local enemies = CursesScript.GetEnemiesWithinArea( self, 750, cur_origin )
                            local taunt_cooldown = 15

                            if ( ( flTauntStamp + ( taunt_cooldown - 3 ) ) <= cur_time)
                            {
                                // printl("taunt")
                                bIsScaring = true
                                local laugh_sound = "vo/halloween_boss/knight_laugh0"+RandomInt(1,4)+".mp3"
                                PrecacheSound(laugh_sound)

                                CursesScript.DispatchParticleEffectEx
                                ( 
                                    "ghost_firepit", 
                                    cur_origin
                                    Vector( 0, 0, 0 ), 
                                    [ 0, 3 ], 
                                    self, 
                                    null
                                )

                                local sound_range = ( 40 + ( 20 * log10( 8000 / 36.0 ) ) ).tointeger();

                                EmitSoundEx
                                ({
                                    sound_name = laugh_sound,
                                    origin = cur_origin,
                                    volume = 1
                                    sound_level = sound_range 
                                    channel = RandomInt(40, 80)
                                    entity = self 
                                    filter_type = RECIPIENT_FILTER_GLOBAL
                                });
                                EmitSoundEx
                                ({
                                    sound_name = laugh_sound,
                                    origin = cur_origin,
                                    volume = 1
                                    sound_level = sound_range 
                                    channel = RandomInt(40, 80)
                                    entity = self 
                                    filter_type = RECIPIENT_FILTER_GLOBAL
                                });

                                flTauntStamp = cur_time + 3
                                CursesScript.PlayAnimationOnPlayer( self, "shake", 3, 1, 0 )
                                EntFireByHandle( self, "RunScriptCode", "CursesScript.PlayAnimationOnPlayer( self, `boo`, 1.5, 1, 0 )", 3, null, null )
                            }

                            if ( (flStunStamp + taunt_cooldown) <= cur_time && bIsScaring == true)
                            {
                                // printl("stun")
                                bIsScaring = false
                                local sound_range = ( 40 + ( 20 * log10( 8000 / 36.0 ) ) ).tointeger();

                                CursesScript.DispatchParticleEffectEx
                                ( 
                                    "ghost_appearation", 
                                    cur_origin
                                    Vector( 0, 0, 0 ), 
                                    [ 0, 1 ], 
                                    self, 
                                    null
                                )

                                EmitSoundEx
                                ({
                                    sound_name = "vo/halloween_boss/knight_alert.mp3",
                                    origin = cur_origin,
                                    volume = 1
                                    sound_level = sound_range 
                                    channel = RandomInt(40, 80)
                                    entity = self 
                                    filter_type = RECIPIENT_FILTER_GLOBAL
                                });
                                EmitSoundEx
                                ({
                                    sound_name = "vo/halloween_boss/knight_alert.mp3",
                                    origin = cur_origin,
                                    volume = 1
                                    sound_level = sound_range 
                                    channel = RandomInt(40, 80)
                                    entity = self 
                                    filter_type = RECIPIENT_FILTER_GLOBAL
                                });
                                foreach (i, enemy in enemies)
                                {
                                    local loadout = CursesScript.GetLoadout( enemy )
                                    local immune_bool = false
                                    
                                    foreach (i, item in loadout)
                                    {
                                        // if hhh head or saxton hale mask, no stun
                                        if ( GetPropInt(item, STRING_NETPROP_ITEMDEF) == 277 || GetPropInt(item, STRING_NETPROP_ITEMDEF) == 278 )
                                        {
                                            local immune_bool = true
                                            break
                                        } 
                                    }

                                    if ( immune_bool == true || enemy.GetClassname() != "player" )
                                    {
                                        continue
                                    }

                                    enemy.StunPlayer( 5.0, 0.5, TF_STUN_LOSER_STATE|TF_STUN_BY_TRIGGER, null )
                                }

                                flStunStamp = cur_time
                            }
                        }

                        GetEntityThinkTable( player ).HHHAbility <- HHHAbility
                    break;

                    case "bot_numpty":
                        player.Teleport( true, Vector(-3605, -3220, 88), false, QAngle(), false, Vector() )
                    break;

                    case "tag_fake_boss":
                        player.SetCustomModelWithClassAnimations( "models/bots/merasmus/merasmus.mdl" )
                        player.SetIsMiniBoss( false ) // players can see where merasmus is via healthbar
                        EntFireByHandle( player, "RunScriptCode", "self.AddCondEx( TF_COND_INVULNERABLE_CARD_EFFECT, -1, null )", 0.1, null, null ) // merasmus is rendered weirdly, ubercharge him to fix   
                        player.AddCondEx( 5, -1, null ) // merasmus is rendered weirdly, ubercharge him to fix   
                        local merasmus_spawn = Vector( -4387, 2902, -240 )         
                        player.Teleport( true, merasmus_spawn, true, QAngle( 3, -45 ), false, Vector() )

                        local base_stun = TF_STUN_CONTROLS
                        EntFireByHandle( player, "RunScriptCode", "self.StunPlayer( 5, 0, TF_STUN_CONTROLS, null )", 5, null, null )

                        // Play animation
                        // Dont unhide model because he briefly appears then teleports
                        EntFireByHandle( player, "RunScriptCode", "CursesScript.PlayAnimationOnPlayerNoModelRevert( self, `primary_death_burning`, 4.45 )", 5, null, null )
                        // players can still attack merasmus for SOME FUCKING DUMB REASON when he plays anims
                        // Considering that bosses have their health bar removed upon death, just hide the health bar and reset Merasmuses health
                        EntFireByHandle( player, "RunScriptCode", "self.SetHealth(9999999999)", 5, null, null )
                        EntFireByHandle( player, "RunScriptCode", "SetPropBool(self, `m_bUseBossHealthBar`, false)", 4.9, null, null )

                        EntFireByHandle( player, "RunScriptCode", "self.AddCondEx( TF_COND_STEALTHED_USER_BUFF, -1, null )", 5, null, null )

                        // i am here

                        CursesScript.DispatchParticleEffectEx( "green_steam_plume", merasmus_spawn, Vector( -75, 270, 90 ), [ 0, 5 ], null, null )
                        CursesScript.DispatchParticleEffectEx( "green_steam_plume", merasmus_spawn, Vector( -68, 44, -45 ), [ 0, 5 ], null, null )
                        DispatchParticleEffect( "green_wof_sparks", merasmus_spawn, Vector() )
                        DispatchParticleEffect( "hammer_lock_vanish01", merasmus_spawn, Vector() )

                        CursesScript.PlaySoundToAllEnemies( player, "misc/halloween/merasmus_appear.wav", -1, -1 )
                        CursesScript.PlaySoundToAllEnemies( player, "ui/halloween_boss_summoned.wav", -1, -1 )
                        CursesScript.PlaySoundToAllEnemies( player, "vo/halloween_merasmus/sf12_appears03.mp3", -1, -1 )

                        CursesScript.DispatchParticleEffectEx
                        ( 
                            "merasmus_spawn", 
                            player.GetOrigin(), 
                            Vector( 0, 0, 0 ), 
                            [ 0, 0.01 ], 
                            null, 
                            null
                        )

                        // ( merasmus is actually dying )

                        EntFireByHandle( player, "RunScriptCode", "CursesScript.GetEntityThinkTable( self ).DrainBossHealth <- CursesScript.DrainBossHealth", 1.5, null, null )

                        for ( local i = 0; i < RandomInt( 10, 20 ); i++ )
                        {
                            local explode_time = RandomFloat( 2, 6 )
                            local zap_particle = ( RandomInt( 0, 1 ) == 1 ) ? "spell_lightningball_hit_zap_red" : "spell_lightningball_hit_zap_blue"
                            //// // printl( zap_particle )

                            local attack_bool = RandomInt( 0, 1 )

                            if ( attack_bool == 1 )
                            {
                                // zap

                                CursesScript.DispatchParticleEffectEx
                                ( 
                                    zap_particle, 
                                    player.GetOrigin() + Vector( 0, 0, 500 ) + Vector( RandomFloat( -100, 100 ), RandomFloat( -100, 100 ), RandomFloat( 10, 110 ) ), 
                                    Vector( 90, 0, 0 ), 
                                    [ explode_time, 9 ], 
                                    player, 
                                    player
                                )

                                CursesScript.PlaySoundToAllEnemies( player, "misc/halloween/spell_lightning_ball_impact.wav", explode_time, -1, 0.5 )
                            }
                            else 
                            {
                                // explode

                                CursesScript.DispatchParticleEffectEx
                                ( 
                                    "merasmus_bomb_explosion", 
                                    player.GetOrigin() + Vector( RandomFloat( -20, 20 ), RandomFloat( -20, 20 ), RandomFloat( 10, 110 ) ), 
                                    Vector( 90, 0, 0 ), 
                                    [ explode_time, 9 ], 
                                    player, 
                                    player
                                )

                                CursesScript.PlaySoundToAllEnemies( player, "misc/halloween/spell_meteor_impact.wav", explode_time, -1, 0.5 )
                            }

                            EntFireByHandle( player, "RunScriptCode", "self.SetHealth( self.GetHealth() - 1000 )", explode_time, null, null )

                            for ( local bot; bot = FindByClassname( bot, "player" ); )
                            {
                                if ( bot.IsBotOfType( TF_BOT_TYPE ) )
                                {
                                    if ( bot.HasBotTag( "tag_real_boss" ) && bot.IsAlive() )
                                    {
                                        EntFireByHandle( bot, "RunScriptCode", "self.SetHealth( self.GetHealth() + 15000 )", explode_time, null, null )
                                    }
                                }
                            }

                            //misc\halloween\merasmus_hiding_explode.wav
                        }

                        CursesScript.DispatchParticleEffectEx
                        ( 
                            "utaunt_hands_green_parent", 
                            player.GetOrigin(), 
                            Vector( 0, 0, 0 ), 
                            [ 1.5, 8.25 ], 
                            player, 
                            null
                        )


                        CursesScript.DispatchParticleEffectEx
                        ( 
                            "utaunt_hands_goop_green", 
                            player.GetOrigin(), 
                            Vector( 0, 0, 0 ), 
                            [ 3.25, 8.25 ], 
                            player, 
                            player.GetActiveWeapon()
                        )
                        
                        CursesScript.DispatchParticleEffectEx
                        ( 
                            "utaunt_hands_goop_green", 
                            player.GetOrigin() + Vector( 0, 0, 105 ), 
                            Vector( 0, 0, 0 ), 
                            [ 3.25, 8.25 ], 
                            player, 
                            null
                        )

                        CursesScript.DispatchParticleEffectEx
                        ( 
                            "utaunt_hands_goop_green", 
                            player.GetOrigin() + Vector( 0, 0, 65 ), 
                            Vector( 0, 0, 0 ), 
                            [ 3.5, 8.25 ], 
                            player, 
                            null
                        )

                        // SetPropInt( self, "m_lifeState", 1 )

                        // oh fuck im dead
                        EntFireByHandle( player, "RunScriptCode", "CursesScript.LookAt( self, Vector( -2830, 1576, -45 ), 9999, 9999 )", 4.5, null, null )
                        // EntFireByHandle( player, "RunScriptCode", "SetPropInt( self, `m_lifeState`, 1 )", 10, null, null )

                        EntFireByHandle( player, "RunScriptCode", "self.RemoveBotAttribute( USE_BOSS_HEALTH_BAR )", 10, null, null )

                        CursesScript.DispatchParticleEffectEx
                        ( 
                            "merasmus_dazed", 
                            player.GetOrigin() + Vector( 0, 0, 150 ), 
                            Vector( 0, 0, 0 ), 
                            [ 3.5, 8.25 ], 
                            player, 
                            null
                        )
                        CursesScript.PlaySoundToAllEnemies( player, "vo/halloween_merasmus/sf12_magic_backfire06.mp3", 5, -1 )
                        CursesScript.PlaySoundToAllEnemies( player, "vo/halloween_merasmus/sf12_defeated12.mp3", 6.5, 2 )
                        CursesScript.PlaySoundToAllEnemies( player, "vo/halloween_merasmus/sf12_defeated04.mp3", 8.25, -1 )
                    

                        CursesScript.DispatchParticleEffectEx
                        ( 
                            "hammer_bell_ring_shockwave", 
                            player.GetOrigin(), 
                            Vector( 0, 0, 0 ), 
                            [ 9.5, 10 ], 
                            player, 
                            null
                        )
                        CursesScript.DispatchParticleEffectEx
                        ( 
                            "hammer_lock_vanish01", 
                            player.GetOrigin(), 
                            Vector( 0, 0, 0 ), 
                            [ 9.5, 10 ], 
                            player, 
                            null
                        )

                        CursesScript.PlaySoundToAllEnemies( player, "misc/halloween/spell_spawn_boss.wav", 9.5, -1 )
                        EntFireByHandle( player, "RunScriptCode", "self.Teleport( true, Vector( -3380, -6065, 573 ), false, QAngle(), false, Vector() )", 12, null, null )

                        EntFireByHandle( player, "RunScriptCode", "self.SetMoveType( MOVETYPE_NOCLIP + MOVETYPE_FLY, MOVECOLLIDE_DEFAULT )", 9.5, null, null )
                        EntFireByHandle( player, "RunScriptCode", "self.AddCondEx( TF_COND_FREEZE_INPUT, -1, null )", 9.5, null, null )
                        EntFireByHandle( player, "RunScriptCode", "self.StunPlayer( -1, 0, TF_STUN_CONTROLS, null )", 9.5, null, null )

                        // StopRandomizing
                        EntFireByHandle( player, "RunScriptCode", "mvm_logic_entity.GetScriptScope().StopRandomizing <- 1", 9.45, null, null )
                        EntFireByHandle( player, "RunScriptCode", "AddThinkToEnt( mvm_logic_entity, null )", 9.65, null, null )

                        // Fade and Shake
                        // ScreenFade(handle player, int red, int green, int blue, int alpha, float fadeTime, float fadeHold, int flags)
                        EntFireByHandle(player, "RunScriptCode", "ScreenFade( null, 112, 13, 0, 255, 1, 3, FFADE_OUT )", 11, null, null)
                        EntFireByHandle(player, "RunScriptCode", "ScreenFade( null, 112, 13, 0, 255, 1, 1, FFADE_IN )", 14, null, null)

                        CursesScript.PlaySoundToAllEnemies( player, "player/taunt_pyro_hellicorn.wav", 20.25, -1, 1, 60 )

                        // knock players away for impact on arrival

                        // for ( local enemy; enemy = FindByClassname( enemy, "player" ); )
                        // {
                        //     if ( enemy.GetTeam() != player.GetTeam() && enemy.GetTeam() != TEAM_SPECTATOR )
                        //     {
                        //         local eye_angles = player.EyeAngles()
                        //         local fwd = eye_angles.Forward()

                        //         local base_knockback_value = 2000
                        //         local mult = base_knockback_value 
                        //         local upward_mult = 300

                        //         enemy.StunPlayer( 0.3, 1, STUN_MOVE | STUN_MOVE_FORWARD_ONLY, null )
                        //         enemy.SetAbsVelocity( Vector( fwd.x * mult, fwd.y * mult, fwd.z + upward_mult ) )
                        //     }
                        // }
                        
                    break;

                    case "tag_real_boss":
                        
                        local melee = CursesScript.GetItemInSlot( player, 2 )
                        local scope = player.GetScriptScope()

                        SetPropBool( player, "m_bForcedSkin", true )
                        SetPropInt( player, "m_nForcedSkin", player.GetSkin() + 3 )
                        SetPropInt( player, "m_iPlayerSkinOverride", 1 )
                        player.SetCustomModelWithClassAnimations( "models/player/pyro.mdl" )
                        
                        // rather then hiding the fire axe, we'll just give the bot heavy's fist
                        // since heavys fist uses his actual model, it makes the weapon seem invisble
                        CursesScript.GiveWeapon( player, "tf_weapon_fists", 5 )

                        PrecacheModel( "models/player/pyro.mdl" )

                        player.SetHealth( 1 )

                        EntFireByHandle( player, "RunScriptCode", "CursesScript.GetEntityThinkTable( self ).RegenBossHealth <- CursesScript.RegenBossHealth", 1.5, null, null )
                        player.GetScriptScope().bIsHealing <- true
                        player.GetScriptScope().flHealStamp <- 0

                        // thanks braindawg
                        local model = PrecacheModel( "models/weapons/c_models/c_scythe/c_scythe.mdl" )

                        local tpWearable = CreateByClassname( "tf_wearable" )
                        SetPropInt( tpWearable, "m_nModelIndex", model )
                        tpWearable.SetModelSimple( "models/weapons/c_models/c_scythe/c_scythe.mdl" ) // script wont recognise model if we dont do this
                        SetPropBool( tpWearable, STRING_NETPROP_INIT, true )
                        SetPropBool( tpWearable, STRING_NETPROP_ATTACH, true )
                        SetPropEntity( tpWearable, "m_hOwnerEntity", player )
                        tpWearable.SetOwner( player )
                        tpWearable.DispatchSpawn()
                        tpWearable.AcceptInput( "SetParent", "!activator", player, player )
                        SetPropInt( tpWearable, "m_fEffects", 129 ) // EF_BONEMERGE|EF_BONEMERGE_FASTCULL
                        PreservedCleanupArray.append( tpWearable )
                        
                        // intro anim
                        // seting last arg to 0.2 makes trasition smoother...?
                        EntFireByHandle( player, "RunScriptCode", "CursesScript.PlayAnimationOnPlayer( self, `taunt_lollichop`, 2.8, 1, 0.075 )", 25, null, null )
                        EntFireByHandle(player, "RunScriptCode", "ScreenFade( null, 112, 13, 0, 255, 1, 1, FFADE_OUT )", 27, null, null)
                        EntFireByHandle(player, "RunScriptCode", "ScreenFade( null, 112, 13, 0, 255, 1, 1, FFADE_IN )", 28, null, null)
                        EntFireByHandle(player, "RunScriptCode", "self.AddBotAttribute(IGNORE_ENEMIES) ", 0, null, null)
                        EntFireByHandle(player, "RunScriptCode", "self.RemoveBotAttribute(IGNORE_ENEMIES) ", 31, null, null)

                        // x (left right)
                        // y (back forth)
                        CursesScript.DispatchParticleEffectEx
                        ( 
                            "superrare_burning2", 
                            ( player.GetAttachmentOrigin( player.LookupAttachment( "eyeglow_R" ) ) + Vector(-5, 60, -40) ), 
                            Vector( 0, 0, 0 ), 
                            [ 23.57, 24 ], 
                            player, 
                            null
                        )
                        CursesScript.DispatchParticleEffectEx
                        ( 
                            "superrare_burning1", 
                            ( player.GetAttachmentOrigin( player.LookupAttachment( "eyeglow_R" ) ) + Vector(-15, 60, -40) ), 
                            Vector( 0, 0, 0 ), 
                            [ 23.57, 24 ], 
                            player, 
                            null
                        )
                        CursesScript.PlaySoundToAllEnemies( player, "ambient/fire/gascan_ignite1.wav", 23.57, 24 )

                        // Teleport all players to the arena
                        for ( local enemy; enemy = FindByClassname( enemy, "player" ); )
                        {
                            if ( enemy.GetTeam() != player.GetTeam() && enemy.GetTeam() != TEAM_SPECTATOR && enemy.IsAlive() )
                            {
                                if ( !enemy.IsAlive() )
                                {
                                    EntFireByHandle( enemy, "RunScriptCode", "self.ForceRegenerateAndRespawn()", ( 12 - SINGLE_TICK ), null, null )
                                }
                                
                                EntFireByHandle( enemy, "RunScriptCode", "self.Teleport( true, Vector( RandomFloat(-1500, -1600), RandomFloat(-5300, -5500), -120 ), false, QAngle(), false, Vector() )", 12, null, null )
                                //EntFireByHandle( enemy, "RunScriptCode", "ScreenShake( self.GetOrigin(), 1500, float flFrequency, float flDuration, float flRadius, int eCommand, bool bAirShake)", 11, null, null)
                                EntFireByHandle( enemy, "RunScriptCode", "self.AddCondEx( TF_COND_INVULNERABLE_CARD_EFFECT, 13, null )", 12.1, null, null )
                                EntFireByHandle( enemy, "RunScriptCode", "self.AddCondEx( TF_COND_FREEZE_INPUT, 12, null )", 11.1, null, null )

                                // EntFireByHandle( enemy, "RunScriptCode", "CursesScript.LookAt( self, activator.EyePosition(), 9999, 9999 ) ", 12, player, null )
                                // ScreenShake( enemy.GetOrigin(), 1500, 0.05, 3, 75, SHAKE_START, true)
                                // CursesScript.DispatchParticleEffectEx
                                // ( 
                                //     "eyeboss_aura_angry", 
                                //     enemy.EyePosition()+enemy.EyeAngles().Forward() * 25, 
                                //     Vector( 0, 0, 0 ), 
                                //     [ 10, 14 ], 
                                //     enemy, 
                                //     null
                                // )
                                EntFireByHandle( enemy, "RunScriptCode", "self.SnapEyeAngles(QAngle(-10, -130)) ", 24, enemy, enemy)
                                local scope = enemy.GetScriptScope()

                                //CursesScript.MiscPrintFunc( player, "27F54D", "Custom music will not play." )

                                if ( "bNoMusic" in scope )
                                {
                                    // CursesScript.PrintTable(scope)
                                    // printl("no music")
                                    EntFireByHandle( enemy, "RunScriptCode", "CursesScript.MiscPrintFunc( self, `9527F5`, `Custom music now playing. You have opted out.` ) ", 8.25, enemy, enemy)
                                }
                                else 
                                {
                                    // CursesScript.PrintTable(scope)
                                    EntFireByHandle( enemy, "RunScriptCode", "CursesScript.MiscPrintFunc( self, `9527F5`, `Now playing:\x07F5B427 Pokemon Ultra Sun/Ultra Moon - Ultra Necrozma Battle Theme` ) ", 8.25, enemy, enemy)

                                    CursesScript.PlaySoundToAllEnemies( player, "usum_unecrozma_bosstheme.mp3", 8.25, -1 )
                                    // CursesScript.PlaySoundToAllEnemies( player, "usum_unecrozma_bosstheme.mp3", 8.25, -1 )
                                    // printl("want music")
                                }

                                local camera = SpawnEntityFromTable("point_viewcontrol",
                                {
                                    origin = Vector( -1842, -5926, -118 ),
                                    angles = "30 270 0",
                                    spawnflags = 8,
                                    vscripts = "curses/camera_fixer"
                                });
                                camera.ValidateScriptScope()
                                camera.GetScriptScope().hPlayer <- enemy
                                PreservedCleanupArray.append( camera )
                                CameraArray.append( camera )
                                EntFireByHandle(camera, "Enable", "", 12, enemy, null);

                                // CursesScript.PlaySoundToAllEnemies( player, "music/hl2_song28.mp3", 12, -1 )
                                EntFireByHandle(enemy, "RunScriptCode", "self.AddHudHideFlags(262143)", 12, null, null)
                                EntFireByHandle(camera, "RunScriptCode", "self.GetScriptScope().AllowThink <- 1", 12, null, null)

                                // EntFireByHandle( enemy, "RunScriptCode", "CursesScript.GetEntityThinkTable( self ).LivesTimeInfo <- CursesScript.LivesTimeInfo", 30, null, null )
                                EntFireByHandle( enemy, "RunScriptCode", "CursesScript.AddHudElement( self, `meter_TimeLeft`, `LivesTime Error` )", 30, null, null )
                            }
                        }
                        
                        // initiate functionality
                        EntFireByHandle( player, "RunScriptCode", "CursesScript.GetEntityThinkTable( self ).PyroSuperbossThink <- CursesScript.PyroSuperbossThink", 30, null, null )
                        EntFireByHandle( player, "RunScriptCode", "CursesScript.bSendToHell = true", 30, null, null )
                        EntFireByHandle( player, "RunScriptCode", "SetPropBool( CursesScript.hSuperBossPlayer, `m_bGlowEnabled`, true )", 30, null, null )
                        CursesScript.hSuperBossPlayer = player
                        
                        SetPropInt( player, "m_Shared.m_iNextMeleeCrit", -2 )

                        // def
                        scope.bSuperboss <- true
                        scope.bIsMovingToNewSpot <- false 
                        scope.flNextMoveStamp <- 0
                        scope.flFailsafeMoveStamp <- 0
                        scope.aryAlivePlayers <- []
                        scope.flAtkTime <- 4
                        scope.flAttackStamp <- Time() + scope.flAtkTime + 12
                        scope.hAreaPoint <- SpawnEntityFromTable( "bot_action_point", 
                        {
                            desired_distance = 1
                            stay_time = 5
                            targetname = "dummy"
                            origin = Vector( 0, 0, 0 )
                            angles = Vector( 0, 0, 0 )
                            command = "taunt"
                        }) 

                        scope.aryPlayers <- []
                        scope.flTimeLeft <- 0
                        scope.flFightTime <- 300 // 300 = 5 minutes in seconds

                        for ( local enemy; enemy = FindByClassname( enemy, "player" ); )
                        {
                            if ( enemy.GetTeam() != player.GetTeam() && enemy.GetTeam() != TEAM_SPECTATOR && !( enemy in scope.aryPlayers ) )
                            {
                                scope.aryPlayers.append( enemy )
                            }
                        }

                        CursesScript.PrintTable(scope.aryPlayers)

                        // atks
                        scope.flProjectileSwarmStamp <- 0
                        scope.flBeginProjectileSwarm <- 0
                        scope.bProjectileSwarming <- false
                        scope.bTriggerSwarmSound <- false
                        scope.aryProjectilesToRotate <- []
                        scope.flSlamAttackDelay <- 0
                        scope.bInitiatingSlam <- false
                        scope.tbGuranteeAttacks <- {}

                        scope.tbGuranteeAttacks[1] <- BOSS_ATTACK_FIREBALL
                        scope.tbGuranteeAttacks[2] <- BOSS_ATTACK_FIREBALL
                        scope.tbGuranteeAttacks[3] <- BOSS_ATTACK_SCYTHE
                        // PrintTable(scope.tbGuranteeAttacks)
                        scope.tbAttackList <- 
                        {
                            "1" : function()
                            {
                                for (local i = 0; i < 0.6; i += 0.2)
                                {
                                    EntFireByHandle(self, "RunScriptCode", "CursesScript.SpawnFireball( self, 1, 20 )", i, null, null)
                                }
                            }
                            "2" : function()
                            {
                                CursesScript.SpawnWhirlwindSlicer( self )
                            }
                            "3" : function()
                            {
                                if ( bProjectileSwarming == false )
                                {
                                    flBeginProjectileSwarm = Time() 
                                }

                                bProjectileSwarming = true
                            }
                            "4" : function()
                            {
                                local sound_range = ( 40 + ( 20 * log10( 16000 / 36.0 ) ) ).tointeger();

                                local taunt_speed = 0.5
                                local frame_begin = 0.45
                                local taunt_duration = 4
                                local attack_time = 2.3

                                EmitSoundEx
                                ({
                                    sound_name = "weapons/stickybomblauncher_charge_up.wav",
                                    origin = self.GetOrigin(),
                                    volume = 1
                                    sound_level = sound_range 
                                    channel = RandomInt(40, 80)
                                    entity = null 
                                    filter_type = RECIPIENT_FILTER_GLOBAL
                                    pitch = 175
                                });
                                EmitSoundEx
                                ({
                                    sound_name = "weapons/stickybomblauncher_charge_up.wav",
                                    origin = self.GetOrigin(),
                                    volume = 1
                                    sound_level = sound_range 
                                    channel = RandomInt(40, 80)
                                    entity = null 
                                    filter_type = RECIPIENT_FILTER_GLOBAL
                                    pitch = 175
                                });

                                CursesScript.PlayAnimationOnPlayer( self, "taunt_yeti", taunt_duration, taunt_speed, frame_begin )

                                bInitiatingSlam = true
                                flSlamAttackDelay = Time()
                            }
                        }
                    break;

                    default:
                        //// // printl( "No eligible tags" )
                    break;
                }
            }
        }

        // MISSION SPECIFIC
        // Use explanation
        local cur_wave = GetPropInt( mvm_logic_entity, "m_nMannVsMachineWaveCount" )
        CursesScript.RunMessageBroadcast( cur_wave, player )
    }  

    // Event Hooks

    // mandatory events
    function OnGameEvent_recalculate_holidays( _ ) 
    { 
        if ( GetRoundState() == 3 ) 
        {
            Cleanup()
        } 
    }
    function OnGameEvent_mvm_wave_complete( _ ) 
    {
        Cleanup() 
    }

    function OnGameEvent_player_team ( params )
    {
        local player = GetPlayerFromUserID( params.userid )
        local old_team = params.oldteam

        if ( player.GetTeam() == TEAM_SPECTATOR )
        {
            PlayerCleanupEx( player )
        }
    }

    function OnGameEvent_mvm_begin_wave ( params ) 
    {

    }

    function OnGameEvent_post_inventory_application ( params )
    {
        local player = GetPlayerFromUserID( params.userid )
        player.ValidateScriptScope()
        local scope = player.GetScriptScope()

    }

    function OnGameEvent_player_spawn ( params )
    {
        local player = GetPlayerFromUserID( params.userid )

        player.ValidateScriptScope()
        local scope = player.GetScriptScope()
        local bInWave = GetPropBool( mvm_logic_entity, "m_bMannVsMachineBetweenWaves" ) 

        if ( iTrueWave == iFinalWave && bInWave == false && bSendToHell == true )
        {
            local arena_center = Vector( -2214, -6035, -122 )
            local nav = FindNavAreaAlongRay( arena_center + Vector(0, 0, 800), arena_center + Vector(0, 0, -1200), null )
            local center = nav.GetCenter()
            local nav_list = {}
            local nav_spots = []
            GetNavAreasInRadius( center, 2500, nav_list )

            foreach (i, nav in nav_list)
            {
                local min_size = 2000

                if ( nav.GetSizeX() * nav.GetSizeY() < min_size )
                {
                    continue
                }

                nav_spots.append(nav)
            }

            local rand_spot = nav_spots[RandomInt( 0, nav_spots.len() - 1) ]
            local vec_to_teleport = rand_spot.FindRandomSpot()
            local tele_script = format
            (
                "self.Teleport( true, Vector(%f, %f, %f), false, QAngle(), false, Vector() )", 
                vec_to_teleport.x, 
                vec_to_teleport.y, 
                vec_to_teleport.z
            )
            EntFireByHandle( player, "RunScriptCode", tele_script, SINGLE_TICK, player, player )
            EntFireByHandle( player, "RunScriptCode", "CursesScript.UnstuckEntity( self )", SINGLE_TICK * 2, player, player )
            player.AddCondEx( TF_COND_INVULNERABLE_CARD_EFFECT, 2, null )
            EntFireByHandle( player, "RunScriptCode", "CursesScript.AddHudElement( self, `meter_TimeLeft`, `LivesTime Error` )", SINGLE_TICK, null, null )
            EntFireByHandle( player, "RunScriptCode", "CursesScript.LookAt( self, CursesScript.hSuperBossPlayer.EyePosition(), 9999, 9999 )", SINGLE_TICK, null, null )

        }

        EntFireByHandle( player, "RunScriptCode", "CursesScript.PlayerSpawnEx( self )", 0, player, player )
    }

    function OnGameEvent_player_hurt ( params )
    {
        local victim = GetPlayerFromUserID( params.userid )
        local attacker = GetPlayerFromUserID( params.attacker )
        local damage = params.damageamount 

        if ( victim.IsPlayer() && attacker.GetTeam() != victim.GetTeam() )
        {
            victim.ValidateScriptScope()
            local scope = victim.GetScriptScope()

            if ( "SufferingCurse5" in scope )
            {
                // For every 5 damage, add 1 fear
                local damage_per_fear = 5
                local fear_to_add = ( damage / damage_per_fear ) * mult
                // //// // printl( "fear to add: "+fear_to_add )
                scope.flFear += fear_to_add
            }
        }
    }

    function OnGameEvent_player_death ( params )
    {
        local victim = GetPlayerFromUserID( params.userid )
        local attacker = GetPlayerFromUserID( params.attacker ) 
        local weapon = EntIndexToHScript( params.inflictor_entindex )
        local assister = GetPlayerFromUserID( params.assister ) 

        local crumpkins = []

        for (local tf_ammo_pack; tf_ammo_pack = FindByClassname(tf_ammo_pack, "tf_ammo_pack");)
        {
            if (GetPropInt(tf_ammo_pack, "m_nModelIndex") == CRUMPKIN_INDEX)
            {
                crumpkins.push(tf_ammo_pack);
            }
        }

        foreach(crumpkin in crumpkins)
        {
            printl(crumpkin)
            crumpkin.Kill();
        }

        if ( attacker == null )
        {
            return
        }

        if ( !( params.death_flags & 32 ) )
        {
            PlayerCleanupEx( victim )
            
            if ( victim.GetTeam() == TF_TEAM_RED )
            {
                for (local i = 0; i < 4; i++)
                {
                    CursesScript.ResetCursedPlayer( victim, i, CURSE_RESPAWN )
                }
            }

            // giant player dies
            if ( victim.IsMiniBoss() && !victim.IsBotOfType( TF_BOT_TYPE ) )
            {
                //// // printl( "punish giant for dying" )
                ExtendRespawnTime( victim, 30, true )
            }
        }

        if ( !( params.death_flags & 32 ) && victim.IsBotOfType( TF_BOT_TYPE ) )
        {
            local bot_tags = {}
            victim.GetAllBotTags( bot_tags )

            // MEGA HACK: Detect if a bot has the cursed particle as a child
            for ( local child = victim.FirstMoveChild(); child != null; child = child.NextMovePeer() )
            {
                if ( child.GetClassname() == "info_particle_system" && child.GetScriptScope().stCurParticle == "utaunt_arcane_purple_sparkle" )
                {
                    SendCurseToKiller( attacker, victim )

                    if ( assister && assister.GetPlayerClass() == TF_CLASS_MEDIC )
                    {
                        SendCurseToKiller( assister, victim )
                    }
                    
                    return
                }
            }

            foreach ( i, tag in bot_tags )
            {
                if ( startswith( tag, "bot_cursed" ) )
                {
                    SendCurseToKiller( attacker, victim )

                    if ( assister )
                    {
                        SendCurseToKiller( assister, victim )
                    }
                    break
                }
            }
        }
    }

    function OnGameEvent_player_disconnect ( params )
    {
        local player = GetPlayerFromUserID( params.userid )
        if ( !player )
        {
            return
        }
        player.ValidateScriptScope()
        local playerscope = player.GetScriptScope()

        player.AcceptInput( "RunScriptCode", "CursesScript.PlayerCleanupEx( self )", player, player )
    }

    function OnGameEvent_player_say ( params ) 
    {
        local text = params.text 
        local player = GetPlayerFromUserID( params.userid )
        local scope = player.GetScriptScope()
        local cur_wave = GetPropInt( mvm_logic_entity, "m_nMannVsMachineWaveCount" )

        if 
        ( 
            text == "#music_optout" 
            && iCurWave == iLastWave 
            && !("bNoMusic" in player.GetScriptScope() ) 
        )
        {
            // // printl("player wants to opt out: "+player)
            player.ValidateScriptScope()
            scope.bNoMusic <- 1
            CursesScript.MiscPrintFunc( player, "27F54D", "You have set Custom music to not play." )
            params.text = "%"
        }
        else if 
        (
            iCurWave == iLastWave 
            && text == "#music_optout" 
            && "bNoMusic" in player.GetScriptScope()
        )
        {
            CursesScript.MiscPrintFunc( player, "27F54D", "Music will not play for you." )
        }

        if 
        ( 
            text == "#music_optin" 
            && "bNoMusic" in player.GetScriptScope() 
            && iCurWave == iLastWave 
        )
        {
            // printl("player wants to opt in: "+player)
            player.ValidateScriptScope()
            delete scope["bNoMusic"]
            CursesScript.MiscPrintFunc( player, "27F54D", "You have set Custom music to play." )
            params.text = ""
        }
        else if 
        (
            iCurWave == iLastWave 
            && text == "#music_optin" 
            && !("bNoMusic" in player.GetScriptScope() )
        )
        {
            CursesScript.MiscPrintFunc( player, "27F54D", "Music will play for you." )
        }
    }

    function OnGameEvent_player_builtobject ( params )
    {
        local player = GetPlayerFromUserID( params.userid )

        local sentry = EntIndexToHScript(params.index )

        local cur_loadout = CursesScript.GetLoadout( player )

        local is_gunslinger = false

        foreach ( i, item in cur_loadout )
        {

            if ( GetPropInt(item, STRING_NETPROP_ITEMDEF) == 142 ) // gunslinger
            {
                is_gunslinger = true
                break
            }
        }

        if ( is_gunslinger == true && player.GetTeam() == TF_TEAM_BLUE)
        {
            sentry.SetModelScale(0.8, 0)
            SetPropBool(sentry, "m_bMiniBuilding", true)
            SetPropInt(sentry, "m_iHighestUpgradeLevel", 1)
            SetPropInt(sentry, "m_iObjectMode", 1)
            sentry.KeyValueFromString("defaultupgrade", "0")
            sentry.SetHealth(100)
            sentry.SetMaxHealth(100)
        }
    }

    function OnGameEvent_teamplay_broadcast_audio ( params )
    {
        local tank_audio = "Announcer.MVM_Tank_Alert_Spawn"
        local tank_audio_alt = "Announcer.MVM_Tank_Alert_Multiple"
        if ( params.sound == tank_audio || params.sound == tank_audio_alt )
        {
            for (local tank; tank = FindByClassname(tank, "tank_boss");)
            {
                tank.ValidateScriptScope()
                local scope = tank.GetScriptScope()
                local health = tank.GetMaxHealth()
                local s = health.tostring()
                local d = s.len()
                if (d > 6) s = s.slice(0, d - 6) + "m"
                else if (d > 3) s = s.slice(0, d - 3) + "k"

                if ( !( "iTankHealthMsg" in scope) )
                {
                    scope.iTankHealthMsg <- 1

                    MiscPrintFunc( null, "99CCFF", "Tank deployed with "+s+ " ("+health+") health!" ) 
                }
            }
        }
    }

    // Runs whenever anything takes damage
    function OnScriptHook_OnTakeDamage ( 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
        };

        ////// // printl( "entity: "+entity )
        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 CursesScript.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", "CursesScript.PlayerSpawnEx( self )", -1, player, player )
    }
}

GetAllAreas( CursesScript.MapNavAreas )
__CollectGameEventCallbacks( CursesScript ) // the OnGameEvent/OnScriptHook outputs need this