#include common_scripts\utility;
#include maps\mp\_utility;
#include maps\mp\gametypes\_hud_util;

init()
{
    // setup default vars
    level.eventBus              = spawnstruct();
    level.eventBus.inVar        = "sv_iw4madmin_in";
    level.eventBus.outVar       = "sv_iw4madmin_out";
    level.eventBus.failKey      = "fail";
    level.eventBus.timeoutKey   = "timeout";
    level.eventBus.timeout      = 30;
    level.eventBus.gamename     = getDvar( "gamename" ); // We want to do a few small detail different on IW5 compared to IW4, nothing where 2 files would make sense.
    
    level.clientDataKey = "clientData";

    level.eventTypes                            = spawnstruct();
    level.eventTypes.localClientEvent           = "client_event";
    level.eventTypes.clientDataReceived         = "ClientDataReceived";
    level.eventTypes.clientDataRequested        = "ClientDataRequested";
    level.eventTypes.setClientDataRequested     = "SetClientDataRequested";
    level.eventTypes.setClientDataCompleted     = "SetClientDataCompleted";
    level.eventTypes.executeCommandRequested    = "ExecuteCommandRequested";
    
    level.iw4adminIntegrationDebug = false;
                                                                             
    SetDvarIfUninitialized( level.eventBus.inVar, "" );
    SetDvarIfUninitialized( level.eventBus.outVar, "" );
    SetDvarIfUninitialized( "sv_iw4madmin_integration_enabled", 1 );
    SetDvarIfUninitialized( "sv_iw4madmin_integration_debug", 0 );
    
    // map the event type to the handler
    level.eventCallbacks = [];
    level.eventCallbacks[level.eventTypes.clientDataReceived]       = ::OnClientDataReceived;
    level.eventCallbacks[level.eventTypes.executeCommandRequested]  = ::OnExecuteCommand; 
    level.eventCallbacks[level.eventTypes.setClientDataCompleted]   = ::OnSetClientDataCompleted;

    level.clientCommandCallbacks = [];
    level.clientCommandRusAsTarget = [];
    
    if ( GetDvarInt( "sv_iw4madmin_integration_enabled" ) != 1 )
    {
        return;
    }
    
    InitializeGameMethods();
    RegisterClientCommands();
    
    // start long running tasks
    level thread MonitorClientEvents();
    level thread MonitorBus();
    level thread OnPlayerConnect();
}



//////////////////////////////////
// Client Methods
//////////////////////////////////

OnPlayerConnect()
{
    level endon ( "disconnect" );
    
    for ( ;; )
    {
        level waittill( "connected", player );
        
        level.iw4adminIntegrationDebug = GetDvarInt( "sv_iw4madmin_integration_debug" );
        
        if ( isDefined(player.pers["isBot"]) && player.pers["isBot"] ) 
        {
            // we don't want to track bots
            continue;    
        }
        
        if ( !isDefined( player.pers[level.clientDataKey] ) )
        {
            player.pers[level.clientDataKey] = spawnstruct();
        }
        
        player thread OnPlayerSpawned();
        player thread OnPlayerJoinedTeam();
        player thread OnPlayerJoinedSpectators();
        player thread PlayerTrackingOnInterval();
        
        // only toggle if it's enabled
        if ( IsDefined( level.nightModeEnabled ) && level.nightModeEnabled )
        {
            player ToggleNightMode();
        }
    }
}

OnPlayerSpawned()
{
    self endon( "disconnect" );

    for ( ;; )
    {
        self waittill( "spawned_player" );
        self PlayerConnectEvents();
    }
}

OnPlayerDisconnect()
{
    level endon ( "disconnect" );

    for ( ;; )
    {
        self waittill( "disconnect" );
        self SaveTrackingMetrics();
    }
}

OnPlayerJoinedTeam()
{
    self endon( "disconnect" );

    for( ;; )
    {
        self waittill( "joined_team" );
        // join spec and join team occur at the same moment - out of order logging would be problematic
        wait( 0.25 ); 
        LogPrint( GenerateJoinTeamString( false ) );
    }
}

