/*
 * usermanager.cpp - Author: Keith Fulton
 *
 * Copyright (C) 2001 Atomic Blue (info@planeshift.it, 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 <string.h>
#include <memory.h>

///////////////////////////////////////////////////////////////////////////////
//  CS INCLUDES
///////////////////////////////////////////////////////////////////////////////
#include <csutil/randomgen.h>
#include <csutil/sysfunc.h>
#include <iengine/mesh.h>
#include <iengine/movable.h>
#include <iengine/sector.h>
#include <iutil/object.h>
#include <iengine/region.h>


///////////////////////////////////////////////////////////////////////////////
//  PLANESHIFT INCLUDES
///////////////////////////////////////////////////////////////////////////////
#include "util/serverconsole.h"
#include "util/pserror.h"
#include "util/psconst.h"
#include "util/log.h"

#include "engine/celbase.h"
#include "engine/netpersist.h"
#include "engine/drmessage.h"
#include "client.h"
#include "clients.h"
#include "events.h"
#include "gem.h"
#include "netmanager.h"
#include "entitymanager.h"
#include "marriagemanager.h"
#include "combatmanager.h"
#include "invitemanager.h"
#include "usermanager.h"
#include "adminmanager.h"
#include "commandmanager.h"
#include "psserver.h"
#include "cachemanager.h"
#include "playergroup.h"
#include "globals.h"
#include "progressionmanager.h"
#include "util/eventmanager.h"
#include "netmanager.h"
#include "advicemanager.h"

#include "bulkobjects/pscharacter.h"
#include "bulkobjects/psraceinfo.h"
#include "bulkobjects/psguildinfo.h"
#include "bulkobjects/pscharacterloader.h"
#include "chatmanager.h"

#define RANGE_TO_CHALLENGE 50


class psUserStatRegeneration : public psGameEvent
{
protected:
    UserManager * usermanager;

public:
    psUserStatRegeneration(UserManager *mgr,csTicks ticks);

    virtual void Trigger();  // Abstract event processing function
};

/** A structure to hold the clients that are pending on duel challenges.
 */
class PendingDuelInvite : public PendingInvite
{
public:

    PendingDuelInvite(Client *inviter,
                      Client *invitee,
                      const char *question)
        : PendingInvite( inviter, invitee, true,
                        question,"Accept","Decline",
                        "You have challenged %s to a duel.",
                        "%s has challenged you to a duel.",
                        "%s has accepted your challenge.",
                        "You have accepted %s's challenge.",
                        "%s has declined your challenge.",
                        "You have declined %s's challenge.", psQuestionMessage::duelConfirm)
    {
    }

    virtual ~PendingDuelInvite() {}

    void HandleAnswer(const csString & answer);
};

/***********************************************************************/

UserManager::UserManager(ClientConnectionSet *cs)
{
    clients       = cs;

    psserver->GetEventManager()->Subscribe(this,MSGTYPE_USERCMD,REQUIRE_READY_CLIENT|REQUIRE_ALIVE);
    psserver->GetEventManager()->Subscribe(this,MSGTYPE_MOTDREQUEST,REQUIRE_ANY_CLIENT);
    psserver->GetEventManager()->Subscribe(this,MSGTYPE_CHARDETAILSREQUEST,REQUIRE_READY_CLIENT);
    psserver->GetEventManager()->Subscribe(this,MSGTYPE_CHARDESCUPDATE,REQUIRE_READY_CLIENT);

    psserver->GetEventManager()->Subscribe(this,MSGTYPE_DEATH_EVENT,NO_VALIDATION);
    psserver->GetEventManager()->Subscribe(this,MSGTYPE_TARGET_EVENT,NO_VALIDATION);
}

UserManager::~UserManager()
{
    if (psserver->GetEventManager())
    {
        psserver->GetEventManager()->Unsubscribe(this,MSGTYPE_USERCMD);
        psserver->GetEventManager()->Unsubscribe(this,MSGTYPE_MOTDREQUEST);
        psserver->GetEventManager()->Unsubscribe(this,MSGTYPE_CHARDETAILSREQUEST);
        psserver->GetEventManager()->Unsubscribe(this,MSGTYPE_CHARDESCUPDATE);

        psserver->GetEventManager()->Unsubscribe(this,MSGTYPE_DEATH_EVENT);
        psserver->GetEventManager()->Unsubscribe(this,MSGTYPE_TARGET_EVENT);
    }
}

void UserManager::HandleMOTDRequest(MsgEntry *me,Client *client)
{
    //Sends MOTD and tip

    unsigned int guildID =0;
    //If data isn't loaded, load from db
    if (!client->GetCharacterData())
    {
        Result result(db->Select("SELECT guild_member_of FROM characters WHERE id = '%d'",client->GetPlayerID()));
        if (result.Count() > 0)
            guildID = result[0].GetUInt32(0);
    }
    else
    {
        psGuildInfo* playerGuild = client->GetCharacterData()->GetGuild();

        if (playerGuild)
            guildID = playerGuild->id;
        else
            guildID =0;
    }

    csString tip = "";
    if (CacheManager::GetSingleton().GetTipLength() > 0)
        CacheManager::GetSingleton().GetTipByID(psserver->GetRandom(CacheManager::GetSingleton().GetTipLength()), tip );

    csString motdMsg(psserver->GetMOTD());

    csString guildMotd("");
    csString guildName("");
    psGuildInfo * guild = CacheManager::GetSingleton().FindGuild(guildID);

    if (guild)
    {
        guildMotd=guild->GetMOTD();
        guildName=guild->GetName();
    }

    psMOTDMessage motd(me->clientnum,tip,motdMsg,guildMotd,guildName);
    motd.SendMessage();
    return;
}

