// 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 "bar_manager.h"
#include "interface_manager.h"
#include "nel/gui/interface_expr.h"
#include "../time_client.h"


using namespace std;
using namespace NLMISC;


// ***************************************************************************
// LOG, for debug
#define ALLOW_BAR_LOG 0

// don't change this (ensure no bug in final version)
#if FINAL_VERSION
#undef ALLOW_BAR_LOG
#define ALLOW_BAR_LOG 0
#endif
#if ALLOW_BAR_LOG
#	define barInfoLog nlinfo
#else // NL_RELEASE
#	ifdef NL_COMP_VC71
#		define barInfoLog __noop
#	else
#		define barInfoLog 0&&
#	endif
#endif // NL_RELEASE


static const char *entryTypeToStr(CBarManager::TEntryType et)
{
	switch(et)
	{
	case CBarManager::EntityType: return "Entity"; break;
	case CBarManager::TeamMemberType: return "TeamMember"; break;
	case CBarManager::AnimalType: return "Animal"; break;
	case CBarManager::TargetType: return "Target"; break;
	default: return "Unknown (Error!!)"; break;
	}
}


// ***************************************************************************
CBarManager		*CBarManager::_Instance= NULL;


// ***************************************************************************
// ***************************************************************************
// ***************************************************************************
// ***************************************************************************


// ***************************************************************************
CBarManager::CBarDataEntry::CBarDataEntry()
{
	clear();
	resetDB();
}

// ***************************************************************************
void	CBarManager::CBarDataEntry::clear()
{
	DataSetId= CLFECOMMON::INVALID_CLIENT_DATASET_INDEX;
	BarInfo.reset();
}

// ***************************************************************************
void	CBarManager::CBarDataEntry::resetDB()
{
	UIDIn= NULL;
	PresentIn= NULL;
	for(uint sc=0;sc<SCORES::NUM_SCORES;sc++)
	{
		ScoreIn[sc]= NULL;
		ScoreOut[sc]= NULL;
	}
}

// ***************************************************************************
void	CBarManager::CBarDataEntry::connectDB(const std::string &baseDBin, const std::string &baseDBout, const std::string &presentDB,
			const std::string &hpDB, const std::string &sapDB, const std::string &staDB, const std::string &focusDB)
{
	CInterfaceManager	*pIM= CInterfaceManager::getInstance();

	// can only manage 4 scores here
	nlctassert(SCORES::NUM_SCORES==4);

	// try to connect each input entry (don't create)
	if(!baseDBin.empty())
	{
		UIDIn= NLGUI::CDBManager::getInstance()->getDbProp(baseDBin+"UID", false);
		if(!presentDB.empty())
			PresentIn= NLGUI::CDBManager::getInstance()->getDbProp(baseDBin+presentDB, false);
		if(!hpDB.empty())
			ScoreIn[SCORES::hit_points]= NLGUI::CDBManager::getInstance()->getDbProp(baseDBin+hpDB, false);
		if(!sapDB.empty())
			ScoreIn[SCORES::sap]= NLGUI::CDBManager::getInstance()->getDbProp(baseDBin+sapDB, false);
		if(!staDB.empty())
			ScoreIn[SCORES::stamina]= NLGUI::CDBManager::getInstance()->getDbProp(baseDBin+staDB, false);
		if(!focusDB.empty())
			ScoreIn[SCORES::focus]= NLGUI::CDBManager::getInstance()->getDbProp(baseDBin+focusDB, false);
	}

	// try to connect each output entry (don't create)
	if(!baseDBout.empty())
	{
		if(!hpDB.empty())
			ScoreOut[SCORES::hit_points]= NLGUI::CDBManager::getInstance()->getDbProp(baseDBout+hpDB, false);
		if(!sapDB.empty())
			ScoreOut[SCORES::sap]= NLGUI::CDBManager::getInstance()->getDbProp(baseDBout+sapDB, false);
		if(!staDB.empty())
			ScoreOut[SCORES::stamina]= NLGUI::CDBManager::getInstance()->getDbProp(baseDBout+staDB, false);
		if(!focusDB.empty())
			ScoreOut[SCORES::focus]= NLGUI::CDBManager::getInstance()->getDbProp(baseDBout+focusDB, false);
	}
}

