/*
* npcbehave.cpp by Keith Fulton <keith@paqrat.com>
*
* Copyright (C) 2003 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 <csutil/csstring.h>
#include <csgeom/transfrm.h>
#include <csgeom/path.h>
#include <iutil/document.h>
#include <iutil/vfs.h>
#include <csutil/randomgen.h>
#include <iengine/mesh.h>
#include <iengine/movable.h>
#include <ivaria/collider.h>
#include <cstool/collider.h>


#include <propclass/mesh.h>
#include <physicallayer/entity.h>
#include <physicallayer/propclas.h>
#include <propclass/linmove.h>
#include <propclass/colldet.h>

#include "net/msghandler.h"
#include "net/npcmessages.h"
#include "npcbehave.h"
#include "npc.h"
#include "tribe.h"
#include "perceptions.h"
#include "npcclient.h"
#include "networkmgr.h"
#include "pathmanager.h"
#include "globals.h"
#include "util/log.h"
#include "util/location.h"
#include "util/psconst.h"
#include "psbehave/psworld.h"

csRandomGen ScriptOperation::rng;
extern bool running;


NPCType::NPCType()
{
}

NPCType::~NPCType()
{
}

bool NPCType::Load(iDocumentNode *node)
{
    const char *parent = node->GetAttributeValue("parent");
    if (parent) // this npctype is a subclass of another npctype
    {
        NPCType *superclass = npcclient->FindNPCType(parent);
        if (superclass)
        {
            DeepCopy(*superclass);  // This pulls everything from the parent into this one.
        }
        else
        {
            Error2("Specified parent npctype '%s' could not be found.\n",
                parent);
        }
    }

    name = node->GetAttributeValue("name");
    if ( name.Length() == 0 )
    {
        Error1("NPCType has no name attribute. Error in XML");
        return false;
    }
    if (node->GetAttributeValueAsFloat("ang_vel") )
        ang_vel = node->GetAttributeValueAsFloat("ang_vel");
    else
        ang_vel = 999;

    if (node->GetAttributeValueAsFloat("vel") )
        vel = node->GetAttributeValueAsFloat("vel");
    else
        vel = 999;

    // Now read in behaviors and reactions
    csRef<iDocumentNodeIterator> iter = node->GetNodes();

    while ( iter->HasNext() )
    {
        csRef<iDocumentNode> node = iter->Next();
        if ( node->GetType() != CS_NODE_ELEMENT )
            continue;

        // This is a widget so read it's factory to create it.
        if ( strcmp( node->GetValue(), "behavior" ) == 0 )
        {
            Behavior *b = new Behavior;
            if (!b->Load(node))
            {
                Error1("Could not load behavior. Error in XML");
                delete b;
                return false;
            }
            behaviors.Add(b);
            CPrintf(CON_DEBUG, "Added behavior '%s' to type %s.\n",b->GetName(),name.GetData() );
        }
        else if ( strcmp( node->GetValue(), "react" ) == 0 )
        {
            Reaction *r = new Reaction;
            if (!r->Load(node,behaviors))
            {
                Error1("Could not load reaction. Error in XML");
                delete r;
                return false;
            }
            // check for duplicates and keeps the last one
            // EXCEPT for time reactions!
            if ( strcmp( r->GetEventType(), "time") != 0) {
              for (size_t i=0; i<reactions.Length(); i++)
              {
                  if (!strcmp(reactions[i]->GetEventType(),r->GetEventType()))
                  {
                      delete reactions[i];
                      reactions.DeleteIndex(i);
                      break;
                  }
              }
            }

            reactions.Insert(0,r);  // reactions get inserted at beginning so subclass ones take precedence over superclass.
        }
        else
        {
            Error1("Node under NPCType is not 'behavior' or 'react'. Error in XML");
            return false;
        }
    }
    return true; // success
}

void NPCType::FirePerception(NPC *npc,EventManager *eventmgr,Perception *pcpt)
{
    for (size_t x=0; x<reactions.Length(); x++)
    {
        reactions[x]->React(npc,eventmgr,pcpt);
    }
}

void NPCType::DeepCopy(NPCType& other)
{
    name = other.name;
    ang_vel = other.ang_vel;
    vel     = other.vel;

    behaviors.DeepCopy(other.behaviors);

    for (size_t x=0; x<other.reactions.Length(); x++)
    {
        reactions.Push( new Reaction(*other.reactions[x],behaviors) );
    }
}

void NPCType::ClearState()
{
    behaviors.ClearState();
    CPrintf(CON_DEBUG, "Clearing NPC State:\n");
    behaviors.DumpBehaviorList();
}

void NPCType::Advance(csTicks delta,NPC *npc,EventManager *eventmgr)
{
    behaviors.Advance(delta,npc,eventmgr);
}

void NPCType::ResumeScript(NPC *npc,EventManager *eventmgr,Behavior *which)
{
    behaviors.ResumeScript(npc,eventmgr,which);
}

void NPCType::Interrupt(NPC *npc,EventManager *eventmgr)
{
    behaviors.Interrupt(npc,eventmgr);
}

void BehaviorSet::ClearState()
{
    for (size_t i = 0; i<behaviors.Length(); i++)
    {
        behaviors[i]->ResetNeed();
        behaviors[i]->SetActive(false);
    }
    active = NULL;
}

void BehaviorSet::Add(Behavior *b)
{
    for (size_t i=0; i<behaviors.Length(); i++)
    {
        if (!strcmp(behaviors[i]->GetName(),b->GetName()))
        {
            delete behaviors[i];
            behaviors[i] = b;  // substitute
            return;
        }
    }
    behaviors.Push(b);
}

void BehaviorSet::Advance(csTicks delta,NPC *npc,EventManager *eventmgr)
{
    Behavior *b;
    max_need = -999;
    bool changed = false;

    // Go through and update needs based on time
    for (size_t i=0; i<behaviors.Length(); i++)
    {
        b = behaviors[i];
        b->Advance(delta,npc,eventmgr);

        if (behaviors[i]->CurrentNeed()!=behaviors[i]->NewNeed()) {
          npc->Printf("Advancing %-30s:\t%1.1f ->%1.1f", behaviors[i]->GetName(), behaviors[i]->CurrentNeed(), behaviors[i]->NewNeed() );
          changed = true;
        }

        if (b->NewNeed() > max_need) // the advance causes re-ordering
        {
            if (i!=0)  // trivial swap if same element
            {
                behaviors[i] = behaviors[0];
                behaviors[0] = b;  // now highest need is elem 0
            }
            max_need = b->NewNeed();
        }
        b->CommitAdvance();   // Update key to correct value
    }
    if (changed)
      npc->DumpBehaviorList();

    // now that behaviours are correctly sorted, select the first one
    b = behaviors[0];

    // use it only if need > 0
    if (behaviors[0]->CurrentNeed()<=0) {
      npc->Printf("NO Active behavior." );
      return;
    }

    if (b != active)
    {
        if (!active)  // if it's the first behaviour ever assigned to this npc
        {
            active = b;
            active->SetActive(true);
            b->StartScript(npc,eventmgr);
        }
        else
        {
            //if (b->CurrentNeed() > active->CurrentNeed() + 20)
            //{
                npc->Printf("%s switching behavior from '%s' to '%s'.\n",
                            npc->GetEntity()->GetName(),
                            active->GetName(), 
                            b->GetName() );
                npc->DumpBehaviorList();
                active->InterruptScript(npc,eventmgr);
                active->SetActive(false);
                b->SetActive(true);
                active = b;
                b->StartScript(npc,eventmgr);
            //}
        }
    }
    
    npc->Printf("Active behavior is %s", active->GetName() );
}

void BehaviorSet::ResumeScript(NPC *npc,EventManager *eventmgr,Behavior *which)
{
    if (which == active)
        active->ResumeScript(npc,eventmgr);
}

void BehaviorSet::Interrupt(NPC *npc,EventManager *eventmgr)
{
    if (active)
        active->InterruptScript(npc,eventmgr);
}

void BehaviorSet::DeepCopy(BehaviorSet& other)
{
    Behavior *b,*b2;
    for (size_t i=0; i<other.behaviors.Length(); i++)
    {
        b  = other.behaviors[i];
        b2 = new Behavior(*b);
        behaviors.Push(b2);
    }
}

Behavior *BehaviorSet::Find(const char *name)
{
    for (size_t i=0; i<behaviors.Length(); i++)
    {
        if (!strcmp(behaviors[i]->GetName(),name))
            return behaviors[i];
    }
    return NULL;
}

void BehaviorSet::DumpBehaviorList()
{
    for (size_t i=0; i<behaviors.Length(); i++)
    {
        CPrintf(CON_CMDOUTPUT, "%-30s\t%1.1f\n",behaviors[i]->GetName(),behaviors[i]->CurrentNeed() );
    }
}




Behavior::Behavior()
{
    loop = false;
    is_active = false;
    need_decay_rate  = 0;
    need_growth_rate = 0;
    completion_decay = 0;
    new_need=-999;
}

bool Behavior::Load(iDocumentNode *node)
{
    // This function can be called recursively, so we only get attributes at top level
    name = node->GetAttributeValue("name");
    if ( name.Length() == 0 )
    {
        Error1("Behavior has no name attribute. Error in XML");
        return false;
    }
    csString loopflag = node->GetAttributeValue("loop");
    if (loopflag == "yes")
        loop = true;

    need_decay_rate  = node->GetAttributeValueAsFloat("decay");
    completion_decay = node->GetAttributeValueAsFloat("completion_decay");
    need_growth_rate = node->GetAttributeValueAsFloat("growth");
    init_need        = node->GetAttributeValueAsFloat("initial");
    current_need     = init_need;

    return LoadScript(node,true);
}

bool Behavior::LoadScript(iDocumentNode *node,bool top_level)
{
    // Now read in script for this behavior
    csRef<iDocumentNodeIterator> iter = node->GetNodes();

    while ( iter->HasNext() )
    {
        csRef<iDocumentNode> node = iter->Next();
        if ( node->GetType() != CS_NODE_ELEMENT )
            continue;

        // This is a widget so read it's factory to create it.
        if ( strcmp( node->GetValue(), "locate" ) == 0 )
        {
            LocateOperation *op = new LocateOperation;
            if (!op->Load(node))
            {
                Error1("Could not load LocateOperation. Error in XML");
                delete op;
                return false;
            }
            sequence.Push(op);
        }
        else if ( strcmp( node->GetValue(), "navigate" ) == 0 )
        {
            NavigateOperation *op = new NavigateOperation;
            if (!op->Load(node))
            {
                Error1("Could not load NavigateOperation. Error in XML");
                delete op;
                return false;
            }
            sequence.Push(op);
        }
        else if ( strcmp( node->GetValue(), "pickup" ) == 0 )
        {
            PickupOperation *op = new PickupOperation;
            if (!op->Load(node))
            {
                Error1("Could not load PickupOperation. Error in XML");
                delete op;
                return false;
            }
            sequence.Push(op);
        }
        else if ( strcmp( node->GetValue(), "equip" ) == 0 )
        {
            EquipOperation *op = new EquipOperation;
            if (!op->Load(node))
            {
                Error1("Could not load EquipOperation. Error in XML");
                delete op;
                return false;
            }
            sequence.Push(op);
        }
        else if ( strcmp( node->GetValue(), "dequip" ) == 0 )
        {
            DequipOperation *op = new DequipOperation;
            if (!op->Load(node))
            {
                Error1("Could not load DequipOperation. Error in XML");
                delete op;
                return false;
            }
            sequence.Push(op);
        }
        else if ( strcmp( node->GetValue(), "wait" ) == 0 )
        {
            WaitOperation *op = new WaitOperation;
            if (!op->Load(node))
            {
                Error1("Could not load WaitOperation. Error in XML");
                delete op;
                return false;
            }
            sequence.Push(op);
        }
        else if ( strcmp( node->GetValue(), "wander" ) == 0 )
        {
            WanderOperation *op = new WanderOperation;
            if (!op->Load(node))
            {
                Error1("Could not load WanderOperation. Error in XML");
                delete op;
                return false;
            }
            sequence.Push(op);
        }
        else if ( strcmp( node->GetValue(), "chase" ) == 0 )
        {
            ChaseOperation *op = new ChaseOperation;
            if (!op->Load(node))
            {
                Error1("Could not load ChaseOperation. Error in XML");
                delete op;
                return false;
            }
            sequence.Push(op);
        }
        else if ( strcmp( node->GetValue(), "drop" ) == 0 )
        {
            DropOperation *op = new DropOperation;
            if (!op->Load(node))
            {
                Error1("Could not load DropOperation. Error in XML");
                delete op;
                return false;
            }
            sequence.Push(op);
        }
        else if ( strcmp( node->GetValue(), "dig" ) == 0 )
        {
            DigOperation *op = new DigOperation;
            if (!op->Load(node))
            {
                Error1("Could not load DigOperation. Error in XML");
                delete op;
                return false;
            }
            sequence.Push(op);
        }
        else if ( strcmp( node->GetValue(), "debug" ) == 0 )
        {
            DebugOperation *op = new DebugOperation;
            if (!op->Load(node))
            {
                Error1("Could not load DebugOperation. Error in XML");
                delete op;
                return false;
            }
            sequence.Push(op);
        }
        else if ( strcmp( node->GetValue(), "move" ) == 0 )
        {
            MoveOperation *op = new MoveOperation;
            if (!op->Load(node))
            {
                Error1("Could not load MoveOperation. Error in XML");
                delete op;
                return false;
            }
            sequence.Push(op);
        }
        else if ( strcmp( node->GetValue(), "moveto" ) == 0 )
        {
            MoveToOperation *op = new MoveToOperation;
            if (!op->Load(node))
            {
                Error1("Could not load MoveToOperation. Error in XML");
                delete op;
                return false;
            }
            sequence.Push(op);
        }
        else if ( strcmp( node->GetValue(), "movepath" ) == 0 )
        {
            MovePathOperation *op = new MovePathOperation;
            if (!op->Load(node))
            {
                Error1("Could not load MovePathOperation. Error in XML");
                delete op;
                return false;
            }
            sequence.Push(op);
        }
        else if ( strcmp( node->GetValue(), "rotate" ) == 0 )
        {
            RotateOperation *op = new RotateOperation;
            if (!op->Load(node))
            {
                Error1("Could not load RotateOperation. Error in XML");
                delete op;
                return false;
            }
            sequence.Push(op);
        }
        else if ( strcmp( node->GetValue(), "melee" ) == 0 )
        {
            MeleeOperation *op = new MeleeOperation;
            if (!op->Load(node))
            {
                Error1("Could not load MeleeOperation. Error in XML");
                delete op;
                return false;
            }
            sequence.Push(op);
        }
        else if ( strcmp( node->GetValue(), "loop" ) == 0 )
        {
            BeginLoopOperation *op = new BeginLoopOperation;
            if (!op->Load(node))
            {
                Error1("Could not load LoopOperation. Error in XML");
                delete op;
                return false;
            }
            int where = (int)sequence.Length(); // Where will sequence be pushed
            sequence.Push(op);

            if (!LoadScript(node,false)) // recursively load within loop
            {
                Error1("Could not load within Loop Operation. Error in XML");
                return false;
            }

            EndLoopOperation *op2 = new EndLoopOperation(where,op->iterations);
            sequence.Push(op2);
        }
        else if ( strcmp( node->GetValue(), "talk" ) == 0 )
        {
            TalkOperation *op = new TalkOperation;
            if (!op->Load(node))
            {
                Error1("Could not load TalkOperation. Error in XML");
                delete op;
                return false;
            }
            sequence.Push(op);
        }
        else if ( strcmp( node->GetValue(), "visible" ) == 0 )
        {
            VisibleOperation *op = new VisibleOperation;
            if (!op->Load(node))
            {
                Error1("Could not load VisibleOperation. Error in XML");
                delete op;
                return false;
            }
            sequence.Push(op);
        }
        else if ( strcmp( node->GetValue(), "invisible" ) == 0 )
        {
            InvisibleOperation *op = new InvisibleOperation;
            if (!op->Load(node))
            {
                Error1("Could not load InvisibleOperation. Error in XML");
                delete op;
                return false;
            }
            sequence.Push(op);
        }
        else if ( strcmp( node->GetValue(), "reproduce" ) == 0 )
        {
            ReproduceOperation *op = new ReproduceOperation;
            if (!op->Load(node))
            {
                Error1("Could not load ReproduceOperation. Error in XML");
                delete op;
                return false;
            }
            sequence.Push(op);
        }
        else
        {
            Error2("Node '%s' under Behavior is not a valid script operation name. Error in XML",node->GetValue() );
            return false;
        }
    }

    return true; // success
}

void Behavior::DeepCopy(Behavior& other)
{
    loop             = other.loop;
    is_active        = other.is_active;
    need_decay_rate  = other.need_decay_rate;  // need lessens while performing behavior
    need_growth_rate = other.need_growth_rate; // need grows while not performing behavior
    completion_decay = other.completion_decay;
    new_need         = -999;
    name             = other.name;

    init_need    = other.init_need;
    current_need = other.current_need;
    last_check   = other.last_check;

    for (size_t x=0; x<other.sequence.Length(); x++)
    {
        sequence.Push( other.sequence[x]->MakeCopy() );
    }
}

void Behavior::Advance(csTicks delta,NPC *npc,EventManager *eventmgr)
{
//    npc->Printf("Advancing %s need of %1.1f/%1.1f",name.GetData(),new_need,current_need);

    if (new_need == -999)
        new_need = current_need;

    float d = .001 * delta;
    if (is_active)
    {
        new_need = new_need - (d * need_decay_rate);
        if (current_step < sequence.Length())
            sequence[current_step]->Advance(d,npc,eventmgr);
    }
    else
    {
        new_need = new_need + (d * need_growth_rate);
    }
}

void Behavior::StartScript(NPC *npc,EventManager *eventmgr)
{
    current_step = 0;
    RunScript(npc,eventmgr);
}
   
Behavior* BehaviorSet::Find(Behavior *key)
{
    size_t found = behaviors.Find(key);
    return (found = SIZET_NOT_FOUND) ? NULL : behaviors[found];
}
     
void Behavior::RunScript(NPC *npc,EventManager *eventmgr)
{
    while (true)
    {
        while (current_step < sequence.Length() )
        {
            if (!sequence[current_step]->Run(npc,eventmgr))
                return;  // Run returning false means that op is not finished but should relinquish
            current_step++;
        }
        if (current_step == sequence.Length())
        {
            if (loop)
            {
                current_step = 0;  // behaviors automatically loop around to the top
            }
            else 
            {
                if (completion_decay)
                {
                    npc->Printf("Subtracting completion decay of %1f from behavior '%s'.\n",completion_decay,GetName() );
                    if (completion_decay == -1)
                        new_need = 0;
                    else
                        new_need = current_need - completion_decay;
                }
                break;
            }
        }
    }
}

void Behavior::InterruptScript(NPC *npc,EventManager *eventmgr)
{
    if (current_step < sequence.Length() )
        sequence[current_step]->InterruptOperation(npc,eventmgr);
}

void Behavior::ResumeScript(NPC *npc,EventManager *eventmgr)
{
    npc->Printf("Resuming behavior %s at step %d.",name.GetData(),current_step);
    if (current_step < sequence.Length())
    {
        if (sequence[current_step]->CompleteOperation(npc,eventmgr))
        {
            current_step++;
            RunScript(npc,eventmgr);
        }
    }
    else
    {
        current_step=0;
        RunScript(npc,eventmgr);
    }
}

float ScriptOperation::GetVelocity(NPC *npc)
{
    if (vel)
        return vel;
    else
        return npc->GetVelocity();
}

void ScriptOperation::Advance(float timedelta,NPC *npc,EventManager *eventmgr) 
{
//    npc->Printf("AnonOp advanced"); 
}

bool MoveOperation::Load(iDocumentNode *node)
{
    vel = node->GetAttributeValueAsFloat("vel");
    action = node->GetAttributeValue("anim");
    duration = node->GetAttributeValueAsFloat("duration");
    return true;
}

ScriptOperation *MoveOperation::MakeCopy()
{
    MoveOperation *op = new MoveOperation;
    op->vel    = vel;
    op->action = action;
    op->duration = duration;

    return op;
}

bool MoveOperation::Run(NPC *npc,EventManager *eventmgr)
{
    npc->Printf("MoveOp ");

    LocationType *rgn = npc->GetRegion();
    if (!rgn)
    {
        CPrintf(CON_ERROR, "ERROR: Region was not specified or found for npc %s (%d).\n",
          npc->GetEntity()->GetName(), npc->GetID());
        return false;  // this halts this script
    }

    csRef<iPcMesh> pcmesh = CEL_QUERY_PROPCLASS(npc->GetEntity()->GetPropertyClassList(), 
        iPcMesh);

    // Get Going at the right velocity
    csVector3 velvec(0,0,-GetVelocity(npc) );
    npc->GetLinMove()->SetVelocity(velvec);

    // SetAction animation for the mesh also, so it looks right
    pcmesh->SetAction(action);

    //now persist
    npcclient->GetNetworkMgr()->QueueDRData(npc->GetEntity(),npc->GetLinMove(),npc->GetDRCounter());

    // Note no "wake me up when over" event here.
    // Move just keeps moving the same direction until pre-empted by something else.
    consec_collisions = 0;

    if(duration > 0)
    {
        psResumeScriptEvent *ev = new psResumeScriptEvent((int)(duration*1000.0),npc,eventmgr,npc->GetCurrentBehavior() );
        eventmgr->Push(ev);
    }

    return false;
}

void MoveOperation::InterruptOperation(NPC *npc,EventManager *eventmgr)
{
    //    CPrintf(CON_SPAM, "Interrupting Move.\n");
    CompleteOperation(npc, eventmgr);
}

bool MoveOperation::CompleteOperation(NPC *npc,EventManager *eventmgr)
{
    //    CPrintf(CON_SPAM, "Interrupting Move.\n");

    // stop the movement
    npc->GetLinMove()->SetVelocity( csVector3(0,0,0) );

    //now persist
    npcclient->GetNetworkMgr()->QueueDRData(npc->GetEntity(),npc->GetLinMove(),npc->GetDRCounter());

    return true;  // Script can keep going
}

void MoveOperation::Advance(float timedelta,NPC *npc,EventManager *eventmgr)
{
    csRef<iPcMesh> pcmesh = CEL_QUERY_PROPCLASS(npc->GetEntity()->GetPropertyClassList(), 
        iPcMesh);

    csVector3 oldpos,pos2;
    float     rot;
    iSector * sector;

    npc->GetLinMove()->GetLastPosition(oldpos,rot,sector);
    npc->GetLinMove()->ExtrapolatePosition(timedelta);
    npc->GetLinMove()->GetLastPosition(pos2,rot,sector);

    if ((oldpos - pos2).SquaredNorm() < 0.01f) // then stopped dead, presumably by collision
    {
        Perception collision("collision");
        npc->TriggerEvent(&collision,eventmgr);
    }
    else
    {
        csVector3 velvec(0,0,-GetVelocity(npc) );
        // Check for non-stationary collisions
        csReversibleTransform rt = pcmesh->GetMesh()->GetMovable()->GetFullTransform();
        csMatrix3 mat = rt.GetT2O();
        csVector3 expected_pos;
        expected_pos = mat*(velvec*timedelta) + oldpos;

        float diffx = fabs(pos2.x - expected_pos.x);
        float diffz = fabs(pos2.z - expected_pos.z);

        if (diffx > EPSILON ||
            diffz > EPSILON)
        {
            consec_collisions++;
            npc->Printf("Bang (%1.2f,%1.2f)...",diffx,diffz);
            if (consec_collisions > 8)  // allow for hitting trees but not walls
            {
                // after a couple seconds of sliding against something
                // the npc should give up and react to the obstacle.
                Perception collision("collision");
                npc->TriggerEvent(&collision,eventmgr);

            }
        }
        else
        {
            consec_collisions = 0;
        }

        LocationType *rgn = npc->GetRegion();

        // check for inside/outside region bounds
        if (inside_rgn)
        {
            if (!rgn->CheckWithinBounds(pos2))
            {
                Perception outbounds("out of bounds");
                npc->TriggerEvent(&outbounds,eventmgr);
                inside_rgn = false;
            }
        }
        else
        {
            if (rgn->CheckWithinBounds(pos2))
            {
                Perception inbounds("in bounds");
                npc->TriggerEvent(&inbounds,eventmgr);
                inside_rgn = true;
            }
        }
    }
}



bool MoveToOperation::Load(iDocumentNode *node)
{
    dest.x = node->GetAttributeValueAsFloat("x");
    dest.y = node->GetAttributeValueAsFloat("y");
    dest.z = node->GetAttributeValueAsFloat("z");

    vel    = node->GetAttributeValueAsFloat("vel");
    action = node->GetAttributeValue("anim");

    return true;
}

ScriptOperation *MoveToOperation::MakeCopy()
{
    MoveToOperation *op = new MoveToOperation;
    op->vel    = vel;
    op->dest   = dest;
    op->action = action;
    return op;
}

void ScriptOperation::TurnTo(NPC *npc, csVector3& dest, iSector* destsect, csVector3& forward)
{
    npc->Printf("TurnTo localDest=(%1.2f,%1.2f,%1.2f)\n",dest.x,dest.y,dest.z);

    csRef<iPcMesh> pcmesh = CEL_QUERY_PROPCLASS(npc->GetEntity()->GetPropertyClassList(), iPcMesh);

    // Turn to face the direction we're going.
    csVector3 pos, up;
    float rot;
    iSector *sector;

    psGameObject::GetPosition(npc->GetEntity(),pos,rot,sector);

    npcclient->GetWorld()->WarpSpace(sector, destsect, pos);

    forward = dest-pos;

    up.Set(0,1,0);
    
    forward.y = 0;

    float angle = psGameObject::CalculateIncidentAngle(pos,dest);
    if (angle < 0) angle += TWO_PI;

    pcmesh->GetMesh()->GetMovable()->GetTransform().LookAt (-forward.Unit(), up.Unit());

    psGameObject::GetPosition(npc->GetEntity(),pos,rot,sector);
    if (rot < 0) rot += TWO_PI;

    //npc->Printf("Calculated angle is %1.4f, actual is %1.4f for npc %s\n",angle,rot,npc->GetName().GetData());

    // Get Going at the right velocity
    csVector3 velvector(0,0,  -GetVelocity(npc) );
    npc->GetLinMove()->SetVelocity(velvector);
}

int ScriptOperation::StartMoveTo(NPC *npc,EventManager *eventmgr,csVector3& dest, iSector* sector, float vel,const char *action, bool autoresume)
{
    csVector3 forward;
    
    TurnTo(npc, dest, sector, forward);
    
    csRef<iPcMesh> pcmesh = CEL_QUERY_PROPCLASS(npc->GetEntity()->GetPropertyClassList(), iPcMesh);
        
    // SetAction animation for the mesh also, so it looks right
    pcmesh->SetAction(action);

    //now persist
    npcclient->GetNetworkMgr()->QueueDRData(npc->GetEntity(),npc->GetLinMove(),npc->GetDRCounter());

    float dist = sqrt(forward.x * forward.x + forward.y*forward.y + forward.z*forward.z);
    int msec = (int)(1000.0 * dist / GetVelocity(npc)); // Move will take this many msecs.
    
    npc->Printf("MoveTo op should take approx %d msec.  ", msec);
    if (autoresume)
    {
        // wake me up when it's over
        psResumeScriptEvent *ev = new psResumeScriptEvent(msec,npc,eventmgr,npc->GetCurrentBehavior() );
        eventmgr->Push(ev);
        npc->Printf("Waking up in %d msec.\n", msec);
    }
    else
        npc->Printf("NO autoresume here.\n", msec);


    return msec;
}

bool MoveToOperation::Run(NPC *npc,EventManager *eventmgr)
{
    CPrintf(CON_SPAM, "MoveToOp");
    CPrintf(CON_SPAM, "dest=(%1.2f,%1.2f,%1.2f) at %1.2f m/sec.\n",dest.x,dest.y,dest.z,vel);

    csVector3 pos, forward, up;
    float rot;
    iSector *sector;

    psGameObject::GetPosition(npc->GetEntity(),pos,rot,sector);
    
    path.SetMaps(npcclient->GetMaps());
    path.SetDest(dest);
    path.CalcLocalDest(pos, sector, localDest);
    
    // Using "true" teleports to dest location after proper time has 
    // elapsed and is therefore more tolerant of CD errors.
    // StartMoveTo(npc,eventmgr,localDest, sector,vel,action, false);
    StartMoveTo(npc, eventmgr, localDest, sector,vel,action, true); 
    return false;
}

void MoveToOperation::Advance(float timedelta,NPC *npc,EventManager *eventmgr)
{
    //  CPrintf(CON_DEBUG, "MoveTo Advance %1.2f\n",timedelta);

    csVector3 pos,pos2;
    float     rot;
    iSector * sector;
    csVector3 forward;

    npc->GetLinMove()->GetLastPosition(pos,rot,sector);
    
    npc->Printf("advance: pos=(%1.2f,%1.2f,%1.2f) rot=%.2f localDest=(%.2f,%.2f,%.2f) dest=(%.2f,%.2f,%.2f) dist=%f\n", 
                pos.x,pos.y,pos.z, rot,
                localDest.x,localDest.y,localDest.z,
                dest.x,dest.y,dest.z,
                Calc2DDistance(localDest, pos));
    
    TurnTo(npc, localDest, sector, forward);
    
    //tolerance must be according to step size
    //we must ignore y
    if (Calc2DDistance(localDest, pos) <= 0.5)
    {
        pos.x = localDest.x;
        pos.z = localDest.z;
        npc->GetLinMove()->SetPosition(pos,rot,sector);
        
        if (Calc2DDistance(localDest,dest) <= 0.5) //tolerance must be according to step size, ignore y
        {
            npc->Printf("MoveTo within minimum acceptable range...Stopping him now.\n");
            // npc->ResumeScript(eventmgr, npc->GetBrain()->GetCurrentBehavior() );
            CompleteOperation(npc, eventmgr);
        }
        else
        {
            npc->Printf("we are at localDest... WHAT DOES THIS MEAN?\n");
            path.CalcLocalDest(pos, sector, localDest);
            StartMoveTo(npc,eventmgr,localDest, sector, vel,action, false);
        }
    }
    else
    {
        npc->GetLinMove()->ExtrapolatePosition(timedelta);
        npc->GetLinMove()->GetLastPosition(pos2,rot,sector);
    
        if ((pos-pos2).SquaredNorm() < SMALL_EPSILON) // then stopped dead, presumably by collision
        {
            Perception collision("collision");
            npc->TriggerEvent(&collision,eventmgr);
        }
    }
}

bool MoveToOperation::CompleteOperation(NPC *npc,EventManager *eventmgr)
{
    //    CPrintf(CON_DEBUG, "MoveTo completed. Stopping now.\n");

    // Stop the movement
    // Set Vel to zero again
    npc->GetLinMove()->SetVelocity( csVector3(0,0,0) );

    // Get the rot and sector here so they don't change in the SetPosition call
    float rot;
    iSector *sector;
    csVector3 pos;
    psGameObject::GetPosition(npc->GetEntity(),pos,rot,sector);

    // Set position to where it is supposed to go
    npc->GetLinMove()->SetPosition(dest,rot,sector);

    //now persist
    npcclient->GetNetworkMgr()->QueueDRData(npc->GetEntity(),npc->GetLinMove(),npc->GetDRCounter());

    return true;  // Script can keep going
}


bool RotateOperation::Load(iDocumentNode *node)
{
    csString type = node->GetAttributeValue("type");
    ang_vel   = node->GetAttributeValueAsFloat("vel");
    ang_vel   *= TWO_PI/360;
    action = node->GetAttributeValue("anim");

    if (type == "inregion")
    {
        op_type = ROT_REGION;
        min_range = node->GetAttributeValueAsFloat("min")*TWO_PI/360.0f;
        max_range = node->GetAttributeValueAsFloat("max")*TWO_PI/360.0f;
        return true;
    }
    else if (type == "random")
    {
        op_type = ROT_RANDOM;
        min_range = node->GetAttributeValueAsFloat("min")*TWO_PI/360.0f;
        max_range = node->GetAttributeValueAsFloat("max")*TWO_PI/360.0f;
        return true;
    }
    else if (type == "absolute")
    {
        op_type = ROT_ABSOLUTE;
        min_range = node->GetAttributeValueAsFloat("value")*TWO_PI/360.0f;
        max_range = node->GetAttributeValueAsFloat("value")*TWO_PI/360.0f;
        return true;
    }
    else if (type == "locatedest")
    {
        op_type = this->ROT_LOCATEDEST;
        return true;
    }
    else
    {
        Error1("Rotate Op type must be 'random' or 'locatedest' right now.\n");
    }
    return false;
}

ScriptOperation *RotateOperation::MakeCopy()
{
    RotateOperation *op = new RotateOperation;
    op->action = action;
    op->op_type = op_type;
    op->max_range = max_range;
    op->min_range = min_range;
    op->ang_vel = ang_vel;

    return op;
}

float RotateOperation::GetAngularVelocity(NPC *npc)
{
    if (ang_vel==0)
        return npc->GetAngularVelocity();
    else
        return ang_vel;
}

bool RotateOperation::Run(NPC *npc,EventManager *eventmgr)
{
    npc->Printf(">>>RotateOp ");

    if (op_type == ROT_RANDOM || op_type == ROT_REGION)
    {
        LocationType *rgn = npc->GetRegion();

        // calculate angle to turn and time it will take to do this turn
        float radians;
        int msec;

        // Start the turn
        bool verified = false;
        int count=0;
        float rot=0,rot_angle=0;
        while (rgn && op_type == ROT_REGION && !verified && count<10)
        {
            // Find range of allowable angles to turn inwards to region again
            csVector3 pos;
            iSector* sector;
            psGameObject::GetPosition(npc->GetEntity(),pos,rot,sector);
            float min_angle = TWO_PI, max_angle = -TWO_PI;

            for (size_t i=0; i<rgn->locs.Length(); i++)
            {
                rot_angle = psGameObject::CalculateIncidentAngle(pos,rgn->locs[i]->pos);
                if (min_angle > rot_angle)
                    min_angle = rot_angle+.05;
                if (max_angle < rot_angle)
                    max_angle = rot_angle-.05;  // The .05 is so it doesn't aim straight for the corner
            }
            if (max_angle-min_angle  > 3.14159 )
            {
                //        CPrintf(CON_DEBUG, "Swapping ranges...");
                float temp=max_angle;  
                max_angle=min_angle+TWO_PI;  
                min_angle=temp;
            }
            //        CPrintf(CON_SPAM, "\nAngle range is %1.3f to %1.3f radians...", min_angle, max_angle);
            // Pick an angle in that range
            radians = SeekAngle(npc, rng.Get() * (max_angle-min_angle) + min_angle);

            npc->Printf("Turning from %1.3f to %1.3f.\n",rot,radians);

            radians -= rot;  // now radians is delta
            if (radians > 3.14159)
            {
                radians-= TWO_PI;  // go negative on turns >180 degrees
            }

            msec = (int)fabs(1000.0*radians/GetAngularVelocity(npc) );

            angle_delta=radians;

            //        CPrintf(CON_SPAM, "%1.2f degrees in %d msec.\n",radians*360/TWO_PI,msec);

            verified = true;
        }
        if (!rgn || op_type == ROT_RANDOM)
        {
            radians = SeekAngle(npc, rng.Get() * (max_range - min_range)+min_range - PI);

            msec = (int)fabs(1000.0*radians/GetAngularVelocity(npc) );
        }
        npc->Printf("%1.2f degrees in %d msec.\n",radians*360/TWO_PI,msec);

        // Save target angle so we can jam that in on Rotate completion.
        target_angle = rot + radians;
        // clamp values
        psGameObject::ClampRadians(target_angle);
        npc->Printf("End angle should be %1.3f, or %1.3f\n",rot+radians,target_angle);

        csVector3 vel(0,(radians>0)?-GetAngularVelocity(npc):GetAngularVelocity(npc),0);

        npc->GetLinMove()->SetAngularVelocity( vel );

        //now persist
        npcclient->GetNetworkMgr()->QueueDRData(npc->GetEntity(),npc->GetLinMove(),npc->GetDRCounter());

        // wake me up when it's over
        psResumeScriptEvent *ev = new psResumeScriptEvent(msec,npc,eventmgr,npc->GetCurrentBehavior() );
        eventmgr->Push(ev);

        angle_delta=target_angle;

        return false;
    }
    else if (op_type == ROT_LOCATEDEST)
    {
        csVector3 dest,pos;
        float     rot=0;
        iSector  *sector;

        npc->GetActiveLocate(dest,rot);

        psGameObject::GetPosition(npc->GetEntity(),pos,rot,sector);

        if(pos == dest)
            return true;

        target_angle = psGameObject::CalculateIncidentAngle(pos,dest);

        psGameObject::ClampRadians(target_angle);
        psGameObject::GetRotationAngle(npc->GetEntity(),rot);

        ang_vel = GetAngularVelocity(npc);

        npc->Printf("Turning from rot %1.2f to angle %1.2f at %1.2f rad/sec...",rot,target_angle,ang_vel);

        float angle = target_angle-rot;
        if (angle > 3.14159)
            angle = angle-TWO_PI;
        if (angle < -3.14159)
            angle = angle+TWO_PI;

        // If the angle is close enough don't worry about it and just go to next command.
        if (fabs(angle) < TWO_PI/60)
            return true;

        int msec = (int)fabs(1000.0*angle/ang_vel);

        npc->GetLinMove()->SetAngularVelocity( csVector3(0,(angle>0)?-ang_vel:ang_vel,0) );

        csRef<iPcMesh> pcmesh = CEL_QUERY_PROPCLASS(npc->GetEntity()->GetPropertyClassList(), 
            iPcMesh);
        pcmesh->SetAction(action);

        npcclient->GetNetworkMgr()->QueueDRData(npc->GetEntity(),npc->GetLinMove(),npc->GetDRCounter());

        // wake me up when it's over
        psResumeScriptEvent *ev = new psResumeScriptEvent(msec,npc,eventmgr,npc->GetCurrentBehavior() );
        eventmgr->Push(ev);

        angle_delta=target_angle;

        npc->Printf("Rotating %1.2f in %d msec.\n",angle,msec);

        return false;
    }
    else if (op_type == ROT_ABSOLUTE)
    {
        float ang_vel = GetAngularVelocity(npc);
        float angle =  max_range ; 
        int msec = (int)fabs(1000.0*angle/ang_vel );

        npc->GetLinMove()->SetAngularVelocity( csVector3(0,(angle>0)?-ang_vel:ang_vel,0) );

        csRef<iPcMesh> pcmesh = CEL_QUERY_PROPCLASS(npc->GetEntity()->GetPropertyClassList(), 
            iPcMesh);
        pcmesh->SetAction(action);

        npcclient->GetNetworkMgr()->QueueDRData(npc->GetEntity(),npc->GetLinMove(),npc->GetDRCounter());

        // wake me up when it's over
        psResumeScriptEvent *ev = new psResumeScriptEvent(msec,npc,eventmgr,npc->GetCurrentBehavior() );
        eventmgr->Push(ev);

        angle_delta=target_angle;

        npc->Printf("Rotating %1.2f in %d msec.\n",angle,msec);

        return false;
    }

    return true;
}

float RotateOperation::SeekAngle(NPC* npc, float targetYRot)
{
    // Try to avoid big ugly stuff in our path
    float rot;
    iSector *sector;
    csVector3 pos;
    psGameObject::GetPosition(npc->GetEntity(),pos,rot,sector);

    csVector3 isect,start,end,dummy,box,legs;
    iPcCollisionDetection* pcDummy;

    // Construct the feeling broom

    // Calculate the start and end poses
    start = pos;
    npc->GetLinMove()->GetCDDimensions(box,legs,dummy,pcDummy);

    // We can walk over some stuff
    start += csVector3(0,0.6f,0);
    end = start + csVector3(sin(targetYRot), 0, cos(targetYRot)) * -2;

    // Feel
    csIntersectingTriangle closest_tri;
    iMeshWrapper* sel = 0;
    float dist = csColliderHelper::TraceBeam (npcclient->GetCollDetSys(), sector,
        start, end, true, closest_tri, isect, &sel);


    if(dist > 0)
    {
        const float begin = (PI/6); // The lowest turning constant
        const float length = 2;

        float left,right,turn = 0;
        for(int i = 1; i <= 3;i++)
        {
            csVector3 broomStart[2],broomEnd[2];

            // Left and right
            left = targetYRot - (begin * float(i));
            right = targetYRot + (begin * float(i));

            // Construct the testing brooms
            broomStart[0] = start;
            broomEnd[0] = start + csVector3(sin(left),0,cos(left)) * -length;

            broomStart[1] = start;
            broomEnd[1] = start + csVector3(sin(right),0,cos(right)) * -length;

            // The broom is already 0.6 over the ground, so we need to cut that out
            //broomStart[0].y += legs.y + box.y - 0.6;
            //broomEnd[1].y += legs.y + box.y - 0.6;

            // Check if we can get the broom through where we want to go
            float dist = csColliderHelper::TraceBeam (npcclient->GetCollDetSys(), sector,
                broomStart[0], broomEnd[0], true, closest_tri, isect, &sel);

            if(dist < 0)
            {
                npc->Printf("Turning left!\n");
                turn = left;
                break;
            }

            // Do again for the other side
            dist = csColliderHelper::TraceBeam (npcclient->GetCollDetSys(), sector,
                broomStart[1], broomEnd[1], true, closest_tri, isect, &sel);

            if(dist < 0)
            {
                npc->Printf("Turning right!\n");
                turn = right;
                break;
            }


        }
        if (turn==0.0)
          printf ("Possible ERROR: turn value was 0 for %s.", (const char *)npc->GetName());
        // Apply turn
        targetYRot = turn;
    }
    return targetYRot;
}
void RotateOperation::InterruptOperation(NPC *npc,EventManager *eventmgr)
{
    // In this case, interruption and completion are same result
    CompleteOperation(npc,eventmgr);
}

bool RotateOperation::CompleteOperation(NPC *npc,EventManager *eventmgr)
{
    //    CPrintf(CON_DEBUG, "Turn completed. Stopping now.\n");

    // Start the turn
    npc->GetLinMove()->SetAngularVelocity( csVector3(0,0,0) );
    //    if (angle_delta != 0)
    psGameObject::SetRotationAngle(npc->GetEntity(),angle_delta);

    //now persist
    npcclient->GetNetworkMgr()->QueueDRData(npc->GetEntity(),npc->GetLinMove(),npc->GetDRCounter());

    return true;  // Script can keep going
}

bool LocateOperation::Load(iDocumentNode *node)
{

    object = node->GetAttributeValue("obj");
    static_loc = node->GetAttributeValueAsBool("static",false);
    if (node->GetAttribute("range"))
    {
        range  = node->GetAttributeValueAsFloat("range");
    }
    else
    {
        range = -1;
    }
    random = node->GetAttributeValueAsBool("random",false);

    return true;
}

ScriptOperation *LocateOperation::MakeCopy()
{
    LocateOperation *op = new LocateOperation;
    op->range  = range;
    op->object = object;
    op->static_loc = static_loc;
    op->random = random;

    return op;
}


void LocateOperation::CalculateWaypointList(NPC *npc, csVector3 start_pos, iSector* start_sector,
                                            csVector3 located_pos, iSector* located_sector, float located_range)
{
    Waypoint *start, *end;
    float start_range = 0.0, end_range = 0.0;
    start = npcclient->FindNearestWaypoint(start_pos,start_sector,-1,&start_range);
    end   = npcclient->FindNearestWaypoint(located_pos,located_sector,-1,&end_range);

    npc->WaypointListClear();

    if (start && end && (located_range == -1 || start_range < located_range))
    {
        if (start == end)
        {
            npc->WaypointListPushBack(start);
        } else
        {
            csList<Waypoint*> waypoint_list;
            
            waypoint_list = npcclient->FindWaypointPath(start,end);
            while (!waypoint_list.IsEmpty())
            {
                npc->WaypointListPushBack(waypoint_list.Front());
                waypoint_list.PopFront();
            }
        }
    }
}


bool LocateOperation::Run(NPC *npc,EventManager *eventmgr)
{
    npc->Printf(">>>LocateOp ");

    // Reset old target
    npc->SetTarget(NULL);
    located_sector = NULL;

    float start_rot;
    iSector *start_sector;
    csVector3 start_pos;
    psGameObject::GetPosition(npc->GetEntity(),start_pos,start_rot,start_sector);


    if (object == "perception")
    {
        if (!npc->GetLastPerception())
            return true;
        if (!npc->GetLastPerception()->GetLocation(located_pos))
            return true;
        located_angle = 0; // not used in perceptions
    }
    else if (object == "target")
    {
        iCelEntity *ent;
        // Since we don't have a current enemy targeted, find one!
        if (range)
            ent = npc->GetMostHated(range);
        else
            ent = npc->GetMostHated(10.0f);     // Default enemy range

        if(ent)
            npc->SetTarget(ent);
        else
            return true;

        float rot;
        iSector *sector;
        csVector3 pos;
        psGameObject::GetPosition(ent,pos,rot,sector);

        located_pos = pos;
        located_angle = 0;
        located_sector = sector;
    }
    else if (object == "owner")
    {
        iCelEntity *ent;
        // Since we don't have a current enemy targeted, find one!
        ent = npc->GetOwner();

        if(ent)
            npc->SetTarget(ent);
        else
            return true;

        float rot;
        iSector *sector;
        csVector3 pos;
        psGameObject::GetPosition(ent,pos,rot,sector);

        located_pos = pos;
        located_angle = 0;
        located_sector = sector;
    }
    else if (object == "self")
    {
        iCelEntity *ent;

        ent = npc->GetEntity();

        if(ent)
            npc->SetTarget(ent);
        else
            return true;

        float rot;
        iSector *sector;
        csVector3 pos;
        psGameObject::GetPosition(ent,pos,rot,sector);

        located_pos = pos;
        located_angle = 0;
        located_sector = sector;
    }
    else if (object == "tribe:home")
    {
        if (!npc->GetTribe())
            return true;

        float radius;
        csVector3 pos;
        npc->GetTribe()->GetHome(pos,radius,located_sector);

        AddRandomRange(pos,radius);
 
        located_pos = pos;
        located_angle = 0;

        CalculateWaypointList(npc,start_pos,start_sector,located_pos,located_sector,-1);

    }
    else if(object == "friend")
    {
        iCelEntity *ent = npc->GetNearestVisibleFriend(20);
        if(ent)
            npc->SetTarget(ent);
        else
            return true;

        float rot;
        iSector *sector;
        csVector3 pos;
        psGameObject::GetPosition(ent,pos,rot,sector);

        located_pos = pos;
        located_angle = 0;
        located_sector = sector;
    }
    else if (object == "waypoint" )
    {
        float located_range=0.0;
        Waypoint * waypoint;

        if (random)
        {
            waypoint = npcclient->FindRandomWaypoint(start_pos,start_sector,range,&located_range);
        }
        else
        {
            waypoint = npcclient->FindNearestWaypoint(start_pos,start_sector,range,&located_range);
        }

        if (!waypoint)
        {
            npc->Printf("Couldn't locate any <%s> in npc script for <%s>.\n",
                (const char *)object,npc->GetEntity()->GetName() );
            return true;
        }
        located_pos = waypoint->loc.pos;
        located_angle = waypoint->loc.rot_angle;

        AddRandomRange(located_pos,waypoint->loc.radius);
        
        CalculateWaypointList(npc,start_pos,start_sector,located_pos,located_sector,located_range);

    }
    else if (!static_loc || !located)
    {
        float located_range=0.0;
        Location * location;

        if (random)
        {
            location = npcclient->FindRandomLocation(object,start_pos,start_sector,range,&located_range);
        }
        else
        {
            location = npcclient->FindNearestLocation(object,start_pos,start_sector,range,&located_range);
        }

        if (!location)
        {
            npc->Printf("Couldn't locate any <%s> in npc script for <%s>.\n",
                (const char *)object,npc->GetEntity()->GetName() );
            return true;
        }
        located_pos = location->pos;
        located_angle = location->rot_angle;
        located_sector = location->sector;

        AddRandomRange(located_pos,location->radius);
        
        if (static_loc)
            located = true;  // if it is a static location, we only have to do this locate once, and save the answer

        CalculateWaypointList(npc,start_pos,start_sector,located_pos,located_sector,located_range);

    }
    else
    {
        npc->Printf("remembered location from last time\n");
    }

    // Save on npc so other operations can refer to value
    npc->SetActiveLocate(located_pos,located_angle);

    psString wp_str;
    if (!npc->WaypointListEmpty())
    {
        csList<Waypoint*> wps = npc->WaypointListGet();
        Waypoint * wp;
        while (!wps.IsEmpty() && (wp = wps.Front()))
        {
            wp_str.AppendFmt(" %s ->",wp->name.GetDataSafe());
            wps.PopFront();
        }
        
    }
    npc->Printf("Located %s at %s (%.2f,%.2f,%2f,%s)",object.GetDataSafe(),
                wp_str.GetDataSafe(),located_pos.x,located_pos.y,located_pos.z,
                (located_sector?located_sector->QueryObject()->GetName():"NULL"));

    return true;
}


bool NavigateOperation::Load(iDocumentNode *node)
{
    action = node->GetAttributeValue("anim");
    return true;
}

ScriptOperation *NavigateOperation::MakeCopy()
{
    NavigateOperation *op = new NavigateOperation;
    op->action = action;
    op->vel    = vel;

    return op;
}

bool NavigateOperation::Run(NPC *npc,EventManager *eventmgr)
{
    npc->Printf(">>>NavigateOp ");

    csVector3 dest;
    float rot=0;

    npc->GetActiveLocate(dest,rot);
    npc->Printf("Located (%1.2f,%1.2f,%1.2f) at %1.2f m/sec.\n",dest.x,dest.y,dest.z, GetVelocity(npc) );

    csVector3 pos;
    iSector* sector;
    float yrot;

    psGameObject::GetPosition(npc->GetEntity(), pos, yrot, sector);

    StartMoveTo(npc,eventmgr,dest,sector,GetVelocity(npc),action);
    return false;
}

void NavigateOperation::Advance(float timedelta,NPC *npc,EventManager *eventmgr) 
{
    npc->Printf("NavigationOp advanced\n"); 
}

bool NavigateOperation::CompleteOperation(NPC *npc,EventManager *eventmgr)
{
    npc->Printf("Navigate completed. Stopping now.\n");

    // Stop the movement

    // Set Vel to zero again
    npc->GetLinMove()->SetVelocity( csVector3(0,0,0) );

    // Get the rot and sector here so they don't change in the SetPosition call
    float rot=0;
    iSector *sector;
    csVector3 pos;
    psGameObject::GetPosition(npc->GetEntity(),pos,rot,sector);

    // Set position to where it is supposed to go
    float angle=0;
    npc->GetActiveLocate(pos,angle);
    npc->GetLinMove()->SetPosition(pos,rot,sector);

    //now persist
    npcclient->GetNetworkMgr()->QueueDRData(npc->GetEntity(),npc->GetLinMove(),npc->GetDRCounter());

    return true;  // Script can keep going
}

void ScriptOperation::AddRandomRange(csVector3& dest,float radius)
{
    float angle = rng.Get()*TWO_PI;
    float range = rng.Get() * radius;

    dest.x += cos(angle)*range;
    dest.z += sin(angle)*range;
}


void WanderOperation::CalculateTargetPos(csVector3& dest)
{
    dest = active_wp->loc.pos;
    AddRandomRange(dest,active_wp->loc.radius);
}

bool WanderOperation::FindNextWaypoint(NPC *npc)
{
    if (random)
    {
        while (true)
        {
            int which_next = rng.Get((int)active_wp->links.Length() );
            Waypoint *new_wp = active_wp->links[which_next];
            
            if (new_wp != prior_wp || active_wp->allow_return || active_wp->links.Length() == 1)
            {
                prior_wp = active_wp;
                active_wp = new_wp;
                return true;
            }
        }
        
    }
    else
    {
        prior_wp = active_wp;
        active_wp = npc->WaypointListGetNext();
        
        return (active_wp != NULL);
    }
    return false;
}

void WanderOperation::StartMoveToWaypoint(NPC *npc,EventManager *eventmgr)
{
    // now calculate new destination from new active wp
    CalculateTargetPos(dest);

    csVector3 pos;
    float rot;
    iSector* sector;
    psGameObject::GetPosition(npc->GetEntity(), pos, rot, sector);
    
    float height_diff = dest.y - current_pos.y;

    float secs = StartMoveTo(npc,eventmgr,dest,sector,GetVelocity(npc),action);
    height_delta_per_sec = height_diff / secs;
}


bool WanderOperation::Run(NPC *npc,EventManager *eventmgr)
{
    npc->Printf(">>>WanderOp ");
    // Locate the nearest waypoint and head towards it
    float rot=0;
    iSector *sector;
    csVector3 pos;
    psGameObject::GetPosition(npc->GetEntity(),pos,rot,sector);
    current_pos = pos;

    active_wp = npcclient->FindNearestWaypoint(pos,sector);

    if (!active_wp) {
      npc->Printf(">>>WanderOp NO waypoints find near NPC, he cannot move.");
      return false;
    }

    // Check if we are on the waypoint, in that case find next
    if (active_wp->CheckWithin(npcclient->GetEngine(),pos,sector))
    {
        if (!FindNextWaypoint(npc))
        {
            npc->Printf(">>>WanderOp At end of waypoint list.");
            return true;
        }
    }

    csRef<iPcCollisionDetection> colldet = CEL_QUERY_PROPCLASS(npc->GetEntity()->GetPropertyClassList(), 
                                                               iPcCollisionDetection);
    colldet->SetOnGround(true); // Wander is ALWAYS on_ground.  This ensures correct animation on the client.

    StartMoveToWaypoint(npc, eventmgr);

    return false;
}

void WanderOperation::Advance(float timedelta,NPC *npc,EventManager *eventmgr)
{
    // This updates the position of the entity every 1/2 second so that 
    // range and distance calculations will work when interrupted.
    csVector3 vel;
    csMatrix3 mat;

    npc->GetLinMove()->GetVelocity(vel);

    csRef<iPcMesh> pcmesh = CEL_QUERY_PROPCLASS(npc->GetEntity()->GetPropertyClassList(), 
        iPcMesh);

    csReversibleTransform rt = pcmesh->GetMesh ()->GetMovable ()->GetFullTransform ();
    mat = rt.GetT2O ();

    current_pos = mat*(vel*timedelta) + current_pos;
    current_pos.y += height_delta_per_sec * timedelta;

    psGameObject::SetPosition(npc->GetEntity(), current_pos);
    // Perhaps check for nearby npcs and dodge them here
    npc->Printf("Waypoint updated to %1.2f, %1.2f, %1.2f\n", current_pos.x, current_pos.y, current_pos.z);
}

bool WanderOperation::CompleteOperation(NPC *npc,EventManager *eventmgr)
{
    // Wander never really completes, so this finds the next
    // waypoint and heads toward it again.
    npc->Printf("Wander waypoint %s found...",active_wp->name.GetData() );

    psGameObject::SetPosition(npc->GetEntity(),dest);
    current_pos = dest;  // make sure waypoint is starting point of next path

    if (FindNextWaypoint(npc))
    {
        StartMoveToWaypoint(npc, eventmgr);

        return false;  // Script requeues termination event so this CompleteOp is essentially an infinite loop
    }
    
    // Stop the movement
    npc->GetLinMove()->SetVelocity(csVector3(0,0,0));

    return true; // End script if no more waypoints.
}

bool WanderOperation::Load(iDocumentNode *node)
{
    action = node->GetAttributeValue("anim");
    vel    = node->GetAttributeValueAsFloat("vel");
    random = node->GetAttributeValueAsBool("random",false);  // Random wander never ends

    return true;
}

ScriptOperation *WanderOperation::MakeCopy()
{
    WanderOperation *op = new WanderOperation;
    op->action = action;
    op->vel    = vel;
    op->random = random;

    return op;
}

bool ChaseOperation::Load(iDocumentNode *node)
{
    action = node->GetAttributeValue("anim");
    csString typestr = node->GetAttributeValue("type");
    if (typestr == "nearest")
        type = NEAREST;
    else if (typestr == "boss")
        type = OWNER;
    else if (typestr == "owner")
        type = OWNER;
    else if (typestr == "target")
        type = TARGET;
    else
        type = UNKNOWN;

    if (node->GetAttributeValue("range"))
        range = node->GetAttributeValueAsFloat("range");
    else
        range = 2;

    if ( node->GetAttributeValue("offset_x") )
        offset.x = node->GetAttributeValueAsFloat("offset_x");
    else
        offset.x = 0.5F;
    
    if ( node->GetAttributeValue( "offset_z" ) )
        offset.z = node->GetAttributeValueAsFloat("offset_z");
    else
        offset.z = 0.5F;

    offset.y = 0.0f;
    return true;
}

ScriptOperation *ChaseOperation::MakeCopy()
{
    ChaseOperation *op = new ChaseOperation;
    op->action = action;
    op->type   = type;
    op->range  = range;
    op->vel    = vel;
    op->offset = offset;

    return op;
}

bool ChaseOperation::Run(NPC *npc,EventManager *eventmgr)
{
    npc->Printf("Starting ChaseOp");
    float targetRot;
    iSector* targetSector;
    csVector3 targetPos;
    
    float myRot;
    iSector* mySector;
    csVector3 myPos;

    csVector3 dest;
    csString name;
    iCelEntity *entity;
    target_id = (uint32_t)~0;
        
    switch (type)
    {
        case NEAREST:
            npc->GetNearestEntity(target_id,dest,name,range);
            npc->Printf("Targeting nearest entity (%s) at (%1.2f,%1.2f,%1.2f) for chase ...\n",
                        (const char *)name,dest.x,dest.y,dest.z);
            if ( target_id != -1 ) entity = npcclient->FindEntity(target_id);
            break;
        case OWNER:
            entity = npc->GetOwner();
            if (entity)
            {
                target_id = entity->GetID();
                psGameObject::GetPosition(entity, dest,targetRot,targetSector);
                npc->Printf("Targeting owner (%s) at (%1.2f,%1.2f,%1.2f) for chase ...\n",
                            entity->GetName(),dest.x,dest.y,dest.z );

            }
            break;
        case TARGET:
            entity = npc->GetTarget();
            if (entity)
            {
                target_id = entity->GetID();
                psGameObject::GetPosition(entity, dest,targetRot,targetSector);
                npc->Printf("Targeting current target (%s) at (%1.2f,%1.2f,%1.2f) for chase ...\n",
                            entity->GetName(),dest.x,dest.y,dest.z );
            }
            break;
    }

    if ( ( target_id != -1 ) && entity )
    {
        psGameObject::GetPosition(npc->GetEntity(),myPos,myRot,mySector);
    
        psGameObject::GetPosition(entity, targetPos,targetRot,targetSector);
        CPrintf(CON_ERROR,"npc %i chasing enemy %i at %f %f %f\n",npc->GetID(), entity->GetID(), targetPos.x,targetPos.y,targetPos.z);

        // We need to work in the target sector space
        npcclient->GetWorld()->WarpSpace(mySector, targetSector, myPos);

        // This prevents NPCs from wanting to occupy the same physical space as something else
        csVector3 displacement = targetPos - myPos;
        float factor = sqrt((offset.x * offset.x)+(offset.z * offset.z)) / displacement.Norm();
        targetPos = displacement - factor * displacement;

        path.SetMaps(npcclient->GetMaps());
        path.SetDest(targetPos);
        path.CalcLocalDest(myPos, mySector, localDest);
        if ( Calc2DDistance( myPos, targetPos ) < .1 )
        {
            return true;  // This operation is complete
        }
        else if ( npc->GetAngularVelocity() > 0 || npc->GetVelocity() > 0 )
        {
            StartMoveTo(npc,eventmgr,localDest,targetSector, GetVelocity(npc),action, false);
            return false;
        }
        else
            return true;  // This operation is complete
    }
    else
    {
        npc->Printf("No one found to chase!");
        return true;
    }
}

void ChaseOperation::Advance(float timedelta,NPC *npc,EventManager *eventmgr)
{

    csVector3 myPos,myNewPos,targetPos;
    float     myRot,dummyrot;
    iSector * mySector, *targetSector;
    csVector3 forward;
    
    npc->GetLinMove()->GetLastPosition(myPos,myRot,mySector);
    

    // Now turn towards entity being chased again
    csString name;
    
    if (type == NEAREST)
        npc->GetNearestEntity(target_id,targetPos,name,range);
 
    iCelEntity *target_entity = npcclient->FindEntity(target_id);

    if (!target_entity) // no entity close to us
    {
        npc->Printf("ChaseOp has no target now!");
        // ResumeScript is called when a timed operation finishes.
        //npc->ResumeScript(eventmgr, npc->GetBrain()->GetCurrentBehavior() );
        return;
    }
    
    if(name.IsEmpty())
        name = target_entity->GetName();
    psGameObject::GetPosition(target_entity,targetPos,dummyrot,targetSector);

    // We now work in the target sector's space
    csVector3 myOldPos(myPos);
    npcclient->GetWorld()->WarpSpace(mySector, targetSector, myPos);

    // This prevents NPCs from wanting to occupy the same physical space as something else
    csVector3 displacement = targetPos - myPos;
    float factor = sqrt((offset.x * offset.x)+(offset.z * offset.z)) / displacement.Norm();
    targetPos = myPos + (1 - factor) * displacement;

    npc->Printf("Still chasing %s at (%1.2f,%1.2f,%1.2f)...\n",(const char *)name,targetPos.x,targetPos.y,targetPos.z);
    
    
    /*if (psGameObject::Distance(pos,pos2) < range)
    {
        // ResumeScript is called when a timed operation finishes.
        npc->ResumeScript(eventmgr, npc->GetBrain()->GetCurrentBehavior() );
        // CompleteOperation(npc,eventmgr);
        return;
    }*/
    
    float angleToTarget = psGameObject::CalculateIncidentAngle(myPos, targetPos);
    csVector3 pathDest = path.GetDest();
    float angleToPath  = psGameObject::CalculateIncidentAngle(myPos, pathDest);

        
    // if the target diverged from the end of our path, we must calculate it again
    if ( fabs( AngleDiff(angleToTarget, angleToPath) ) > EPSILON  )
    {
        npc->Printf("turn to target..\n");
        path.SetDest(targetPos);
        //        path.CalcLocalDest(myPos, mySector, localDest);
        path.CalcLocalDest(myPos, targetSector, localDest);
        StartMoveTo(npc,eventmgr,localDest, targetSector, GetVelocity(npc),action, false);
    }
    
    
    if (Calc2DDistance(localDest, myPos) <= 0.5)
    {
        myPos.x = localDest.x;
        myPos.z = localDest.z;
        npc->GetLinMove()->SetPosition(myPos,myRot,mySector);
        
        if (Calc2DDistance(myPos,targetPos) <= 0.5)
        {
            npc->Printf("we are done..\n");
            npc->ResumeScript(eventmgr, npc->GetBrain()->GetCurrentBehavior() );
            return;
        }
        else
        {
            npc->Printf("we are at localDest..\n");
            path.SetDest(targetPos);
            path.CalcLocalDest(myPos, targetSector, localDest);
            StartMoveTo(npc,eventmgr,localDest, targetSector,vel,action, false);
        }
    }
    else
        TurnTo(npc,localDest, targetSector,forward);


    npc->Printf("advance: pos=(%f.2,%f.2,%f.2) rot=%.2f localDest=(%f.2,%f.2,%f.2) dist=%f\n", 
                myPos.x,myPos.y,myPos.z, myRot,
                localDest.x,localDest.y,localDest.z,
                Calc2DDistance(localDest, myPos));
        
    npc->GetLinMove()->ExtrapolatePosition(timedelta);
    npc->GetLinMove()->GetLastPosition(myNewPos,myRot,mySector);
    if((fabs(myPos.x)> 1000 || fabs(myNewPos.x)> 1000) || (fabs(myPos.y)>1000 || fabs(myNewPos.y)>1000) || (fabs(myPos.z)>1000 || fabs(myNewPos.z)>1000))
    {
        npc->Printf("Moved from %f %f %f to %f %f %f, timedelta is %f, chase error!\n",npc->GetID(), npc->GetName().GetData(), myPos.x,myPos.y,myPos.z, myNewPos.x,myNewPos.y,myNewPos.z, timedelta);
    }

    // This check must be done in our original sector's space
    if ((myOldPos - myNewPos).SquaredNorm() < SMALL_EPSILON) // then stopped dead, presumably by collision
    {
        Perception collision("collision");
        npc->TriggerEvent(&collision,eventmgr);
        npc->Printf("Collided! Moving from %f %f %f to %f %f %f, timedelta is %f!\n",npc->GetID(), npc->GetName().GetData(), myPos.x,myPos.y,myPos.z, myNewPos.x,myNewPos.y,myNewPos.z, timedelta);
    }
}

