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 {
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") {
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") {
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") {
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") {
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") {
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") {
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") {
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") {
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") {
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") {
uint32_t 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") {
uint8_t secid = section_id_for_name(decode_sjis(tokens.at(1)));
if (secid == 0xFF) {
send_text_message(c, u"$C6No such section ID");
return;
} else {
c->game_data.player()->disp.section_id = secid;
c->game_data.player()->disp.visual.section_id = secid;
}
} else if (tokens.at(0) == "name") {
c->game_data.player()->disp.name = add_language_marker(tokens.at(1), 'J');
} else if (tokens.at(0) == "npc") {
if (tokens.at(1) == "none") {
c->game_data.player()->disp.extra_model = 0;
c->game_data.player()->disp.v2_flags &= 0xFD;
c->game_data.player()->disp.visual.extra_model = 0;
c->game_data.player()->disp.visual.v2_flags &= 0xFD;
} else {
uint8_t npc = npc_for_name(decode_sjis(tokens.at(1)));
if (npc == 0xFF) {
send_text_message(c, u"$C6No such NPC");
return;
}
c->game_data.player()->disp.extra_model = npc;
c->game_data.player()->disp.v2_flags |= 0x02;
c->game_data.player()->disp.visual.extra_model = npc;
c->game_data.player()->disp.visual.v2_flags |= 0x02;
}
} else if (tokens.at(0) == "tech") {
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;
/* 00BC */ le_uint32_t unknown_a11;
/* 00C0 */ parray<uint8_t, 0x14> technique_levels; // Last byte is uninitialized
/* 00D4 */ struct {
parray<uint8_t, 0x10> name;
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;
/* 00D4 */ PlayerVisualConfig visual;
/* 0124 */ PlayerStats stats;
/* 0148 */ struct {
le_uint32_t num_items;
// 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 {
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__;
// 6xB3x3A / CAx3A: Unknown
+1 -1
View File
@@ -182,7 +182,7 @@ public:
uint32_t num_destroyed_fcs;
uint8_t unknown_a16;
uint8_t unknown_a17;
PlayerStats stats;
PlayerBattleStats stats;
};
} // namespace Episode3
+7 -7
View File
@@ -688,11 +688,11 @@ void HandAndEquipState::clear_FF() {
this->unused2.clear(0xFF);
}
PlayerStats::PlayerStats() {
PlayerBattleStats::PlayerBattleStats() {
this->clear();
}
void PlayerStats::clear() {
void PlayerBattleStats::clear() {
this->damage_given = 0;
this->damage_taken = 0;
this->num_opponent_cards_destroyed = 0;
@@ -715,7 +715,7 @@ void PlayerStats::clear() {
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:
// 35
// + (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);
}
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));
}
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)));
}
@@ -744,7 +744,7 @@ static const float RANK_THRESHOLDS[RANK_THRESHOLD_COUNT] = {
static const char* RANK_NAMES[RANK_THRESHOLD_COUNT + 1] = {
"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;
while (rank < RANK_THRESHOLD_COUNT && RANK_THRESHOLDS[rank] <= score) {
rank++;
@@ -752,7 +752,7 @@ uint8_t PlayerStats::rank_for_score(float score) {
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) {
throw invalid_argument("invalid rank");
}
+2 -2
View File
@@ -234,7 +234,7 @@ struct HandAndEquipState {
std::string str() const;
} __attribute__((packed));
struct PlayerStats {
struct PlayerBattleStats {
le_uint16_t damage_given;
le_uint16_t damage_taken;
le_uint16_t num_opponent_cards_destroyed;
@@ -256,7 +256,7 @@ struct PlayerStats {
le_uint16_t action_card_negated_damage;
le_uint16_t unused;
PlayerStats();
PlayerBattleStats();
void clear();
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)
} 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) {
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
switch (item.data.data1[2]) {
case 0: // Power Material
c->game_data.player()->disp.stats.atp += 2;
c->game_data.player()->disp.stats.char_stats.atp += 2;
break;
case 1: // Mind Material
c->game_data.player()->disp.stats.mst += 2;
c->game_data.player()->disp.stats.char_stats.mst += 2;
break;
case 2: // Evade Material
c->game_data.player()->disp.stats.evp += 2;
c->game_data.player()->disp.stats.char_stats.evp += 2;
break;
case 3: // HP Material
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;
break;
case 5: // Def Material
c->game_data.player()->disp.stats.dfp += 2;
c->game_data.player()->disp.stats.char_stats.dfp += 2;
break;
case 6: // Luck Material
c->game_data.player()->disp.stats.lck += 2;
c->game_data.player()->disp.stats.char_stats.lck += 2;
break;
default:
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) {
// Cell of MAG 502
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) {
// Cell of MAG 213
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) {
// Parts of RoboChao
@@ -169,7 +169,7 @@ void player_use_item(shared_ptr<ServerState> s, shared_ptr<Client> c, size_t ite
try {
const auto& combo = s->item_parameter_table->get_item_combination(
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");
}
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");
}
}
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");
}
// 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
if (evolution_number < 1) {
switch (player->disp.char_class) {
switch (player->disp.visual.char_class) {
case 0: // HUmar
case 1: // HUnewearl
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 (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 pow = mag_item.data.data1w[3] / 100;
uint16_t dex = mag_item.data.data1w[4] / 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;
bool is_hunter = char_class_is_hunter(player->disp.char_class);
bool is_ranger = char_class_is_ranger(player->disp.char_class);
bool is_force = char_class_is_force(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.visual.char_class);
bool is_force = char_class_is_force(player->disp.visual.char_class);
if (is_force) {
table_index += 12;
} 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 mind = mag_item.data.data1w[5] / 100;
bool is_hunter = char_class_is_hunter(player->disp.char_class);
bool is_ranger = char_class_is_ranger(player->disp.char_class);
bool is_force = char_class_is_force(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.visual.char_class);
bool is_force = char_class_is_force(player->disp.visual.char_class);
if (is_hunter + is_ranger + is_force != 1) {
throw logic_error("char class is not exactly one of the top-level classes");
}
if (is_hunter) {
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) ? 0x0C : 0x05);
} 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) ? 0x17 : 0x13);
} 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) ? 0x07 : 0x1E);
}
} else if (is_ranger) {
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) ? 0x0C : 0x06);
} 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) ? 0x0C : 0x06);
} 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) ? 0x08 : 0x05);
}
} else if (is_force) {
if (flags & 0x120) {
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) ? 0x1E : 0x1C);
} else {
@@ -424,7 +424,7 @@ void player_feed_mag(std::shared_ptr<ServerState> s, std::shared_ptr<Client> c,
}
} else if (flags & 0x008) {
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) ? 0x1F : 0x25);
} else {
@@ -432,7 +432,7 @@ void player_feed_mag(std::shared_ptr<ServerState> s, std::shared_ptr<Client> c,
}
} else if (flags & 0x010) {
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) ? 0x15 : 0x11);
} 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());
}
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) {
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];
}
void LevelTable::LevelStats::apply(PlayerStats& ps) const {
void LevelTable::LevelStats::apply(CharacterStats& ps) const {
ps.ata += this->ata;
ps.atp += this->atp;
ps.dfp += this->dfp;
+5 -5
View File
@@ -6,7 +6,7 @@
#include <phosg/Encoding.hh>
#include <string>
struct PlayerStats {
struct CharacterStats {
le_uint16_t atp;
le_uint16_t mst;
le_uint16_t evp;
@@ -15,7 +15,7 @@ struct PlayerStats {
le_uint16_t ata;
le_uint16_t lck;
PlayerStats() noexcept;
CharacterStats() noexcept;
} __attribute__((packed));
class LevelTable { // from PlyLevelTbl.prs
@@ -31,18 +31,18 @@ public:
uint8_t tp;
le_uint32_t experience;
void apply(PlayerStats& ps) const;
void apply(CharacterStats& ps) const;
} __attribute__((packed));
struct Table {
PlayerStats base_stats[12];
CharacterStats base_stats[12];
le_uint32_t unknown[12];
LevelStats levels[12][200];
} __attribute__((packed));
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;
private:
+60 -195
View File
@@ -27,7 +27,7 @@ static const string ACCOUNT_FILE_SIGNATURE =
static FileContentsCache player_files_cache(300 * 1000 * 1000);
PlayerStats::PlayerStats() noexcept
CharacterStats::CharacterStats() noexcept
: atp(0),
mst(0),
evp(0),
@@ -36,11 +36,13 @@ PlayerStats::PlayerStats() noexcept
ata(0),
lck(0) {}
PlayerDispDataDCPCV3::PlayerDispDataDCPCV3() noexcept
PlayerStats::PlayerStats() noexcept
: level(0),
experience(0),
meseta(0),
unknown_a2(0),
meseta(0) {}
PlayerVisualConfig::PlayerVisualConfig() noexcept
: unknown_a2(0),
name_color(0),
extra_model(0),
name_color_checksum(0),
@@ -62,121 +64,43 @@ PlayerDispDataDCPCV3::PlayerDispDataDCPCV3() noexcept
void PlayerDispDataDCPCV3::enforce_v2_limits() {
// V1/V2 have fewer classes, so we'll substitute some here
if (this->char_class == 11) {
this->char_class = 0; // FOmar -> HUmar
} else if (this->char_class == 10) {
this->char_class = 1; // RAmarl -> HUnewearl
} else if (this->char_class == 9) {
this->char_class = 5; // HUcaseal -> RAcaseal
if (this->visual.char_class == 11) {
this->visual.char_class = 0; // FOmar -> HUmar
} else if (this->visual.char_class == 10) {
this->visual.char_class = 1; // RAmarl -> HUnewearl
} else if (this->visual.char_class == 9) {
this->visual.char_class = 5; // HUcaseal -> RAcaseal
}
// If the player is somehow still not a valid class, make them appear as the
// "ninja" NPC
if (this->char_class > 8) {
this->extra_model = 0;
this->v2_flags |= 2;
if (this->visual.char_class > 8) {
this->visual.extra_model = 0;
this->visual.v2_flags |= 2;
}
this->version = 2;
this->visual.version = 2;
}
PlayerDispDataBB PlayerDispDataDCPCV3::to_bb() const {
PlayerDispDataBB bb;
bb.stats.atp = this->stats.atp;
bb.stats.mst = this->stats.mst;
bb.stats.evp = this->stats.evp;
bb.stats.hp = this->stats.hp;
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.stats = this->stats;
bb.visual = this->visual;
bb.visual.name = " 0";
bb.name = add_language_marker(this->visual.name, 'J');
bb.config = this->config;
bb.technique_levels = this->technique_levels;
return bb;
}
PlayerDispDataBB::PlayerDispDataBB() noexcept
: level(0),
experience(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) {}
: play_time(0),
unknown_a3(0) {}
PlayerDispDataDCPCV3 PlayerDispDataBB::to_dcpcv3() const {
PlayerDispDataDCPCV3 ret;
ret.stats.atp = this->stats.atp;
ret.stats.mst = this->stats.mst;
ret.stats.evp = this->stats.evp;
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.stats = this->stats;
ret.visual = this->visual;
ret.visual.name = remove_language_marker(this->name);
ret.config = this->config;
ret.technique_levels = this->technique_levels;
return ret;
@@ -184,105 +108,46 @@ PlayerDispDataDCPCV3 PlayerDispDataBB::to_dcpcv3() const {
PlayerDispDataBBPreview PlayerDispDataBB::to_preview() const {
PlayerDispDataBBPreview pre;
pre.level = this->level;
pre.experience = this->experience;
pre.guild_card = this->guild_card;
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.level = this->stats.level;
pre.experience = this->stats.experience;
pre.visual = this->visual;
pre.name = this->name;
pre.play_time = this->play_time;
return pre;
}
void PlayerDispDataBB::apply_preview(const PlayerDispDataBBPreview& pre) {
this->level = pre.level;
this->experience = pre.experience;
this->guild_card = pre.guild_card;
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->stats.level = pre.level;
this->stats.experience = pre.experience;
this->visual = pre.visual;
this->name = pre.name;
}
void PlayerDispDataBB::apply_dressing_room(const PlayerDispDataBBPreview& pre) {
this->name_color = pre.name_color;
this->extra_model = pre.extra_model;
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->visual.name_color = pre.visual.name_color;
this->visual.extra_model = pre.visual.extra_model;
this->visual.name_color_checksum = pre.visual.name_color_checksum;
this->visual.section_id = pre.visual.section_id;
this->visual.char_class = pre.visual.char_class;
this->visual.v2_flags = pre.visual.v2_flags;
this->visual.version = pre.visual.version;
this->visual.v1_flags = pre.visual.v1_flags;
this->visual.costume = pre.visual.costume;
this->visual.skin = pre.visual.skin;
this->visual.face = pre.visual.face;
this->visual.head = pre.visual.head;
this->visual.hair = pre.visual.hair;
this->visual.hair_r = pre.visual.hair_r;
this->visual.hair_g = pre.visual.hair_g;
this->visual.hair_b = pre.visual.hair_b;
this->visual.proportion_x = pre.visual.proportion_x;
this->visual.proportion_y = pre.visual.proportion_y;
this->name = pre.name;
}
PlayerDispDataBBPreview::PlayerDispDataBBPreview() noexcept
: experience(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) {}
GuildCardV3::GuildCardV3() noexcept
@@ -416,14 +281,14 @@ void ClientGameData::create_player(
const PlayerDispDataBBPreview& preview,
shared_ptr<const LevelTable> level_table) {
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) {
throw runtime_error("player data header is incorrect");
}
try {
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) {
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.reserved1 = 0;
ret.reserved2 = 0;
ret.section_id = player->disp.section_id;
ret.char_class = player->disp.char_class;
ret.section_id = player->disp.visual.section_id;
ret.char_class = player->disp.visual.char_class;
ret.unknown3 = 0;
ret.symbol_chats = account->symbol_chats;
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
// item is meseta, we have to modify disp instead.
if (pid == MESETA_IDENTIFIER) {
this->disp.meseta += item.data.data2d;
if (this->disp.meseta > 999999) {
this->disp.meseta = 999999;
this->disp.stats.meseta += item.data.data2d;
if (this->disp.stats.meseta > 999999) {
this->disp.stats.meseta = 999999;
}
return;
}
@@ -762,10 +627,10 @@ PlayerInventoryItem SavedPlayerDataBB::remove_item(
// If we're removing meseta (signaled by an invalid item ID), then create a
// meseta item.
if (item_id == 0xFFFFFFFF) {
if (amount <= this->disp.meseta) {
this->disp.meseta -= amount;
if (amount <= this->disp.stats.meseta) {
this->disp.stats.meseta -= amount;
} else if (allow_meseta_overdraft) {
this->disp.meseta = 0;
this->disp.stats.meseta = 0;
} else {
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 {
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);
for (size_t x = 0; x < this->inventory.num_items; x++) {
const auto& item = this->inventory.items[x];
+49 -76
View File
@@ -83,41 +83,56 @@ struct PendingCardTrade {
struct PlayerDispDataBB;
struct PlayerDispDataDCPCV3 { // 0xD0 bytes
PlayerStats stats;
parray<uint8_t, 0x0A> unknown_a1;
le_uint32_t level;
le_uint32_t experience;
le_uint32_t meseta;
ptext<char, 0x10> name;
uint64_t unknown_a2;
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_float proportion_x;
le_float proportion_y;
parray<uint8_t, 0x48> config;
parray<uint8_t, 0x14> technique_levels;
struct PlayerStats {
/* 00 */ CharacterStats char_stats;
/* 0E */ parray<uint8_t, 0x0A> unknown_a1;
/* 18 */ le_uint32_t level;
/* 1C */ le_uint32_t experience;
/* 20 */ le_uint32_t meseta;
/* 24 */
PlayerStats() noexcept;
} __attribute__((packed));
struct PlayerVisualConfig {
/* 00 */ ptext<char, 0x10> name;
/* 10 */ uint64_t unknown_a2;
/* 18 */ le_uint32_t name_color;
/* 1C */ uint8_t extra_model;
/* 1D */ parray<uint8_t, 0x0F> unused;
/* 2C */ le_uint32_t name_color_checksum;
/* 30 */ uint8_t section_id;
/* 31 */ uint8_t char_class;
/* 32 */ uint8_t v2_flags;
/* 33 */ uint8_t version;
/* 34 */ le_uint32_t v1_flags;
/* 38 */ le_uint16_t costume;
/* 3A */ le_uint16_t skin;
/* 3C */ le_uint16_t face;
/* 3E */ le_uint16_t head;
/* 40 */ le_uint16_t hair;
/* 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
// 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
// send uninitialized memory to the client.
PlayerDispDataDCPCV3() noexcept;
PlayerDispDataDCPCV3() noexcept = default;
void enforce_v2_limits();
PlayerDispDataBB to_bb() const;
@@ -127,27 +142,9 @@ struct PlayerDispDataDCPCV3 { // 0xD0 bytes
struct PlayerDispDataBBPreview {
le_uint32_t experience;
le_uint32_t level;
ptext<char, 0x10> guild_card;
uint64_t unknown_a2;
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_float proportion_x;
le_float proportion_y;
// The name field in this structure is used for the player's Guild Card
// number, apparently (possibly because it's a char array and this is BB)
PlayerVisualConfig visual;
ptext<char16_t, 0x10> name;
uint32_t play_time;
@@ -157,31 +154,7 @@ struct PlayerDispDataBBPreview {
// BB player appearance and stats data
struct PlayerDispDataBB {
PlayerStats stats;
parray<uint8_t, 0x0A> unknown_a1;
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;
PlayerVisualConfig visual;
ptext<char16_t, 0x0C> name;
le_uint32_t play_time;
uint32_t unknown_a3;
+18 -18
View File
@@ -1025,11 +1025,11 @@ static HandlerResult C_GXB_61(shared_ptr<ServerState>,
pd.disp.name = " ";
modified = true;
}
if (session.options.red_name && pd.disp.name_color != 0xFFFF0000) {
pd.disp.name_color = 0xFFFF0000;
if (session.options.red_name && pd.disp.visual.name_color != 0xFFFF0000) {
pd.disp.visual.name_color = 0xFFFF0000;
modified = true;
} else if (session.options.blank_name && pd.disp.name_color != 0x00000000) {
pd.disp.name_color = 0x00000000;
} else if (session.options.blank_name && pd.disp.visual.name_color != 0x00000000) {
pd.disp.visual.name_color = 0x00000000;
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());
}
if (session.options.blank_name) {
pd->disp.name = " ";
pd->disp.visual.name = " ";
modified = true;
}
if (session.options.red_name && pd->disp.name_color != 0xFFFF0000) {
pd->disp.name_color = 0xFFFF0000;
if (session.options.red_name && pd->disp.visual.name_color != 0xFFFF0000) {
pd->disp.visual.name_color = 0xFFFF0000;
modified = true;
} else if (session.options.blank_name && pd->disp.name_color != 0x00000000) {
pd->disp.name_color = 0x00000000;
} else if (session.options.blank_name && pd->disp.visual.name_color != 0x00000000) {
pd->disp.visual.name_color = 0x00000000;
modified = true;
}
}
@@ -1340,7 +1340,7 @@ static HandlerResult S_65_67_68_EB(shared_ptr<ServerState>,
if (index >= session.lobby_players.size()) {
session.log.warning("Ignoring invalid player index %zu at position %zu", index, x);
} 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)) {
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];
p.guild_card_number = cmd.entries[x].lobby_data.guild_card;
p.name = name;
p.section_id = cmd.entries[x].disp.section_id;
p.char_class = cmd.entries[x].disp.char_class;
p.section_id = cmd.entries[x].disp.visual.section_id;
p.char_class = cmd.entries[x].disp.visual.char_class;
session.log.info("Added lobby player: (%zu) %" PRIu32 " %s",
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];
p.guild_card_number = cmd->lobby_data[x].guild_card;
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.section_id = cmd_ep3->players_ep3[x].disp.section_id;
p.char_class = cmd_ep3->players_ep3[x].disp.char_class;
p.section_id = cmd_ep3->players_ep3[x].disp.visual.section_id;
p.char_class = cmd_ep3->players_ep3[x].disp.visual.char_class;
} else {
p.name.clear();
}
@@ -1470,10 +1470,10 @@ static HandlerResult S_E8(shared_ptr<ServerState>,
auto& p = session.lobby_players[x];
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.section_id = player_entry.disp.section_id;
p.char_class = player_entry.disp.char_class;
p.section_id = player_entry.disp.visual.section_id;
p.char_class = player_entry.disp.visual.char_class;
session.log.info("Added lobby player: (%zu) %" PRIu32 " %s",
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);
if (game->is_ep3()) {
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 {
info += string_printf("%zu: $C6%s$C7 %s L%" PRIu32 "\n",
x + 1, name.c_str(),
abbreviation_for_char_class(player->disp.char_class),
player->disp.level + 1);
abbreviation_for_char_class(player->disp.visual.char_class),
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.");
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.");
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.");
break;
}
@@ -3125,7 +3125,7 @@ shared_ptr<Lobby> create_game_generic(
}
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
// actually encounter while playing the game normally
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->section_id = c->options.override_section_id >= 0
? c->options.override_section_id
: c->game_data.player()->disp.section_id;
: c->game_data.player()->disp.visual.section_id;
game->episode = episode;
game->mode = mode;
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");
}
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) {
case 0:
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.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;
}
if ((c->game_data.player()->bank.meseta + cmd.meseta_amount) > 999999) {
return;
}
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
auto item = c->game_data.player()->remove_item(
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) {
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;
}
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
auto bank_item = c->game_data.player()->bank.remove_item(cmd.item_id, cmd.item_amount);
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);
auto& disp = c->game_data.player()->disp;
if (cmd.meseta_amount > disp.meseta) {
disp.meseta = 0;
if (cmd.meseta_amount > disp.stats.meseta) {
disp.stats.meseta = 0;
} 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) {
c->game_data.player()->disp.experience += exp;
c->game_data.player()->disp.stats.experience += exp;
send_give_experience(l, c, exp);
bool leveled_up = false;
do {
const auto& level = s->level_table->stats_for_level(
c->game_data.player()->disp.char_class, c->game_data.player()->disp.level + 1);
if (c->game_data.player()->disp.experience >= level.experience) {
c->game_data.player()->disp.visual.char_class, c->game_data.player()->disp.stats.level + 1);
if (c->game_data.player()->disp.stats.experience >= level.experience) {
leveled_up = true;
level.apply(c->game_data.player()->disp.stats);
c->game_data.player()->disp.level++;
level.apply(c->game_data.player()->disp.stats.char_stats);
c->game_data.player()->disp.stats.level++;
} else {
break;
}
} while (c->game_data.player()->disp.level < 199);
} while (c->game_data.player()->disp.stats.level < 199);
if (leveled_up) {
send_level_up(l, c);
}
@@ -1460,7 +1460,7 @@ static void on_steal_exp_bb(shared_ptr<ServerState> s,
if (special >= 0x09 && special <= 0x0B) {
// 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 stolen_exp = min<uint32_t>((enemy_exp * percent) / 100, 80);
if (c->options.debug) {
@@ -1524,7 +1524,7 @@ static void on_enemy_killed_bb(shared_ptr<ServerState> s,
if (!other_c) {
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
}
@@ -1558,10 +1558,10 @@ void on_meseta_reward_request_bb(shared_ptr<ServerState>,
auto p = c->game_data.player();
if (cmd.amount < 0) {
if (-cmd.amount > static_cast<int32_t>(p->disp.meseta.load())) {
p->disp.meseta = 0;
if (-cmd.amount > static_cast<int32_t>(p->disp.stats.meseta.load())) {
p->disp.stats.meseta = 0;
} else {
p->disp.meseta += cmd.amount;
p->disp.stats.meseta += cmd.amount;
}
} else if (cmd.amount > 0) {
PlayerInventoryItem item;
@@ -1649,7 +1649,7 @@ static void on_identify_item_bb(shared_ptr<ServerState>,
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.data.data1[4] &= 0x7F;
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(
cmd.item_id, cmd.amount, c->version() != GameVersion::BB);
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.meseta + price, 999999);
c->game_data.player()->disp.stats.meseta = min<uint32_t>(
c->game_data.player()->disp.stats.meseta + price, 999999);
auto name = item.data.name(false);
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;
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");
}
c->game_data.player()->disp.meseta -= price;
c->game_data.player()->disp.stats.meseta -= price;
item.data.id = cmd.inventory_item_id;
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) {
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");
}
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;
u16string name = source->game_data.player()->disp.name;
u16string description = source->game_data.player()->guild_card_description;
uint8_t section_id = source->game_data.player()->disp.section_id;
uint8_t char_class = source->game_data.player()->disp.char_class;
uint8_t section_id = source->game_data.player()->disp.visual.section_id;
uint8_t char_class = source->game_data.player()->disp.visual.char_class;
send_guild_card(
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.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];
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;
remove_language_marker_inplace(e.name);
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++;
}
@@ -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].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].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);
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++;
}
@@ -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);
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();
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].guild_card_number = l->clients[z]->license->serial_number;
cmd.entries[z].name = l->clients[z]->game_data.player()->disp.name;
remove_language_marker_inplace(cmd.entries[z].name);
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++;
}
}
@@ -2052,7 +2052,7 @@ void send_shop(shared_ptr<Client> c, uint8_t shop_type) {
// notifies players about a level up
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++) {
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.dfp,
stats.ata,
c->game_data.player()->disp.level.load(),
c->game_data.player()->disp.stats.level.load(),
0};
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();
return string_printf(
"%s CLv%" PRIu32 " %c",
name_for_char_class(player->disp.char_class),
player->disp.level + 1,
name_for_char_class(player->disp.visual.char_class),
player->disp.stats.level + 1,
char_for_language_code(player->inventory.language));
}