fix cross-version lobby appearance and name colors

This commit is contained in:
Martin Michelsen
2023-10-21 20:44:01 -07:00
parent 7b035d311f
commit c4790068ef
9 changed files with 259 additions and 83 deletions
+2 -2
View File
@@ -834,7 +834,7 @@ static void server_command_edit(shared_ptr<Client> c, const std::u16string& args
} else if (tokens.at(0) == "npc") { } else if (tokens.at(0) == "npc") {
if (tokens.at(1) == "none") { if (tokens.at(1) == "none") {
p->disp.visual.extra_model = 0; p->disp.visual.extra_model = 0;
p->disp.visual.v2_flags &= 0xFD; p->disp.visual.validation_flags &= 0xFD;
} else { } else {
uint8_t npc = npc_for_name(decode_sjis(tokens.at(1))); uint8_t npc = npc_for_name(decode_sjis(tokens.at(1)));
if (npc == 0xFF) { if (npc == 0xFF) {
@@ -842,7 +842,7 @@ static void server_command_edit(shared_ptr<Client> c, const std::u16string& args
return; return;
} }
p->disp.visual.extra_model = npc; p->disp.visual.extra_model = npc;
p->disp.visual.v2_flags |= 0x02; p->disp.visual.validation_flags |= 0x02;
} }
} else if (tokens.at(0) == "tech") { } else if (tokens.at(0) == "tech") {
uint8_t level = stoul(tokens.at(2)) - 1; uint8_t level = stoul(tokens.at(2)) - 1;
+3 -2
View File
@@ -1258,10 +1258,11 @@ struct S_JoinGame_GC_Ep3_64 : S_JoinGame_GC_64 {
// This field is only present if the game (and client) is Episode 3. Similarly // This field is only present if the game (and client) is Episode 3. Similarly
// to lobby_data in the base struct, all four of these are always present and // to lobby_data in the base struct, all four of these are always present and
// they are filled in in slot positions. // they are filled in in slot positions.
struct { struct Ep3PlayerEntry {
PlayerInventory inventory; PlayerInventory inventory;
PlayerDispDataDCPCV3 disp; PlayerDispDataDCPCV3 disp;
} __packed__ players_ep3[4]; } __packed__;
parray<Ep3PlayerEntry, 4> players_ep3;
} __packed__; } __packed__;
struct S_JoinGame_XB_64 : S_JoinGame_DC_PC<PlayerLobbyDataXB> { struct S_JoinGame_XB_64 : S_JoinGame_DC_PC<PlayerLobbyDataXB> {
+113 -15
View File
@@ -6,6 +6,7 @@
#include <phosg/Filesystem.hh> #include <phosg/Filesystem.hh>
#include <phosg/Hash.hh> #include <phosg/Hash.hh>
#include <phosg/Random.hh>
#include <stdexcept> #include <stdexcept>
#include "ItemData.hh" #include "ItemData.hh"
@@ -19,29 +20,126 @@ using namespace std;
FileContentsCache player_files_cache(300 * 1000 * 1000); FileContentsCache player_files_cache(300 * 1000 * 1000);
uint32_t PlayerVisualConfig::compute_name_color_checksum(uint32_t name_color) {
uint8_t x = (random_object<uint32_t>() % 0xFF) + 1;
uint8_t y = (random_object<uint32_t>() % 0xFF) + 1;
// name_color = ABCDEFGHabcdefghIJKLMNOPijklmnop
// name_color_checksum = ---------ijklmabcdeIJKLM-------- ^ (xxxxxxxxyyyyyyyyxxxxxxxxyyyyyyyy)
uint32_t xbrgx95558 = ((name_color << 15) & 0x007C0000) | ((name_color >> 6) & 0x0003E000) | ((name_color >> 3) & 0x00001F00);
uint32_t mask = (x << 24) | (y << 16) | (x << 8) | y;
return xbrgx95558 ^ mask;
}
void PlayerVisualConfig::compute_name_color_checksum() {
this->name_color_checksum = this->compute_name_color_checksum(this->name_color);
}
void PlayerDispDataDCPCV3::enforce_lobby_join_limits(GameVersion target_version) { void PlayerDispDataDCPCV3::enforce_lobby_join_limits(GameVersion target_version) {
struct ClassMaxes {
uint16_t costume;
uint16_t skin;
uint16_t face;
uint16_t head;
uint16_t hair;
uint16_t hair_r;
uint16_t hair_g;
uint16_t hair_b;
};
static constexpr ClassMaxes v1_v2_class_maxes[14] = {
{0x0009, 0x0004, 0x0005, 0x0000, 0x0007, 0x0100, 0x0100, 0x0100},
{0x0009, 0x0004, 0x0005, 0x0000, 0x000A, 0x0100, 0x0100, 0x0100},
{0x0000, 0x0009, 0x0000, 0x0005, 0x0000, 0x0000, 0x0000, 0x0000},
{0x0009, 0x0004, 0x0005, 0x0000, 0x0007, 0x0100, 0x0100, 0x0100},
{0x0000, 0x0009, 0x0000, 0x0005, 0x0000, 0x0000, 0x0000, 0x0000},
{0x0000, 0x0009, 0x0000, 0x0005, 0x0000, 0x0000, 0x0000, 0x0000},
{0x0009, 0x0004, 0x0005, 0x0000, 0x000A, 0x0100, 0x0100, 0x0100},
{0x0009, 0x0004, 0x0005, 0x0000, 0x0007, 0x0100, 0x0100, 0x0100},
{0x0009, 0x0004, 0x0005, 0x0000, 0x000A, 0x0100, 0x0100, 0x0100},
{0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000},
{0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000},
{0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000},
{0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000},
{0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000},
};
static constexpr ClassMaxes v3_v4_class_maxes[19] = {
{0x0012, 0x0004, 0x0005, 0x0000, 0x000A, 0x0100, 0x0100, 0x0100},
{0x0012, 0x0004, 0x0005, 0x0000, 0x000A, 0x0100, 0x0100, 0x0100},
{0x0000, 0x0019, 0x0000, 0x0005, 0x0000, 0x0000, 0x0000, 0x0000},
{0x0012, 0x0004, 0x0005, 0x0000, 0x000A, 0x0100, 0x0100, 0x0100},
{0x0000, 0x0019, 0x0000, 0x0005, 0x0000, 0x0000, 0x0000, 0x0000},
{0x0000, 0x0019, 0x0000, 0x0005, 0x0000, 0x0000, 0x0000, 0x0000},
{0x0012, 0x0004, 0x0005, 0x0000, 0x000A, 0x0100, 0x0100, 0x0100},
{0x0012, 0x0004, 0x0005, 0x0000, 0x000A, 0x0100, 0x0100, 0x0100},
{0x0012, 0x0004, 0x0005, 0x0000, 0x000A, 0x0100, 0x0100, 0x0100},
{0x0000, 0x0019, 0x0000, 0x0005, 0x0000, 0x0000, 0x0000, 0x0000},
{0x0012, 0x0004, 0x0005, 0x0000, 0x000A, 0x0100, 0x0100, 0x0100},
{0x0012, 0x0004, 0x0005, 0x0000, 0x000A, 0x0100, 0x0100, 0x0100},
{0x0000, 0x0000, 0x0000, 0x0000, 0x0001, 0x0100, 0x0100, 0x0100},
{0x0000, 0x0000, 0x0000, 0x0000, 0x0001, 0x0100, 0x0100, 0x0100},
{0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000},
{0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000},
{0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000},
{0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000},
{0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000}};
const ClassMaxes* maxes;
if ((target_version == GameVersion::PC) || (target_version == GameVersion::DC)) { if ((target_version == GameVersion::PC) || (target_version == GameVersion::DC)) {
// V1/V2 have fewer classes, so we'll substitute some here // V1/V2 have fewer classes, so we'll substitute some here
if (this->visual.char_class == 9) { switch (this->visual.char_class) {
case 0: // HUmar
case 1: // HUnewearl
case 2: // HUcast
case 3: // RAmar
case 4: // RAcast
case 5: // RAcaseal
case 6: // FOmarl
case 7: // FOnewm
case 8: // FOnewearl
case 12: // V3 custom 1
case 13: // V3 custom 2
break;
case 9: // HUcaseal
this->visual.char_class = 5; // HUcaseal -> RAcaseal this->visual.char_class = 5; // HUcaseal -> RAcaseal
} else if (this->visual.char_class == 10) { break;
case 10: // FOmar
this->visual.char_class = 0; // FOmar -> HUmar this->visual.char_class = 0; // FOmar -> HUmar
} else if (this->visual.char_class == 11) { break;
case 11: // RAmarl
this->visual.char_class = 1; // RAmarl -> HUnewearl this->visual.char_class = 1; // RAmarl -> HUnewearl
break;
case 14: // V2 custom 1 / V3 custom 3
case 15: // V2 custom 2 / V3 custom 4
case 16: // V2 custom 3 / V3 custom 5
case 17: // V2 custom 4 / V3 custom 6
case 18: // V2 custom 5 / V3 custom 7
this->visual.char_class -= 5;
break;
default:
this->visual.char_class = 0; // Invalid classes -> HUmar
} }
// V1/V2 has fewer costumes and android skins, so substitute them here too maxes = &v1_v2_class_maxes[this->visual.char_class];
this->visual.costume %= 9;
this->visual.skin %= 9;
// If the player is somehow still not a valid class, make them appear as the
// "ninja" NPC
if (this->visual.char_class > 8) {
this->visual.extra_model = 0;
this->visual.v2_flags |= 2;
}
this->visual.version = 2; this->visual.version = 2;
} else {
if (this->visual.char_class >= 19) {
this->visual.char_class = 0; // Invalid classes -> HUmar
} }
maxes = &v3_v4_class_maxes[this->visual.char_class];
}
// V1/V2 has fewer costumes and android skins, so substitute them here
this->visual.costume %= maxes->costume;
this->visual.skin %= maxes->skin;
this->visual.face %= maxes->face;
this->visual.head %= maxes->head;
this->visual.hair %= maxes->hair;
this->visual.hair_r %= maxes->hair_r;
this->visual.hair_g %= maxes->hair_g;
this->visual.hair_b %= maxes->hair_b;
this->visual.compute_name_color_checksum();
this->visual.class_flags = class_flags_for_class(this->visual.char_class);
} }
void PlayerDispDataBB::enforce_lobby_join_limits(GameVersion) { void PlayerDispDataBB::enforce_lobby_join_limits(GameVersion) {
@@ -90,10 +188,10 @@ void PlayerDispDataBB::apply_preview(const PlayerDispDataBBPreview& pre) {
void PlayerDispDataBB::apply_dressing_room(const PlayerDispDataBBPreview& pre) { void PlayerDispDataBB::apply_dressing_room(const PlayerDispDataBBPreview& pre) {
this->visual.name_color = pre.visual.name_color; this->visual.name_color = pre.visual.name_color;
this->visual.extra_model = pre.visual.extra_model; this->visual.extra_model = pre.visual.extra_model;
this->visual.unknown_a3 = pre.visual.unknown_a3; this->visual.name_color_checksum = pre.visual.name_color_checksum;
this->visual.section_id = pre.visual.section_id; this->visual.section_id = pre.visual.section_id;
this->visual.char_class = pre.visual.char_class; this->visual.char_class = pre.visual.char_class;
this->visual.v2_flags = pre.visual.v2_flags; this->visual.validation_flags = pre.visual.validation_flags;
this->visual.version = pre.visual.version; this->visual.version = pre.visual.version;
this->visual.class_flags = pre.visual.class_flags; this->visual.class_flags = pre.visual.class_flags;
this->visual.costume = pre.visual.costume; this->visual.costume = pre.visual.costume;
+14 -3
View File
@@ -104,13 +104,21 @@ struct PlayerDispDataBB;
struct PlayerVisualConfig { struct PlayerVisualConfig {
/* 00 */ ptext<char, 0x10> name; /* 00 */ ptext<char, 0x10> name;
/* 10 */ parray<uint8_t, 8> unknown_a2; /* 10 */ parray<uint8_t, 8> unknown_a2;
/* 18 */ le_uint32_t name_color = 0x00000000; // RGBA /* 18 */ le_uint32_t name_color = 0x00000000; // ARGB
/* 1C */ uint8_t extra_model = 0; /* 1C */ uint8_t extra_model = 0;
/* 1D */ parray<uint8_t, 0x0F> unused; /* 1D */ parray<uint8_t, 0x0F> unused;
/* 2C */ le_uint32_t unknown_a3 = 0; // See compute_name_color_checksum for details on how this is computed. This
// field is ignored on V3 and BB.
/* 2C */ le_uint32_t name_color_checksum = 0;
/* 30 */ uint8_t section_id = 0; /* 30 */ uint8_t section_id = 0;
/* 31 */ uint8_t char_class = 0; /* 31 */ uint8_t char_class = 0;
/* 32 */ uint8_t v2_flags = 0; // validation_flags specifies that some parts of this structure are not valid
// and should be ignored. The bits are:
// -----FCS
// F = class_flags is incorrect for the character's char_class value
// C = char_class is out of range
// S = section_id is out of range
/* 32 */ uint8_t validation_flags = 0;
/* 33 */ uint8_t version = 0; /* 33 */ uint8_t version = 0;
// class_flags specifies features of the character's class. The bits are: // class_flags specifies features of the character's class. The bits are:
// -------- -------- -------- FRHANMfm // -------- -------- -------- FRHANMfm
@@ -129,6 +137,9 @@ struct PlayerVisualConfig {
/* 48 */ le_float proportion_x = 0.0; /* 48 */ le_float proportion_x = 0.0;
/* 4C */ le_float proportion_y = 0.0; /* 4C */ le_float proportion_y = 0.0;
/* 50 */ /* 50 */
static uint32_t compute_name_color_checksum(uint32_t name_color);
void compute_name_color_checksum();
} __attribute__((packed)); } __attribute__((packed));
struct PlayerDispDataDCPCV3 { struct PlayerDispDataDCPCV3 {
+2 -2
View File
@@ -1405,11 +1405,11 @@ std::string disassemble_quest_script(const void* data, size_t size, QuestScriptV
lines.emplace_back(string_printf(" %04zX extra_model %02hhX", l->offset + offsetof(PlayerVisualConfig, extra_model), visual.extra_model)); lines.emplace_back(string_printf(" %04zX extra_model %02hhX", l->offset + offsetof(PlayerVisualConfig, extra_model), visual.extra_model));
string unused = format_data_string(visual.unused.data(), visual.unused.bytes()); string unused = format_data_string(visual.unused.data(), visual.unused.bytes());
lines.emplace_back(string_printf(" %04zX unused %s", l->offset + offsetof(PlayerVisualConfig, unused), unused.c_str())); lines.emplace_back(string_printf(" %04zX unused %s", l->offset + offsetof(PlayerVisualConfig, unused), unused.c_str()));
lines.emplace_back(string_printf(" %04zX a3 %08" PRIX32, l->offset + offsetof(PlayerVisualConfig, unknown_a3), visual.unknown_a3.load())); lines.emplace_back(string_printf(" %04zX name_color_checksum %08" PRIX32, l->offset + offsetof(PlayerVisualConfig, name_color_checksum), visual.name_color_checksum.load()));
string secid_name = name_for_section_id(visual.section_id); string secid_name = name_for_section_id(visual.section_id);
lines.emplace_back(string_printf(" %04zX section_id %02hhX (%s)", l->offset + offsetof(PlayerVisualConfig, section_id), visual.section_id, secid_name.c_str())); lines.emplace_back(string_printf(" %04zX section_id %02hhX (%s)", l->offset + offsetof(PlayerVisualConfig, section_id), visual.section_id, secid_name.c_str()));
lines.emplace_back(string_printf(" %04zX char_class %02hhX (%s)", l->offset + offsetof(PlayerVisualConfig, char_class), visual.char_class, name_for_char_class(visual.char_class))); lines.emplace_back(string_printf(" %04zX char_class %02hhX (%s)", l->offset + offsetof(PlayerVisualConfig, char_class), visual.char_class, name_for_char_class(visual.char_class)));
lines.emplace_back(string_printf(" %04zX v2_flags %02hhX", l->offset + offsetof(PlayerVisualConfig, v2_flags), visual.v2_flags)); lines.emplace_back(string_printf(" %04zX validation_flags %02hhX", l->offset + offsetof(PlayerVisualConfig, validation_flags), visual.validation_flags));
lines.emplace_back(string_printf(" %04zX version %02hhX", l->offset + offsetof(PlayerVisualConfig, version), visual.version)); lines.emplace_back(string_printf(" %04zX version %02hhX", l->offset + offsetof(PlayerVisualConfig, version), visual.version));
lines.emplace_back(string_printf(" %04zX class_flags %08" PRIX32, l->offset + offsetof(PlayerVisualConfig, class_flags), visual.class_flags.load())); lines.emplace_back(string_printf(" %04zX class_flags %08" PRIX32, l->offset + offsetof(PlayerVisualConfig, class_flags), visual.class_flags.load()));
lines.emplace_back(string_printf(" %04zX costume %04hX", l->offset + offsetof(PlayerVisualConfig, costume), visual.costume.load())); lines.emplace_back(string_printf(" %04zX costume %04hX", l->offset + offsetof(PlayerVisualConfig, costume), visual.costume.load()));
+50
View File
@@ -281,12 +281,62 @@ void ReplaySession::apply_default_mask(shared_ptr<Event> ev) {
mask_data, mask_size, sizeof(S_JoinGame_GC_Ep3_64)); mask_data, mask_size, sizeof(S_JoinGame_GC_Ep3_64));
mask.variations.clear(0); mask.variations.clear(0);
mask.rare_seed = 0; mask.rare_seed = 0;
for (size_t offset = sizeof(S_JoinGame_GC_64) +
offsetof(S_JoinGame_GC_Ep3_64::Ep3PlayerEntry, disp.visual.name_color_checksum);
offset + 4 <= mask_size;
offset += sizeof(S_JoinGame_GC_Ep3_64::Ep3PlayerEntry)) {
*reinterpret_cast<uint32_t*>(reinterpret_cast<char*>(mask_data) + offset) = 0;
}
}
break;
case 0xEB:
if (version != GameVersion::GC) {
break;
}
[[fallthrough]];
case 0x65:
case 0x67:
case 0x68:
if ((version == GameVersion::DC) || (version == GameVersion::GC)) {
for (size_t offset = offsetof(S_JoinLobby_DC_GC_65_67_68_Ep3_EB, entries) +
offsetof(S_JoinLobby_DC_GC_65_67_68_Ep3_EB::Entry, disp.visual.name_color_checksum);
offset + 4 <= mask_size;
offset += sizeof(S_JoinLobby_DC_GC_65_67_68_Ep3_EB::Entry)) {
*reinterpret_cast<uint32_t*>(reinterpret_cast<char*>(mask_data) + offset) = 0;
}
} else if (version == GameVersion::XB) {
for (size_t offset = offsetof(S_JoinLobby_XB_65_67_68, entries) +
offsetof(S_JoinLobby_XB_65_67_68::Entry, disp.visual.name_color_checksum);
offset + 4 <= mask_size;
offset += sizeof(S_JoinLobby_XB_65_67_68::Entry)) {
*reinterpret_cast<uint32_t*>(reinterpret_cast<char*>(mask_data) + offset) = 0;
}
} else if (version == GameVersion::PC) {
for (size_t offset = offsetof(S_JoinLobby_PC_65_67_68, entries) +
offsetof(S_JoinLobby_PC_65_67_68::Entry, disp.visual.name_color_checksum);
offset + 4 <= mask_size;
offset += sizeof(S_JoinLobby_PC_65_67_68::Entry)) {
*reinterpret_cast<uint32_t*>(reinterpret_cast<char*>(mask_data) + offset) = 0;
}
} else if (version == GameVersion::BB) {
for (size_t offset = offsetof(S_JoinLobby_BB_65_67_68, entries) +
offsetof(S_JoinLobby_BB_65_67_68::Entry, disp.visual.name_color_checksum);
offset + 4 <= mask_size;
offset += sizeof(S_JoinLobby_BB_65_67_68::Entry)) {
*reinterpret_cast<uint32_t*>(reinterpret_cast<char*>(mask_data) + offset) = 0;
}
} }
break; break;
case 0xE8: case 0xE8:
if (version == GameVersion::GC) { if (version == GameVersion::GC) {
auto& mask = check_size_t<S_JoinSpectatorTeam_GC_Ep3_E8>(mask_data, mask_size); auto& mask = check_size_t<S_JoinSpectatorTeam_GC_Ep3_E8>(mask_data, mask_size);
mask.rare_seed = 0; mask.rare_seed = 0;
for (size_t z = 0; z < 4; z++) {
mask.players[z].disp.visual.name_color_checksum = 0;
}
for (size_t z = 0; z < 8; z++) {
mask.spectator_players[z].disp.visual.name_color_checksum = 0;
}
} }
break; break;
case 0xB1: case 0xB1:
+5
View File
@@ -618,6 +618,7 @@ void send_complete_player_bb(shared_ptr<Client> c) {
SC_SyncCharacterSaveFile_BB_00E7 cmd; SC_SyncCharacterSaveFile_BB_00E7 cmd;
cmd.inventory = player->inventory; cmd.inventory = player->inventory;
cmd.disp = player->disp; cmd.disp = player->disp;
cmd.disp.visual.compute_name_color_checksum();
cmd.disp.play_time = 0; cmd.disp.play_time = 0;
cmd.unknown_a1 = 0; cmd.unknown_a1 = 0;
cmd.creation_timestamp = 0; cmd.creation_timestamp = 0;
@@ -1451,6 +1452,7 @@ static void send_join_spectator_team(shared_ptr<Client> c, shared_ptr<Lobby> l)
p.inventory.encode_mags(c->version()); p.inventory.encode_mags(c->version());
p.disp = wc_p->disp.to_dcpcv3(); p.disp = wc_p->disp.to_dcpcv3();
remove_language_marker_inplace(p.disp.visual.name); remove_language_marker_inplace(p.disp.visual.name);
p.disp.enforce_lobby_join_limits(c->version());
auto& e = cmd.entries[z]; auto& e = cmd.entries[z];
e.player_tag = 0x00010000; e.player_tag = 0x00010000;
@@ -1491,6 +1493,7 @@ static void send_join_spectator_team(shared_ptr<Client> c, shared_ptr<Lobby> l)
p.inventory.encode_mags(c->version()); p.inventory.encode_mags(c->version());
p.disp = entry.disp; p.disp = entry.disp;
remove_language_marker_inplace(p.disp.visual.name); remove_language_marker_inplace(p.disp.visual.name);
p.disp.enforce_lobby_join_limits(c->version());
auto& e = cmd.entries[client_id]; auto& e = cmd.entries[client_id];
e.player_tag = 0x00010000; e.player_tag = 0x00010000;
@@ -1522,6 +1525,7 @@ static void send_join_spectator_team(shared_ptr<Client> c, shared_ptr<Lobby> l)
cmd_p.inventory = other_p->inventory; cmd_p.inventory = other_p->inventory;
cmd_p.disp = other_p->disp.to_dcpcv3(); cmd_p.disp = other_p->disp.to_dcpcv3();
remove_language_marker_inplace(cmd_p.disp.visual.name); remove_language_marker_inplace(cmd_p.disp.visual.name);
cmd_p.disp.enforce_lobby_join_limits(c->version());
cmd_e.player_tag = 0x00010000; cmd_e.player_tag = 0x00010000;
cmd_e.guild_card_number = other_c->license->serial_number; cmd_e.guild_card_number = other_c->license->serial_number;
@@ -1628,6 +1632,7 @@ void send_join_game(shared_ptr<Client> c, shared_ptr<Lobby> l) {
cmd.players_ep3[x].inventory = other_p->inventory; cmd.players_ep3[x].inventory = other_p->inventory;
cmd.players_ep3[x].inventory.encode_mags(c->version()); cmd.players_ep3[x].inventory.encode_mags(c->version());
cmd.players_ep3[x].disp = convert_player_disp_data<PlayerDispDataDCPCV3>(other_p->disp); cmd.players_ep3[x].disp = convert_player_disp_data<PlayerDispDataDCPCV3>(other_p->disp);
cmd.players_ep3[x].disp.enforce_lobby_join_limits(c->version());
} }
} }
send_command_t(c, 0x64, player_count, cmd); send_command_t(c, 0x64, player_count, cmd);
+9
View File
@@ -793,3 +793,12 @@ const char* name_for_area(Episode episode, uint8_t area) {
throw logic_error("invalid episode for drop area"); throw logic_error("invalid episode for drop area");
} }
} }
uint32_t class_flags_for_class(uint8_t char_class) {
static constexpr uint8_t flags[12] = {
0x25, 0x2A, 0x31, 0x45, 0x51, 0x52, 0x86, 0x89, 0x8A, 0x32, 0x85, 0x46};
if (char_class >= 12) {
throw runtime_error("invalid character class");
}
return flags[char_class];
}
+2
View File
@@ -84,3 +84,5 @@ extern const std::unordered_map<std::string, uint8_t> mag_color_for_name;
size_t area_limit_for_episode(Episode ep); size_t area_limit_for_episode(Episode ep);
uint8_t area_for_name(const std::string& name); uint8_t area_for_name(const std::string& name);
const char* name_for_area(Episode episode, uint8_t area); const char* name_for_area(Episode episode, uint8_t area);
uint32_t class_flags_for_class(uint8_t char_class);