void ChaseOperation::InterruptOperation(NPC *npc,EventManager *eventmgr)
{
    // Stop the movement

    // Set Vel to zero again
    npc->GetLinMove()->SetVelocity( csVector3(0,0,0) );

    //now persist
    npcclient->GetNetworkMgr()->QueueDRData(npc->GetEntity(),npc->GetLinMove(),npc->GetDRCounter());
}

bool ChaseOperation::CompleteOperation(NPC *npc,EventManager *eventmgr)
{
    npc->Printf("Chase completed. Stopping now.\n");

    // Stop the movement
    // Set Vel to zero again
    npc->GetLinMove()->SetVelocity( csVector3(0,0,0) );

    //now persist
    npcclient->GetNetworkMgr()->QueueDRData(npc->GetEntity(),npc->GetLinMove(),npc->GetDRCounter());

    return true;  // Script can keep going
}

bool PickupOperation::Load(iDocumentNode *node)
{
    object = node->GetAttributeValue("obj");
    slot   = node->GetAttributeValue("equip");
    count   = node->GetAttributeValueAsInt("count");
    if (count <= 0) count = 1; // Allways pick up at least one.
    return true;
}

ScriptOperation *PickupOperation::MakeCopy()
{
    PickupOperation *op = new PickupOperation;
    op->object = object;
    op->slot   = slot;
    op->count  = count;
    return op;
}