void UserManager::HandleUserCommand(MsgEntry *me,Client *client)
{
    if (client->IsFrozen()) //disable most commands
        return;
    
    psUserCmdMessage msg(me);

    Debug3(LOG_USER, client->GetClientNum(),"Received user command: %s from %s\n",
        me->bytes->payload, (const char *)client->GetName());

    if (!msg.valid)
    {
        psserver->SendSystemError(me->clientnum,"Command not supported by server yet.");
        return;
    }

    if (msg.command == "/who")
    {
        Who(msg,client,me->clientnum);
    }
    else if (msg.command == "/buddy")
    {
        Buddy(msg,client,me->clientnum);
    }
    else if (msg.command == "/notbuddy")
    {
        NotBuddy(msg,client, me->clientnum);
    }
    else if (msg.command == "/buddylist")
    {
        BuddyList(client,me->clientnum, UserManager::ALL_PLAYERS);
    }
    else if (msg.command == "/roll")
    {
        RollDice(msg,client,me->clientnum);
    }
    else if (msg.command == "/pos")
    {
        ReportPosition(msg,client,me->clientnum);
    }
    /*else if (msg.command == "/spawn")
    {
        MoveToSpawnPos(msg,client,me->clientnum);*
    }*/
    else if (msg.command == "/unstick")
    {
        MoveToValidPos(msg,client,me->clientnum);
    }
    else if (msg.command == "/attack")
    {
        HandleAttack(msg,client,me->clientnum);
    }
    else if (msg.command == "/stopattack")
    {
        StopAttack(msg,client,me->clientnum);
    }
    else if ( msg.command == "/admin" )
    {
        psserver->GetAdminManager()->Admin(client->GetPlayerID(), me->clientnum, client);
    }
    else if ( msg.command == "/loot" )
    {
        HandleLoot(client);
    }
    else if ( msg.command == "/quests" )
    {
        HandleQuests(client);
    }
    else if ( msg.command == "/train" )
    {
        HandleTraining(client);
    }
    else if (msg.command == "/greet" )
    {
        psSystemMessage newmsg(me->clientnum, MSG_INFO_BASE, "%s greets %s.",
            client->GetActor()->GetName(),
            (client->GetTargetObject())?client->GetTargetObject()->GetName():"everyone" );
        newmsg.Multicast(client->GetActor()->GetMulticastClients(),0,CHAT_SAY_RANGE);

        psUserActionMessage anim(me->clientnum, client->GetActor()->GetEntity()->GetID(),"greet");
        anim.Multicast( client->GetActor()->GetMulticastClients(),0,PROX_LIST_ANY_RANGE );
    }
    else if (msg.command == "/sit" )
    {
        if ((msg.filter == "down" || msg.filter == "") && client->GetActor()->GetMode() == PSCHARACTER_MODE_PEACE)
        {
            client->GetActor()->SetMode(PSCHARACTER_MODE_SIT);

            psUserActionMessage anim(me->clientnum, client->GetActor()->GetEntity()->GetID(), "sit");
            anim.Multicast( client->GetActor()->GetMulticastClients(),0,PROX_LIST_ANY_RANGE );
        }
        else if ((msg.filter == "up" || msg.filter == "") && client->GetActor()->GetMode() == PSCHARACTER_MODE_SIT)
        {
            client->GetActor()->SetMode(PSCHARACTER_MODE_PEACE);

            psUserActionMessage anim(me->clientnum, client->GetActor()->GetEntity()->GetID(), "stand up");
            anim.Multicast( client->GetActor()->GetMulticastClients(),0,PROX_LIST_ANY_RANGE );
        }
    }
    else if (msg.command == "/starttrading")
    {
        client->GetCharacterData()->SetTradingStopped(false);
        psserver->SendSystemInfo(me->clientnum,"You can trade now.");
    }
    else if (msg.command == "/stoptrading")
    {
        client->GetCharacterData()->SetTradingStopped(true);
        psserver->SendSystemInfo(me->clientnum,"You are busy and can't trade.");
    }
    else if ( msg.command == "/assist" )
    {
        Assist( msg, client, me->clientnum );
    }

    /*
    else if (msg.command == "/advisormode")
    {
        Advisor(client, me->clientnum, msg);
    }
    */
    else if (msg.command == "/tip")
    {
        GiveTip(me->clientnum);
    }
    else if (msg.command == "/motd")
    {
        GiveMOTD(me->clientnum);
    }
    else if (msg.command == "/challenge")
    {
        ChallengeToDuel(msg,client);
    }
    else if (msg.command == "/yield")
    {
        YieldDuel(msg,client);
    }
    else if (msg.command == "/duelpoints")
    {
        csString str;
        str.Format("You currently have %1.2f Duel Points.",client->GetCharacterData()->duel_points);
        psserver->SendSystemInfo(me->clientnum, str);
    }
    else if ( msg.command == "/die" )
    {
        gemActor* actor = client->GetActor();
        if ( client->GetDuelClientCount() != 0 )
            YieldDuel(msg, client);
        if (!actor)
            return;
        actor->Kill(actor);
    }

    else if (msg.command == "/marriage")
    {
        if (msg.action == "propose")
        {
            if ( msg.player.IsEmpty() || msg.text.IsEmpty() )
            {
                psserver->SendSystemError( client->GetClientNum(), "Usage: /marriage propose [first name] [message]" );
                return;
            }

            // Send propose message
            psserver->GetMarriageManager()->Propose(client,  msg.player,  msg.text);
        }
        else if (msg.action == "divorce")
        {
            if ( msg.text.IsEmpty() )
            {
                psserver->SendSystemError( client->GetClientNum(), "Usage: /marriage divorce [message]" );
                return;
            }

            // Send divorce prompt
            psserver->GetMarriageManager()->ContemplateDivorce(client,  msg.text);
        }
        else 
        {
            psserver->SendSystemError( client->GetClientNum(), "Usage: /marriage [propose|divorce]" );
        }

    }
    
    else
    {
        psserver->SendSystemError(me->clientnum,"Command not supported by server yet.");
    }
}

void UserManager::HandleCharDetailsRequest(MsgEntry *me,Client *client)
{
    psCharacterDetailsRequestMessage msg(me);

    gemActor *myactor;
    if (!msg.isMe)
    {
        gemObject *target = client->GetTargetObject();
        if (!target)    return;

        myactor = target->GetActorPtr();
        if (!myactor) return;
    }
    else
    {
        myactor = client->GetActor();
        if (!myactor) return;
    }


    psCharacter* charData = myactor->GetCharacterData();
    if (!charData) return;


    SendCharacterDescription(client, charData, false, msg.isSimple, msg.requestor);
}

csString intToStr(int f)
{
    csString s;
    s.Format("%i", f);
    return s;
}

