/* * npc.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 "net/msghandler.h" #include "net/npcmessages.h" #include "npc.h" #include "util/log.h" #include "npcclient.h" #include "globals.h" #include "gem.h" #include "util/psdatabase.h" #include "util/location.h" #include "psbehave/psworld.h" extern iDataConnection *db; NPC::NPC(): checked(false) { brain=NULL; id=0; last_update=0; entity=NULL; linmove=NULL; DRcounter=0; ang_vel=vel=999; region=NULL; last_perception=NULL; debugging=0; alive=false; owner_id=(uint32_t)-1; target_id=(uint32_t)-1; tribe=NULL; checkedSector=NULL; } NPC::~NPC() { if (brain) { delete brain; } } bool NPC::Load(iResultRow& row,BinaryRBTree& npctypes) { name = row["name"]; id = row.GetInt("char_id"); if ( id == 0 ) { Error1("NPC has no id attribute. Error in XML"); return false; } type = row["npctype"]; if ( type.Length() == 0 ) { Error1("NPC has no type attribute. Error in XML"); return false; } region_name = row["region"]; // optional NPCType key(type),*t; t = npctypes.Find(&key); if (!t) { Error2("NPC type '%s' is not found. Error in XML",(const char *)type); return false; } if (row.GetFloat("ang_vel_override")) ang_vel = row.GetFloat("ang_vel_override"); if (row.GetFloat("move_vel_override")) vel = row.GetFloat("move_vel_override"); const char *d = row["console_debug"]; if (d && *d=='Y') { debugging = 5; } else { debugging = 0; } owner_id = row.GetInt("char_id_owner"); brain = new NPCType(*t); // deep copy constructor return true; // success } bool NPC::InsertCopy(int use_char_id) { int r = db->Command("insert into sc_npc_definitions " "(name, char_id, npctype, region, ang_vel_override, move_vel_override, console_debug, char_id_owner) values " "('%s', %d, '%s', '%s', %f, %f, '%c',%d)", name.GetData(), use_char_id, type.GetData(), region_name.GetData(), ang_vel, vel, IsDebugging()?'Y':'N', owner_id); if (r!=1) { Error3("Error in InsertCopy: %s->%s",db->GetLastQuery(),db->GetLastError() ); } else { Debug2(LOG_NEWCHAR,use_char_id,"Inserted %s",db->GetLastQuery()); } return (r==1); } void NPC::SetEntity(iCelEntity* ent) { entity = ent; // Initialize active location to a known ok value if (ent) { iSector *sector; psGameObject::GetPosition(entity,active_locate_pos,active_locate_angle,sector); csRef lm = CEL_QUERY_PROPCLASS(ent->GetPropertyClassList(), iPcLinearMovement); linmove = lm; } else linmove = NULL; } void NPC::Advance(csTicks when,EventManager *eventmgr) { if (last_update) { brain->Advance(when-last_update,this,eventmgr); } last_update = when; } void NPC::ResumeScript(EventManager *eventmgr,Behavior *which) { brain->ResumeScript(this,eventmgr,which); } void NPC::TriggerEvent(Perception *pcpt,EventManager *eventmgr) { // Printf("Got event '%s'.",pcpt->GetName() ); brain->FirePerception(this,eventmgr,pcpt); } void NPC::SetLastPerception(Perception *pcpt) { if (last_perception) delete last_perception; last_perception = pcpt; } iCelEntity *NPC::GetMostHated(float range, bool include_invisible, bool include_invincible) { iSector *sector=NULL; csVector3 pos; float yrot; psGameObject::GetPosition(entity,pos,yrot,sector); return hatelist.GetMostHated(sector,pos,range,include_invisible,include_invincible); } void NPC::AddToHateList(iCelEntity *attacker,float delta) { CPrintf(CON_DEBUG, "Adding %1.2f to hatelist score for %s.\n",delta,attacker->GetName() ); hatelist.AddHate(attacker->GetID(),delta); if(IsDebugging(5)) { DumpHateList(); } } void NPC::RemoveFromHateList(PS_ID who) { if (hatelist.Remove(who)) CPrintf(CON_DEBUG, "Removed %d from hate list.\n",who ); } float NPC::GetEntityHate(iCelEntity *ent) { return hatelist.GetHate( ent->GetID() ); } LocationType *NPC::GetRegion() { if (region) { return region; } else { region = npcclient->FindRegion(region_name); return region; } } void NPC::DumpBehaviorList() { CPrintf(CON_CMDOUTPUT, "\nBehaviors for %s (%d)\n----------------------------------------\n", name.GetData(),id ); brain->DumpBehaviorList(); } void NPC::DumpHateList() { CPrintf(CON_CMDOUTPUT, "\nHate list for %s (%d)\n----------------------------------------\n", name.GetData(),id ); hatelist.DumpHateList(); } void NPC::ClearState() { brain->ClearState(); last_perception = NULL; hatelist.Clear(); } void NPC::GetNearestEntity(uint32_t& target_id,csVector3& dest,csString& name,float range) { csVector3 loc; iSector* sector; float rot,min_range; target_id = (uint32_t)-1; psGameObject::GetPosition(entity,loc,rot,sector); csRef nearlist = npcclient->GetPlLayer()->FindNearbyEntities(sector,loc,range); if (nearlist) { min_range=range; for (size_t i=0; iGetCount(); i++) { iCelEntity *ent = nearlist->Get(i); if(ent == entity) continue; csVector3 loc2; iSector *sector2; float rot2; psGameObject::GetPosition(ent,loc2,rot2,sector2); float dist = npcclient->GetWorld()->Distance(loc, sector, loc2, sector2); if (dist < min_range) { min_range = dist; dest = loc2; name = ent->GetName(); target_id = ent->GetID(); } } } } iCelEntity* NPC::GetNearestVisibleFriend(float range) { csVector3 loc; iSector* sector; float rot,min_range; iCelEntity *friendEnt = NULL; psGameObject::GetPosition(entity,loc,rot,sector); csRef nearlist = npcclient->GetPlLayer()->FindNearbyEntities(sector,loc,range); if (nearlist) { min_range=range; for (size_t i=0; iGetCount(); i++) { iCelEntity *ent = nearlist->Get(i); NPC* npcFriend = npcclient->FindAttachedNPC(ent); if (!npcFriend || npcFriend == this) continue; csVector3 loc2, isect; iSector *sector2; float rot2; psGameObject::GetPosition(ent,loc2,rot2,sector2); float dist = (loc2 - loc).Norm(); if(min_range < dist) continue; // Is this friend visible? csIntersectingTriangle closest_tri; iMeshWrapper* sel = 0; dist = csColliderHelper::TraceBeam (npcclient->GetCollDetSys(), sector, loc + csVector3(0, 0.6f, 0), loc2 + csVector3(0, 0.6f, 0), true, closest_tri, isect, &sel); // Not visible if (dist > 0) continue; min_range = (loc2 - loc).Norm(); friendEnt = ent; } } return friendEnt; } void NPC::Printf(const char *msg,...) { if (!IsDebugging(5)) return; char str[1024]; va_list args; va_start(args, msg); vsprintf(str, msg, args); va_end(args); CPrintf(CON_CMDOUTPUT, "%s (%d)> %s\n",GetName().GetDataSafe(),id,str); } void NPC::Printf(int debug, const char *msg,...) { if (!IsDebugging(debug)) return; char str[1024]; va_list args; va_start(args, msg); vsprintf(str, msg, args); va_end(args); CPrintf(CON_CMDOUTPUT, "%s (%d)> %s\n",GetName().GetDataSafe(),id,str); } iCelEntity *NPC::GetTarget() { // If something is targeted, use it. if (target_id != -1 && target_id != 0) { // Check if visible gemNPCObject * obj = npcclient->FindEntityID(target_id); if (obj && !obj->IsVisible()) return NULL; iCelEntity *target = npcclient->FindEntity(target_id); return target; } else // if not, try the last perception entity { if (GetLastPerception()) { iCelEntity *target = GetLastPerception()->GetEntity(); CPrintf(CON_NOTIFY,"GetTarget returning last perception entity: %s\n",target ? target->GetName() : "None specified"); return target; } return NULL; } } void NPC::SetTarget(iCelEntity *ent) { if (ent == NULL) target_id = (uint32_t)~0; else target_id = ent->GetID(); } iCelEntity *NPC::GetOwner() { if (owner_id) { gemNPCObject *obj = npcclient->FindEntityID( owner_id ); if (obj) { return obj->GetEntity(); } else { return NULL; } } else return NULL; } csString NPC::GetOwnerName() { if (owner_id) { gemNPCObject *obj = npcclient->FindEntityID( owner_id ); if (obj) { return obj->GetName(); } } return ""; } void NPC::SetTribe(psTribe * new_tribe) { tribe = new_tribe; } psTribe * NPC::GetTribe() { return tribe; } void NPC::CheckPosition() { // We only need to check the position once csRef pcmesh = CEL_QUERY_PROPCLASS(entity->GetPropertyClassList(), iPcMesh); if(checked) { if(checkedPos == pcmesh->GetMesh()->GetMovable()->GetPosition() && checkedSector == pcmesh->GetMesh()->GetMovable()->GetSectors()->Get(0)) { SetAlive(checkedResult); CPrintf(CON_NOTIFY,"Extrapolation skipped, result of: %s\n", checkedResult ? "Alive" : "Dead"); return; } } if(!alive || linmove->IsPath()) return; // Give the npc a jump start to make sure gravity will be applied. csVector3 startVel(0.0f,1.0f,0.0f); csVector3 vel; linmove->AddVelocity(startVel); linmove->SetOnGround(false); csVector3 pos(pcmesh->GetMesh()->GetMovable()->GetPosition()); // See what happens in the next 10000 ticks. linmove->ExtrapolatePosition(10); linmove->GetVelocity(vel); // Bad starting position - npc is falling at high speed, server should automatically kill it if(vel.y < -50) { CPrintf(CON_ERROR,"Got bad starting location %f %f %f, killing %s (%i).\n", pos.x,pos.y,pos.z,name.GetData(),id); SetAlive(false); } if(vel == startVel) { // Collision detection is not being applied! linmove->SetVelocity(csVector3(0.0f, 0.0f, 0.0f)); psGameObject::SetPosition(entity, pos); } checked = true; checkedPos = pos; checkedSector = pcmesh->GetMesh()->GetMovable()->GetSectors()->Get(0); checkedResult = alive; } //----------------------------------------------------------------------------- void HateList::AddHate(int entity_id,float delta) { HateListEntry *h = hatelist.Get(entity_id, 0); if (!h) { h = new HateListEntry; h->entity_id = entity_id; h->hate_amount = delta; hatelist.Put(entity_id,h); } else { h->hate_amount += delta; } } iCelEntity *HateList::GetMostHated(iSector *sector, csVector3& pos, float range, bool include_invisible, bool include_invincible) { iCelEntity *most = NULL; float most_hate_amount=0; csRef list = npcclient->GetPlLayer()->FindNearbyEntities(sector,pos,range); for (size_t i=0; iGetCount(); i++) { HateListEntry *h = hatelist.Get( list->Get(i)->GetID(),0 ); if (h) { // Skipp if not visible gemNPCObject * obj = npcclient->FindEntityID(list->Get(i)->GetID()); if (obj && !(!obj->IsVisible()||include_invisible)) continue; if (obj && !(!obj->IsInvincible()||include_invisible)) continue; if (!most || h->hate_amount > most_hate_amount) { most = list->Get(i); most_hate_amount = h->hate_amount; } } } return most; } bool HateList::Remove(int entity_id) { return hatelist.DeleteAll(entity_id); } void HateList::Clear() { hatelist.DeleteAll(); } float HateList::GetHate(int ent) { HateListEntry *h = hatelist.Get(ent, 0); if (h) return h->hate_amount; else return 0; } void HateList::DumpHateList() { csHash::GlobalIterator iter = hatelist.GetIterator(); while (iter.HasNext()) { HateListEntry *h = (HateListEntry *)iter.Next(); csVector3 pos(9.9f,9.9f,9.9f); gemNPCObject* obj = npcclient->FindEntityID(h->entity_id); csString sectorName; float yrot; if (obj) { iSector* sector; psGameObject::GetPosition(obj->GetEntity(),pos,yrot,sector); if(sector) sectorName = sector->QueryObject()->GetName(); pos = obj->pcmesh->GetMesh()->GetMovable()->GetPosition(); CPrintf(CON_CMDOUTPUT, "Entity: %d Hated: %.1f\tPos: %.2f %.2f %.2f Sector: %s\n", h->entity_id,h->hate_amount,pos.x,pos.y,pos.z,sectorName.GetDataSafe()); } else { // This is an error situation. Should not hate something that isn't online. CPrintf(CON_CMDOUTPUT, "Entity: %d Hated: %.1f\n", h->entity_id,h->hate_amount); } } CPrintf(CON_CMDOUTPUT, "\n"); }