bool PickupOperation::Run(NPC *npc,EventManager *eventmgr)
{
    npc->Printf("PickupOp ");

    iCelEntity *item = NULL;

    if (object == "perception")
    {
        if (!npc->GetLastPerception())
            return true;
        if (!(item = npc->GetLastPerception()->GetEntity()))
            return true;
    } else
    {
        // TODO: Insert code to find the nearest item
        //       with name given by object.
        return true;
    }

    npc->Printf("   Who: %s What: %s Count: %d",npc->GetName().GetData(),item->GetName(), count);

    npcclient->GetNetworkMgr()->QueuePickupCommand(npc->GetEntity(), item, count);

    return true;
}

bool EquipOperation::Load(iDocumentNode *node)
{
    item    = node->GetAttributeValue("item");
    slot    = node->GetAttributeValue("slot");
    count   = node->GetAttributeValueAsInt("count");
    if (count <= 0) count = 1; // Allways equip at least one.
    return true;
}

ScriptOperation *EquipOperation::MakeCopy()
{
    EquipOperation *op = new EquipOperation;
    op->item   = item;
    op->slot   = slot;
    op->count  = count;
    return op;
}

bool EquipOperation::Run(NPC *npc,EventManager *eventmgr)
{
    npc->Printf("EquipOp ");

    iCelEntity *obj = NULL;

    npc->Printf("   Who: %s What: %s Where: %s Count: %d",
                npc->GetName().GetData(),item.GetData(), slot.GetData(), count);

    npcclient->GetNetworkMgr()->QueueEquipCommand(npc->GetEntity(), item, slot, count);

    return true;
}

