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) 2020 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 "game_share/visual_slot_manager.h"
23 #include "server_share/r2_variables.h"
25 #include "ai_bot_npc.h"
26 #include "ai_grp_npc.h"
27 #include "ai_mgr_npc.h"
28 #include "ai_player.h"
30 #include "ai_profile_npc.h"
31 #include "ai_control_npc.h"
33 #include "ais_user_models.h"
34 #include "dyn_grp_inline.h"
36 using namespace MULTI_LINE_FORMATER
;
39 using namespace RYAI_MAP_CRUNCH
;
41 // Stuff used for management of log messages
42 static bool VerboseLog
= false;
43 #define LOG if (!VerboseLog) { } else nlinfo
45 extern CAIVector
randomPos(double dispersionRadius
);
47 //////////////////////////////////////////////////////////////////////////////
49 //////////////////////////////////////////////////////////////////////////////
51 CSpawnBotNpc::CSpawnBotNpc(TDataSetRow
const& row
, CBot
& owner
, NLMISC::CEntityId
const& id
, float radius
, uint32 level
, RYAI_MAP_CRUNCH::TAStarFlag denyFlags
)
52 : CSpawnBot(row
, owner
, id
, radius
, level
, denyFlags
)
54 _OldHpPercentage
= -1.f
;
55 _NbCurrentDynChats
= 0;
60 bool CSpawnBotNpc::isBotAttackable() const
62 return getPersistent().grp().getBotAttackable();
65 void CSpawnBotNpc::sendInfoToEGS() const
67 if (!EGSHasMirrorReady
)
70 CSpawnBot::sendInfoToEGS();
72 TGenNpcDescMsgImp msg
;
73 msg
.setEntityIndex(dataSetRow());
75 getPersistent().fillDescriptionMsg(msg
);
77 msg
.setChat(_CurrentChatProfile
);
81 CSpawnGroupNpc
& CSpawnBotNpc::spawnGrp() const
83 return static_cast<CSpawnGroupNpc
&>(CSpawnBot::spawnGrp());
86 void CSpawnBotNpc::processEvent(CCombatInterface::CEvent
const& event
)
89 if (event
._targetRow
==event
._originatorRow
)
92 if ((event
._nature
==ACTNATURE::FIGHT
|| event
._nature
==ACTNATURE::OFFENSIVE_MAGIC
) && !getPersistent().ignoreOffensiveActions())
94 float aggro
= event
._weight
;
99 if (event
._nature
==ACTNATURE::OFFENSIVE_MAGIC
)
101 aggro
=(1.f
+aggro
)*0.5f
-1.f
; // maximize aggro for magic
102 //insure if aggressor is player, player have it's target seted for BOSS assist
103 CAIEntityPhysical
*ep
=CAIS::instance().getEntityPhysical(event
._originatorRow
);
104 CBotPlayer
*player
=dynamic_cast<CBotPlayer
*>(ep
);
107 CAIEntityPhysical
*target
=player
->getVisualTarget();
109 player
->setTarget(target
);
112 addAggroFor(event
._originatorRow
, aggro
, true);
113 spawnGrp().addAggroFor(event
._originatorRow
, aggro
, true);
117 void CSpawnBotNpc::update(uint32 ticks
)
119 ++AISStat::BotTotalUpdCtr
;
120 ++AISStat::BotNpcUpdCtr
;
124 // Fix for HP triggers
125 // :FIXME: Clean that triggering stuff, make it generic
126 CGroupNpc
& persGrp
= spawnGrp().getPersistent();
127 if (persGrp
.haveHpTriggers())
129 float newHpPercentage
= getPhysical().hpPercentage();
130 if (_OldHpPercentage
>=0.f
&& newHpPercentage
!=_OldHpPercentage
)
132 persGrp
.hpTriggerCb(_OldHpPercentage
, newHpPercentage
);
134 _OldHpPercentage
= newHpPercentage
;
138 _OldHpPercentage
= getPhysical().hpPercentage();
142 // Bot chat and dyn chat override AI profile unless fighting
143 // The directing adjustment is done client-slide (each player sees the NPC facing him)
144 if (!getActiveChats().empty())
146 if (getNbActiveDynChats() > 0)
151 // position before profile update
152 CAIPos
const startPos
= CAIPos(pos());
154 // nullify _PlayerController if it is not valid anymore
155 if (_PlayerController
!= NULL
&& !_PlayerController
->isValid())
157 CSpawnBot
* sp
= _PlayerController
->getSpawnBot();
160 sp
->getPersistent().notifyStopNpcControl();
163 _PlayerController
= NULL
;
167 if (_PlayerController
== NULL
)
169 if (!getAISpawnProfile().isNull())
171 H_AUTO(BotNpcUpdateProfile
);
172 updateProfile(ticks
); // then we can update the current bot profile.
177 H_AUTO(BotNpcUpdateControl
);
178 // if there is a valid player controller, it overrides the current profile
179 _PlayerController
->updateControl(ticks
);
183 H_AUTO(BotNpcUpdateAgro
);
184 // every time we need to update bot aggros.
185 this->CBotAggroOwner::update(ticks
);
188 // If sit and move then stand up
192 if (getMode() == MBEHAV::SIT
193 && (x().asInt() != startPos
.x().asInt() || y().asInt() != startPos
.y().asInt()) )
195 setMode(MBEHAV::NORMAL
);
199 if (_FacingTick
!= 0)
201 uint32 tick
= CTimeInterface::gameCycle();
202 if ( (tick
- _FacingTick
) > 40)
204 setTheta(_FacingTheta
);
212 void CSpawnBotNpc::setFacing(CAngle theta
)
214 if (_FacingTick
== 0)
216 _FacingTheta
= pos().theta();
220 _FacingTick
= CTimeInterface::gameCycle();
223 void CSpawnBotNpc::setUserModelId(const std::string
&id
)
228 void CSpawnBotNpc::setCustomLootTableId(const std::string
&id
)
230 _CustomLootTableId
= id
;
234 void CSpawnBotNpc::setPrimAlias(uint32 primAlias
)
236 _PrimAlias
= primAlias
;
238 void CSpawnBotNpc::updateChat(CAIState
const* state
)
242 CBotNpc
const& botNpc
= getPersistent();
244 FOREACHC(itChat
, CCont
<CAIStateChat
>, state
->chats())
246 if (!itChat
->testCompatibility(botNpc
))
249 // update chat information if any
250 CNpcChatProfileImp
const* const chatProfile
= botNpc
.getChat();
253 _CurrentChatProfile
= CNpcChatProfileImp::combineChatProfile(*chatProfile
, itChat
->getChat());
254 // the chat profile has been combined, send it to EGS
261 std::vector
<std::string
> CSpawnBotNpc::getMultiLineInfoString() const
263 std::vector
<std::string
> container
;
264 std::vector
<std::string
> strings
;
267 pushTitle(container
, "CSpawnBotNpc");
269 strings
= CSpawnBot::getMultiLineInfoString();
270 FOREACHC(itString
, std::vector
<std::string
>, strings
)
271 pushEntry(container
, *itString
);
272 pushEntry(container
, "profile: " + CProfilePtr::getOneLineInfoString());
273 pushEntry(container
, "state: " + spawnGrp().getPersistent().buidStateInstanceDebugString());
274 std::string userModelId
= getPersistent().getUserModelId();
275 if (!(userModelId
.empty()))
277 pushEntry(container
, "UserModelId: " + userModelId
);
279 if (_CurrentChatProfile
.getMissions().empty())
280 pushEntry(container
, "no mission");
283 vector
<uint32
> const& missions
= _CurrentChatProfile
.getMissions();
284 pushEntry(container
, "missions:");
285 for (size_t i
=0; i
<missions
.size(); ++i
)
287 string name
= getAIInstance()->findMissionName(missions
[i
]);
288 pushEntry(container
, NLMISC::toString(" %u (%s)", missions
[i
], name
.c_str()));
291 pushFooter(container
);
297 void CSpawnBotNpc::beginBotChat(CBotPlayer
* plr
)
300 for (size_t i
=0; i
<_ActiveChats
.size(); ++i
)
302 if (_ActiveChats
[i
]==plr
)
303 nlwarning("Chat pair added more than once!!!");
307 // add an entry to the bot chat vector
308 _ActiveChats
.push_back(plr
);
311 void CSpawnBotNpc::endBotChat(CBotPlayer
* plr
)
313 // run through the bot chat vector looking for a player to erase
314 for (size_t i
=0;i
<_ActiveChats
.size();++i
)
316 if (_ActiveChats
[i
]!=plr
)
319 // we've found a match for the player so remove the entry from the _aciveChats vector and return
320 _ActiveChats
[i
] = _ActiveChats
[_ActiveChats
.size()-1];
321 _ActiveChats
.pop_back();
324 for (size_t j
=i
;j
<_ActiveChats
.size();++j
)
326 if (_ActiveChats
[i
]==plr
)
328 nlwarning("Chat pair removed but another of the same still exists!!!");
334 // we should never end up here!
335 nlwarning("Chat pair removed but not previously added!!!");
338 void CSpawnBotNpc::propagateAggro() const
340 if (getAggroPropagationRadius()<0.f
)
342 CGroup
* pGroup
= getPersistent().getOwner();
343 CSpawnGroup
* spGroup
= pGroup
->getSpawnObj();
344 CDynGrpBase
* myDgb
= pGroup
->getGrpDynBase();
346 CFamilyBehavior
* myfb
= myDgb
->getFamilyBehavior();
348 CAIVision
<CPersistentOfPhysical
> vision
;
349 // look for bots around
350 vision
.updateBotsAndPlayers(pGroup
->getAIInstance(), CAIVector(pos()), 0, (uint32
)getAggroPropagationRadius());
352 typedef map
<CSpawnBot
*, float> TCandidatesCont
;
353 TCandidatesCont candidates
;
357 FOREACH(it
, CAIVision
<CPersistentOfPhysical
>, vision
)
359 CBot
& otherPBot
= static_cast<CBot
&>(*it
);
360 CSpawnBot
* otherSpBot
= otherPBot
.getSpawnObj();
361 CGroup
* otherPGroup
= otherPBot
.getOwner();
362 CSpawnGroup
* otherSpGroup
= otherPGroup
->getSpawnObj();
364 // If bot is in our group skip it
365 if (otherSpGroup
==spGroup
)
368 // If bot is not in a dynamic system skip it
369 CDynGrpBase
* otherDGB
= otherPGroup
->getGrpDynBase();
373 // If bot is not in our family skip it
374 CFamilyBehavior
* otherFB
= otherDGB
->getFamilyBehavior();
378 // ok, this group should help us !
380 // TODO : take only groups with the 'activity_figth' property
381 // filter out if the group is already in combat mode
383 // If group is not spawned skip it (???) (:TODO: See why this test)
384 if (!otherPGroup
->isSpawned())
387 CProfilePtr
& otherFightProfile
= otherSpGroup
->fightProfile();
389 if (!otherFightProfile
.getAIProfile() || otherFightProfile
.getAIProfileType()!=AITYPES::FIGHT_NORMAL
|| !(static_cast<CGrpProfileFight
*>(otherFightProfile
.getAIProfile())->stillHaveEnnemy()))
391 float dist
= (float)CAIVector(otherSpBot
->pos()).quickDistTo(pos());
392 if (dist
<getAggroPropagationRadius())
393 candidates
[otherSpBot
] = dist
;
398 if (!candidates
.empty())
399 nldebug("Tick %u, prop aggro from %p to %u bots", CTickEventHandler::getGameCycle(), this, candidates
.size());
401 FOREACH (it
, TCandidatesCont
, candidates
)
403 float propFactor
= 1.0f
- (it
->second
/ getAggroPropagationRadius());
404 nldebug(" prop aggro from %p to %p at %f meters, prop factor = %f",
409 // add aggro list of this group to the other group pondered by distance
410 it
->first
->mergeAggroList(*this, propFactor
);
414 float CSpawnBotNpc::getReturnDistCheck() const
416 if (getPersistent().isSheetValid() && getPersistent().getSheet()->AggroReturnDistCheck()>=0.f
)
417 return getPersistent().getSheet()->AggroReturnDistCheck();
419 return AggroReturnDistCheckNpc
;
422 void CSpawnBotNpc::setPlayerController(CBotPlayer
* player
)
426 _PlayerController
= new CPlayerControlNpc(player
, this);
427 nlassert(_PlayerController
->isValid());
432 _PlayerController
= NULL
;
436 void CSpawnBotNpc::setCurrentChatProfile(CNpcChatProfileImp
* chatProfile
)
439 _CurrentChatProfile
= *chatProfile
;
441 _CurrentChatProfile
.clear(); // clear the chat profile
444 CBotNpc
& CSpawnBotNpc::getPersistent() const
446 return static_cast<CBotNpc
&>(CSpawnBot::getPersistent());
449 //////////////////////////////////////////////////////////////////////////////
451 //////////////////////////////////////////////////////////////////////////////
453 CBotNpc::CBotNpc(CGroup
* owner
, CAIAliasDescriptionNode
* alias
)
457 _Sheet
= CBotNpcSheetPtr(new CBotNpcSheet(NULL
));
458 _Sheet
->setSheet(CBot::getSheet());
462 CBotNpc::CBotNpc(CGroup
* owner
, uint32 alias
, std::string
const& name
)
463 : CBot(owner
, alias
, name
)
466 _Sheet
= CBotNpcSheetPtr(new CBotNpcSheet(NULL
));
467 _Sheet
->setSheet(CBot::getSheet());
479 void CBotNpc::calcSpawnPos(RYAI_MAP_CRUNCH::CWorldMap
const& worldMap
)
481 CAIStatePositional
const* const state
= static_cast<CAIStatePositional
*>(grp().getStartState());
482 RYAI_MAP_CRUNCH::CWorldPosition wp
;
483 uint32 maxTries
= 100;
490 if (state
->shape().hasPatat() && state
->shape().getRandomPosCount())
494 RYAI_MAP_CRUNCH::CWorldPosition wp
;
495 state
->shape().getRandomPos(wp
);
498 } while(!worldMap
.setWorldPosition(_VerticalPos
, wp
, _StartPos
) && maxTries
);
499 _StartPos
.setTheta(0); // to initialise among vertices deltas (no?).
503 if (!state
->shape().hasPoints())
506 std::vector
<CShape::TPosition
> const& posList
= state
->shape().getGeometry();
509 const uint32 a
=CAIS::rand16((uint32
)posList
.size());
510 const uint32 b
=(a
+1)%posList
.size();
511 const double weight
=CAIS::frand();
512 _StartPos
.setXY(posList
[a
].toAIVector()+(posList
[b
].toAIVector()-posList
[a
].toAIVector())*weight
);
514 } while (!worldMap
.setWorldPosition(_VerticalPos
, wp
, _StartPos
) && maxTries
);
515 _StartPos
.setTheta(0); // to initialise among vertices deltas (no?).
518 if (!wp
.isValid() && !isStuck())
519 nlwarning("Cannot generate a valid position for Npc %s", getFullName().c_str());
522 CGroupNpc
& CBotNpc::grp() const
524 return *static_cast<CGroupNpc
*>(getOwner());
527 void CBotNpc::setUserModelId(const std::string
&userModelId
)
529 if (!userModelId
.empty())
531 if (!CAIUserModelManager::getInstance()->isUserModel(_PrimAlias
, userModelId
))
533 nlwarning("Error while parsing equipment params: cannot find user model id '%s' associated to primAlias '%u'", userModelId
.c_str(), _PrimAlias
);
537 _UserModelId
= userModelId
;
541 void CBotNpc::setCustomLootTableId(const std::string
&customLootTableId
)
543 if (!customLootTableId
.empty())
545 if (!CAIUserModelManager::getInstance()->isCustomLootTable(_PrimAlias
, customLootTableId
))
547 nlwarning("Error while parsing equipment params: invalid customLootTableId specified: '%s'", customLootTableId
.c_str());
548 _CustomLootTableId
= "";
551 _CustomLootTableId
= customLootTableId
;
555 std::string
CBotNpc::getUserModelId()
560 std::string
CBotNpc::getCustomLootTableId()
562 return _CustomLootTableId
;
565 void CBotNpc::setPrimAlias(uint32 alias
)
571 uint32
CBotNpc::getPrimAlias() const
577 void CBotNpc::fillDescriptionMsg(RYMSG::TGenNpcDescMsg
& msg
) const
579 msg
.setPlayerAttackable(grp().getPlayerAttackable());
580 msg
.setBotAttackable(grp().getBotAttackable());
581 msg
.setAlias(getAlias());
582 msg
.setGrpAlias(grp().getAlias());
584 msg
.setSheet(getSheet()->SheetId());
586 msg
.setRightHandItem(getSheet()->RightItem());
587 msg
.setRightHandItemQuality(1);
589 msg
.setLeftHandItem(getSheet()->LeftItem());
590 msg
.setLeftHandItemQuality(1);
592 msg
.setDontFollow(isStuck());
593 msg
.setBuildingBot(isBuildingBot());
595 for (size_t i
=0; i
<_LootList
.size(); ++i
)
597 NLMISC::CSheetId
const& sheetRef
= _LootList
[i
];
598 if (sheetRef
!=NLMISC::CSheetId::Unknown
)
599 msg
.getLootList().push_back(sheetRef
);
602 if (_PrimAlias
>= 900 && _PrimAlias
<= 999) //Spawned bots
603 msg
.getOptionalProperties().push_back("Name:"+getName());
605 CGroupNpc::TFactionAttackableSet
const& factionAttackableAbove
= grp().getFactionAttackableAbove();
606 FOREACHC(itFaction
, CGroupNpc::TFactionAttackableSet
, factionAttackableAbove
)
607 msg
.getOptionalProperties().push_back("FactionAttackableAbove:" + itFaction
->first
+ ":" + NLMISC::toString(itFaction
->second
));
608 CGroupNpc::TFactionAttackableSet
const& factionAttackableBelow
= grp().getFactionAttackableBelow();
609 FOREACHC(itFaction
, CGroupNpc::TFactionAttackableSet
, factionAttackableBelow
)
610 msg
.getOptionalProperties().push_back("FactionAttackableBelow:" + itFaction
->first
+ ":" + NLMISC::toString(itFaction
->second
));
612 msg
.setMaxHitRangeForPC(_MaxHitRangeForPC
);
614 // msg.setIsMissionStepIconDisplayable(_MissionIconFlags.IsMissionStepIconDisplayable);
615 // msg.setIsMissionGiverIconDisplayable(_MissionIconFlags.IsMissionGiverIconDisplayable);
616 msg
.setUserModelId(_UserModelId
);
617 msg
.setCustomLootTableId(_CustomLootTableId
);
618 msg
.setPrimAlias(_PrimAlias
);
621 // :KLUDGE: This methods is a part of the trick for bot respawn
622 // :TODO: Clean that mess
623 bool CBotNpc::finalizeSpawnNpc()
625 // For squads, setup specific mirror properties
626 COutpost
* ownerOutpost
= dynamic_cast<COutpost
*>(getOwner()->getOwner()->getOwner());
629 // Propagate the alliance id and outpost alias to the instanciated bot
630 // Currently, all squads are assumed to be defender of the outpost (not attacker)
631 getSpawn()->setOutpostAlias(ownerOutpost
->getAlias());
632 getSpawn()->setOutpostSide(_OutpostSide
);
635 CMirrors::initSheetServer(getSpawn()->dataSetRow(), getSheet()->SheetId());
637 getSpawn()->setCurrentChatProfile(_ChatProfile
);
638 getSpawn()->sendInfoToEGS();
640 if (_useVisualProperties
) // use VisualPropertyA, B, C
642 sendVisualProperties();
644 else // use alternate VPA
649 getSpawn()->spawnGrp().botHaveSpawn(this);
654 void CBotNpc::initAdditionalMirrorValues()
656 // Write the Mission Alias to mirror now - it must be done before CMirrors::declareEntity()
657 // to ensure the FS will receive the Sheet and it at the same time, hence it can't be
658 // done in finalizeSpawnNpc(), which is called after that
659 EGSPD::CPeople::TPeople race
= getSheet()->Race();
660 if (race
< EGSPD::CPeople::Creature
||
661 race
== EGSPD::CPeople::Kami
||
662 race
== EGSPD::CPeople::Unknown
) // only for humanoid NPCs and bot objects (beware, even some creatures have the entity type RYZOMID::npc)
664 //if ((_ChatProfile != NULL) && (!_ChatProfile->getMissions().empty())) // only if the bot has missions to give: not
665 CMirrors::initNPCAlias(getSpawn()->dataSetRow(), getAlias());
669 bool CBotNpc::spawn()
673 nlwarning("spawn() Aborted for bot '%s' due to bad sheet", getFullName().c_str());
680 // :KLUDGE: Last part calls a tricky method also called by sheetChanged
681 // :TODO: Clean that mess
682 return finalizeSpawnNpc();
685 void CBotNpc::sendVPA() // alternate VPA
687 SAltLookProp visProp
;
688 // sets weapons information.
690 CVisualSlotManager
* visualSlotManager
= CVisualSlotManager::getInstance();
691 NLMISC::CSheetId rightSheet
= getSheet()->RightItem();
692 NLMISC::CSheetId leftSheet
= getSheet()->LeftItem();
694 visProp
.Element
.WeaponRightHand
= visualSlotManager
->rightItem2Index(rightSheet
);
695 visProp
.Element
.WeaponLeftHand
= visualSlotManager
->leftItem2Index(leftSheet
);
698 // setting up the visual property A mirror record
699 visProp
.Element
.ColorTop
= getSheet()->ColorBody();
700 visProp
.Element
.ColorBot
= getSheet()->ColorLegs();
701 visProp
.Element
.ColorHair
= getSheet()->ColorHead();
702 visProp
.Element
.ColorGlove
= getSheet()->ColorHands();
703 visProp
.Element
.ColorBoot
= getSheet()->ColorFeets();
704 visProp
.Element
.ColorArm
= getSheet()->ColorArms();
705 visProp
.Element
.Hat
= _Hat
;
706 visProp
.Element
.Seed
= getAlias()!=0?getAlias():(uint32
)(size_t)(void*)this;
707 LOG("BOT: %s L: %d R: %u H: %u CHEAD: %u CARMS: %u CHANDS: %u CBODY: %u CLEGS: %u CFEETS: %u SEED: %u",
709 visProp
.Element
.WeaponLeftHand
,
710 visProp
.Element
.WeaponRightHand
,
712 visProp
.Element
.ColorTop
,
713 visProp
.Element
.ColorBot
,
714 visProp
.Element
.ColorHair
,
715 visProp
.Element
.ColorGlove
,
716 visProp
.Element
.ColorBoot
,
717 visProp
.Element
.ColorArm
,
718 visProp
.Element
.Seed
);
720 CMirrors::setVPA(getSpawn()->dataSetRow(), visProp
);
723 void CBotNpc::sendVisualProperties() // VisualPropertyA, B, C
727 CMirrors::setVisualPropertyA( getSpawn()->dataSetRow(), _VisualPropertyA
);
728 CMirrors::setVisualPropertyB( getSpawn()->dataSetRow(), _VisualPropertyB
);
729 CMirrors::setVisualPropertyC( getSpawn()->dataSetRow(), _VisualPropertyC
);
733 bool CBotNpc::reSpawn(bool sendMessage
)
738 getSpawn()->updateChat(grp().getCAIState());
742 void CBotNpc::despawnBot()
746 getSpawn()->spawnGrp().botHaveDespawn(this);
751 void CBotNpc::equipmentInit()
755 _useVisualProperties
= false; // default is to use alternate VPA
756 _VisualPropertyA
= 0;
757 _VisualPropertyB
= 0;
758 _VisualPropertyC
= 0;
759 _FaunaBotUseBotName
= false;
762 void CBotNpc::equipmentAdd(std::string
const& input
)
764 // if string is empty just return without making a fuss
768 // split string into keyword and tail
769 std::string keyword
, tail
;
770 if (!AI_SHARE::stringToKeywordAndTail(input
,keyword
,tail
))
772 nlwarning("Bot '%s'%s: failed to parse equipment text: '%s'",
773 getAliasFullName().c_str(),
774 getAliasString().c_str(),
779 // do something depending on keyword
780 if (NLMISC::nlstricmp(keyword
,"ri")==0)
784 nlwarning("No sheet name supplied for equipment slot 'ri': '%s'%s",getAliasFullName().c_str(),getAliasString().c_str());
785 _Sheet
->setRightItem(NLMISC::CSheetId::Unknown
);
787 else if (NLMISC::nlstricmp(tail
,"none")==0)
789 _Sheet
->setRightItem(NLMISC::CSheetId::Unknown
);
793 _Sheet
->setRightItem(NLMISC::CSheetId(tail
));
796 else if (NLMISC::nlstricmp(keyword
,"li")==0)
800 nlwarning("No sheet name supplied for equipment slot 'li': '%s'%s",getAliasFullName().c_str(),getAliasString().c_str());
801 _Sheet
->setLeftItem(NLMISC::CSheetId::Unknown
);
803 else if (NLMISC::nlstricmp(tail
,"none")==0)
805 _Sheet
->setLeftItem(NLMISC::CSheetId::Unknown
);
809 _Sheet
->setLeftItem(NLMISC::CSheetId(tail
));
812 else if (NLMISC::nlstricmp(keyword
,"HAT")==0 || NLMISC::nlstricmp(keyword
,"IH")==0)
817 NLMISC::nlstricmp(keyword
,"UPPER" )==0 || NLMISC::nlstricmp(keyword
,"CU")==0 ||
818 NLMISC::nlstricmp(keyword
,"LOWER" )==0 || NLMISC::nlstricmp(keyword
,"CL")==0 ||
819 NLMISC::nlstricmp(keyword
,"HAIR" )==0 || NLMISC::nlstricmp(keyword
,"CH")==0 ||
820 NLMISC::nlstricmp(keyword
,"CHEAD" )==0 ||
821 NLMISC::nlstricmp(keyword
,"CARMS" )==0 ||
822 NLMISC::nlstricmp(keyword
,"CHANDS")==0 ||
823 NLMISC::nlstricmp(keyword
,"CBODY" )==0 ||
824 NLMISC::nlstricmp(keyword
,"CLEGS" )==0 ||
825 NLMISC::nlstricmp(keyword
,"CFEETS")==0
831 NLMISC::nlstricmp(keyword
,"VPA")==0 || NLMISC::nlstricmp(keyword
,"VPB")==0 ||
832 NLMISC::nlstricmp(keyword
,"VPC")==0
836 setVisualProperties(input
);
838 else if ( NLMISC::nlstricmp(keyword
,"CLIENT_SHEET")==0 )
840 setClientSheet(tail
+ ".creature");
842 else if (NLMISC::nlstricmp(keyword
,"loot")==0)
846 nlwarning("No sheet name supplied for loot list entry: '%s'%s",getAliasFullName().c_str(),getAliasString().c_str());
850 _LootList
.push_back(NLMISC::CSheetId(tail
));
853 else if (NLMISC::nlstricmp(keyword
,"FAUNA_BOT_USE_BOTNAME")==0)
855 _FaunaBotUseBotName
= true;
857 else if (NLMISC::nlstricmp(keyword
, "USER_MODEL") == 0)
860 uint32 primAlias
= getAlias() >> LigoConfig
.getDynamicAliasSize();
861 nldebug("Parsing userModelId '%s' with primAlias: '%u'", tail
.c_str(), primAlias
);
862 setPrimAlias(primAlias
);
863 setUserModelId(tail
);
865 else if (NLMISC::nlstricmp(keyword
, "CUSTOM_LOOT_TABLE") == 0)
867 uint32 primAlias
= getAlias() >> LigoConfig
.getDynamicAliasSize();
868 nldebug("Parsing customLootTableId '%s' with primAlias: '%u'", tail
.c_str(), primAlias
);
869 setPrimAlias(primAlias
);
870 setCustomLootTableId(tail
);
874 nlwarning("Bot '%s'%s:failed to parse equipment argument: '%s'",
875 getAliasFullName().c_str(),
876 getAliasString().c_str(),
882 Colors are something like that:
885 1: BEIGE ORANGE BEIGE
886 2: VERT CITRON VERT CITRON GREEN
887 3: VERT VERT TURQUOISE
889 5: ROUGE dark ROUGE (normal) CRIMSON
891 7: NOIR BLEU very dark BLACK
893 3D column is (probably) used for equipment
895 void CBotNpc::setColour(uint8 colour
)
897 _Sheet
->setColorHead(colour
);
898 _Sheet
->setColorArms(colour
);
899 _Sheet
->setColorHands(colour
);
900 _Sheet
->setColorBody(colour
);
901 _Sheet
->setColorLegs(colour
);
902 _Sheet
->setColorFeets(colour
);
905 void CBotNpc::setColours(std::string input
)
907 // stuff for manageing colour names
908 int const numColours
= 8;
909 static std::vector
<std::string
> colourNames
[numColours
];
910 static bool init
= false;
912 // if string is empty just return without making a fuss
918 // lookup 'ColourNames' in config file (should be a multi-line field)
919 NLMISC::CConfigFile::CVar
* varPtr
= NLNET::IService::getInstance()->ConfigFile
.getVarPtr(std::string("ColourNames"));
922 // for each line in config file var try to add an alternative name for one of the colour slots
923 for (uint i
=0; i
<varPtr
->size(); ++i
)
925 // split line into name and idx (line example: 'red: 5')
926 std::string name
, idxStr
;
927 if (AI_SHARE::stringToKeywordAndTail(varPtr
->asString(i
),name
,idxStr
))
929 // split succeeded so verify that idxStr contains anumber in range 0..7 and add
930 // the name to the list of valid names for the given slot
932 NLMISC::fromString(idxStr
, idx
);
933 if (NLMISC::toString(idx
)==idxStr
&& idx
<numColours
)
934 colourNames
[idx
].push_back(name
);
941 // split 'input' string into keyword and tail
942 std::string keyword
, tail
;
943 if (!AI_SHARE::stringToKeywordAndTail(input
,keyword
,tail
))
945 nlwarning("Failed to parse colour text: '%s' for bot: '%s'",input
.c_str(),getAliasNode()->fullName().c_str());
949 // do something depending on keyword
950 if (NLMISC::nlstricmp(keyword
,"UPPER" )==0 || NLMISC::nlstricmp(keyword
,"CU")==0 ||
951 NLMISC::nlstricmp(keyword
,"LOWER" )==0 || NLMISC::nlstricmp(keyword
,"CL")==0 ||
952 NLMISC::nlstricmp(keyword
,"HAIR" )==0 || NLMISC::nlstricmp(keyword
,"CH")==0 ||
953 NLMISC::nlstricmp(keyword
,"CHEAD" )==0 ||
954 NLMISC::nlstricmp(keyword
,"CARMS" )==0 ||
955 NLMISC::nlstricmp(keyword
,"CHANDS")==0 ||
956 NLMISC::nlstricmp(keyword
,"CBODY" )==0 ||
957 NLMISC::nlstricmp(keyword
,"CLEGS" )==0 ||
958 NLMISC::nlstricmp(keyword
,"CFEETS")==0
961 std::vector
<uint32
> results
;
963 while (!tail
.empty())
965 // extract the next word from the tail
966 AI_SHARE::stringToWordAndTail(tail
,colour
,tail
);
967 // if the colour string is a number then treat it directly
969 NLMISC::fromString(colour
, idx
);
970 if (NLMISC::toString(idx
)==colour
)
972 // we've got a number so make sue it's in valid range and add to results vector
974 results
.push_back(idx
);
976 nlwarning("Bot '%s'%s: failed to identify colour: '%s' in line: '%s'",
977 getAliasFullName().c_str(),
978 getAliasString().c_str(),
984 // try to find an entry in the colour list that matches the 'colour' string
987 for (i
=0; i
<numColours
&& !done
; ++i
)
989 for (size_t j
=0; j
<colourNames
[i
].size() && !done
; ++j
)
991 if (NLMISC::nlstricmp(colour
,colourNames
[i
][j
])==0)
993 // found an entry so add to the results vector
994 results
.push_back((uint32
)i
);
1001 nlwarning("Failed to identify colour: '%s' in line: '%s'",colour
.c_str(),input
.c_str());
1005 // assuming that we found more than 0 results pick one at random
1006 if (results
.empty())
1008 nlwarning("Bot '%s'%s: failed to identify any valid input in line: '%s'",
1009 getAliasFullName().c_str(),
1010 getAliasString().c_str(),
1014 NLMISC::CRandom generator
;
1015 sint32 seed
= getAlias();
1016 uint8
* p
= (uint8
*)&seed
;
1020 if ( NLMISC::nlstricmp(keyword
,"UPPER")==0 || NLMISC::nlstricmp(keyword
,"CU")==0 )
1022 // upper body colour
1023 generator
.srand(seed
+975*0x10000);
1024 uint8 val
= (uint8
)results
[generator
.rand((uint16
)results
.size()-1)];
1025 _Sheet
->setColorArms(val
);
1026 _Sheet
->setColorHands(val
);
1027 _Sheet
->setColorBody(val
);
1029 else if ( NLMISC::nlstricmp(keyword
,"LOWER")==0 || NLMISC::nlstricmp(keyword
,"CL")==0 )
1031 // lower body colour
1032 generator
.srand(seed
+977*0x10000);
1033 uint8 val
= (uint8
)results
[generator
.rand((uint16
)results
.size()-1)];
1034 _Sheet
->setColorLegs(val
);
1035 _Sheet
->setColorFeets(val
);
1037 else if ( NLMISC::nlstricmp(keyword
,"HAIR")==0 || NLMISC::nlstricmp(keyword
,"CH")==0 )
1039 // hair colour, mapped to head
1040 generator
.srand(seed
+976*0x10000);
1041 uint8 val
= (uint8
)results
[generator
.rand((uint16
)results
.size()-1)];
1042 _Sheet
->setColorHead(val
);
1044 else if ( NLMISC::nlstricmp(keyword
,"CHEAD")==0)
1047 generator
.srand(seed
+979*0x10000);
1048 uint8 val
= (uint8
)results
[generator
.rand((uint16
)results
.size()-1)];
1049 _Sheet
->setColorHead(val
);
1051 else if ( NLMISC::nlstricmp(keyword
,"CARMS")==0)
1054 generator
.srand(seed
+981*0x10000);
1055 uint8 val
= (uint8
)results
[generator
.rand((uint16
)results
.size()-1)];
1056 _Sheet
->setColorArms(val
);
1058 else if ( NLMISC::nlstricmp(keyword
,"CHANDS")==0)
1061 generator
.srand(seed
+983*0x10000);
1062 uint8 val
= (uint8
)results
[generator
.rand((uint16
)results
.size()-1)];
1063 _Sheet
->setColorHands(val
);
1065 else if ( NLMISC::nlstricmp(keyword
,"CBODY")==0)
1068 generator
.srand(seed
+985*0x10000);
1069 uint8 val
= (uint8
)results
[generator
.rand((uint16
)results
.size()-1)];
1070 _Sheet
->setColorBody(val
);
1072 else if ( NLMISC::nlstricmp(keyword
,"CLEGS")==0)
1075 generator
.srand(seed
+987*0x10000);
1076 uint8 val
= (uint8
)results
[generator
.rand((uint16
)results
.size()-1)];
1077 _Sheet
->setColorLegs(val
);
1079 else if ( NLMISC::nlstricmp(keyword
,"CFEETS")==0)
1082 generator
.srand(seed
+989*0x10000);
1083 uint8 val
= (uint8
)results
[generator
.rand((uint16
)results
.size()-1)];
1084 _Sheet
->setColorFeets(val
);
1089 nlwarning("Bot '%s'%s: failed to parse colours argument: '%s'",
1090 getAliasFullName().c_str(),
1091 getAliasString().c_str(),
1098 void CBotNpc::setVisualProperties(std::string input
) // AJM
1100 // set the VisualPropertyA, VisualPropertyB, VisualPropertyC bitfield values
1101 // using a hexadecimal entry format
1103 // if string is empty just return without making a fuss
1108 std::vector
< std::string
> vpx
;
1109 NLMISC::splitString(input
, ";", vpx
);
1110 for (uint8 i
= 0; i
< vpx
.size(); i
++)
1112 // split 'input' string into keyword and tail
1113 std::string keyword
, tail
;
1114 if(!AI_SHARE::stringToKeywordAndTail(vpx
[i
],keyword
,tail
))
1116 nlwarning("Failed to parse visual property text: '%s' for bot: '%s'", vpx
[i
].c_str(), getAliasNode()->fullName().c_str());
1120 // load val from tail
1121 // accept 64bit hex value
1123 sscanf( tail
.c_str(), "%" NL_I64
"x", &val
);
1125 // can't set into mirror row until bot is spawned, so save away
1126 if( NLMISC::nlstricmp( keyword
,"VPA")==0 ) // VisualPropertyA
1128 _VisualPropertyA
= val
;
1129 _useVisualProperties
= true;
1131 else if( NLMISC::nlstricmp( keyword
,"VPB")==0 ) // VisualPropertyB
1133 _VisualPropertyB
= val
;
1134 _useVisualProperties
= true;
1136 else if( NLMISC::nlstricmp( keyword
,"VPC")==0 ) // VisualPropertyC
1138 _VisualPropertyC
= val
;
1139 _useVisualProperties
= true;
1143 nlwarning("Bot '%s'%s: failed to parse visual property argument: '%s'",
1144 getAliasFullName().c_str(),
1145 getAliasString().c_str(),
1151 void CBotNpc::setStartPos(double x
, double y
, float theta
, AITYPES::TVerticalPos verticalPos
)
1153 _StartPos
= CAIPos(x
, y
, 0, theta
);
1154 _VerticalPos
= verticalPos
;
1157 void CBotNpc::init()
1159 _ChatProfile
= NULL
;
1160 _MaxHitRangeForPC
= -1.0f
;
1161 _DispersionRadius
= 0;
1162 // _MissionIconFlags.IsMissionStepIconDisplayable = true;
1163 // _MissionIconFlags.IsMissionGiverIconDisplayable = true;
1168 CSpawnBotNpc
* CBotNpc::getSpawn()
1170 return static_cast<CSpawnBotNpc
*>(getSpawnObj());
1173 CSpawnBotNpc
const* CBotNpc::getSpawn() const
1175 return static_cast<CSpawnBotNpc
const*>(getSpawnObj());
1178 void CBotNpc::getSpawnPos(CAIVector
& triedPos
, RYAI_MAP_CRUNCH::CWorldPosition
& pos
, RYAI_MAP_CRUNCH::CWorldMap
const& worldMap
, CAngle
& spawnTheta
)
1180 if (_DispersionRadius
> 0)
1182 RYAI_MAP_CRUNCH::CWorldPosition wp
;
1183 CAIVector rpos
= _FirstPosition
;
1184 uint32 maxTries
= 100;
1187 rpos
= _FirstPosition
;
1188 rpos
+= randomPos(_DispersionRadius
);
1191 while (!worldMap
.setWorldPosition(AITYPES::vp_auto
, wp
, rpos
) && maxTries
>0);
1192 if (maxTries
> 0 ) {
1193 nlinfo("set pos %f,%f", rpos
.x().asDouble(), rpos
.x().asDouble());
1194 _StartPos
.setXY(rpos
);
1197 else if (_StartPos
.isNull())
1198 calcSpawnPos(worldMap
);
1200 if (isStuck() || IsRingShard
)
1201 worldMap
.setWorldPosition(_VerticalPos
, pos
, _StartPos
);
1203 CWorldContainer::calcNearestWPosFromPosAnRadius(_VerticalPos
, worldMap
, pos
, _StartPos
, 10, 200, CWorldContainer::CPosValidatorDefault());
1206 if (!pos
.isValid() && !isStuck())
1208 nlwarning("Npc Spawn Pos Error %s", pos
.toString().c_str());
1212 spawnTheta
= _StartPos
.theta();
1213 triedPos
= _StartPos
;
1216 CSpawnBot
* CBotNpc::getSpawnBot(TDataSetRow
const& row
, NLMISC::CEntityId
const& id
, float radius
)
1218 return new CSpawnBotNpc(row
, *this, id
, radius
, getSheet()->Level(), getGroup().getAStarFlag());
1221 void CBotNpc::newChat()
1223 _ChatProfile
= new CNpcChatProfileImp();
1226 CAIS::CCounter
& CBotNpc::getSpawnCounter()
1228 return CAIS::instance()._NpcBotCounter
;
1231 void CBotNpc::setSheet(AISHEETS::ICreatureCPtr
const& sheet
)
1233 _Sheet
->setSheet(sheet
);
1237 // :KLUDGE: This method is very tricky. It's a copy'n'paste of the method in CBot (BAD!) with more content
1238 // :TODO: Clean that mess
1239 void CBotNpc::sheetChanged()
1244 RYAI_MAP_CRUNCH::CWorldPosition botWPos
= getSpawnObj()->wpos();
1245 CAngle spawnTheta
= getSpawnObj()->theta();
1246 float botMeterSize
= getSheet()->Scale()*getSheet()->Radius();
1247 // :TODO: Save profile info
1249 // If stuck bot position may be outside collision and must be recomputed
1250 if (isStuck() || IsRingShard
)
1251 getSpawnPos(lastTriedPos
, botWPos
, CWorldContainer::getWorldMap(), spawnTheta
);
1254 CMirrors::removeEntity(getSpawnObj()->getEntityId());
1255 setSpawn(NULL
); // automatic smart pointer deletion
1258 // Finalize spawn object creation
1259 if (!finalizeSpawn(botWPos
, spawnTheta
, botMeterSize
))
1262 // :KLUDGE: Both finalizeSpawn and finalizeSpawnNpc are called,
1263 // sheetChanged has a strange herited meaning and may confuse future
1265 // :TODO: Clean that mess and find a more elegant C++ solution to the
1271 //////////////////////////////////////////////////////////////////////////////
1273 //////////////////////////////////////////////////////////////////////////////
1275 // Control over verbose nature of logging
1276 NLMISC_COMMAND(verboseNPCBotProfiles
, "Turn on or off or check the state of verbose bot profile change logging","")
1282 NLMISC::fromString(args
[0], VerboseLog
);
1284 nlinfo("VerboseLogging is %s",VerboseLog
?"ON":"OFF");
1287 // virtual function so do not need to be inlined
1288 bool CBotNpc::getFaunaBotUseBotName() const
1290 return _FaunaBotUseBotName
;