499 lines
18 KiB
C++
499 lines
18 KiB
C++
// Ryzom - MMORPG Framework <http://dev.ryzom.com/projects/ryzom/>
|
|
// Copyright (C) 2010 Winch Gate Property Limited
|
|
//
|
|
// This program is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU Affero General Public License as
|
|
// published by the Free Software Foundation, either version 3 of the
|
|
// License, or (at your option) any later version.
|
|
//
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU Affero General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU Affero General Public License
|
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
#include "stdpch.h"
|
|
#include "npc_icon.h"
|
|
#include "ingame_database_manager.h"
|
|
#include "game_share/generic_xml_msg_mngr.h"
|
|
#include "entities.h"
|
|
#include "net_manager.h"
|
|
|
|
using namespace std;
|
|
using namespace NLMISC;
|
|
|
|
CNPCIconCache* CNPCIconCache::_Instance = NULL;
|
|
|
|
// Time after which the state of a NPC is considered obsolete and must be refreshed (because it's in gamecycle, actual time increases if server slows down, to avoid server congestion)
|
|
NLMISC::TGameCycle CNPCIconCache::_CacheRefreshTimerDelay = NPC_ICON::DefaultClientNPCIconRefreshTimerDelayGC;
|
|
|
|
// Time between updates of the "catchall timer"
|
|
NLMISC::TGameCycle CNPCIconCache::_CatchallTimerPeriod = NPC_ICON::DefaultClientNPCIconRefreshTimerDelayGC;
|
|
|
|
extern CEntityManager EntitiesMngr;
|
|
extern CGenericXmlMsgHeaderManager GenericMsgHeaderMngr;
|
|
extern CNetManager NetMngr;
|
|
|
|
|
|
// #pragma optimize ("", off)
|
|
|
|
|
|
CNPCIconCache::CNPCIconCache() : _LastRequestTimestamp(0), _LastTimerUpdateTimestamp(0), _Enabled(true)
|
|
{
|
|
_Icons[NPC_ICON::IconNone].init("", "");
|
|
_Icons[NPC_ICON::IconNotAMissionGiver].init("", "");
|
|
_Icons[NPC_ICON::IconListHasOutOfReachMissions].init("mission_available.tga", ""); //"MP_Blood.tga"
|
|
_Icons[NPC_ICON::IconListHasAlreadyTakenMissions].init("ICO_Task_Generic.tga", "r2ed_tool_redo");
|
|
_Icons[NPC_ICON::IconListHasAvailableMission].init("mission_available.tga", "", CViewRadar::MissionList); //"MP_Wood.tga"
|
|
_Icons[NPC_ICON::IconAutoHasUnavailableMissions].init("spe_com.tga", "");
|
|
_Icons[NPC_ICON::IconAutoHasAvailableMission].init("spe_com.tga", "", CViewRadar::MissionAuto); //"MP_Oil.tga"
|
|
_Icons[NPC_ICON::IconStepMission].init("mission_step.tga", "", CViewRadar::MissionStep); //"MP_Shell.tga"
|
|
|
|
_DescriptionsToRequest.reserve(256);
|
|
}
|
|
|
|
void CNPCIconCache::release()
|
|
{
|
|
if (_Instance)
|
|
{
|
|
delete _Instance;
|
|
_Instance = NULL;
|
|
}
|
|
}
|
|
|
|
const CNPCIconCache::CNPCIconDesc& CNPCIconCache::getNPCIcon(const CEntityCL *entity, bool bypassEnabled)
|
|
{
|
|
// Not applicable? Most entities (creatures, characters) have a null key here.
|
|
BOMB_IF(!entity, "NULL entity in getNPCIcon", return _Icons[NPC_ICON::IconNone]);
|
|
TNPCIconCacheKey npcIconCacheKey = CNPCIconCache::entityToKey(entity);
|
|
if (npcIconCacheKey == 0)
|
|
return _Icons[NPC_ICON::IconNone];
|
|
|
|
// Is system disabled?
|
|
if ((!enabled()) && !bypassEnabled)
|
|
return _Icons[NPC_ICON::IconNone];
|
|
|
|
// This method must be reasonably fast, because it constantly gets called by the radar view
|
|
H_AUTO(GetNPCIconWithKey);
|
|
|
|
// Not applicable (more checks)?
|
|
if (!entity->canHaveMissionIcon())
|
|
return _Icons[NPC_ICON::IconNone];
|
|
if (!entity->isFriend()) // to display icons in the radar, we need the Contextual property to be received as soon as possible
|
|
return _Icons[NPC_ICON::IconNone];
|
|
|
|
// Temporarily not shown if the player is in interaction with the NPC
|
|
if (UserEntity->interlocutor() != CLFECOMMON::INVALID_SLOT)
|
|
{
|
|
CEntityCL *interlocutorEntity = EntitiesMngr.entity(UserEntity->interlocutor());
|
|
if (interlocutorEntity && (entityToKey(interlocutorEntity) == npcIconCacheKey))
|
|
return _Icons[NPC_ICON::IconNone];
|
|
}
|
|
if (UserEntity->trader() != CLFECOMMON::INVALID_SLOT)
|
|
{
|
|
CEntityCL *traderEntity = EntitiesMngr.entity(UserEntity->trader());
|
|
if (traderEntity && (entityToKey(traderEntity) == npcIconCacheKey))
|
|
return _Icons[NPC_ICON::IconNone];
|
|
}
|
|
|
|
// 1. Test if the NPC is involved in a current goal
|
|
if (isNPCaCurrentGoal(npcIconCacheKey))
|
|
return _Icons[NPC_ICON::IconStepMission];
|
|
|
|
// 2. Compute "has mission to take": take from cache, or query the server
|
|
H_AUTO(GetNPCIcon_GIVER);
|
|
CMissionGiverMap::iterator img = _MissionGivers.find(npcIconCacheKey);
|
|
if (img != _MissionGivers.end())
|
|
{
|
|
CNPCMissionGiverDesc& giver = (*img).second;
|
|
if (giver.getState() != NPC_ICON::AwaitingFirstData)
|
|
{
|
|
// Ask the server to refresh the state if the information is old
|
|
// but only known mission givers that have a chance to propose new missions
|
|
if ((giver.getState() != NPC_ICON::NotAMissionGiver) &&
|
|
// (giver.getState() != NPC_ICON::ListHasAlreadyTakenMissions) && // commented out because it would not refresh in case an auto mission become available
|
|
(!giver.isDescTransient()))
|
|
{
|
|
NLMISC::TGameCycle informationAge = NetMngr.getCurrentServerTick() - giver.getLastUpdateTimestamp();
|
|
if (informationAge > _CacheRefreshTimerDelay)
|
|
{
|
|
queryMissionGiverData(npcIconCacheKey);
|
|
giver.setDescTransient();
|
|
}
|
|
}
|
|
|
|
// Return the icon depending on the state in the cache
|
|
return _Icons[giver.getState()]; // TNPCIconId maps TNPCMissionGiverState
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Create mission giver entry and query the server
|
|
CNPCMissionGiverDesc giver;
|
|
CMissionGiverMap::iterator itg = _MissionGivers.insert(make_pair(npcIconCacheKey, giver)).first;
|
|
queryMissionGiverData(npcIconCacheKey);
|
|
//(*itg).second.setDescTransient(); // already made transient by constructor
|
|
}
|
|
|
|
return _Icons[NPC_ICON::IconNone];
|
|
}
|
|
|
|
#define getArraySize(a) (sizeof(a)/sizeof(a[0]))
|
|
|
|
void CNPCIconCache::addObservers()
|
|
{
|
|
// Disabled?
|
|
if (!enabled())
|
|
return;
|
|
|
|
// Mission Journal
|
|
static const char *missionStartStopLeavesToMonitor [2] = {"TITLE", "FINISHED"};
|
|
IngameDbMngr.addBranchObserver( IngameDbMngr.getNodePtr(), "MISSIONS", MissionStartStopObserver, missionStartStopLeavesToMonitor, getArraySize(missionStartStopLeavesToMonitor));
|
|
static const char *missionNpcAliasLeavesToMonitor [1] = {"NPC_ALIAS"};
|
|
IngameDbMngr.addBranchObserver( IngameDbMngr.getNodePtr(), "MISSIONS", MissionNpcAliasObserver, missionNpcAliasLeavesToMonitor, getArraySize(missionNpcAliasLeavesToMonitor));
|
|
|
|
// Skills
|
|
static const char *skillLeavesToMonitor [2] = {"SKILL", "BaseSKILL"};
|
|
IngameDbMngr.addBranchObserver( IngameDbMngr.getNodePtr(), "CHARACTER_INFO:SKILLS", MissionPrerequisitEventObserver, skillLeavesToMonitor, getArraySize(skillLeavesToMonitor));
|
|
|
|
// Owned Items
|
|
static const char *bagLeavesToMonitor [1] = {"SHEET"}; // just saves 2000 bytes or so (500 * observer pointer entry in vector) compared to one observer per bag slot
|
|
IngameDbMngr.addBranchObserver( IngameDbMngr.getNodePtr(), "INVENTORY:BAG", MissionPrerequisitEventObserver, bagLeavesToMonitor, getArraySize(bagLeavesToMonitor));
|
|
|
|
// Worn Items
|
|
IngameDbMngr.addBranchObserver( "INVENTORY:HAND", &MissionPrerequisitEventObserver);
|
|
IngameDbMngr.addBranchObserver( "INVENTORY:EQUIP", &MissionPrerequisitEventObserver);
|
|
|
|
// Known Bricks
|
|
IngameDbMngr.addBranchObserver( "BRICK_FAMILY", &MissionPrerequisitEventObserver);
|
|
|
|
// For other events, search for calls of onEventForMissionAvailabilityForThisChar()
|
|
}
|
|
|
|
void CNPCIconCache::removeObservers()
|
|
{
|
|
// Disabled?
|
|
if (!enabled())
|
|
return;
|
|
|
|
// Mission Journal
|
|
IngameDbMngr.getNodePtr()->removeBranchObserver("MISSIONS", MissionStartStopObserver);
|
|
IngameDbMngr.getNodePtr()->removeBranchObserver("MISSIONS", MissionNpcAliasObserver);
|
|
|
|
// Skills
|
|
IngameDbMngr.getNodePtr()->removeBranchObserver("CHARACTER_INFO:SKILLS", MissionPrerequisitEventObserver);
|
|
|
|
// Owned Items
|
|
IngameDbMngr.getNodePtr()->removeBranchObserver("INVENTORY:BAG", MissionPrerequisitEventObserver);
|
|
|
|
// Worn Items
|
|
IngameDbMngr.getNodePtr()->removeBranchObserver("INVENTORY:HAND", MissionPrerequisitEventObserver);
|
|
IngameDbMngr.getNodePtr()->removeBranchObserver("INVENTORY:EQUIP", MissionPrerequisitEventObserver);
|
|
|
|
// Known Bricks
|
|
IngameDbMngr.getNodePtr()->removeBranchObserver("BRICK_FAMILY", MissionPrerequisitEventObserver);
|
|
}
|
|
|
|
void CNPCIconCache::CMissionStartStopObserver::update(ICDBNode* node)
|
|
{
|
|
// Every time a mission in progress is started or stopped, refresh the icon for visible NPCs (including mission giver information)
|
|
CNPCIconCache::getInstance().onEventForMissionInProgress();
|
|
}
|
|
|
|
void CNPCIconCache::CMissionNpcAliasObserver::update(ICDBNode* node)
|
|
{
|
|
CNPCIconCache::getInstance().onNpcAliasChangedInMissionGoals();
|
|
}
|
|
|
|
void CNPCIconCache::CMissionPrerequisitEventObserver::update(ICDBNode* node)
|
|
{
|
|
// Every time a mission in progress changes, refresh the icon for the related npc
|
|
CNPCIconCache::getInstance().onEventForMissionAvailabilityForThisChar();
|
|
}
|
|
|
|
void CNPCIconCache::onEventForMissionAvailabilityForThisChar()
|
|
{
|
|
// Disabled?
|
|
if (!enabled())
|
|
return;
|
|
|
|
queryAllVisibleMissionGiverData(0);
|
|
}
|
|
|
|
void CNPCIconCache::queryMissionGiverData(TNPCIconCacheKey npcIconCacheKey)
|
|
{
|
|
_DescriptionsToRequest.push_back(npcIconCacheKey);
|
|
//static set<TNPCIconCacheKey> requests1;
|
|
//requests1.insert(npcIconCacheKey);
|
|
//nldebug("%u: queryMissionGiverData %u (total %u)", NetMngr.getCurrentServerTick(), npcIconCacheKey, requests1.size());
|
|
|
|
}
|
|
|
|
void CNPCIconCache::queryAllVisibleMissionGiverData(NLMISC::TGameCycle olderThan)
|
|
{
|
|
// Request an update for all npcs (qualifying, i.e. that have missions) in vision
|
|
for (uint i=0; i<EntitiesMngr.entities().size(); ++i)
|
|
{
|
|
CEntityCL *entity = EntitiesMngr.entity(i);
|
|
if (!entity || !(entity->canHaveMissionIcon() && entity->isFriend()))
|
|
continue;
|
|
TNPCIconCacheKey npcIconCacheKey = CNPCIconCache::entityToKey(entity);
|
|
CMissionGiverMap::iterator img = _MissionGivers.find(npcIconCacheKey);
|
|
if (img == _MissionGivers.end())
|
|
continue; // if the NPC does not have an entry yet, it will be created by getNPCIcon()
|
|
|
|
// Refresh only known mission givers that have a chance to propose new missions
|
|
CNPCMissionGiverDesc& giver = (*img).second;
|
|
if (giver.getState() == NPC_ICON::NotAMissionGiver)
|
|
continue;
|
|
// if (giver.getState() == NPC_ICON::ListHasAlreadyTakenMissions)
|
|
// continue; // commented out because it would not refresh in case an auto mission becomes available
|
|
|
|
if (olderThan != 0)
|
|
{
|
|
// Don't refresh desscriptions already awaiting an update
|
|
if (giver.isDescTransient())
|
|
continue;
|
|
|
|
// Don't refresh NPCs having data more recent than specified
|
|
NLMISC::TGameCycle informationAge = NetMngr.getCurrentServerTick() - giver.getLastUpdateTimestamp();
|
|
if (informationAge <= olderThan)
|
|
continue;
|
|
|
|
// Don't refresh NPC being involved in a mission goal (the step icon has higher priority over the giver icon)
|
|
// If later the NPC is no more involved before the information is considered old, it will show
|
|
// the same giver state until the information is considered old. That's why we let refresh
|
|
// the NPC when triggered by an event (olderThan == 0).
|
|
if (isNPCaCurrentGoal(npcIconCacheKey))
|
|
continue;
|
|
}
|
|
|
|
_DescriptionsToRequest.push_back(npcIconCacheKey);
|
|
giver.setDescTransient();
|
|
|
|
//static set<TNPCIconCacheKey> requests2;
|
|
//requests2.insert(npcIconCacheKey);
|
|
//nldebug("%u: queryAllVisibleMissionGiverData %u (total %u)", NetMngr.getCurrentServerTick(), npcIconCacheKey, requests2.size());
|
|
}
|
|
}
|
|
|
|
void CNPCIconCache::update()
|
|
{
|
|
// Every CatchallTimerPeriod, browse visible entities and refresh the ones with outdated state
|
|
// (e.g. the ones not displayed in radar).
|
|
if (NetMngr.getCurrentServerTick() > _LastTimerUpdateTimestamp + _CatchallTimerPeriod)
|
|
{
|
|
_LastTimerUpdateTimestamp = NetMngr.getCurrentServerTick();
|
|
|
|
// Disabled?
|
|
if (!enabled())
|
|
return;
|
|
|
|
queryAllVisibleMissionGiverData(_CacheRefreshTimerDelay);
|
|
}
|
|
|
|
// Every tick update at most (2 cycles actually, cf. server<->client communication frequency),
|
|
// send all pending requests in a single message.
|
|
if (NetMngr.getCurrentServerTick() > _LastRequestTimestamp)
|
|
{
|
|
if (!_DescriptionsToRequest.empty())
|
|
{
|
|
CBitMemStream out;
|
|
GenericMsgHeaderMngr.pushNameToStream("NPC_ICON:GET_DESC", out);
|
|
uint8 nb8 = uint8(_DescriptionsToRequest.size() & 0xff); // up to vision size (255 i.e. 256 minus user)
|
|
out.serial(nb8);
|
|
for (CSmallKeyList::const_iterator ikl=_DescriptionsToRequest.begin(); ikl!=_DescriptionsToRequest.end(); ++ikl)
|
|
{
|
|
TNPCIconCacheKey key = *ikl;
|
|
out.serial(key);
|
|
}
|
|
NetMngr.push(out);
|
|
//nldebug("%u: Pushing %hu NPC desc requests", NetMngr.getCurrentServerTick(), nb8);
|
|
_DescriptionsToRequest.clear();
|
|
}
|
|
_LastRequestTimestamp = NetMngr.getCurrentServerTick();
|
|
}
|
|
}
|
|
|
|
void CNPCIconCache::onEventForMissionInProgress()
|
|
{
|
|
// Disabled?
|
|
if (!enabled())
|
|
return;
|
|
|
|
// Immediately reflect the mission journal (Step icons)
|
|
refreshIconsOfScene(true);
|
|
|
|
// Ask the server to update availability status (will refresh icons if there is at least one change)
|
|
onEventForMissionAvailabilityForThisChar();
|
|
}
|
|
|
|
void CNPCIconCache::onNpcAliasChangedInMissionGoals()
|
|
{
|
|
// Disabled?
|
|
if (!enabled())
|
|
return;
|
|
|
|
// Update the storage of keys having a current mission goal.
|
|
storeKeysOfCurrentGoals();
|
|
|
|
// Immediately reflect the mission journal (Step icons)
|
|
refreshIconsOfScene(true);
|
|
}
|
|
|
|
bool CNPCIconCache::isNPCaCurrentGoal(TNPCIconCacheKey npcIconCacheKey) const
|
|
{
|
|
// There aren't many simultaneous goals, we can safely browse the vector
|
|
for (CSmallKeyList::const_iterator ikl=_KeysOfCurrentGoals.begin(); ikl!=_KeysOfCurrentGoals.end(); ++ikl)
|
|
{
|
|
if ((*ikl) == npcIconCacheKey)
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void CNPCIconCache::storeKeysOfCurrentGoals()
|
|
{
|
|
// This event is very unfrequent, and the number of elements of _KeysOfCurrentGoals is usually very small
|
|
// (typically 0 to 3, while theoretical max is 15*20) so we don't mind rebuilding the list.
|
|
_KeysOfCurrentGoals.clear();
|
|
CCDBNodeBranch *missionNode = safe_cast<CCDBNodeBranch*>(IngameDbMngr.getNodePtr()->getNode(ICDBNode::CTextId("MISSIONS")));
|
|
BOMB_IF (!missionNode, "MISSIONS node missing in DB", return);
|
|
uint nbCurrentMissionSlots = missionNode->getNbNodes();
|
|
for (uint i=0; i!=nbCurrentMissionSlots; ++i)
|
|
{
|
|
ICDBNode *missionEntry = missionNode->getNode((uint16)i);
|
|
ICDBNode::CTextId titleNode("TITLE");
|
|
if (missionEntry->getProp(titleNode) == 0)
|
|
continue;
|
|
|
|
CCDBNodeBranch *stepsToDoNode = safe_cast<CCDBNodeBranch*>(missionEntry->getNode(ICDBNode::CTextId("GOALS")));
|
|
BOMB_IF(!stepsToDoNode, "GOALS node missing in MISSIONS DB", return);
|
|
uint nbGoals = stepsToDoNode->getNbNodes();
|
|
for (uint j=0; j!=nbGoals; ++j)
|
|
{
|
|
ICDBNode *stepNode = stepsToDoNode->getNode((uint16)j);
|
|
CCDBNodeLeaf *aliasNode = safe_cast<CCDBNodeLeaf*>(stepNode->getNode(ICDBNode::CTextId("NPC_ALIAS")));
|
|
BOMB_IF(!aliasNode, "NPC_ALIAS node missing in MISSIONS DB", return);
|
|
TNPCIconCacheKey npcIconCacheKey = (TNPCIconCacheKey)aliasNode->getValue32();
|
|
if (npcIconCacheKey != 0)
|
|
_KeysOfCurrentGoals.push_back(npcIconCacheKey);
|
|
}
|
|
}
|
|
}
|
|
|
|
void CNPCIconCache::refreshIconsOfScene(bool force)
|
|
{
|
|
// Browse all NPCs in vision, and refresh their inscene interface
|
|
for (uint i=0; i<EntitiesMngr.entities().size(); ++i)
|
|
{
|
|
CEntityCL *entity = EntitiesMngr.entity(i);
|
|
if (!entity) continue;
|
|
|
|
CMissionGiverMap::iterator it = _MissionGivers.find(CNPCIconCache::entityToKey(entity));
|
|
if ((it!=_MissionGivers.end()) && ((*it).second.hasChanged() || force))
|
|
{
|
|
EntitiesMngr.refreshInsceneInterfaceOfFriendNPC(i);
|
|
(*it).second.setChanged(false);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool CNPCIconCache::onReceiveMissionAvailabilityForThisChar(TNPCIconCacheKey npcIconCacheKey, NPC_ICON::TNPCMissionGiverState state)
|
|
{
|
|
CMissionGiverMap::iterator img = _MissionGivers.find(npcIconCacheKey);
|
|
BOMB_IF(img == _MissionGivers.end(), "Mission Giver " << npcIconCacheKey << "not found", return false);
|
|
|
|
//if (state != NPC_ICON::NotAMissionGiver)
|
|
//{
|
|
// static set<TNPCIconCacheKey> qualifs;
|
|
// qualifs.insert(npcIconCacheKey);
|
|
// nldebug("NPC %u qualifies (total=%u)", npcIconCacheKey, qualifs.size());
|
|
//}
|
|
|
|
return (*img).second.updateMissionAvailabilityForThisChar(state);
|
|
}
|
|
|
|
bool CNPCMissionGiverDesc::updateMissionAvailabilityForThisChar(NPC_ICON::TNPCMissionGiverState state)
|
|
{
|
|
_HasChanged = (state != _MissionGiverState);
|
|
_MissionGiverState = state;
|
|
_LastUpdateTimestamp = NetMngr.getCurrentServerTick();
|
|
_IsDescTransient = false;
|
|
return _HasChanged;
|
|
}
|
|
|
|
void CNPCIconCache::setMissionGiverTimer(NLMISC::TGameCycle delay)
|
|
{
|
|
_CacheRefreshTimerDelay = delay;
|
|
_CatchallTimerPeriod = delay;
|
|
}
|
|
|
|
std::string CNPCIconCache::getDump() const
|
|
{
|
|
string s = toString("System %s\nCurrent timers: %u %u\n", _Enabled?"enabled":"disabled", _CacheRefreshTimerDelay, _CatchallTimerPeriod);
|
|
s += toString("%u NPCs in mission giver map:\n", _MissionGivers.size());
|
|
for (CMissionGiverMap::const_iterator img=_MissionGivers.begin(); img!=_MissionGivers.end(); ++img)
|
|
{
|
|
const CNPCMissionGiverDesc& giver = (*img).second;
|
|
s += toString("NPC %u: ", (*img).first) + giver.getDump() + "\n";
|
|
}
|
|
s += "Current NPC goals:\n";
|
|
for (CSmallKeyList::const_iterator ikl=_KeysOfCurrentGoals.begin(); ikl!=_KeysOfCurrentGoals.end(); ++ikl)
|
|
{
|
|
s += toString("NPC %u", (*ikl));
|
|
}
|
|
return s;
|
|
}
|
|
|
|
std::string CNPCMissionGiverDesc::getDump() const
|
|
{
|
|
return toString("%u [%u s ago]", _MissionGiverState, (NetMngr.getCurrentServerTick()-_LastUpdateTimestamp)/10);
|
|
}
|
|
|
|
void CNPCIconCache::setEnabled(bool b)
|
|
{
|
|
if (!_Enabled && b)
|
|
{
|
|
_Enabled = b;
|
|
addObservers(); // with _Enabled true
|
|
storeKeysOfCurrentGoals(); // import from the DB
|
|
refreshIconsOfScene(true);
|
|
}
|
|
else if (_Enabled && !b)
|
|
{
|
|
removeObservers(); // with _Enabled true
|
|
_Enabled = b;
|
|
refreshIconsOfScene(true);
|
|
}
|
|
}
|
|
|
|
#ifndef FINAL_VERSION
|
|
#error FINAL_VERSION should be defined (0 or 1)
|
|
#endif
|
|
|
|
#if !FINAL_VERSION
|
|
|
|
NLMISC_COMMAND(dumpNPCIconCache, "Display descriptions of NPCs", "")
|
|
{
|
|
log.displayNL(CNPCIconCache::getInstance().getDump().c_str());
|
|
return true;
|
|
}
|
|
|
|
NLMISC_COMMAND(queryMissionGiverData, "Query mission giver data for the specified alias", "<alias>")
|
|
{
|
|
if (args.empty())
|
|
return false;
|
|
uint32 alias;
|
|
NLMISC::fromString(args[0], alias);
|
|
|
|
CNPCIconCache::getInstance().queryMissionGiverData(alias);
|
|
//giver.setDescTransient();
|
|
return true;
|
|
}
|
|
|
|
#endif
|
|
|
|
//#pragma optimize ("", on)
|