bool DequipOperation::Load(iDocumentNode *node)
{
    slot   = node->GetAttributeValue("slot");
    return true;
}

ScriptOperation *DequipOperation::MakeCopy()
{
    DequipOperation *op = new DequipOperation;
    op->slot   = slot;
    return op;
}

bool DequipOperation::Run(NPC *npc,EventManager *eventmgr)
{
    npc->Printf("DequipOp ");

    iCelEntity *obj = NULL;

    npc->Printf("   Who: %s Where: %s",
                npc->GetName().GetData(), slot.GetData());

    npcclient->GetNetworkMgr()->QueueDequipCommand(npc->GetEntity(), slot );

    return true;
}

bool TalkOperation::Load(iDocumentNode *node)
{
    text = node->GetAttributeValue("text");
    target = node->GetAttributeValueAsBool("target");
    command = node->GetAttributeValue("command");

    return true;
}

ScriptOperation *TalkOperation::MakeCopy()
{
    TalkOperation *op = new TalkOperation;
    op->text = text;
    op->target = target;
    op->command = command;
    return op;
}

bool TalkOperation::Run(NPC *npc,EventManager *eventmgr)
{

    if(!target)
    {
        npcclient->GetNetworkMgr()->QueueTalkCommand(npc->GetEntity(), npc->GetName() + text);
        return true;
    }
    
    if(!npc->GetTarget())
        return true;

    NPC* friendNPC = npcclient->FindAttachedNPC(npc->GetTarget());
    if(friendNPC)
    {
        npcclient->GetNetworkMgr()->QueueTalkCommand(npc->GetEntity(), npc->GetName() + text);
        
        Perception collision("friend:" + command);
        friendNPC->TriggerEvent(&collision,eventmgr);
    }
    return true;
}

