/*
* adminmanager.cpp
*
* Copyright (C) 2001-2005 Atomic Blue (info@planeshift.it, http://www.atomicblue.org)
* http://www.atomicblue.org )
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation (version 2 of the License)
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
*
*/

#include <psconfig.h>
#include <ctype.h>
#include "iutil/object.h"
#include <iengine/campos.h>
#include <iengine/region.h>
#include <propclass/linmove.h>
#include <iutil/stringarray.h>

#include "adminmanager.h"
#include "spawnmanager.h"
#include "chatmanager.h"
#include "marriagemanager.h"
#include "gem.h"
#include "clients.h"
#include "psserver.h"
#include "playergroup.h"
#include "util/psdatabase.h"
#include "net/msghandler.h"
#include "entitymanager.h"
#include "bulkobjects/psnpcdialog.h"
#include "bulkobjects/psnpcloader.h"
#include "engine/drmessage.h"
#include "util/log.h"
#include "util/serverconsole.h"
#include "util/strutil.h"
#include "util/eventmanager.h"
#include "psserver.h"
#include "usermanager.h"
#include "globals.h"
#include "iserver/idal.h"
#include "iengine/mesh.h"
#include "iengine/movable.h"

#include "bulkobjects/pscharacterloader.h"
#include "bulkobjects/psitem.h"
#include "bulkobjects/psraceinfo.h"
#include "bulkobjects/psmerchantinfo.h"
#include "bulkobjects/psactionlocationinfo.h"
#include "cachemanager.h"
#include "combatmanager.h"
#include "netmanager.h"
#include "npcmanager.h"
#include "psserverchar.h"
#include "guildmanager.h"
#include "weathermanager.h"
#include "commandmanager.h"
#include "authentserver.h"

// Show only items up to this ID when using the item spawn GUI (hide randomly generated items with IDs set above this)
#define SPAWN_ITEM_ID_CEILING 10000

//-----------------------------------------------------------------------------

AdminManager::AdminManager()
{
    clients = psserver->GetNetManager()->GetConnections();

    psserver->GetEventManager()->Subscribe(this,MSGTYPE_ADMIN,REQUIRE_READY_CLIENT);
    psserver->GetEventManager()->Subscribe(this,MSGTYPE_ADMINCMD,REQUIRE_READY_CLIENT);
    psserver->GetEventManager()->Subscribe(this,MSGTYPE_PETITION_REQUEST,REQUIRE_READY_CLIENT);
    psserver->GetEventManager()->Subscribe(this,MSGTYPE_GMGUI,REQUIRE_READY_CLIENT);
    psserver->GetEventManager()->Subscribe(this,MSGTYPE_GMSPAWNITEMS,REQUIRE_READY_CLIENT);
    psserver->GetEventManager()->Subscribe(this,MSGTYPE_GMSPAWNITEM,REQUIRE_READY_CLIENT);

    // this makes sure that the player dictionary exists on start up.
    npcdlg = new psNPCDialog(NULL);
    npcdlg->Initialize( db );
}


AdminManager::~AdminManager()
{
    psserver->GetEventManager()->Unsubscribe(this,MSGTYPE_ADMIN);
    psserver->GetEventManager()->Unsubscribe(this,MSGTYPE_ADMINCMD);
    psserver->GetEventManager()->Unsubscribe(this,MSGTYPE_PETITION_REQUEST);
    psserver->GetEventManager()->Unsubscribe(this,MSGTYPE_GMGUI);
    psserver->GetEventManager()->Unsubscribe(this,MSGTYPE_GMSPAWNITEMS);
    psserver->GetEventManager()->Unsubscribe(this,MSGTYPE_GMSPAWNITEM);

    delete npcdlg;
}

void AdminManager::AdminItemRequestTypes(uint32_t clientnum)
{
    csString outBuffer;
    outBuffer.Append("<UL>");
/* TODO - this is currently not used, which is good since it has a infinite loop
    unsigned int i=0;
    psItemStats *itemstats;
    char itemBuffer[256];
    while ((itemstats=CacheManager::GetSingleton().GetBasicItemStatsByIndex(i))!=NULL)
    {
        csString escpxml = EscpXML(itemstats->GetName());
        sprintf(itemBuffer,  "<ITEM NAME=\"%s\" ID=\"%u\" />",
            escpxml.GetData(),
            itemstats->GetUID() );

        outBuffer.Append(itemBuffer);
    }
*/
    outBuffer.Append("</UL>");

    psAdminMessage out( clientnum,ADMIN_ITEM_REQUEST_TYPES,outBuffer );
    out.SendMessage();
}


bool AdminManager::AdminItemCreate( psAdminMessage &msg )
{
    csRef<iDocumentSystem> xml = csPtr<iDocumentSystem>(new csTinyDocumentSystem);

    csRef<iDocument> doc = xml->CreateDocument();

    const char* error = doc->Parse(msg.data);
    if ( error )
    {
        Error2("Error in XML: %s", error );
        return false;
    }

    csRef<iDocumentNode> newitemNode = doc->GetRoot()->GetNode("NEWITEM");

    if (!newitemNode)
        return false;

    return CreateItem(newitemNode->GetAttributeValue("NAME"),
        newitemNode->GetAttributeValueAsFloat("X"),
        newitemNode->GetAttributeValueAsFloat("Y"),
        newitemNode->GetAttributeValueAsFloat("Z"),
        0.0f,newitemNode->GetAttributeValue("SECTOR"),
        newitemNode->GetAttributeValueAsInt("COUNT"),0,0);
}

void AdminManager::HandleMessage(MsgEntry *me,Client *client)
{
    switch ( me->GetType() )
    {
    case MSGTYPE_ADMIN:
        {
            psAdminMessage msg(me);
            HandleAdminMessage(me, msg,client);
            break;
        }
    case MSGTYPE_ADMINCMD:
        {
            psAdminCmdMessage msg(me);
            HandleAdminCmdMessage(me, msg,client);
            break;
        }
    case MSGTYPE_PETITION_REQUEST:
        {
            psPetitionRequestMessage msg(me);
            HandlePetitionMessage(me, msg,client);
            break;
        }
    case MSGTYPE_GMGUI:
        {
            psGMGuiMessage msg(me);
            HandleGMGuiMessage(me, msg,client);
            break;
        }
    case MSGTYPE_GMSPAWNITEMS:
        {
            psGMSpawnItems msg(me);
            SendSpawnItems(me,msg,client);
            break;
        }
    case MSGTYPE_GMSPAWNITEM:
        {
            psGMSpawnItem msg(me);
            SpawnItemInv(me,msg,client);
            break;
        }
    }
}

void AdminManager::HandleAdminMessage(MsgEntry *me, psAdminMessage & msg,Client *client)
{
    if (!msg.valid)
        return;

    switch ( msg.command )
    {
    case ADMIN_ITEM_REQUEST_TYPES:
        {
            AdminItemRequestTypes(me->clientnum);
            break;
        }

    case ADMIN_ITEM_CREATE:
        {
            if ( AdminItemCreate(msg) )
            {
                psserver->SendSystemInfo(me->clientnum,"Item Created");
            }
            break;
        }

        /* These three cases send a list of the races available with a
        particular gender
        */
    case ADMIN_MALE_RACES:
        {
            AdminRaceRequest(me->clientnum, "M",client);
            break;
        }
    case ADMIN_FEMALE_RACES:
        {
            AdminRaceRequest(me->clientnum, "F",client);
            break;
        }
    case ADMIN_NEUTRAL_RACES:
        {
            AdminRaceRequest(me->clientnum, "N",client);
            break;
        }

    case ADMIN_REQUEST_KNOWLEDGE_AREAS:
        {
            AdminRequestKnowledgeAreas(me->clientnum);
            break;
        }

        /// A new NPC is incomming
    case ADMIN_CREATE_NEW_NPC:
        {
            Debug1(LOG_ADMIN,me->clientnum,"Creating new NPC!");
            AdminCreateNewNPC(me->clientnum, msg.data);
            break;
        }

        /// A request for the triggers in a dialog area
    case ADMIN_REQUEST_TRIGGERS:
        {
            Debug1(LOG_ADMIN,me->clientnum,"Admin_request_triggers");
            AdminRequestTriggers( me->clientnum, msg.data);
            break;
        }

    case ADMIN_REQUEST_RESPONSES:
        {
            Debug1(LOG_ADMIN,me->clientnum,"Admin request responses");
            AdminRequestResponses(me->clientnum, msg.data);
            break;
        }
    case ADMIN_DIALOG_CHANGE:
        {
            Debug1(LOG_ADMIN,me->clientnum,"Admin dialog change");
            AdminDialogChange(me->clientnum, msg.data);
        }

    }
}

bool AdminManager::IsReseting(const csString& command)
{
    // Grab the first 8 characters after the command and see if we're resetting ourself
    // Everyone is allowed to reset themself via /deputize (should probably use this for other commands, too)
    return command.Slice(command.FindFirst(' ')+1,8) == "me reset";
}

void AdminManager::HandleAdminCmdMessage(MsgEntry *me, psAdminCmdMessage & msg,Client *client)
{
    if (!msg.valid)
    {
        psserver->SendSystemInfo(me->clientnum,"Invalid admin command");
        return;
    }

    // Security check
    if ( !IsReseting(msg.cmd) && !Valid(client->GetSecurityLevel(), msg.command,  me->clientnum) )
        return;       
    
    // Called functions should report all needed errors
    gemObject* targetobject = NULL;
    gemActor* targetactor = NULL;
    Client* targetclient = NULL;

    // Targeting for all commands
    if ( msg.player.Length() > 0 )
    {
        if (msg.player == "target")
        {
            targetobject = client->GetTargetObject();
            if (!targetobject)
            {
                psserver->SendSystemError(client->GetClientNum(), "You must have a target selected.");
                return;
            }
        }
        else if (msg.player.StartsWith("area:",true))
        {
            int range = atoi( msg.player.Slice(msg.player.FindLast(':')+1).GetDataSafe() );
            CommandArea(me,msg,client,range);
            return;  // Done.
        }
        else
        {
            if (msg.player == "me")
                targetclient = client; // Self
            else
                targetclient = FindPlayerClient(msg.player,0); // Other player?

            if (targetclient) // Found client
            {
                targetactor = targetclient->GetActor();
                targetobject = (gemObject*)targetactor;
            }
            else // Not found yet
                targetobject = FindObjectByString(msg.player); // Find by ID or name
        }
    }
    else // Only command specified; just get the target
    {
        targetobject = client->GetTargetObject();
    }

    if (targetobject && !targetactor) // Get the actor, client, and name for a found object
    {
        targetactor = targetobject->GetActorPtr();
        targetclient = targetobject->GetClient();
        msg.player = (targetclient)?targetclient->GetName():targetobject->GetName();
    }

    // Sector finding for all commands
    if ( msg.sector == "here" )
    {
        iSector* here = NULL;
        if (client->GetActor())
            here = client->GetActor()->GetSector();

        if (here)
            msg.sector = here->QueryObject()->GetName();
        else
            msg.sector.Clear();  // Bad sector
    }

    int targetID = 0;
    if (targetobject)
        targetID = targetobject->GetPlayerID();

    LogGMCommand( client->GetPlayerID(), targetID, msg.cmd );
    
    if (msg.command == "/npc")
    {
        CreateNPC(me,msg,client,targetactor);
    }
    else if (msg.command == "/killnpc")
    {
        KillNPC(me,msg,client);
    }
    else if (msg.command == "/item")
    {
        if (msg.item.Length())  // If arg, make simple
            CreateItem(me,msg,client);
        else  // If no arg, load up the spawn item GUI
            SendSpawnTypes(me,msg,client);
    }
    else if (msg.command == "/key")
    {
        ModifyKey(me,msg,client);
    }
    else if (msg.command == "/rain")
    {
        Rain(me,msg,client);
    }
    else if (msg.command == "/snow")
    {
        Snow(me,msg,client);
    }
    else if (msg.command == "/thunder")
    {
        Thunder(me,msg,client);
    }
    else if (msg.command == "/fog")
    {
        Fog(me,msg,client);
    }
    else if (msg.command == "/info")
    {
        GetInfo(me,msg,client,targetobject);
    }
    else if (msg.command == "/charlist")
    {
        GetSiblingChars(me,msg,client);
    }
    else if (msg.command == "/crystal")
    {
        CreateHuntLocation(me,msg,client);
    }
    else if (msg.command == "/mute")
    {
        MutePlayer(me,msg,client,targetclient);
    }
    else if (msg.command == "/unmute")
    {
        UnmutePlayer(me,msg,client,targetclient);
    }
    else if (msg.command == "/teleport")
    {
        Teleport(me,msg,client,targetobject);
    }
    else if (msg.command == "/slide")
    {
        Slide(me,msg,client,targetobject);
    }
    else if (msg.command == "/petition")
    {
        HandleAddPetition(me,msg,client);
    }
    else if (msg.command == "/warn")
    {
        WarnMessage(me,msg,client,targetclient);
    }
    else if (msg.command == "/kick")
    {
        KickPlayer(me,msg,client,targetclient);
    }
    else if (msg.command == "/death" )
    {
        Death(me,msg,client,targetactor);
    }
    else if (msg.command == "/impersonate" )
    {
        Impersonate(me, msg,client);
    }
    else if (msg.command == "/deputize")
    {
        TempSecurityLevel(me,msg,client,targetclient);
    }
    else if (msg.command == "/deletechar" )
    {
        DeleteCharacter( me, msg, client );
    }
    else if (msg.command == "/changename" )
    {
        ChangeName( me, msg,client );
    }
    else if (msg.command == "/changeguildname")
    {
        RenameGuild( me, msg,client );
    }
    else if (msg.command == "/banname" )
    {
        BanName( me, msg,client );
    }
    else if (msg.command == "/unbanname" )
    {
        UnBanName( me, msg,client );
    }
    else if (msg.command == "/ban" )
    {
        BanClient( me,msg,client );
    }
    else if (msg.command == "/unban" )
    {
        UnbanClient( me,msg,client );
    }
    else if (msg.command == "/awardexp" )
    {
        AwardExperience(me,msg,client,targetclient);
    }
    else if (msg.command == "/giveitem" )
    {
        TransferItem(me,msg,client,targetclient);
    }
    else if (msg.command == "/takeitem" )
    {
        TransferItem(me,msg,targetclient,client);
    }
    else if (msg.command == "/freeze")
    {
        FreezeClient(me,msg,client,targetclient);
    }
    else if (msg.command == "/thaw")
    {
        ThawClient(me, msg, client,targetclient);
    }
    else if (msg.command == "/inspect")
    {
        Inspect(me,msg,client,targetactor);
    }
    else if (msg.command == "/modify")
    {
        ModifyHuntLocation(me,msg,client,targetobject);
    }
    else if (msg.command == "/morph")
    {
        Morph(me,msg,client,targetclient);
    }
    else if (msg.command == "/setskill")
    {
        SetSkill(me,msg,client);
    }
    else if (msg.command == "/set")
    {
        SetAttrib(me,msg,client);
    }
    else if (msg.command == "/divorce")
    {
        Divorce(me, msg, client);
    }
    else if (msg.command == "/waypoint")
    {
        Waypoint(me, msg, client);
    }
}

gemObject* AdminManager::FindObjectByString(const csString& str)
{
    gemObject* found = NULL;
    GEMSupervisor *gem = psserver->GetEntityManager()->GetGEM();
    if (!gem) return NULL;

    if ( str.StartsWith("pid:",true) ) // Find by player ID
    {
        int pID = atoi( str.Slice(4).GetDataSafe() );
        if (pID)
            found = gem->FindPlayerEntity( pID );
    }
    else if ( str.StartsWith("eid:",true) ) // Find by entity ID
    {
        PS_ID eID = (PS_ID)atoi( str.Slice(4).GetDataSafe() );
        if (eID)
            found = gem->FindObject( eID );
    }
    else // Try finding an entity by name
        found = gem->FindObject(str);

    return found;
}

void AdminManager::CommandArea(MsgEntry *me, psAdminCmdMessage& msg, Client *client, int range)
{
    if (!Valid(client->GetSecurityLevel(), "command area", me->clientnum))
        return;

    if (range)
    {
        gemActor* self = client->GetActor();
        if (!self)
        {
            psserver->SendSystemError(client->GetClientNum(), "You do not exist...");
            return;
        }

        int mode = 0;
        if (msg.player.StartsWith("area:players",true))
            mode = 0;
        else if (msg.player.StartsWith("area:actors",true))
            mode = 1;
        else if (msg.player.StartsWith("area:items",true))
            mode = 2;
        else if (msg.player.StartsWith("area:entities",true))
            mode = 3;

        csVector3 pos;
        iSector* sector;
        self->GetPosition(pos,sector);
                
        GEMSupervisor* gem = psserver->GetEntityManager()->GetGEM();
        if (!gem)
            return;

        csRef<iCelEntityList> nearlist = gem->pl->FindNearbyEntities(sector,pos,range);
        size_t count = nearlist->GetCount();
               
        if (!count)
        {
            psserver->SendSystemError(client->GetClientNum(), "Nothing of specified type in range.");
            return;
        }

        for (size_t i=0; i<count; i++)
        {
            gemObject *nearobj = gem->GetObjectFromEntityList(nearlist,i);
            if (!nearobj)
                continue;

            switch (mode)
            {
                case 0:  // Target players
                {
                    if (nearobj->GetClientID())
                    {
                        msg.player.Format("pid:%d",nearobj->GetPlayerID());
                        break;
                    }
                    else
                        continue;
                }
                case 1:  // Target actors
                {
                    if (nearobj->GetPlayerID())
                    {
                        msg.player.Format("pid:%d",nearobj->GetPlayerID());
                        break;
                    }
                    else
                        continue;
                }
                case 2:  // Target items
                {
                    if (nearobj->GetItem())
                    {
                        msg.player.Format("eid:%u",nearobj->GetEntity()->GetID());
                        break;
                    }
                    else
                        continue;
                }
                case 3:  // Target everything
                {
                    msg.player.Format("eid:%u",nearobj->GetEntity()->GetID());
                    break;
                }
            }

            // Run this once for every target in range  (each one will be verified and logged seperately)
            HandleAdminCmdMessage(me,msg,client);
        }
    }
    else
    {
        psserver->SendSystemError(client->GetClientNum(), "You must specify a range.");
    }
}

