/* * npcbehave.cpp by Keith Fulton * * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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/waypoint.h" #include "util/psconst.h" #include "util/strutil.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 iter = node->GetNodes(); while ( iter->HasNext() ) { csRef 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; iGetEventType(),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; xReact(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; xResetNeed(); behaviors[i]->SetActive(false); } active = NULL; } void BehaviorSet::Add(Behavior *b) { for (size_t i=0; iGetName(),b->GetName())) { delete behaviors[i]; behaviors[i] = b; // substitute return; } } behaviors.Push(b); } void BehaviorSet::Advance(csTicks delta,NPC *npc,EventManager *eventmgr) { while (true) { max_need = -999; bool behaviours_changed = false; // Go through and update needs based on time for (size_t i=0; iApplicableToNPCState(npc)) { b->Advance(delta,npc,eventmgr); if (behaviors[i]->CurrentNeed() != behaviors[i]->NewNeed()) { npc->Printf(3,"Advancing %-30s:\t%1.1f ->%1.1f", behaviors[i]->GetName(), behaviors[i]->CurrentNeed(), behaviors[i]->NewNeed() ); behaviours_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(); behaviours_changed = true; } b->CommitAdvance(); // Update key to correct value } } // Dump bahaviour list if changed if (behaviours_changed && npc->IsDebugging(3)) { npc->DumpBehaviorList(); } // now that behaviours are correctly sorted, select the first one Behavior *new_behaviour = behaviors[0]; // use it only if need > 0 if (new_behaviour->CurrentNeed()<=0 || !new_behaviour->ApplicableToNPCState(npc)) { npc->Printf(1,"NO Active applicable behavior." ); return; } if (new_behaviour != active) { if (!active) // if it's the first behaviour ever assigned to this npc { active = new_behaviour; active->SetActive(true); if (!active->StartScript(npc,eventmgr)) { break; } } else { npc->Printf(1,"Switching behavior from '%s' to '%s'", active->GetName(), new_behaviour->GetName() ); // Interrupt and stop current behaviour active->InterruptScript(npc,eventmgr); active->SetActive(false); // Set the new active behaviour active = new_behaviour; // Activate the new behaviour active->SetActive(true); if (!active->StartScript(npc,eventmgr)) { break; } } } else { break; } } npc->Printf(3,"Active behavior is '%s'", active->GetName() ); } void BehaviorSet::ResumeScript(NPC *npc,EventManager *eventmgr,Behavior *which) { if (which == active && which->ApplicableToNPCState(npc)) { 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; iGetName(),name)) return behaviors[i]; } return NULL; } void BehaviorSet::DumpBehaviorList() { for (size_t i=0; iIsInterrupted()?"*":" "), 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; interrupted = false; resume_after_interrupt = false; current_step = 0; } Behavior::Behavior(const char *n) { loop = false; is_active = false; need_decay_rate = 0; need_growth_rate = 0; completion_decay = 0; new_need=-999; interrupted = false; resume_after_interrupt = false; current_step = 0; name = n; } 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; is_applicable_when_dead = other.is_applicable_when_dead; resume_after_interrupt = other.resume_after_interrupt; for (size_t x=0; xMakeCopy() ); } // Instance local variables. No need to copy. current_step = 0; interrupted = false; } 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; } loop = node->GetAttributeValueAsBool("loop",false); need_decay_rate = node->GetAttributeValueAsFloat("decay"); completion_decay = node->GetAttributeValueAsFloat("completion_decay"); need_growth_rate = node->GetAttributeValueAsFloat("growth"); init_need = node->GetAttributeValueAsFloat("initial"); is_applicable_when_dead = node->GetAttributeValueAsBool("when_dead"); resume_after_interrupt = node->GetAttributeValueAsBool("resume",false); current_need = init_need; return LoadScript(node,true); } bool Behavior::LoadScript(iDocumentNode *node,bool top_level) { // Now read in script for this behavior csRef iter = node->GetNodes(); while ( iter->HasNext() ) { csRef 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 if ( strcmp( node->GetValue(), "resurrect" ) == 0 ) { ResurrectOperation *op = new ResurrectOperation; if (!op->Load(node)) { Error1("Could not load ResurrectOperation. Error in XML"); delete op; return false; } sequence.Push(op); } else if ( strcmp( node->GetValue(), "memorize" ) == 0 ) { MemorizeOperation *op = new MemorizeOperation; if (!op->Load(node)) { Error1("Could not load MemorizeOperation. 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::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); } } bool Behavior::ApplicableToNPCState(NPC *npc) { return npc->IsAlive() || (!npc->IsAlive() && is_applicable_when_dead); } bool Behavior::StartScript(NPC *npc,EventManager *eventmgr) { if (interrupted && resume_after_interrupt) { npc->Printf(1,"Resuming behavior %s after interrupt at step %d.",name.GetData(),current_step); interrupted = false; return RunScript(npc,eventmgr,true); } else { current_step = 0; return RunScript(npc,eventmgr,false); } } Behavior* BehaviorSet::Find(Behavior *key) { size_t found = behaviors.Find(key); return (found = SIZET_NOT_FOUND) ? NULL : behaviors[found]; } bool Behavior::RunScript(NPC *npc,EventManager *eventmgr,bool interrupted) { while (true) { while (current_step < sequence.Length() ) { npc->Printf(2,">>>Step %d %s operation%s",current_step,sequence[current_step]->GetName(), (interrupted?" Interrupted":"")); if (!sequence[current_step]->Run(npc,eventmgr,interrupted)) // Run returning false means that { // op is not finished but should // relinquish return false; // This behavior isn't done yet } interrupted = false; // Only the first script operation should be interrupted. current_step++; } if (current_step == sequence.Length()) { if (loop) { current_step = 0; // behaviors automatically loop around to the top npc->Printf(1,"Loop back to start of behaviour '%s'",GetName()); } else { if (completion_decay) { npc->Printf("Subtracting completion decay of %1f from behavior '%s'.",completion_decay,GetName() ); if (completion_decay == -1) new_need = 0; else new_need = current_need - completion_decay; } npc->Printf(1,"End of non looping behaviour '%s'",GetName()); break; // This behavior is done } } } return true; // This behavior is done } void Behavior::InterruptScript(NPC *npc,EventManager *eventmgr) { if (current_step < sequence.Length() ) { sequence[current_step]->InterruptOperation(npc,eventmgr); interrupted = true; } } bool 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++; return RunScript(npc,eventmgr,false); } } else { current_step=0; return RunScript(npc,eventmgr,false); } } ScriptOperation::ScriptOperation(const char* scriptName) :completed(true),resumeScriptEvent(NULL),name(scriptName) { } 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"); } void ScriptOperation::InterruptOperation(NPC *npc,EventManager *eventmgr) { npc->Printf("Interrupting %s",name.GetDataSafe()); StopResume(); psGameObject::GetPosition(npc->GetEntity(),interrupted_position,interrupted_angle,interrupted_sector); } bool ScriptOperation::AtInterruptedPosition(const csVector3& pos, const iSector* sector) { return (npcclient->GetWorld()->Distance(pos,sector,interrupted_position,interrupted_sector) < 0.5f); } bool ScriptOperation::AtInterruptedAngle(const csVector3& pos, const iSector* sector, float angle) { return (npcclient->GetWorld()->Distance(pos,sector,interrupted_position,interrupted_sector) < 0.5f && fabs(angle - interrupted_angle) < 0.01f); } bool ScriptOperation::AtInterruptedPosition(NPC *npc) { float angle; csVector3 pos; iSector *sector; psGameObject::GetPosition(npc->GetEntity(),pos,angle,sector); return AtInterruptedPosition(pos,sector); } bool ScriptOperation::AtInterruptedAngle(NPC *npc) { float angle; csVector3 pos; iSector *sector; psGameObject::GetPosition(npc->GetEntity(),pos,angle,sector); return AtInterruptedAngle(pos,sector,angle); } 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,bool interrupted) { 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 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->SetAnimation(action, false); //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) { Resume((int)(duration*1000.0),npc,eventmgr); } return false; } void MoveOperation::InterruptOperation(NPC *npc,EventManager *eventmgr) { ScriptOperation::InterruptOperation(npc,eventmgr); StopMovement(npc); } bool MoveOperation::CompleteOperation(NPC *npc,EventManager *eventmgr) { StopMovement(npc); return true; // Script can keep going } void MoveOperation::Advance(float timedelta,NPC *npc,EventManager *eventmgr) { csRef 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(npcclient->GetEngine(),pos2,sector)) { Perception outbounds("out of bounds"); npc->TriggerEvent(&outbounds,eventmgr); inside_rgn = false; } } else { if (rgn->CheckWithinBounds(npcclient->GetEngine(),pos2,sector)) { 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::Resume(csTicks delay, NPC *npc, EventManager *eventmgr) { resumeScriptEvent = new psResumeScriptEvent(delay, npc, eventmgr, npc->GetCurrentBehavior(), this); eventmgr->Push(resumeScriptEvent); } void ScriptOperation::ResumeTrigger(psResumeScriptEvent * event) { // If we end out getting a trigger from a another event than we currently have // registerd something went wrong somewhere. CS_ASSERT(event == resumeScriptEvent); resumeScriptEvent = NULL; } void ScriptOperation::StopResume() { if (resumeScriptEvent) { resumeScriptEvent->SetValid(false); } } void ScriptOperation::TurnTo(NPC *npc, csVector3& dest, iSector* destsect, csVector3& forward) { npc->Printf("TurnTo localDest=%s\n",toString(dest,destsect).GetData()); csRef 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; npc->Printf("Forward is %s",toString(forward).GetDataSafe()); 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); } void ScriptOperation::StopMovement(NPC *npc) { // 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()); } 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 pcmesh = CEL_QUERY_PROPCLASS(npc->GetEntity()->GetPropertyClassList(), iPcMesh); // SetAction animation for the mesh also, so it looks right pcmesh->SetAnimation(action, false); //now persist npcclient->GetNetworkMgr()->QueueDRData(npc->GetEntity(),npc->GetLinMove(),npc->GetDRCounter()); float dist = forward.Norm(); 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 Resume(msec,npc,eventmgr); 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,bool interrupted) { npc->Printf("MoveToOp Start dest=(%1.2f,%1.2f,%1.2f) at %1.2f m/sec.\n", dest.x,dest.y,dest.z,GetVelocity(npc)); 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."); // npc->ResumeScript(eventmgr, npc->GetBrain()->GetCurrentBehavior() ); CompleteOperation(npc, eventmgr); } else { npc->Printf("we are at localDest... WHAT DOES THIS MEAN?"); 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) { // 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()); npc->Printf("MoveTo Completed. pos=(%1.2f,%1.2f,%1.2f) rot=%.2f dest set=(%1.2f,%1.2f,%1.2f)", pos.x,pos.y,pos.z, rot, dest.x,dest.y,dest.z); 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,bool interrupted) { 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; ilocs.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 Resume(msec,npc,eventmgr); angle_delta=target_angle; return false; } else if (op_type == ROT_LOCATEDEST) { csVector3 dest,pos; float rot=0,dest_rot; iSector *sector,*dest_sector; npc->GetActiveLocate(dest,dest_sector,dest_rot); psGameObject::GetPosition(npc->GetEntity(),pos,rot,sector); if(pos == dest && sector == dest_sector && rot == dest_rot) 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.0) return true; int msec = (int)fabs(1000.0*angle/ang_vel); npc->GetLinMove()->SetAngularVelocity( csVector3(0,(angle>0)?-ang_vel:ang_vel,0) ); csRef pcmesh = CEL_QUERY_PROPCLASS(npc->GetEntity()->GetPropertyClassList(), iPcMesh); pcmesh->SetAnimation(action, false); npcclient->GetNetworkMgr()->QueueDRData(npc->GetEntity(),npc->GetLinMove(),npc->GetDRCounter()); // wake me up when it's over Resume(msec,npc,eventmgr); 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 pcmesh = CEL_QUERY_PROPCLASS(npc->GetEntity()->GetPropertyClassList(), iPcMesh); pcmesh->SetAnimation(action, false); npcclient->GetNetworkMgr()->QueueDRData(npc->GetEntity(),npc->GetLinMove(),npc->GetDRCounter()); // wake me up when it's over Resume(msec,npc,eventmgr); 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) { ScriptOperation::InterruptOperation(npc,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); locate_invisible = node->GetAttributeValueAsBool("invisible",false); locate_invincible = node->GetAttributeValueAsBool("invincible",false); return true; } ScriptOperation *LocateOperation::MakeCopy() { LocateOperation *op = new LocateOperation; op->range = range; op->object = object; op->static_loc = static_loc; op->random = random; op->locate_invisible = locate_invisible; op->locate_invincible = locate_invincible; return op; } Waypoint* LocateOperation::CalculateWaypoint(NPC *npc, csVector3 located_pos, iSector* located_sector, float located_range) { Waypoint *end; float end_range = 0.0; end = npcclient->FindNearestWaypoint(located_pos,located_sector,-1,&end_range); if (end && (located_range == -1 || end_range >= located_range)) { npc->Printf("Located WP : %30s at %s",end->loc.name.GetDataSafe(),toString(end->loc.pos,end->loc.GetSector(npcclient->GetEngine())).GetDataSafe()); return end; } return NULL; } bool LocateOperation::Run(NPC *npc,EventManager *eventmgr,bool interrupted) { npc->Printf(">>>LocateOp "); // Reset old target npc->SetTarget(NULL); located_pos = csVector3(0.0f,0.0f,0.0f); located_angle = 0.0f; located_sector = NULL; located_wp = NULL; float start_rot; iSector *start_sector; csVector3 start_pos; psGameObject::GetPosition(npc->GetEntity(),start_pos,start_rot,start_sector); csArray split_obj = psSplit(object,':'); if (split_obj[0] == "perception") { if (!npc->GetLastPerception()) return true; if (!npc->GetLastPerception()->GetLocation(located_pos,located_sector)) return true; located_angle = 0; // not used in perceptions } else if (split_obj[0] == "target") { iCelEntity *ent; // Since we don't have a current enemy targeted, find one! if (range) ent = npc->GetMostHated(range,locate_invisible,locate_invincible); else ent = npc->GetMostHated(10.0f,locate_invisible,locate_invincible); // 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 (split_obj[0] == "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 (split_obj[0] == "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 (split_obj[0] == "tribe") { if (!npc->GetTribe()) return true; if (split_obj[1] == "home") { float radius; csVector3 pos; npc->GetTribe()->GetHome(pos,radius,located_sector); AddRandomRange(pos,radius); located_pos = pos; located_angle = 0; } else if (split_obj[1] == "memory") { float located_range=0.0; psTribe::Memory * memory; if (random) { memory = npc->GetTribe()->FindRandomMemory(split_obj[2],start_pos,start_sector,range,&located_range); } else { memory = npc->GetTribe()->FindNearestMemory(split_obj[2],start_pos,start_sector,range,&located_range); } if (!memory) { npc->Printf("Couldn't locate any <%s> in npc script for <%s>.", (const char *)object,npc->GetEntity()->GetName() ); return true; } located_pos = memory->pos; located_sector = memory->sector; AddRandomRange(located_pos,memory->radius); } else if (split_obj[1] == "resource") { npc->GetTribe()->GetResource(npc,start_pos,start_sector,located_pos,located_sector,range,random); located_angle = 0.0; } located_wp = CalculateWaypoint(npc,located_pos,located_sector,-1); } else if(split_obj[0] == "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 (split_obj[0] == "waypoint" ) { float located_range=0.0; if (split_obj.Length() >= 2) { located_wp = npcclient->FindWaypoint(split_obj[1]); } else if (random) { located_wp = npcclient->FindRandomWaypoint(start_pos,start_sector,range,&located_range); } else { located_wp = npcclient->FindNearestWaypoint(start_pos,start_sector,range,&located_range); } if (!located_wp) { npc->Printf("Couldn't locate any <%s> in npc script for <%s>.\n", (const char *)object,npc->GetEntity()->GetName() ); return true; } npc->Printf("Located waypoint: %s at %s",located_wp->loc.name.GetDataSafe(), toString(located_wp->loc.pos,located_wp->loc.GetSector(npcclient->GetEngine())).GetData()); located_pos = located_wp->loc.pos; located_angle = located_wp->loc.rot_angle; located_sector = located_wp->loc.GetSector(npcclient->GetEngine()); AddRandomRange(located_pos,located_wp->loc.radius); located_wp = CalculateWaypoint(npc,located_pos,located_sector,-1); } else if (!static_loc || !located) { float located_range=0.0; Location * location; if (split_obj.Length() >= 2) { location = npcclient->FindLocation(split_obj[0],split_obj[1]); } else if (random) { location = npcclient->FindRandomLocation(split_obj[0],start_pos,start_sector,range,&located_range); } else { location = npcclient->FindNearestLocation(split_obj[0],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 located_wp = CalculateWaypoint(npc,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_sector,located_angle,located_wp); npc->Printf("Active located: pos %s rot %.2f wp %s", toString(located_pos,located_sector).GetData(),located_angle, (located_wp?located_wp->loc.name.GetDataSafe():"(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,bool interrupted) { npc->Printf(">>>NavigateOp "); csVector3 dest; float rot=0; iSector* sector; npc->GetActiveLocate(dest,sector,rot); npc->Printf("Located %s at %1.2f m/sec.\n",toString(dest,sector).GetData(), GetVelocity(npc) ); StartMoveTo(npc,eventmgr,dest,sector,GetVelocity(npc),action); return false; } void NavigateOperation::Advance(float timedelta,NPC *npc,EventManager *eventmgr) { npc->Printf("NavigationOp advanced\n"); npc->GetLinMove()->ExtrapolatePosition(timedelta); } void NavigateOperation::InterruptOperation(NPC *npc,EventManager *eventmgr) { ScriptOperation::InterruptOperation(npc,eventmgr); StopMovement(npc); } 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) ); // Set position to where it is supposed to go float rot=0; iSector *sector; csVector3 pos; npc->GetActiveLocate(pos,sector,rot); 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, iSector*§or) { dest = active_wp->loc.pos; sector = active_wp->loc.GetSector(npcclient->GetEngine()); AddRandomRange(dest,active_wp->loc.radius); } bool WanderOperation::FindNextWaypoint(NPC *npc) { float rot=0; iSector *sector; csVector3 pos; psGameObject::GetPosition(npc->GetEntity(),pos,rot,sector); current_pos = pos; current_sector = sector; if (random) { while (true) { int which_next = rng.Get((int)active_wp->links.Length() ); Waypoint *new_wp = active_wp->links[which_next]; bool wander = !active_wp->prevent_wander[which_next]; if (((new_wp != prior_wp) && wander) || (new_wp == prior_wp && new_wp->allow_return && wander) || (active_wp->links.Length() == 1)) { prior_wp = active_wp; active_wp = new_wp; npc->Printf("Next waypoint: %s at %s",active_wp->loc.name.GetDataSafe(), toString(active_wp->loc.pos, active_wp->loc.GetSector(npcclient->GetEngine())).GetDataSafe()); return true; } } } else { prior_wp = active_wp; active_wp = WaypointListGetNext(); if (active_wp) { // Check if we are on the waypoint, in that case find next if (active_wp->CheckWithin(npcclient->GetEngine(),pos,sector)) { if (FindNextWaypoint(npc)) { return true; } else { npc->Printf(">>>WanderOp At end of waypoint list."); return false; } } else { npc->Printf("Next waypoint: %s at %s",active_wp->loc.name.GetDataSafe(), toString(active_wp->loc.pos, active_wp->loc.GetSector(npcclient->GetEngine())).GetDataSafe()); return true; } } } return false; } void WanderOperation::StartMoveToWaypoint(NPC *npc,EventManager *eventmgr) { // now calculate new destination from new active wp CalculateTargetPos(dest,dest_sector); csVector3 pos; csVector3 dest_tmp = dest; // Destination in current sector space float rot; iSector* sector; psGameObject::GetPosition(npc->GetEntity(), pos, rot, sector); npcclient->GetWorld()->WarpSpace(dest_sector,sector,dest_tmp); float height_diff = dest_tmp.y - current_pos.y; float secs = StartMoveTo(npc,eventmgr,dest,dest_sector,GetVelocity(npc),action); height_delta_per_sec = height_diff / secs; } bool WanderOperation::CalculateWaypointList(NPC *npc, bool interrupted) { float start_rot; csVector3 start_pos; iSector* start_sector; psGameObject::GetPosition(npc->GetEntity(), start_pos, start_rot, start_sector); if (random) { if (active_wp == NULL) { active_wp = npcclient->FindNearestWaypoint(start_pos,start_sector); } else if (interrupted && !AtInterruptedPosition(start_pos,start_sector)) { active_wp = npcclient->FindNearestWaypoint(start_pos,start_sector); } return true; } else { // Do not recalcualte if interrupted and at interrupted position if (interrupted && AtInterruptedPosition(start_pos,start_sector)) { return true; } Waypoint *start, *end; npc->GetActiveLocate(end); if (!end) { return false; } start = npcclient->FindNearestWaypoint(start_pos,start_sector); WaypointListClear(); if (start && end) { npc->Printf("Start WP: %30s at %s",start->loc.name.GetDataSafe(),toString(start->loc.pos,start->loc.GetSector(npcclient->GetEngine())).GetDataSafe()); npc->Printf("End WP : %30s at %s",end->loc.name.GetDataSafe(),toString(end->loc.pos,end->loc.GetSector(npcclient->GetEngine())).GetDataSafe()); if (start == end) { WaypointListPushBack(start); } else { csList wps; wps = npcclient->FindWaypointPath(start,end); while (!wps.IsEmpty()) { WaypointListPushBack(wps.Front()); wps.PopFront(); } } if (npc->IsDebugging(5)) { npcclient->ListWaypoints(""); } psString wp_str; if (!WaypointListEmpty()) { csList wps = WaypointListGet(); Waypoint * wp; while (!wps.IsEmpty() && (wp = wps.Front())) { wp_str.AppendFmt("%s",wp->loc.name.GetDataSafe()); wps.PopFront(); if (!wps.IsEmpty()) { wp_str.Append(" -> "); } } npc->Printf("Waypoint list: %s",wp_str.GetDataSafe()); } } } return true; } bool WanderOperation::Run(NPC *npc,EventManager *eventmgr,bool interrupted) { npc->Printf(">>>WanderOp "); active_wp = NULL; if (!CalculateWaypointList(npc,interrupted)) { npc->Printf(">>>WanderOp no list to wander",npc->GetName().GetDataSafe()); return true; } if (!FindNextWaypoint(npc)) { npc->Printf(">>>WanderOp NO waypoints, %s cannot move.",npc->GetName().GetDataSafe()); return true; } csRef 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 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; csVector3 pos; float rot; iSector *sector; psGameObject::GetPosition(npc->GetEntity(),pos,rot,sector); if (sector != current_sector) { npcclient->GetWorld()->WarpSpace(current_sector,sector,current_pos); npc->Printf("Wander from sector %s to %s",current_sector->QueryObject()->GetName(), sector->QueryObject()->GetName()); current_sector = sector; } psGameObject::SetPosition(npc->GetEntity(), current_pos, current_sector); // 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); } void WanderOperation::InterruptOperation(NPC *npc,EventManager *eventmgr) { ScriptOperation::InterruptOperation(npc,eventmgr); StopMovement(npc); if (!random) { waypoint_list.PushFront(active_wp); } } 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->loc.name.GetData() ); psGameObject::SetPosition(npc->GetEntity(),dest,dest_sector); 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; } Waypoint* WanderOperation::WaypointListGetNext() { if (waypoint_list.IsEmpty()) return NULL; Waypoint * wp; wp = waypoint_list.Front(); if (wp) { waypoint_list.PopFront(); return wp; } return NULL; } 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,bool interrupted) { 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) { ScriptOperation::InterruptOperation(npc,eventmgr); StopMovement(npc); } 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,bool interrupted) { 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,bool interrupted) { 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,bool interrupted) { 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,bool interrupted) { 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,bool interrupted) { 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,bool interrupted) { 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,bool interrupted) { if(!npc->GetTarget()) return true; NPC * friendNPC = npcclient->FindAttachedNPC(npc->GetTarget()); if(friendNPC) { npc->Printf(">>> Reproduce"); npcclient->GetNetworkMgr()->QueueSpawnCommand(friendNPC->GetEntity(), friendNPC->GetTarget()); } return true; } bool ResurrectOperation::Load(iDocumentNode *node) { return true; } ScriptOperation *ResurrectOperation::MakeCopy() { ResurrectOperation *op = new ResurrectOperation; return op; } bool ResurrectOperation::Run(NPC *npc,EventManager *eventmgr,bool interrupted) { npc->Printf(">>> Resurrect"); psTribe * tribe = npc->GetTribe(); if ( !tribe ) return true; csVector3 where; float radius; iSector* sector; float rot = 0; // Todo: Set to a random rotation tribe->GetHome(where,radius,sector); // Todo: Add a random delta within radius to the where value. npcclient->GetNetworkMgr()->QueueResurrectCommand(where,rot,sector->QueryObject()->GetName(),npc->GetID()); return true; } bool MemorizeOperation::Load(iDocumentNode *node) { return true; } ScriptOperation *MemorizeOperation::MakeCopy() { MemorizeOperation *op = new MemorizeOperation; return op; } bool MemorizeOperation::Run(NPC *npc,EventManager *eventmgr,bool interrupted) { Perception * percept = npc->GetLastPerception(); if (!percept) { npc->Printf(">>> Memorize No Perception."); return true; // Nothing more to do for this op. } npc->Printf(">>> Memorize '%s' '%s'.",percept->GetType(),percept->GetName()); psTribe * tribe = npc->GetTribe(); if ( !tribe ) return true; // Nothing more to do for this op. tribe->Memorize(npc, percept ); return true; // Nothing more to do for this op. } bool MeleeOperation::Load(iDocumentNode *node) { seek_range = node->GetAttributeValueAsFloat("seek_range"); melee_range = node->GetAttributeValueAsFloat("melee_range"); attack_invisible = node->GetAttributeValueAsBool("invisible",false); attack_invincible= node->GetAttributeValueAsBool("invincible",false); // 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; op->attack_invisible = attack_invisible; op->attack_invincible = attack_invincible; attacked_ent = NULL; return op; } bool MeleeOperation::Run(NPC *npc,EventManager *eventmgr,bool interrupted) { npc->Printf("MeleeOperation starting."); completed = false; attacked_ent = npc->GetMostHated(seek_range,attack_invisible,attack_invincible); 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,attack_invisible,attack_invincible); 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,attack_invisible,attack_invincible); // 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(5)) { 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) { ScriptOperation::InterruptOperation(npc,eventmgr); 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,bool interrupted) { 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,bool interrupted) { 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,bool interrupted) { npc->Printf(">>>WaitOp\n"); if (!interrupted) { remaining = duration; } csRef pcmesh = CEL_QUERY_PROPCLASS(npc->GetEntity()->GetPropertyClassList(), iPcMesh); // SetAction animation for the mesh, so it looks right pcmesh->SetAnimation(action, false); //now persist npcclient->GetNetworkMgr()->QueueDRData(npc->GetEntity(),npc->GetLinMove(),npc->GetDRCounter()); Resume((int)(remaining*1000.0),npc,eventmgr); return false; } void WaitOperation::Advance(float timedelta,NPC *npc,EventManager *eventmgr) { remaining -= timedelta; npc->Printf("waiting... %.2f",remaining); } 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,bool interrupted) { 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,bool interrupted) { 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,bool interrupted) { 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,bool interrupted) { 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) { ScriptOperation::InterruptOperation(npc,eventmgr); npc->GetLinMove()->SetPathSpeed(0); StopMovement(npc); } psResumeScriptEvent::psResumeScriptEvent(int offsetticks, NPC *which,EventManager *mgr,Behavior *behave,ScriptOperation * script) : psGameEvent(0,offsetticks,"psResumeScriptEvent") { npc = which; eventmgr = mgr; behavior = behave; scriptOp = script; } void psResumeScriptEvent::Trigger() { if (running) { scriptOp->ResumeTrigger(this); npc->ResumeScript(eventmgr,behavior); } } void psGameObject::GetPosition(iCelEntity *entity, csVector3& pos, float& yrot,iSector*& sector) { csRef 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 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 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; } 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; }