save all fields when applying npc skins

This commit is contained in:
Martin Michelsen
2025-06-24 20:12:24 -07:00
parent 28ab1bea9c
commit e9bf51f3f7
2 changed files with 83 additions and 36 deletions
+2 -35
View File
@@ -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;
}
+81 -1
View File
@@ -32,7 +32,21 @@ struct PlayerVisualConfigT {
/* 10 */ parray<uint8_t, 8> unknown_a2;
/* 18 */ U32T<BE> name_color = 0xFFFFFFFF; // ARGB
/* 1C */ uint8_t extra_model = 0;
/* 1D */ parray<uint8_t, 0x0F> 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<uint8_t, 2> unused;
/* 28 */ F32T<BE> 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;