void UserManager::SendCharacterDescription(Client * client, psCharacter * charData, bool full, bool simple, const csString & requestor)
{
    StatSet* playerAttr = client->GetCharacterData()->GetAttributes();
    StatSet* charAttr   = charData->GetAttributes();
    
    bool isSelf = (charData->GetCharacterID() == client->GetCharacterData()->GetCharacterID());

    csString charName = charData->GetCharFullName();
    csString raceName = charData->GetRaceInfo()->name;
    csString desc     = charData->GetDescription();

    if ( !simple && CacheManager::GetSingleton().GetCommandManager()->Validate(client->GetSecurityLevel(), "view stats") )
        full = true;  // GMs can view the stats list

    if (full)
    {
        desc += "\n\n";
        desc += "HP: "+intToStr(int(charData->GetHP()))+" Max HP: "+intToStr(int(charData->GetHitPointsMax()))+"\n";
        desc += "STR: "+intToStr(int(charAttr->GetStat(PSITEMSTATS_STAT_STRENGTH)))+"\n";
        desc += "END: "+intToStr(int(charAttr->GetStat(PSITEMSTATS_STAT_ENDURANCE)))+"\n";
        desc += "AGI: "+intToStr(int(charAttr->GetStat(PSITEMSTATS_STAT_AGILITY)))+"\n";
        desc += "INT: "+intToStr(int(charAttr->GetStat(PSITEMSTATS_STAT_INTELLIGENCE)))+"\n";
        desc += "WIL: "+intToStr(int(charAttr->GetStat(PSITEMSTATS_STAT_WILL)))+"\n";
        desc += "CHA: "+intToStr(int(charAttr->GetStat(PSITEMSTATS_STAT_CHARISMA)))+"\n";
    }

    // No fancy things if simple description is requested
    if ( !simple )
    {
        // Don't guess strength if we can't attack the character or if he's
        //  dead or if we are viewing our own description
        if ( !charData->impervious_to_attack && (charData->GetMode() != PSCHARACTER_MODE_DEAD) && !isSelf )
        {
            if ( playerAttr->GetStat(PSITEMSTATS_STAT_INTELLIGENCE) < 50 )
                desc.AppendFmt( "\n\nYou try to evaluate the strength of %s, but you have no clue.", charName.GetData() );
            else
            {
                // Character's Strength assessment code below.
                static const char* const StrengthGuessPhrases[] =
                    { "won't require any effort to defeat",
                      "is noticeably weaker than you",
                      "won't pose much of a challenge",
                      "is not quite as strong as you",
                      "is about as strong as you",
                      "is somewhat stronger than you",
                      "will pose a challenge to defeat",
                      "is significantly more powerful than you",
                      "may be impossible to defeat" };
    
                bool smart = (playerAttr->GetStat(PSITEMSTATS_STAT_INTELLIGENCE) >= 100);

                int CharsLvl      = charData->GetCharLevel();
                int PlayersLvl    = client->GetCharacterData()->GetCharLevel();
                int LvlDifference = PlayersLvl - CharsLvl;
                int Phrase        = 0;

                if ( LvlDifference >= 50 )
                    Phrase = 0;
                else if ( LvlDifference > 30 )
                    Phrase = 1;
                else if ( LvlDifference > 15 && smart)
                    Phrase = 2;
                else if ( LvlDifference > 5 )
                    Phrase = 3;
                else if ( LvlDifference >= -5 && LvlDifference <= 5 )
                    Phrase = 4;
                else if ( LvlDifference >= -15 )
                    Phrase = 5;
                else if ( LvlDifference >= -30 && smart)
                    Phrase = 6;
                else if ( LvlDifference >= -45 )
                    Phrase = 7;
                else if ( LvlDifference < -50 )
                    Phrase = 8;

                // Enable for Debugging only
                // desc+="\n CharsLvl: "; desc+=CharsLvl; desc+=" | YourLvl: "; desc+=PlayersLvl; desc+="\n";

                desc.AppendFmt( "\n\nYou evaluate that %s %s.", charName.GetData(), StrengthGuessPhrases[Phrase] );
            }
        }

        // Show spouse name if character is married
        if ( charData->GetIsMarried() && !isSelf )
            desc.AppendFmt( "\n\nMarried to: %s", charData->GetSpouseName() );


        // Show owner name if character is a pet
        if ( charData->IsPet() && charData->GetOwnerID() )
        {
            gemActor *owner = psserver->GetEntityManager()->GetGEM()->FindPlayerEntity( charData->GetOwnerID() );
            if (owner)
                desc.AppendFmt( "\n\nA pet owned by: %s", owner->GetName() );
        }
    }

    // Finally send the details message
    psCharacterDetailsMessage detailmsg(client->GetClientNum(), charName, 
        (short unsigned int)charData->GetRaceInfo()->gender, raceName,
        desc, requestor );
    detailmsg.SendMessage();
}

void UserManager::HandleCharDescUpdate(MsgEntry *me,Client *client)
{
    psCharacterDescriptionUpdateMessage descUpdate(me);
    psCharacter* charData = client->GetCharacterData();
    if (!charData)
        return;

    charData->SetDescription(descUpdate.newValue);
    Debug3(LOG_USER, client->GetClientNum(), "Character description updated for %s (%d)\n",charData->GetCharFullName(),client->GetAccountID());
}

void UserManager::HandleDeathEvent(MsgEntry *me)
{
    Debug1(LOG_COMBAT, 0,"UserManager handling Death Event\n");
    // UserManager only stops duels and awards credits on death events
    psDeathEvent evt(me);

    Client *loser=NULL,*winner=NULL;
    loser  = clients->Find( evt.deadActor->GetClientID() );

    if(evt.killer)
        winner = clients->Find( evt.killer->GetClientID() );

    csString loserName("None");
    csString winnerName("the world");

    if(loser && loser->GetActor())
        loserName = loser->GetName();

    if(winner && winner->GetActor())
        winnerName = winner->GetName();

    csString info;
    if ( loser == winner )
    {
        info.Format("%s has just killed themself", loserName.GetData());
    }
    else
    {
        info.Format("%s has just been killed by %s", loserName.GetData(), winnerName.GetData());
    }

    // Remove every attacker
    if (loser)
    {
        for(int i = loser->GetDuelClientCount()-1; i >= 0;i--)
        {
            Client* client = clients->Find(loser->GetDuelClient(i));
            if(!client)
            {
                loser->RemoveDuelClient(loser->GetDuelClient(i));
                continue;
            }

            if(client != winner)
            {
                psserver->SendSystemInfo(client->GetClientNum(),info);
                StopDuel(loser,client,false);
            }
            else
                StopDuel(loser,client,true);

            if(loser->GetTargetObject() == client->GetActor())
            {
                // Stop attacking if we are attacking the killed one
                psserver->combatmanager->StopAttack(loser->GetActor());
            }
        }
    }
}

