Você tem o código disponível? Se tiver poste-o na caixa de código que está dentro do spoiler abaixo:
Spoiler
/**
* The Forgotten Server - a free and open-source MMORPG server emulator
* Copyright (C) 2019 Mark Samman <[email protected]>
*
* 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.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
if (!IOLoginData::preloadPlayer(player, name)) {
disconnectClient("Your character could not be loaded.");
return;
}
if (IOBan::isPlayerNamelocked(player->getGUID())) {
disconnectClient("Your character has been namelocked.");
return;
}
if (g_game.getGameState() == GAME_STATE_CLOSING && !player->hasFlag(PlayerFlag_CanAlwaysLogin)) {
disconnectClient("The game is just going down.\nPlease try again later.");
return;
}
if (g_game.getGameState() == GAME_STATE_CLOSED && !player->hasFlag(PlayerFlag_CanAlwaysLogin)) {
disconnectClient("Server is currently closed.\nPlease try again later.");
return;
}
if (g_config.getBoolean(ConfigManager::ONE_PLAYER_ON_ACCOUNT) && player->getAccountType() < ACCOUNT_TYPE_GAMEMASTER && g_game.getPlayerByAccount(player->getAccount())) {
disconnectClient("You may only login with one character\nof your account at the same time.");
return;
}
if (!player->hasFlag(PlayerFlag_CannotBeBanned)) {
BanInfo banInfo;
if (IOBan::isAccountBanned(accountId, banInfo)) {
if (banInfo.reason.empty()) {
banInfo.reason = "(none)";
}
std::ostringstream ss;
if (banInfo.expiresAt > 0) {
ss << "Your account has been banned until " << formatDateShort(banInfo.expiresAt) << " by " << banInfo.bannedBy << ".\n\nReason specified:\n" << banInfo.reason;
} else {
ss << "Your account has been permanently banned by " << banInfo.bannedBy << ".\n\nReason specified:\n" << banInfo.reason;
}
disconnectClient(ss.str());
return;
}
}
if (!IOLoginData::loadPlayerById(player, player->getGUID())) {
disconnectClient("Your character could not be loaded.");
return;
}
// New Prey
if (!IOLoginData::loadPlayerPreyData(player)) {
std::cout << "Prey data could not be loaded" << std::endl;
return;
};
player->setOperatingSystem(operatingSystem);
if (!g_game.placeCreature(player, player->getLoginPosition())) {
if (!g_game.placeCreature(player, player->getTemplePosition(), false, true)) {
disconnectClient("Temple position is wrong. Contact the administrator.");
return;
}
}
if (operatingSystem >= CLIENTOS_OTCLIENT_LINUX) {
player->registerCreatureEvent("ExtendedOpcode");
}
Player* foundPlayer = g_game.getPlayerByID(playerId);
if (!foundPlayer || foundPlayer->client) {
disconnectClient("You are already logged in.");
return;
}
if (isConnectionExpired()) {
//ProtocolGame::release() has been called at this point and the Connection object
//no longer exists, so we return to prevent leakage of the Player.
return;
}
player = foundPlayer;
player->incrementReferenceCounter();
if (!player->isRemoved()) {
if (!forced) {
if (!player->isAccessPlayer()) {
if (player->getTile()->hasFlag(TILESTATE_NOLOGOUT)) {
player->sendCancelMessage(RETURNVALUE_YOUCANNOTLOGOUTHERE);
return;
}
if (!player->getTile()->hasFlag(TILESTATE_PROTECTIONZONE) && player->hasCondition(CONDITION_INFIGHT)) {
player->sendCancelMessage(RETURNVALUE_YOUMAYNOTLOGOUTDURINGAFIGHT);
return;
}
}
//scripting event - onLogout
if (!g_creatureEvents->playerLogout(player)) {
//Let the script handle the error message
return;
}
}
if (version < g_config.getNumber(ConfigManager::VERSION_MIN) || version > g_config.getNumber(ConfigManager::VERSION_MAX)) {
std::ostringstream ss;
ss << "Only clients with protocol " << g_config.getString(ConfigManager::VERSION_STR) << " allowed!";
disconnectClient(ss.str());
return;
}
if (g_game.getGameState() == GAME_STATE_STARTUP) {
disconnectClient("Gameworld is starting up. Please wait.");
return;
}
if (g_game.getGameState() == GAME_STATE_MAINTAIN) {
disconnectClient("Gameworld is under maintenance. Please re-connect in a while.");
return;
}
BanInfo banInfo;
if (IOBan::isIpBanned(getIP(), banInfo)) {
if (banInfo.reason.empty()) {
banInfo.reason = "(none)";
}
std::ostringstream ss;
ss << "Your IP has been banned until " << formatDateShort(banInfo.expiresAt) << " by " << banInfo.bannedBy << ".\n\nReason specified:\n" << banInfo.reason;
disconnectClient(ss.str());
return;
}
uint32_t accountId = IOLoginData::gameworldAuthentication(accountName, password, characterName);
if (accountId == 0) {
disconnectClient("Account name or password is not correct.");
return;
}
// Go back and write checksum
output->skipBytes(-12);
// To support 11.10-, not have problems with 11.11+
output->add<uint32_t>(adlerChecksum(output->getOutputBuffer() + sizeof(uint32_t), 8));
//a dead player can not perform actions
if (!player || player->isRemoved() || player->getHealth() <= 0) {
if (recvbyte == 0x0F) {
// we need to make the player pointer != null in this part, game.cpp release is the first step
// login(player->getName(), player->getAccount(), player->operatingSystem);
disconnect();
return;
}
switch (recvbyte) {
case 0x14: g_dispatcher.addTask(createTask(std::bind(&ProtocolGame::logout, getThis(), true, false))); break;
case 0x1D: addGameTask(&Game::playerReceivePingBack, player->getID()); break;
case 0x1E: addGameTask(&Game::playerReceivePing, player->getID()); break;
case 0x32: parseExtendedOpcode(msg); break; //otclient extended opcode
case 0x64: parseAutoWalk(msg); break;
case 0x65: addGameTask(&Game::playerMove, player->getID(), DIRECTION_NORTH); break;
case 0x66: addGameTask(&Game::playerMove, player->getID(), DIRECTION_EAST); break;
case 0x67: addGameTask(&Game::playerMove, player->getID(), DIRECTION_SOUTH); break;
case 0x68: addGameTask(&Game::playerMove, player->getID(), DIRECTION_WEST); break;
case 0x69: addGameTask(&Game::playerStopAutoWalk, player->getID()); break;
case 0x6A: addGameTask(&Game::playerMove, player->getID(), DIRECTION_NORTHEAST); break;
case 0x6B: addGameTask(&Game::playerMove, player->getID(), DIRECTION_SOUTHEAST); break;
case 0x6C: addGameTask(&Game::playerMove, player->getID(), DIRECTION_SOUTHWEST); break;
case 0x6D: addGameTask(&Game::playerMove, player->getID(), DIRECTION_NORTHWEST); break;
case 0x6F: addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerTurn, player->getID(), DIRECTION_NORTH); break;
case 0x70: addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerTurn, player->getID(), DIRECTION_EAST); break;
case 0x71: addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerTurn, player->getID(), DIRECTION_SOUTH); break;
case 0x72: addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerTurn, player->getID(), DIRECTION_WEST); break;
case 0x78: parseThrow(msg); break;
case 0x79: parseLookInShop(msg); break;
case 0x7A: parsePlayerPurchase(msg); break;
case 0x7B: parsePlayerSale(msg); break;
case 0x7C: addGameTask(&Game::playerCloseShop, player->getID()); break;
case 0x7D: parseRequestTrade(msg); break;
case 0x7E: parseLookInTrade(msg); break;
case 0x7F: addGameTask(&Game::playerAcceptTrade, player->getID()); break;
case 0x80: addGameTask(&Game::playerCloseTrade, player->getID()); break;
case 0x82: parseUseItem(msg); break;
case 0x83: parseUseItemEx(msg); break;
case 0x84: parseUseWithCreature(msg); break;
case 0x85: parseRotateItem(msg); break;
case 0x87: parseCloseContainer(msg); break;
case 0x88: parseUpArrowContainer(msg); break;
case 0x89: parseTextWindow(msg); break;
case 0x8A: parseHouseWindow(msg); break;
case 0x8B: parseWrapableItem(msg); break;
case 0x8C: parseLookAt(msg); break;
case 0x8D: parseLookInBattleList(msg); break;
case 0x8E: /* join aggression */ break;
case 0x96: parseSay(msg); break;
case 0x97: addGameTask(&Game::playerRequestChannels, player->getID()); break;
case 0x98: parseOpenChannel(msg); break;
case 0x99: parseCloseChannel(msg); break;
case 0x9A: parseOpenPrivateChannel(msg); break;
case 0x9E: addGameTask(&Game::playerCloseNpcChannel, player->getID()); break;
case 0xA0: parseFightModes(msg); break;
case 0xA1: parseAttack(msg); break;
case 0xA2: parseFollow(msg); break;
case 0xA3: parseInviteToParty(msg); break;
case 0xA4: parseJoinParty(msg); break;
case 0xA5: parseRevokePartyInvite(msg); break;
case 0xA6: parsePassPartyLeadership(msg); break;
case 0xA7: addGameTask(&Game::playerLeaveParty, player->getID()); break;
case 0xA8: parseEnableSharedPartyExperience(msg); break;
case 0xAA: addGameTask(&Game::playerCreatePrivateChannel, player->getID()); break;
case 0xAB: parseChannelInvite(msg); break;
case 0xAC: parseChannelExclude(msg); break;
case 0xBE: addGameTask(&Game::playerCancelAttackAndFollow, player->getID()); break;
case 0xC9: /* update tile */ break;
case 0xCA: parseUpdateContainer(msg); break;
case 0xCB: parseBrowseField(msg); break;
case 0xCC: parseSeekInContainer(msg); break;
case 0xD2: addGameTask(&Game::playerRequestOutfit, player->getID()); break;
case 0xD3: parseSetOutfit(msg); break;
case 0xD4: parseToggleMount(msg); break;
case 0xD5: parseApplyImbuemente(msg); break;
case 0xD6: parseClearingImbuement(msg); break;
case 0xD7: parseCloseImbuingWindow(msg); break;
case 0xDC: parseAddVip(msg); break;
case 0xDD: parseRemoveVip(msg); break;
case 0xDE: parseEditVip(msg); break;
case 0xE6: parseBugReport(msg); break;
case 0xE7: /* thank you */ break;
case 0xE8: parseDebugAssert(msg); break;
case 0xEF: if (!g_config.getBoolean(ConfigManager::STOREMODULES)) { parseCoinTransfer(msg); } break; /* premium coins transfer */
case 0xF0: addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerShowQuestLog, player->getID()); break;
case 0xF1: parseQuestLine(msg); break;
case 0xF2: parseRuleViolationReport(msg); break;
case 0xF3: /* get object info */ break;
case 0xF4: parseMarketLeave(); break;
case 0xF5: parseMarketBrowse(msg); break;
case 0xF6: parseMarketCreateOffer(msg); break;
case 0xF7: parseMarketCancelOffer(msg); break;
case 0xF8: parseMarketAcceptOffer(msg); break;
case 0xF9: parseModalWindowAnswer(msg); break;
case 0xFA: if (!g_config.getBoolean(ConfigManager::STOREMODULES)) { parseStoreOpen(msg); } break;
case 0xFB: if (!g_config.getBoolean(ConfigManager::STOREMODULES)) { parseStoreRequestOffers(msg); } break;
case 0xFC: if (!g_config.getBoolean(ConfigManager::STOREMODULES)) { parseStoreBuyOffer(msg); } break;
// case 0xFD: parseStoreOpenTransactionHistory(msg); break;
// case 0xFE: parseStoreRequestTransactionHistory(msg); break;
const TileItemVector* items = tile->getItemList();
if (items) {
for (auto it = items->getBeginTopItem(), end = items->getEndTopItem(); it != end; ++it) {
AddItem(msg, *it);
void ProtocolGame::checkCreatureAsKnown(uint32_t id, bool& known, uint32_t& removedKnown)
{
auto result = knownCreatureSet.insert(id);
if (!result.second) {
known = true;
return;
}
known = false;
if (knownCreatureSet.size() > 1300) {
// Look for a creature to remove
for (auto it = knownCreatureSet.begin(), end = knownCreatureSet.end(); it != end; ++it) {
Creature* creature = g_game.getCreatureByID(*it);
if (!canSee(creature)) {
removedKnown = *it;
knownCreatureSet.erase(it);
return;
}
}
// Bad situation. Let's just remove anyone.
auto it = knownCreatureSet.begin();
if (*it == id) {
++it;
}
bool ProtocolGame::canSee(int32_t x, int32_t y, int32_t z) const
{
if (!player) {
return false;
}
const Position& myPos = player->getPosition();
if (myPos.z <= 7) {
//we are on ground level or above (7 -> 0)
//view is from 7 -> 0
if (z > 7) {
return false;
}
} else if (myPos.z >= 8) {
//we are underground (8 -> 15)
//view is +/- 2 from the floor we stand on
if (std::abs(myPos.getZ() - z) > 2) {
return false;
}
}
//negative offset means that the action taken place is on a lower floor than ourself
int32_t offsetz = myPos.getZ() - z;
if ((x >= myPos.getX() - 8 + offsetz) && (x <= myPos.getX() + 9 + offsetz) &&
(y >= myPos.getY() - 6 + offsetz) && (y <= myPos.getY() + 7 + offsetz)) {
return true;
}
return false;
}
std::forward_list<Direction> path;
for (uint8_t i = 0; i < numdirs; ++i) {
uint8_t rawdir = msg.getPreviousByte();
switch (rawdir) {
case 1: path.push_front(DIRECTION_EAST); break;
case 2: path.push_front(DIRECTION_NORTHEAST); break;
case 3: path.push_front(DIRECTION_NORTH); break;
case 4: path.push_front(DIRECTION_NORTHWEST); break;
case 5: path.push_front(DIRECTION_WEST); break;
case 6: path.push_front(DIRECTION_SOUTHWEST); break;
case 7: path.push_front(DIRECTION_SOUTH); break;
case 8: path.push_front(DIRECTION_SOUTHEAST); break;
default: break;
}
}
// Prey window
if (player->getVocation()->getId() == 0) {
msg.addByte(0);
} else {
msg.addByte(1); // has reached Main (allow player to open Prey window)
}
if (shop.size() <= 5) {
// For very small shops it's not worth it to create the complete map
for (const ShopInfo& shopInfo : shop) {
if (shopInfo.sellPrice == 0) {
continue;
}
uint32_t count = player->getItemTypeCount(shopInfo.itemId, subtype);
if (count > 0) {
saleMap[shopInfo.itemId] = count;
}
}
} else {
// Large shop, it's better to get a cached map of all item counts and use it
// We need a temporary map since the finished map should only contain items
// available in the shop
std::map<uint32_t, uint32_t> tempSaleMap;
player->getAllItemTypeCount(tempSaleMap);
// We must still check manually for the special items that require subtype matches
// (That is, fluids such as potions etc., actually these items are very few since
// health potions now use their own ID)
for (const ShopInfo& shopInfo : shop) {
if (shopInfo.sellPrice == 0) {
continue;
}
uint8_t i = 0;
for (std::map<uint16_t, uint32_t>::const_iterator it = saleMap.begin(); i < itemsToSend; ++it, ++i) {
msg.addItemId(it->first);
msg.addByte(std::min<uint32_t>(it->second, std::numeric_limits<uint8_t>::max()));
}
uint16_t i = 0;
for (std::map<uint16_t, uint32_t>::const_iterator it = depotItems.begin(); i < itemsToSend; ++it, ++i) {
msg.add<uint16_t>(it->first);
msg.add<uint16_t>(std::min<uint32_t>(0xFFFF, it->second));
}
for (const Mission& mission : quest->getMissions()) {
if (mission.isStarted(player)) {
if (player->getProtocolVersion() >= 1120){
msg.add<uint16_t>(0x00); // missionID (TODO, this is used for quest tracker)
}
msg.addString(mission.getName(player));
msg.addString(mission.getDescription(player));
}
}
if (player->operatingSystem == CLIENTOS_NEW_WINDOWS) {
sendQuestTracker();
}
if (isLogin) {
sendMagicEffect(pos, CONST_ME_TELEPORT);
}
for (int i = CONST_SLOT_FIRST; i <= CONST_SLOT_LAST; ++i) {
sendInventoryItem(static_cast<slots_t>(i), player->getInventoryItem(static_cast<slots_t>(i)));
}
protocolOutfits.emplace_back(outfit.name, outfit.lookType, addons);
if (protocolOutfits.size() == 150 && version < 1185) { // Game client doesn't allow more than 100 outfits
break;
}
}
if (player->canWear(looktype, addons)) { //player can wear the offer already
disabled=1;
if (addons == 0) { //addons == 0 //oufit-only offer and player already has it
disabledReason << "You already have this outfit.";
} else {
disabledReason << "You already have this outfit/addon.";
}
} else {
if (outfitOffer->type == OUTFIT_ADDON && !player->canWear(looktype,0)) { //addon offer and player doesnt have the base outfit
disabled=1;
disabledReason << "You don't have the outfit, you can't buy the addon.";
}
}
} else if (offer->type == MOUNT) {
MountOffer* mountOffer = (MountOffer*) offer;
Mount* m = g_game.mounts.getMountByID(mountOffer->mountId);
if (player->hasMount(m)) {
disabled=1;
disabledReason << "You already have this mount.";
}
} else if (offer->type == PROMOTION) {
if (player->isPromoted() || !player->isPremium()) { //TODO: add support to multiple promotion levels
disabled=1;
disabledReason << "You can't get this promotion";
}
}
msg.addByte(disabled);
if (disabled) {
msg.addString(disabledReason.str());
}
//TODO: Support multiple pages
isLastPage=0x01; //FIXME
page=0x00;
////////////////////////
msg.addByte(0xFD); //BrowseTransactionHistory
msg.add<uint32_t>(page); //which page
msg.add<uint32_t>(isLastPage); //is the last page? /
msg.addByte((uint8_t)list.size()); //how many elements follows
for(HistoryStoreOffer offer:list) {
msg.add<uint32_t>(offer.time);
msg.addByte(offer.mode);
msg.add<uint32_t>(offer.amount); //FIXME: investigate why it doesn't send the price properly
msg.addString(offer.description);
}
for (uint8_t i = SKILL_FIRST; i <= SKILL_FISHING; ++i) {
msg.add<uint16_t>(std::min<int32_t>(player->getSkillLevel(i), std::numeric_limits<uint16_t>::max()));
msg.add<uint16_t>(player->getBaseSkill(i));
if (version >= 1200) {
msg.add<uint16_t>(player->getBaseSkill(i));
msg.add<uint16_t>(player->getSkillPercent(i) * 100);
} else {
msg.addByte(player->getSkillPercent(i));
}
}
for (uint8_t i = SKILL_CRITICAL_HIT_CHANCE; i <= SKILL_LAST; ++i) {
msg.add<uint16_t>(std::min<int32_t>(player->getSkillLevel(i), std::numeric_limits<uint16_t>::max()));
msg.add<uint16_t>(player->getBaseSkill(i));
}
if (version >= 1150) { // used for imbuement (Feather)
msg.add<uint32_t>(player->getCapacity()); // total capacity
msg.add<uint32_t>(player->getCapacity()); // base total capacity
}
}
Antes de fazer a sua pergunta, tenha certeza de ter lido as regras da seção e o guia abaixo:
Descreva em algumas palavras a base utilizada. (Nome do servidor / Nome do cliente / Nome do website / etc.).
Ex: The Forgotten Server 1.3 >
Alguém sabe me dizer como eu coloco á vida e mana dos personagens em porcentagem (100%) como esse do link:
Testei todos, mas nenhum funcionou.
link dos logs
http://prntscr.com/ouffp4
Você tem o código disponível? Se tiver poste-o na caixa de código que está dentro do spoiler abaixo:
/**
* The Forgotten Server - a free and open-source MMORPG server emulator
* Copyright (C) 2019 Mark Samman <[email protected]>
*
* 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.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "otpch.h"
#include <boost/range/adaptor/reversed.hpp>
#include "protocolgame.h"
#include "outputmessage.h"
#include "player.h"
#include "configmanager.h"
#include "actions.h"
#include "game.h"
#include "iologindata.h"
#include "iomarket.h"
#include "waitlist.h"
#include "ban.h"
#include "scheduler.h"
#include "modules.h"
#include "spells.h"
#include "imbuements.h"
extern Game g_game;
extern ConfigManager g_config;
extern Actions actions;
extern CreatureEvents* g_creatureEvents;
extern Chat* g_chat;
extern Modules* g_modules;
extern Spells* g_spells;
extern Imbuements* g_imbuements;
void ProtocolGame::release()
{
//dispatcher thread
if (player && player->client == shared_from_this()) {
player->client.reset();
player->decrementReferenceCounter();
player = nullptr;
}
OutputMessagePool::getInstance().removeProtocolFromAutosend(shared_from_this());
Protocol::release();
}
void ProtocolGame::login(const std::string& name, uint32_t accountId, OperatingSystem_t operatingSystem)
{
//dispatcher thread
Player* foundPlayer = g_game.getPlayerByName(name);
if (!foundPlayer || g_config.getBoolean(ConfigManager::ALLOW_CLONES)) {
player = new Player(getThis());
player->setName(name);
player->incrementReferenceCounter();
player->setID();
if (!IOLoginData::preloadPlayer(player, name)) {
disconnectClient("Your character could not be loaded.");
return;
}
if (IOBan::isPlayerNamelocked(player->getGUID())) {
disconnectClient("Your character has been namelocked.");
return;
}
if (g_game.getGameState() == GAME_STATE_CLOSING && !player->hasFlag(PlayerFlag_CanAlwaysLogin)) {
disconnectClient("The game is just going down.\nPlease try again later.");
return;
}
if (g_game.getGameState() == GAME_STATE_CLOSED && !player->hasFlag(PlayerFlag_CanAlwaysLogin)) {
disconnectClient("Server is currently closed.\nPlease try again later.");
return;
}
if (g_config.getBoolean(ConfigManager::ONE_PLAYER_ON_ACCOUNT) && player->getAccountType() < ACCOUNT_TYPE_GAMEMASTER && g_game.getPlayerByAccount(player->getAccount())) {
disconnectClient("You may only login with one character\nof your account at the same time.");
return;
}
if (!player->hasFlag(PlayerFlag_CannotBeBanned)) {
BanInfo banInfo;
if (IOBan::isAccountBanned(accountId, banInfo)) {
if (banInfo.reason.empty()) {
banInfo.reason = "(none)";
}
std::ostringstream ss;
if (banInfo.expiresAt > 0) {
ss << "Your account has been banned until " << formatDateShort(banInfo.expiresAt) << " by " << banInfo.bannedBy << ".\n\nReason specified:\n" << banInfo.reason;
} else {
ss << "Your account has been permanently banned by " << banInfo.bannedBy << ".\n\nReason specified:\n" << banInfo.reason;
}
disconnectClient(ss.str());
return;
}
}
WaitingList& waitingList = WaitingList::getInstance();
if (!waitingList.clientLogin(player)) {
uint32_t currentSlot = waitingList.getClientSlot(player);
uint32_t retryTime = WaitingList::getTime(currentSlot);
std::ostringstream ss;
ss << "Too many players online.\nYou are at place "
<< currentSlot << " on the waiting list.";
auto output = OutputMessagePool::getOutputMessage();
output->addByte(0x16);
output->addString(ss.str());
output->addByte(retryTime);
send(output);
disconnect();
return;
}
if (!IOLoginData::loadPlayerById(player, player->getGUID())) {
disconnectClient("Your character could not be loaded.");
return;
}
// New Prey
if (!IOLoginData::loadPlayerPreyData(player)) {
std::cout << "Prey data could not be loaded" << std::endl;
return;
};
player->setOperatingSystem(operatingSystem);
if (!g_game.placeCreature(player, player->getLoginPosition())) {
if (!g_game.placeCreature(player, player->getTemplePosition(), false, true)) {
disconnectClient("Temple position is wrong. Contact the administrator.");
return;
}
}
if (operatingSystem >= CLIENTOS_OTCLIENT_LINUX) {
player->registerCreatureEvent("ExtendedOpcode");
}
player->lastIP = player->getIP();
player->lastLoginSaved = std::max<time_t>(time(nullptr), player->lastLoginSaved + 1);
acceptPackets = true;
} else {
if (eventConnect != 0 || !g_config.getBoolean(ConfigManager::REPLACE_KICK_ON_LOGIN)) {
//Already trying to connect
disconnectClient("You are already logged in.");
return;
}
if (foundPlayer->client) {
foundPlayer->disconnect();
foundPlayer->isConnecting = true;
eventConnect = g_scheduler.addEvent(createSchedulerTask(1000, std::bind(&ProtocolGame::connect, getThis(), foundPlayer->getID(), operatingSystem)));
} else {
connect(foundPlayer->getID(), operatingSystem);
}
}
OutputMessagePool::getInstance().addProtocolToAutosend(shared_from_this());
}
void ProtocolGame::connect(uint32_t playerId, OperatingSystem_t operatingSystem)
{
eventConnect = 0;
Player* foundPlayer = g_game.getPlayerByID(playerId);
if (!foundPlayer || foundPlayer->client) {
disconnectClient("You are already logged in.");
return;
}
if (isConnectionExpired()) {
//ProtocolGame::release() has been called at this point and the Connection object
//no longer exists, so we return to prevent leakage of the Player.
return;
}
player = foundPlayer;
player->incrementReferenceCounter();
g_chat->removeUserFromAllChannels(*player);
player->clearModalWindows();
player->setOperatingSystem(operatingSystem);
player->isConnecting = false;
player->client = getThis();
sendAddCreature(player, player->getPosition(), 0, false);
player->lastIP = player->getIP();
player->lastLoginSaved = std::max<time_t>(time(nullptr), player->lastLoginSaved + 1);
acceptPackets = true;
}
Void ProtocolGame::AddPlayerStats(NetworkMessage& msg)
{
msg.addByte(0xA0);
msg.add<uint16_t>(std::floor(player->getHealth() * 100.f / player->getMaxHealth()));
msg.add<uint16_t>(100);
msg.add<uint32_t>(player->getFreeCapacity());
msg.add<uint32_t>(player->getCapacity());
msg.add<uint64_t>(player->getExperience());
msg.add<uint16_t>(player->getLevel());
msg.addByte(player->getLevelPercent());
msg.add<uint16_t>(100); // base xp gain rate
msg.add<uint16_t>(0); // xp voucher
msg.add<uint16_t>(0); // low level bonus
msg.add<uint16_t>(0); // xp boost
msg.add<uint16_t>(100); // stamina multiplier (100 = x1.0)
msg.add<uint16_t>(std::floor(player->getMana() * 100.f / std::max<uint32_t>(player->getMaxMana(), 1)));
msg.add<uint16_t>(100);
msg.addByte(std::min<uint32_t>(player->getMagicLevel(), std::numeric_limits<uint8_t>::max()));
msg.addByte(std::min<uint32_t>(player->getBaseMagicLevel(), std::numeric_limits<uint8_t>::max()));
msg.addByte(player->getMagicLevelPercent());
msg.addByte(player->getSoul());
msg.add<uint16_t>(player->getStaminaMinutes());
msg.add<uint16_t>(player->getBaseSpeed() / 2);
Condition* condition = player->getCondition(CONDITION_REGENERATION);
msg.add<uint16_t>(condition ? condition->getTicks() / 1000 : 0x00);
msg.add<uint16_t>(player->getOfflineTrainingTime() / 60 / 1000);
msg.add<uint16_t>(0); // xp boost time (seconds)
msg.addByte(0); // enables exp boost in the store
}
void ProtocolGame::logout(bool displayEffect, bool forced)
{
//dispatcher thread
if (!player) {
return;
}
if (!player->isRemoved()) {
if (!forced) {
if (!player->isAccessPlayer()) {
if (player->getTile()->hasFlag(TILESTATE_NOLOGOUT)) {
player->sendCancelMessage(RETURNVALUE_YOUCANNOTLOGOUTHERE);
return;
}
if (!player->getTile()->hasFlag(TILESTATE_PROTECTIONZONE) && player->hasCondition(CONDITION_INFIGHT)) {
player->sendCancelMessage(RETURNVALUE_YOUMAYNOTLOGOUTDURINGAFIGHT);
return;
}
}
//scripting event - onLogout
if (!g_creatureEvents->playerLogout(player)) {
//Let the script handle the error message
return;
}
}
if (displayEffect && player->getHealth() > 0) {
g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF);
}
}
disconnect();
g_game.removeCreature(player);
}
void ProtocolGame::onRecvFirstMessage(NetworkMessage& msg)
{
if (g_game.getGameState() == GAME_STATE_SHUTDOWN) {
disconnect();
return;
}
OperatingSystem_t operatingSystem = static_cast<OperatingSystem_t>(msg.get<uint16_t>());
version = msg.get<uint16_t>();
if (version >= 1111) {
enableCompact();
}
clientVersion = msg.get<uint32_t>();
msg.skipBytes(3); // U32 client version, U8 client type, U16 dat revision
if (clientVersion >= 1149 && clientVersion < 1200) {
// on 1149.6xxx, this was removed later.
// extra byte for "optimise connection stability"
if (msg.getLength() - msg.getBufferPosition() > 128) {
shouldAddExivaRestrictions = true;
msg.skipBytes(1);
}
}
if (!Protocol::RSA_decrypt(msg)) {
disconnect();
return;
}
uint32_t key[4];
key[0] = msg.get<uint32_t>();
key[1] = msg.get<uint32_t>();
key[2] = msg.get<uint32_t>();
key[3] = msg.get<uint32_t>();
enableXTEAEncryption();
setXTEAKey(key);
if (operatingSystem >= CLIENTOS_OTCLIENT_LINUX) {
disconnectClient("Only official client is allowed!");
return;
}
msg.skipBytes(1); // gamemaster flag
std::string sessionKey = msg.getString();
size_t pos = sessionKey.find('\n');
if (pos == std::string::npos) {
disconnectClient("You must enter your account name.");
return;
}
std::string accountName = sessionKey.substr(0, pos);
if (accountName.empty()) {
disconnectClient("You must enter your account name.");
return;
}
std::string password = sessionKey.substr(pos + 1);
std::string characterName = msg.getString();
uint32_t timeStamp = msg.get<uint32_t>();
uint8_t randNumber = msg.getByte();
if (challengeTimestamp != timeStamp || challengeRandom != randNumber) {
disconnect();
return;
}
if (version < g_config.getNumber(ConfigManager::VERSION_MIN) || version > g_config.getNumber(ConfigManager::VERSION_MAX)) {
std::ostringstream ss;
ss << "Only clients with protocol " << g_config.getString(ConfigManager::VERSION_STR) << " allowed!";
disconnectClient(ss.str());
return;
}
if (g_game.getGameState() == GAME_STATE_STARTUP) {
disconnectClient("Gameworld is starting up. Please wait.");
return;
}
if (g_game.getGameState() == GAME_STATE_MAINTAIN) {
disconnectClient("Gameworld is under maintenance. Please re-connect in a while.");
return;
}
BanInfo banInfo;
if (IOBan::isIpBanned(getIP(), banInfo)) {
if (banInfo.reason.empty()) {
banInfo.reason = "(none)";
}
std::ostringstream ss;
ss << "Your IP has been banned until " << formatDateShort(banInfo.expiresAt) << " by " << banInfo.bannedBy << ".\n\nReason specified:\n" << banInfo.reason;
disconnectClient(ss.str());
return;
}
uint32_t accountId = IOLoginData::gameworldAuthentication(accountName, password, characterName);
if (accountId == 0) {
disconnectClient("Account name or password is not correct.");
return;
}
g_dispatcher.addTask(createTask(std::bind(&ProtocolGame::login, getThis(), characterName, accountId, operatingSystem)));
}
void ProtocolGame::onConnect()
{
auto output = OutputMessagePool::getOutputMessage();
static std::random_device rd;
static std::ranlux24 generator(rd());
static std::uniform_int_distribution<uint16_t> randNumber(0x00, 0xFF);
// Skip checksum
output->skipBytes(sizeof(uint32_t));
// Packet length & type
output->add<uint16_t>(0x0006);
output->addByte(0x1F);
// Add timestamp & random number
challengeTimestamp = static_cast<uint32_t>(time(nullptr));
output->add<uint32_t>(challengeTimestamp);
challengeRandom = randNumber(generator);
output->addByte(challengeRandom);
// Go back and write checksum
output->skipBytes(-12);
// To support 11.10-, not have problems with 11.11+
output->add<uint32_t>(adlerChecksum(output->getOutputBuffer() + sizeof(uint32_t), 8));
send(std::move(output));
}
void ProtocolGame::disconnectClient(const std::string& message) const
{
auto output = OutputMessagePool::getOutputMessage();
output->addByte(0x14);
output->addString(message);
send(output);
disconnect();
}
void ProtocolGame::writeToOutputBuffer(const NetworkMessage& msg)
{
auto out = getOutputBuffer(msg.getLength());
out->append(msg);
}
void ProtocolGame::parsePacket(NetworkMessage& msg)
{
if (!acceptPackets || g_game.getGameState() == GAME_STATE_SHUTDOWN || msg.getLength() <= 0) {
return;
}
uint8_t recvbyte = msg.getByte();
//a dead player can not perform actions
if (!player || player->isRemoved() || player->getHealth() <= 0) {
if (recvbyte == 0x0F) {
// we need to make the player pointer != null in this part, game.cpp release is the first step
// login(player->getName(), player->getAccount(), player->operatingSystem);
disconnect();
return;
}
if (recvbyte != 0x14) {
return;
}
}
g_dispatcher.addTask(createTask(std::bind(&Modules::executeOnRecvbyte, g_modules, player, msg, recvbyte)));
switch (recvbyte) {
case 0x14: g_dispatcher.addTask(createTask(std::bind(&ProtocolGame::logout, getThis(), true, false))); break;
case 0x1D: addGameTask(&Game::playerReceivePingBack, player->getID()); break;
case 0x1E: addGameTask(&Game::playerReceivePing, player->getID()); break;
case 0x32: parseExtendedOpcode(msg); break; //otclient extended opcode
case 0x64: parseAutoWalk(msg); break;
case 0x65: addGameTask(&Game::playerMove, player->getID(), DIRECTION_NORTH); break;
case 0x66: addGameTask(&Game::playerMove, player->getID(), DIRECTION_EAST); break;
case 0x67: addGameTask(&Game::playerMove, player->getID(), DIRECTION_SOUTH); break;
case 0x68: addGameTask(&Game::playerMove, player->getID(), DIRECTION_WEST); break;
case 0x69: addGameTask(&Game::playerStopAutoWalk, player->getID()); break;
case 0x6A: addGameTask(&Game::playerMove, player->getID(), DIRECTION_NORTHEAST); break;
case 0x6B: addGameTask(&Game::playerMove, player->getID(), DIRECTION_SOUTHEAST); break;
case 0x6C: addGameTask(&Game::playerMove, player->getID(), DIRECTION_SOUTHWEST); break;
case 0x6D: addGameTask(&Game::playerMove, player->getID(), DIRECTION_NORTHWEST); break;
case 0x6F: addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerTurn, player->getID(), DIRECTION_NORTH); break;
case 0x70: addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerTurn, player->getID(), DIRECTION_EAST); break;
case 0x71: addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerTurn, player->getID(), DIRECTION_SOUTH); break;
case 0x72: addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerTurn, player->getID(), DIRECTION_WEST); break;
case 0x78: parseThrow(msg); break;
case 0x79: parseLookInShop(msg); break;
case 0x7A: parsePlayerPurchase(msg); break;
case 0x7B: parsePlayerSale(msg); break;
case 0x7C: addGameTask(&Game::playerCloseShop, player->getID()); break;
case 0x7D: parseRequestTrade(msg); break;
case 0x7E: parseLookInTrade(msg); break;
case 0x7F: addGameTask(&Game::playerAcceptTrade, player->getID()); break;
case 0x80: addGameTask(&Game::playerCloseTrade, player->getID()); break;
case 0x82: parseUseItem(msg); break;
case 0x83: parseUseItemEx(msg); break;
case 0x84: parseUseWithCreature(msg); break;
case 0x85: parseRotateItem(msg); break;
case 0x87: parseCloseContainer(msg); break;
case 0x88: parseUpArrowContainer(msg); break;
case 0x89: parseTextWindow(msg); break;
case 0x8A: parseHouseWindow(msg); break;
case 0x8B: parseWrapableItem(msg); break;
case 0x8C: parseLookAt(msg); break;
case 0x8D: parseLookInBattleList(msg); break;
case 0x8E: /* join aggression */ break;
case 0x96: parseSay(msg); break;
case 0x97: addGameTask(&Game::playerRequestChannels, player->getID()); break;
case 0x98: parseOpenChannel(msg); break;
case 0x99: parseCloseChannel(msg); break;
case 0x9A: parseOpenPrivateChannel(msg); break;
case 0x9E: addGameTask(&Game::playerCloseNpcChannel, player->getID()); break;
case 0xA0: parseFightModes(msg); break;
case 0xA1: parseAttack(msg); break;
case 0xA2: parseFollow(msg); break;
case 0xA3: parseInviteToParty(msg); break;
case 0xA4: parseJoinParty(msg); break;
case 0xA5: parseRevokePartyInvite(msg); break;
case 0xA6: parsePassPartyLeadership(msg); break;
case 0xA7: addGameTask(&Game::playerLeaveParty, player->getID()); break;
case 0xA8: parseEnableSharedPartyExperience(msg); break;
case 0xAA: addGameTask(&Game::playerCreatePrivateChannel, player->getID()); break;
case 0xAB: parseChannelInvite(msg); break;
case 0xAC: parseChannelExclude(msg); break;
case 0xBE: addGameTask(&Game::playerCancelAttackAndFollow, player->getID()); break;
case 0xC9: /* update tile */ break;
case 0xCA: parseUpdateContainer(msg); break;
case 0xCB: parseBrowseField(msg); break;
case 0xCC: parseSeekInContainer(msg); break;
case 0xD2: addGameTask(&Game::playerRequestOutfit, player->getID()); break;
case 0xD3: parseSetOutfit(msg); break;
case 0xD4: parseToggleMount(msg); break;
case 0xD5: parseApplyImbuemente(msg); break;
case 0xD6: parseClearingImbuement(msg); break;
case 0xD7: parseCloseImbuingWindow(msg); break;
case 0xDC: parseAddVip(msg); break;
case 0xDD: parseRemoveVip(msg); break;
case 0xDE: parseEditVip(msg); break;
case 0xE6: parseBugReport(msg); break;
case 0xE7: /* thank you */ break;
case 0xE8: parseDebugAssert(msg); break;
case 0xEF: if (!g_config.getBoolean(ConfigManager::STOREMODULES)) { parseCoinTransfer(msg); } break; /* premium coins transfer */
case 0xF0: addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerShowQuestLog, player->getID()); break;
case 0xF1: parseQuestLine(msg); break;
case 0xF2: parseRuleViolationReport(msg); break;
case 0xF3: /* get object info */ break;
case 0xF4: parseMarketLeave(); break;
case 0xF5: parseMarketBrowse(msg); break;
case 0xF6: parseMarketCreateOffer(msg); break;
case 0xF7: parseMarketCancelOffer(msg); break;
case 0xF8: parseMarketAcceptOffer(msg); break;
case 0xF9: parseModalWindowAnswer(msg); break;
case 0xFA: if (!g_config.getBoolean(ConfigManager::STOREMODULES)) { parseStoreOpen(msg); } break;
case 0xFB: if (!g_config.getBoolean(ConfigManager::STOREMODULES)) { parseStoreRequestOffers(msg); } break;
case 0xFC: if (!g_config.getBoolean(ConfigManager::STOREMODULES)) { parseStoreBuyOffer(msg); } break;
// case 0xFD: parseStoreOpenTransactionHistory(msg); break;
// case 0xFE: parseStoreRequestTransactionHistory(msg); break;
//case 0x77 Equip Hotkey.
//case 0xDF, 0xE0, 0xE1, 0xFB, 0xFC, 0xFD, 0xFE Premium Shop.
default:
// std::cout << "Player: " << player->getName() << " sent an unknown packet header: 0x" << std::hex << static_cast<uint16_t>(recvbyte) << std::dec << "!" << std::endl;
break;
}
/* temporary solution to disconnections while opening store
if (msg.isOverrun()) {
disconnect();
}
*/
}
void ProtocolGame::GetTileDescription(const Tile* tile, NetworkMessage& msg)
{
if (version < 1200) {
msg.add<uint16_t>(0x00); //environmental effects
}
int32_t count;
Item* ground = tile->getGround();
if (ground) {
AddItem(msg, ground);
count = 1;
} else {
count = 0;
}
const TileItemVector* items = tile->getItemList();
if (items) {
for (auto it = items->getBeginTopItem(), end = items->getEndTopItem(); it != end; ++it) {
AddItem(msg, *it);
count++;
if (count == 9 && tile->getPosition() == player->getPosition()) {
break;
}
else if (count == 10) {
return;
}
}
}
const CreatureVector* creatures = tile->getCreatures();
if (creatures) {
bool playerAdded = false;
for (const Creature* creature : boost::adaptors::reverse(*creatures)) {
if (!player->canSeeCreature(creature)) {
continue;
}
if (tile->getPosition() == player->getPosition() && count == 9 && !playerAdded) {
creature = player;
}
if (creature->getID() == player->getID()) {
playerAdded = true;
}
bool known;
uint32_t removedKnown;
checkCreatureAsKnown(creature->getID(), known, removedKnown);
AddCreature(msg, creature, known, removedKnown);
if (++count == 10) {
return;
}
}
}
if (items) {
for (auto it = items->getBeginDownItem(), end = items->getEndDownItem(); it != end; ++it) {
AddItem(msg, *it);
if (++count == 10) {
return;
}
}
}
}
void ProtocolGame::GetMapDescription(int32_t x, int32_t y, int32_t z, int32_t width, int32_t height, NetworkMessage& msg)
{
int32_t skip = -1;
int32_t startz, endz, zstep;
if (z > 7) {
startz = z - 2;
endz = std::min<int32_t>(MAP_MAX_LAYERS - 1, z + 2);
zstep = 1;
} else {
startz = 7;
endz = 0;
zstep = -1;
}
for (int32_t nz = startz; nz != endz + zstep; nz += zstep) {
GetFloorDescription(msg, x, y, nz, width, height, z - nz, skip);
}
if (skip >= 0) {
msg.addByte(skip);
msg.addByte(0xFF);
}
}
void ProtocolGame::GetFloorDescription(NetworkMessage& msg, int32_t x, int32_t y, int32_t z, int32_t width, int32_t height, int32_t offset, int32_t& skip)
{
for (int32_t nx = 0; nx < width; nx++) {
for (int32_t ny = 0; ny < height; ny++) {
Tile* tile = g_game.map.getTile(x + nx + offset, y + ny + offset, z);
if (tile) {
if (skip >= 0) {
msg.addByte(skip);
msg.addByte(0xFF);
}
skip = 0;
GetTileDescription(tile, msg);
} else if (skip == 0xFE) {
msg.addByte(0xFF);
msg.addByte(0xFF);
skip = -1;
} else {
++skip;
}
}
}
}
void ProtocolGame::checkCreatureAsKnown(uint32_t id, bool& known, uint32_t& removedKnown)
{
auto result = knownCreatureSet.insert(id);
if (!result.second) {
known = true;
return;
}
known = false;
if (knownCreatureSet.size() > 1300) {
// Look for a creature to remove
for (auto it = knownCreatureSet.begin(), end = knownCreatureSet.end(); it != end; ++it) {
Creature* creature = g_game.getCreatureByID(*it);
if (!canSee(creature)) {
removedKnown = *it;
knownCreatureSet.erase(it);
return;
}
}
// Bad situation. Let's just remove anyone.
auto it = knownCreatureSet.begin();
if (*it == id) {
++it;
}
removedKnown = *it;
knownCreatureSet.erase(it);
} else {
removedKnown = 0;
}
}
bool ProtocolGame::canSee(const Creature* c) const
{
if (!c || !player || c->isRemoved()) {
return false;
}
if (!player->canSeeCreature(c)) {
return false;
}
return canSee(c->getPosition());
}
bool ProtocolGame::canSee(const Position& pos) const
{
return canSee(pos.x, pos.y, pos.z);
}
bool ProtocolGame::canSee(int32_t x, int32_t y, int32_t z) const
{
if (!player) {
return false;
}
const Position& myPos = player->getPosition();
if (myPos.z <= 7) {
//we are on ground level or above (7 -> 0)
//view is from 7 -> 0
if (z > 7) {
return false;
}
} else if (myPos.z >= 8) {
//we are underground (8 -> 15)
//view is +/- 2 from the floor we stand on
if (std::abs(myPos.getZ() - z) > 2) {
return false;
}
}
//negative offset means that the action taken place is on a lower floor than ourself
int32_t offsetz = myPos.getZ() - z;
if ((x >= myPos.getX() - 8 + offsetz) && (x <= myPos.getX() + 9 + offsetz) &&
(y >= myPos.getY() - 6 + offsetz) && (y <= myPos.getY() + 7 + offsetz)) {
return true;
}
return false;
}
// Parse methods
void ProtocolGame::parseChannelInvite(NetworkMessage& msg)
{
const std::string name = msg.getString();
addGameTask(&Game::playerChannelInvite, player->getID(), name);
}
void ProtocolGame::parseChannelExclude(NetworkMessage& msg)
{
const std::string name = msg.getString();
addGameTask(&Game::playerChannelExclude, player->getID(), name);
}
void ProtocolGame::parseOpenChannel(NetworkMessage& msg)
{
uint16_t channelId = msg.get<uint16_t>();
addGameTask(&Game::playerOpenChannel, player->getID(), channelId);
}
void ProtocolGame::parseCloseChannel(NetworkMessage& msg)
{
uint16_t channelId = msg.get<uint16_t>();
addGameTask(&Game::playerCloseChannel, player->getID(), channelId);
}
void ProtocolGame::parseOpenPrivateChannel(NetworkMessage& msg)
{
const std::string receiver = msg.getString();
addGameTask(&Game::playerOpenPrivateChannel, player->getID(), receiver);
}
void ProtocolGame::parseAutoWalk(NetworkMessage& msg)
{
uint8_t numdirs = msg.getByte();
if (numdirs == 0 || (msg.getBufferPosition() + numdirs) != (msg.getLength() + 8)) {
return;
}
msg.skipBytes(numdirs);
std::forward_list<Direction> path;
for (uint8_t i = 0; i < numdirs; ++i) {
uint8_t rawdir = msg.getPreviousByte();
switch (rawdir) {
case 1: path.push_front(DIRECTION_EAST); break;
case 2: path.push_front(DIRECTION_NORTHEAST); break;
case 3: path.push_front(DIRECTION_NORTH); break;
case 4: path.push_front(DIRECTION_NORTHWEST); break;
case 5: path.push_front(DIRECTION_WEST); break;
case 6: path.push_front(DIRECTION_SOUTHWEST); break;
case 7: path.push_front(DIRECTION_SOUTH); break;
case 8: path.push_front(DIRECTION_SOUTHEAST); break;
default: break;
}
}
if (path.empty()) {
return;
}
addGameTask(&Game::playerAutoWalk, player->getID(), path);
}
void ProtocolGame::parseSetOutfit(NetworkMessage& msg)
{
Outfit_t newOutfit;
newOutfit.lookType = msg.get<uint16_t>();
newOutfit.lookHead = msg.getByte();
newOutfit.lookBody = msg.getByte();
newOutfit.lookLegs = msg.getByte();
newOutfit.lookFeet = msg.getByte();
newOutfit.lookAddons = msg.getByte();
newOutfit.lookMount = msg.get<uint16_t>();
addGameTask(&Game::playerChangeOutfit, player->getID(), newOutfit);
}
void ProtocolGame::parseToggleMount(NetworkMessage& msg)
{
bool mount = msg.getByte() != 0;
addGameTask(&Game::playerToggleMount, player->getID(), mount);
}
void ProtocolGame::parseApplyImbuemente(NetworkMessage& msg)
{
uint8_t slot = msg.getByte();
uint32_t imbuementId = msg.get<uint32_t>();
bool protectionCharm = msg.getByte() != 0x00;
addGameTask(&Game::playerApplyImbuement, player->getID(), imbuementId, slot, protectionCharm);
}
void ProtocolGame::parseClearingImbuement(NetworkMessage& msg)
{
uint8_t slot = msg.getByte();
addGameTask(&Game::playerClearingImbuement, player->getID(), slot);
}
void ProtocolGame::parseCloseImbuingWindow(NetworkMessage&)
{
addGameTask(&Game::playerCloseImbuingWindow, player->getID());
}
void ProtocolGame::parseUseItem(NetworkMessage& msg)
{
Position pos = msg.getPosition();
uint16_t spriteId = msg.get<uint16_t>();
uint8_t stackpos = msg.getByte();
uint8_t index = msg.getByte();
addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerUseItem, player->getID(), pos, stackpos, index, spriteId);
}
void ProtocolGame::parseUseItemEx(NetworkMessage& msg)
{
Position fromPos = msg.getPosition();
uint16_t fromSpriteId = msg.get<uint16_t>();
uint8_t fromStackPos = msg.getByte();
Position toPos = msg.getPosition();
uint16_t toSpriteId = msg.get<uint16_t>();
uint8_t toStackPos = msg.getByte();
addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerUseItemEx, player->getID(), fromPos, fromStackPos, fromSpriteId, toPos, toStackPos, toSpriteId);
}
void ProtocolGame::parseUseWithCreature(NetworkMessage& msg)
{
Position fromPos = msg.getPosition();
uint16_t spriteId = msg.get<uint16_t>();
uint8_t fromStackPos = msg.getByte();
uint32_t creatureId = msg.get<uint32_t>();
addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerUseWithCreature, player->getID(), fromPos, fromStackPos, creatureId, spriteId);
}
void ProtocolGame::parseCloseContainer(NetworkMessage& msg)
{
uint8_t cid = msg.getByte();
addGameTask(&Game::playerCloseContainer, player->getID(), cid);
}
void ProtocolGame::parseUpArrowContainer(NetworkMessage& msg)
{
uint8_t cid = msg.getByte();
addGameTask(&Game::playerMoveUpContainer, player->getID(), cid);
}
void ProtocolGame::parseUpdateContainer(NetworkMessage& msg)
{
uint8_t cid = msg.getByte();
addGameTask(&Game::playerUpdateContainer, player->getID(), cid);
}
void ProtocolGame::parseThrow(NetworkMessage& msg)
{
Position fromPos = msg.getPosition();
uint16_t spriteId = msg.get<uint16_t>();
uint8_t fromStackpos = msg.getByte();
Position toPos = msg.getPosition();
uint8_t count = msg.getByte();
if (toPos != fromPos) {
addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerMoveThing, player->getID(), fromPos, spriteId, fromStackpos, toPos, count);
}
}
void ProtocolGame::parseLookAt(NetworkMessage& msg)
{
Position pos = msg.getPosition();
msg.skipBytes(2); // spriteId
uint8_t stackpos = msg.getByte();
addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerLookAt, player->getID(), pos, stackpos);
}
void ProtocolGame::parseLookInBattleList(NetworkMessage& msg)
{
uint32_t creatureId = msg.get<uint32_t>();
addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerLookInBattleList, player->getID(), creatureId);
}
void ProtocolGame::parseSay(NetworkMessage& msg)
{
std::string receiver;
uint16_t channelId;
SpeakClasses type = static_cast<SpeakClasses>(msg.getByte());
switch (type) {
case TALKTYPE_PRIVATE_TO:
case TALKTYPE_PRIVATE_RED_TO:
receiver = msg.getString();
channelId = 0;
break;
case TALKTYPE_CHANNEL_Y:
case TALKTYPE_CHANNEL_R1:
channelId = msg.get<uint16_t>();
break;
default:
channelId = 0;
break;
}
const std::string text = msg.getString();
if (text.length() > 255) {
return;
}
addGameTask(&Game::playerSay, player->getID(), channelId, type, receiver, text);
}
void ProtocolGame::parseFightModes(NetworkMessage& msg)
{
uint8_t rawFightMode = msg.getByte(); // 1 - offensive, 2 - balanced, 3 - defensive
uint8_t rawChaseMode = msg.getByte(); // 0 - stand while fightning, 1 - chase opponent
uint8_t rawSecureMode = msg.getByte(); // 0 - can't attack unmarked, 1 - can attack unmarked
// uint8_t rawPvpMode = msg.getByte(); // pvp mode introduced in 10.0
fightMode_t fightMode;
if (rawFightMode == 1) {
fightMode = FIGHTMODE_ATTACK;
} else if (rawFightMode == 2) {
fightMode = FIGHTMODE_BALANCED;
} else {
fightMode = FIGHTMODE_DEFENSE;
}
addGameTask(&Game::playerSetFightModes, player->getID(), fightMode, rawChaseMode != 0, rawSecureMode != 0);
}
void ProtocolGame::parseAttack(NetworkMessage& msg)
{
uint32_t creatureId = msg.get<uint32_t>();
// msg.get<uint32_t>(); creatureId (same as above)
addGameTask(&Game::playerSetAttackedCreature, player->getID(), creatureId);
}
void ProtocolGame::parseFollow(NetworkMessage& msg)
{
uint32_t creatureId = msg.get<uint32_t>();
// msg.get<uint32_t>(); creatureId (same as above)
addGameTask(&Game::playerFollowCreature, player->getID(), creatureId);
}
void ProtocolGame::parseTextWindow(NetworkMessage& msg)
{
uint32_t windowTextId = msg.get<uint32_t>();
const std::string newText = msg.getString();
addGameTask(&Game::playerWriteItem, player->getID(), windowTextId, newText);
}
void ProtocolGame::parseHouseWindow(NetworkMessage& msg)
{
uint8_t doorId = msg.getByte();
uint32_t id = msg.get<uint32_t>();
const std::string text = msg.getString();
addGameTask(&Game::playerUpdateHouseWindow, player->getID(), doorId, id, text);
}
void ProtocolGame::parseLookInShop(NetworkMessage& msg)
{
uint16_t id = msg.get<uint16_t>();
uint8_t count = msg.getByte();
addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerLookInShop, player->getID(), id, count);
}
void ProtocolGame::parsePlayerPurchase(NetworkMessage& msg)
{
uint16_t id = msg.get<uint16_t>();
uint8_t count = msg.getByte();
uint8_t amount = msg.getByte();
bool ignoreCap = msg.getByte() != 0;
bool inBackpacks = msg.getByte() != 0;
addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerPurchaseItem, player->getID(), id, count, amount, ignoreCap, inBackpacks);
}
void ProtocolGame::parsePlayerSale(NetworkMessage& msg)
{
uint16_t id = msg.get<uint16_t>();
uint8_t count = msg.getByte();
uint8_t amount = msg.getByte();
bool ignoreEquipped = msg.getByte() != 0;
addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerSellItem, player->getID(), id, count, amount, ignoreEquipped);
}
void ProtocolGame::parseRequestTrade(NetworkMessage& msg)
{
Position pos = msg.getPosition();
uint16_t spriteId = msg.get<uint16_t>();
uint8_t stackpos = msg.getByte();
uint32_t playerId = msg.get<uint32_t>();
addGameTask(&Game::playerRequestTrade, player->getID(), pos, stackpos, playerId, spriteId);
}
void ProtocolGame::parseLookInTrade(NetworkMessage& msg)
{
bool counterOffer = (msg.getByte() == 0x01);
uint8_t index = msg.getByte();
addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerLookInTrade, player->getID(), counterOffer, index);
}
void ProtocolGame::parseAddVip(NetworkMessage& msg)
{
const std::string name = msg.getString();
addGameTask(&Game::playerRequestAddVip, player->getID(), name);
}
void ProtocolGame::parseRemoveVip(NetworkMessage& msg)
{
uint32_t guid = msg.get<uint32_t>();
addGameTask(&Game::playerRequestRemoveVip, player->getID(), guid);
}
void ProtocolGame::parseEditVip(NetworkMessage& msg)
{
uint32_t guid = msg.get<uint32_t>();
const std::string description = msg.getString();
uint32_t icon = std::min<uint32_t>(10, msg.get<uint32_t>()); // 10 is max icon in 9.63
bool notify = msg.getByte() != 0;
addGameTask(&Game::playerRequestEditVip, player->getID(), guid, description, icon, notify);
}
void ProtocolGame::parseRotateItem(NetworkMessage& msg)
{
Position pos = msg.getPosition();
uint16_t spriteId = msg.get<uint16_t>();
uint8_t stackpos = msg.getByte();
addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerRotateItem, player->getID(), pos, stackpos, spriteId);
}
void ProtocolGame::parseWrapableItem(NetworkMessage& msg)
{
Position pos = msg.getPosition();
uint16_t spriteId = msg.get<uint16_t>();
uint8_t stackpos = msg.getByte();
addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerWrapableItem, player->getID(), pos, stackpos, spriteId);
}
void ProtocolGame::parseRuleViolationReport(NetworkMessage &msg)
{
uint8_t reportType = msg.getByte();
uint8_t reportReason = msg.getByte();
const std::string& targetName = msg.getString();
const std::string& comment = msg.getString();
std::string translation;
if (reportType == REPORT_TYPE_NAME) {
translation = msg.getString();
} else if (reportType == REPORT_TYPE_STATEMENT) {
translation = msg.getString();
msg.get<uint32_t>(); // statement id, used to get whatever player have said, we don't log that.
}
addGameTask(&Game::playerReportRuleViolationReport, player->getID(), targetName, reportType, reportReason, comment, translation);
}
void ProtocolGame::parseBugReport(NetworkMessage& msg)
{
uint8_t category = msg.getByte();
std::string message = msg.getString();
Position position;
if (category == BUG_CATEGORY_MAP) {
position = msg.getPosition();
}
addGameTask(&Game::playerReportBug, player->getID(), message, position, category);
}
void ProtocolGame::parseDebugAssert(NetworkMessage& msg)
{
if (debugAssertSent) {
return;
}
debugAssertSent = true;
std::string assertLine = msg.getString();
std::string date = msg.getString();
std::string description = msg.getString();
std::string comment = msg.getString();
addGameTask(&Game::playerDebugAssert, player->getID(), assertLine, date, description, comment);
}
void ProtocolGame::parseInviteToParty(NetworkMessage& msg)
{
uint32_t targetId = msg.get<uint32_t>();
addGameTask(&Game::playerInviteToParty, player->getID(), targetId);
}
void ProtocolGame::parseJoinParty(NetworkMessage& msg)
{
uint32_t targetId = msg.get<uint32_t>();
addGameTask(&Game::playerJoinParty, player->getID(), targetId);
}
void ProtocolGame::parseRevokePartyInvite(NetworkMessage& msg)
{
uint32_t targetId = msg.get<uint32_t>();
addGameTask(&Game::playerRevokePartyInvitation, player->getID(), targetId);
}
void ProtocolGame::parsePassPartyLeadership(NetworkMessage& msg)
{
uint32_t targetId = msg.get<uint32_t>();
addGameTask(&Game::playerPassPartyLeadership, player->getID(), targetId);
}
void ProtocolGame::parseEnableSharedPartyExperience(NetworkMessage& msg)
{
bool sharedExpActive = msg.getByte() == 1;
addGameTask(&Game::playerEnableSharedPartyExperience, player->getID(), sharedExpActive);
}
void ProtocolGame::parseQuestLine(NetworkMessage& msg)
{
uint16_t questId = msg.get<uint16_t>();
addGameTask(&Game::playerShowQuestLine, player->getID(), questId);
}
void ProtocolGame::parseMarketLeave()
{
addGameTask(&Game::playerLeaveMarket, player->getID());
}
void ProtocolGame::parseMarketBrowse(NetworkMessage& msg)
{
uint16_t browseId = msg.get<uint16_t>();
if (browseId == MARKETREQUEST_OWN_OFFERS) {
addGameTask(&Game::playerBrowseMarketOwnOffers, player->getID());
} else if (browseId == MARKETREQUEST_OWN_HISTORY) {
addGameTask(&Game::playerBrowseMarketOwnHistory, player->getID());
} else {
addGameTask(&Game::playerBrowseMarket, player->getID(), browseId);
}
}
void ProtocolGame::parseStoreOpen(NetworkMessage &msg) {
uint8_t serviceType = msg.getByte();
addGameTaskTimed(600,&Game::playerStoreOpen, player->getID(), serviceType);
}
void ProtocolGame::parseStoreRequestOffers(NetworkMessage &message) {
//StoreService_t serviceType = SERVICE_STANDARD;
message.getByte(); // discard service type byte // version >= 1092
std::string categoryName = message.getString();
const int16_t index = g_game.gameStore.getCategoryIndexByName(categoryName);
if (index >= 0) {
addGameTaskTimed(350, &Game::playerShowStoreCategoryOffers, player->getID(),
g_game.gameStore.getCategoryOffers().at(index));
} else {
std::cout << "[Warning - ProtocolGame::parseStoreRequestOffers] requested category: \""<< categoryName <<"\" doesn't exists" << std::endl;
}
}
void ProtocolGame::parseStoreBuyOffer(NetworkMessage &message) {
uint32_t offerId = message.get<uint32_t>();
uint8_t productType = message.getByte(); //used only in return of a namechange offer request
std::string additionalInfo;
if (productType == ADDITIONALINFO) {
additionalInfo = message.getString();
}
addGameTaskTimed(350, &Game::playerBuyStoreOffer, player->getID(), offerId, productType, additionalInfo);
}
void ProtocolGame::parseStoreOpenTransactionHistory(NetworkMessage& msg)
{
uint8_t entriesPerPage = msg.getByte();
if (entriesPerPage > 0 && entriesPerPage != GameStore::HISTORY_ENTRIES_PER_PAGE) {
GameStore::HISTORY_ENTRIES_PER_PAGE=entriesPerPage;
}
addGameTaskTimed(2000, &Game::playerStoreTransactionHistory, player->getID(), 1);
}
void ProtocolGame::parseStoreRequestTransactionHistory(NetworkMessage& msg)
{
uint32_t pageNumber = msg.get<uint32_t>();
addGameTaskTimed(2000,&Game::playerStoreTransactionHistory,player->getID(), pageNumber);
}
void ProtocolGame::parseCoinTransfer(NetworkMessage& msg)
{
std::string receiverName =msg.getString();
uint32_t amount = msg.get<uint32_t>();
if (amount > 0) {
addGameTaskTimed(350, &Game::playerCoinTransfer, player->getID(), receiverName, amount);
}
updateCoinBalance();
}
void ProtocolGame::parseMarketCreateOffer(NetworkMessage& msg)
{
uint8_t type = msg.getByte();
uint16_t spriteId = msg.get<uint16_t>();
uint16_t amount = msg.get<uint16_t>();
uint32_t price = msg.get<uint32_t>();
bool anonymous = (msg.getByte() != 0);
if (amount > 0 && price > 0) {
addGameTask(&Game::playerCreateMarketOffer, player->getID(), type, spriteId, amount, price, anonymous);
}
}
void ProtocolGame::parseMarketCancelOffer(NetworkMessage& msg)
{
uint32_t timestamp = msg.get<uint32_t>();
uint16_t counter = msg.get<uint16_t>();
if (counter > 0) {
addGameTask(&Game::playerCancelMarketOffer, player->getID(), timestamp, counter);
}
updateCoinBalance();
}
void ProtocolGame::parseMarketAcceptOffer(NetworkMessage& msg)
{
uint32_t timestamp = msg.get<uint32_t>();
uint16_t counter = msg.get<uint16_t>();
uint16_t amount = msg.get<uint16_t>();
if (amount > 0 && counter > 0) {
addGameTask(&Game::playerAcceptMarketOffer, player->getID(), timestamp, counter, amount);
}
updateCoinBalance();
}
void ProtocolGame::parseModalWindowAnswer(NetworkMessage& msg)
{
uint32_t id = msg.get<uint32_t>();
uint8_t button = msg.getByte();
uint8_t choice = msg.getByte();
addGameTask(&Game::playerAnswerModalWindow, player->getID(), id, button, choice);
}
void ProtocolGame::parseBrowseField(NetworkMessage& msg)
{
const Position& pos = msg.getPosition();
addGameTask(&Game::playerBrowseField, player->getID(), pos);
}
void ProtocolGame::parseSeekInContainer(NetworkMessage& msg)
{
uint8_t containerId = msg.getByte();
uint16_t index = msg.get<uint16_t>();
addGameTask(&Game::playerSeekInContainer, player->getID(), containerId, index);
}
// Send methods
void ProtocolGame::sendOpenPrivateChannel(const std::string& receiver)
{
NetworkMessage msg;
msg.addByte(0xAD);
msg.addString(receiver);
writeToOutputBuffer(msg);
}
void ProtocolGame::sendChannelEvent(uint16_t channelId, const std::string& playerName, ChannelEvent_t channelEvent)
{
NetworkMessage msg;
msg.addByte(0xF3);
msg.add<uint16_t>(channelId);
msg.addString(playerName);
msg.addByte(channelEvent);
writeToOutputBuffer(msg);
}
void ProtocolGame::sendCreatureOutfit(const Creature* creature, const Outfit_t& outfit)
{
if (!canSee(creature)) {
return;
}
NetworkMessage msg;
msg.addByte(0x8E);
msg.add<uint32_t>(creature->getID());
AddOutfit(msg, outfit);
writeToOutputBuffer(msg);
}
void ProtocolGame::sendCreatureLight(const Creature* creature)
{
if (!canSee(creature)) {
return;
}
NetworkMessage msg;
AddCreatureLight(msg, creature);
writeToOutputBuffer(msg);
}
void ProtocolGame::sendWorldLight(const LightInfo& lightInfo)
{
NetworkMessage msg;
AddWorldLight(msg, lightInfo);
writeToOutputBuffer(msg);
}
void ProtocolGame::sendCreatureWalkthrough(const Creature* creature, bool walkthrough)
{
if (!canSee(creature)) {
return;
}
NetworkMessage msg;
msg.addByte(0x92);
msg.add<uint32_t>(creature->getID());
msg.addByte(walkthrough ? 0x00 : 0x01);
writeToOutputBuffer(msg);
}
void ProtocolGame::sendCreatureShield(const Creature* creature)
{
if (!canSee(creature)) {
return;
}
NetworkMessage msg;
msg.addByte(0x91);
msg.add<uint32_t>(creature->getID());
msg.addByte(player->getPartyShield(creature->getPlayer()));
writeToOutputBuffer(msg);
}
void ProtocolGame::sendCreatureSkull(const Creature* creature)
{
if (g_game.getWorldType() != WORLD_TYPE_PVP) {
return;
}
if (!canSee(creature)) {
return;
}
NetworkMessage msg;
msg.addByte(0x90);
msg.add<uint32_t>(creature->getID());
msg.addByte(player->getSkullClient(creature));
writeToOutputBuffer(msg);
}
void ProtocolGame::sendCreatureType(const Creature* creature, uint8_t creatureType)
{
NetworkMessage msg;
msg.addByte(0x95);
msg.add<uint32_t>(creature->getID());
msg.addByte(creatureType);
if (player->getOperatingSystem() == CLIENTOS_WINDOWS && player->getProtocolVersion() >= 1120) {
msg.addByte(creatureType); // type or any byte idk
}
if (creatureType == CREATURETYPE_SUMMONPLAYER && player->getProtocolVersion() >= 1120) {
const Creature* master = creature->getMaster();
if (master) {
msg.add<uint32_t>(master->getID());
}
}
writeToOutputBuffer(msg);
}
void ProtocolGame::sendCreatureHelpers(uint32_t creatureId, uint16_t helpers)
{
if (version >= 1185) {
return;
}
NetworkMessage msg;
msg.addByte(0x94);
msg.add<uint32_t>(creatureId);
msg.add<uint16_t>(helpers);
writeToOutputBuffer(msg);
}
void ProtocolGame::sendCreatureSquare(const Creature* creature, SquareColor_t color)
{
if (!canSee(creature)) {
return;
}
NetworkMessage msg;
msg.addByte(0x93);
msg.add<uint32_t>(creature->getID());
msg.addByte(0x01);
msg.addByte(color);
writeToOutputBuffer(msg);
}
void ProtocolGame::sendTutorial(uint8_t tutorialId)
{
NetworkMessage msg;
msg.addByte(0xDC);
msg.addByte(tutorialId);
writeToOutputBuffer(msg);
}
void ProtocolGame::sendAddMarker(const Position& pos, uint8_t markType, const std::string& desc)
{
NetworkMessage msg;
msg.addByte(0xDD);
msg.addPosition(pos);
msg.addByte(markType);
msg.addString(desc);
writeToOutputBuffer(msg);
}
void ProtocolGame::sendReLoginWindow(uint8_t unfairFightReduction)
{
NetworkMessage msg;
msg.addByte(0x28);
msg.addByte(0x00);
msg.addByte(unfairFightReduction);
if (version >= 1120) {
msg.addByte(0x00); // use death redemption (boolean)
}
writeToOutputBuffer(msg);
}
void ProtocolGame::sendStats()
{
NetworkMessage msg;
AddPlayerStats(msg);
writeToOutputBuffer(msg);
}
void ProtocolGame::sendBasicData()
{
NetworkMessage msg;
msg.addByte(0x9F);
if (player->isPremium()) {
msg.addByte(1);
msg.add<uint32_t>(time(nullptr) + (player->premiumDays * 86400));
} else {
msg.addByte(0);
msg.add<uint32_t>(0);
}
msg.addByte(player->getVocation()->getClientId());
// Prey window
if (player->getVocation()->getId() == 0) {
msg.addByte(0);
} else {
msg.addByte(1); // has reached Main (allow player to open Prey window)
}
std::list<uint16_t> spellsList = g_spells->getSpellsByVocation(player->getVocationId());
msg.add<uint16_t>(spellsList.size());
for (uint8_t sid : spellsList) {
msg.addByte(sid);
}
writeToOutputBuffer(msg);
}
void ProtocolGame::sendBlessStatus()
{
NetworkMessage msg;
uint8_t blessCount = 0;
uint8_t maxBlessings = (player->operatingSystem == CLIENTOS_NEW_WINDOWS) ? 8 : 6;
for (int i = 1; i <= maxBlessings; i++) {
if (player->hasBlessing(i)) {
blessCount++;
}
}
msg.addByte(0x9C);
if (blessCount >= 5) {
if (player->getProtocolVersion() >= 1120) {
uint8_t blessFlag = 0;
uint8_t maxFlag = static_cast<uint8_t>((maxBlessings == 8) ? 256 : 64);
for (int i = 2; i < maxFlag; i *= 2) {
blessFlag += i;
}
msg.add<uint16_t>(blessFlag - 1);
} else {
msg.add<uint16_t>(0x01);
}
} else {
msg.add<uint16_t>(0x00);
}
if (player->getProtocolVersion() >= 1120) {
msg.addByte((blessCount >= 5) ? 2 : 1); // 1 = Disabled | 2 = normal | 3 = green
}
writeToOutputBuffer(msg);
}
void ProtocolGame::sendStoreHighlight()
{
NetworkMessage msg;
bool haveSale = g_game.gameStore.haveCategoryByState(StoreState_t::SALE);
bool haveNewItem = g_game.gameStore.haveCategoryByState(StoreState_t::NEW);
msg.addByte(0x19);
msg.addByte((haveSale) ? 1 : 0);
msg.addByte((haveNewItem) ? 1 : 0);
writeToOutputBuffer(msg);
}
void ProtocolGame::sendPremiumTrigger()
{
if (!g_config.getBoolean(ConfigManager::FREE_PREMIUM)) {
NetworkMessage msg;
msg.addByte(0x9E);
msg.addByte(16);
for (uint16_t i = 0; i <= 15; i++) {
//PREMIUM_TRIGGER_TRAIN_OFFLINE = false, PREMIUM_TRIGGER_XP_BOOST = false, PREMIUM_TRIGGER_MARKET = false, PREMIUM_TRIGGER_VIP_LIST = false, PREMIUM_TRIGGER_DEPOT_SPACE = false, PREMIUM_TRIGGER_INVITE_PRIVCHAT = false
msg.addByte(0x01);
}
writeToOutputBuffer(msg);
}
}
// Send preyInfo
void ProtocolGame::sendPreyData()
{
NetworkMessage msg;
for (int i = 0; i < 3; i++) {
msg.addByte(0xE8);
msg.addByte(i);
msg.addByte(0x00);
msg.addByte(0x00);
msg.add<uint16_t>(0);
if (version >= 1190) {
msg.addByte(0x00); // wildCards
}
}
msg.addByte(0xEC);
msg.addByte(0xEE);
msg.addByte(0x0A);
msg.add<uint64_t>(0);
msg.addByte(0xEE);
msg.addByte(0x01);
msg.add<uint64_t>(0);
// prey prices
msg.addByte(0xE9);
msg.add<uint32_t>(0);
if (version >= 1190) {
msg.addByte(0x00); // unknown price
msg.addByte(0x00); // selectCreatureDirectly price (5 in tibia)
}
writeToOutputBuffer(msg);
}
void ProtocolGame::sendTextMessage(const TextMessage& message)
{
NetworkMessage msg;
msg.addByte(0xB4);
msg.addByte(message.type);
switch (message.type) {
case MESSAGE_DAMAGE_DEALT:
case MESSAGE_DAMAGE_RECEIVED:
case MESSAGE_DAMAGE_OTHERS: {
msg.addPosition(message.position);
msg.add<uint32_t>(message.primary.value);
msg.addByte(message.primary.color);
msg.add<uint32_t>(message.secondary.value);
msg.addByte(message.secondary.color);
break;
}
case MESSAGE_HEALED:
case MESSAGE_HEALED_OTHERS:
case MESSAGE_EXPERIENCE:
case MESSAGE_EXPERIENCE_OTHERS: {
msg.addPosition(message.position);
msg.add<uint32_t>(message.primary.value);
msg.addByte(message.primary.color);
break;
}
case MESSAGE_GUILD:
case MESSAGE_PARTY_MANAGEMENT:
case MESSAGE_PARTY:
msg.add<uint16_t>(message.channelId);
break;
default: {
break;
}
}
msg.addString(message.text);
writeToOutputBuffer(msg);
}
void ProtocolGame::sendClosePrivate(uint16_t channelId)
{
NetworkMessage msg;
msg.addByte(0xB3);
msg.add<uint16_t>(channelId);
writeToOutputBuffer(msg);
}
void ProtocolGame::sendCreatePrivateChannel(uint16_t channelId, const std::string& channelName)
{
NetworkMessage msg;
msg.addByte(0xB2);
msg.add<uint16_t>(channelId);
msg.addString(channelName);
msg.add<uint16_t>(0x01);
msg.addString(player->getName());
msg.add<uint16_t>(0x00);
writeToOutputBuffer(msg);
}
void ProtocolGame::sendChannelsDialog()
{
NetworkMessage msg;
msg.addByte(0xAB);
const ChannelList& list = g_chat->getChannelList(*player);
msg.addByte(list.size());
for (ChatChannel* channel : list) {
msg.add<uint16_t>(channel->getId());
msg.addString(channel->getName());
}
writeToOutputBuffer(msg);
}
void ProtocolGame::sendChannel(uint16_t channelId, const std::string& channelName, const UsersMap* channelUsers, const InvitedMap* invitedUsers)
{
NetworkMessage msg;
msg.addByte(0xAC);
msg.add<uint16_t>(channelId);
msg.addString(channelName);
if (channelUsers) {
msg.add<uint16_t>(channelUsers->size());
for (const auto& it : *channelUsers) {
msg.addString(it.second->getName());
}
} else {
msg.add<uint16_t>(0x00);
}
if (invitedUsers) {
msg.add<uint16_t>(invitedUsers->size());
for (const auto& it : *invitedUsers) {
msg.addString(it.second->getName());
}
} else {
msg.add<uint16_t>(0x00);
}
writeToOutputBuffer(msg);
}
void ProtocolGame::sendIcons(uint16_t icons)
{
NetworkMessage msg;
msg.addByte(0xA2);
if (version >= 1140) { // TODO: verify compatibility of the new icon range ( 16-31 )
msg.add<uint32_t>(icons);
} else {
msg.add<uint16_t>(icons);
}
writeToOutputBuffer(msg);
}
void ProtocolGame::sendChannelMessage(const std::string& author, const std::string& text, SpeakClasses type, uint16_t channel)
{
NetworkMessage msg;
msg.addByte(0xAA);
msg.add<uint32_t>(0x00);
msg.addString(author);
msg.add<uint16_t>(0x00);
msg.addByte(type);
msg.add<uint16_t>(channel);
msg.addString(text);
writeToOutputBuffer(msg);
}
void ProtocolGame::sendUnjustifiedPoints(const uint8_t& dayProgress, const uint8_t& dayLeft, const uint8_t& weekProgress, const uint8_t& weekLeft, const uint8_t& monthProgress, const uint8_t& monthLeft, const uint8_t& skullDuration)
{
NetworkMessage msg;
msg.addByte(0xB7);
msg.addByte(dayProgress);
msg.addByte(dayLeft);
msg.addByte(weekProgress);
msg.addByte(weekLeft);
msg.addByte(monthProgress);
msg.addByte(monthLeft);
msg.addByte(skullDuration);
writeToOutputBuffer(msg);
}
void ProtocolGame::sendContainer(uint8_t cid, const Container* container, bool hasParent, uint16_t firstIndex)
{
NetworkMessage msg;
msg.addByte(0x6E);
msg.addByte(cid);
if (container->getID() == ITEM_BROWSEFIELD) {
AddItem(msg, 1987, 1);
msg.addString("Browse Field");
} else {
AddItem(msg, container);
msg.addString(container->getName());
}
msg.addByte(container->capacity());
msg.addByte(hasParent ? 0x01 : 0x00);
msg.addByte(container->isUnlocked() ? 0x01 : 0x00); // Drag and drop
msg.addByte(container->hasPagination() ? 0x01 : 0x00); // Pagination
uint32_t containerSize = container->size();
msg.add<uint16_t>(containerSize);
msg.add<uint16_t>(firstIndex);
uint32_t maxItemsToSend;
if (container->hasPagination() && firstIndex > 0) {
maxItemsToSend = std::min<uint32_t>(container->capacity(), containerSize - firstIndex);
} else {
maxItemsToSend = container->capacity();
}
if (firstIndex >= containerSize) {
msg.addByte(0x00);
} else {
msg.addByte(std::min<uint32_t>(maxItemsToSend, containerSize));
uint32_t i = 0;
const ItemDeque& itemList = container->getItemList();
for (ItemDeque::const_iterator it = itemList.begin() + firstIndex, end = itemList.end(); i < maxItemsToSend && it != end; ++it, ++i) {
AddItem(msg, *it);
}
}
writeToOutputBuffer(msg);
}
void ProtocolGame::sendShop(Npc* npc, const ShopInfoList& itemList)
{
NetworkMessage msg;
msg.addByte(0x7A);
msg.addString(npc->getName());
uint16_t itemsToSend = std::min<size_t>(itemList.size(), std::numeric_limits<uint16_t>::max());
msg.add<uint16_t>(itemsToSend);
uint16_t i = 0;
for (auto it = itemList.begin(); i < itemsToSend; ++it, ++i) {
AddShopItem(msg, *it);
}
writeToOutputBuffer(msg);
}
void ProtocolGame::sendCloseShop()
{
NetworkMessage msg;
msg.addByte(0x7C);
writeToOutputBuffer(msg);
}
void ProtocolGame::sendClientCheck()
{
NetworkMessage msg;
msg.addByte(0x63);
msg.add<uint32_t>(1);
msg.addByte(1);
writeToOutputBuffer(msg);
}
void ProtocolGame::sendGameNews()
{
NetworkMessage msg;
msg.addByte(0x98);
msg.add<uint32_t>(1); // unknown
msg.addByte(1); //(0 = open | 1 = highlight)
writeToOutputBuffer(msg);
}
void ProtocolGame::sendResourceBalance(uint64_t money, uint64_t bank)
{
NetworkMessage msg;
msg.addByte(0xEE);
msg.addByte(0x00);
msg.add<uint64_t>(bank);
msg.addByte(0xEE);
msg.addByte(0x01);
msg.add<uint64_t>(money);
writeToOutputBuffer(msg);
}
void ProtocolGame::sendSaleItemList(const std::list<ShopInfo>& shop)
{
if (player->getProtocolVersion() >= 1100) {
sendResourceBalance(player->getMoney(), player->getBankBalance());
}
NetworkMessage msg;
msg.addByte(0x7B);
msg.add<uint64_t>(player->getMoney() + player->getBankBalance());
std::map<uint16_t, uint32_t> saleMap;
if (shop.size() <= 5) {
// For very small shops it's not worth it to create the complete map
for (const ShopInfo& shopInfo : shop) {
if (shopInfo.sellPrice == 0) {
continue;
}
int8_t subtype = -1;
const ItemType& itemType = Item::items[shopInfo.itemId];
if (itemType.hasSubType() && !itemType.stackable) {
subtype = (shopInfo.subType == 0 ? -1 : shopInfo.subType);
}
uint32_t count = player->getItemTypeCount(shopInfo.itemId, subtype);
if (count > 0) {
saleMap[shopInfo.itemId] = count;
}
}
} else {
// Large shop, it's better to get a cached map of all item counts and use it
// We need a temporary map since the finished map should only contain items
// available in the shop
std::map<uint32_t, uint32_t> tempSaleMap;
player->getAllItemTypeCount(tempSaleMap);
// We must still check manually for the special items that require subtype matches
// (That is, fluids such as potions etc., actually these items are very few since
// health potions now use their own ID)
for (const ShopInfo& shopInfo : shop) {
if (shopInfo.sellPrice == 0) {
continue;
}
int8_t subtype = -1;
const ItemType& itemType = Item::items[shopInfo.itemId];
if (itemType.hasSubType() && !itemType.stackable) {
subtype = (shopInfo.subType == 0 ? -1 : shopInfo.subType);
}
if (subtype != -1) {
uint32_t count;
if (!itemType.isFluidContainer() && !itemType.isSplash()) {
count = player->getItemTypeCount(shopInfo.itemId, subtype); // This shop item requires extra checks
} else {
count = subtype;
}
if (count > 0) {
saleMap[shopInfo.itemId] = count;
}
} else {
std::map<uint32_t, uint32_t>::const_iterator findIt = tempSaleMap.find(shopInfo.itemId);
if (findIt != tempSaleMap.end() && findIt->second > 0) {
saleMap[shopInfo.itemId] = findIt->second;
}
}
}
}
uint8_t itemsToSend = std::min<size_t>(saleMap.size(), std::numeric_limits<uint8_t>::max());
msg.addByte(itemsToSend);
uint8_t i = 0;
for (std::map<uint16_t, uint32_t>::const_iterator it = saleMap.begin(); i < itemsToSend; ++it, ++i) {
msg.addItemId(it->first);
msg.addByte(std::min<uint32_t>(it->second, std::numeric_limits<uint8_t>::max()));
}
writeToOutputBuffer(msg);
}
void ProtocolGame::sendMarketEnter(uint32_t depotId)
{
NetworkMessage msg;
msg.addByte(0xF6);
msg.add<uint64_t>(player->getBankBalance());
msg.addByte(std::min<uint32_t>(IOMarket::getPlayerOfferCount(player->getGUID()), std::numeric_limits<uint8_t>::max()));
DepotLocker* depotLocker = player->getDepotLocker(depotId);
if (!depotLocker) {
msg.add<uint16_t>(0x00);
writeToOutputBuffer(msg);
return;
}
player->setInMarket(true);
std::map<uint16_t, uint32_t> depotItems;
std::forward_list<Container*> containerList{depotLocker};
do {
Container* container = containerList.front();
containerList.pop_front();
for (Item* item : container->getItemList()) {
Container* c = item->getContainer();
if (c && !c->empty()) {
containerList.push_front(c);
continue;
}
const ItemType& itemType = Item::items[item->getID()];
if (itemType.wareId == 0) {
continue;
}
if (c && (!itemType.isContainer() || c->capacity() != itemType.maxItems)) {
continue;
}
if (!item->hasMarketAttributes()) {
continue;
}
depotItems[itemType.wareId] += Item::countByType(item, -1);
}
} while (!containerList.empty());
uint16_t itemsToSend = std::min<size_t>(depotItems.size(), std::numeric_limits<uint16_t>::max());
msg.add<uint16_t>(itemsToSend);
uint16_t i = 0;
for (std::map<uint16_t, uint32_t>::const_iterator it = depotItems.begin(); i < itemsToSend; ++it, ++i) {
msg.add<uint16_t>(it->first);
msg.add<uint16_t>(std::min<uint32_t>(0xFFFF, it->second));
}
writeToOutputBuffer(msg);
updateCoinBalance();
sendResourceBalance(player->getMoney(), player->getBankBalance());
}
void ProtocolGame::sendCoinBalance()
{
if (!player) {
return;
}
NetworkMessage msg;
msg.addByte(0xF2);
msg.addByte(0x01);
msg.addByte(0xDF);
msg.addByte(0x01);
msg.add<uint32_t>(player->coinBalance); //total coins
msg.add<uint32_t>(player->coinBalance); //transferable coins
writeToOutputBuffer(msg);
}
void ProtocolGame::updateCoinBalance()
{
NetworkMessage msg;
msg.addByte(0xF2);
msg.addByte(0x00);
writeToOutputBuffer(msg);
g_dispatcher.addTask(
createTask(std::bind([](ProtocolGame_ptr client) {
client->sendCoinBalance();
}, getThis()))
);
}
void ProtocolGame::sendMarketLeave()
{
NetworkMessage msg;
msg.addByte(0xF7);
writeToOutputBuffer(msg);
}
void ProtocolGame::sendMarketBrowseItem(uint16_t itemId, const MarketOfferList& buyOffers, const MarketOfferList& sellOffers)
{
NetworkMessage msg;
msg.addByte(0xF9);
msg.addItemId(itemId);
msg.add<uint32_t>(buyOffers.size());
for (const MarketOffer& offer : buyOffers) {
msg.add<uint32_t>(offer.timestamp);
msg.add<uint16_t>(offer.counter);
msg.add<uint16_t>(offer.amount);
msg.add<uint32_t>(offer.price);
msg.addString(offer.playerName);
}
msg.add<uint32_t>(sellOffers.size());
for (const MarketOffer& offer : sellOffers) {
msg.add<uint32_t>(offer.timestamp);
msg.add<uint16_t>(offer.counter);
msg.add<uint16_t>(offer.amount);
msg.add<uint32_t>(offer.price);
msg.addString(offer.playerName);
}
updateCoinBalance();
writeToOutputBuffer(msg);
}
void ProtocolGame::sendMarketAcceptOffer(const MarketOfferEx& offer)
{
NetworkMessage msg;
msg.addByte(0xF9);
msg.addItemId(offer.itemId);
if (offer.type == MARKETACTION_BUY) {
msg.add<uint32_t>(0x01);
msg.add<uint32_t>(offer.timestamp);
msg.add<uint16_t>(offer.counter);
msg.add<uint16_t>(offer.amount);
msg.add<uint32_t>(offer.price);
msg.addString(offer.playerName);
msg.add<uint32_t>(0x00);
} else {
msg.add<uint32_t>(0x00);
msg.add<uint32_t>(0x01);
msg.add<uint32_t>(offer.timestamp);
msg.add<uint16_t>(offer.counter);
msg.add<uint16_t>(offer.amount);
msg.add<uint32_t>(offer.price);
msg.addString(offer.playerName);
}
writeToOutputBuffer(msg);
}
void ProtocolGame::sendMarketBrowseOwnOffers(const MarketOfferList& buyOffers, const MarketOfferList& sellOffers)
{
NetworkMessage msg;
msg.addByte(0xF9);
msg.add<uint16_t>(MARKETREQUEST_OWN_OFFERS);
msg.add<uint32_t>(buyOffers.size());
for (const MarketOffer& offer : buyOffers) {
msg.add<uint32_t>(offer.timestamp);
msg.add<uint16_t>(offer.counter);
msg.addItemId(offer.itemId);
msg.add<uint16_t>(offer.amount);
msg.add<uint32_t>(offer.price);
}
msg.add<uint32_t>(sellOffers.size());
for (const MarketOffer& offer : sellOffers) {
msg.add<uint32_t>(offer.timestamp);
msg.add<uint16_t>(offer.counter);
msg.addItemId(offer.itemId);
msg.add<uint16_t>(offer.amount);
msg.add<uint32_t>(offer.price);
}
writeToOutputBuffer(msg);
}
void ProtocolGame::sendMarketCancelOffer(const MarketOfferEx& offer)
{
NetworkMessage msg;
msg.addByte(0xF9);
msg.add<uint16_t>(MARKETREQUEST_OWN_OFFERS);
if (offer.type == MARKETACTION_BUY) {
msg.add<uint32_t>(0x01);
msg.add<uint32_t>(offer.timestamp);
msg.add<uint16_t>(offer.counter);
msg.addItemId(offer.itemId);
msg.add<uint16_t>(offer.amount);
msg.add<uint32_t>(offer.price);
msg.add<uint32_t>(0x00);
} else {
msg.add<uint32_t>(0x00);
msg.add<uint32_t>(0x01);
msg.add<uint32_t>(offer.timestamp);
msg.add<uint16_t>(offer.counter);
msg.addItemId(offer.itemId);
msg.add<uint16_t>(offer.amount);
msg.add<uint32_t>(offer.price);
}
writeToOutputBuffer(msg);
}
void ProtocolGame::sendMarketBrowseOwnHistory(const HistoryMarketOfferList& buyOffers, const HistoryMarketOfferList& sellOffers)
{
uint32_t i = 0;
std::map<uint32_t, uint16_t> counterMap;
uint32_t buyOffersToSend = std::min<uint32_t>(buyOffers.size(), 810 + std::max<int32_t>(0, 810 - sellOffers.size()));
uint32_t sellOffersToSend = std::min<uint32_t>(sellOffers.size(), 810 + std::max<int32_t>(0, 810 - buyOffers.size()));
NetworkMessage msg;
msg.addByte(0xF9);
msg.add<uint16_t>(MARKETREQUEST_OWN_HISTORY);
msg.add<uint32_t>(buyOffersToSend);
for (auto it = buyOffers.begin(); i < buyOffersToSend; ++it, ++i) {
msg.add<uint32_t>(it->timestamp);
msg.add<uint16_t>(counterMap[it->timestamp]++);
msg.addItemId(it->itemId);
msg.add<uint16_t>(it->amount);
msg.add<uint32_t>(it->price);
msg.addByte(it->state);
}
counterMap.clear();
i = 0;
msg.add<uint32_t>(sellOffersToSend);
for (auto it = sellOffers.begin(); i < sellOffersToSend; ++it, ++i) {
msg.add<uint32_t>(it->timestamp);
msg.add<uint16_t>(counterMap[it->timestamp]++);
msg.addItemId(it->itemId);
msg.add<uint16_t>(it->amount);
msg.add<uint32_t>(it->price);
msg.addByte(it->state);
}
writeToOutputBuffer(msg);
}
void ProtocolGame::sendMarketDetail(uint16_t itemId)
{
NetworkMessage msg;
msg.addByte(0xF8);
msg.addItemId(itemId);
const ItemType& it = Item::items[itemId];
if (it.armor != 0) {
msg.addString(std::to_string(it.armor));
} else {
msg.add<uint16_t>(0x00);
}
if (it.attack != 0) {
// TODO: chance to hit, range
// example:
// "attack +x, chance to hit +y%, z fields"
if (it.abilities && it.abilities->elementType != COMBAT_NONE && it.abilities->elementDamage != 0) {
std::ostringstream ss;
ss << it.attack << " physical +" << it.abilities->elementDamage << ' ' << getCombatName(it.abilities->elementType);
msg.addString(ss.str());
} else {
msg.addString(std::to_string(it.attack));
}
} else {
msg.add<uint16_t>(0x00);
}
if (it.isContainer()) {
msg.addString(std::to_string(it.maxItems));
} else {
msg.add<uint16_t>(0x00);
}
if (it.defense != 0) {
if (it.extraDefense != 0) {
std::ostringstream ss;
ss << it.defense << ' ' << std::showpos << it.extraDefense << std::noshowpos;
msg.addString(ss.str());
} else {
msg.addString(std::to_string(it.defense));
}
} else {
msg.add<uint16_t>(0x00);
}
if (!it.description.empty()) {
const std::string& descr = it.description;
if (descr.back() == '.') {
msg.addString(std::string(descr, 0, descr.length() - 1));
} else {
msg.addString(descr);
}
} else {
msg.add<uint16_t>(0x00);
}
if (it.decayTime != 0) {
std::ostringstream ss;
ss << it.decayTime << " seconds";
msg.addString(ss.str());
} else {
msg.add<uint16_t>(0x00);
}
if (it.abilities) {
std::ostringstream ss;
bool separator = false;
for (size_t i = 0; i < COMBAT_COUNT; ++i) {
if (it.abilities->absorbPercent == 0) {
continue;
}
if (separator) {
ss << ", ";
} else {
separator = true;
}
ss << getCombatName(indexToCombatType(i)) << ' ' << std::showpos << it.abilities->absorbPercent << std::noshowpos << '%';
}
msg.addString(ss.str());
} else {
msg.add<uint16_t>(0x00);
}
if (it.minReqLevel != 0) {
msg.addString(std::to_string(it.minReqLevel));
} else {
msg.add<uint16_t>(0x00);
}
if (it.minReqMagicLevel != 0) {
msg.addString(std::to_string(it.minReqMagicLevel));
} else {
msg.add<uint16_t>(0x00);
}
msg.addString(it.vocationString);
msg.addString(it.runeSpellName);
if (it.abilities) {
std::ostringstream ss;
bool separator = false;
for (uint8_t i = SKILL_FIRST; i <= SKILL_FISHING; i++) {
if (!it.abilities->skills) {
continue;
}
if (separator) {
ss << ", ";
} else {
separator = true;
}
ss << getSkillName(i) << ' ' << std::showpos << it.abilities->skills << std::noshowpos;
}
for (uint8_t i = SKILL_CRITICAL_HIT_CHANCE; i <= SKILL_LAST; i++) {
if (!it.abilities->skills) {
continue;
}
if (separator) {
ss << ", ";
}
else {
separator = true;
}
ss << getSkillName(i) << ' ' << std::showpos << it.abilities->skills << std::noshowpos << '%';
}
if (it.abilities->stats[STAT_MAGICPOINTS] != 0) {
if (separator) {
ss << ", ";
} else {
separator = true;
}
ss << "magic level " << std::showpos << it.abilities->stats[STAT_MAGICPOINTS] << std::noshowpos;
}
if (it.abilities->speed != 0) {
if (separator) {
ss << ", ";
}
ss << "speed " << std::showpos << (it.abilities->speed >> 1) << std::noshowpos;
}
msg.addString(ss.str());
} else {
msg.add<uint16_t>(0x00);
}
if (it.charges != 0) {
msg.addString(std::to_string(it.charges));
} else {
msg.add<uint16_t>(0x00);
}
std::string weaponName = getWeaponName(it.weaponType);
if (it.slotPosition & SLOTP_TWO_HAND) {
if (!weaponName.empty()) {
weaponName += ", two-handed";
} else {
weaponName = "two-handed";
}
}
msg.addString(weaponName);
if (it.weight != 0) {
std::ostringstream ss;
if (it.weight < 10) {
ss << "0.0" << it.weight;
} else if (it.weight < 100) {
ss << "0." << it.weight;
} else {
std::string weightString = std::to_string(it.weight);
weightString.insert(weightString.end() - 2, '.');
ss << weightString;
}
ss << " oz";
msg.addString(ss.str());
} else {
msg.add<uint16_t>(0x00);
}
if (version > 1099) {
uint8_t slot = Item::items[itemId].imbuingSlots;
if(slot > 0) {
msg.addString(std::to_string(slot));
} else {
msg.add<uint16_t>(0x00);
}
}
MarketStatistics* statistics = IOMarket::getInstance().getPurchaseStatistics(itemId);
if (statistics) {
msg.addByte(0x01);
msg.add<uint32_t>(statistics->numTransactions);
msg.add<uint32_t>(std::min<uint64_t>(std::numeric_limits<uint32_t>::max(), statistics->totalPrice));
msg.add<uint32_t>(statistics->highestPrice);
msg.add<uint32_t>(statistics->lowestPrice);
} else {
msg.addByte(0x00);
}
statistics = IOMarket::getInstance().getSaleStatistics(itemId);
if (statistics) {
msg.addByte(0x01);
msg.add<uint32_t>(statistics->numTransactions);
msg.add<uint32_t>(std::min<uint64_t>(std::numeric_limits<uint32_t>::max(), statistics->totalPrice));
msg.add<uint32_t>(statistics->highestPrice);
msg.add<uint32_t>(statistics->lowestPrice);
} else {
msg.addByte(0x00);
}
writeToOutputBuffer(msg);
}
void ProtocolGame::sendQuestTracker()
{
NetworkMessage msg;
msg.addByte(0xD0); // byte quest tracker
msg.addByte(1); // send quests of quest log ??
msg.add<uint16_t>(1); // unknown
writeToOutputBuffer(msg);
}
void ProtocolGame::sendQuestLog()
{
NetworkMessage msg;
msg.addByte(0xF0);
msg.add<uint16_t>(g_game.quests.getQuestsCount(player));
for (const Quest& quest : g_game.quests.getQuests()) {
if (quest.isStarted(player)) {
msg.add<uint16_t>(quest.getID());
msg.addString(quest.getName());
msg.addByte(quest.isCompleted(player));
}
}
writeToOutputBuffer(msg);
}
void ProtocolGame::sendQuestLine(const Quest* quest)
{
NetworkMessage msg;
msg.addByte(0xF1);
msg.add<uint16_t>(quest->getID());
msg.addByte(quest->getMissionsCount(player));
for (const Mission& mission : quest->getMissions()) {
if (mission.isStarted(player)) {
if (player->getProtocolVersion() >= 1120){
msg.add<uint16_t>(0x00); // missionID (TODO, this is used for quest tracker)
}
msg.addString(mission.getName(player));
msg.addString(mission.getDescription(player));
}
}
if (player->operatingSystem == CLIENTOS_NEW_WINDOWS) {
sendQuestTracker();
}
writeToOutputBuffer(msg);
}
void ProtocolGame::sendTradeItemRequest(const std::string& traderName, const Item* item, bool ack)
{
NetworkMessage msg;
if (ack) {
msg.addByte(0x7D);
} else {
msg.addByte(0x7E);
}
msg.addString(traderName);
if (const Container* tradeContainer = item->getContainer()) {
std::list<const Container*> listContainer {tradeContainer};
std::list<const Item*> itemList {tradeContainer};
while (!listContainer.empty()) {
const Container* container = listContainer.front();
listContainer.pop_front();
for (Item* containerItem : container->getItemList()) {
Container* tmpContainer = containerItem->getContainer();
if (tmpContainer) {
listContainer.push_back(tmpContainer);
}
itemList.push_back(containerItem);
}
}
msg.addByte(itemList.size());
for (const Item* listItem : itemList) {
AddItem(msg, listItem);
}
} else {
msg.addByte(0x01);
AddItem(msg, item);
}
writeToOutputBuffer(msg);
}
void ProtocolGame::sendCloseTrade()
{
NetworkMessage msg;
msg.addByte(0x7F);
writeToOutputBuffer(msg);
}
void ProtocolGame::sendCloseContainer(uint8_t cid)
{
NetworkMessage msg;
msg.addByte(0x6F);
msg.addByte(cid);
writeToOutputBuffer(msg);
}
void ProtocolGame::sendCreatureTurn(const Creature* creature, uint32_t stackPos)
{
if (!canSee(creature)) {
return;
}
NetworkMessage msg;
msg.addByte(0x6B);
msg.addPosition(creature->getPosition());
msg.addByte(stackPos);
msg.add<uint16_t>(0x63);
msg.add<uint32_t>(creature->getID());
msg.addByte(creature->getDirection());
msg.addByte(player->canWalkthroughEx(creature) ? 0x00 : 0x01);
writeToOutputBuffer(msg);
}
void ProtocolGame::sendCreatureSay(const Creature* creature, SpeakClasses type, const std::string& text, const Position* pos/* = nullptr*/)
{
NetworkMessage msg;
msg.addByte(0xAA);
static uint32_t statementId = 0;
msg.add<uint32_t>(++statementId);
msg.addString(creature->getName());
//Add level only for players
if (const Player* speaker = creature->getPlayer()) {
msg.add<uint16_t>(speaker->getLevel());
} else {
msg.add<uint16_t>(0x00);
}
msg.addByte(type);
if (pos) {
msg.addPosition(*pos);
} else {
msg.addPosition(creature->getPosition());
}
msg.addString(text);
writeToOutputBuffer(msg);
}
void ProtocolGame::sendToChannel(const Creature* creature, SpeakClasses type, const std::string& text, uint16_t channelId)
{
NetworkMessage msg;
msg.addByte(0xAA);
static uint32_t statementId = 0;
msg.add<uint32_t>(++statementId);
if (!creature) {
msg.add<uint32_t>(0x00);
} else if (type == TALKTYPE_CHANNEL_R2) {
msg.add<uint32_t>(0x00);
type = TALKTYPE_CHANNEL_R1;
} else {
msg.addString(creature->getName());
//Add level only for players
if (const Player* speaker = creature->getPlayer()) {
msg.add<uint16_t>(speaker->getLevel());
} else {
msg.add<uint16_t>(0x00);
}
}
msg.addByte(type);
msg.add<uint16_t>(channelId);
msg.addString(text);
writeToOutputBuffer(msg);
}
void ProtocolGame::sendPrivateMessage(const Player* speaker, SpeakClasses type, const std::string& text)
{
NetworkMessage msg;
msg.addByte(0xAA);
static uint32_t statementId = 0;
msg.add<uint32_t>(++statementId);
if (speaker) {
msg.addString(speaker->getName());
msg.add<uint16_t>(speaker->getLevel());
} else {
msg.add<uint32_t>(0x00);
}
msg.addByte(type);
msg.addString(text);
writeToOutputBuffer(msg);
}
void ProtocolGame::sendCancelTarget()
{
NetworkMessage msg;
msg.addByte(0xA3);
msg.add<uint32_t>(0x00);
writeToOutputBuffer(msg);
}
void ProtocolGame::sendChangeSpeed(const Creature* creature, uint32_t speed)
{
NetworkMessage msg;
msg.addByte(0x8F);
msg.add<uint32_t>(creature->getID());
msg.add<uint16_t>(creature->getBaseSpeed() / 2);
msg.add<uint16_t>(speed / 2);
writeToOutputBuffer(msg);
}
void ProtocolGame::sendCancelWalk()
{
if (player) {
NetworkMessage msg;
msg.addByte(0xB5);
msg.addByte(player->getDirection());
writeToOutputBuffer(msg);
}
}
void ProtocolGame::sendSkills()
{
NetworkMessage msg;
AddPlayerSkills(msg);
writeToOutputBuffer(msg);
}
void ProtocolGame::sendPing()
{
if (player) {
NetworkMessage msg;
msg.addByte(0x1D);
writeToOutputBuffer(msg);
}
}
void ProtocolGame::sendPingBack()
{
NetworkMessage msg;
msg.addByte(0x1E);
writeToOutputBuffer(msg);
}
void ProtocolGame::sendDistanceShoot(const Position& from, const Position& to, uint8_t type)
{
NetworkMessage msg;
msg.addByte(0x85);
msg.addPosition(from);
msg.addPosition(to);
msg.addByte(type);
writeToOutputBuffer(msg);
}
void ProtocolGame::sendMagicEffect(const Position& pos, uint8_t type)
{
if (!canSee(pos)) {
return;
}
NetworkMessage msg;
msg.addByte(0x83);
msg.addPosition(pos);
msg.addByte(type);
writeToOutputBuffer(msg);
}
void ProtocolGame::sendCreatureHealth(const Creature* creature)
{
NetworkMessage msg;
msg.addByte(0x8C);
msg.add<uint32_t>(creature->getID());
if (creature->isHealthHidden()) {
msg.addByte(0x00);
} else {
msg.addByte(std::ceil((static_cast<double>(creature->getHealth()) / std::max<int32_t>(creature->getMaxHealth(), 1)) * 100));
}
writeToOutputBuffer(msg);
}
void ProtocolGame::sendFYIBox(const std::string& message)
{
NetworkMessage msg;
msg.addByte(0x15);
msg.addString(message);
writeToOutputBuffer(msg);
}
//tile
void ProtocolGame::sendMapDescription(const Position& pos)
{
NetworkMessage msg;
msg.addByte(0x64);
msg.addPosition(player->getPosition());
GetMapDescription(pos.x - 8, pos.y - 6, pos.z, 18, 14, msg);
writeToOutputBuffer(msg);
}
void ProtocolGame::sendAddTileItem(const Position& pos, uint32_t stackpos, const Item* item)
{
if (!canSee(pos)) {
return;
}
NetworkMessage msg;
msg.addByte(0x6A);
msg.addPosition(pos);
msg.addByte(stackpos);
AddItem(msg, item);
writeToOutputBuffer(msg);
}
void ProtocolGame::sendUpdateTileItem(const Position& pos, uint32_t stackpos, const Item* item)
{
if (!canSee(pos)) {
return;
}
NetworkMessage msg;
msg.addByte(0x6B);
msg.addPosition(pos);
msg.addByte(stackpos);
AddItem(msg, item);
writeToOutputBuffer(msg);
}
void ProtocolGame::sendRemoveTileThing(const Position& pos, uint32_t stackpos)
{
if (!canSee(pos)) {
return;
}
NetworkMessage msg;
RemoveTileThing(msg, pos, stackpos);
writeToOutputBuffer(msg);
}
void ProtocolGame::sendUpdateTile(const Tile* tile, const Position& pos)
{
if (!canSee(pos)) {
return;
}
NetworkMessage msg;
msg.addByte(0x69);
msg.addPosition(pos);
if (tile) {
GetTileDescription(tile, msg);
msg.addByte(0x00);
msg.addByte(0xFF);
} else {
msg.addByte(0x01);
msg.addByte(0xFF);
}
writeToOutputBuffer(msg);
}
void ProtocolGame::sendPendingStateEntered()
{
NetworkMessage msg;
msg.addByte(0x0A);
writeToOutputBuffer(msg);
}
void ProtocolGame::sendEnterWorld()
{
NetworkMessage msg;
msg.addByte(0x0F);
writeToOutputBuffer(msg);
}
void ProtocolGame::sendFightModes()
{
NetworkMessage msg;
msg.addByte(0xA7);
msg.addByte(player->fightMode);
msg.addByte(player->chaseMode);
msg.addByte(player->secureMode);
msg.addByte(PVP_MODE_DOVE);
writeToOutputBuffer(msg);
}
void ProtocolGame::sendAddCreature(const Creature* creature, const Position& pos, int32_t stackpos, bool isLogin)
{
if (!canSee(pos)) {
return;
}
if (creature != player) {
if (stackpos >= 10) {
return;
}
NetworkMessage msg;
msg.addByte(0x6A);
msg.addPosition(pos);
msg.addByte(stackpos);
bool known;
uint32_t removedKnown;
checkCreatureAsKnown(creature->getID(), known, removedKnown);
AddCreature(msg, creature, known, removedKnown);
writeToOutputBuffer(msg);
if (isLogin) {
sendMagicEffect(pos, CONST_ME_TELEPORT);
}
return;
}
NetworkMessage msg;
msg.addByte(0x17);
msg.add<uint32_t>(player->getID());
msg.add<uint16_t>(0x32); // beat duration (50)
msg.addDouble(Creature::speedA, 3);
msg.addDouble(Creature::speedB, 3);
msg.addDouble(Creature::speedC, 3);
// can report bugs?
if (player->getAccountType() >= ACCOUNT_TYPE_NORMAL) {
msg.addByte(0x01);
} else {
msg.addByte(0x00);
}
msg.addByte(0x00); // can change pvp framing option
msg.addByte(0x00); // expert mode button enabled
msg.addString(g_config.getString(ConfigManager::STORE_IMAGES_URL));
msg.add<uint16_t>(static_cast<uint16_t>(g_config.getNumber(ConfigManager::STORE_COIN_PACKET)));
if (version >= 1150 || shouldAddExivaRestrictions) {
msg.addByte(0x00); // exiva button enabled
}
if (version >= 1215) {
msg.addByte(0x01); // tournament button enabled
}
writeToOutputBuffer(msg);
sendPendingStateEntered();
sendEnterWorld();
sendMapDescription(pos);
loggedIn = true;
if (isLogin) {
sendMagicEffect(pos, CONST_ME_TELEPORT);
}
for (int i = CONST_SLOT_FIRST; i <= CONST_SLOT_LAST; ++i) {
sendInventoryItem(static_cast<slots_t>(i), player->getInventoryItem(static_cast<slots_t>(i)));
}
sendStats();
sendSkills();
sendBlessStatus();
sendPremiumTrigger();
sendStoreHighlight();
//gameworld light-settings
sendWorldLight(g_game.getWorldLightInfo());
//player light level
sendCreatureLight(creature);
const std::forward_list<VIPEntry>& vipEntries = IOLoginData::getVIPEntries(player->getAccount());
if (player->isAccessPlayer()) {
for (const VIPEntry& entry : vipEntries) {
VipStatus_t vipStatus;
Player* vipPlayer = g_game.getPlayerByGUID(entry.guid);
if (!vipPlayer) {
vipStatus = VIPSTATUS_OFFLINE;
} else {
vipStatus = VIPSTATUS_ONLINE;
}
sendVIP(entry.guid, entry.name, entry.description, entry.icon, entry.notify, vipStatus);
}
} else {
for (const VIPEntry& entry : vipEntries) {
VipStatus_t vipStatus;
Player* vipPlayer = g_game.getPlayerByGUID(entry.guid);
if (!vipPlayer || vipPlayer->isInGhostMode()) {
vipStatus = VIPSTATUS_OFFLINE;
} else {
vipStatus = VIPSTATUS_ONLINE;
}
sendVIP(entry.guid, entry.name, entry.description, entry.icon, entry.notify, vipStatus);
}
}
sendBasicData();
sendInventoryClientIds();
sendPreyData();
if (player->getProtocolVersion() >= 1130) {
player->sendClientCheck();
player->sendGameNews();
}
player->sendIcons();
}
void ProtocolGame::sendMoveCreature(const Creature* creature, const Position& newPos, int32_t newStackPos, const Position& oldPos, int32_t oldStackPos, bool teleport)
{
if (creature == player) {
if (oldStackPos >= 10) {
sendMapDescription(newPos);
} else if (teleport) {
NetworkMessage msg;
RemoveTileThing(msg, oldPos, oldStackPos);
writeToOutputBuffer(msg);
sendMapDescription(newPos);
} else {
NetworkMessage msg;
if (oldPos.z == 7 && newPos.z >= 8) {
RemoveTileThing(msg, oldPos, oldStackPos);
} else {
msg.addByte(0x6D);
msg.addPosition(oldPos);
msg.addByte(oldStackPos);
msg.addPosition(newPos);
}
if (newPos.z > oldPos.z) {
MoveDownCreature(msg, creature, newPos, oldPos);
} else if (newPos.z < oldPos.z) {
MoveUpCreature(msg, creature, newPos, oldPos);
}
if (oldPos.y > newPos.y) { // north, for old x
msg.addByte(0x65);
GetMapDescription(oldPos.x - 8, newPos.y - 6, newPos.z, 18, 1, msg);
} else if (oldPos.y < newPos.y) { // south, for old x
msg.addByte(0x67);
GetMapDescription(oldPos.x - 8, newPos.y + 7, newPos.z, 18, 1, msg);
}
if (oldPos.x < newPos.x) { // east, [with new y]
msg.addByte(0x66);
GetMapDescription(newPos.x + 9, newPos.y - 6, newPos.z, 1, 14, msg);
} else if (oldPos.x > newPos.x) { // west, [with new y]
msg.addByte(0x68);
GetMapDescription(newPos.x - 8, newPos.y - 6, newPos.z, 1, 14, msg);
}
writeToOutputBuffer(msg);
}
} else if (canSee(oldPos) && canSee(creature->getPosition())) {
if (teleport || (oldPos.z == 7 && newPos.z >= 8) || oldStackPos >= 10) {
sendRemoveTileThing(oldPos, oldStackPos);
sendAddCreature(creature, newPos, newStackPos, false);
} else {
NetworkMessage msg;
msg.addByte(0x6D);
msg.addPosition(oldPos);
msg.addByte(oldStackPos);
msg.addPosition(creature->getPosition());
writeToOutputBuffer(msg);
}
} else if (canSee(oldPos)) {
sendRemoveTileThing(oldPos, oldStackPos);
} else if (canSee(creature->getPosition())) {
sendAddCreature(creature, newPos, newStackPos, false);
}
}
void ProtocolGame::sendInventoryItem(slots_t slot, const Item* item)
{
NetworkMessage msg;
if (item) {
msg.addByte(0x78);
msg.addByte(slot);
AddItem(msg, item);
} else {
msg.addByte(0x79);
msg.addByte(slot);
}
writeToOutputBuffer(msg);
}
void ProtocolGame::sendInventoryClientIds()
{
std::map<uint16_t, uint16_t> items = player->getInventoryClientIds();
NetworkMessage msg;
msg.addByte(0xF5);
msg.add<uint16_t>(items.size() + 11);
for (uint16_t i = 1; i <= 11; i++) {
msg.add<uint16_t>(i);
msg.addByte(0x00);
msg.add<uint16_t>(0x01);
}
for (const auto& it : items) {
msg.add<uint16_t>(it.first);
msg.addByte(0x00);
msg.add<uint16_t>(it.second);
}
writeToOutputBuffer(msg);
}
void ProtocolGame::sendAddContainerItem(uint8_t cid, uint16_t slot, const Item* item)
{
NetworkMessage msg;
msg.addByte(0x70);
msg.addByte(cid);
msg.add<uint16_t>(slot);
AddItem(msg, item);
writeToOutputBuffer(msg);
}
void ProtocolGame::sendUpdateContainerItem(uint8_t cid, uint16_t slot, const Item* item)
{
NetworkMessage msg;
msg.addByte(0x71);
msg.addByte(cid);
msg.add<uint16_t>(slot);
AddItem(msg, item);
writeToOutputBuffer(msg);
}
void ProtocolGame::sendRemoveContainerItem(uint8_t cid, uint16_t slot, const Item* lastItem)
{
NetworkMessage msg;
msg.addByte(0x72);
msg.addByte(cid);
msg.add<uint16_t>(slot);
if (lastItem) {
AddItem(msg, lastItem);
} else {
msg.add<uint16_t>(0x00);
}
writeToOutputBuffer(msg);
}
void ProtocolGame::sendTextWindow(uint32_t windowTextId, Item* item, uint16_t maxlen, bool canWrite)
{
NetworkMessage msg;
msg.addByte(0x96);
msg.add<uint32_t>(windowTextId);
AddItem(msg, item);
if (canWrite) {
msg.add<uint16_t>(maxlen);
msg.addString(item->getText());
} else {
const std::string& text = item->getText();
msg.add<uint16_t>(text.size());
msg.addString(text);
}
const std::string& writer = item->getWriter();
if (!writer.empty()) {
msg.addString(writer);
} else {
msg.add<uint16_t>(0x00);
}
time_t writtenDate = item->getDate();
if (writtenDate != 0) {
msg.addString(formatDateShort(writtenDate));
} else {
msg.add<uint16_t>(0x00);
}
writeToOutputBuffer(msg);
}
void ProtocolGame::sendTextWindow(uint32_t windowTextId, uint32_t itemId, const std::string& text)
{
NetworkMessage msg;
msg.addByte(0x96);
msg.add<uint32_t>(windowTextId);
AddItem(msg, itemId, 1);
msg.add<uint16_t>(text.size());
msg.addString(text);
msg.add<uint16_t>(0x00);
msg.add<uint16_t>(0x00);
writeToOutputBuffer(msg);
}
void ProtocolGame::sendHouseWindow(uint32_t windowTextId, const std::string& text)
{
NetworkMessage msg;
msg.addByte(0x97);
msg.addByte(0x00);
msg.add<uint32_t>(windowTextId);
msg.addString(text);
writeToOutputBuffer(msg);
}
void ProtocolGame::sendOutfitWindow()
{
NetworkMessage msg;
msg.addByte(0xC8);
Outfit_t currentOutfit = player->getDefaultOutfit();
Mount* currentMount = g_game.mounts.getMountByID(player->getCurrentMount());
if (currentMount) {
currentOutfit.lookMount = currentMount->clientId;
}
AddOutfit(msg, currentOutfit);
std::vector<ProtocolOutfit> protocolOutfits;
if (player->isAccessPlayer()) {
static const std::string gamemasterOutfitName = "Game Master";
protocolOutfits.emplace_back(gamemasterOutfitName, 75, 0);
static const std::string gmCustomerSupport = "Customer Support";
protocolOutfits.emplace_back(gmCustomerSupport, 266, 0);
static const std::string communityManager = "Community Manager";
protocolOutfits.emplace_back(communityManager, 302, 0);
}
const auto& outfits = Outfits::getInstance().getOutfits(player->getSex());
protocolOutfits.reserve(outfits.size());
for (const Outfit& outfit : outfits) {
uint8_t addons;
if (!player->getOutfitAddons(outfit, addons)) {
continue;
}
protocolOutfits.emplace_back(outfit.name, outfit.lookType, addons);
if (protocolOutfits.size() == 150 && version < 1185) { // Game client doesn't allow more than 100 outfits
break;
}
}
if (version >= 1185) {
msg.add<uint16_t>(protocolOutfits.size());
} else {
msg.addByte(protocolOutfits.size());
}
for (const ProtocolOutfit& outfit : protocolOutfits) {
msg.add<uint16_t>(outfit.lookType);
msg.addString(outfit.name);
msg.addByte(outfit.addons);
if (version >= 1185) {
msg.addByte(0x00);
}
}
std::vector<const Mount*> mounts;
for (const Mount& mount : g_game.mounts.getMounts()) {
if (player->hasMount(&mount)) {
mounts.push_back(&mount);
}
}
if (version >= 1185) {
msg.add<uint16_t>(mounts.size());
} else {
msg.addByte(mounts.size());
}
for (const Mount* mount : mounts) {
msg.add<uint16_t>(mount->clientId);
msg.addString(mount->name);
if (version >= 1185) {
msg.addByte(0x00);
}
}
if (version >= 1185) {
msg.addByte(0x00);
msg.addByte(0x00);
}
writeToOutputBuffer(msg);
}
void ProtocolGame::sendUpdatedVIPStatus(uint32_t guid, VipStatus_t newStatus)
{
NetworkMessage msg;
msg.addByte(0xD3);
msg.add<uint32_t>(guid);
msg.addByte(newStatus);
writeToOutputBuffer(msg);
}
void ProtocolGame::sendVIP(uint32_t guid, const std::string& name, const std::string& description, uint32_t icon, bool notify, VipStatus_t status)
{
NetworkMessage msg;
msg.addByte(0xD2);
msg.add<uint32_t>(guid);
msg.addString(name);
msg.addString(description);
msg.add<uint32_t>(std::min<uint32_t>(10, icon));
msg.addByte(notify ? 0x01 : 0x00);
msg.addByte(status);
if (version >= 1110) {
/* vipGroups: This is used for showing VipGroups by ids */
msg.addByte(0x00);
}
writeToOutputBuffer(msg);
}
void ProtocolGame::sendSpellCooldown(uint8_t spellId, uint32_t time)
{
NetworkMessage msg;
msg.addByte(0xA4);
if (player->getProtocolVersion() < 1120 && spellId >= 170) {
spellId = 150;
}
msg.addByte(spellId);
msg.add<uint32_t>(time);
writeToOutputBuffer(msg);
}
void ProtocolGame::sendSpellGroupCooldown(SpellGroup_t groupId, uint32_t time)
{
NetworkMessage msg;
msg.addByte(0xA5);
msg.addByte(groupId);
msg.add<uint32_t>(time);
writeToOutputBuffer(msg);
}
void ProtocolGame::sendOpenStore(uint8_t)
{
NetworkMessage msg;
msg.addByte(0xFB); //open store
msg.addByte(0x00);
//add categories
uint16_t categoriesCount = g_game.gameStore.getCategoryOffers().size();
msg.add<uint16_t>(categoriesCount);
for(StoreCategory* category : g_game.gameStore.getCategoryOffers())
{
msg.addString(category->name);
msg.addString(category->description);
uint8_t stateByte;
switch(category->state) {
case NORMAL:
stateByte=0;
break;
case NEW:
stateByte=1;
break;
case SALE:
stateByte=2;
break;
case LIMITED_TIME:
stateByte=3;
break;
default:
stateByte=0;
break;
}
msg.addByte(stateByte);
msg.addByte((uint8_t)category->icons.size());
for(std::string iconStr : category->icons) {
msg.addString(iconStr);
}
msg.addString(""); //TODO: parentCategory
}
writeToOutputBuffer(msg);
}
void ProtocolGame::sendStoreCategoryOffers(StoreCategory* category)
{
NetworkMessage msg;
msg.addByte(0xFC); //StoreOffers
msg.addString(category->name);
msg.add<uint16_t>(category->offers.size());
for(BaseOffer* offer : category->offers) {
msg.add<uint32_t>(offer->id);
std::stringstream offername;
if (offer->type==Offer_t::ITEM || offer->type == Offer_t::STACKABLE_ITEM) {
if (((ItemOffer*)offer)->count > 1) {
offername << ((ItemOffer*)offer)->count << "x ";
}
}
offername << offer->name;
msg.addString(offername.str());
msg.addString(offer->description);
msg.add<uint32_t>(offer->price);
msg.addByte((uint8_t) offer->state);
//outfits
uint8_t disabled = 0;
std::stringstream disabledReason;
disabledReason <<"";
if (offer->type == OUTFIT || offer->type == OUTFIT_ADDON) {
OutfitOffer* outfitOffer = (OutfitOffer*) offer;
uint16_t looktype = (player->getSex() == PLAYERSEX_MALE) ? outfitOffer->maleLookType : outfitOffer->femaleLookType;
uint8_t addons = outfitOffer->addonNumber;
if (player->canWear(looktype, addons)) { //player can wear the offer already
disabled=1;
if (addons == 0) { //addons == 0 //oufit-only offer and player already has it
disabledReason << "You already have this outfit.";
} else {
disabledReason << "You already have this outfit/addon.";
}
} else {
if (outfitOffer->type == OUTFIT_ADDON && !player->canWear(looktype,0)) { //addon offer and player doesnt have the base outfit
disabled=1;
disabledReason << "You don't have the outfit, you can't buy the addon.";
}
}
} else if (offer->type == MOUNT) {
MountOffer* mountOffer = (MountOffer*) offer;
Mount* m = g_game.mounts.getMountByID(mountOffer->mountId);
if (player->hasMount(m)) {
disabled=1;
disabledReason << "You already have this mount.";
}
} else if (offer->type == PROMOTION) {
if (player->isPromoted() || !player->isPremium()) { //TODO: add support to multiple promotion levels
disabled=1;
disabledReason << "You can't get this promotion";
}
}
msg.addByte(disabled);
if (disabled) {
msg.addString(disabledReason.str());
}
//add icons
msg.addByte((uint8_t)offer->icons.size());
for(std::string iconName : offer->icons ) {
msg.addString(iconName);
}
msg.add<uint16_t>(0);
//TODO: add support to suboffers
}
writeToOutputBuffer(msg);
}
void ProtocolGame::sendStoreError(GameStoreError_t error, const std::string& message)
{
NetworkMessage msg;
msg.addByte(0xE0); //storeError
msg.addByte(error);
msg.addString(message);
writeToOutputBuffer(msg);
}
void ProtocolGame::sendStorePurchaseSuccessful(const std::string& message, const uint32_t coinBalance)
{
NetworkMessage msg;
msg.addByte(0xFE); //CompletePurchase
msg.addByte(0x00);
msg.addString(message);
msg.add<uint32_t>(coinBalance); //dont know why the client needs it duplicated. But ok...
msg.add<uint32_t>(coinBalance);
writeToOutputBuffer(msg);
}
void ProtocolGame::sendStoreRequestAdditionalInfo(uint32_t offerId, ClientOffer_t clientOfferType)
{
NetworkMessage msg;
msg.addByte(0xE1); //RequestPurchaseData
msg.add<uint32_t>(offerId);
msg.addByte(clientOfferType);
writeToOutputBuffer(msg);
}
void ProtocolGame::sendStoreTrasactionHistory(HistoryStoreOfferList &list, uint32_t page, uint8_t entriesPerPage)
{
NetworkMessage msg;
uint32_t isLastPage = (list.size() <= entriesPerPage) ? 0x01:0x00;
//TODO: Support multiple pages
isLastPage=0x01; //FIXME
page=0x00;
////////////////////////
msg.addByte(0xFD); //BrowseTransactionHistory
msg.add<uint32_t>(page); //which page
msg.add<uint32_t>(isLastPage); //is the last page? /
msg.addByte((uint8_t)list.size()); //how many elements follows
for(HistoryStoreOffer offer:list) {
msg.add<uint32_t>(offer.time);
msg.addByte(offer.mode);
msg.add<uint32_t>(offer.amount); //FIXME: investigate why it doesn't send the price properly
msg.addString(offer.description);
}
writeToOutputBuffer(msg);
}
void ProtocolGame::sendModalWindow(const ModalWindow& modalWindow)
{
NetworkMessage msg;
msg.addByte(0xFA);
msg.add<uint32_t>(modalWindow.id);
msg.addString(modalWindow.title);
msg.addString(modalWindow.message);
msg.addByte(modalWindow.buttons.size());
for (const auto& it : modalWindow.buttons) {
msg.addString(it.first);
msg.addByte(it.second);
}
msg.addByte(modalWindow.choices.size());
for (const auto& it : modalWindow.choices) {
msg.addString(it.first);
msg.addByte(it.second);
}
msg.addByte(modalWindow.defaultEscapeButton);
msg.addByte(modalWindow.defaultEnterButton);
msg.addByte(modalWindow.priority ? 0x01 : 0x00);
writeToOutputBuffer(msg);
}
////////////// Add common messages
void ProtocolGame::AddCreature(NetworkMessage& msg, const Creature* creature, bool known, uint32_t remove)
{
CreatureType_t creatureType = creature->getType();
const Player* otherPlayer = creature->getPlayer();
if (known) {
msg.add<uint16_t>(0x62);
msg.add<uint32_t>(creature->getID());
} else {
msg.add<uint16_t>(0x61);
msg.add<uint32_t>(remove);
msg.add<uint32_t>(creature->getID());
msg.addByte(creatureType);
if (player->getProtocolVersion() >= 1120) {
if (creatureType == CREATURETYPE_SUMMONPLAYER) {
const Creature* master = creature->getMaster();
if (master) {
msg.add<uint32_t>(master->getID());
}
}
}
msg.addString(creature->getName());
}
if (creature->isHealthHidden()) {
msg.addByte(0x00);
} else {
msg.addByte(std::ceil((static_cast<double>(creature->getHealth()) / std::max<int32_t>(creature->getMaxHealth(), 1)) * 100));
}
msg.addByte(creature->getDirection());
if (!creature->isInGhostMode() && !creature->isInvisible()) {
AddOutfit(msg, creature->getCurrentOutfit());
} else {
static Outfit_t outfit;
AddOutfit(msg, outfit);
}
LightInfo lightInfo = creature->getCreatureLight();
msg.addByte(player->isAccessPlayer() ? 0xFF : lightInfo.level);
msg.addByte(lightInfo.color);
msg.add<uint16_t>(creature->getStepSpeed() / 2);
msg.addByte(player->getSkullClient(creature));
msg.addByte(player->getPartyShield(otherPlayer));
if (!known) {
msg.addByte(player->getGuildEmblem(otherPlayer));
}
if (player->getProtocolVersion() >= 1120) {
if (creatureType == CREATURETYPE_MONSTER) {
const Creature* master = creature->getMaster();
if (master) {
const Player* masterPlayer = master->getPlayer();
if (masterPlayer) {
creatureType = CREATURETYPE_SUMMONPLAYER;
}
}
}
}
msg.addByte(creatureType); // Type (for summons)
if (player->getProtocolVersion() >= 1120) {
if (creatureType == CREATURETYPE_SUMMONPLAYER) {
const Creature* master = creature->getMaster();
if (master) {
msg.add<uint32_t>(master->getID());
}
}
}
msg.addByte(creature->getSpeechBubble());
msg.addByte(0xFF); // MARK_UNMARKED
if (version >= 1110) {
msg.addByte(0x00); // inspection type
}
if (version < 1185) {
if (otherPlayer) {
msg.add<uint16_t>(otherPlayer->getHelpers());
} else {
msg.add<uint16_t>(0x00);
}
}
msg.addByte(player->canWalkthroughEx(creature) ? 0x00 : 0x01);
}
void ProtocolGame::AddPlayerStats(NetworkMessage& msg)
{
msg.addByte(0xA0);
msg.add<uint16_t>(std::min<int32_t>(player->getHealth(), std::numeric_limits<uint16_t>::max()));
msg.add<uint16_t>(std::min<int32_t>(player->getMaxHealth(), std::numeric_limits<uint16_t>::max()));
msg.add<uint32_t>(player->getFreeCapacity());
if (version < 1150) {
msg.add<uint32_t>(player->getCapacity());
}
msg.add<uint64_t>(player->getExperience());
msg.add<uint16_t>(player->getLevel());
msg.addByte(player->getLevelPercent());
msg.add<uint16_t>(player->getBaseXpGain()); // base xp gain rate
if (version < 1150) {
msg.add<uint16_t>(player->getVoucherXpBoost()); // xp voucher
}
msg.add<uint16_t>(player->getGrindingXpBoost()); // low level bonus
msg.add<uint16_t>(player->getStoreXpBoost()); // xp boost
msg.add<uint16_t>(player->getStaminaXpBoost()); // stamina multiplier (100 = 1.0x)
msg.add<uint16_t>(std::min<int32_t>(player->getMana(), std::numeric_limits<uint16_t>::max()));
msg.add<uint16_t>(std::min<int32_t>(player->getMaxMana(), std::numeric_limits<uint16_t>::max()));
if (version < 1200) {
msg.addByte(std::min<uint32_t>(player->getMagicLevel(), std::numeric_limits<uint8_t>::max()));
msg.addByte(std::min<uint32_t>(player->getBaseMagicLevel(), std::numeric_limits<uint8_t>::max()));
msg.addByte(player->getMagicLevelPercent());
}
msg.addByte(player->getSoul());
msg.add<uint16_t>(player->getStaminaMinutes());
msg.add<uint16_t>(player->getBaseSpeed() / 2);
Condition* condition = player->getCondition(CONDITION_REGENERATION);
msg.add<uint16_t>(condition ? condition->getTicks() / 1000 : 0x00);
msg.add<uint16_t>(player->getOfflineTrainingTime() / 60 / 1000);
msg.add<uint16_t>(player->getExpBoostStamina()); // xp boost time (seconds)
msg.addByte(1); // enables exp boost in the store
}
void ProtocolGame::AddPlayerSkills(NetworkMessage& msg)
{
msg.addByte(0xA1);
if (version >= 1200) {
msg.add<uint16_t>(player->getMagicLevel());
msg.add<uint16_t>(player->getBaseMagicLevel());
msg.add<uint16_t>(player->getBaseMagicLevel());
msg.add<uint16_t>(player->getMagicLevelPercent() * 100);
}
for (uint8_t i = SKILL_FIRST; i <= SKILL_FISHING; ++i) {
msg.add<uint16_t>(std::min<int32_t>(player->getSkillLevel(i), std::numeric_limits<uint16_t>::max()));
msg.add<uint16_t>(player->getBaseSkill(i));
if (version >= 1200) {
msg.add<uint16_t>(player->getBaseSkill(i));
msg.add<uint16_t>(player->getSkillPercent(i) * 100);
} else {
msg.addByte(player->getSkillPercent(i));
}
}
for (uint8_t i = SKILL_CRITICAL_HIT_CHANCE; i <= SKILL_LAST; ++i) {
msg.add<uint16_t>(std::min<int32_t>(player->getSkillLevel(i), std::numeric_limits<uint16_t>::max()));
msg.add<uint16_t>(player->getBaseSkill(i));
}
if (version >= 1150) { // used for imbuement (Feather)
msg.add<uint32_t>(player->getCapacity()); // total capacity
msg.add<uint32_t>(player->getCapacity()); // base total capacity
}
}
void ProtocolGame::AddOutfit(NetworkMessage& msg, const Outfit_t& outfit)
{
msg.add<uint16_t>(outfit.lookType);
if (outfit.lookType != 0) {
msg.addByte(outfit.lookHead);
msg.addByte(outfit.lookBody);
msg.addByte(outfit.lookLegs);
msg.addByte(outfit.lookFeet);
msg.addByte(outfit.lookAddons);
} else {
msg.addItemId(outfit.lookTypeEx);
}
msg.add<uint16_t>(outfit.lookMount);
}
void ProtocolGame::addImbuementInfo(NetworkMessage& msg, uint32_t imbuid)
{
Imbuement* imbuement = g_imbuements->getImbuement(imbuid);
BaseImbue* base = g_imbuements->getBaseByID(imbuement->getBaseID());
Category* category = g_imbuements->getCategoryByID(imbuement->getCategory());
msg.add<uint32_t>(imbuid);
msg.addString(base->name + " " + imbuement->getName());
msg.addString(imbuement->getDescription());
msg.addString(category->name + imbuement->getSubGroup());
msg.add<uint16_t>(imbuement->getIconID());
msg.add<uint32_t>(base->duration);
msg.addByte(imbuement->isPremium() ? 0x01 : 0x00);
const auto& items = imbuement->getItems();
msg.addByte(items.size());
for (const auto& itm : items) {
const ItemType& it = Item::items[itm.first];
msg.addItemId(itm.first);
msg.addString(it.name);
msg.add<uint16_t>(itm.second);
}
msg.add<uint32_t>(base->price);
msg.addByte(base->percent);
msg.add<uint32_t>(base->protection);
}
void ProtocolGame::sendImbuementWindow(Item* item)
{
const ItemType& it = Item::items[item->getID()];
uint8_t slot = it.imbuingSlots;
bool itemHasImbue = false;
for (uint8_t i = 0; i < slot; i++) {
uint32_t info = item->getImbuement(i);
if (info >> 8) {
itemHasImbue = true;
break;
}
}
std::vector<Imbuement*> imbuements = g_imbuements->getImbuements(player, item);
if (!itemHasImbue && imbuements.empty()) {
player->sendTextMessage(MESSAGE_EVENT_ADVANCE, "You cannot imbue this item.");
return;
}
// Seting imbuing item
player->inImbuing(item);
NetworkMessage msg;
msg.addByte(0xEB);
msg.addItemId(item->getID());
msg.addByte(slot);
for (uint8_t i = 0; i < slot; i++) {
uint32_t info = item->getImbuement(i);
if (info >> 8) {
msg.addByte(0x01);
addImbuementInfo(msg, (info & 0xFF));
msg.add<uint32_t>(info >> 8);
msg.add<uint32_t>(g_imbuements->getBaseByID(g_imbuements->getImbuement((info & 0xFF))->getBaseID())->removecust);
} else {
msg.addByte(0x00);
}
}
std::unordered_map<uint16_t, uint16_t> needItems;
msg.add<uint16_t>(imbuements.size());
for (Imbuement* ib : imbuements) {
addImbuementInfo(msg, ib->getId());
const auto& items = ib->getItems();
for (const auto& itm : items) {
if (!needItems.count(itm.first)) {
needItems[itm.first] = player->getItemTypeCount(itm.first);
}
}
}
msg.add<uint32_t>(needItems.size());
for (const auto& itm : needItems) {
msg.addItemId(itm.first);
msg.add<uint16_t>(itm.second);
}
if (player->getProtocolVersion() >= 1100) {
sendResourceBalance(player->getMoney(), player->getBankBalance());
}
writeToOutputBuffer(msg);
}
void ProtocolGame::AddItem(NetworkMessage& msg, uint16_t id, uint8_t count)
{
const ItemType& it = Item::items[id];
msg.add<uint16_t>(it.clientId);
if (version < 1200) {
msg.addByte(0xFF); // MARK_UNMARKED
}
if (it.stackable) {
msg.addByte(count);
} else if (it.isSplash() || it.isFluidContainer()) {
msg.addByte(fluidMap[count & 7]);
} else if (version >= 1150 && it.isContainer()) {
msg.addByte(0x00);
}
if (it.isAnimation) {
msg.addByte(0xFE); // random phase (0xFF for async)
}
}
void ProtocolGame::AddItem(NetworkMessage& msg, const Item* item)
{
const ItemType& it = Item::items[item->getID()];
msg.add<uint16_t>(it.clientId);
if (version < 1200) {
msg.addByte(0xFF); // MARK_UNMARKED
}
if (it.stackable) {
msg.addByte(std::min<uint16_t>(0xFF, item->getItemCount()));
} else if (it.isSplash() || it.isFluidContainer()) {
msg.addByte(fluidMap[item->getFluidType() & 7]);
} else if (version >= 1150 && it.isContainer()) {
msg.addByte(0x00);
}
if (it.isAnimation) {
msg.addByte(0xFE); // random phase (0xFF for async)
}
}
void ProtocolGame::AddWorldLight(NetworkMessage& msg, LightInfo lightInfo)
{
msg.addByte(0x82);
msg.addByte((player->isAccessPlayer() ? 0xFF : lightInfo.level));
msg.addByte(lightInfo.color);
}
void ProtocolGame::AddCreatureLight(NetworkMessage& msg, const Creature* creature)
{
LightInfo lightInfo = creature->getCreatureLight();
msg.addByte(0x8D);
msg.add<uint32_t>(creature->getID());
msg.addByte((player->isAccessPlayer() ? 0xFF : lightInfo.level));
msg.addByte(lightInfo.color);
}
//tile
void ProtocolGame::RemoveTileThing(NetworkMessage& msg, const Position& pos, uint32_t stackpos)
{
if (stackpos >= 10) {
return;
}
msg.addByte(0x6C);
msg.addPosition(pos);
msg.addByte(stackpos);
}
void ProtocolGame::MoveUpCreature(NetworkMessage& msg, const Creature* creature, const Position& newPos, const Position& oldPos)
{
if (creature != player) {
return;
}
//floor change up
msg.addByte(0xBE);
//going to surface
if (newPos.z == 7) {
int32_t skip = -1;
GetFloorDescription(msg, oldPos.x - 8, oldPos.y - 6, 5, 18, 14, 3, skip); //(floor 7 and 6 already set)
GetFloorDescription(msg, oldPos.x - 8, oldPos.y - 6, 4, 18, 14, 4, skip);
GetFloorDescription(msg, oldPos.x - 8, oldPos.y - 6, 3, 18, 14, 5, skip);
GetFloorDescription(msg, oldPos.x - 8, oldPos.y - 6, 2, 18, 14, 6, skip);
GetFloorDescription(msg, oldPos.x - 8, oldPos.y - 6, 1, 18, 14, 7, skip);
GetFloorDescription(msg, oldPos.x - 8, oldPos.y - 6, 0, 18, 14, 8, skip);
if (skip >= 0) {
msg.addByte(skip);
msg.addByte(0xFF);
}
}
//underground, going one floor up (still underground)
else if (newPos.z > 7) {
int32_t skip = -1;
GetFloorDescription(msg, oldPos.x - 8, oldPos.y - 6, oldPos.getZ() - 3, 18, 14, 3, skip);
if (skip >= 0) {
msg.addByte(skip);
msg.addByte(0xFF);
}
}
//moving up a floor up makes us out of sync
//west
msg.addByte(0x68);
GetMapDescription(oldPos.x - 8, oldPos.y - 5, newPos.z, 1, 14, msg);
//north
msg.addByte(0x65);
GetMapDescription(oldPos.x - 8, oldPos.y - 6, newPos.z, 18, 1, msg);
}
void ProtocolGame::MoveDownCreature(NetworkMessage& msg, const Creature* creature, const Position& newPos, const Position& oldPos)
{
if (creature != player) {
return;
}
//floor change down
msg.addByte(0xBF);
//going from surface to underground
if (newPos.z == 8) {
int32_t skip = -1;
GetFloorDescription(msg, oldPos.x - 8, oldPos.y - 6, newPos.z, 18, 14, -1, skip);
GetFloorDescription(msg, oldPos.x - 8, oldPos.y - 6, newPos.z + 1, 18, 14, -2, skip);
GetFloorDescription(msg, oldPos.x - 8, oldPos.y - 6, newPos.z + 2, 18, 14, -3, skip);
if (skip >= 0) {
msg.addByte(skip);
msg.addByte(0xFF);
}
}
//going further down
else if (newPos.z > oldPos.z && newPos.z > 8 && newPos.z < 14) {
int32_t skip = -1;
GetFloorDescription(msg, oldPos.x - 8, oldPos.y - 6, newPos.z + 2, 18, 14, -3, skip);
if (skip >= 0) {
msg.addByte(skip);
msg.addByte(0xFF);
}
}
//moving down a floor makes us out of sync
//east
msg.addByte(0x66);
GetMapDescription(oldPos.x + 9, oldPos.y - 7, newPos.z, 1, 14, msg);
//south
msg.addByte(0x67);
GetMapDescription(oldPos.x - 8, oldPos.y + 7, newPos.z, 18, 1, msg);
}
void ProtocolGame::AddShopItem(NetworkMessage& msg, const ShopInfo& item)
{
const ItemType& it = Item::items[item.itemId];
msg.add<uint16_t>(it.clientId);
if (it.isSplash() || it.isFluidContainer()) {
msg.addByte(serverFluidToClient(item.subType));
} else {
msg.addByte(0x00);
}
msg.addString(item.realName);
msg.add<uint32_t>(it.weight);
msg.add<uint32_t>(item.buyPrice == 4294967295 ? 0 : item.buyPrice);
msg.add<uint32_t>(item.sellPrice == 4294967295 ? 0 : item.sellPrice);
}
void ProtocolGame::parseExtendedOpcode(NetworkMessage& msg)
{
uint8_t opcode = msg.getByte();
const std::string& buffer = msg.getString();
// process additional opcodes via lua script event
addGameTask(&Game::parsePlayerExtendedOpcode, player->getID(), opcode, buffer);
}
Compartilhar este post
Link para o post
Compartilhar em outros sites