// ***************************************************************************
void	CBarManager::CBarDataEntry::flushDBOut()
{
	for(uint sc=0;sc<SCORES::NUM_SCORES;sc++)
	{
		if(ScoreOut[sc])
			ScoreOut[sc]->setValue8(BarInfo.Score[sc]);
	}
}

// ***************************************************************************
void	CBarManager::CBarDataEntry::modifyFromDBIn(CBarInfo &barInfo) const
{
	for(uint sc=0;sc<SCORES::NUM_SCORES;sc++)
	{
		if(ScoreIn[sc])
			barInfo.Score[sc]= ScoreIn[sc]->getValue8();
	}
}


// ***************************************************************************
// ***************************************************************************
// ***************************************************************************
// ***************************************************************************


// ***************************************************************************
CBarManager::CBarManager()
{
	// if more than 256 entities, must change MaxEntity
	nlctassert(sizeof(CLFECOMMON::TCLEntityId)==1);

	// Allocate the array now
	nlctassert(MaxEntryType==4);
	_EntryBars[EntityType].resize(MaxEntity);
	_EntryBars[TeamMemberType].resize(MaxTeamMember);
	_EntryBars[AnimalType].resize(MaxAnimal);
	_EntryBars[TargetType].resize(MaxTarget);

	// no msg still sent
	_LastUserBarMsgNumber= 0;
}

// ***************************************************************************
void CBarManager::releaseInstance()
{
	if( _Instance )
	{
		delete _Instance;
		_Instance = NULL;
	}
}

