Resolve "Toggle Free Look with Hotkey"
[ryzomcore.git] / ryzom / server / src / ai_service / ai_bot.cpp
blobacce67cf313a3a63cc7a9ffb048811487784de57
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) 2019 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 "ai_bot.h"
22 #include "visual_properties_interface.h"
23 #include "server_share/msg_brick_service.h"
24 #include "server_share/r2_variables.h"
26 using namespace MULTI_LINE_FORMATER;
28 extern NLLIGO::CLigoConfig LigoConfig;
30 //////////////////////////////////////////////////////////////////////////////
31 // CSpawnBot //
32 //////////////////////////////////////////////////////////////////////////////
34 CSpawnBot::CSpawnBot(TDataSetRow const& entityIndex, CBot& owner, NLMISC::CEntityId const& id, float radius, uint32 level, RYAI_MAP_CRUNCH::TAStarFlag denyFlags)
35 : CModEntityPhysical(owner, entityIndex, id, radius, level, denyFlags)
36 , _SpawnGroup(owner.getOwner()->getSpawnObj())
37 , CProfileOwner()
38 , CDynSpawnBot(owner)
39 , _DamageSpeedCoef(1.f)
40 , _DamageCoef(1.f)
41 , _LastHealTick(0)
42 , _LastSelfHealTick(0)
43 , _SpeedFactor(1.f)
45 nlassert(owner.getOwner()->getSpawnObj());
46 spawnGrp().incSpawnedBot(getPersistent());
47 getPersistent().getSpawnCounter().inc();
48 setInstanceNumber(getAIInstance()->getInstanceNumber());
51 CSpawnBot::~CSpawnBot()
53 clearAggroList();
54 getPersistent().unlinkFromWorldMap();
55 spawnGrp().decSpawnedBot();
56 getPersistent().getSpawnCounter().dec();
59 CAIInstance* CSpawnBot::getAIInstance() const
61 return getPersistent().getAIInstance();
64 void CSpawnBot::setVisualPropertiesName()
66 CBot& botRef = CSpawnBot::getPersistent();
67 ucstring name = botRef.getName();
69 if (CVisualPropertiesInterface::UseIdForName)
71 name = NLMISC::toString("AI:%s", botRef.getIndexString().c_str());
74 if (name.empty() && CVisualPropertiesInterface::ForceNames)
76 name = NLMISC::CFile::getFilenameWithoutExtension(botRef.getSheet()->SheetId().toString().c_str());
79 if (!botRef.getCustomName().empty())
80 name = botRef.getCustomName();
82 // no name the bot will appear without name on the client.
83 if (name.empty())
84 return;
87 // In ringshard we use npc with fauna sheet but we want to be enable to change theire name
88 // Ulu : Ring is dead but i keep this code below to prevent side effects. Just add a condition : always set the custom name if exists
89 if (! botRef.getFaunaBotUseBotName() && botRef.getCustomName().empty()) //getFaunaBotUseBotName is false by default
91 if (botRef.getSheet()->ForceDisplayCreatureName())
92 return;
93 // the npc name is displayed as a fauna
96 CVisualPropertiesInterface::setName(dataSetRow(), name);
99 float CSpawnBot::fightWeight() const
101 if (getPersistent().isSheetValid())
103 AISHEETS::ICreatureCPtr sheet = getPersistent().getSheet();
104 if (!sheet->FightConfig(AISHEETS::FIGHTCFG_MELEE).isNULL())
106 // Melee fighters use a lot of space
107 return .98f;
109 else
111 if (!sheet->FightConfig(AISHEETS::FIGHTCFG_NUKE).isNULL())
112 return .01f;
113 if (!sheet->FightConfig(AISHEETS::FIGHTCFG_RANGE).isNULL())
114 return .01f;
115 // :FIXME: We consider healers are melee, coz they can't heal for the moment and use default attack.
116 if (!sheet->FightConfig(AISHEETS::FIGHTCFG_HEAL).isNULL())
117 return .98f;
118 // All others are like melee (default attack)
119 return .98f;
122 return this->CAIEntityPhysical::fightValue();
125 float CSpawnBot::fightValue() const
127 if (getPersistent().isSheetValid())
129 AISHEETS::ICreatureCPtr sheet = getPersistent().getSheet();
130 return (float)(sheet->XPLevel() * sheet->NbPlayers());
132 return this->CAIEntityPhysical::fightValue();
135 CSpawnGroup& CSpawnBot::spawnGrp() const
137 #if !FINAL_VERSION
138 nlassert(NLMISC::safe_cast<CSpawnGroup*>(getPersistent().getGroup().getSpawnObj()) == _SpawnGroup);
139 #endif
140 return *_SpawnGroup;
143 std::vector<std::string> CSpawnBot::getMultiLineInfoString() const
145 std::vector<std::string> container;
148 pushTitle(container, "CSpawnBot");
149 pushEntry(container, "dataSetRow=" + NLMISC::toString("%x", dataSetRow().counter()) + ":" + NLMISC::toString("%X", dataSetRow().getIndex()));
150 container.back() += " pos/rot=" + pos().toString();
151 if ((CAIEntityPhysical*)getTarget())
152 container.back() += NLMISC::toString(" target=%s", getTarget()->getEntityId().toString().c_str());
153 if (isBlinded())
154 container.back() += NLMISC::toString(" blinded");
155 if (isRooted())
156 container.back() += NLMISC::toString(" rooted");
157 if (isStuned())
158 container.back() += NLMISC::toString(" stuned");
159 if (isFeared())
160 container.back() += NLMISC::toString(" feared");
161 pushEntry(container, "outpost: ");
162 container.back() += " alias=" + LigoConfig.aliasToString(outpostAlias());
163 container.back() += " side=";
164 container.back() += outpostSide()?"attacker":"defender";
165 pushEntry(container, "haveAggro=" + NLMISC::toString(haveAggro()));
166 container.back() += " isReturning=" + NLMISC::toString(isReturning());
167 pushFooter(container);
170 return container;
173 NLMISC::CSmartPtr<CAIPlace const> CSpawnBot::buildFirstHitPlace(TDataSetRow const& aggroBot) const
175 NLMISC::CSmartPtr<CAIPlace const> thisPlace, groupPlace;
177 NLMISC::CSmartPtr<CAIPlaceFastXYR> place = NLMISC::CSmartPtr<CAIPlaceFastXYR>(new CAIPlaceFastXYR(NULL));
178 place->setPosAndRadius(AITYPES::vp_auto, pos(), (uint32)(getD1Radius()*1000.f));
179 thisPlace = place;
181 groupPlace = getPersistent().getOwner()->getSpawnObj()->buildFirstHitPlace(aggroBot);
182 if (groupPlace)
184 NLMISC::CSmartPtr<CAIPlaceIntersect> place = NLMISC::CSmartPtr<CAIPlaceIntersect>(new CAIPlaceIntersect(NULL));
185 place->setPlace1(thisPlace);
186 place->setPlace2(groupPlace);
187 thisPlace = place;
189 return thisPlace;
192 std::set<CBotAggroOwner*> CSpawnBot::getAggroGroup(bool primary) const
194 if (primary)
196 if (getPrimaryGroupAggroDist()>0.f)
197 return std::set<CBotAggroOwner*>(); /// @TODO Fill this
198 else
199 return std::set<CBotAggroOwner*>();
201 else
203 if (getSecondaryGroupAggroDist()>0.f)
204 return std::set<CBotAggroOwner*>(); /// @TODO Fill this
205 else
206 return std::set<CBotAggroOwner*>();
210 float CSpawnBot::getReturnDistCheck() const
212 if (getPersistent().isSheetValid() && getPersistent().getSheet()->AggroReturnDistCheck()>=0.f)
213 return getPersistent().getSheet()->AggroReturnDistCheck();
214 else
215 return CBotAggroOwner::getReturnDistCheck();
218 float CSpawnBot::getD1Radius() const
220 if (getPersistent().isSheetValid() && getPersistent().getSheet()->AggroRadiusD1()>=0.f)
221 return getPersistent().getSheet()->AggroRadiusD1();
222 else
223 return CBotAggroOwner::getD1Radius();
226 float CSpawnBot::getD2Radius() const
228 if (getPersistent().isSheetValid() && getPersistent().getSheet()->AggroRadiusD2()>=0.f)
229 return getPersistent().getSheet()->AggroRadiusD2();
230 else
231 return CBotAggroOwner::getD2Radius();
234 float CSpawnBot::getPrimaryGroupAggroDist() const
236 if (getPersistent().isSheetValid() && getPersistent().getSheet()->AggroPrimaryGroupDist()>=0.f)
237 return getPersistent().getSheet()->AggroPrimaryGroupDist();
238 else
239 return CBotAggroOwner::getPrimaryGroupAggroDist();
242 float CSpawnBot::getPrimaryGroupAggroCoef() const
244 if (getPersistent().isSheetValid() && getPersistent().getSheet()->AggroPrimaryGroupCoef()>=0.f)
245 return getPersistent().getSheet()->AggroPrimaryGroupCoef();
246 else
247 return CBotAggroOwner::getPrimaryGroupAggroCoef();
250 float CSpawnBot::getSecondaryGroupAggroDist() const
252 if (getPersistent().isSheetValid() && getPersistent().getSheet()->AggroSecondaryGroupDist()>=0.f)
253 return getPersistent().getSheet()->AggroSecondaryGroupDist();
254 else
255 return CBotAggroOwner::getSecondaryGroupAggroDist();
258 float CSpawnBot::getSecondaryGroupAggroCoef() const
260 if (getPersistent().isSheetValid() && getPersistent().getSheet()->AggroSecondaryGroupCoef()>=0.f)
261 return getPersistent().getSheet()->AggroSecondaryGroupCoef();
262 else
263 return CBotAggroOwner::getSecondaryGroupAggroCoef();
266 float CSpawnBot::getAggroPropagationRadius() const
268 if (getPersistent().isSheetValid() && getPersistent().getSheet()->AggroPropagationRadius()>=0.f)
269 return getPersistent().getSheet()->AggroPropagationRadius();
270 else
271 return CBotAggroOwner::getAggroPropagationRadius();
274 bool CSpawnBot::canHeal()
276 if (!getPersistent().getSheet()->FightConfig(AISHEETS::FIGHTCFG_HEAL)->_HasNormalAction)
277 return false;
278 if (_LastHealTick==0)
279 return true;
280 else
281 return (CTimeInterface::gameCycle() - _LastHealTick) > HealSpecificDowntime;
284 bool CSpawnBot::canSelfHeal()
286 if (!getPersistent().getSheet()->FightConfig(AISHEETS::FIGHTCFG_HEAL)->_HasSelfAction)
287 return false;
288 if (_LastSelfHealTick==0)
289 return true;
290 else
291 return (CTimeInterface::gameCycle() - _LastSelfHealTick) > HealSpecificDowntimeSelf;
294 void CSpawnBot::healTriggered()
296 _LastHealTick = CTimeInterface::gameCycle();
299 void CSpawnBot::selfHealTriggered()
301 _LastSelfHealTick = CTimeInterface::gameCycle();
304 void CSpawnBot::aggroLost(TDataSetRow const& aggroBot) const
306 sAggroLost(aggroBot, dataSetRow());
309 void CSpawnBot::aggroGain(TDataSetRow const& aggroBot) const
311 sAggroGain(aggroBot, dataSetRow());
314 bool CSpawnBot::getProp(size_t Id, uint32& value) const
316 TPropList::const_iterator it = _PropList.find(Id);
317 if (it==_PropList.end())
318 return false;
320 value = it->second;
321 return true;
324 void CSpawnBot::setProp(size_t Id, uint32 value)
326 _PropList[Id] = value;
329 CBot& CSpawnBot::getPersistent() const
331 return static_cast<CBot&>(CSpawnable<CPersistentOfPhysical>::getPersistent());
334 void CSpawnBot::setTheta(CAngle theta)
336 if (getPersistent().getSheet()->CanTurn())
337 CModEntityPhysical::setTheta(theta);
340 // :KLUDGE: This method change the sheet of the bot in the mirror. It appear
341 // that the client is totally unable to handle a sheet change during an entity
342 // lifetime. So this method should not be called.
343 /// @TODO Remove the method from spawn classes since its now longer called
344 // when sheet changes, and since spawn object is in fact destroyed and
345 // recreated.
346 void CSpawnBot::sheetChanged()
348 // CMirrors::initSheet(dataSetRow(), getPersistent().getSheet()->SheetId());
351 void CSpawnBot::sendInfoToEGS() const
353 if (!EGSHasMirrorReady)
354 return;
356 const uint32& maxHp = getPersistent().getCustomMaxHp();
357 if (maxHp > 0.f)
359 CChangeCreatureMaxHPMsg& msgList = CAIS::instance().getCreatureChangeMaxHP();
361 msgList.Entities.push_back(dataSetRow());
362 msgList.MaxHp.push_back((uint32)(maxHp));
363 msgList.SetFull.push_back((uint8)(1));
368 //////////////////////////////////////////////////////////////////////////////
369 // CBot //
370 //////////////////////////////////////////////////////////////////////////////
372 CBot::CBot(CGroup* owner, CAIAliasDescriptionNode* alias)
373 : CAliasChild<CGroup>(owner, alias)
374 , _VerticalPos(AITYPES::vp_auto)
375 , _Sheet(NULL)
376 , _ClientCSheet(NULL)
377 , _Stuck(false)
378 , _IgnoreOffensiveActions(false)
379 , _Healer(false)
380 , _SetSheetData(NULL)
381 , _Observers(NULL)
382 , _ProfileData(NULL)
383 , _CustomMaxHp(0)
387 CBot::CBot(CGroup* owner, uint32 alias, std::string const& name)
388 : CAliasChild<CGroup>(owner,alias, name)
389 , _VerticalPos(AITYPES::vp_auto)
390 , _Sheet(NULL)
391 , _ClientCSheet(NULL)
392 , _Stuck(false)
393 , _IgnoreOffensiveActions(false)
394 , _Healer(false)
395 , _SetSheetData(NULL)
396 , _Observers(NULL)
397 , _ProfileData(NULL)
398 , _CustomMaxHp(0.f)
402 CBot::~CBot()
404 // despawn before calling this destructor, because it may will generate a pure virtual call error!
405 #if !FINAL_VERSION
406 nlassert(!isSpawned());
407 #endif
409 if (_Observers != NULL)
411 notifyBotDespawn();
412 delete _Observers;
416 std::string CBot::getOneLineInfoString() const
418 return std::string("Bot '") + getName() + "'";
421 std::vector<std::string> CBot::getMultiLineInfoString() const
423 std::vector<std::string> container;
424 std::vector<std::string> strings;
427 pushTitle(container, "CBot");
428 pushEntry(container, "id=" + getIndexString());
429 container.back() += " eid=" + getEntityIdString();
430 container.back() += " alias=" + getAliasTreeOwner()->getAliasString() + " raw alias=" + NLMISC::toString(getAliasTreeOwner()->getAlias());
431 pushEntry(container, " name=" + getName());
432 if (isSheetValid())
433 container.back() += " sheet=" + NLMISC::CFile::getFilenameWithoutExtension(getSheet()->SheetId().toString());
434 pushEntry(container, "fullname=" + getFullName());
435 if (isSheetValid())
437 strings = getSheet()->getMultiLineInfoString();
438 FOREACHC(it, std::vector<std::string>, strings)
439 pushEntry(container, *it);
441 else
442 pushEntry(container, "<invalid sheet>");
443 if (isSpawned())
445 strings = getSpawnObj()->getMultiLineInfoString();
446 FOREACHC(it, std::vector<std::string>, strings)
447 pushEntry(container, *it);
449 else
450 pushEntry(container, "<not spawned>");
451 pushFooter(container);
454 return container;
457 std::string CBot::getIndexString() const
459 return getOwner()->getIndexString()+NLMISC::toString(":b%u", getChildIndex());
462 std::string CBot::getEntityIdString() const
464 if (isSpawned())
465 return getSpawnObj()->getEntityId().toString() ;
466 else
467 return NLMISC::CEntityId().toString();
470 CAIInstance* CBot::getAIInstance() const
472 return getOwner()->getAIInstance();
476 void CBot::addEnergy() const
478 getOwner()->getOwner()->getOwner()->addEnergy(botEnergyValue());
481 void CBot::removeEnergy() const
483 getOwner()->getOwner()->getOwner()->removeEnergy(botEnergyValue());
486 std::string CBot::getFullName() const
488 return std::string(getOwner()->getFullName() +":"+ getName());
491 NLMISC::CEntityId CBot::createEntityId() const
493 // motif is always 0 as we use automatic id assignement from mirror
494 uint64 motif = 0;
496 // fake, just waiting real pets implementation in mirror.
497 RYZOMID::TTypeId botType=getRyzomType();
498 if (botType==RYZOMID::pack_animal)
499 botType=RYZOMID::creature;
501 #ifndef NL_DEBUG
502 nlassert(botType!=RYZOMID::player);
503 #endif
504 return NLMISC::CEntityId(botType,motif);
507 bool CBot::isHealer() const
509 return _Healer && isSheetValid() && !getSheet()->FightConfig(AISHEETS::FIGHTCFG_HEAL).isNULL();
512 void CBot::initEnergy(float energyCoef)
514 if (isSheetValid())
515 setBotEnergyValue((uint32)((double)energyCoef*(double)getSheet()->EnergyValue()));
518 void CBot::serviceEvent(CServiceEvent const& info)
520 if (!isSpawned())
521 return;
523 CSpawnBot* spawnBot = getSpawnObj();
525 if (info.getEventType()==CServiceEvent::SERVICE_UP)
527 if (info.getServiceName()=="IOS")
529 spawnBot->setVisualPropertiesName();
531 else if (info.getServiceName()=="EGS")
533 spawnBot->sendInfoToEGS();
534 spawnBot->removeActionFlags(RYZOMACTIONFLAGS::Attacks); // clear Action Flags to avoid stuck problems with EGS.
535 spawnBot->stun()=0;
536 spawnBot->blind()=0;
537 spawnBot->root()=0;
542 CSpawnBot* CBot::getSpawnObj() const
544 return static_cast<CSpawnBot*>(CPersistentOfPhysical::getSpawnObj());
547 // :KLUDGE: This method is was created to avoid duplication of code. It
548 // results that it doesn't have a meaning without prior code to be executed
549 // (see sheetChanged and spawn). It's a trick that should be removed since
550 // there is code duplication anyway in sheetChanged method and its overrides.
551 /// @TODO Clean that mess
552 bool CBot::finalizeSpawn(RYAI_MAP_CRUNCH::CWorldPosition const& botWPos, CAngle const& spawnTheta, float botMeterSize)
554 // Create eid
555 NLMISC::CEntityId eid = createEntityId();
556 // Create row
557 TDataSetRow const row = CMirrors::createEntity(eid);
558 if (!row.isValid())
560 nlwarning("***> Not Enough Mirror Space for Type: %s", RYZOMID::toString(getRyzomType()).c_str());
561 return false;
564 if (_ClientSheet!=NLMISC::CSheetId::Unknown)
565 CMirrors::initSheet(row, _ClientSheet);
566 else
567 CMirrors::initSheet(row, getSheet()->SheetId());
569 // get the spawn of this persistent objet.
570 setSpawn(getSpawnBot(row, eid, botMeterSize));
572 CSpawnBot* spawnBot = getSpawnObj();
573 nlassert(spawnBot);
575 spawnBot->setVisualPropertiesName();
577 CAIPos botPos;
578 if (!isStuck() && !IsRingShard)
579 botPos = CAIPos(botWPos.toAIVector(),0,0);
580 else
581 botPos = CAIPos(lastTriedPos, 0, 0);
582 spawnBot->setPos(botPos, botWPos);
583 // Use base class method to avoid overload in
584 spawnBot->CModEntityPhysical::setTheta(spawnTheta);
586 this->initAdditionalMirrorValues(); // let derived class do its additional inits before declaring the entity
587 CMirrors::declareEntity(row);
588 linkToWorldMap(this, spawnBot->pos(), getAIInstance()->botMatrix());
590 return true;
593 bool CBot::spawn()
595 nlassert(!isSpawned());
596 nlassert(getChildIndex()!=-1); // significates that we spawn an unattached bot.
598 // Check we can spawn
599 if (isSpawned())
600 return true;
601 if (getSheet()->SheetId() == NLMISC::CSheetId::Unknown)
603 nlwarning("Bot '%s'%s: invalid sheet id", getAliasFullName().c_str(), getAliasString().c_str());
604 return false;
606 if (!getSpawnCounter().remainToMax())
607 return false;
609 // Get initial state
610 RYAI_MAP_CRUNCH::CWorldPosition botWPos;
611 CAngle spawnTheta;
612 getSpawnPos(lastTriedPos, botWPos, CWorldContainer::getWorldMap(), spawnTheta);
613 float botMeterSize = getSheet()->Scale()*getSheet()->Radius();
615 // Check the initial position is valid
616 if (!botWPos.isValid() && !isStuck())
618 nlwarning("Bot '%s'%s: invalid spawn pos and not stuck", getAliasFullName().c_str(), getAliasString().c_str());
619 return false;
622 // Finalize spawn object creation
623 return finalizeSpawn(botWPos, spawnTheta, botMeterSize);
626 void CBot::despawnBot()
628 #if !FINAL_VERSION
629 nlassert(isSpawned());
630 #endif
631 if (!isSpawned())
632 return;
634 CMirrors::removeEntity(getSpawnObj()->getEntityId());
635 setSpawn(NULL); // automatic smart pointer deletion
636 notifyBotDespawn();
639 bool CBot::reSpawn(bool sendMessage)
641 return spawn();
644 /// @KLUDGE This method is overridden in npc and fauna classes in a strange
645 /// way. The initial code is copied'n'pasted, and the finalizeSpawn method is
646 /// called along with a more specific one. This may confuse future coders and
647 /// be hard to maintain.
648 /// @TODO Clean that mess
649 void CBot::sheetChanged()
651 if (getSpawnObj())
653 // Get bot state
654 RYAI_MAP_CRUNCH::CWorldPosition botWPos = getSpawnObj()->wpos();
655 CAngle spawnTheta = getSpawnObj()->theta();
656 float botMeterSize = getSheet()->Scale()*getSheet()->Radius();
657 // :TODO: Save profile info
659 // If stuck bot position may be outside collision and must be recomputed
660 if (isStuck() || IsRingShard)
661 getSpawnPos(lastTriedPos, botWPos, CWorldContainer::getWorldMap(), spawnTheta);
663 // Delete old bot
664 CMirrors::removeEntity(getSpawnObj()->getEntityId());
665 setSpawn(NULL); // automatic smart pointer deletion
666 notifyBotDespawn();
668 // Finalize spawn object creation
669 finalizeSpawn(botWPos, spawnTheta, botMeterSize);
673 class CSetSheetTimerEvent
674 : public CTimerEvent
676 NLMISC::CSmartPtr<CBot> _Bot;
677 uint32 _Step;
678 public:
679 CSetSheetTimerEvent(CBot* bot, uint32 step) : _Bot(bot), _Step(step) { }
680 virtual void timerCallback(CTimer* owner)
682 if (!_Bot.isNull())
683 _Bot->setSheetDelayed(_Step);
687 void CBot::setSheet(AISHEETS::ICreatureCPtr const& sheet)
689 _Sheet = sheet;
690 sheetChanged();
693 void CBot::setClientSheet(const std::string & clientSheetName)
695 // Message warning is print if clientSheetName is not in sheet id
696 if (!clientSheetName.empty())
698 if (!_ClientSheet.buildSheetId(clientSheetName))
700 nlwarning("Invalid CLIENT_SHEET %s", clientSheetName.c_str());
701 return;
704 AISHEETS::ICreatureCPtr sheet = AISHEETS::CSheets::getInstance()->lookup(NLMISC::CSheetId(clientSheetName));
706 if (!sheet || sheet->SheetId() == NLMISC::CSheetId::Unknown)
708 nlwarning("Unknown sheet %s", clientSheetName.c_str());
709 return;
712 _ClientCSheet = sheet;
714 sheetChanged();
718 // 1: Stop the bot
719 void CBot::triggerSetSheet(AISHEETS::ICreatureCPtr const& sheet)
721 if (_SetSheetData)
723 nlwarning("Another sheet change is going, this one is canceled");
724 return;
727 NLMISC::CSheetId sheetId= (BotRepopFx.get().empty()? NLMISC::CSheetId::Unknown: NLMISC::CSheetId(BotRepopFx.get()));
728 if (getSpawnObj() && sheetId!=NLMISC::CSheetId::Unknown)
730 _SetSheetData = new CSetSheetData();
731 _SetSheetData->_FxSheetId = sheetId;
732 _SetSheetData->_SheetToSet = sheet;
733 // Timer 1 is to let time for the bot to stop moving
734 _SetSheetTimer.setRemaining(12, new CSetSheetTimerEvent(this, 0));
736 getSpawnObj()->setSpeedFactor(0.f);
738 else
740 setSheet(sheet);
744 // 2: Trigger Fx
745 void CBot::setSheetDelayed0()
747 nlassert(_SetSheetData);
748 // Timer 2 is to let time for the fx to hide the bot despawn
749 _SetSheetTimer.setRemaining(8, new CSetSheetTimerEvent(this, 1));
752 _SetSheetData->_Fx = CFxEntityManager::getInstance()->create(getSpawnObj()->pos(), _SetSheetData->_FxSheetId);
753 if (!_SetSheetData->_Fx->spawn())
755 nlwarning("Unable to spawn fx entity (mirror range full?)");
756 CFxEntityManager::getInstance()->destroy(_SetSheetData->_Fx);
757 _SetSheetData->_Fx = NULL;
761 // 3: Change the sheet (depop/repop)
762 void CBot::setSheetDelayed1()
764 nlassert(_SetSheetData);
765 // Timer 3 is to let time for the fx to hide the bot spawn
766 _SetSheetTimer.setRemaining(15, new CSetSheetTimerEvent(this, 2));
768 setSheet(_SetSheetData->_SheetToSet);
769 _SetSheetData->_SheetToSet = NULL;
772 // 4: Stop the Fx
773 void CBot::setSheetDelayed2()
775 nlassert(_SetSheetData);
776 if (!_SetSheetData->_Fx.isNull())
778 _SetSheetData->_Fx->despawn();
779 CFxEntityManager::getInstance()->destroy(_SetSheetData->_Fx);
781 delete _SetSheetData;
782 _SetSheetData = NULL;
785 void CBot::setSheetDelayed(uint32 step)
787 switch (step)
789 case 0: setSheetDelayed0(); break;
790 case 1: setSheetDelayed1(); break;
791 case 2: setSheetDelayed2(); break;
792 default: nlerror("setSheetDelayed called with an invalid step number");
796 void CBot::attachObserver(IObserver* obs)
798 if (_Observers == NULL)
800 _Observers = new std::vector<IObserver*>;
801 _Observers->push_back(obs);
802 return;
805 std::vector<IObserver*>::const_iterator it = std::find(_Observers->begin(), _Observers->end(), obs);
806 if (it == _Observers->end())
807 _Observers->push_back(obs);
810 void CBot::detachObserver(IObserver* obs)
812 if (_Observers == NULL)
813 return;
815 std::vector<IObserver*>::iterator it = std::find(_Observers->begin(), _Observers->end(), obs);
816 if (it != _Observers->end())
818 *it = _Observers->back();
819 _Observers->pop_back();
823 void CBot::notifyBotDespawn()
825 if (_Observers == NULL)
826 return;
828 FOREACH(it, std::vector<IObserver*>, (*_Observers))
829 (*it)->notifyBotDespawn(this);
832 void CBot::notifyBotDeath()
834 if (_Observers == NULL)
835 return;
837 FOREACH(it, std::vector<IObserver*>, (*_Observers))
838 (*it)->notifyBotDeath(this);
841 void CBot::notifyStopNpcControl()
843 if (_Observers == NULL)
844 return;
846 FOREACH(it, std::vector<IObserver*>, (*_Observers))
847 (*it)->notifyStopNpcControl(this);