bool VisibleOperation::Load(iDocumentNode *node)
{
    return true;
}

ScriptOperation *VisibleOperation::MakeCopy()
{
    VisibleOperation *op = new VisibleOperation;
    return op;
}

bool VisibleOperation::Run(NPC *npc,EventManager *eventmgr)
{
    npc->Printf(">>>VisibleOp\n");
    npcclient->GetNetworkMgr()->QueueVisibilityCommand(npc->GetEntity(), true);
    return true;
}

bool InvisibleOperation::Load(iDocumentNode *node)
{
    return true;
}

ScriptOperation *InvisibleOperation::MakeCopy()
{
    InvisibleOperation *op = new InvisibleOperation;
    return op;
}

bool InvisibleOperation::Run(NPC *npc,EventManager *eventmgr)
{
    npc->Printf(">>>InvisibleOp\n");
    npcclient->GetNetworkMgr()->QueueVisibilityCommand(npc->GetEntity(), false);
    return true;
}

bool ReproduceOperation::Load(iDocumentNode *node)
{
    return true;
}

ScriptOperation *ReproduceOperation::MakeCopy()
{
    ReproduceOperation *op = new ReproduceOperation;
    return op;
}

bool ReproduceOperation::Run(NPC *npc,EventManager *eventmgr)
{
    if(!npc->GetTarget())
        return true;
    NPC * friendNPC = npcclient->FindAttachedNPC(npc->GetTarget());
    if(friendNPC)
        npcclient->GetNetworkMgr()->QueueSpawnCommand(npc->GetEntity(), npc->GetTarget());
    return true;
}