OnPlayerJoinedSpectators()
{
    self endon( "disconnect" );

    for( ;; )
    {
        self waittill( "joined_spectators" );
        LogPrint( GenerateJoinTeamString( true ) );
    }
}

OnGameEnded() 
{
    level endon ( "disconnect" );
    
    for ( ;; )
    {
        level waittill( "game_ended" );
        // note: you can run data code here but it's possible for 
        // data to get truncated, so we will try a timer based approach for now
    }
}

DisplayWelcomeData()
{
    self endon( "disconnect" );

    clientData = self.pers[level.clientDataKey];
    
    if ( clientData.permissionLevel == "User" || clientData.permissionLevel == "Flagged" ) 
    {
        return;
    } 
    
    self IPrintLnBold( "Welcome, your level is ^5" + clientData.permissionLevel );
    wait( 2.0 );
    self IPrintLnBold( "You were last seen ^5" + clientData.lastConnection );
}

PlayerConnectEvents() 
{
    self endon( "disconnect" );

    if ( IsDefined( self.isHidden ) && self.isHidden )
    {
        self HideImpl();
    }
    
    clientData = self.pers[level.clientDataKey];
    
    // this gives IW4MAdmin some time to register the player before making the request;
    // although probably not necessary some users might have a slow database or poll rate
    wait ( 2 );

    if ( isDefined( clientData.state ) && clientData.state == "complete" ) 
    {
        return;
    }
    
    self RequestClientBasicData();
    // example of requesting meta from IW4MAdmin
    // self RequestClientMeta( "LastServerPlayed" );
}

PlayerTrackingOnInterval() 
{
    self endon( "disconnect" );

    for ( ;; )
    {
        wait ( 120 );
        if ( IsAlive( self ) )
        {
            self SaveTrackingMetrics();
        }
    }
}

MonitorClientEvents()
{
    level endon( "disconnect" );
    self endon( "disconnect" );
    
    for ( ;; ) 
    {
        level waittill( level.eventTypes.localClientEvent, client );
 
        if ( level.iw4adminIntegrationDebug == 1 )
        {
            IPrintLn( "Processing Event " + client.event.type + "-" + client.event.subtype );
        }
        
        eventHandler = level.eventCallbacks[client.event.type];

        if ( isDefined( eventHandler ) )
        {
            client [[eventHandler]]( client.event );
        }
        
        client.eventData = [];
    }
}

//////////////////////////////////
// Helper Methods
//////////////////////////////////

RegisterClientCommands() 
{
    AddClientCommand( "GiveWeapon",     true,  ::GiveWeaponImpl );
    AddClientCommand( "TakeWeapons",    true,  ::TakeWeaponsImpl );
    AddClientCommand( "SwitchTeams",    true,  ::TeamSwitchImpl );
    AddClientCommand( "Hide",           false, ::HideImpl );
    AddClientCommand( "Unhide",         false, ::UnhideImpl );
    AddClientCommand( "Alert",          true,  ::AlertImpl );
    AddClientCommand( "Goto",           false, ::GotoImpl );
    AddClientCommand( "Kill",           true,  ::KillImpl );
    AddClientCommand( "SetSpectator",   true,  ::SetSpectatorImpl );
    AddClientCommand( "NightMode",      false, ::NightModeImpl ); //This really should be a level command
    AddClientCommand( "LockControls",   true,  ::LockControlsImpl ); 
    AddClientCommand( "UnlockControls", true,  ::UnlockControlsImpl );
    AddClientCommand( "PlayerToMe",     true,  ::PlayerToMeImpl );
    AddClientCommand( "NoClip",         false, ::NoClipImpl );
    AddClientCommand( "NoClipOff",      false, ::NoClipOffImpl );
}

