split disp data into stats and visual substructures
This commit is contained in:
+16
-16
@@ -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
@@ -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
|
||||
|
||||
@@ -182,7 +182,7 @@ public:
|
||||
uint32_t num_destroyed_fcs;
|
||||
uint8_t unknown_a16;
|
||||
uint8_t unknown_a17;
|
||||
PlayerStats stats;
|
||||
PlayerBattleStats stats;
|
||||
};
|
||||
|
||||
} // namespace Episode3
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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));
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user