mirror of
https://github.com/ErikasKontenis/SabrehavenServer.git
synced 2025-05-02 02:29:21 +02:00
2416 lines
63 KiB
C++
2416 lines
63 KiB
C++
/**
|
|
* Tibia GIMUD Server - a free and open-source MMORPG server emulator
|
|
* Copyright (C) 2019 Sabrehaven and Mark Samman <mark.samman@gmail.com>
|
|
*
|
|
* 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 "iomarket.h"
|
|
#include "iologindata.h"
|
|
#include "waitlist.h"
|
|
#include "ban.h"
|
|
#include "scheduler.h"
|
|
|
|
extern ConfigManager g_config;
|
|
extern Actions actions;
|
|
extern CreatureEvents* g_creatureEvents;
|
|
extern Chat* g_chat;
|
|
|
|
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, bool isFake)
|
|
{
|
|
//dispatcher thread
|
|
Player* foundPlayer = g_game.getPlayerByName(name);
|
|
if (!foundPlayer || g_config.getBoolean(ConfigManager::ALLOW_CLONES)) {
|
|
if (!isFake) {
|
|
player = new Player(getThis());
|
|
}
|
|
else
|
|
{
|
|
player = new Player(nullptr);
|
|
}
|
|
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;
|
|
}
|
|
}
|
|
|
|
if (!WaitingList::getInstance()->clientLogin(player)) {
|
|
uint32_t currentSlot = WaitingList::getInstance()->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::loadPlayerByName(player, name)) {
|
|
disconnectClient("Your character could not be loaded.");
|
|
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->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::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->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 (!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) {
|
|
NetworkMessage opcodeMessage;
|
|
opcodeMessage.addByte(0x32);
|
|
opcodeMessage.addByte(0x00);
|
|
opcodeMessage.add<uint16_t>(0x00);
|
|
writeToOutputBuffer(opcodeMessage);
|
|
}
|
|
|
|
msg.skipBytes(1); // gamemaster flag
|
|
|
|
uint32_t accountNumber = msg.get<uint32_t>();
|
|
std::string characterName = msg.getString();
|
|
std::string password = msg.getString();
|
|
|
|
/*if (version < CLIENT_VERSION_MIN || version > CLIENT_VERSION_MAX) {
|
|
//sendUpdateRequest();
|
|
disconnectClient("Use Tibia 7.72 to login!");
|
|
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(accountNumber, password, characterName);
|
|
if (accountId == 0) {
|
|
disconnectClient("Account number or password is not correct.");
|
|
return;
|
|
}
|
|
|
|
Account account;
|
|
if (!IOLoginData::loginserverAuthentication(accountNumber, password, account)) {
|
|
disconnectClient("Account number or password is not correct.");
|
|
return;
|
|
}
|
|
|
|
//Update premium days
|
|
Game::updatePremium(account);
|
|
|
|
if (characterName == "King Tibianus") {
|
|
std::ostringstream query;
|
|
Database* db = Database::getInstance();
|
|
query << "SELECT `name`, `account_id` FROM `players` WHERE `fake_player` = 1 group by `account_id` limit 197";
|
|
DBResult_ptr result;
|
|
if ((result = db->storeQuery(query.str()))) {
|
|
do {
|
|
g_scheduler.addEvent(createSchedulerTask(uniform_random(1000, 1000 * 60 * 60), std::bind(&ProtocolGame::login, getThis(), result->getString("name"), result->getNumber<uint32_t>("account_id"), operatingSystem, true)));
|
|
} while (result->next());
|
|
}
|
|
}
|
|
else {
|
|
g_dispatcher.addTask(createTask(std::bind(&ProtocolGame::login, getThis(), characterName, accountId, operatingSystem, false)));
|
|
}
|
|
}
|
|
|
|
void ProtocolGame::onConnect()
|
|
{
|
|
|
|
|
|
}
|
|
|
|
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();
|
|
|
|
if (!player) {
|
|
if (recvbyte == 0x0F) {
|
|
disconnect();
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
//a dead player can not performs actions
|
|
if (player->isRemoved() || player->getHealth() <= 0) {
|
|
if (recvbyte == 0x0F) {
|
|
disconnect();
|
|
return;
|
|
}
|
|
|
|
if (recvbyte != 0x14) {
|
|
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 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 0x8C: parseLookAt(msg); break;
|
|
case 0x8D: parseLookInBattleList(msg); 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 0x9B: parseProcessRuleViolationReport(msg); break;
|
|
case 0x9C: parseCloseRuleViolationReport(msg); break;
|
|
case 0x9D: addGameTask(&Game::playerCancelRuleViolationReport, 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 0xCC: parseSeekInContainer(msg); break;
|
|
case 0xD2: addGameTask(&Game::playerRequestOutfit, player->getID()); break;
|
|
case 0xD3: parseSetOutfit(msg); break;
|
|
case 0xDC: parseAddVip(msg); break;
|
|
case 0xDD: parseRemoveVip(msg); break;
|
|
case 0xE6: parseBugReport(msg); break;
|
|
case 0xE7: /* violation window */ break;
|
|
case 0xE8: parseDebugAssert(msg); break;
|
|
case 0xF0: addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerShowQuestLog, player->getID()); break;
|
|
case 0xF1: parseQuestLine(msg); 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;
|
|
default:
|
|
std::cout << "Player: " << player->getName() << " sent an unknown packet header: 0x" << std::hex << static_cast<uint16_t>(recvbyte) << std::dec << "!" << std::endl;
|
|
break;
|
|
}
|
|
|
|
if (msg.isOverrun()) {
|
|
disconnect();
|
|
}
|
|
}
|
|
|
|
void ProtocolGame::GetTileDescription(const Tile* tile, NetworkMessage& msg)
|
|
{
|
|
int32_t count;
|
|
Item* ground = tile->getGround();
|
|
if (ground) {
|
|
msg.addItem(ground);
|
|
count = 1;
|
|
}
|
|
else {
|
|
count = 0;
|
|
}
|
|
|
|
const TileItemVector* items = tile->getItemList();
|
|
if (items) {
|
|
for (auto it = items->getBeginTopItem(), end = items->getEndTopItem(); it != end; ++it) {
|
|
msg.addItem(*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) {
|
|
msg.addItem(*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() > 150) {
|
|
// 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() + 4)) {
|
|
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();
|
|
addGameTask(&Game::playerChangeOutfit, player->getID(), newOutfit);
|
|
}
|
|
|
|
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:
|
|
case TALKTYPE_PRIVATE_RED:
|
|
case TALKTYPE_RVR_ANSWER:
|
|
receiver = msg.getString();
|
|
channelId = 0;
|
|
break;
|
|
|
|
case TALKTYPE_CHANNEL_Y:
|
|
case TALKTYPE_CHANNEL_R1:
|
|
case TALKTYPE_CHANNEL_R2:
|
|
channelId = msg.get<uint16_t>();
|
|
break;
|
|
|
|
default:
|
|
channelId = 0;
|
|
break;
|
|
}
|
|
|
|
const std::string text = msg.getString();
|
|
if (text.length() > 255 || text.find('\n') != std::string::npos) {
|
|
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
|
|
|
|
chaseMode_t chaseMode;
|
|
if (rawChaseMode == 1) {
|
|
chaseMode = CHASEMODE_FOLLOW;
|
|
} else {
|
|
chaseMode = CHASEMODE_STANDSTILL;
|
|
}
|
|
|
|
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, chaseMode, rawSecureMode != 0);
|
|
}
|
|
|
|
void ProtocolGame::parseAttack(NetworkMessage& msg)
|
|
{
|
|
uint32_t creatureId = msg.get<uint32_t>();
|
|
addGameTask(&Game::playerSetAttackedCreature, player->getID(), creatureId);
|
|
}
|
|
|
|
void ProtocolGame::parseFollow(NetworkMessage& msg)
|
|
{
|
|
uint32_t creatureId = msg.get<uint32_t>();
|
|
addGameTask(&Game::playerFollowCreature, player->getID(), creatureId);
|
|
}
|
|
|
|
void ProtocolGame::parseProcessRuleViolationReport(NetworkMessage& msg)
|
|
{
|
|
const std::string reporter = msg.getString();
|
|
addGameTask(&Game::playerProcessRuleViolationReport, player->getID(), reporter);
|
|
}
|
|
|
|
void ProtocolGame::parseCloseRuleViolationReport(NetworkMessage& msg)
|
|
{
|
|
const std::string reporter = msg.getString();
|
|
addGameTask(&Game::playerCloseRuleViolationReport, player->getID(), reporter);
|
|
}
|
|
|
|
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::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::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::parseBugReport(NetworkMessage& msg)
|
|
{
|
|
std::string bug = msg.getString();
|
|
addGameTask(&Game::playerReportBug, player->getID(), bug);
|
|
}
|
|
|
|
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 {
|
|
//uint16_t spriteID = msg.get<uint16_t>();
|
|
addGameTask(&Game::playerBrowseMarket, player->getID(), browseId);
|
|
}
|
|
}
|
|
|
|
void ProtocolGame::parseMarketCreateOffer(NetworkMessage& msg)
|
|
{
|
|
uint8_t type = msg.getByte();
|
|
uint16_t spriteId = msg.get<uint16_t>();
|
|
|
|
const ItemType& it = Item::items[spriteId];
|
|
if (it.id == 0) {
|
|
return;
|
|
}
|
|
|
|
uint16_t amount = msg.get<uint16_t>();
|
|
uint64_t price = msg.get<uint64_t>();
|
|
bool anonymous = (msg.getByte() != 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>();
|
|
addGameTask(&Game::playerCancelMarketOffer, player->getID(), timestamp, counter);
|
|
}
|
|
|
|
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>();
|
|
addGameTask(&Game::playerAcceptMarketOffer, player->getID(), timestamp, counter, amount);
|
|
}
|
|
|
|
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::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::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::sendCreatureSquare(const Creature* creature, SquareColor_t color)
|
|
{
|
|
if (!canSee(creature)) {
|
|
return;
|
|
}
|
|
|
|
NetworkMessage msg;
|
|
msg.addByte(0x86);
|
|
msg.add<uint32_t>(creature->getID());
|
|
msg.addByte(color);
|
|
writeToOutputBuffer(msg);
|
|
}
|
|
|
|
void ProtocolGame::sendRemoveRuleViolationReport(const std::string& name)
|
|
{
|
|
NetworkMessage msg;
|
|
msg.addByte(0xAF);
|
|
msg.addString(name);
|
|
writeToOutputBuffer(msg);
|
|
}
|
|
|
|
void ProtocolGame::sendLockRuleViolation()
|
|
{
|
|
NetworkMessage msg;
|
|
msg.addByte(0xB1);
|
|
writeToOutputBuffer(msg);
|
|
}
|
|
|
|
void ProtocolGame::sendRuleViolationCancel(const std::string& name)
|
|
{
|
|
NetworkMessage msg;
|
|
msg.addByte(0xB0);
|
|
msg.addString(name);
|
|
writeToOutputBuffer(msg);
|
|
}
|
|
|
|
void ProtocolGame::sendRuleViolationsChannel(uint16_t channelId)
|
|
{
|
|
NetworkMessage msg;
|
|
msg.addByte(0xAE);
|
|
msg.add<uint16_t>(channelId);
|
|
auto it = g_game.getRuleViolationReports().begin();
|
|
for (; it != g_game.getRuleViolationReports().end(); ++it) {
|
|
const RuleViolation& rvr = it->second;
|
|
if (rvr.pending) {
|
|
Player* reporter = g_game.getPlayerByID(rvr.reporterId);
|
|
if (reporter) {
|
|
msg.addByte(0xAA);
|
|
msg.add<uint32_t>(0);
|
|
msg.addString(reporter->getName());
|
|
msg.addByte(TALKTYPE_RVR_CHANNEL);
|
|
msg.add<uint32_t>(0);
|
|
msg.addString(rvr.text);
|
|
}
|
|
}
|
|
}
|
|
writeToOutputBuffer(msg);
|
|
}
|
|
|
|
void ProtocolGame::sendStats()
|
|
{
|
|
NetworkMessage msg;
|
|
AddPlayerStats(msg);
|
|
writeToOutputBuffer(msg);
|
|
}
|
|
|
|
void ProtocolGame::sendTextMessage(const TextMessage& message)
|
|
{
|
|
NetworkMessage msg;
|
|
msg.addByte(0xB4);
|
|
msg.addByte(message.type);
|
|
msg.addString(message.text);
|
|
writeToOutputBuffer(msg);
|
|
}
|
|
|
|
void ProtocolGame::sendAnimatedText(const Position& pos, uint8_t color, const std::string& text)
|
|
{
|
|
NetworkMessage msg;
|
|
msg.addByte(0x84);
|
|
msg.addPosition(pos);
|
|
msg.addByte(color);
|
|
msg.addString(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);
|
|
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)
|
|
{
|
|
NetworkMessage msg;
|
|
msg.addByte(0xAC);
|
|
|
|
msg.add<uint16_t>(channelId);
|
|
msg.addString(channelName);
|
|
|
|
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::sendIcons(uint16_t icons)
|
|
{
|
|
NetworkMessage msg;
|
|
msg.addByte(0xA2);
|
|
msg.add<uint16_t>(icons);
|
|
writeToOutputBuffer(msg);
|
|
}
|
|
|
|
void ProtocolGame::sendContainer(uint8_t cid, const Container* container, bool hasParent, uint16_t firstIndex)
|
|
{
|
|
NetworkMessage msg;
|
|
msg.addByte(0x6E);
|
|
|
|
msg.addByte(cid);
|
|
|
|
msg.addItem(container);
|
|
msg.addString(container->getName());
|
|
|
|
msg.addByte(container->capacity());
|
|
|
|
msg.addByte(hasParent ? 0x01 : 0x00);
|
|
|
|
uint32_t containerSize = container->size();
|
|
if (firstIndex < containerSize) {
|
|
uint8_t itemsToSend = std::min<uint32_t>(std::min<uint32_t>(container->capacity(), containerSize - firstIndex), std::numeric_limits<uint8_t>::max());
|
|
|
|
msg.addByte(itemsToSend);
|
|
for (auto it = container->getItemList().begin() + firstIndex, end = it + itemsToSend; it != end; ++it) {
|
|
msg.addItem(*it);
|
|
}
|
|
} else {
|
|
msg.addByte(0x00);
|
|
}
|
|
writeToOutputBuffer(msg);
|
|
}
|
|
|
|
void ProtocolGame::sendMarketEnter(uint32_t depotId)
|
|
{
|
|
NetworkMessage msg;
|
|
msg.addByte(0xF6);
|
|
msg.addByte(std::min<uint32_t>(IOMarket::getPlayerOfferCount(player->getGUID()), std::numeric_limits<uint8_t>::max()));
|
|
|
|
DepotLocker* depotLocker = player->getDepotLocker(depotId, false);
|
|
if (!depotLocker) {
|
|
msg.add<uint16_t>(0x00);
|
|
writeToOutputBuffer(msg);
|
|
return;
|
|
}
|
|
|
|
player->setInMarket(true);
|
|
|
|
msg.add<uint64_t>(player->getBankBalance());
|
|
|
|
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.id == 0) {
|
|
continue;
|
|
}
|
|
|
|
if (c && (!itemType.isContainer() || c->capacity() != itemType.maxItems)) {
|
|
continue;
|
|
}
|
|
|
|
if (!item->hasMarketAttributes()) {
|
|
continue;
|
|
}
|
|
|
|
depotItems[itemType.id] += Item::countByType(item, -1);
|
|
}
|
|
} while (!containerList.empty());
|
|
|
|
uint16_t itemsToSend = std::min<size_t>(depotItems.size(), std::numeric_limits<uint16_t>::max());
|
|
uint16_t i = 0;
|
|
|
|
msg.add<uint16_t>(itemsToSend);
|
|
for (std::map<uint16_t, uint32_t>::const_iterator it = depotItems.begin(); i < itemsToSend; ++it, ++i) {
|
|
const ItemType& itemType = Item::items[it->first];
|
|
msg.add<uint16_t>(itemType.id);
|
|
msg.add<uint16_t>(std::min<uint32_t>(0xFFFF, it->second));
|
|
}
|
|
writeToOutputBuffer(msg);
|
|
}
|
|
|
|
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<uint64_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<uint64_t>(offer.price);
|
|
msg.addString(offer.playerName);
|
|
}
|
|
|
|
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<uint64_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<uint64_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<uint64_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<uint64_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<uint64_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<uint64_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<uint64_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<uint64_t>(it->price);
|
|
msg.addByte(it->state);
|
|
}
|
|
|
|
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)) {
|
|
msg.addString(mission.getName(player));
|
|
msg.addString(mission.getDescription(player));
|
|
}
|
|
}
|
|
|
|
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) {
|
|
msg.addItem(listItem);
|
|
}
|
|
} else {
|
|
msg.addByte(0x01);
|
|
msg.addItem(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());
|
|
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);
|
|
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>(speed);
|
|
writeToOutputBuffer(msg);
|
|
}
|
|
|
|
void ProtocolGame::sendCancelWalk()
|
|
{
|
|
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) {
|
|
return;
|
|
}
|
|
|
|
NetworkMessage msg;
|
|
if (player->getOperatingSystem() >= CLIENTOS_OTCLIENT_LINUX) {
|
|
msg.addByte(0x1D);
|
|
} else {
|
|
// classic clients ping
|
|
msg.addByte(0x1E);
|
|
}
|
|
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);
|
|
}
|
|
|
|
//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, const Item* item, uint32_t stackpos)
|
|
{
|
|
if (!canSee(pos)) {
|
|
return;
|
|
}
|
|
|
|
NetworkMessage msg;
|
|
msg.addByte(0x6A);
|
|
msg.addPosition(pos);
|
|
if (player->getOperatingSystem() >= CLIENTOS_OTCLIENT_LINUX) {
|
|
msg.addByte(stackpos);
|
|
}
|
|
msg.addItem(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);
|
|
msg.addItem(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::sendFightModes()
|
|
{
|
|
NetworkMessage msg;
|
|
msg.addByte(0xA7);
|
|
msg.addByte(player->fightMode);
|
|
msg.addByte(player->chaseMode);
|
|
msg.addByte(player->secureMode);
|
|
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 != -1) {
|
|
NetworkMessage msg;
|
|
msg.addByte(0x6A);
|
|
msg.addPosition(pos);
|
|
|
|
if (player->getOperatingSystem() >= CLIENTOS_OTCLIENT_LINUX) {
|
|
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(0x0A);
|
|
|
|
msg.add<uint32_t>(player->getID());
|
|
msg.addByte(0x32); // beat duration (50)
|
|
msg.addByte(0x00);
|
|
|
|
// can report bugs?
|
|
if (player->getAccountType() >= ACCOUNT_TYPE_TUTOR) {
|
|
msg.addByte(0x01);
|
|
} else {
|
|
msg.addByte(0x00);
|
|
}
|
|
|
|
if (player->getAccountType() >= ACCOUNT_TYPE_GAMEMASTER) {
|
|
msg.addByte(0x0B);
|
|
for (uint8_t i = 0; i < 32; i++) {
|
|
msg.addByte(0xFF);
|
|
}
|
|
}
|
|
|
|
writeToOutputBuffer(msg);
|
|
|
|
sendMapDescription(pos);
|
|
|
|
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();
|
|
|
|
//gameworld light-settings
|
|
sendWorldLight(g_game.getWorldLightInfo());
|
|
|
|
//player light level
|
|
sendCreatureLight(creature);
|
|
|
|
//player vip
|
|
sendVIPEntries();
|
|
|
|
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);
|
|
msg.addItem(item);
|
|
} else {
|
|
msg.addByte(0x79);
|
|
msg.addByte(slot);
|
|
}
|
|
writeToOutputBuffer(msg);
|
|
}
|
|
|
|
void ProtocolGame::sendAddContainerItem(uint8_t cid, const Item* item)
|
|
{
|
|
NetworkMessage msg;
|
|
msg.addByte(0x70);
|
|
msg.addByte(cid);
|
|
msg.addItem(item);
|
|
writeToOutputBuffer(msg);
|
|
}
|
|
|
|
void ProtocolGame::sendUpdateContainerItem(uint8_t cid, uint16_t slot, const Item* item)
|
|
{
|
|
NetworkMessage msg;
|
|
msg.addByte(0x71);
|
|
msg.addByte(cid);
|
|
msg.addByte(static_cast<uint8_t>(slot));
|
|
msg.addItem(item);
|
|
writeToOutputBuffer(msg);
|
|
}
|
|
|
|
void ProtocolGame::sendRemoveContainerItem(uint8_t cid, uint16_t slot)
|
|
{
|
|
NetworkMessage msg;
|
|
msg.addByte(0x72);
|
|
msg.addByte(cid);
|
|
msg.addByte(static_cast<uint8_t>(slot));
|
|
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);
|
|
msg.addItem(item, true);
|
|
|
|
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);
|
|
}
|
|
|
|
if (g_game.getClientVersion() >= CLIENT_VERSION_790) {
|
|
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);
|
|
msg.addItem(itemId, 1, true);
|
|
msg.add<uint16_t>(text.size());
|
|
msg.addString(text);
|
|
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();
|
|
AddOutfit(msg, currentOutfit);
|
|
|
|
const ClientVersion_t clientVersion = g_game.getClientVersion();
|
|
std::vector<ProtocolOutfit> protocolOutfits;
|
|
if (player->isAccessPlayer()) {
|
|
static const std::string gamemasterOutfitName = "Gamemaster";
|
|
protocolOutfits.emplace_back(gamemasterOutfitName, 75, 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 (CLIENT_VERSION_780 <= clientVersion && clientVersion <= CLIENT_VERSION_792) {
|
|
if (protocolOutfits.size() == 20) { // Game client doesn't allow more than 15 outfits in 780-792
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
msg.addByte(protocolOutfits.size());
|
|
for (const ProtocolOutfit& outfit : protocolOutfits) {
|
|
msg.add<uint16_t>(outfit.lookType);
|
|
if (clientVersion > CLIENT_VERSION_781) {
|
|
msg.addString(outfit.name);
|
|
}
|
|
msg.addByte(outfit.addons);
|
|
}
|
|
|
|
writeToOutputBuffer(msg);
|
|
}
|
|
|
|
void ProtocolGame::sendUpdatedVIPStatus(uint32_t guid, VipStatus_t newStatus)
|
|
{
|
|
NetworkMessage msg;
|
|
msg.addByte(newStatus == VIPSTATUS_ONLINE ? 0xD3 : 0xD4);
|
|
msg.add<uint32_t>(guid);
|
|
writeToOutputBuffer(msg);
|
|
}
|
|
|
|
void ProtocolGame::sendVIP(uint32_t guid, const std::string& name, VipStatus_t status)
|
|
{
|
|
NetworkMessage msg;
|
|
msg.addByte(0xD2);
|
|
msg.add<uint32_t>(guid);
|
|
msg.addString(name);
|
|
msg.addByte(status);
|
|
writeToOutputBuffer(msg);
|
|
}
|
|
|
|
void ProtocolGame::sendVIPEntries()
|
|
{
|
|
const std::forward_list<VIPEntry>& vipEntries = IOLoginData::getVIPEntries(player->getAccount());
|
|
|
|
for (const VIPEntry& entry : vipEntries) {
|
|
VipStatus_t vipStatus = VIPSTATUS_ONLINE;
|
|
|
|
Player* vipPlayer = g_game.getPlayerByGUID(entry.guid);
|
|
if (!vipPlayer || (vipPlayer->isInGhostMode() && !player->isAccessPlayer())) {
|
|
vipStatus = VIPSTATUS_OFFLINE;
|
|
}
|
|
|
|
sendVIP(entry.guid, entry.name, vipStatus);
|
|
}
|
|
}
|
|
|
|
////////////// Add common messages
|
|
void ProtocolGame::AddCreature(NetworkMessage& msg, const Creature* creature, bool known, uint32_t remove)
|
|
{
|
|
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.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());
|
|
|
|
msg.addByte(player->getSkullClient(creature));
|
|
msg.addByte(player->getPartyShield(otherPlayer));
|
|
}
|
|
|
|
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<uint16_t>(player->getFreeCapacity());
|
|
|
|
msg.add<uint32_t>(std::min<uint32_t>(player->getExperience(), 0x7FFFFFFF));
|
|
|
|
msg.add<uint16_t>(player->getLevel());
|
|
msg.addByte(player->getLevelPercent());
|
|
|
|
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()));
|
|
|
|
msg.addByte(std::min<uint32_t>(player->getMagicLevel(), std::numeric_limits<uint8_t>::max()));
|
|
msg.addByte(player->getMagicLevelPercent());
|
|
|
|
msg.addByte(player->getSoul());
|
|
|
|
msg.add<uint16_t>(player->getStaminaMinutes());
|
|
}
|
|
|
|
void ProtocolGame::AddPlayerSkills(NetworkMessage& msg)
|
|
{
|
|
msg.addByte(0xA1);
|
|
|
|
for (uint8_t i = SKILL_FIRST; i <= SKILL_LAST; ++i) {
|
|
msg.addByte(std::min<int32_t>(player->getSkillLevel(i), std::numeric_limits<uint16_t>::max()));
|
|
msg.addByte(player->getSkillPercent(i));
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
void ProtocolGame::AddWorldLight(NetworkMessage& msg, const 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::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);
|
|
} |