implement full character backups on GC

This commit is contained in:
Martin Michelsen
2024-04-06 19:52:22 -07:00
parent 91131f8b36
commit af4d3a3325
64 changed files with 3155 additions and 2208 deletions
+106 -499
View File
@@ -22,184 +22,6 @@
using namespace std;
PlayerInventoryItem::PlayerInventoryItem(const ItemData& item, bool equipped)
: present(1),
unknown_a1(0),
extension_data1(0),
extension_data2(0),
flags(equipped ? 8 : 0),
data(item) {}
uint32_t PlayerVisualConfig::compute_name_color_checksum(uint32_t name_color) {
uint8_t x = (random_object<uint32_t>() % 0xFF) + 1;
uint8_t y = (random_object<uint32_t>() % 0xFF) + 1;
// name_color (ARGB) = ABCDEFGHabcdefghIJKLMNOPijklmnop
// name_color_checksum = 000000000ijklmabcdeIJKLM00000000 ^ xxxxxxxxyyyyyyyyxxxxxxxxyyyyyyyy
uint32_t xbrgx95558 = ((name_color << 15) & 0x007C0000) | ((name_color >> 6) & 0x0003E000) | ((name_color >> 3) & 0x00001F00);
uint32_t mask = (x << 24) | (y << 16) | (x << 8) | y;
return xbrgx95558 ^ mask;
}
void PlayerVisualConfig::compute_name_color_checksum() {
this->name_color_checksum = this->compute_name_color_checksum(this->name_color);
}
void PlayerVisualConfig::enforce_lobby_join_limits_for_version(Version v) {
struct ClassMaxes {
uint16_t costume;
uint16_t skin;
uint16_t face;
uint16_t head;
uint16_t hair;
};
static constexpr ClassMaxes v1_v2_class_maxes[14] = {
{0x0009, 0x0004, 0x0005, 0x0000, 0x0007},
{0x0009, 0x0004, 0x0005, 0x0000, 0x000A},
{0x0000, 0x0009, 0x0000, 0x0005, 0x0000},
{0x0009, 0x0004, 0x0005, 0x0000, 0x0007},
{0x0000, 0x0009, 0x0000, 0x0005, 0x0000},
{0x0000, 0x0009, 0x0000, 0x0005, 0x0000},
{0x0009, 0x0004, 0x0005, 0x0000, 0x000A},
{0x0009, 0x0004, 0x0005, 0x0000, 0x0007},
{0x0009, 0x0004, 0x0005, 0x0000, 0x000A},
{0x0000, 0x0000, 0x0000, 0x0000, 0x0000},
{0x0000, 0x0000, 0x0000, 0x0000, 0x0000},
{0x0000, 0x0000, 0x0000, 0x0000, 0x0000},
{0x0000, 0x0000, 0x0000, 0x0000, 0x0000},
{0x0000, 0x0000, 0x0000, 0x0000, 0x0000},
};
static constexpr ClassMaxes v3_v4_class_maxes[19] = {
{0x0012, 0x0004, 0x0005, 0x0000, 0x000A},
{0x0012, 0x0004, 0x0005, 0x0000, 0x000A},
{0x0000, 0x0019, 0x0000, 0x0005, 0x0000},
{0x0012, 0x0004, 0x0005, 0x0000, 0x000A},
{0x0000, 0x0019, 0x0000, 0x0005, 0x0000},
{0x0000, 0x0019, 0x0000, 0x0005, 0x0000},
{0x0012, 0x0004, 0x0005, 0x0000, 0x000A},
{0x0012, 0x0004, 0x0005, 0x0000, 0x000A},
{0x0012, 0x0004, 0x0005, 0x0000, 0x000A},
{0x0000, 0x0019, 0x0000, 0x0005, 0x0000},
{0x0012, 0x0004, 0x0005, 0x0000, 0x000A},
{0x0012, 0x0004, 0x0005, 0x0000, 0x000A},
{0x0000, 0x0000, 0x0000, 0x0000, 0x0001},
{0x0000, 0x0000, 0x0000, 0x0000, 0x0001},
{0x0000, 0x0000, 0x0000, 0x0000, 0x0000},
{0x0000, 0x0000, 0x0000, 0x0000, 0x0000},
{0x0000, 0x0000, 0x0000, 0x0000, 0x0000},
{0x0000, 0x0000, 0x0000, 0x0000, 0x0000},
{0x0000, 0x0000, 0x0000, 0x0000, 0x0000}};
const ClassMaxes* maxes;
if (v == Version::GC_NTE) {
// GC NTE has HUcaseal, FOmar, and RAmarl, but missing others
if (this->char_class >= 12) {
this->char_class = 0; // Invalid classes -> HUmar
}
// GC NTE is basically v2, but uses v3 maxes
this->version = min<uint8_t>(this->version, 2);
maxes = &v3_v4_class_maxes[this->char_class];
// Prevent GC NTE from crashing from extra models
this->extra_model = 0;
this->validation_flags &= 0xFD;
} else if (is_v1_or_v2(v)) {
// V1/V2 have fewer classes, so we'll substitute some here
switch (this->char_class) {
case 0: // HUmar
case 1: // HUnewearl
case 2: // HUcast
case 3: // RAmar
case 4: // RAcast
case 5: // RAcaseal
case 6: // FOmarl
case 7: // FOnewm
case 8: // FOnewearl
case 12: // V3 custom 1
case 13: // V3 custom 2
break;
case 9: // HUcaseal
this->char_class = 5; // HUcaseal -> RAcaseal
break;
case 10: // FOmar
this->char_class = 0; // FOmar -> HUmar
break;
case 11: // RAmarl
this->char_class = 1; // RAmarl -> HUnewearl
break;
case 14: // V2 custom 1 / V3 custom 3
case 15: // V2 custom 2 / V3 custom 4
case 16: // V2 custom 3 / V3 custom 5
case 17: // V2 custom 4 / V3 custom 6
case 18: // V2 custom 5 / V3 custom 7
this->char_class -= 5;
break;
default:
this->char_class = 0; // Invalid classes -> HUmar
}
this->version = min<uint8_t>(this->version, is_v1(v) ? 0 : 2);
maxes = &v1_v2_class_maxes[this->char_class];
} else {
if (this->char_class >= 19) {
this->char_class = 0; // Invalid classes -> HUmar
}
this->version = min<uint8_t>(this->version, 3);
maxes = &v3_v4_class_maxes[this->char_class];
}
// V1/V2 has fewer costumes and android skins, so substitute them here
this->costume = maxes->costume ? (this->costume % maxes->costume) : 0;
this->skin = maxes->skin ? (this->skin % maxes->skin) : 0;
this->face = maxes->face ? (this->face % maxes->face) : 0;
this->head = maxes->head ? (this->head % maxes->head) : 0;
this->hair = maxes->hair ? (this->hair % maxes->hair) : 0;
if (this->name_color == 0) {
this->name_color = 0xFFFFFFFF;
}
if (is_v1_or_v2(v)) {
this->compute_name_color_checksum();
} else {
this->name_color_checksum = 0;
}
this->class_flags = class_flags_for_class(this->char_class);
this->name.clear_after_bytes(0x0C);
}
void PlayerDispDataDCPCV3::enforce_lobby_join_limits_for_version(Version v) {
this->visual.enforce_lobby_join_limits_for_version(v);
}
void PlayerDispDataBB::enforce_lobby_join_limits_for_version(Version v) {
this->visual.enforce_lobby_join_limits_for_version(v);
this->name.clear_after_bytes(0x18); // 12 characters
}
PlayerDispDataBB PlayerDispDataDCPCV3::to_bb(uint8_t to_language, uint8_t from_language) const {
PlayerDispDataBB bb;
bb.stats = this->stats;
bb.visual = this->visual;
bb.visual.name.encode(" 0");
string decoded_name = this->visual.name.decode(from_language);
bb.name.encode(decoded_name, to_language);
bb.config = this->config;
bb.technique_levels_v1 = this->technique_levels_v1;
return bb;
}
PlayerDispDataDCPCV3 PlayerDispDataBB::to_dcpcv3(uint8_t to_language, uint8_t from_language) const {
PlayerDispDataDCPCV3 ret;
ret.stats = this->stats;
ret.visual = this->visual;
string decoded_name = this->name.decode(from_language);
ret.visual.name.encode(decoded_name, to_language);
ret.config = this->config;
ret.technique_levels_v1 = this->technique_levels_v1;
return ret;
}
void PlayerDispDataBB::apply_dressing_room(const PlayerDispDataBBPreview& pre) {
this->visual.name_color = pre.visual.name_color;
this->visual.extra_model = pre.visual.extra_model;
@@ -233,6 +55,106 @@ void GuildCardBB::clear() {
this->char_class = 0;
}
GuildCardDCNTE::operator GuildCardBB() const {
GuildCardBB ret;
ret.guild_card_number = this->guild_card_number;
ret.name.encode(this->name.decode(this->language), this->language);
ret.description.encode(this->description.decode(this->language), this->language);
ret.present = this->present;
ret.language = this->language;
ret.section_id = this->section_id;
ret.char_class = this->char_class;
return ret;
}
GuildCardDC::operator GuildCardBB() const {
GuildCardBB ret;
ret.guild_card_number = this->guild_card_number;
ret.name.encode(this->name.decode(this->language), this->language);
ret.description.encode(this->description.decode(this->language), this->language);
ret.present = this->present;
ret.language = this->language;
ret.section_id = this->section_id;
ret.char_class = this->char_class;
return ret;
}
GuildCardPC::operator GuildCardBB() const {
GuildCardBB ret;
ret.guild_card_number = this->guild_card_number;
ret.name.encode(this->name.decode(this->language), this->language);
ret.description.encode(this->description.decode(this->language), this->language);
ret.present = this->present;
ret.language = this->language;
ret.section_id = this->section_id;
ret.char_class = this->char_class;
return ret;
}
GuildCardXB::operator GuildCardBB() const {
GuildCardBB ret;
ret.guild_card_number = this->guild_card_number;
ret.name.encode(this->name.decode(this->language), this->language);
ret.description.encode(this->description.decode(this->language), this->language);
ret.present = this->present;
ret.language = this->language;
ret.section_id = this->section_id;
ret.char_class = this->char_class;
return ret;
}
GuildCardBB::operator GuildCardDCNTE() const {
GuildCardDCNTE ret;
ret.player_tag = 0x00010000;
ret.guild_card_number = this->guild_card_number;
ret.name.encode(this->name.decode(this->language), this->language);
ret.description.encode(this->description.decode(this->language), this->language);
ret.present = this->present;
ret.language = this->language;
ret.section_id = this->section_id;
ret.char_class = this->char_class;
return ret;
}
GuildCardBB::operator GuildCardDC() const {
GuildCardDC ret;
ret.player_tag = 0x00010000;
ret.guild_card_number = this->guild_card_number;
ret.name.encode(this->name.decode(this->language), this->language);
ret.description.encode(this->description.decode(this->language), this->language);
ret.present = this->present;
ret.language = this->language;
ret.section_id = this->section_id;
ret.char_class = this->char_class;
return ret;
}
GuildCardBB::operator GuildCardPC() const {
GuildCardPC ret;
ret.player_tag = 0x00010000;
ret.guild_card_number = this->guild_card_number;
ret.name.encode(this->name.decode(this->language), this->language);
ret.description.encode(this->description.decode(this->language), this->language);
ret.present = this->present;
ret.language = this->language;
ret.section_id = this->section_id;
ret.char_class = this->char_class;
return ret;
}
GuildCardBB::operator GuildCardXB() const {
GuildCardXB ret;
ret.player_tag = 0x00010000;
ret.guild_card_number = this->guild_card_number;
ret.name.encode(this->name.decode(this->language), this->language);
ret.description.encode(this->description.decode(this->language), this->language);
ret.present = this->present;
ret.language = this->language;
ret.section_id = this->section_id;
ret.char_class = this->char_class;
return ret;
}
void PlayerLobbyDataPC::clear() {
this->player_tag = 0;
this->guild_card_number = 0;
@@ -279,7 +201,7 @@ void PlayerLobbyDataBB::clear() {
this->hide_help_prompt = 0;
}
PlayerRecordsBB_Challenge::PlayerRecordsBB_Challenge(const PlayerRecordsDC_Challenge& rec)
PlayerRecordsChallengeBB::PlayerRecordsChallengeBB(const PlayerRecordsChallengeDC& rec)
: title_color(rec.title_color),
unknown_u0(rec.unknown_u0),
times_ep1_online(rec.times_ep1_online),
@@ -303,7 +225,7 @@ PlayerRecordsBB_Challenge::PlayerRecordsBB_Challenge(const PlayerRecordsDC_Chall
rank_title(rec.rank_title.decode(), 1),
unknown_l7(0) {}
PlayerRecordsBB_Challenge::PlayerRecordsBB_Challenge(const PlayerRecordsPC_Challenge& rec)
PlayerRecordsChallengeBB::PlayerRecordsChallengeBB(const PlayerRecordsChallengePC& rec)
: title_color(rec.title_color),
unknown_u0(rec.unknown_u0),
times_ep1_online(rec.times_ep1_online),
@@ -327,35 +249,8 @@ PlayerRecordsBB_Challenge::PlayerRecordsBB_Challenge(const PlayerRecordsPC_Chall
rank_title(rec.rank_title.decode(), 1),
unknown_l7(0) {}
PlayerRecordsBB_Challenge::PlayerRecordsBB_Challenge(const PlayerRecordsV3_Challenge<false>& rec)
: title_color(rec.stats.title_color),
unknown_u0(rec.stats.unknown_u0),
times_ep1_online(rec.stats.times_ep1_online),
times_ep2_online(rec.stats.times_ep2_online),
times_ep1_offline(rec.stats.times_ep1_offline),
grave_is_ep2(rec.stats.grave_is_ep2),
grave_stage_num(rec.stats.grave_stage_num),
grave_floor(rec.stats.grave_floor),
unknown_g0(rec.stats.unknown_g0),
grave_deaths(rec.stats.grave_deaths),
unknown_u4(rec.stats.unknown_u4),
grave_time(rec.stats.grave_time),
grave_defeated_by_enemy_rt_index(rec.stats.grave_defeated_by_enemy_rt_index),
grave_x(rec.stats.grave_x),
grave_y(rec.stats.grave_y),
grave_z(rec.stats.grave_z),
grave_team(rec.stats.grave_team.decode(), 1),
grave_message(rec.stats.grave_message.decode(), 1),
unknown_m5(rec.stats.unknown_m5),
unknown_t6(rec.stats.unknown_t6),
ep1_online_award_state(rec.stats.ep1_online_award_state),
ep2_online_award_state(rec.stats.ep2_online_award_state),
ep1_offline_award_state(rec.stats.ep1_offline_award_state),
rank_title(rec.rank_title.decode(), 1),
unknown_l7(rec.unknown_l7) {}
PlayerRecordsBB_Challenge::operator PlayerRecordsDC_Challenge() const {
PlayerRecordsDC_Challenge ret;
PlayerRecordsChallengeBB::operator PlayerRecordsChallengeDC() const {
PlayerRecordsChallengeDC ret;
ret.title_color = this->title_color;
ret.unknown_u0 = this->unknown_u0;
ret.rank_title.encode(this->rank_title.decode());
@@ -384,8 +279,8 @@ PlayerRecordsBB_Challenge::operator PlayerRecordsDC_Challenge() const {
return ret;
}
PlayerRecordsBB_Challenge::operator PlayerRecordsPC_Challenge() const {
PlayerRecordsPC_Challenge ret;
PlayerRecordsChallengeBB::operator PlayerRecordsChallengePC() const {
PlayerRecordsChallengePC ret;
ret.title_color = this->title_color;
ret.unknown_u0 = this->unknown_u0;
ret.rank_title.encode(this->rank_title.decode());
@@ -414,289 +309,6 @@ PlayerRecordsBB_Challenge::operator PlayerRecordsPC_Challenge() const {
return ret;
}
PlayerRecordsBB_Challenge::operator PlayerRecordsV3_Challenge<false>() const {
PlayerRecordsV3_Challenge<false> ret;
ret.stats.title_color = this->title_color;
ret.stats.unknown_u0 = this->unknown_u0;
ret.stats.times_ep1_online = this->times_ep1_online;
ret.stats.times_ep2_online = this->times_ep2_online;
ret.stats.times_ep1_offline = this->times_ep1_offline;
ret.stats.grave_is_ep2 = this->grave_is_ep2;
ret.stats.grave_stage_num = this->grave_stage_num;
ret.stats.grave_floor = this->grave_floor;
ret.stats.unknown_g0 = this->unknown_g0;
ret.stats.grave_deaths = this->grave_deaths;
ret.stats.unknown_u4 = this->unknown_u4;
ret.stats.grave_time = this->grave_time;
ret.stats.grave_defeated_by_enemy_rt_index = this->grave_defeated_by_enemy_rt_index;
ret.stats.grave_x = this->grave_x;
ret.stats.grave_y = this->grave_y;
ret.stats.grave_z = this->grave_z;
ret.stats.grave_team.encode(this->grave_team.decode(), 1);
ret.stats.grave_message.encode(this->grave_message.decode(), 1);
ret.stats.unknown_m5 = this->unknown_m5;
ret.stats.unknown_t6 = this->unknown_t6;
ret.stats.ep1_online_award_state = this->ep1_online_award_state;
ret.stats.ep2_online_award_state = this->ep2_online_award_state;
ret.stats.ep1_offline_award_state = this->ep1_offline_award_state;
ret.rank_title.encode(this->rank_title.decode(), 1);
ret.unknown_l7 = this->unknown_l7;
return ret;
}
void PlayerBank::add_item(const ItemData& item, const ItemData::StackLimits& limits) {
uint32_t primary_identifier = item.primary_identifier();
if (primary_identifier == 0x04000000) {
this->meseta += item.data2d;
if (this->meseta > 999999) {
this->meseta = 999999;
}
return;
}
size_t combine_max = item.max_stack_size(limits);
if (combine_max > 1) {
size_t y;
for (y = 0; y < this->num_items; y++) {
if (this->items[y].data.primary_identifier() == primary_identifier) {
break;
}
}
if (y < this->num_items) {
uint8_t new_count = this->items[y].data.data1[5] + item.data1[5];
if (new_count > combine_max) {
throw runtime_error("stack size would exceed limit");
}
this->items[y].data.data1[5] = new_count;
this->items[y].amount = new_count;
return;
}
}
if (this->num_items >= 200) {
throw runtime_error("no free space in bank");
}
auto& last_item = this->items[this->num_items];
last_item.data = item;
last_item.amount = (item.max_stack_size(limits) > 1) ? item.data1[5] : 1;
last_item.present = 1;
this->num_items++;
}
ItemData PlayerBank::remove_item(uint32_t item_id, uint32_t amount, const ItemData::StackLimits& limits) {
size_t index = this->find_item(item_id);
auto& bank_item = this->items[index];
ItemData ret;
if (amount && (bank_item.data.stack_size(limits) > 1) && (amount < bank_item.data.data1[5])) {
ret = bank_item.data;
ret.data1[5] = amount;
bank_item.data.data1[5] -= amount;
bank_item.amount -= amount;
return ret;
}
ret = bank_item.data;
this->num_items--;
for (size_t x = index; x < this->num_items; x++) {
this->items[x] = this->items[x + 1];
}
auto& last_item = this->items[this->num_items];
last_item.amount = 0;
last_item.present = 0;
last_item.data.clear();
return ret;
}
size_t PlayerInventory::find_item(uint32_t item_id) const {
for (size_t x = 0; x < this->num_items; x++) {
if (this->items[x].data.id == item_id) {
return x;
}
}
throw out_of_range("item not present");
}
size_t PlayerInventory::find_item_by_primary_identifier(uint32_t primary_identifier) const {
for (size_t x = 0; x < this->num_items; x++) {
if (this->items[x].data.primary_identifier() == primary_identifier) {
return x;
}
}
throw out_of_range("item not present");
}
size_t PlayerInventory::find_equipped_item(EquipSlot slot) const {
ssize_t ret = -1;
for (size_t y = 0; y < this->num_items; y++) {
const auto& i = this->items[y];
if (!(i.flags & 0x00000008)) {
continue;
}
if (!i.data.can_be_equipped_in_slot(slot)) {
continue;
}
// Units can be equipped in multiple slots, so the currently-equipped slot
// is stored in the item data itself.
if (((slot == EquipSlot::UNIT_1) && (i.data.data1[4] != 0x00)) ||
((slot == EquipSlot::UNIT_2) && (i.data.data1[4] != 0x01)) ||
((slot == EquipSlot::UNIT_3) && (i.data.data1[4] != 0x02)) ||
((slot == EquipSlot::UNIT_4) && (i.data.data1[4] != 0x03))) {
continue;
}
if (ret < 0) {
ret = y;
} else {
throw runtime_error("multiple items are equipped in the same slot");
}
}
if (ret < 0) {
throw out_of_range("no item is equipped in this slot");
}
return ret;
}
bool PlayerInventory::has_equipped_item(EquipSlot slot) const {
try {
this->find_equipped_item(slot);
return true;
} catch (const out_of_range&) {
return false;
}
}
void PlayerInventory::equip_item_id(uint32_t item_id, EquipSlot slot, bool allow_overwrite) {
this->equip_item_index(this->find_item(item_id), slot, allow_overwrite);
}
void PlayerInventory::equip_item_index(size_t index, EquipSlot slot, bool allow_overwrite) {
auto& item = this->items[index];
if ((slot == EquipSlot::UNKNOWN) || !item.data.can_be_equipped_in_slot(slot)) {
slot = item.data.default_equip_slot();
}
if (this->has_equipped_item(slot)) {
if (allow_overwrite) {
this->unequip_item_slot(slot);
} else {
throw runtime_error("equip slot is already in use");
}
}
item.flags |= 0x00000008;
// Units store which slot they're equipped in within the item data itself
if ((item.data.data1[0] == 0x01) && (item.data.data1[1] == 0x03)) {
item.data.data1[4] = static_cast<uint8_t>(slot) - 9;
}
}
void PlayerInventory::unequip_item_id(uint32_t item_id) {
this->unequip_item_index(this->find_item(item_id));
}
void PlayerInventory::unequip_item_slot(EquipSlot slot) {
this->unequip_item_index(this->find_equipped_item(slot));
}
void PlayerInventory::unequip_item_index(size_t index) {
auto& item = this->items[index];
item.flags &= (~0x00000008);
// Units store which slot they're equipped in within the item data itself
if ((item.data.data1[0] == 0x01) && (item.data.data1[1] == 0x03)) {
item.data.data1[4] = 0x00;
}
// If the item is an armor, remove all units too
if ((item.data.data1[0] == 0x01) && (item.data.data1[1] == 0x01)) {
for (size_t z = 0; z < 30; z++) {
auto& unit = this->items[z];
if ((unit.data.data1[0] == 0x01) && (unit.data.data1[1] == 0x03)) {
unit.flags &= (~0x00000008);
unit.data.data1[4] = 0x00;
}
}
}
}
size_t PlayerInventory::remove_all_items_of_type(uint8_t data1_0, int16_t data1_1) {
size_t write_offset = 0;
for (size_t read_offset = 0; read_offset < this->num_items; read_offset++) {
bool should_delete = ((this->items[read_offset].data.data1[0] == data1_0) &&
((data1_1 < 0) || (this->items[read_offset].data.data1[1] == static_cast<uint8_t>(data1_1))));
if (!should_delete) {
if (read_offset != write_offset) {
this->items[write_offset].present = this->items[read_offset].present;
this->items[write_offset].unknown_a1 = this->items[read_offset].unknown_a1;
this->items[write_offset].flags = this->items[read_offset].flags;
this->items[write_offset].data = this->items[read_offset].data;
}
write_offset++;
}
}
size_t ret = this->num_items - write_offset;
this->num_items = write_offset;
return ret;
}
void PlayerInventory::decode_from_client(shared_ptr<Client> c) {
for (size_t z = 0; z < this->items.size(); z++) {
this->items[z].data.decode_for_version(c->version());
}
}
void PlayerInventory::encode_for_client(shared_ptr<Client> c) {
Version v = c->version();
if (v == Version::DC_NTE) {
// DC NTE has the item count as a 32-bit value here, whereas every other
// version uses a single byte. To stop DC NTE from crashing by trying to
// construct far more than 30 TItem objects, we clear the fields DC NTE
// doesn't know about. Note that the 11/2000 prototype does not have this
// issue - its inventory format matches the rest of the versions.
this->hp_from_materials = 0;
this->tp_from_materials = 0;
this->language = 0;
} else if ((v != Version::PC_NTE) && (v != Version::PC_V2)) {
if (this->language > 4) {
this->language = 0;
}
} else {
if (this->language > 7) {
this->language = 0;
}
}
// For pre-V2 clients, use the V2 parameter table, since the V1 table doesn't
// have correct encodings for backward-compatible V2 items.
auto item_parameter_table = c->require_server_state()->item_parameter_table_for_encode(v);
for (size_t z = 0; z < this->items.size(); z++) {
this->items[z].data.encode_for_version(v, item_parameter_table);
}
}
size_t PlayerBank::find_item(uint32_t item_id) {
for (size_t x = 0; x < this->num_items; x++) {
if (this->items[x].data.id == item_id) {
return x;
}
}
throw out_of_range("item not present");
}
void PlayerBank::sort() {
std::sort(this->items.data(), this->items.data() + this->num_items);
}
void PlayerBank::assign_ids(uint32_t base_id) {
for (size_t z = 0; z < this->num_items; z++) {
this->items[z].data.id = base_id + z;
}
}
QuestFlagsV1& QuestFlagsV1::operator=(const QuestFlags& other) {
this->data[0] = other.data[0];
this->data[1] = other.data[1];
@@ -1087,11 +699,6 @@ const ChallengeTemplateDefinition& get_challenge_template_definition(Version ver
}
}
SymbolChat::SymbolChat()
: spec(0),
corner_objects(0x00FF),
face_parts() {}
void RecentSwitchFlags::add(uint16_t flag_num) {
if ((flag_num != ((this->flag_nums >> 48) & 0xFFFF)) &&
(flag_num != ((this->flag_nums >> 32) & 0xFFFF)) &&