if ( !("STEP_HEIGHT" in ROOT) )
    IncludeScript( "motherland_constants.nut" )

// this messes with engi hints
Convars.SetValue( "sig_etc_entity_limit_manager_convert_server_entity", 0 )

local resource = FindByClassname( null, "tf_objective_resource" )

::__motherland_exp_popname <- GetPropString( resource, "m_iszMvMPopfileName" )

local gateb = FindByName( null, "gate2_door_trigger" )
// lazy hack to revert gatebots without all the popfile boilerplate
EntityOutputs.AddOutput( gateb, "OnCapTeam2", "player", "RunScriptCode", "if ( self.IsBotOfType( TF_BOT_TYPE ) && self.HasBotAttribute( AGGRESSIVE|IGNORE_FLAG ) && !self.HasBotTag( `tag_alwayspush` ) ) self.RemoveBotAttribute( AGGRESSIVE|IGNORE_FLAG )", 0, -1 )

// Temporary until we start utilizing the flank bomb path more
EntFire( "hologramrelay_mainbomb", "Trigger" )

// Wavebar netprops
local netprop_classnames = "m_iszMannVsMachineWaveClassNames"
local netprop_flags      = "m_nMannVsMachineWaveClassFlags"
local netprop_counts     = "m_nMannVsMachineWaveClassCounts"
local netprop_active     = "m_bMannVsMachineWaveClassActive"
local netprop_enemycount = "m_nMannVsMachineWaveEnemyCount"
local size_array = GetPropArraySize( resource, netprop_counts )

local altbomb = FindByName( null, "gate2_bomb2" )
altbomb.ValidateScriptScope()
local altbomb_scope = altbomb.GetScriptScope()

altbomb_scope.InputEnable <- function() {

    _Motherland_Expert.Utils.FakeBomb()
    return true
}
altbomb_scope.InputDisable <- function() {

    for ( local child = self.FirstMoveChild(); child != null; child = child.NextMovePeer() )
        if ( child.GetClassname() == "item_teamflag" )
            EntFireByHandle( child, "Kill", "", -1, null, null )

    return true
}
altbomb_scope.Inputenable  <- altbomb_scope.InputEnable
altbomb_scope.Inputdisable <- altbomb_scope.InputDisable

