implement BB item usage and mag feeding
This commit is contained in:
@@ -4266,6 +4266,7 @@ struct G_SyncGameStateHeader_6x6B_6x6C_6x6D_6x6E {
|
||||
|
||||
// Decompressed format is a list of these
|
||||
struct G_SyncEnemyState_6x6B_Entry_Decompressed {
|
||||
// TODO: Verify this format on DC and PC. It appears correct for GC and BB.
|
||||
le_uint32_t unknown_a1; // Possibly some kind of flags
|
||||
// enemy_index is not the same as enemy_id, unfortunately - the enemy_id sent
|
||||
// in the 6x76 command when an enemy is killed does not match enemy_index
|
||||
@@ -4290,6 +4291,7 @@ struct G_SyncObjectState_6x6C_Entry_Decompressed {
|
||||
// Compressed format is the same as 6x6B.
|
||||
|
||||
struct G_SyncItemState_6x6D_Decompressed {
|
||||
// TODO: Verify this format on DC and PC. It appears correct for GC and BB.
|
||||
// Note: 16 vs. 15 is not a bug here - there really is an extra field in the
|
||||
// total drop count vs. the floor item count. Despite this, Pioneer 2 or Lab
|
||||
// (area 0) isn't included in total_items_dropped_per_area (so Forest 1 is [0]
|
||||
|
||||
+140
-34
@@ -76,6 +76,17 @@ size_t ItemData::max_stack_size() const {
|
||||
return max_stack_size_for_item(this->data1[0], this->data1[1]);
|
||||
}
|
||||
|
||||
bool ItemData::is_common_consumable(uint32_t primary_identifier) {
|
||||
if (primary_identifier == 0x030200) {
|
||||
return false;
|
||||
}
|
||||
return (primary_identifier >= 0x030000) && (primary_identifier < 0x030A00);
|
||||
}
|
||||
|
||||
bool ItemData::is_common_consumable() const {
|
||||
return this->is_common_consumable(this->primary_identifier());
|
||||
}
|
||||
|
||||
void ItemData::assign_mag_stats(const ItemMagStats& mag) {
|
||||
// this->data1[0] and [1] unchanged
|
||||
this->data1[2] = mag.level();
|
||||
@@ -98,6 +109,114 @@ void ItemData::clear_mag_stats() {
|
||||
}
|
||||
}
|
||||
|
||||
uint16_t ItemData::compute_mag_level() const {
|
||||
return (this->data1w[2] / 100) +
|
||||
(this->data1w[3] / 100) +
|
||||
(this->data1w[4] / 100) +
|
||||
(this->data1w[5] / 100);
|
||||
}
|
||||
|
||||
uint16_t ItemData::compute_mag_strength_flags() const {
|
||||
uint16_t pow = this->data1w[3] / 100;
|
||||
uint16_t dex = this->data1w[4] / 100;
|
||||
uint16_t mind = this->data1w[5] / 100;
|
||||
|
||||
uint16_t ret = 0;
|
||||
if ((dex < pow) && (mind < pow)) {
|
||||
ret = 0x008;
|
||||
}
|
||||
if ((pow < dex) && (mind < dex)) {
|
||||
ret |= 0x010;
|
||||
}
|
||||
if ((dex < mind) && (pow < mind)) {
|
||||
ret |= 0x020;
|
||||
}
|
||||
|
||||
uint16_t highest = max<uint16_t>(dex, max<uint16_t>(pow, mind));
|
||||
if ((pow == highest) + (dex == highest) + (mind == highest) > 1) {
|
||||
ret |= 0x100;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
uint8_t ItemData::mag_photon_blast_for_slot(uint8_t slot) const {
|
||||
uint8_t flags = this->data2[2];
|
||||
uint8_t pb_nums = this->data1[3];
|
||||
|
||||
if (slot == 0) { // Center
|
||||
return (flags & 1) ? (pb_nums & 0x07) : 0xFF;
|
||||
|
||||
} else if (slot == 1) { // Right
|
||||
return (flags & 2) ? ((pb_nums & 0x38) >> 3) : 0xFF;
|
||||
|
||||
} else if (slot == 2) { // Left
|
||||
if (!(flags & 4)) {
|
||||
return 0xFF;
|
||||
}
|
||||
|
||||
uint8_t used_pbs[6] = {0, 0, 0, 0, 0, 0};
|
||||
used_pbs[pb_nums & 0x07] = '\x01';
|
||||
used_pbs[(pb_nums & 0x38) >> 3] = '\x01';
|
||||
uint8_t left_pb_num = (pb_nums & 0xC0) >> 6;
|
||||
for (size_t z = 0; z < 6; z++) {
|
||||
if (!used_pbs[z]) {
|
||||
if (!left_pb_num) {
|
||||
return z;
|
||||
}
|
||||
left_pb_num--;
|
||||
}
|
||||
}
|
||||
throw logic_error("failed to find unused photon blast number");
|
||||
|
||||
} else {
|
||||
throw logic_error("invalid slot index");
|
||||
}
|
||||
}
|
||||
|
||||
bool ItemData::mag_has_photon_blast_in_any_slot(uint8_t pb_num) const {
|
||||
if (pb_num < 6) {
|
||||
for (size_t slot = 0; slot < 3; slot++) {
|
||||
if (this->mag_photon_blast_for_slot(slot) == pb_num) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void ItemData::add_mag_photon_blast(uint8_t pb_num) {
|
||||
if (pb_num >= 6) {
|
||||
return;
|
||||
}
|
||||
if (this->mag_has_photon_blast_in_any_slot(pb_num)) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t& flags = this->data2[2];
|
||||
uint8_t& pb_nums = this->data1[3];
|
||||
|
||||
if (!(flags & 1)) { // Center
|
||||
pb_nums |= pb_num;
|
||||
flags |= 1;
|
||||
} else if (!(flags & 2)) { // Right
|
||||
pb_nums |= (pb_num << 3);
|
||||
flags |= 2;
|
||||
} else if (!(flags & 4)) {
|
||||
uint8_t orig_pb_num = pb_num;
|
||||
if (this->mag_photon_blast_for_slot(0) < orig_pb_num) {
|
||||
pb_num--;
|
||||
}
|
||||
if (this->mag_photon_blast_for_slot(1) < orig_pb_num) {
|
||||
pb_num--;
|
||||
}
|
||||
if (pb_num >= 4) {
|
||||
throw runtime_error("left photon blast number is too high");
|
||||
pb_nums |= (pb_num << 6);
|
||||
}
|
||||
flags |= 4;
|
||||
}
|
||||
}
|
||||
|
||||
void ItemData::set_sealed_item_kill_count(uint16_t v) {
|
||||
this->data1[10] = (v >> 8) | 0x80;
|
||||
this->data1[11] = v;
|
||||
@@ -1245,6 +1364,15 @@ const unordered_map<uint32_t, ItemNameInfo> name_info_for_primary_identifier({
|
||||
{0x031903, "Team Points 10000"},
|
||||
});
|
||||
|
||||
string ItemData::hex() const {
|
||||
return string_printf("%02hhX%02hhX%02hhX%02hhX %02hhX%02hhX%02hhX%02hhX %02hhX%02hhX%02hhX%02hhX (%08" PRIX32 ") %02hhX%02hhX%02hhX%02hhX",
|
||||
this->data1[0], this->data1[1], this->data1[2], this->data1[3],
|
||||
this->data1[4], this->data1[5], this->data1[6], this->data1[7],
|
||||
this->data1[8], this->data1[9], this->data1[10], this->data1[11],
|
||||
this->id.load(),
|
||||
this->data2[0], this->data2[1], this->data2[2], this->data2[3]);
|
||||
}
|
||||
|
||||
string ItemData::name(bool include_color_codes) const {
|
||||
if (this->data1[0] == 0x04) {
|
||||
return string_printf("%s%" PRIu32 " Meseta",
|
||||
@@ -1271,7 +1399,7 @@ string ItemData::name(bool include_color_codes) const {
|
||||
}
|
||||
}
|
||||
// Mags can be wrapped as well
|
||||
if ((this->data1[0] == 0x02) && (this->data2[1] & 0x40)) {
|
||||
if ((this->data1[0] == 0x02) && (this->data2[2] & 0x40)) {
|
||||
ret_tokens.emplace_back("Wrapped");
|
||||
}
|
||||
|
||||
@@ -1391,49 +1519,27 @@ string ItemData::name(bool include_color_codes) const {
|
||||
ret_tokens.emplace_back(string_printf("%d/%d/%d/%d",
|
||||
this->data1w[2] / 100, this->data1w[3] / 100,
|
||||
this->data1w[4] / 100, this->data1w[5] / 100));
|
||||
ret_tokens.emplace_back(string_printf("%hhu%%", this->data2[3]));
|
||||
ret_tokens.emplace_back(string_printf("%hhuIQ", this->data2[2]));
|
||||
ret_tokens.emplace_back(string_printf("%hhu%%", this->data2[0]));
|
||||
ret_tokens.emplace_back(string_printf("%hhuIQ", this->data2[1]));
|
||||
|
||||
uint8_t flags = this->data2[1];
|
||||
uint8_t flags = this->data2[2];
|
||||
if (flags & 7) {
|
||||
static const vector<const char*> pb_shortnames = {
|
||||
"F", "E", "G", "P", "L", "M&Y", "MG", "GR"};
|
||||
|
||||
const char* pb_names[3] = {nullptr, nullptr, nullptr};
|
||||
uint8_t center_pb = (flags & 2) ? (this->data1[3] & 7) : 0xFF;
|
||||
uint8_t right_pb = (flags & 1) ? ((this->data1[3] >> 3) & 7) : 0xFF;
|
||||
uint8_t left_pb = (flags & 4) ? ((this->data1[3] >> 6) & 3) : 0xFF;
|
||||
uint8_t left_pb = this->mag_photon_blast_for_slot(2);
|
||||
uint8_t center_pb = this->mag_photon_blast_for_slot(0);
|
||||
uint8_t right_pb = this->mag_photon_blast_for_slot(1);
|
||||
if (left_pb != 0xFF) {
|
||||
pb_names[0] = pb_shortnames[left_pb];
|
||||
}
|
||||
if (center_pb != 0xFF) {
|
||||
pb_names[1] = pb_shortnames[center_pb];
|
||||
}
|
||||
if (right_pb != 0xFF) {
|
||||
pb_names[2] = pb_shortnames[right_pb];
|
||||
}
|
||||
if (left_pb != 0xFF) {
|
||||
// There are only two bits for the left PB (as opposed to 3 for the
|
||||
// center and right PBs). This works because PBs can't be duplicated;
|
||||
// there are 6 valid PBs for each slot, but the center and right slots
|
||||
// are used first, leaving 4 valid options for the left slot. To encode
|
||||
// this in two bits, the game takes the list of all PBs, removes the
|
||||
// center and right PBs from the list, and the left PB is then used as
|
||||
// an index into this modified list to determine the actual left PB.
|
||||
// Here, we don't construct a temporary list and instead just skip the
|
||||
// center and right PB values with a loop.
|
||||
uint8_t actual_left_pb = 0;
|
||||
for (;;) {
|
||||
if ((actual_left_pb == center_pb) || (actual_left_pb == right_pb)) {
|
||||
actual_left_pb++;
|
||||
continue;
|
||||
}
|
||||
if (left_pb > 0) {
|
||||
actual_left_pb++;
|
||||
left_pb--;
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
pb_names[0] = pb_shortnames[actual_left_pb];
|
||||
}
|
||||
|
||||
string token = "PB:";
|
||||
for (size_t x = 0; x < 3; x++) {
|
||||
@@ -1469,9 +1575,9 @@ string ItemData::name(bool include_color_codes) const {
|
||||
/* 12 */ "costume color",
|
||||
});
|
||||
try {
|
||||
ret_tokens.emplace_back(string_printf("(%s)", mag_colors.at(this->data2[0])));
|
||||
ret_tokens.emplace_back(string_printf("(%s)", mag_colors.at(this->data2[3])));
|
||||
} catch (const out_of_range&) {
|
||||
ret_tokens.emplace_back(string_printf("(!CL:%02hhX)", this->data2[0]));
|
||||
ret_tokens.emplace_back(string_printf("(!CL:%02hhX)", this->data2[3]));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -68,6 +68,7 @@ struct ItemData { // 0x14 bytes
|
||||
|
||||
void clear();
|
||||
|
||||
std::string hex() const;
|
||||
std::string name(bool include_color_codes) const;
|
||||
uint32_t primary_identifier() const;
|
||||
|
||||
@@ -75,8 +76,16 @@ struct ItemData { // 0x14 bytes
|
||||
size_t stack_size() const;
|
||||
size_t max_stack_size() const;
|
||||
|
||||
static bool is_common_consumable(uint32_t primary_identifier);
|
||||
bool is_common_consumable() const;
|
||||
|
||||
void assign_mag_stats(const ItemMagStats& mag);
|
||||
void clear_mag_stats();
|
||||
uint16_t compute_mag_level() const;
|
||||
uint16_t compute_mag_strength_flags() const;
|
||||
uint8_t mag_photon_blast_for_slot(uint8_t slot) const;
|
||||
bool mag_has_photon_blast_in_any_slot(uint8_t pb_num) const;
|
||||
void add_mag_photon_blast(uint8_t pb_num);
|
||||
|
||||
void set_sealed_item_kill_count(uint16_t v);
|
||||
uint8_t get_tool_item_amount() const;
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
using namespace std;
|
||||
|
||||
ItemParameterTable::ItemParameterTable(shared_ptr<const string> data)
|
||||
: data(data), r(*data) {
|
||||
: data(data),
|
||||
r(*data) {
|
||||
size_t offset_table_offset = this->r.pget_u32l(this->data->size() - 0x10);
|
||||
this->offsets = &r.pget<TableOffsets>(offset_table_offset);
|
||||
}
|
||||
@@ -111,6 +112,19 @@ float ItemParameterTable::get_sale_divisor(uint8_t data1_0, uint8_t data1_1) con
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
const ItemParameterTable::MagFeedResult& ItemParameterTable::get_mag_feed_result(
|
||||
uint8_t table_index, uint8_t item_index) const {
|
||||
if (table_index >= 8) {
|
||||
throw runtime_error("invalid mag feed table index");
|
||||
}
|
||||
if (item_index >= 11) {
|
||||
throw runtime_error("invalid mag feed item index");
|
||||
}
|
||||
const auto& table_offsets = this->r.pget<MagFeedResultsListOffsets>(this->offsets->mag_feed_table);
|
||||
const auto& results = this->r.pget<MagFeedResultsList>(table_offsets.offsets[table_index]);
|
||||
return results.results[item_index];
|
||||
}
|
||||
|
||||
uint8_t ItemParameterTable::get_item_stars(uint16_t slot) const {
|
||||
if ((slot >= 0xB1) && (slot < 0x437)) {
|
||||
return this->r.pget_u8(this->offsets->star_value_table + slot - 0xB1);
|
||||
@@ -172,8 +186,8 @@ uint8_t ItemParameterTable::get_item_base_stars(const ItemData& item) const {
|
||||
return this->get_item_stars(this->get_item_definition(item).id);
|
||||
} else if (item.data1[0] == 3) {
|
||||
const auto& def = (item.data1[1] == 2)
|
||||
? this->get_tool(2, item.data1[4])
|
||||
: this->get_tool(item.data1[1], item.data1[2]);
|
||||
? this->get_tool(2, item.data1[4])
|
||||
: this->get_tool(item.data1[1], item.data1[2]);
|
||||
return (def.item_flag & 0x80) ? 12 : 0;
|
||||
} else {
|
||||
return 0;
|
||||
@@ -221,6 +235,59 @@ bool ItemParameterTable::is_unsealable_item(const ItemData& item) const {
|
||||
return false;
|
||||
}
|
||||
|
||||
void ItemParameterTable::populate_item_combination_index() const {
|
||||
if (!this->item_combination_index.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto& co = this->r.pget<CountAndOffset>(this->offsets->combination_table);
|
||||
const auto* defs = &this->r.pget<ItemCombination>(
|
||||
co.offset, co.count * sizeof(ItemCombination));
|
||||
for (size_t z = 0; z < co.count; z++) {
|
||||
const auto& def = defs[z];
|
||||
uint32_t key = (def.used_item[0] << 16) | (def.used_item[1] << 8) | def.used_item[2];
|
||||
this->item_combination_index[key].emplace_back(def);
|
||||
}
|
||||
}
|
||||
|
||||
const ItemParameterTable::ItemCombination& ItemParameterTable::get_item_combination(
|
||||
const ItemData& used_item, const ItemData& equipped_item) const {
|
||||
for (const auto& def : this->get_all_combinations_for_used_item(used_item)) {
|
||||
if ((def.equipped_item[0] == 0xFF || def.equipped_item[0] == equipped_item.data1[0]) &&
|
||||
(def.equipped_item[1] == 0xFF || def.equipped_item[1] == equipped_item.data1[1]) &&
|
||||
(def.equipped_item[2] == 0xFF || def.equipped_item[2] == equipped_item.data1[2])) {
|
||||
return def;
|
||||
}
|
||||
}
|
||||
throw out_of_range("no item combination applies");
|
||||
}
|
||||
|
||||
const std::vector<ItemParameterTable::ItemCombination>& ItemParameterTable::get_all_combinations_for_used_item(
|
||||
const ItemData& used_item) const {
|
||||
try {
|
||||
uint32_t key = (used_item.data1[0] << 16) | (used_item.data1[1] << 8) | used_item.data1[2];
|
||||
return this->get_all_item_combinations().at(key);
|
||||
} catch (const out_of_range&) {
|
||||
static const vector<ItemCombination> ret;
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
const std::map<uint32_t, std::vector<ItemParameterTable::ItemCombination>>& ItemParameterTable::get_all_item_combinations() const {
|
||||
this->populate_item_combination_index();
|
||||
return this->item_combination_index;
|
||||
}
|
||||
|
||||
std::pair<const ItemParameterTable::EventItem*, size_t> ItemParameterTable::get_event_items(uint8_t event_number) const {
|
||||
const auto& co = this->r.pget<CountAndOffset>(this->offsets->unwrap_table);
|
||||
if (event_number >= co.count) {
|
||||
throw runtime_error("invalid event number");
|
||||
}
|
||||
const auto& event_co = this->r.pget<CountAndOffset>(co.offset + sizeof(CountAndOffset) * event_number);
|
||||
const auto* defs = &this->r.pget<EventItem>(event_co.offset, event_co.count * sizeof(ItemCombination));
|
||||
return make_pair(defs, event_co.count);
|
||||
}
|
||||
|
||||
size_t ItemParameterTable::price_for_item(const ItemData& item) const {
|
||||
switch (item.data1[0]) {
|
||||
case 0: {
|
||||
@@ -275,9 +342,7 @@ size_t ItemParameterTable::price_for_item(const ItemData& item) const {
|
||||
const auto& def = this->get_armor_or_shield(item.data1[1], item.data1[2]);
|
||||
double power_factor = def.dfp + def.evp + def_bonus + evp_bonus;
|
||||
double power_factor_floor = static_cast<int32_t>((power_factor * power_factor) / sale_divisor);
|
||||
return power_factor_floor + (70.0 *
|
||||
static_cast<double>(item.data1[5] + 1) *
|
||||
static_cast<double>(def.required_level + 1));
|
||||
return power_factor_floor + (70.0 * static_cast<double>(item.data1[5] + 1) * static_cast<double>(def.required_level + 1));
|
||||
}
|
||||
|
||||
case 2:
|
||||
@@ -296,3 +361,15 @@ size_t ItemParameterTable::price_for_item(const ItemData& item) const {
|
||||
}
|
||||
throw logic_error("this should be impossible");
|
||||
}
|
||||
|
||||
MagEvolutionTable::MagEvolutionTable(shared_ptr<const string> data)
|
||||
: data(data),
|
||||
r(*data) {
|
||||
size_t offset_table_offset = this->r.pget_u32l(this->data->size() - 0x10);
|
||||
this->offsets = &r.pget<TableOffsets>(offset_table_offset);
|
||||
}
|
||||
|
||||
uint8_t MagEvolutionTable::get_evolution_number(uint8_t data1_1) const {
|
||||
const auto& table = this->r.pget<EvolutionNumberTable>(this->offsets->evolution_number);
|
||||
return table.values[data1_1];
|
||||
}
|
||||
|
||||
+52
-15
@@ -2,15 +2,22 @@
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <phosg/Encoding.hh>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "ItemData.hh"
|
||||
#include "Text.hh"
|
||||
|
||||
class ItemParameterTable {
|
||||
public:
|
||||
struct CountAndOffset {
|
||||
le_uint32_t count;
|
||||
le_uint32_t offset;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct ItemBase {
|
||||
le_uint32_t id;
|
||||
le_uint16_t type;
|
||||
@@ -104,12 +111,12 @@ public:
|
||||
} __attribute__((packed));
|
||||
|
||||
struct MagFeedResult {
|
||||
int8_t defense;
|
||||
int8_t power;
|
||||
int8_t dexterity;
|
||||
int8_t def;
|
||||
int8_t pow;
|
||||
int8_t dex;
|
||||
int8_t mind;
|
||||
int8_t iq;
|
||||
int8_t sync;
|
||||
int8_t synchro;
|
||||
parray<uint8_t, 2> unused;
|
||||
} __attribute__((packed));
|
||||
|
||||
@@ -117,8 +124,8 @@ public:
|
||||
parray<MagFeedResult, 11> results;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct MagFeedResultsTable {
|
||||
parray<MagFeedResultsList, 8> table;
|
||||
struct MagFeedResultsListOffsets {
|
||||
parray<le_uint32_t, 8> offsets; // Offsets of MagFeedResultsList structs
|
||||
} __attribute__((packed));
|
||||
|
||||
struct ItemStarValue {
|
||||
@@ -146,7 +153,7 @@ public:
|
||||
parray<uint8_t, 3> used_item;
|
||||
parray<uint8_t, 3> equipped_item;
|
||||
parray<uint8_t, 3> result_item;
|
||||
uint8_t maglevel;
|
||||
uint8_t mag_level;
|
||||
uint8_t grind;
|
||||
uint8_t level;
|
||||
uint8_t char_class;
|
||||
@@ -190,26 +197,21 @@ public:
|
||||
/* 1C / 0E194 */ le_uint32_t weapon_range_table; // -> ???
|
||||
/* 20 / 0F5A8 */ le_uint32_t weapon_sale_divisor_table; // -> [float](0xED)
|
||||
/* 24 / 0F83C */ le_uint32_t sale_divisor_table; // -> NonWeaponSaleDivisors
|
||||
/* 28 / 1502C */ le_uint32_t mag_feed_table; // -> [offset -> MagFeedResultsList](8)
|
||||
/* 28 / 1502C */ le_uint32_t mag_feed_table; // -> MagFeedResultsTable
|
||||
/* 2C / 0FB0C */ le_uint32_t star_value_table; // -> [uint8_t] (indexed by .id from weapon, armor, etc.)
|
||||
/* 30 / 0FE3C */ le_uint32_t special_data_table; // -> [Special]
|
||||
/* 34 / 0FEE0 */ le_uint32_t weapon_effect_table; // -> [16-byte structs]
|
||||
/* 38 / 1275C */ le_uint32_t stat_boost_table; // -> [StatBoost]
|
||||
/* 3C / 11C80 */ le_uint32_t shield_effect_table; // -> [8-byte structs]
|
||||
/* 40 / 12894 */ le_uint32_t max_tech_level_table; // -> MaxTechniqueLevels
|
||||
/* 44 / 14FF4 */ le_uint32_t combination_table; // -> [{count, offset -> [ItemCombination]}]
|
||||
/* 44 / 14FF4 */ le_uint32_t combination_table; // -> {count, offset -> [ItemCombination]}
|
||||
/* 48 / 12754 */ le_uint32_t unknown_a1;
|
||||
/* 4C / 14278 */ le_uint32_t tech_boost_table; // -> [TechniqueBoost] (always 0x2C of them? from counts struct?)
|
||||
/* 50 / 15014 */ le_uint32_t unwrap_table; // -> {count, offset -> [{count, offset -> [4-byte structs]}]}
|
||||
/* 50 / 15014 */ le_uint32_t unwrap_table; // -> {count, offset -> [{count, offset -> [EventItem]}]}
|
||||
/* 54 / 1501C */ le_uint32_t unsealable_table; // -> {count, offset -> [UnsealableItem]}
|
||||
/* 58 / 15024 */ le_uint32_t ranged_special_table; // -> {count, offset -> [4-byte structs]}
|
||||
} __attribute__((packed));
|
||||
|
||||
struct CountAndOffset {
|
||||
le_uint32_t count;
|
||||
le_uint32_t offset;
|
||||
} __attribute__((packed));
|
||||
|
||||
ItemParameterTable(std::shared_ptr<const std::string> data);
|
||||
~ItemParameterTable() = default;
|
||||
|
||||
@@ -230,9 +232,44 @@ public:
|
||||
uint8_t get_item_adjusted_stars(const ItemData& item) const;
|
||||
bool is_item_rare(const ItemData& item) const;
|
||||
bool is_unsealable_item(const ItemData& param_1) const;
|
||||
const ItemCombination& get_item_combination(const ItemData& used_item, const ItemData& equipped_item) const;
|
||||
const std::vector<ItemCombination>& get_all_combinations_for_used_item(const ItemData& used_item) const;
|
||||
const std::map<uint32_t, std::vector<ItemCombination>>& get_all_item_combinations() const;
|
||||
std::pair<const EventItem*, size_t> get_event_items(uint8_t event_number) const;
|
||||
|
||||
size_t price_for_item(const ItemData& item) const;
|
||||
|
||||
private:
|
||||
std::shared_ptr<const std::string> data;
|
||||
StringReader r;
|
||||
const TableOffsets* offsets;
|
||||
|
||||
// Key is used_item. We can't index on (used_item, equipped_item) because
|
||||
// equipped_item may contain wildcards, and the matching order matters.
|
||||
void populate_item_combination_index() const;
|
||||
mutable std::map<uint32_t, std::vector<ItemCombination>> item_combination_index;
|
||||
};
|
||||
|
||||
class MagEvolutionTable {
|
||||
public:
|
||||
struct TableOffsets {
|
||||
/* 00 / 0400 */ le_uint32_t unknown_a1; // -> [offset -> (0xC-byte struct)[0x53], offset -> (same as first offset)]
|
||||
/* 04 / 0408 */ le_uint32_t unknown_a2; // -> (2-byte struct, or single word)[0x53]
|
||||
/* 08 / 04AE */ le_uint32_t unknown_a3; // -> (0xA8 bytes; possibly (8-byte struct)[0x15])
|
||||
/* 0C / 0556 */ le_uint32_t unknown_a4; // -> (uint8_t)[0x53]
|
||||
/* 10 / 05AC */ le_uint32_t unknown_a5; // -> (float)[0x48]
|
||||
/* 14 / 06CC */ le_uint32_t evolution_number; // -> (uint8_t)[0x53]
|
||||
} __attribute__((packed));
|
||||
|
||||
struct EvolutionNumberTable {
|
||||
parray<uint8_t, 0x53> values;
|
||||
} __attribute__((packed));
|
||||
|
||||
MagEvolutionTable(std::shared_ptr<const std::string> data);
|
||||
~MagEvolutionTable() = default;
|
||||
|
||||
uint8_t get_evolution_number(uint8_t data1_1) const;
|
||||
|
||||
private:
|
||||
std::shared_ptr<const std::string> data;
|
||||
StringReader r;
|
||||
|
||||
+425
-174
@@ -4,184 +4,43 @@
|
||||
|
||||
#include <phosg/Random.hh>
|
||||
|
||||
#include "SendCommands.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
/* These items all need some kind of special handling that hasn't been implemented yet.
|
||||
|
||||
030B04 = TP Material (?)
|
||||
030C00 = Cell Of MAG 502
|
||||
030C01 = Cell Of MAG 213
|
||||
030C02 = Parts Of RoboChao
|
||||
030C03 = Heart Of Opa Opa
|
||||
030C04 = Heart Of Pian
|
||||
030C05 = Heart Of Chao
|
||||
|
||||
030D00 = Sorcerer's Right Arm
|
||||
030D01 = S-beat's Arms
|
||||
030D02 = P-arm's Arms
|
||||
030D03 = Delsaber's Right Arm
|
||||
030D04 = C-bringer's Right Arm
|
||||
030D05 = Delsaber's Left Arm
|
||||
030D06 = S-red's Arms
|
||||
030D07 = Dragon's Claw
|
||||
030D08 = Hildebear's Head
|
||||
030D09 = Hildeblue's Head
|
||||
030D0A = Parts of Baranz
|
||||
030D0B = Belra's Right Arms
|
||||
030D0C = GIGUE'S ARMS
|
||||
030D0D = S-BERILL'S ARMS
|
||||
030D0E = G-ASSASIN'S ARMS
|
||||
030D0F = BOOMA'S RIGHT ARMS
|
||||
030D10 = GOBOOMA'S RIGHT ARMS
|
||||
030D11 = GIGOBOOMA'S RIGHT ARMS
|
||||
030D12 = GAL WIND
|
||||
030D13 = RAPPY'S WING
|
||||
|
||||
030E00 = BERILL PHOTON
|
||||
030E01 = PARASITIC GENE FLOW
|
||||
030E02 = MAGICSTONE IRITISTA
|
||||
030E03 = BLUE BLACK STONE
|
||||
030E04 = SYNCESTA
|
||||
030E05 = MAGIC WATER
|
||||
030E06 = PARASITIC CELL TYPE D
|
||||
030E07 = MAGIC ROCK HEART KEY
|
||||
030E08 = MAGIC ROCK MOOLA
|
||||
030E09 = STAR AMPLIFIER
|
||||
030E0A = BOOK OF HITOGATA
|
||||
030E0B = HEART OF CHU CHU
|
||||
030E0C = PART OF EGG BLASTER
|
||||
030E0D = HEART OF ANGLE
|
||||
030E0E = HEART OF DEVIL
|
||||
030E0F = KIT OF HAMBERGER
|
||||
030E10 = PANTHER'S SPIRIT
|
||||
030E11 = KIT OF MARK3
|
||||
030E12 = KIT OF MASTER SYSTEM
|
||||
030E13 = KIT OF GENESIS
|
||||
030E14 = KIT OF SEGA SATURN
|
||||
030E15 = KIT OF DREAMCAST
|
||||
030E16 = AMP. RESTA
|
||||
030E17 = AMP. ANTI
|
||||
030E18 = AMP. SHIFTA
|
||||
030E19 = AMP. DEBAND
|
||||
030E1A = AMP.
|
||||
030E1B = AMP.
|
||||
030E1C = AMP.
|
||||
030E1D = AMP.
|
||||
030E1E = AMP.
|
||||
030E1F = AMP.
|
||||
030E20 = AMP.
|
||||
030E21 = AMP.
|
||||
030E22 = AMP.
|
||||
030E23 = AMP.
|
||||
030E24 = AMP.
|
||||
030E25 = AMP.
|
||||
030E26 = HEART OF KAPUKAPU
|
||||
030E27 = PROTON BOOSTER
|
||||
030F00 = ADD SLOT
|
||||
031000 = PHOTON DROP
|
||||
031001 = PHOTON SPHERE
|
||||
031002 = PHOTON CRYSTAL
|
||||
031100 = BOOK OF KATANA 1
|
||||
031101 = BOOK OF KATANA 2
|
||||
031102 = BOOK OF KATANA 3
|
||||
031200 = WEAPONS BRONZE BADGE
|
||||
031201 = WEAPONS SILVER BADGE
|
||||
031202 = WEAPONS GOLD BADGE
|
||||
031203 = WEAPONS CRYSTAL BADGE
|
||||
031204 = WEAPONS STEEL BADGE
|
||||
031205 = WEAPONS ALUMINUM BADGE
|
||||
031206 = WEAPONS LEATHER BADGE
|
||||
031207 = WEAPONS BONE BADGE
|
||||
031208 = LETTER OF APPRECATION
|
||||
031209 = AUTOGRAPH ALBUM
|
||||
03120A = VALENTINE'S CHOCOLATE
|
||||
03120B = NEWYEAR'S CARD
|
||||
03120C = CRISMAS CARD
|
||||
03120D = BIRTHDAY CARD
|
||||
03120E = PROOF OF SONIC TEAM
|
||||
03120F = SPECIAL EVENT TICKET
|
||||
031300 = PRESENT
|
||||
031400 = CHOCOLATE
|
||||
031401 = CANDY
|
||||
031402 = CAKE
|
||||
031403 = SILVER BADGE
|
||||
031404 = GOLD BADGE
|
||||
031405 = CRYSTAL BADGE
|
||||
031406 = IRON BADGE
|
||||
031407 = ALUMINUM BADGE
|
||||
031408 = LEATHER BADGE
|
||||
031409 = BONE BADGE
|
||||
03140A = BONQUET
|
||||
03140B = DECOCTION
|
||||
031500 = CRISMAS PRESENT
|
||||
031501 = EASTER EGG
|
||||
031502 = JACK-O'S-LANTERN
|
||||
031700 = HUNTERS REPORT
|
||||
031701 = HUNTERS REPORT RANK A
|
||||
031702 = HUNTERS REPORT RANK B
|
||||
031703 = HUNTERS REPORT RANK C
|
||||
031704 = HUNTERS REPORT RANK F
|
||||
031705 = HUNTERS REPORT
|
||||
031705 = HUNTERS REPORT
|
||||
031705 = HUNTERS REPORT
|
||||
031705 = HUNTERS REPORT
|
||||
031705 = HUNTERS REPORT
|
||||
031705 = HUNTERS REPORT
|
||||
031705 = HUNTERS REPORT
|
||||
031705 = HUNTERS REPORT
|
||||
031705 = HUNTERS REPORT
|
||||
031802 = Dragon Scale
|
||||
031803 = Heaven Striker Coat
|
||||
031807 = Rappys Beak
|
||||
031802 = Dragon Scale */
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void player_use_item(shared_ptr<Client> c, size_t item_index) {
|
||||
auto player = c->game_data.player();
|
||||
|
||||
ssize_t equipped_weapon = -1;
|
||||
// ssize_t equipped_armor = -1;
|
||||
// ssize_t equipped_shield = -1;
|
||||
// ssize_t equipped_mag = -1;
|
||||
for (size_t y = 0; y < c->game_data.player()->inventory.num_items; y++) {
|
||||
if (c->game_data.player()->inventory.items[y].flags & 0x00000008) {
|
||||
if (c->game_data.player()->inventory.items[y].data.data1[0] == 0) {
|
||||
equipped_weapon = y;
|
||||
}
|
||||
// else if ((c->game_data.player()->inventory.items[y].data.data1[0] == 1) &&
|
||||
// (c->game_data.player()->inventory.items[y].data.data1[1] == 1)) {
|
||||
// equipped_armor = y;
|
||||
// } else if ((c->game_data.player()->inventory.items[y].data.data1[0] == 1) &&
|
||||
// (c->game_data.player()->inventory.items[y].data.data1[1] == 2)) {
|
||||
// equipped_shield = y;
|
||||
// } else if (c->game_data.player()->inventory.items[y].data.data1[0] == 2) {
|
||||
// equipped_mag = y;
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
void player_use_item(shared_ptr<ServerState> s, shared_ptr<Client> c, size_t item_index) {
|
||||
// On PC (and presumably DC), the client sends a 6x29 after this to delete the
|
||||
// used item. On GC and later versions, this does not happen, so we should
|
||||
// delete the item here.
|
||||
bool should_delete_item = (c->version() != GameVersion::DC) &&
|
||||
(c->version() != GameVersion::PC);
|
||||
bool should_delete_item = (c->version() != GameVersion::DC) && (c->version() != GameVersion::PC);
|
||||
|
||||
auto& item = c->game_data.player()->inventory.items[item_index];
|
||||
if (item.data.data1w[0] == 0x0203) { // technique disk
|
||||
c->game_data.player()->disp.technique_levels.data()[item.data.data1[4]] = item.data.data1[2];
|
||||
auto player = c->game_data.player();
|
||||
auto& item = player->inventory.items[item_index];
|
||||
uint32_t item_identifier = item.data.primary_identifier();
|
||||
|
||||
} else if (item.data.data1w[0] == 0x0A03) { // grinder
|
||||
if (equipped_weapon < 0) {
|
||||
throw invalid_argument("grinder used with no weapon equipped");
|
||||
if (item.data.is_common_consumable()) { // Monomate, etc.
|
||||
// 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]);
|
||||
if (item.data.data1[2] > max_level) {
|
||||
throw runtime_error("technique level too high");
|
||||
}
|
||||
player->disp.technique_levels.data()[item.data.data1[4]] = item.data.data1[2];
|
||||
|
||||
} else if ((item_identifier & 0xFFFF00) == 0x030A00) { // Grinder
|
||||
if (item.data.data1[2] > 2) {
|
||||
throw invalid_argument("incorrect grinder value");
|
||||
}
|
||||
c->game_data.player()->inventory.items[equipped_weapon].data.data1[3] += (item.data.data1[2] + 1);
|
||||
// TODO: we should check for max grind here
|
||||
auto& weapon = player->inventory.items[player->inventory.find_equipped_weapon()];
|
||||
auto weapon_def = s->item_parameter_table->get_weapon(
|
||||
weapon.data.data1[1], weapon.data.data1[2]);
|
||||
if (weapon.data.data1[3] >= weapon_def.max_grind) {
|
||||
throw runtime_error("weapon already at maximum grind");
|
||||
}
|
||||
weapon.data.data1[3] += (item.data.data1[2] + 1);
|
||||
|
||||
} else if (item.data.data1w[0] == 0x0B03) { // material
|
||||
} else if ((item_identifier & 0xFFFF00) == 0x030B00) { // Material
|
||||
switch (item.data.data1[2]) {
|
||||
case 0: // Power Material
|
||||
c->game_data.player()->disp.stats.atp += 2;
|
||||
@@ -208,20 +67,412 @@ void player_use_item(shared_ptr<Client> c, size_t item_index) {
|
||||
throw invalid_argument("unknown material used");
|
||||
}
|
||||
|
||||
} else if ((item_identifier & 0xFFFF00) == 0x030F00) { // AddSlot
|
||||
auto& armor = player->inventory.items[player->inventory.find_equipped_armor()];
|
||||
if (armor.data.data1[5] >= 4) {
|
||||
throw runtime_error("armor already at maximum slot count");
|
||||
}
|
||||
armor.data.data1[5]++;
|
||||
|
||||
} else if ((item.data.data1[0] == 0x02) && (item.data.data2[2] & 0x40)) {
|
||||
// Unwrap mag present
|
||||
item.data.data2[2] &= 0xBF;
|
||||
should_delete_item = false;
|
||||
|
||||
} else if ((item.data.data1[0] != 0x02) && (item.data.data1[4] & 0x40)) {
|
||||
// Unwrap non-mag present
|
||||
item.data.data1[4] &= 0xBF;
|
||||
should_delete_item = false;
|
||||
|
||||
} else if (item_identifier == 0x003300) {
|
||||
// Unseal Sealed J-Sword => Tsumikiri J-Sword
|
||||
item.data.data1[1] = 0x32;
|
||||
should_delete_item = false;
|
||||
|
||||
} else if (item_identifier == 0x00AB00) {
|
||||
// Unseal Lame d'Argent => Excalibur
|
||||
item.data.data1[1] = 0xAC;
|
||||
should_delete_item = false;
|
||||
|
||||
} else if (item_identifier == 0x01034D) {
|
||||
// Unseal Limiter => Adept
|
||||
item.data.data1[2] = 0x4E;
|
||||
should_delete_item = false;
|
||||
|
||||
} else if (item_identifier == 0x01034F) {
|
||||
// Unseal Swordsman Lore => Proof of Sword-Saint
|
||||
item.data.data1[2] = 0x50;
|
||||
should_delete_item = false;
|
||||
|
||||
} 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;
|
||||
|
||||
} 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;
|
||||
|
||||
} else if (item_identifier == 0x030C02) {
|
||||
// Parts of RoboChao
|
||||
auto& mag = player->inventory.items[player->inventory.find_equipped_mag()];
|
||||
mag.data.data1[1] = 0x28;
|
||||
|
||||
} else if (item_identifier == 0x030C03) {
|
||||
// Heart of Opa Opa
|
||||
auto& mag = player->inventory.items[player->inventory.find_equipped_mag()];
|
||||
mag.data.data1[1] = 0x29;
|
||||
|
||||
} else if (item_identifier == 0x030C04) {
|
||||
// Heart of Pian
|
||||
auto& mag = player->inventory.items[player->inventory.find_equipped_mag()];
|
||||
mag.data.data1[1] = 0x2A;
|
||||
|
||||
} else if (item_identifier == 0x030C05) {
|
||||
// Heart of Chao
|
||||
auto& mag = player->inventory.items[player->inventory.find_equipped_mag()];
|
||||
mag.data.data1[1] = 0x2B;
|
||||
|
||||
} else if ((item_identifier & 0xFFFF00) == 0x031500) {
|
||||
// Christmas Present, etc. - use unwrap_table + probabilities therein
|
||||
auto table = s->item_parameter_table->get_event_items(item.data.data1[2]);
|
||||
size_t sum = 0;
|
||||
for (size_t z = 0; z < table.second; z++) {
|
||||
sum += table.first[z].probability;
|
||||
}
|
||||
if (sum == 0) {
|
||||
throw runtime_error("no unwrap results available for event");
|
||||
}
|
||||
size_t det = random_object<size_t>() % sum;
|
||||
for (size_t z = 0; z < table.second; z++) {
|
||||
const auto& entry = table.first[z];
|
||||
if (det > entry.probability) {
|
||||
det -= entry.probability;
|
||||
} else {
|
||||
item.data.data2d = 0;
|
||||
item.data.data1[0] = entry.item[0];
|
||||
item.data.data1[1] = entry.item[1];
|
||||
item.data.data1[2] = entry.item[2];
|
||||
item.data.data1.clear_after(3);
|
||||
should_delete_item = false;
|
||||
|
||||
auto l = s->find_lobby(c->lobby_id);
|
||||
send_create_inventory_item(l, c, item.data);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
// default item action is to unwrap the item if it's a present
|
||||
if ((item.data.data1[0] == 2) && (item.data.data2[2] & 0x40)) {
|
||||
item.data.data2[2] &= 0xBF;
|
||||
should_delete_item = false;
|
||||
} else if ((item.data.data1[0] != 2) && (item.data.data1[4] & 0x40)) {
|
||||
item.data.data1[4] &= 0xBF;
|
||||
should_delete_item = false;
|
||||
// Use item combinations table from ItemPMT
|
||||
bool combo_applied = false;
|
||||
for (size_t z = 0; z < player->inventory.num_items; z++) {
|
||||
auto& inv_item = player->inventory.items[z];
|
||||
if (!(inv_item.flags & 0x00000008)) {
|
||||
continue;
|
||||
}
|
||||
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) {
|
||||
throw runtime_error("item combination requires specific char_class");
|
||||
}
|
||||
if (combo.mag_level != 0xFF) {
|
||||
if (inv_item.data.data1[0] != 2) {
|
||||
throw runtime_error("item combination applies with mag level requirement, but equipped item is not a mag");
|
||||
}
|
||||
if (inv_item.data.compute_mag_level() < combo.mag_level) {
|
||||
throw runtime_error("item combination applies with mag level requirement, but equipped mag level is too low");
|
||||
}
|
||||
}
|
||||
if (combo.grind != 0xFF) {
|
||||
if (inv_item.data.data1[0] != 0) {
|
||||
throw runtime_error("item combination applies with grind requirement, but equipped item is not a weapon");
|
||||
}
|
||||
if (inv_item.data.data1[3] < combo.grind) {
|
||||
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) {
|
||||
throw runtime_error("item combination applies with level requirement, but player level is too low");
|
||||
}
|
||||
// If we get here, then the combo applies
|
||||
if (combo_applied) {
|
||||
throw runtime_error("multiple combinations apply");
|
||||
}
|
||||
combo_applied = true;
|
||||
|
||||
inv_item.data.data1[0] = combo.result_item[0];
|
||||
inv_item.data.data1[1] = combo.result_item[1];
|
||||
inv_item.data.data1[2] = combo.result_item[2];
|
||||
inv_item.data.data1[3] = 0; // Grind
|
||||
inv_item.data.data1[4] = 0; // Flags + special
|
||||
} catch (const out_of_range&) {
|
||||
}
|
||||
}
|
||||
|
||||
if (!combo_applied) {
|
||||
throw runtime_error("no combinations apply");
|
||||
}
|
||||
}
|
||||
|
||||
if (should_delete_item) {
|
||||
// Allow overdrafting meseta if the client is not BB, since the server isn't
|
||||
// informed when meseta is added or removed from the bank.
|
||||
c->game_data.player()->remove_item(item.data.id, 1, c->version() != GameVersion::BB);
|
||||
player->remove_item(item.data.id, 1, c->version() != GameVersion::BB);
|
||||
}
|
||||
}
|
||||
|
||||
void player_feed_mag(std::shared_ptr<ServerState> s, std::shared_ptr<Client> c, size_t mag_item_index, size_t fed_item_index) {
|
||||
static const unordered_map<uint32_t, size_t> result_index_for_fed_item({
|
||||
{0x030000, 0}, // Monomate
|
||||
{0x030001, 1}, // Dimate
|
||||
{0x030002, 2}, // Trimate
|
||||
{0x030100, 3}, // Monofluid
|
||||
{0x030101, 4}, // Difluid
|
||||
{0x030102, 5}, // Trifluid
|
||||
{0x030600, 6}, // Antidote
|
||||
{0x030601, 7}, // Antiparalysis
|
||||
{0x030300, 8}, // Sol Atomizer
|
||||
{0x030400, 9}, // Moon Atomizer
|
||||
{0x030500, 10}, // Star Atomizer
|
||||
});
|
||||
|
||||
auto player = c->game_data.player();
|
||||
auto& fed_item = player->inventory.items[fed_item_index];
|
||||
auto& mag_item = player->inventory.items[mag_item_index];
|
||||
|
||||
size_t result_index = result_index_for_fed_item.at(fed_item.data.primary_identifier());
|
||||
const auto& mag_def = s->item_parameter_table->get_mag(mag_item.data.data1[1]);
|
||||
const auto& feed_result = s->item_parameter_table->get_mag_feed_result(mag_def.feed_table, result_index);
|
||||
|
||||
fprintf(stderr, "[feed-mag] table = %hu, index = %zu\n", mag_def.feed_table.load(), result_index);
|
||||
print_data(stderr, &feed_result, sizeof(feed_result));
|
||||
|
||||
auto update_stat = +[](ItemData& data, size_t which, int8_t delta) -> void {
|
||||
uint16_t existing_stat = data.data1w[which] % 100;
|
||||
if ((delta > 0) || ((delta < 0) && (-delta < existing_stat))) {
|
||||
uint16_t level = data.compute_mag_level();
|
||||
if (level > 200) {
|
||||
throw runtime_error("mag level is too high");
|
||||
}
|
||||
if ((level == 200) && ((99 - existing_stat) < delta)) {
|
||||
delta = 99 - existing_stat;
|
||||
}
|
||||
data.data1w[which] += delta;
|
||||
}
|
||||
};
|
||||
|
||||
auto print_mag_item = [&](const char* step) -> void {
|
||||
auto hex = mag_item.data.hex();
|
||||
fprintf(stderr, "[feed-mag] step: %s\n[feed-mag] %s\n", step, hex.c_str());
|
||||
};
|
||||
|
||||
print_mag_item("pre");
|
||||
update_stat(mag_item.data, 2, feed_result.def);
|
||||
print_mag_item("update-def");
|
||||
update_stat(mag_item.data, 3, feed_result.pow);
|
||||
print_mag_item("update-pow");
|
||||
update_stat(mag_item.data, 4, feed_result.dex);
|
||||
print_mag_item("update-dex");
|
||||
update_stat(mag_item.data, 5, feed_result.mind);
|
||||
print_mag_item("update-mind");
|
||||
mag_item.data.data2[0] = clamp<ssize_t>(static_cast<ssize_t>(mag_item.data.data2[0]) + feed_result.synchro, 0, 120);
|
||||
print_mag_item("update-synchro");
|
||||
mag_item.data.data2[1] = clamp<ssize_t>(static_cast<ssize_t>(mag_item.data.data2[1]) + feed_result.iq, 0, 200);
|
||||
print_mag_item("update-iq");
|
||||
|
||||
uint8_t mag_level = mag_item.data.compute_mag_level();
|
||||
mag_item.data.data1[2] = mag_level;
|
||||
print_mag_item("compute-level");
|
||||
uint8_t evolution_number = s->mag_evolution_table->get_evolution_number(mag_item.data.data1[1]);
|
||||
uint8_t mag_number = mag_item.data.data1[1];
|
||||
fprintf(stderr, "[feed-mag] evo_num = %02hhX, mag_num = %02hhX\n", evolution_number, mag_number);
|
||||
|
||||
// Note: Sega really did just hardcode all these rules into the client. There
|
||||
// is no data file describing these evolutions, unfortunately.
|
||||
|
||||
if (mag_level < 10) {
|
||||
// Nothing to do
|
||||
|
||||
} else if (mag_level < 35) { // Level 10 evolution
|
||||
if (evolution_number < 1) {
|
||||
switch (player->disp.char_class) {
|
||||
case 0: // HUmar
|
||||
case 1: // HUnewearl
|
||||
case 2: // HUcast
|
||||
case 9: // HUcaseal
|
||||
mag_item.data.data1[1] = 0x01; // Varuna
|
||||
break;
|
||||
case 3: // RAmar
|
||||
case 11: // RAmarl
|
||||
case 4: // RAcast
|
||||
case 5: // RAcaseal
|
||||
mag_item.data.data1[1] = 0x0D; // Kalki
|
||||
break;
|
||||
case 10: // FOmar
|
||||
case 6: // FOmarl
|
||||
case 7: // FOnewm
|
||||
case 8: // FOnewearl
|
||||
mag_item.data.data1[1] = 0x19; // Vritra
|
||||
break;
|
||||
default:
|
||||
throw runtime_error("invalid character class");
|
||||
}
|
||||
}
|
||||
|
||||
} else if (mag_level < 50) { // Level 35 evolution
|
||||
if (evolution_number < 2) {
|
||||
uint16_t flags = mag_item.data.compute_mag_strength_flags();
|
||||
if (mag_number == 0x0D) {
|
||||
if ((flags & 0x110) == 0) {
|
||||
mag_item.data.data1[1] = 0x02;
|
||||
} else if (flags & 8) {
|
||||
mag_item.data.data1[1] = 0x03;
|
||||
} else if (flags & 0x20) {
|
||||
mag_item.data.data1[1] = 0x0B;
|
||||
}
|
||||
} else if (mag_number == 1) {
|
||||
if (flags & 0x108) {
|
||||
mag_item.data.data1[1] = 0x0E;
|
||||
} else if (flags & 0x10) {
|
||||
mag_item.data.data1[1] = 0x0F;
|
||||
} else if (flags & 0x20) {
|
||||
mag_item.data.data1[1] = 0x04;
|
||||
}
|
||||
} else if (mag_number == 0x19) {
|
||||
if (flags & 0x120) {
|
||||
mag_item.data.data1[1] = 0x1A;
|
||||
} else if (flags & 8) {
|
||||
mag_item.data.data1[1] = 0x1B;
|
||||
} else if (flags & 0x10) {
|
||||
mag_item.data.data1[1] = 0x14;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} else if ((mag_level % 5) == 0) { // Level 50 (and beyond) evolutions
|
||||
if (evolution_number < 4) {
|
||||
|
||||
if (mag_level >= 100) {
|
||||
uint8_t section_id_group = player->disp.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);
|
||||
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);
|
||||
if (is_force) {
|
||||
table_index += 12;
|
||||
} else if (is_ranger) {
|
||||
table_index += 6;
|
||||
} else if (!is_hunter) {
|
||||
throw logic_error("char class is not any of the top-level classes");
|
||||
}
|
||||
|
||||
// Note: The original code checks the class (hunter/ranger/force) again
|
||||
// here, and goes into 3 branches that each do these same checks.
|
||||
// However, the result of all 3 branches is exactly the same!
|
||||
if (((section_id_group == 0) && (pow + mind == def + dex)) ||
|
||||
((section_id_group == 1) && (pow + dex == mind + def)) ||
|
||||
((section_id_group == 2) && (pow + def == mind + dex))) {
|
||||
// clang-format off
|
||||
static const uint8_t result_table[] = {
|
||||
// M0 F0 M1 F1 M2 F2
|
||||
0x39, 0x3B, 0x3A, 0x3B, 0x3A, 0x3B, // Hunter
|
||||
0x3D, 0x3C, 0x3D, 0x3C, 0x3D, 0x3E, // Ranger
|
||||
0x41, 0x3F, 0x41, 0x40, 0x41, 0x40, // Force
|
||||
};
|
||||
// clang-format on
|
||||
mag_item.data.data1[1] = result_table[table_index];
|
||||
}
|
||||
}
|
||||
|
||||
// If a special evolution did not occur, do a normal level 50 evolution
|
||||
if (mag_number == mag_item.data.data1[1]) {
|
||||
uint16_t flags = mag_item.data.compute_mag_strength_flags();
|
||||
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_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);
|
||||
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)
|
||||
? ((dex < mind) ? 0x08 : 0x06)
|
||||
: ((dex < mind) ? 0x0C : 0x05);
|
||||
} else if (flags & 0x010) {
|
||||
mag_item.data.data1[1] = (player->disp.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)
|
||||
? ((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)
|
||||
? ((mind < pow) ? 0x0A : 0x05)
|
||||
: ((mind < pow) ? 0x0C : 0x06);
|
||||
} else if (flags & 0x008) {
|
||||
mag_item.data.data1[1] = (player->disp.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)
|
||||
? ((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)
|
||||
? ((pow < dex) ? 0x17 : 0x09)
|
||||
: ((pow < dex) ? 0x1E : 0x1C);
|
||||
} else {
|
||||
mag_item.data.data1[1] = 0x24;
|
||||
}
|
||||
} else if (flags & 0x008) {
|
||||
if (def < 45) {
|
||||
mag_item.data.data1[1] = (player->disp.section_id & 1)
|
||||
? ((dex < mind) ? 0x1C : 0x20)
|
||||
: ((dex < mind) ? 0x1F : 0x25);
|
||||
} else {
|
||||
mag_item.data.data1[1] = 0x23;
|
||||
}
|
||||
} else if (flags & 0x010) {
|
||||
if (def < 45) {
|
||||
mag_item.data.data1[1] = (player->disp.section_id & 1)
|
||||
? ((mind < pow) ? 0x12 : 0x0C)
|
||||
: ((mind < pow) ? 0x15 : 0x11);
|
||||
} else {
|
||||
mag_item.data.data1[1] = 0x24;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
print_mag_item("evolution-check");
|
||||
|
||||
// If the mag has evolved, add its new photon blast
|
||||
if (mag_number != mag_item.data.data1[1]) {
|
||||
const auto& new_mag_def = s->item_parameter_table->get_mag(mag_item.data.data1[1]);
|
||||
mag_item.data.add_mag_photon_blast(new_mag_def.photon_blast);
|
||||
}
|
||||
|
||||
print_mag_item("add-pb");
|
||||
}
|
||||
|
||||
+3
-1
@@ -6,6 +6,8 @@
|
||||
#include <random>
|
||||
|
||||
#include "Client.hh"
|
||||
#include "ServerState.hh"
|
||||
#include "StaticGameData.hh"
|
||||
|
||||
void player_use_item(std::shared_ptr<Client> c, size_t item_index);
|
||||
void player_use_item(std::shared_ptr<ServerState> s, std::shared_ptr<Client> c, size_t item_index);
|
||||
void player_feed_mag(std::shared_ptr<ServerState> s, std::shared_ptr<Client> c, size_t mag_item_index, size_t fed_item_index);
|
||||
|
||||
+66
-3
@@ -832,7 +832,7 @@ PlayerBankItem PlayerBank::remove_item(uint32_t item_id, uint32_t amount) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
size_t PlayerInventory::find_item(uint32_t item_id) {
|
||||
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;
|
||||
@@ -841,6 +841,69 @@ size_t PlayerInventory::find_item(uint32_t item_id) {
|
||||
throw out_of_range("item not present");
|
||||
}
|
||||
|
||||
size_t PlayerInventory::find_equipped_weapon() const {
|
||||
ssize_t ret = -1;
|
||||
for (size_t y = 0; y < this->num_items; y++) {
|
||||
if (!(this->items[y].flags & 0x00000008)) {
|
||||
continue;
|
||||
}
|
||||
if (this->items[y].data.data1[0] != 0) {
|
||||
continue;
|
||||
}
|
||||
if (ret < 0) {
|
||||
ret = y;
|
||||
} else {
|
||||
throw runtime_error("multiple weapons are equipped");
|
||||
}
|
||||
}
|
||||
if (ret < 0) {
|
||||
throw runtime_error("no weapon is equipped");
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
size_t PlayerInventory::find_equipped_armor() const {
|
||||
ssize_t ret = -1;
|
||||
for (size_t y = 0; y < this->num_items; y++) {
|
||||
if (!(this->items[y].flags & 0x00000008)) {
|
||||
continue;
|
||||
}
|
||||
if (this->items[y].data.data1[0] != 1 || this->items[y].data.data1[1] != 1) {
|
||||
continue;
|
||||
}
|
||||
if (ret < 0) {
|
||||
ret = y;
|
||||
} else {
|
||||
throw runtime_error("multiple armors are equipped");
|
||||
}
|
||||
}
|
||||
if (ret < 0) {
|
||||
throw runtime_error("no armor is equipped");
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
size_t PlayerInventory::find_equipped_mag() const {
|
||||
ssize_t ret = -1;
|
||||
for (size_t y = 0; y < this->num_items; y++) {
|
||||
if (!(this->items[y].flags & 0x00000008)) {
|
||||
continue;
|
||||
}
|
||||
if (this->items[y].data.data1[0] != 2) {
|
||||
continue;
|
||||
}
|
||||
if (ret < 0) {
|
||||
ret = y;
|
||||
} else {
|
||||
throw runtime_error("multiple mags are equipped");
|
||||
}
|
||||
}
|
||||
if (ret < 0) {
|
||||
throw runtime_error("no mag is equipped");
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
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) {
|
||||
@@ -856,7 +919,7 @@ void SavedPlayerDataBB::print_inventory(FILE* stream) const {
|
||||
for (size_t x = 0; x < this->inventory.num_items; x++) {
|
||||
const auto& item = this->inventory.items[x];
|
||||
auto name = item.data.name(false);
|
||||
fprintf(stream, "[PlayerInventory] %zu (%08" PRIX32 "): %06" PRIX32 " (%s)\n",
|
||||
x, item.data.id.load(), item.data.primary_identifier(), name.c_str());
|
||||
auto hex = item.data.hex();
|
||||
fprintf(stream, "[PlayerInventory] %zu: %s (%s)\n", x, hex.c_str(), name.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
+5
-1
@@ -46,7 +46,11 @@ struct PlayerInventory { // 0x34C bytes
|
||||
|
||||
PlayerInventory();
|
||||
|
||||
size_t find_item(uint32_t item_id);
|
||||
size_t find_item(uint32_t item_id) const;
|
||||
|
||||
size_t find_equipped_weapon() const;
|
||||
size_t find_equipped_armor() const;
|
||||
size_t find_equipped_mag() const;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct PlayerBank { // 0x12C8 bytes
|
||||
|
||||
@@ -788,8 +788,12 @@ static void on_equip_unequip_item(shared_ptr<ServerState>,
|
||||
forward_subcommand(l, c, command, flag, data);
|
||||
}
|
||||
|
||||
static void on_use_item(shared_ptr<ServerState>,
|
||||
shared_ptr<Lobby> l, shared_ptr<Client> c, uint8_t command, uint8_t flag,
|
||||
static void on_use_item(
|
||||
shared_ptr<ServerState> s,
|
||||
shared_ptr<Lobby> l,
|
||||
shared_ptr<Client> c,
|
||||
uint8_t command,
|
||||
uint8_t flag,
|
||||
const string& data) {
|
||||
const auto& cmd = check_size_sc<G_UseItem_6x27>(data);
|
||||
|
||||
@@ -807,7 +811,7 @@ static void on_use_item(shared_ptr<ServerState>,
|
||||
name = item.name(false);
|
||||
colored_name = item.name(true);
|
||||
}
|
||||
player_use_item(c, index);
|
||||
player_use_item(s, c, index);
|
||||
|
||||
l->log.info("Player used item %hu:%08" PRIX32 " (%s)",
|
||||
cmd.header.client_id.load(), cmd.item_id.load(), name.c_str());
|
||||
@@ -821,6 +825,57 @@ static void on_use_item(shared_ptr<ServerState>,
|
||||
forward_subcommand(l, c, command, flag, data);
|
||||
}
|
||||
|
||||
static void on_feed_mag(
|
||||
shared_ptr<ServerState> s,
|
||||
shared_ptr<Lobby> l,
|
||||
shared_ptr<Client> c,
|
||||
uint8_t command,
|
||||
uint8_t flag,
|
||||
const string& data) {
|
||||
const auto& cmd = check_size_sc<G_FeedMAG_6x28>(data);
|
||||
|
||||
if (cmd.header.client_id != c->lobby_client_id) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (l->flags & Lobby::Flag::ITEM_TRACKING_ENABLED) {
|
||||
size_t mag_index = c->game_data.player()->inventory.find_item(cmd.mag_item_id);
|
||||
size_t fed_index = c->game_data.player()->inventory.find_item(cmd.fed_item_id);
|
||||
string mag_name, mag_colored_name, fed_name, fed_colored_name;
|
||||
{
|
||||
// Note: We do this weird scoping thing because player_use_item will
|
||||
// likely delete the item, which will break the reference here.
|
||||
const auto& fed_item = c->game_data.player()->inventory.items[fed_index].data;
|
||||
fed_name = fed_item.name(false);
|
||||
fed_colored_name = fed_item.name(true);
|
||||
const auto& mag_item = c->game_data.player()->inventory.items[mag_index].data;
|
||||
mag_name = mag_item.name(false);
|
||||
mag_colored_name = mag_item.name(true);
|
||||
}
|
||||
player_feed_mag(s, c, mag_index, fed_index);
|
||||
|
||||
// On BB, the player only sends a 6x28; on other versions, the player sends
|
||||
// a 6x29 immediately after to destroy the fed item. So on BB, we should
|
||||
// remove the fed item here, but on other versions, we allow the following
|
||||
// 6x29 command to do that.
|
||||
if (l->version == GameVersion::BB) {
|
||||
c->game_data.player()->remove_item(cmd.fed_item_id, 1, false);
|
||||
}
|
||||
|
||||
l->log.info("Player fed item %hu:%08" PRIX32 " (%s) to mag %hu:%08" PRIX32 " (%s)",
|
||||
cmd.header.client_id.load(), cmd.fed_item_id.load(), fed_name.c_str(),
|
||||
cmd.header.client_id.load(), cmd.mag_item_id.load(), mag_name.c_str());
|
||||
if (c->options.debug) {
|
||||
send_text_message_printf(c, "$C5Items: feed %08" PRIX32 "\n%s\n...to %08" PRIX32 "\n%s",
|
||||
cmd.fed_item_id.load(), fed_colored_name.c_str(),
|
||||
cmd.mag_item_id.load(), mag_colored_name.c_str());
|
||||
}
|
||||
c->game_data.player()->print_inventory(stderr);
|
||||
}
|
||||
|
||||
forward_subcommand(l, c, command, flag, data);
|
||||
}
|
||||
|
||||
static void on_open_shop_bb_or_ep3_battle_subs(shared_ptr<ServerState> s,
|
||||
shared_ptr<Lobby> l, shared_ptr<Client> c, uint8_t command, uint8_t flag,
|
||||
const string& data) {
|
||||
@@ -1413,7 +1468,7 @@ subcommand_handler_t subcommand_handlers[0x100] = {
|
||||
/* 25 */ on_equip_unequip_item, // Equip item
|
||||
/* 26 */ on_equip_unequip_item, // Unequip item
|
||||
/* 27 */ on_use_item,
|
||||
/* 28 */ on_forward_check_size_game, // Feed MAG
|
||||
/* 28 */ on_feed_mag, // Feed MAG
|
||||
/* 29 */ on_destroy_inventory_item, // Delete item (via bank deposit / sale / feeding MAG)
|
||||
/* 2A */ on_player_drop_item,
|
||||
/* 2B */ on_create_inventory_item, // Create inventory item (e.g. from tekker or bank withdrawal)
|
||||
|
||||
+7
-2
@@ -862,9 +862,14 @@ void ServerState::load_item_tables() {
|
||||
}
|
||||
|
||||
config_log.info("Loading item definition table");
|
||||
shared_ptr<string> data(new string(prs_decompress(load_file(
|
||||
shared_ptr<string> pmt_data(new string(prs_decompress(load_file(
|
||||
"system/blueburst/ItemPMT.prs"))));
|
||||
this->item_parameter_table.reset(new ItemParameterTable(data));
|
||||
this->item_parameter_table.reset(new ItemParameterTable(pmt_data));
|
||||
|
||||
config_log.info("Loading mag evolution table");
|
||||
shared_ptr<string> mag_data(new string(prs_decompress(load_file(
|
||||
"system/blueburst/ItemMagEdit.prs"))));
|
||||
this->mag_evolution_table.reset(new MagEvolutionTable(mag_data));
|
||||
}
|
||||
|
||||
void ServerState::load_ep3_data() {
|
||||
|
||||
+1
-1
@@ -16,7 +16,6 @@
|
||||
#include "FunctionCompiler.hh"
|
||||
#include "GSLArchive.hh"
|
||||
#include "ItemParameterTable.hh"
|
||||
#include "Items.hh"
|
||||
#include "LevelTable.hh"
|
||||
#include "License.hh"
|
||||
#include "Lobby.hh"
|
||||
@@ -83,6 +82,7 @@ struct ServerState {
|
||||
std::shared_ptr<const ToolRandomSet> tool_random_set;
|
||||
std::array<std::shared_ptr<const WeaponRandomSet>, 4> weapon_random_sets;
|
||||
std::shared_ptr<const ItemParameterTable> item_parameter_table;
|
||||
std::shared_ptr<const MagEvolutionTable> mag_evolution_table;
|
||||
|
||||
std::shared_ptr<Episode3::TournamentIndex> ep3_tournament_index;
|
||||
|
||||
|
||||
@@ -377,6 +377,59 @@ const char* abbreviation_for_char_class(uint8_t cls) {
|
||||
}
|
||||
}
|
||||
|
||||
enum ClassFlag {
|
||||
MALE = 0x01,
|
||||
HUMAN = 0x02,
|
||||
NEWMAN = 0x04,
|
||||
ANDROID = 0x08,
|
||||
HUNTER = 0x10,
|
||||
RANGER = 0x20,
|
||||
FORCE = 0x40,
|
||||
};
|
||||
|
||||
static array<uint8_t, 12> class_flags = {
|
||||
ClassFlag::HUNTER | ClassFlag::HUMAN | ClassFlag::MALE, // HUmar
|
||||
ClassFlag::HUNTER | ClassFlag::NEWMAN, // HUnewearl
|
||||
ClassFlag::HUNTER | ClassFlag::ANDROID | ClassFlag::MALE, // HUcast
|
||||
ClassFlag::RANGER | ClassFlag::HUMAN | ClassFlag::MALE, // RAmar
|
||||
ClassFlag::RANGER | ClassFlag::ANDROID | ClassFlag::MALE, // RAcast
|
||||
ClassFlag::RANGER | ClassFlag::ANDROID, // RAcaseal
|
||||
ClassFlag::FORCE | ClassFlag::HUMAN, // FOmarl
|
||||
ClassFlag::FORCE | ClassFlag::NEWMAN | ClassFlag::MALE, // FOnewm
|
||||
ClassFlag::FORCE | ClassFlag::NEWMAN, // FOnewearl
|
||||
ClassFlag::HUNTER | ClassFlag::ANDROID, // HUcaseal
|
||||
ClassFlag::FORCE | ClassFlag::HUMAN | ClassFlag::MALE, // FOmar
|
||||
ClassFlag::RANGER | ClassFlag::HUMAN, // RAmarl
|
||||
};
|
||||
|
||||
bool char_class_is_male(uint8_t cls) {
|
||||
return class_flags.at(cls) & ClassFlag::MALE;
|
||||
}
|
||||
|
||||
bool char_class_is_human(uint8_t cls) {
|
||||
return class_flags.at(cls) & ClassFlag::HUMAN;
|
||||
}
|
||||
|
||||
bool char_class_is_newman(uint8_t cls) {
|
||||
return class_flags.at(cls) & ClassFlag::NEWMAN;
|
||||
}
|
||||
|
||||
bool char_class_is_android(uint8_t cls) {
|
||||
return class_flags.at(cls) & ClassFlag::ANDROID;
|
||||
}
|
||||
|
||||
bool char_class_is_hunter(uint8_t cls) {
|
||||
return class_flags.at(cls) & ClassFlag::HUNTER;
|
||||
}
|
||||
|
||||
bool char_class_is_ranger(uint8_t cls) {
|
||||
return class_flags.at(cls) & ClassFlag::RANGER;
|
||||
}
|
||||
|
||||
bool char_class_is_force(uint8_t cls) {
|
||||
return class_flags.at(cls) & ClassFlag::FORCE;
|
||||
}
|
||||
|
||||
const char* name_for_difficulty(uint8_t difficulty) {
|
||||
static const array<const char*, 4> names = {
|
||||
"Normal",
|
||||
|
||||
@@ -62,6 +62,13 @@ uint8_t npc_for_name(const std::u16string& name);
|
||||
|
||||
const char* name_for_char_class(uint8_t cls);
|
||||
const char* abbreviation_for_char_class(uint8_t cls);
|
||||
bool char_class_is_male(uint8_t cls);
|
||||
bool char_class_is_human(uint8_t cls);
|
||||
bool char_class_is_newman(uint8_t cls);
|
||||
bool char_class_is_android(uint8_t cls);
|
||||
bool char_class_is_hunter(uint8_t cls);
|
||||
bool char_class_is_ranger(uint8_t cls);
|
||||
bool char_class_is_force(uint8_t cls);
|
||||
|
||||
const char* name_for_difficulty(uint8_t difficulty);
|
||||
char abbreviation_for_difficulty(uint8_t difficulty);
|
||||
|
||||
+1
-1
@@ -271,7 +271,7 @@ struct parray {
|
||||
if (offset + SubCount > Count) {
|
||||
throw std::out_of_range("sub-array out of range");
|
||||
}
|
||||
return *reinterpret_cast<parray<ItemT, SubCount>*>(&this->items[offset]);
|
||||
return *reinterpret_cast<const parray<ItemT, SubCount>*>(&this->items[offset]);
|
||||
}
|
||||
|
||||
void assign_range(const ItemT* new_items, size_t count = Count, size_t start_offset = 0) {
|
||||
|
||||
Reference in New Issue
Block a user