bool MeleeOperation::Load(iDocumentNode *node)
{
    seek_range   = node->GetAttributeValueAsFloat("seek_range");
    melee_range  = node->GetAttributeValueAsFloat("melee_range");
    // hardcoded in server atm to prevent npc/server conflicts
    melee_range  = 3.0f;
    return true;
}

ScriptOperation *MeleeOperation::MakeCopy()
{
    MeleeOperation *op = new MeleeOperation;
    op->seek_range  = seek_range;
    op->melee_range = melee_range;
    attacked_ent = NULL;
    return op;
}

bool MeleeOperation::Run(NPC *npc,EventManager *eventmgr)
{
    npc->Printf("MeleeOperation starting.");

    completed = false;

    attacked_ent = npc->GetMostHated(seek_range);
    if (attacked_ent)
    {
        npcclient->GetNetworkMgr()->QueueAttackCommand(npc->GetEntity(),attacked_ent);
    }
    else
    {
        // We know who attacked us, even if they aren't in range.
        npc->SetTarget( npc->GetLastPerception()->GetEntity() );
    }

    return false;
}

void MeleeOperation::Advance(float timedelta,NPC *npc,EventManager *eventmgr)
{
    if (completed)
        return;

    // Check hate list to make sure we are still attacking the right person
    iCelEntity *ent = npc->GetMostHated(melee_range);
    if (!ent)
    {
        npc->Printf("No Melee target in range (%2.2f), going to chase!",melee_range);

        // No enemy to whack on in melee range, search far
        ent = npc->GetMostHated(seek_range);

        // The idea here is to save the next best target and chase
        // him if out of range.
        if (ent)
        {
            npc->SetTarget(ent);
        
            // If the chase doesn't work, it will return to fight, which still
            // may not find a target, and return to chase.  This -10 reduces
            // the need to fight as he can't find anyone and stops this infinite
            // loop.
            npc->GetCurrentBehavior()->ApplyNeedDelta(-5);

            Perception range("target out of range");
            npc->TriggerEvent(&range,eventmgr);
        }
        else // no hated targets around
        {
            if(npc->IsDebugging())
                npc->DumpHateList();
            npc->Printf("No hated target in seek range (%2.2f)!",seek_range);
            npc->GetCurrentBehavior()->ApplyNeedDelta(-5); // don't want to fight as badly
        }
        return;
    }
    if (ent != attacked_ent)
    {
        npc->Printf("Melee switching to attack %s\n",ent->GetName() );
        attacked_ent = ent;
        npcclient->GetNetworkMgr()->QueueAttackCommand(npc->GetEntity(),ent);
    }
}