InitializeGameMethods() 
{
    level.overrideMethods = [];
    level.overrideMethods["god"] = ::_god;
    level.overrideMethods["noclip"] = ::UnsupportedFunc;
    
    if ( isDefined( ::God ) )
    {
        level.overrideMethods["god"] = ::God;
    }
    
    if ( isDefined( ::NoClip ) )
    {
        level.overrideMethods["noclip"] = ::NoClip;
    }

    if ( level.eventBus.gamename == "IW5" ) 
    { //PlutoIW5 only allows Godmode and NoClip if cheats are on..
        level.overrideMethods["god"] = ::IW5_God;
        level.overrideMethods["noclip"] = ::IW5_NoClip;
    }
}

UnsupportedFunc()
{ 
    self IPrintLnBold( "Function is not supported!" );
}

RequestClientMeta( metaKey )
{
    getClientMetaEvent = BuildEventRequest( true, level.eventTypes.clientDataRequested, "Meta", self, metaKey );
    level thread QueueEvent( getClientMetaEvent, level.eventTypes.clientDataRequested, self );
}

RequestClientBasicData()
{
    getClientDataEvent = BuildEventRequest( true, level.eventTypes.clientDataRequested, "None", self, "" );
    level thread QueueEvent( getClientDataEvent, level.eventTypes.clientDataRequested, self );
}

IncrementClientMeta( metaKey, incrementValue, clientId )
{
    SetClientMeta( metaKey, incrementValue, clientId, "increment" );
}

DecrementClientMeta( metaKey, decrementValue, clientId )
{
    SetClientMeta( metaKey, decrementValue, clientId, "decrement" );
}

GenerateJoinTeamString( isSpectator ) 
{
    team = self.team;

    if ( IsDefined( self.joining_team ) )
    {
        team = self.joining_team;
    }
    else
    {
        if ( isSpectator || !IsDefined( team ) ) 
        {
            team = "spectator";
        }
    }

    guid = self GetXuid();

    if ( guid == "0" )
    {
        guid = self.guid;
    }

    if ( !IsDefined( guid ) || guid == "0" )
    {
        guid = "undefined";
    }

    return "JT;" + guid + ";" + self getEntityNumber() + ";" + team + ";" + self.name + "\n";
}

SetClientMeta( metaKey, metaValue, clientId, direction )
{
    data = "key=" + metaKey + "|value=" + metaValue;
    clientNumber = -1;

    if ( IsDefined ( clientId ) )
    {
        data = data + "|clientId=" + clientId;
        clientNumber = -1;
    }

    if ( IsDefined( direction ) )
    {
        data = data + "|direction=" + direction;
    }

    if ( IsPlayer( self ) )
    {
        clientNumber = self getEntityNumber();
    }

    setClientMetaEvent = BuildEventRequest( true, level.eventTypes.setClientDataRequested, "Meta", clientNumber, data );
    level thread QueueEvent( setClientMetaEvent, level.eventTypes.setClientDataRequested, self );
}

SaveTrackingMetrics()
{
    if ( level.iw4adminIntegrationDebug == 1 )
    {
        IPrintLn( "Saving tracking metrics for " + self.persistentClientId );
    }
    
    if ( !IsDefined( self.lastShotCount ) )
    {
        self.lastShotCount = 0;
    }

    currentShotCount = self getPlayerStat( "mostshotsfired" );
    change = currentShotCount - self.lastShotCount;
    self.lastShotCount = currentShotCount;

    if ( level.iw4adminIntegrationDebug == 1 )
    {
        IPrintLn( "Total Shots Fired increased by " + change );
    }

    if ( !IsDefined( change ) )
    {
        change = 0;
    }
    
    if ( change == 0 )
    {
        return;
    }

    IncrementClientMeta( "TotalShotsFired", change, self.persistentClientId );

}

BuildEventRequest( responseExpected, eventType, eventSubtype, entOrId, data ) 
{
    if ( !isDefined( data ) )
    {
        data = "";
    }
    
    if ( !isDefined( eventSubtype ) )
    {
        eventSubtype = "None";
    }

    if ( IsPlayer( entOrId ) )
    {
        entOrId = entOrId getEntityNumber();
    }
    
    request = "0";
    
    if ( responseExpected ) 
    {
        request = "1";
    }
  
    request = request + ";" + eventType + ";" + eventSubtype + ";" + entOrId + ";" + data;
    return request;
}

