/*
 * cmdusers.h - 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 <iengine/movable.h>
#include <iengine/mesh.h>
#include <imesh/sprite3d.h>
#include <csgeom/math3d.h>

#include "globals.h"
#include "cmdusers.h"
#include "net/cmdhandler.h"
#include "net/msghandler.h"

#include "pscelclient.h"
#include "gui/chatwindow.h"
#include "paws/pawsmanager.h"
#include "gui/pawsdetailwindow.h"
#include "gui/pawsinfowindow.h"
#include "gui/inventorywindow.h"
#include "gui/pawsactivemagicwindow.h"
#include "gui/pawspetstatwindow.h"
#include "gui/pawscontrolwindow.h"
#include "gui/psmainwidget.h"
#include "gui/pawsgameboard.h"
#include "engine/netpersist.h"
#include "util/strutil.h"
#include "modehandler.h"
#include "net/messages.h"
#include "net/npcmessages.h"


psUserCommands::psUserCommands(MsgHandler* mh,CmdHandler *ch,iObjectRegistry* obj)
  : psCmdBase(mh,ch,obj)
{
//    msgqueue->Subscribe(MSGTYPE_CHAT,this);

    cmdsource->Subscribe("/who",this);       // list players on server
    cmdsource->Subscribe("/buddy",this);     // add named player to buddy list
    cmdsource->Subscribe("/notbuddy",this);  // remove named player from buddy list
    cmdsource->Subscribe("/buddylist",this); // display current buddy list
    cmdsource->Subscribe("/roll",this);
    cmdsource->Subscribe("/pos",this);
    //cmdsource->Subscribe("/spawn",this);
    cmdsource->Subscribe("/unstick",this);
    cmdsource->Subscribe("/attack",this);
    cmdsource->Subscribe("/stopattack",this);
    cmdsource->Subscribe("/admin", this);
    cmdsource->Subscribe("/greet", this);
    cmdsource->Subscribe("/stoptrading", this);
    cmdsource->Subscribe("/starttrading", this);
    cmdsource->Subscribe("/buy", this);
    cmdsource->Subscribe("/sell", this);
    cmdsource->Subscribe("/trade",this);
    cmdsource->Subscribe("/give",this);
    cmdsource->Subscribe("/assist", this);
    cmdsource->Subscribe("/ignore", this);                             
    cmdsource->Subscribe("/add_ignore", this);
    cmdsource->Subscribe("/remove_ignore", this);                         
    cmdsource->Subscribe("/cast", this);
    cmdsource->Subscribe("/away", this);
    cmdsource->Subscribe("/loot", this);
    cmdsource->Subscribe("/clear", this);
    cmdsource->Subscribe("/advisormode", this);
    cmdsource->Subscribe("/list_advice_requests", this);
    cmdsource->Subscribe("/advisor", this);
    cmdsource->Subscribe("/advice", this);
    cmdsource->Subscribe("/train", this);
    cmdsource->Subscribe("/quests", this);
    cmdsource->Subscribe("/use", this);
    cmdsource->Subscribe("/dig", this);
    cmdsource->Subscribe("/target", this);
    cmdsource->Subscribe("/target_context", this);
    cmdsource->Subscribe("/tip", this);
    cmdsource->Subscribe("/motd", this);
    cmdsource->Subscribe("/challenge", this);
    cmdsource->Subscribe("/yield", this);
    cmdsource->Subscribe("/picklock",this);
    cmdsource->Subscribe("/targetinfo",this);
    cmdsource->Subscribe("/equip", this);
    cmdsource->Subscribe("/dequip", this);
    cmdsource->Subscribe("/show_active_magic", this);
    cmdsource->Subscribe("/duelpoints", this);
    cmdsource->Subscribe("/pet", this);
    cmdsource->Subscribe("/sit", this);
    cmdsource->Subscribe("/pickup", this);
    cmdsource->Subscribe("/craft", this);
    cmdsource->Subscribe("/die", this);
    cmdsource->Subscribe("/combine", this);
    cmdsource->Subscribe("/show", this);
    cmdsource->Subscribe("/marriage", this);
    cmdsource->Subscribe("/repair", this);
    cmdsource->Subscribe("/game", this);
}

psUserCommands::~psUserCommands()
{
//    msgqueue->Unsubscribe(MSGTYPE_CHAT,this);

    cmdsource->Unsubscribe("/who",      this);      
    cmdsource->Unsubscribe("/buddy",    this);    
    cmdsource->Unsubscribe("/notbuddy", this); 
    cmdsource->Unsubscribe("/buddylist",this);
    cmdsource->Unsubscribe("/roll",     this);
    cmdsource->Unsubscribe("/pos",      this);
    //cmdsource->Unsubscribe("/spawn",    this);
    cmdsource->Unsubscribe("/unstick",  this);
    cmdsource->Unsubscribe("/attack",   this);
    cmdsource->Unsubscribe("/stopattack",this);
    cmdsource->Unsubscribe("/admin",    this);
    cmdsource->Unsubscribe("/greet",    this);
    cmdsource->Unsubscribe("/stoptrading", this);
    cmdsource->Unsubscribe("/starttrading", this);
    cmdsource->Unsubscribe("/buy",    this);
    cmdsource->Unsubscribe("/sell",   this);
    cmdsource->Unsubscribe("/trade",  this);
    cmdsource->Unsubscribe("/give",   this);
    cmdsource->Unsubscribe("/assist", this );
    cmdsource->Unsubscribe("/ignore", this );
    cmdsource->Unsubscribe("/add_ignore", this );
    cmdsource->Unsubscribe("/remove_ignore", this );    
    cmdsource->Unsubscribe("/cast",   this );
    cmdsource->Unsubscribe("/away",   this );
    cmdsource->Unsubscribe("/loot",   this );
    cmdsource->Unsubscribe("/clear",  this );
    cmdsource->Unsubscribe("/advisormode",          this );
    cmdsource->Unsubscribe("/list_advice_requests", this );
    cmdsource->Unsubscribe("/advisor",              this );
    cmdsource->Unsubscribe("/advice",               this );
    cmdsource->Unsubscribe("/train",     this );
    cmdsource->Unsubscribe("/quests",    this );
    cmdsource->Unsubscribe("/use",       this );
    cmdsource->Unsubscribe("/dig",       this );
    cmdsource->Unsubscribe("/target",    this);
    cmdsource->Unsubscribe("/target_context",this);
    cmdsource->Unsubscribe("/tip",       this );
    cmdsource->Unsubscribe("/motd",      this );
    cmdsource->Unsubscribe("/challenge", this );
    cmdsource->Unsubscribe("/yield",     this );
    cmdsource->Unsubscribe("/picklock",  this);
    cmdsource->Unsubscribe("/targetinfo",this);
    cmdsource->Unsubscribe("/equip",     this);
    cmdsource->Unsubscribe("/dequip",    this);
    cmdsource->Unsubscribe("/show_active_magic", this);
    cmdsource->Unsubscribe("/duelpoints", this);
    cmdsource->Unsubscribe("/pet",        this);
    cmdsource->Unsubscribe("/sit",        this);
    cmdsource->Unsubscribe("/pickup",     this);
    cmdsource->Unsubscribe("/craft",      this );
    cmdsource->Unsubscribe("/die",        this);
    cmdsource->Unsubscribe("/combine",    this);
    cmdsource->Unsubscribe("/show",       this);
    cmdsource->Unsubscribe("/marriage",   this);
    cmdsource->Unsubscribe("/repair",     this);
    cmdsource->Unsubscribe("/game",       this);
}

const char *psUserCommands::HandleCommand(const char *cmd)
{
    WordArray words(cmd);

    if (words.GetCount() == 0)
        return "";

    char buff[1024];
    
    if (  words[0] == "/show")
    {
        if (words.GetCount() > 1)
        {
            pawsControlWindow* ctrlWindow = dynamic_cast<pawsControlWindow*>(PawsManager::GetSingleton().FindWidget("ControlWindow"));
            if(!ctrlWindow || ctrlWindow->HandleWindowName(words[1]))
                return NULL;

            else return "That window cannot be found.";
        }
        return "You need to specify a window to show";
    }
    else if ( words[0] == "/craft" )
    {
        pawsWidget * widget = PawsManager::GetSingleton().FindWidget("CraftWindow");
        if ( widget )
            widget->Show();
            
        return NULL;                    
    }
    else if (words[0] == "/equip" || words[0] == "/use" && words.GetCount() > 1)
    {
        if ( words.GetCount() < 2 )
            return "Usage: /equip  [stack count] [item name]";
        int quanity = atoi(words[1]);
        csString itemName;
        if (quanity == 0)
        {
            quanity = 1;
            itemName = words.GetTail(1);
        }
        else itemName = words.GetTail(2);
        pawsInventoryWindow* window = (pawsInventoryWindow*)PawsManager::GetSingleton().FindWidget("InventoryWindow");
        window->Equip( itemName, quanity );
    }
    
    else if ( words[0] == "/dequip" )
    {
        if ( words.GetCount() < 2 )
            return "Usage: /dequip [item name|slot name]";
            
        pawsInventoryWindow* window = (pawsInventoryWindow*)PawsManager::GetSingleton().FindWidget("InventoryWindow");
        csString itemName( words.GetTail(1) );
        window->Dequip( itemName );                                                    
    }
    
    else if (words[0] == "/sell")
    {
        if (words.GetCount() > 1){
        csString tail = words.GetTail(1);
            sprintf(buff,"<R TYPE=\"SELL\" TARGET=\"%s\"/>",tail.GetData());
        }
        else
        {
            sprintf(buff,"<R TYPE=\"SELL\"/>"); // If no target specified by user use active target
        }
        psGUIMerchantMessage exchange(psGUIMerchantMessage::REQUEST,buff);
        msgqueue->SendMessage(exchange.msg);
    }
    else if ( words[0] == "/buddylist" )
    {
        pawsWidget* window     = PawsManager::GetSingleton().FindWidget("BuddyWindow");
        if ( !window )
            return "Buddy List Not Found";
        else
            window->Show();                                             
    }
    else if (words[0] == "/buy")
    {
        if (words.GetCount() > 1){
        csString tail = words.GetTail(1);
            sprintf(buff,"<R TYPE=\"BUY\" TARGET=\"%s\"/>",tail.GetData());
        }
        else
        {
            sprintf(buff,"<R TYPE=\"BUY\"/>"); // If no target specified by user use active target
        }
        psGUIMerchantMessage exchange(psGUIMerchantMessage::REQUEST,buff);
        msgqueue->SendMessage(exchange.msg);
    }
    
    else if (words[0] == "/trade")
    {
        psExchangeRequestMsg exchange(true);
        msgqueue->SendMessage(exchange.msg);
    }

    else if (words[0] == "/give")
    {
        psExchangeRequestMsg exchange(false);
        msgqueue->SendMessage(exchange.msg);
    }

    else if (words[0] == "/ignore")
    {
        pawsIgnoreWindow* window     = (pawsIgnoreWindow*) PawsManager::GetSingleton().FindWidget("IgnoreWindow");
        if ( !window )
            return "Ignore Window Not Found";
            
        window->Show();            
    }            
    
       
    else if (words[0] == "/add_ignore")
    {        
        pawsIgnoreWindow* window     = (pawsIgnoreWindow*) PawsManager::GetSingleton().FindWidget("IgnoreWindow");
        if ( !window )
            return "Ignore Window Not Found";
            
        if (words.GetCount() < 2)
            window->Show();
        else
        {
            csString person(words[1]);
            person.Downcase();
            person.SetAt(0,toupper(person.GetAt(0)));
            
            window->AddIgnore(person);            
        }
    }
    
    else if (words[0] == "/remove_ignore")
    {       
        pawsIgnoreWindow* window     = (pawsIgnoreWindow*) PawsManager::GetSingleton().FindWidget("IgnoreWindow");
        if ( !window )
            return "Ignore Window Not Found";
            
        if (words.GetCount() < 2)
            window->Show();
        else
        {
            csString person(words[1]);
            person.Downcase();
            person.SetAt(0,toupper(person.GetAt(0)));
            
            if (window->IsIgnored(person))
                window->RemoveIgnore(person);            
            else
                return "Player is not ignored!";                
        }
    }
    else if (words[0] == "/cast")
    {
       if (words.GetCount() > 1)
        {
            csString tail_word = words.GetTail(1);
            psSpellCastMessage mesg( tail_word, psengine->GetKFactor() );
            msgqueue->SendMessage( mesg.msg );
        }
        else
            return "You must specify a spell name";
    }
    else if (words[0] == "/away")
    {
        pawsChatWindow* ChatWindow = (pawsChatWindow*) PawsManager::GetSingleton().FindWidget("ChatWindow");

        if (words.GetCount() > 1)
            ChatWindow->SetAway( words.GetTail(1) );
        else
            ChatWindow->SetAway("");
    }
    else if (words[0] == "/clear")
    {
        printf("Clearing  history\n");
        pawsChatWindow* ChatWindow = (pawsChatWindow*) PawsManager::GetSingleton().FindWidget("ChatWindow");
        ChatWindow->Clear();
    }
    else if (words[0] == "/target")
    {
        if (words[1] == "") {
            return "You can use /target [self|clear] or /target [prev|next] [item|npc|player|any].";
        } else if (words[1] == "self")
            psengine->GetCharManager()->SetTarget(psengine->GetCelClient()->GetMainPlayer(),"select");
        else
        {
            csString tail = words.GetTail(words.GetCount()-1);
            SearchDirection dir = (words[1] == "prev") ? SEARCH_BACK : SEARCH_FORWARD;

            if (tail == "item")
                UpdateTarget(dir,PSENTITYTYPE_ITEM);
            else if (tail == "npc")
                UpdateTarget(dir, PSENTITYTYPE_NON_PLAYER_CHARACTER);
            else if (tail == "player" || tail == "pc")
                UpdateTarget(dir, PSENTITYTYPE_PLAYER_CHARACTER);
            else if (tail == "any")
                UpdateTarget(dir, PSENTITYTYPE_NO_TARGET);
            else if (tail == "clear")
                psengine->GetCharManager()->SetTarget(NULL,"select");
            else
                psengine->GetCharManager()->SetTarget(FindEntityWithName(tail),"select");
        }
    }
    else if (words[0] == "/target_context")
    {
        GEMClientObject *object = NULL;

        if (words[1] == "")
            object = psengine->GetCharManager()->GetTarget();
        else
            object = FindEntityWithName(words[1]);

        if (object)
            psengine->GetCharManager()->SetTarget(object,"context");
    }
    else if (words[0] == "/use" ||
             words[0] == "/combine" ||
             words[0] == "/dig" ||
             words[0] == "/repair" )
    {
        psWorkCmdMessage work(cmd);
        msgqueue->SendMessage(work.msg);
    }
    else if (words[0] == "/picklock")
    {
        psLockpickMessage lockpick("");
        msgqueue->SendMessage(lockpick.msg);
    }
    else if(words[0] == "/targetinfo")
    {
        pawsDetailWindow* detail = (pawsDetailWindow*)PawsManager::GetSingleton().FindWidget("DetailWindow");
        detail->RequestDetails();
        return NULL;
    }
    else if (words[0] == "/advisormode" )
    {
        csString pPerson = "";
        csString pText = "";

        if ( words.GetCount() > 1 )
             pText = words.GetTail( 1 );

        psAdviceMessage advice(0,words[0],pPerson, pText);
        msgqueue->SendMessage(advice.msg);
        return NULL;
    }
    else if (words[0] == "/list_advice_requests" )
    {
        csString pPerson = "";
        csString pText = "";

        psAdviceMessage advice(0,words[0],pPerson, pText);
        msgqueue->SendMessage(advice.msg);
        return NULL;
    }
    else if (words[0] == "/advisor" )
    {
        csString pPerson = "";
        csString pText( words.GetTail( 1 ) );

        if (pText.IsEmpty())
            return "You must enter the text. e.g /Advisor [text]";

        psAdviceMessage advice(0,words[0],pPerson, pText);
        msgqueue->SendMessage(advice.msg);
        return NULL;
    }
    else if (words[0] == "/advice")
    {

        if (words.GetCount() < 2)
            return "You must enter the text. e.g /Advice [user] <text>";
        csString pPerson( words[1] );
        csString pText(words.GetTail(2));

        psAdviceMessage advice(0,words[0],pPerson, pText);
        msgqueue->SendMessage(advice.msg);
        return NULL;
    }
    else if (  words[0] == "/show_active_magic")
    {
        pawsActiveMagicWindow* window     = (pawsActiveMagicWindow*) PawsManager::GetSingleton().FindWidget("ActiveMagicWindow");
        if ( !window )
            return "Active Magic Window Not Found";
            
        window->Show();            
    }
    else if (  words[0] == "/pet")
    {
        ///* Multiple Pets Code
        const char *errorMsg = "You must enter the text. e.g /pet [petname,] <follow|stay|dismiss|summon|attack|guard|assist|name|target> <options>";
        //*/
        //const char *errorMsg = "You must enter the text. e.g /pet <follow|stay|dismiss|summon|attack|guard|assist|name|target> <options>";
        if (words.GetCount() < 2)
            return errorMsg;

        csString pCommand = words[1];
        csString target, options;

        if ( pCommand.FindFirst( ',' ) != -1 )
        {
            // Pet name specified
            if (words.GetCount() < 3)
                return errorMsg;
            pCommand = words[2];
            target = words[1];
            options = words.GetTail( 3 );
        }
        else
        {
            // No pet specified, use default
            pCommand = words[1];
            target.Clear();
            options = words.GetTail( 2 );
        }

        pCommand.Upcase();
        int command;
        if      ( pCommand == "FOLLOW" )    { command = psPETCommandMessage::CMD_FOLLOW;      }
        else if ( pCommand == "STAY" )      { command = psPETCommandMessage::CMD_STAY;        }
        else if ( pCommand == "DISMISS" )   { command = psPETCommandMessage::CMD_DISMISS;     }
        else if ( pCommand == "SUMMON" )    { command = psPETCommandMessage::CMD_SUMMON;      }
        else if ( pCommand == "ATTACK" )    { command = psPETCommandMessage::CMD_ATTACK;      }
        else if ( pCommand == "GUARD" )     { command = psPETCommandMessage::CMD_GUARD;       }
        else if ( pCommand == "ASSIST" )    { command = psPETCommandMessage::CMD_ASSIST;      }
        else if ( pCommand == "ATTACK" )    { command = psPETCommandMessage::CMD_STOPATTACK;  }
        else if ( pCommand == "NAME" )      { command = psPETCommandMessage::CMD_NAME;        }
        else if ( pCommand == "TARGET" )    { command = psPETCommandMessage::CMD_TARGET;      }
        else                                { return errorMsg; }

        //target.Clear(); // for multiple pets, not currently implemented
        //options = words.GetTail(2);
        /* Multiple Pets Code for command line
        pCommand = words[2];
        target = words[1];
        options = words.GetTail(3);
        */
        
        // If we're dismissing the pet, we should close the stat window
        if (command == psPETCommandMessage::CMD_DISMISS)
        {
            pawsPetStatWindow* petwindow = (pawsPetStatWindow*)PawsManager::GetSingleton().FindWidget("PetStatWindow");
            if ( !petwindow )
                return "Pet stat window not found!";
            petwindow->Hide();
        }

        psPETCommandMessage cmd(0, command, target.GetData(), options.GetData());
        msgqueue->SendMessage(cmd.msg);
        return NULL;
    }

    else if (words[0] == "/pickup")
    {
         GEMClientObject *object = NULL;

         if (words[1] == "")
             object = psengine->GetCharManager()->GetTarget();
         else
             object = FindEntityWithName(words[1]);
         if (object)
             psengine->GetCharManager()->SetTarget(object,"pickup");
    }

    else if ( words[0] == "/game" )
    {
        // Find the game board window and start a new game session.
        pawsGameBoard *gameWindow = dynamic_cast<pawsGameBoard *>
                (PawsManager::GetSingleton().FindWidget("GameBoardWindow"));
        if (gameWindow)
            gameWindow->StartGame();
        else
            Error1("Couldn't find widget GameBoardWindow");

        // The window will be shown when the server responds back with the game layout.
        return NULL;
    }

    else
    {
        psUserCmdMessage cmdmsg(cmd);
        msgqueue->SendMessage(cmdmsg.msg);
    }
    
    return NULL;  // don't display anything here
}