void AdminManager::GetSiblingChars(MsgEntry* me,psAdminCmdMessage& msg,Client *client)
{
    if (!msg.player || msg.player.Length() == 0)
    {
        psserver->SendSystemError(me->clientnum, "You must specify a character name.");
        return;
    }

    size_t accountId = 0;
    Result result(db->Select("SELECT account_id FROM characters WHERE name = '%s'", msg.player.GetData()));
    if (result.IsValid() && result.Count())
    {
        iResultRow& accountRow = result[0];
        accountId = accountRow.GetUInt32("account_id");

        psserver->SendSystemInfo(client->GetClientNum(), "Account ID of player %s is %d. Characters on this account:",
                                 msg.player.GetData(), accountId);
    }
    else
    {
        psserver->SendSystemError(me->clientnum, "No player with name %s found in database.", msg.player.GetData());
        return;
    }

    if (accountId != 0)
    {
        Result result2(db->Select("SELECT id, name, lastname, last_login FROM characters WHERE account_id = %d", accountId));

        if (result2.IsValid() && result2.Count())
        {
            for (int i = 0; i < (int)result2.Count(); i++)
            {
                iResultRow& row = result2[i];
                psserver->SendSystemInfo(client->GetClientNum(), "Player ID: %d, %s %s, last login: %s",
                                         row.GetUInt32("id"), row["name"], row["lastname"], row["last_login"] );
            }
        }
    }
    else
    {
        psserver->SendSystemError(me->clientnum, "Error executing SQL-statement to retrieve characters on the account of player %s.", msg.player.GetData());
        return;
    }
}


bool AdminManager::Valid( int level, const char* command, int clientnum )
{
    csString errorStr;
    
    if ( !CacheManager::GetSingleton().GetCommandManager()->Validate( level, command, errorStr ) )
    {
        psserver->SendSystemError(clientnum, errorStr);
        return false;
    }
    return true;
}


void AdminManager::GetInfo(MsgEntry* me,psAdminCmdMessage& msg,Client *client, gemObject* target)
{    
    PS_ID entityId = 0;
    if ( target && target->GetEntity() )
        entityId = target->GetEntity()->GetID();

    if (target && strcmp(target->GetObjectType(), "ActionLocation") == 0) // Action location
    {
        gemActionLocation *item = dynamic_cast<gemActionLocation *>(target);
        if (!item)
        {
            psserver->SendSystemError(client->GetClientNum(), "Error! Target is not a valid gemActionLocation object.");
            return;
        }
        psActionLocation *action = item->GetAction();

        csString info;
        info.Format("ActionLocation: %s with ", item->GetName());
        if (action)
            info.AppendFmt("ID %u, and instance ID of the container %u.", action->id, action->GetInstanceIDOfContainer());
        else
            info.Append("no action location information.");

        psserver->SendSystemInfo(client->GetClientNum(), info);

        return;
    }

    if ( target && target->GetItem() && target->GetItem()->GetBaseStats() ) // Item
    {
          psItem* item = target->GetItem();

          csString info;
          info.Format("Item: %s ", item->GetName() );
          
          if ( item->GetStackCount() > 1 )
              info.AppendFmt("(x%d) ", item->GetStackCount() );

          info.AppendFmt("with item ID %u, instance ID %u, and entity ID %u",
                      item->GetBaseStats()->GetUID(),
                      item->GetUID(),
                      entityId );

          if ( item->GetScheduledItem() )
              info.AppendFmt(", spawns with interval %d + %d max modifier",
                             item->GetScheduledItem()->GetInterval(),
                             item->GetScheduledItem()->GetMaxModifier() );

          // Get all flags on this item
          int flags = item->GetFlags();
          if (flags)
          {
              info += ", has flags:";

              if ( flags & PSITEM_FLAG_CRAFTER_ID_IS_VALID )
                  info += " 'vlaid crafter id'";
              if ( flags & PSITEM_FLAG_GUILD_ID_IS_VALID )
                  info += " 'vlaid guild id'";
              if ( flags & PSITEM_FLAG_UNIQUE_ITEM )
                  info += " 'unique'";
              if ( flags & PSITEM_FLAG_USES_BASIC_ITEM )
                  info += " 'uses basic item'";
              if ( flags & PSITEM_FLAG_PURIFIED )
                  info += " 'purified'";
              if ( flags & PSITEM_FLAG_PURIFYING )
                  info += " 'purifying'";
              if ( flags & PSITEM_FLAG_LOCKED )
                  info += " 'locked'";
              if ( flags & PSITEM_FLAG_LOCKABLE )
                  info += " 'lockable'";
              if ( flags & PSITEM_FLAG_NOPICKUP )
                  info += " 'no pickup'";
              if ( flags & PSITEM_FLAG_KEY )
                  info += " 'key'";
              if ( flags & PSITEM_FLAG_TRANSIENT )
                  info += " 'transient'";
          }

          psserver->SendSystemInfo(client->GetClientNum(),info);
          return; // Done
    }

    char ipaddr[20] = {0};
    csString name, ipAddress, securityLevel;
    int playerId = 0, accountId = 0;
    float timeConnected = 0.0f;
    
    if (target) // Online
    {
        Client* targetclient = target->GetClient();

        playerId = target->GetPlayerID();
        if (target->GetCharacterData())
            timeConnected = target->GetCharacterData()->GetTimeConnected() / 3600;

        if (targetclient) // Player
        {
            name = targetclient->GetName();
            targetclient->GetIPAddress(ipaddr);
            ipAddress = ipaddr;
            accountId = targetclient->GetAccountID();

            // Because of /deputize we'll need to get the real SL from DB
            int currSL = targetclient->GetSecurityLevel();
            int trueSL = GetTrueSecurityLevel(accountId);
            
            if (currSL != trueSL)
                securityLevel.Format("%d(%d)",currSL,trueSL);
            else
                securityLevel.Format("%d",currSL);
        }
        else // NPC
        {
            name = target->GetName();
            psserver->SendSystemInfo(client->GetClientNum(),
                "NPC: %s has a player ID %d, entity ID %u, and has been active for %1.1f hours",
                name.GetData(),
                playerId,
                entityId,
                timeConnected );
            return; // Done
        }
    }
    else // Offline
    {
        Result result(db->Select("SELECT id, name, lastname, account_id, time_connected_sec from characters where name='%s'", msg.player.GetData()));

        if (!result.IsValid() || result.Count() == 0)
        {
             psserver->SendSystemError(client->GetClientNum(), "Cannot find player %s",msg.player.GetData() );
             return;
        }
        else
        {
             iResultRow& row = result[0];
             name = row["name"];
             if (row["lastname"] != "")
             {
                  name.Append(" ");
                  name.Append(row["lastname"]);
             }
             playerId = row.GetUInt32("id");
             accountId = row.GetUInt32("account_id");
             ipAddress = "(offline)";
             timeConnected = row.GetFloat("time_connected_sec") / 3600;
             securityLevel.Format("%d",GetTrueSecurityLevel(accountId));
        }
    }
    
    if (playerId != 0)
    {
        csString info;
        info.Format("Player: %s has ", name.GetData() );
        
        if (securityLevel != "0")
            info.AppendFmt("security level %s, ", securityLevel.GetData() );

        info.AppendFmt("account ID %d, player ID %d, ", accountId, playerId );
        
        if (ipAddress != "(offline)")
            info.AppendFmt("entity ID %u, IP is %s, ", entityId, ipAddress.GetData() );
        else
            info.Append("is offline, ");
            
        info.AppendFmt("total time connected is %1.1f hours", timeConnected );

        psserver->SendSystemInfo(client->GetClientNum(),info);
    }
    else
    {
        psserver->SendSystemError(client->GetClientNum(), "Error!  Object is not an item, player, or NPC." );
    }
}

void AdminManager::HandlePetitionMessage(MsgEntry *me, psPetitionRequestMessage& msg,Client *client)
{
    // Check which message and if this is a GM message or user message
    if (msg.request == "query")
    {
        if (msg.isGM)
            GMListPetitions(me, msg,client);
        else
            ListPetitions(me, msg,client);
    }
    else if (msg.request == "cancel" && !msg.isGM)
    {
        CancelPetition(me, msg,client);
    }
    else if (msg.isGM)
    {
        GMHandlePetition(me, msg,client);
    }
}
void AdminManager::HandleGMGuiMessage(MsgEntry *me, psGMGuiMessage& msg,Client *client)
{
    if (msg.type == psGMGuiMessage::TYPE_QUERYPLAYERLIST)
    {
        SendGMPlayerList(me, msg,client);
    }
}

void AdminManager::CreateHuntLocation(MsgEntry* me,psAdminCmdMessage& msg,Client *client)
{    
    if (msg.item.IsEmpty())
    {
        psserver->SendSystemError(me->clientnum, "Insufficent parameters. Use /crystal <interval> <random> <itemname>");
        return;
    }

    if (msg.interval != msg.interval || msg.random != msg.random)
    {
        psserver->SendSystemError(me->clientnum, "Invalid interval(s)");
        return;
    }
    if (msg.interval < 1 || msg.random < 1)
    {
        psserver->SendSystemError(me->clientnum, "Intervals need to be greater than 0");
        return;
    }

    // In seconds
    int interval = 1000*msg.interval;
    int random   = 1000*msg.random;

    psItemStats* rawitem = CacheManager::GetSingleton().GetBasicItemStatsByName(msg.item);
    if (!rawitem)
    {
        psserver->SendSystemError(me->clientnum, "Invalid item to spawn");
        return;
    }

    // Find the location
    csVector3 pos;
    float angle;
    iSector* sector = 0;
    client->GetActor()->GetPosition(pos, angle, sector);
    if (!sector)
    {
        psserver->SendSystemError(me->clientnum, "Invalid sector");
        return;
    }

    psSectorInfo *spawnsector = CacheManager::GetSingleton().GetSectorInfoByName(sector->QueryObject()->GetName());
    if(!spawnsector)
    {
        CPrintf(CON_ERROR,"Player is in invaild sector %s!",sector->QueryObject()->GetName());
        return;
    }
 
    // to db
    db->Command(
        "INSERT INTO hunt_locations"
        "(`x`,`y`,`z`,`itemid`,`sector`,`interval`,`max_random`)"
        "VALUES ('%f','%f','%f','%u','%s','%d','%d')",
        pos.x,pos.y,pos.z, rawitem->GetUID(),sector->QueryObject()->GetName(),interval,random);

    psScheduledItem* schedule = new psScheduledItem(db->GetLastInsertID(),rawitem->GetUID(),pos,spawnsector,interval,random);
    psItemSpawnEvent* event = new psItemSpawnEvent(schedule);
    psserver->GetEventManager()->Push(event);

    // Done!
    psserver->SendSystemInfo(me->clientnum,"New hunt location created!");
}

void AdminManager::SetAttrib(MsgEntry* me, psAdminCmdMessage& msg, Client *client)
{
    gemActor * actor = client->GetActor();

    bool onoff = false;
    bool toggle = false;
    bool already = false;

    if (msg.setting == "on")
        onoff = true;
    else if (msg.setting == "off")
        onoff = false;
    else
        toggle = true;

    if (msg.attribute == "list")
    {
        psserver->SendSystemInfo(me->clientnum, "invincible = %s\n"
                                                "invisible = %s\n"
                                                "viewall = %s\n"
                                                "nevertired = %s\n"
                                                "nofalldamage = %s\n"
                                                "infiniteinventory = %s\n"
                                                "questtester = %s",
                                                (actor->GetInvincibility())?"on":"off",
                                                (!actor->GetVisibility())?"on":"off",
                                                (actor->GetViewAllObjects())?"on":"off",
                                                (actor->nevertired)?"on":"off",
                                                (actor->safefall)?"on":"off",
                                                (!actor->GetFiniteInventory())?"on":"off",
                                                (actor->questtester)?"on":"off" );
        return;
    }
    else if (msg.attribute == "invinciblity" || msg.attribute == "invincible")
    {
        if (toggle)
        {
            actor->SetInvincibility(!actor->GetInvincibility());
            onoff = actor->GetInvincibility();
        }
        else if (actor->GetInvincibility() == onoff)
            already = true;
        else
            actor->SetInvincibility(onoff);
    }
    else if (msg.attribute == "invisiblity" || msg.attribute == "invisible")
    {
        if (toggle)
        {
            actor->SetVisibility(!actor->GetVisibility());
            onoff = !actor->GetVisibility();
        }
        else if (actor->GetVisibility() == !onoff)
            already = true;
        else
            actor->SetVisibility(!onoff);
    }
    else if (msg.attribute == "viewall")
    {
        if (toggle)
        {
            actor->SetViewAllObjects(!actor->GetViewAllObjects());
            onoff = actor->GetViewAllObjects();
        }
        else if (actor->GetViewAllObjects() == onoff)
            already = true;
        else
            actor->SetViewAllObjects(onoff);
    }
    else if (msg.attribute == "nevertired")
    {
        if (toggle)
        {
            actor->nevertired = !actor->nevertired;
            onoff = actor->nevertired;
        }
        else if (actor->nevertired == onoff)
            already = true;
        else
            actor->nevertired = onoff;
    }
    else if (msg.attribute == "nofalldamage")
    {
        if (toggle)
        {
            actor->safefall = !actor->safefall;
            onoff = actor->safefall;
        }
        else if (actor->safefall == onoff)
            already = true;
        else
            actor->safefall = onoff;
    }
    else if (msg.attribute == "infiniteinventory")
    {
        if (toggle)
        {
            actor->SetFiniteInventory(!actor->GetFiniteInventory());
            onoff = !actor->GetFiniteInventory();
        }
        else if (actor->GetFiniteInventory() == !onoff)
            already = true;
        else
            actor->SetFiniteInventory(!onoff);
    }
    else if (msg.attribute == "questtester")
    {
        if (toggle)
        {
            actor->questtester = !actor->questtester;
            onoff = actor->questtester;
        }
        else if (actor->questtester == onoff)
            already = true;
        else
            actor->questtester = onoff;
    }
    else if (!msg.attribute.IsEmpty())
    {
        psserver->SendSystemInfo(me->clientnum, "%s is not a supported attribute", msg.attribute.GetData() );
        return;
    }
    else
    {
        psserver->SendSystemInfo(me->clientnum, "Correct syntax is: \"/set [attribute] [on|off]\"");
        return;
    }

    psserver->SendSystemInfo(me->clientnum, "%s %s %s",
                                            msg.attribute.GetData(),
                                            (already)?"is already":"has been",
                                            (onoff)?"enabled":"disabled" );
}

void AdminManager::Divorce(MsgEntry* me, psAdminCmdMessage& msg, Client *client)
{
    bool onlineDivorcer = false;
    bool onlineTarget = false;
   
    if ( !msg.player.Length())
    {
        psserver->SendSystemInfo( me->clientnum, "Usage: \"/divorce [character]\"");
        return;
    }

    Client* divorcer = clients->Find( msg.player );
    onlineDivorcer = ( divorcer != NULL );
    
    //If the player that wishes to divorce is not online, we can't proceed.
    if ( !onlineDivorcer )
    {
        psserver->SendSystemInfo( me->clientnum, "The player that wishes to divorce must be online." );
        return;
    }

    psCharacter* divorcerChar = divorcer->GetCharacterData();
    //If the player is not married, we can't divorce anything.
    if( !divorcerChar->GetIsMarried() )
    {
        psserver->SendSystemInfo( me->clientnum, "For obtaining a divorce, there must be a marriage before.");
        return;
    }
    csString spouseFullName = divorcerChar->GetSpouseName();
    csString spouseName = spouseFullName.Slice( 0, spouseFullName.FindFirst(' '));

    //If the "spouse" is online, than there is no need to use GM powers for the divorce.
    Client* target = clients->Find( spouseName );
    onlineTarget = ( target != NULL );

    if ( onlineTarget )
    {
        psserver->SendSystemInfo( me->clientnum, "The two players can agree by themselves on the divorce, since both are online.");
        return;
    }

    if ( !psserver->GetCharManager()->HasConnected( spouseName ))//The player was connected recently.
    {
        psserver->SendSystemInfo( me->clientnum, "The spouse has been online recently, therefore it is not up to you to divorce the couple.");
        return;
    }

    //Now is it time to divorce the player.
    psMarriageManager* marriageMgr = psserver->GetMarriageManager();
    if ( !marriageMgr )
    {
        psserver->SendSystemError( me->clientnum, "Can't load MarriageManager.");
        Error1( "MarriageManager failed to load." );
        return;
    }

    // Delete entries of character's from DB
    marriageMgr->DeleteMarriageInfo( divorcerChar );
    psserver->SendSystemInfo( me->clientnum, "You have divorced %s from %s.", msg.player.GetData(), spouseName.GetData() );
    Debug3( LOG_MARRIAGE, me->clientnum, "%s divorced from %s.", msg.player.GetData(), spouseName.GetData() );

}