MonitorBus()
{
    level endon( "game_ended" );
    
    for( ;; )
    {
        wait ( 0.1 );
        
        // check to see if IW4MAdmin is ready to receive more data
        if ( getDvar( level.eventBus.inVar ) == "" ) 
        {
            level notify( "bus_ready" );
        }
        
        eventString = getDvar( level.eventBus.outVar );
        
        if ( eventString == "" ) 
        {
            continue;
        }
        if ( level.iw4adminIntegrationDebug == 1 )
        {
            IPrintLn( "-> " + eventString );
        }
        
        NotifyClientEvent( strtok( eventString, ";" ) );
        
        SetDvar( level.eventBus.outVar, "" );
    }
}

QueueEvent( request, eventType, notifyEntity ) 
{
    level endon( "disconnect" );

    start = GetTime();
    maxWait = level.eventBus.timeout * 1000; // 30 seconds
    timedOut = "";
   
    while ( GetDvar( level.eventBus.inVar ) != "" && ( GetTime() - start ) < maxWait )
    {
        level waittill_notify_or_timeout( "bus_ready", 1 );
        
        if ( GetDvar( level.eventBus.inVar ) != "" )
        {
            if ( level.iw4adminIntegrationDebug == 1 )
            {
                IPrintLn( "A request is already in progress..." );
            }
            timedOut = "set";
            continue;
        }
        
        timedOut = "unset";
    }
   
    if ( timedOut == "set")
    {
        if ( level.iw4adminIntegrationDebug == 1 )
        {
            IPrintLn( "Timed out waiting for response..." );
        }
        
        if ( IsDefined( notifyEntity) )
        {
            notifyEntity NotifyClientEventTimeout( eventType );
        }

        return;
    }
    
    if ( level.iw4adminIntegrationDebug == 1 )
    {
        IPrintLn("<- " + request);
    }
    
    SetDvar( level.eventBus.inVar, request );
}

ParseDataString( data ) 
{
    dataParts = strtok( data, "|" );
    dict = [];
    
    counter = 0;
    foreach ( part in dataParts )
    {
        splitPart = strtok( part, "=" );
        key = splitPart[0];
        value = splitPart[1];
        dict[key] = value;
        dict[counter] = key;
        counter++;
    }
    
    return dict;
}

NotifyClientEventTimeout( eventType ) 
{
    // todo: make this actual eventing
    if ( eventType == level.eventTypes.clientDataRequested )
    {
        self.pers["clientData"].state = level.eventBus.timeoutKey;
    }
}

NotifyClientEvent( eventInfo )
{
    origin = getPlayerFromClientNum( int( eventInfo[3] ) );
    target = getPlayerFromClientNum( int( eventInfo[4] ) );
    
    event = spawnstruct();
    event.type = eventInfo[1];
    event.subtype = eventInfo[2];
    event.data = eventInfo[5];
    event.origin = origin;
    event.target = target;
    
    if ( level.iw4adminIntegrationDebug == 1 )
    {
        IPrintLn( "NotifyClientEvent->" + event.data );
        if( int( eventInfo[3] ) != -1 && !isDefined( origin ) )
        {
            IPrintLn( "origin is null but the slot id is " + int( eventInfo[3] ) );
        }
        if( int( eventInfo[4] ) != -1 && !isDefined( target ) )
        {
            IPrintLn( "target is null but the slot id is " + int( eventInfo[4] ) );
        }
    }

    if( isDefined( target ) )
    {
        client = event.target;
    }
    else if( isDefined( origin ) )
    {
        client = event.origin;
    }
    else
    {
        if ( level.iw4adminIntegrationDebug == 1 )
        {
            IPrintLn( "Neither origin or target are set but we are a Client Event, aborting" );
        }
        
        return;
    }
    client.event = event;

    level notify( level.eventTypes.localClientEvent, client );
}

