Togu
Advanced OT User
I'm studying TFS 1.3 and OTClient sources to improve my project. And now I need to parse a new information (attack speed) to OTClient.
So, till now, what I understood is that the server can send and receive informations or commands (packets) to/from the client.
And client can send and receive informations or commands (packets) to/from the server too.
1) So, in server, we have in protocolgame.cpp:
This function receives commands/information from connected clients, am I right?
2) We also have in protocolgame.cpp the send methods, and one of them is "sendStats":
And on client we have in protocolgameparse.cpp:
What I want to do is replace:
For something like:
But I don't know what exactly this will do. Didn't understand how things work. How the client identifies the exactly byte sent? How do I know which byte is available to use? Why sometimes is used msg.add and other times msg.addByte ?
I'm a student, so I want theorical answers, I'll be glad if anybody can help me with that.
Edit:
I was following this post [Help] how to parse the information sent form the client (https://otland.net/threads/help-how-to-parse-the-information-sent-form-the-client.252669/) to understand things.
So, till now, what I understood is that the server can send and receive informations or commands (packets) to/from the client.
And client can send and receive informations or commands (packets) to/from the server too.
1) So, in server, we have in protocolgame.cpp:
Code:
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 0x77: parseEquipObject(msg); 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 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 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 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;
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();
}
}
This function receives commands/information from connected clients, am I right?
2) We also have in protocolgame.cpp the send methods, and one of them is "sendStats":
Code:
void ProtocolGame::sendStats()
{
NetworkMessage msg;
AddPlayerStats(msg);
writeToOutputBuffer(msg);
}
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());
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::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(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::writeToOutputBuffer(const NetworkMessage& msg)
{
auto out = getOutputBuffer(msg.getLength());
out->append(msg);
}
And on client we have in protocolgameparse.cpp:
Code:
void ProtocolGame::parsePlayerStats(const InputMessagePtr& msg)
{
double health;
double maxHealth;
if(g_game.getFeature(Otc::GameDoubleHealth)) {
health = msg->getU32();
maxHealth = msg->getU32();
} else {
health = msg->getU16();
maxHealth = msg->getU16();
}
double freeCapacity;
if(g_game.getFeature(Otc::GameDoubleFreeCapacity))
freeCapacity = msg->getU32() / 100.0;
else
freeCapacity = msg->getU16() / 100.0;
double totalCapacity = 0;
if(g_game.getFeature(Otc::GameTotalCapacity))
totalCapacity = msg->getU32() / 100.0;
double experience;
if(g_game.getFeature(Otc::GameDoubleExperience))
experience = msg->getU64();
else
experience = msg->getU32();
double level = msg->getU16();
double levelPercent = msg->getU8();
if(g_game.getFeature(Otc::GameExperienceBonus)) {
if(g_game.getClientVersion() <= 1096) {
double experienceBonus = msg->getDouble();
} else {
int baseXpGain = msg->getU16();
int voucherAddend = msg->getU16();
int grindingAddend = msg->getU16();
int storeBoostAddend = msg->getU16();
int huntingBoostFactor = msg->getU16();
}
}
double mana;
double maxMana;
if(g_game.getFeature(Otc::GameDoubleHealth)) {
mana = msg->getU32();
maxMana = msg->getU32();
} else {
mana = msg->getU16();
maxMana = msg->getU16();
}
double magicLevel = msg->getU8();
double baseMagicLevel;
if(g_game.getFeature(Otc::GameSkillsBase))
baseMagicLevel = msg->getU8();
else
baseMagicLevel = magicLevel;
double magicLevelPercent = msg->getU8();
double soul = msg->getU8();
double stamina = 0;
if(g_game.getFeature(Otc::GamePlayerStamina))
stamina = msg->getU16();
double baseSpeed = 0;
if(g_game.getFeature(Otc::GameSkillsBase))
baseSpeed = msg->getU16();
double regeneration = 0;
if(g_game.getFeature(Otc::GamePlayerRegenerationTime))
regeneration = msg->getU16();
double training = 0;
if(g_game.getFeature(Otc::GameOfflineTrainingTime)) {
training = msg->getU16();
if(g_game.getClientVersion() >= 1097) {
int remainingStoreXpBoostSeconds = msg->getU16();
bool canBuyMoreStoreXpBoosts = msg->getU8();
}
}
m_localPlayer->setHealth(health, maxHealth);
m_localPlayer->setFreeCapacity(freeCapacity);
m_localPlayer->setTotalCapacity(totalCapacity);
m_localPlayer->setExperience(experience);
m_localPlayer->setLevel(level, levelPercent);
m_localPlayer->setMana(mana, maxMana);
m_localPlayer->setMagicLevel(magicLevel, magicLevelPercent);
m_localPlayer->setBaseMagicLevel(baseMagicLevel);
m_localPlayer->setStamina(stamina);
m_localPlayer->setSoul(soul);
m_localPlayer->setBaseSpeed(baseSpeed);
m_localPlayer->setRegenerationTime(regeneration);
m_localPlayer->setOfflineTrainingTime(training);
}
What I want to do is replace:
Code:
msg.add<uint16_t>(player->getOfflineTrainingTime() / 60 / 1000);
For something like:
Code:
msg.add<uint16_t>(player->getAttackSpeed());
But I don't know what exactly this will do. Didn't understand how things work. How the client identifies the exactly byte sent? How do I know which byte is available to use? Why sometimes is used msg.add and other times msg.addByte ?
I'm a student, so I want theorical answers, I'll be glad if anybody can help me with that.
Edit:
I was following this post [Help] how to parse the information sent form the client (https://otland.net/threads/help-how-to-parse-the-information-sent-form-the-client.252669/) to understand things.