/* * psCelClient.cpp by Matze Braun * * Copyright (C) 2002 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. * * Implements the varrious things relating to the CEL for the client. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "globals.h" #include "engine/celbase.h" #include "engine/netpersist.h" #include "net/messages.h" #include "net/msghandler.h" #include "util/psxmlparser.h" #include "psbehave/psworld.h" #include "clientvitals.h" #include "modehandler.h" #include "zonehandler.h" #include "pscelclient.h" #include "psclientdr.h" #include "entitylabels.h" #include "clientcachemanager.h" #include "pscharcontrol.h" #include "psclientchar.h" #include "pscal3dcallback.h" #include "util/psconst.h" #include "util/psmeshutil.h" #include "gui/pawsinfowindow.h" #include "gui/pawsconfigkeys.h" #include "gui/inventorywindow.h" #include "gui/chatwindow.h" #include "gui/pawslootwindow.h" #include "paws/pawsmanager.h" psCelClient *GEMClientObject::cel = NULL; psCelClient::psCelClient() : ignore_others(false) { requeststatus = 0; clientdr = NULL; modehandler = NULL; zonehandler = NULL; entityLabels = NULL; gameWorld = NULL; local_player = NULL; unresSector = NULL; } psCelClient::~psCelClient() { delete gameWorld; Clear(); if (msghandler) { msghandler->Unsubscribe( this, MSGTYPE_CELPERSIST); msghandler->Unsubscribe( this, MSGTYPE_PERSIST_WORLD ); msghandler->Unsubscribe( this, MSGTYPE_PERSIST_ACTOR ); msghandler->Unsubscribe( this, MSGTYPE_PERSIST_ITEM ); msghandler->Unsubscribe( this, MSGTYPE_PERSIST_ACTIONLOCATION ); msghandler->Unsubscribe( this, MSGTYPE_REMOVE_OBJECT ); msghandler->Unsubscribe( this, MSGTYPE_NAMECHANGE ); msghandler->Unsubscribe( this, MSGTYPE_GUILDCHANGE ); msghandler->Unsubscribe( this, MSGTYPE_GROUPCHANGE ); msghandler->Unsubscribe( this, MSGTYPE_STATS ); } if (clientdr) delete clientdr; if (entityLabels) delete entityLabels; } iCelEntity* psCelClient::GetMainActor() { return local_player ? local_player->GetEntity() : NULL; } bool psCelClient::Initialize(iObjectRegistry* object_reg, MsgHandler* newmsghandler,ModeHandler *modehndlr, ZoneHandler *zonehndlr) { if (!CelBase::Initialize(object_reg)) return false; entityLabels = new psEntityLabels(); entityLabels->Initialize(object_reg, this); vfs = CS_QUERY_REGISTRY(object_reg, iVFS); if (!vfs) return false; modehandler = modehndlr; zonehandler = zonehndlr; msghandler = newmsghandler; msghandler->Subscribe( this, MSGTYPE_CELPERSIST); msghandler->Subscribe( this, MSGTYPE_PERSIST_WORLD ); msghandler->Subscribe( this, MSGTYPE_PERSIST_ACTOR ); msghandler->Subscribe( this, MSGTYPE_PERSIST_ITEM ); msghandler->Subscribe( this, MSGTYPE_PERSIST_ACTIONLOCATION ); msghandler->Subscribe( this, MSGTYPE_REMOVE_OBJECT ); msghandler->Subscribe( this, MSGTYPE_NAMECHANGE ); msghandler->Subscribe( this, MSGTYPE_GUILDCHANGE ); msghandler->Subscribe( this, MSGTYPE_GROUPCHANGE ); msghandler->Subscribe( this, MSGTYPE_STATS ); clientdr = new psClientDR; if (!clientdr->Initialize(object_reg, this, msghandler )) { delete clientdr; clientdr = NULL; return false; } LoadTextures(); unresSector = psengine->GetEngine()->CreateSector("SectorWhereWeKeepEntitiesResidingInUnloadedMaps"); return true; } GEMClientObject* psCelClient::FindObject( int id ) { return entities_hash.Get (id, NULL); } void psCelClient::SetMainActor(iCelEntity* entity) { if (!entity) return; mainPlayerEntity = entity; // ModeHandler has no good way to find out the entity modehandler->SetEntity(entity); } void psCelClient::RequestServerWorld() { psPersistWorldRequest world; msghandler->SendMessage( world.msg ); requeststatus=1; } bool psCelClient::IsReady() { if ( local_player == NULL || gameWorld == NULL ) return false; else return true; } void psCelClient::HandleWorld( MsgEntry* me ) { psPersistWorld mesg( me ); gameWorld = new psWorld; gameWorld->Initialize( object_reg ); zonehandler->SetWorld(gameWorld); // Tell the user that we are loading the world psengine->AddLoadingWindowMsg( "Loading world" ); gameWorld->Initialize( object_reg ); zonehandler->LoadZone(mesg.sector); requeststatus = 0; RequestActor(); } void psCelClient::RequestActor() { psPersistActorRequest mesg; msghandler->SendMessage( mesg.msg ); } void psCelClient::HandleActor( MsgEntry* me ) { // Check for loading errors first if (psengine->LoadingError() || !GetClientDR()->GetMsgStrings() ) { Error1("Ignoring Actor message. LoadingError or missing strings table!\n"); psengine->FatalError("Cannot load main actor. Error during loading.\n"); return; } psPersistActor mesg( me, GetClientDR()->GetMsgStrings(), psengine->GetEngine() ); // We already have an entity with this id so we must have missed the remove object message // so delete and remake it. GEMClientObject* found = (GEMClientObject *) FindObject(mesg.entityid); if (found) { if ( found == local_player ) { HandleMainActor( mesg ); return; } Debug3(LOG_CELPERSIST, 0, "Found existing object<%s> with id %u, removing.\n", found->GetName(), mesg.entityid); RemoveObject(found); } if (ignore_others) { if (local_player != 0) return; // From here on, we have no main character // Ignore everything that isn't controllable if (!mesg.control) return; } GEMClientActor* actor = new GEMClientActor( this, mesg ); // The first actor that is sent to the client is his own one. if ( local_player == NULL && actor->control ) { local_player = actor; SetMainActor( local_player->GetEntity() ); // This triggers the server to update our proxlist local_player->SendDRUpdate(PRIORITY_LOW,GetClientDR()->GetMsgStrings()); } if (actor->linmove) actor->linmove->SetDeltaLimit(0.2f); // csVector3 pos = actor->pcmesh->GetMesh()->GetMovable()->GetPosition(); // iSector* sector = actor->pcmesh->GetMesh()->GetMovable()->GetSectors()->Get(0); // printf("Recevied actor(%d) '%s' Pos (%.2f,%.2f,%.2f) sector '%s'\n", actor->GetID(),actor->GetName(), pos.x,pos.y,pos.z, sector->QueryObject()->GetName() ); entities.Push(actor); entities_hash.Put(actor->GetID(), actor); } void psCelClient::HandleMainActor( psPersistActor& mesg ) { // Saved for when we reset if ( local_player_defaultFactName.IsEmpty() ) { local_player_defaultFactName = local_player->GetFactName(); local_player_defaultFact = local_player->pcmesh->GetMesh()->GetFactory(); local_player_defaultMesh = local_player->pcmesh->GetMesh()->GetMeshObject(); } // Update equipment list local_player->equipment = mesg.equipment; if (mesg.factname != local_player->GetFactName()) { csRef factory = psengine->GetEngine()->GetMeshFactories()->FindByName(mesg.factname); if (!factory) { Error2("Couldn't morph main player! Factory %s doesn't exist!", mesg.factname.GetData() ); return; } // Remove any equipment psengine->BuildEquipment(local_player->pcmesh->GetMesh(), local_player->equipment, local_player->traitList, true); // New or resetting? if (local_player_defaultFactName != mesg.factname) { csRef meshwrap = psengine->GetEngine()->CreateMeshWrapper(factory, mesg.factname); csRef mesh = meshwrap->GetMeshObject(); // Update local_player->pcmesh->GetMesh()->SetMeshObject(mesh); local_player->pcmesh->GetMesh()->SetFactory(factory); // Cal3d csRef calstate = SCF_QUERY_INTERFACE(local_player->pcmesh->GetMesh()->GetMeshObject(),iSpriteCal3DState); if (calstate) { calstate->SetUserData((void*)local_player); calstate->SetVelocity(0.0,&psengine->GetRandomGen()); } } else { // Reset local_player->pcmesh->GetMesh()->SetMeshObject(local_player_defaultMesh); local_player->pcmesh->GetMesh()->SetFactory(local_player_defaultFact); } // Update factory local_player->factname = mesg.factname; // Update cal3d local_player->RefreshCal3d(); // Replace any equipment psengine->BuildEquipment(local_player->pcmesh->GetMesh(), local_player->equipment, local_player->traitList, false); } } void psCelClient::HandleItem( MsgEntry* me ) { psPersistItem mesg( me ); // We already have an entity with this id so we must have missed the remove object message // so delete and remake it. GEMClientObject* found = (GEMClientObject *) FindObject(mesg.id); if(found) { Debug3(LOG_CELPERSIST, 0, "Found existing item<%s> object with id %u, removing.\n", found->GetName(), mesg.id); RemoveObject(found); } GEMClientItem * newItem = new GEMClientItem( this, mesg ); entities.Push(newItem); entities_hash.Put(newItem->GetID(), newItem); } void psCelClient::HandleActionLocation( MsgEntry* me ) { psPersistActionLocation mesg( me ); // We already have an entity with this id so we must have missed the remove object message // so delete and remake it. GEMClientObject* found = (GEMClientObject *) FindObject(mesg.id); if ( found ) { Debug3(LOG_CELPERSIST, 0, "Found existing location<%s> object with id %u, removing.\n", found->GetName(), mesg.id); RemoveObject( found ); } GEMClientActionLocation * newAction = new GEMClientActionLocation( this, mesg ); entities.Push( newAction ); actions.Push( newAction ); entities_hash.Put ( newAction->GetID (), newAction ); } void psCelClient::HandleObjectRemoval( MsgEntry* me ) { psRemoveObject mesg(me); GEMClientObject* entity = FindObject( mesg.object ); Debug2( LOG_CELPERSIST, 0, "Object %d Removed", mesg.object ); if (entity != NULL) { RemoveObject(entity); } } csList::Iterator psCelClient::FindUnresolvedPos(GEMClientObject * entity) { csList::Iterator posIter(unresPos); while (posIter.HasNext()) { UnresolvedPos * pos = posIter.Next(); if (pos->entity == entity) return posIter; } return csList::Iterator(); } void psCelClient::RemoveObject(GEMClientObject* entity) { if (entity == local_player) { Error1("Nearly deleted player's object"); return; } // csVector3 pos = entity->pcmesh->GetMesh()->GetMovable()->GetPosition(); // iSector* sector = entity->pcmesh->GetMesh()->GetMovable()->GetSectors()->Get(0); // printf("Removing entity(%d) '%s' Pos (%.2f,%.2f,%.2f) sector '%s'\n", entity->GetID(),entity->GetName(), pos.x,pos.y,pos.z, sector->QueryObject()->GetName() ); if ( psengine->GetCharManager()->GetTarget() == entity ) { pawsWidget* widget = PawsManager::GetSingleton().FindWidget("InteractWindow"); if(widget) { widget->Hide(); psengine->GetCharManager()->SetTarget(NULL, "select"); } } entityLabels->RemoveObject(entity); pawsLootWindow* loot = (pawsLootWindow*)PawsManager::GetSingleton().FindWidget("LootWindow"); if(loot && loot->GetLootingActor() == entity->GetID()) loot->Hide(); // delete record in unresolved position list csList::Iterator posIter = FindUnresolvedPos(entity); if (posIter.HasCurrent()) { //Error2("Removing deleted entity %s from unresolved",entity->GetName()); delete *posIter; unresPos.Delete(posIter); } // Delete from action list if action if(dynamic_cast(entity)) actions.Delete( static_cast(entity) ); entities_hash.Delete (entity->GetID (), entity); entities.Delete(entity); } bool psCelClient::IsMeshSubjectToAction(const char* sector,const char* mesh) { if(!mesh) return false; for(size_t i = 0; i < actions.Length();i++) { GEMClientActionLocation* action = actions[i]; const char* sec = action->pcmesh->GetMesh()->GetMovable()->GetSectors()->Get(0)->QueryObject()->GetName(); if(!strcmp(action->GetMesh(),mesh) && !strcmp(sector,sec)) return true; } return false; } void psCelClient::HandleNameChange( MsgEntry* me ) { psUpdateObjectNameMessage msg (me); GEMClientObject* object = FindObject(msg.objectID); if(!object) { printf("Warning: Got rename message, but couldn't find actor %d!\n",msg.objectID); return; } // Slice the names into parts csString oldFirstName = object->GetName(); oldFirstName = oldFirstName.Slice(0,oldFirstName.FindFirst(' ')); csString newFirstName = msg.newObjName; newFirstName = newFirstName.Slice(0,newFirstName.FindFirst(' ')); // Remove old name from chat auto complete and add new pawsChatWindow* chat = (pawsChatWindow*)(PawsManager::GetSingleton().FindWidget( "ChatWindow" )); chat->RemoveAutoCompleteName(oldFirstName); chat->AddAutoCompleteName(newFirstName); // Apply the new name object->ChangeName(msg.newObjName); // We don't have a label over our own char if (psengine->GetCelClient()->GetMainActor() != object->GetEntity()) entityLabels->OnObjectArrived(object); // If object is targeted update the target information. if (psengine->GetCharManager()->GetTarget() == object) { PawsManager::GetSingleton().Publish("sTargetName",object->GetName() ); } } void psCelClient::HandleGuildChange( MsgEntry* me ) { psUpdatePlayerGuildMessage msg (me); // Change every entity for(size_t i = 0; i < msg.objectID.Length();i++) { int id = (int)msg.objectID[i]; GEMClientActor* actor = dynamic_cast(FindObject(id)); //Apply the new name if(!actor) { Error2("Couldn't find actor %d!",id); continue; } actor->SetGuildName(msg.newGuildName); // we don't have a label over our own char if (psengine->GetCelClient()->GetMainActor() != actor->GetEntity()) entityLabels->OnObjectArrived(actor); } } void psCelClient::HandleGroupChange(MsgEntry* me) { psUpdatePlayerGroupMessage msg(me); GEMClientActor* actor = (GEMClientActor*)FindObject(msg.objectID); //Apply the new name if(!actor) { Error2("Couldn't find object %d, ignoring group change..",msg.objectID); return; } printf("Got group update for actor %d (%s) to group %d\n",actor->GetID(),actor->GetName(),msg.groupID); unsigned int oldGroup = actor->GetGroupID(); actor->SetGroupID(msg.groupID); // repaint label if (GetMainActor() != actor->GetEntity()) entityLabels->RepaintObjectLabel(actor); else // If it's us, we need to repaint every label with the group = ours { for(size_t i =0; i < entities.Length();i++) { GEMClientObject* object = entities[i]; GEMClientActor* act = dynamic_cast(object); if(!act) continue; if(act != actor && (actor->IsGroupedWith(act) || (oldGroup != 0 && oldGroup == act->GetGroupID()) ) ) entityLabels->RepaintObjectLabel(act); } } } void psCelClient::HandleStats( MsgEntry* me ) { psStatsMessage msg(me); PawsManager::GetSingleton().Publish( "fmaxhp", msg.hp ); PawsManager::GetSingleton().Publish( "fmaxmana", msg.mana ); PawsManager::GetSingleton().Publish( "fmaxweight", msg.weight ); PawsManager::GetSingleton().Publish( "fmaxcapacity", msg.capacity ); } void psCelClient::QueueNewActor(MsgEntry *me) { me->IncRef(); newActorQueue.Push(me); } void psCelClient::QueueNewItem(MsgEntry *me) { me->IncRef(); newItemQueue.Push(me); } void psCelClient::CheckEntityQueues() { if (newActorQueue.Length()) { MsgEntry *me = newActorQueue.Pop(); HandleActor(me); me->DecRef(); return; } if (newItemQueue.Length()) { MsgEntry *me = newItemQueue.Pop(); HandleItem(me); me->DecRef(); return; } } void psCelClient::HandleMessage(MsgEntry *me) { switch ( me->GetType() ) { case MSGTYPE_STATS: { HandleStats(me); break; } case MSGTYPE_PERSIST_WORLD: { HandleWorld( me ); break; } case MSGTYPE_PERSIST_ACTOR: { QueueNewActor( me ); break; } case MSGTYPE_PERSIST_ITEM: { if (!ignore_others) QueueNewItem( me ); break; } case MSGTYPE_PERSIST_ACTIONLOCATION: { HandleActionLocation( me ); break; } case MSGTYPE_REMOVE_OBJECT: { HandleObjectRemoval( me ); break; } case MSGTYPE_NAMECHANGE: { HandleNameChange( me ); break; } case MSGTYPE_GUILDCHANGE: { HandleGuildChange( me ); break; } case MSGTYPE_GROUPCHANGE: { HandleGroupChange( me ); break; } default: { Error1("CEL UNKNOWN MESSAGE!!!\n"); break; } } } void psCelClient::SetPlayerReady(bool flag) { if (local_player) local_player->ready = flag; } void psCelClient::OnRegionsDeleted(csArray& regions) { size_t entNum; for (entNum = 0; entNum < entities.Length(); entNum++) { iPcMesh * mesh = entities[entNum]->pcmesh; if (mesh != NULL && mesh->GetMesh() ) { iMovable* movable = mesh->GetMesh()->GetMovable(); iSectorList* sectors = movable->GetSectors(); // Shortcut the lengthy region check if possible if(IsUnresSector(movable->GetSectors()->Get(0))) continue; bool unresolved = true; // Are all the sectors going to be unloaded? for(int i = 0;iGetCount();i++) { // Get the iRegion this sector belongs to csRef region = SCF_QUERY_INTERFACE(sectors->Get(i)->QueryObject()->GetObjectParent(), iRegion); if(regions.Find(region)==csArrayItemNotFound) { // We've found a sector that won't be unloaded so the mesh won't need to be moved unresolved = false; break; } } if(unresolved) { // All the sectors the mesh is in are going to be unloaded Warning1(LOG_ANY,"Moving entity to temporary sector"); // put the mesh to the sector that server uses for keeping meshes located in unload maps HandleUnresolvedPos(entities[entNum], movable->GetPosition(), 0.0f,sectors->Get(0)->QueryObject ()->GetName ()); } } } } void psCelClient::HandleUnresolvedPos(GEMClientObject * entity, const csVector3 & pos, float rot, const csString & sector) { //Error3("Handling unresolved %s at %s", entity->GetName(),sector.GetData()); csList::Iterator posIter = FindUnresolvedPos(entity); // if we already have a record about this entity, just update it, otherwise create new one if (posIter.HasCurrent()) { (*posIter)->pos = pos; (*posIter)->rot = rot; (*posIter)->sector = sector; //Error1("-editing"); } else { unresPos.PushBack(new UnresolvedPos(entity, pos, rot, sector)); //Error1("-adding"); } GEMClientActor* actor = dynamic_cast (entity); if(actor && actor->colldet) { // This will disable CD algorithms temporarily actor->colldet->SetOnGround(true); if (actor == local_player && psengine->GetCharControl()) psengine->GetCharControl()->GetMovementManager()->StopAllMovement(); actor->StopMoving(true); } // move the entity to special sector where we keep entities with unresolved position entity->SetPosition(pos, rot, unresSector); } void psCelClient::OnMapsLoaded() { // look if some of the unresolved entities can't be resolved now csList::Iterator posIter(unresPos); ++posIter; while (posIter.HasCurrent()) { UnresolvedPos * pos = posIter.FetchCurrent(); //Error3("Re-resolving %s at %s",pos->entity->GetName(), pos->sector.GetData()); iSector * sector = psengine->GetEngine()->GetSectors ()->FindByName (pos->sector); if (sector) { //Error2("Successfuly resolved %s", pos->sector.GetData()); if(pos->entity->pcmesh && pos->entity->pcmesh->GetMesh() && pos->entity->pcmesh->GetMesh()->GetMovable()) { // If we have a mesh, no need to re set the position. iMovable* movable = pos->entity->pcmesh->GetMesh()->GetMovable(); // Check if entity has moved to a different sector now, so no need to move back if(IsUnresSector(movable->GetSectors()->Get(0))) movable->SetSector(sector); } else pos->entity->SetPosition(pos->pos, pos->rot, sector); GEMClientActor* actor = dynamic_cast (pos->entity); if(actor && actor->colldet) // we are now in a physical sector actor->colldet->SetOnGround(false); delete *posIter; // Deleting automatically increments the iterator. unresPos.Delete(posIter); } else ++posIter; } } void psCelClient::PruneEntities() { // Only perform every 3 frames static int count = 0; count++; if (count % 3 != 0) return; else count = 0; for (size_t entNum = 0; entNum < entities.Length(); entNum++) { if ((GEMClientActor*) entities[entNum] == local_player) continue; iPcMesh * mesh = entities[entNum]->pcmesh; if (mesh != NULL && mesh->GetMesh()) { GEMClientActor* actor = dynamic_cast (entities[entNum]); if (actor) { csVector3 vel; actor->linmove->GetVelocity(vel); if (vel.y < -50) // Large speed puts too much stress on CPU { Debug3(LOG_ANY,0, "Disabling CD on actor(%d): %s", actor->GetID(),actor->GetName()); actor->colldet->SetOnGround(true); // Reset velocity actor->StopMoving(true); } } } } } //------------------------------------------------------------------------------- GEMClientObject::GEMClientObject() { entitylabel = NULL; flags = 0; } GEMClientObject::GEMClientObject( psCelClient* cel, PS_ID id ) { if (!this->cel) this->cel = cel; entity = cel->GetPlLayer()->CreateEntity(id); entitylabel = NULL; } GEMClientObject::~GEMClientObject() { iMeshWrapper* mesh = pcmesh->GetMesh(); if(mesh) psengine->GetEngine()->RemoveObject (mesh); cel->GetPlLayer()->RemoveEntity( entity ); } bool GEMClientObject::SetPosition(const csVector3 & pos, float rot, iSector * sector) { pcmesh->MoveMesh( sector , pos ); // Rotation csMatrix3 matrix = (csMatrix3) csYRotMatrix3 (rot); pcmesh->GetMesh()->GetMovable()->GetTransform().SetO2T (matrix); return true; } void GEMClientObject::Move(const csVector3& pos,float rotangle, const char* room) { iSector* sector = psengine->GetEngine()->FindSector(room); if (sector) GEMClientObject::SetPosition(pos, rotangle, sector); else psengine->GetCelClient()->HandleUnresolvedPos(this, pos, rotangle, room); } bool GEMClientObject::InitMesh( const char *factname, const char *filename, const csVector3& pos, const float rotangle, const char* room ) { csRef pc; pc = cel->GetPlLayer()->CreatePropertyClass(entity, "pcmesh"); if ( !pc ) { Error1("Could not create Item because pcmesh class couldn't be created."); return false; } pcmesh = SCF_QUERY_INTERFACE ( pc, iPcMesh ); if ( !pcmesh ) { Error1("Could not create Item because pcmesh class doesn't implement iPcMesh."); return false; } // Helm Mesh Check // If there is helm specific item and we don't have any race yet, fall back to // the stonebreaker model csString replacement("stonebm"); if ( cel->GetMainPlayer() ) { replacement = cel->GetMainPlayer()->helmGroup; } psString factoryName(factname); factoryName.ReplaceAllSubString("$H", replacement); csRef factory = psengine->GetEngine()->GetMeshFactories()->FindByName (factoryName); if ( !factory ) { Error2( "Mesh Factory %s not found", factoryName.GetData() ); return false; } else { csRef meshWrapper = factory->CreateMeshWrapper(); pcmesh->SetMesh(meshWrapper); csRef calstate = SCF_QUERY_INTERFACE(pcmesh->GetMesh()->GetMeshObject(), iSpriteCal3DState); if (calstate) calstate->SetUserData((void *)this); } iMeshWrapper* mesh = pcmesh->GetMesh(); if ( !mesh ) { Error2("Could not create Item because could not load %s file into mesh.",factname); return false; } Move(pos,rotangle,room); return true; } void GEMClientObject::ChangeName(const char* name) { if (entity) entity->SetName(name); this->name = name; } GEMClientActor::GEMClientActor( psCelClient* cel, psPersistActor& mesg ) : GEMClientObject( cel, mesg.entityid ) { name = mesg.name; race = mesg.race; helmGroup = mesg.helmGroup; entity->SetName( mesg.name ); id = mesg.entityid; type = mesg.type; guildName = mesg.guild; control = mesg.control; flags = mesg.flags; linmove = NULL; colldet = NULL; groupID = mesg.groupID; factname = mesg.factname; ownerID = mesg.ownerID; lastSentVelocity = lastSentRotation = 0.0f; stationary = true; mode = (size_t)~0; alive = true; vitalManager = new psClientVitals; if ( helmGroup.Length() == 0 ) helmGroup = factname; Debug3( LOG_CELPERSIST, 0, "Actor %s(%d) Received", mesg.name.GetData(), mesg.entityid ); if ( !InitMesh(mesg.factname, mesg.filename, mesg.pos, mesg.yrot, mesg.sectorName) ) { Error3("Fatal Error: Could not create actor %s(%d)", mesg.name.GetData(), mesg.entityid ); return; } DRcounter = 0; // mesg.counter cannot be trusted as it may have changed while the object was gone DRcounter_set = false; InitLinMove( mesg.pos, mesg.yrot, mesg.sectorName, mesg.top, mesg.bottom, mesg.offset ); if (mesg.sector != NULL) linmove->SetDRData(mesg.on_ground,1.0f,mesg.pos,mesg.yrot,mesg.sector,mesg.vel,mesg.worldVel,mesg.ang_vel); else cel->HandleUnresolvedPos(this, mesg.pos, mesg.yrot, mesg.sectorName); InitCharData( mesg.texParts, mesg.equipment ); RefreshCal3d(); SetCharacterMode(mesg.mode,true); SetAnimationVelocity(mesg.vel); if (!control) cel->GetEntityLabels()->OnObjectArrived(this); lastDRUpdateTime = 0; ready = false; } GEMClientActor::~GEMClientActor() { delete vitalManager; } int GEMClientActor::GetAnimIndex (csStringHash* msgstrings, csStringID animid) { if (!cal3dstate) return -1; int idx = anim_hash.Get (animid, -1); if (idx >= 0) return idx; // Not cached yet. csString animName = msgstrings->Request (animid); idx = cal3dstate->FindAnim (animName); if (idx >= 0) { // Cache it. anim_hash.Put (animid, idx); } return idx; } csVector3 GEMClientActor::Pos() { csVector3 pos; iSector* sector; float yrot; linmove->GetLastPosition (pos,yrot, sector); return pos; } csVector3 GEMClientActor::Rot() { csVector3 pos; csVector3 rot(0,0,0); iSector* sector; linmove->GetLastPosition (pos,rot.y, sector); return rot; } iSector *GEMClientActor::GetSector() { csVector3 pos; float yrot; iSector* sector; linmove->GetLastPosition (pos,yrot, sector); return sector; } bool GEMClientActor::NeedDRUpdate(unsigned char& priority) { linmove->GetVelocity(vel); linmove->GetAngularVelocity(angularVelocity); // Never send DR messages until client is "ready" if (!ready ) return false; if (linmove->IsPath() && !path_sent) { priority = PRIORITY_HIGH; return true; } csTicks timenow = csGetTicks(); float delta = timenow - lastDRUpdateTime; priority = PRIORITY_LOW; if (vel.IsZero () && angularVelocity.IsZero () && lastSentVelocity.IsZero () && lastSentRotation.IsZero () && delta>3000 && !stationary) { lastSentRotation = angularVelocity; lastSentVelocity = vel; priority = PRIORITY_HIGH; stationary = true; return true; } // has the velocity rotation changed since last DR msg sent? if ((angularVelocity != lastSentRotation || vel.x != lastSentVelocity.x || vel.z != lastSentVelocity.z) && ((delta>250) || angularVelocity.IsZero () || lastSentRotation.IsZero () || vel.IsZero () || lastSentVelocity.IsZero ())) { lastSentRotation = angularVelocity; lastSentVelocity = vel; stationary = false; lastDRUpdateTime = timenow; return true; } if ((vel.y && !lastSentVelocity.y) || (!vel.y && lastSentVelocity.y) || (vel.y > 0 && lastSentVelocity.y < 0) || (vel.y < 0 && lastSentVelocity.y > 0)) { lastSentRotation = angularVelocity; lastSentVelocity = vel; stationary = false; lastDRUpdateTime = timenow; return true; } // if in motion, send every second instead of every 3 secs. if ((!lastSentVelocity.IsZero () || !lastSentRotation.IsZero () ) && (delta > 1000)) { lastSentRotation = angularVelocity; lastSentVelocity = vel; stationary = false; lastDRUpdateTime = timenow; return true; } // Real position/rotation/action is as calculated, no need for update. return false; } void GEMClientActor::SendDRUpdate(unsigned char priority, csStringHash* msgstrings) { // send update out PS_ID mappedid = entity->GetID(); // no mapping anymore, IDs are identical bool on_ground; float speed,yrot,ang_vel; csVector3 pos, worldVel; iSector *sector; // bool hackflag=false; linmove->GetDRData(on_ground,speed,pos,yrot,sector,vel,worldVel,ang_vel); // Hack to guarantee out of order packet detection -- KWF //if (DRcounter%20 == 0) //{ // DRcounter-=2; // hackflag = true; //} // ++DRcounter is the sequencer of these messages so the server and other // clients do not use out of date messages when delivered out of order. psDRMessage drmsg(0, mappedid,on_ground,0,++DRcounter,pos,yrot,sector,vel,worldVel,ang_vel,msgstrings); drmsg.msg->priority = priority; //if (hackflag) // DRcounter+=2; psengine->GetMsgHandler()->SendMessage(drmsg.msg); } void GEMClientActor::SetDRData(psDRMessage& drmsg) { if (drmsg.sector != NULL) { if (!DRcounter_set || drmsg.IsNewerThan(DRcounter)) { float cur_yrot; csVector3 cur_pos; iSector *cur_sector; linmove->GetLastPosition(cur_pos,cur_yrot,cur_sector); // Force hard DR update on sector change, low speed, or large delta pos if (drmsg.sector != cur_sector || (drmsg.vel < 0.1f) || (csSquaredDist::PointPoint(cur_pos,drmsg.pos) > 25.0f)) { // Do hard DR when it would make you slide linmove->SetDRData(drmsg.on_ground,1.0f,drmsg.pos,drmsg.yrot,drmsg.sector,drmsg.vel,drmsg.worldVel,drmsg.ang_vel); } else { // Do soft DR when moving linmove->SetSoftDRData(drmsg.on_ground,1.0f,drmsg.pos,drmsg.yrot,drmsg.sector,drmsg.vel,drmsg.worldVel,drmsg.ang_vel); } DRcounter = drmsg.counter; DRcounter_set = true; } else { Error4("Ignoring DR pkt version %d for entity %s with version %d.", drmsg.counter, GetName(), DRcounter ); return; } } else { psengine->GetCelClient()->HandleUnresolvedPos(this, drmsg.pos, drmsg.yrot, drmsg.sectorName); } // Update character mode and idle animation SetCharacterMode(drmsg.mode); // Update the animations to match velocity SetAnimationVelocity(drmsg.vel); } void GEMClientActor::StopMoving(bool worldVel) { // stop this actor from moving csVector3 zeros(0.0f, 0.0f, 0.0f); linmove->SetVelocity(zeros); linmove->SetAngularVelocity(zeros); if(worldVel) linmove->ClearWorldVelocity(); } bool GEMClientActor::SetPosition(const csVector3 & pos, float rot, iSector * sector) { if (linmove) linmove->SetPosition(pos, rot, sector); return true; } bool GEMClientActor::InitCharData( const char* textParts, const char* equipment ) { this->traits = textParts; this->equipment = equipment; iDocumentSystem* xml = psengine->GetXMLParser (); csRef doc = xml->CreateDocument(); const char* traitError = doc->Parse(textParts); if ( traitError ) { Error2("Error in XML: %s", traitError ); return false; } csRef traitIter = doc->GetRoot()->GetNode("traits")->GetNodes("trait"); while ( traitIter->HasNext() ) { csRef traitNode = traitIter->Next(); Trait * trait = new Trait; trait->Load(traitNode); traitList.Push(trait); } bool a = psengine->BuildAppearance(pcmesh->GetMesh(),textParts); bool e = psengine->BuildEquipment(pcmesh->GetMesh(),equipment, traitList); return (a && e); } bool GEMClientActor::InitLinMove(const csVector3& pos, float angle, const char* sector, csVector3 top, csVector3 bottom, csVector3 offset ) { csRef pc; pc = cel->GetPlLayer()->CreatePropertyClass(entity, "pclinearmovement"); if ( !pc ) { Error1("Could not create property class pclinearmovement. Actor not created."); return false; } linmove = SCF_QUERY_INTERFACE(pc, iPcLinearMovement); top.x *= .7f; top.z *= .7f; bottom.x *= .7f; bottom.z *= .7f; linmove->InitCD(top, bottom,offset, NULL); iSector * sectorObj = psengine->GetEngine()->FindSector(sector); if (sectorObj != NULL) linmove->SetPosition(pos,angle,sectorObj); else psengine->GetCelClient()->HandleUnresolvedPos(this, pos, angle, sector); colldet = CEL_QUERY_PROPCLASS(GetEntity()->GetPropertyClassList(), iPcCollisionDetection); return true; // right now this func never fail, but might later. } bool GEMClientActor::IsGroupedWith(GEMClientActor* actor) { if(actor && actor->GetGroupID() == groupID && groupID != 0) return true; else return false; } bool GEMClientActor::SetAnimation(const char* anim, int duration) { // Check if the duration demands more than 1 playback? if (duration > cal3dstate->GetAnimationDuration()) { // Yes. Set up callback to handle repetition int repeat = (int)(duration / cal3dstate->GetAnimationDuration()); csRef sprite = SCF_QUERY_INTERFACE(pcmesh->GetMesh()->GetFactory()->GetMeshObjectFactory(), iSpriteCal3DFactoryState); vmAnimCallback* callback = new vmAnimCallback; // Stuff callback's face with what he needs. callback->callbackcount = repeat; callback->callbacksprite = sprite; callback->callbackspstate = cal3dstate; callback->callbackanimation = anim; if (!sprite->RegisterAnimCallback(anim,callback,99999999999.9F)) // Make time huge as we do want only the end here { Error2("Failed to register callback for animation %s",anim); delete callback; } } float fadein = 0.25; float fadeout = 0.25; // TODO: Get these numbers from a file somewhere if ( strcmp(anim,"sit")==0 || strcmp(anim,"stand up")==0 ) { fadein = 0.0; fadeout = 1.5; } return cal3dstate->SetAnimAction(anim,fadein,fadeout); } void GEMClientActor::SetAnimationVelocity(const csVector3& velocity) { if (!alive) // No zombies please return; // Taking larger of the 2 axis; cal3d axis are the opposite of CEL's bool useZ = ABS(velocity.z) > ABS(velocity.x); float cal3dvel = useZ ? velocity.z : velocity.x; cal3dstate->SetVelocity( -cal3dvel, &psengine->GetRandomGen() ); } void GEMClientActor::SetCharacterMode(size_t newmode, bool newactor) { if (newmode == mode) return; mode = newmode; const char* anim = psengine->GetCharControl()->GetMovementManager()->GetModeIdleAnim(mode); if (strcmp(anim,"death")==0) { SetAlive(false,newactor); cal3dstate->ClearAllAnims(); cal3dstate->SetAnimAction(anim,0.0f,1.0f); if (newactor) // If we're creating a new actor that's already dead, we shouldn't show the animation... cal3dstate->SetAnimationTime(9999); // the very end of the death animation ;) } else // Regular mode { SetAlive(true,newactor); SetIdleAnimation(anim); } } void GEMClientActor::SetAlive( bool newvalue, bool newactor ) { if (alive == newvalue) return; alive = newvalue; if (!newactor) psengine->GetCelClient()->GetEntityLabels()->RepaintObjectLabel(this); if (!alive) psengine->GetCelClient()->GetClientDR()->HandleDeath(this); } void GEMClientActor::SetIdleAnimation(const char* anim) { cal3dstate->SetDefaultIdleAnim(anim); } void GEMClientActor::RefreshCal3d() { cal3dstate = SCF_QUERY_INTERFACE( pcmesh->GetMesh()->GetMeshObject(), iSpriteCal3DState ); CS_ASSERT(cal3dstate); } GEMClientItem::GEMClientItem( psCelClient* cel, psPersistItem& mesg ) : GEMClientObject( cel, mesg.id ) { name = mesg.name; Debug3( LOG_CELPERSIST, 0, "Item %s(%d) Received", mesg.name.GetData(), mesg.id ); entity->SetName( mesg.name ); entity->SetID( mesg.id ); id = mesg.id; type = mesg.type; factname = mesg.factname; InitMesh( mesg.factname, mesg.filename, mesg.pos, mesg.yRot, mesg.sector ); cel->GetEntityLabels()->OnObjectArrived(this); } GEMClientActionLocation::GEMClientActionLocation( psCelClient* cel, psPersistActionLocation& mesg ) : GEMClientObject( cel, mesg.id ) { name = mesg.name; Debug3( LOG_CELPERSIST, 0, "Action %s(%d) Received", mesg.name.GetData(), mesg.id ); entity->SetName( mesg.name ); entity->SetID( mesg.id ); id = mesg.id; type = mesg.type; meshname = mesg.mesh; csRef pc; pc = cel->GetPlLayer()->CreatePropertyClass(entity, "pcmesh"); if ( !pc ) { Error1("Could not create GEMClientActionLocation because pcmesh class couldn't be created."); return ; } pcmesh = SCF_QUERY_INTERFACE ( pc, iPcMesh ); if ( !pcmesh ) { Error1("Could not create GEMClientActionLocation because pcmesh class doesn't implement iPcMesh."); return ; } csRef< iEngine > engine = psengine->GetEngine(); csRef< iMeshWrapper > nullmesh = engine->CreateMeshWrapper("crystalspace.mesh.object.null", "ActionLocation"); if ( !nullmesh ) { Error1("Could not create GEMClientActionLocation because crystalspace.mesh.onbject.null could not be created."); return ; } csRef state = SCF_QUERY_INTERFACE(nullmesh->GetMeshObject(), iNullMeshState); if (!state) { Error1("No NullMeshState."); return ; } state->SetRadius(1.0); pcmesh->SetMesh( nullmesh ); Move( csVector3(0,0,0), 0.0f, mesg.sector); }