1410 lines
39 KiB
C++
1410 lines
39 KiB
C++
#include "classic_parser.h"
|
|
|
|
#include <cstring>
|
|
#include <vector>
|
|
|
|
namespace mtnet::classic {
|
|
|
|
void ClassicParser::parse_login_success(const SimplePlayer *players, const uint32_t *guild_id,
|
|
const char (*guild_name)[GUILD_NAME_MAX_LEN + 1], int n, uint32_t handle, uint32_t rkey) {
|
|
m_slots.clear();
|
|
m_slot_count = n;
|
|
for (int i = 0; i < n; ++i) {
|
|
const SimplePlayer &sp = players[i];
|
|
CharSlot s;
|
|
s.id = sp.id;
|
|
s.name.assign(sp.name, strnlen(sp.name, sizeof(sp.name)));
|
|
s.job = sp.job;
|
|
s.level = sp.level;
|
|
s.st = sp.st;
|
|
s.ht = sp.ht;
|
|
s.dx = sp.dx;
|
|
s.iq = sp.iq;
|
|
s.play_minutes = sp.play_minutes;
|
|
s.x = sp.x;
|
|
s.y = sp.y;
|
|
s.main_part = sp.main_part;
|
|
s.hair_part = sp.hair_part;
|
|
s.skill_group = sp.skill_group;
|
|
s.change_name = sp.change_name != 0;
|
|
s.guild_id = guild_id[i];
|
|
s.guild_name.assign(guild_name[i], strnlen(guild_name[i], GUILD_NAME_MAX_LEN + 1));
|
|
m_slots.push_back(std::move(s));
|
|
}
|
|
m_handle = handle;
|
|
m_random_key = rkey;
|
|
m_char_list_ready = true;
|
|
}
|
|
|
|
bool ClassicParser::on_gc(uint8_t header, const uint8_t *body, uint32_t len) {
|
|
switch (header) {
|
|
case HDR_GC_LOGIN_SUCCESS_NEWSLOT: { // 32 — what 40250 actually sends
|
|
GCLoginSuccess p;
|
|
if (!fill(p, header, body, len)) {
|
|
return false;
|
|
}
|
|
parse_login_success(p.players, p.guild_id, p.guild_name, PLAYER_PER_ACCOUNT, p.handle,
|
|
p.random_key);
|
|
return true;
|
|
}
|
|
case HDR_GC_LOGIN_SUCCESS: { // 6 — legacy 3-slot
|
|
GCLoginSuccess3 p;
|
|
if (!fill(p, header, body, len)) {
|
|
return false;
|
|
}
|
|
parse_login_success(p.players, p.guild_id, p.guild_name, PLAYER_PER_ACCOUNT3, p.handle,
|
|
p.random_key);
|
|
return true;
|
|
}
|
|
case HDR_GC_LOGIN_FAILURE: {
|
|
GCLoginFailure p;
|
|
if (!fill(p, header, body, len)) {
|
|
return false;
|
|
}
|
|
m_login_failure.assign(p.status, strnlen(p.status, sizeof(p.status)));
|
|
return true;
|
|
}
|
|
case HDR_GC_AUTH_SUCCESS:
|
|
case HDR_GC_AUTH_SUCCESS_OPENID:
|
|
case HDR_GC_MATRIX_CARD:
|
|
case HDR_GC_RUNUP_MATRIX_QUIZ:
|
|
case HDR_GC_REQUEST_PASSPOD:
|
|
case HDR_GC_REQUEST_PASSPOD_FAILED:
|
|
case HDR_GC_HS_REQUEST:
|
|
case HDR_GC_XTRAP_CS1_REQUEST:
|
|
case HDR_GC_PANAMA_PACK:
|
|
// These packets belong to optional account/anti-cheat integrations.
|
|
// Their static sizes are still registered in wire_classic.h so they
|
|
// cannot desynchronise the game stream; this client has no matching
|
|
// Godot UI or vendor runtime to act on them.
|
|
return true;
|
|
case HDR_GC_CHARACTER_CREATE_SUCCESS: {
|
|
GCPlayerCreateSuccess p;
|
|
if (!fill(p, header, body, len)) {
|
|
return false;
|
|
}
|
|
if (p.account_slot < m_slots.size()) {
|
|
CharSlot &s = m_slots[p.account_slot];
|
|
s.id = p.player.id;
|
|
s.name.assign(p.player.name, strnlen(p.player.name, sizeof(p.player.name)));
|
|
s.job = p.player.job;
|
|
s.level = p.player.level;
|
|
s.st = p.player.st;
|
|
s.ht = p.player.ht;
|
|
s.dx = p.player.dx;
|
|
s.iq = p.player.iq;
|
|
s.play_minutes = p.player.play_minutes;
|
|
s.x = p.player.x;
|
|
s.y = p.player.y;
|
|
s.main_part = p.player.main_part;
|
|
s.hair_part = p.player.hair_part;
|
|
s.skill_group = p.player.skill_group;
|
|
s.change_name = p.player.change_name != 0;
|
|
}
|
|
m_char_events.push_back({CharEvent::CreateOk, p.account_slot, 0});
|
|
return true;
|
|
}
|
|
case HDR_GC_CREATE_FAILURE: {
|
|
GCPlayerCreateFailure p;
|
|
if (!fill(p, header, body, len)) {
|
|
return false;
|
|
}
|
|
m_char_events.push_back({CharEvent::CreateFail, -1, p.type});
|
|
return true;
|
|
}
|
|
case HDR_GC_DELETE_SUCCESS: {
|
|
GCPlayerDeleteSuccess p;
|
|
if (!fill(p, header, body, len)) {
|
|
return false;
|
|
}
|
|
if (p.account_index < m_slots.size()) {
|
|
m_slots[p.account_index] = CharSlot{};
|
|
}
|
|
m_char_events.push_back({CharEvent::DeleteOk, p.account_index, 0});
|
|
return true;
|
|
}
|
|
case HDR_GC_DELETE_WRONG_SOCIAL_ID:
|
|
m_char_events.push_back({CharEvent::DeleteFail, -1, 0});
|
|
return true;
|
|
case HDR_GC_CHANGE_NAME: {
|
|
GCChangeNameNotice p;
|
|
if (!fill(p, header, body, len)) {
|
|
return false;
|
|
}
|
|
std::string name(p.name, strnlen(p.name, sizeof(p.name)));
|
|
for (auto &s : m_slots) {
|
|
if (s.id == p.pid) {
|
|
s.name = name;
|
|
// GC_CHANGE_NAME is the successful completion of the forced
|
|
// rename flow; do not prompt again on the next Start click.
|
|
s.change_name = false;
|
|
}
|
|
}
|
|
m_name_events.push_back({p.pid, std::move(name)});
|
|
return true;
|
|
}
|
|
case HDR_GC_LOGIN_KEY: {
|
|
GCLoginKey p;
|
|
if (!fill(p, header, body, len)) {
|
|
return false;
|
|
}
|
|
m_login_key = p.login_key;
|
|
return true;
|
|
}
|
|
case HDR_GC_EMPIRE: {
|
|
GCEmpire p;
|
|
if (!fill(p, header, body, len)) {
|
|
return false;
|
|
}
|
|
m_empire = p.empire;
|
|
return true;
|
|
}
|
|
|
|
case HDR_GC_MAIN_CHARACTER: { // 113
|
|
GCMainCharacter p;
|
|
if (!fill(p, header, body, len)) {
|
|
return false;
|
|
}
|
|
m_world.mut_spawn_main(p.vid, p.race,
|
|
std::string(p.name, strnlen(p.name, sizeof(p.name))), (float)p.x, (float)p.y,
|
|
(float)p.z);
|
|
return true;
|
|
}
|
|
case HDR_GC_MAIN_CHARACTER3_BGM: {
|
|
GCMainCharacter3BGM p;
|
|
if (!fill(p, header, body, len)) {
|
|
return false;
|
|
}
|
|
m_world.mut_spawn_main(p.vid, p.race,
|
|
std::string(p.name, strnlen(p.name, sizeof(p.name))), (float)p.x, (float)p.y,
|
|
(float)p.z);
|
|
return true;
|
|
}
|
|
case HDR_GC_MAIN_CHARACTER4_BGM_VOL: {
|
|
GCMainCharacter4BGM p;
|
|
if (!fill(p, header, body, len)) {
|
|
return false;
|
|
}
|
|
m_world.mut_spawn_main(p.vid, p.race,
|
|
std::string(p.name, strnlen(p.name, sizeof(p.name))), (float)p.x, (float)p.y,
|
|
(float)p.z);
|
|
return true;
|
|
}
|
|
case HDR_GC_CHARACTER_ADD: { // 1
|
|
GCCharacterAdd p;
|
|
if (!fill(p, header, body, len)) {
|
|
return false;
|
|
}
|
|
m_world.mut_spawn(p.vid, p.race, p.type, /*name*/ std::string(), (float)p.x, (float)p.y,
|
|
(float)p.z, p.angle, p.moving_speed, p.attack_speed);
|
|
return true;
|
|
}
|
|
case HDR_GC_CHARACTER_ADD2: {
|
|
GCCharacterAdd2 p;
|
|
if (!fill(p, header, body, len)) {
|
|
return false;
|
|
}
|
|
m_world.mut_spawn(p.vid, p.race, p.type,
|
|
std::string(p.name, strnlen(p.name, sizeof(p.name))), (float)p.x, (float)p.y,
|
|
(float)p.z, p.angle, p.moving_speed, p.attack_speed);
|
|
m_world.mut_char_info(p.vid, {}, p.parts, p.empire, (int32_t)p.guild, 0,
|
|
p.alignment, p.pk_mode, p.mount_vnum);
|
|
return true;
|
|
}
|
|
case HDR_GC_CHARACTER_DEL: { // 2
|
|
GCCharacterDel p;
|
|
if (!fill(p, header, body, len)) {
|
|
return false;
|
|
}
|
|
m_world.mut_despawn(p.vid);
|
|
return true;
|
|
}
|
|
case HDR_GC_MOVE: { // 3
|
|
GCMove p;
|
|
if (!fill(p, header, body, len)) {
|
|
return false;
|
|
}
|
|
// classic wire heading: deg = bRot * 5 (client NetStream Send/Recv Move)
|
|
m_world.mut_move(p.vid, (float)p.rot * 5.0f, p.func, (float)p.x, (float)p.y,
|
|
p.duration);
|
|
return true;
|
|
}
|
|
case HDR_GC_CHARACTER_POINTS: { // 16
|
|
GCPoints p;
|
|
if (!fill(p, header, body, len)) {
|
|
return false;
|
|
}
|
|
m_world.mut_set_points(p.points, POINT_MAX_NUM);
|
|
return true;
|
|
}
|
|
case HDR_GC_CHARACTER_POINT_CHANGE: { // 17 — struct has int32_t header
|
|
GCPointChange p;
|
|
if (!fill(p, header, body, len)) {
|
|
return false;
|
|
}
|
|
m_world.mut_set_point(p.type, p.value, p.vid);
|
|
return true;
|
|
}
|
|
|
|
case HDR_GC_ITEM_SET: { // 21
|
|
GCItemSet p;
|
|
if (!fill(p, header, body, len)) {
|
|
return false;
|
|
}
|
|
mtnet::ItemAttr a[ITEM_ATTRIBUTE_MAX_NUM];
|
|
for (int i = 0; i < ITEM_ATTRIBUTE_MAX_NUM; ++i) {
|
|
a[i] = {p.attrs[i].type, p.attrs[i].value};
|
|
}
|
|
m_world.mut_item_set(p.cell.window_type, p.cell.cell, p.vnum, p.count, p.flags,
|
|
p.anti_flags, p.sockets, a);
|
|
return true;
|
|
}
|
|
case HDR_GC_ITEM_DEL: { // 20 — inventory pos only
|
|
GCItemDel p;
|
|
if (!fill(p, header, body, len)) {
|
|
return false;
|
|
}
|
|
m_world.mut_item_del(/*WINDOW_INVENTORY*/ 1, p.pos);
|
|
return true;
|
|
}
|
|
case HDR_GC_ITEM_UPDATE: { // 25
|
|
GCItemUpdate p;
|
|
if (!fill(p, header, body, len)) {
|
|
return false;
|
|
}
|
|
mtnet::ItemAttr a[ITEM_ATTRIBUTE_MAX_NUM];
|
|
for (int i = 0; i < ITEM_ATTRIBUTE_MAX_NUM; ++i) {
|
|
a[i] = {p.attrs[i].type, p.attrs[i].value};
|
|
}
|
|
m_world.mut_item_update(p.cell.window_type, p.cell.cell, p.count, p.sockets, a);
|
|
return true;
|
|
}
|
|
case HDR_GC_ITEM_GROUND_ADD: { // 26
|
|
GCItemGroundAdd p;
|
|
if (!fill(p, header, body, len)) {
|
|
return false;
|
|
}
|
|
m_world.mut_ground_add(p.vid, p.vnum, (float)p.x, (float)p.y, (float)p.z);
|
|
return true;
|
|
}
|
|
case HDR_GC_ITEM_GROUND_DEL: { // 27
|
|
GCItemGroundDel p;
|
|
if (!fill(p, header, body, len)) {
|
|
return false;
|
|
}
|
|
m_world.mut_ground_del(p.vid);
|
|
return true;
|
|
}
|
|
|
|
case HDR_GC_CHARACTER_UPDATE:
|
|
case HDR_GC_CHARACTER_UPDATE2:
|
|
case HDR_GC_CHARACTER_UPDATE_NEW: { // 19 / 24
|
|
GCCharacterUpdate p;
|
|
if (!fill(p, header, body, len)) {
|
|
return false;
|
|
}
|
|
m_world.mut_char_update(p.vid, p.parts, p.moving_speed, p.attack_speed,
|
|
(int32_t)p.guild_id, p.alignment, p.pk_mode, p.mount_vnum);
|
|
return true;
|
|
}
|
|
case HDR_GC_CHARACTER_POSITION: { // 43
|
|
GCCharacterPosition p;
|
|
if (!fill(p, header, body, len)) {
|
|
return false;
|
|
}
|
|
m_world.mut_set_position(p.vid, p.position);
|
|
return true;
|
|
}
|
|
case HDR_GC_CHANGE_SPEED: { // 18
|
|
GCChangeSpeed p;
|
|
if (!fill(p, header, body, len)) {
|
|
return false;
|
|
}
|
|
m_world.mut_change_speed(p.vid, p.moving_speed);
|
|
return true;
|
|
}
|
|
case HDR_GC_WALK_MODE: { // 111
|
|
GCWalkMode p;
|
|
if (!fill(p, header, body, len)) {
|
|
return false;
|
|
}
|
|
m_world.mut_walk_mode(p.vid, p.mode);
|
|
return true;
|
|
}
|
|
case HDR_GC_STUN: { // 13
|
|
GCStun p;
|
|
if (!fill(p, header, body, len)) {
|
|
return false;
|
|
}
|
|
m_world.mut_stun(p.vid);
|
|
return true;
|
|
}
|
|
case HDR_GC_DEAD: { // 14
|
|
GCDead p;
|
|
if (!fill(p, header, body, len)) {
|
|
return false;
|
|
}
|
|
m_world.mut_dead(p.vid);
|
|
return true;
|
|
}
|
|
case HDR_GC_MOTION: { // 36
|
|
GCMotion p;
|
|
if (!fill(p, header, body, len)) {
|
|
return false;
|
|
}
|
|
m_world.mut_motion(p.vid, p.victim_vid, p.motion);
|
|
return true;
|
|
}
|
|
case HDR_GC_TARGET: { // 63
|
|
GCTarget p;
|
|
if (!fill(p, header, body, len)) {
|
|
return false;
|
|
}
|
|
m_world.mut_target(p.vid, p.hp_percent);
|
|
return true;
|
|
}
|
|
case HDR_GC_DAMAGE_INFO: { // 135
|
|
GCDamageInfo p;
|
|
if (!fill(p, header, body, len)) {
|
|
return false;
|
|
}
|
|
m_world.mut_damage(p.vid, p.flag, p.damage);
|
|
return true;
|
|
}
|
|
case HDR_GC_FISHING: {
|
|
GCFishing packet;
|
|
if (!fill(packet, header, body, len)) {
|
|
return false;
|
|
}
|
|
m_world.mut_fishing(packet.subheader, packet.info, packet.dir);
|
|
return true;
|
|
}
|
|
case HDR_GC_CHAR_ADDITIONAL_INFO: { // 136
|
|
GCCharAddInfo p;
|
|
if (!fill(p, header, body, len)) {
|
|
return false;
|
|
}
|
|
m_world.mut_char_info(p.vid, std::string(p.name, strnlen(p.name, sizeof(p.name))),
|
|
p.parts, p.empire, (int32_t)p.guild_id, (int32_t)p.level, p.alignment,
|
|
p.pk_mode, p.mount_vnum);
|
|
return true;
|
|
}
|
|
case HDR_GC_SKILL_LEVEL: { // 76
|
|
GCSkillLevel p;
|
|
if (!fill(p, header, body, len)) {
|
|
return false;
|
|
}
|
|
uint8_t level[SKILL_MAX_NUM];
|
|
uint8_t master[SKILL_MAX_NUM];
|
|
for (int i = 0; i < SKILL_MAX_NUM; ++i) {
|
|
level[i] = p.skills[i].level;
|
|
master[i] = p.skills[i].master_type;
|
|
}
|
|
m_world.mut_skill_levels(level, master, SKILL_MAX_NUM);
|
|
return true;
|
|
}
|
|
case HDR_GC_SKILL_COOLTIME_END: {
|
|
GCSkillCooltimeEnd p;
|
|
if (!fill(p, header, body, len)) {
|
|
return false;
|
|
}
|
|
m_world.mut_skill_cooldown_end(p.skill);
|
|
return true;
|
|
}
|
|
case HDR_GC_QUICKSLOT_ADD: { // 28
|
|
GCQuickSlotAdd p;
|
|
if (!fill(p, header, body, len)) {
|
|
return false;
|
|
}
|
|
m_world.mut_quickslot_set(p.pos, p.slot.type, p.slot.position);
|
|
return true;
|
|
}
|
|
case HDR_GC_QUICKSLOT_DEL: { // 29
|
|
GCQuickSlotDel p;
|
|
if (!fill(p, header, body, len)) {
|
|
return false;
|
|
}
|
|
m_world.mut_quickslot_del(p.pos);
|
|
return true;
|
|
}
|
|
case HDR_GC_QUICKSLOT_SWAP: { // 30
|
|
GCQuickSlotSwap p;
|
|
if (!fill(p, header, body, len)) {
|
|
return false;
|
|
}
|
|
m_world.mut_quickslot_swap(p.pos, p.pos_to);
|
|
return true;
|
|
}
|
|
case HDR_GC_AFFECT_ADD: { // 126
|
|
GCAffectAdd p;
|
|
if (!fill(p, header, body, len)) {
|
|
return false;
|
|
}
|
|
m_world.mut_affect_add(p.elem.type, p.elem.point_idx, p.elem.apply_value, p.elem.flag,
|
|
p.elem.duration);
|
|
return true;
|
|
}
|
|
case HDR_GC_AFFECT_REMOVE: { // 127
|
|
GCAffectRemove p;
|
|
if (!fill(p, header, body, len)) {
|
|
return false;
|
|
}
|
|
m_world.mut_affect_remove(p.type);
|
|
return true;
|
|
}
|
|
case HDR_GC_WHISPER: { // 34 (dynamic) — body = type, name_from[25], text
|
|
const size_t name_max = CHARACTER_NAME_MAX_LEN + 1;
|
|
if (len < 1u + name_max) {
|
|
return true;
|
|
}
|
|
uint8_t wtype = body[0];
|
|
const char *name = reinterpret_cast<const char *>(body + 1);
|
|
const char *text = reinterpret_cast<const char *>(body + 1 + name_max);
|
|
size_t text_len = len - 1 - name_max;
|
|
m_world.mut_whisper(wtype, std::string(name, strnlen(name, name_max)),
|
|
std::string(text, strnlen(text, text_len)));
|
|
return true;
|
|
}
|
|
case HDR_GC_SYNC_POSITION: { // 5 (dynamic) — body = N x SyncPosElement
|
|
size_t n = len / sizeof(SyncPosElement);
|
|
const auto *els = reinterpret_cast<const SyncPosElement *>(body);
|
|
for (size_t i = 0; i < n; ++i) {
|
|
m_world.mut_snap_position(els[i].vid, (float)els[i].x, (float)els[i].y);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
case HDR_GC_PARTY_INVITE: { // 77
|
|
GCPartyInvite p;
|
|
if (!fill(p, header, body, len)) {
|
|
return false;
|
|
}
|
|
m_world.mut_party_invite(p.leader_vid);
|
|
return true;
|
|
}
|
|
case HDR_GC_PARTY_ADD: { // 78
|
|
GCPartyAdd p;
|
|
if (!fill(p, header, body, len)) {
|
|
return false;
|
|
}
|
|
m_world.mut_party_add(p.pid, std::string(p.name, strnlen(p.name, sizeof(p.name))));
|
|
return true;
|
|
}
|
|
case HDR_GC_PARTY_UPDATE: { // 79
|
|
GCPartyUpdate p;
|
|
if (!fill(p, header, body, len)) {
|
|
return false;
|
|
}
|
|
m_world.mut_party_update(p.pid, p.role, p.percent_hp, p.affects);
|
|
return true;
|
|
}
|
|
case HDR_GC_PARTY_REMOVE: { // 80
|
|
GCPartyRemove p;
|
|
if (!fill(p, header, body, len)) {
|
|
return false;
|
|
}
|
|
m_world.mut_party_remove(p.pid);
|
|
return true;
|
|
}
|
|
case HDR_GC_PARTY_LINK: { // 91
|
|
GCPartyLink p;
|
|
if (!fill(p, header, body, len)) {
|
|
return false;
|
|
}
|
|
m_world.mut_party_link(p.pid, p.vid);
|
|
return true;
|
|
}
|
|
case HDR_GC_PARTY_UNLINK: { // 92
|
|
GCPartyUnlink p;
|
|
if (!fill(p, header, body, len)) {
|
|
return false;
|
|
}
|
|
m_world.mut_party_unlink(p.pid);
|
|
return true;
|
|
}
|
|
case HDR_GC_PARTY_PARAMETER: { // 83
|
|
GCPartyParameter p;
|
|
if (!fill(p, header, body, len)) {
|
|
return false;
|
|
}
|
|
m_world.mut_party_parameter(p.distribute_mode);
|
|
return true;
|
|
}
|
|
|
|
case HDR_GC_MESSENGER: { // 74 (dynamic) — body = subheader + records
|
|
if (len < 1) {
|
|
return true;
|
|
}
|
|
const uint8_t sub = body[0];
|
|
const uint8_t *cur = body + 1;
|
|
const uint8_t *end = body + len;
|
|
auto read_name = [&](uint8_t n) -> std::string {
|
|
if (cur + n > end) {
|
|
return {};
|
|
}
|
|
std::string name(reinterpret_cast<const char *>(cur), n);
|
|
cur += n;
|
|
return name;
|
|
};
|
|
switch (sub) {
|
|
case MESSENGER_GC_LIST:
|
|
// LIST is a complete snapshot, so remove friends from a previous
|
|
// character/server before applying the records that follow.
|
|
m_world.mut_friend_clear();
|
|
while (cur + sizeof(MessengerListEntry) <= end) {
|
|
const uint8_t connected = cur[0];
|
|
const uint8_t n = cur[1];
|
|
cur += sizeof(MessengerListEntry);
|
|
std::string name = read_name(n);
|
|
if (name.size() != n) {
|
|
break;
|
|
}
|
|
m_world.mut_friend_set(name, (connected & 1u) != 0);
|
|
m_world.mut_friend_mobile(name, (connected & 2u) != 0);
|
|
}
|
|
return true;
|
|
case MESSENGER_GC_LOGIN:
|
|
case MESSENGER_GC_LOGOUT:
|
|
if (cur < end) {
|
|
const uint8_t n = *cur++;
|
|
std::string name = read_name(n);
|
|
if (name.size() == n) {
|
|
m_world.mut_friend_set(name, sub == MESSENGER_GC_LOGIN);
|
|
}
|
|
}
|
|
return true;
|
|
case MESSENGER_GC_MOBILE:
|
|
// 40250 sends [state][name_length][name]. The state is
|
|
// the mobile-presence flag, not an online/offline transition.
|
|
if (cur + 2 <= end) {
|
|
const uint8_t state = *cur++;
|
|
const uint8_t n = *cur++;
|
|
std::string name = read_name(n);
|
|
if (name.size() == n) {
|
|
m_world.mut_friend_mobile(name, state != 0);
|
|
}
|
|
}
|
|
return true;
|
|
default:
|
|
// INVITE has no local representation; it is handled by the
|
|
// command channel in this 40250 client.
|
|
return true;
|
|
}
|
|
}
|
|
|
|
case HDR_GC_EXCHANGE: { // 42 (static)
|
|
GCExchange p;
|
|
if (!fill(p, header, body, len)) {
|
|
return false;
|
|
}
|
|
const bool self = p.is_me != 0;
|
|
switch (p.subheader) {
|
|
case EXCHANGE_GC_START:
|
|
m_world.mut_exchange_start(p.arg1);
|
|
break;
|
|
case EXCHANGE_GC_ITEM_ADD:
|
|
m_world.mut_exchange_item(self, static_cast<uint8_t>(p.arg2.cell), p.arg1,
|
|
static_cast<uint8_t>(p.arg3));
|
|
break;
|
|
case EXCHANGE_GC_ITEM_DEL:
|
|
m_world.mut_exchange_item_del(self, static_cast<uint8_t>(p.arg1));
|
|
break;
|
|
case EXCHANGE_GC_GOLD_ADD:
|
|
m_world.mut_exchange_gold(self, static_cast<int64_t>(p.arg1));
|
|
break;
|
|
case EXCHANGE_GC_ACCEPT:
|
|
m_world.mut_exchange_accept(self, p.arg1 != 0);
|
|
break;
|
|
case EXCHANGE_GC_END:
|
|
m_world.mut_exchange_end();
|
|
break;
|
|
case EXCHANGE_GC_ALREADY:
|
|
case EXCHANGE_GC_LESS_GOLD:
|
|
m_world.mut_exchange_notice();
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
case HDR_GC_SAFEBOX_SIZE: { // 88
|
|
GCSafeboxSize p;
|
|
if (!fill(p, header, body, len)) {
|
|
return false;
|
|
}
|
|
m_world.mut_safebox_open(p.size);
|
|
return true;
|
|
}
|
|
case HDR_GC_SAFEBOX_MONEY_CHANGE: { // 84
|
|
GCSafeboxMoneyChange p;
|
|
if (!fill(p, header, body, len)) {
|
|
return false;
|
|
}
|
|
m_world.mut_safebox_money(p.money);
|
|
return true;
|
|
}
|
|
case HDR_GC_SAFEBOX_SET: { // 85
|
|
GCItemSet p;
|
|
if (!fill(p, header, body, len)) {
|
|
return false;
|
|
}
|
|
mtnet::ItemAttr attrs[ITEM_ATTRIBUTE_MAX_NUM] = {};
|
|
for (int i = 0; i < ITEM_ATTRIBUTE_MAX_NUM; ++i) {
|
|
attrs[i] = {p.attrs[i].type, p.attrs[i].value};
|
|
}
|
|
m_world.mut_safebox_set(p.cell.cell, p.vnum, p.count, p.flags, p.anti_flags, p.sockets,
|
|
attrs);
|
|
return true;
|
|
}
|
|
case HDR_GC_SAFEBOX_DEL: { // 86
|
|
GCItemDel p;
|
|
if (!fill(p, header, body, len)) {
|
|
return false;
|
|
}
|
|
m_world.mut_safebox_del(p.pos);
|
|
return true;
|
|
}
|
|
case HDR_GC_SAFEBOX_WRONG_PASSWORD: // 87
|
|
m_world.mut_safebox_wrong_password();
|
|
return true;
|
|
|
|
case HDR_GC_MALL_OPEN: { // 122
|
|
GCMallOpen p;
|
|
if (!fill(p, header, body, len)) {
|
|
return false;
|
|
}
|
|
m_world.mut_mall_open(p.size);
|
|
return true;
|
|
}
|
|
case HDR_GC_MALL_SET: { // 128
|
|
GCItemSet p;
|
|
if (!fill(p, header, body, len)) {
|
|
return false;
|
|
}
|
|
mtnet::ItemAttr attrs[ITEM_ATTRIBUTE_MAX_NUM] = {};
|
|
for (int i = 0; i < ITEM_ATTRIBUTE_MAX_NUM; ++i) {
|
|
attrs[i] = {p.attrs[i].type, p.attrs[i].value};
|
|
}
|
|
m_world.mut_mall_set(p.cell.cell, p.vnum, p.count, p.flags, p.anti_flags, p.sockets,
|
|
attrs);
|
|
return true;
|
|
}
|
|
case HDR_GC_MALL_DEL: { // 129
|
|
GCItemDel p;
|
|
if (!fill(p, header, body, len)) {
|
|
return false;
|
|
}
|
|
m_world.mut_mall_del(p.pos);
|
|
return true;
|
|
}
|
|
|
|
case HDR_GC_SCRIPT: { // 45 (dynamic) — body = [skin][u16 src_size][text]
|
|
if (len < 3) {
|
|
return true;
|
|
}
|
|
uint8_t skin = body[0];
|
|
const char *txt = reinterpret_cast<const char *>(body + 3);
|
|
size_t txt_len = len - 3;
|
|
m_world.mut_script(skin, std::string(txt, strnlen(txt, txt_len)));
|
|
return true;
|
|
}
|
|
case HDR_GC_QUEST_CONFIRM: { // 46 (static)
|
|
GCQuestConfirm p;
|
|
if (!fill(p, header, body, len)) {
|
|
return false;
|
|
}
|
|
m_world.mut_quest_confirm(std::string(p.msg, strnlen(p.msg, sizeof(p.msg))), p.timeout,
|
|
p.request_pid);
|
|
return true;
|
|
}
|
|
case HDR_GC_QUEST_INFO: { // 81 (dynamic) — body = [u16 index][u8 flag][flag-driven...]
|
|
if (len < 3) {
|
|
return true;
|
|
}
|
|
uint16_t index;
|
|
std::memcpy(&index, body, 2);
|
|
uint8_t flag = body[2];
|
|
const uint8_t *cur = body + 3;
|
|
const uint8_t *end = body + len;
|
|
auto take_str = [&](size_t cap) -> std::string {
|
|
size_t n = 0;
|
|
while (cur + n < end && cur[n] != 0 && n < cap) {
|
|
++n;
|
|
}
|
|
std::string s(reinterpret_cast<const char *>(cur), n);
|
|
cur += (cur + n < end) ? n + 1 : n; // skip NUL if present
|
|
return s;
|
|
};
|
|
auto take_i32 = [&]() -> int32_t {
|
|
int32_t v = 0;
|
|
if (cur + 4 <= end) {
|
|
std::memcpy(&v, cur, 4);
|
|
cur += 4;
|
|
}
|
|
return v;
|
|
};
|
|
std::string title, clock_name, counter_name, icon;
|
|
int32_t clock_value = 0, counter_value = 0;
|
|
if (flag & QUEST_SEND_TITLE) {
|
|
title = take_str(28);
|
|
}
|
|
if (flag & QUEST_SEND_CLOCK_NAME) {
|
|
clock_name = take_str(16);
|
|
}
|
|
if (flag & QUEST_SEND_CLOCK_VALUE) {
|
|
clock_value = take_i32();
|
|
}
|
|
if (flag & QUEST_SEND_COUNTER_NAME) {
|
|
counter_name = take_str(16);
|
|
}
|
|
if (flag & QUEST_SEND_COUNTER_VALUE) {
|
|
counter_value = take_i32();
|
|
}
|
|
if (flag & QUEST_SEND_ICON_FILE) {
|
|
icon = take_str(24);
|
|
}
|
|
m_world.mut_quest_info(index, flag, (flag & QUEST_SEND_IS_BEGIN) != 0, title, clock_name,
|
|
clock_value, counter_name, counter_value, icon);
|
|
return true;
|
|
}
|
|
|
|
case HDR_GC_SHOP: { // 38 (dynamic) — body = [subheader][sub-specific...]
|
|
if (len < 1) {
|
|
return true;
|
|
}
|
|
uint8_t sub = body[0];
|
|
const uint8_t *cur = body + 1;
|
|
const uint8_t *end = body + len;
|
|
auto read_tab = [&](ShopTab &tab) {
|
|
for (int j = 0; j < SHOP_HOST_ITEM_MAX_NUM &&
|
|
cur + (int)sizeof(ShopItem43) <= end;
|
|
++j) {
|
|
ShopItem43 si;
|
|
std::memcpy(&si, cur, sizeof(si));
|
|
cur += sizeof(si);
|
|
if (si.vnum != 0) {
|
|
tab.items.push_back({si.vnum, si.price, si.count, si.display_pos});
|
|
}
|
|
}
|
|
};
|
|
switch (sub) {
|
|
case SHOP_SUB_START: {
|
|
if (cur + 4 > end) {
|
|
return true;
|
|
}
|
|
uint32_t vid;
|
|
std::memcpy(&vid, cur, 4);
|
|
cur += 4;
|
|
ShopTab tab; // unnamed -> flat single shelf
|
|
read_tab(tab);
|
|
m_world.mut_shop_open(vid, {std::move(tab)});
|
|
return true;
|
|
}
|
|
case SHOP_SUB_START_EX: {
|
|
if (cur + 5 > end) {
|
|
return true;
|
|
}
|
|
uint32_t vid;
|
|
std::memcpy(&vid, cur, 4);
|
|
cur += 4;
|
|
uint8_t tab_count = *cur++;
|
|
std::vector<ShopTab> tabs;
|
|
for (uint8_t t = 0; t < tab_count && t < SHOP_TAB_COUNT_MAX; ++t) {
|
|
if (cur + (int)sizeof(ShopTabHead32) > end) {
|
|
break;
|
|
}
|
|
ShopTabHead32 th;
|
|
std::memcpy(&th, cur, sizeof(th));
|
|
cur += sizeof(th);
|
|
ShopTab tab;
|
|
tab.name.assign(th.name, strnlen(th.name, SHOP_TAB_NAME_MAX));
|
|
tab.coin_type = th.coin_type;
|
|
read_tab(tab);
|
|
tabs.push_back(std::move(tab));
|
|
}
|
|
m_world.mut_shop_open(vid, tabs);
|
|
return true;
|
|
}
|
|
case SHOP_SUB_END:
|
|
m_world.mut_shop_close();
|
|
return true;
|
|
case SHOP_SUB_NOT_ENOUGH_MONEY:
|
|
case SHOP_SUB_NOT_ENOUGH_MONEY_EX:
|
|
m_world.mut_shop_error("NOT_ENOUGH_MONEY");
|
|
return true;
|
|
case SHOP_SUB_SOLDOUT:
|
|
case SHOP_SUB_SOLD_OUT:
|
|
m_world.mut_shop_error("SOLDOUT");
|
|
return true;
|
|
case SHOP_SUB_INVENTORY_FULL:
|
|
m_world.mut_shop_error("INVENTORY_FULL");
|
|
return true;
|
|
case SHOP_SUB_INVALID_POS:
|
|
m_world.mut_shop_error("INVALID_POS");
|
|
return true;
|
|
case SHOP_SUB_UPDATE_ITEM: {
|
|
if (cur + (int)sizeof(ShopUpdateItem) > end) {
|
|
return true;
|
|
}
|
|
ShopUpdateItem update;
|
|
std::memcpy(&update, cur, sizeof(update));
|
|
m_world.mut_shop_update_item(update.pos, update.item.vnum, update.item.price,
|
|
update.item.count);
|
|
return true;
|
|
}
|
|
case SHOP_SUB_UPDATE_PRICE: {
|
|
if (cur + (int)sizeof(ShopUpdatePrice) > end) {
|
|
return true;
|
|
}
|
|
ShopUpdatePrice update;
|
|
std::memcpy(&update, cur, sizeof(update));
|
|
m_world.mut_shop_update_price(update.price);
|
|
return true;
|
|
}
|
|
default:
|
|
return true; // OK has no state payload
|
|
}
|
|
}
|
|
|
|
case HDR_GC_SHOP_SIGN: {
|
|
GCShopSign packet;
|
|
if (!fill(packet, header, body, len)) {
|
|
return false;
|
|
}
|
|
m_world.mut_shop_sign(packet.vid,
|
|
std::string(packet.sign, strnlen(packet.sign, sizeof(packet.sign))));
|
|
return true;
|
|
}
|
|
|
|
case HDR_GC_GUILD: { // dynamic — body = [subheader][sub-specific...]
|
|
if (len < 1) {
|
|
return true;
|
|
}
|
|
const uint8_t sub = body[0];
|
|
const uint8_t *cur = body + 1;
|
|
const uint8_t *end = body + len;
|
|
switch (sub) {
|
|
case GUILD_GC_LOGIN:
|
|
case GUILD_GC_LOGOUT:
|
|
if (cur + 4 <= end) {
|
|
uint32_t pid;
|
|
std::memcpy(&pid, cur, sizeof(pid));
|
|
m_world.mut_guild_member_online(pid, sub == GUILD_GC_LOGIN);
|
|
}
|
|
return true;
|
|
case GUILD_GC_INFO: {
|
|
if (cur + (int)sizeof(GuildInfo35) > end) {
|
|
return true;
|
|
}
|
|
GuildInfo35 info;
|
|
std::memcpy(&info, cur, sizeof(info));
|
|
m_world.mut_guild_info(info.member_count, info.max_member_count, info.guild_id,
|
|
info.master_pid, info.exp, info.level,
|
|
std::string(info.name, strnlen(info.name, sizeof(info.name))), info.gold,
|
|
info.has_land != 0);
|
|
return true;
|
|
}
|
|
case GUILD_GC_LIST: {
|
|
if ((end - cur) % (int)sizeof(GuildMember38) != 0) {
|
|
return true;
|
|
}
|
|
m_world.mut_guild_clear_members();
|
|
while (cur + (int)sizeof(GuildMember38) <= end) {
|
|
GuildMember38 member;
|
|
std::memcpy(&member, cur, sizeof(member));
|
|
cur += sizeof(member);
|
|
m_world.mut_guild_member(member.pid, member.grade, member.is_general != 0,
|
|
member.job, member.level, member.offer,
|
|
std::string(member.name, strnlen(member.name, sizeof(member.name))));
|
|
}
|
|
return true;
|
|
}
|
|
case GUILD_GC_GRADE: {
|
|
if (cur >= end) {
|
|
return true;
|
|
}
|
|
const uint8_t count = *cur++;
|
|
for (uint8_t i = 0; i < count; ++i) {
|
|
if (cur + 1 + (int)sizeof(GuildGrade10) > end) {
|
|
return true;
|
|
}
|
|
const uint8_t grade = *cur++;
|
|
GuildGrade10 data;
|
|
std::memcpy(&data, cur, sizeof(data));
|
|
cur += sizeof(data);
|
|
m_world.mut_guild_grade(grade,
|
|
std::string(data.name, strnlen(data.name, sizeof(data.name))), data.auth);
|
|
}
|
|
return true;
|
|
}
|
|
case GUILD_GC_ADD: {
|
|
if (cur + (int)sizeof(GuildMember38) > end) {
|
|
return true;
|
|
}
|
|
GuildMember38 member;
|
|
std::memcpy(&member, cur, sizeof(member));
|
|
m_world.mut_guild_member(member.pid, member.grade, member.is_general != 0,
|
|
member.job, member.level, member.offer,
|
|
std::string(member.name, strnlen(member.name, sizeof(member.name))));
|
|
return true;
|
|
}
|
|
case GUILD_GC_REMOVE:
|
|
if (cur + 4 <= end) {
|
|
uint32_t pid;
|
|
std::memcpy(&pid, cur, sizeof(pid));
|
|
m_world.mut_guild_remove_member(pid);
|
|
}
|
|
return true;
|
|
case GUILD_GC_GRADE_NAME:
|
|
if (cur + 1 + GUILD_GRADE_NAME_MAX_LEN + 1 <= end) {
|
|
const uint8_t grade = *cur++;
|
|
m_world.mut_guild_grade_name(grade,
|
|
std::string(reinterpret_cast<const char *>(cur),
|
|
strnlen(reinterpret_cast<const char *>(cur), GUILD_GRADE_NAME_MAX_LEN + 1)));
|
|
}
|
|
return true;
|
|
case GUILD_GC_GRADE_AUTH:
|
|
if (cur + 2 <= end) {
|
|
m_world.mut_guild_grade_auth(cur[0], cur[1]);
|
|
}
|
|
return true;
|
|
case GUILD_GC_CHANGE_EXP:
|
|
if (cur + 5 <= end) {
|
|
const uint8_t level = *cur++;
|
|
uint32_t exp;
|
|
std::memcpy(&exp, cur, sizeof(exp));
|
|
m_world.mut_guild_exp(level, exp);
|
|
}
|
|
return true;
|
|
case GUILD_GC_CHANGE_MEMBER_GRADE:
|
|
if (cur + 5 <= end) {
|
|
uint32_t pid;
|
|
std::memcpy(&pid, cur, sizeof(pid));
|
|
m_world.mut_guild_member_grade(pid, cur[4]);
|
|
}
|
|
return true;
|
|
case GUILD_GC_SKILL_INFO: {
|
|
if (cur + (int)sizeof(GuildSkill17) > end) {
|
|
return true;
|
|
}
|
|
GuildSkill17 skill;
|
|
std::memcpy(&skill, cur, sizeof(skill));
|
|
m_world.mut_guild_skill(skill.skill_point, skill.levels, skill.guild_point,
|
|
skill.max_guild_point);
|
|
return true;
|
|
}
|
|
case GUILD_GC_CHANGE_MEMBER_GENERAL:
|
|
if (cur + 5 <= end) {
|
|
uint32_t pid;
|
|
std::memcpy(&pid, cur, sizeof(pid));
|
|
m_world.mut_guild_member_general(pid, cur[4] != 0);
|
|
}
|
|
return true;
|
|
case GUILD_GC_WAR:
|
|
if (cur + (int)sizeof(GuildWar10) <= end) {
|
|
GuildWar10 war;
|
|
std::memcpy(&war, cur, sizeof(war));
|
|
m_world.mut_guild_war(war.self_id, war.opponent_id, war.type, war.state);
|
|
}
|
|
return true;
|
|
case GUILD_GC_GUILD_WAR_LIST:
|
|
case GUILD_GC_GUILD_WAR_END_LIST: {
|
|
if ((end - cur) % (int)sizeof(GuildWarPair8) != 0) {
|
|
return true;
|
|
}
|
|
std::vector<GuildWarPair> pairs;
|
|
while (cur + (int)sizeof(GuildWarPair8) <= end) {
|
|
GuildWarPair8 pair;
|
|
std::memcpy(&pair, cur, sizeof(pair));
|
|
cur += sizeof(pair);
|
|
pairs.push_back({pair.src, pair.dst});
|
|
}
|
|
m_world.mut_guild_war_pairs(pairs, sub == GUILD_GC_GUILD_WAR_END_LIST);
|
|
return true;
|
|
}
|
|
case GUILD_GC_WAR_POINT:
|
|
if (cur + (int)sizeof(GuildWarPoint12) <= end) {
|
|
GuildWarPoint12 point;
|
|
std::memcpy(&point, cur, sizeof(point));
|
|
m_world.mut_guild_war_score(point.gain_guild_id, point.opponent_guild_id,
|
|
point.point);
|
|
}
|
|
return true;
|
|
case GUILD_GC_COMMENTS: {
|
|
if (cur >= end) {
|
|
m_world.mut_guild_comments({});
|
|
return true;
|
|
}
|
|
const uint8_t count = *cur++;
|
|
std::vector<GuildComment> comments;
|
|
comments.reserve(count);
|
|
for (uint8_t i = 0; i < count; ++i) {
|
|
if (cur + (int)sizeof(GuildComment80) > end) {
|
|
break;
|
|
}
|
|
GuildComment80 packet;
|
|
std::memcpy(&packet, cur, sizeof(packet));
|
|
cur += sizeof(packet);
|
|
comments.push_back({packet.id,
|
|
std::string(packet.name, strnlen(packet.name, sizeof(packet.name))),
|
|
std::string(packet.content, strnlen(packet.content, sizeof(packet.content)))});
|
|
}
|
|
m_world.mut_guild_comments(comments);
|
|
return true;
|
|
}
|
|
case GUILD_GC_GUILD_INVITE: {
|
|
GuildInvite17 packet;
|
|
if (cur + (int)sizeof(packet) <= end) {
|
|
std::memcpy(&packet, cur, sizeof(packet));
|
|
m_world.mut_guild_invite(packet.guild_id,
|
|
std::string(packet.guild_name,
|
|
strnlen(packet.guild_name, sizeof(packet.guild_name))));
|
|
}
|
|
return true;
|
|
}
|
|
case GUILD_GC_MONEY_CHANGE:
|
|
if (cur + 4 <= end) {
|
|
uint32_t gold;
|
|
std::memcpy(&gold, cur, sizeof(gold));
|
|
m_world.mut_guild_money(gold);
|
|
}
|
|
return true;
|
|
case GUILD_GC_NAME:
|
|
if ((end - cur) % (int)sizeof(GuildName16) != 0) {
|
|
return true;
|
|
}
|
|
while (cur + (int)sizeof(GuildName16) <= end) {
|
|
GuildName16 name;
|
|
std::memcpy(&name, cur, sizeof(name));
|
|
cur += sizeof(name);
|
|
m_world.mut_guild_name(name.id,
|
|
std::string(name.name, strnlen(name.name, sizeof(name.name))));
|
|
}
|
|
return true;
|
|
default:
|
|
return true;
|
|
}
|
|
}
|
|
|
|
case HDR_GC_ITEM_USE: {
|
|
GCItemUse packet;
|
|
if (!fill(packet, header, body, len)) {
|
|
return false;
|
|
}
|
|
m_world.mut_item_used(packet.vnum);
|
|
return true;
|
|
}
|
|
case HDR_GC_ITEM_OWNERSHIP: {
|
|
GCItemOwnership packet;
|
|
if (!fill(packet, header, body, len)) {
|
|
return false;
|
|
}
|
|
m_world.mut_ground_owner(packet.vid,
|
|
std::string(packet.owner, strnlen(packet.owner, sizeof(packet.owner))));
|
|
return true;
|
|
}
|
|
case HDR_GC_OWNERSHIP: {
|
|
GCOwnership packet;
|
|
if (!fill(packet, header, body, len)) {
|
|
return false;
|
|
}
|
|
m_world.mut_ground_owner(packet.victim_vid, std::to_string(packet.owner_vid));
|
|
return true;
|
|
}
|
|
case HDR_GC_MOUNT: {
|
|
GCMount packet;
|
|
if (!fill(packet, header, body, len)) {
|
|
return false;
|
|
}
|
|
m_world.mut_mount(packet.vid, packet.mount_vnum);
|
|
return true;
|
|
}
|
|
case HDR_GC_CREATE_FLY: {
|
|
GCCreateFly packet;
|
|
if (!fill(packet, header, body, len)) {
|
|
return false;
|
|
}
|
|
m_world.mut_fly(packet.type, packet.start_vid, packet.end_vid);
|
|
return true;
|
|
}
|
|
case HDR_GC_FLY_TARGETING:
|
|
case HDR_GC_ADD_FLY_TARGETING: {
|
|
GCFlyTargeting packet;
|
|
if (!fill(packet, header, body, len)) {
|
|
return false;
|
|
}
|
|
m_world.mut_fly_target(packet.shooter_vid, packet.target_vid, packet.x, packet.y,
|
|
header == HDR_GC_ADD_FLY_TARGETING);
|
|
return true;
|
|
}
|
|
case HDR_GC_PVP: {
|
|
GCPvp packet;
|
|
if (!fill(packet, header, body, len)) {
|
|
return false;
|
|
}
|
|
m_world.mut_pvp(packet.src_vid, packet.dst_vid, packet.mode);
|
|
return true;
|
|
}
|
|
case HDR_GC_DUEL_START: {
|
|
// TPacketGCDuelStart is [header][u16 whole_size] followed by the
|
|
// DWORD VIDs of every opponent in the duel. ClassicStream has
|
|
// already consumed the three-byte dynamic prefix, so `body` is the
|
|
// packed VID list here.
|
|
if (len % sizeof(uint32_t) != 0) {
|
|
return false;
|
|
}
|
|
std::vector<uint32_t> opponents;
|
|
opponents.reserve(len / sizeof(uint32_t));
|
|
for (uint32_t i = 0; i < len; i += sizeof(uint32_t)) {
|
|
uint32_t vid = 0;
|
|
std::memcpy(&vid, body + i, sizeof(vid));
|
|
opponents.push_back(vid);
|
|
}
|
|
m_world.mut_duel_start(opponents.data(), (int)opponents.size());
|
|
return true;
|
|
}
|
|
case HDR_GC_SPECIAL_EFFECT: {
|
|
GCSpecialEffect packet;
|
|
if (!fill(packet, header, body, len)) {
|
|
return false;
|
|
}
|
|
m_world.mut_special_effect(packet.vid, packet.type);
|
|
return true;
|
|
}
|
|
case HDR_GC_SPECIFIC_EFFECT: {
|
|
GCSpecificEffect packet;
|
|
if (!fill(packet, header, body, len)) {
|
|
return false;
|
|
}
|
|
m_world.mut_specific_effect(packet.vid,
|
|
std::string(packet.effect_file, strnlen(packet.effect_file, sizeof(packet.effect_file))));
|
|
return true;
|
|
}
|
|
case HDR_GC_SKILL_GROUP: {
|
|
GCSkillGroup packet;
|
|
if (!fill(packet, header, body, len)) {
|
|
return false;
|
|
}
|
|
m_world.mut_skill_group(packet.skill_group);
|
|
return true;
|
|
}
|
|
case HDR_GC_SKILL_LEVEL_OLD: {
|
|
GCSkillLevelOld packet;
|
|
if (!fill(packet, header, body, len)) {
|
|
return false;
|
|
}
|
|
m_world.mut_skill_levels(packet.levels, nullptr, SKILL_MAX_NUM);
|
|
return true;
|
|
}
|
|
case HDR_GC_OBSERVER_ADD:
|
|
case HDR_GC_OBSERVER_MOVE: {
|
|
GCObserverMove packet;
|
|
if (!fill(packet, header, body, len)) {
|
|
return false;
|
|
}
|
|
m_world.mut_observer(header == HDR_GC_OBSERVER_ADD ? ObserverEvent::Add : ObserverEvent::Move,
|
|
packet.vid, (int32_t)packet.x * 100, (int32_t)packet.y * 100);
|
|
return true;
|
|
}
|
|
case HDR_GC_OBSERVER_REMOVE: {
|
|
GCObserverRemove packet;
|
|
if (!fill(packet, header, body, len)) {
|
|
return false;
|
|
}
|
|
m_world.mut_observer(ObserverEvent::Remove, packet.vid, 0, 0);
|
|
return true;
|
|
}
|
|
case HDR_GC_REFINE_INFORMATION_OLD:
|
|
case HDR_GC_REFINE_INFORMATION: {
|
|
GCRefineInfo packet;
|
|
if (!fill(packet, header, body, len)) {
|
|
return false;
|
|
}
|
|
RefineCue::Mat materials[5] = {};
|
|
for (int i = 0; i < 5; ++i) {
|
|
materials[i].vnum = packet.materials[i].vnum;
|
|
materials[i].count = packet.materials[i].count;
|
|
}
|
|
m_world.mut_refine(packet.type, packet.pos, packet.src_vnum, packet.result_vnum,
|
|
packet.material_count, packet.cost, packet.prob, materials);
|
|
return true;
|
|
}
|
|
case HDR_GC_DRAGON_SOUL_REFINE: {
|
|
GCDragonSoulRefine packet;
|
|
if (!fill(packet, header, body, len)) {
|
|
return false;
|
|
}
|
|
m_world.mut_dragon_soul_refine(packet.sub_type, packet.pos.window_type,
|
|
packet.pos.cell);
|
|
return true;
|
|
}
|
|
case HDR_GC_WARP: {
|
|
GCWarpClassic packet;
|
|
if (!fill(packet, header, body, len)) {
|
|
return false;
|
|
}
|
|
m_world.mut_warp(packet.x, packet.y, packet.addr, packet.port);
|
|
return true;
|
|
}
|
|
case HDR_GC_TIME: {
|
|
GCTime packet;
|
|
if (!fill(packet, header, body, len)) {
|
|
return false;
|
|
}
|
|
m_world.mut_server_time(packet.time);
|
|
return true;
|
|
}
|
|
case HDR_GC_CHANNEL: {
|
|
GCChannel packet;
|
|
if (!fill(packet, header, body, len)) {
|
|
return false;
|
|
}
|
|
m_world.mut_channel(packet.channel);
|
|
return true;
|
|
}
|
|
case HDR_GC_DUNGEON: {
|
|
if (len < sizeof(GCDungeonHead)) {
|
|
return true;
|
|
}
|
|
GCDungeonHead packet;
|
|
std::memcpy(&packet, body, sizeof(packet));
|
|
if (packet.subheader == 1 && len >= sizeof(packet) + sizeof(GCDungeonDestination)) {
|
|
GCDungeonDestination destination;
|
|
std::memcpy(&destination, body + sizeof(packet), sizeof(destination));
|
|
m_world.mut_dungeon(packet.subheader, destination.x, destination.y, true);
|
|
} else {
|
|
m_world.mut_dungeon(packet.subheader, 0, 0, false);
|
|
}
|
|
return true;
|
|
}
|
|
case HDR_GC_NPC_POSITION: {
|
|
if (len < sizeof(GCNPCPositionHead)) {
|
|
return true;
|
|
}
|
|
GCNPCPositionHead head;
|
|
std::memcpy(&head, body, sizeof(head));
|
|
const uint8_t *cur = body + sizeof(head);
|
|
const uint8_t *end = body + len;
|
|
std::vector<NPCMark> marks;
|
|
for (uint16_t i = 0; i < head.count && cur + (int)sizeof(NPCPosition34) <= end; ++i) {
|
|
NPCPosition34 entry;
|
|
std::memcpy(&entry, cur, sizeof(entry));
|
|
cur += sizeof(entry);
|
|
marks.push_back({entry.type, 0,
|
|
std::string(entry.name, strnlen(entry.name, sizeof(entry.name))), entry.x, entry.y});
|
|
}
|
|
m_world.mut_npc_marks(marks);
|
|
return true;
|
|
}
|
|
case HDR_GC_LAND_LIST: {
|
|
const uint8_t *cur = body;
|
|
const uint8_t *end = body + len;
|
|
if (len % sizeof(LandPacketElement24) != 0) {
|
|
return true;
|
|
}
|
|
std::vector<LandArea> areas;
|
|
while (cur + (int)sizeof(LandPacketElement24) <= end) {
|
|
LandPacketElement24 entry;
|
|
std::memcpy(&entry, cur, sizeof(entry));
|
|
cur += sizeof(entry);
|
|
areas.push_back({entry.id, entry.x, entry.y, entry.width, entry.height, entry.guild_id});
|
|
}
|
|
m_world.mut_land_areas(areas);
|
|
return true;
|
|
}
|
|
case HDR_GC_TARGET_CREATE: {
|
|
GCTargetCreate43 packet;
|
|
if (!fill(packet, header, body, len)) {
|
|
return false;
|
|
}
|
|
m_world.mut_marker_create(packet.id,
|
|
std::string(packet.name, strnlen(packet.name, sizeof(packet.name))), packet.vid,
|
|
packet.type);
|
|
return true;
|
|
}
|
|
case HDR_GC_TARGET_UPDATE: {
|
|
GCTargetUpdate13 packet;
|
|
if (!fill(packet, header, body, len)) {
|
|
return false;
|
|
}
|
|
m_world.mut_marker_update(packet.id, packet.x, packet.y);
|
|
return true;
|
|
}
|
|
case HDR_GC_TARGET_DELETE: {
|
|
GCTargetDelete5 packet;
|
|
if (!fill(packet, header, body, len)) {
|
|
return false;
|
|
}
|
|
m_world.mut_marker_delete(packet.id);
|
|
return true;
|
|
}
|
|
case HDR_GC_LOVER_INFO: {
|
|
GCLoverInfo packet;
|
|
if (!fill(packet, header, body, len)) {
|
|
return false;
|
|
}
|
|
m_world.mut_lover(std::string(packet.name, strnlen(packet.name, sizeof(packet.name))),
|
|
packet.love_point);
|
|
return true;
|
|
}
|
|
case HDR_GC_LOVE_POINT_UPDATE: {
|
|
GCLovePointUpdate packet;
|
|
if (!fill(packet, header, body, len)) {
|
|
return false;
|
|
}
|
|
m_world.mut_love_point(packet.love_point);
|
|
return true;
|
|
}
|
|
case HDR_GC_DIG_MOTION: {
|
|
GCDigMotion packet;
|
|
if (!fill(packet, header, body, len)) {
|
|
return false;
|
|
}
|
|
m_world.mut_dig(packet.vid, packet.target_vid, packet.count);
|
|
return true;
|
|
}
|
|
case HDR_GC_REQUEST_MAKE_GUILD:
|
|
++m_guild_make_requests;
|
|
return true;
|
|
case HDR_GC_VIEW_EQUIP: {
|
|
GCViewEquip packet;
|
|
if (!fill(packet, header, body, len)) {
|
|
return false;
|
|
}
|
|
Item items[mtnet::VIEW_EQUIP_WEAR_MAX_NUM] = {};
|
|
for (int i = 0; i < mtnet::VIEW_EQUIP_WEAR_MAX_NUM && i < CLASSIC_WEAR_MAX_NUM; ++i) {
|
|
items[i].vnum = packet.equips[i].vnum;
|
|
items[i].count = packet.equips[i].count;
|
|
std::memcpy(items[i].sockets, packet.equips[i].sockets, sizeof(items[i].sockets));
|
|
for (int j = 0; j < ITEM_ATTRIBUTE_MAX_NUM; ++j) {
|
|
items[i].attrs[j] = {packet.equips[i].attrs[j].type,
|
|
packet.equips[i].attrs[j].value};
|
|
}
|
|
}
|
|
m_world.mut_view_equipment(packet.vid, items, mtnet::VIEW_EQUIP_WEAR_MAX_NUM);
|
|
return true;
|
|
}
|
|
|
|
case HDR_GC_CHAT: { // 4 (dynamic) — body = type,vid,empire,text (after [hdr][u16 size])
|
|
if (len < 6) {
|
|
return true; // malformed, ignore
|
|
}
|
|
uint8_t type = body[0];
|
|
uint32_t vid;
|
|
std::memcpy(&vid, body + 1, 4);
|
|
// body[5] = empire; text follows
|
|
const char *txt = reinterpret_cast<const char *>(body + 6);
|
|
m_world.mut_chat(type, vid, std::string(txt, strnlen(txt, len - 6)));
|
|
return true;
|
|
}
|
|
|
|
default:
|
|
// not handled yet — harmless, keep going. classic_stream already
|
|
// rejected unknown *static* headers via the size table.
|
|
return true;
|
|
}
|
|
}
|
|
|
|
} // namespace mtnet::classic
|