void MeleeOperation::InterruptOperation(NPC *npc,EventManager *eventmgr)
{
    npc->Printf("Interrupting melee operation");
    npcclient->GetNetworkMgr()->QueueAttackCommand(npc->GetEntity(),NULL);
}

bool MeleeOperation::CompleteOperation(NPC *npc,EventManager *eventmgr)
{
    npc->Printf("Completing melee operation");
    npcclient->GetNetworkMgr()->QueueAttackCommand(npc->GetEntity(),NULL);
    completed = true;

    return true;
}




bool BeginLoopOperation::Load(iDocumentNode *node)
{
    iterations = node->GetAttributeValueAsInt("iterations");
    return true;
}

ScriptOperation *BeginLoopOperation::MakeCopy()
{
    BeginLoopOperation *op = new BeginLoopOperation;
    op->iterations = iterations;
    return op;
}

bool BeginLoopOperation::Run(NPC *npc,EventManager *eventmgr)
{
    npc->Printf("BeginLoop");
    return true;
}

bool EndLoopOperation::Load(iDocumentNode *node)
{
    return true;
}

ScriptOperation *EndLoopOperation::MakeCopy()
{
    EndLoopOperation *op = new EndLoopOperation(loopback_op,iterations);
    return op;
}

bool EndLoopOperation::Run(NPC *npc,EventManager *eventmgr)
{
    Behavior * behavior = npc->GetCurrentBehavior();

    current++;

    if (current < iterations)
    {
        behavior->SetCurrentStep(loopback_op-1);
        npc->Printf("EndLoop - Loop %d of %d",current,iterations);
        return true;
    }

    current = 0; // Make sure we will loop next time to

    npc->Printf("EndLoop - Exit %d %d",current,iterations);
    return true;
}