void UserManager::HandleTargetEvent(MsgEntry *me)
{
    psTargetChangeEvent targetevent(me);

    Client *targeter = NULL;
    Client *targeted = NULL;
    if (targetevent.character)
    {
        Debug2(LOG_USER, targetevent.character->GetClientID(),"UserManager handling target event for %s\n", targetevent.character->GetName());
        targeter = clients->Find( targetevent.character->GetClientID() );
        if (!targeter)
            return;
    }
    if (targetevent.target)
        targeted = clients->Find( targetevent.target->GetClientID() );

    if(targetevent.character->GetMode() == PSCHARACTER_MODE_COMBAT)
        psserver->combatmanager->StopAttack(targeter->GetActor());

    if(!targeted
        && dynamic_cast<gemNPC*>(targetevent.target)
        && targetevent.character->GetMode() == PSCHARACTER_MODE_COMBAT) // NPC?
    {
        if (targeter->IsAllowedToAttack(targetevent.target))
            SwitchAttackTarget( targeter, targeted );

        return;
    }
    else if (!targeted)
        return;

    if (targeted->IsReady() && targeter->IsReady() )
    {
        if (targetevent.character->GetMode() == PSCHARACTER_MODE_COMBAT)
            SwitchAttackTarget( targeter, targeted );
    }
    else
    {
        psserver->SendSystemError(targeter->GetClientNum(),"Target is not ready yet");
    }
}

void UserManager::HandleMessage(MsgEntry *me,Client *client)
{
    switch (me->GetType())
    {
        case MSGTYPE_MOTDREQUEST:
        {
            HandleMOTDRequest(me,client);
            break;
        }
        case MSGTYPE_USERCMD:
        {
            HandleUserCommand(me,client);
            break;
        }
        case MSGTYPE_CHARDETAILSREQUEST:
        {
            HandleCharDetailsRequest(me,client);
            break;
        }
        case MSGTYPE_CHARDESCUPDATE:
        {
            HandleCharDescUpdate(me,client);
            break;
        }
        case MSGTYPE_DEATH_EVENT:
        {
            HandleDeathEvent(me);
            break;
        }
        case MSGTYPE_TARGET_EVENT:
        {
            HandleTargetEvent(me);
            break;
        }
    }
}

void UserManager::Who(psUserCmdMessage& msg, Client* client, int clientnum)
{
    csString message((size_t) 1024);
    csString temp((size_t) 1024);
    csString headerMsg("Players Currently Online");

    if (!msg.filter.IsEmpty())
    {
        size_t pos = msg.filter.FindFirst('%');
        while (pos != (size_t) -1)
        {
            msg.filter.DeleteAt(pos);
            pos = msg.filter.FindFirst('%');
        }

        StrToLowerCase(msg.filter);

        headerMsg.Append(" (Applying Filter: '*");
        headerMsg.Append(msg.filter);
        headerMsg.Append("*')");
    }

    message.Append(headerMsg);
    headerMsg.Append("\n-------------------------------------");

    unsigned count = 0;

    // Guild rank, guild and title should come from acraig's player prop class.
     ClientIterator i(*clients);     
     for (Client* curr = i.First(); curr && count<30; curr = i.Next()) 
    {  
        csString playerName(curr->GetName());
        csString guildTitle;
        csString guildName;
        csString format("%s"); // Player name.

        if (curr->IsSuperClient() || !curr->GetActor())
            continue;

        psGuildInfo* guild = curr->GetActor()->GetGuild();
        if (guild != NULL)
        {
            if (guild->id && (!guild->IsSecret()
                              || guild->id == client->GetGuildID()))
            {
                psGuildLevel* level = curr->GetActor()->GetGuildLevel();
                if (level)
                {
                    format.Append(", %s in %s"); // Guild level title.
                    guildTitle = level->title;
                }
                else
                {
                    format.Append(", %s");
                }
                guildName = guild->name;
            }
        }
        temp.Format(format.GetData(), curr->GetName(), guildTitle.GetData(),
                    guildName.GetData());

        csString lower(temp);
        StrToLowerCase(lower);
        if (!msg.filter.IsEmpty() && lower.FindStr(msg.filter) == (size_t)-1)
            continue;

        // If the message is too big, send in chunks.
        if (temp.Length() + message.Length() > 1000)
        {
            psSystemMessageSafe newmsg(clientnum ,MSG_WHO, message);
            newmsg.SendMessage();

            message.Clear();
        }
        else
            message.Append('\n');
        message.Append(temp);

        count++;
    }

    // Could be about to overflow by now, so check.
    temp.Format("%u shown from %u players online\n", count, clients->Count());
    if (temp.Length() + message.Length() > 1000)
    {
        psSystemMessageSafe newmsg(clientnum ,MSG_WHO, message);
        newmsg.SendMessage();
        message.Clear();
    }
    else
        message.Append('\n');
    message.Append(temp);

    psSystemMessageSafe newmsg(clientnum ,MSG_WHO, message);
    newmsg.SendMessage();
}

void UserManager::StrToLowerCase(csString& str)
{
    for (register size_t i = 0; i < str.Length(); ++i)
        str[i] = tolower(str[i]);
}

void UserManager::Buddy(psUserCmdMessage& msg,Client *client,int clientnum)
{
    msg.player = NormalizeCharacterName(msg.player);

    if (msg.player.Length() == 0)
    {
        psserver->SendSystemError(clientnum,"The character name of your buddy must be specified.");
        return;
    }
    if (client->GetCharacterData()==NULL)
    {
        Error3("Client for account '%s' attempted to add buddy '%s' but has no character data!",client->GetName(),msg.player.GetData());
        return;
    }


    unsigned int selfid=client->GetCharacterData()->GetCharacterID();

    bool searchNPCs = false;
    unsigned int buddyid=psServer::CharacterLoader.FindCharacterID(msg.player.GetData(), searchNPCs);

    if (buddyid==0)
    {
        psserver->SendSystemError(clientnum,"Could not add buddy: Character '%s' not found.", msg.player.GetData());
        return;
    }

    if ( !client->GetCharacterData()->AddBuddy( buddyid, msg.player ) )
    {
        psserver->SendSystemError(clientnum,"%s could not be added to buddy list.",(const char *)msg.player);
        return;
    }

    Client* buddyClient = clients->FindPlayer( buddyid );
    if ( buddyClient && buddyClient->IsReady() )
    {
        buddyClient->GetCharacterData()->BuddyOf( selfid );
    }



    if (!psserver->AddBuddy(selfid,buddyid))
    {
        psserver->SendSystemError(clientnum,"%s is already on your buddy list.",(const char *)msg.player);
        return;
    }

    BuddyList( client, clientnum, true );

    psserver->SendSystemInfo(clientnum,"%s has been added to your buddy list.",(const char *)msg.player);
}