void AdminManager::Teleport(MsgEntry* me, psAdminCmdMessage& msg, Client *client, gemObject* subject)
{
    if (msg.target == "")
    {
        psserver->SendSystemInfo(client->GetClientNum(), "Use: /teleport subject destination\n"
                                 "Subject    : me/target/<object name>/<NPC name>/<player name>/eid:<EID>/pid:<PID>\n"
                                 "Destination: here/last/spawn/restore/map [<map name>|here] x y z\n");
        return;
    }
    
    // If player is offline and the special argument is called
    if (subject == NULL && msg.target == "restore")
    {
        psString sql;
        iSector * mySector;
        csVector3 myPoint;
        float yRot = 0.0;
        client->GetActor()->GetPosition(myPoint, yRot, mySector);

        psSectorInfo * mysectorinfo = NULL;
        if (mySector != NULL)
            mysectorinfo = CacheManager::GetSingleton().GetSectorInfoByName(mySector->QueryObject()->GetName());
        if (mysectorinfo == NULL)
        {
            psserver->SendSystemError(client->GetClientNum(), "Sector not found!");
            return;
        }
        
        sql.AppendFmt("update characters set loc_x=%10.2f, loc_y=%10.2f, loc_z=%10.2f, loc_yrot=%10.2f, loc_sector_id=%u where name='%s'",
                      myPoint.x, myPoint.y, myPoint.z, yRot, mysectorinfo->uid, msg.player.GetData());
        
        if (db->Command(sql) != 1)
        {
            Error3 ("Couldn't save character's position to database.\nCommand was "
                    "<%s>.\nError returned was <%s>\n",db->GetLastQuery(),db->GetLastError());

            psserver->SendSystemError(client->GetClientNum(), "Offline character %s could not be moved!", msg.player.GetData());
        }
        else
            psserver->SendSystemResult(client->GetClientNum(), "%s will next log in at your current location", msg.player.GetData());
        
        return;
    }
    else if (subject == NULL)
    {
        psserver->SendSystemError(client->GetClientNum(), "Cannot teleport target");
        return;
    }

    csVector3 targetPoint;
    float yRot = 0.0;
    iSector *targetSector;
    if ( !GetTargetOfTeleport(client, msg, targetSector, targetPoint, yRot, subject) )
    {
        psserver->SendSystemError(client->GetClientNum(), "Cannot teleport %s to %s", msg.player.GetData(), msg.target.GetData() );
        return;
    }
    
    //Error6("tele %s to %s %f %f %f",subject->GetName(), targetSector->QueryObject()->GetName(), targetPoint.x, targetPoint.y, targetPoint.z);
    
    csVector3 oldpos;
    float oldyrot;
    iSector *oldsector;
    subject->GetPosition(oldpos,oldyrot,oldsector);

    if ( (oldsector == targetSector) && (oldpos == targetPoint) )
    {
        psserver->SendSystemError(client->GetClientNum(), "What's the point?");
        return;
    }
    
    // Do the teleport
    if ( !MoveObject(client,subject,targetPoint,yRot,targetSector) )
        return;

    csString destName;
    if (msg.map.Length())
        destName.Format("map %s", msg.map.GetData() );
    else
        destName.Format("sector %s", targetSector->QueryObject()->GetName() );

    // Update ProxList on sector crossing
    if (oldsector != targetSector)
    {
        subject->UpdateProxList(true);
        psserver->SendSystemOK(subject->GetClientID(), "Welcome to " + destName);
    }
    else
        subject->UpdateProxList(false); // Update ProxList if needed

    if ( dynamic_cast<gemActor*>(subject) ) // Record old location of actor, for undo
        ((gemActor*)subject)->UpdateValidLocation(oldpos, 0.0f, oldyrot, oldsector, true);

    // Send explanations
    if (subject->GetClientID() != (int)client->GetClientNum())
    {
        psserver->SendSystemResult(client->GetClientNum(), "Teleported %s to %s", subject->GetName(), ((msg.target=="map")?destName:msg.target).GetData() );
        psserver->SendSystemResult(subject->GetClientID(), "You were moved by a GM");
    }
    
    if (msg.player == "me"  &&  msg.target != "map"  &&  msg.target != "here")
    {
        psGUITargetUpdateMessage updateMessage( client->GetClientNum(), subject->GetEntity()->GetID() );
        updateMessage.SendMessage();
    }
}

int AdminManager::WaypointCreate(csString& name, csVector3& pos, csString& sectorName, float radius, csString& flags)
{
    const char *fieldnames[]=
        {
            "name",
            "x",
            "y",
            "z",
            "radius",
            "flags",
            "loc_sector_id"
        };

    psSectorInfo * si = CacheManager::GetSingleton().GetSectorInfoByName(sectorName);
    
    psStringArray values;
    values.FormatPush("%s", name.GetDataSafe());
    values.FormatPush("%10.2f",pos.x);
    values.FormatPush("%10.2f",pos.y);
    values.FormatPush("%10.2f",pos.z);
    values.FormatPush("%10.2f",radius);
    values.FormatPush("%s",flags.GetDataSafe());
    values.FormatPush("%u",si->uid);
    
    unsigned int id = db->GenericInsertWithID("sc_waypoints",fieldnames,values);
    if (id==0)
    {
        Error2("Failed to create new WP Error %s",db->GetLastError());
        return -1;
    }
    
    return id;
}


void AdminManager::Waypoint(MsgEntry* me, psAdminCmdMessage& msg, Client *client)
{
    if ( !msg.waypointCmd.Length())
    {
        psserver->SendSystemInfo( me->clientnum, "Usage: \"/waypoint [add|create|link|path]\"");
        return;
    }
    

    if (msg.waypointCmd == "help")
    {
        psserver->SendSystemInfo( me->clientnum, "/waypoint help\n"
                                  "/waypoint add radius [flags]\n"
                                  "/waypoint create WP radius [flags]\n"
                                  "/waypoint link WP1 WP2 [flags]\n"
                                  "/waypoint path format\n"
                                  "/waypoint display\n"
                                  "/waypoint hide");
    }
    else if (msg.waypointCmd == "path")
    {
        client->WaypointSetPath(msg.wp1,msg.value);
        csString wp;
        wp.Format(client->WaypointGetPathName(),client->WaypointGetPathIndex());
        psserver->SendSystemInfo( me->clientnum, "New waypoint path. First WP will be: '%s'",wp.GetDataSafe());
    }
    else if (msg.waypointCmd == "create")
    {
        if ( !msg.wp1.Length() || msg.radius == 0.0f)
        {
            psserver->SendSystemInfo( me->clientnum, "Usage: \"/waypoint create WP radius [flags]\"");
            return;
        }
        csVector3 myPos;
        float myRotY;
        iSector* mySector = 0;
        int wp_id;
        
        client->GetActor()->GetPosition(myPos, myRotY, mySector);
        csString sectorName = mySector->QueryObject()->GetName();
        
        wp_id = WaypointCreate(msg.wp1,myPos,sectorName,msg.radius,msg.attribute);

        psserver->SendSystemInfo( me->clientnum, "Created new WP %u",wp_id);
    }
    else if (msg.waypointCmd == "add")
    {
        if ( msg.radius == 0.0f )
        {
            psserver->SendSystemInfo( me->clientnum, "Usage: \"/waypoint add radius [flags]\"");
            return;
        }
        csVector3 myPos;
        float myRotY;
        iSector* mySector = 0;
        int wp_id,wp_last;
        
        client->GetActor()->GetPosition(myPos, myRotY, mySector);
        csString sectorName = mySector->QueryObject()->GetName();
        
        csString wp;
        wp.Format(client->WaypointGetPathName(),client->WaypointGetNewPathIndex());

        wp_id = WaypointCreate(wp,myPos,sectorName,msg.radius,msg.attribute);

        wp_last = client->WaypointGetLast();
        if (wp_last != -1)
        {
            const char *fieldnames[]=
                {
                    "wp1",
                    "wp2",
                    "flags"
                };
            
            psStringArray values;
            values.FormatPush("%d", wp_last);
            values.FormatPush("%d", wp_id);
            values.FormatPush("%s",msg.attribute2.GetDataSafe());
            
            unsigned int link_id = db->GenericInsertWithID("sc_waypoint_links",fieldnames,values);
            if (link_id==0)
            {
                Error2("Failed to create new WP Link Error %s",db->GetLastError());
            }


        }
        client->WaypointSetLast(wp_id);

        psserver->SendSystemInfo( me->clientnum, "Created new WP %s(%u)",wp.GetDataSafe(),wp_id);
    }
    else if (msg.waypointCmd == "link")
    {
        if ( !msg.wp1.Length() || !msg.wp2.Length())
        {
            psserver->SendSystemInfo( me->clientnum, "Usage: \"/waypoint link wp1 wp2 [flags]\"");
            return;
        }

        
        const char *fieldnames[]=
            {
                "wp1",
                "wp2",
                "flags"
            };

        psStringArray values;
        values.FormatPush("%s", msg.wp1.GetDataSafe());
        values.FormatPush("%s", msg.wp2.GetDataSafe());
        values.FormatPush("%s",msg.attribute.GetDataSafe());
        
        unsigned int link_id = db->GenericInsertWithID("sc_waypoint_links",fieldnames,values);
        psserver->SendSystemInfo( me->clientnum, "Created new WP link %u",link_id);
 
    }
    else if (msg.waypointCmd == "display")
    {
        csVector3 myPos;
        float myRotY;
        iSector* mySector = 0;
        
        client->GetActor()->GetPosition(myPos, myRotY, mySector);
        csString sectorName = mySector->QueryObject()->GetName();

        Result rs(db->Select("select wp.* from sc_waypoints wp, sectors s where wp.loc_sector_id = s.id and s.name ='%s'",sectorName.GetDataSafe()));

        if (!rs.IsValid())
        {
            Error2("Could not load waypoints from db: %s",db->GetLastError() );
            return ;
        }
        
        for (int i=0; i<(int)rs.Count(); i++)
        {
            
            csVector3 pos(rs[i].GetFloat("x"),rs[i].GetFloat("y"),rs[i].GetFloat("z"));
            psEffectMessage msg(me->clientnum,"x_obj_star",pos,0,0,client->WaypointGetEffectID());
            msg.SendMessage();
        }
        
        psserver->SendSystemInfo(me->clientnum, "Displaying all WP in sector");
    }
    else if (msg.waypointCmd == "hide")
    {
        psStopEffectMessage msg(me->clientnum,client->WaypointGetEffectID());
        msg.SendMessage();
        psserver->SendSystemInfo(me->clientnum, "All WPs hidden");
    }
}


bool AdminManager::GetTargetOfTeleport(Client *client, psAdminCmdMessage& msg, iSector * & targetSector,  csVector3 & targetPoint, float &yRot, gemObject *subject)
{
    // when teleporting to a map
    if (msg.target == "map")
    {
        if (msg.sector.Length())
        {
            targetSector = psserver->GetEntityManager()->GetEngine()->FindSector(msg.sector);
            if (!targetSector)
            {
                psserver->SendSystemError(client->GetClientNum(), "Cannot find sector " + msg.sector);
                return false;
            }
            targetPoint = csVector3(msg.x, msg.y, msg.z);
        }
        else
        {
             return GetStartOfMap(client, msg.map, targetSector, targetPoint);
        }
    }
    // when teleporting to the place where we are standing at
    else if (msg.target == "here")
    {
        client->GetActor()->GetPosition(targetPoint, yRot, targetSector);
    }
    // Teleport to last valid location (force unstick/teleport undo)
    else if (msg.target == "last")
    {
        if ( dynamic_cast<gemActor*>(subject) )
            ((gemActor*)subject)->GetValidPos(targetPoint, yRot, targetSector);
        else
            return false; // Actors only
    }
    // Teleport to spawn point
    else if (msg.target == "spawn")
    {
        if ( dynamic_cast<gemActor*>(subject) )
            ((gemActor*)subject)->GetSpawnPos(targetPoint, yRot, targetSector);
        else
            return false; // Actors only
    }
    // Teleport to target
    else if (msg.target == "target")
    {
        gemObject* obj = client->GetTargetObject();
        if (!obj)
            return false;
        
        obj->GetPosition(targetPoint, yRot, targetSector);
    }
    // when teleporting to a player/npc
    else
    {
        Client * player = FindPlayerClient(msg.target,0);
        if (player)
        {
            player->GetActor()->GetPosition(targetPoint, yRot, targetSector);
        }
        else
        {
            gemObject* obj = FindObjectByString(msg.target); // Find by ID or name
            if (!obj) // Didn't find
                return false;

            obj->GetPosition(targetPoint, yRot, targetSector);
        }
    }
    return true;
}

bool AdminManager::GetStartOfMap(Client * client, const csString & map, iSector * & targetSector,  csVector3 & targetPoint)
{
    iEngine* engine = psserver->GetEntityManager()->GetEngine();
    iRegionList* regions = engine->GetRegions();

    assert(regions);

    if ( map.Length() == 0 )
    {
        psserver->SendSystemError( client->GetClientNum(), "Map name not given");
        return false;
    }

    iRegion* region = regions->FindByName(map.GetData());
    if (region == NULL)
    {
        psserver->SendSystemError(client->GetClientNum(), "Map not found.");
        return false;
    }

    iCameraPosition* loc = region->FindCameraPosition("Camera01");
    if (loc == NULL)
    {
        psserver->SendSystemError(client->GetClientNum(), "Starting location not found in map.");
        return false;
    }

    targetSector = engine->FindSector(loc->GetSector());
    targetPoint  = loc->GetPosition();
    return true;
}

void AdminManager::Slide(MsgEntry* me, psAdminCmdMessage& msg, Client *client, gemObject *target)
{    
    if (!target)
    {
        psserver->SendSystemError(me->clientnum, "Invalid target");
        return;
    }

    if (msg.direction.IsEmpty())
    {
        psserver->SendSystemError(me->clientnum, "Syntax: /slide [name|'target'] [direction]\nAllowed directions: U D L R F B T");
        return;
    }

    float slideAmount = (msg.amt == 0)?1:msg.amt; // default to 1

    if (slideAmount > 1000 || slideAmount < -1000 || slideAmount != slideAmount) // Check bounds and NaN
    {
        psserver->SendSystemError(me->clientnum, "Invalid slide amount");
        return;
    }

    csVector3 pos;
    float yrot;
    iSector* sector = 0;

    target->GetPosition(pos, yrot, sector);

    if (sector)
    {
        switch (toupper(msg.direction.GetAt(0)))
        {
            case 'U':
                pos.y += slideAmount;
                break;
            case 'D':
                pos.y -= slideAmount;
                break;
            case 'L':
                pos.x += slideAmount*cos(yrot);
                pos.z -= slideAmount*sin(yrot);
                break;
            case 'R':
                pos.x -= slideAmount*cos(yrot);
                pos.z += slideAmount*sin(yrot);
                break;
            case 'F':
                pos.x -= slideAmount*sin(yrot);
                pos.z -= slideAmount*cos(yrot);
                break;
            case 'B':
                pos.x += slideAmount*sin(yrot);
                pos.z += slideAmount*cos(yrot);
                break;
            case 'T':
                slideAmount = (msg.amt == 0)?90:msg.amt; // defualt to 90 deg
                yrot += slideAmount*PI/180; // Rotation units are degrees
                break;
            default:
                psserver->SendSystemError(me->clientnum, "Invalid direction given (Use one of: U D L R F B T)");
                return;
        }

        // Update the object
        if ( !MoveObject(client,target,pos,yrot,sector) )
            return;

        target->UpdateProxList(false); // Update ProxList if needed

        if (target->GetActorPtr() && client->GetActor() != target->GetActorPtr())
            psserver->SendSystemInfo(me->clientnum, "Sliding %s...", target->GetName());
    }
    else
    {
        psserver->SendSystemError(me->clientnum,
            "Invalid sector; cannot slide.  Please contact Planeshift support.");
    }
}

bool AdminManager::MoveObject(Client *client, gemObject *target, csVector3& pos, float yrot, iSector* sector)
{
    // This is a powerful feature; not everyone is allowed to use all of it
    csString response;
    bool allowedToMoveOthers = CacheManager::GetSingleton().GetCommandManager()->Validate(client->GetSecurityLevel(), "move others", response);
    if ( client->GetActor() != (gemActor*)target && !allowedToMoveOthers )
    {
        psserver->SendSystemError(client->GetClientNum(),response);
        return false;
    }

    if ( dynamic_cast<gemItem*>(target) ) // Item?
    {
        gemItem* item = (gemItem*)target;

        // Check to see if this client has the admin level to move this particular item
        bool extras = CacheManager::GetSingleton().GetCommandManager()->Validate(client->GetSecurityLevel(), "move unpickupables/spawns", response);

        if ( !item->IsPickable() && !extras )
        {
            psserver->SendSystemError(client->GetClientNum(),response);
            return false;
        }

        // Move the item
        item->GetMeshWrapper()->GetMovable()->SetPosition(pos);
        item->SetPosition(pos,yrot,sector);

        // Check to see if this client has the admin level to move this spawn point
        if ( item->GetItem()->GetScheduledItem() && extras )
        {
            psserver->SendSystemInfo(client->GetClientNum(), "Moving spawn point for %s", item->GetName());

            // Update spawn pos
            item->GetItem()->GetScheduledItem()->UpdatePosition(pos,sector->QueryObject()->GetName());
        }
    }
    else if ( dynamic_cast<gemActor*>(target) ) // Actor? (Player/NPC)
    {
        gemActor* actor = (gemActor*)target;
        actor->pcmove->SetVelocity(csVector3(0.0f,0.0f,0.0f)); // Halt actor
        actor->SetPosition(pos,yrot,sector);
        actor->GetCharacterData()->SaveLocationInWorld(); // force save the pos
        actor->MulticastDRUpdate();
    }
    else
    {
        psserver->SendSystemError(client->GetClientNum(),"Unknown target type");
        return false;
    }

    return true;
}