// ***************************************************************************
void		CBarManager::initInGame()
{
	CInterfaceManager	*pIM= CInterfaceManager::getInstance();

	//resetShardSpecificData(); // directly called by CFarTP::disconnectFromPreviousShard()

	/* *** Verify that MaxTeamMember is correct according to database
		change MaxTeamMember if no more the case
	*/
	uint	i=0;
	while( NLGUI::CDBManager::getInstance()->getDbProp(toString("SERVER:GROUP:%d:NAME", i), false) )
		i++;
	nlassert(i==MaxTeamMember);


	// *** create connexion to the Local Output database
	for(i=0;i<_EntryBars[TeamMemberType].size();i++)
	{
		// don't connect FOCUS, since not setuped by SERVER
		_EntryBars[TeamMemberType][i].connectDB(
			toString("SERVER:GROUP:%d:",i),
			toString("UI:VARIABLES:BARS:TEAM:%d:",i),
			"PRESENT",
			"HP", "SAP", "STA", "");
	}
	for(i=0;i<_EntryBars[AnimalType].size();i++)
	{
		// don't connect STA, SAP and FOCUS for animal, since they don't have
		_EntryBars[AnimalType][i].connectDB(
			toString("SERVER:PACK_ANIMAL:BEAST%d:",i),
			toString("UI:VARIABLES:BARS:ANIMAL:%d:",i),
			"STATUS",
			"HP", "", "", "");
	}
	nlassert(_EntryBars[TargetType].size()==1);
	_EntryBars[TargetType][0].connectDB(
		"SERVER:TARGET:BARS:",
		"UI:VARIABLES:BARS:TARGET:",
		"",	// no present flag for target (not so important)
		"HP", "SAP", "STA", "FOCUS");

	// NB: don't connect the DB for entities, since CEntityCL read it directly from getBarsByEntityId() (simpler and faster)


	// *** init _EntryScoreFlags
	nlctassert(MaxEntryType==4);
	nlctassert(SCORES::NUM_SCORES==4);
	// For each entry type, tells what score they can affect (see DB connection above)
	_EntryScoreFlags[EntityType]= HpFlag | SapFlag | StaFlag | FocusFlag;	// all
	_EntryScoreFlags[TeamMemberType]= HpFlag | SapFlag | StaFlag;			// anything but focus
	_EntryScoreFlags[AnimalType]= HpFlag;									// Hp only
	_EntryScoreFlags[TargetType]= HpFlag | SapFlag | StaFlag | FocusFlag;	// all


	// *** create connexion for User Bar mgt
	// user now can only manage 4 scores
	nlctassert(SCORES::NUM_SCORES==4);
	// Input max values
	_UserScores[SCORES::hit_points].DBInMax= NLGUI::CDBManager::getInstance()->getDbProp("SERVER:CHARACTER_INFO:SCORES0:Max", false);
	_UserScores[SCORES::sap].DBInMax= NLGUI::CDBManager::getInstance()->getDbProp("SERVER:CHARACTER_INFO:SCORES2:Max", false);
	_UserScores[SCORES::stamina].DBInMax= NLGUI::CDBManager::getInstance()->getDbProp("SERVER:CHARACTER_INFO:SCORES1:Max", false);
	_UserScores[SCORES::focus].DBInMax= NLGUI::CDBManager::getInstance()->getDbProp("SERVER:CHARACTER_INFO:SCORES3:Max", false);
	// Output real values
	_UserScores[SCORES::hit_points].DBOutVal= NLGUI::CDBManager::getInstance()->getDbProp("UI:VARIABLES:USER:HP", false);
	_UserScores[SCORES::sap].DBOutVal= NLGUI::CDBManager::getInstance()->getDbProp("UI:VARIABLES:USER:SAP", false);
	_UserScores[SCORES::stamina].DBOutVal= NLGUI::CDBManager::getInstance()->getDbProp("UI:VARIABLES:USER:STA", false);
	_UserScores[SCORES::focus].DBOutVal= NLGUI::CDBManager::getInstance()->getDbProp("UI:VARIABLES:USER:FOCUS", false);
	// Output ratio values
	_UserScores[SCORES::hit_points].DBOutRatio= NLGUI::CDBManager::getInstance()->getDbProp("UI:VARIABLES:USER:HP_RATIO", false);
	_UserScores[SCORES::sap].DBOutRatio= NLGUI::CDBManager::getInstance()->getDbProp("UI:VARIABLES:USER:SAP_RATIO", false);
	_UserScores[SCORES::stamina].DBOutRatio= NLGUI::CDBManager::getInstance()->getDbProp("UI:VARIABLES:USER:STA_RATIO", false);
	_UserScores[SCORES::focus].DBOutRatio= NLGUI::CDBManager::getInstance()->getDbProp("UI:VARIABLES:USER:FOCUS_RATIO", false);
}

// ***************************************************************************
void		CBarManager::releaseInGame()
{
	// Reset all, and also the DB out
	for(uint type=0;type<MaxEntryType;type++)
	{
		for(uint i=0;i<_EntryBars[type].size();i++)
		{
			_EntryBars[type][i].clear();
			_EntryBars[type][i].resetDB();
		}
	}

	// Reset the map
	_UIDBars.clear();
}

// ***************************************************************************
void		CBarManager::resetShardSpecificData()
{
	// Reset update counter to update properly when reselecting character
	_LastUserBarMsgNumber = 0;
}

// ***************************************************************************
void		CBarManager::addEntry(TEntryType type, uint entryId, uint dataSetId)
{
	std::vector<CBarDataEntry> &entryArray= _EntryBars[type];
	nlassert(entryId<entryArray.size());

	barInfoLog("BARS: addEntry(%s, eid=%d, dsid=%x)", entryTypeToStr(type), entryId, dataSetId);

	// if bad id, quit
	if(dataSetId==CLFECOMMON::INVALID_CLIENT_DATASET_INDEX)
		return;
	// if already registered, quit
	if(entryArray[entryId].DataSetId==dataSetId)
		return;

	// remove the preceding entry, reseting values if necessary
	delEntry(type, entryId);

	// Add me to list of entries
	CBarDataEntry	&bde= entryArray[entryId];
	bde.DataSetId= dataSetId;
	// Add the entry connexion to map by DataSetId
	CBarDataUID		&barUID= _UIDBars[dataSetId];
	barUID.EntryId[type].insert(entryId);

	// Special case: if the entry added is the user (slot 0), then force the correct bars info
	// This is important because the UserEntity can be created AFTER the receive of USER:BARS message.
	if(dataSetId==_EntryBars[EntityType][0].DataSetId)
	{
		barUID.BarInfo= _UserBarInfo;
	}

	// Copy the current Bar Info from the map by dataSetId in case it exists before...
	// Eg: an entity comes in vision (EntityType entry) while precedingly available in TeamMemberType Entry
	bde.BarInfo= barUID.BarInfo;
	// then report to DB (if any)
	bde.flushDBOut();
}

