// NeL - MMORPG Framework // Copyright (C) 2010 Winch Gate Property Limited // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as // published by the Free Software Foundation, either version 3 of the // License, or (at your option) any later version. // // 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 Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . #include "collision_service.h" #include using namespace SBSERVICE; using namespace NLMISC; using namespace NLNET; using namespace NLPACS; using namespace std; ////////////////////////////////////////////////////////////////////////////// /// /// /// VARIABLES /// /// /// ////////////////////////////////////////////////////////////////////////////// CCollisionService::CClientMap CCollisionService::_Clients; NLPACS::URetrieverBank *CCollisionService::_RetrieverBank; NLPACS::UGlobalRetriever *CCollisionService::_GlobalRetriever; NLPACS::UMoveContainer *CCollisionService::_MoveContainer; CCollisionService::CMovePrimitiveVector CCollisionService::_StaticMovePrimitives; TTime CCollisionService::_LastTime, CCollisionService::_NewTime, CCollisionService::_DiffTime; double CCollisionService::_DiffTimeSeconds; float CCollisionService::_DiffTimeFloat; ////////////////////////////////////////////////////////////////////////////// /// /// /// BASIC FUNCTIONS /// /// /// ////////////////////////////////////////////////////////////////////////////// void CCollisionService::commandStart() { } void CCollisionService::init() { // set down callback CUnifiedNetwork::getInstance()->setServiceDownCallback("*", cbDown); // set the data path CPath::addSearchPath(ConfigFile.getVar("DataPath").asString(), true, false); // init the global retriever, the retriever bank, and the move container _RetrieverBank = URetrieverBank::createRetrieverBank(ConfigFile.getVar("RetrieverBankName").asString().c_str()); _GlobalRetriever = UGlobalRetriever::createGlobalRetriever(ConfigFile.getVar("GlobalRetrieverName").asString().c_str(), _RetrieverBank); _MoveContainer = UMoveContainer::createMoveContainer(_GlobalRetriever, 100, 100, 6.0); // some silly snowballs specific code to load static instance groups, redo CConfigFile::CVar igv = ConfigFile.getVar("InstanceGroups"); for (uint i = 0; i < igv.size(); ++i) { NL3D::UInstanceGroup *ig = NL3D::UInstanceGroup::createInstanceGroup(igv.asString(i)); if (ig == NULL) nlwarning("Instance group '%s' not found", igv.asString(i).c_str()); else { for (uint i = 0; i < ig->getNumInstance(); ++i) { UMovePrimitive *primitive = _MoveContainer->addCollisionablePrimitive(0, 1); primitive->setPrimitiveType(UMovePrimitive::_2DOrientedCylinder); primitive->setReactionType(UMovePrimitive::DoNothing); primitive->setTriggerType(UMovePrimitive::NotATrigger); primitive->setCollisionMask(2); primitive->setOcclusionMask(1); primitive->setObstacle(true); string name = ig->getShapeName(i); float rad; if (strlwr(name) == "pi_po_igloo_a") rad = 4.5f; else if (strlwr(name) == "pi_po_snowman_a") rad = 1.0f; else if (strlwr(name) == "pi_po_pinetree_a") rad = 2.0f; else if (strlwr(name) == "pi_po_tree_a") rad = 2.0f; else if (strlwr(name) == "pi_po_pingoo_stat_a") rad = 1.0f; else if (strlwr(name) == "pi_po_gnu_stat_a") rad = 1.0f; else { rad = 2.0f; nlwarning ("Instance name '%s' doesn't have a good radius for collision", name.c_str()); } primitive->setRadius(rad); primitive->setHeight(6.0f); primitive->insertInWorldImage(0); CVector pos = ig->getInstancePos(i); primitive->setGlobalPosition(CVectorD(pos.x, pos.y, pos.z - 1.5f), 0); _StaticMovePrimitives.push_back(primitive); } } delete ig; } _NewTime = CTime::getLocalTime(); } bool CCollisionService::update() { _LastTime = _NewTime; _NewTime = CTime::getLocalTime(); _DiffTime = _NewTime - _LastTime; _DiffTimeSeconds = (double)_DiffTime / 1000.0; _DiffTimeFloat = (float)_DiffTimeSeconds; for (CClientMap::iterator it = _Clients.begin(); it != _Clients.end(); it++) for (CEntityMap::iterator it2 = it->second.begin(); it2 != it->second.end(); it2++) { CEntity &entity = it2->second; if (entity.Moving) { // nldebug("entity.Moving"); CVector movement = entity.NewClientPosition - entity.OldClientPosition; entity.Distance = movement.norm(); if (entity.Distance == 0.0f) { entity.Moving = false; entity.Retry = false; } CVectorD speed = CVectorD(movement) / _DiffTimeSeconds; entity.MovePrimitive->move(speed, 0); } // else nldebug("!entity.Moving"); } // apparently this thingy does the collision checks _MoveContainer->evalCollision(_DiffTimeSeconds, 0); // check all wrong stuff (use some different way maybe) for (CClientMap::iterator it = _Clients.begin(); it != _Clients.end(); it++) for (CEntityMap::iterator it2 = it->second.begin(); it2 != it->second.end(); it2++) { CEntity &entity = it2->second; if (entity.Moving) { UGlobalPosition globalPosition; entity.MovePrimitive->getGlobalPosition(globalPosition, 0); CVector serverPosition = _GlobalRetriever->getGlobalPosition(globalPosition); //nlinfo("clientPosition: %f %f %f", entity.NewClientPosition.x, entity.NewClientPosition.y, entity.NewClientPosition.z); //nlinfo("serverPosition: %f %f %f", serverPosition.x, serverPosition.y, serverPosition.z); // Allow the difference between the external client position and the // local server position to be up to half of the traveled distance // plus the entity's height or radius, twice (using Retry). float allowedDifference = entity.Distance * 0.5f; bool move = false; if (abs(entity.NewClientPosition.z - serverPosition.z) > 1.0f + allowedDifference) { move = true; } else { // fake server position has same z as client position if ok serverPosition.z = entity.NewClientPosition.z; if ((serverPosition - entity.NewClientPosition).norm() > entity.MovePrimitive->getRadius() + allowedDifference) move = true; } if (move) // Entity moved incorrectly. { if (entity.Retry) // If second try. { nldebug("entity.Retry"); msgPosition(it2->first, serverPosition); } else nldebug("!entity.Retry"); entity.Retry = !entity.Retry; // Else the entity gets one more chance. } // The difference to the new position must be from local server position. entity.OldClientPosition = serverPosition; } } msgUpdate(); return true; } void CCollisionService::release() { } ////////////////////////////////////////////////////////////////////////////// /// /// /// OTHER FUNCTIONS /// /// /// ////////////////////////////////////////////////////////////////////////////// void CCollisionService::sendMessage(CMessage &msgout) { CUnifiedNetwork *instance = CUnifiedNetwork::getInstance(); for (CClientMap::iterator it = _Clients.begin(); it != _Clients.end(); it++) CUnifiedNetwork::getInstance()->send(TServiceId(it->first), msgout); } ////////////////////////////////////////////////////////////////////////////// /// /// /// MESSAGE SENDERS /// /// /// ////////////////////////////////////////////////////////////////////////////// /**************************************************************************** * Function: msgUpdate * Send CLS_UPDATE message to registered services * * Arguments: ****************************************************************************/ void CCollisionService::msgUpdate() { if (!_Clients.size()) return; static CMessage msgout("CLS_UPDATE"); sendMessage(msgout); //nldebug("Sent CLS_UPDATE to %u services", _Clients.size()); } /**************************************************************************** * Function: msgPosition * Send CLS_POSITION message to registered services * * Arguments: * - id: entity id * - position: new position ****************************************************************************/ void CCollisionService::msgPosition(uint32 id, CVector position) { if (!_Clients.size()) return; CMessage msgout("CLS_POSITION"); msgout.serial(id, position); sendMessage(msgout); nldebug("Sent CLS_POSITION %u %f %f %f to %u services", id, position.x, position.y, position.z, _Clients.size()); } ////////////////////////////////////////////////////////////////////////////// /// /// /// MESSAGE CALLBACKS /// /// /// ////////////////////////////////////////////////////////////////////////////// /**************************************************************************** * Function: cbAdd * Receives an "ADD" message. ****************************************************************************/ void CCollisionService::cbAdd(CMessage &msgin, const std::string &serviceName, TServiceId sid) { // Read incoming message uint32 id; CVector position; float radius; msgin.serial(id, position, radius); nldebug("Received ADD %u %f %f %f %f", id, position.x, position.y, position.z, radius); // Do something with it CEntity &entity = _Clients[sid.get()][id] = CEntity(); entity.OldClientPosition = position; entity.NewClientPosition = position; entity.MovePrimitive = _MoveContainer->addCollisionablePrimitive(0, 1); entity.MovePrimitive->setPrimitiveType(UMovePrimitive::_2DOrientedCylinder); entity.MovePrimitive->setReactionType(UMovePrimitive::Slide); entity.MovePrimitive->setTriggerType(UMovePrimitive::NotATrigger); entity.MovePrimitive->setCollisionMask(3); entity.MovePrimitive->setOcclusionMask(2); entity.MovePrimitive->setObstacle(true); entity.MovePrimitive->setRadius(radius); entity.MovePrimitive->setHeight(1.8f); entity.MovePrimitive->insertInWorldImage(0); entity.MovePrimitive->setGlobalPosition(position, 0); } /**************************************************************************** * Function: cbMove * Receives a "MOVE" message. ****************************************************************************/ void CCollisionService::cbMove(CMessage &msgin, const std::string &serviceName, TServiceId sid) { // Read incoming message uint32 id; CVector position; msgin.serial(id, position); // nldebug("Received MOVE %u %f %f %f", id, position.x, position.y, position.z); // Do something with it CEntity &entity = _Clients[sid.get()][id]; entity.NewClientPosition = position; if (entity.OldClientPosition != position) entity.Moving = true; } /**************************************************************************** * Function: cbRemove * Receives a "REMOVE" message. ****************************************************************************/ void CCollisionService::cbRemove(CMessage &msgin, const std::string &serviceName, TServiceId sid) { // Read incoming message uint32 id; msgin.serial(id); nldebug("Received REMOVE %u", id); // Do something with it if (_Clients[sid.get()].find(id) == _Clients[sid.get()].end()) { nlwarning("Unknown entity %u", id); return; } CEntity &entity = _Clients[sid.get()][id]; _MoveContainer->removePrimitive(entity.MovePrimitive); _Clients[sid.get()].erase(id); } /**************************************************************************** * Function: cbRegister * Receives a "REGISTER" message. ****************************************************************************/ void CCollisionService::cbRegister(CMessage &msgin, const std::string &serviceName, TServiceId sid) { // Read incoming message nldebug("Received REGISTER %s %s", serviceName.c_str(), sid.toString().c_str()); // Do something with it _Clients[sid.get()] = CEntityMap(); } ////////////////////////////////////////////////////////////////////////////// /// /// /// NETWORK CALLBACKS /// /// /// ////////////////////////////////////////////////////////////////////////////// /**************************************************************************** * Function: cbDown * Called when a registered service goes down ****************************************************************************/ void CCollisionService::cbDown(const string &serviceName, TServiceId sid, void *arg) { CClientMap::iterator it = _Clients.find(sid.get()); if (it != _Clients.end()) { // remove all entities for (CEntityMap::iterator it2 = it->second.begin(); it2 != it->second.end(); it2++) _MoveContainer->removePrimitive(it2->second.MovePrimitive); // erase the client _Clients.erase(it); } } ////////////////////////////////////////////////////////////////////////////// /// /// /// SERVICE CONFIGURATION /// /// /// ////////////////////////////////////////////////////////////////////////////// /**************************************************************************** * CallbackArray * * It define the functions to call when receiving a specific message ****************************************************************************/ TUnifiedCallbackItem CallbackArray[] = { { "REGISTER", CCollisionService::cbRegister }, { "ADD", CCollisionService::cbAdd }, { "MOVE", CCollisionService::cbMove }, { "REMOVE", CCollisionService::cbRemove }, }; /**************************************************************************** * SNOWBALLS COLLISION SERVICE MAIN Function * * This call create a main function for the world_service: * * - based on the service class CCollisionService inherited from IService * - having the short name "CLS" * - having the long name "collision_service" * - listening on an automatically allocated port (0) by the naming service * - and callback actions set to "CallbackArray" * ****************************************************************************/ NLNET_SERVICE_MAIN(CCollisionService, "CLS", "collision_service", 0, CallbackArray, "", "") /* end of file */