void AdminManager::CreateNPC(MsgEntry* me,psAdminCmdMessage& msg,Client *client, gemActor* basis)
{        
    if (!basis || !basis->GetCharacterData())
    {
        psserver->SendSystemError(me->clientnum, "Invalid target");
        return;
    }

    unsigned int masterNPC = 0;
    gemNPC *masternpc = basis->GetNPCPtr();
    if (masternpc)
        masterNPC = masternpc->GetCharacterData()->GetCharacterID();
    if (masterNPC == 0)
    {
        psserver->SendSystemError(me->clientnum, "%s was not found as a valid master NPC", basis->GetName() );
        return;
    }

    if ( !psserver->GetConnections()->FindAccount(masternpc->GetSuperclientID()) )
    {
        psserver->SendSystemError(me->clientnum, "%s's superclient is not online", basis->GetName() );
        return;
    }

    csVector3 pos;
    float angle;
    psSectorInfo* sectorInfo = NULL;
    client->GetActor()->GetCharacterData()->GetLocationInWorld(sectorInfo, pos.x, pos.y, pos.z, angle );

    iSector* sector = NULL;
    if (sectorInfo != NULL)
        sector = psserver->GetEntityManager()->FindSector(sectorInfo->name);
    if (sector == NULL)
    {
        psserver->SendSystemError(me->clientnum, "Invalid sector");
        return;
    }

    // Copy the master NPC into a new player record, with all child tables also
    unsigned int newNPCID = CopyNPCFromDatabase(masterNPC, pos.x, pos.y, pos.z, angle, sectorInfo->name );
    if (newNPCID == 0)
    {
        psserver->SendSystemError(me->clientnum, "Could not copy the master NPC");
        return;
    }

    // Make new entity
    PS_ID eid = psserver->GetEntityManager()->CreateNPC(newNPCID, false);
        
    // Get gemNPC for new entity
    gemNPC* npc = psserver->GetEntityManager()->GetGEM()->FindNPCEntity(newNPCID);
    if (npc == NULL)
    {
        psserver->SendSystemError(client->GetClientNum(), "Could not find GEM and set its location");
        return;
    }

    npc->GetCharacterData()->SetLocationInWorld(sectorInfo, pos.x, pos.y, pos.z, angle);
    npc->SetPosition(pos, angle, sector);

    psserver->npcmanager->ControlNPC(npc);
    psserver->npcmanager->NewNPCNotify(npc, masterNPC);

    npc->UpdateProxList(true);

    psserver->SendSystemInfo(me->clientnum, "New %s with PID %u and EID %u at (%1.2f,%1.2f,%1.2f) in %s.",
                                            npc->GetName(), newNPCID, eid,
                                            pos.x, pos.y, pos.z, sectorInfo->name.GetData() );
    psserver->SendSystemOK(me->clientnum, "New NPC created!");
}

int AdminManager::CopyNPCFromDatabase(int master_id, float x, float y, float z, float angle, const csString & sector)
{
    psCharacter* npc = NULL;
    int new_id;

    npc = psServer::CharacterLoader.LoadCharacterData(master_id,false);
    if (npc == NULL)
        return 0;

    psSectorInfo* sectorInfo = CacheManager::GetSingleton().GetSectorInfoByName( sector );
    if (sectorInfo != NULL)
        npc->SetLocationInWorld(sectorInfo,x,y,z,angle);

    if (psServer::CharacterLoader.NewNPCCharacterData(0, npc))
    {
        new_id = npc->GetCharacterID();
        db->Command("update characters set npc_master_id=%i where id=%i", master_id, new_id);
    }
    else
        new_id = 0;

    delete npc;

    return new_id;
}

void AdminManager::CreateItem(MsgEntry* me, psAdminCmdMessage& msg,Client *client)
{
    csVector3 pos;
    iSector*  sector = 0;
    float angle;

    client->GetActor()->GetPosition(pos, angle, sector);

    // TODO: Get number of items to create from client
    int stackCount = 1;
    if (CreateItem((const char*)msg.item,pos.x,pos.y,pos.z,angle,sector->QueryObject()->GetName(),stackCount,msg.random,msg.value))
    {
        psserver->SendSystemInfo(me->clientnum, "New item %s added!",msg.item.GetData());
    }
    else
    {
        psserver->SendSystemError(me->clientnum, "Can't create item %s!",msg.item.GetData());
    }
}

bool AdminManager::CreateItem(const char * name, double xPos, double yPos, double zPos, float angle, const char * sector, int stackCount, int random, int cap)
{
    psSectorInfo *sectorinfo = CacheManager::GetSingleton().GetSectorInfoByName(sector);
    if (sectorinfo==NULL)
    {
        Error2("'%s' was not found as a valid sector.",sector);
        return false;
    }

    if ( name == NULL )
    {
        Error1( "No item name was given" );
        return false;
    }
    
    // retrieve base stats item
    psItemStats *basestats=CacheManager::GetSingleton().GetBasicItemStatsByName(name);
    if (basestats==NULL)
    {
        Error2("'%s' was not found as a valid base item.",name);
        return false;
    }

    psItem *newitem = NULL;

    // randomize if requested
    if (random) {
      LootRandomizer* lootRandomizer = psserver->GetSpawnManager()->GetLootRandomizer();
      psItemStats *newstats = lootRandomizer->RandomizeItem( basestats, cap );
      newitem = newstats->InstantiateBasicItem(true);
    } else
      newitem = basestats->InstantiateBasicItem(true);

    if (newitem==NULL)
    {
        Error2("Could not instanciate from base item '%s'.",name);
        return false;
    }

    newitem->SetItemQuality(basestats->GetQuality(),false);
    newitem->SetStackCount(stackCount);
    newitem->SetLocationInWorld(sectorinfo,xPos,yPos,zPos,angle);

    if (!psserver->GetEntityManager()->CreateItem(newitem, true))
    {
        delete newitem;
        return false;
    }

    newitem->SetLoaded();  // Item is fully created
    newitem->Save();    // First save

    return true;
}

void AdminManager::ModifyKey(MsgEntry *me, psAdminCmdMessage& msg,Client *client)
{
    psItem* key = client->GetCharacterData()->Inventory().GetEquipmentItem(PSCHARACTER_SLOT_RIGHTHAND);

    if (msg.keyCmd == "make")
    {
        if ( !key )
            key = client->GetCharacterData()->Inventory().GetEquipmentItem(PSCHARACTER_SLOT_LEFTHAND);
        if ( !key )
        {
            psserver->SendSystemError(me->clientnum,"You need to be holding the object you want to make a key");
            return;
        }
        if ( key->GetIsKey() )
        {
            psserver->SendSystemError(me->clientnum,"item %s is already a key", key->GetName());
            return;
        }
        key->SetIsKey(true);
        psserver->SendSystemOK(me->clientnum,"item %s is now a key", key->GetName());
        return;
    }
    
    if ( !key || !key->GetIsKey() )
    {
        if (key)
            psserver->SendSystemError(me->clientnum,"item in right hand %s is not a key", key->GetName());
        key = client->GetCharacterData()->Inventory().GetEquipmentItem(PSCHARACTER_SLOT_LEFTHAND);
    }
    if ( !key || !key->GetIsKey() )
    {
        if (key)
            psserver->SendSystemError(me->clientnum,"item in left hand %s is not a key", key->GetName());
        psserver->SendSystemError(me->clientnum,"You need to be holding the key you want to work on");
        return;
    }

    if ( msg.keyCmd == "clearlocks" )
    {
        key->ClearOpenableLocks();
        psserver->SendSystemInfo(me->clientnum, "Your %s can no longer unlock anything", key->GetName());
        return;
    }

    if ( msg.keyCmd == "addlock" || msg.keyCmd == "removelock" )
    {
        gemItem* target = (gemItem*)client->GetTargetObject();

        if(!target)
        {
            if ( msg.keyCmd == "addlock" )
                psserver->SendSystemError(me->clientnum,"You need to target the item you want to encode the key to unlock");
            else
                psserver->SendSystemError(me->clientnum,"You need to target the item you want to stop the key from unlocking");
            return;
        }

        psItem* item = target->GetItem();

        if ( !item )
        {
            Error1("Found gemItem but no psItem was attached!\n");
            return;
        }                
            
        if(!item->GetIsLockable())
        {
            psserver->SendSystemError(me->clientnum,"This object isn't lockable");
            return;
        }

        if ( msg.keyCmd == "addlock" )
        {
            key->AddOpenableLock(item->GetUID());
            psserver->SendSystemInfo(me->clientnum, "You encoded %s to unlock %s", key->GetName(), item->GetName());
        }
        else
        {
            key->RemoveOpenableLock(item->GetUID());
            psserver->SendSystemInfo(me->clientnum, "Your %s can no longer unlock %s", key->GetName(), item->GetName());
        }
        return;                
    }

    if ( msg.keyCmd == "skel" )
    {
        bool b = key->GetIsSkeleton();
        key->MakeSkeleton(!b);
        if (b)
            psserver->SendSystemInfo(me->clientnum, "Your %s is no longer a skeleton key", key->GetName());
        else
            psserver->SendSystemInfo(me->clientnum, "Your %s is now a skeleton key", key->GetName());
    }
}

void AdminManager::KillNPC (MsgEntry *me, psAdminCmdMessage& msg, Client *client )
{
    gemObject* obj = NULL;

    if(msg.target.Length() != 0)
        if (msg.target.FindFirst(':')!=(size_t)-1) 
            obj = psserver->GetEntityManager()->GetGEM()->FindNPCEntity(atoi(msg.target.Slice(3).GetData()));

    if ( !obj )
        obj = client->GetTargetObject();

    if ( obj )
    {
        gemActor *target = obj->GetActorPtr();
        if (target && target->GetClientID() == 0)
        {
            if (msg.action != "reload")
                target->Kill(client->GetActor());
            else
            {
                unsigned int npcid = target->GetCharacterData()->GetCharacterID();
                
                psCharacter * npcdata = psServer::CharacterLoader.LoadCharacterData(npcid,true);
                psserver->GetEntityManager()->RemoveActor(obj);
                psserver->GetEntityManager()->CreateNPC(npcdata);
                psserver->SendSystemResult(me->clientnum, "NPC (id %d) has been reloaded.",npcid);
            }
            return;
        }
    }
    psserver->SendSystemError(me->clientnum, "No NPC was targeted.");
}


void AdminManager::Admin ( int playerID, int clientnum,Client *client )
{    
    // Set client security level in case security level have
    // changed in database.
    //psSystemMessage admin(clientnum, level, buffer);
    csString commandList;
    int type = client->GetSecurityLevel();

    // for now consider all levels > 30 as level 30.
    if (type>30) type=30;
    CacheManager::GetSingleton().GetCommandManager()->BuildXML( type, commandList );
    
    psAdminMessage admin(clientnum, ADMIN_ACCESS, commandList.GetDataSafe() );
    admin.SendMessage();
}

void AdminManager::AdminRaceRequest(int clientnum, const char* sex,Client *client)
{
    // Check to see if this client has admin level
    if ( client->GetSecurityLevel() < GM_LEVEL_4 )
    {
        //TODO: Should be an automatic kick here for trying to cheat
        return;
    }

    // Builds up the xml list
    psAdminRaceList list;
    psRaceInfo *raceinfo;

    int i=0;
    PSCHARACTER_GENDER gender;
    gender=PSCHARACTER_GENDER_NONE;
    if (sex[0]=='M' || sex[0]=='m')
        gender=PSCHARACTER_GENDER_MALE;
    if (sex[0]=='F' || sex[0]=='f')
        gender=PSCHARACTER_GENDER_FEMALE;

    while ((raceinfo=CacheManager::GetSingleton().GetRaceInfoByIndex(i++))!=NULL)
    {
        if (raceinfo->gender==gender)
            list.AddRace(raceinfo->name);
    }

    // Send race list back to client
    psAdminMessage mesg( clientnum, ADMIN_RACES_LIST, list.XML() );
    mesg.SendMessage();
}

void AdminManager::AdminRequestKnowledgeAreas( int clientnum )
{
    psAdminKnowledgeAreaList list;

    /// Fill up the XML list
    Result result(db->Select("SELECT DISTINCT area FROM npc_knowledge_areas"));

    if (!result.IsValid())
    {
        Error1("Error with npc_knowledge_areas table!");
        return;
    }

    for ( unsigned int i=0; i < result.Count(); i++ )
    {
        list.AddArea( result[i][0] );
    }

    psAdminMessage mesg( clientnum,
        ADMIN_REQUEST_KNOWLEDGE_AREAS,
        list.XML() );

    mesg.SendMessage();
}

class psAdminGameEvent : public psGameEvent
{
public:
    psAdminGameEvent( AdminManager * mgr, csString& NPCName )
        : psGameEvent(0,0,"psAdminGameEvent"),adminMgr(mgr),name(NPCName)
    {
    }
    virtual void Trigger()
    {
        adminMgr->AdminCreateNewNPC(0,name);
    }

private:
    AdminManager * adminMgr;
    csString name;
};

/**
* This function is thread safe, uses a game event to call
* the executing function.
*/
void AdminManager::AdminCreateNewNPC(csString& data)
{
    psserver->GetEventManager()->Push( new psAdminGameEvent(this, data ) );
}


void AdminManager::AdminCreateNewNPC(int clientnum, csString& data)
{

    CPrintf(CON_DEBUG, "Start loading npc: %s\n",data.GetData());

    // Removed pending changes
    /*

    bool success = false;

    // Create the npcdata cracker
    psAdminNPCData * newnpc = new psAdminNPCData(data);

    // Insert data into the database
    int id = database->CreateNewNPC(newnpc);

    if ( id )
    {
    #if 1
    {
    Debug1(LOG_ADMIN,"Dictionary Exists");
    int totalTrig = newnpc->dialogManager->triggerIDs.Length();
    for ( int trig = 0; trig < totalTrig; trig++ )
    {
    int idToAdd = newnpc->dialogManager->triggerIDs[trig];
    npcdlg->AddNewTrigger( idToAdd );
    }

    int totalResp = newnpc->dialogManager->responseIDs.Length();
    for ( int resp = 0; resp < totalResp; resp++ )
    {
    int idToAdd = newnpc->dialogManager->responseIDs[resp];
    npcdlg->AddNewResponse( idToAdd );
    }
    }
    #endif

    // entitymanager handles the detials of broadcasting the npc
    if ( entitymanager->CreateNPC(id) )
    {
    success = true;
    }
    }




    // Error response to send to client.
    char buffer[256];
    if (success)
    {
    sprintf(buffer, "New NPC %s created and added", newnpc->GetName());
    Debug2(LOG_ADMIN,"Result: %s\n", buffer);
    }
    else
    {
    sprintf(buffer, "New NPC %s FAILED to be created", newnpc->GetName());
    Error2("Result: %s\n", buffer);
    }

    if (clientnum != 0)
    {
    psSystemMessage newmsg(clientnum,MSG_INFO, buffer);
    if (newmsg.valid)
    eventmanager->SendMessage(newmsg.msg);
    }

    delete newnpc;
    */
}


void AdminManager::AdminRequestTriggers( uint32_t clientnum, csString& data )
{
    iResultSet* triggers = psserver->GetAllTriggersInArea(data);
    if (triggers == NULL)
        return;

    psAdminTriggerList triggerList;
    for ( unsigned int i = 0; i < triggers->Count(); i++ )
    {
        triggerList.AddTrigger( (*triggers)[i][0] );
    }

    psAdminMessage outmessage(clientnum,
        ADMIN_REQUEST_TRIGGERS,
        triggerList.XML());

    outmessage.SendMessage();
    triggers->Release();
}


void AdminManager::AdminRequestResponses(uint32_t clientnum, csString& trig)
{
    iResultSet* responses = psserver->GetAllResponses(trig);

    if ( !responses )
        return;

    psAdminResponseList responseList;

    responseList.AddResponse( (*responses)[0]["response1"]);
    responseList.AddResponse( (*responses)[0]["response2"]);
    responseList.AddResponse( (*responses)[0]["response3"]);
    responseList.AddResponse( (*responses)[0]["response4"]);
    responseList.AddResponse( (*responses)[0]["response5"]);
    responseList.AddPronounSet( (*responses)[0]["pronoun_him"],
        (*responses)[0]["pronoun_her"],
        (*responses)[0]["pronoun_it"],
        (*responses)[0]["pronoun_them"]);

    responses->Release();

    psAdminMessage outmesg(clientnum,
        ADMIN_REQUEST_RESPONSES,
        responseList.XML() );
    if (outmesg.valid)
        outmesg.SendMessage();
}

void AdminManager::AdminDialogChange(uint32_t clientnum, csString& data)
{
    psAdminResponseList dialog(data);

    for ( int z = 0; z < dialog.responses.Length(); z++ )
    {
        psserver->UpdateDialog( dialog.GetArea(),
            dialog.GetTrigger(),
            dialog.responses[z]->GetData(),
            z+1);   // database goes from 1-5
    }

    // Update pronoun set
    psserver->UpdateDialog( dialog.GetArea(),
        dialog.GetTrigger(),
        dialog.GetPronounHim(),
        dialog.GetPronounHer(),
        dialog.GetPronounIt(),
        dialog.GetPronounThem());


    psserver->SendSystemInfo(clientnum,"Dialog changes made");
}