GetPlayerFromClientNum( clientNum )
{
    if ( clientNum < 0 )
        return undefined;
    
    for ( i = 0; i < level.players.size; i++ )
    {
        if ( level.players[i] getEntityNumber() == clientNum )
        {
            return level.players[i];
        }
    }
    return undefined;
}

AddClientCommand( commandName, shouldRunAsTarget, callback, shouldOverwrite )
{
    if ( isDefined( level.clientCommandCallbacks[commandName] ) && isDefined( shouldOverwrite ) && !shouldOverwrite ) {

        return;
    }
    level.clientCommandCallbacks[commandName] = callback;
    level.clientCommandRusAsTarget[commandName] = shouldRunAsTarget == true; //might speed up things later in case someone gives us a string or number instead of a boolean
}



//////////////////////////////////
// Event Handlers
/////////////////////////////////

OnClientDataReceived( event )
{
    event.data = ParseDataString( event.data );
    clientData = self.pers[level.clientDataKey];

    if ( event.subtype == "Fail" ) 
    {
        if ( level.iw4adminIntegrationDebug == 1 )
        {
            IPrintLn( "Received fail response" );
        }
        clientData.state = level.eventBus.failKey;
        return;
    }

    if ( event.subtype == "Meta" )
    {
        if ( !isDefined( clientData.meta ) )
        {
            clientData.meta = [];
        }
        
        metaKey = event.data[0];
        clientData.meta[metaKey] = event.data[metaKey];
        
        return;
    }
    
    clientData.permissionLevel = event.data["level"];
    clientData.clientId = event.data["clientId"];
    clientData.lastConnection = event.data["lastConnection"];
    clientData.state = "complete";
    self.persistentClientId = event.data["clientId"];

    self thread DisplayWelcomeData();
}

OnExecuteCommand( event ) 
{
    data = ParseDataString( event.data );
    response = "";

    command = level.clientCommandCallbacks[event.subtype];
    runAsTarget = level.clientCommandRusAsTarget[event.subtype];
    executionContextEntity = event.origin;
    if ( runAsTarget ) {
        executionContextEntity = event.target;
    }
    if ( isDefined( command ) ) {
        response = executionContextEntity [[command]]( event, data );
    }
    else if ( level.iw4adminIntegrationDebug == 1 )
    {
        IPrintLn( "Unkown Client command->" +  event.subtype);
    }
    
    // send back the response to the origin, but only if they're not the target
    if ( response != "" && IsPlayer( event.origin ) && event.origin != event.target ) 
    {
        event.origin IPrintLnBold( response );
    }
}

OnSetClientDataCompleted( event )
{
    // IW4MAdmin let us know it persisted (success or fail)
    if ( level.iw4adminIntegrationDebug == 1 )
    {
        IPrintLn( "Set Client Data -> subtype = " + event.subType + " status = " + event.data["status"] );
    }
}

//////////////////////////////////
// Command Implementations
/////////////////////////////////

GiveWeaponImpl( event, data )
{
    if ( !IsAlive( self ) )
    {
        return self.name + "^7 is not alive";
    }
    
    self IPrintLnBold( "You have been given a new weapon" );
    self GiveWeapon( data["weaponName"] );
    self SwitchToWeapon( data["weaponName"] );
    
    return self.name + "^7 has been given ^5" + data["weaponName"]; 
}

TakeWeaponsImpl()
{
    if ( !IsAlive( self ) )
    {
        return self.name + "^7 is not alive";
    }
    
    self TakeAllWeapons();
    self IPrintLnBold( "All your weapons have been taken" );
    
    return "Took weapons from " + self.name;
}

TeamSwitchImpl()
{
    if ( !IsAlive( self ) )
    {
        return self + "^7 is not alive";
    }
    
    team = level.allies;
    
    if ( self.team == "allies" ) 
    {
        team = level.axis;
    }
    
    self IPrintLnBold( "You are being team switched" );
    wait( 2 );
    self [[team]]();

    return self.name + "^7 switched to " + self.team;
}

