split disp data into stats and visual substructures

This commit is contained in:
Martin Michelsen
2023-06-26 21:25:24 -07:00
parent e1b4bd32c9
commit 8656222be3
14 changed files with 237 additions and 427 deletions
+16 -16
View File
@@ -735,51 +735,51 @@ static void server_command_edit(shared_ptr<ServerState> s, shared_ptr<Lobby> l,
try { try {
if (tokens.at(0) == "atp") { if (tokens.at(0) == "atp") {
c->game_data.player()->disp.stats.atp = stoul(tokens.at(1)); c->game_data.player()->disp.stats.char_stats.atp = stoul(tokens.at(1));
} else if (tokens.at(0) == "mst") { } else if (tokens.at(0) == "mst") {
c->game_data.player()->disp.stats.mst = stoul(tokens.at(1)); c->game_data.player()->disp.stats.char_stats.mst = stoul(tokens.at(1));
} else if (tokens.at(0) == "evp") { } else if (tokens.at(0) == "evp") {
c->game_data.player()->disp.stats.evp = stoul(tokens.at(1)); c->game_data.player()->disp.stats.char_stats.evp = stoul(tokens.at(1));
} else if (tokens.at(0) == "hp") { } else if (tokens.at(0) == "hp") {
c->game_data.player()->disp.stats.hp = stoul(tokens.at(1)); c->game_data.player()->disp.stats.char_stats.hp = stoul(tokens.at(1));
} else if (tokens.at(0) == "dfp") { } else if (tokens.at(0) == "dfp") {
c->game_data.player()->disp.stats.dfp = stoul(tokens.at(1)); c->game_data.player()->disp.stats.char_stats.dfp = stoul(tokens.at(1));
} else if (tokens.at(0) == "ata") { } else if (tokens.at(0) == "ata") {
c->game_data.player()->disp.stats.ata = stoul(tokens.at(1)); c->game_data.player()->disp.stats.char_stats.ata = stoul(tokens.at(1));
} else if (tokens.at(0) == "lck") { } else if (tokens.at(0) == "lck") {
c->game_data.player()->disp.stats.lck = stoul(tokens.at(1)); c->game_data.player()->disp.stats.char_stats.lck = stoul(tokens.at(1));
} else if (tokens.at(0) == "meseta") { } else if (tokens.at(0) == "meseta") {
c->game_data.player()->disp.meseta = stoul(tokens.at(1)); c->game_data.player()->disp.stats.meseta = stoul(tokens.at(1));
} else if (tokens.at(0) == "exp") { } else if (tokens.at(0) == "exp") {
c->game_data.player()->disp.experience = stoul(tokens.at(1)); c->game_data.player()->disp.stats.experience = stoul(tokens.at(1));
} else if (tokens.at(0) == "level") { } else if (tokens.at(0) == "level") {
c->game_data.player()->disp.level = stoul(tokens.at(1)) - 1; c->game_data.player()->disp.stats.level = stoul(tokens.at(1)) - 1;
} else if (tokens.at(0) == "namecolor") { } else if (tokens.at(0) == "namecolor") {
uint32_t new_color; uint32_t new_color;
sscanf(tokens.at(1).c_str(), "%8X", &new_color); sscanf(tokens.at(1).c_str(), "%8X", &new_color);
c->game_data.player()->disp.name_color = new_color; c->game_data.player()->disp.visual.name_color = new_color;
} else if (tokens.at(0) == "secid") { } else if (tokens.at(0) == "secid") {
uint8_t secid = section_id_for_name(decode_sjis(tokens.at(1))); uint8_t secid = section_id_for_name(decode_sjis(tokens.at(1)));
if (secid == 0xFF) { if (secid == 0xFF) {
send_text_message(c, u"$C6No such section ID"); send_text_message(c, u"$C6No such section ID");
return; return;
} else { } else {
c->game_data.player()->disp.section_id = secid; c->game_data.player()->disp.visual.section_id = secid;
} }
} else if (tokens.at(0) == "name") { } else if (tokens.at(0) == "name") {
c->game_data.player()->disp.name = add_language_marker(tokens.at(1), 'J'); c->game_data.player()->disp.name = add_language_marker(tokens.at(1), 'J');
} else if (tokens.at(0) == "npc") { } else if (tokens.at(0) == "npc") {
if (tokens.at(1) == "none") { if (tokens.at(1) == "none") {
c->game_data.player()->disp.extra_model = 0; c->game_data.player()->disp.visual.extra_model = 0;
c->game_data.player()->disp.v2_flags &= 0xFD; c->game_data.player()->disp.visual.v2_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) {
send_text_message(c, u"$C6No such NPC"); send_text_message(c, u"$C6No such NPC");
return; return;
} }
c->game_data.player()->disp.extra_model = npc; c->game_data.player()->disp.visual.extra_model = npc;
c->game_data.player()->disp.v2_flags |= 0x02; c->game_data.player()->disp.visual.v2_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 -31
View File
@@ -4444,36 +4444,8 @@ struct G_SyncPlayerDispAndInventory_V3_6x70 {
/* 00B8 */ le_uint32_t unknown_a10; /* 00B8 */ le_uint32_t unknown_a10;
/* 00BC */ le_uint32_t unknown_a11; /* 00BC */ le_uint32_t unknown_a11;
/* 00C0 */ parray<uint8_t, 0x14> technique_levels; // Last byte is uninitialized /* 00C0 */ parray<uint8_t, 0x14> technique_levels; // Last byte is uninitialized
/* 00D4 */ struct { /* 00D4 */ PlayerVisualConfig visual;
parray<uint8_t, 0x10> name; /* 0124 */ PlayerStats stats;
uint64_t unknown_a2; // Same as unknown_a2 in PlayerDispDataDCPCV3, presumably
le_uint32_t name_color;
uint8_t extra_model;
parray<uint8_t, 0x0F> unused;
le_uint32_t name_color_checksum;
uint8_t section_id;
uint8_t char_class;
uint8_t v2_flags;
uint8_t version;
le_uint32_t v1_flags;
le_uint16_t costume;
le_uint16_t skin;
le_uint16_t face;
le_uint16_t head;
le_uint16_t hair;
le_uint16_t hair_r;
le_uint16_t hair_g;
le_uint16_t hair_b;
le_uint32_t proportion_x;
le_uint32_t proportion_y;
} __packed__ disp_part2;
/* 0124 */ struct {
PlayerStats stats;
parray<uint8_t, 0x0A> unknown_a1;
le_uint32_t level;
le_uint32_t experience;
le_uint32_t meseta;
} __packed__ disp_part1;
/* 0148 */ struct { /* 0148 */ struct {
le_uint32_t num_items; le_uint32_t num_items;
// Entries >= num_items in this array contain uninitialized data (usually // Entries >= num_items in this array contain uninitialized data (usually
@@ -6110,7 +6082,7 @@ struct G_CardCountsRequest_GC_Ep3_6xB5x38 {
struct G_UpdateAllPlayerStatistics_GC_Ep3_6xB4x39 { struct G_UpdateAllPlayerStatistics_GC_Ep3_6xB4x39 {
G_CardBattleCommandHeader header = {0xB4, sizeof(G_UpdateAllPlayerStatistics_GC_Ep3_6xB4x39) / 4, 0, 0x39, 0, 0, 0}; G_CardBattleCommandHeader header = {0xB4, sizeof(G_UpdateAllPlayerStatistics_GC_Ep3_6xB4x39) / 4, 0, 0x39, 0, 0, 0};
parray<Episode3::PlayerStats, 4> stats; parray<Episode3::PlayerBattleStats, 4> stats;
} __packed__; } __packed__;
// 6xB3x3A / CAx3A: Unknown // 6xB3x3A / CAx3A: Unknown
+1 -1
View File
@@ -182,7 +182,7 @@ public:
uint32_t num_destroyed_fcs; uint32_t num_destroyed_fcs;
uint8_t unknown_a16; uint8_t unknown_a16;
uint8_t unknown_a17; uint8_t unknown_a17;
PlayerStats stats; PlayerBattleStats stats;
}; };
} // namespace Episode3 } // namespace Episode3
+7 -7
View File
@@ -688,11 +688,11 @@ void HandAndEquipState::clear_FF() {
this->unused2.clear(0xFF); this->unused2.clear(0xFF);
} }
PlayerStats::PlayerStats() { PlayerBattleStats::PlayerBattleStats() {
this->clear(); this->clear();
} }
void PlayerStats::clear() { void PlayerBattleStats::clear() {
this->damage_given = 0; this->damage_given = 0;
this->damage_taken = 0; this->damage_taken = 0;
this->num_opponent_cards_destroyed = 0; this->num_opponent_cards_destroyed = 0;
@@ -715,7 +715,7 @@ void PlayerStats::clear() {
this->unused = 0; this->unused = 0;
} }
float PlayerStats::score(size_t num_rounds) const { float PlayerBattleStats::score(size_t num_rounds) const {
// Note: This formula doesn't match the formula on PSO-World, which is: // Note: This formula doesn't match the formula on PSO-World, which is:
// 35 // 35
// + (Attack Damage - Damage Taken) // + (Attack Damage - Damage Taken)
@@ -730,11 +730,11 @@ float PlayerStats::score(size_t num_rounds) const {
return 38.0f + 0.8f * this->action_card_negated_damage - 2.3f * num_rounds - 1.8f * this->sc_damage_taken + 3.0f * this->max_attack_combo_size + (this->damage_given - this->damage_taken); return 38.0f + 0.8f * this->action_card_negated_damage - 2.3f * num_rounds - 1.8f * this->sc_damage_taken + 3.0f * this->max_attack_combo_size + (this->damage_given - this->damage_taken);
} }
uint8_t PlayerStats::rank(size_t num_rounds) const { uint8_t PlayerBattleStats::rank(size_t num_rounds) const {
return this->rank_for_score(this->score(num_rounds)); return this->rank_for_score(this->score(num_rounds));
} }
const char* PlayerStats::rank_name(size_t num_rounds) const { const char* PlayerBattleStats::rank_name(size_t num_rounds) const {
return this->name_for_rank(this->rank_for_score(this->score(num_rounds))); return this->name_for_rank(this->rank_for_score(this->score(num_rounds)));
} }
@@ -744,7 +744,7 @@ static const float RANK_THRESHOLDS[RANK_THRESHOLD_COUNT] = {
static const char* RANK_NAMES[RANK_THRESHOLD_COUNT + 1] = { static const char* RANK_NAMES[RANK_THRESHOLD_COUNT + 1] = {
"E", "D", "D+", "C", "C+", "B", "B+", "A", "A+", "S"}; "E", "D", "D+", "C", "C+", "B", "B+", "A", "A+", "S"};
uint8_t PlayerStats::rank_for_score(float score) { uint8_t PlayerBattleStats::rank_for_score(float score) {
size_t rank = 0; size_t rank = 0;
while (rank < RANK_THRESHOLD_COUNT && RANK_THRESHOLDS[rank] <= score) { while (rank < RANK_THRESHOLD_COUNT && RANK_THRESHOLDS[rank] <= score) {
rank++; rank++;
@@ -752,7 +752,7 @@ uint8_t PlayerStats::rank_for_score(float score) {
return rank; return rank;
} }
const char* PlayerStats::name_for_rank(uint8_t rank) { const char* PlayerBattleStats::name_for_rank(uint8_t rank) {
if (rank >= RANK_THRESHOLD_COUNT + 1) { if (rank >= RANK_THRESHOLD_COUNT + 1) {
throw invalid_argument("invalid rank"); throw invalid_argument("invalid rank");
} }
+2 -2
View File
@@ -234,7 +234,7 @@ struct HandAndEquipState {
std::string str() const; std::string str() const;
} __attribute__((packed)); } __attribute__((packed));
struct PlayerStats { struct PlayerBattleStats {
le_uint16_t damage_given; le_uint16_t damage_given;
le_uint16_t damage_taken; le_uint16_t damage_taken;
le_uint16_t num_opponent_cards_destroyed; le_uint16_t num_opponent_cards_destroyed;
@@ -256,7 +256,7 @@ struct PlayerStats {
le_uint16_t action_card_negated_damage; le_uint16_t action_card_negated_damage;
le_uint16_t unused; le_uint16_t unused;
PlayerStats(); PlayerBattleStats();
void clear(); void clear();
float score(size_t num_rounds) const; float score(size_t num_rounds) const;
+28 -28
View File
@@ -22,7 +22,7 @@ void player_use_item(shared_ptr<ServerState> s, shared_ptr<Client> c, size_t ite
// Nothing to do (it should be deleted) // Nothing to do (it should be deleted)
} else if (item_identifier == 0x030200) { // Technique disk } else if (item_identifier == 0x030200) { // Technique disk
uint8_t max_level = s->item_parameter_table->get_max_tech_level(player->disp.char_class, item.data.data1[4]); uint8_t max_level = s->item_parameter_table->get_max_tech_level(player->disp.visual.char_class, item.data.data1[4]);
if (item.data.data1[2] > max_level) { if (item.data.data1[2] > max_level) {
throw runtime_error("technique level too high"); throw runtime_error("technique level too high");
} }
@@ -43,13 +43,13 @@ void player_use_item(shared_ptr<ServerState> s, shared_ptr<Client> c, size_t ite
} else if ((item_identifier & 0xFFFF00) == 0x030B00) { // Material } else if ((item_identifier & 0xFFFF00) == 0x030B00) { // Material
switch (item.data.data1[2]) { switch (item.data.data1[2]) {
case 0: // Power Material case 0: // Power Material
c->game_data.player()->disp.stats.atp += 2; c->game_data.player()->disp.stats.char_stats.atp += 2;
break; break;
case 1: // Mind Material case 1: // Mind Material
c->game_data.player()->disp.stats.mst += 2; c->game_data.player()->disp.stats.char_stats.mst += 2;
break; break;
case 2: // Evade Material case 2: // Evade Material
c->game_data.player()->disp.stats.evp += 2; c->game_data.player()->disp.stats.char_stats.evp += 2;
break; break;
case 3: // HP Material case 3: // HP Material
c->game_data.player()->inventory.hp_materials_used += 2; c->game_data.player()->inventory.hp_materials_used += 2;
@@ -58,10 +58,10 @@ void player_use_item(shared_ptr<ServerState> s, shared_ptr<Client> c, size_t ite
c->game_data.player()->inventory.tp_materials_used += 2; c->game_data.player()->inventory.tp_materials_used += 2;
break; break;
case 5: // Def Material case 5: // Def Material
c->game_data.player()->disp.stats.dfp += 2; c->game_data.player()->disp.stats.char_stats.dfp += 2;
break; break;
case 6: // Luck Material case 6: // Luck Material
c->game_data.player()->disp.stats.lck += 2; c->game_data.player()->disp.stats.char_stats.lck += 2;
break; break;
default: default:
throw invalid_argument("unknown material used"); throw invalid_argument("unknown material used");
@@ -102,12 +102,12 @@ void player_use_item(shared_ptr<ServerState> s, shared_ptr<Client> c, size_t ite
} else if (item_identifier == 0x030C00) { } else if (item_identifier == 0x030C00) {
// Cell of MAG 502 // Cell of MAG 502
auto& mag = player->inventory.items[player->inventory.find_equipped_mag()]; auto& mag = player->inventory.items[player->inventory.find_equipped_mag()];
mag.data.data1[1] = (player->disp.section_id & 1) ? 0x1D : 0x21; mag.data.data1[1] = (player->disp.visual.section_id & 1) ? 0x1D : 0x21;
} else if (item_identifier == 0x030C01) { } else if (item_identifier == 0x030C01) {
// Cell of MAG 213 // Cell of MAG 213
auto& mag = player->inventory.items[player->inventory.find_equipped_mag()]; auto& mag = player->inventory.items[player->inventory.find_equipped_mag()];
mag.data.data1[1] = (player->disp.section_id & 1) ? 0x27 : 0x22; mag.data.data1[1] = (player->disp.visual.section_id & 1) ? 0x27 : 0x22;
} else if (item_identifier == 0x030C02) { } else if (item_identifier == 0x030C02) {
// Parts of RoboChao // Parts of RoboChao
@@ -169,7 +169,7 @@ void player_use_item(shared_ptr<ServerState> s, shared_ptr<Client> c, size_t ite
try { try {
const auto& combo = s->item_parameter_table->get_item_combination( const auto& combo = s->item_parameter_table->get_item_combination(
item.data, inv_item.data); item.data, inv_item.data);
if (combo.char_class != 0xFF && combo.char_class != player->disp.char_class) { if (combo.char_class != 0xFF && combo.char_class != player->disp.visual.char_class) {
throw runtime_error("item combination requires specific char_class"); throw runtime_error("item combination requires specific char_class");
} }
if (combo.mag_level != 0xFF) { if (combo.mag_level != 0xFF) {
@@ -188,7 +188,7 @@ void player_use_item(shared_ptr<ServerState> s, shared_ptr<Client> c, size_t ite
throw runtime_error("item combination applies with grind requirement, but equipped weapon grind is too low"); throw runtime_error("item combination applies with grind requirement, but equipped weapon grind is too low");
} }
} }
if (combo.level != 0xFF && player->disp.level + 1 < combo.level) { if (combo.level != 0xFF && player->disp.stats.level + 1 < combo.level) {
throw runtime_error("item combination applies with level requirement, but player level is too low"); throw runtime_error("item combination applies with level requirement, but player level is too low");
} }
// If we get here, then the combo applies // If we get here, then the combo applies
@@ -275,7 +275,7 @@ void player_feed_mag(std::shared_ptr<ServerState> s, std::shared_ptr<Client> c,
} else if (mag_level < 35) { // Level 10 evolution } else if (mag_level < 35) { // Level 10 evolution
if (evolution_number < 1) { if (evolution_number < 1) {
switch (player->disp.char_class) { switch (player->disp.visual.char_class) {
case 0: // HUmar case 0: // HUmar
case 1: // HUnewearl case 1: // HUnewearl
case 2: // HUcast case 2: // HUcast
@@ -333,17 +333,17 @@ void player_feed_mag(std::shared_ptr<ServerState> s, std::shared_ptr<Client> c,
if (evolution_number < 4) { if (evolution_number < 4) {
if (mag_level >= 100) { if (mag_level >= 100) {
uint8_t section_id_group = player->disp.section_id % 3; uint8_t section_id_group = player->disp.visual.section_id % 3;
uint16_t def = mag_item.data.data1w[2] / 100; uint16_t def = mag_item.data.data1w[2] / 100;
uint16_t pow = mag_item.data.data1w[3] / 100; uint16_t pow = mag_item.data.data1w[3] / 100;
uint16_t dex = mag_item.data.data1w[4] / 100; uint16_t dex = mag_item.data.data1w[4] / 100;
uint16_t mind = mag_item.data.data1w[5] / 100; uint16_t mind = mag_item.data.data1w[5] / 100;
bool is_male = char_class_is_male(player->disp.char_class); bool is_male = char_class_is_male(player->disp.visual.char_class);
size_t table_index = (is_male ? 0 : 1) + section_id_group * 2; size_t table_index = (is_male ? 0 : 1) + section_id_group * 2;
bool is_hunter = char_class_is_hunter(player->disp.char_class); bool is_hunter = char_class_is_hunter(player->disp.visual.char_class);
bool is_ranger = char_class_is_ranger(player->disp.char_class); bool is_ranger = char_class_is_ranger(player->disp.visual.char_class);
bool is_force = char_class_is_force(player->disp.char_class); bool is_force = char_class_is_force(player->disp.visual.char_class);
if (is_force) { if (is_force) {
table_index += 12; table_index += 12;
} else if (is_ranger) { } else if (is_ranger) {
@@ -378,45 +378,45 @@ void player_feed_mag(std::shared_ptr<ServerState> s, std::shared_ptr<Client> c,
uint16_t dex = mag_item.data.data1w[4] / 100; uint16_t dex = mag_item.data.data1w[4] / 100;
uint16_t mind = mag_item.data.data1w[5] / 100; uint16_t mind = mag_item.data.data1w[5] / 100;
bool is_hunter = char_class_is_hunter(player->disp.char_class); bool is_hunter = char_class_is_hunter(player->disp.visual.char_class);
bool is_ranger = char_class_is_ranger(player->disp.char_class); bool is_ranger = char_class_is_ranger(player->disp.visual.char_class);
bool is_force = char_class_is_force(player->disp.char_class); bool is_force = char_class_is_force(player->disp.visual.char_class);
if (is_hunter + is_ranger + is_force != 1) { if (is_hunter + is_ranger + is_force != 1) {
throw logic_error("char class is not exactly one of the top-level classes"); throw logic_error("char class is not exactly one of the top-level classes");
} }
if (is_hunter) { if (is_hunter) {
if (flags & 0x108) { if (flags & 0x108) {
mag_item.data.data1[1] = (player->disp.section_id & 1) mag_item.data.data1[1] = (player->disp.visual.section_id & 1)
? ((dex < mind) ? 0x08 : 0x06) ? ((dex < mind) ? 0x08 : 0x06)
: ((dex < mind) ? 0x0C : 0x05); : ((dex < mind) ? 0x0C : 0x05);
} else if (flags & 0x010) { } else if (flags & 0x010) {
mag_item.data.data1[1] = (player->disp.section_id & 1) mag_item.data.data1[1] = (player->disp.visual.section_id & 1)
? ((mind < pow) ? 0x12 : 0x10) ? ((mind < pow) ? 0x12 : 0x10)
: ((mind < pow) ? 0x17 : 0x13); : ((mind < pow) ? 0x17 : 0x13);
} else if (flags & 0x020) { } else if (flags & 0x020) {
mag_item.data.data1[1] = (player->disp.section_id & 1) mag_item.data.data1[1] = (player->disp.visual.section_id & 1)
? ((pow < dex) ? 0x16 : 0x24) ? ((pow < dex) ? 0x16 : 0x24)
: ((pow < dex) ? 0x07 : 0x1E); : ((pow < dex) ? 0x07 : 0x1E);
} }
} else if (is_ranger) { } else if (is_ranger) {
if (flags & 0x110) { if (flags & 0x110) {
mag_item.data.data1[1] = (player->disp.section_id & 1) mag_item.data.data1[1] = (player->disp.visual.section_id & 1)
? ((mind < pow) ? 0x0A : 0x05) ? ((mind < pow) ? 0x0A : 0x05)
: ((mind < pow) ? 0x0C : 0x06); : ((mind < pow) ? 0x0C : 0x06);
} else if (flags & 0x008) { } else if (flags & 0x008) {
mag_item.data.data1[1] = (player->disp.section_id & 1) mag_item.data.data1[1] = (player->disp.visual.section_id & 1)
? ((dex < mind) ? 0x0A : 0x26) ? ((dex < mind) ? 0x0A : 0x26)
: ((dex < mind) ? 0x0C : 0x06); : ((dex < mind) ? 0x0C : 0x06);
} else if (flags & 0x020) { } else if (flags & 0x020) {
mag_item.data.data1[1] = (player->disp.section_id & 1) mag_item.data.data1[1] = (player->disp.visual.section_id & 1)
? ((pow < dex) ? 0x18 : 0x1E) ? ((pow < dex) ? 0x18 : 0x1E)
: ((pow < dex) ? 0x08 : 0x05); : ((pow < dex) ? 0x08 : 0x05);
} }
} else if (is_force) { } else if (is_force) {
if (flags & 0x120) { if (flags & 0x120) {
if (def < 45) { if (def < 45) {
mag_item.data.data1[1] = (player->disp.section_id & 1) mag_item.data.data1[1] = (player->disp.visual.section_id & 1)
? ((pow < dex) ? 0x17 : 0x09) ? ((pow < dex) ? 0x17 : 0x09)
: ((pow < dex) ? 0x1E : 0x1C); : ((pow < dex) ? 0x1E : 0x1C);
} else { } else {
@@ -424,7 +424,7 @@ void player_feed_mag(std::shared_ptr<ServerState> s, std::shared_ptr<Client> c,
} }
} else if (flags & 0x008) { } else if (flags & 0x008) {
if (def < 45) { if (def < 45) {
mag_item.data.data1[1] = (player->disp.section_id & 1) mag_item.data.data1[1] = (player->disp.visual.section_id & 1)
? ((dex < mind) ? 0x1C : 0x20) ? ((dex < mind) ? 0x1C : 0x20)
: ((dex < mind) ? 0x1F : 0x25); : ((dex < mind) ? 0x1F : 0x25);
} else { } else {
@@ -432,7 +432,7 @@ void player_feed_mag(std::shared_ptr<ServerState> s, std::shared_ptr<Client> c,
} }
} else if (flags & 0x010) { } else if (flags & 0x010) {
if (def < 45) { if (def < 45) {
mag_item.data.data1[1] = (player->disp.section_id & 1) mag_item.data.data1[1] = (player->disp.visual.section_id & 1)
? ((mind < pow) ? 0x12 : 0x0C) ? ((mind < pow) ? 0x12 : 0x0C)
: ((mind < pow) ? 0x15 : 0x11); : ((mind < pow) ? 0x15 : 0x11);
} else { } else {
+2 -2
View File
@@ -21,7 +21,7 @@ LevelTable::LevelTable(shared_ptr<const string> data, bool compressed) {
this->table = reinterpret_cast<const Table*>(this->data->data()); this->table = reinterpret_cast<const Table*>(this->data->data());
} }
const PlayerStats& LevelTable::base_stats_for_class(uint8_t char_class) const { const CharacterStats& LevelTable::base_stats_for_class(uint8_t char_class) const {
if (char_class >= 12) { if (char_class >= 12) {
throw out_of_range("invalid character class"); throw out_of_range("invalid character class");
} }
@@ -39,7 +39,7 @@ const LevelTable::LevelStats& LevelTable::stats_for_level(
return this->table->levels[char_class][level]; return this->table->levels[char_class][level];
} }
void LevelTable::LevelStats::apply(PlayerStats& ps) const { void LevelTable::LevelStats::apply(CharacterStats& ps) const {
ps.ata += this->ata; ps.ata += this->ata;
ps.atp += this->atp; ps.atp += this->atp;
ps.dfp += this->dfp; ps.dfp += this->dfp;
+5 -5
View File
@@ -6,7 +6,7 @@
#include <phosg/Encoding.hh> #include <phosg/Encoding.hh>
#include <string> #include <string>
struct PlayerStats { struct CharacterStats {
le_uint16_t atp; le_uint16_t atp;
le_uint16_t mst; le_uint16_t mst;
le_uint16_t evp; le_uint16_t evp;
@@ -15,7 +15,7 @@ struct PlayerStats {
le_uint16_t ata; le_uint16_t ata;
le_uint16_t lck; le_uint16_t lck;
PlayerStats() noexcept; CharacterStats() noexcept;
} __attribute__((packed)); } __attribute__((packed));
class LevelTable { // from PlyLevelTbl.prs class LevelTable { // from PlyLevelTbl.prs
@@ -31,18 +31,18 @@ public:
uint8_t tp; uint8_t tp;
le_uint32_t experience; le_uint32_t experience;
void apply(PlayerStats& ps) const; void apply(CharacterStats& ps) const;
} __attribute__((packed)); } __attribute__((packed));
struct Table { struct Table {
PlayerStats base_stats[12]; CharacterStats base_stats[12];
le_uint32_t unknown[12]; le_uint32_t unknown[12];
LevelStats levels[12][200]; LevelStats levels[12][200];
} __attribute__((packed)); } __attribute__((packed));
LevelTable(std::shared_ptr<const std::string> data, bool compressed); LevelTable(std::shared_ptr<const std::string> data, bool compressed);
const PlayerStats& base_stats_for_class(uint8_t char_class) const; const CharacterStats& base_stats_for_class(uint8_t char_class) const;
const LevelStats& stats_for_level(uint8_t char_class, uint8_t level) const; const LevelStats& stats_for_level(uint8_t char_class, uint8_t level) const;
private: private:
+60 -195
View File
@@ -27,7 +27,7 @@ static const string ACCOUNT_FILE_SIGNATURE =
static FileContentsCache player_files_cache(300 * 1000 * 1000); static FileContentsCache player_files_cache(300 * 1000 * 1000);
PlayerStats::PlayerStats() noexcept CharacterStats::CharacterStats() noexcept
: atp(0), : atp(0),
mst(0), mst(0),
evp(0), evp(0),
@@ -36,11 +36,13 @@ PlayerStats::PlayerStats() noexcept
ata(0), ata(0),
lck(0) {} lck(0) {}
PlayerDispDataDCPCV3::PlayerDispDataDCPCV3() noexcept PlayerStats::PlayerStats() noexcept
: level(0), : level(0),
experience(0), experience(0),
meseta(0), meseta(0) {}
unknown_a2(0),
PlayerVisualConfig::PlayerVisualConfig() noexcept
: unknown_a2(0),
name_color(0), name_color(0),
extra_model(0), extra_model(0),
name_color_checksum(0), name_color_checksum(0),
@@ -62,121 +64,43 @@ PlayerDispDataDCPCV3::PlayerDispDataDCPCV3() noexcept
void PlayerDispDataDCPCV3::enforce_v2_limits() { void PlayerDispDataDCPCV3::enforce_v2_limits() {
// V1/V2 have fewer classes, so we'll substitute some here // V1/V2 have fewer classes, so we'll substitute some here
if (this->char_class == 11) { if (this->visual.char_class == 11) {
this->char_class = 0; // FOmar -> HUmar this->visual.char_class = 0; // FOmar -> HUmar
} else if (this->char_class == 10) { } else if (this->visual.char_class == 10) {
this->char_class = 1; // RAmarl -> HUnewearl this->visual.char_class = 1; // RAmarl -> HUnewearl
} else if (this->char_class == 9) { } else if (this->visual.char_class == 9) {
this->char_class = 5; // HUcaseal -> RAcaseal this->visual.char_class = 5; // HUcaseal -> RAcaseal
} }
// If the player is somehow still not a valid class, make them appear as the // If the player is somehow still not a valid class, make them appear as the
// "ninja" NPC // "ninja" NPC
if (this->char_class > 8) { if (this->visual.char_class > 8) {
this->extra_model = 0; this->visual.extra_model = 0;
this->v2_flags |= 2; this->visual.v2_flags |= 2;
} }
this->version = 2; this->visual.version = 2;
} }
PlayerDispDataBB PlayerDispDataDCPCV3::to_bb() const { PlayerDispDataBB PlayerDispDataDCPCV3::to_bb() const {
PlayerDispDataBB bb; PlayerDispDataBB bb;
bb.stats.atp = this->stats.atp; bb.stats = this->stats;
bb.stats.mst = this->stats.mst; bb.visual = this->visual;
bb.stats.evp = this->stats.evp; bb.visual.name = " 0";
bb.stats.hp = this->stats.hp; bb.name = add_language_marker(this->visual.name, 'J');
bb.stats.dfp = this->stats.dfp;
bb.stats.ata = this->stats.ata;
bb.stats.lck = this->stats.lck;
bb.unknown_a1 = this->unknown_a1;
bb.level = this->level;
bb.experience = this->experience;
bb.meseta = this->meseta;
bb.guild_card = " 0";
bb.unknown_a2 = this->unknown_a2;
bb.name_color = this->name_color;
bb.extra_model = this->extra_model;
bb.unused = this->unused;
bb.name_color_checksum = this->name_color_checksum;
bb.section_id = this->section_id;
bb.char_class = this->char_class;
bb.v2_flags = this->v2_flags;
bb.version = this->version;
bb.v1_flags = this->v1_flags;
bb.costume = this->costume;
bb.skin = this->skin;
bb.face = this->face;
bb.head = this->head;
bb.hair = this->hair;
bb.hair_r = this->hair_r;
bb.hair_g = this->hair_g;
bb.hair_b = this->hair_b;
bb.proportion_x = this->proportion_x;
bb.proportion_y = this->proportion_y;
bb.name = add_language_marker(this->name, 'J');
bb.config = this->config; bb.config = this->config;
bb.technique_levels = this->technique_levels; bb.technique_levels = this->technique_levels;
return bb; return bb;
} }
PlayerDispDataBB::PlayerDispDataBB() noexcept PlayerDispDataBB::PlayerDispDataBB() noexcept
: level(0), : play_time(0),
experience(0), unknown_a3(0) {}
meseta(0),
unknown_a2(0),
name_color(0),
extra_model(0),
name_color_checksum(0),
section_id(0),
char_class(0),
v2_flags(0),
version(0),
v1_flags(0),
costume(0),
skin(0),
face(0),
head(0),
hair(0),
hair_r(0),
hair_g(0),
hair_b(0),
proportion_x(0),
proportion_y(0) {}
PlayerDispDataDCPCV3 PlayerDispDataBB::to_dcpcv3() const { PlayerDispDataDCPCV3 PlayerDispDataBB::to_dcpcv3() const {
PlayerDispDataDCPCV3 ret; PlayerDispDataDCPCV3 ret;
ret.stats.atp = this->stats.atp; ret.stats = this->stats;
ret.stats.mst = this->stats.mst; ret.visual = this->visual;
ret.stats.evp = this->stats.evp; ret.visual.name = remove_language_marker(this->name);
ret.stats.hp = this->stats.hp;
ret.stats.dfp = this->stats.dfp;
ret.stats.ata = this->stats.ata;
ret.stats.lck = this->stats.lck;
ret.unknown_a1 = this->unknown_a1;
ret.level = this->level;
ret.experience = this->experience;
ret.meseta = this->meseta;
ret.unknown_a2 = this->unknown_a2;
ret.name_color = this->name_color;
ret.extra_model = this->extra_model;
ret.unused = this->unused;
ret.name_color_checksum = this->name_color_checksum;
ret.section_id = this->section_id;
ret.char_class = this->char_class;
ret.v2_flags = this->v2_flags;
ret.version = this->version;
ret.v1_flags = this->v1_flags;
ret.costume = this->costume;
ret.skin = this->skin;
ret.face = this->face;
ret.head = this->head;
ret.hair = this->hair;
ret.hair_r = this->hair_r;
ret.hair_g = this->hair_g;
ret.hair_b = this->hair_b;
ret.proportion_x = this->proportion_x;
ret.proportion_y = this->proportion_y;
ret.name = remove_language_marker(this->name);
ret.config = this->config; ret.config = this->config;
ret.technique_levels = this->technique_levels; ret.technique_levels = this->technique_levels;
return ret; return ret;
@@ -184,105 +108,46 @@ PlayerDispDataDCPCV3 PlayerDispDataBB::to_dcpcv3() const {
PlayerDispDataBBPreview PlayerDispDataBB::to_preview() const { PlayerDispDataBBPreview PlayerDispDataBB::to_preview() const {
PlayerDispDataBBPreview pre; PlayerDispDataBBPreview pre;
pre.level = this->level; pre.level = this->stats.level;
pre.experience = this->experience; pre.experience = this->stats.experience;
pre.guild_card = this->guild_card; pre.visual = this->visual;
pre.unknown_a2 = this->unknown_a2;
pre.name_color = this->name_color;
pre.extra_model = this->extra_model;
pre.unused = this->unused;
pre.name_color_checksum = this->name_color_checksum;
pre.section_id = this->section_id;
pre.char_class = this->char_class;
pre.v2_flags = this->v2_flags;
pre.version = this->version;
pre.v1_flags = this->v1_flags;
pre.costume = this->costume;
pre.skin = this->skin;
pre.face = this->face;
pre.head = this->head;
pre.hair = this->hair;
pre.hair_r = this->hair_r;
pre.hair_g = this->hair_g;
pre.hair_b = this->hair_b;
pre.proportion_x = this->proportion_x;
pre.proportion_y = this->proportion_y;
pre.name = this->name; pre.name = this->name;
pre.play_time = this->play_time; pre.play_time = this->play_time;
return pre; return pre;
} }
void PlayerDispDataBB::apply_preview(const PlayerDispDataBBPreview& pre) { void PlayerDispDataBB::apply_preview(const PlayerDispDataBBPreview& pre) {
this->level = pre.level; this->stats.level = pre.level;
this->experience = pre.experience; this->stats.experience = pre.experience;
this->guild_card = pre.guild_card; this->visual = pre.visual;
this->unknown_a2 = pre.unknown_a2;
this->name_color = pre.name_color;
this->extra_model = pre.extra_model;
this->unused = pre.unused;
this->name_color_checksum = pre.name_color_checksum;
this->section_id = pre.section_id;
this->char_class = pre.char_class;
this->v2_flags = pre.v2_flags;
this->version = pre.version;
this->v1_flags = pre.v1_flags;
this->costume = pre.costume;
this->skin = pre.skin;
this->face = pre.face;
this->head = pre.head;
this->hair = pre.hair;
this->hair_r = pre.hair_r;
this->hair_g = pre.hair_g;
this->hair_b = pre.hair_b;
this->proportion_x = pre.proportion_x;
this->proportion_y = pre.proportion_y;
this->name = pre.name; this->name = pre.name;
} }
void PlayerDispDataBB::apply_dressing_room(const PlayerDispDataBBPreview& pre) { void PlayerDispDataBB::apply_dressing_room(const PlayerDispDataBBPreview& pre) {
this->name_color = pre.name_color; this->visual.name_color = pre.visual.name_color;
this->extra_model = pre.extra_model; this->visual.extra_model = pre.visual.extra_model;
this->name_color_checksum = pre.name_color_checksum; this->visual.name_color_checksum = pre.visual.name_color_checksum;
this->section_id = pre.section_id; this->visual.section_id = pre.visual.section_id;
this->char_class = pre.char_class; this->visual.char_class = pre.visual.char_class;
this->v2_flags = pre.v2_flags; this->visual.v2_flags = pre.visual.v2_flags;
this->version = pre.version; this->visual.version = pre.visual.version;
this->v1_flags = pre.v1_flags; this->visual.v1_flags = pre.visual.v1_flags;
this->costume = pre.costume; this->visual.costume = pre.visual.costume;
this->skin = pre.skin; this->visual.skin = pre.visual.skin;
this->face = pre.face; this->visual.face = pre.visual.face;
this->head = pre.head; this->visual.head = pre.visual.head;
this->hair = pre.hair; this->visual.hair = pre.visual.hair;
this->hair_r = pre.hair_r; this->visual.hair_r = pre.visual.hair_r;
this->hair_g = pre.hair_g; this->visual.hair_g = pre.visual.hair_g;
this->hair_b = pre.hair_b; this->visual.hair_b = pre.visual.hair_b;
this->proportion_x = pre.proportion_x; this->visual.proportion_x = pre.visual.proportion_x;
this->proportion_y = pre.proportion_y; this->visual.proportion_y = pre.visual.proportion_y;
this->name = pre.name; this->name = pre.name;
} }
PlayerDispDataBBPreview::PlayerDispDataBBPreview() noexcept PlayerDispDataBBPreview::PlayerDispDataBBPreview() noexcept
: experience(0), : experience(0),
level(0), level(0),
unknown_a2(0),
name_color(0),
extra_model(0),
name_color_checksum(0),
section_id(0),
char_class(0),
v2_flags(0),
version(0),
v1_flags(0),
costume(0),
skin(0),
face(0),
head(0),
hair(0),
hair_r(0),
hair_g(0),
hair_b(0),
proportion_x(0),
proportion_y(0),
play_time(0) {} play_time(0) {}
GuildCardV3::GuildCardV3() noexcept GuildCardV3::GuildCardV3() noexcept
@@ -416,14 +281,14 @@ void ClientGameData::create_player(
const PlayerDispDataBBPreview& preview, const PlayerDispDataBBPreview& preview,
shared_ptr<const LevelTable> level_table) { shared_ptr<const LevelTable> level_table) {
shared_ptr<SavedPlayerDataBB> data(new SavedPlayerDataBB( shared_ptr<SavedPlayerDataBB> data(new SavedPlayerDataBB(
load_object_file<SavedPlayerDataBB>(player_template_filename(preview.char_class)))); load_object_file<SavedPlayerDataBB>(player_template_filename(preview.visual.char_class))));
if (data->signature != PLAYER_FILE_SIGNATURE) { if (data->signature != PLAYER_FILE_SIGNATURE) {
throw runtime_error("player data header is incorrect"); throw runtime_error("player data header is incorrect");
} }
try { try {
data->disp.apply_preview(preview); data->disp.apply_preview(preview);
data->disp.stats = level_table->base_stats_for_class(data->disp.char_class); data->disp.stats.char_stats = level_table->base_stats_for_class(data->disp.visual.char_class);
} catch (const exception& e) { } catch (const exception& e) {
throw runtime_error(string_printf("template application failed: %s", e.what())); throw runtime_error(string_printf("template application failed: %s", e.what()));
} }
@@ -572,8 +437,8 @@ PlayerBB ClientGameData::export_player_bb() {
ret.guild_card_description = player->guild_card_description; ret.guild_card_description = player->guild_card_description;
ret.reserved1 = 0; ret.reserved1 = 0;
ret.reserved2 = 0; ret.reserved2 = 0;
ret.section_id = player->disp.section_id; ret.section_id = player->disp.visual.section_id;
ret.char_class = player->disp.char_class; ret.char_class = player->disp.visual.char_class;
ret.unknown3 = 0; ret.unknown3 = 0;
ret.symbol_chats = account->symbol_chats; ret.symbol_chats = account->symbol_chats;
ret.shortcuts = account->shortcuts; ret.shortcuts = account->shortcuts;
@@ -678,9 +543,9 @@ void SavedPlayerDataBB::add_item(const PlayerInventoryItem& item) {
// Annoyingly, meseta is in the disp data, not in the inventory struct. If the // Annoyingly, meseta is in the disp data, not in the inventory struct. If the
// item is meseta, we have to modify disp instead. // item is meseta, we have to modify disp instead.
if (pid == MESETA_IDENTIFIER) { if (pid == MESETA_IDENTIFIER) {
this->disp.meseta += item.data.data2d; this->disp.stats.meseta += item.data.data2d;
if (this->disp.meseta > 999999) { if (this->disp.stats.meseta > 999999) {
this->disp.meseta = 999999; this->disp.stats.meseta = 999999;
} }
return; return;
} }
@@ -762,10 +627,10 @@ PlayerInventoryItem SavedPlayerDataBB::remove_item(
// If we're removing meseta (signaled by an invalid item ID), then create a // If we're removing meseta (signaled by an invalid item ID), then create a
// meseta item. // meseta item.
if (item_id == 0xFFFFFFFF) { if (item_id == 0xFFFFFFFF) {
if (amount <= this->disp.meseta) { if (amount <= this->disp.stats.meseta) {
this->disp.meseta -= amount; this->disp.stats.meseta -= amount;
} else if (allow_meseta_overdraft) { } else if (allow_meseta_overdraft) {
this->disp.meseta = 0; this->disp.stats.meseta = 0;
} else { } else {
throw out_of_range("player does not have enough meseta"); throw out_of_range("player does not have enough meseta");
} }
@@ -919,7 +784,7 @@ size_t PlayerBank::find_item(uint32_t item_id) {
} }
void SavedPlayerDataBB::print_inventory(FILE* stream) const { void SavedPlayerDataBB::print_inventory(FILE* stream) const {
fprintf(stream, "[PlayerInventory] Meseta: %" PRIu32 "\n", this->disp.meseta.load()); fprintf(stream, "[PlayerInventory] Meseta: %" PRIu32 "\n", this->disp.stats.meseta.load());
fprintf(stream, "[PlayerInventory] %hhu items\n", this->inventory.num_items); fprintf(stream, "[PlayerInventory] %hhu items\n", this->inventory.num_items);
for (size_t x = 0; x < this->inventory.num_items; x++) { for (size_t x = 0; x < this->inventory.num_items; x++) {
const auto& item = this->inventory.items[x]; const auto& item = this->inventory.items[x];
+49 -76
View File
@@ -83,41 +83,56 @@ struct PendingCardTrade {
struct PlayerDispDataBB; struct PlayerDispDataBB;
struct PlayerDispDataDCPCV3 { // 0xD0 bytes struct PlayerStats {
PlayerStats stats; /* 00 */ CharacterStats char_stats;
parray<uint8_t, 0x0A> unknown_a1; /* 0E */ parray<uint8_t, 0x0A> unknown_a1;
le_uint32_t level; /* 18 */ le_uint32_t level;
le_uint32_t experience; /* 1C */ le_uint32_t experience;
le_uint32_t meseta; /* 20 */ le_uint32_t meseta;
ptext<char, 0x10> name; /* 24 */
uint64_t unknown_a2;
le_uint32_t name_color; PlayerStats() noexcept;
uint8_t extra_model; } __attribute__((packed));
parray<uint8_t, 0x0F> unused;
le_uint32_t name_color_checksum; struct PlayerVisualConfig {
uint8_t section_id; /* 00 */ ptext<char, 0x10> name;
uint8_t char_class; /* 10 */ uint64_t unknown_a2;
uint8_t v2_flags; /* 18 */ le_uint32_t name_color;
uint8_t version; /* 1C */ uint8_t extra_model;
le_uint32_t v1_flags; /* 1D */ parray<uint8_t, 0x0F> unused;
le_uint16_t costume; /* 2C */ le_uint32_t name_color_checksum;
le_uint16_t skin; /* 30 */ uint8_t section_id;
le_uint16_t face; /* 31 */ uint8_t char_class;
le_uint16_t head; /* 32 */ uint8_t v2_flags;
le_uint16_t hair; /* 33 */ uint8_t version;
le_uint16_t hair_r; /* 34 */ le_uint32_t v1_flags;
le_uint16_t hair_g; /* 38 */ le_uint16_t costume;
le_uint16_t hair_b; /* 3A */ le_uint16_t skin;
le_float proportion_x; /* 3C */ le_uint16_t face;
le_float proportion_y; /* 3E */ le_uint16_t head;
parray<uint8_t, 0x48> config; /* 40 */ le_uint16_t hair;
parray<uint8_t, 0x14> technique_levels; /* 42 */ le_uint16_t hair_r;
/* 44 */ le_uint16_t hair_g;
/* 46 */ le_uint16_t hair_b;
/* 48 */ le_float proportion_x;
/* 4C */ le_float proportion_y;
/* 50 */
PlayerVisualConfig() noexcept;
} __attribute__((packed));
struct PlayerDispDataDCPCV3 {
/* 00 */ PlayerStats stats;
/* 24 */ PlayerVisualConfig visual;
/* 74 */ parray<uint8_t, 0x48> config;
/* BC */ parray<uint8_t, 0x14> technique_levels;
/* D0 */
// Note: This struct has a default constructor because it's used in a command // Note: This struct has a default constructor because it's used in a command
// that has a fixed-size array. If we didn't define this constructor, the // that has a fixed-size array. If we didn't define this constructor, the
// trivial fields in that array's members would be uninitialized, and we could // trivial fields in that array's members would be uninitialized, and we could
// send uninitialized memory to the client. // send uninitialized memory to the client.
PlayerDispDataDCPCV3() noexcept; PlayerDispDataDCPCV3() noexcept = default;
void enforce_v2_limits(); void enforce_v2_limits();
PlayerDispDataBB to_bb() const; PlayerDispDataBB to_bb() const;
@@ -127,27 +142,9 @@ struct PlayerDispDataDCPCV3 { // 0xD0 bytes
struct PlayerDispDataBBPreview { struct PlayerDispDataBBPreview {
le_uint32_t experience; le_uint32_t experience;
le_uint32_t level; le_uint32_t level;
ptext<char, 0x10> guild_card; // The name field in this structure is used for the player's Guild Card
uint64_t unknown_a2; // number, apparently (possibly because it's a char array and this is BB)
le_uint32_t name_color; PlayerVisualConfig visual;
uint8_t extra_model;
parray<uint8_t, 0x0F> unused;
le_uint32_t name_color_checksum;
uint8_t section_id;
uint8_t char_class;
uint8_t v2_flags;
uint8_t version;
le_uint32_t v1_flags;
le_uint16_t costume;
le_uint16_t skin;
le_uint16_t face;
le_uint16_t head;
le_uint16_t hair;
le_uint16_t hair_r;
le_uint16_t hair_g;
le_uint16_t hair_b;
le_float proportion_x;
le_float proportion_y;
ptext<char16_t, 0x10> name; ptext<char16_t, 0x10> name;
uint32_t play_time; uint32_t play_time;
@@ -157,31 +154,7 @@ struct PlayerDispDataBBPreview {
// BB player appearance and stats data // BB player appearance and stats data
struct PlayerDispDataBB { struct PlayerDispDataBB {
PlayerStats stats; PlayerStats stats;
parray<uint8_t, 0x0A> unknown_a1; PlayerVisualConfig visual;
le_uint32_t level;
le_uint32_t experience;
le_uint32_t meseta;
ptext<char, 0x10> guild_card;
uint64_t unknown_a2;
le_uint32_t name_color; // ARGB8888
uint8_t extra_model;
parray<uint8_t, 0x0F> unused;
le_uint32_t name_color_checksum;
uint8_t section_id;
uint8_t char_class;
uint8_t v2_flags;
uint8_t version;
le_uint32_t v1_flags;
le_uint16_t costume;
le_uint16_t skin;
le_uint16_t face;
le_uint16_t head;
le_uint16_t hair;
le_uint16_t hair_r;
le_uint16_t hair_g;
le_uint16_t hair_b;
le_float proportion_x;
le_float proportion_y;
ptext<char16_t, 0x0C> name; ptext<char16_t, 0x0C> name;
le_uint32_t play_time; le_uint32_t play_time;
uint32_t unknown_a3; uint32_t unknown_a3;
+18 -18
View File
@@ -1025,11 +1025,11 @@ static HandlerResult C_GXB_61(shared_ptr<ServerState>,
pd.disp.name = " "; pd.disp.name = " ";
modified = true; modified = true;
} }
if (session.options.red_name && pd.disp.name_color != 0xFFFF0000) { if (session.options.red_name && pd.disp.visual.name_color != 0xFFFF0000) {
pd.disp.name_color = 0xFFFF0000; pd.disp.visual.name_color = 0xFFFF0000;
modified = true; modified = true;
} else if (session.options.blank_name && pd.disp.name_color != 0x00000000) { } else if (session.options.blank_name && pd.disp.visual.name_color != 0x00000000) {
pd.disp.name_color = 0x00000000; pd.disp.visual.name_color = 0x00000000;
modified = true; modified = true;
} }
@@ -1054,14 +1054,14 @@ static HandlerResult C_GXB_61(shared_ptr<ServerState>,
add_color_inplace(pd->info_board.data(), pd->info_board.size()); add_color_inplace(pd->info_board.data(), pd->info_board.size());
} }
if (session.options.blank_name) { if (session.options.blank_name) {
pd->disp.name = " "; pd->disp.visual.name = " ";
modified = true; modified = true;
} }
if (session.options.red_name && pd->disp.name_color != 0xFFFF0000) { if (session.options.red_name && pd->disp.visual.name_color != 0xFFFF0000) {
pd->disp.name_color = 0xFFFF0000; pd->disp.visual.name_color = 0xFFFF0000;
modified = true; modified = true;
} else if (session.options.blank_name && pd->disp.name_color != 0x00000000) { } else if (session.options.blank_name && pd->disp.visual.name_color != 0x00000000) {
pd->disp.name_color = 0x00000000; pd->disp.visual.name_color = 0x00000000;
modified = true; modified = true;
} }
} }
@@ -1340,7 +1340,7 @@ static HandlerResult S_65_67_68_EB(shared_ptr<ServerState>,
if (index >= session.lobby_players.size()) { if (index >= session.lobby_players.size()) {
session.log.warning("Ignoring invalid player index %zu at position %zu", index, x); session.log.warning("Ignoring invalid player index %zu at position %zu", index, x);
} else { } else {
string name = encode_sjis(cmd.entries[x].disp.name); string name = encode_sjis(cmd.entries[x].disp.visual.name);
if (session.license && (cmd.entries[x].lobby_data.guild_card == session.remote_guild_card_number)) { if (session.license && (cmd.entries[x].lobby_data.guild_card == session.remote_guild_card_number)) {
cmd.entries[x].lobby_data.guild_card = session.license->serial_number; cmd.entries[x].lobby_data.guild_card = session.license->serial_number;
@@ -1353,8 +1353,8 @@ static HandlerResult S_65_67_68_EB(shared_ptr<ServerState>,
auto& p = session.lobby_players[index]; auto& p = session.lobby_players[index];
p.guild_card_number = cmd.entries[x].lobby_data.guild_card; p.guild_card_number = cmd.entries[x].lobby_data.guild_card;
p.name = name; p.name = name;
p.section_id = cmd.entries[x].disp.section_id; p.section_id = cmd.entries[x].disp.visual.section_id;
p.char_class = cmd.entries[x].disp.char_class; p.char_class = cmd.entries[x].disp.visual.char_class;
session.log.info("Added lobby player: (%zu) %" PRIu32 " %s", session.log.info("Added lobby player: (%zu) %" PRIu32 " %s",
index, p.guild_card_number, p.name.c_str()); index, p.guild_card_number, p.name.c_str());
} }
@@ -1409,10 +1409,10 @@ static HandlerResult S_64(shared_ptr<ServerState>,
auto& p = session.lobby_players[x]; auto& p = session.lobby_players[x];
p.guild_card_number = cmd->lobby_data[x].guild_card; p.guild_card_number = cmd->lobby_data[x].guild_card;
if (cmd_ep3) { if (cmd_ep3) {
ptext<char, 0x10> name = cmd_ep3->players_ep3[x].disp.name; ptext<char, 0x10> name = cmd_ep3->players_ep3[x].disp.visual.name;
p.name = name; p.name = name;
p.section_id = cmd_ep3->players_ep3[x].disp.section_id; p.section_id = cmd_ep3->players_ep3[x].disp.visual.section_id;
p.char_class = cmd_ep3->players_ep3[x].disp.char_class; p.char_class = cmd_ep3->players_ep3[x].disp.visual.char_class;
} else { } else {
p.name.clear(); p.name.clear();
} }
@@ -1470,10 +1470,10 @@ static HandlerResult S_E8(shared_ptr<ServerState>,
auto& p = session.lobby_players[x]; auto& p = session.lobby_players[x];
p.guild_card_number = player_entry.lobby_data.guild_card; p.guild_card_number = player_entry.lobby_data.guild_card;
ptext<char, 0x10> name = player_entry.disp.name; ptext<char, 0x10> name = player_entry.disp.visual.name;
p.name = name; p.name = name;
p.section_id = player_entry.disp.section_id; p.section_id = player_entry.disp.visual.section_id;
p.char_class = player_entry.disp.char_class; p.char_class = player_entry.disp.visual.char_class;
session.log.info("Added lobby player: (%zu) %" PRIu32 " %s", session.log.info("Added lobby player: (%zu) %" PRIu32 " %s",
x, p.guild_card_number, p.name.c_str()); x, p.guild_card_number, p.name.c_str());
} }
+7 -7
View File
@@ -1478,12 +1478,12 @@ static void on_09(shared_ptr<ServerState> s, shared_ptr<Client> c,
auto name = encode_sjis(player->disp.name); auto name = encode_sjis(player->disp.name);
if (game->is_ep3()) { if (game->is_ep3()) {
info += string_printf("%zu: $C6%s$C7 L%" PRIu32 "\n", info += string_printf("%zu: $C6%s$C7 L%" PRIu32 "\n",
x + 1, name.c_str(), player->disp.level + 1); x + 1, name.c_str(), player->disp.stats.level + 1);
} else { } else {
info += string_printf("%zu: $C6%s$C7 %s L%" PRIu32 "\n", info += string_printf("%zu: $C6%s$C7 %s L%" PRIu32 "\n",
x + 1, name.c_str(), x + 1, name.c_str(),
abbreviation_for_char_class(player->disp.char_class), abbreviation_for_char_class(player->disp.visual.char_class),
player->disp.level + 1); player->disp.stats.level + 1);
} }
} }
} }
@@ -1893,11 +1893,11 @@ static void on_10(shared_ptr<ServerState> s, shared_ptr<Client> c,
send_lobby_message_box(c, u"$C6Incorrect password."); send_lobby_message_box(c, u"$C6Incorrect password.");
break; break;
} }
if (c->game_data.player()->disp.level < game->min_level) { if (c->game_data.player()->disp.stats.level < game->min_level) {
send_lobby_message_box(c, u"$C6Your level is too\nlow to join this\ngame."); send_lobby_message_box(c, u"$C6Your level is too\nlow to join this\ngame.");
break; break;
} }
if (c->game_data.player()->disp.level > game->max_level) { if (c->game_data.player()->disp.stats.level > game->max_level) {
send_lobby_message_box(c, u"$C6Your level is too\nhigh to join this\ngame."); send_lobby_message_box(c, u"$C6Your level is too\nhigh to join this\ngame.");
break; break;
} }
@@ -3125,7 +3125,7 @@ shared_ptr<Lobby> create_game_generic(
} }
if (!(c->license->privileges & Privilege::FREE_JOIN_GAMES) && if (!(c->license->privileges & Privilege::FREE_JOIN_GAMES) &&
(min_level > c->game_data.player()->disp.level)) { (min_level > c->game_data.player()->disp.stats.level)) {
// Note: We don't throw here because this is a situation players might // Note: We don't throw here because this is a situation players might
// actually encounter while playing the game normally // actually encounter while playing the game normally
send_lobby_message_box(c, u"Your level is too\nlow for this\ndifficulty"); send_lobby_message_box(c, u"Your level is too\nlow for this\ndifficulty");
@@ -3156,7 +3156,7 @@ shared_ptr<Lobby> create_game_generic(
game->version = c->version(); game->version = c->version();
game->section_id = c->options.override_section_id >= 0 game->section_id = c->options.override_section_id >= 0
? c->options.override_section_id ? c->options.override_section_id
: c->game_data.player()->disp.section_id; : c->game_data.player()->disp.visual.section_id;
game->episode = episode; game->episode = episode;
game->mode = mode; game->mode = mode;
game->difficulty = difficulty; game->difficulty = difficulty;
+26 -26
View File
@@ -1115,7 +1115,7 @@ static void on_open_shop_bb_or_ep3_battle_subs(shared_ptr<ServerState> s,
throw logic_error("item creator missing from BB game"); throw logic_error("item creator missing from BB game");
} }
size_t level = c->game_data.player()->disp.level + 1; size_t level = c->game_data.player()->disp.stats.level + 1;
switch (cmd.shop_type) { switch (cmd.shop_type) {
case 0: case 0:
c->game_data.shop_contents[0] = l->item_creator->generate_tool_shop_contents(level); c->game_data.shop_contents[0] = l->item_creator->generate_tool_shop_contents(level);
@@ -1163,14 +1163,14 @@ static void on_bank_action_bb(shared_ptr<ServerState>,
if (cmd.action == 0) { // deposit if (cmd.action == 0) { // deposit
if (cmd.item_id == 0xFFFFFFFF) { // meseta if (cmd.item_id == 0xFFFFFFFF) { // meseta
if (cmd.meseta_amount > c->game_data.player()->disp.meseta) { if (cmd.meseta_amount > c->game_data.player()->disp.stats.meseta) {
return; return;
} }
if ((c->game_data.player()->bank.meseta + cmd.meseta_amount) > 999999) { if ((c->game_data.player()->bank.meseta + cmd.meseta_amount) > 999999) {
return; return;
} }
c->game_data.player()->bank.meseta += cmd.meseta_amount; c->game_data.player()->bank.meseta += cmd.meseta_amount;
c->game_data.player()->disp.meseta -= cmd.meseta_amount; c->game_data.player()->disp.stats.meseta -= cmd.meseta_amount;
} else { // item } else { // item
auto item = c->game_data.player()->remove_item( auto item = c->game_data.player()->remove_item(
cmd.item_id, cmd.item_amount, c->version() != GameVersion::BB); cmd.item_id, cmd.item_amount, c->version() != GameVersion::BB);
@@ -1182,11 +1182,11 @@ static void on_bank_action_bb(shared_ptr<ServerState>,
if (cmd.meseta_amount > c->game_data.player()->bank.meseta) { if (cmd.meseta_amount > c->game_data.player()->bank.meseta) {
return; return;
} }
if ((c->game_data.player()->disp.meseta + cmd.meseta_amount) > 999999) { if ((c->game_data.player()->disp.stats.meseta + cmd.meseta_amount) > 999999) {
return; return;
} }
c->game_data.player()->bank.meseta -= cmd.meseta_amount; c->game_data.player()->bank.meseta -= cmd.meseta_amount;
c->game_data.player()->disp.meseta += cmd.meseta_amount; c->game_data.player()->disp.stats.meseta += cmd.meseta_amount;
} else { // item } else { // item
auto bank_item = c->game_data.player()->bank.remove_item(cmd.item_id, cmd.item_amount); auto bank_item = c->game_data.player()->bank.remove_item(cmd.item_id, cmd.item_amount);
PlayerInventoryItem item = bank_item; PlayerInventoryItem item = bank_item;
@@ -1406,29 +1406,29 @@ static void on_charge_attack_bb(shared_ptr<ServerState>,
const auto& cmd = check_size_t<G_ChargeAttack_BB_6xC7>(data, size); const auto& cmd = check_size_t<G_ChargeAttack_BB_6xC7>(data, size);
auto& disp = c->game_data.player()->disp; auto& disp = c->game_data.player()->disp;
if (cmd.meseta_amount > disp.meseta) { if (cmd.meseta_amount > disp.stats.meseta) {
disp.meseta = 0; disp.stats.meseta = 0;
} else { } else {
disp.meseta -= cmd.meseta_amount; disp.stats.meseta -= cmd.meseta_amount;
} }
} }
static void add_player_exp(shared_ptr<ServerState> s, shared_ptr<Lobby> l, shared_ptr<Client> c, uint32_t exp) { static void add_player_exp(shared_ptr<ServerState> s, shared_ptr<Lobby> l, shared_ptr<Client> c, uint32_t exp) {
c->game_data.player()->disp.experience += exp; c->game_data.player()->disp.stats.experience += exp;
send_give_experience(l, c, exp); send_give_experience(l, c, exp);
bool leveled_up = false; bool leveled_up = false;
do { do {
const auto& level = s->level_table->stats_for_level( const auto& level = s->level_table->stats_for_level(
c->game_data.player()->disp.char_class, c->game_data.player()->disp.level + 1); c->game_data.player()->disp.visual.char_class, c->game_data.player()->disp.stats.level + 1);
if (c->game_data.player()->disp.experience >= level.experience) { if (c->game_data.player()->disp.stats.experience >= level.experience) {
leveled_up = true; leveled_up = true;
level.apply(c->game_data.player()->disp.stats); level.apply(c->game_data.player()->disp.stats.char_stats);
c->game_data.player()->disp.level++; c->game_data.player()->disp.stats.level++;
} else { } else {
break; break;
} }
} while (c->game_data.player()->disp.level < 199); } while (c->game_data.player()->disp.stats.level < 199);
if (leveled_up) { if (leveled_up) {
send_level_up(l, c); send_level_up(l, c);
} }
@@ -1460,7 +1460,7 @@ static void on_steal_exp_bb(shared_ptr<ServerState> s,
if (special >= 0x09 && special <= 0x0B) { if (special >= 0x09 && special <= 0x0B) {
// Master's = 8, Lord's = 10, King's = 12 // Master's = 8, Lord's = 10, King's = 12
uint32_t percent = 8 + ((special - 9) << 1) + (char_class_is_android(c->game_data.player()->disp.char_class) ? 30 : 0); uint32_t percent = 8 + ((special - 9) << 1) + (char_class_is_android(c->game_data.player()->disp.visual.char_class) ? 30 : 0);
uint32_t enemy_exp = s->battle_params->get(l->mode == GameMode::SOLO, l->episode, l->difficulty, enemy.type).experience; uint32_t enemy_exp = s->battle_params->get(l->mode == GameMode::SOLO, l->episode, l->difficulty, enemy.type).experience;
uint32_t stolen_exp = min<uint32_t>((enemy_exp * percent) / 100, 80); uint32_t stolen_exp = min<uint32_t>((enemy_exp * percent) / 100, 80);
if (c->options.debug) { if (c->options.debug) {
@@ -1524,7 +1524,7 @@ static void on_enemy_killed_bb(shared_ptr<ServerState> s,
if (!other_c) { if (!other_c) {
continue; // No player continue; // No player
} }
if (other_c->game_data.player()->disp.level >= 199) { if (other_c->game_data.player()->disp.stats.level >= 199) {
continue; // Player is level 200 or higher continue; // Player is level 200 or higher
} }
@@ -1558,10 +1558,10 @@ void on_meseta_reward_request_bb(shared_ptr<ServerState>,
auto p = c->game_data.player(); auto p = c->game_data.player();
if (cmd.amount < 0) { if (cmd.amount < 0) {
if (-cmd.amount > static_cast<int32_t>(p->disp.meseta.load())) { if (-cmd.amount > static_cast<int32_t>(p->disp.stats.meseta.load())) {
p->disp.meseta = 0; p->disp.stats.meseta = 0;
} else { } else {
p->disp.meseta += cmd.amount; p->disp.stats.meseta += cmd.amount;
} }
} else if (cmd.amount > 0) { } else if (cmd.amount > 0) {
PlayerInventoryItem item; PlayerInventoryItem item;
@@ -1649,7 +1649,7 @@ static void on_identify_item_bb(shared_ptr<ServerState>,
return; // only weapons can be identified return; // only weapons can be identified
} }
c->game_data.player()->disp.meseta -= 100; c->game_data.player()->disp.stats.meseta -= 100;
c->game_data.identify_result = c->game_data.player()->inventory.items[x]; c->game_data.identify_result = c->game_data.player()->inventory.items[x];
c->game_data.identify_result.data.data1[4] &= 0x7F; c->game_data.identify_result.data.data1[4] &= 0x7F;
send_item_identify_result(l, c); send_item_identify_result(l, c);
@@ -1698,8 +1698,8 @@ static void on_sell_item_at_shop_bb(shared_ptr<ServerState> s,
auto item = c->game_data.player()->remove_item( auto item = c->game_data.player()->remove_item(
cmd.item_id, cmd.amount, c->version() != GameVersion::BB); cmd.item_id, cmd.amount, c->version() != GameVersion::BB);
size_t price = (s->item_parameter_table->price_for_item(item.data) >> 3) * cmd.amount; size_t price = (s->item_parameter_table->price_for_item(item.data) >> 3) * cmd.amount;
c->game_data.player()->disp.meseta = min<uint32_t>( c->game_data.player()->disp.stats.meseta = min<uint32_t>(
c->game_data.player()->disp.meseta + price, 999999); c->game_data.player()->disp.stats.meseta + price, 999999);
auto name = item.data.name(false); auto name = item.data.name(false);
l->log.info("Inventory item %hu:%08" PRIX32 " destroyed via sale (%s)", l->log.info("Inventory item %hu:%08" PRIX32 " destroyed via sale (%s)",
@@ -1733,10 +1733,10 @@ static void on_buy_shop_item_bb(shared_ptr<ServerState>,
size_t price = item.data.data2d * cmd.amount; size_t price = item.data.data2d * cmd.amount;
item.data.data2d = 0; item.data.data2d = 0;
if (c->game_data.player()->disp.meseta < price) { if (c->game_data.player()->disp.stats.meseta < price) {
throw runtime_error("player does not have enough money"); throw runtime_error("player does not have enough money");
} }
c->game_data.player()->disp.meseta -= price; c->game_data.player()->disp.stats.meseta -= price;
item.data.id = cmd.inventory_item_id; item.data.id = cmd.inventory_item_id;
c->game_data.player()->add_item(item); c->game_data.player()->add_item(item);
@@ -1758,10 +1758,10 @@ static void on_medical_center_bb(shared_ptr<ServerState>,
shared_ptr<Lobby> l, shared_ptr<Client> c, uint8_t, uint8_t, const void*, size_t) { shared_ptr<Lobby> l, shared_ptr<Client> c, uint8_t, uint8_t, const void*, size_t) {
if (l->version == GameVersion::BB) { if (l->version == GameVersion::BB) {
if (c->game_data.player()->disp.meseta < 10) { if (c->game_data.player()->disp.stats.meseta < 10) {
throw runtime_error("insufficient funds"); throw runtime_error("insufficient funds");
} }
c->game_data.player()->disp.meseta -= 10; c->game_data.player()->disp.stats.meseta -= 10;
} }
} }
+13 -13
View File
@@ -1044,8 +1044,8 @@ void send_guild_card(shared_ptr<Client> c, shared_ptr<Client> source) {
uint32_t guild_card_number = source->license->serial_number; uint32_t guild_card_number = source->license->serial_number;
u16string name = source->game_data.player()->disp.name; u16string name = source->game_data.player()->disp.name;
u16string description = source->game_data.player()->guild_card_description; u16string description = source->game_data.player()->guild_card_description;
uint8_t section_id = source->game_data.player()->disp.section_id; uint8_t section_id = source->game_data.player()->disp.visual.section_id;
uint8_t char_class = source->game_data.player()->disp.char_class; uint8_t char_class = source->game_data.player()->disp.visual.char_class;
send_guild_card( send_guild_card(
c->channel, guild_card_number, name, u"", description, section_id, char_class); c->channel, guild_card_number, name, u"", description, section_id, char_class);
@@ -1387,7 +1387,7 @@ static void send_join_spectator_team(shared_ptr<Client> c, shared_ptr<Lobby> l)
p.inventory.items[y].data.bswap_data2_if_mag(); p.inventory.items[y].data.bswap_data2_if_mag();
} }
p.disp = watched_lobby->clients[z]->game_data.player()->disp.to_dcpcv3(); p.disp = watched_lobby->clients[z]->game_data.player()->disp.to_dcpcv3();
remove_language_marker_inplace(p.disp.name); remove_language_marker_inplace(p.disp.visual.name);
auto& e = cmd.entries[z]; auto& e = cmd.entries[z];
e.player_tag = 0x00010000; e.player_tag = 0x00010000;
@@ -1395,7 +1395,7 @@ static void send_join_spectator_team(shared_ptr<Client> c, shared_ptr<Lobby> l)
e.name = watched_lobby->clients[z]->game_data.player()->disp.name; e.name = watched_lobby->clients[z]->game_data.player()->disp.name;
remove_language_marker_inplace(e.name); remove_language_marker_inplace(e.name);
e.present = 1; e.present = 1;
e.level = watched_lobby->clients[z]->game_data.player()->disp.level.load(); e.level = watched_lobby->clients[z]->game_data.player()->disp.stats.level.load();
player_count++; player_count++;
} }
@@ -1421,13 +1421,13 @@ static void send_join_spectator_team(shared_ptr<Client> c, shared_ptr<Lobby> l)
cmd.players[client_id].inventory.items[z].data.bswap_data2_if_mag(); cmd.players[client_id].inventory.items[z].data.bswap_data2_if_mag();
} }
cmd.players[client_id].disp = entry.disp; cmd.players[client_id].disp = entry.disp;
remove_language_marker_inplace(cmd.players[client_id].disp.name); remove_language_marker_inplace(cmd.players[client_id].disp.visual.name);
cmd.entries[client_id].player_tag = 0x00010000; cmd.entries[client_id].player_tag = 0x00010000;
cmd.entries[client_id].guild_card_number = entry.lobby_data.guild_card; cmd.entries[client_id].guild_card_number = entry.lobby_data.guild_card;
cmd.entries[client_id].name = entry.disp.name; cmd.entries[client_id].name = entry.disp.visual.name;
remove_language_marker_inplace(cmd.entries[client_id].name); remove_language_marker_inplace(cmd.entries[client_id].name);
cmd.entries[client_id].present = 1; cmd.entries[client_id].present = 1;
cmd.entries[client_id].level = entry.disp.level.load(); cmd.entries[client_id].level = entry.disp.stats.level.load();
player_count++; player_count++;
} }
@@ -1444,13 +1444,13 @@ static void send_join_spectator_team(shared_ptr<Client> c, shared_ptr<Lobby> l)
remove_language_marker_inplace(cmd.spectator_players[z - 4].lobby_data.name); remove_language_marker_inplace(cmd.spectator_players[z - 4].lobby_data.name);
cmd.spectator_players[z - 4].inventory = l->clients[z]->game_data.player()->inventory; cmd.spectator_players[z - 4].inventory = l->clients[z]->game_data.player()->inventory;
cmd.spectator_players[z - 4].disp = l->clients[z]->game_data.player()->disp.to_dcpcv3(); cmd.spectator_players[z - 4].disp = l->clients[z]->game_data.player()->disp.to_dcpcv3();
remove_language_marker_inplace(cmd.spectator_players[z - 4].disp.name); remove_language_marker_inplace(cmd.spectator_players[z - 4].disp.visual.name);
cmd.entries[z].player_tag = 0x00010000; cmd.entries[z].player_tag = 0x00010000;
cmd.entries[z].guild_card_number = l->clients[z]->license->serial_number; cmd.entries[z].guild_card_number = l->clients[z]->license->serial_number;
cmd.entries[z].name = l->clients[z]->game_data.player()->disp.name; cmd.entries[z].name = l->clients[z]->game_data.player()->disp.name;
remove_language_marker_inplace(cmd.entries[z].name); remove_language_marker_inplace(cmd.entries[z].name);
cmd.entries[z].present = 1; cmd.entries[z].present = 1;
cmd.entries[z].level = l->clients[z]->game_data.player()->disp.level.load(); cmd.entries[z].level = l->clients[z]->game_data.player()->disp.stats.level.load();
player_count++; player_count++;
} }
} }
@@ -2052,7 +2052,7 @@ void send_shop(shared_ptr<Client> c, uint8_t shop_type) {
// notifies players about a level up // notifies players about a level up
void send_level_up(shared_ptr<Lobby> l, shared_ptr<Client> c) { void send_level_up(shared_ptr<Lobby> l, shared_ptr<Client> c) {
PlayerStats stats = c->game_data.player()->disp.stats; CharacterStats stats = c->game_data.player()->disp.stats.char_stats;
for (size_t x = 0; x < c->game_data.player()->inventory.num_items; x++) { for (size_t x = 0; x < c->game_data.player()->inventory.num_items; x++) {
if ((c->game_data.player()->inventory.items[x].flags & 0x08) && if ((c->game_data.player()->inventory.items[x].flags & 0x08) &&
@@ -2072,7 +2072,7 @@ void send_level_up(shared_ptr<Lobby> l, shared_ptr<Client> c) {
stats.hp, stats.hp,
stats.dfp, stats.dfp,
stats.ata, stats.ata,
c->game_data.player()->disp.level.load(), c->game_data.player()->disp.stats.level.load(),
0}; 0};
send_command_t(l, 0x60, 0x00, cmd); send_command_t(l, 0x60, 0x00, cmd);
} }
@@ -2304,8 +2304,8 @@ string ep3_description_for_client(shared_ptr<Client> c) {
auto player = c->game_data.player(); auto player = c->game_data.player();
return string_printf( return string_printf(
"%s CLv%" PRIu32 " %c", "%s CLv%" PRIu32 " %c",
name_for_char_class(player->disp.char_class), name_for_char_class(player->disp.visual.char_class),
player->disp.level + 1, player->disp.stats.level + 1,
char_for_language_code(player->inventory.language)); char_for_language_code(player->inventory.language));
} }