void AdminManager::WarnMessage(MsgEntry* me, psAdminCmdMessage& msg,Client *client,Client *target)
{
    if (!target)
    {
        psserver->SendSystemError(me->clientnum, "Invalid target to warn");
        return;
    }
    
    if (msg.reason.Length() == 0)
    {
        psserver->SendSystemError(me->clientnum, "Please enter a warn message");
        return;
    }

    // This message will be shown in adminColor (red) in all chat tabs for this player
    psSystemMessage newmsg(target->GetClientNum(), MSG_INFO_SERVER, "GM warning from %s: " + msg.reason, client->GetName());
    psserver->GetEventManager()->SendMessage(newmsg.msg);

    // This message will be in big red letters on their screen
    psserver->SendSystemError(target->GetClientNum(), msg.reason);

    psserver->SendSystemInfo(client->GetClientNum(), "You warned '%s': " + msg.reason, target->GetName());
}


void AdminManager::KickPlayer(MsgEntry* me, psAdminCmdMessage& msg,Client *client,Client *target)
{
    if (!target)
    {
        psserver->SendSystemError(me->clientnum, "Invalid target to kick");
        return;
    }

    if (msg.reason.Length() < 5)
    {
        psserver->SendSystemError(me->clientnum, "You must specify a reason to kick");
        return;
    }

    // Remove from server and show the reason message
    psserver->RemovePlayer(target->GetClientNum(),"You were kicked from the server by a GM. Reason: " + msg.reason);

    psserver->SendSystemInfo(me->clientnum,"You kicked '%s' off the server.",(const char*)msg.player);
}

void AdminManager::Death( MsgEntry* me, psAdminCmdMessage& msg, Client *client, gemActor* target)
{    
    if (!target)
    {
        psserver->SendSystemError(me->clientnum,"You can't kill things that are not alive!");
        return;
    }
    
    target->Kill(NULL);  // Have a nice day ;)

    if (target->GetClientID() != 0)
        psserver->SendSystemError(target->GetClientID(), "You were killed by a GM");
}


void AdminManager::Impersonate( MsgEntry* me, psAdminCmdMessage& msg, Client *client)
{        if (msg.player.IsEmpty() || msg.text.IsEmpty() || msg.commandMod.IsEmpty())
    {
        psserver->SendSystemError(me->clientnum, "Invalid parameters");
        return;
    }
    
    // If no commandMod is given, default to say
    if (msg.commandMod != "say" && msg.commandMod != "shout" && msg.commandMod != "worldshout")
    {
        msg.text = msg.commandMod + " " + msg.text;
        msg.commandMod = "say";
    }

    csString sendText; // We need specialised say/shout as it is a special GM chat message

    if (msg.player == "text")
        sendText = msg.text;
    else
        sendText.Format("%s %ss: %s", msg.player.GetData(), msg.commandMod.GetData(), msg.text.GetData() );

    psChatMessage newMsg(client->GetClientNum(), msg.player, sendText, CHAT_GM, false);

    gemObject* source = (gemObject*)client->GetActor();

    // Invisible; multicastclients list is empty
    if (!source->GetVisibility() && msg.commandMod != "worldshout")
    {
        // Try to use target as source
        source = client->GetTargetObject();

        if (source == NULL || source->GetClientID() == client->GetClientNum())
        {
            psserver->SendSystemError(me->clientnum, "Invisible; select a target to use as source");
            return;
        }
    }

    if (msg.commandMod == "say")
        newMsg.Multicast(source->GetMulticastClients(), 0, CHAT_SAY_RANGE);
    else if (msg.commandMod == "shout")
        newMsg.Multicast(source->GetMulticastClients(), 0, PROX_LIST_ANY_RANGE);
    else if (msg.commandMod == "worldshout")
        psserver->GetEventManager()->Broadcast(newMsg.msg, NetBase::BC_EVERYONE);
    else
        psserver->SendSystemInfo(me->clientnum, "Syntax: /impersonate name command text\nCommand can be one of say, shout, or worldshout.\nIf name is \"text\" the given text will be the by itself.");
}

void AdminManager::MutePlayer(MsgEntry* me, psAdminCmdMessage& msg, Client *client, Client *target)
{
    if (!target)
    {
        psserver->SendSystemError(me->clientnum, "Invalid target to mute");
        return;
    }

    psserver->MutePlayer(target->GetClientNum(),"You were muted by a GM, until log off.");

    // Finally, notify the GM that the client was successfully muted
    psserver->SendSystemInfo(me->clientnum, "You muted '%s' until he/she/it logs back in.",(const char*)msg.player);
}


void AdminManager::UnmutePlayer(MsgEntry* me, psAdminCmdMessage& msg, Client *client, Client *target)
{
    if (!target)
    {
        psserver->SendSystemError(me->clientnum, "Invalid target to unmute");
        return;
    }

    psserver->UnmutePlayer(target->GetClientNum(),"You were unmuted by a GM.");

    // Finally, notify the GM that the client was successfully unmuted
    psserver->SendSystemInfo(me->clientnum, "You unmuted '%s'.",(const char*)msg.player);
}


void AdminManager::HandleAddPetition(MsgEntry *me, psAdminCmdMessage& msg,Client *client)
{
    if (msg.petition.Length() == 0)
    {
        psserver->SendSystemError(me->clientnum,"You must enter a petition question/description after '/petition '");
        return;
    }
    
    // Try and add the petition to the database:
    if (!AddPetition(client->GetPlayerID(), (const char*)msg.petition))
    {
        psserver->SendSystemError(me->clientnum,"SQL Error: %s", db->GetLastError());
        return;
    }

    // Tell client the petition was added:
    psserver->SendSystemInfo(me->clientnum, "Your petition was successfully submitted!");

    BroadcastDirtyPetitions(me->clientnum, true);
}

void AdminManager::BroadcastDirtyPetitions(int clientNum, bool includeSelf)
{
    psPetitionMessage dirty(clientNum, NULL, "", true, PETITION_DIRTY, true);
    if (dirty.valid)
    {
        if (includeSelf)
            psserver->GetEventManager()->Broadcast(dirty.msg, NetBase::BC_EVERYONE);
        else
            psserver->GetEventManager()->Broadcast(dirty.msg, NetBase::BC_EVERYONEBUTSELF);
    }
}

void AdminManager::ListPetitions(MsgEntry *me, psPetitionRequestMessage& msg,Client *client)
{
    // Try and grab the result set from the database:
    iResultSet *rs = GetPetitions(client->GetPlayerID());
    if (rs)
    {
        // Send list to client:
        csArray<psPetitionInfo> petitions;
        psPetitionInfo info;
        for (unsigned int i=0; i<rs->Count(); i++)
        {
            // Set info
            info.id = atoi((*rs)[i][0]);
            info.petition = (*rs)[i][1];
            info.status = (*rs)[i][2];
            info.created = csString((*rs)[i][3]).Slice(0, 16);
            info.assignedgm = (*rs)[i][4];
            if (info.assignedgm.Length() == 0)
                info.assignedgm = "No GM Assigned";

            // Append to the message:
            petitions.Push(info);
        }

        psPetitionMessage message(me->clientnum, &petitions, "List retrieved successfully.", true, PETITION_LIST);
        message.SendMessage();
        rs->Release();
    }
    else
    {
        // Return no succeed message to client
        csString error;
        error.Format("SQL Error: %s", db->GetLastError());
        psPetitionMessage message(me->clientnum, NULL, error, false, PETITION_LIST);
        message.SendMessage();
    }
}

void AdminManager::CancelPetition(MsgEntry *me, psPetitionRequestMessage& msg,Client *client)
{
    // Tell the database to change the status of this petition:
    if (!CancelPetition(client->GetPlayerID(), msg.id))
    {
        psPetitionMessage error(me->clientnum, NULL, db->GetLastError(), false, PETITION_CANCEL);
        error.SendMessage();
        return;
    }

    // Try and grab the result set from the database:
    iResultSet *rs = GetPetitions(client->GetPlayerID());
    if (rs)
    {
        // Send list to client:
        csArray<psPetitionInfo> petitions;
        psPetitionInfo info;
        for (unsigned int i=0; i<rs->Count(); i++)
        {
            // Set info
            info.id = atoi((*rs)[i][0]);
            info.petition = (*rs)[i][1];
            info.status = (*rs)[i][2];
            info.created = (*rs)[i][3];
            info.assignedgm = (*rs)[i][4];
            if (info.assignedgm.Length() == 0)
                info.assignedgm = "No GM Assigned";

            // Append to the message:
            petitions.Push(info);
        }

        psPetitionMessage message(me->clientnum, &petitions, "Cancel was successful.", true, PETITION_CANCEL);
        message.SendMessage();
        rs->Release();
    }
    else
    {
        // Tell client deletion was successful:
        psPetitionMessage message(me->clientnum, NULL, "Cancel was successful.", true, PETITION_CANCEL);
        message.SendMessage();
    }
    BroadcastDirtyPetitions(me->clientnum);
}

void AdminManager::GMListPetitions(MsgEntry *me, psPetitionRequestMessage& msg,Client *client)
{
    // Check to see if this client has GM level access
    if ( client->GetSecurityLevel() < GM_LEVEL_1 && client->GetSecurityLevel() <= GM_LEVEL_9 )
    {
        psserver->SendSystemError(me->clientnum, "Access denied. Only GMs can manage petitions.");
        return;
    }

    // Try and grab the result set from the database:
    iResultSet *rs = GetPetitions(-1, client->GetPlayerID(), client->GetSecurityLevel());
    if (rs)
    {
        // Send list to GM:
        csArray<psPetitionInfo> petitions;
        psPetitionInfo info;
        for (unsigned int i=0; i<rs->Count(); i++)
        {
            // Set info
            info.id = atoi((*rs)[i][0]);
            info.petition = (*rs)[i][1];
            info.status = (*rs)[i][2];
            info.escalation = atoi((*rs)[i][3]);
            info.created = csString((*rs)[i][4]).Slice(0, 16);
            info.player = (*rs)[i][5];

            // Append to the message:
            petitions.Push(info);
        }

        psPetitionMessage message(me->clientnum, &petitions, "List retrieved successfully.", true, PETITION_LIST, true);
        message.SendMessage();
        rs->Release();
    }
    else
    {
        // Return no succeed message to GM
        csString error;
        error.Format("SQL Error: %s", db->GetLastError());
        psPetitionMessage message(me->clientnum, NULL, error, false, PETITION_LIST, true);
        message.SendMessage();
    }
}

void AdminManager::GMHandlePetition(MsgEntry *me, psPetitionRequestMessage& msg,Client *client)
{
    // Check to see if this client has GM level access
    if ( client->GetSecurityLevel() < GM_LEVEL_1 && 
         client->GetSecurityLevel() > GM_LEVEL_9 )
    {
        psserver->SendSystemError(me->clientnum, "Access denied. Only GMs can manage petitions.");
        return;
    }

    // Check what operation we are executing based on the request:
    int type = -1;
    bool result = false;
    if (msg.request == "cancel")
    {
        // Cancellation:
        type = PETITION_CANCEL;
        result = CancelPetition(-1, msg.id);
    }
    else if (msg.request == "close")
    {
        // Closing petition:
        type = PETITION_CLOSE;
        result = ClosePetition(client->GetPlayerID(), msg.id, msg.desc);
    }
    else if (msg.request == "assign")
    {
        // Assigning petition:
        type = PETITION_ASSIGN;
        result = AssignPetition(client->GetPlayerID(), msg.id);
    }
    else if (msg.request == "escalate")
    {
        // Escalate petition:
        type = PETITION_ESCALATE;
        result = EscalatePetition(client->GetPlayerID(), client->GetSecurityLevel(), msg.id);
    }

    else if (msg.request == "descalate")
    {
        // Descalate petition:
        type = PETITION_DESCALATE;
        result = DescalatePetition(client->GetPlayerID(), client->GetSecurityLevel(), msg.id);
    }

    // Check result of operation
    if (!result)
    {
        psPetitionMessage error(me->clientnum, NULL, db->GetLastError(), false, type, true);
        error.SendMessage();
        return;
    }

    // Try and grab the result set from the database:
    iResultSet *rs = GetPetitions(-1, client->GetPlayerID(), client->GetSecurityLevel());
    if (rs)
    {
        // Send list to GM:
        csArray<psPetitionInfo> petitions;
        psPetitionInfo info;
        for (unsigned int i=0; i<rs->Count(); i++)
        {
            // Set info
            info.id = atoi((*rs)[i][0]);
            info.petition = (*rs)[i][1];
            info.status = (*rs)[i][2];
            info.escalation = atoi((*rs)[i][3]);
            info.created = (*rs)[i][4];
            info.player = (*rs)[i][5];

            // Append to the message:
            petitions.Push(info);
        }        
        // Tell GM operation was successful
        psPetitionMessage message(me->clientnum, &petitions, "Successful", true, type, true);
        message.SendMessage();
        rs->Release();
    }
    else
    {
        // Tell GM operation was successful eventhough we don't have a list of petitions
        psPetitionMessage message(me->clientnum, NULL, "Successful", true, type, true);
        message.SendMessage();
    }
    BroadcastDirtyPetitions(me->clientnum);
}

void AdminManager::SendGMPlayerList(MsgEntry* me, psGMGuiMessage& msg,Client *client)
{
    if ( client->GetSecurityLevel() < GM_LEVEL_1  &&
         client->GetSecurityLevel() > GM_LEVEL_9  && !client->IsSuperClient())
    {
        psserver->SendSystemError(me->clientnum,"You don't have access to GM functions!");
        CPrintf(CON_ERROR, "Client %d tried to get GM player list, but hasn't got GM access!\n");
        return;
    }
    
    csArray<psGMGuiMessage::PlayerInfo> playerList;

    // build the list of players
    Client *curr;
    ClientIterator i(*clients);
    for (curr = i.First(); curr; curr = i.Next())
    {
        if (curr->IsSuperClient() || !curr->GetActor()) continue;

        psGMGuiMessage::PlayerInfo playerInfo;
        
        playerInfo.name = curr->GetName();
        playerInfo.lastName = curr->GetCharacterData()->lastname;
        playerInfo.gender = curr->GetCharacterData()->GetRaceInfo()->gender;

        psGuildInfo *guild = curr->GetCharacterData()->GetGuild();
        if (guild)
            playerInfo.guild = guild->GetName();
        else
            playerInfo.guild = "";

        //Get sector name
        csVector3 vpos;
        float yrot;
        iSector* sector;

        curr->GetActor()->GetPosition(vpos,yrot,sector);

        playerInfo.sector = sector->QueryObject()->GetName();

        playerList.Push(playerInfo);
    }

    // send the list of players
    psGMGuiMessage message(me->clientnum, &playerList, psGMGuiMessage::TYPE_PLAYERLIST);
    message.SendMessage();
}

bool AdminManager::EscalatePetition(int gmID, int gmLevel, int petitionID)
{
    int result = db->Command("UPDATE petitions SET status='Open',assigned_gm=-1,"
                            "escalation_level=(escalation_level+1)"
                            "WHERE id=%d AND escalation_level<=%d AND (assigned_gm=%d OR status='Open')", petitionID, gmLevel, gmID);
    // If this failed if means that there is a serious error
    if (result == -1)
    {
        lasterror.Format("Couldn't escalate petition #%d.",
            petitionID);
        return false;
    }
    return (result != -1);
}

bool AdminManager::DescalatePetition(int gmID, int gmLevel, int petitionID)
{
    int result = db->Command("UPDATE petitions SET status='Open',assigned_gm=-1,"
                            "escalation_level=(escalation_level-1)"
                            "WHERE id=%d AND escalation_level<=%d AND (assigned_gm=%d OR status='Open')", petitionID, gmLevel, gmID);
    // If this failed if means that there is a serious error
    if (result == -1)
    {
        lasterror.Format("Couldn't descalate petition #%d.",
            petitionID);
        return false;
    }
    return (result != -1);
}

bool AdminManager::AddPetition(int playerID, const char* petition)
{
    /* The columns in the table NOT included in this command
     * have default values and thus we do not need to put them in
     * the INSERT statement
     */
    csString escape;
    db->Escape( escape, petition );
    int result = db->Command("INSERT INTO petitions "
                             "(player,petition,created_date,status,resolution) "
                             "VALUES (%d,\"%s\",Now(),\"Open\",\"Not Resolved\")",playerID, escape.GetData());

    return (result != -1);
}

iResultSet *AdminManager::GetPetitions(int playerID, int gmID, int gmLevel)
{
    iResultSet *rs;

    // Check player ID (if ID is -1, get a complete list for the GM):
    if (playerID == -1)
    {
        rs = db->Select("SELECT pet.id,pet.petition,pet.status,pet.escalation_level,pet.created_date,pl.name FROM petitions pet, "
                    "characters pl WHERE (pet.player!=%d AND ((pet.status=\"Open\" AND pet.escalation_level<=%d) "
                    "OR (pet.assigned_gm=%d AND pet.status=\"In Progress\"))) "
                    "AND pet.player=pl.id "
                    "ORDER BY pet.status ASC,pet.escalation_level DESC,pet.created_date ASC", gmID, gmLevel, gmID);
    }
    else
    {
        rs = db->Select("SELECT pet.id,pet.petition,pet.status,pet.created_date,pl.name "
                    "FROM petitions pet LEFT JOIN characters pl "
                    "ON pet.assigned_gm=pl.id "
                    "WHERE pet.player=%d AND pet.status!=\"Closed\" "
                    "AND pet.status!=\"Cancelled\" "
                    "ORDER BY pet.status ASC,pet.escalation_level DESC", playerID);
    }

    if (!rs)
    {
        lasterror = GetLastSQLError();
    }

    return rs;
}