LockControlsImpl()
{
    if ( !IsAlive( self ) )
    {
        return self.name + "^7 is not alive";
    }
    

    self freezeControls( true );
    self call [[level.overrideMethods["god"]]]( true );
    self Hide();

    info = [];
    info[ "alertType" ] = "Alert!";
    info[ "message" ] = "You have been frozen!";
    
    self AlertImpl( undefined, info );

    return self.name + "\'s controls are locked";
}

UnlockControlsImpl()
{
    if ( !IsAlive( self ) )
    {
        return self.name + "^7 is not alive";
    }
    
    self freezeControls( false );
    self call [[level.overrideMethods["god"]]]( false );
    self Show();

    return self.name + "\'s controls are unlocked";
}

NoClipImpl()
{
    if ( !IsAlive( self ) )
    {
        self IPrintLnBold( "You are not alive" );
        // Due to bug when sometimes disabling noclip game thinks you're dead
        // removing the return and allowing them to go back into noclip is probably better.
        //return; 
    }

    self SetClientDvar( "sv_cheats", 1 );
    self SetClientDvar( "cg_thirdperson", 1 );
    self SetClientDvar( "sv_cheats", 0 );

    self call [[level.overrideMethods["god"]]]( true );
    self call [[level.overrideMethods["noclip"]]]( true );
    self Hide();

    self.isNoClipped = true;

    self IPrintLnBold( "NoClip enabled" );
}

NoClipOffImpl()
{
    if ( !IsDefined( self.isNoClipped ) || !self.isNoClipped ) 
    {
        self IPrintLnBold( "You are not no-clipped" );
        return;
    }

    self SetClientDvar( "sv_cheats", 1 );
    self SetClientDvar( "cg_thirdperson", 0 );
    self SetClientDvar( "sv_cheats", 0 );

    self call [[level.overrideMethods["god"]]]( false );
    self call [[level.overrideMethods["noclip"]]]( false );
    self Show();

    self IPrintLnBold( "NoClip disabled" );

    if ( !IsAlive( self ) && self.isNoClipped )
    {
        // Sometimes you will bug exiting noclip where the game thinks you're dead
        // but you're not. You will retain godmode but be able to run around and kill people. 
        // So, it's important to let the user know.
        self IPrintLnBold( "^1You are bugged! ^4Swap team." );
    }

    self.isNoClipped = false;
}

HideImpl()
{
    if ( !IsAlive( self ) )
    {
        self IPrintLnBold( "You are not alive" );
        return;
    }

    self SetClientDvar( "sv_cheats", 1 );
    self SetClientDvar( "cg_thirdperson", 1 );
    self SetClientDvar( "sv_cheats", 0 );

    if ( !IsDefined( self.savedHealth ) || self.health < 1000 )
    {
        self.savedHealth = self.health;
        self.savedMaxHealth = self.maxhealth;
    }

    self call [[level.overrideMethods["god"]]]( true );
    self Hide();

    self.isHidden = true;

    self IPrintLnBold( "You are now ^5hidden ^7from other players" );
}

UnhideImpl()
{
    if ( !IsAlive( self ) )
    {
        self IPrintLnBold( "You are not alive" );
        return;
    }
    
    if ( !IsDefined( self.isHidden ) || !self.isHidden ) 
    {
        self IPrintLnBold( "You are not hidden" );
        return;
    }

    self SetClientDvar( "sv_cheats", 1 );
    self SetClientDvar( "cg_thirdperson", 0 );
    self SetClientDvar( "sv_cheats", 0 );

    self call [[level.overrideMethods["god"]]]( false );
    self Show();

    self.isHidden = false;
    
    self IPrintLnBold( "You are now ^5visible ^7to other players" );
}