void UserManager::NotBuddy(psUserCmdMessage& msg,Client *client,int clientnum)
{
    msg.player = NormalizeCharacterName(msg.player);

    if (msg.player.Length() == 0)
    {
        psserver->SendSystemError(clientnum,"The character name of your buddy must be specified.");
        return;
    }
    if (client->GetCharacterData()==NULL)
    {
        Error3("Client for account '%s' attempted to remove buddy '%s' but has no character data!",client->GetName(),msg.player.GetData());
        return;
    }
    unsigned int selfid=client->GetCharacterData()->GetCharacterID();

    bool searchNPCs = false;
    unsigned int buddyid=psServer::CharacterLoader.FindCharacterID(msg.player.GetData(), searchNPCs);
    if (buddyid==0)
    {
        psserver->SendSystemError(clientnum,"Could not remove buddy: Character '%s' not found.", msg.player.GetData());
        return;
    }

    client->GetCharacterData()->RemoveBuddy( buddyid );
    Client* buddyClient = clients->FindPlayer( buddyid );
    if ( buddyClient )
    {
        psCharacter* buddyChar = buddyClient->GetCharacterData();
        if (buddyChar)
            buddyChar->NotBuddyOf( selfid );
    }

    if (!psserver->RemoveBuddy(selfid,buddyid))
    {
        psserver->SendSystemError(clientnum,"%s is not on your buddy list.",(const char *)msg.player);
        return;
    }
    BuddyList( client, clientnum, true );

    psserver->SendSystemInfo(clientnum,"%s has been removed from your buddy list.",(const char *)msg.player);
}

void UserManager::BuddyList(Client *client,int clientnum,bool filter)
{
    psCharacter *chardata=client->GetCharacterData();
    if (chardata==NULL)
    {
        Error2("Client for account '%s' attempted to display buddy list but has no character data!",client->GetName());
        return;
    }

    int totalBuddies = (int)chardata->buddyList.Length(); //psBuddyListMsg should have as parameter a size_t. This is temporary.


    psBuddyListMsg mesg( clientnum, totalBuddies );
    for ( int i = 0; i < totalBuddies; i++ )
    {
        mesg.AddBuddy( i, chardata->buddyList[i].name, (clients->Find(chardata->buddyList[i].name)? true : false) );
    }

    mesg.Build();
    mesg.SendMessage();
}


void UserManager::NotifyBuddies(Client * client, bool logged_in)
{
    csString name (client->GetName());

    for (size_t i=0; i< client->GetCharacterData()->buddyOfList.Length(); i++)
    {
        Client *buddy = clients->FindPlayer( client->GetCharacterData()->buddyOfList[i] );  // name of player buddy

        if (buddy)  // is buddy online at the moment?  if so let him know buddy just logged on
        {
            psBuddyStatus status( buddy->GetClientNum(),  name , logged_in );
            status.SendMessage();

            if (logged_in)
            {
                psserver->SendSystemInfo(buddy->GetClientNum(),"%s just joined Planeshift",client->GetName());
            }
            else
            {
                psserver->SendSystemInfo(buddy->GetClientNum(),"%s has quit",client->GetName());
            }
        }
    }
}

void UserManager::RollDice(psUserCmdMessage& msg,Client *client,int clientnum)
{
    int total=0;

    if (msg.dice > 10)
        msg.dice = 10;
    if (msg.sides > 10000)
        msg.sides = 10000;

    if ( msg.dice < 1 )
        msg.dice = 1;
    if ( msg.sides < 1 )
        msg.sides = 1;

    for (int i = 0; i<msg.dice; i++)
    {
        // must use msg.sides instead of msg.sides-1 because rand never actually
        // returns max val, and int truncation never results in max val as a result
        total = total + psserver->rng->Get(msg.sides) + 1;
    }

    if (msg.dice > 1)
    {
        psSystemMessage newmsg(clientnum,MSG_INFO_BASE,
            "Player %s has rolled %d %d-sided dice for a %d.",
            client->GetName(),
            msg.dice,
            msg.sides,
            total);

        newmsg.Multicast(client->GetActor()->GetMulticastClients(),0, 10);
    }
    else
    {
        psSystemMessage newmsg(clientnum,MSG_INFO_BASE,
            "Player %s has rolled a %d-sided die for a %d.",
            client->GetName(),
            msg.sides,
            total);

        newmsg.Multicast(client->GetActor()->GetMulticastClients(),0, 10);
    }
}


void UserManager::ReportPosition(psUserCmdMessage& msg,Client *client,int clientnum)
{
    gemObject *object = NULL;
    bool self = true;

    bool extras = CacheManager::GetSingleton().GetCommandManager()->Validate(client->GetSecurityLevel(), "pos extras");

    // Allow GMs to get other players' and entities' locations
    if (extras && msg.player.Length())
    {
        self = false;
        Client* c = psserver->GetAdminManager()->FindPlayerClient(msg.player,0);
        if (c) object = (gemObject*)c->GetActor();
        if (!object) object = psserver->GetAdminManager()->FindObjectByString(msg.player);
    }
    else
        object = client->GetActor();

    if (object)
    {
        csVector3 pos;
        iSector* sector = 0;
        float angle;
       
        object->GetPosition(pos, angle, sector);

        csString sector_name = (sector) ? sector->QueryObject()->GetName() : "(null)";
        
        csString name;
        if (self) name = "Your";
        else name.Format("%s's",object->GetName());

        // Report extra info to GMs (players will use skills to determine correct direction)
        if (extras)
        {
            // Get the iRegion this sector belongs to
            csRef<iRegion> region = SCF_QUERY_INTERFACE(sector->QueryObject()->GetObjectParent(), iRegion);
            csString region_name = (region) ? region->QueryObject()->GetName() : "(null)";

            int degrees = (int)(angle*180.0/PI);
            psserver->SendSystemInfo(clientnum,
                                    "%s current position is %1.2f,%1.2f,%1.2f angle: %d in sector: %s, region: %s",
                                    name.GetData(), pos.x, pos.y, pos.z, degrees,
                                    sector_name.GetData(), region_name.GetData() );
        }
        else
        {
            psserver->SendSystemInfo(clientnum,"%s current position is %1.2f,%1.2f,%1.2f in sector: %s",
                                     name.GetData(), pos.x, pos.y, pos.z, sector_name.GetData() );
        }
    }
}