bool AdminManager::CancelPetition(int playerID, int petitionID)
{
    // If player ID is -1, just cancel the petition (a GM is requesting the change)
    if (playerID == -1)
    {
        int result = db->Command("UPDATE petitions SET status='Cancelled' WHERE id=%d", petitionID);
        return (result != -1);
    }

    // Attempt to select this petition; two things can go wrong: it doesn't exist or the player didn't create it
    int result = db->SelectSingleNumber("SELECT id FROM petitions WHERE id=%d AND player=%d", petitionID, playerID);
    if (!result || result <= -1)
    {
        // Failure was due to nonexistant petition or ownership rights:
        lasterror.Format("Couldn't cancel the petition. Either it does not exist, or you did not "
            "create the petition.");
        return false;
    }

    // Update the petition status
    result = db->Command("UPDATE petitions SET status='Cancelled' WHERE id=%d AND player=%d", petitionID, playerID);

    return (result != -1);
}

bool AdminManager::ClosePetition(int gmID, int petitionID, const char* desc)
{
    csString escape;
    db->Escape( escape, desc );
    int result = db->Command("UPDATE petitions SET status='Closed',closed_date=Now(),resolution='%s' "
                             "WHERE id=%d AND assigned_gm=%d", escape.GetData(), petitionID, gmID);

    // If this failed if means that there is a serious error, or the GM was not assigned
    if (result == -1)
    {
        lasterror.Format("Couldn't close petition #%d.  You must be assigned to the petition before you close it.",
            petitionID);
        return false;
    }

    return (result != -1);
}

bool AdminManager::AssignPetition(int gmID, int petitionID)
{
    int result = db->Command("UPDATE petitions SET assigned_gm=%d,status=\"In Progress\" WHERE id=%d AND assigned_gm=-1",gmID, petitionID);

    // If this failed if means that there is a serious error, or another GM was already assigned
    if (result == -1)
    {
        lasterror.Format("Couldn't assign you to petition #%d.  Another GM is already assigned to that petition.",
            petitionID);
        return false;
    }

    return true;
}

bool AdminManager::LogGMCommand(int gmID, int playerID, const char* cmd)
{
    if (!strncmp(cmd,"/slide",6)) // don't log all these.  spamming the GM log table.
        return true;

    csString escape;
    db->Escape( escape, cmd );
    int result = db->Command("INSERT INTO gm_command_log "
                             "(gm,command,player,ex_time) "
                             "VALUES (%d,\"%s\",%d,Now())",gmID, escape.GetData(), playerID);
    return (result != -1);
}

const char *AdminManager::GetLastSQLError()
{
    if (!db)
        return "";

    return db->GetLastError();
}

void AdminManager::DeleteCharacter(MsgEntry* me, psAdminCmdMessage& msg,Client *client)
{
    WordArray words (msg.cmd, false);
    csString zombie = words[1];
    csString requestor = words[2];
    unsigned int zombieID = 0;
    
    if ( zombie.StartsWith("pid:",true) ) // Find by player ID
    {
        zombieID = atoi( zombie.Slice(4).GetData() );
        if (!zombieID)
        {
            psserver->SendSystemError(me->clientnum,"Error, bad PID");
            return;
        }
    }

    if (zombieID == 0)  // Deleting by name; verify the petitioner gave us one of their characters
    {
        if (words.GetCount() < 3)
        {
            psserver->SendSystemInfo(me->clientnum,"Syntax: \"/deletechar CharacterName RequestorName\" OR \"/deletechar pid:[id]\"");
            return;
        }
    
        csString escape;
        db->Escape( escape, zombie );
       
        // Check account
        unsigned int zombieAccount = db->SelectSingleNumber( "SELECT account_id FROM characters WHERE name='%s'\n", escape.GetData() );
        if ( zombieAccount == QUERY_FAILED )
        {
            psserver->SendSystemInfo(me->clientnum,"Character %s has no account.", zombie.GetData());
            return;
        }
        zombieID = (unsigned int)db->SelectSingleNumber( "SELECT id FROM characters WHERE name='%s'\n", escape.GetData() );

        db->Escape( escape, requestor );
        unsigned int requestorAccount = db->SelectSingleNumber( "SELECT account_id FROM characters WHERE name='%s'\n", escape.GetData() );
        if ( requestorAccount == QUERY_FAILED )
        {
            psserver->SendSystemInfo(me->clientnum,"Requestor %s has no account.", requestor.GetData());
            return;
        }

        if ( zombieAccount != requestorAccount )
        {
            psserver->SendSystemInfo(me->clientnum,"Zombie/Requestor Mismatch, no deletion.");
            return;
        }
    }
    else  // Deleting by PID; make sure this isn't a unique or master NPC
    {
        Result result(db->Select("SELECT name, character_type, npc_master_id FROM characters WHERE id='%u'",zombieID));
        if (!result.IsValid() || result.Count() != 1)
        {
            psserver->SendSystemError(me->clientnum,"No character found with PID %u!",zombieID);
            return;
        }
        
        iResultRow& row = result[0];
        zombie = row["name"];
        unsigned int charType = row.GetUInt32("character_type");
        unsigned int masterID = row.GetUInt32("npc_master_id");

        if (charType == PSCHARACTER_TYPE_NPC)
        {
            if (masterID == 0)
            {
                psserver->SendSystemError(me->clientnum,"%s is a unique NPC, and may not be deleted", zombie.GetData() );
                return;
            }

            if (masterID == zombieID)
            {
                psserver->SendSystemError(me->clientnum,"%s is a master NPC, and may not be deleted", zombie.GetData() );
                return;
            }
        }
    }
        
    csString error;
    if ( psserver->CharacterLoader.DeleteCharacterData(zombieID,error) )
        psserver->SendSystemInfo(me->clientnum,"Character %s (PID %u) has been deleted.", zombie.GetData(), zombieID );
    else
    {
        if ( error.Length() )
            psserver->SendSystemError(me->clientnum,"Deletion error: %s", error.GetData() );
        else
            psserver->SendSystemError(me->clientnum,"Character deletion got unkown error!", error.GetData() );
    }
}



void AdminManager::ChangeName(MsgEntry* me, psAdminCmdMessage& msg,Client *client)
{    
    if (!msg.player.Length())
    {
        psserver->SendSystemError(me->clientnum,"You have to specify a character");
        return;
    }

    if (!msg.newName.Length())
    {
        psserver->SendSystemError(me->clientnum,"You have to specify a new character name");
        return;
    }

    // Fix names
    msg.newName = NormalizeCharacterName(msg.newName);
    msg.newLastName = NormalizeCharacterName(msg.newLastName);
    csString name = NormalizeCharacterName(msg.player);
    
    bool online;
    unsigned int id;
    unsigned int type;

    Client* target = clients->Find(msg.player);
    online = (target != NULL);

    csString prevFirstName,prevLastName;

    // Check the DB if the player isn't online
    if(!online)
    {
        Result result(db->Select("SELECT id,name,lastname,character_type FROM characters WHERE name='%s' LIMIT 1",name.GetData()));
        if (!result.IsValid() || result.Count() == 0)
        {
            psserver->SendSystemError(me->clientnum,"No online or offline player found with the name %s!",name.GetData());               
            return;
        }
        else
        {
            iResultRow& row = result[0];
            prevFirstName = row["name"];
            prevLastName = row["lastname"];
            id = row.GetUInt32("id");

            type = row.GetUInt32("character_type");
            if (type == PSCHARACTER_TYPE_NPC)
            {
                if (!Valid(client->GetSecurityLevel(), "change NPC names", me->clientnum))
                    return;
            }
        }
    }
    else
    {
        prevFirstName = target->GetCharacterData()->GetCharName();
        prevLastName = target->GetCharacterData()->GetCharLastName();
        id = target->GetCharacterData()->GetCharacterID();

        type = target->GetCharacterData()->GetCharType();
    }

    bool checkFirst=true; //If firstname is same as before, skip DB check
    bool checkLast=true; //If we make the newLastName var the current value, we need to skip the db check on that    

    if(msg.newLastName == "No")
    {
        msg.newLastName = "";
        checkLast = false;
    }
    else if (msg.newLastName.Length() == 0 || msg.newLastName == prevLastName)
    {
        msg.newLastName = prevLastName;
        checkLast = false;
    }

    if (msg.player == msg.newName)
        checkFirst = false;

    if (!checkFirst && !checkLast && msg.newLastName.Length() != 0)
        return;

    if(checkFirst)
    {   
        if (!psServerCharManager::FilterName(msg.newName))   
        {   
            psserver->SendSystemError(me->clientnum,"The name %s is invalid!",msg.newName.GetData());   
            return;   
        }   
    }
    if(checkLast)   
    {   
        if (!psServerCharManager::FilterName(msg.newLastName))   
        {
            psserver->SendSystemError(me->clientnum,"The last name %s is invalid!",msg.newLastName.GetData());   
            return;
        }   
    }

    // Querying the DB is slow, but I don't see another way to do it
    if (checkFirst && type == PSCHARACTER_TYPE_PLAYER)
    {
        Result result1(db->Select("SELECT * FROM characters WHERE name='%s'",msg.newName.GetData()));
        if (result1.IsValid() && result1.Count() > 0)
        {
            psserver->SendSystemError(me->clientnum,"The name %s is not unique!",msg.newName.GetData());               
            return;
        }
    }

    // If the last name should be unique, check it
    if (msg.uniqueName && checkLast && msg.newLastName.Length())
    {
        Result result1(db->Select("SELECT * FROM characters WHERE lastname='%s'",msg.newLastName.GetData()));
        if (result1.IsValid() && result1.Count() > 0)
        {
            psserver->SendSystemError(me->clientnum,"The last name %s is not unique!",msg.newLastName.GetData());               
            return;
        }
    }

    // Apply
    csString fullName;
    PS_ID actorId = 0;
    if(online)
    {
        target->GetCharacterData()->SetFullName(msg.newName, msg.newLastName);
        fullName = target->GetCharacterData()->GetCharFullName();
        target->SetName(msg.newName);
        target->GetActor()->SetName(fullName);
        actorId = target->GetActor()->GetEntity()->GetID();

    }
    else if (type == PSCHARACTER_TYPE_NPC || type == PSCHARACTER_TYPE_PET)
    {
        gemNPC *npc = psserver->GetEntityManager()->GetGEM()->FindNPCEntity( id );
        if (!npc)
        {
            psserver->SendSystemError(me->clientnum,"Unable to find NPC %s!", 
                name.GetData());
            return;
        }
        npc->GetCharacterData()->SetFullName(msg.newName, msg.newLastName);
        fullName = npc->GetCharacterData()->GetCharFullName();
        actorId = npc->GetEntity()->GetID();
    }

    // Inform
    if(online)
    {
        psserver->SendSystemInfo(
                        target->GetClientNum(),
                        "Your name has been changed to %s %s by GM %s",
                        msg.newName.GetData(),
                        msg.newLastName.GetData(),
                        client->GetName()
                        );
    }

    psserver->SendSystemInfo(me->clientnum,
                             "%s %s is now known as %s %s",
                             prevFirstName.GetDataSafe(),
                             prevLastName.GetDataSafe(),
                             msg.newName.GetDataSafe(),
                             msg.newLastName.GetDataSafe()
                             );

    // Update
    if (online || type == PSCHARACTER_TYPE_NPC || type == PSCHARACTER_TYPE_PET)
    {
        psUpdateObjectNameMessage newNameMsg(0, actorId, fullName);
        psserver->GetEventManager()->Broadcast(
            newNameMsg.msg, NetBase::BC_EVERYONE);
    }

    // Need instant DB update if we should be able to change the same persons name twice
    db->Command("UPDATE characters SET name='%s', lastname='%s' WHERE id='%u'",msg.newName.GetData(),msg.newLastName.GetDataSafe(), id );

    // Resend group list
    if(online)
    {
        csRef<PlayerGroup> group = target->GetActor()->GetGroup();
        if(group)
            group->BroadcastMemberList();

        // Handle guild update
        psGuildInfo* guild = target->GetActor()->GetGuild();
        if(guild)
        {
            psGuildMember* mem = guild->FindMember((unsigned int)target->GetActor()->GetCharacterData()->GetCharacterID());
            if(mem)
                mem->name = msg.newName;
        }
    }
    
    Client* buddy;
    /*We update the buddy list of the people who have the target in their own buddy list and 
      they are online. */
    if(online)
    {
        csArray<int> buddyOfList=target->GetCharacterData()->buddyOfList;
    
        for (size_t i=0; i<buddyOfList.Length(); i++)
        {
            buddy = clients->FindPlayer(buddyOfList[i]);
            if (buddy)
            {
                buddy->GetCharacterData()->RemoveBuddy(id);
                buddy->GetCharacterData()->AddBuddy(id, msg.newName);
                //We refresh the buddy list
                psserver->usermanager->BuddyList(buddy, buddy->GetClientNum(), true);
           }
        }
    }
    else
    {
        unsigned int buddyid;

        //If the target is offline then we select all the players online that have him in the buddylist
        Result result2(db->Select("SELECT player_id FROM buddy_list WHERE player_buddy='%u'",id));

        if (result2.IsValid())
        {
            for(unsigned long j=0; j<result2.Count();j++)
            {
                iResultRow& row = result2[j];
                buddyid = row.GetUInt32("player_id");
                buddy = clients->FindPlayer(buddyid);
                if (buddy)
                {
                    buddy->GetCharacterData()->RemoveBuddy(id);
                    buddy->GetCharacterData()->AddBuddy(id, msg.newName);
                    //We refresh the buddy list
                    psserver->usermanager->BuddyList(buddy, buddy->GetClientNum(), true);
                }
           }
        }
     }
}

void AdminManager::BanName(MsgEntry* me, psAdminCmdMessage& msg, Client *client)
{
    if (!msg.player.Length())
    {
        psserver->SendSystemError(me->clientnum,"You have to specify a name to ban");
        return;
    }

    if (psserver->GetCharManager()->IsBanned(msg.player))
    {
        psserver->SendSystemError(me->clientnum,"That name is already banned");
        return;
    }
    
    CacheManager::GetSingleton().AddBadName(msg.player);
    psserver->SendSystemInfo(me->clientnum,"You banned the name '%s'",msg.player.GetDataSafe());
}

void AdminManager::UnBanName(MsgEntry* me, psAdminCmdMessage& msg, Client *client)
{
    if (!msg.player.Length())
    {
        psserver->SendSystemError(me->clientnum,"You have to specify a name to unban");
        return;
    }

    if (!psserver->GetCharManager()->IsBanned(msg.player))
    {
        psserver->SendSystemError(me->clientnum,"That name is not banned");
        return;
    }
    
    CacheManager::GetSingleton().DelBadName(msg.player);
    psserver->SendSystemInfo(me->clientnum,"You unbanned the name '%s'",msg.player.GetDataSafe());
}

void AdminManager::BanClient(MsgEntry* me, psAdminCmdMessage& msg,Client *client)
{
    const time_t year = 31536000UL; //one year should be enough
    time_t secs = (msg.mins * 60) + (msg.hours * 60 * 60) + (msg.days * 24 * 60 * 60);
    if ((secs > year) || (secs == 0))
        secs = year; //some errors if time was too high 

    if (msg.player.Length() == 0)
    {
        psserver->SendSystemError(me->clientnum, "You must specify a player name or an account name or number.");
        return;
    }
    
    if (msg.reason.Length() < 5)
    {
        psserver->SendSystemError(me->clientnum, "You must specify a reason to ban");
        return;
    }

    Result result;
    unsigned int account = atoi( msg.player.GetDataSafe() );  // See if we're going by character name or account ID

    if (account == 0)
    {
        if ( !GetAccount(msg.player,result) )
        {
            // not found
            psserver->SendSystemError(me->clientnum, "Couldn't find account with the name %s",msg.player.GetData());
            return;
        }
        account = result[0].GetUInt32("id");
    }
    else
    {
        result = db->Select("SELECT * FROM accounts WHERE id = '%u' LIMIT 1",account);
        if ( !result.IsValid() || !result.Count() )
        {
            psserver->SendSystemError(me->clientnum, "Couldn't find account with id %u",account);
            return;
        }
    }

    csString user = result[0]["username"];

    // Ban by IP range, as well as account
    csString ip_range = Client::GetIPRange(result[0]["last_login_ip"]);

    if ( !psserver->GetAuthServer()->GetBanManager()->AddBan(account,ip_range,secs,msg.reason) )
    {
        // Error adding; entry must already exist
        psserver->SendSystemError(me->clientnum, "%s is already banned", user.GetData() );
        return;
    }

    // Find client to get target
    Client *target = clients->Find(NormalizeCharacterName(msg.player));

    // Now we have a valid player target, so remove from server
    if(target)
    {
        if (secs < year)
        {
            csString reason;
            reason.Format("You were banned from the server by a GM for %d minutes, %d hours and %d days. Reason: %s",
                          msg.mins, msg.hours, msg.days, msg.reason.GetData() );

            psserver->RemovePlayer(target->GetClientNum(),reason);
        }
        else
            psserver->RemovePlayer(target->GetClientNum(),"You were banned from the server by a GM. Reason: " + msg.reason);
    }
    
    csString notify;
    notify.Format("You%s banned '%s' off the server for ", (target)?" kicked and":"", user.GetData() );
    if (secs == year)
        notify.Append("a year. ");
    else
        notify.AppendFmt("%d minutes, %d hours and %d days. ", msg.mins, msg.hours, msg.days );
    notify.AppendFmt("They will also be banned by IP range%s.", (secs > 60*60*24*2)?" for the first 2 days":"" );

    // Finally, notify the client who kicked the target
    psserver->SendSystemInfo(me->clientnum,notify);
}