EntityOutputs.AddOutput(altbomb, "OnPickup", "gate2_bomb2", "RunScriptCode", @"

    self.SetTeam( TF_TEAM_PVE_DEFENDERS )

    for ( local child = self.FirstMoveChild(); child != null; child = child.NextMovePeer() )
        if ( child.GetClassname() == `item_teamflag` )
            EntFireByHandle( child, `Kill`, ``, -1, null, null )

", 0, -1)

EntityOutputs.AddOutput(altbomb, "OnDrop", "gate2_bomb2", "RunScriptCode", @"

    self.SetTeam( TF_TEAM_PVE_INVADERS )
    _Motherland_Expert.Utils.FakeBomb()

", 0, -1)

EntityOutputs.AddOutput(altbomb, "OnReturn", "gate2_bomb2", "RunScriptCode", @"

    self.SetTeam( TF_TEAM_PVE_INVADERS )
    _Motherland_Expert.Utils.FakeBomb()

", 0, -1)

if ( "_Motherland_Expert" in getroottable() )
    delete ::_Motherland_Expert

::_Motherland_Expert <- class {

    Utils = {

        IgnoreTable = {
            "self"         		   : null
            "__vname"      		   : null
            "__vrefs"      		   : null
        }

        function PlayerCleanup( player ) {

            local scope = player.GetScriptScope()
            player.ClearAllBotTags()

            AddThinkToEnt( player, null )

            local scope_keys = scope.keys()

            if ( scope_keys.len() > IgnoreTable.len() )
                foreach ( k in scope_keys )
                    if ( !( k in IgnoreTable ) )
                        delete scope[ k ]
        }

        function FakeBomb() {

            for ( local child = altbomb.FirstMoveChild(); child != null; child = child.NextMovePeer() )
                if ( child.GetClassname() == "item_teamflag" )
                    EntFireByHandle( child, "Kill", "", -1, null, null )

            local fakebomb = CreateByClassname( "item_teamflag" )

            fakebomb.SetTeam( TF_TEAM_PVE_DEFENDERS )

            fakebomb.KeyValueFromString( "targetname", "gate2_bomb2_fake" )
            fakebomb.KeyValueFromInt( "trail_effect", 0 )
            fakebomb.KeyValueFromInt( "ReturnTime", 0 )
            fakebomb.KeyValueFromInt( "GameType", 1 )
            fakebomb.AcceptInput( "ShowTimer", "0", null, null )

            fakebomb.SetAbsOrigin( altbomb.GetOrigin() )
            fakebomb.AcceptInput( "SetParent", "gate2_bomb2", null, null )
            fakebomb.DisableDraw()
        }

        function KillNoTeleHints() {

            for ( local hint; hint = FindByClassname( hint, "bot_hint*" ); )
                if ( endswith( hint.GetName(), "_notele" ) )
                    EntFireByHandle( hint, "Kill", "", -1, null, null )
        }

        function SplitOnce( s, sep = null ) {

            if ( sep == null ) return [s, null]

            local pos = s.find( sep )
            local result_left = pos == 0 ? null : s.slice( 0, pos )
            local result_right = pos == s.len() - 1 ? null : s.slice( pos + 1 )

            return [result_left, result_right]
        }

        function ScriptEntFireSafe( target, code, delay = -1, activator = null, caller = null, allow_dead = false ) {

            local entfirefunc = typeof target == "string" ? DoEntFire : EntFireByHandle

            entfirefunc( target, "RunScriptCode", format( @"

                if ( self && self.IsValid() ) {

                    if ( self.IsPlayer() && !self.IsAlive() && !%d ) {

                        return
                    }

                    // code passed to ScriptEntFireSafe
                    %s

                    return
                }

            ", allow_dead.tointeger(), code ), delay, activator, caller )

            PurgeGameString( code )
        }

        function PurgeGameString( str ) {

            local dummy = CreateByClassname( "info_target" )
            SetPropString( dummy, "m_iName", str )
            SetPropBool( dummy, "m_bForcePurgeFixedupStrings", true )
            dummy.Kill()
        }

        function PressButton( player, button, duration = -1 ) {

            SetPropInt( player, "m_afButtonForced", GetPropInt( player, "m_afButtonForced" ) | button )
            SetPropInt( player, "m_nButtons", GetPropInt( player, "m_nButtons" ) | button )

            if ( duration != -1 )
                _Motherland_Expert.Utils.ScriptEntFireSafe( player, format( "_Motherland_Expert.Utils.ReleaseButton( self, %d )", button ), duration )
        }

        function ReleaseButton( player, button ) {

            SetPropInt( player, "m_afButtonForced", GetPropInt( player, "m_afButtonForced" ) & ~button )
            SetPropInt( player, "m_nButtons", GetPropInt( player, "m_nButtons" ) & ~button )
        }

        function GetItemInSlot( player, slot ) {

            local item
            for ( local i = 0; i < SLOT_COUNT; i++ ) {
                local wep = GetPropEntityArray( player, STRING_NETPROP_MYWEAPONS, i )
                if ( wep == null || wep.GetSlot() != slot ) continue

                item = wep
                break
            }
            return item
        }

        function ParseTagArguments( bot, tag ) {

            local newtags = {}

            if ( !tag.find( "{" ) ) return {}

            local separator = tag.find( "{" ) ? "{" : "|"

            local splittag = SplitOnce( tag, separator )

            if ( separator == "{" )  {

                // Allow inputting strings using backticks.
                local arr = split( splittag[1], "`" )
                local end = arr.len() - 1
                if ( end > 1 ) {
                    local str = ""
                    foreach ( i, sub in arr ) {

                        if ( i == end ) {
                            str += sub
                            break
                        }
                        str += sub + "\""
                    }
                    compilestring( format( @"::__motherlandtagstemp <- { %s", str ) )()
                } else {
                    compilestring( format( @"::__motherlandtagstemp <- { %s", splittag[1] ) )()
                }
                foreach( k, v in ::__motherlandtagstemp ) newtags[k] <- v

                delete ::__motherlandtagstemp
            }

            return newtags
        }

        function EvaluateTags( bot ) {

            local bot_tags = {}

            bot.GetAllBotTags( bot_tags )

            // bot has no tags
            if ( !bot_tags.len() ) return

            foreach( i, tag in bot_tags ) {

                local func = split( tag, "{" )[0]
                local args = _Motherland_Expert.Utils.ParseTagArguments( bot, tag )

                if ( func in _Motherland_Expert.Tags )
                    _Motherland_Expert.Tags[func].call( bot.GetScriptScope(), bot, args )
            }
        }
    }

    Wavebar = {

        // add/remove icon from the wavebar, does not preserve ordering
        function SetWaveIcon( name, flags, count, change_max_enemy_count = true ) {

            for ( local a = 0; a < 2; a++ ) {

                local suffix = a == 0 ? "" : "2"

                for ( local i = 0; i < size_array; i++ ) {

                    local name_slot = GetPropStringArray( resource, format( "%s%s", netprop_classnames, suffix ), i )
                    local count_slot = GetPropIntArray( resource, format( "%s%s", netprop_counts, suffix ), i )
                    local flags_slot = GetPropIntArray( resource, format( "%s%s", netprop_flags, suffix ), i )
                    local enemy_count = GetPropInt( resource, netprop_enemycount )

                    if ( count == null ) count = count_slot
                    if ( flags == null ) flags = flags_slot

                    if ( name_slot == "" && count > 0 ) {

                        SetPropStringArray( resource, format( "%s%s", netprop_classnames, suffix ), name, i )
                        SetPropIntArray( resource, format( "%s%s", netprop_counts, suffix ), count, i )
                        SetPropIntArray( resource, format( "%s%s", netprop_flags, suffix ), flags, i )

                        if ( change_max_enemy_count && flags & MVM_CLASS_FLAG_NORMAL ) {

                            SetPropInt( resource, "m_nMannVsMachineWaveEnemyCount", enemy_count + count )
                        }
                        return
                    }

                    if ( name_slot == name && ( flags == MVM_CLASS_FLAG_NONE || flags_slot == flags ) ) {

                        local pre_count = count_slot
                        SetPropIntArray( resource, format( "%s%s", netprop_counts, suffix ), count, i )

                        if ( change_max_enemy_count && flags & MVM_CLASS_FLAG_NORMAL ) {

                            SetPropInt( resource, netprop_enemycount, enemy_count + count - pre_count )
                        }
                        if ( count <= 0 ) {

                            SetPropStringArray( resource, format( "%s%s", netprop_classnames, suffix ), "", i )
                            SetPropIntArray( resource, format( "%s%s", netprop_flags, suffix ), 0, i )
                            SetPropBoolArray( resource, format( "%s%s", netprop_active, suffix ), false, i )
                        }
                        return
                    }
                }
            }
        }

        // preserve wavebar ordering
        function SetWaveIconSlot( name, slot = null, flags = null, count = null, index_override = -1, incrementer = false, change_max_enemy_count = true ) {

            for ( local a = 0; a < 2; a++ ) {

                local suffix = a == 0 ? "" : "2"

                local indices = {}

                for ( local i = 0; i < size_array; i++ ) {

                    local name_slot = GetPropStringArray( resource, format( "%s%s", netprop_classnames, suffix ), i )
                    local flags_slot = GetPropIntArray( resource, format( "%s%s", netprop_flags, suffix ), i )
                    local count_slot = GetPropIntArray( resource, format( "%s%s", netprop_counts, suffix ), i )
                    local enemy_count = GetPropInt( resource, netprop_enemycount )

                    if ( count == null ) count = count_slot
                    if ( flags == null ) flags = flags_slot

                    if ( index_override != -1 ) {

                        indices[i] <- [name_slot, flags_slot, count_slot, false]
                        if ( flags_slot & MVM_CLASS_FLAG_MISSION )
                            indices[i][3] = true
                    }

                    if ( name_slot == name && ( flags == MVM_CLASS_FLAG_NONE || flags_slot == flags ) ) {

                        local pre_count = count_slot

                        if ( count == 0 ) {

                            SetPropStringArray( resource, format( "%s%s", netprop_classnames, suffix ), "", i )
                            SetPropIntArray( resource, format( "%s%s", netprop_flags, suffix ), 0, i )
                            SetPropBoolArray( resource, format( "%s%s", netprop_active, suffix ), false, i )

                            if ( change_max_enemy_count )
                                SetPropInt( resource, netprop_enemycount, enemy_count - pre_count )

                            return
                        }

                        else if ( incrementer ) {

                            count = count_slot + count
                            SetPropIntArray( resource, format( "%s%s", netprop_counts, suffix ), count, i )

                            if ( count_slot <= 0 ) {
                                SetPropStringArray( resource, format( "%s%s", netprop_classnames, suffix ), "", i )
                                SetPropIntArray( resource, format( "%s%s", netprop_flags, suffix ), 0, i )
                                SetPropBoolArray( resource, format( "%s%s", netprop_active, suffix ), false, i )
                            }
                            return
                        }

                        if ( index_override != -1 ) {

                            SetPropStringArray( resource, format( "%s%s", netprop_classnames, suffix ), indices[i][0], i )
                            SetPropIntArray( resource, format( "%s%s", netprop_flags, suffix ), indices[i][1], i )
                            SetPropIntArray( resource, format( "%s%s", netprop_counts, suffix ), indices[i][2], i )
                            SetPropBoolArray( resource, format( "%s%s", netprop_active, suffix ), indices[i][3], i )

                            SetPropIntArray( resource, format( "%s%s", netprop_counts, suffix ), 0, i )
                            SetPropStringArray( resource, format( "%s%s", netprop_classnames, suffix ), "", i )
                            SetPropIntArray( resource, format( "%s%s", netprop_flags, suffix ), 0, i )
                        }

                        SetPropIntArray( resource, format( "%s%s", netprop_counts, suffix ), count, index_override )
                        SetPropStringArray( resource, format( "%s%s", netprop_classnames, suffix ), slot, index_override )
                        SetPropIntArray( resource, format( "%s%s", netprop_flags, suffix ), flags, index_override )

                        if ( change_max_enemy_count && flags & MVM_CLASS_FLAG_NORMAL )
                            SetPropInt( resource, netprop_enemycount, GetPropInt( resource, netprop_enemycount ) + count - pre_count )
                        return
                    }
                }
            }
        }

        function GetWaveIconSlot( name, flags ) {

            local size_array = GetPropArraySize( resource, netprop_counts )

            for ( local a = 0; a < 2; a++ ) {

                local suffix = a == 0 ? "" : "2"

                for ( local i = 0; i < size_array; i++ ) {

                    local name_slot = GetPropStringArray( resource, format( "%s%s", netprop_classnames, suffix ), i )
                    local flags_slot = GetPropIntArray( resource, format( "%s%s", netprop_flags, suffix ), i )

                    if ( name_slot == name && flags_slot == flags ) {
                        return i
                    }
                }
            }
            return -1
        }

        function SetWaveIconFlags( name, flags ) {

            local size_array = GetPropArraySize( resource, netprop_counts )
            for ( local a = 0; a < 2; a++ ) {

                local suffix = a == 0 ? "" : "2"

                for ( local i = 0; i < size_array; i++ ) {

                    local name_slot = GetPropStringArray( resource, format( "%s%s", netprop_classnames, suffix ), i )

                    if ( name_slot == name )
                        SetPropIntArray( resource, format( "%s%s", netprop_flags, suffix ), flags, i )
                }
            }
        }

        // for mission/limited support
        function SetWaveIconActive( name, flags, active ) {

            for ( local a = 0; a < 2; a++ ) {

                local suffix = a == 0 ? "" : "2"

                for ( local i = 0; i < size_array; i++ ) {

                    local name_slot = GetPropStringArray( resource, format( "%s%s", netprop_classnames, suffix ), i )

                    if ( name_slot == name && ( flags == MVM_CLASS_FLAG_NONE || flags == GetPropIntArray( resource, format( "%s%s", netprop_flags, suffix ), i ) ) ) {

                        SetPropBoolArray( resource, format( "%s%s", netprop_active, suffix ), active, i )
                        return
                    }
                }
            }
        }

        function GetWaveIcon( name, flags ) {

            for ( local a = 0; a < 2; a++ ) {

                local suffix = a == 0 ? "" : "2"

                for ( local i = 0; i < size_array * 2; i++ ) {

                    if ( GetPropStringArray( resource, format( "%s%s", netprop_classnames, suffix ), i ) == name && ( flags == MVM_CLASS_FLAG_NONE || GetPropIntArray( resource, format( "%s%s", netprop_flags, suffix ), i ) == flags ) ) {

                        return GetPropIntArray( resource, format( "%s%s", netprop_counts, suffix ), i )
                    }
                }
            }
            return 0
        }

        function GetWaveIconFlags( name ) {

            for ( local a = 0; a < 2; a++ ) {

                local suffix = a == 0 ? "" : "2"

                for ( local i = 0; i < size_array; i++ ) {

                    local name_slot = GetPropStringArray( resource, format( "%s%s", netprop_classnames, suffix ), i )

                    if ( name_slot == name )
                        return GetPropIntArray( resource, format( "%s%s", netprop_flags, suffix ), i )
                }
            }
            return 0
        }

        function GetAllWaveIconFlags( name ) {

            local size_array = GetPropArraySize( resource, netprop_counts )
            local flags = []
            for ( local a = 0; a < 2; a++ ) {

                local suffix = a == 0 ? "" : "2"

                for ( local i = 0; i < size_array; i++ ) {

                    local name_slot = GetPropStringArray( resource, format( "%s%s", netprop_classnames, suffix ), i )

                    if ( name_slot == name )
                        flags.append( GetPropIntArray( resource, format( "%s%s", netprop_flags, suffix ), i ) )
                }
            }
            return flags
        }

        function IncrementWaveIcon( name, flags, count = 1, change_max_enemy_count = true ) {

            SetWaveIcon( name, flags, GetWaveIcon( name, flags ) + count, change_max_enemy_count )
        }

        function RemoveWaveIcon( name, flags ) {

            SetWaveIcon( name, flags, 0 )
        }
    }

    Tags = {

        function motherland_suicidecounter( bot, args ) {

            local interval  	= "interval" in args ? args.interval : 1.0
            local duration 		= "duration" in args ? args.duration : 0.0

            local inflictor 	= "inflictor" in args ? args.inflictor : bot
            local attacker 		= "attacker" in args ? args.attacker : bot
            local weapon 		= "weapon" in args ? args.weapon : null
            local force 	    = "force" in args ? args.force : Vector()
            local position 		= "position" in args ? args.position : bot.GetOrigin()
            local amount 		= "amount" in args ? args.amount : 1.0
            local damage_type 	= "damage_type" in args ? args.damage_type : DMG_PREVENT_PHYSICS_FORCE
            local damage_custom = "damage_custom" in args ? args.damage_custom : TF_DMG_CUSTOM_NONE

            local cooldowntime = 0.0

            bot.GetScriptScope().BotThinkTable.SuicideCounterThink <- function() {

                if ( cooldowntime > Time() ) return

                bot.TakeDamageCustom( inflictor, attacker, weapon, force, position, amount, damage_type, damage_custom )

                cooldowntime = Time() + interval
            }
            if ( duration )
                _Motherland_Expert.Utils.ScriptEntFireSafe( bot, "delete self.GetScriptScope().BotThinkTable.SuicideCounterThink", duration )
        }

        function motherland_revertgatebot( bot, args ) {
            if ( GetPropBool( gateb, "m_bLocked" ) && bot.HasBotAttribute( AGGRESSIVE|IGNORE_FLAG ) && !bot.HasBotTag( "tag_alwayspush" ) )
                bot.RemoveBotAttribute( AGGRESSIVE|IGNORE_FLAG )
        }

        function motherland_altfire( bot, args ) {

            bot.PressAltFireButton( "duration" in args ? args.duration.tofloat() : INT_MAX )
        }

        function motherland_alwaysglow( bot, args ) {

            SetPropBool( bot, "m_bGlowEnabled", true )
        } 

        function motherland_fireweapon( bot, args ) {

            local button 		=  args.button.tointeger()
            local cooldown 		=  "cooldown" in args      ? args.cooldown.tointeger() : 0
            local duration 		=  "duration" in args      ? args.duration.tointeger() : 0
            local delay		 	=  "delay" in args         ? args.delay.tointeger() : 0
            local repeats 		=  "repeats" in args       ? args.repeats.tointeger() : INT_MAX
            local ifhealthbelow =  "ifhealthbelow" in args ? args.ifhealthbelow.tointeger() : INT_MAX

            local maxrepeats = 0
            local cooldowntime = Time() + cooldown
            local delaytime = Time() + delay

            bot.GetScriptScope().BotThinkTable.FireWeaponThink <- function() {

                if ( ( maxrepeats ) >= repeats ) {

                    delete bot.GetScriptScope().BotThinkTable.FireWeaponThink
                    return
                }

                if (
                    Time() < delaytime
                    || ( Time() < cooldowntime )
                    || bot.GetHealth() > ifhealthbelow
                    || bot.HasBotAttribute( SUPPRESS_FIRE )
                )
                    return

                maxrepeats++

                _Motherland_Expert.Utils.ScriptEntFireSafe( bot, format( "_Motherland_Expert.PressButton( self, %d, %d )", button, duration ), delay )
                cooldowntime = Time() + cooldown
            }
        }

        function motherland_minisentry( bot, args ) {

            bot.GetScriptScope().BuiltObjectTable.MiniSentry <- function( params ) {

                local _bot = GetPlayerFromUserID( params.userid )

                if ( _bot != bot ) return

                local sentry = EntIndexToHScript( params.index )

                if ( params.object == OBJ_SENTRYGUN ) {

                    local nearest_hint = FindByClassnameNearest( "bot_hint_sentrygun", sentry.GetOrigin(), 16 )

                    if ( !nearest_hint ) return

                    sentry.ValidateScriptScope()

                    sentry.GetScriptScope().CheckBuiltThink <- function() {

                        if ( GetPropBool( sentry, "m_bBuilding" ) ) return

                        // create a minisentry

                        local minisentry = SpawnEntityFromTable( "obj_sentrygun", {

                            origin     	   = sentry.GetOrigin()
                            angles     	   = sentry.GetAbsAngles()
                            defaultupgrade = 0
                            TeamNum    	   = sentry.GetTeam()
                            vscripts   	   = "motherland_ents"
                            spawnflags 	   = 64
                        })

                        EntFireByHandle( minisentry, "RunScriptCode", "self.SetSkin( 3 )", -1, null, null ) // this is supposed to be set by the motherland_ents but for some reason it's not working
                        minisentry.AcceptInput( "SetBuilder", "!activator", _bot, _bot )
                        nearest_hint.SetOwner( minisentry )
                        sentry.Kill()
                    }

                    AddThinkToEnt( sentry, "CheckBuiltThink" )
                }
            }
        }

        // TODO: handle hauling/moving to new hints better for sentry override
        // Engi-bots will try to haul their sentry to the next hint and this confuses them a lot
        function motherland_dispenseroverride( bot, args ) {

            local alwaysfire = bot.HasBotAttribute( ALWAYS_FIRE_WEAPON )

            //force deploy dispenser when leaving spawn and kill it immediately
            if ( !alwaysfire && args.type == OBJ_SENTRYGUN ) bot.PressFireButton( INT_MAX )

            bot.GetScriptScope().BotThinkTable.DispenserOverride <- function() {

                //start forcing primary attack when near hint
                local hint = FindByClassnameWithin( null, "bot_hint*", bot.GetOrigin(), 16 )
                if ( hint && !alwaysfire ) bot.PressFireButton( 0.0 )
            }

            bot.GetScriptScope().BuiltObjectTable.DispenserOverride <- function( params ) {

                local _bot = GetPlayerFromUserID( params.userid )

                if ( _bot != bot ) return

                local obj = params.object

                //dispenser built, stop force firing
                if ( !alwaysfire ) _bot.PressFireButton( 0.0 )

                if ( obj == args.type ) {

                    if ( obj == OBJ_SENTRYGUN )
                        _bot.AddCustomAttribute( "engy sentry radius increased", FLT_SMALL, -1 )

                    _bot.AddCustomAttribute( "upgrade rate decrease", 8, -1 )
                    local building = EntIndexToHScript( params.index )

                    if ( obj != OBJ_DISPENSER ) {

                        building.ValidateScriptScope()
                        building.GetScriptScope().CheckBuiltThink <- function() {

                            if ( GetPropBool( building, "m_bBuilding" ) ) return

                            EntFireByHandle( building, "Disable", "", -1, null, null )
                            delete building.GetScriptScope().CheckBuiltThink
                        }
                        AddThinkToEnt( building, "CheckBuiltThink" )
                    }

                    //kill the first alwaysfire built dispenser when leaving spawn
                    local hint = FindByClassnameWithin( null, "bot_hint*", building.GetOrigin(), 16 )

                    if ( !hint ) {
                        building.Kill()
                        return
                    }

                    //hide the building
                    building.SetModelScale( 0.01, 0.0 )
                    SetPropInt( building, "m_nRenderMode", kRenderTransColor )
                    SetPropInt( building, "m_clrRender", 0 )
                    building.SetHealth( INT_MAX )
                    building.SetSolid( SOLID_NONE )

                    SetPropString( building, "m_iName", format( "building%d", building.entindex() ) )

                    //create a dispenser
                    local dispenser = CreateByClassname( "obj_dispenser" )

                    SetPropEntity( dispenser, "m_hBuilder", _bot )

                    SetPropString( dispenser, "m_iName", format( "dispenser%d", dispenser.entindex() ) )

                    dispenser.SetTeam( _bot.GetTeam() )
                    dispenser.SetSkin( _bot.GetSkin() )

                    dispenser.DispatchSpawn()

                    //post-spawn stuff

                    // SetPropInt( dispenser, "m_iHighestUpgradeLevel", 2 ) //doesn't work

                    local builder = _Motherland_Expert.Utils.GetItemInSlot( _bot, SLOT_PDA )

                    local builtobj = GetPropEntity( builder, "m_hObjectBeingBuilt" )
                    SetPropInt( builder, "m_iObjectType", 0 )
                    SetPropInt( builder, "m_iBuildState", 2 )
                    // if ( builtobj && builtobj.GetClassname() != "obj_dispenser" ) builtobj.Kill()
                    SetPropEntity( builder, "m_hObjectBeingBuilt", dispenser ) //makes dispenser a null reference

                    _bot.Weapon_Switch( builder )
                    builder.PrimaryAttack()

                    //m_hObjectBeingBuilt messes with our dispenser reference, do radius check to grab it again
                    for ( local d; d = FindByClassnameWithin( d, "obj_dispenser", building.GetOrigin(), 128 ); )
                        if ( GetPropEntity( d, "m_hBuilder" ) == _bot )
                            dispenser = d

                    dispenser.SetLocalOrigin( building.GetLocalOrigin() )
                    dispenser.SetLocalAngles( building.GetLocalAngles() )

                    AddOutput( dispenser, "OnDestroyed", building.GetName(), "Kill", "", -1, -1 ) //kill it to avoid showing up in killfeed
                    AddOutput( building, "OnDestroyed", dispenser.GetName(), "Destroy", "", -1, -1 ) //always destroy the dispenser
                }
            }
        }
    }

    Events = {

        function OnGameEvent_recalculate_holidays( params ) {

            if ( GetRoundState() != GR_STATE_PREROUND )
                return

            for ( local i = 0; i <= MAX_CLIENTS; i++ ) {

                local player = PlayerInstanceFromIndex( i )

                if ( !player || !player.IsBotOfType( TF_BOT_TYPE ) )
                    continue

                _Motherland_Expert.Utils.PlayerCleanup( player )
            }

            if ( GetPropString( resource, "m_iszMvMPopfileName" ) == __motherland_exp_popname )
                return

            delete ::__motherland_exp_popname
            delete ::_Motherland_Expert
        }

        function OnGameEvent_post_inventory_application( params ) {

            local player = GetPlayerFromUserID( params.userid )

            if ( player.IsBotOfType( TF_BOT_TYPE ) )
                return

            _Motherland_Expert.Utils.ScriptEntFireSafe( player, "self.AddCustomAttribute( `cannot pick up intelligence`, 1.0, -1 )", 0.1, null, null )
        }

        function OnGameEvent_player_spawn( params ) {

            local player = GetPlayerFromUserID( params.userid )

            if ( !player.IsBotOfType( TF_BOT_TYPE ) ) {
                return
            }

            local bot = player

            local scope = bot.GetScriptScope()

            if ( !scope ) {

                bot.ValidateScriptScope()
                scope = bot.GetScriptScope()
            }

            if ( !( "BotThinkTable" in scope ) )
                scope.BotThinkTable <- {}

            if ( !( "BuiltObjectTable" in scope ) )
                scope.BuiltObjectTable <- {}

            scope.BotThinks <- function() {

                foreach ( name, func in scope.BotThinkTable )
                    func.call( scope )
                return -1
            }

            AddThinkToEnt( bot, "BotThinks" )

            _Motherland_Expert.Utils.ScriptEntFireSafe( bot, "_Motherland_Expert.Utils.EvaluateTags( self )", 0.015, null, null )

            if ( bot.GetPlayerClass() == TF_CLASS_MEDIC ) {

                _Motherland_Expert.Utils.ScriptEntFireSafe( bot, @"

                    for ( local child = self.FirstMoveChild(); child != null; child = child.NextMovePeer() ) {

                        if ( GetPropInt( child, `m_AttributeManager.m_Item.m_iItemDefinitionIndex` ) == 998 ) {

                            EntFireByHandle( GetPropEntity( child, `m_hExtraWearable` ), `Kill`, ``, -1, null, null )
                            break
                        }
                    }
                ", 0.1, null, null )
            }
        }

        function OnGameEvent_player_death( params ) {

            local bot = GetPlayerFromUserID( params.userid )

            if ( !bot.IsBotOfType( TF_BOT_TYPE ) )
                return

            _Motherland_Expert.Utils.PlayerCleanup( bot )
        }

        function OnGameEvent_player_builtobject( params ) {

            local player = GetPlayerFromUserID( params.userid )

            if ( "BuiltObjectTable" in player.GetScriptScope() )
                foreach ( name, func in player.GetScriptScope().BuiltObjectTable )
                    func.call( player.GetScriptScope(), params )
        }
    }
}

// class member ents must be created after class declaration
// otherwise this will be a null instance
_Motherland_Expert.TriggerHurt <- CreateByClassname( "trigger_hurt" )

__CollectGameEventCallbacks( _Motherland_Expert.Events )