/* * actionmanager.cpp * * Copyright (C) 2005 Atomic Blue (info@planeshift.it, http://www.atomicblue.org) * * Credits : * Michael Cummings * * 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. * * Creation Date: 1/20/2005 * Description : server manager for clickable map object actions * */ #include #include #include "iutil/object.h" #include #include #include #include #include "globals.h" #include "iserver/idal.h" #include "actionmanager.h" #include "gem.h" #include "clients.h" #include "events.h" #include "psserver.h" #include "util/psdatabase.h" #include "net/msghandler.h" #include "net/messages.h" #include "util/log.h" #include "util/serverconsole.h" #include "cachemanager.h" #include "combatmanager.h" #include "netmanager.h" #include "npcmanager.h" #include "psserverchar.h" #include "bulkobjects/psactionlocationinfo.h" #include "entitymanager.h" #include "progressionmanager.h" #include "util/eventmanager.h" psActionTimeoutGameEvent::psActionTimeoutGameEvent( ActionManager *mgr, const psActionLocation * actionLocation, size_t clientnum) : psGameEvent(0, 1000, "psActionTimeoutGameEvent") { valid = false; if ( !mgr ) return; actionmanager = mgr; info = actionLocation; client = clientnum; valid = true; } psActionTimeoutGameEvent::~psActionTimeoutGameEvent() { valid = false; actionmanager = NULL; info = NULL; client = 0; } void psActionTimeoutGameEvent::Trigger() { psSectorInfo *sector; float pos_x, pos_y, pos_z, yrot; Client *clientPtr = psserver->GetNetManager()->GetClient( (int)client ); if ( clientPtr && info ) { clientPtr->GetCharacterData()->GetLocationInWorld( sector, pos_x, pos_y, pos_z, yrot ); csVector3 clientPos(pos_x, pos_y, pos_z ); if ( csSquaredDist::PointPoint( clientPos, info->position ) > ( info->radius * info->radius ) ) { actionmanager->RemoveActiveTrigger( client, info ); valid = false; } else { // still in range create new event psActionTimeoutGameEvent *newevent = new psActionTimeoutGameEvent( actionmanager, info, client ); psserver->GetEventManager()->Push( newevent ); } } } //---------------------------------------------------------------------------- ActionManager::ActionManager( psDatabase *db) { database = db; //clients = psserver->GetNetManager()->GetConnections(); // Action Messages from client that need handling psserver->GetEventManager()->Subscribe( this, MSGTYPE_MAPACTION, REQUIRE_READY_CLIENT ); //// Used to handle proximity triggers // psserver->GetEventManager()->Subscribe( this, MSGTYPE_DEAD_RECKONING, REQUIRE_READY_CLIENT ); //PreloadActionLocations(); } ActionManager::~ActionManager() { // Unsubscribe from Messages psserver->GetEventManager()->Unsubscribe( this, MSGTYPE_MAPACTION ); //psserver->GetEventManager()->Unsubscribe( this, MSGTYPE_DEAD_RECKONING ); { csHash::GlobalIterator it (actionLocationList.GetIterator ()); while ( it.HasNext () ) { psActionLocation* actionLocation = it.Next (); delete actionLocation; } } database = NULL; } void ActionManager::HandleMessage( MsgEntry *me, Client *client ) { switch ( me->GetType() ) { case MSGTYPE_MAPACTION: { psMapActionMessage msg( me ); if ( msg.valid ) HandleMessage( &msg, client ); break; } //case MSGTYPE_DEAD_RECKONING: // { // psDRMessage msg( me, CacheManager::GetSingleton().GetMsgStrings(), EntityManager::GetSingleton().GetEngine() ); // if ( msg.valid ) // HandleMessage( &msg, client ); // break; // } } } void ActionManager::HandleMessage( psMapActionMessage *msg, Client *client ) { if ( !msg->valid ) return; switch ( msg->command ) { case psMapActionMessage::QUERY : HandleQueryMessage( msg->actionXML, client ); break; case psMapActionMessage::SAVE : HandleSaveMessage( msg->actionXML, client ); break; case psMapActionMessage::LIST_QUERY : HandleListMessage( msg->actionXML, client ); break; case psMapActionMessage::DELETE_ACTION: HandleDeleteMessage( msg->actionXML, client ); break; case psMapActionMessage::RELOAD_CACHE: HandleReloadMessage( client ); break; } } //void ActionManager::HandleMessage( psDRMessage *msg, Client *client ) //{ // gemActor *actor = client->GetActor(); // // // We don't want zombies // if (actor && !actor->IsAlive()) // return; // // if (msg->sector == NULL) return; // // // Create Query Message for Query // const char* xmlFormat = "%s%0.4f%.4f%.4fPROXIMITY"; // // csString xml(""); // xml.Format( xmlFormat, EscpXML( msg->sectorName ).GetData(), msg->pos.x, msg->pos.y, msg->pos.z ); // // psMapActionMessage mamsg( client->GetClientNum(), psMapActionMessage::QUERY, xml ); // psserver->GetEventManager()->Publish( mamsg.msg ); //} void ActionManager::HandleQueryMessage( csString xml, Client *client ) { csString triggerType; bool handled = false; // Search for matching locations csRef doc; csRef root, topNode, node; if ((doc = ParseString( xml )) && ((root = doc->GetRoot())) && ((topNode = root->GetNode("location"))) && (node = topNode->GetNode("triggertype"))) { triggerType = node->GetContentsValue(); if ( triggerType.CompareNoCase( "SELECT" ) ) handled = HandleSelectQuery ( topNode, client ); if ( triggerType.CompareNoCase( "PROXIMITY" ) ) handled = HandleProximityQuery( topNode, client ); if ( !handled ) { psMapActionMessage msg( client->GetClientNum(), psMapActionMessage::NOT_HANDLED, xml ); if ( msg.valid ) msg.SendMessage(); } } else { Error4("Player (%s)(%d) tried to send a bogus XML string as an action message\nString was %s", client->GetActor()->GetCharacterData()->GetCharName(), client->GetActor()->GetPlayerID(), xml.GetData() ); } } bool ActionManager::HandleSelectQuery( csRef topNode, Client *client ) { csString triggerType(""), sectorName(""), meshName(""); bool handled = false; // Search for matching locations csRef node; // sector node = topNode->GetNode( "triggertype" ); if ( node ) { triggerType = node->GetContentsValue(); } // sector node = topNode->GetNode( "sector" ); if ( node ) { sectorName = node->GetContentsValue(); } // mesh node = topNode->GetNode( "mesh" ); if ( node ) { meshName = node->GetContentsValue(); } psActionLocation *search = new psActionLocation(); search->Load( topNode ); // Search for matches csArray matchMesh; // matches on sector + mesh csArray matchPoly; // matches on sector + mesh + poly csArray matchPoint; // matches on sector + mesh + poly + point w/in radius csArray matches; psActionLocation* actionLocation; csHash::Iterator iter ( actionLocationList.GetIterator( csHashCompute( triggerType + sectorName + meshName ) ) ); while ( iter.HasNext() ) { actionLocation = iter.Next (); switch ( actionLocation->IsMatch( search ) ) { case 0: // No Match break; case 1: // Match on Sector + Mesh matchMesh.Push( actionLocation ); break; case 2: // Match on Poly matchPoly.Push( actionLocation ); break; case 3: // Match on Point matchPoint.Push( actionLocation ); break; } } // Use correct Set of Matches if ( matchPoint.Length() != 0 ) matchPoint.TransferTo( matches ); else if ( matchPoly.Length() != 0 ) matchPoly.TransferTo( matches ); else if ( matchMesh.Length() != 0 ) matchMesh.TransferTo( matches ); // ProcessMatches handled = ProcessMatches( matches, client ); // cleanup delete search; return handled; } bool ActionManager::HandleProximityQuery( csRef topNode, Client *client ) { csString sectorName(""); csVector3 position( 0.0f, 0.0f, 0.0f ); bool handled = false; // Search for matching locations csRef node; // sector node = topNode->GetNode( "sector" ); if ( node ) { sectorName = node->GetContentsValue(); } csRef posNode = topNode->GetNode( "position" ); if ( topNode ) { float posx=0.0f, posy=0.0f, posz=0.0f; node = posNode->GetNode( "x" ); if ( node ) posx = node->GetContentsValueAsFloat(); node = posNode->GetNode( "y" ); if ( node ) posy = node->GetContentsValueAsFloat(); node = posNode->GetNode( "z" ); if ( node ) posz = node->GetContentsValueAsFloat(); position = csVector3( posx, posy, posz ); } csArray matches; psActionLocation* actionLocation; csHash::Iterator iter ( actionLocation_by_sector.GetIterator( csHashCompute( sectorName ) ) ); while (iter.HasNext ()) { actionLocation = iter.Next (); if ( actionLocation->triggertype == "PROXIMITY" ) { csString key(""); size_t id= actionLocation->id; key.AppendFmt("%zu", id); size_t clientnum=client->GetClientNum(); key.AppendFmt( "%zu", clientnum ); csHash::Iterator active ( activeTriggers.GetIterator( csHashCompute( key ) ) ); if ( ( actionLocation->sectorname == sectorName ) && ( csSquaredDist::PointPoint( actionLocation->position, position ) < ( actionLocation->radius * actionLocation->radius ) ) ) { // Found match if ( !active.HasNext() ) matches.Push( actionLocation ); } } } // ProcessMatches handled = ProcessMatches( matches, client ); csArray::Iterator results( matches.GetIterator() ); while ( results.HasNext() ) { actionLocation = results.Next(); csString key(""); size_t id=actionLocation->id; size_t clientnum=client->GetClientNum(); key.AppendFmt("%zu",id ); key.AppendFmt( "%zu", clientnum ); activeTriggers.Put( csHashCompute( key ), actionLocation ); psActionTimeoutGameEvent *event = new psActionTimeoutGameEvent( this, actionLocation, client->GetClientNum() ); psserver->GetEventManager()->Push( event ); } return handled; } psActionLocation *ActionManager::FindAction( uint id ) { psActionLocation* actionLocation; csHash::GlobalIterator iter ( actionLocationList.GetIterator() ); while ( iter.HasNext() ) { actionLocation = iter.Next(); if ( actionLocation->GetGemObject()->GetEntity()->GetID() == id ) { return actionLocation; } } return NULL; } bool ActionManager::ProcessMatches( csArray matches, Client* client ) { bool handled = false; psActionLocation* actionLocation; // Call correct OperationHandler csArray::Iterator results( matches.GetIterator() ); while ( results.HasNext() ) { actionLocation = results.Next(); if ( actionLocation->responsetype == "EXAMINE" ) { HandleExamineOperation( actionLocation, client ); handled = true; } if ( actionLocation->responsetype == "SCRIPT" ) { HandleScriptOperation( actionLocation, client ); handled = true; } } return handled; } void ActionManager::HandleListMessage( csString xml, Client *client ) { if ( client->GetSecurityLevel() <= 29) { psserver->SendSystemError(client->GetAccountID(), "Access is denied. Only Admin level 9 can manage Actions."); return; } csString sectorName; // Search for matching locations csRef doc ; csRef root, topNode, node; if ((doc = ParseString( xml )) && ((root = doc->GetRoot())) && ((topNode = root->GetNode("location"))) && (node = topNode->GetNode("sector"))) // sector { sectorName = node->GetContentsValue(); // Call proper operation csString responseXML(""); responseXML.Append( "" ); csHash::Iterator iter ( actionLocation_by_sector.GetIterator( csHashCompute( sectorName ) ) ); while (iter.HasNext ()) { psActionLocation* actionLocation = iter.Next (); responseXML.Append( actionLocation->ToXML() ); } responseXML.Append( "" ); psMapActionMessage msg( client->GetClientNum(), psMapActionMessage::LIST, responseXML ); if ( msg.valid ) msg.SendMessage(); } else { Error4("Player (%s)(%d) tried to send a bogus XML string as an action message\nString was %s", client->GetActor()->GetCharacterData()->GetCharName(), client->GetActor()->GetPlayerID(), xml.GetData() ); } } void ActionManager::HandleSaveMessage( csString xml, Client *client ) { if ( client->GetSecurityLevel() <= 29) { psserver->SendSystemError(client->GetClientNum(), "Access is denied. Only Admin level 9 can manage Actions."); return; } // Search for matching locations csRef doc; csRef root, topNode; if ((doc = ParseString( xml )) && ((root = doc->GetRoot())) && ((topNode = root->GetNode("location"))))// sector { csRef node; psActionLocation *action; node = topNode->GetNode( "id" ); if ( !node ) return; csString id( node->GetContentsValue() ); node = topNode->GetNode( "name" ); if ( !node ) return; csString name( node->GetContentsValue() ); node = topNode->GetNode( "sector" ); if ( !node ) return; csString sectorName( node->GetContentsValue() ); node = topNode->GetNode( "mesh" ); if ( !node ) return; csString meshName( node->GetContentsValue() ); if ( id.Length() == 0 ) { // Add New Location action = new psActionLocation(); CS_ASSERT( action != NULL ); } else { // Update existing location psActionLocation *current; // Find Elememt csHash::Iterator iter ( actionLocation_by_sector.GetIterator( csHashCompute( sectorName ) ) ); while ( iter.HasNext() ) { current = iter.Next(); CS_ASSERT( current != NULL ); if ( current->id == (size_t)atoi( id.GetData() ) ) { action = current; } } // Remove from Cache actionLocationList.Delete( csHashCompute( action->triggertype + action->sectorname + action->meshname ), action ); actionLocation_by_name.Delete( csHashCompute( action->name ), action ); actionLocation_by_sector.Delete( csHashCompute( action->sectorname ), action ); } if ( action != NULL ) { // Update DB if ( action->Load( topNode ) ) { action->Save(); //Update Cache if ( EntityManager::GetSingleton().CreateActionLocation( action, false ) ) { actionLocationList.Put( csHashCompute( action->triggertype + action->sectorname + action->meshname ), action ); actionLocation_by_name.Put( csHashCompute( action->name ), action ); actionLocation_by_sector.Put( csHashCompute( action->sectorname ), action ); } else { Error2("Failed to find load action : \"%s\"", action->name.GetData()); delete action; } } else { Error2("Failed to find load action : \"%s\"", action->name.GetData()); delete action; } } csString xmlMsg; csString escpxml = EscpXML(sectorName); xmlMsg.Format("%s", escpxml.GetData() ); HandleListMessage(xmlMsg, client ); } else { Error4("Player (%s)(%d) tried to send a bogus XML string as an action message\nString was %s", client->GetActor()->GetCharacterData()->GetCharName(), client->GetActor()->GetPlayerID(), xml.GetData() ); } } void ActionManager::HandleDeleteMessage( csString xml, Client *client ) { if ( client->GetSecurityLevel() <= 29) { psserver->SendSystemError(client->GetClientNum(), "Access is denied. Only Admin level 9 can manage Actions."); return; } // Search for matching locations csRef doc; csRef root, topNode, node; if ((doc = ParseString( xml )) && ((root = doc->GetRoot())) && ((topNode = root->GetNode("location"))) && (node = topNode->GetNode("id"))) { csString id( node->GetContentsValue() ); // Find Elememt psActionLocation *current, *actionLocation; csHash::GlobalIterator iter ( actionLocationList.GetIterator() ); while ( iter.HasNext() ) { current = iter.Next(); CS_ASSERT( current != NULL ); if ( current->id == (size_t)atoi( id.GetData() ) ) { actionLocation = current; } } // No Match if ( !actionLocation ) return; csString sectorName = actionLocation->sectorname; // Update DB if ( actionLocation->Delete() ) { // Remove from Cache actionLocationList.Delete( csHashCompute( actionLocation->triggertype + actionLocation->sectorname + actionLocation->meshname ), actionLocation ); actionLocation_by_name.Delete( csHashCompute( actionLocation->name ), actionLocation ); actionLocation_by_sector.Delete( csHashCompute( actionLocation->sectorname ), actionLocation ); delete actionLocation; } csString xmlMsg; csString escpxml = EscpXML(sectorName); xmlMsg.Format("%s", escpxml.GetData() ); HandleListMessage(xmlMsg, client ); } else { Error4("Player (%s)(%d) tried to send a bogus XML string as an action message\nString was %s", client->GetActor()->GetCharacterData()->GetCharName(), client->GetActor()->GetPlayerID(), xml.GetData() ); } } void ActionManager::HandleReloadMessage(Client * client) { if ( client->GetSecurityLevel() <= 29) { psserver->SendSystemError(client->GetClientNum(), "Access is denied. Only Admin level 9 can manage Actions."); return; } csHash::GlobalIterator it (actionLocationList.GetIterator ()); while (it.HasNext ()) { psActionLocation* actionLocation = it.Next (); delete actionLocation; } actionLocationList.DeleteAll(); actionLocation_by_sector.DeleteAll(); actionLocation_by_name.DeleteAll(); RepopulateActionLocations(); } void ActionManager::HandleExamineOperation( psActionLocation* action, Client *client ) { // Create Entity on Client action->Send( client->GetClientNum() ); // Set as the target client->SetTargetObject( action->GetGemObject(), true ); // check if the actionlocation is linked to real item int instance_id = action->GetInstanceIDOfContainer(); if (instance_id==-1) { if (action->GetGemObject()->GetItem()) instance_id = (int)action->GetGemObject()->GetItem()->GetUID(); } gemItem* realItem = GEMSupervisor::GetSingleton().FindItemEntity( instance_id ); // Invoke Interaction menu int options = psGUIInteractMessage::EXAMINE; if (!action->IsGameBoard()) options |= psGUIInteractMessage::USE; else options |= psGUIInteractMessage::PLAYGAME; options |= psGUIInteractMessage::CLOSE; if (realItem && realItem->IsContainer()) options |= psGUIInteractMessage::COMBINE; if (realItem && realItem->IsLockable()) options |= psGUIInteractMessage::UNLOCK; psGUIInteractMessage interactMsg( client->GetClientNum(), options ); interactMsg.SendMessage(); } void ActionManager::HandleScriptOperation( psActionLocation* action, Client *client ) { // if no event is specified, do nothing if ( action->response.Length() > 0) { ProgressionEvent *progEvent = psserver->GetProgressionManager()->FindEvent( action->response ); if (progEvent) { if ( progEvent->Run( client->GetActor(), NULL, psserver->GetProgressionManager() ) ) { return; } } else { Error2("Failed to find progression event \"%s\"", action->response.GetData()); return; } } } void ActionManager::RemoveActiveTrigger( size_t clientnum, const psActionLocation *actionLocation ) { csString key(""); size_t id= actionLocation->id; key.AppendFmt("%zu", id); key.AppendFmt("%zu", clientnum ); csHash::Iterator active ( activeTriggers.GetIterator( csHashCompute( key ) ) ); while ( active.HasNext() ) activeTriggers.Delete( csHashCompute( key ), active.Next() ); } bool ActionManager::RepopulateActionLocations(psSectorInfo *sectorinfo) { unsigned int currentrow; psActionLocation* newaction; csString query; if ( sectorinfo ) query.Format("SELECT al.*, master.triggertype master_triggertype, master.responsetype master_responsetype, master.response master_response FROM action_locations al LEFT OUTER JOIN action_locations master ON al.master_id = master.id WHERE al.sectorname='%s'", sectorinfo->name.GetData()); else query = "SELECT al.*, master.triggertype master_triggertype, master.responsetype master_responsetype, master.response master_response FROM action_locations al LEFT OUTER JOIN action_locations master ON al.master_id = master.id"; Result result( db->Select(query) ); if (!result.IsValid()) return false; for ( currentrow = 0; currentrow < result.Count(); currentrow++) { newaction = new psActionLocation(); CS_ASSERT(newaction != NULL); if ( newaction->Load( result[currentrow] ) ) { if ( EntityManager::GetSingleton().CreateActionLocation( newaction, false ) ) { actionLocationList.Put( csHashCompute( newaction->triggertype + newaction->sectorname + newaction->meshname ), newaction ); actionLocation_by_name.Put( csHashCompute( newaction->name ), newaction ); actionLocation_by_sector.Put( csHashCompute( newaction->sectorname ), newaction ); } else { Error2("Failed to find load action : \"%s\"", newaction->name.GetData()); delete newaction; } } else { Error2("Failed to find load action : \"%s\"", newaction->name.GetData()); delete newaction; } } return true; }