1 // Ryzom - MMORPG Framework <http://dev.ryzom.com/projects/ryzom/>
2 // Copyright (C) 2010 Winch Gate Property Limited
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.
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/>.
20 #include "server_share/r2_variables.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
39 nlwarning("FIXME: Quick hack to work round drunken NPC problem");
44 TYPE_VISION_COUNTER n
= 255-currentVisionCounter();
45 ++AISStat::VisionCtr
[n
];
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()
61 _Instance
= new CAIEntityPhysicalLocator();
65 CAIEntityPhysical
* CAIEntityPhysicalLocator::getEntity(TDataSetRow
const& row
) const
67 std::map
<TDataSetRow
, CAIEntityPhysical
*>::const_iterator it
= _EntitiesByRow
.find(row
);
68 if (it
!=_EntitiesByRow
.end())
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())
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
)
111 setMode (MBEHAV::NORMAL
);
112 setBehaviour (MBEHAV::IDLE
);
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;
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();
140 deltaPos
= CAIVector(0.1f
,0);
144 coef
=(cmpDist
-norm
)/norm
;
146 repulse
+= deltaPos
*(coef
*coef
*repulsCoef
);
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
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
))
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();
228 // ok the repulsion is successful
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
;
245 for (it
=getAIInstance()->botMatrix().beginEntities(CAIS::instance().bestLinearMatrixIteratorTbl((uint32
) botDist
),pos
);!it
.end();++it
)
247 const CAIEntityPhysical
* entityPhysical
=(*it
).getSpawnObj();
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();
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
;
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();
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
);
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();
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())
336 CAIVector straightRepulsion
;
337 if (calcStraightRepulsionFrom(repulsion
+pos
, nonTraversableBots
, straightRepulsion
))
339 repulsion
+= straightRepulsion
;
343 // did not find a repulsion without collision
347 void CModEntityPhysical::setPos(const CAIPos
&pos
, const RYAI_MAP_CRUNCH::CWorldPosition
&wpos
)
351 nlassert(RYAI_MAP_CRUNCH::CMapPosition(pos
)==wpos
);
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>]")
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());
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());
405 //////////////////////////////////////////////////////////////////////////////
407 NLMISC_COMMAND(entityPlayerVisibilityDistance
,"Set distance (0 to 255) at which entities consider they see a player","")
415 NLMISC::fromString(args
[0], n
);
417 CAIEntityPhysical::_PlayerVisibilityDistance
= n
;
420 log
.displayNL("entityPlayerVisibilityDistance is %d",CAIEntityPhysical::_PlayerVisibilityDistance
);