// ***************************************************************************
void		CBarManager::delEntry(TEntryType type, uint entryId)
{
	std::vector<CBarDataEntry> &entryArray= _EntryBars[type];
	nlassert(entryId<entryArray.size());

	barInfoLog("BARS: delEntry(%s, eid=%d)", entryTypeToStr(type), entryId);

	// if a DataSetId was registered before
	uint	dataSetId= entryArray[entryId].DataSetId;
	if(dataSetId!=CLFECOMMON::INVALID_CLIENT_DATASET_INDEX)
	{
		// then unconnect this from the map by UID
		_UIDBars[dataSetId].EntryId[type].erase(entryId);
		// if no more connexion are made to this UID
		if(_UIDBars[dataSetId].noMoreEntry())
		{
			// erase it
			_UIDBars.erase(dataSetId);
		}
	}

	// clear entry
	entryArray[entryId].clear();
	// flush the clear to the DB if any
	entryArray[entryId].flushDBOut();
}

// ***************************************************************************
void		CBarManager::addEntity(CLFECOMMON::TCLEntityId entityId, uint dataSetId)
{
	addEntry(EntityType, entityId, dataSetId);
}

// ***************************************************************************
void		CBarManager::delEntity(CLFECOMMON::TCLEntityId entityId)
{
	delEntry(EntityType, entityId);
}

// ***************************************************************************
void		CBarManager::addTeamMember(uint teamMemberId, uint dataSetId)
{
	addEntry(TeamMemberType, teamMemberId, dataSetId);
}

// ***************************************************************************
void		CBarManager::delTeamMember(uint teamMemberId)
{
	delEntry(TeamMemberType, teamMemberId);
}

// ***************************************************************************
void		CBarManager::addAnimal(uint paId, uint dataSetId)
{
	addEntry(AnimalType, paId, dataSetId);
}

// ***************************************************************************
void		CBarManager::delAnimal(uint paId)
{
	delEntry(AnimalType, paId);
}

// ***************************************************************************
void		CBarManager::addTarget(uint dataSetId)
{
	addEntry(TargetType, 0, dataSetId);
}

// ***************************************************************************
void		CBarManager::delTarget()
{
	delEntry(TargetType, 0);
}

// ***************************************************************************
void		CBarManager::updateBars(uint dataSetId, CBarInfo barInfo, TGameCycle serverTick, uint scoreFlags)
{
	// if dataSetId not registered, quit
	TUIDToDatas::iterator	it= _UIDBars.find(dataSetId);
	if(it==_UIDBars.end())
		return;

	barInfoLog("BARS: updateBars(dsid=%x, biHP=%d, t=%d, sf=%x", dataSetId, barInfo.Score[SCORES::hit_points], serverTick, scoreFlags);

	// special Case: if the info is for the User (slot 0)
	if(dataSetId==_EntryBars[EntityType][0].DataSetId)
	{
		/* Then override the bar info with the one received by USER:BARS message (always take this one)
			For instance user bars can be received from VP or from TARGET database (if he targets himself)
			But always consider the message USER:BARS as the most accurate one
		*/
		barInfo= _UserBarInfo;
	}

	// fill bar info, with relevant values only
	CBarDataUID		&barUid= it->second;
	for(uint sc=0;sc<SCORES::NUM_SCORES;sc++)
	{
		// if the update affect this score, and if the modification date is more recent (or at least the same)
		if( (scoreFlags&(1<<sc)) && serverTick>=barUid.ScoreDate[sc] )
		{
			// then change this score with the more recent one!
			barUid.ScoreDate[sc]= serverTick;
			barUid.BarInfo.Score[sc]= barInfo.Score[sc];
		}
	}

	// and report result to all connected entries
	for(uint type=0;type<MaxEntryType;type++)
	{
		// For any connected entries of this type (see EntryId definitio why a set is necessary)
		std::set<uint>::iterator	itEid= barUid.EntryId[type].begin();
		std::set<uint>::iterator	itEidEnd= barUid.EntryId[type].end();
		for(;itEid!=itEidEnd;itEid++)
		{
			uint	entryId= *itEid;
			nlassert(entryId<_EntryBars[type].size());
			CBarDataEntry	&bde= _EntryBars[type][entryId];
			// copy the bar info (with relevant values)
			bde.BarInfo= barUid.BarInfo;
			// and flush DB (if any)
			bde.flushDBOut();
		}
	}
}