void AdminManager::UnbanClient(MsgEntry* me, psAdminCmdMessage& msg,Client *gm)
{
    if (msg.player.Length() == 0)
    {
        psserver->SendSystemError(me->clientnum, "You must specify a player name or an account name or number.");
        return;
    }
    
    Client* target = clients->Find(msg.player);
    // Check if the target is online, if he/she/it is he/she/it can't be unbanned (No logic in it).
    if (target)
    {
        psserver->SendSystemError(me->clientnum, "The player is active and is playing.");
        return;
    }

    Result result;
    unsigned int account = atoi( msg.player.GetDataSafe() );  // See if we're going by character name or account ID

    if (account == 0)
    {
        if ( !GetAccount(msg.player,result) )
        {
            // not found
            psserver->SendSystemError(me->clientnum, "Couldn't find account with the name %s",msg.player.GetDataSafe());
            return;
        }
        account = result[0].GetUInt32("id");
    }
    else
    {
        result = db->Select("SELECT * FROM accounts WHERE id = '%u' LIMIT 1",account);
        if ( !result.IsValid() || !result.Count() )
        {
            psserver->SendSystemError(me->clientnum, "Couldn't find account with id %u",account);
            return;
        }
    }

    csString user = result[0]["username"];

    if ( psserver->GetAuthServer()->GetBanManager()->RemoveBan(account) )
        psserver->SendSystemResult(me->clientnum, "%s has been unbanned", user.GetData() );
    else
        psserver->SendSystemError(me->clientnum, "%s is not banned", user.GetData() );
}

bool AdminManager::GetAccount(csString useroracc,Result& resultre )
{
    unsigned int id = 0;
    bool character = false;
    csString usr;

    // Check if it's a character
    // Uppercase in names
    usr = NormalizeCharacterName(useroracc);
    resultre = db->Select("SELECT * FROM characters WHERE name = '%s' LIMIT 1",usr.GetData());
    if (resultre.IsValid() && resultre.Count() == 1)
    {
        id = resultre[0].GetUInt32("account_id"); // store id
        character = true;
    }
    
    if (character)
        resultre = db->Select("SELECT * FROM accounts WHERE id = '%u' LIMIT 1",id);
    else
    {
        // account uses lowercase
        usr.Downcase();
        resultre = db->Select("SELECT * FROM accounts WHERE username = '%s' LIMIT 1",usr.GetData());
    }

    if ( !resultre.IsValid() || !resultre.Count() )
        return false;

    return true;
}

void AdminManager::SendSpawnTypes(MsgEntry* me, psAdminCmdMessage& msg,Client *client)
{
    csArray<csString> itemCat;
    unsigned int size = 0;

    for(int i = 1;; i++)
    {
        psItemCategory* cat = CacheManager::GetSingleton().GetItemCategoryByID(i);
        if(!cat)
            break;

        size += (int)strlen(cat->name)+1;
        itemCat.Push(cat->name);
    }
    
    itemCat.Sort();
    psGMSpawnTypes msg2(me->clientnum,size);

    // Add the numbers of types
    msg2.msg->Add((uint32_t)itemCat.Length());

    for(size_t i = 0;i < itemCat.Length(); i++)
    {
        msg2.msg->Add(itemCat.Get(i));
    }

    msg2.SendMessage();
}

void AdminManager::SendSpawnItems (MsgEntry* me, psGMSpawnItems& msg,Client *client)
{
    csArray<psItemStats*> items;
    unsigned int size = 0;

    psItemCategory * category = CacheManager::GetSingleton().GetItemCategoryByName(msg.type);    
    if ( !category )
    {
        psserver->SendSystemError(me->clientnum, "Category %s is not valid.", msg.type.GetData() );
        return;    
    }

    // Database hit.  
    // Justification:  This is a rare event and it is quicker than us doing a sort.  
    //                 Is also a read only event.     
    Result result(db->Select("SELECT id FROM item_stats WHERE category_id=%d AND id < %d ORDER BY Name", category->id, SPAWN_ITEM_ID_CEILING));
    if (!result.IsValid() || result.Count() == 0)
    {
        psserver->SendSystemError(me->clientnum, "Could not query database for category %s.", msg.type.GetData() );
        return;
    }
    
    for ( unsigned int i=0; i < result.Count(); i++ )
    {
        unsigned id = result[i].GetUInt32(0);
        psItemStats* item = CacheManager::GetSingleton().GetBasicItemStatsByID(id);
        if(item)
        {
            csString name(item->GetName());
            csString mesh(item->GetMeshName());
            size += (int)name.Length()+(int)mesh.Length()+2;
            items.Push(item);           
        }            
    }

    psGMSpawnItems msg2(me->clientnum,msg.type,size);

    // Add the numbers of types
    msg2.msg->Add((uint32_t)items.Length());

    for(size_t i = 0;i < items.Length(); i++)
    {
        psItemStats* item = items.Get(i);
        msg2.msg->Add(item->GetName());
        msg2.msg->Add(item->GetMeshName());
    }

    CPrintf(CON_DEBUG, "Sending %d items from the %s category to client %d\n",items.Length(),msg.type.GetData(),me->clientnum);

    msg2.SendMessage();
}

void AdminManager::SpawnItemInv(MsgEntry* me, psGMSpawnItem& msg,Client *client)
{
    psCharacter* charData = client->GetCharacterData();
    if (!charData)
    {
        psserver->SendSystemError(me->clientnum, "Couldn't find your character data!");
        return;
    }

    // Get the basic stats
    psItemStats* stats = CacheManager::GetSingleton().GetBasicItemStatsByName(msg.item);
    if (!stats)
    {
        psserver->SendSystemError(me->clientnum, "Couldn't find basic stats for that item!");
        return;
    }

    // Check skill
    PSSKILL skill = CacheManager::GetSingleton().ConvertSkillString(msg.lskill);
    if (skill == PSSKILL_NONE && msg.lockable)
    {
        psserver->SendSystemError(me->clientnum, "Couldn't find the lock skill!");
        return;
    }

    // Create the new item
    psItem *item = stats->InstantiateBasicItem();

	item->SetStackCount(msg.count);
    item->SetIsLockable(msg.lockable);
    item->SetIsLocked(msg.locked);
    item->SetIsPickupable(msg.pickupable);

    if (msg.lockable)
    {        
        item->SetLockpickSkill(skill);
        item->SetLockStrength(msg.lstr);
    }
    
    // Place the new item in the GM's inventory
    csString text;
    if ( charData->MoveToInventory(item) )
    {
        if (item)
        {
            item->SetLoaded();  // Item is fully created
            item->Save();    // First save
        }
        text.Format("You spawned %s to your inventory",msg.item.GetData());
    }            
    else
    {
        text.Format("Couldn't spawn %s to your inventory, maybe it's full?",msg.item.GetData());
        CacheManager::GetSingleton().RemoveInstance(item);
    }

    psserver->SendSystemInfo(me->clientnum,text);
}

void AdminManager::AwardExperience(MsgEntry* me, psAdminCmdMessage& msg, Client* client, Client* target)
{    
    if (!target || !target->GetCharacterData())
    {
        psserver->SendSystemError(me->clientnum, "Invalid target to award experience to");
        return;
    }
    
    if (msg.value == 0)
    {
        psserver->SendSystemError(me->clientnum, "Invalid experience specified");
        return;
    }

    int pp = target->GetCharacterData()->GetProgressionPoints();

    if (pp == 0 && msg.value < 0)
    {
        psserver->SendSystemError(me->clientnum, "Target has no experience to penalize");
        return;
    }

    pp += msg.value; // Negative changes are allowed
    if (pp < 0) // Negative values are not
    {
        msg.value += -pp;
        pp = 0;
    }

    target->GetCharacterData()->SetProgressionPoints(pp,true);

    if (msg.value > 0)
    {
        psserver->SendSystemOK(target->GetClientNum(),"You have been awarded experience by a GM");
        psserver->SendSystemInfo(target->GetClientNum(),"You gained %d progression points.", msg.value);
    }
    else
    {
        psserver->SendSystemError(target->GetClientNum(),"You have been penalized experience by a GM");
        psserver->SendSystemInfo(target->GetClientNum(),"You lost %d progression points.", -msg.value);
    }

    psserver->SendSystemInfo(me->clientnum, "You awarded %s %d progression points.", msg.player.GetData(), msg.value);
}

void AdminManager::TransferItem(MsgEntry* me, psAdminCmdMessage& msg, Client* source, Client* target)
{
    if (!target || !target->GetCharacterData())
    {
        psserver->SendSystemError(me->clientnum, "Invalid character to give to");
        return;
    }
    
    if (!source || !source->GetCharacterData())
    {
        psserver->SendSystemError(me->clientnum, "Invalid character to take from");
        return;
    }
    
    if (source == target)
    {
        psserver->SendSystemError(me->clientnum, "Source and target must be different");
        return;
    }
    
    if (msg.value == 0 || msg.item.IsEmpty())
    {
        psserver->SendSystemError(me->clientnum, "Syntax: \"/[giveitem|takeitem] [target] [quantity|'all'|''] [item]\"");
        return;
    }

    psItemStats* itemstats = CacheManager::GetSingleton().GetBasicItemStatsByName(msg.item);
    if (!itemstats)
    {
        psserver->SendSystemError(me->clientnum, "Invalid item name");
        return;
    }

    psCharacter* targetchar = target->GetCharacterData();
    psCharacter* sourcechar = source->GetCharacterData();

    bool equipped = false;

    int slot = sourcechar->Inventory().FindItemInTopLevelBulkWithStats(itemstats);
    if (slot == -1)
    {
        equipped = true;
        slot = sourcechar->Inventory().FindItemInTopLevelEquipmentWithStats(itemstats);

        if (slot == -1)
        {
            psserver->SendSystemError(me->clientnum, "There are not any %s in %s's inventory",
                                                      msg.item.GetData(), source->GetActor()->GetName() );
            return;
        }
    }

    psItem* item;
    if (!equipped)
    {
        item = sourcechar->Inventory().GetBulkItem(slot);
        
        int count = item->GetStackCount();
        if (msg.value == -1)  // All
            msg.value = count;
    
        if (count < msg.value)
        {
            psserver->SendSystemError(me->clientnum, "There are only %d, not %d", count, msg.value );
            return;
        }
    }
    else
    {
        item = sourcechar->Inventory().GetEquipmentItem(slot);
        msg.value = 1;
    }

    size_t room = targetchar->Inventory().CanFit(item);
    if (room && room < (size_t)msg.value)
    {
        psserver->SendSystemError(me->clientnum, "Only %u can fit, not %d", room, msg.value );
        return;
    }
    else if (room == 0)
    {
        psserver->SendSystemError(me->clientnum, "Target inventory is full");
        return;
    }

    if (!equipped)
        item = sourcechar->Inventory().RemoveBulk(slot,msg.value);
    else
        item = sourcechar->Inventory().RemoveEquipment(slot);

    if ( targetchar->MoveToInventory(item) )
    {
        if (equipped)  // If we removed from equipment, change shown equipment
            psserver->GetCharManager()->SendOutEquipmentMessages( source,
                                                                  slot,
                                                                  item,
                                                                  psEquipmentMessage::DEEQUIP );

        // Inform the GM doing the transfer
        psserver->SendSystemOK(me->clientnum, "%s transfered from %s's %s to %s",
                                              item->GetName(),
                                              source->GetActor()->GetName(),
                                              equipped?"equipment":"inventory",
                                              target->GetActor()->GetName() );
                            
        // If we're giving to someone else, notify them
        if (target->GetClientNum() != me->clientnum)
             psserver->SendSystemOK(target->GetClientNum(), "You were given %s by GM %s",
                                                            item->GetName(),
                                                            source->GetActor()->GetName() );

        // If we're taking from someone else, notify them
        if (source->GetClientNum() != me->clientnum)
             psserver->SendSystemResult(source->GetClientNum(), "%s was taken by GM %s",
                                                                item->GetName(),
                                                                target->GetActor()->GetName() );
    }
    else  // All slots are probably full
    {
        sourcechar->Inventory().PutInBulk(item);
        psserver->SendSystemError(me->clientnum, "Target inventory is full");
    }
}

void AdminManager::FreezeClient(MsgEntry* me, psAdminCmdMessage& msg, Client* client, Client* target)
{
    if (!target)
    {
        psserver->SendSystemError(me->clientnum,"Invalid target for freeze");
        return;
    }
    
    if (target->IsFrozen())
    {
        psserver->SendSystemError(me->clientnum,"The player is alreday frozen");
        return;
    }   
    
    target->SetFrozen(true);
    target->GetActor()->SetMode(PSCHARACTER_MODE_SIT);
    psserver->SendSystemError(target->GetClientNum(), "You have been frozen in place by a GM.");
    psserver->SendSystemInfo(me->clientnum, "You froze '%s'.",(const char*)msg.player);
}

void AdminManager::ThawClient(MsgEntry* me, psAdminCmdMessage& msg, Client* client, Client* target)
{
    if (!target)
    {
        psserver->SendSystemError(me->clientnum,"Invalid target for thaw");
        return;
    }

    if (!target->IsFrozen())
    {
        psserver->SendSystemError(me->clientnum,"The player is not frozen");
        return;
    }   
    
    target->SetFrozen(false);
    target->GetActor()->SetMode(PSCHARACTER_MODE_PEACE);
    psserver->SendSystemOK(target->GetClientNum(), "You have been released by a GM.");
    psserver->SendSystemInfo(me->clientnum, "You released '%s'.",(const char*)msg.player);
}

void AdminManager::SetSkill(MsgEntry* me, psAdminCmdMessage& msg, Client* client)
{    
    if (msg.value<0  ||  msg.value>MAX_SKILL)
    {
        psserver->SendSystemError(me->clientnum, "Valid values are between 0 and %i", MAX_SKILL);
        return;
    }

    if (msg.skill.IsEmpty())
    {
        psserver->SendSystemError(me->clientnum, "Syntax: /setskill [skill] [value]");
        return;
    }

    psCharacter * pchar = client->GetCharacterData();
    if (!pchar)
    {
        psserver->SendSystemError(me->clientnum, "No character data!");
        return;
    }

    if (msg.skill == "all")
    {
        for (int i=0; i<PSSKILL_COUNT; i++)
        {
            psSkillInfo * skill = CacheManager::GetSingleton().GetSkillByID(i);
            if (skill == NULL) continue;
            pchar->SetSkillRank(skill->id, msg.value);
        }
    }
    else
    {
        psSkillInfo * skill = CacheManager::GetSingleton().GetSkillByName(msg.skill);
        if (skill == NULL)
        {
            psserver->SendSystemError(me->clientnum, "Skill not found");
            return;
        }
        
        // Validate that if the skill is a stat it's limit is MAX_STAT
        if ( skill->category == PSSKILLS_CATEGORY_STATS && msg.value > MAX_STAT )
        {
            psserver->SendSystemError(me->clientnum, "Stats have a limit of %i", MAX_STAT);    
            return;
        }
        
        pchar->SetSkillRank(skill->id, msg.value);
    }

    psserver->SendSystemInfo(me->clientnum, "Fine");
}

void AdminManager::Inspect(MsgEntry* me, psAdminCmdMessage& msg, Client* client, gemActor* target)
{    
    if (!target)
    {
        psserver->SendSystemError(me->clientnum,"You need to specify or target a player or NPC");
        return;
    }

    if (!target->GetCharacterData())
    {
        psserver->SendSystemError(me->clientnum,"Critical error! The entity hasn't got any character data!");
        return;
    }

    // We got our target, now let's print it's inventory
    csString message; // Dump all data formated in this
    bool npc = (target->GetClientID() == 0);

    message.Format("Inventory for %s %s:\n",
                   npc?"NPC":"player",
                   target->GetName() );

    message.AppendFmt("Total weight is %d / %d\nTotal money is %d\n",
                      (int)target->GetCharacterData()->Inventory().Weight(),
                      (int)target->GetCharacterData()->Inventory().MaxWeight(),
                      target->GetCharacterData()->Money().GetTotal() );

    bool found = false;
    message += "Equipment:\n";
    for (int i = 0; i < PSCHARACTER_SLOT_COUNT;i++)
    {
        psItem* item = target->GetCharacterData()->Inventory().GetEquipmentItem(i);
        if (item)
        {
            found = true;
            message += item->GetName();
            if (item->GetStackCount() > 1)
                message.AppendFmt(" (x%u)", item->GetStackCount() );
            message += "\n";
        }
    }
    if (!found)
        message += "(none)\n";

    found = false;
    message += "Bulk:\n";
    for (int i = 0; i < PSCHARACTER_BULK_COUNT;i++)
    {
        psItem* item = target->GetCharacterData()->Inventory().GetBulkItem(i);
        if (item)
        {
            found = true;
            message += item->GetName();
            if (item->GetStackCount() > 1)
                message.AppendFmt(" (x%u)", item->GetStackCount() );
            message += "\n";
        }
    }
    if (!found)
        message += "(none)\n";

    message.Truncate(message.Length() -1);

    psserver->SendSystemInfo(me->clientnum,message);
}