/*void UserManager::MoveToSpawnPos(psUserCmdMessage& msg, Client *client,
                                 int clientnum)
{
    gemActor *actor = client->GetActor();
    if (!actor)
        return;

    StopAllCombat(client, actor);
    LogStuck(client, actor);

    if (actor->MoveToSpawnPos())
    {
        csVector3 pos;
        float yrot;
        iSector* sector;

        actor->GetPosition(pos, yrot, sector);
        psserver->SendSystemInfo(clientnum,
                                 "This command should not be used, please use "
                                 "/unstick instead. Moving back to spawn point"
                                 " in sector %s...",
                                 sector->QueryObject()->GetName());
    }
    else
    {
        psserver->SendSystemInfo(clientnum,
                                 "Cannot move back to spawn point in null "
                                 "sector.  Please contact Planeshift support.");
    }
}

*/

void UserManager::MoveToValidPos(psUserCmdMessage& msg, Client *client,
                                 int clientnum)
{
    gemActor *actor = client->GetActor();
    if (!actor)
        return;

    StopAllCombat(client, actor);
    LogStuck(client, actor);

    if (actor->MoveToValidPos())
    {
        csVector3 pos;
        iSector* sector;

        actor->GetPosition(pos, sector);
        psserver->SendSystemInfo(clientnum,
                                 "Moving back to valid position in sector %s...",
                                 sector->QueryObject()->GetName());
    }
}

void UserManager::LogStuck(Client* client, gemActor* actor)
{
    csVector3 pos;
    float yrot;
    iSector* sector;

    actor->GetPosition(pos, yrot, sector);
    psRaceInfo* race = actor->GetCharacterData()->GetRaceInfo();

    csString buffer;
    buffer.Format("%s, %s, %d, %s, %.3f, %.3f, %.3f, %.3f", client->GetName(),
                  race->name.GetDataSafe(), race->gender,
                  sector->QueryObject()->GetName(), pos.x, pos.y, pos.z, yrot);
    psserver->GetLogCSV()->Write(CSV_STUCK, buffer);
}

void UserManager::StopAllCombat(Client *client, gemActor *actor)
{
    // If we are in a duel, yield.
    for (int i = client->GetDuelClientCount() - 1; i >= 0; i--)
    {
        Client* duel = clients->Find(client->GetDuelClient(i));
        if (!duel || !duel->GetActor())
            continue;

        StopDuel(client, duel);
        if (duel->GetTargetObject() == actor)
        {
            // Stop attacking if we are attacking the spawner.
            psserver->combatmanager->StopAttack(duel->GetActor());
        }

        psserver->SendSystemInfo(client->GetClientNum(), "You yielded to %s",
                                 duel->GetName());
        psserver->SendSystemInfo(duel->GetClientNum(), "%s yielded to you",
                                 client->GetName());
    }

    // Stop attacking.
    psserver->combatmanager->StopAttack(actor);
}

void UserManager::HandleAttack(psUserCmdMessage& msg,Client *client,int clientnum)
{
    Attack(msg.mode, client, clientnum);
}

void UserManager::Attack(int mode, Client *client,int clientnum)
{
    if (!client->IsAlive() || client->IsFrozen())
    {
        psserver->SendSystemError(client->GetClientNum(),"You are dead, you cannot fight now");
        return;
    }

    gemObject *target = client->GetTargetObject();
    if ( ! target )
    {
        psserver->SendSystemError(clientnum,"You do not have a target selected.");
        return;
    }
    if ( target->IsAlive() == false )
    {
        psserver->SendSystemError(clientnum,"%s is already dead", (const char*)target->GetName() );
        return;
    }

    if (client->IsAllowedToAttack(target))
        psserver->combatmanager->AttackSomeone(client->GetActor(), target, mode );
}

void UserManager::StopAttack(psUserCmdMessage& ,Client *client,int )
{
    // Stop attack if something is targeted.
    psserver->combatmanager->StopAttack(client->GetActor() );
}

void UserManager::Assist( psUserCmdMessage& msg, Client* client, int clientnum )
{
    Client* targetClient = NULL;

    if (!client->IsAlive())
    {
        psserver->SendSystemError(client->GetClientNum(),"You are dead, you cannot assist anybody now");
        return;
    }

    // If the player doesn't provide an argument, use the players current
    // target instead.
    if ( msg.player == "" )
    {
        int currentTarget = client->GetTargetClientID();
        if ( currentTarget == -1 )
        {
            psserver->SendSystemInfo( clientnum, "You have no target selected.");
            return;
        }

        if ( currentTarget == 0 )
        {
            psserver->SendSystemInfo( clientnum, "You can assist other players only.");
            return;
        }

        targetClient = clients->Find( currentTarget );
        if ( targetClient == NULL )
        {
            psserver->SendSystemInfo( clientnum, "Internal error - client not found.");
            return;
        }
    }
    else
    {
        csString playerName = NormalizeCharacterName(msg.player);
        targetClient = clients->Find( playerName );

        if ( !targetClient )
        {
            psserver->SendSystemInfo( clientnum,"Specified player is not online." );
            return;
        }
    }

    if ( targetClient == client )
    {
        psserver->SendSystemInfo( clientnum,"You cannot assist yourself." );
        return;
    }

    if ( !client->GetActor()->IsNear( targetClient->GetActor(), ASSIST_MAX_DIST ) )
    {
        psserver->SendSystemInfo(clientnum,
                                "Specified player is too far away." );
        return;
    }

    gemObject* targetObject = targetClient->GetTargetObject();
    if(!targetObject)
    {
        psserver->SendSystemInfo(clientnum,
                                "Specified player has no target selected." );
        return;
    }

    client->SetTargetObject( targetObject, true );
}

void UserManager::UserStatRegeneration()
{
    psserver->GetEntityManager()->GetGEM()->UpdateAllStats();

    // Push a new event
    psUserStatRegeneration* event;
    nextUserStatRegeneration += 1000;
    event = new psUserStatRegeneration(this,nextUserStatRegeneration);
    psserver->GetEventManager()->Push(event);
}

