/* * npcclient.cpp - author: 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 "util/serverconsole.h" #include "pathfind.h" #include "util/psdatabase.h" #include "net/connection.h" #include "net/msghandler.h" #include "util/eventmanager.h" #include "util/location.h" #include "util/waypoint.h" #include "util/psstring.h" #include "util/strutil.h" #include "npcclient.h" #include "psbehave/psworld.h" #include "networkmgr.h" #include "npcbehave.h" #include "npc.h" #include "perceptions.h" #include "pathmanager.h" #include "pathfind.h" #include "gem.h" #include "tribe.h" #ifndef INFINITY #define INFINITY 999999999.0F #endif bool running; extern iDataConnection *db; class psNPCTick : public psGameEvent { protected: psNPCClient *client; public: psNPCTick(int offsetticks, psNPCClient *c); virtual void Trigger(); // Abstract event processing function }; psNPCClient::psNPCClient () { world = NULL; eventmanager = NULL; running = true; database = NULL; // Initialize the RNG using current time() as the seed value randomGen.Initialize(); tick_counter = 0; } psNPCClient::~psNPCClient() { running = false; if (database) delete database; } bool psNPCClient::Initialize(iObjectRegistry* object_reg,const char *_host, const char *_user, const char *_pass, int _port) { objreg = object_reg; if (!CelBase::Initialize(object_reg)) return false; configmanager = CS_QUERY_REGISTRY(object_reg, iConfigManager); if (!configmanager) { CPrintf (CON_ERROR, "Couldn't find Configmanager!\n"); return false; } engine = CS_QUERY_REGISTRY(object_reg, iEngine); if (!engine) { CPrintf (CON_ERROR, "Couldn't find Engine!\n"); return false; } // Start Database database = new psDatabase(object_reg); csString db_host, db_user, db_pass, db_name; db_host = configmanager->GetStr("Planeshift.Database.npchost", "localhost"); db_user = configmanager->GetStr("Planeshift.Database.npcuserid", "planeshift"); db_pass = configmanager->GetStr("Planeshift.Database.npcpassword", "planeshift"); db_name = configmanager->GetStr("Planeshift.Database.npcname", "planeshift"); Debug4(LOG_STARTUP,0,COL_BLUE "Database Host: '%s' User: '%s' Databasename: '%s'\n" COL_NORMAL, (const char*) db_host, (const char*) db_user, (const char*) db_name); if (!database->Initialize(db_host, db_user, db_pass, db_name)) { Error1("Could not create database or connect to it.\n"); delete database; return false; } csString user,pass,host; int port; host = _host ? _host : configmanager->GetStr("Planeshift.NPCClient.host", "localhost"); user = _user ? _user : configmanager->GetStr("Planeshift.NPCClient.userid", "superclient"); pass = _pass ? _pass : configmanager->GetStr("Planeshift.NPCClient.password", "planeshift"); port = _port ? _port : configmanager->GetInt("Planeshift.NPCClient.port", 13331); CPrintf(CON_DEBUG, "Connecting to Host: '%s' User: '%s' Password: '%s' Port %d...\n", (const char*) host, (const char*) user, (const char*) pass, port); CPrintf (CON_DEBUG, "Initialize Network Thread...\n"); connection = new psNetConnection; if (!connection->Initialize(object_reg)) { CPrintf (CON_ERROR, "Network thread initialization failed!\n"); delete connection; return false; } if (!connection->Connect(host,port)) { CPrintf(CON_ERROR, "Couldn't connect to %s on port %d.\n",(const char *)host,port); exit(1); } eventmanager = new EventManager; msghandler = eventmanager; psMessageCracker::msghandler = eventmanager; if (!msghandler->Initialize(connection, 1000)) return false; // Attach to incoming messages. if (!msghandler->StartThread() ) return false; CPrintf(CON_DEBUG, "Initialising CEL...\n"); vfs = CS_QUERY_REGISTRY (object_reg, iVFS); if (!vfs) { CPrintf(CON_ERROR, "could not open VFS\n"); exit(1); } if (!LoadNPCTypes("/this/data/npcbehave.xml")) { CPrintf(CON_ERROR, "Couldn't load npcbehave.xml.\n"); exit(1); } if (!LoadWaypoints()) { CPrintf(CON_ERROR, "Couldn't load the sc_waypoints table\n"); exit(1); } if (!LoadTribes()) { CPrintf(CON_ERROR, "Couldn't load the tribes table\n"); exit(1); } if (!LoadNPCDefinitions("/this/data/npcdefs.xml")) { CPrintf(CON_ERROR, "Couldn't load npcdefs.xml\n"); exit(1); } if (!LoadLocations()) { CPrintf(CON_ERROR, "Couldn't load the sc_locations table\n"); exit(1); } cdsys = CS_QUERY_REGISTRY (objreg, iCollideSystem); PFMaps = new psPFMaps(objreg); network = new NetworkManager(msghandler,connection); network->Authenticate(host,port,user,pass); return true; } int psNPCClient::MainLoop () { // Eventually load an autoexec file csRef cmdline = CS_QUERY_REGISTRY(objreg, iCommandLineParser); if (cmdline) { const char* ofile = cmdline->GetOption ("output"); if (ofile != NULL) { ConsoleOut::SetOutputFile (ofile, false); } else { const char* afile = cmdline->GetOption ("append"); if (afile != NULL) { ConsoleOut::SetOutputFile (afile, true); } } const char* autofile; for (int i = 0; (autofile = cmdline->GetOption("run", i)); i++) { char toexec[1000]; strcpy(toexec, "exec "); strcat(toexec, autofile); ServerConsole::ExecuteScript(toexec); } } ConsoleOut::SetMaximumOutputClassStdout (CON_SPAM); ConsoleOut::SetMaximumOutputClassFile (CON_SPAM); // This starts the NPC AI processing loop. psNPCTick *tick = new psNPCTick(1000,this); eventmanager->Push(tick); return ServerConsole::MainLoop (); } void psNPCClient::Disconnect() { network->Disconnect(); } bool psNPCClient::LoadNPCTypes(const char *xmlfile) { csRef xml = csPtr(new csTinyDocumentSystem); csRef buff = vfs->ReadFile( xmlfile ); if ( !buff || !buff->GetSize() ) { return false; } csRef doc = xml->CreateDocument(); const char* error = doc->Parse( buff ); if ( error ) { Error3("%s in %s", error, xmlfile); return false; } csRef root = doc->GetRoot(); if(!root) { Error2("No XML root in %s", xmlfile); return false; } csRef topNode = root->GetNode("npctypes"); if(!topNode) { Error2("No tag in %s", xmlfile); return false; } csRef iter = topNode->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(), "npctype" ) == 0 ) { NPCType *npctype = new NPCType; if (npctype->Load(node)) { npctypes.Insert(npctype); } else { delete npctype; return false; } } } return true; } void psNPCClient::Add( gemNPCObject* object ) { objects.Push( object ); } void psNPCClient::Remove ( unsigned id ) { gemNPCObject * obj = FindEntityID( id ); if (obj == NULL) { CPrintf(CON_DEBUG, "NPCObject %d cannot be removed - not found\n", id); return; } // Remove entity from all hated lists. for (size_t x=0; xRemoveFromHateList(id); } npc_entities.DeleteAll( id ); objects.Delete( obj ); delete obj; } gemNPCObject *psNPCClient::FindEntityID(unsigned entityID) { size_t i; for (i=0; iGetEntity()->GetID() == entityID) return objects[i]; } return NULL; } gemNPCObject *psNPCClient::FindCharacterID(int char_id) { size_t i; for (i=0; iGetPlayerID() == char_id) return objects[i]; } return NULL; } void psNPCClient::RemoveAll() { size_t i; for (i=0; iGetEntity(),FindAttachedNPC(objects[i]->GetEntity())); } objects.DeleteAll(); npc_entities.DeleteAll(); } NPC* psNPCClient::ReadSingleNPC(int char_id) { Result result(db->Select("select * from sc_npc_definitions where char_id=%d",char_id)); if (!result.IsValid()) { Error2("Error loading char_id %d.",char_id); return NULL; } NPC *newnpc = new NPC; if (newnpc->Load(result[0],npctypes)) { npcs.Push(newnpc); return newnpc; } else { delete newnpc; return NULL; } } bool psNPCClient::ReadNPCsFromDatabase() { Result rs(db->Select("select * from sc_npc_definitions")); if (!rs.IsValid()) { Error2("Could not load npcs from db: %s",db->GetLastError() ); return false; } for (unsigned long i=0; iLoad(rs[i],npctypes)) { npcs.Push(npc); CheckAttachTribes(npc); } else { delete npc; return false; } } return true; } bool psNPCClient::LoadNPCDefinitions(const char *xmlfile) { csRef xml = csPtr(new csTinyDocumentSystem); csRef buff = vfs->ReadFile( xmlfile ); if ( !buff || !buff->GetSize() ) { return false; } csRef doc = xml->CreateDocument(); const char* error = doc->Parse( buff ); if ( error ) { Error3("Error %s in %s", error, xmlfile); return false; } if (!ReadNPCsFromDatabase()) return false; csRef root = doc->GetRoot(); if (!root) { Error2("No XML root in %s", xmlfile); return false; } csRef topNode = root->GetNode("paths"); if (!topNode) { Error2("No tag in %s", xmlfile); return false; } pathmgr = new PathManager; if (!pathmgr->Load(topNode)) return false; return true; } bool psNPCClient::ImportNPCDefinitions(const char *xmlfile) { csRef xml = csPtr(new csTinyDocumentSystem); csRef buff = vfs->ReadFile( xmlfile ); if ( !buff || !buff->GetSize() ) { return false; } csRef doc = xml->CreateDocument(); const char* error = doc->Parse( buff ); if ( error ) { Error3("Error %s in %s", error, xmlfile); return false; } csRef root = doc->GetRoot(); if (!root) { Error2("No XML root in %s", xmlfile); return false; } csRef topNode = root->GetNode("locations"); if(!topNode) { Error2("No tag in %s", xmlfile); return false; } if (!ImportLocations(topNode)) return false; topNode = root->GetNode("waypoints"); if (!topNode) { Error2("No tag in %s", xmlfile); return false; } if (!ImportWaypoints(topNode)) return false; return true; } bool psNPCClient::ImportLocations(iDocumentNode *topNode) { csRef iter = topNode->GetNodes(); while ( iter->HasNext() ) { csRef node = iter->Next(); if ( node->GetType() != CS_NODE_ELEMENT ) continue; if ( strcmp( node->GetValue(), "loctype" ) == 0) { csString name = node->GetAttributeValue("name"); if ( !name.Length() ) { CPrintf(CON_ERROR, "Location Types must all have name attributes.\n"); return false; } LocationType *loctype = FindLocation(name); if (!loctype) { loctype = new LocationType; loctype->id = -1; if (loctype->Import(node,db)) { loctypes.Insert(loctype); } else { CPrintf(CON_ERROR, "Failed to import location type(%s): %s.\n", name.GetData(),db->GetLastError()); delete loctype; return false; } } 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(), "loc" ) == 0 ) { csString locname = node->GetAttributeValue("name"); if (!locname.Length() ) { CPrintf(CON_ERROR, "Locations must all have name attribute.\n"); return false; } Location *loc = FindLocation(name,locname); if (!loc) { loc = new Location; loc->id = -1; loctype->locs.Push(loc); } if (!loc->Import(node,db,loctype->id)) { CPrintf(CON_ERROR, "Failed to import location(%s): %s.\n", locname.GetData(),db->GetLastError()); return false; } } } } } return true; } bool psNPCClient::ImportWaypoints(iDocumentNode *topNode) { csRef iter = topNode->GetNodes(); while ( iter->HasNext() ) { csRef node = iter->Next(); if ( node->GetType() != CS_NODE_ELEMENT ) continue; if (!strcmp( node->GetValue(), "waypointlist" )) { csRef iter = node->GetNodes(); while (iter->HasNext()) { csRef node = iter->Next(); if ( node->GetType() != CS_NODE_ELEMENT ) continue; if (!strcmp(node->GetValue(),"waypoint")) { csString wpname = node->GetAttributeValue("name"); if (!wpname.Length() ) { CPrintf(CON_ERROR, "Waypoints must all have name attribute.\n"); return false; } Waypoint *wp = FindWaypoint(wpname); if (!wp) { wp = new Waypoint(wpname); waypoints.Insert(wp,true); } if (!wp->Import(node,engine,db)) { CPrintf(CON_ERROR, "Failed to import waypoint(%s): %s.\n", wpname.GetData(),db->GetLastError()); return false; } } } } else if (!strcmp(node->GetValue(), "waylinks" )) { csRef iter = node->GetNodes(); while (iter->HasNext()) { csRef node = iter->Next(); if ( node->GetType() != CS_NODE_ELEMENT ) continue; if (!strcmp(node->GetValue(),"point")) { Waypoint key; key.loc.name = node->GetAttributeValue("name"); Waypoint *wp = waypoints.Find(&key); if (!wp) { CPrintf(CON_ERROR, "Waypoint called '%s' not defined.\n",key.loc.name.GetData() ); return false; } else { csRef iter = node->GetNodes(); while (iter->HasNext()) { csRef node = iter->Next(); if ( node->GetType() != CS_NODE_ELEMENT ) continue; if (!strcmp(node->GetValue(),"link")) { Waypoint key; key.loc.name = node->GetAttributeValue("name"); Waypoint *wlink = waypoints.Find(&key); if (!wlink) { CPrintf(CON_ERROR, "Waypoint called '%s' not defined.\n",key.loc.name.GetData() ); return false; } else { bool one_way = node->GetAttributeValueAsBool("oneway",false); bool prevent_wander = node->GetAttributeValueAsBool("no_wander",false); float dist = GetWorld()->Distance(wp->loc.pos,wp->loc.GetSector(engine), wlink->loc.pos,wlink->loc.GetSector(engine)); wp->links.Push(wlink); wp->dists.Push(dist); wp->prevent_wander.Push(prevent_wander); if (!one_way) { wlink->links.Push(wp); // bi-directional link is implied wlink->dists.Push(dist); wlink->prevent_wander.Push(prevent_wander); } const char * fields[] = {"wp1","wp2","flags"}; psStringArray values; values.FormatPush("%d",wp->loc.id); values.FormatPush("%d",wlink->loc.id); csString flagStr; if (one_way) { flagStr.Append("ONEWAY"); } values.Push(flagStr); int id = db->GenericInsertWithID("sc_waypoint_links",fields,values); if (id == 0) { CPrintf(CON_ERROR, "Failed to import waypoint link: %s.\n", db->GetLastError()); return false; } } } } } } } } } return true; } bool psNPCClient::LoadWaypoints() { Result rs(db->Select("select wp.*,s.name as sector from sc_waypoints wp, sectors s where wp.loc_sector_id = s.id")); if (!rs.IsValid()) { Error2("Could not load waypoints from db: %s",db->GetLastError() ); return false; } for (int i=0; i<(int)rs.Count(); i++) { Waypoint *wp = new Waypoint(); if (wp->Load(rs[i],engine)) { waypoints.Insert(wp,true); } else { Error2("Could not load waypoint: %s",db->GetLastError() ); delete wp; return false; } } Result rs1(db->Select("select * from sc_waypoint_links")); if (!rs1.IsValid()) { Error2("Could not load waypoint links from db: %s",db->GetLastError() ); return false; } for (int i=0; i<(int)rs1.Count(); i++) { Waypoint * wp1 = FindWaypoint(rs1[i].GetInt("wp1")); Waypoint * wp2 = FindWaypoint(rs1[i].GetInt("wp2")); psString flagstr(rs1[i]["flags"]); bool one_way = flagstr.FindSubString("ONEWAY",0,true)!=-1; bool prevent_wander = flagstr.FindSubString("NO_WANDER",0,true)!=-1; float dist = GetWorld()->Distance(wp1->loc.pos,wp1->loc.GetSector(engine), wp2->loc.pos,wp2->loc.GetSector(engine)); wp1->links.Push(wp2); wp1->dists.Push(dist); wp1->prevent_wander.Push(prevent_wander); if (!one_way) { wp2->links.Push(wp1); // bi-directional link is implied wp2->dists.Push(dist); wp2->prevent_wander.Push(prevent_wander); } } return true; } bool psNPCClient::LoadLocations() { Result rs(db->Select("select * from sc_location_type")); if (!rs.IsValid()) { Error2("Could not load locations from db: %s",db->GetLastError() ); return false; } for (int i=0; i<(int)rs.Count(); i++) { LocationType *loctype = new LocationType(); if (loctype->Load(rs[i],engine,db)) { loctypes.Insert(loctype); CPrintf(CON_DEBUG, "Added location type '%s'(%d)\n",loctype->name.GetDataSafe(),loctype->id); } else { Error2("Could not load location: %s",db->GetLastError() ); delete loctype; return false; } } return true; } bool psNPCClient::LoadTribes() { Result rs(db->Select("SELECT t.*, s.name AS home_sector_name FROM tribes t, sectors s WHERE s.id = t.home_sector_id")); if (!rs.IsValid()) { Error2("Could not load tribes from db: %s",db->GetLastError() ); return false; } for (int i=0; i<(int)rs.Count(); i++) { psTribe *tribe = new psTribe(); if (tribe->Load(rs[i])) { tribes.Push(tribe); Result rs2(db->Select("select * from tribe_members where tribe_id=%d",tribe->GetID())); if (!rs2.IsValid()) { Error2("Could not load tribe members from db: %s",db->GetLastError() ); return false; } for (int j=0; j<(int)rs2.Count(); j++) { tribe->LoadMember(rs2[j]); } } else { delete tribe; return false; } } return true; } void psNPCClient::CheckAttachTribes( NPC* npc) { // Check if this NPC is part of a tribe for (size_t j=0; jCheckAttach(npc); } } void psNPCClient::AttachNPC( gemNPCActor* actor, uint8_t DRcounter) { if (!actor) return; NPC *npc = NULL; // Check based on characterID npc = FindNPC( actor->GetPlayerID() ); if ( !npc ) { csString templateName; templateName.Append( "FAMILIAR:" ); templateName.Append( actor->GetRace() ); npc = FindNPC( templateName.GetData() ); if (!npc) { // Try to mach on name npc = FindNPC( actor->GetName() ); } if ( npc ) { // Insert a row in the db for this guy next. // We will get an entity msg in a second to make him come alive. npc->InsertCopy(actor->GetPlayerID()); // Now requery so we have the new guy on our list when we get the entity msg. if ( !ReadSingleNPC(actor->GetPlayerID() ) ) { Error3("Error creating copy of master %d as id %d.",npc->GetID(), actor->GetPlayerID()); return; } npc = FindNPC( actor->GetPlayerID() ); } } if ( !npc ) { CPrintf(CON_NOTIFY,"NPC %s (ID %d) was not found in scripted npcs for this npcclient.\n",actor->GetName().GetData(), actor->GetPlayerID() ); return; } if(npc->GetEntity() != NULL) { CPrintf(CON_NOTIFY,"NPC %s (ID %d) (eid %d) is already assigned eid %d name %s.\n",actor->GetName().GetData(), actor->GetPlayerID(), actor->GetEntity()->GetID(), npc->GetEntity()->GetID(), npc->GetName().GetData() ); CS_ASSERT(npc->GetEntity() == NULL); } npc->SetEntity( actor->GetEntity() ); npc->SetOwnerID( actor->GetOwnerID() ); AttachNPC( actor->GetEntity(), npc ); npc->SetAlive(true); if(DRcounter != (uint8_t) -1) npc->SetDRCounter(DRcounter); CheckAttachTribes(npc); npc_entities.Put(actor->GetEntity()->GetID() ,actor->GetEntity() ); npc->Printf("We are now managing NPC: %s(%d) EID %u.\n", actor->GetName().GetData(), actor->GetPlayerID(), actor->GetEntity()->GetID()); // Test if this actor is in a valid starting position. npc->CheckPosition(); // Report final correct starting position. GetNetworkMgr()->QueueDRData(npc->GetEntity(),npc->GetLinMove(),npc->GetDRCounter()); } NPC *psNPCClient::FindNPC(int character_id) { for (size_t x=0; xGetID() == character_id) return npcs[x]; } csArrayCmp compare(character_id); size_t i = npcsDeferred.FindSortedKey(compare); if (i != csArrayItemNotFound) { NPC * npc = ReadSingleNPC(character_id); if(npc) npcsDeferred.DeleteIndex(i); return npc; } return NULL; } NPC *psNPCClient::FindNPC(PS_ID entid) { iCelEntity *ent = npc_entities.Get( entid, 0 ); if (ent) { return FindAttachedNPC(ent); } return NULL; } NPC *psNPCClient::FindNPC(const char *name) { csString scpNPC( name ); for (size_t x=0; xGetName() == scpNPC ) return npcs[x]; } return NULL; } void psNPCClient::TriggerEvent(NPC *npc,Perception *pcpt,float max_range, csVector3 *base_pos, iSector *base_sector) { if (npc) { npc->TriggerEvent(pcpt, eventmanager); } else { max_range *= max_range; // compare to squared version for (size_t i=0; iTriggerEvent(pcpt,eventmanager); } else { if (npcs[i]->GetEntity() == NULL) continue; iSector *sector; csVector3 pos; float yrot; psGameObject::GetPosition(npcs[i]->GetEntity(),pos,yrot,sector); float dist = GetWorld()->Distance(pos,sector,*base_pos,base_sector); if (dist <= max_range) { npcs[i]->TriggerEvent(pcpt,eventmanager); } } } } } void psNPCClient::SetEntityPos(PS_ID id, csVector3& pos, iSector* sector) { iCelEntity *ent = FindEntity(id); if (ent) { iCelEntity *npc = npc_entities.Get( ent->GetID(), 0 ); if (npc) { // Skipp updating NPC // CPrintf(CON_DEBUG, "Update npc %s skipped.\n",npc->GetName() ); return; } psGameObject::SetPosition(ent,pos,sector); // CPrintf(CON_DEBUG, "Setting entity '%s' to (%1.2f,%1.2f,%1.2f)...\n",ent->GetName(),pos.x,pos.y,pos.z); } else { CPrintf(CON_DEBUG, "Entity %d not found!\n",id); } } iCelEntity* psNPCClient::FindActor( const char* actor ) { csString name(actor); for ( size_t x = 0; x < objects.Length(); x++ ) { if ( name == objects[x]->GetName() ) { return objects[x]->GetEntity(); } } CPrintf(CON_DEBUG, "Could not find actor: %s\n", actor ); return NULL; } bool psNPCClient::IsReady() { return network->IsReady(); } void psNPCClient::Tick() { if (IsReady()) { tick_counter++; csTicks when = csGetTicks(); for (size_t i=0; iAdvance(when,eventmanager); } for (size_t j=0; jAdvance(when,eventmanager); } if (tick_counter % 4 == 0) { PerceptProximityItems(); } } network->SendAllCommands(); psNPCTick *tick = new psNPCTick(250,this); eventmanager->Push(tick); } // Class which is used to attach to an iCelEntity so that // we can find the NPC * again. SCF_VERSION (NPCFinder, 0, 0, 1); struct NPCFinder : public csObject { private: NPC* npc; public: NPCFinder (NPC *npc) { NPCFinder::npc = npc; } virtual ~NPCFinder () { } NPC* GetNPC() const { return npc; } SCF_DECLARE_IBASE_EXT (csObject); }; SCF_IMPLEMENT_IBASE_EXT (NPCFinder) SCF_IMPLEMENTS_INTERFACE (NPCFinder) SCF_IMPLEMENT_IBASE_EXT_END void psNPCClient::AttachNPC(iCelEntity *entity, NPC *npc) { NPC* old_npc = FindAttachedNPC(entity); if (old_npc == npc) return; if (old_npc != 0) UnattachNPC(entity, old_npc); CS_ASSERT(FindAttachedNPC(entity) == NULL); csRef object (SCF_QUERY_INTERFACE (entity, iObject)); csRef cef = csPtr (new NPCFinder(npc) ); cef->SetName ("__npcfind__"); // @@@ For debugging mostly. csRef cef_obj (SCF_QUERY_INTERFACE (cef, iObject)); object->ObjAdd (cef_obj); } void psNPCClient::UnattachNPC(iCelEntity *entity, NPC *npc) { csRef object (SCF_QUERY_INTERFACE (entity, iObject)); csRef cef (CS_GET_CHILD_OBJECT (object, NPCFinder)); if (cef) { if (cef->GetNPC () != npc) { return; } csRef cef_obj (SCF_QUERY_INTERFACE (cef, iObject)); object->ObjRemove (cef_obj); } } NPC* psNPCClient::FindAttachedNPC(iCelEntity *entity) { csRef object (SCF_QUERY_INTERFACE (entity, iObject)); csRef cef (CS_GET_CHILD_OBJECT (object, NPCFinder)); if (cef) return cef->GetNPC(); return 0; } bool psNPCClient::LoadMap(const char* mapfile) { if (!world) { world = new psWorld(); world->Initialize( object_reg ); world->CreateMap(mapfile, mapfile,psWorld::LOAD_NOW ); } else { world->NewRegion(mapfile,psWorld::LOAD_NOW); } return true; } LocationType *psNPCClient::FindRegion(const char *regname) { if (!regname) return NULL; LocationType key; key.name = regname; LocationType *found = loctypes.Find(&key); if (found && found->locs[0] && found->locs[0]->IsRegion()) { return found; } return NULL; } LocationType *psNPCClient::FindLocation(const char *locname) { if (!locname) return NULL; LocationType key; key.name = locname; LocationType *found = loctypes.Find(&key); return found; } NPCType *psNPCClient::FindNPCType(const char *npctype_name) { NPCType key(npctype_name); return npctypes.Find(&key); } Location *psNPCClient::FindLocation(const char *loctype, const char *name) { LocationType key; key.name = loctype; LocationType *found = loctypes.Find(&key); if (found) { for (size_t i=0; ilocs.Length(); i++) { if (found->locs[i]->name == name) { return found->locs[i]; } } } return NULL; } Location *psNPCClient::FindNearestLocation(const char *loctype, csVector3& pos, iSector* sector, float range, float *found_range) { LocationType key; key.name = loctype; LocationType *found = loctypes.Find(&key); if (found) { float min_range = range*range; // Working with Squared values if (range == -1) min_range = -1; // -1*-1 = 1, will use -1 later int min_i = -1; for (size_t i=0; ilocs.Length(); i++) { float dist2 = GetWorld()->Distance(pos,sector,found->locs[i]->pos,found->locs[i]->GetSector(engine)); if (min_range < 0 || dist2 < min_range) { min_range = dist2; min_i = (int)i; } } if (min_i > -1) // found closest one { if (found_range) *found_range = sqrt(min_range); return found->locs[(size_t)min_i]; } } return NULL; } Location *psNPCClient::FindRandomLocation(const char *loctype, csVector3& pos, iSector* sector, float range, float *found_range) { LocationType key; key.name = loctype; csArray nearby; csArray dist; LocationType *found = loctypes.Find(&key); if (found) { float min_range = range*range; // Working with Squared values if (range == -1) min_range = -1; // -1*-1 = 1, will use -1 later for (size_t i=0; ilocs.Length(); i++) { float dist2 = GetWorld()->Distance(pos,sector,found->locs[i]->pos,found->locs[i]->GetSector(engine)); if (min_range < 0 || dist2 < min_range) { nearby.Push(found->locs[i]); dist.Push(dist2); } } if (nearby.Length()>0) // found one or more closer than range { size_t pick = GetRandom((uint32)nearby.Length()); if (found_range) *found_range = sqrt(dist[pick]); return nearby[pick]; } } return NULL; } Waypoint *psNPCClient::FindNearestWaypoint(csVector3& v,iSector *sector, float range, float * found_range) { BinaryRBIterator iter(&waypoints); Waypoint *wp; float min_range = range*range; // Working with Squared values if (range == -1) min_range = -1; // -1*-1 = 1, will use -1 later Waypoint *min_wp = NULL; for (wp = iter.First(); wp; wp = ++iter) { float dist2 = GetWorld()->Distance(v,sector,wp->loc.pos,wp->GetSector(engine)); if (min_range < 0 || dist2 < min_range) { min_range = dist2; min_wp = wp; } } if (min_wp && found_range) *found_range = sqrt(min_range); return min_wp; } Waypoint *psNPCClient::FindRandomWaypoint(csVector3& v,iSector *sector, float range, float * found_range) { BinaryRBIterator iter(&waypoints); Waypoint *wp; csArray nearby; csArray dist; float min_range = range*range; // Working with Squared values if (range == -1) min_range = -1; // -1*-1 = 1, will use -1 later for (wp = iter.First(); wp; wp = ++iter) { float dist2 = GetWorld()->Distance(v,sector,wp->loc.pos,wp->GetSector(engine)); if (min_range < 0 || dist2 < min_range) { nearby.Push(wp); dist.Push(dist2); } } if (nearby.Length()>0) // found one or more closer than range { size_t pick = GetRandom((uint32)nearby.Length()); if (found_range) *found_range = sqrt(dist[pick]); return nearby[pick]; } return NULL; } Waypoint *psNPCClient::FindWaypoint(int id) { BinaryRBIterator iter(&waypoints); Waypoint *wp; for (wp = iter.First(); wp; wp = ++iter) { if (wp->loc.id == id) { return wp; } } return NULL; } Waypoint *psNPCClient::FindWaypoint(const char * name) { BinaryRBIterator iter(&waypoints); Waypoint *wp; for (wp = iter.First(); wp; wp = ++iter) { if (wp->loc.name == name) { return wp; } } return NULL; } csList psNPCClient::FindWaypointPath(Waypoint * start, Waypoint * end) { csList waypoint_list; csList priority; // Should have been a priority queue BinaryRBIterator iter(&waypoints); Waypoint *wp; // Using Dijkstra's algorithm to find shortest way // Initialize for (wp = iter.First(); wp; wp = ++iter) { wp->distance = INFINITY; wp->pi = NULL; // Insert WP into priority queue priority.PushBack(wp); } start->distance = 0; while (!priority.IsEmpty()) { Waypoint *wp_u = NULL, *pri_wp = NULL; // Extract min from priority queue csList::Iterator pri(priority); csList::Iterator pri_loc; while (pri.HasNext()) { pri_wp = pri.Next(); if (!wp_u || pri_wp->distance < wp_u->distance) { wp_u = pri_wp; pri_loc = pri; } } priority.Delete(pri_loc); size_t v; for (v = 0; v < wp_u->links.Length(); v++) { Waypoint * wp_v = wp_u->links[v]; // Relax if (wp_v->distance > wp_u->distance + wp_u->dists[v]) { wp_v->distance = wp_u->distance + wp_u->dists[v]; wp_v->pi = wp_u; } } // if wp == end, we should be done!!!!!! } wp = end; if (end->pi) { wp = end; while (wp) { waypoint_list.PushFront(wp); wp = wp->pi; } } return waypoint_list; } void psNPCClient::ListAllNPCs(char * pattern) { CPrintf(CON_CMDOUTPUT, "%-9s %-5s %-30s %-6s %-6s %-20s %-15s %-20s %-20s %-3s\n", "NPC ID", "EID", "Name", "Entity", "Status", "Brain","Behaviour","Step","Owner","Tribe","Dbg"); for (size_t i = 0; i < npcs.Length(); i++) { if (!pattern || strstr(npcs[i]->GetName().GetData(),pattern)) { CPrintf(CON_CMDOUTPUT, "%-9d %-5d %-30s %-6s %-6s %-20s %-15s %4d %-20s %-20s %-3s\n" , npcs[i]->GetID(), (npcs[i]->GetEntity()?npcs[i]->GetEntity()->GetID():0), npcs[i]->GetName().GetDataSafe(), (npcs[i]->GetEntity()?"Entity":"None "), (npcs[i]->IsAlive()?"Alive":"Dead"), (npcs[i]->GetBrain()?npcs[i]->GetBrain()->GetName().GetDataSafe():""), (npcs[i]->GetCurrentBehavior()?npcs[i]->GetCurrentBehavior()->GetName():""), (npcs[i]->GetCurrentBehavior()?npcs[i]->GetCurrentBehavior()->GetCurrentStep():0), npcs[i]->GetOwnerName().GetDataSafe(), (npcs[i]->GetTribe()?npcs[i]->GetTribe()->GetName().GetDataSafe():""), (npcs[i]->IsDebugging()?"Yes":"No") ); } } } bool psNPCClient::DumpNPC(char *pattern) { int id = atoi(pattern); for (size_t i=0; iGetID() == id) { npcs[i]->DumpBehaviorList(); npcs[i]->DumpHateList(); return true; } } return false; } void psNPCClient::ListAllEntities(char * pattern, bool onlyCharacters) { char * format = "%-9s %-5s %-15s %-20s\n"; if(onlyCharacters) { CPrintf(CON_CMDOUTPUT, "%-9s %-5s %-10s %-30s %-3s\n" ,"Player ID", "EID","Type","Name","Vis","Inv"); for (size_t i=0; i < objects.Length(); i++) { gemNPCActor * actor = dynamic_cast (objects[i]); if(!actor) continue; if (!pattern || strstr(actor->GetName().GetData(),pattern)) { CPrintf(CON_CMDOUTPUT, "%-9d %-5d %-10s %-30s %-3s %-3s\n", actor->GetPlayerID(), actor->GetEntity()->GetID(), actor->GetObjectType(), actor->GetName().GetData(), (actor->IsVisible()?"Yes":"No"), (actor->IsInvincible()?"Yes":"No")); } } return; } CPrintf(CON_CMDOUTPUT, "%-5s %-10s %-30s %-3s %-3s %-4s Position\n","EID","Type","Name","Vis","Inv","Pick"); for (size_t i=0; i < objects.Length(); i++) { gemNPCObject * obj = objects[i]; iCelEntity *ent = obj->GetEntity(); csVector3 pos; float rot; iSector *sector; psGameObject::GetPosition(ent,pos,rot,sector); if (!pattern || strstr(obj->GetName().GetData(),pattern)) { CPrintf(CON_CMDOUTPUT, "%5d %-10s %-30s %-3s %-3s %-4s %s\n", ent->GetID(), obj->GetObjectType(), obj->GetName().GetData(), (obj->IsVisible()?"Yes":"No"), (obj->IsInvincible()?"Yes":"No"), (obj->IsPickable()?"Yes":"No"), toString(pos,sector).GetData()); } } } void psNPCClient::ListTribes(char * pattern) { CPrintf(CON_CMDOUTPUT, "%9s %-30s %-7s %-7s %7s %7s %7s %7s %-15s \n", "Tribe id", "Name", "MCount","NPCs","x","y","z","r","sector"); for (size_t i = 0; i < tribes.Length(); i++) { if (!pattern || strstr(tribes[i]->GetName().GetData(),pattern)) { csVector3 pos; iSector* sector; float radius; tribes[i]->GetHome(pos,radius,sector); CPrintf(CON_CMDOUTPUT, "%9d %-30s %-7d %-7d %7.1f %7.1f %7.1f %7.1f %-15s\n" , tribes[i]->GetID(), tribes[i]->GetName().GetDataSafe(), tribes[i]->GetMemberIDCount(), tribes[i]->GetMemberCount(), pos.x,pos.y,pos.z,radius,(sector?sector->QueryObject()->GetName():"(null)")); CPrintf(CON_CMDOUTPUT,"Members:\n"); CPrintf(CON_CMDOUTPUT, "%-6s %-6s %-30s %-6s %-6s %-15s %-15s %-20s %-20s\n", "NPC ID", "EID", "Name", "Entity", "Status", "Brain","Behaviour","Owner","Tribe"); for (size_t j = 0; j < tribes[i]->GetMemberCount(); j++) { NPC * npc = tribes[i]->GetMember(j); CPrintf(CON_CMDOUTPUT, "%6d %6d %-30s %-6s %-6s %-15s %-15s %-20s %-20s\n" , npc->GetID(), (npc->GetEntity()?npc->GetEntity()->GetID():0), npc->GetName().GetDataSafe(), (npc->GetEntity()?"Entity":"None "), (npc->IsAlive()?"Alive":"Dead"), (npc->GetBrain()?npc->GetBrain()->GetName().GetDataSafe():""), (npc->GetCurrentBehavior()?npc->GetCurrentBehavior()->GetName():""), npc->GetOwnerName().GetDataSafe(), (npc->GetTribe()?npc->GetTribe()->GetName().GetDataSafe():"") ); } CPrintf(CON_CMDOUTPUT,"Resources:\n"); CPrintf(CON_CMDOUTPUT,"%-20s %7s\n","Name","Amount"); for (size_t r = 0; r < tribes[i]->GetResourceCount(); r++) { CPrintf(CON_CMDOUTPUT,"%-20s %d\n", tribes[i]->GetResource(r).name.GetData(), tribes[i]->GetResource(r).amount); } CPrintf(CON_CMDOUTPUT,"Memories:\n"); CPrintf(CON_CMDOUTPUT,"%-20s Position Radius %-20s %-20s\n","Name","Sector","Private to NPC"); csList::Iterator it = tribes[i]->GetMemoryIterator(); while (it.HasNext()) { psTribe::Memory* memory = it.Next(); csString name; if (memory->npc) { name.Format("%s(%d)",memory->npc->GetName().GetDataSafe(),memory->npc->GetID()); } CPrintf(CON_CMDOUTPUT,"%-20s %7.1f %7.1f %7.1f %7.1f %-20s %-20s\n", memory->name.GetDataSafe(), memory->pos.x,memory->pos.y,memory->pos.z,memory->radius, (memory->sector?memory->sector->QueryObject()->GetName():""), name.GetDataSafe()); } } } } void psNPCClient::ListWaypoints(char * pattern) { BinaryRBIterator iter(&waypoints); Waypoint *wp; CPrintf(CON_CMDOUTPUT, "%9s %-30s %-40s %-10s %10s %s\n", "WP id", "Name", "Position","Radius","Dist","PI"); for (wp = iter.First(); wp; wp = ++iter) { if (!pattern || strstr(wp->loc.name.GetDataSafe(),pattern)) { CPrintf(CON_CMDOUTPUT, "%9d %-30s (%9.3f,%9.3f,%9.3f, %s) %9.3f %9.3f %s" , wp->loc.id,wp->loc.name.GetDataSafe(),wp->loc.pos.x,wp->loc.pos.y,wp->loc.pos.z, wp->loc.sectorName.GetDataSafe(),wp->loc.radius,wp->distance, (wp->pi?wp->pi->loc.name.GetDataSafe():"")); for (size_t i = 0; i < wp->links.Length(); i++) { CPrintf(CON_CMDOUTPUT," %s%s(%.1f)",(wp->prevent_wander[i]?"#":""), wp->links[i]->loc.name.GetDataSafe(),wp->dists[i]); } CPrintf(CON_CMDOUTPUT,"\n"); } } } void psNPCClient::ListLocations(char * pattern) { BinaryRBIterator iter(&loctypes); LocationType *loc; CPrintf(CON_CMDOUTPUT, "%9s %9s %-30s %-10s %10s\n", "Type id", "Loc id", "Name", "Region",""); for (loc = iter.First(); loc; loc = ++iter) { if (!pattern || strstr(loc->name.GetDataSafe(),pattern)) { CPrintf(CON_CMDOUTPUT, "%9d %9s %-30s %-10s\n" , loc->id,"",loc->name.GetDataSafe(),"",""); for (size_t i = 0; i < loc->locs.Length(); i++) { CPrintf(CON_CMDOUTPUT, "%9s %9d %-30s %-10s (%9.3f,%9.3f,%9.3f, %s) %9.3f %9.3f\n" , "",loc->locs[i]->id,loc->locs[i]->name.GetDataSafe(), (loc->locs[i]->IsRegion()?"True":"False"), loc->locs[i]->pos.x,loc->locs[i]->pos.y,loc->locs[i]->pos.z, loc->locs[i]->sectorName.GetDataSafe(), loc->locs[i]->radius,loc->locs[i]->rot_angle); if (loc->locs[i]->IsRegion()) { for (size_t j = 0; j < loc->locs[i]->locs.Length(); j++) { CPrintf(CON_CMDOUTPUT, "%9s %9s %-30s %-10s (%9.3f,%9.3f,%9.3f, %s) %9.3f\n" , "","","","", loc->locs[i]->locs[j]->pos.x,loc->locs[i]->locs[j]->pos.y,loc->locs[i]->locs[j]->pos.z, loc->locs[i]->locs[j]->sectorName.GetDataSafe(), loc->locs[i]->locs[j]->radius); } } } } } } void psNPCClient::HandleDeath(NPC *who) { who->GetBrain()->Interrupt(who,eventmanager); who->SetAlive(false); if (who->GetTribe()) { who->GetTribe()->HandleDeath(who); } } void psNPCClient::HandlePlayerDeath(PS_ID who) { } iCelEntity* psNPCClient::FindEntity( uint32_t id ) { for (size_t x = 0; x < objects.Length(); x++ ) { if ( objects[x]->GetEntity()->GetID() == id ) return objects[x]->GetEntity(); } return NULL; } void psNPCClient::PerceptProximityItems() { for (size_t i=0; i < objects.Length(); i++) { gemNPCItem * item = dynamic_cast(objects[i]); if (item && item->GetEntity() && item->IsPickable()) { ItemPerception pcpt_adjacent("item adjacent", item->GetEntity()); ItemPerception pcpt_nearby("item nearby", item->GetEntity()); ItemPerception pcpt_sensed("item sensed", item->GetEntity()); iSector *sector; csVector3 pos; float yrot; psGameObject::GetPosition(item->GetEntity(),pos,yrot,sector); TriggerEvent(NULL,&pcpt_adjacent,PERSONAL_RANGE_PERCEPTION,&pos,sector); TriggerEvent(NULL,&pcpt_nearby,SHORT_RANGE_PERCEPTION,&pos,sector); TriggerEvent(NULL,&pcpt_sensed,LONG_RANGE_PERCEPTION,&pos,sector); } } BinaryRBIterator iter(&loctypes); LocationType *loc; for (loc = iter.First(); loc; loc = ++iter) { for (size_t i = 0; i < loc->locs.Length(); i++) { LocationPerception pcpt_sensed("location sensed",loc->name, loc->locs[i]); TriggerEvent(NULL,&pcpt_sensed,LONG_RANGE_PERCEPTION,&loc->locs[i]->pos,loc->locs[i]->GetSector(engine)); } } } void psNPCClient::CatchCommand(const char *cmd) { printf("Caught command: %s\n",cmd); network->SendConsoleCommand(cmd+1); } /*------------------------------------------------------------------*/ psNPCTick::psNPCTick(int offsetticks, psNPCClient *c) : psGameEvent(0,offsetticks,"psResumeScriptEvent") { client = c; } void psNPCTick::Trigger() { if (running) client->Tick(); }