// ***************************************************************************
void		CBarManager::updateEntryFromDBNoAddDel(TEntryType type, CBarDataEntry &bde)
{
	// get the Bar Info, from the input DB of this entry (only values linked)
	CBarInfo	barInfo;
	bde.modifyFromDBIn(barInfo);

	// Get the last DB modification server tick
	TGameCycle	serverTick= 0;
	/* To do this, I test all DB input, and choose the highest changeDate
		This works because the branch is atomic (all DB are relevant to the others, and
		therefore relevant agst the most recent one)
	*/
	if(bde.UIDIn && bde.UIDIn->getLastChangeGC() > serverTick )
		serverTick= bde.UIDIn->getLastChangeGC();
	if(bde.PresentIn && bde.PresentIn->getLastChangeGC() > serverTick )
		serverTick= bde.PresentIn->getLastChangeGC();
	for(uint sc=0;sc<SCORES::NUM_SCORES;sc++)
	{
		if( bde.ScoreIn[sc] && bde.ScoreIn[sc]->getLastChangeGC() > serverTick )
			serverTick= bde.ScoreIn[sc]->getLastChangeGC();
	}

	// then update the bars
	updateBars(bde.DataSetId, barInfo, serverTick, _EntryScoreFlags[type] );
}

// ***************************************************************************
void		CBarManager::updateEntryFromDB(TEntryType type, uint entryId)
{
	std::vector<CBarDataEntry> &entryArray= _EntryBars[type];
	nlassert(entryId<entryArray.size());
	CBarDataEntry	&bde= entryArray[entryId];

	// if the UID db was not found, can't do nothing... => abort
	if(bde.UIDIn==NULL)
		return;

	// get the new UID from the SERVER DB
	uint	newDataSetId= bde.UIDIn->getValue32();
	// if present flag is linked, and if 0, then force invalid data set id
	if(bde.PresentIn && bde.PresentIn->getValue32()==0)
		newDataSetId= CLFECOMMON::INVALID_CLIENT_DATASET_INDEX;

	// *** if the data set id does not correspond as the cached one
	if(newDataSetId!=bde.DataSetId)
	{
		// if deleted, delete this entry
		if(newDataSetId==CLFECOMMON::INVALID_CLIENT_DATASET_INDEX)
			delEntry(type, entryId);
		// else add
		else
			addEntry(type, entryId, newDataSetId);
		// should be changed
		nlassert(bde.DataSetId==newDataSetId);
	}

	// *** update from DB
	if(newDataSetId!=CLFECOMMON::INVALID_CLIENT_DATASET_INDEX)
	{
		updateEntryFromDBNoAddDel(type, bde);
	}
}

// ***************************************************************************
void		CBarManager::updateTeamMemberFromDB(uint teamMemberId)
{
	updateEntryFromDB(TeamMemberType, teamMemberId);
}

// ***************************************************************************
void		CBarManager::updateAnimalFromDB(uint paId)
{
	updateEntryFromDB(AnimalType, paId);
}