void UserManager::Ready()
{
    nextUserStatRegeneration = csGetTicks();
    UserStatRegeneration();
}

/**
 * Check target dead
 * Check target lootable by this client
 * Return lootable items list if present
 */
void UserManager::HandleLoot(Client *client)
{
    int clientnum = client->GetClientNum();

    if (!client->IsAlive())
    {
        psserver->SendSystemError(client->GetClientNum(),"You are dead, you cannot loot now");
        return;
    }

    gemObject *target = client->GetTargetObject();
    if (!target)
    {
        psserver->SendSystemError(clientnum,"You don't have a target selected.");
        return;
    }

    if (target->IsAlive())
    {
        psserver->SendSystemError(clientnum, "You can't loot person that is alive");
        return;
    }

    // Check target lootable by this client
    gemNPC *npc = target->GetNPCPtr();
    if (!npc)
    {
        psserver->SendSystemError(clientnum, "You can loot NPCs only");
        return;
    }

    if (!npc->IsLootableClient(client->GetClientNum()))
    {
        Debug2(LOG_USER, client->GetClientNum(),"Client %d tried to loot mob that wasn't theirs.\n",client->GetClientNum() );
        psserver->SendSystemError(client->GetClientNum(),"You are not allowed to loot %s.",target->GetName() );
        return;
    }

    // Check to make sure loot is in range.
    if (client->GetActor()->RangeTo(target) > RANGE_TO_LOOT)
    {
        psserver->SendSystemError(client->GetClientNum(), "Too far away to loot %s", target->GetName() );         return;
    }
    

    // Return lootable items list if present
    psCharacter *chr = target->GetCharacterData();
    if (chr)
    {
        // Send items to looting player
        psLootMessage loot;
        size_t count = chr->GetLootItems(loot,
                                      target->GetEntity()->GetID(),
                                      client->GetClientNum() );
        if (count)
        {
            Debug3(LOG_LOOT, client->GetClientNum(),"Sending %u loot items to %s.\n",count,client->GetActor()->GetName() );
            loot.SendMessage();
        }
        else if(!count && !chr->GetLootMoney())
        {
            Debug1(LOG_LOOT, client->GetClientNum(),"Mob doesn't have loot.\n");
            psserver->SendSystemError(client->GetClientNum(),"%s has nothing to be looted.",target->GetName() );
        }

        // Split up money among group
        int money = chr->GetLootMoney();
        if (money)
        {
            Debug2(LOG_LOOT, client->GetClientNum(),"Splitting up %d money.\n", money);
            csRef<PlayerGroup> group = client->GetActor()->GetGroup();
            int remainder,each;
            if (group)
            {
                remainder = money %(int) group->GetMemberCount();
                each      = money /(int) group->GetMemberCount();
                psMoney eachmoney = psMoney(each).Normalized();
                psMoney remmoney  = psMoney(remainder).Normalized();
                csString remstr   = remmoney.ToUserString();
                csString eachstr  = eachmoney.ToUserString();

                if(each)
                {
                    psSystemMessage loot(client->GetClientNum(),MSG_LOOT,"Everyone has looted %s.",eachstr.GetData() );
                    client->GetActor()->SendGroupMessage(loot.msg);

                    npc->AdjustMoneyLootClients(eachmoney);
                }
                if(remainder)
                {
                    psSystemMessage loot2(client->GetClientNum(),MSG_LOOT,"You have looted an extra %s.",remstr.GetData() );
                    loot2.SendMessage();

                    client->GetCharacterData()->AdjustMoney(remmoney);
                }
            }
            else
            {
                psMoney m = psMoney(money).Normalized();
                csString mstr = m.ToUserString();

                psSystemMessage loot(client->GetClientNum(),MSG_LOOT,"You have looted %s.",mstr.GetData() );
                loot.SendMessage();

                client->GetCharacterData()->AdjustMoney(m);
            }
            chr->AddLootMoney(-money);  // zero out loot now
        }
        else
            Debug1(LOG_LOOT, client->GetClientNum(),"Mob has no money to loot.\n");
    }
}

void UserManager::HandleQuests(Client *client)
{
    psQuestListMessage quests;
    size_t count = client->GetActor()->GetCharacterData()->GetAssignedQuests(quests,client->GetClientNum() );

    if (count)
    {
        Debug3(LOG_QUESTS, client->GetClientNum(),"Sending %u quests to player %s.\n",count,client->GetName() );
        quests.SendMessage();
    }
    else
    {
        Debug1(LOG_QUESTS, client->GetClientNum(),"Client has no quests yet.\n");
        psserver->SendSystemError(client->GetClientNum(),"You have no active quests right now.",client->GetName() );
    }
}

void UserManager::HandleTraining(Client *client)
{
    if (!client->IsAlive())
    {
        psserver->SendSystemError(client->GetClientNum(),"You are dead, you cannot train your skills now");
        return;
    }

    // Check target is a Trainer
    gemObject *target = client->GetTargetObject();
    if (!target || !target->GetActorPtr())
    {
        psserver->SendSystemInfo(client->GetClientNum(),
                            "No target selected for training!");
        return;
    }

    // Check range
    if (client->GetActor()->RangeTo(target) > RANGE_TO_SELECT)
    {
        psserver->SendSystemInfo(client->GetClientNum(),
                            "You are not in range to train with %s.",target->GetCharacterData()->GetCharName());
        return;
    }

    if (!target->IsAlive())
    {
        psserver->SendSystemInfo(client->GetClientNum(),
                            "Can't train with a dead trainer!");
        return;
    }

    psCharacter * trainer = target->GetCharacterData();
    if (!trainer->IsTrainer())
    {
        psserver->SendSystemInfo(client->GetClientNum(),
                            "%s isn't a trainer.",target->GetCharacterData()->GetCharName());
        return;
    }

    psserver->GetProgressionManager()->StartTraining(client,trainer);
}


void UserManager::GiveTip(int id)
{
    unsigned int max=CacheManager::GetSingleton().GetTipLength();
    unsigned int rnd = psserver->rng->Get(max);

    csString tip;
    CacheManager::GetSingleton().GetTipByID(rnd, tip);
    psserver->SendSystemInfo(id,tip.GetData());
}

void UserManager::GiveMOTD(int id)
{
    psserver->SendSystemInfo(id,psserver->GetMOTD());
}


