Resolve "Toggle Free Look with Hotkey"
[ryzomcore.git] / ryzom / server / src / ai_service / ai_grp_npc.cpp
blob88e1c22d993426584884bfc478d174fd620028b3
1 // Ryzom - MMORPG Framework <http://dev.ryzom.com/projects/ryzom/>
2 // Copyright (C) 2010 Winch Gate Property Limited
3 //
4 // This source file has been modified by the following contributors:
5 // Copyright (C) 2014-2020 Jan BOON (Kaetemi) <jan.boon@kaetemi.be>
6 //
7 // This program is free software: you can redistribute it and/or modify
8 // it under the terms of the GNU Affero General Public License as
9 // published by the Free Software Foundation, either version 3 of the
10 // License, or (at your option) any later version.
12 // This program is distributed in the hope that it will be useful,
13 // but WITHOUT ANY WARRANTY; without even the implied warranty of
14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 // GNU Affero General Public License for more details.
17 // You should have received a copy of the GNU Affero General Public License
18 // along with this program. If not, see <http://www.gnu.org/licenses/>.
20 #include "stdpch.h"
21 #include "server_share/r2_variables.h"
22 #include "messages.h"
23 #include "ai_grp_npc.h"
24 #include "ai_mgr_npc.h"
25 #include "ai_bot_npc.h"
26 #include "game_share/base_types.h"
27 #include "npc_description_msg.h"
28 #include "ai_player.h"
29 #include "ai_profile_npc.h"
30 #include "server_share/r2_variables.h"
32 extern bool simulateBug(int bugId);
34 #include "dyn_grp_inline.h"
36 using namespace MULTI_LINE_FORMATER;
38 using namespace NLMISC;
39 using namespace NLNET;
40 using namespace std;
41 using namespace AITYPES;
43 extern CVariable<uint32> DefaultNpcAggroDist;
44 extern CVariable<float> DefaultEscortRange;
46 static bool VerboseLog=false;
47 #define LOG if (!VerboseLog) {} else nlinfo
49 // 5 minute in ticks (10 Hz)
50 uint32 DEFAULT_RESPAWN_DELAY = 5*60*10;
52 //////////////////////////////////////////////////////////////////////////////
53 // CSpawnGroupNpc //
54 //////////////////////////////////////////////////////////////////////////////
56 uint32 CSpawnGroupNpc::_SlowUpdatePeriod = 10;
57 vector<uint32> CSpawnGroupNpc::_SlowUpdateBuckets(_SlowUpdatePeriod);
59 CSpawnGroupNpc::CSpawnGroupNpc(CPersistent<CSpawnGroup>& owner)
60 : CSpawnGroup(owner)
61 , _GroupInVision(false)
63 sint32 const randomVal = (sint32)CTimeInterface::gameCycle()-CAIS::rand32(20);
64 _LastUpdate = (randomVal>=0)?randomVal:CTimeInterface::gameCycle();
65 _Cell = 0;
66 _LastBotUpdate = CTimeInterface::gameCycle();
67 activityProfile().setAIProfile(new CGrpProfileNormal(this));
68 _BotUpdateTimer.set((CAIS::rand32(40)+((intptr_t)this>>2))%20); // start with a random value.
69 resetSlowUpdateCycle();
70 _DespawnBotsWhenNoMoreHandleTimerActive = false;
73 void CSpawnGroupNpc::setSlowUpdatePeriod(uint32 ticks)
75 // Check args
76 nlassert(ticks>0);
77 if (ticks==0) return;
78 // Save ticks number
79 _SlowUpdatePeriod = ticks;
80 // Reset cycles buckets
81 _SlowUpdateBuckets.clear();
82 _SlowUpdateBuckets.resize(_SlowUpdatePeriod);
83 // For each group
84 FOREACH (itInstance, CCont<CAIInstance>, CAIS::instance().AIList())
86 FOREACH (itManager, CCont<CManager>, itInstance->managers())
88 FOREACH (itGroup, CCont<CGroup>, itManager->groups())
90 // Find the spawn
91 CGroup* group = *itGroup;
92 CSpawnGroup* spawnGroup = group->getSpawnObj();
93 CSpawnGroupNpc* spawnGroupNpc = dynamic_cast<CSpawnGroupNpc*>(spawnGroup);
94 if (spawnGroupNpc!=NULL)
95 spawnGroupNpc->resetSlowUpdateCycle();
101 uint32 CSpawnGroupNpc::getSlowUpdatePeriod()
103 return _SlowUpdatePeriod;
106 void CSpawnGroupNpc::resetSlowUpdateCycle()
108 nlassert(_SlowUpdateBuckets.size() == _SlowUpdatePeriod);
109 // Find the lowest bucket
110 vector<uint32>::iterator it = std::min_element(_SlowUpdateBuckets.begin(), _SlowUpdateBuckets.end());
111 // Assign it to the group
112 _SlowUpdateCycle = (uint32)(it - _SlowUpdateBuckets.begin());
113 // Fill the bucket with the group
114 *it += bots().size();
117 void CSpawnGroupNpc::displaySlowUpdateBuckets()
119 std::vector<uint32>::iterator it, end=_SlowUpdateBuckets.end();
120 for (it = _SlowUpdateBuckets.begin(); it!=end; ++it)
122 nlinfo("Bucket %d: %d", it-_SlowUpdateBuckets.begin(), *it);
126 void CSpawnGroupNpc::noMoreHandle(uint32 nNbTickBeforeDespawn)
128 _DespawnBotsWhenNoMoreHandleTimerActive = true;
129 _DespawnBotsWhenNoMoreHandleTimer.set(nNbTickBeforeDespawn);
132 void CSpawnGroupNpc::handlePresent()
134 _DespawnBotsWhenNoMoreHandleTimerActive = false;
137 void CSpawnGroupNpc::sendInfoToEGS() const
139 FOREACHC(itBot, CCont<CBot>, bots())
141 CBot const* bot = *itBot;
142 if (!bot->isSpawned())
143 continue;
144 // :NOTE: As it can be called during CSpawnBotNpc dtor rely on dynamic cast to verify object is valid
145 CSpawnBotNpc const* spawn = dynamic_cast<CSpawnBotNpc const*>(bot->getSpawnObj());
146 if (spawn)
147 spawn->sendInfoToEGS();
151 void CSpawnGroupNpc::update()
153 H_AUTO(GrpNpcUpdate);
155 ++AISStat::GrpTotalUpdCtr;
156 ++AISStat::GrpNpcUpdCtr;
158 uint32 const Dt = CTimeInterface::gameCycle()-_LastUpdate;
159 bool const inFight = activityProfile().getAIProfileType()==FIGHT_NORMAL;
161 uint32 updateTrigger; // in tick
163 // use the lowest update time available
164 if (inFight)
166 updateTrigger = 0;
168 else if ( getPersistent().isRingGrp() )
170 updateTrigger = 4;
172 else if (_GroupInVision)
174 updateTrigger = 10;
176 else
178 updateTrigger = 30;
181 bool const haveToUpdateGroupBehaviour = (Dt>=updateTrigger); // every second.
183 if (haveToUpdateGroupBehaviour)
185 H_AUTO(GrpNpcUpdateBehaviour);
186 // record the tick at which we ran this update (for future refference)
187 _LastUpdate = CTimeInterface::gameCycle();
189 checkDespawn();
190 checkRespawn();
191 getPersistent().updateStateInstance();
194 // bot update()s --------------------------------------------------
196 if (haveToUpdateGroupBehaviour)
198 H_AUTO(GrpNpcUpdateBots);
199 _GroupInVision = false;
201 FOREACH(first,CCont<CBot>, bots())
203 CSpawnBotNpc const* const bot = static_cast<CBotNpc*>(*first)->getSpawn();
204 if (!bot)
205 continue;
206 if (simulateBug(3))
208 if (bot->isAlive())
209 continue;
211 else // Normal behaviour
213 if (!bot->isAlive())
214 continue;
216 if (!bot->havePlayersAround())
217 continue;
219 _GroupInVision = true;
220 break;
224 // TODO : hack : remove this when the "can't reach and turn arround" debility is corrected
225 bool fastUpdate = _GroupInVision || inFight;
226 bool slowUpdate = (CTimeInterface::gameCycle()%_SlowUpdatePeriod)==_SlowUpdateCycle;
228 if (fastUpdate || slowUpdate)
230 uint32 const botDt = CTimeInterface::gameCycle()-_LastBotUpdate;
231 _LastBotUpdate = CTimeInterface::gameCycle();
233 FOREACH(first, CCont<CBot>, bots())
235 CSpawnBotNpc* const bot = static_cast<CBotNpc*>(*first)->getSpawn();
236 if (bot && bot->isAlive())
238 H_AUTO(GrpNpcUpdateBotsUpdate);
239 bot->update(botDt);
242 _BotUpdateTimer.set(10);
246 if (haveToUpdateGroupBehaviour)
248 H_AUTO(GrpNpcUpdateGrpBehaviour);
249 if (!activityProfile().getAISpawnProfile().isNull()) // Check if we have a behaviour.
250 activityProfile().updateProfile(Dt); // If so, then update it !
251 // this->CBotAggroOwner::update(Dt);
254 if (_DespawnBotsWhenNoMoreHandleTimerActive)
256 if (_DespawnBotsWhenNoMoreHandleTimer.test())
258 _DespawnBotsWhenNoMoreHandleTimerActive = false;
259 despawnBots(true);
264 void CSpawnGroupNpc::stateChange(CAIState const* oldState, CAIState const* newState)
266 // Find changing group profiles.
268 setProfileParameters(getPersistent().profileParameters());
270 IAIProfileFactory* moveProfile = newState->moveProfile();
271 IAIProfileFactory* actProfile = newState->activityProfile();
273 mergeProfileParameters(newState->profileParameters());
275 FOREACHC(it, CCont<CAIStateProfile>, newState->profiles())
277 if (!it->testCompatibility(getPersistent()))
278 continue;
280 if (it->moveProfile()!= RYAI_GET_FACTORY(CGrpProfileNoChangeFactory))
281 moveProfile = it->moveProfile();
283 if (it->activityProfile()!=RYAI_GET_FACTORY(CGrpProfileNoChangeFactory))
284 actProfile = it->activityProfile();
286 mergeProfileParameters(it->profileParameters());
287 break;
290 breakable
292 if (oldState)
294 if (!newState->isPositional() && oldState->isPositional())
296 // begin of punctual state
297 // need to backup the current profiles
298 _PunctualHoldActivityProfile = activityProfile();
299 _PunctualHoldMovingProfile = movingProfile();
301 activityProfile() = CProfilePtr();
302 movingProfile() = CProfilePtr();
303 break;
306 if (newState->isPositional() && !oldState->isPositional())
308 // end of punctual state
309 // need to restore the backuped profile
310 activityProfile() = _PunctualHoldActivityProfile;
311 movingProfile() = _PunctualHoldMovingProfile;
313 _PunctualHoldActivityProfile = CProfilePtr();
314 _PunctualHoldMovingProfile = CProfilePtr();
316 // resume the profiles
317 activityProfile().getAISpawnProfile()->resumeProfile();
318 movingProfile().getAISpawnProfile()->resumeProfile();
319 break;
323 // normal behavior, transition from positionnal to positionnal state
324 if (moveProfile!=RYAI_GET_FACTORY(CGrpProfileNoChangeFactory))
325 setMoveProfileFromStateMachine(moveProfile);
327 if (actProfile!=RYAI_GET_FACTORY(CGrpProfileNoChangeFactory))
328 setActivityProfileFromStateMachine(actProfile);
330 break;
334 // iterate through bots changing their chat.
335 for (CCont<CBot>::iterator it=bots().begin(), itEnd=bots().end();it!=itEnd;++it)
337 CBotNpc *const botNpc=static_cast<CBotNpc *>(*it);
338 if (!botNpc->isSpawned())
339 continue;
340 botNpc->getSpawn()->updateChat(newState);
344 void CSpawnGroupNpc::spawnBots(const std::string &name)
346 ucstring ucName;
347 ucName.fromUtf8(name);
349 FOREACH(itBot, CCont<CBot>, bots())
351 CBot* bot = *itBot;
352 if (!bot->isSpawned()) {
353 bot->spawn();
355 if (!ucName.empty())
357 CSpawnBot *spawnBot = bot->getSpawnObj();
358 if (spawnBot)
360 TDataSetRow row = spawnBot->dataSetRow();
361 NLNET::CMessage msgout("CHARACTER_NAME");
362 msgout.serial(row);
363 msgout.serial(ucName);
364 sendMessageViaMirror("IOS", msgout);
365 spawnBot->getPersistent().setCustomName(ucName);
369 if (_Cell < 0) {
370 CEntityId id = bot->getSpawnObj()->getEntityId();
371 sint32 x = bot->getSpawnObj()->pos().x();
372 sint32 y = bot->getSpawnObj()->pos().y();
373 sint32 z = bot->getSpawnObj()->pos().h();
374 float t = bot->getSpawnObj()->pos().theta().asRadians();
375 uint8 cont = 0;
376 uint8 slide = 1;
377 NLMISC::TGameCycle tick = CTickEventHandler::getGameCycle() + 1;
378 CMessage msgout2("ENTITY_TELEPORTATION");
379 msgout2.serial( id );
380 msgout2.serial( x );
381 msgout2.serial( y );
382 msgout2.serial( z );
383 msgout2.serial( t );
384 msgout2.serial( tick );
385 msgout2.serial( cont );
386 msgout2.serial( _Cell );
387 msgout2.serial( slide );
389 sendMessageViaMirror("GPMS", msgout2);
395 void CSpawnGroupNpc::spawnBots()
397 FOREACH(itBot, CCont<CBot>, bots())
399 CBot* bot = *itBot;
400 if (!bot->isSpawned()) {
401 bot->spawn();
403 if (_Cell < 0) {
404 CEntityId id = bot->getSpawnObj()->getEntityId();
405 sint32 x = bot->getSpawnObj()->pos().x();
406 sint32 y = bot->getSpawnObj()->pos().y();
407 sint32 z = bot->getSpawnObj()->pos().h();
408 float t = bot->getSpawnObj()->pos().theta().asRadians();
409 uint8 cont = 0;
410 uint8 slide = 1;
411 NLMISC::TGameCycle tick = CTickEventHandler::getGameCycle() + 1;
412 CMessage msgout2("ENTITY_TELEPORTATION");
413 msgout2.serial( id );
414 msgout2.serial( x );
415 msgout2.serial( y );
416 msgout2.serial( z );
417 msgout2.serial( t );
418 msgout2.serial( tick );
419 msgout2.serial( cont );
420 msgout2.serial( _Cell );
421 msgout2.serial( slide );
423 sendMessageViaMirror("GPMS", msgout2);
429 void CSpawnGroupNpc::despawnBots(bool immediately)
431 FOREACH(itBot, CCont<CBot>, bots())
433 CBot* const bot = *itBot;
434 if (!bot->isSpawned())
435 continue;
437 if (TheDataset.getOnlineTimestamp( bot->getSpawnObj()->dataSetRow()) >= CTickEventHandler::getGameCycle())
438 nlwarning("Bots %s:%s spawn/despawn in the same tick ! despawn ignored", getPersistent().getName().c_str(), bot->getName().c_str());
439 else
440 bot->despawnBot();
442 if (getPersistent()._AutoDestroy)
443 getPersistent().getOwner()->groups().removeChildByIndex(getPersistent().getChildIndex());
446 CGroupNpc& CSpawnGroupNpc::getPersistent() const
448 return static_cast<CGroupNpc&>(CSpawnGroup::getPersistent());
451 //////////////////////////////////////////////////////////////////////////////
452 // CGroupNpc //
453 //////////////////////////////////////////////////////////////////////////////
455 CGroupNpc::CGroupNpc(CMgrNpc* mgr, CAIAliasDescriptionNode* aliasTree, RYAI_MAP_CRUNCH::TAStarFlag denyFlags)
456 : CGroup(mgr, denyFlags, aliasTree)
457 , CDynGrpBase()
458 , CPersistentStateInstance(*mgr->getStateMachine())
460 _BotsAreNamed = true;
462 _PlayerAttackable = false;
463 _BotAttackable = false;
464 _AggroDist = 0;
465 _RingGrp = false;
468 CGroupNpc::CGroupNpc(CMgrNpc* mgr, uint32 alias, std::string const& name, RYAI_MAP_CRUNCH::TAStarFlag denyFlags)
469 : CGroup(mgr, denyFlags, alias, name)
470 , CDynGrpBase()
471 , CPersistentStateInstance(*mgr->getStateMachine())
473 _BotsAreNamed = true;
475 _PlayerAttackable = false;
476 _BotAttackable = false;
477 _AggroDist = 0;
480 CGroupNpc::~CGroupNpc()
482 // avoid re-deletion by despawn
483 _AutoDestroy = false;
484 if (isSpawned()) // to avoid bad CDbgPtr link interpretation
485 despawnGrp();
487 // clear all persistent state instance
488 while (!_PSIChilds.empty())
490 _PSIChilds.back()->setParentStateInstance(NULL);
493 _PSIChilds.clear();
496 std::string CGroupNpc::getOneLineInfoString() const
498 return std::string("NPC Group '") + getName() + "'";
501 std::vector<std::string> CGroupNpc::getMultiLineInfoString() const
503 std::vector<std::string> container;
504 std::vector<std::string> strings;
506 pushTitle(container, "CGroupNpc");
507 strings = CGroup::getMultiLineInfoString();
508 FOREACHC(itString, std::vector<std::string>, strings)
509 pushEntry(container, *itString);
510 pushEntry(container, "faction=" + (faction().empty()?string("<none>"):faction().toString()));
511 pushEntry(container, "friends=" + (friendFaction().empty()?string("<none>"):friendFaction().toString()));
512 pushEntry(container, "ennemies=" + (ennemyFaction().empty()?string("<none>"):ennemyFaction().toString()));
513 pushEntry(container, NLMISC::toString("attackable: player=%s bot=%s", _PlayerAttackable?"yes":"no", _BotAttackable?"yes":"no"));
514 pushEntry(container, "state=" + (getState()?getState()->getName():string("<null>")));
515 pushFooter(container);
517 return container;
520 CMgrNpc& CGroupNpc::mgr() const
522 return *static_cast<CMgrNpc*>(getOwner());
525 void CGroupNpc::updateDependencies(CAIAliasDescriptionNode const& aliasTree, CAliasTreeOwner* aliasTreeOwner)
527 switch(aliasTree.getType())
529 case AITypeEvent:
531 CAIEventReaction* const eventPtr = NLMISC::safe_cast<CAIEventReaction*>(mgr().getStateMachine()->eventReactions().getChildByAlias(aliasTree.getAlias()));
532 nlassert(eventPtr);
533 if (!eventPtr)
534 break;
535 eventPtr->setType(CAIEventReaction::FixedGroup);
536 eventPtr->setGroup(getAlias());
537 break;
542 IAliasCont* CGroupNpc::getAliasCont(TAIType type)
544 switch(type)
546 case AITypeBot:
547 return &_Bots;
549 case AITypeFolder: // Nothing ..
550 default:
551 return NULL;
555 CAliasTreeOwner* CGroupNpc::createChild(IAliasCont* cont, CAIAliasDescriptionNode* aliasTree)
557 CAliasTreeOwner* child = NULL;
559 switch (aliasTree->getType())
561 case AITypeOutpostBuilding:
562 case AITypeBot:
563 child = new CBotNpc(this, aliasTree);
564 break;
566 case AITypeFolder:
567 default:
568 break;
571 if (child!=NULL)
572 cont->addAliasChild(child);
573 return (child);
576 CSmartPtr<CSpawnGroup> CGroupNpc::createSpawnGroup()
578 return new CSpawnGroupNpc(*this);
581 void CGroupNpc::serviceEvent (const CServiceEvent &info)
583 CGroup::serviceEvent(info);
585 // If the EGS crash
586 if ( (info.getServiceName() == "EGS") && (info.getEventType() == CServiceEvent::SERVICE_DOWN) )
588 // If the Group of NPC has handles on itself
589 if (!_Handles.empty())
591 // Remove all handles
592 _Handles.clear();
593 // and despawn bots
594 if (getSpawnObj() != NULL)
596 if (getSpawnObj()->isGroupAlive()) // is there some bots spawned
598 getSpawnObj()->noMoreHandle(_DespawnTimeWhenNoMoreHandle);
604 if ((info.getServiceName() == "EGS") && (info.getEventType() == CServiceEvent::SERVICE_UP))
606 processStateEvent(getEventContainer().EventEGSUp);
612 bool CGroupNpc::spawn ()
614 if (CGroup::spawn())
616 setStartState(getStartState()); // stateInstance.
618 // inform the EGS of our existence - simulate connection of EGS
619 serviceEvent (CServiceEvent(NLNET::TServiceId(0),std::string("EGS"),CServiceEvent::SERVICE_UP));
621 if (isAutoSpawn())
623 CCont<CBot >::iterator first(bots().begin()), last(bots().end());
624 for (; first != last; ++first)
626 CBotNpc *bot = static_cast<CBotNpc*>(*first);
627 // ok, we can spawn this bot
628 bot->spawn();
631 return true;
633 return false;
636 void CGroupNpc::despawnGrp()
638 CGroup::despawnGrp();
641 void CGroupNpc::clearParameters()
643 _PlayerAttackable = false;
644 _BotAttackable = false;
645 _RingGrp = false;
646 _AggroDist = DefaultNpcAggroDist;
647 setEscortTeamId(CTEAM::InvalidTeamId);
648 setEscortRange(DefaultEscortRange);
649 respawnTime() = (uint32) DEFAULT_RESPAWN_DELAY;
650 despawnTime() = (uint32) DEFAULT_RESPAWN_DELAY / 4;
653 // Parse a paremeter for this group.
654 void CGroupNpc::addParameter(std::string const& parameter)
656 static std::string ATTACKABLE("attackable");
657 static std::string PLAYER_ATTACKABLE("player_attackable");
658 static std::string BOT_ATTACKABLE("bot_attackable");
659 static std::string BADGUY("bad guy");
660 static std::string AGGRO_RANGE("aggro range");
661 static std::string ESCORT_RANGE("escort range");
662 static std::string RESPAWN_TIME("respawn time");
663 static std::string DESPAWN_TIME("despawn time");
664 static std::string RING("ring");
665 static std::string DENIED_ASTAR_FLAGS("denied_astar_flags");
667 std::string key, tail;
669 // force lowercase
670 std::string p = NLMISC::toLowerAscii(parameter);
671 AI_SHARE::stringToKeywordAndTail(p, key, tail);
673 breakable
675 if (key == RING)
677 _RingGrp = true;
678 break;
680 if (key == BOT_ATTACKABLE)
682 _BotAttackable = true;
683 break;
686 if (key == ATTACKABLE || key == PLAYER_ATTACKABLE)
688 // the bots are attackable !
689 _PlayerAttackable = true;
690 if (!IsRingShard) // In Ring shard, BotAttackable means attackable by bot not vulnerable
692 // attackable implie vulnerable!
693 _BotAttackable = true;
695 break;
698 if (key == BADGUY)
700 // the bots are bad guys! they will attack players in their aggro range.
701 if (!tail.empty())
703 NLMISC::fromString(tail, _AggroDist);
704 // bad guy imply attackable!
705 _PlayerAttackable = true;
706 // bad guy imply vulnerable!
707 _BotAttackable = true;
709 else
711 nlwarning("GrpNpc::addParameter : in parameter '%s', missing agrodist !", parameter.c_str());
713 break;
716 if (key == AGGRO_RANGE)
718 if (!tail.empty())
720 NLMISC::fromString(tail, _AggroDist);
723 else
725 nlwarning("GrpNpc::addParameter : in parameter '%s', missing agrodist !", parameter.c_str());
727 break;
730 if (key == ESCORT_RANGE)
732 if (!tail.empty())
734 float range = float(atof(tail.c_str()));
735 LOG("Setting range to %f", range);
736 setEscortRange(range);
738 else
740 nlwarning("GrpNpc::addParameter : in parameter '%s', missing escort range !", parameter.c_str());
742 break;
745 if (key == RESPAWN_TIME)
747 if (!tail.empty())
749 uint32 respawntime = NLMISC::atoui(tail.c_str());
750 // TODO:kxu: fix CAITimer!
751 if (respawntime > 0x7fffffff)
752 respawntime = 0x7fffffff; // AI timers do not treat properly delta times greater than the max signed int
753 LOG("Setting respawn time to %u", respawntime);
754 respawnTime()=respawntime;
756 else
758 nlwarning("GrpNpc::addParameter : in parameter '%s', missing respawn time !", parameter.c_str());
760 break;
763 if (key == DESPAWN_TIME)
765 if (!tail.empty())
767 uint32 despawntime = NLMISC::atoui(tail.c_str());
768 // TODO:kxu: fix CAITimer!
769 if (despawntime > 0x7fffffff)
770 despawntime = 0x7fffffff; // AI timers do not treat properly delta times greater than the max signed int
771 LOG("Setting despawn time to %u", despawntime);
772 despawnTime()=despawntime;
774 else
776 nlwarning("GrpNpc::addParameter : in parameter '%s', missing despawn time !", parameter.c_str());
778 break;
781 if (key == DENIED_ASTAR_FLAGS)
783 if (!tail.empty())
785 RYAI_MAP_CRUNCH::TAStarFlag flags = RYAI_MAP_CRUNCH::Nothing;
786 vector<string> sFlags;
787 NLMISC::splitString(tail, "|", sFlags);
788 FOREACHC(it, vector<string>, sFlags)
790 flags = RYAI_MAP_CRUNCH::TAStarFlag( flags | RYAI_MAP_CRUNCH::toAStarFlag(*it) );
793 LOG("Setting denied AStar flags to (%s) = %u", tail.c_str(), flags);
794 _DenyFlags = flags;
796 else
798 nlwarning("GrpNpc::addParameter : in parameter '%s', missing denied AStar flags !", parameter.c_str());
800 break;
803 if (parameter.empty())
804 break;
806 nlwarning("CAIBotNpc::addParameter unknown parameter '%s'", parameter.c_str());
810 void CGroupNpc::addHpUpTrigger(float threshold, int eventId)
812 _hpUpTriggers.insert(std::make_pair(threshold, eventId));
815 void CGroupNpc::delHpUpTrigger(float threshold, int eventId)
817 CGroupNpc::THpTriggerList& hpTriggers = _hpUpTriggers;
818 CGroupNpc::THpTriggerList::iterator first, last, trigger;
819 first = hpTriggers.lower_bound(threshold);
820 last = hpTriggers.upper_bound(threshold);
822 for (; first!=last; ++first)
824 if (first->second==eventId)
825 break;
827 if (first!=last)
828 hpTriggers.erase(first);
831 void CGroupNpc::addHpDownTrigger(float threshold, int eventId)
833 _hpDownTriggers.insert(std::make_pair(threshold, eventId));
836 void CGroupNpc::delHpDownTrigger(float threshold, int eventId)
838 CGroupNpc::THpTriggerList& hpTriggers = _hpDownTriggers;
839 CGroupNpc::THpTriggerList::iterator first, last, trigger;
840 first = hpTriggers.lower_bound(threshold);
841 last = hpTriggers.upper_bound(threshold);
843 for (; first!=last; ++first)
845 if (first->second==eventId)
846 break;
848 if (first!=last)
849 hpTriggers.erase(first);
852 void CGroupNpc::addHpUpTrigger(float threshold, std::string cbFunc)
854 _hpUpTriggers2.insert(std::make_pair(threshold, cbFunc));
857 void CGroupNpc::delHpUpTrigger(float threshold, std::string cbFunc)
859 CGroupNpc::THpTriggerList2& hpTriggers = _hpUpTriggers2;
860 CGroupNpc::THpTriggerList2::iterator first, last, trigger;
861 first = hpTriggers.lower_bound(threshold);
862 last = hpTriggers.upper_bound(threshold);
864 for (; first!=last; ++first)
866 if (first->second==cbFunc)
867 break;
869 if (first!=last)
870 hpTriggers.erase(first);
873 void CGroupNpc::addHpDownTrigger(float threshold, std::string cbFunc)
875 _hpDownTriggers2.insert(std::make_pair(threshold, cbFunc));
878 void CGroupNpc::delHpDownTrigger(float threshold, std::string cbFunc)
880 CGroupNpc::THpTriggerList2& hpTriggers = _hpDownTriggers2;
881 CGroupNpc::THpTriggerList2::iterator first, last, trigger;
882 first = hpTriggers.lower_bound(threshold);
883 last = hpTriggers.upper_bound(threshold);
885 for (; first!=last; ++first)
887 if (first->second==cbFunc)
888 break;
890 if (first!=last)
891 hpTriggers.erase(first);
894 bool CGroupNpc::haveHpTriggers()
896 return (_hpUpTriggers.size()+_hpDownTriggers.size()+_hpUpTriggers2.size()+_hpDownTriggers2.size())>0;
899 void CGroupNpc::hpTriggerCb(float oldVal, float newVal)
901 if (newVal>oldVal)
903 CGroupNpc::THpTriggerList::const_iterator first, last, trigger, triggerProcessed;
904 first = _hpUpTriggers.upper_bound(oldVal);
905 last = _hpUpTriggers.upper_bound(newVal);
906 for (trigger=first; trigger!=last;)
908 triggerProcessed = trigger;
909 ++trigger;
910 processStateEvent(getEventContainer().EventUserEvent[triggerProcessed->second]);
912 CGroupNpc::THpTriggerList2::const_iterator first2, last2, trigger2, triggerProcessed2;
913 first2 = _hpUpTriggers2.upper_bound(oldVal);
914 last2 = _hpUpTriggers2.upper_bound(newVal);
915 for (trigger2=first2; trigger2!=last2;)
917 triggerProcessed2 = trigger2;
918 ++trigger2;
919 callScriptCallBack(this, NLMISC::CStringMapper::map(triggerProcessed2->second));
922 if (newVal<oldVal)
924 CGroupNpc::THpTriggerList::const_iterator first, last, trigger, triggerProcessed;
925 first = _hpDownTriggers.lower_bound(newVal);
926 last = _hpDownTriggers.lower_bound(oldVal);
927 for (trigger=first; trigger!=last;)
929 triggerProcessed = trigger;
930 ++trigger;
931 processStateEvent(getEventContainer().EventUserEvent[triggerProcessed->second]);
933 CGroupNpc::THpTriggerList2::const_iterator first2, last2, trigger2, triggerProcessed2;
934 first2 = _hpDownTriggers2.lower_bound(newVal);
935 last2 = _hpDownTriggers2.lower_bound(oldVal);
936 for (trigger2=first2; trigger2!=last2;)
938 triggerProcessed2 = trigger2;
939 ++trigger2;
940 callScriptCallBack(this, NLMISC::CStringMapper::map(triggerProcessed2->second));
945 void CGroupNpc::addNamedEntityListener(std::string const& name, std::string const& prop, int event)
947 _namedEntityListeners.insert(std::make_pair(std::make_pair(name, prop), event));
948 CNamedEntityManager::getInstance()->get(name).addListenerGroup(prop, this);
951 void CGroupNpc::delNamedEntityListener(std::string const& name, std::string const& prop, int event)
953 TNamedEntityListenerList::iterator first, last, listener;
954 listener = _namedEntityListeners.lower_bound(std::make_pair(name, prop));
955 last = _namedEntityListeners.upper_bound(std::make_pair(name, prop));
956 while (listener!=last)
958 if (listener->second==event)
960 _namedEntityListeners.erase(listener);
961 CNamedEntityManager::getInstance()->get(name).delListenerGroup(prop, this);
962 break;
964 ++listener;
968 void CGroupNpc::addNamedEntityListener(std::string const& name, std::string const& prop, std::string functionName)
970 _namedEntityListeners2.insert(std::make_pair(std::make_pair(name, prop), functionName));
971 CNamedEntityManager::getInstance()->get(name).addListenerGroup(prop, this);
974 void CGroupNpc::delNamedEntityListener(std::string const& name, std::string const& prop, std::string functionName)
976 TNamedEntityListenerList2::iterator first, last, listener;
977 listener = _namedEntityListeners2.lower_bound(std::make_pair(name, prop));
978 last = _namedEntityListeners2.upper_bound(std::make_pair(name, prop));
979 while (listener!=last)
981 if (listener->second==functionName)
983 _namedEntityListeners2.erase(listener);
984 CNamedEntityManager::getInstance()->get(name).delListenerGroup(prop, this);
985 break;
987 ++listener;
991 void CGroupNpc::namedEntityListenerCb(std::string const& name, std::string const& prop)
993 TNamedEntityListenerList::iterator first, last, listener;
994 first = _namedEntityListeners.lower_bound(std::make_pair(name, prop));
995 last = _namedEntityListeners.upper_bound(std::make_pair(name, prop));
996 for (listener=first; listener!=last; ++listener)
998 processStateEvent(getEventContainer().EventUserEvent[listener->second]);
1000 TNamedEntityListenerList2::iterator first2, last2, listener2;
1001 first2 = _namedEntityListeners2.lower_bound(std::make_pair(name, prop));
1002 last2 = _namedEntityListeners2.upper_bound(std::make_pair(name, prop));
1003 std::queue<NLMISC::TStringId> listeners;
1004 for (listener2=first2; listener2!=last2; ++listener2)
1005 listeners.push(NLMISC::CStringMapper::map(listener2->second));
1006 while(!listeners.empty())
1008 callScriptCallBack(this, listeners.front());
1009 listeners.pop();
1013 //--------------------------------------------------------------------
1014 // addHandle
1015 //--------------------------------------------------------------------
1016 void CGroupNpc::addHandle(TDataSetRow playerRowId, uint32 missionAlias, uint32 DespawnTimeInTick)
1018 // If the group is not spawned -> spawn it
1020 // Save some states when adding first handle
1021 if (_Handles.empty())
1023 _AutoSpawnWhenNoMoreHandle = isAutoSpawn();
1026 setAutoSpawn(true);
1027 // There is always a spawned object for group
1028 if (getSpawnObj() != NULL)
1030 if (! getSpawnObj()->isGroupAlive() ) // if no bots spawned
1032 getSpawnObj()->spawnBots();
1034 getSpawnObj()->handlePresent();
1036 // Register the handle
1037 SHandle h;
1038 h.MissionAlias = missionAlias;
1039 h.PlayerRowId = playerRowId;
1041 set<SHandle>::const_iterator it = _Handles.find(h);
1043 if (it != _Handles.end())
1045 nlwarning("CGroupNpc::addHandle handle already added player:%d missionAlias:%d", playerRowId.getIndex(), missionAlias);
1047 else
1049 _Handles.insert(h);
1052 _DespawnTimeWhenNoMoreHandle = DespawnTimeInTick;
1054 CHandledAIGroupSpawnedMsg msg;
1055 msg.PlayerRowId = playerRowId;
1056 msg.MissionAlias = missionAlias;
1057 msg.GroupAlias = getAlias();
1058 msg.send("EGS");
1061 //--------------------------------------------------------------------
1062 // delHandle
1063 //--------------------------------------------------------------------
1064 void CGroupNpc::delHandle(TDataSetRow playerRowId, uint32 missionAlias)
1066 // Unregister the handle
1067 SHandle h;
1068 h.MissionAlias = missionAlias;
1069 h.PlayerRowId = playerRowId;
1071 set<SHandle>::iterator it = _Handles.find(h);
1072 if (it != _Handles.end())
1074 _Handles.erase(it);
1076 else
1078 // With the new queue_* generation system multiple handle_release can be done -> do not flood with this message
1079 // nlwarning("CGroupNpc::delHandle handle not found player:%d missionAlias:%d", playerRowId.getIndex(), missionAlias);
1082 // If no more handle despawn the group and restore saved variables
1083 if (_Handles.empty())
1085 if (getSpawnObj() != NULL)
1087 if (getSpawnObj()->isGroupAlive()) // is there some bots spawned
1089 getSpawnObj()->noMoreHandle(_DespawnTimeWhenNoMoreHandle);
1092 setAutoSpawn(_AutoSpawnWhenNoMoreHandle);
1095 CHandledAIGroupDespawnedMsg msg;
1096 msg.PlayerRowId = playerRowId;
1097 msg.MissionAlias = missionAlias;
1098 msg.GroupAlias = getAlias();
1099 msg.send("EGS");
1102 //--------------------------------------------------------------------
1103 // the default display()
1104 //--------------------------------------------------------------------
1105 std::string CSpawnGroupNpc::buildDebugString(uint idx) const
1107 if (idx == 0)
1108 return "No debug info";
1109 return std::string();
1112 std::string CGroupNpc::buildDebugString(uint idx) const
1115 switch(idx)
1117 case 0: return "-- CGroupNpc -----------------------------";
1118 case 1: {
1119 std::string s;
1120 if (!isSpawned())
1122 s += "id=" + getIndexString();
1123 s += " alias=" + getAliasString();
1124 s += " info=<not spawned>";
1125 s += " name=" + getName();
1127 else
1129 s += "id=" + getIndexString();
1130 s += " alias="+getAliasString();
1131 s += " info='" + getSpawnObj()->buildDebugString(0) + "'";
1132 s += " name="+getName();
1134 return s;
1136 case 2:
1137 return NLMISC::toString(": %s :", getAliasNode()->getName().c_str()) + buidStateInstanceDebugString();
1138 case 3:
1139 return NLMISC::toString(" User Timers: %s %s %s %s",
1140 userTimer(0).toString().c_str(),
1141 userTimer(1).toString().c_str(),
1142 userTimer(2).toString().c_str(),
1143 userTimer(3).toString().c_str());
1144 case 4:
1145 if (getEscortTeamId() != CTEAM::InvalidTeamId)
1146 return NLMISC::toString(" Escorted by team %u", getEscortTeamId());
1147 else
1148 return std::string(" Not escorted");
1149 case 5: return "----------------------------------------";
1151 return std::string();
1154 void CGroupNpc::display(CStringWriter &stringWriter) const
1156 #ifdef NL_DEBUG
1157 nlstopex(("not implemented"));
1158 #endif
1161 CAIS::CCounter& CGroupNpc::getSpawnCounter()
1163 return CAIS::instance()._NpcBotCounter;
1166 void CGroupNpc::lastBotDespawned()
1168 // send message
1169 processStateEvent(getEventContainer().EventLastBotDespawned);
1172 void CGroupNpc::firstBotSpawned()
1174 setFirstBotSpawned();
1177 void CGroupNpc::setStateAlias(const std::string &state, uint32 alias)
1179 TStatesToAlias::iterator it=_StatesToAlias.find(state);
1180 if (it != _StatesToAlias.end())
1181 it->second = alias;
1182 else
1183 _StatesToAlias.insert(make_pair(state, alias));
1186 uint32 CGroupNpc::getStateAlias(const std::string &state)
1188 TStatesToAlias::iterator it=_StatesToAlias.find(state);
1189 if (it != _StatesToAlias.end())
1190 return it->second;
1191 return 0;
1194 void CGroupNpc::setStateEventAlias(const std::string &stateEvent, uint32 alias)
1196 TStatesToAlias::iterator it=_StateEventToAlias.find(stateEvent);
1197 if (it != _StateEventToAlias.end())
1198 it->second = alias;
1199 else
1200 _StateEventToAlias.insert(make_pair(stateEvent, alias));
1203 uint32 CGroupNpc::getStateEventAlias(const std::string &state)
1205 TStatesToAlias::iterator it=_StateEventToAlias.find(state);
1206 if (it != _StateEventToAlias.end())
1207 return it->second;
1208 return 0;
1212 void CGroupNpc::setColour(uint8 colour)
1214 FOREACH(itBot, CCont<CBot>, bots())
1216 CBotNpc* bot = static_cast<CBotNpc*>(*itBot);
1217 if (bot)
1218 bot->setColour(colour);
1222 void CGroupNpc::setOutpostSide(OUTPOSTENUMS::TPVPSide side)
1224 FOREACH(itBot, CCont<CBot>, bots())
1226 CBotNpc* bot = static_cast<CBotNpc*>(*itBot);
1227 if (bot)
1228 bot->setOutpostSide(side);
1232 void CGroupNpc::setOutpostFactions(const std::string &alias, OUTPOSTENUMS::TPVPSide side)
1234 // Attack only the declared ennemies of the outpost
1235 if (side == OUTPOSTENUMS::OutpostOwner)
1237 // Bots factions
1238 faction ().addProperty(NLMISC::toString("outpost:%s:bot_defender", alias.c_str()));
1239 friendFaction().addProperty(NLMISC::toString("outpost:%s:bot_defender", alias.c_str()));
1240 ennemyFaction().addProperty(NLMISC::toString("outpost:%s:bot_attacker", alias.c_str()));
1241 // Players faction
1242 ennemyFaction().addProperty(NLMISC::toString("outpost:%s:attacker", alias.c_str()));
1244 if (side == OUTPOSTENUMS::OutpostAttacker)
1246 // Bots factions
1247 faction ().addProperty(NLMISC::toString("outpost:%s:bot_attacker", alias.c_str()));
1248 friendFaction().addProperty(NLMISC::toString("outpost:%s:bot_attacker", alias.c_str()));
1249 ennemyFaction().addProperty(NLMISC::toString("outpost:%s:bot_defender", alias.c_str()));
1250 // Players faction
1251 ennemyFaction().addProperty(NLMISC::toString("outpost:%s:defender", alias.c_str()));
1255 void CGroupNpc::setFactionAttackableAbove(std::string faction, sint32 threshold, bool botAttackable)
1257 if (botAttackable)
1258 _FactionAttackableAbove.insert(std::make_pair(faction, threshold));
1259 else
1260 _FactionAttackableAbove.erase(std::make_pair(faction, threshold));
1263 void CGroupNpc::setFactionAttackableBelow(std::string faction, sint32 threshold, bool botAttackable)
1265 if (botAttackable)
1266 _FactionAttackableBelow.insert(std::make_pair(faction, threshold));
1267 else
1268 _FactionAttackableBelow.erase(std::make_pair(faction, threshold));
1271 bool CGroupNpc::isFactionAttackable(std::string faction, sint32 fame)
1273 TFactionAttackableSet::const_iterator it, itEnd;
1274 for (it=_FactionAttackableAbove.begin(), itEnd=_FactionAttackableAbove.end(); it!=itEnd; ++it)
1275 if (faction==it->first && fame>it->second)
1276 return true;
1277 for (it=_FactionAttackableBelow.begin(), itEnd=_FactionAttackableBelow.end(); it!=itEnd; ++it)
1278 if (faction==it->first && fame<it->second)
1279 return true;
1280 return false;
1283 void CGroupNpc::stateChange(CAIState const* oldState, CAIState const* newState)
1285 if (isSpawned())
1286 getSpawnObj()->stateChange(oldState, newState);
1289 void CGroupNpc::setEvent(uint eventId)
1291 nlassert(eventId<10);
1292 if (eventId >= 10) return;
1293 processStateEvent(getEventContainer().EventUserEvent[eventId]);
1296 //////////////////////////////////////////////////////////////////////////////
1297 // Commands //
1298 //////////////////////////////////////////////////////////////////////////////
1300 //---------------------------------------------------------------------------------------
1301 // Control over verbose nature of logging
1302 //---------------------------------------------------------------------------------------
1304 NLMISC_COMMAND(verboseNPCGrp,"Turn on or off or check the state of verbose npc group logging","")
1306 if(args.size()>1)
1307 return false;
1309 if(args.size()==1)
1310 StrToBool (VerboseLog, args[0]);
1312 nlinfo("VerboseLogging is %s",VerboseLog?"ON":"OFF");
1313 return true;
1316 NLMISC_COMMAND(NpcGroupSlowUpdatePeriod, "Slow update period of the NPC groups","[<period_ticks>]")
1318 if (args.size()==0 || args.size()==1)
1320 if (args.size()==1)
1322 uint32 ticks;
1323 NLMISC::fromString(args[0], ticks);
1324 if (ticks>0)
1325 CSpawnGroupNpc::setSlowUpdatePeriod(ticks);
1326 else
1327 return false;
1329 log.displayNL("Slow update period of the NPC groups is %d ticks", CSpawnGroupNpc::getSlowUpdatePeriod());
1330 return true;
1332 return false;
1335 #include "event_reaction_include.h"