// ***************************************************************************
void		CBarManager::updateTargetFromDB()
{
	/* Special case for Target. The DataSetId is not replaced with those in DB (setuped with setLocalTarget())
		Instead, we ensure that the 2 match, else ignore DB change
	*/
	CBarDataEntry	&bde= _EntryBars[TargetType][0];

	// if the UID db was not found, can't do nothing... => abort
	if(bde.UIDIn==NULL)
		return;

	// get the new UID from the SERVER DB
	uint	serverDataSetId= bde.UIDIn->getValue32();

	barInfoLog("BARS: updateTargetFromDB(dsid=%x)", serverDataSetId);

	// *** if the server data set id correspond to the local one (and not invalid), update from DB
	if(serverDataSetId==bde.DataSetId && serverDataSetId!=CLFECOMMON::INVALID_CLIENT_DATASET_INDEX)
	{
		updateEntryFromDBNoAddDel(TargetType, bde);
	}
}

// ***************************************************************************
void		CBarManager::setLocalTarget(uint dataSetId)
{
	CBarDataEntry	&bde= _EntryBars[TargetType][0];

	barInfoLog("BARS: setLocalTarget(dsid=%x)", dataSetId);

	// *** if the data set id does not correspond as the cached one
	if(dataSetId!=bde.DataSetId)
	{
		// if deleted, delete this entry
		if(dataSetId==CLFECOMMON::INVALID_CLIENT_DATASET_INDEX)
			delEntry(TargetType, 0);
		// else add
		else
			addEntry(TargetType, 0, dataSetId);
		// should be changed
		nlassert(bde.DataSetId==dataSetId);
	}
}

// ***************************************************************************
CBarManager::CBarInfo	CBarManager::getBarsByEntityId(CLFECOMMON::TCLEntityId entityId) const
{
	const std::vector<CBarDataEntry>		&entityBars= _EntryBars[EntityType];
	nlassert(entityId<entityBars.size());

	return entityBars[entityId].BarInfo;
}

// ***************************************************************************
void	CBarManager::setupUserBarInfo(uint8 msgNumber, sint32 hp, sint32 sap, sint32 sta, sint32 focus)
{
	/*
		Since we are not sure of the message order, use a little counter to discard old messages
		suppose that cannot have more than a jump of 127 messages.
		eg:
			If i Receive:			0..1..4..3..2..5
			This will be applied:	0..1..4........5
	*/

	// compute the difference beetween the msgNumber and the last received
	sint32	diff;
	if(msgNumber>=_LastUserBarMsgNumber)
		diff= (sint32)msgNumber - (sint32)_LastUserBarMsgNumber;
	else
		diff= (sint32)msgNumber + 256 - (sint32)_LastUserBarMsgNumber;
	// if too big difference, suppose a roll (eg last==255, cur=0, real diff==1)
	if(diff>127)
		diff-=256;

	// if diff<0, means that the cur message is actually too old => discard
	if(diff<0)
		return;
	else
	{
		// bkup last
		_LastUserBarMsgNumber= msgNumber;
		// user now can only manage 4 scores
		nlctassert(SCORES::NUM_SCORES==4);
		_UserScores[SCORES::hit_points].Score= hp;
		_UserScores[SCORES::sap].Score= sap;
		_UserScores[SCORES::stamina].Score= sta;
		_UserScores[SCORES::focus].Score= focus;

		// update actual database now.
		for(uint i=0;i<SCORES::NUM_SCORES;i++)
		{
			// Clamp To 0, since used only by entries that don't need negative values (for comma mode)
			if(_UserScores[i].DBOutVal)	_UserScores[i].DBOutVal->setValue32(max((sint32)0,_UserScores[i].Score));
		}

		// Update the Player Entry
		updateUserBars();
	}
}