void UserManager::ChallengeToDuel(psUserCmdMessage& msg,Client *client)
{
    int clientnum = client->GetClientNum();

    if (!client->IsAlive())
    {
        psserver->SendSystemError(client->GetClientNum(),"You are dead, you cannot challenge opponents now");
        return;
    }

    // Check target dead
    gemObject *target = client->GetTargetObject();
    if (!target)
    {
        psserver->SendSystemError(clientnum,"You don't have another player targeted.");
        return;
    }

    Client * targetClient = psserver->GetNetManager()->GetClient(target->GetClientID());
    if (!targetClient)
    {
        psserver->SendSystemError(clientnum, "You can challenge other players only");
        return;
    }

    // Distance
    if(client->GetActor()->RangeTo(target) > RANGE_TO_CHALLENGE)
    {
        psserver->SendSystemError(clientnum,target->GetName() + csString(" is too far away"));
        return;
    }

    if (!target->IsAlive())
    {
        psserver->SendSystemError(clientnum, "You can't challenge a dead person");
        return;
    }

    if (targetClient == client)
    {
        psserver->SendSystemError(clientnum, "You can't challenge yourself.");
        return;
    }

    // Check for pre-existing duel with this person
    if (client->IsDuelClient(target->GetClientID()))
    {
        psserver->SendSystemError(clientnum, "You have already agreed on this duel !");
        return;
    }

    // Challenge
    csString question;
    question.Format("%s has challenged you to a duel!  Click on Accept to allow the duel or Reject to ignore the challenge.",
                    client->GetName() );
    PendingDuelInvite *invite = new PendingDuelInvite(client,
                                                      targetClient,
                                                      question);
    psserver->questionmanager->SendQuestion(invite);
}

void UserManager::AcceptDuel(PendingDuelInvite *invite)
{
    Client * inviteeClient = clients->Find(invite->clientnum);
    Client * inviterClient = clients->Find(invite->inviterClientNum);

    if (inviteeClient!=NULL  &&  inviterClient!=NULL)
    {
        inviteeClient->AddDuelClient( invite->inviterClientNum );
        inviterClient->AddDuelClient( invite->clientnum );

        // Target eachother and update their GUIs
        inviteeClient->SetTargetObject(inviterClient->GetActor(),true);
        inviterClient->SetTargetObject(inviteeClient->GetActor(),true);
    }
}

void PendingDuelInvite::HandleAnswer(const csString & answer)
{
    Client * client = psserver->GetConnections()->Find(clientnum);
    if (!client  ||  client->IsDuelClient(inviterClientNum))
        return;

    PendingInvite::HandleAnswer(answer);
    if (answer == "yes")
        psserver->usermanager->AcceptDuel(this);
}

void UserManager::StopDuel(Client *loser, Client *winner,bool award)
{
    // Remove the duel from both sides.
    loser->RemoveDuelClient(winner->GetClientNum() );
    loser->GetActor()->SetMode(PSCHARACTER_MODE_PEACE);
    winner->RemoveDuelClient(loser->GetClientNum());
    winner->GetActor()->SetMode(PSCHARACTER_MODE_PEACE);

    if(award)
    {
        // Award duel points
        float delta = loser->GetCharacterData()->GetSkills()->GetBestSkillValue(true);
        if (delta)
        {
            float div = winner->GetCharacterData()->GetSkills()->GetBestSkillValue(true);
            delta /= (div)?div:1;
        }

        csString str;
        str.Format("%1.2f Duel points from %s awarded to %s.",
                delta,loser->GetName(),winner->GetName() );

        Debug1(LOG_DUELS, loser->GetClientNum(),str);

        winner->GetCharacterData()->duel_points += delta;
        loser->GetCharacterData()->duel_points  -= delta;

        /*psserver->SendSystemInfo(loser->GetClientNum(),str);
        psserver->SendSystemInfo(winner->GetClientNum(),str);*/

        str.Format("You've lost %1.2f Duel Points!\nYou currently have %1.2f Duel Points.",
                delta,
                loser->GetCharacterData()->duel_points);
        psserver->SendSystemInfo(loser->GetClientNum(), str);
        
        str.Format("You've gained %1.2f Duel Points!\nYou currently have %1.2f Duel Points.",
                delta,
                winner->GetCharacterData()->duel_points);
        psserver->SendSystemInfo(winner->GetClientNum(), str);

        psserver->SendSystemOK(winner->GetClientNum(),"You've won the duel against %s!",loser->GetName());
        psserver->SendSystemError(loser->GetClientNum(),"You've lost the duel against %s!",winner->GetName());

        if (delta)
        {
            db->Command("update characters set duel_points=%1.2f where id=%d",
                        winner->GetCharacterData()->duel_points,
                        winner->GetCharacterData()->characterid);

            db->Command("update characters set duel_points=%1.2f where id=%d",
                        loser->GetCharacterData()->duel_points,
                        loser->GetCharacterData()->characterid);
        }
    }
}

void UserManager::YieldDuel(psUserCmdMessage& msg,Client *client)
{
    int target = client->GetTargetClientID();
    if (!target)
    {
        psserver->SendSystemError(client->GetClientNum(),"You don't have another player targeted.");
        return;
    }

    if (!client->IsDuelClient(target))
    {
        psserver->SendSystemError(client->GetClientNum(),"You are not in a duel with your target.");
        return;
    }


    Client *targetClient = psserver->GetNetManager()->GetClient(target);
    if (targetClient == NULL)
        return;

    StopDuel(client,targetClient);
}

void UserManager::SwitchAttackTarget(Client *targeter, Client *targeted )
{
    // If we switch targets while in combat, start attacking the new
    // target, unless we no longer have a target.

    csString attackCmd;
    attackCmd.Format("/attack %i", targeter->GetCharacterData()->GetCombatStance());
    psUserCmdMessage msg( attackCmd.GetData() );
    if( targeted )
        Attack( msg.mode, targeter, targeter->GetClientNum() );
    else
        StopAttack( msg, targeter, targeter->GetClientNum() );
}

/*---------------------------------------------------------------------*/

psUserStatRegeneration::psUserStatRegeneration(UserManager *mgr, csTicks ticks)
: psGameEvent(ticks,0,"psUserStatRegeneration")
{
    usermanager = mgr;
}

void psUserStatRegeneration::Trigger()
{
    usermanager->UserStatRegeneration();
}