void psUserCommands::HandleMessage(MsgEntry *msg)
{
    (void) msg;
}

// Starting from startingEntity, this function returns the next nearest or
// furthest PC, NPC or ITEM.  If startingEntity is NULL, it returns the the nearest
// or furthest PC, NPC or ITEM from the player.  If the search doesn't find a new
// target because we are already at the furthest or nearest entity, it cycles
// around to the nearest or furthest entity respectively.
void psUserCommands::UpdateTarget(SearchDirection searchDirection,
                                  EntityTypes entityType)
{
    GEMClientObject* startingEntity = psengine->GetCharManager()->GetTarget();
    psCelClient* cel = psengine->GetCelClient();

    GEMClientObject* myEntity = cel->GetMainPlayer();
    csRef<iPcMesh> myMesh = myEntity->pcmesh;        
    iMovable* myMov = myMesh->GetMesh()->GetMovable();
    csVector3 myPos = myMov->GetPosition();

    float seDistance;
    csVector3 sePos;
    if (startingEntity != NULL)
    {
        csRef<iPcMesh> seMesh = startingEntity->pcmesh;
        sePos = seMesh->GetMesh()->GetMovable()->GetPosition();
        seDistance = csSquaredDist::PointPoint(myPos, sePos);
    }
    else
        seDistance = (searchDirection == SEARCH_FORWARD) ? 0 : FLT_MAX;

    float max_range = NEARBY_TARGET_MAX_RANGE;
  
    if (entityType == PSENTITYTYPE_ITEM)
    {
       max_range = RANGE_TO_SELECT;
    }

    // Find all entities within a certain radius.
    csRef<iCelEntityList> entities =
        cel->GetPlLayer()->FindNearbyEntities(myMov->GetSectors()->Get(0),
                                              myPos,
                                              max_range);

    // Best entity is the next entity in a search.
    GEMClientObject* bestObject = startingEntity;
    float bestDistance = FLT_MAX;

    // Loop entity is the entity returned if we're already at the last entity
    // and need to cycle through.
    GEMClientObject* loopObject = startingEntity;
    float loopDistance = (searchDirection == SEARCH_FORWARD) ? FLT_MAX : 0;

    // Iterate through the entity list looking for the nearest one.
    size_t entityCount = entities->GetCount();
    
    for (size_t i = 0; i < entityCount; ++i)
    {
        // Get the next entity, skip if it's me or the starting entity.
        iCelEntity* entity = entities->Get(i);
        iCelEntity* other = ( startingEntity == NULL ) ? NULL : startingEntity->GetEntity();
        if (entity == myEntity->GetEntity() || entity == other)
            continue;

        GEMClientObject* object = cel->FindObject( entity->GetID() );
        CS_ASSERT( object );
        
        // Skip if it's not the type of entity we're looking for.
        int eType = object->GetType();
        if ((entityType == PSENTITYTYPE_PLAYER_CHARACTER && eType < 0)
            || (entityType == PSENTITYTYPE_NON_PLAYER_CHARACTER && (eType >= 0 || eType == -2))
            || (entityType == PSENTITYTYPE_ITEM && eType != -2))
            continue;

        csRef<iPcMesh> mesh = object->pcmesh;
        csVector3 pos = mesh->GetMesh()->GetMovable()->GetPosition();

        // Calculate the squared distance, update if we found a better one.
        float distFromMe = csSquaredDist::PointPoint(myPos, pos);

        // This is the distance from the starting entity to the current one.
        // If it's negative, the current entity is invalid for the search
        // (it can still be a valid loop entity, but never a best entity.)
        float dist = ((startingEntity != NULL)
                      ? ((searchDirection == SEARCH_FORWARD)
                         ? ((seDistance < distFromMe)
                            ? csSquaredDist::PointPoint(sePos, pos)
                            : -1)
                         : ((seDistance > distFromMe)
                            ? csSquaredDist::PointPoint(sePos, pos)
                            : -1))
                      : seDistance - distFromMe);

        // Update loop entity.
        if ((searchDirection == SEARCH_FORWARD && distFromMe < loopDistance)
            || (searchDirection == SEARCH_BACK && distFromMe > loopDistance))
        {
            loopObject = object;
            loopDistance = distFromMe;
        }

        // If dist < 0, the entity is on the wrong side of the starting
        // entity.
        if (dist < 0)
            continue;

        // Update best entity.
        if (dist < bestDistance)
        {
            bestObject = object;
            bestDistance = dist;
        }
    }
    
    return psengine->GetCharManager()->SetTarget((bestObject == startingEntity) ? loopObject : bestObject,"select");
}

GEMClientObject* psUserCommands::FindEntityWithName(const char *name)
{
    psCelClient* cel = psengine->GetCelClient();

    GEMClientObject* myEntity = cel->GetMainPlayer();
    csRef<iPcMesh> myMesh = myEntity->pcmesh;        
    iMovable* myMov = myMesh->GetMesh()->GetMovable();
    csVector3 myPos = myMov->GetPosition();

    // Find all entities within a certain radius.
    csRef<iCelEntityList> entities =
        cel->GetPlLayer()->FindNearbyEntities(myMov->GetSectors()->Get(0),
                                              myPos,
                                              NEARBY_TARGET_MAX_RANGE);

    // Iterate through the entity list looking for one with the right name.
    size_t entityCount = entities->GetCount();
    
    for (size_t i = 0; i < entityCount; ++i)
    {
        // Get the next entity, skip if it's me or the starting entity.
        iCelEntity* entity = entities->Get(i);
        GEMClientObject* object = cel->FindObject( entity->GetID() );
        CS_ASSERT( object );

        // Skip if the entity is an object.
        if ( object->GetType() == -2 )
            continue;

        if (csString(object->GetName()).StartsWith(name, true))
            return object;
    }

    return NULL;
}

