1 // Ryzom - MMORPG Framework <http://dev.ryzom.com/projects/ryzom/>
2 // Copyright (C) 2010 Winch Gate Property Limited
4 // This source file has been modified by the following contributors:
5 // Copyright (C) 2019 Jan BOON (Kaetemi) <jan.boon@kaetemi.be>
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/>.
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 //////////////////////////////////////////////////////////////////////////////
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())
39 , _DamageSpeedCoef(1.f
)
42 , _LastSelfHealTick(0)
45 nlassert(owner
.getOwner()->getSpawnObj());
46 spawnGrp().incSpawnedBot(getPersistent());
47 getPersistent().getSpawnCounter().inc();
48 setInstanceNumber(getAIInstance()->getInstanceNumber());
51 CSpawnBot::~CSpawnBot()
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.
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())
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
111 if (!sheet
->FightConfig(AISHEETS::FIGHTCFG_NUKE
).isNULL())
113 if (!sheet
->FightConfig(AISHEETS::FIGHTCFG_RANGE
).isNULL())
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())
118 // All others are like melee (default attack)
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
138 nlassert(NLMISC::safe_cast
<CSpawnGroup
*>(getPersistent().getGroup().getSpawnObj()) == _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());
154 container
.back() += NLMISC::toString(" blinded");
156 container
.back() += NLMISC::toString(" rooted");
158 container
.back() += NLMISC::toString(" stuned");
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
);
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
));
181 groupPlace
= getPersistent().getOwner()->getSpawnObj()->buildFirstHitPlace(aggroBot
);
184 NLMISC::CSmartPtr
<CAIPlaceIntersect
> place
= NLMISC::CSmartPtr
<CAIPlaceIntersect
>(new CAIPlaceIntersect(NULL
));
185 place
->setPlace1(thisPlace
);
186 place
->setPlace2(groupPlace
);
192 std::set
<CBotAggroOwner
*> CSpawnBot::getAggroGroup(bool primary
) const
196 if (getPrimaryGroupAggroDist()>0.f
)
197 return std::set
<CBotAggroOwner
*>(); /// @TODO Fill this
199 return std::set
<CBotAggroOwner
*>();
203 if (getSecondaryGroupAggroDist()>0.f
)
204 return std::set
<CBotAggroOwner
*>(); /// @TODO Fill this
206 return std::set
<CBotAggroOwner
*>();
210 float CSpawnBot::getReturnDistCheck() const
212 if (getPersistent().isSheetValid() && getPersistent().getSheet()->AggroReturnDistCheck()>=0.f
)
213 return getPersistent().getSheet()->AggroReturnDistCheck();
215 return CBotAggroOwner::getReturnDistCheck();
218 float CSpawnBot::getD1Radius() const
220 if (getPersistent().isSheetValid() && getPersistent().getSheet()->AggroRadiusD1()>=0.f
)
221 return getPersistent().getSheet()->AggroRadiusD1();
223 return CBotAggroOwner::getD1Radius();
226 float CSpawnBot::getD2Radius() const
228 if (getPersistent().isSheetValid() && getPersistent().getSheet()->AggroRadiusD2()>=0.f
)
229 return getPersistent().getSheet()->AggroRadiusD2();
231 return CBotAggroOwner::getD2Radius();
234 float CSpawnBot::getPrimaryGroupAggroDist() const
236 if (getPersistent().isSheetValid() && getPersistent().getSheet()->AggroPrimaryGroupDist()>=0.f
)
237 return getPersistent().getSheet()->AggroPrimaryGroupDist();
239 return CBotAggroOwner::getPrimaryGroupAggroDist();
242 float CSpawnBot::getPrimaryGroupAggroCoef() const
244 if (getPersistent().isSheetValid() && getPersistent().getSheet()->AggroPrimaryGroupCoef()>=0.f
)
245 return getPersistent().getSheet()->AggroPrimaryGroupCoef();
247 return CBotAggroOwner::getPrimaryGroupAggroCoef();
250 float CSpawnBot::getSecondaryGroupAggroDist() const
252 if (getPersistent().isSheetValid() && getPersistent().getSheet()->AggroSecondaryGroupDist()>=0.f
)
253 return getPersistent().getSheet()->AggroSecondaryGroupDist();
255 return CBotAggroOwner::getSecondaryGroupAggroDist();
258 float CSpawnBot::getSecondaryGroupAggroCoef() const
260 if (getPersistent().isSheetValid() && getPersistent().getSheet()->AggroSecondaryGroupCoef()>=0.f
)
261 return getPersistent().getSheet()->AggroSecondaryGroupCoef();
263 return CBotAggroOwner::getSecondaryGroupAggroCoef();
266 float CSpawnBot::getAggroPropagationRadius() const
268 if (getPersistent().isSheetValid() && getPersistent().getSheet()->AggroPropagationRadius()>=0.f
)
269 return getPersistent().getSheet()->AggroPropagationRadius();
271 return CBotAggroOwner::getAggroPropagationRadius();
274 bool CSpawnBot::canHeal()
276 if (!getPersistent().getSheet()->FightConfig(AISHEETS::FIGHTCFG_HEAL
)->_HasNormalAction
)
278 if (_LastHealTick
==0)
281 return (CTimeInterface::gameCycle() - _LastHealTick
) > HealSpecificDowntime
;
284 bool CSpawnBot::canSelfHeal()
286 if (!getPersistent().getSheet()->FightConfig(AISHEETS::FIGHTCFG_HEAL
)->_HasSelfAction
)
288 if (_LastSelfHealTick
==0)
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())
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
346 void CSpawnBot::sheetChanged()
348 // CMirrors::initSheet(dataSetRow(), getPersistent().getSheet()->SheetId());
351 void CSpawnBot::sendInfoToEGS() const
353 if (!EGSHasMirrorReady
)
356 const uint32
& maxHp
= getPersistent().getCustomMaxHp();
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 //////////////////////////////////////////////////////////////////////////////
370 //////////////////////////////////////////////////////////////////////////////
372 CBot::CBot(CGroup
* owner
, CAIAliasDescriptionNode
* alias
)
373 : CAliasChild
<CGroup
>(owner
, alias
)
374 , _VerticalPos(AITYPES::vp_auto
)
376 , _ClientCSheet(NULL
)
378 , _IgnoreOffensiveActions(false)
380 , _SetSheetData(NULL
)
387 CBot::CBot(CGroup
* owner
, uint32 alias
, std::string
const& name
)
388 : CAliasChild
<CGroup
>(owner
,alias
, name
)
389 , _VerticalPos(AITYPES::vp_auto
)
391 , _ClientCSheet(NULL
)
393 , _IgnoreOffensiveActions(false)
395 , _SetSheetData(NULL
)
404 // despawn before calling this destructor, because it may will generate a pure virtual call error!
406 nlassert(!isSpawned());
409 if (_Observers
!= NULL
)
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());
433 container
.back() += " sheet=" + NLMISC::CFile::getFilenameWithoutExtension(getSheet()->SheetId().toString());
434 pushEntry(container
, "fullname=" + getFullName());
437 strings
= getSheet()->getMultiLineInfoString();
438 FOREACHC(it
, std::vector
<std::string
>, strings
)
439 pushEntry(container
, *it
);
442 pushEntry(container
, "<invalid sheet>");
445 strings
= getSpawnObj()->getMultiLineInfoString();
446 FOREACHC(it
, std::vector
<std::string
>, strings
)
447 pushEntry(container
, *it
);
450 pushEntry(container
, "<not spawned>");
451 pushFooter(container
);
457 std::string
CBot::getIndexString() const
459 return getOwner()->getIndexString()+NLMISC::toString(":b%u", getChildIndex());
462 std::string
CBot::getEntityIdString() const
465 return getSpawnObj()->getEntityId().toString() ;
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
496 // fake, just waiting real pets implementation in mirror.
497 RYZOMID::TTypeId botType
=getRyzomType();
498 if (botType
==RYZOMID::pack_animal
)
499 botType
=RYZOMID::creature
;
502 nlassert(botType
!=RYZOMID::player
);
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
)
515 setBotEnergyValue((uint32
)((double)energyCoef
*(double)getSheet()->EnergyValue()));
518 void CBot::serviceEvent(CServiceEvent
const& info
)
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.
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
)
555 NLMISC::CEntityId eid
= createEntityId();
557 TDataSetRow
const row
= CMirrors::createEntity(eid
);
560 nlwarning("***> Not Enough Mirror Space for Type: %s", RYZOMID::toString(getRyzomType()).c_str());
564 if (_ClientSheet
!=NLMISC::CSheetId::Unknown
)
565 CMirrors::initSheet(row
, _ClientSheet
);
567 CMirrors::initSheet(row
, getSheet()->SheetId());
569 // get the spawn of this persistent objet.
570 setSpawn(getSpawnBot(row
, eid
, botMeterSize
));
572 CSpawnBot
* spawnBot
= getSpawnObj();
575 spawnBot
->setVisualPropertiesName();
578 if (!isStuck() && !IsRingShard
)
579 botPos
= CAIPos(botWPos
.toAIVector(),0,0);
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());
595 nlassert(!isSpawned());
596 nlassert(getChildIndex()!=-1); // significates that we spawn an unattached bot.
598 // Check we can spawn
601 if (getSheet()->SheetId() == NLMISC::CSheetId::Unknown
)
603 nlwarning("Bot '%s'%s: invalid sheet id", getAliasFullName().c_str(), getAliasString().c_str());
606 if (!getSpawnCounter().remainToMax())
610 RYAI_MAP_CRUNCH::CWorldPosition botWPos
;
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());
622 // Finalize spawn object creation
623 return finalizeSpawn(botWPos
, spawnTheta
, botMeterSize
);
626 void CBot::despawnBot()
629 nlassert(isSpawned());
634 CMirrors::removeEntity(getSpawnObj()->getEntityId());
635 setSpawn(NULL
); // automatic smart pointer deletion
639 bool CBot::reSpawn(bool sendMessage
)
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()
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
);
664 CMirrors::removeEntity(getSpawnObj()->getEntityId());
665 setSpawn(NULL
); // automatic smart pointer deletion
668 // Finalize spawn object creation
669 finalizeSpawn(botWPos
, spawnTheta
, botMeterSize
);
673 class CSetSheetTimerEvent
676 NLMISC::CSmartPtr
<CBot
> _Bot
;
679 CSetSheetTimerEvent(CBot
* bot
, uint32 step
) : _Bot(bot
), _Step(step
) { }
680 virtual void timerCallback(CTimer
* owner
)
683 _Bot
->setSheetDelayed(_Step
);
687 void CBot::setSheet(AISHEETS::ICreatureCPtr
const& sheet
)
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());
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());
712 _ClientCSheet
= sheet
;
719 void CBot::triggerSetSheet(AISHEETS::ICreatureCPtr
const& sheet
)
723 nlwarning("Another sheet change is going, this one is canceled");
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
);
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
;
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
)
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
);
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
)
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
)
828 FOREACH(it
, std::vector
<IObserver
*>, (*_Observers
))
829 (*it
)->notifyBotDespawn(this);
832 void CBot::notifyBotDeath()
834 if (_Observers
== NULL
)
837 FOREACH(it
, std::vector
<IObserver
*>, (*_Observers
))
838 (*it
)->notifyBotDeath(this);
841 void CBot::notifyStopNpcControl()
843 if (_Observers
== NULL
)
846 FOREACH(it
, std::vector
<IObserver
*>, (*_Observers
))
847 (*it
)->notifyStopNpcControl(this);