khanat-opennel-code/code/ryzom/server/src/shard_unifier_service/character_sync.cpp
2014-06-12 19:27:09 +02:00

1652 lines
55 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/>.
/** The character synchronizer module is responsible for
* synchronizing the ring database with the actual character
* when they evolve in the EGS.
* This include character names, guild membering, and best combat level.
* Obviously, the module is also responsible for creating and deleting
* character record in the database, according to user operation.
*
* The module also implement the character name unifier witch
* replace the existing one in legacy Ryzom in the EGS.
* The char name unifier is used to guarantee the uniqueness
* of each character name as well as the validity of
* name using a set of rules about name content, length,
* forbidden parts etc.
*/
#include "stdpch.h"
#include <functional>
#include "nel/misc/common.h"
#include "nel/misc/eid_translator.h"
#include "nel/net/module.h"
#include "nel/net/module_builder_parts.h"
#include "game_share/character_sync_itf.h"
#include "game_share/utils.h"
#include "game_share/shard_names.h"
#include "game_share/ryzom_entity_id.h"
#include "server_share/mysql_wrapper.h"
#include "database_mapping.h"
#include "character_sync.h"
#include "name_manager.h"
#include "entity_locator.h"
using namespace std;
using namespace NLMISC;
using namespace NLNET;
using namespace RSMGR;
using namespace ENTITYLOC;
using namespace MSW;
CVariable<string> RingAccessLimits("r2", "RingAccessLimits", "Limiter for ring access levels - eg 'l5:p3:d0' ", string(), 0, true);
namespace CHARSYNC
{
class CCharacterSync:
public CEmptyModuleCommBehav<CEmptyModuleServiceBehav<CEmptySocketBehav<CModuleBase> > >,
public CCharacterSyncSkel,
public CNameUnifierSkel,
public ICharacterSync
{
// Database connection
MSW::CConnection _RingDB;
// Name manager
CNameManager _NameManager;
/// Client that need name table update
set<TModuleProxyPtr> _UnifierClients;
typedef uint32 TCharId;
/// utility struct to store info abount client eid begin filled
struct TRunningEidInit
{
IModuleProxy *Module;
// the next eid info to send
CNameManager::TCharSlot NextEidToSend;
};
typedef list<TRunningEidInit> TRunningEidInits;
/// The list of running eid init
TRunningEidInits _RunningEidInits;
public:
CCharacterSync()
{
CCharacterSyncSkel::init(this);
CNameUnifierSkel::init(this);
}
bool initModule(const TParsedCommandLine &pcl)
{
// recall base class
bool ret = CModuleBase::initModule(pcl);
// init ring db
const TParsedCommandLine *initRingDb = pcl.getParam("ring_db");
if (initRingDb == NULL)
{
nlwarning("RSM : missing ring db connection information");
return false;
}
// connect to the database
if (!_RingDB.connect(*initRingDb))
{
nlwarning("Failed to connect to database using %s", initRingDb->toString().c_str());
return false;
}
// init the name manager
_NameManager._Database = &_RingDB;
_NameManager.loadAllNames();
// init the shard names table
CShardNames::getInstance().init(IService::getInstance()->ConfigFile);
return ret;
}
void onModuleUp(IModuleProxy *proxy)
{
}
void onModuleDown(IModuleProxy *proxy)
{
if (_UnifierClients.find(proxy) != _UnifierClients.end())
{
// remove it of the name unifier client list
_UnifierClients.erase(proxy);
}
}
const std::string &getShardName(uint32 homeSessionId)
{
// implement me please !
nlstop;
static string emptyString;
return emptyString;
}
void onModuleUpdate()
{
H_AUTO(CCharacterSync_onModuleUpdate);
// update running EId update
processRunningEidInits();
// update the name manager
if (!_NameManager._ReleasedNames.empty() || !_NameManager._ChangedNames.empty())
{
// build the vector to send
vector<TCharId> releasedNames(_NameManager._ReleasedNames.begin(), _NameManager._ReleasedNames.end());
vector<TNameEntry> changedNames;
set<TCharId>::iterator first(_NameManager._ChangedNames.begin()), last(_NameManager._ChangedNames.end());
for (;first != last; ++first)
{
TCharId charId = *first;
CNameManager::TCharSlot cs(charId);
changedNames.push_back(TNameEntry());
TNameEntry &ne = changedNames.back();
const CNameManager::TFullName *pname = _NameManager._Names.getA(cs);
if (pname != NULL)
{
ucstring name;
name.fromUtf8(CShardNames::getInstance().makeFullName(pname->Name, pname->HomeSessionId));
ne.setName(name);
ne.setUserId(cs.UserId);
ne.setCharIndex(cs.CharIndex);
// retrieve the account name
ne.setUserName(_NameManager._AccountNames[cs.UserId]);
ne.setShardId(pname->HomeSessionId.asInt());
}
}
// now, broadcast the data to all the clients
CNameUnifierClientProxy::broadcast_updateEIdTranslator(_UnifierClients.begin(), _UnifierClients.end(), this, releasedNames, changedNames);
// cleanup the containers
_NameManager._ReleasedNames.clear();
_NameManager._ChangedNames.clear();
}
}
CCharacterPtr lookupChar(CRingUserPtr &ringUser, uint32 charId)
{
// load the characters of the user
if (!ringUser->loadCharacters(_RingDB, __FILE__, __LINE__))
{
nlwarning("CharacterSync::lookupChar : Failed to load the character of the ring user %u, character will not be added", ringUser->getObjectId());
return CCharacterPtr();
}
// check if the character already exist or not
return ringUser->getCharactersById(charId);
}
/** Get the name of a user */
std::string getUserName(uint32 userId)
{
CRingUserPtr user = CRingUser::load(_RingDB, userId, __FILE__, __LINE__);
if (user == NULL)
{
static string emptyString;
return emptyString;
}
return user->getUserName();
}
/** Get the name of a character */
ucstring getCharacterName(uint32 charId)
{
CCharacterPtr character = CCharacter::load(_RingDB, charId, __FILE__, __LINE__);
if (character == NULL)
{
static ucstring emptyString;
return emptyString;
}
ucstring ret;
ret.fromUtf8(CShardNames::getInstance().makeFullName(character->getCharName(), TSessionId(character->getHomeMainlandSessionId())));
return ret;
}
/// Try to find a shard id from a name and session id. Return 0 if not found
virtual uint32 findCharId(const std::string &charName, uint32 homeSessionId)
{
return _NameManager.findCharId(charName, homeSessionId);
}
virtual uint32 findCharId(const std::string &charName)
{
return _NameManager.findCharId(charName);
}
void _renameCharacter(IModuleProxy *sender, uint32 charId, bool updateToClient)
{
CCharacterPtr character = CCharacter::load(_RingDB, charId, __FILE__, __LINE__);
BOMB_IF(character == NULL, "Failed to find character "<<charId<<" in database for renaming", return);
ucstring newName = _NameManager.renameCharacter(charId, character->getHomeMainlandSessionId());
// update the database
character->setCharName(newName.toUtf8());
character->update(_RingDB);
// send back the new name to the EGS
CNameUnifierClientProxy nuc (sender);
nuc.characterRenamed(this, charId, newName.toUtf8(), updateToClient);
}
bool csrRenamePlayer(uint32 csrCharId, const string &oldNameIn, const string &newNameIn)
{
// 1st, we must retrieve the full name of the csr character
CCharacterPtr csrCharacter = CCharacter::load(_RingDB, csrCharId, __FILE__, __LINE__);
BOMB_IF(csrCharacter == NULL, "Failed to load CSR character "<<csrCharId<<" to rename a player", return false);
// resolve the player character full name
string oldName;
TSessionId playerSessionId;
CShardNames::getInstance().parseRelativeName(TSessionId(csrCharacter->getHomeMainlandSessionId()), oldNameIn, oldName, playerSessionId);
// retrieve the character id if any
uint32 playerCharId = _NameManager.findCharId(oldName, playerSessionId.asInt());
if (playerCharId == 0)
return false;
// ok, we have the correct char id !, resolve the new name (we don't realy now what the csr have typed)
string newName;
TSessionId notUsed;
CShardNames::getInstance().parseRelativeName(playerSessionId, newNameIn, newName, notUsed);
// ok, we can rename the character !
ucstring ucNewName;
ucNewName.fromUtf8(newName);
// check the name validity
if (_NameManager.isNameUsable(ucNewName, playerCharId >> 4, uint8(playerCharId & 0xf), playerSessionId.asInt()) != TCharacterNameResult::cnr_ok)
{
return false;
}
// assign the name
if (!_NameManager.assignName(playerCharId, ucNewName, playerSessionId.asInt()))
{
return false;
}
// ok, save the name in the database
CCharacterPtr playerCharacter = CCharacter::load(_RingDB, playerCharId, __FILE__, __LINE__);
BOMB_IF(playerCharacter == NULL, "Failed to load renamed character "<<playerCharId<<" from database", return false);
playerCharacter->setCharName(newName);
playerCharacter->update(_RingDB);
// inform EGS that the character is renamed
CNameUnifierClientProxy::broadcast_characterRenamed(
_UnifierClients.begin(), _UnifierClients.end(),
this, playerCharId, CShardNames::getInstance().makeFullName(newName, playerSessionId), false);
// all done. New name will be dispatched at next module update
return true;
}
string makeRingAccessString(const CONTINENT::TRespawnPointCounters &respawnPoints)
{
map<char, uint32> access;
CONTINENT::TRespawnPointCounters::const_iterator first(respawnPoints.begin()), last(respawnPoints.end());
for (; first != last; ++first)
{
const CONTINENT::TContinentId &cont = first->first;
const uint32 &count = first->second;
if (cont == CONTINENT::FYROS)
access['D'] += count;
else if (cont == CONTINENT::ZORAI)
access['J'] += count;
if (cont == CONTINENT::BAGNE
|| cont == CONTINENT::NEXUS
|| cont == CONTINENT::ROUTE_GOUFFRE
|| cont == CONTINENT::SOURCES
|| cont == CONTINENT::TERRE)
access['P'] += count;
if (cont == CONTINENT::MATIS)
access['F'] += count;
if (cont == CONTINENT::TRYKER)
access['L'] += count;
}
// parse the RingAccessLimits variable and build up a limits map...
map<char, uint32> limitsMap;
CSString s= RingAccessLimits.get();
while (!s.empty())
{
CSString chunk= s.strtok(":").toUpper();
if (chunk.empty())
continue;
char key= chunk[0];
uint32 val= chunk.leftCrop(1).atoui();
DROP_IF(chunk!=NLMISC::toString("%c%u",key,val),"RingAccessLimit IGNORING string chunk: '"+chunk+"'",continue);
limitsMap[key]= val;
}
string ret("A1:");
// build the resulting string
{
map<char, uint32>::iterator first(access.begin()), last(access.end());
for (; first != last; ++first)
{
// default to no limit
uint32 limit=~0u;
// if there's a limit in the limis map then use it instead...
if (limitsMap.find(first->first)!=limitsMap.end())
{
limit= limitsMap[first->first];
nldebug("RingAccessLimit limiting %c to %u",first->first,limit);
}
// add the entry to the result string
ret += first->first + toString("%u", min(first->second,limit));
ret += ":";
}
}
return ret;
}
/// Check coherency of a character regarding it's guild association
void checkCharacter(CCharacterPtr character)
{
if (character == NULL)
return;
uint32 guildId = character->getGuildId();
if (guildId == 0)
return;
// ok, we have a guild to check, get the high bits for the shard id
uint32 guildShardId = guildId>>20;
// compare this with the character hame mainland id
if (guildShardId != character->getHomeMainlandSessionId())
{
nlwarning("CCharacterSync::checkCharacter : the character %u is associated to guild %u but this guild is on a different shard, removing the character from the guild",
character->getObjectId(),
guildId);
// bad association, ask the shard owning the guild to remove the offending member
IModuleProxy *guildHomeModule = IEntityLocator::getInstance()->getLocatorModuleForShard(guildShardId);
if (guildHomeModule == NULL)
{
nlwarning("CCharacterSync::checkCharacter : can't find a module to remove %u character from guild %u (in shard %u)",
character->getObjectId(),
guildId,
guildShardId);
}
else
{
CNameUnifierClientProxy cuc(guildHomeModule);
cuc.removeCharFromGuild(this, character->getObjectId(), guildId);
}
}
}
//////////////////////////////////////////////////
///// name unifier module interface callbacks
//////////////////////////////////////////////////
void processRunningEidInits()
{
if (_RunningEidInits.empty())
return;
TRunningEidInits::iterator firstRun(_RunningEidInits.begin()), lastRun(_RunningEidInits.end());
for (; firstRun != lastRun; ++firstRun)
{
TRunningEidInit &rei = *firstRun;
bool firstPacket = false;
bool lastPacket = false;
// check for first packed
if (rei.NextEidToSend == CNameManager::TCharSlot(0,0))
{
firstPacket = true;
}
// send 200 eid each frames
vector<TNameEntry> nameEntries;
// CNameManager::TNamesIndex::TAToBMap::const_iterator first(_NameManager._Names.getAToBMap().begin()), last(_NameManager._Names.getAToBMap().end());
CNameManager::TNamesIndex::TBToAMap::const_iterator first(_NameManager._Names.getBToAMap().lower_bound(rei.NextEidToSend)), last(_NameManager._Names.getBToAMap().end());
for (uint i=0; first != last && i<200; ++first, ++i)
{
nameEntries.push_back(TNameEntry());
TNameEntry &ne = nameEntries.back();
ne.setName(CShardNames::getInstance().makeFullName(first->second.Name, first->second.HomeSessionId));
ne.setUserId(first->first.UserId);
ne.setCharIndex(first->first.CharIndex);
// retrieve the account name
ne.setUserName(_NameManager._AccountNames[first->first.UserId]);
ne.setShardId(first->second.HomeSessionId.asInt());
}
// check for last packet
if (first == _NameManager._Names.getBToAMap().end())
{
lastPacket = true;
rei.NextEidToSend = CNameManager::TCharSlot();
}
else
{
// update the running task
rei.NextEidToSend = first->first;
}
// send it
CNameUnifierClientProxy nuc(rei.Module);
nuc.initEIdTranslator(this, firstPacket, lastPacket, nameEntries);
}
// cleanup loop
{
TRunningEidInits::iterator firstRun(_RunningEidInits.begin()), lastRun(_RunningEidInits.end());
for (; firstRun != lastRun; ++firstRun)
{
TRunningEidInit &rei = *firstRun;
if (rei.NextEidToSend == CNameManager::TCharSlot())
{
_RunningEidInits.erase(firstRun);
break;
}
}
}
}
// EGS register it's name unifier in order to receive
// an updated eid to name translation table
virtual void registerNameUnifierClient(NLNET::IModuleProxy *sender)
{
_UnifierClients.insert(sender);
// add an eid init runner
// we must do it with the eid init runner in order to
// not stall EGS when it receive a full init (more than 100K character)
// We only send 200 entity at a time, leaving EGS the time to digest
// them.
TRunningEidInit rei;
rei.Module = sender;
rei.NextEidToSend = CNameManager::TCharSlot(0,0);
_RunningEidInits.push_back(rei);
// // build the initial name table
// vector<TNameEntry> nameEntries;
//
// CNameManager::TNamesIndex::TAToBMap::const_iterator first(_NameManager._Names.getAToBMap().begin()), last(_NameManager._Names.getAToBMap().end());
// for (; first != last; ++first)
// {
// nameEntries.push_back(TNameEntry());
// TNameEntry &ne = nameEntries.back();
//
// ne.setName(CShardNames::getInstance().makeFullName(first->first.Name, first->first.HomeSessionId));
// ne.setUserId(first->second.UserId);
// ne.setCharIndex(first->second.CharIndex);
//
// // retrieve the account name
// ne.setUserName(_NameManager._AccountNames[first->second.UserId]);
//
// ne.setShardId(first->first.HomeSessionId.asInt());
// }
//
// // send it
// CNameUnifierClientProxy nuc(sender);
// nuc.initEIdTranslator(this, nameEntries);
}
// EGS ask to validate a character name
// If the NU validate the name, it temporary
// lock it to the associated player.
virtual void validateCharacterName(NLNET::IModuleProxy *sender, uint32 userId, uint8 charIndex, const std::string &name, uint32 homeMainlandSessionId)
{
nldebug("CHARSYNC : validateCharacterName : module '%s' ask to validate name '%s' for user %u, character %u",
sender->getModuleName().c_str(),
name.c_str(),
userId,
charIndex);
CValidateNameResult ret;
TCharacterNameResult result = _NameManager.isNameUsable(name, userId, charIndex, homeMainlandSessionId);
// fill the return value
ret.setResult(result);
ret.setCharIndex(charIndex);
ret.setUserId(userId);
// send the response back to sender
CNameUnifierClientProxy nuc(sender);
nuc.validateCharacterNameResult(this, ret);
}
// EGS ask to assign a name to a character
virtual void assignNameToCharacter(NLNET::IModuleProxy *sender, uint32 charId, const std::string &name, uint32 homeSessionId)
{
CValidateNameResult ret;
ret.setUserId(charId >> 4);
ret.setCharIndex(uint8(charId & 0xf));
ucstring ucName;
ucName.fromUtf8(name);
if (_NameManager.assignName(charId, ucName, homeSessionId))
{
// ok, the name assignment is validated
ret.setResult(TCharacterNameResult::cnr_ok);
ret.setFullName(ucstring::makeFromUtf8(CShardNames::getInstance().makeFullName(name, TSessionId(homeSessionId))));
}
else
{
// assignment refused
nlinfo("VALID_NAME::CHARSYNC::assignNameToCharacter name %s assignement rejected", name.c_str());
ret.setResult(TCharacterNameResult::cnr_invalid_name);
}
// send the response back to sender
CNameUnifierClientProxy nuc(sender);
nuc.assignCharacterNameResult(this, ret);
}
// EGS ask to rename a character.
// Renaming consist of assigning a default randomly generated name to the character
virtual void renameCharacter(NLNET::IModuleProxy *sender, uint32 charId)
{
_renameCharacter(sender, charId, true);
}
// EGS send info about the list of loaded guild.
// The name unifier will update is internal name table if needed
// and rename any guild having a conflicting name.
// If any guild is renamed, then the name unifier send back
// a guildRenamed message to EGS.
virtual void registerLoadedGuildNames(NLNET::IModuleProxy *sender, uint32 shardId, const std::vector < CGuildInfo > &guildInfos)
{
std::vector<uint32> renamedGuildIds;
std::map<uint32, ucstring> guilds;
// build the map of guilds
for (uint i=0; i<guildInfos.size(); ++i)
{
const CGuildInfo &gi = guildInfos[i];
guilds.insert(make_pair(gi.getGuildId(), gi.getGuildName()));
}
// register the guilds
_NameManager.registerLoadedGuildNames(shardId, guilds, renamedGuildIds);
// TODO : update renamed guild !
// update the database with the new guild list
CShardPtr shard = CShard::load(_RingDB, shardId, __FILE__, __LINE__);
if (shard == NULL)
{
// no entry for this shard, create one
shard = CShard::createTransient(__FILE__, __LINE__);
shard->setObjectId(shardId);
shard->create(_RingDB);
}
// load the associated guilds
BOMB_IF(!shard->loadGuilds(_RingDB, __FILE__, __LINE__), "Failed to load the guilds of shard "<<shardId, return);
vector<uint32> guildToRemove;
// for each guild in database, check that they are in the loaded guild or remove them
std::map < uint32, CGuildPtr >::const_iterator first(shard->getGuilds().begin()), last(shard->getGuilds().end());
for (; first != last; ++first)
{
uint32 guildId = first->first;
if (guilds.find(guildId) == guilds.end())
{
// this guild no more exist, remove the record
guildToRemove.push_back(guildId);
}
}
// remove old guilds
if (!guildToRemove.empty())
{
nlinfo("CCharacterSync:registerLoadedGuild : deleting %u old guilds from database", guildToRemove.size());
for (uint i=0; i<guildToRemove.size(); ++i)
{
WARN_IF(!CGuild::removeById(_RingDB, guildToRemove[i]), "Failed to remove guild "<<guildToRemove[i]);
}
}
uint32 nbCreateGuild = 0;
// for each loaded guild, check that they are in database or create a new one
for (uint i=0; i<guildInfos.size(); ++i)
{
uint32 guildId = guildInfos[i].getGuildId();
CGuildPtr guild = shard->getGuildsById(guildId);
if (guild == NULL)
{
// this guild is not in the database
guild = CGuild::createTransient(__FILE__, __LINE__);
guild->setObjectId(guildId);
guild->setGuildName(guildInfos[i].getGuildName().toUtf8());
guild->setShardId(shardId);
guild->create(_RingDB);
++nbCreateGuild;
}
else
{
// update the guild
guild->setGuildName(guildInfos[i].getGuildName().toUtf8());
guild->setShardId(shardId);
guild->update(_RingDB);
}
}
if (nbCreateGuild)
{
nlinfo("CCharacterSync:registerLoadedGuild : %u new guilds created in database", nbCreateGuild);
}
}
// EGS ask to name unifier to validate a new guild name
virtual void validateGuildName(NLNET::IModuleProxy *sender, uint32 guildId, const ucstring &guildName)
{
TCharacterNameResult ret;
// ask to name manager to validate the guild name
ret = _NameManager.isGuildNameUsable(guildName, guildId);
// send back the result to the client
CNameUnifierClientProxy nuc(sender);
nuc.validateGuildNameResult(this, guildId, guildName, ret);
}
// EGS add newly created guild info
virtual void addGuild(NLNET::IModuleProxy *sender, uint32 shardId, uint32 guildId, const ucstring &guildName)
{
// register the new guild name in the name manager
ucstring name;
// check that the name is correct
if (!_NameManager.assignGuildName(shardId, guildId, guildName))
{
// assignation has assigned a new name because of a conflict
ucstring newName = _NameManager.getGuildName(guildId);
// we need to warn EGS that the guild name has been changed by name manager
CNameUnifierClientProxy nuc(sender);
nuc.guildRenamed(this, guildId, newName);
name = newName;
}
else
{
// ok, the name is valid
name = guildName;
}
// create an entry in the database
CGuildPtr guild = CGuild::createTransient(__FILE__, __LINE__);
guild->setObjectId(guildId);
guild->setGuildName(name.toUtf8());
guild->setShardId(shardId);
// store the record
guild->create(_RingDB);
}
// EGS remove deleted guild info
virtual void removeGuild(NLNET::IModuleProxy *sender, uint32 shardId, uint32 guildId)
{
// Release the name in the name manager
_NameManager.releaseGuildName(shardId, guildId);
// delete the record in the database
WARN_IF(!CGuild::removeById(_RingDB, guildId), "Failed to erase guild "<<guildId<<" from database");
}
//////////////////////////////////////////////////
///// char sync module interface callbacks
//////////////////////////////////////////////////
// A new character have been create by a client
void addCharacter(NLNET::IModuleProxy *sender, const TCharInfo &charInfo)
{
nldebug("CharacterSync::addCharacter : module '%s' add character %s named '%s'",
sender->getModuleName().c_str(),
charInfo.getCharEId().toString().c_str(),
charInfo.getCharName().c_str());
// decompose character eid
uint32 userId = uint32(charInfo.getCharEId().getShortId() >> 4);
uint32 charId = uint32(charInfo.getCharEId().getShortId());
uint32 charIndex = charId & 0xf;
string charName;
TSessionId sessionId;
CShardNames::getInstance().parseRelativeName(charInfo.getHomeSessionId(), charInfo.getCharName(), charName, sessionId);
// load the user
CRingUserPtr ru = CRingUser::load(_RingDB, userId, __FILE__, __LINE__);
if (ru == NULL)
{
nlwarning("CharacterSync::addCharacter : Failed to find a ring user record for user %u, character will not be added", userId);
return;
}
// retrieve the character
CCharacterPtr character = lookupChar(ru, charId);
if (character != NULL)
{
// the character already exist, just update the record
nlwarning("CharacterSync::addCharacter : the character %s already exist in the ring database, updating info", charInfo.getCharEId().toString().c_str());
character->setCharName(charName);
character->setBestCombatLevel(charInfo.getBestCombatLevel());
character->setGuildId(charInfo.getGuildId());
character->setHomeMainlandSessionId(charInfo.getHomeSessionId());
character->setRace(charInfo.getRace());
character->setCivilisation(charInfo.getCivilisation());
character->setCult(charInfo.getCult());
// save the change
character->update(_RingDB);
}
else
{
// the character does not exit, create a new one
character = CCharacter::createTransient(__FILE__, __LINE__);
character->setObjectId(charId);
character->setUserId(userId);
character->setCharName(charName);
character->setBestCombatLevel(charInfo.getBestCombatLevel());
character->setGuildId(charInfo.getGuildId());
character->setHomeMainlandSessionId(charInfo.getHomeSessionId());
character->setRace(charInfo.getRace());
character->setCivilisation(charInfo.getCivilisation());
character->setCult(charInfo.getCult());
character->setCreationDate(CTime::getSecondsSince1970());
// save the new record
character->create(_RingDB);
}
// make sure the name manager is synchronised with database
_NameManager.assignName(character->getObjectId(), character->getCharName(), character->getHomeMainlandSessionId());
}
// A character have been deleted
void deleteCharacter(NLNET::IModuleProxy *sender, uint32 charId)
{
nldebug("CharacterSync::deleteCharacter : module '%s' delete character %u",
sender->getModuleName().c_str(),
charId);
// decompose character eid
uint32 userId = charId >> 4;
uint32 charIndex = charId & 0xf;
// load the user
CRingUserPtr ru = CRingUser::load(_RingDB, userId, __FILE__, __LINE__);
DROP_IF(ru == NULL, "CharacterSync::deleteCharacter : Failed to find a ring user record for user "+toString(userId)+", character will not be deleted", return);
// retrieve the character
CCharacterPtr character = lookupChar(ru, charId);
DROP_IF(character == NULL, "CharacterSync::deleteCharacter : Failed to find the character "<<charId<<" to delete, nothing to do", return);;
// inform interested module that this character will be deleted
NLMISC_BROADCAST_TO_LISTENER(ICharacterSyncCb, onBeforeCharacterDelete(charId));
// 'erase' the mails associated to this character
breakable
{
// first, create an erase series
string query = "INSERT INTO mfs_erased_mail_series (erased_char_id, erased_char_name, erase_date) VALUES("+toString(charId)+", '"+character->getCharName()+"', '"+MSW::encodeDate(CTime::getSecondsSince1970())+"')";
DROP_IF(!_RingDB.query(query), "ERROR : failed to create an mail erase series", break;);
uint32 eraseSeries = _RingDB.getLastGeneratedId();
// second, update the erase series in all active mail
query = "UPDATE mfs_mail SET erase_series = "+toString(eraseSeries)+" WHERE erase_series = 0";
DROP_IF(!_RingDB.query(query), "ERROR : failed to associate mail with the erase series "+toString(eraseSeries), break;);
}
// we found it, so remove it
WARN_IF(!character->remove(_RingDB), ("Failed to remove char %u from the database", charId));
// liberate the name in the name manager
_NameManager.liberateName(charId);
}
// The name of a character have been changed
// void updateCharName(NLNET::IModuleProxy *sender, const NLMISC::CEntityId &charEId, const std::string &charName)
// {
// nldebug("CharacterSync::updateCharName : module '%s' update character %s name as '%s'",
// sender->getModuleName().c_str(),
// charEId.toString().c_str(),
// charName.c_str());
// // decompose character eid
// uint32 userId = uint32(charEId.getShortId() >> 4);
// uint32 charId = uint32(charEId.getShortId());
// uint32 charIndex = charId & 0xf;
//
// // load the user
// CRingUserPtr ru = CRingUser::load(_RingDB, userId, __FILE__, __LINE__);
// DROP_IF(ru == NULL, "CharacterSync::updateCharName: Failed to find a ring user record for user "<<toString(userId)<<", character will not be updated", return);
//
// // retrieve the character
// CCharacterPtr character = lookupChar(ru, charId);
// DROP_IF(character == NULL, "CharacterSync::updateCharName: Failed to find the character '"<<charEId.toString()<<"', character will not be updated", return);
//
// std::string oldName = character->getCharName();
// // update the char and save
// character->setCharName(charName);
// character->update(_RingDB);
//
// // callback char sync clients
// NLMISC_BROADCAST_TO_LISTENER(ICharacterSyncCb, onCharacterNameUpdated(charId, oldName, charName));
//
// // update the name manager
// ucstring ucName;
// ucName.fromUtf8(charName);
// _NameManager.assignName(charId, ucName, character->getHomeMainlandSessionId());
// }
// A character guild have changed
void updateCharGuild(NLNET::IModuleProxy *sender, const NLMISC::CEntityId &charEId, uint32 guildId)
{
nldebug("CharacterSync::updateCharGuild : module '%s' update character %s guild as %u",
sender->getModuleName().c_str(),
charEId.toString().c_str(),
guildId);
// decompose character eid
uint32 userId = uint32(charEId.getShortId() >> 4);
uint32 charId = uint32(charEId.getShortId());
uint32 charIndex = charId & 0xf;
// load the user
CRingUserPtr ru = CRingUser::load(_RingDB, userId, __FILE__, __LINE__);
DROP_IF(ru == NULL, ("CharacterSync::updateCharGuild: Failed to find a ring user record for user "+toString(userId)+", character will not be updated"), return);
// retrieve the character
CCharacterPtr character = lookupChar(ru, charId);
DROP_IF(character == NULL, ("CharacterSync::updateCharGuild: Failed to find the character '%s', character will not be updated", charEId.toString().c_str()), return);
// update the char and save
character->setGuildId(guildId);
character->update(_RingDB);
// Consistency checking : guild id shard and character home mainland must be the same
checkCharacter(character);
}
// Update the respawn points count of a character
virtual void updateCharRespawnPoints(NLNET::IModuleProxy *sender, const NLMISC::CEntityId &charEId, const CONTINENT::TRespawnPointCounters &respawnPoints)
{
nldebug("CharacterSync::updateCharRespawnPoints : module '%s' update character %s %u respawn points counters",
sender->getModuleName().c_str(),
charEId.toString().c_str(),
respawnPoints.size());
// decompose character eid
uint32 userId = uint32(charEId.getShortId() >> 4);
uint32 charId = uint32(charEId.getShortId());
uint32 charIndex = charId & 0xf;
// load the user
CRingUserPtr ru = CRingUser::load(_RingDB, userId, __FILE__, __LINE__);
DROP_IF(ru == NULL, ("CharacterSync::updateCharRespawnPoints: Failed to find a ring user record for user "+toString(userId)+", character will not be updated"), return);
// retrieve the character
CCharacterPtr character = lookupChar(ru, charId);
DROP_IF(character == NULL, ("CharacterSync::updateCharRespawnPoints: Failed to find the character '%s', character will not be updated", charEId.toString().c_str()), return);
string ringAccess = makeRingAccessString(respawnPoints);
// update the char and save
character->setRingAccess(ringAccess);
character->update(_RingDB);
}
// Update the newbie flag of a characters
virtual void updateCharNewbieFlag(NLNET::IModuleProxy *sender, const NLMISC::CEntityId &charEId, bool newbie)
{
nldebug("CharacterSync::updateCharNewbieFlag : module '%s' update newbie flag to %s for character %s",
sender->getModuleName().c_str(),
newbie ? "true" : "false",
charEId.toString().c_str());
TCharId charId = uint32(charEId.getShortId());
CCharacterPtr character = CCharacter::load(_RingDB, charId, __FILE__, __LINE__);
BOMB_IF(character == NULL, "Failed to load character "<<charId<<" from database", return);
character->setNewcomer(newbie);
character->update(_RingDB);;
}
// The best level of a character has changed
void updateCharsBestLevel(NLNET::IModuleProxy *sender, const std::vector < TCharBestLevelInfo > &charLevelInfos)
{
nldebug("CharacterSync::updateCharsBestLevel : module '%s' update best level for %u characters",
sender->getModuleName().c_str(),
charLevelInfos.size());
std::vector < TCharBestLevelInfo > fakeCharLevelInfos;
for (uint i=0; i<charLevelInfos.size(); ++i)
{
// decompose character eid
const TCharBestLevelInfo &cbli = charLevelInfos[i];
uint32 userId = uint32(cbli.getCharEId().getShortId() >> 4);
uint32 charId = uint32(cbli.getCharEId().getShortId());
uint32 charIndex = charId & 0xf;
// load the user
CRingUserPtr ru = CRingUser::load(_RingDB, userId, __FILE__, __LINE__);
DROP_IF(ru == NULL, ("CharacterSync::updateCharsBestLevel: Failed to find a ring user record for user "+toString(userId)+", character will not be updated"), continue);
// retrieve the character
CCharacterPtr character = lookupChar(ru, charId);
DROP_IF(character == NULL, ("CharacterSync::updateCharsBestLevel: Failed to find the character '%s', character will not be updated", cbli.getCharEId().toString().c_str()), continue);
// update the char
character->setBestCombatLevel(cbli.getBestCombatLevel());
character->update(_RingDB);
}
if (!fakeCharLevelInfos.empty())
{
updateCharsBestLevel(sender, fakeCharLevelInfos);
}
}
// Update the allegiance of a characters
void updateCharAllegiance(NLNET::IModuleProxy *sender, const NLMISC::CEntityId &charEId, TCivilisation civilisation, TCult cult)
{
nldebug("CharacterSync::updateCharAllegiance : module '%s' updates character %s with cult '%s' and civ '%s'",
sender->getModuleName().c_str(),
charEId.toString().c_str(),
cult.toString().c_str(),
civilisation.toString().c_str());
// decompose character eid
uint32 userId = uint32(charEId.getShortId() >> 4);
uint32 charId = uint32(charEId.getShortId());
// load the user
CRingUserPtr ru = CRingUser::load(_RingDB, userId, __FILE__, __LINE__);
if (ru == NULL)
{
nlwarning("CharacterSync::updateCharAllegiance : Failed to find a ring user record for user %u, character will not be deleted", userId);
return;
}
// retrieve the character
CCharacterPtr character = lookupChar(ru, charId);
if (character != NULL)
{
character->setCivilisation(civilisation);
character->setCult(cult);
character->update(_RingDB);
}
else
{
// the character does not exit, create a new one (the additional fields will be set later by syncUserChars)
character = CCharacter::createTransient(__FILE__, __LINE__);
character->setObjectId(charId);
character->setCivilisation(civilisation);
character->setCult(cult);
// save the new record
character->create(_RingDB);
}
}
// Set HomeMainlandSessionId (when converting an old file)
void updateCharHomeMainlandSessionId(NLNET::IModuleProxy *sender, const NLMISC::CEntityId &charEId, TSessionId homeMainlandSessionId)
{
nldebug("CharacterSync::updateCharHomeMainlandSessionId : module '%s' updates character %s with '%u'",
sender->getModuleName().c_str(),
charEId.toString().c_str(),
homeMainlandSessionId.asInt());
// decompose character eid
uint32 userId = uint32(charEId.getShortId() >> 4);
uint32 charId = uint32(charEId.getShortId());
// load the user
CRingUserPtr ru = CRingUser::load(_RingDB, userId, __FILE__, __LINE__);
if (ru == NULL)
{
nlwarning("CharacterSync::updateCharHomeMainlandSessionId : Failed to find a ring user record for user %u, character will not be deleted", userId);
return;
}
// retrieve the character
CCharacterPtr character = lookupChar(ru, charId);
if (character != NULL)
{
if (character->getHomeMainlandSessionId() != homeMainlandSessionId.asInt())
{
character->setHomeMainlandSessionId(homeMainlandSessionId.asInt());
character->update(_RingDB);
// update the name
_NameManager.assignName(character->getObjectId(), character->getCharName(), character->getHomeMainlandSessionId());
}
}
else
{
// the character does not exit, create a new one (the additional fields will be set later by syncUserChars)
character = CCharacter::createTransient(__FILE__, __LINE__);
character->setObjectId(charId);
character->setHomeMainlandSessionId(homeMainlandSessionId.asInt());
// save the new record
character->create(_RingDB);
}
}
// The characters for a player have been loaded
// EGS send the full list to SU to make
// sure any divergence in the database is cleared
void syncUserChars(NLNET::IModuleProxy *sender, uint32 userId, const std::vector < TCharInfo > &charInfos)
{
nldebug("CharacterSync::syncUserChars : module '%s' update %u characters",
sender->getModuleName().c_str(),
charInfos.size());
// prepare a proxy for sending response
CNameUnifierClientProxy nuc(sender);
// load the user
CRingUserPtr ru = CRingUser::load(_RingDB, userId, __FILE__, __LINE__);
DROP_IF(ru == NULL, ("CharacterSync::syncUserChars: Failed to find a ring user record for user "+toString(userId)+", characters will not be synchronized"), nuc.userCharSyncFailed(this, userId); return);
// load the characters
DROP_IF(!ru->loadCharacters(_RingDB, __FILE__, __LINE__), ("CharacterSync::syncUserChars: Failed to load characters for ring user "+toString(userId)+", characters will not be synchronized"), nuc.userCharSyncFailed(this, userId); return);
const map<uint32, CCharacterPtr> characters = ru->getCharacters();
// build a set of existing characters
set<uint32> charToRemove;
{
map<uint32, CCharacterPtr>::const_iterator first(characters.begin()), last(characters.end());
for (; first != last; ++first)
{
charToRemove.insert(first->first);
}
}
for (uint i=0; i<charInfos.size(); ++i)
{
const TCharInfo &charInfo = charInfos[i];
// decompose character eid
uint32 charUserId = uint32(charInfo.getCharEId().getShortId() >> 4);
uint32 charId = uint32(charInfo.getCharEId().getShortId());
uint32 charIndex = charId & 0xf;
// remove this char from the remove set
charToRemove.erase(charId);
DROP_IF(charUserId != userId, ("CharacterSync::syncUserChars : the "+toString(i)+"th received character belong to user "+toString(charUserId)+" instead of user "+toString(userId)+", skiping"), continue);
std::string oldName;
// retrieve the character
CCharacterPtr character = lookupChar(ru, charId);
if (character != NULL)
{
// the character already exist, just update the record
character->setBestCombatLevel(charInfo.getBestCombatLevel());
character->setGuildId(charInfo.getGuildId());
character->setRingAccess(makeRingAccessString(charInfo.getRespawnPoints()));
character->setRace(charInfo.getRace());
character->setCivilisation(charInfo.getCivilisation());
character->setCult(charInfo.getCult());
character->setNewcomer(charInfo.getNewcomer());
// for session id, if the database contains '0', then assume the EGS have
// the correct value
if (character->getHomeMainlandSessionId() == 0)
character->setHomeMainlandSessionId(charInfo.getHomeSessionId());
// save the change
character->update(_RingDB);
}
else
{
// the character does not exit, create a new one
character = CCharacter::createTransient(__FILE__, __LINE__);
string charName;
TSessionId homeSession;
CShardNames::getInstance().parseRelativeName(charInfo.getHomeSessionId(), charInfo.getCharName(), charName, homeSession);
if (_NameManager.isNameUsable(charName, userId, uint8(charIndex), charInfo.getHomeSessionId()) != CHARSYNC::TCharacterNameResult::cnr_ok)
{
nlinfo("CharacterSync::syncUserChars : character %u use name '%s' (parsed from '%s') that is not usable, renaming it",
charId,
charName.c_str(),
charInfo.getCharName().c_str());
// set a default name for now
charName = _NameManager.generateDefaultName(charId, charInfo.getHomeSessionId()).toUtf8();
}
character->setObjectId(charId);
character->setUserId(userId);
character->setCharName(charName);
character->setBestCombatLevel(charInfo.getBestCombatLevel());
character->setGuildId(charInfo.getGuildId());
character->setHomeMainlandSessionId(charInfo.getHomeSessionId());
character->setRingAccess(makeRingAccessString(charInfo.getRespawnPoints()));
character->setRace(charInfo.getRace());
character->setCivilisation(charInfo.getCivilisation());
character->setCult(charInfo.getCult());
character->setNewcomer(charInfo.getNewcomer());
character->setCreationDate(CTime::getSecondsSince1970());
// save the new record
character->create(_RingDB);
// store the new name assoc
_NameManager.assignName(character->getObjectId(), character->getCharName(), character->getHomeMainlandSessionId(), true);
}
// auto correct invalid char name in database
if (character->getCharName().find("_default") != string::npos
|| character->getCharName().empty())
{
string charName;
TSessionId sessionId;
CShardNames::getInstance().parseRelativeName(TSessionId(character->getHomeMainlandSessionId()), charInfo.getCharName(), charName, sessionId);
// check that the name is valid before replacing it
if (_NameManager.isNameUsable(charName, userId, uint8(charIndex), charInfo.getHomeSessionId()) == CHARSYNC::TCharacterNameResult::cnr_ok)
{
// we use the name provided by EGS instead of the database name
character->setCharName(charName);
character->update(_RingDB);
// store the new name assoc
_NameManager.assignName(character->getObjectId(), character->getCharName(), character->getHomeMainlandSessionId());
}
}
checkCharacter(character);
}
// delete any no more existing chars
while (!charToRemove.empty())
{
uint32 charId = *charToRemove.begin();
CCharacterPtr character = CCharacter::load(_RingDB, charId, __FILE__, __LINE__);
if (character != NULL)
{
nldebug("syncUserChars : deleting unused character %u from database", charId);
character->remove(_RingDB);
}
charToRemove.erase(charToRemove.begin());
}
// synchronize name manager
uint8 charIndex = 0;
for (uint i=0; i<charInfos.size(); ++i)
{
const TCharInfo &ci = charInfos[i];
uint8 currentCharIndex = uint8(ci.getCharEId().getShortId()&0xf);
CCharacterPtr character = lookupChar(ru, uint32(ci.getCharEId().getShortId()));
BOMB_IF(character == NULL, "syncUserChars : cannot find character "<<uint32(ci.getCharEId().getShortId())<<" in the database !", continue);
while (charIndex < currentCharIndex)
{
// erase any reserved name
_NameManager.liberateName((userId<<4)+charIndex);
++charIndex;
}
// // check that the name is valid
// if (_NameManager.isNameUsable(character->getCharName(), userId, currentCharIndex, character->getHomeMainlandSessionId()) == CHARSYNC::TCharacterNameResult::cnr_ok)
// {
// _NameManager.assignName(uint32(ci.getCharEId().getShortId()), character->getCharName(), character->getHomeMainlandSessionId());
// }
// else
// {
// nlinfo("CharacterSync::syncUserChars : character %u use name '%s' that is not usable, renaming it",
// uint32(ci.getCharEId().getShortId()),
// character->getCharName().c_str());
//
// // we need to rename this character !
// _renameCharacter(sender, uint32(ci.getCharEId().getShortId()), false);
// }
charIndex = currentCharIndex+1;
}
while (charIndex < 16)
{
// erase any reserved name
_NameManager.liberateName((userId<<4)+charIndex);
++charIndex;
}
// inform our client that all name have been checked
// build the result vector
vector<TCharSyncResultEntry> charEntries(ru->getCharacters().size());
std::map<uint32, CCharacterPtr>::const_iterator first(ru->getCharacters().begin()), last(ru->getCharacters().end());
for (uint i=0; first != last; ++first, ++i)
{
const CCharacterPtr &character = first->second;
charEntries[i].setCharId(first->first);
charEntries[i].setCharName(ucstring::makeFromUtf8(CShardNames::getInstance().makeFullName(character->getCharName(), TSessionId(character->getHomeMainlandSessionId()))));
charEntries[i].setHomeSessionId(TSessionId(character->getHomeMainlandSessionId()));
charEntries[i].setEditionSessionId(0);
charEntries[i].setActiveAnimSessionId(0);
{
CSString query;
TSessionId editSession;
query << "SELECT session_id FROM sessions WHERE owner = "<<first->first;
query << " AND session_type = 'st_edit'";
if (_RingDB.query(query) )
{
std::auto_ptr<CStoreResult> result = auto_ptr<CStoreResult>(_RingDB.storeResult());
bool sessionClosed = false;
if (!result->getNumRows() == 0)
{
result->fetchRow();
uint32 editSession;
result->getField(0, editSession);
charEntries[i].setEditionSessionId(editSession);
}
// get the row
}
}
// Find out if the character has an active session for which he is the DM (for resume button)
bool isOwnerOfResumableSession = false;
TSessionId currentSessionId = character->getCurrentSession();
if (currentSessionId != 0)
{
CSessionPtr session = CSession::load(_RingDB, currentSessionId, __FILE__, __LINE__);
if (session != NULL)
{
isOwnerOfResumableSession = ((session->getOwnerId() == first->first) &&
(session->getSessionType() == RSMGR::TSessionType::st_anim) &&
(session->getState() == RSMGR::TSessionState::ss_open));
if (isOwnerOfResumableSession)
{
nldebug("Char %u can resume session %u", first->first, currentSessionId.asInt());
}
}
}
charEntries[i].setIsOwnerOfActiveAnimSession(isOwnerOfResumableSession);
}
nuc.userCharUpdatedAndValidated(this, userId, charEntries);
}
NLMISC_COMMAND_HANDLER_TABLE_EXTEND_BEGIN(CCharacterSync, CModuleBase)
NLMISC_COMMAND_HANDLER_ADD(CCharacterSync, dump, "dump the internal state of the module", "no args");
NLMISC_COMMAND_HANDLER_ADD(CCharacterSync, loadReservedNameFile, "Add the content of a reserved names file in the reserved names list", "<filename>");
NLMISC_COMMAND_HANDLER_ADD(CCharacterSync, relocChar, "relocalise a character on the specified shard", "<charId | charEID> <newShardId | newShardName>");
// NLMISC_COMMAND_HANDLER_ADD(CCharacterSync, changeCharOwner, "change the owner of a character in the database. NB : you need to manually change the account_xxx_yy.pdr files", "<charId>|<charEID> <newUserId>|<newUserName>");
NLMISC_COMMAND_HANDLER_TABLE_END
// NLMISC_CLASS_COMMAND_DECL(changeCharOwner)
// {
// if (args.size() != 2)
// return false;
//
// uint32 charId;
// uint32 srdUserId;
// uint32 dstUserId;
//
// if (args[0].size() > 1 && args[0][0] == '(')
// {
// // parameter is an EID
// CEntityId eid(args[0]);
// charId = uint32(eid.getShortId());
// }
// else
// {
// NLMISC::fromString(args[0], charId);
// }
//
// srcUserId = charId >> 4;
//
// uint32 dstUserId;
// NLMISC::fromString(args[1], dstUserId);
//
// if (dstUserId == 0)
// {
// // try to retrieve the user by name
// CSString req;
// req << "SELECT user_id FROM ring_users WHERE user_name = '" << args[1] << "'";
// if (!_RingDB.query(req))
// {
// log.displayNL("Can't find user '%s'", args[1].c_str());
// return true;
// }
//
// std::auto_ptr<CStoreResult> result = std::auto_ptr<CStoreResult>(_RingDB.storeResult());
//
// result->fetchRow();
// result->getField(0, dstUserId);
//
// if (dstUserId == 0)
// {
// log.displayNL("Can't find user '%s'", args[1].c_str());
// return true;
// }
// }
//
// CRingUserPtr dstUser = CRingUser::load(_RingDB, dstUserId, __FILE__, __LINE__);
// if (dstUserId == NULL)
// {
// log.displayNL("Can't load dest user %u from database", dstUserId);
// return true;
// }
//
// CRingUserPtr srcUser = CRingUser::load(_RingDB, srcUserId, __FILE__, __LINE__);
// if (srcUserId == NULL)
// {
// log.displayNL("Can't load src user %u from database", srcUserId);
// return true;
// }
//
// IEntityLocator *el = IEntityLocator::getInstance();
// if (el == NULL)
// {
// log.displayNL("Can't reloc character because entity locator is not found");
// return true;
// }
//
// if (el->isUserOnline(srcUserId))
// {
// log.displayNL("Can't reloc character because src user %u owner of char %u is online",
// userId,
// charId);
// return true;
// }
// if (el->isUserOnline(dstUserId))
// {
// log.displayNL("Can't reloc character because dst user %u is online",
// userId);
// return true;
// }
//
// // ok, try to load the character
// CCharacterPtr character = CCharacter::load(_RingDB, charId, __FILE__, __LINE__);
// if (character == NULL)
// {
// log.displayNL("Can't reloc character %u of user %u because character not found in database",
// charId,
// userId);
// return true;
// }
//
// // load all characters of dst users
// dstUser->loadCharacters(_RingDB, __FILE__, __LINE__);
// // make sure the dest slot is empty
//
// // update the home mainland
// character->setHomeMainlandSessionId(shardId);
// character->update(_RingDB);
//
// log.displayNL("The character %u of user %u has been relocated on shard %u",
// charId,
// userId,
// shardId);
//
// return true;
// }
NLMISC_CLASS_COMMAND_DECL(relocChar)
{
if (args.size() != 2)
return false;
uint32 charId;
uint32 userId;
if (args[0].size() > 1 && args[0][0] == '(')
{
// parameter is an EID
CEntityId eid(args[0]);
charId = uint32(eid.getShortId());
}
else
{
NLMISC::fromString(args[0], charId);
}
userId = charId >> 4;
uint32 shardId;
NLMISC::fromString(args[1], shardId);
if (shardId == 0)
{
// try to retrieve the shard by name
CShardNames &cn = CShardNames::getInstance();
const CShardNames::TSessionNames &shardNames = cn.getSessionNames();
for (uint i=0; i<shardNames.size(); ++i)
{
if (nlstricmp(args[1].c_str(), shardNames[i].DisplayName.c_str()) == 0)
{
// ok, we found it
shardId = shardNames[i].SessionId.asInt();
}
}
}
if (shardId == 0)
{
log.displayNL("Can't reloc character because new shard '%s' is invalid (either as a shard id or shard name)",
args[1].c_str());
return true;
}
IEntityLocator *el = IEntityLocator::getInstance();
if (el == NULL)
{
log.displayNL("Can't reloc character because entity locator is not found");
return true;
}
if (el->isUserOnline(userId))
{
log.displayNL("Can't reloc character because user %u owner of char %u is online",
userId,
charId);
return true;
}
// ok, try to load the character
CCharacterPtr character = CCharacter::load(_RingDB, charId, __FILE__, __LINE__);
if (character == NULL)
{
log.displayNL("Can't reloc character %u of user %u because character not found in database",
charId,
userId);
return true;
}
// update the home mainland
character->setHomeMainlandSessionId(shardId);
character->update(_RingDB);
// update the entity translator
CEntityId eid(RYZOMID::player, charId);
ucstring charName;
sint8 charSlot;
uint32 tempUserId;
string userName;
bool online;
NLMISC::CEntityIdTranslator::getInstance()->getEntityIdInfo(eid, charName, charSlot, tempUserId, userName, online);
NLMISC::CEntityIdTranslator::getInstance()->updateEntity(eid, charName, charSlot, userId, userName, shardId);
vector<TCharId> releasedNames;
vector<TNameEntry> changedNames;
TNameEntry nameEntry;
nameEntry.setCharIndex(charSlot);
nameEntry.setName(charName);
nameEntry.setShardId(shardId);
nameEntry.setUserId(userId);
nameEntry.setUserName(userName);
changedNames.push_back(nameEntry);
// now, broadcast the data to all the clients
CNameUnifierClientProxy::broadcast_updateEIdTranslator(_UnifierClients.begin(), _UnifierClients.end(), this, releasedNames, changedNames);
log.displayNL("The character %u of user %u has been relocated on shard %u",
charId,
userId,
shardId);
return true;
}
NLMISC_CLASS_COMMAND_DECL(dump)
{
if (args.size() > 1)
return false;
log.displayNL("---------------------------");
log.displayNL("Dumping character sync :");
log.displayNL("---------------------------");
{
log.displayNL(" Listing %u character synch clients :", _UnifierClients.size());
set<TModuleProxyPtr>::iterator first(_UnifierClients.begin()), last(_UnifierClients.end());
for (; first != last; ++first)
{
TModuleProxyPtr client = *first;
log.displayNL(" '%s' (class '%s')",
client->getModuleName().c_str(),
client->getModuleClassName().c_str());
}
}
log.displayNL("");
_NameManager.cmdHandler_dump("", args, log, quiet, human);
return true;
}
NLMISC_CLASS_COMMAND_DECL(loadReservedNameFile)
{
if (args.size() != 1)
return false;
string filename = args[0];
if (!_NameManager.loadReservedNames(filename.c_str()))
{
log.displayNL("Failed to load the file '%s'", filename.c_str());
}
else
{
log.displayNL("The content of the file '%s' has been added to the reserved names list", filename.c_str());
}
return true;
}
};
NLNET_REGISTER_MODULE_FACTORY(CCharacterSync, "CharacterSynchronisation");
} // namespace CHARSYNC
// nel command handler forwarded from EGS from client GM
NLMISC_COMMAND( renamePlayer, "rename a player", "<CSR eId><player name><new playerName>" )
{
CHARSYNC::CCharacterSync *cs = dynamic_cast<CHARSYNC::CCharacterSync *>(CHARSYNC::ICharacterSync::getInstance());
if (cs == NULL)
{
return true;
}
BOMB_IF(args.size() != 3, "renamePlayer must receive 3 argument, "<<args.size()<<" received instead", return false);
CEntityId csrId(args[0]);
BOMB_IF(csrId == CEntityId::Unknown, "Invalid csr EID '"<<args[0]<<"', evaluated to unknown EID", return false);
BOMB_IF(csrId.getType() != RYZOMID::player, "Invalid csr EID '"<<args[0]<<"', type must be player, found '"<<RYZOMID::toString(RYZOMID::TTypeId(csrId.getType()))<<"'", return false);
// extract the csr charId
uint32 csrCharId = uint32(csrId.getShortId());
return static_cast<CHARSYNC::CCharacterSync*>(CHARSYNC::ICharacterSync::getInstance())->csrRenamePlayer(csrCharId, args[1], args[2]);
}
// force module linking
void forceCharSyncLink()
{
}