void AdminManager::RenameGuild(MsgEntry* me, psAdminCmdMessage& msg, Client* client)
{
    if(msg.target.IsEmpty() || msg.newName.IsEmpty())
    {
        psserver->SendSystemError(me->clientnum,"Syntax: /changeguildname guildname newguildname");
        return;
    }

    psGuildInfo* guild = CacheManager::GetSingleton().FindGuild(msg.target);
    if(!guild)
    {
        psserver->SendSystemError(me->clientnum,"No guild with that name");
        return;
    }

    guild->SetName(msg.newName);
    psserver->GetGuildManager()->ResendGuildData(guild->id);

    // Notify the guild leader if he is online
    psGuildMember* gleader = guild->FindLeader();
    if(gleader)
    {
        if(gleader->actor && gleader->actor->GetActor())
        {
            psserver->SendSystemInfo(gleader->actor->GetActor()->GetClientID(),
                "Your guild has been renamed to %s by a GM",
                msg.newName.GetData()
                );
        }
    }

    psserver->SendSystemOK(me->clientnum,"Guild renamed to '%s'",msg.newName.GetData());
    
    // Get all connected guild members
    csArray<uint32_t> array;
    for (size_t i = 0; i < guild->members.Length();i++)
    {
        psGuildMember* member = guild->members[i];
        if(member->actor)
            array.Push(member->actor->GetActor()->GetEntity()->GetID());
    }   

    // Update the labels
    int length = (int)array.Length();
    psUpdatePlayerGuildMessage newNameMsg(0,length,msg.newName,false);

    // Copy array
    for(size_t i = 0; i < array.Length();i++)
        newNameMsg.AddPlayer(array[i]);

    // Broadcast to everyone
    psserver->GetEventManager()->Broadcast(newNameMsg.msg,NetBase::BC_EVERYONE);
}

void AdminManager::Thunder(MsgEntry* me, psAdminCmdMessage& msg, Client *client)
{   
    // Find the sector
    psSectorInfo *sectorinfo = NULL;

    if (!msg.sector.IsEmpty())
        sectorinfo = CacheManager::GetSingleton().GetSectorInfoByName(msg.sector);
    else
    {
        csVector3 pos;
        iSector* sect;
        // Get the current sector
        client->GetActor()->GetPosition(pos,sect);
        if(!sect)
        {
            psserver->SendSystemError(me->clientnum,"Invalid sector");
            return;
        }

        sectorinfo = CacheManager::GetSingleton().GetSectorInfoByName(sect->QueryObject()->GetName());
    }

    if (!sectorinfo)
    {
        psserver->SendSystemError(me->clientnum,"Sector not found!");
        return;
    }
    
    if (sectorinfo->lightning_max_gap == 0)
    {
        psserver->SendSystemError(me->clientnum, "Lightning not defined for this sector!");
        return;
    }

    if (!sectorinfo->is_raining)
    {
        psserver->SendSystemError(me->clientnum, "You cannot create a lightning "
                                  "if no rain or rain is fading out!");
        return;
    }


    // Queue thunder
    psserver->GetWeatherManager()->QueueNextEvent(0, psWeatherMessage::LIGHTNING, 0, 0, 0, 
                                                  sectorinfo->name, sectorinfo, 
                                                  client->GetActor()->GetEntity()->GetID());

}

void AdminManager::Fog(MsgEntry* me, psAdminCmdMessage& msg, Client *client)
{
    if( !msg.sector.Length() )
    {
        psserver->SendSystemError(me->clientnum, "Syntax: /fog sector [density [[r g b] fade]|off]");
        return;
    }

    // Find the sector
    psSectorInfo *sectorinfo = CacheManager::GetSingleton().GetSectorInfoByName(msg.sector);
    if(!sectorinfo)
    {
        psserver->SendSystemError(me->clientnum,"Sector not found!");
        return;
    }

    // Queue fog
    if(msg.density == -1)
    {
        if ( !sectorinfo->fog_density )
        {
            psserver->SendSystemInfo( me->clientnum, "You need to have fog in this sector for turning it off." );
            return;
        }
        psserver->SendSystemInfo( me->clientnum, "You have turned off the fog." );
        // Reset fog
        psserver->GetWeatherManager()->QueueNextEvent(0, psWeatherMessage::FOG, 0, 0, 0,
                                                      sectorinfo->name, sectorinfo,0,0,0,0);
    }
    else
    {
        // Set fog
        psserver->GetWeatherManager()->QueueNextEvent(0, psWeatherMessage::FOG, 
                                                      msg.density, 0, msg.fade, 
                                                      sectorinfo->name, sectorinfo,0,
                                                      (int)msg.x,(int)msg.y,(int)msg.z); //rgb
    }
    
}

void AdminManager::Rain(MsgEntry* me, psAdminCmdMessage& msg, Client *client)
{    
    if(msg.sector.Length() == 0)
    {
        psserver->SendSystemError(me->clientnum, "Syntax: /rain sector [drops [length [fade]]|start|stop|off]");
        return;
    }

    if (msg.rainDrops < 0 || msg.rainDrops > WEATHER_MAX_RAIN_DROPS)
    {
        psserver->SendSystemError(me->clientnum, "Rain drops should be between %d and %d",
                                  0,WEATHER_MAX_RAIN_DROPS);
        return;
    }

    // Find the sector
    psSectorInfo *sectorinfo = CacheManager::GetSingleton().GetSectorInfoByName(msg.sector);
    if(!sectorinfo)
    {
        psserver->SendSystemError(me->clientnum,"Sector not found!");
        return;
    }

    if (msg.interval == -1)        // Start automatic weather
    {
        if (!sectorinfo->rain_enabled)
        {
            psserver->SendSystemInfo(me->clientnum,"Automatic weather started in sector %s",
                                     msg.sector.GetDataSafe());
            sectorinfo->rain_enabled = true;
            psserver->GetWeatherManager()->StartWeather(sectorinfo);
        }
        else
        {
            psserver->SendSystemInfo(me->clientnum,"The weather was already automatic in sector %s",
                                     msg.sector.GetDataSafe());
        }
        
    }
    else if (msg.interval == -2) // Stop automatic weather
    {
        if ( sectorinfo->rain_enabled )
        {
            psserver->SendSystemInfo(me->clientnum,"Automatic weather stopped in sector %s",
                                 msg.sector.GetDataSafe());
            sectorinfo->rain_enabled = false; // Disable this will prevent any new
            // rain events from beeing started. Will not stop current shower and 
            // that is ok since we don't have a user determined fade factor here.
        }
        else
        {
            psserver->SendSystemInfo(me->clientnum,"The automatic weather was already off in sector %s",
                                     msg.sector.GetDataSafe());
        }       
    }
    else if (msg.interval == -3 || (msg.interval == 0 && msg.rainDrops == 0 && msg.fade == 0 )) //Stop weather if any
    {
        if( !sectorinfo->is_raining) //If it is not raining already then you don't stop anything.
        { 
            psserver->SendSystemInfo( me->clientnum, "You need some weather, first." );
            return;
        }
        else
        {
            psserver->SendSystemInfo( me->clientnum, "The weather was stopped." );
           
            // queue the event
            psserver->GetWeatherManager()->QueueNextEvent(0, psWeatherMessage::RAIN, 
                                                      0, 0,
                                                      0, msg.sector, sectorinfo);
         }
    }
    else
    {
        // queue the event
        psserver->GetWeatherManager()->QueueNextEvent(0, psWeatherMessage::RAIN, 
                                                      msg.rainDrops, msg.interval,
                                                      msg.fade, msg.sector, sectorinfo);
    }
}

void AdminManager::Snow(MsgEntry* me, psAdminCmdMessage& msg, Client *client)
{
    if(msg.sector.Length() == 0)
    {
        psserver->SendSystemError(me->clientnum, "Syntax: /snow sector [flakes [length [fade]]|start|stop |off]");
        return;
    }

    if (msg.rainDrops < 0 || msg.rainDrops > WEATHER_MAX_SNOW_FALKES)
    {
        psserver->SendSystemError(me->clientnum, "Snow flakes should be between %d and %d",
                                  0,WEATHER_MAX_SNOW_FALKES);
        return;
    }

    // Find the sector
    psSectorInfo *sectorinfo = CacheManager::GetSingleton().GetSectorInfoByName(msg.sector);
    if(!sectorinfo)
    {
        psserver->SendSystemError(me->clientnum,"Sector not found!");
        return;
    }
     if (msg.interval == -1 || msg.interval == -2)        // Start automatic weather
    {    
        psserver->SendSystemInfo(me->clientnum,"Automatic weather is not yet implemented for snow",
                                    msg.sector.GetDataSafe());   
        return;
        
    }
    else if (msg.interval == -3 || (msg.interval == 0 && msg.fade == 0 && msg.rainDrops == 0 )) //Stop weather if any
    {
        if( !sectorinfo->is_snowing) //If it is not raining already then you don't stop anything.
        { 
            psserver->SendSystemInfo( me->clientnum, "You need some snow, first." );
            return;
        }
        else
        {
            psserver->SendSystemInfo( me->clientnum, "The snow was stopped." );
           
            // queue the event
            psserver->GetWeatherManager()->QueueNextEvent(0, psWeatherMessage::SNOW, 
                                                      0, 0,
                                                      0, msg.sector, sectorinfo);
         }
    }
    else
    {
        // queue the event
        psserver->GetWeatherManager()->QueueNextEvent(0, psWeatherMessage::SNOW, msg.rainDrops, 
                                                    msg.interval, msg.fade, msg.sector, sectorinfo);
    }
}


void AdminManager::ModifyHuntLocation(MsgEntry* me, psAdminCmdMessage& msg, Client* client, gemObject* object)
{    
    if (!object)
    {
        psserver->SendSystemError(me->clientnum,"You need to specify an item in the world with 'target' or 'eid:#'");
        return;
    }

    psItem* item = object->GetItem();
    if (!item)
    {
        psserver->SendSystemError(me->clientnum,"You can only use modify on items");
        return;
    }

    if (msg.action == "remove")
    {
        if (item->GetScheduledItem())
        {
            item->GetScheduledItem()->Remove();
            psserver->SendSystemInfo(me->clientnum,"Spawn point deleted for %s",item->GetName());
        }

        psserver->GetEntityManager()->RemoveActor(object); // Remove from world
        psserver->SendSystemInfo(me->clientnum,"%s was removed from the world",item->GetName());
        item->Destroy(); // Remove from db
        delete item;
        item = NULL;
    }
    else if (msg.action == "intervals")
    {
        if (msg.interval < 0 || msg.random < 0 || msg.interval != msg.interval || msg.random != msg.random)
        {
            psserver->SendSystemError(me->clientnum,"Invalid intervals specified");
            return;
        }

        // In seconds
        int interval = 1000*msg.interval;
        int random   = 1000*msg.random;

        if (item->GetScheduledItem())
        {
            item->GetScheduledItem()->ChangeIntervals(interval,random);
            psserver->SendSystemInfo(me->clientnum,"Intervals for %s set to %d base + %d max modifier",item->GetName(),msg.interval,msg.random);
        }
        else
            psserver->SendSystemError(me->clientnum,"This item does not spawn; no intervals");
    }
    else
    {
        bool onoff;
        if (msg.setting == "true")
            onoff = true;
        else if (msg.setting == "false")
            onoff = false;
        else
        {
            psserver->SendSystemError(me->clientnum,"Invalid settings");
            return;
        }

        if (msg.action == "pickupable")
        {
            item->SetIsPickupable(onoff);
            psserver->SendSystemInfo(me->clientnum,"%s is now %s",item->GetName(),(onoff)?"pickupable":"un-pickupable");
        }
        else if (msg.action == "transient")
        {
            item->SetIsTransient(onoff);
            psserver->SendSystemInfo(me->clientnum,"%s is now %s",item->GetName(),(onoff)?"transient":"non-transient");
        }
        // TODO: Add more flags
        else
            psserver->SendSystemError(me->clientnum,"Invalid action");
    }
}

void AdminManager::Morph(MsgEntry* me, psAdminCmdMessage& msg, Client *client, Client *targetclient)
{
    if (msg.player == "list" && msg.mesh.IsEmpty())
    {
        static csString list;
        if (list.IsEmpty())  // Construct list once
        {
            // Get array of mounted model directories
            const char* modelsPath = "/planeshift/models/";
            size_t modelsPathLength = strlen(modelsPath);
            csRef<iVFS> vfs = CS_QUERY_REGISTRY(psserver->GetObjectReg(),iVFS);
            csRef<iStringArray> dirPaths = vfs->FindFiles(modelsPath);
            csStringArray dirNames;
            for (size_t i=0; i < dirPaths->Length(); i++)
            {
                csString path = dirPaths->Get(i);
                csString name = path.Slice( modelsPathLength, path.Length()-modelsPathLength-1 );
                if (name.Length() && name.GetAt(0) != '.')
                {
                    if ( vfs->Exists(path+name+".cal3d") )
                        dirNames.Push(name);
                    else
                        Error2("Model dir %s lacks a valid cal3d file!", name.GetData() );
                }
            }

            // Make alphabetized list
            dirNames.Sort();
            list = "Available models:  ";
            for (size_t i=0; i<dirNames.Length(); i++)
            {
                list += dirNames[i];
                if (i < dirNames.Length()-1)
                    list += ", ";
            }
        }

        psserver->SendSystemInfo(me->clientnum, "%s", list.GetData() );
        return;
    }

    if (!targetclient || !targetclient->GetActor())
    {
        psserver->SendSystemError(me->clientnum,"Invalid target for morph");
        return;
    }
    
    gemActor* target = targetclient->GetActor();

    if (msg.mesh == "reset")
    {
        if ( target->ResetMesh() )
            psserver->SendSystemInfo(me->clientnum,"Resetting mesh for %s", targetclient->GetName() );
        else
            psserver->SendSystemError(me->clientnum,"Error resetting mesh for %s!", targetclient->GetName() );

    }
    else
    {
        if ( target->SetMesh(msg.mesh) )
            psserver->SendSystemInfo(me->clientnum,"Setting mesh for %s to %s", targetclient->GetName(), msg.mesh.GetData() );
        else
            psserver->SendSystemError(me->clientnum,"Error setting mesh %s!", msg.mesh.GetData() );
    }
}

void AdminManager::TempSecurityLevel(MsgEntry* me, psAdminCmdMessage& msg, Client *client, Client *target)
{
    if (!target || !target->GetActor())
    {
        psserver->SendSystemError(me->clientnum,"Invalid target");
        return;
    }
    
    // Can only set others to a max of 3 levels below own level (ex: GM4 can set someone to GM1)
    int maxleveltoset = client->GetSecurityLevel() - 3;

    int value;

    msg.setting.Downcase();
    if (msg.setting == "reset")
    {
        int trueSL = GetTrueSecurityLevel( target->GetAccountID() );
        if (trueSL < 0)
        {
            psserver->SendSystemError(client->GetClientNum(), "Cannot reset access level for %s!", target->GetName() );
            return;
        }

        target->SetSecurityLevel(trueSL);
        target->GetActor()->SetSecurityLevel(trueSL);

        psserver->SendSystemOK(target->GetClientNum(),"Your access level was reset");
        if (target != client)
            psserver->SendSystemOK(me->clientnum,"Access level for %s was reset",target->GetName());
            
        if (trueSL)
            psserver->SendSystemInfo(target->GetClientNum(),"Your access level has been reset to %d",trueSL);

        return;
    }
    else if (msg.setting == "player")
        value = 0;
    else if (msg.setting == "tester")
        value = 10;
    else if (msg.setting == "gm")
        value = GM_LEVEL_1;
    else if (msg.setting.StartsWith("gm",true) && msg.setting.Length() == 3)
        value = atoi(msg.setting.Slice(2,1)) + GM_LEVEL_0;
    else
    {
        psserver->SendSystemError(me->clientnum,"Valid settings are:  player, tester, GM, or reset.  GM levels may be specified:  GM1, ... GM5");
        return;
    }

    if (!CacheManager::GetSingleton().GetCommandManager()->GroupExists(value) )
    {
        psserver->SendSystemError(me->clientnum,"Specified access level does not exist!");
        return;
    }

    if ( target == client && value > GetTrueSecurityLevel(target->GetAccountID()) )
    {
        psserver->SendSystemError(me->clientnum,"You cannot upgrade your own level!");
        return;
    }
    else if ( target != client && value > maxleveltoset )
    {
        psserver->SendSystemError(me->clientnum,"Max access level you may set is %d", maxleveltoset);
        return;
    }

    if (target->GetSecurityLevel() == value)
    {
        psserver->SendSystemError(me->clientnum,"%s is already at that access level", target->GetName());
        return;
    }

    if (value == 0)
    {
        psserver->SendSystemInfo(target->GetClientNum(),"Your access level has been disabled for this session.");
    }
    else  // Notify of added/removed commands
    {
        psserver->SendSystemInfo(target->GetClientNum(),"Your access level has been changed for this session. "
                                 " Use \"/admin\" to enable and list available GM commands.");
    }

    if (value < GM_LEVEL_4) // Cannot access this command, but may still reset
    {
        psserver->SendSystemInfo(target->GetClientNum(),"You may do \"/deputize me reset\" at any time to reset yourself. "
                                 " The temporary access level will also expire on logout.");
    }
    

    // Set temporary security level (not saved to DB)
    target->SetSecurityLevel(value);
    target->GetActor()->SetSecurityLevel(value);
    
    // Refresh the label
    target->GetActor()->UpdateProxList(true);

    psserver->SendSystemOK(me->clientnum,"Access level for %s set to %s",target->GetName(),msg.setting.GetData());
    if (target != client)
    {
        psserver->SendSystemOK(target->GetClientNum(),"Your access level was set to %s by a GM",msg.setting.GetData());
    }
    
}

int AdminManager::GetTrueSecurityLevel(int accountID)
{
    Result result(db->Select("SELECT security_level FROM accounts WHERE id='%d'", accountID ));

    if (!result.IsValid() || result.Count() != 1)
        return -99;
    else
        return result[0].GetUInt32("security_level");
}
