setVpx accept now all VPX using ; as delimiter
[ryzomcore.git] / ryzom / server / src / ai_service / ai_bot_npc.cpp
blob227987de2296f8a2326b5561fb7a0ff640ea7260
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) 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"
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"
29 #include "states.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;
38 using namespace std;
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 //////////////////////////////////////////////////////////////////////////////
48 // CSpawnBotNpc //
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;
56 _FacingTheta = 0;
57 _FacingTick = 0;
60 bool CSpawnBotNpc::isBotAttackable() const
62 return getPersistent().grp().getBotAttackable();
65 void CSpawnBotNpc::sendInfoToEGS() const
67 if (!EGSHasMirrorReady)
68 return;
70 CSpawnBot::sendInfoToEGS();
72 TGenNpcDescMsgImp msg;
73 msg.setEntityIndex(dataSetRow());
75 getPersistent().fillDescriptionMsg(msg);
77 msg.setChat(_CurrentChatProfile);
78 msg.send("EGS");
81 CSpawnGroupNpc& CSpawnBotNpc::spawnGrp() const
83 return static_cast<CSpawnGroupNpc&>(CSpawnBot::spawnGrp());
86 void CSpawnBotNpc::processEvent(CCombatInterface::CEvent const& event)
88 // no self aggro.
89 if (event._targetRow==event._originatorRow)
90 return;
92 if ((event._nature==ACTNATURE::FIGHT || event._nature==ACTNATURE::OFFENSIVE_MAGIC) && !getPersistent().ignoreOffensiveActions())
94 float aggro = event._weight;
95 if (aggro > -0.20f)
97 aggro = -0.20f;
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);
105 if(player)
107 CAIEntityPhysical *target=player->getVisualTarget();
108 if (target)
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;
123 H_AUTO(AIHpTrig);
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;
136 else
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())
145 return;
146 if (getNbActiveDynChats() > 0)
147 return;
148 if (isStuned())
149 return;
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();
158 if (sp)
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.
175 else
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
190 if (IsRingShard)
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);
205 _FacingTick = 0;
212 void CSpawnBotNpc::setFacing(CAngle theta)
214 if (_FacingTick == 0)
216 _FacingTheta = pos().theta();
219 setTheta(theta);
220 _FacingTick = CTimeInterface::gameCycle();
223 void CSpawnBotNpc::setUserModelId(const std::string &id)
225 _UserModelId = 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)
240 if (!state)
241 return;
242 CBotNpc const& botNpc = getPersistent();
244 FOREACHC(itChat, CCont<CAIStateChat>, state->chats())
246 if (!itChat->testCompatibility(botNpc))
247 continue;
249 // update chat information if any
250 CNpcChatProfileImp const* const chatProfile = botNpc.getChat();
251 if (chatProfile)
253 _CurrentChatProfile = CNpcChatProfileImp::combineChatProfile(*chatProfile, itChat->getChat());
254 // the chat profile has been combined, send it to EGS
255 sendInfoToEGS();
257 break;
261 std::vector<std::string> CSpawnBotNpc::getMultiLineInfoString() const
263 std::vector<std::string> container;
264 std::vector<std::string> strings;
267 pushTitle(container, "CSpawnBotNpc");
268 strings.clear();
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");
281 else
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);
294 return container;
297 void CSpawnBotNpc::beginBotChat(CBotPlayer* plr)
299 #ifdef NL_DEBUG
300 for (size_t i=0; i<_ActiveChats.size(); ++i)
302 if (_ActiveChats[i]==plr)
303 nlwarning("Chat pair added more than once!!!");
305 #endif
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)
317 continue;
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();
323 #ifdef NL_DEBUG
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!!!");
331 #endif
332 return;
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)
341 return;
342 CGroup* pGroup = getPersistent().getOwner();
343 CSpawnGroup* spGroup = pGroup->getSpawnObj();
344 CDynGrpBase* myDgb = pGroup->getGrpDynBase();
345 nlassert(myDgb);
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;
355 if (myfb!=NULL)
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)
366 continue;
368 // If bot is not in a dynamic system skip it
369 CDynGrpBase* otherDGB = otherPGroup->getGrpDynBase();
370 if (!otherDGB)
371 continue;
373 // If bot is not in our family skip it
374 CFamilyBehavior* otherFB = otherDGB->getFamilyBehavior();
375 if (otherFB!=myfb)
376 continue;
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())
385 continue;
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",
405 this,
406 it->first,
407 it->second,
408 propFactor);
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();
418 else
419 return AggroReturnDistCheckNpc;
422 void CSpawnBotNpc::setPlayerController(CBotPlayer* player)
424 if (player != NULL)
426 _PlayerController = new CPlayerControlNpc(player, this);
427 nlassert(_PlayerController->isValid());
430 else
432 _PlayerController = NULL;
436 void CSpawnBotNpc::setCurrentChatProfile(CNpcChatProfileImp* chatProfile)
438 if (chatProfile)
439 _CurrentChatProfile = *chatProfile;
440 else
441 _CurrentChatProfile.clear(); // clear the chat profile
444 CBotNpc& CSpawnBotNpc::getPersistent() const
446 return static_cast<CBotNpc&>(CSpawnBot::getPersistent());
449 //////////////////////////////////////////////////////////////////////////////
450 // CBotNpc //
451 //////////////////////////////////////////////////////////////////////////////
453 CBotNpc::CBotNpc(CGroup* owner, CAIAliasDescriptionNode* alias)
454 : CBot(owner, alias)
455 , _Sheet(NULL)
457 _Sheet = CBotNpcSheetPtr(new CBotNpcSheet(NULL));
458 _Sheet->setSheet(CBot::getSheet());
459 init();
462 CBotNpc::CBotNpc(CGroup* owner, uint32 alias, std::string const& name)
463 : CBot(owner, alias, name)
464 , _Sheet(NULL)
466 _Sheet = CBotNpcSheetPtr(new CBotNpcSheet(NULL));
467 _Sheet->setSheet(CBot::getSheet());
468 init();
471 CBotNpc::~CBotNpc()
473 if (isSpawned())
475 despawnBot();
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;
485 breakable
487 if (!state)
488 break;
490 if (state->shape().hasPatat() && state->shape().getRandomPosCount())
494 RYAI_MAP_CRUNCH::CWorldPosition wp;
495 state->shape().getRandomPos(wp);
496 _StartPos.setXY(wp);
497 maxTries--;
498 } while(!worldMap.setWorldPosition(_VerticalPos, wp, _StartPos) && maxTries);
499 _StartPos.setTheta(0); // to initialise among vertices deltas (no?).
500 break;
503 if (!state->shape().hasPoints())
504 break;
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);
513 --maxTries;
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);
534 _UserModelId = "";
535 return;
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 = "";
549 return;
551 _CustomLootTableId = customLootTableId;
555 std::string CBotNpc::getUserModelId()
557 return _UserModelId;
560 std::string CBotNpc::getCustomLootTableId()
562 return _CustomLootTableId;
565 void CBotNpc::setPrimAlias(uint32 alias)
567 _PrimAlias = alias;
571 uint32 CBotNpc::getPrimAlias() const
573 return _PrimAlias;
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());
627 if (ownerOutpost)
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
646 sendVPA ();
649 getSpawn()->spawnGrp().botHaveSpawn(this);
651 return true;
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()
671 if (!isSheetValid())
673 nlwarning("spawn() Aborted for bot '%s' due to bad sheet", getFullName().c_str());
674 return false;
677 if (!CBot::spawn())
678 return false;
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",
708 getName().c_str(),
709 visProp.Element.WeaponLeftHand,
710 visProp.Element.WeaponRightHand,
711 visProp.Element.Hat,
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
725 if (getSpawn())
727 CMirrors::setVisualPropertyA( getSpawn()->dataSetRow(), _VisualPropertyA );
728 CMirrors::setVisualPropertyB( getSpawn()->dataSetRow(), _VisualPropertyB );
729 CMirrors::setVisualPropertyC( getSpawn()->dataSetRow(), _VisualPropertyC );
733 bool CBotNpc::reSpawn(bool sendMessage)
735 if (!spawn())
736 return false;
738 getSpawn()->updateChat(grp().getCAIState());
739 return true;
742 void CBotNpc::despawnBot()
744 if (isSpawned())
746 getSpawn()->spawnGrp().botHaveDespawn(this);
747 CBot::despawnBot();
751 void CBotNpc::equipmentInit()
753 _Sheet->reset();
754 _Hat = false;
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
765 if (input.empty())
766 return;
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(),
775 input.c_str());
776 return;
779 // do something depending on keyword
780 if (NLMISC::nlstricmp(keyword,"ri")==0)
782 if (tail.empty())
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);
791 else
793 _Sheet->setRightItem(NLMISC::CSheetId(tail));
796 else if (NLMISC::nlstricmp(keyword,"li")==0)
798 if (tail.empty())
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);
807 else
809 _Sheet->setLeftItem(NLMISC::CSheetId(tail));
812 else if (NLMISC::nlstricmp(keyword,"HAT")==0 || NLMISC::nlstricmp(keyword,"IH")==0)
814 _Hat = true;
816 else if (
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
828 setColours(input);
830 else if(
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)
844 if (tail.empty())
846 nlwarning("No sheet name supplied for loot list entry: '%s'%s",getAliasFullName().c_str(),getAliasString().c_str());
848 else
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);
872 else
874 nlwarning("Bot '%s'%s:failed to parse equipment argument: '%s'",
875 getAliasFullName().c_str(),
876 getAliasString().c_str(),
877 input.c_str());
882 Colors are something like that:
883 3D INTERFACE MP
884 0: ROUGE ROUGE RED
885 1: BEIGE ORANGE BEIGE
886 2: VERT CITRON VERT CITRON GREEN
887 3: VERT VERT TURQUOISE
888 4: BLEU BLEU BLUE
889 5: ROUGE dark ROUGE (normal) CRIMSON
890 6: BLANC JAUNE WHITE
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
913 if (input.empty())
914 return;
916 if (!init)
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"));
920 if (varPtr!=NULL)
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
931 uint32 idx;
932 NLMISC::fromString(idxStr, idx);
933 if (NLMISC::toString(idx)==idxStr && idx<numColours)
934 colourNames[idx].push_back(name);
938 init = true;
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());
946 return;
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;
962 std::string colour;
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
968 uint32 idx;
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
973 if (idx<numColours)
974 results.push_back(idx);
975 else
976 nlwarning("Bot '%s'%s: failed to identify colour: '%s' in line: '%s'",
977 getAliasFullName().c_str(),
978 getAliasString().c_str(),
979 colour.c_str(),
980 input.c_str());
982 else
984 // try to find an entry in the colour list that matches the 'colour' string
985 bool done=false;
986 size_t i;
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);
995 done=true;
1000 if (i==numColours)
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(),
1011 input.c_str());
1012 return;
1014 NLMISC::CRandom generator;
1015 sint32 seed = getAlias();
1016 uint8* p = (uint8*)&seed;
1017 p[1] ^= p[0];
1018 p[2] ^= p[0];
1019 p[3] ^= p[0];
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)
1046 // head color
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)
1053 // arms color
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)
1060 // arms color
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)
1067 // arms color
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)
1074 // arms color
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)
1081 // arms color
1082 generator.srand(seed+989*0x10000);
1083 uint8 val = (uint8)results[generator.rand((uint16)results.size()-1)];
1084 _Sheet->setColorFeets(val);
1087 else
1089 nlwarning("Bot '%s'%s: failed to parse colours argument: '%s'",
1090 getAliasFullName().c_str(),
1091 getAliasString().c_str(),
1092 input.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
1104 if(input.empty())
1105 return;
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());
1117 return;
1120 // load val from tail
1121 // accept 64bit hex value
1122 uint64 val;
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;
1141 else
1143 nlwarning("Bot '%s'%s: failed to parse visual property argument: '%s'",
1144 getAliasFullName().c_str(),
1145 getAliasString().c_str(),
1146 input.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;
1165 equipmentInit();
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);
1189 --maxTries;
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);
1202 else
1203 CWorldContainer::calcNearestWPosFromPosAnRadius(_VerticalPos, worldMap, pos, _StartPos, 10, 200, CWorldContainer::CPosValidatorDefault());
1205 #ifdef NL_DEBUG
1206 if (!pos.isValid() && !isStuck())
1208 nlwarning("Npc Spawn Pos Error %s", pos.toString().c_str());
1210 #endif
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);
1234 sheetChanged();
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()
1241 if (getSpawnObj())
1243 // Get bot state
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);
1253 // Delete old bot
1254 CMirrors::removeEntity(getSpawnObj()->getEntityId());
1255 setSpawn(NULL); // automatic smart pointer deletion
1256 notifyBotDespawn();
1258 // Finalize spawn object creation
1259 if (!finalizeSpawn(botWPos, spawnTheta, botMeterSize))
1260 return;
1262 // :KLUDGE: Both finalizeSpawn and finalizeSpawnNpc are called,
1263 // sheetChanged has a strange herited meaning and may confuse future
1264 // coders
1265 // :TODO: Clean that mess and find a more elegant C++ solution to the
1266 // problem
1267 finalizeSpawnNpc();
1271 //////////////////////////////////////////////////////////////////////////////
1272 // Commmands //
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","")
1278 if (args.size()>1)
1279 return false;
1281 if (args.size()==1)
1282 NLMISC::fromString(args[0], VerboseLog);
1284 nlinfo("VerboseLogging is %s",VerboseLog?"ON":"OFF");
1285 return true;
1287 // virtual function so do not need to be inlined
1288 bool CBotNpc::getFaunaBotUseBotName() const
1290 return _FaunaBotUseBotName;