// ***************************************************************************
void	CBarManager::updateUserBars()
{
	// for all scores
	for(uint i=0;i<SCORES::NUM_SCORES;i++)
	{
		CUserScore	&us= _UserScores[i];

		// get max value
		sint32	maxVal= 1;
		if(us.DBInMax)
		{
			maxVal= us.DBInMax->getValue32();
			maxVal= max((sint32)1, maxVal);
		}

		// setup signed ratio. It is used for the view of Player interface
		if(us.DBOutRatio)
		{
			sint32	v= UserBarMaxRatio*us.Score / maxVal;
			clamp(v, (sint32)-UserBarMaxRatio, (sint32)UserBarMaxRatio);
			us.DBOutRatio->setValue32(v);
		}

		// compute signed ratio -127 / 127, for CBarInfo replace
		{
			sint32	v= 127*us.Score / maxVal;
			clamp(v, -127, 127);
			_UserBarInfo.Score[i]= (sint8)v;
		}
	}

	// replace the user Entry with the bar info.
	// This is used only for the view of bars InScene, and in case the user target himself
	uint	userDataSetId= _EntryBars[EntityType][0].DataSetId;
	if(userDataSetId!=CLFECOMMON::INVALID_CLIENT_DATASET_INDEX)
	{
		// Do a little cheat: force the update by retrieving the last update time in the BarData
		TUIDToDatas::iterator	it= _UIDBars.find(userDataSetId);
		if(it!=_UIDBars.end())
		{
			TGameCycle	serverTick= 0;
			for(uint sc=0;sc<SCORES::NUM_SCORES;sc++)
			{
				if(it->second.ScoreDate[sc] > serverTick)
					serverTick= it->second.ScoreDate[sc];
			}

			// update (user can only manage 4 scores for now)
			nlctassert(SCORES::NUM_SCORES==4);
			updateBars(userDataSetId, _UserBarInfo, serverTick, HpFlag | SapFlag | StaFlag | FocusFlag);
		}
	}
}

// ***************************************************************************
sint32 CBarManager::getUserScore(SCORES::TScores score)
{
	nlassert((uint) score < SCORES::NUM_SCORES);
	nlassert(_UserScores[score].DBOutVal); // initInGame() not called ?
	return _UserScores[score].DBOutVal->getValue32();
}


// ***************************************************************************
// ***************************************************************************
// ACTION HANDLERS
// ***************************************************************************
// ***************************************************************************



// ***************************************************************************
DECLARE_INTERFACE_CONSTANT(getMaxAnimal, CBarManager::MaxAnimal);
DECLARE_INTERFACE_CONSTANT(getMaxTeamMember, CBarManager::MaxTeamMember);


// ***************************************************************************
class CAHBarManagerOnTarget : public IActionHandler
{
public:
	virtual void execute(CCtrlBase * /* pCaller */, const string &/* Params */)
	{
		CBarManager::getInstance()->updateTargetFromDB();
	}
};
REGISTER_ACTION_HANDLER(CAHBarManagerOnTarget, "bar_manager_on_target");

// ***************************************************************************
class CAHBarManagerOnTeam : public IActionHandler
{
public:
	virtual void execute(CCtrlBase * /* pCaller */, const string &Params)
	{
		uint	index;
		fromString(Params, index);
		if(index<CBarManager::MaxTeamMember)
			CBarManager::getInstance()->updateTeamMemberFromDB(index);
	}
};
REGISTER_ACTION_HANDLER(CAHBarManagerOnTeam, "bar_manager_on_team");

// ***************************************************************************
class CAHBarManagerOnAnimal : public IActionHandler
{
public:
	virtual void execute(CCtrlBase * /* pCaller */, const string &Params)
	{
		uint	index;
		fromString(Params, index);
		if(index<CBarManager::MaxAnimal)
			CBarManager::getInstance()->updateAnimalFromDB(index);
	}
};
REGISTER_ACTION_HANDLER(CAHBarManagerOnAnimal, "bar_manager_on_animal");

// ***************************************************************************
class CAHBarManagerOnUserScores : public IActionHandler
{
public:
	virtual void execute(CCtrlBase * /* pCaller */, const string &/* Params */)
	{
		CBarManager::getInstance()->updateUserBars();
	}
};
REGISTER_ACTION_HANDLER(CAHBarManagerOnUserScores, "bar_manager_on_user_scores");