mangos/src/game/WorldHandlers/CharacterHandler.cpp
2015-09-10 00:40:36 +01:00

1046 lines
39 KiB
C++

/**
* MaNGOS is a full featured server for World of Warcraft, supporting
* the following clients: 1.12.x, 2.4.3, 3.3.5a, 4.3.4a and 5.4.8
*
* Copyright (C) 2005-2015 MaNGOS project <http://getmangos.eu>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* World of Warcraft, and all World of Warcraft or Warcraft art, images,
* and lore are copyrighted by Blizzard Entertainment, Inc.
*/
#include "Common.h"
#include "Database/DatabaseEnv.h"
#include "WorldPacket.h"
#include "SharedDefines.h"
#include "WorldSession.h"
#include "Opcodes.h"
#include "Log.h"
#include "World.h"
#include "ObjectMgr.h"
#include "Player.h"
#include "Guild.h"
#include "GuildMgr.h"
#include "UpdateMask.h"
#include "Auth/md5.h"
#include "ObjectAccessor.h"
#include "Group.h"
#include "Database/DatabaseImpl.h"
#include "PlayerDump.h"
#include "SocialMgr.h"
#include "Util.h"
#include "Language.h"
#include "Chat.h"
#include "SpellMgr.h"
#ifdef ENABLE_ELUNA
#include "LuaEngine.h"
#endif /* ENABLE_ELUNA */
#ifdef ENABLE_PLAYERBOTS
#include "playerbot.h"
#include "PlayerbotAIConfig.h"
#endif
// config option SkipCinematics supported values
enum CinematicsSkipMode
{
CINEMATICS_SKIP_NONE = 0,
CINEMATICS_SKIP_SAME_RACE = 1,
CINEMATICS_SKIP_ALL = 2
};
class LoginQueryHolder : public SqlQueryHolder
{
private:
uint32 m_accountId;
ObjectGuid m_guid;
public:
LoginQueryHolder(uint32 accountId, ObjectGuid guid)
: m_accountId(accountId), m_guid(guid) { }
ObjectGuid GetGuid() const { return m_guid; }
uint32 GetAccountId() const { return m_accountId; }
bool Initialize();
};
#ifdef ENABLE_PLAYERBOTS
class PlayerbotLoginQueryHolder : public LoginQueryHolder
{
private:
uint32 masterAccountId;
PlayerbotHolder* playerbotHolder;
public:
PlayerbotLoginQueryHolder(PlayerbotHolder* playerbotHolder, uint32 masterAccount, uint32 accountId, ObjectGuid guid)
: LoginQueryHolder(accountId, guid), masterAccountId(masterAccount), playerbotHolder(playerbotHolder) { }
public:
uint32 GetMasterAccountId() const { return masterAccountId; }
PlayerbotHolder* GetPlayerbotHolder() { return playerbotHolder; }
};
#endif
bool LoginQueryHolder::Initialize()
{
SetSize(MAX_PLAYER_LOGIN_QUERY);
bool res = true;
// NOTE: all fields in `characters` must be read to prevent lost character data at next save in case wrong DB structure.
// !!! NOTE: including unused `zone`,`online`
res &= SetPQuery(PLAYER_LOGIN_QUERY_LOADFROM, "SELECT guid, account, name, race, class, gender, level, xp, money, playerBytes, playerBytes2, playerFlags,"
"position_x, position_y, position_z, map, orientation, taximask, cinematic, totaltime, leveltime, rest_bonus, logout_time, is_logout_resting, resettalents_cost,"
"resettalents_time, trans_x, trans_y, trans_z, trans_o, transguid, extra_flags, stable_slots, at_login, zone, online, death_expire_time, taxi_path,"
"honor_highest_rank, honor_standing, stored_honor_rating, stored_dishonorable_kills, stored_honorable_kills,"
"watchedFaction, drunk,"
"health, power1, power2, power3, power4, power5, exploredZones, equipmentCache, ammoId, actionBars FROM characters WHERE guid = '%u'", m_guid.GetCounter());
res &= SetPQuery(PLAYER_LOGIN_QUERY_LOADGROUP, "SELECT groupId FROM group_member WHERE memberGuid ='%u'", m_guid.GetCounter());
res &= SetPQuery(PLAYER_LOGIN_QUERY_LOADBOUNDINSTANCES, "SELECT id, permanent, map, resettime FROM character_instance LEFT JOIN instance ON instance = id WHERE guid = '%u'", m_guid.GetCounter());
res &= SetPQuery(PLAYER_LOGIN_QUERY_LOADAURAS, "SELECT caster_guid,item_guid,spell,stackcount,remaincharges,basepoints0,basepoints1,basepoints2,periodictime0,periodictime1,periodictime2,maxduration,remaintime,effIndexMask FROM character_aura WHERE guid = '%u'", m_guid.GetCounter());
res &= SetPQuery(PLAYER_LOGIN_QUERY_LOADSPELLS, "SELECT spell,active,disabled FROM character_spell WHERE guid = '%u'", m_guid.GetCounter());
res &= SetPQuery(PLAYER_LOGIN_QUERY_LOADQUESTSTATUS, "SELECT quest,status,rewarded,explored,timer,mobcount1,mobcount2,mobcount3,mobcount4,itemcount1,itemcount2,itemcount3,itemcount4 FROM character_queststatus WHERE guid = '%u'", m_guid.GetCounter());
res &= SetPQuery(PLAYER_LOGIN_QUERY_LOADHONORCP, "SELECT victim_type,victim,honor,date,type FROM character_honor_cp WHERE used=0 AND guid = '%u'", m_guid.GetCounter());
res &= SetPQuery(PLAYER_LOGIN_QUERY_LOADREPUTATION, "SELECT faction,standing,flags FROM character_reputation WHERE guid = '%u'", m_guid.GetCounter());
res &= SetPQuery(PLAYER_LOGIN_QUERY_LOADINVENTORY, "SELECT data,bag,slot,item,item_template FROM character_inventory JOIN item_instance ON character_inventory.item = item_instance.guid WHERE character_inventory.guid = '%u' ORDER BY bag,slot", m_guid.GetCounter());
res &= SetPQuery(PLAYER_LOGIN_QUERY_LOADITEMLOOT, "SELECT guid,itemid,amount,property FROM item_loot WHERE owner_guid = '%u'", m_guid.GetCounter());
res &= SetPQuery(PLAYER_LOGIN_QUERY_LOADACTIONS, "SELECT button,action,type FROM character_action WHERE guid = '%u' ORDER BY button", m_guid.GetCounter());
res &= SetPQuery(PLAYER_LOGIN_QUERY_LOADSOCIALLIST, "SELECT friend,flags FROM character_social WHERE guid = '%u' LIMIT 255", m_guid.GetCounter());
res &= SetPQuery(PLAYER_LOGIN_QUERY_LOADHOMEBIND, "SELECT map,zone,position_x,position_y,position_z FROM character_homebind WHERE guid = '%u'", m_guid.GetCounter());
res &= SetPQuery(PLAYER_LOGIN_QUERY_LOADSPELLCOOLDOWNS, "SELECT spell,item,time FROM character_spell_cooldown WHERE guid = '%u'", m_guid.GetCounter());
res &= SetPQuery(PLAYER_LOGIN_QUERY_LOADGUILD, "SELECT guildid,rank FROM guild_member WHERE guid = '%u'", m_guid.GetCounter());
res &= SetPQuery(PLAYER_LOGIN_QUERY_LOADBGDATA, "SELECT instance_id, team, join_x, join_y, join_z, join_o, join_map FROM character_battleground_data WHERE guid = '%u'", m_guid.GetCounter());
res &= SetPQuery(PLAYER_LOGIN_QUERY_LOADSKILLS, "SELECT skill, value, max FROM character_skills WHERE guid = '%u'", m_guid.GetCounter());
res &= SetPQuery(PLAYER_LOGIN_QUERY_LOADMAILS, "SELECT id,messageType,sender,receiver,subject,itemTextId,expire_time,deliver_time,money,cod,checked,stationery,mailTemplateId,has_items FROM mail WHERE receiver = '%u' ORDER BY id DESC", m_guid.GetCounter());
res &= SetPQuery(PLAYER_LOGIN_QUERY_LOADMAILEDITEMS, "SELECT data, mail_id, item_guid, item_template FROM mail_items JOIN item_instance ON item_guid = guid WHERE receiver = '%u'", m_guid.GetCounter());
return res;
}
// don't call WorldSession directly
// it may get deleted before the query callbacks get executed
// instead pass an account id to this handler
class CharacterHandler
{
public:
void HandleCharEnumCallback(QueryResult* result, uint32 account)
{
WorldSession* session = sWorld.FindSession(account);
if (!session)
{
delete result;
return;
}
session->HandleCharEnum(result);
}
void HandlePlayerLoginCallback(QueryResult * /*dummy*/, SqlQueryHolder* holder)
{
if (!holder) { return; }
WorldSession* session = sWorld.FindSession(((LoginQueryHolder*)holder)->GetAccountId());
if (!session)
{
delete holder;
return;
}
#ifdef ENABLE_PLAYERBOTS
ObjectGuid guid = ((LoginQueryHolder*)holder)->GetGuid();
#endif
session->HandlePlayerLogin((LoginQueryHolder*)holder);
#ifdef ENABLE_PLAYERBOTS
Player* player = sObjectMgr.GetPlayer(guid, true);
if (player && !player->GetPlayerbotAI())
{
player->SetPlayerbotMgr(new PlayerbotMgr(player));
sRandomPlayerbotMgr.OnPlayerLogin(player);
}
#endif
}
#ifdef ENABLE_PLAYERBOTS
void HandlePlayerBotLoginCallback(QueryResult * dummy, SqlQueryHolder * holder)
{
if (!holder)
return;
PlayerbotLoginQueryHolder* lqh = (PlayerbotLoginQueryHolder*)holder;
if (sObjectMgr.GetPlayer(lqh->GetGuid()))
{
delete holder;
return;
}
PlayerbotHolder* playerbotHolder = lqh->GetPlayerbotHolder();
uint32 masterAccount = lqh->GetMasterAccountId();
WorldSession* masterSession = masterAccount ? sWorld.FindSession(masterAccount) : NULL;
// The bot's WorldSession is owned by the bot's Player object
// The bot's WorldSession is deleted by PlayerbotMgr::LogoutPlayerBot
uint32 botAccountId = lqh->GetAccountId();
WorldSession *botSession = new WorldSession(botAccountId, NULL, SEC_PLAYER, 0, LOCALE_enUS);
botSession->m_Address = "bot";
botSession->HandlePlayerLogin(lqh); // will delete lqh
Player* bot = botSession->GetPlayer();
if (!bot)
return;
bool allowed = false;
if (botAccountId == masterAccount)
allowed = true;
else if (masterSession && sPlayerbotAIConfig.allowGuildBots && bot->GetGuildId() == masterSession->GetPlayer()->GetGuildId())
allowed = true;
else if (sPlayerbotAIConfig.IsInRandomAccountList(botAccountId))
allowed = true;
if (allowed)
playerbotHolder->OnBotLogin(bot);
else if (masterSession)
{
ChatHandler ch(masterSession);
ch.PSendSysMessage("You are not allowed to control bot %s...", bot->GetName());
playerbotHolder->LogoutPlayerBot(bot->GetObjectGuid().GetRawValue());
}
}
#endif
} chrHandler;
#ifdef ENABLE_PLAYERBOTS
void PlayerbotHolder::AddPlayerBot(uint64 playerGuid, uint32 masterAccountId)
{
// has bot already been added?
if (sObjectMgr.GetPlayer(ObjectGuid(playerGuid)))
return;
uint32 accountId = sObjectMgr.GetPlayerAccountIdByGUID(ObjectGuid(playerGuid));
if (accountId == 0)
return;
PlayerbotLoginQueryHolder *holder = new PlayerbotLoginQueryHolder(this, masterAccountId, accountId, ObjectGuid(playerGuid));
if (!holder->Initialize())
{
delete holder; // delete all unprocessed queries
return;
}
CharacterDatabase.DelayQueryHolder(&chrHandler, &CharacterHandler::HandlePlayerBotLoginCallback, holder);
}
#endif
void WorldSession::HandleCharEnum(QueryResult* result)
{
WorldPacket data(SMSG_CHAR_ENUM, 100); // we guess size
uint8 num = 0;
data << num;
if (result)
{
do
{
uint32 guidlow = (*result)[0].GetUInt32();
DETAIL_LOG("Build enum data for char guid %u from account %u.", guidlow, GetAccountId());
if (Player::BuildEnumData(result, &data))
{ ++num; }
}
while (result->NextRow());
delete result;
}
data.put<uint8>(0, num);
SendPacket(&data);
}
void WorldSession::HandleCharEnumOpcode(WorldPacket & /*recv_data*/)
{
/// get all the data necessary for loading all characters (along with their pets) on the account
CharacterDatabase.AsyncPQuery(&chrHandler, &CharacterHandler::HandleCharEnumCallback, GetAccountId(),
// 0 1 2 3 4 5 6 7
"SELECT characters.guid, characters.name, characters.race, characters.class, characters.gender, characters.playerBytes, characters.playerBytes2, characters.level, "
// 8 9 10 11 12 13 14
"characters.zone, characters.map, characters.position_x, characters.position_y, characters.position_z, guild_member.guildid, characters.playerFlags, "
// 15 16 17 18 19
"characters.at_login, character_pet.entry, character_pet.modelid, character_pet.level, characters.equipmentCache "
"FROM characters LEFT JOIN character_pet ON characters.guid=character_pet.owner AND character_pet.slot='%u' "
"LEFT JOIN guild_member ON characters.guid = guild_member.guid "
"WHERE characters.account = '%u' ORDER BY characters.guid",
PET_SAVE_AS_CURRENT, GetAccountId());
}
void WorldSession::HandleCharCreateOpcode(WorldPacket& recv_data)
{
std::string name;
uint8 race_, class_;
recv_data >> name;
recv_data >> race_;
recv_data >> class_;
// extract other data required for player creating
uint8 gender, skin, face, hairStyle, hairColor, facialHair, outfitId;
recv_data >> gender >> skin >> face;
recv_data >> hairStyle >> hairColor >> facialHair >> outfitId;
WorldPacket data(SMSG_CHAR_CREATE, 1); // returned with diff.values in all cases
if (GetSecurity() == SEC_PLAYER)
{
if (uint32 mask = sWorld.getConfig(CONFIG_UINT32_CHARACTERS_CREATING_DISABLED))
{
bool disabled = false;
Team team = Player::TeamForRace(race_);
switch (team)
{
case ALLIANCE: disabled = mask & (1 << 0); break;
case HORDE: disabled = mask & (1 << 1); break;
default: break;
}
if (disabled)
{
data << (uint8)CHAR_CREATE_DISABLED;
SendPacket(&data);
return;
}
}
}
ChrClassesEntry const* classEntry = sChrClassesStore.LookupEntry(class_);
ChrRacesEntry const* raceEntry = sChrRacesStore.LookupEntry(race_);
if (!classEntry || !raceEntry)
{
data << (uint8)CHAR_CREATE_FAILED;
SendPacket(&data);
sLog.outError("Class: %u or Race %u not found in DBC (Wrong DBC files?) or Cheater?", class_, race_);
return;
}
// prevent character creating with invalid name
if (!normalizePlayerName(name))
{
data << (uint8)CHAR_NAME_NO_NAME;
SendPacket(&data);
sLog.outError("Account:[%d] but tried to Create character with empty [name]", GetAccountId());
return;
}
// check name limitations
uint8 res = ObjectMgr::CheckPlayerName(name, true);
if (res != CHAR_NAME_SUCCESS)
{
data << uint8(res);
SendPacket(&data);
return;
}
if (GetSecurity() == SEC_PLAYER && sObjectMgr.IsReservedName(name))
{
data << (uint8)CHAR_NAME_RESERVED;
SendPacket(&data);
return;
}
if (sObjectMgr.GetPlayerGuidByName(name))
{
data << (uint8)CHAR_CREATE_NAME_IN_USE;
SendPacket(&data);
return;
}
QueryResult* resultacct = LoginDatabase.PQuery("SELECT SUM(numchars) FROM realmcharacters WHERE acctid = '%u'", GetAccountId());
if (resultacct)
{
Field* fields = resultacct->Fetch();
uint32 acctcharcount = fields[0].GetUInt32();
delete resultacct;
if (acctcharcount >= sWorld.getConfig(CONFIG_UINT32_CHARACTERS_PER_ACCOUNT))
{
data << (uint8)CHAR_CREATE_ACCOUNT_LIMIT;
SendPacket(&data);
return;
}
}
QueryResult* result = CharacterDatabase.PQuery("SELECT COUNT(guid) FROM characters WHERE account = '%u'", GetAccountId());
uint8 charcount = 0;
if (result)
{
Field* fields = result->Fetch();
charcount = fields[0].GetUInt8();
delete result;
if (charcount >= sWorld.getConfig(CONFIG_UINT32_CHARACTERS_PER_REALM))
{
data << (uint8)CHAR_CREATE_SERVER_LIMIT;
SendPacket(&data);
return;
}
}
bool AllowTwoSideAccounts = !sWorld.IsPvPRealm() || sWorld.getConfig(CONFIG_BOOL_ALLOW_TWO_SIDE_ACCOUNTS) || GetSecurity() > SEC_PLAYER;
CinematicsSkipMode skipCinematics = CinematicsSkipMode(sWorld.getConfig(CONFIG_UINT32_SKIP_CINEMATICS));
bool have_same_race = false;
if (!AllowTwoSideAccounts || skipCinematics == CINEMATICS_SKIP_SAME_RACE)
{
QueryResult* result2 = CharacterDatabase.PQuery("SELECT race FROM characters WHERE account = '%u' %s",
GetAccountId(), (skipCinematics == CINEMATICS_SKIP_SAME_RACE) ? "" : "LIMIT 1");
if (result2)
{
Team team_ = Player::TeamForRace(race_);
Field* field = result2->Fetch();
uint8 acc_race = field[0].GetUInt32();
// need to check team only for first character
// TODO: what to if account already has characters of both races?
if (!AllowTwoSideAccounts)
{
if (acc_race == 0 || Player::TeamForRace(acc_race) != team_)
{
data << (uint8)CHAR_CREATE_PVP_TEAMS_VIOLATION;
SendPacket(&data);
delete result2;
return;
}
}
// search same race for cinematic or same class if need
// TODO: check if cinematic already shown? (already logged in?; cinematic field)
while (skipCinematics == CINEMATICS_SKIP_SAME_RACE && !have_same_race)
{
if (!result2->NextRow())
{ break; }
field = result2->Fetch();
acc_race = field[0].GetUInt32();
have_same_race = race_ == acc_race;
}
delete result2;
}
}
Player* pNewChar = new Player(this);
if (!pNewChar->Create(sObjectMgr.GeneratePlayerLowGuid(), name, race_, class_, gender, skin, face, hairStyle, hairColor, facialHair, outfitId))
{
// Player not create (race/class problem?)
delete pNewChar;
data << (uint8)CHAR_CREATE_ERROR;
SendPacket(&data);
return;
}
if ((have_same_race && skipCinematics == CINEMATICS_SKIP_SAME_RACE) || skipCinematics == CINEMATICS_SKIP_ALL)
{ pNewChar->setCinematic(1); } // not show intro
pNewChar->SetAtLoginFlag(AT_LOGIN_FIRST); // First login
// Player created, save it now
pNewChar->SaveToDB();
charcount += 1;
LoginDatabase.PExecute("DELETE FROM realmcharacters WHERE acctid= '%u' AND realmid = '%u'", GetAccountId(), realmID);
LoginDatabase.PExecute("INSERT INTO realmcharacters (numchars, acctid, realmid) VALUES (%u, %u, %u)", charcount, GetAccountId(), realmID);
data << (uint8)CHAR_CREATE_SUCCESS;
SendPacket(&data);
std::string IP_str = GetRemoteAddress();
BASIC_LOG("Account: %d (IP: %s) Create Character:[%s] (guid: %u)", GetAccountId(), IP_str.c_str(), name.c_str(), pNewChar->GetGUIDLow());
sLog.outChar("Account: %d (IP: %s) Create Character:[%s] (guid: %u)", GetAccountId(), IP_str.c_str(), name.c_str(), pNewChar->GetGUIDLow());
// Used by Eluna
#ifdef ENABLE_ELUNA
sEluna->OnCreate(pNewChar);
#endif /* ENABLE_ELUNA */
delete pNewChar; // created only to call SaveToDB()
}
void WorldSession::HandleCharDeleteOpcode(WorldPacket& recv_data)
{
ObjectGuid guid;
recv_data >> guid;
// can't delete loaded character
if (sObjectMgr.GetPlayer(guid))
{ return; }
uint32 accountId = 0;
std::string name;
// is guild leader
if (sGuildMgr.GetGuildByLeader(guid))
{
WorldPacket data(SMSG_CHAR_DELETE, 1);
data << (uint8)CHAR_DELETE_FAILED_GUILD_LEADER;
SendPacket(&data);
return;
}
uint32 lowguid = guid.GetCounter();
QueryResult* result = CharacterDatabase.PQuery("SELECT account,name FROM characters WHERE guid='%u'", lowguid);
if (result)
{
Field* fields = result->Fetch();
accountId = fields[0].GetUInt32();
name = fields[1].GetCppString();
delete result;
}
// prevent deleting other players' characters using cheating tools
if (accountId != GetAccountId())
{ return; }
std::string IP_str = GetRemoteAddress();
BASIC_LOG("Account: %d (IP: %s) Delete Character:[%s] (guid: %u)", GetAccountId(), IP_str.c_str(), name.c_str(), lowguid);
sLog.outChar("Account: %d (IP: %s) Delete Character:[%s] (guid: %u)", GetAccountId(), IP_str.c_str(), name.c_str(), lowguid);
// Used by Eluna
#ifdef ENABLE_ELUNA
sEluna->OnDelete(lowguid);
#endif /* ENABLE_ELUNA */
if (sLog.IsOutCharDump()) // optimize GetPlayerDump call
{
std::string dump = PlayerDumpWriter().GetDump(lowguid);
sLog.outCharDump(dump.c_str(), GetAccountId(), lowguid, name.c_str());
}
Player::DeleteFromDB(guid, GetAccountId());
WorldPacket data(SMSG_CHAR_DELETE, 1);
data << (uint8)CHAR_DELETE_SUCCESS;
SendPacket(&data);
}
void WorldSession::HandlePlayerLoginOpcode(WorldPacket& recv_data)
{
ObjectGuid playerGuid;
recv_data >> playerGuid;
if (PlayerLoading() || GetPlayer() != NULL)
{
sLog.outError("Player tryes to login again, AccountId = %d", GetAccountId());
return;
}
m_playerLoading = true;
DEBUG_LOG("WORLD: Received opcode Player Logon Message");
LoginQueryHolder* holder = new LoginQueryHolder(GetAccountId(), playerGuid);
if (!holder->Initialize())
{
delete holder; // delete all unprocessed queries
m_playerLoading = false;
return;
}
CharacterDatabase.DelayQueryHolder(&chrHandler, &CharacterHandler::HandlePlayerLoginCallback, holder);
}
void WorldSession::HandlePlayerLogin(LoginQueryHolder* holder)
{
/* Store the player's GUID for later reference */
ObjectGuid playerGuid = holder->GetGuid();
/* Create a new instance of the player object */
Player* pCurrChar = new Player(this);
/* Initialize a motion generator */
pCurrChar->GetMotionMaster()->Initialize();
/* Account ID is validated in LoadFromDB (prevents cheaters logging in to characters not on their account) */
if (!pCurrChar->LoadFromDB(playerGuid, holder)) /// Could not load character from database, cancel login
{
/* Disconnect the game client */
KickPlayer();
/* Remove references to avoid dangling pointers */
delete pCurrChar;
delete holder;
/* Checked in WorldSession::Update */
m_playerLoading = false;
return;
}
/* Validation check completely, assign player to WorldSession::_player for later use */
SetPlayer(pCurrChar);
WorldPacket data(SMSG_LOGIN_VERIFY_WORLD, 20);
data << pCurrChar->GetMapId();
data << pCurrChar->GetPositionX();
data << pCurrChar->GetPositionY();
data << pCurrChar->GetPositionZ();
data << pCurrChar->GetOrientation();
SendPacket(&data);
data.Initialize(SMSG_ACCOUNT_DATA_TIMES, 128);
for (int i = 0; i < 32; ++i)
{ data << uint32(0); }
SendPacket(&data);
/* 1.12.1 does not have SMSG_MOTD, so we send a server message */
/* Used for counting number of newlines in MOTD */
uint32 linecount = 0;
/* The MOTD itself */
std::string str_motd = sWorld.GetMotd();
/* Used for tracking our position within the MOTD while iterating through it */
std::string::size_type pos = 0, nextpos;
/* Find the next occurance of @ in the string
* This is how newlines are represented */
while ((nextpos = str_motd.find('@', pos)) != std::string::npos)
{
/* If these are not equal, it means a '@' was found
* These are used to represent newlines in the string
* It is set by the code above here */
if (nextpos != pos)
{
/* Send the player a system message containing the substring from pos to nextpos - pos */
ChatHandler(pCurrChar).PSendSysMessage("%s", str_motd.substr(pos, nextpos - pos).c_str());
++linecount;
}
pos = nextpos + 1;
}
/* There are no more newlines in our MOTD, so we send whatever is left */
if (pos < str_motd.length())
{
ChatHandler(pCurrChar).PSendSysMessage("%s", str_motd.substr(pos).c_str());
}
DEBUG_LOG("WORLD: Sent motd (SMSG_MOTD)");
/* Attempt to load guild for player */
if (QueryResult *resultGuild = holder->GetResult(PLAYER_LOGIN_QUERY_LOADGUILD))
{
/* We're in a guild, so set the player's guild data to represent that */
Field* fields = resultGuild->Fetch();
pCurrChar->SetInGuild(fields[0].GetUInt32());
pCurrChar->SetRank(fields[1].GetUInt32());
/* Avoid dangling pointers */
delete resultGuild;
}
/* Player thinks they have a guild, but it isn't in the database. Clear that information */
else if (pCurrChar->GetGuildId())
{
pCurrChar->SetInGuild(0);
pCurrChar->SetRank(0);
}
/* Player is in a guild
* TODO: Can we move this code into the block above? Not sure why it's down here */
if (pCurrChar->GetGuildId() != 0)
{
/* Get guild based on what we set the player's guild to above */
Guild* guild = sGuildMgr.GetGuildById(pCurrChar->GetGuildId());
/* More checks to see if they're in a guild? I'm sure this is redundant */
if (guild)
{
/* Build MOTD packet and send it to the player */
data.Initialize(SMSG_GUILD_EVENT, (1 + 1 + guild->GetMOTD().size() + 1));
data << uint8(GE_MOTD);
data << uint8(1);
data << guild->GetMOTD();
SendPacket(&data);
DEBUG_LOG("WORLD: Sent guild-motd (SMSG_GUILD_EVENT)");
/* Let everyone in the guild know you've just signed in */
guild->BroadcastEvent(GE_SIGNED_ON, pCurrChar->GetObjectGuid(), pCurrChar->GetName());
}
/* If the player is not in a guild */
else
{
sLog.outError("Player %s (GUID: %u) marked as member of nonexistent guild (id: %u), removing guild membership for player.",
pCurrChar->GetName(),
pCurrChar->GetGUIDLow(),
pCurrChar->GetGuildId());
/* Set guild to 0 (again) */
pCurrChar->SetInGuild(0);
}
}
/* Don't let the player get stuck logging in with no corpse */
if (!pCurrChar->IsAlive())
{
pCurrChar->SendCorpseReclaimDelay(true);
}
/* Sends information required before the player can be added to the map
* TODO: See if we can send information about game objects here (prevent alt+f4 through object) */
pCurrChar->SendInitialPacketsBeforeAddToMap();
/* If it's the player's first login, send a cinematic */
if (!pCurrChar->getCinematic())
{
pCurrChar->setCinematic(1);
/* Set the start location to the player's racial starting point */
if (ChrRacesEntry const* rEntry = sChrRacesStore.LookupEntry(pCurrChar->getRace()))
{ pCurrChar->SendCinematicStart(rEntry->CinematicSequence); }
}
uint32 miscRequirement = 0;
AreaLockStatus lockStatus = AREA_LOCKSTATUS_OK;
if (AreaTrigger const* at = sObjectMgr.GetMapEntranceTrigger(pCurrChar->GetMapId()))
{
lockStatus = pCurrChar->GetAreaTriggerLockStatus(at, miscRequirement);
}
else
{
// Some basic checks in case of a map without areatrigger
MapEntry const* mapEntry = sMapStore.LookupEntry(pCurrChar->GetMapId());
if (!mapEntry)
{ lockStatus = AREA_LOCKSTATUS_UNKNOWN_ERROR; }
}
/* This code is run if we can not add the player to the map for some reason */
if (lockStatus != AREA_LOCKSTATUS_OK || !pCurrChar->GetMap()->Add(pCurrChar))
{
/* Attempt to find an areatrigger to teleport the player for us */
AreaTrigger const* at = sObjectMgr.GetGoBackTrigger(pCurrChar->GetMapId());
if (at)
{
lockStatus = pCurrChar->GetAreaTriggerLockStatus(at, miscRequirement);
}
/* We couldn't find an areatrigger to teleport, so just move the player back to their home bind */
if (!at || lockStatus != AREA_LOCKSTATUS_OK || !pCurrChar->TeleportTo(at->target_mapId, at->target_X, at->target_Y, at->target_Z, pCurrChar->GetOrientation()))
{
pCurrChar->TeleportToHomebind();
}
}
sObjectAccessor.AddObject(pCurrChar);
DEBUG_LOG("Player %s added to map %i", pCurrChar->GetName(), pCurrChar->GetMapId());
/* send the player's social lists */
pCurrChar->GetSocial()->SendFriendList();
pCurrChar->GetSocial()->SendIgnoreList();
/* Send packets that must be sent only after player is added to the map */
pCurrChar->SendInitialPacketsAfterAddToMap();
/* Mark player as online in the database */
static SqlStatementID updChars;
static SqlStatementID updAccount;
SqlStatement stmt = CharacterDatabase.CreateStatement(updChars, "UPDATE characters SET online = 1 WHERE guid = ?");
stmt.PExecute(pCurrChar->GetGUIDLow());
#ifdef ENABLE_PLAYERBOTS
if (pCurrChar->GetSession()->GetRemoteAddress() != "bot")
{
#endif
stmt = LoginDatabase.CreateStatement(updAccount, "UPDATE account SET active_realm_id = ? WHERE id = ?");
stmt.PExecute(realmID, GetAccountId());
#ifdef ENABLE_PLAYERBOTS
}
#endif
/* Sync player's in-game time with server time */
pCurrChar->SetInGameTime(WorldTimer::getMSTime());
/* Send logon notification to player's group
* This is sent after player is added to the world so that player receives it too */
if (Group* group = pCurrChar->GetGroup())
{ group->SendUpdate(); }
/* Inform player's friends that player has come online */
sSocialMgr.SendFriendStatus(pCurrChar, FRIEND_ONLINE, pCurrChar->GetObjectGuid(), true);
/* Load the player's corpse if it exists, or resurrect the player if not */
pCurrChar->LoadCorpse();
/* If the player is dead, we need to set them as a ghost and increase movespeed */
if (pCurrChar->m_deathState != ALIVE)
{
/* If player is a night elf, wisp racial should be applied */
if (pCurrChar->getRace() == RACE_NIGHTELF)
{
pCurrChar->CastSpell(pCurrChar, 20584, true); // auras SPELL_AURA_INCREASE_SPEED(+speed in wisp form), SPELL_AURA_INCREASE_SWIM_SPEED(+swim speed in wisp form), SPELL_AURA_TRANSFORM (to wisp form)
}
/* Apply ghost spell to player */
pCurrChar->CastSpell(pCurrChar, 8326, true); // auras SPELL_AURA_GHOST, SPELL_AURA_INCREASE_SPEED(why?), SPELL_AURA_INCREASE_SWIM_SPEED(why?)
/* Allow player to walk on water */
pCurrChar->SetWaterWalk(true);
}
/* If player is on a taxi, continue their flight */
pCurrChar->ContinueTaxiFlight();
/* Load pet if player has one
* If the player is dead or on a taxi, the pet will be remembered as a temporary summon */
pCurrChar->LoadPet();
/* If we're running an FFA PvP realm and the player isn't a GM, mark them as PvP flagged */
if (sWorld.IsFFAPvPRealm() && !pCurrChar->isGameMaster() && !pCurrChar->HasFlag(PLAYER_FLAGS, PLAYER_FLAGS_RESTING))
{
pCurrChar->SetFFAPvP(true);
}
if (pCurrChar->HasFlag(PLAYER_FLAGS, PLAYER_FLAGS_CONTESTED_PVP))
{
pCurrChar->SetContestedPvP();
}
/* Apply onLogon requests (such as talent resets) */
if (pCurrChar->HasAtLoginFlag(AT_LOGIN_RESET_SPELLS))
{
pCurrChar->resetSpells();
SendNotification(LANG_RESET_SPELLS);
}
if (pCurrChar->HasAtLoginFlag(AT_LOGIN_RESET_TALENTS))
{
pCurrChar->resetTalents(true);
SendNotification(LANG_RESET_TALENTS); // we can use SMSG_TALENTS_INVOLUNTARILY_RESET here
}
// Used by Eluna
#ifdef ENABLE_ELUNA
if (pCurrChar->HasAtLoginFlag(AT_LOGIN_FIRST))
sEluna->OnFirstLogin(pCurrChar);
#endif /* ENABLE_ELUNA */
/* We've done what we need to, remove the flag */
if (pCurrChar->HasAtLoginFlag(AT_LOGIN_FIRST))
{
pCurrChar->RemoveAtLoginFlag(AT_LOGIN_FIRST);
}
/* If the server is shutting down, show shutdown time remaining */
if (sWorld.IsShutdowning())
{
sWorld.ShutdownMsg(true, pCurrChar);
}
/* If player should have all taxi paths, give them to the player */
if (sWorld.getConfig(CONFIG_BOOL_ALL_TAXI_PATHS))
{
pCurrChar->SetTaxiCheater(true);
}
/* Send GM notifications */
if (pCurrChar->isGameMaster())
{
SendNotification(LANG_GM_ON);
}
if (!pCurrChar->isGMVisible())
{
SendNotification(LANG_INVISIBLE_INVISIBLE);
SpellEntry const* invisibleAuraInfo = sSpellStore.LookupEntry(sWorld.getConfig(CONFIG_UINT32_GM_INVISIBLE_AURA));
if (invisibleAuraInfo && IsSpellAppliesAura(invisibleAuraInfo))
{ pCurrChar->CastSpell(pCurrChar, invisibleAuraInfo, true); }
}
std::string IP_str = GetRemoteAddress();
sLog.outChar("Account: %d (IP: %s) Login Character:[%s] (guid: %u)",
GetAccountId(), IP_str.c_str(), pCurrChar->GetName(), pCurrChar->GetGUIDLow());
/* Make player stand up if they're not already stood up and not stunned */
if (!pCurrChar->IsStandState() && !pCurrChar->hasUnitState(UNIT_STAT_STUNNED))
{ pCurrChar->SetStandState(UNIT_STAND_STATE_STAND); }
m_playerLoading = false;
// Used by Eluna
#ifdef ENABLE_ELUNA
sEluna->OnLogin(pCurrChar);
#endif /* ENABLE_ELUNA */
/* Used for movement */
m_clientTimeDelay = 0;
/* Used for looting */
pCurrChar->lastTimeLooted = time(NULL);
delete holder;
}
void WorldSession::HandleSetFactionAtWarOpcode(WorldPacket& recv_data)
{
DEBUG_LOG("WORLD: Received opcode CMSG_SET_FACTION_ATWAR");
uint32 repListID;
uint8 flag;
recv_data >> repListID;
recv_data >> flag;
GetPlayer()->GetReputationMgr().SetAtWar(repListID, flag);
}
void WorldSession::HandleTutorialFlagOpcode(WorldPacket& recv_data)
{
uint32 iFlag;
recv_data >> iFlag;
uint32 wInt = (iFlag / 32);
if (wInt >= 8)
{
// sLog.outError("CHEATER? Account:[%d] Guid[%u] tried to send wrong CMSG_TUTORIAL_FLAG", GetAccountId(),GetGUID());
return;
}
uint32 rInt = (iFlag % 32);
uint32 tutflag = GetTutorialInt(wInt);
tutflag |= (1 << rInt);
SetTutorialInt(wInt, tutflag);
// DEBUG_LOG("Received Tutorial Flag Set {%u}.", iFlag);
}
void WorldSession::HandleTutorialClearOpcode(WorldPacket & /*recv_data*/)
{
for (int i = 0; i < 8; ++i)
{ SetTutorialInt(i, 0xFFFFFFFF); }
}
void WorldSession::HandleTutorialResetOpcode(WorldPacket & /*recv_data*/)
{
for (int i = 0; i < 8; ++i)
{ SetTutorialInt(i, 0x00000000); }
}
void WorldSession::HandleSetWatchedFactionOpcode(WorldPacket& recv_data)
{
DEBUG_LOG("WORLD: Received opcode CMSG_SET_WATCHED_FACTION");
int32 repId;
recv_data >> repId;
GetPlayer()->SetInt32Value(PLAYER_FIELD_WATCHED_FACTION_INDEX, repId);
}
void WorldSession::HandleSetFactionInactiveOpcode(WorldPacket& recv_data)
{
DEBUG_LOG("WORLD: Received opcode CMSG_SET_FACTION_INACTIVE");
uint32 replistid;
uint8 inactive;
recv_data >> replistid >> inactive;
_player->GetReputationMgr().SetInactive(replistid, inactive);
}
void WorldSession::HandleShowingHelmOpcode(WorldPacket & /*recv_data*/)
{
DEBUG_LOG("CMSG_SHOWING_HELM for %s", _player->GetName());
_player->ToggleFlag(PLAYER_FLAGS, PLAYER_FLAGS_HIDE_HELM);
}
void WorldSession::HandleShowingCloakOpcode(WorldPacket & /*recv_data*/)
{
DEBUG_LOG("CMSG_SHOWING_CLOAK for %s", _player->GetName());
_player->ToggleFlag(PLAYER_FLAGS, PLAYER_FLAGS_HIDE_CLOAK);
}
void WorldSession::HandleCharRenameOpcode(WorldPacket& recv_data)
{
ObjectGuid guid;
std::string newname;
recv_data >> guid;
recv_data >> newname;
// prevent character rename to invalid name
if (!normalizePlayerName(newname))
{
WorldPacket data(SMSG_CHAR_RENAME, 1);
data << uint8(CHAR_NAME_NO_NAME);
SendPacket(&data);
return;
}
uint8 res = ObjectMgr::CheckPlayerName(newname, true);
if (res != CHAR_NAME_SUCCESS)
{
WorldPacket data(SMSG_CHAR_RENAME, 1);
data << uint8(res);
SendPacket(&data);
return;
}
// check name limitations
if (GetSecurity() == SEC_PLAYER && sObjectMgr.IsReservedName(newname))
{
WorldPacket data(SMSG_CHAR_RENAME, 1);
data << uint8(CHAR_NAME_RESERVED);
SendPacket(&data);
return;
}
std::string escaped_newname = newname;
CharacterDatabase.escape_string(escaped_newname);
// make sure that the character belongs to the current account, that rename at login is enabled
// and that there is no character with the desired new name
CharacterDatabase.AsyncPQuery(&WorldSession::HandleChangePlayerNameOpcodeCallBack,
GetAccountId(), newname,
"SELECT guid, name FROM characters WHERE guid = %u AND account = %u AND (at_login & %u) = %u AND NOT EXISTS (SELECT NULL FROM characters WHERE name = '%s')",
guid.GetCounter(), GetAccountId(), AT_LOGIN_RENAME, AT_LOGIN_RENAME, escaped_newname.c_str()
);
}
void WorldSession::HandleChangePlayerNameOpcodeCallBack(QueryResult* result, uint32 accountId, std::string newname)
{
WorldSession* session = sWorld.FindSession(accountId);
if (!session)
{
delete result;
return;
}
if (!result)
{
WorldPacket data(SMSG_CHAR_RENAME, 1);
data << uint8(CHAR_CREATE_ERROR);
session->SendPacket(&data);
return;
}
uint32 guidLow = result->Fetch()[0].GetUInt32();
ObjectGuid guid = ObjectGuid(HIGHGUID_PLAYER, guidLow);
std::string oldname = result->Fetch()[1].GetCppString();
delete result;
CharacterDatabase.BeginTransaction();
CharacterDatabase.PExecute("UPDATE characters set name = '%s', at_login = at_login & ~ %u WHERE guid ='%u'", newname.c_str(), uint32(AT_LOGIN_RENAME), guidLow);
CharacterDatabase.CommitTransaction();
sLog.outChar("Account: %d (IP: %s) Character:[%s] (guid:%u) Changed name to: %s", session->GetAccountId(), session->GetRemoteAddress().c_str(), oldname.c_str(), guidLow, newname.c_str());
WorldPacket data(SMSG_CHAR_RENAME, 1 + 8 + (newname.size() + 1));
data << uint8(RESPONSE_SUCCESS);
data << guid;
data << newname;
session->SendPacket(&data);
sWorld.InvalidatePlayerDataToAllClient(guid);
}