// Ryzom - 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 "stdpch.h" #include "server_share/r2_variables.h" #include "ai_mgr.h" #include "ai_entity_physical.h" #include "ai_entity_physical_inline.h" #include "ai_instance.h" // critical routine, get backward of weak technical design legacy. const float repulsCoef=1.f/6.f; // arbitrary int CAIEntityPhysical::_PlayerVisibilityDistance = 64; bool CAIEntityPhysical::havePlayersAround() const { // todo Sadge: fixme! The correct fix is to handle the vision count mirror field in GPMS // until then, ring shard swill do much more work than required moving NPCs about if (IsRingShard) { #ifdef NL_DEBUG nlwarning("FIXME: Quick hack to work round drunken NPC problem"); #endif return true; } TYPE_VISION_COUNTER n = 255-currentVisionCounter(); ++AISStat::VisionCtr[n]; if (n==255) return false; return n <= _PlayerVisibilityDistance; } template <> CKnapsackSolver::Algorithm CTargetable::_TargeterChoiceAlgorithm = CKnapsackSolver::FastSingleReplace; template <> float const CTargetable::_DefaultFightTargetersWeightMax = 6.f; template <> float const CTargetable::_DefaultFightWeight = .98f; template <> float const CTargetable::_DefaultFightValue = 1.f; CAIEntityPhysicalLocator* CAIEntityPhysicalLocator::_Instance = NULL; CAIEntityPhysicalLocator* CAIEntityPhysicalLocator::getInstance() { if (!_Instance) _Instance = new CAIEntityPhysicalLocator(); return _Instance; } CAIEntityPhysical* CAIEntityPhysicalLocator::getEntity(TDataSetRow const& row) const { std::map::const_iterator it = _EntitiesByRow.find(row); if (it!=_EntitiesByRow.end()) return it->second; else return NULL; } CAIEntityPhysical* CAIEntityPhysicalLocator::getEntity(NLMISC::CEntityId const& id) const { std::map::const_iterator it = _EntitiesById.find(id); if (it!=_EntitiesById.end()) return it->second; else return NULL; } void CAIEntityPhysicalLocator::addEntity(TDataSetRow const& row, NLMISC::CEntityId const& id, CAIEntityPhysical* entity) { _EntitiesByRow.insert(std::make_pair(row, entity)); _EntitiesById.insert(std::make_pair(id, entity)); } void CAIEntityPhysicalLocator::delEntity(TDataSetRow const& row, NLMISC::CEntityId const& id, CAIEntityPhysical* entity) { _EntitiesById.erase(id); _EntitiesByRow.erase(row); } sint32 CAIEntityPhysical::getFameIndexed(uint32 factionIndex, bool modulated, bool returnUnknownValue) const { return CFameInterface::getInstance().getFameIndexed(getEntityId(), factionIndex, modulated, returnUnknownValue); } sint32 CAIEntityPhysical::getFame(std::string const& faction, bool modulated, bool returnUnknownValue) const { return CFameInterface::getInstance().getFame(getEntityId(), NLMISC::CStringMapper::map(faction), modulated, returnUnknownValue); } ////////////////////////////////////////////////////////////////////////////// CModEntityPhysical::CModEntityPhysical(CPersistentOfPhysical& owner, TDataSetRow const& entityIndex, NLMISC::CEntityId const& id, float radius, uint32 level, RYAI_MAP_CRUNCH::TAStarFlag const& AStarFlags) : CAIEntityPhysical(owner, entityIndex, id, radius, level, AStarFlags) , _Decalage(0, 0) { setMode (MBEHAV::NORMAL); setBehaviour (MBEHAV::IDLE); } inline CAIVector CModEntityPhysical::calcRepulsionFrom(const CAIVector& pos, const std::vector& entities) const { H_AUTO(CalcRepulsionFrom) const double thisDist=radius()+0.5; CAIVector repulse; // collide with entities FOREACHC(it, std::vector, entities) { const CAIEntityPhysical* entityPhysical = *it; nlassert(entityPhysical != NULL); CAIVector deltaPos=pos; deltaPos -=entityPhysical->pos(); const double norm = deltaPos.quickNorm(); const double cmpDist = thisDist+entityPhysical->radius(); if (norm& entities, CAIVector& repulsion) const { H_AUTO(CalcStraightRepulsionFrom) // init the repulsion to (0,0) repulsion.setXY(0.0, 0.0); const CAIVector move = pos - this->pos(); const double moveNorm = move.quickNorm(); const double moveAngle = move.asAngle().asRadians(); const double orientation = theta().asRadians(); // collide with entities FOREACHC(it, std::vector, entities) { const CAIEntityPhysical* entityPhysical = *it; nlassert(entityPhysical != NULL); CAIVector deltaPos = pos; deltaPos -= entityPhysical->pos(); const double norm = deltaPos.quickNorm(); const double cmpDist = radius() + entityPhysical->radius() + 0.5; // if the entity is colliding us if (norm < cmpDist) { // first do a perpendicular repulsion deltaPos.normalize(float( 1000.0 * (cmpDist - norm) )); repulsion += deltaPos; // then add an oriented repulsion double speedFactor = 1.0; double repulsionAngle = deltaPos.asAngle().asRadians(); double angle = computeShortestAngle( moveAngle, (-deltaPos).asAngle().asRadians() ); if (angle > -NLMISC::Pi/2 && angle < NLMISC::Pi/2) { const double angle1 = NLMISC::Pi/2 - angle; const double angle2 = -NLMISC::Pi/2 - angle; const double deviation1 = computeShortestAngle(orientation, repulsionAngle + angle1); const double deviation2 = computeShortestAngle(orientation, repulsionAngle + angle2); if (fabs(deviation1) < fabs(deviation2)) { angle = angle1; } else { angle = angle2; } speedFactor = 1.0 - fabs(angle)/NLMISC::Pi; repulsionAngle += angle; deltaPos = CAngle(repulsionAngle).asVector2d() * norm; } deltaPos.normalize(float( 1000.0 * speedFactor * (cmpDist - norm) )); repulsion += deltaPos; } } // now check that the repulsed position does not collide any entity const CAIVector repulsedPos = pos + repulsion; FOREACHC(it, std::vector, entities) { const CAIEntityPhysical* entityPhysical = *it; nlassert(entityPhysical != NULL); CAIVector deltaPos = repulsedPos; deltaPos -= entityPhysical->pos(); const double norm = deltaPos.quickNorm(); const double cmpDist = radius() + entityPhysical->radius(); if (norm < cmpDist) { return false; } } // ok the repulsion is successful return true; } CAIVector CModEntityPhysical::calcRepulsion(const CAIPos& pos) const { H_AUTO(CalcRepulsion) const double thisDist=radius()+0.5; const double botDist=(double)thisDist+5; // worth case scenario FOR BOTS. const double humanDist=(double)thisDist+2; // worth case scenario FOR HUMANS. std::vector nearbyEntities; { CAIEntityMatrix::CEntityIteratorLinear it; // get nearby bots for (it=getAIInstance()->botMatrix().beginEntities(CAIS::instance().bestLinearMatrixIteratorTbl((uint32) botDist),pos);!it.end();++it) { const CAIEntityPhysical* entityPhysical=(*it).getSpawnObj(); if ( entityPhysical && entityPhysical->isAlive() && entityPhysical!=this) { nearbyEntities.push_back(entityPhysical); } } // get nearby players for (it=getAIInstance()->playerMatrix().beginEntities(CAIS::instance().bestLinearMatrixIteratorTbl((uint32) humanDist),pos);!it.end();++it) { const CAIEntityPhysical* entityPhysical=(*it).getSpawnObj(); if ( entityPhysical && entityPhysical->isAlive() && entityPhysical!=this) { nearbyEntities.push_back(entityPhysical); } } } return calcRepulsionFrom(pos, nearbyEntities); } bool CModEntityPhysical::calcStraightRepulsion(CAIPos const& pos, CAIVector& repulsion) const { H_AUTO(CalcStraightRepulsion) const CAIVector move = pos - this->pos(); const double moveNorm = move.quickNorm(); const CAngle moveAngle = move.asAngle(); const double thisDist=radius()+0.5; const double botDist=(double)thisDist+5; // worth case scenario FOR CREATURES. const double humanDist=(double)thisDist+2; // worth case scenario FOR HUMANS. std::vector nonTraversableBots; std::vector nearbyEntities; { CAIEntityMatrix::CEntityIteratorLinear it; // get nearby bots for (it=getAIInstance()->botMatrix().beginEntities(CAIS::instance().bestLinearMatrixIteratorTbl((uint32) botDist),pos);!it.end();++it) { const CBot* bot = NLMISC::safe_cast(&*it); const CAIEntityPhysical* entityPhysical = bot->getSpawnObj(); if ( entityPhysical && entityPhysical->isAlive() && entityPhysical!=this) { // if it is a bot object with a significant radius // TODO: kxu: add a sheet param to recognize these bots that MUST not be crossed by other bots if ( bot->getSheet()->NotTraversable() && entityPhysical->radius() > 1.f && entityPhysical->walkSpeed() == 0.f && entityPhysical->runSpeed() == 0.f) { nonTraversableBots.push_back(entityPhysical); } else { nearbyEntities.push_back(entityPhysical); } } } // get nearby players for (it=getAIInstance()->playerMatrix().beginEntities(CAIS::instance().bestLinearMatrixIteratorTbl((uint32) humanDist),pos);!it.end();++it) { const CAIEntityPhysical* entityPhysical=(*it).getSpawnObj(); if ( entityPhysical && entityPhysical->isAlive() && entityPhysical!=this) { nearbyEntities.push_back(entityPhysical); } } } repulsion = calcRepulsionFrom(pos, nearbyEntities); // scale the repulsion // repulsion speed is 71% of movement speed repulsion.normalize(float( 710.0 * std::max(moveNorm, 0.025) )); if (nonTraversableBots.empty()) { return true; } CAIVector straightRepulsion; if (calcStraightRepulsionFrom(repulsion+pos, nonTraversableBots, straightRepulsion)) { repulsion += straightRepulsion; return true; } // did not find a repulsion without collision return false; } void CModEntityPhysical::setPos(const CAIPos &pos, const RYAI_MAP_CRUNCH::CWorldPosition &wpos) { // coherence test. #ifdef NL_DEBUG nlassert(RYAI_MAP_CRUNCH::CMapPosition(pos)==wpos); #endif _pos.setXY(pos); _wpos = wpos; // setWPos(wpos); coz no test. _pos.setH((sint32)(_wpos.getRootCell()?_wpos.getRootCell()->getMetricHeight(_wpos) : 0)); // if we're not linked to the world map then nothing to do CPersistentOfPhysical &persOfPhys=getPersistent(); if (persOfPhys.isLinkedToWorldMap()) { CAIInstance* aii = getAIInstance(); persOfPhys.linkEntityToMatrix(pos, aii->botMatrix()); } } ////////////////////////////////////////////////////////////////////////////// NLMISC_COMMAND(targeterChoiceAlgorithm,"Set algorithm used to select targeters","[list|]") { if(args.size()>1) return false; if(args.size()==1) { if (args[0]=="list") { log.displayNL("Possible algorithms are:"); CKnapsackSolver::Algorithm algorithms[] = { CKnapsackSolver::Optimal, // CKnapsackSolver::FullAddCheck, // CKnapsackSolver::AddCheck, CKnapsackSolver::FastAddCheck, // CKnapsackSolver::FullSingleReplace, // CKnapsackSolver::SingleReplace, CKnapsackSolver::FastSingleReplace, CKnapsackSolver::VeryFastSingleReplace }; size_t algorithmCount = sizeof(algorithms)/sizeof(algorithms[0]); for (size_t i=0; i::_TargeterChoiceAlgorithm = algorithm; } } log.displayNL("targeterChoiceAlgorithm is %s", CKnapsackSolver::toString(CTargetable::_TargeterChoiceAlgorithm).c_str()); return true; } ////////////////////////////////////////////////////////////////////////////// NLMISC_COMMAND(entityPlayerVisibilityDistance,"Set distance (0 to 255) at which entities consider they see a player","") { if(args.size()>1) return false; if(args.size()==1) { sint n; NLMISC::fromString(args[0], n); if (n>=0 && n<=255) CAIEntityPhysical::_PlayerVisibilityDistance = n; } log.displayNL("entityPlayerVisibilityDistance is %d",CAIEntityPhysical::_PlayerVisibilityDistance); return true; }