Resolve "Toggle Free Look with Hotkey"
[ryzomcore.git] / ryzom / server / src / ai_service / ai_entity_physical.cpp
blob5a6ea603d7ecaf676cfeead6a3266b28bf2350c7
1 // Ryzom - MMORPG Framework <http://dev.ryzom.com/projects/ryzom/>
2 // Copyright (C) 2010 Winch Gate Property Limited
3 //
4 // This program is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU Affero General Public License as
6 // published by the Free Software Foundation, either version 3 of the
7 // License, or (at your option) any later version.
8 //
9 // This program is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 // GNU Affero General Public License for more details.
14 // You should have received a copy of the GNU Affero General Public License
15 // along with this program. If not, see <http://www.gnu.org/licenses/>.
19 #include "stdpch.h"
20 #include "server_share/r2_variables.h"
21 #include "ai_mgr.h"
22 #include "ai_entity_physical.h"
23 #include "ai_entity_physical_inline.h"
24 #include "ai_instance.h"
27 // critical routine, get backward of weak technical design legacy.
29 const float repulsCoef=1.f/6.f; // arbitrary
31 int CAIEntityPhysical::_PlayerVisibilityDistance = 64;
32 bool CAIEntityPhysical::havePlayersAround() const
34 // todo Sadge: fixme! The correct fix is to handle the vision count mirror field in GPMS
35 // until then, ring shard swill do much more work than required moving NPCs about
36 if (IsRingShard)
38 #ifdef NL_DEBUG
39 nlwarning("FIXME: Quick hack to work round drunken NPC problem");
40 #endif
41 return true;
44 TYPE_VISION_COUNTER n = 255-currentVisionCounter();
45 ++AISStat::VisionCtr[n];
46 if (n==255)
47 return false;
49 return n <= _PlayerVisibilityDistance;
52 template <> CKnapsackSolver::Algorithm CTargetable<CAIEntityPhysical>::_TargeterChoiceAlgorithm = CKnapsackSolver::FastSingleReplace;
53 template <> float const CTargetable<CAIEntityPhysical>::_DefaultFightTargetersWeightMax = 6.f;
54 template <> float const CTargetable<CAIEntityPhysical>::_DefaultFightWeight = .98f;
55 template <> float const CTargetable<CAIEntityPhysical>::_DefaultFightValue = 1.f;
57 CAIEntityPhysicalLocator* CAIEntityPhysicalLocator::_Instance = NULL;
58 CAIEntityPhysicalLocator* CAIEntityPhysicalLocator::getInstance()
60 if (!_Instance)
61 _Instance = new CAIEntityPhysicalLocator();
62 return _Instance;
65 CAIEntityPhysical* CAIEntityPhysicalLocator::getEntity(TDataSetRow const& row) const
67 std::map<TDataSetRow, CAIEntityPhysical*>::const_iterator it = _EntitiesByRow.find(row);
68 if (it!=_EntitiesByRow.end())
69 return it->second;
70 else
71 return NULL;
74 CAIEntityPhysical* CAIEntityPhysicalLocator::getEntity(NLMISC::CEntityId const& id) const
76 std::map<NLMISC::CEntityId, CAIEntityPhysical*>::const_iterator it = _EntitiesById.find(id);
77 if (it!=_EntitiesById.end())
78 return it->second;
79 else
80 return NULL;
83 void CAIEntityPhysicalLocator::addEntity(TDataSetRow const& row, NLMISC::CEntityId const& id, CAIEntityPhysical* entity)
85 _EntitiesByRow.insert(std::make_pair(row, entity));
86 _EntitiesById.insert(std::make_pair(id, entity));
89 void CAIEntityPhysicalLocator::delEntity(TDataSetRow const& row, NLMISC::CEntityId const& id, CAIEntityPhysical* entity)
91 _EntitiesById.erase(id);
92 _EntitiesByRow.erase(row);
95 sint32 CAIEntityPhysical::getFameIndexed(uint32 factionIndex, bool modulated, bool returnUnknownValue) const
97 return CFameInterface::getInstance().getFameIndexed(getEntityId(), factionIndex, modulated, returnUnknownValue);
100 sint32 CAIEntityPhysical::getFame(std::string const& faction, bool modulated, bool returnUnknownValue) const
102 return CFameInterface::getInstance().getFame(getEntityId(), NLMISC::CStringMapper::map(faction), modulated, returnUnknownValue);
105 //////////////////////////////////////////////////////////////////////////////
107 CModEntityPhysical::CModEntityPhysical(CPersistentOfPhysical& owner, TDataSetRow const& entityIndex, NLMISC::CEntityId const& id, float radius, uint32 level, RYAI_MAP_CRUNCH::TAStarFlag const& AStarFlags)
108 : CAIEntityPhysical(owner, entityIndex, id, radius, level, AStarFlags)
109 , _Decalage(0, 0)
111 setMode (MBEHAV::NORMAL);
112 setBehaviour (MBEHAV::IDLE);
115 inline
116 CAIVector CModEntityPhysical::calcRepulsionFrom(const CAIVector& pos, const std::vector<const CAIEntityPhysical*>& entities) const
118 H_AUTO(CalcRepulsionFrom)
120 const double thisDist=radius()+0.5;
121 CAIVector repulse;
123 // collide with entities
124 FOREACHC(it, std::vector<const CAIEntityPhysical*>, entities)
126 const CAIEntityPhysical* entityPhysical = *it;
127 nlassert(entityPhysical != NULL);
129 CAIVector deltaPos=pos;
130 deltaPos -=entityPhysical->pos();
132 const double norm = deltaPos.quickNorm();
133 const double cmpDist = thisDist+entityPhysical->radius();
134 if (norm<cmpDist)
136 double coef;
137 if (norm<0.001f)
139 coef=1;
140 deltaPos = CAIVector(0.1f,0);
142 else
144 coef=(cmpDist-norm)/norm;
146 repulse += deltaPos*(coef*coef*repulsCoef);
149 return repulse;
152 inline
153 bool CModEntityPhysical::calcStraightRepulsionFrom(const CAIVector& pos, const std::vector<const CAIEntityPhysical*>& entities, CAIVector& repulsion) const
155 H_AUTO(CalcStraightRepulsionFrom)
157 // init the repulsion to (0,0)
158 repulsion.setXY(0.0, 0.0);
160 const CAIVector move = pos - this->pos();
161 const double moveNorm = move.quickNorm();
162 const double moveAngle = move.asAngle().asRadians();
163 const double orientation = theta().asRadians();
165 // collide with entities
166 FOREACHC(it, std::vector<const CAIEntityPhysical*>, entities)
168 const CAIEntityPhysical* entityPhysical = *it;
169 nlassert(entityPhysical != NULL);
171 CAIVector deltaPos = pos;
172 deltaPos -= entityPhysical->pos();
173 const double norm = deltaPos.quickNorm();
174 const double cmpDist = radius() + entityPhysical->radius() + 0.5;
176 // if the entity is colliding us
177 if (norm < cmpDist)
179 // first do a perpendicular repulsion
180 deltaPos.normalize(float( 1000.0 * (cmpDist - norm) ));
181 repulsion += deltaPos;
183 // then add an oriented repulsion
184 double speedFactor = 1.0;
185 double repulsionAngle = deltaPos.asAngle().asRadians();
186 double angle = computeShortestAngle( moveAngle, (-deltaPos).asAngle().asRadians() );
187 if (angle > -NLMISC::Pi/2 && angle < NLMISC::Pi/2)
189 const double angle1 = NLMISC::Pi/2 - angle;
190 const double angle2 = -NLMISC::Pi/2 - angle;
191 const double deviation1 = computeShortestAngle(orientation, repulsionAngle + angle1);
192 const double deviation2 = computeShortestAngle(orientation, repulsionAngle + angle2);
193 if (fabs(deviation1) < fabs(deviation2))
195 angle = angle1;
197 else
199 angle = angle2;
201 speedFactor = 1.0 - fabs(angle)/NLMISC::Pi;
202 repulsionAngle += angle;
203 deltaPos = CAngle(repulsionAngle).asVector2d() * norm;
205 deltaPos.normalize(float( 1000.0 * speedFactor * (cmpDist - norm) ));
206 repulsion += deltaPos;
210 // now check that the repulsed position does not collide any entity
211 const CAIVector repulsedPos = pos + repulsion;
212 FOREACHC(it, std::vector<const CAIEntityPhysical*>, entities)
214 const CAIEntityPhysical* entityPhysical = *it;
215 nlassert(entityPhysical != NULL);
217 CAIVector deltaPos = repulsedPos;
218 deltaPos -= entityPhysical->pos();
220 const double norm = deltaPos.quickNorm();
221 const double cmpDist = radius() + entityPhysical->radius();
222 if (norm < cmpDist)
224 return false;
228 // ok the repulsion is successful
229 return true;
232 CAIVector CModEntityPhysical::calcRepulsion(const CAIPos& pos) const
234 H_AUTO(CalcRepulsion)
236 const double thisDist=radius()+0.5;
237 const double botDist=(double)thisDist+5; // worth case scenario FOR BOTS.
238 const double humanDist=(double)thisDist+2; // worth case scenario FOR HUMANS.
240 std::vector<const CAIEntityPhysical*> nearbyEntities;
242 CAIEntityMatrix<CPersistentOfPhysical>::CEntityIteratorLinear it;
244 // get nearby bots
245 for (it=getAIInstance()->botMatrix().beginEntities(CAIS::instance().bestLinearMatrixIteratorTbl((uint32) botDist),pos);!it.end();++it)
247 const CAIEntityPhysical* entityPhysical=(*it).getSpawnObj();
248 if ( entityPhysical
249 && entityPhysical->isAlive()
250 && entityPhysical!=this)
252 nearbyEntities.push_back(entityPhysical);
256 // get nearby players
257 for (it=getAIInstance()->playerMatrix().beginEntities(CAIS::instance().bestLinearMatrixIteratorTbl((uint32) humanDist),pos);!it.end();++it)
259 const CAIEntityPhysical* entityPhysical=(*it).getSpawnObj();
260 if ( entityPhysical
261 && entityPhysical->isAlive()
262 && entityPhysical!=this)
264 nearbyEntities.push_back(entityPhysical);
269 return calcRepulsionFrom(pos, nearbyEntities);
272 bool CModEntityPhysical::calcStraightRepulsion(CAIPos const& pos, CAIVector& repulsion) const
274 H_AUTO(CalcStraightRepulsion)
276 const CAIVector move = pos - this->pos();
277 const double moveNorm = move.quickNorm();
278 const CAngle moveAngle = move.asAngle();
280 const double thisDist=radius()+0.5;
281 const double botDist=(double)thisDist+5; // worth case scenario FOR CREATURES.
282 const double humanDist=(double)thisDist+2; // worth case scenario FOR HUMANS.
284 std::vector<const CAIEntityPhysical*> nonTraversableBots;
285 std::vector<const CAIEntityPhysical*> nearbyEntities;
287 CAIEntityMatrix<CPersistentOfPhysical>::CEntityIteratorLinear it;
289 // get nearby bots
290 for (it=getAIInstance()->botMatrix().beginEntities(CAIS::instance().bestLinearMatrixIteratorTbl((uint32) botDist),pos);!it.end();++it)
292 const CBot* bot = NLMISC::safe_cast<const CBot*>(&*it);
293 const CAIEntityPhysical* entityPhysical = bot->getSpawnObj();
294 if ( entityPhysical
295 && entityPhysical->isAlive()
296 && entityPhysical!=this)
298 // if it is a bot object with a significant radius
299 // TODO: kxu: add a sheet param to recognize these bots that MUST not be crossed by other bots
300 if ( bot->getSheet()->NotTraversable()
301 && entityPhysical->radius() > 1.f
302 && entityPhysical->walkSpeed() == 0.f
303 && entityPhysical->runSpeed() == 0.f)
305 nonTraversableBots.push_back(entityPhysical);
307 else
309 nearbyEntities.push_back(entityPhysical);
314 // get nearby players
315 for (it=getAIInstance()->playerMatrix().beginEntities(CAIS::instance().bestLinearMatrixIteratorTbl((uint32) humanDist),pos);!it.end();++it)
317 const CAIEntityPhysical* entityPhysical=(*it).getSpawnObj();
318 if ( entityPhysical
319 && entityPhysical->isAlive()
320 && entityPhysical!=this)
322 nearbyEntities.push_back(entityPhysical);
327 repulsion = calcRepulsionFrom(pos, nearbyEntities);
328 // scale the repulsion
329 // repulsion speed is 71% of movement speed
330 repulsion.normalize(float( 710.0 * std::max(moveNorm, 0.025) ));
331 if (nonTraversableBots.empty())
333 return true;
336 CAIVector straightRepulsion;
337 if (calcStraightRepulsionFrom(repulsion+pos, nonTraversableBots, straightRepulsion))
339 repulsion += straightRepulsion;
340 return true;
343 // did not find a repulsion without collision
344 return false;
347 void CModEntityPhysical::setPos(const CAIPos &pos, const RYAI_MAP_CRUNCH::CWorldPosition &wpos)
349 // coherence test.
350 #ifdef NL_DEBUG
351 nlassert(RYAI_MAP_CRUNCH::CMapPosition(pos)==wpos);
352 #endif
354 _pos.setXY(pos);
355 _wpos = wpos; // setWPos(wpos); coz no test.
356 _pos.setH((sint32)(_wpos.getRootCell()?_wpos.getRootCell()->getMetricHeight(_wpos) : 0));
358 // if we're not linked to the world map then nothing to do
359 CPersistentOfPhysical &persOfPhys=getPersistent();
360 if (persOfPhys.isLinkedToWorldMap())
362 CAIInstance* aii = getAIInstance();
363 persOfPhys.linkEntityToMatrix(pos, aii->botMatrix());
367 //////////////////////////////////////////////////////////////////////////////
369 NLMISC_COMMAND(targeterChoiceAlgorithm,"Set algorithm used to select targeters","[list|<algorithm name>]")
371 if(args.size()>1)
372 return false;
374 if(args.size()==1)
376 if (args[0]=="list")
378 log.displayNL("Possible algorithms are:");
379 CKnapsackSolver::Algorithm algorithms[] = {
380 CKnapsackSolver::Optimal,
381 // CKnapsackSolver::FullAddCheck,
382 // CKnapsackSolver::AddCheck,
383 CKnapsackSolver::FastAddCheck,
384 // CKnapsackSolver::FullSingleReplace,
385 // CKnapsackSolver::SingleReplace,
386 CKnapsackSolver::FastSingleReplace,
387 CKnapsackSolver::VeryFastSingleReplace
389 size_t algorithmCount = sizeof(algorithms)/sizeof(algorithms[0]);
390 for (size_t i=0; i<algorithmCount; ++i)
391 log.displayNL(" - %s", CKnapsackSolver::toString(algorithms[i]).c_str());
393 else
395 CKnapsackSolver::Algorithm algorithm = CKnapsackSolver::fromString(args[0]);
396 if (algorithm!=CKnapsackSolver::UndefinedAlgorithm)
397 CTargetable<CAIEntityPhysical>::_TargeterChoiceAlgorithm = algorithm;
401 log.displayNL("targeterChoiceAlgorithm is %s", CKnapsackSolver::toString(CTargetable<CAIEntityPhysical>::_TargeterChoiceAlgorithm).c_str());
402 return true;
405 //////////////////////////////////////////////////////////////////////////////
407 NLMISC_COMMAND(entityPlayerVisibilityDistance,"Set distance (0 to 255) at which entities consider they see a player","")
409 if(args.size()>1)
410 return false;
412 if(args.size()==1)
414 sint n;
415 NLMISC::fromString(args[0], n);
416 if (n>=0 && n<=255)
417 CAIEntityPhysical::_PlayerVisibilityDistance = n;
420 log.displayNL("entityPlayerVisibilityDistance is %d",CAIEntityPhysical::_PlayerVisibilityDistance);
421 return true;