AlertImpl( event, data )
{
    if ( level.eventBus.gamename == "IW4" ) {
        self thread maps\mp\gametypes\_hud_message::oldNotifyMessage( data["alertType"], data["message"], "compass_waypoint_target", ( 1, 0, 0 ), "ui_mp_nukebomb_timer", 7.5 );
    }
    if ( level.eventBus.gamename == "IW5" ) { //IW5's notification are a bit different...
        self thread maps\mp\gametypes\_hud_message::oldNotifyMessage( data["alertType"], data["message"], undefined, ( 1, 0, 0 ), "ui_mp_nukebomb_timer", 7.5 );
    }
    return "Sent alert to " + self.name; 
}

GotoImpl( event, data )
{
    if ( IsDefined( event.target ) )
    {
        return self GotoPlayerImpl( event.target );
    }
    else
    {
        return self GotoCoordImpl( data );
    }
}

GotoCoordImpl( data )
{
    if ( !IsAlive( self ) )
    {
        self IPrintLnBold( "You are not alive" );
        return;
    }

    position = ( int(data["x"]), int(data["y"]), int(data["z"]) );
    self SetOrigin( position );
    self IPrintLnBold( "Moved to " + "("+ position[0] + "," + position[1] + "," + position[2] + ")" );
}

GotoPlayerImpl( target )
{
    if ( !IsAlive( target ) )
    {
        self IPrintLnBold( target.name + " is not alive" );
        return;
    }

    self SetOrigin( target GetOrigin() );
    self IPrintLnBold( "Moved to " + target.name );
}

PlayerToMeImpl( event )
{
    if ( !IsAlive( self ) )
    {
        return self.name + " is not alive";
    }

    self SetOrigin( event.origin GetOrigin() );
    return "Moved here " + self.name;    
}


KillImpl()
{
    if ( !IsAlive( self ) )
    {
        return self.name + " is not alive";
    }

    self Suicide();
    self IPrintLnBold( "You were killed by " + self.name );

    return "You killed " + self.name;
}

NightModeImpl()
{
    if ( !IsDefined ( level.nightModeEnabled ) )
    {
        level.nightModeEnabled = true;
    }
    else
    {
       level.nightModeEnabled = !level.nightModeEnabled;
    }

    message = "^5NightMode ^7is disabled";

    if ( level.nightModeEnabled )
    {
        message = "^5NightMode ^7is enabled";
    }

    IPrintLnBold( message );

    foreach( player in level.players )
    {
        player ToggleNightMode();
    }
}

ToggleNightMode()
{
    colorMap = 1;
    fxDraw = 1;

    if ( IsDefined( level.nightModeEnabled ) && level.nightModeEnabled )
    {
        colorMap = 0;
        fxDraw = 0;
    }

    self SetClientDvar( "sv_cheats", 1 );
    self SetClientDvar( "r_colorMap", colorMap );
    self SetClientDvar( "fx_draw", fxDraw );
    self SetClientDvar( "sv_cheats", 0 );
}

SetSpectatorImpl()
{
    if ( self.pers["team"] == "spectator" ) 
    {
        return self.name + " is already spectating";
    }
    
    self [[level.spectator]]();
    self IPrintLnBold( "You have been moved to spectator" );
    
    return self.name + " has been moved to spectator";
}

//////////////////////////////////
// Function Overrides
//////////////////////////////////

_god( isEnabled ) 
{
    if ( isEnabled == true ) 
    {
        if ( !IsDefined( self.savedHealth ) || self.health < 1000  )
        {
            self.savedHealth = self.health;
            self.savedMaxHealth = self.maxhealth;
        }
        
        self.maxhealth = 99999;
        self.health = 99999;
    }
    
    else 
    {
        if ( !IsDefined( self.savedHealth ) || !IsDefined( self.savedMaxHealth ) )
        {
            return;
        }
        
        self.health = self.savedHealth;
        self.maxhealth = self.savedMaxHealth;
    }
}


IW5_God()
{
    SetDvar( "sv_cheats", 1 );
    self God();
    SetDvar( "sv_cheats", 0 );
}

IW5_NoClip()
{
    SetDvar( "sv_cheats", 1 );
    self NoClip();
    SetDvar( "sv_cheats", 0 );
}