bool WaitOperation::Load(iDocumentNode *node)
{
    duration = node->GetAttributeValueAsFloat("duration");
    action = node->GetAttributeValue("anim");
    return true;
}

ScriptOperation *WaitOperation::MakeCopy()
{
    WaitOperation *op = new WaitOperation;
    op->duration = duration;
    op->action   = action;
    return op;
}

bool WaitOperation::Run(NPC *npc,EventManager *eventmgr)
{
    npc->Printf(">>>WaitOp\n");

    csRef<iPcMesh> pcmesh = CEL_QUERY_PROPCLASS(npc->GetEntity()->GetPropertyClassList(), 
        iPcMesh);

    // SetAction animation for the mesh, so it looks right
    pcmesh->SetAction(action);

    //now persist
    npcclient->GetNetworkMgr()->QueueDRData(npc->GetEntity(),npc->GetLinMove(),npc->GetDRCounter());

    psResumeScriptEvent *ev = new psResumeScriptEvent((int)(duration*1000.0),npc,eventmgr,npc->GetCurrentBehavior() );
    eventmgr->Push(ev);

    return false;
}

void WaitOperation::Advance(float timedelta,NPC *npc,EventManager *eventmgr)
{
    npc->Printf("waiting...\n");
}

bool DropOperation::Load(iDocumentNode *node)
{
    slot = node->GetAttributeValue("slot");
    return true;
}

ScriptOperation *DropOperation::MakeCopy()
{
    DropOperation *op = new DropOperation;
    op->slot = slot;
    return op;
}

bool DropOperation::Run(NPC *npc,EventManager *eventmgr)
{
    npc->Printf("DropOp ");

    npcclient->GetNetworkMgr()->QueueDropCommand(npc->GetEntity(), slot );

    return true;
}

bool DigOperation::Load(iDocumentNode *node)
{
    resource = node->GetAttributeValue("resource");
    return true;
}

ScriptOperation *DigOperation::MakeCopy()
{
    DigOperation *op = new DigOperation;
    op->resource = resource;
    return op;
}

bool DigOperation::Run(NPC *npc,EventManager *eventmgr)
{
    npc->Printf("DigOp ");

    npcclient->GetNetworkMgr()->QueueDigCommand(npc->GetEntity(), resource );

    return true;
}

bool DebugOperation::Load(iDocumentNode *node)
{
    exclusive = node->GetAttributeValue("exclusive");
    state = node->GetAttributeValueAsBool("state");
    return true;
}

ScriptOperation *DebugOperation::MakeCopy()
{
    DebugOperation *op = new DebugOperation;
    op->exclusive = exclusive;
    op->state = state;
    return op;
}

bool DebugOperation::Run(NPC *npc,EventManager *eventmgr)
{
    if (exclusive.Length())
    {
        static bool debug_exclusive = false;
        
        if (state && debug_exclusive)
        {
            // Can't turn on when exclusive is set.
            return true;
        }
        if (state)
        {
            debug_exclusive = true;
        }
        else
        {
            debug_exclusive = false;
        }
    }

    
    if (!state) // Print before when turning off
    {
        npc->Printf("DebugOp Set debug %s",(state?"on":"off"));
    }
    
    npc->SetDebugging(state);
    
    if (state) // Print after when turning on
    {
        npc->Printf("DebugOp Set debug %s",(state?"on":"off"));            
    }

    return true;
}

bool MovePathOperation::Load(iDocumentNode *node)
{
    pathname = node->GetAttributeValue("path");
    path = NULL;
    return true;
}

ScriptOperation *MovePathOperation::MakeCopy()
{
    MovePathOperation *op = new MovePathOperation;
    op->pathname = pathname;
    op->action   = action;
    op->path     = NULL;

    return op;
}

bool MovePathOperation::Run(NPC *npc,EventManager *eventmgr)
{
    csVector3 offsetvec(0,0,0),startpt(0,0,0);

    if (!path)
        path = npcclient->GetPathManager()->CreatePath(pathname,offsetvec,startpt);

    if (!path)
        return true;

    npc->GetLinMove()->SetPath((iPath *)path);
    npc->GetLinMove()->SetPathTime(0);
    npc->GetLinMove()->SetPathSpeed(1);

    // Get Going at the right velocity
    csVector3 velvec(0,0,-GetVelocity(npc) );
    npc->GetLinMove()->SetVelocity(velvec);

    Spline *spl = npcclient->GetPathManager()->Find(pathname);
    for (int i=0; i < spl->points.Length(); i++)
    {
        npc->GetLinMove()->SetPathAction(i,spl->points[i]->action);
    }
    npc->GetLinMove()->SetPathSector(spl->sector);

    npcclient->GetNetworkMgr()->QueueDRData(npc->GetEntity(),npc->GetLinMove(),npc->GetDRCounter());

    return false;
}

void MovePathOperation::Advance(float timedelta,NPC *npc,EventManager *eventmgr)
{
    npc->GetLinMove()->ExtrapolatePosition(timedelta);

    csVector3 pos; float rot; iSector *sec;
    psGameObject::GetPosition(npc->GetEntity(),pos,rot,sec);

    npc->Printf("MovePath Loc is %1.2f, %1.2f, %1.2f Rot: %1.2f\n",pos.x,pos.y,pos.z,rot);

    // None linear movement so we have to queue DRData updates.

    npcclient->GetNetworkMgr()->QueueDRData(npc->GetEntity(),npc->GetLinMove(),npc->GetDRCounter());

    if (!npc->GetLinMove()->IsPath())
    {
        npc->GetLinMove()->SetPathSpeed(0);
        npc->GetLinMove()->SetVelocity( csVector3(0,0,0) );
        npcclient->GetNetworkMgr()->QueueDRData(npc->GetEntity(),npc->GetLinMove(),npc->GetDRCounter());

        npc->Printf("we are done..\n");
        npc->ResumeScript(eventmgr, npc->GetBrain()->GetCurrentBehavior() );
        return;
    }

}

void MovePathOperation::InterruptOperation(NPC *npc,EventManager *eventmgr)
{
    npc->GetLinMove()->SetPathSpeed(0);
    npc->GetLinMove()->SetVelocity( csVector3(0,0,0) );
    npcclient->GetNetworkMgr()->QueueDRData(npc->GetEntity(),npc->GetLinMove(),npc->GetDRCounter());
}





psResumeScriptEvent::psResumeScriptEvent(int offsetticks, NPC *which,EventManager *mgr,Behavior *behave)
: psGameEvent(0,offsetticks,"psResumeScriptEvent")
{
    npc = which;
    eventmgr = mgr;
    behavior = behave;
}

void psResumeScriptEvent::Trigger()
{
    if (running)
        npc->ResumeScript(eventmgr,behavior);
}


void psGameObject::GetPosition(iCelEntity *entity, csVector3& pos, float& yrot,iSector*& sector)
{
    csRef<iPcMesh> pcmesh = CEL_QUERY_PROPCLASS(entity->GetPropertyClassList(), 
        iPcMesh);

    // Position
    if(!pcmesh->GetMesh())
    {
        CPrintf(CON_ERROR,"ERROR! NO MESH FOUND FOR OBJECT %s!\n",entity->GetName());
        return;
    }

    pos = pcmesh->GetMesh()->GetMovable()->GetPosition();

    // rotation
    csMatrix3 transf = pcmesh->GetMesh()->GetMovable()->GetTransform().GetT2O();
    yrot = Matrix2YRot(transf);

    // Sector
    if (pcmesh->GetMesh()->GetMovable()->GetSectors()->GetCount())
        sector = pcmesh->GetMesh()->GetMovable()->GetSectors()->Get(0);
    else
        sector = NULL;
}

void psGameObject::SetPosition(iCelEntity *entity, csVector3& pos, iSector* sector)
{
    csRef<iPcMesh> pcmesh = CEL_QUERY_PROPCLASS(entity->GetPropertyClassList(), 
        iPcMesh);

    pcmesh->MoveMesh(sector,pos);

    if (pos.z > 1000 || pos.z < -1000)
        pos.z = pos.z;
}

float psGameObject::Matrix2YRot(const csMatrix3& mat)
{
    csVector3 vec(0,0,1);
    vec = mat * vec;
    vec.Normalize();

    //    CPrintf(CON_SPAM, "Get angle from %1.3f, %1.3f...",vec.z,vec.x);
    return GetAngle (vec.z, vec.x);
}

float psGameObject::GetAngle(float x, float y)
{
    if ( x > 1.0 )  x = 1.0;
    if ( x < -1.0 ) x = -1.0;

    float angle = acos(x);
    if (y < 0)
        angle = 2*PI - angle;

    //    CPrintf(CON_SPAM, "Got angle of %1.3f rads or %1.2f degrees.\n", angle, angle*360/TWO_PI);
    return angle;
}

void psGameObject::SetRotationAngle(iCelEntity *entity, float angle)
{
    csRef<iPcMesh> pcmesh = CEL_QUERY_PROPCLASS(entity->GetPropertyClassList(), 
        iPcMesh);

    csMatrix3 matrix = (csMatrix3) csYRotMatrix3 (angle);
    pcmesh->GetMesh()->GetMovable()->GetTransform().SetO2T (matrix);
}

float psGameObject::CalculateIncidentAngle(csVector3& pos, csVector3& dest)
{
    csVector3 diff = dest-pos;  // Get vector from player to desired position

    if (!diff.x)
        diff.x = .000001F; // div/0 protect

    float angle = atan2(-diff.x,-diff.z);
    //    CPrintf(CON_SPAM, "%1.3f/%1.3f atan2 yields %1.3f radians.\n",-diff.x,-diff.z,angle);

    return angle;
}

float psGameObject::Distance(csVector3& pos, iSector* sector, csVector3& dest, iSector* sector2)
{
    csVector3 displacement(pos);

    npcclient->GetWorld()->WarpSpace(sector, sector2, displacement);
    displacement = dest-displacement;

    return displacement.Norm();
}

void psGameObject::ClampRadians(float &target_angle)
{
    while (target_angle < 0)
        target_angle += 2*3.14159F;
    while (target_angle > 2*3.14159F)
        target_angle -= 2*3.14159F;
}



