diff --git a/src/ChatCommands.cc b/src/ChatCommands.cc index e2a8dd8b..72033ab5 100644 --- a/src/ChatCommands.cc +++ b/src/ChatCommands.cc @@ -1001,46 +1001,13 @@ ChatCommandDefinition cc_edit( if (tokens.at(1) == "none") { p->disp.visual.extra_model = 0; p->disp.visual.validation_flags &= 0xFD; - // Restore saved fields, if any - if (p->disp.visual.unused[0] == 0x8D) { - p->disp.visual.char_class = p->disp.visual.unused[1]; - p->disp.visual.head = p->disp.visual.unused[2]; - p->disp.visual.hair = p->disp.visual.unused[3]; - p->disp.visual.unused.clear(0); - } + p->disp.visual.restore_npc_saved_fields(); } else { uint8_t npc = npc_for_name(tokens.at(1), a.c->version()); if (npc == 0xFF) { throw precondition_failed("$C6No such NPC"); } - - // Some NPCs can crash the client if the character's class is - // incorrect. To handle this, we save the affected fields in the unused - // bytes after extra_model. - int8_t replacement_class = -1; - switch (npc) { - case 1: // Rico (replace with HUnewearl) - case 6: // Elly (replace with HUnewearl) - replacement_class = 0x01; - break; - case 0: // Ninja (replace with HUmar) - case 2: // Sonic (replace with HUmar) - case 5: // Flowen (replace with HUmar) - replacement_class = 0x00; - break; - } - if (replacement_class >= 0) { - if (p->disp.visual.unused[0] != 0x8D) { - p->disp.visual.unused[0] = 0x8D; - p->disp.visual.unused[1] = p->disp.visual.char_class; - p->disp.visual.unused[2] = p->disp.visual.head; - p->disp.visual.unused[3] = p->disp.visual.hair; - } - p->disp.visual.char_class = replacement_class; - p->disp.visual.head = 0x00; - p->disp.visual.hair = 0x00; - } - + p->disp.visual.backup_npc_saved_fields(); p->disp.visual.extra_model = npc; p->disp.visual.validation_flags |= 0x02; } diff --git a/src/PlayerSubordinates.hh b/src/PlayerSubordinates.hh index c99bdfcd..4be6bcfe 100644 --- a/src/PlayerSubordinates.hh +++ b/src/PlayerSubordinates.hh @@ -32,7 +32,21 @@ struct PlayerVisualConfigT { /* 10 */ parray unknown_a2; /* 18 */ U32T name_color = 0xFFFFFFFF; // ARGB /* 1C */ uint8_t extra_model = 0; - /* 1D */ parray unused; + // Some NPCs can crash the client if the character's class is incorrect. To + // handle this, we save the affected fields in the unused bytes after + // extra_model. This is a newserv-specific extension; it appears the + // following 15 bytes were simply never used by Sega. + /* 1D */ uint8_t npc_saved_data_type = 0; + /* 1E */ uint8_t npc_saved_costume = 0; + /* 1F */ uint8_t npc_saved_skin = 0; + /* 20 */ uint8_t npc_saved_face = 0; + /* 21 */ uint8_t npc_saved_head = 0; + /* 22 */ uint8_t npc_saved_hair = 0; + /* 23 */ uint8_t npc_saved_hair_r = 0; + /* 24 */ uint8_t npc_saved_hair_g = 0; + /* 25 */ uint8_t npc_saved_hair_b = 0; + /* 26 */ parray unused; + /* 28 */ F32T npc_saved_proportion_y = 0.0; // See compute_name_color_checksum for details on how this is computed. If the // value is incorrect, V1 and V2 will ignore the name_color field and use the // default color instead. This field is ignored on GC; on BB (and presumably @@ -81,6 +95,72 @@ struct PlayerVisualConfigT { this->name_color_checksum = this->compute_name_color_checksum(this->name_color); } + void backup_npc_saved_fields() { + if (this->npc_saved_data_type == 0x8E) { + return; + } + + // Restore old-format data if needed before backing up again + this->restore_npc_saved_fields(); + + this->npc_saved_data_type = 0x8E; + this->npc_saved_costume = this->costume; + this->npc_saved_skin = this->skin; + this->npc_saved_face = this->face; + this->npc_saved_head = this->head; + this->npc_saved_hair = this->hair; + this->npc_saved_hair_r = this->hair_r; + this->npc_saved_hair_g = this->hair_g; + this->npc_saved_hair_b = this->hair_b; + this->npc_saved_proportion_y = this->proportion_y; + this->costume = 0; + this->skin = 0; + this->face = 0; + this->head = 0; + this->hair = 0; + this->hair_r = 0; + this->hair_g = 0; + this->hair_b = 0; + this->proportion_y = 0; + } + + void restore_npc_saved_fields() { + switch (this->npc_saved_data_type) { + case 0x00: + break; + case 0x8D: // Old format + this->char_class = this->npc_saved_costume; + this->head = this->npc_saved_skin; + this->hair = this->npc_saved_face; + break; + case 0x8E: // New format + this->costume = this->npc_saved_costume; + this->skin = this->npc_saved_skin; + this->face = this->npc_saved_face; + this->head = this->npc_saved_head; + this->hair = this->npc_saved_hair; + this->hair_r = this->npc_saved_hair_r; + this->hair_g = this->npc_saved_hair_g; + this->hair_b = this->npc_saved_hair_b; + this->proportion_y = this->npc_saved_proportion_y; + break; + default: + throw std::runtime_error("unknown saved NPC data format"); + } + + this->npc_saved_data_type = 0; + this->npc_saved_costume = 0; + this->npc_saved_skin = 0; + this->npc_saved_face = 0; + this->npc_saved_head = 0; + this->npc_saved_hair = 0; + this->npc_saved_hair_r = 0; + this->npc_saved_hair_g = 0; + this->npc_saved_hair_b = 0; + this->unused.clear(0); + this->npc_saved_proportion_y = 0.0; + } + void enforce_lobby_join_limits_for_version(Version v) { struct ClassMaxes { uint16_t costume;