use GC logic for BB nonrare item drop generation and shops
This commit is contained in:
@@ -44,6 +44,7 @@ add_executable(newserv
|
||||
src/Channel.cc
|
||||
src/ChatCommands.cc
|
||||
src/Client.cc
|
||||
src/CommonItemSet.cc
|
||||
src/Compression.cc
|
||||
src/DNSServer.cc
|
||||
src/Episode3/AssistServer.cc
|
||||
@@ -63,6 +64,9 @@ add_executable(newserv
|
||||
src/GSLArchive.cc
|
||||
src/IPFrameInfo.cc
|
||||
src/IPStackSimulator.cc
|
||||
src/ItemCreator.cc
|
||||
src/ItemData.cc
|
||||
src/ItemParameterTable.cc
|
||||
src/Items.cc
|
||||
src/LevelTable.cc
|
||||
src/License.cc
|
||||
|
||||
+4
-4
@@ -989,7 +989,7 @@ static void server_command_what(shared_ptr<ServerState>, shared_ptr<Lobby> l,
|
||||
send_text_message(c, u"$C4No items are near you");
|
||||
} else {
|
||||
const auto& item = l->item_id_to_floor_item.at(nearest_item_id);
|
||||
string name = name_for_item(item.inv_item.data, true);
|
||||
string name = item.inv_item.data.name(true);
|
||||
send_text_message(c, decode_sjis(name));
|
||||
}
|
||||
}
|
||||
@@ -1091,7 +1091,7 @@ static void server_command_item(shared_ptr<ServerState>, shared_ptr<Lobby> l,
|
||||
l->add_item(item, c->area, c->x, c->z);
|
||||
send_drop_stacked_item(l, item.data, c->area, c->x, c->z);
|
||||
|
||||
string name = name_for_item(item.data, true);
|
||||
string name = item.data.name(true);
|
||||
send_text_message(c, u"$C7Item created:\n" + decode_sjis(name));
|
||||
}
|
||||
|
||||
@@ -1137,14 +1137,14 @@ static void proxy_command_item(shared_ptr<ServerState>,
|
||||
if (set_drop) {
|
||||
session.next_drop_item = item;
|
||||
|
||||
string name = name_for_item(session.next_drop_item.data, true);
|
||||
string name = session.next_drop_item.data.name(true);
|
||||
send_text_message(session.client_channel, u"$C7Next drop:\n" + decode_sjis(name));
|
||||
|
||||
} else {
|
||||
send_drop_stacked_item(session.client_channel, item.data, session.area, session.x, session.z);
|
||||
send_drop_stacked_item(session.server_channel, item.data, session.area, session.x, session.z);
|
||||
|
||||
string name = name_for_item(item.data, true);
|
||||
string name = item.data.name(true);
|
||||
send_text_message(session.client_channel, u"$C7Item created:\n" + decode_sjis(name));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3925,8 +3925,8 @@ struct G_DropItem_PC_V3_BB_6x5F {
|
||||
struct G_EnemyDropItemRequest_DC_6x60 {
|
||||
G_UnusedHeader header;
|
||||
uint8_t area;
|
||||
uint8_t enemy_id;
|
||||
le_uint16_t request_id;
|
||||
uint8_t rt_index;
|
||||
le_uint16_t enemy_id;
|
||||
le_float x;
|
||||
le_float z;
|
||||
le_uint16_t unknown_a1;
|
||||
|
||||
@@ -0,0 +1,140 @@
|
||||
#include "CommonItemSet.hh"
|
||||
|
||||
#include "StaticGameData.hh"
|
||||
|
||||
|
||||
|
||||
CommonItemSet::CommonItemSet(shared_ptr<const string> data)
|
||||
: gsl(data, true) { }
|
||||
|
||||
const CommonItemSet::BETable& CommonItemSet::get_table(
|
||||
Episode episode, GameMode mode, uint8_t difficulty, uint8_t secid) const {
|
||||
// TODO: What should we do for Ep4?
|
||||
string filename = string_printf("ItemPT%s%s%c%1d.rel",
|
||||
((mode == GameMode::CHALLENGE) ? "c" : ""),
|
||||
((episode == Episode::EP2) ? "l" : ""),
|
||||
tolower(abbreviation_for_difficulty(difficulty)),
|
||||
secid);
|
||||
auto data = this->gsl.get(filename);
|
||||
if (data.second < sizeof(BETable)) {
|
||||
throw runtime_error(string_printf(
|
||||
"ItemPT entry %s is too small (received %zX bytes, expected %zX bytes)",
|
||||
filename.c_str(), data.second, sizeof(BETable)));
|
||||
}
|
||||
return *reinterpret_cast<const BETable*>(data.first);
|
||||
}
|
||||
|
||||
|
||||
|
||||
RELFileSet::RELFileSet(std::shared_ptr<const std::string> data)
|
||||
: data(data), r(*this->data) { }
|
||||
|
||||
|
||||
|
||||
ArmorRandomSet::ArmorRandomSet(std::shared_ptr<const std::string> data)
|
||||
: RELFileSet(data) {
|
||||
// For some reason the footer tables are doubly indirect in this file
|
||||
uint32_t specs_offset_offset = r.pget_u32b(data->size() - 0x10);
|
||||
uint32_t specs_offset = r.pget_u32b(specs_offset_offset);
|
||||
this->tables = &r.pget<parray<TableSpec, 3>>(specs_offset);
|
||||
}
|
||||
|
||||
std::pair<const ArmorRandomSet::WeightTableEntry8*, size_t>
|
||||
ArmorRandomSet::get_armor_table(size_t index) const {
|
||||
return this->get_table<WeightTableEntry8>(this->tables->at(0), index);
|
||||
}
|
||||
|
||||
std::pair<const ArmorRandomSet::WeightTableEntry8*, size_t>
|
||||
ArmorRandomSet::get_shield_table(size_t index) const {
|
||||
return this->get_table<WeightTableEntry8>(this->tables->at(1), index);
|
||||
}
|
||||
|
||||
std::pair<const ArmorRandomSet::WeightTableEntry8*, size_t>
|
||||
ArmorRandomSet::get_unit_table(size_t index) const {
|
||||
return this->get_table<WeightTableEntry8>(this->tables->at(2), index);
|
||||
}
|
||||
|
||||
|
||||
|
||||
ToolRandomSet::ToolRandomSet(std::shared_ptr<const std::string> data)
|
||||
: RELFileSet(data) {
|
||||
uint32_t specs_offset = r.pget_u32b(data->size() - 0x10);
|
||||
this->common_recovery_table_spec = &r.pget<TableSpec>(r.pget_u32b(
|
||||
specs_offset));
|
||||
this->rare_recovery_table_spec = &r.pget<TableSpec>(r.pget_u32b(
|
||||
specs_offset + sizeof(uint32_t)), 2 * sizeof(TableSpec));
|
||||
this->tech_disk_table_spec = this->rare_recovery_table_spec + 1;
|
||||
this->tech_disk_level_table_spec = &r.pget<TableSpec>(r.pget_u32b(
|
||||
specs_offset + 2 * sizeof(uint32_t)));
|
||||
}
|
||||
|
||||
pair<const uint8_t*, size_t> ToolRandomSet::get_common_recovery_table(
|
||||
size_t index) const {
|
||||
return this->get_table<uint8_t>(*this->common_recovery_table_spec, index);
|
||||
}
|
||||
|
||||
pair<const ToolRandomSet::WeightTableEntry8*, size_t>
|
||||
ToolRandomSet::get_rare_recovery_table(size_t index) const {
|
||||
return this->get_table<WeightTableEntry8>(*this->rare_recovery_table_spec, index);
|
||||
}
|
||||
|
||||
pair<const ToolRandomSet::WeightTableEntry8*, size_t>
|
||||
ToolRandomSet::get_tech_disk_table(size_t index) const {
|
||||
return this->get_table<WeightTableEntry8>(*this->tech_disk_table_spec, index);
|
||||
}
|
||||
|
||||
pair<const ToolRandomSet::TechDiskLevelEntry*, size_t>
|
||||
ToolRandomSet::get_tech_disk_level_table(size_t index) const {
|
||||
return this->get_table<TechDiskLevelEntry>(*this->tech_disk_level_table_spec, index);
|
||||
}
|
||||
|
||||
|
||||
|
||||
WeaponRandomSet::WeaponRandomSet(std::shared_ptr<const std::string> data)
|
||||
: RELFileSet(data) {
|
||||
uint32_t offsets_offset = this->r.pget_u32b(data->size() - 0x10);
|
||||
this->offsets = &this->r.pget<Offsets>(offsets_offset);
|
||||
}
|
||||
|
||||
std::pair<const WeaponRandomSet::WeightTableEntry8*, size_t>
|
||||
WeaponRandomSet::get_weapon_type_table(size_t index) const {
|
||||
const auto& spec = this->r.pget<TableSpec>(
|
||||
this->offsets->weapon_type_table + index * sizeof(TableSpec));
|
||||
const auto* data = &this->r.pget<WeightTableEntry8>(
|
||||
spec.offset, spec.entries_per_table * sizeof(WeightTableEntry8));
|
||||
return make_pair(data, spec.entries_per_table);
|
||||
}
|
||||
|
||||
const parray<WeaponRandomSet::WeightTableEntry32, 6>*
|
||||
WeaponRandomSet::get_bonus_type_table(size_t which, size_t index) const {
|
||||
uint32_t base_offset = which
|
||||
? this->offsets->bonus_type_table2 : this->offsets->bonus_type_table1;
|
||||
return &this->r.pget<parray<WeightTableEntry32, 6>>(
|
||||
base_offset + sizeof(parray<WeightTableEntry32, 6>) * index);
|
||||
}
|
||||
|
||||
const WeaponRandomSet::RangeTableEntry*
|
||||
WeaponRandomSet::get_bonus_range(size_t which, size_t index) const {
|
||||
uint32_t base_offset = which
|
||||
? this->offsets->bonus_range_table2 : this->offsets->bonus_range_table1;
|
||||
return &this->r.pget<RangeTableEntry>(
|
||||
base_offset + sizeof(RangeTableEntry) * index);
|
||||
}
|
||||
|
||||
const parray<WeaponRandomSet::WeightTableEntry32, 3>*
|
||||
WeaponRandomSet::get_special_mode_table(size_t index) const {
|
||||
return &this->r.pget<parray<WeightTableEntry32, 3>>(
|
||||
this->offsets->special_mode_table + sizeof(parray<WeightTableEntry32, 3>) * index);
|
||||
}
|
||||
|
||||
const WeaponRandomSet::RangeTableEntry*
|
||||
WeaponRandomSet::get_standard_grind_range(size_t index) const {
|
||||
return &this->r.pget<RangeTableEntry>(
|
||||
this->offsets->standard_grind_range_table + sizeof(RangeTableEntry) * index);
|
||||
}
|
||||
|
||||
const WeaponRandomSet::RangeTableEntry*
|
||||
WeaponRandomSet::get_favored_grind_range(size_t index) const {
|
||||
return &this->r.pget<RangeTableEntry>(
|
||||
this->offsets->favored_grind_range_table + sizeof(RangeTableEntry) * index);
|
||||
}
|
||||
+326
-172
@@ -2,204 +2,358 @@
|
||||
|
||||
#include <phosg/Encoding.hh>
|
||||
|
||||
#include "GSLArchive.hh"
|
||||
#include "StaticGameData.hh"
|
||||
#include "Text.hh"
|
||||
|
||||
|
||||
class CommonItemSet {
|
||||
public:
|
||||
template <typename IntT>
|
||||
struct Range {
|
||||
IntT min;
|
||||
IntT max;
|
||||
} __attribute__((packed));
|
||||
|
||||
// This file describes the structure of PSO GC ItemPT.gsl entries.
|
||||
// TODO: This is not (yet) used anywhere in newserv, but we can use it as a base
|
||||
// for PSOBB common item generation. Implement this.
|
||||
// The Table structure below describes the format of a single ItemPT entry
|
||||
// (which corresponds to a single difficulty, episode, section ID, and
|
||||
// challenge flag). ItemPT entries (within ItemPT.gsl) are named like this:
|
||||
// string_printf("ItemPT%s%s%c%1d.rel",
|
||||
// (is_challenge ? "c" : ""),
|
||||
// (is_ep2 ? "l" : ""),
|
||||
// char_for_difficulty(difficulty), // One of "nhvu"
|
||||
// section_id);
|
||||
// For GC, use be_uint16_t/be_uint32_t; for other platforms use le variants
|
||||
template <typename U16T, typename U32T>
|
||||
struct Table {
|
||||
// This data structure uses index probability tables in multiple places. An
|
||||
// index probability table is a table where each entry holds the probability
|
||||
// that that entry's index is used. For example, if the armor slot count
|
||||
// probability table contains [77, 17, 5, 1, 0], this means there is a 77%
|
||||
// chance of no slots, 17% chance of 1 slot, 5% chance of 2 slots, 1% chance
|
||||
// of 3 slots, and no chance of 4 slots. The values in index probability
|
||||
// tables do not have to add up to 100; the game sums all of them and
|
||||
// chooses a random number less than that maximum.
|
||||
|
||||
// The ItemPT structure below describes the format of a single ItemPT entry
|
||||
// (which corresponds to a single difficulty, episode, section ID, and challenge
|
||||
// flag). ItemPT entries (within ItemPT.gsl) are named like this:
|
||||
// string_printf("ItemPT_%s%s%c%1d.rel",
|
||||
// (is_challenge ? "c" : ""),
|
||||
// (is_ep2 ? "l" : ""),
|
||||
// char_for_difficulty(difficulty), // One of "nhvu"
|
||||
// section_id);
|
||||
// The area (floor) number is used in many places as well. Unlike the normal
|
||||
// area numbers, which start with Pioneer 2, the area numbers in this
|
||||
// structure start with Forest 1, and boss areas are treated as the first
|
||||
// area of the next section (so De Rol Le has Mines 1 drops, for example).
|
||||
// Final boss areas are treated as the last non-boss area (so Dark Falz
|
||||
// boxes are like Ruins 3 boxes). We refer to these adjusted area numbers as
|
||||
// (area - 1).
|
||||
|
||||
template <typename IntT>
|
||||
struct Range {
|
||||
IntT low;
|
||||
IntT high;
|
||||
} __attribute__((packed));
|
||||
// This index probability table determines the types of non-rare weapons.
|
||||
// The indexes in this table correspond to the non-rare weapon types 01
|
||||
// through 0C (Saber through Wand).
|
||||
/* 0000 */ parray<uint8_t, 0x0C> base_weapon_type_prob_table;
|
||||
|
||||
// For GC, use be_uint16_t/be_uint32_t; for other platforms use the le variants
|
||||
template <typename U16T, U32T>
|
||||
struct ItemPT {
|
||||
// This data structure uses index probability tables in multiple places. An
|
||||
// index probability table is a table where each entry holds the probability
|
||||
// that that entry's index is used. For example, if the armor slot count
|
||||
// probability table contains [77, 17, 5, 1, 0], this means there is a 77%
|
||||
// chance of no slots, 17% chance of 1 slot, 5% chance of 2 slots, 1% chance
|
||||
// of 3 slots, and no chance of 4 slots. The values in index probability
|
||||
// tables do not have to add up to 100; the game sums all of them and chooses
|
||||
// a random number less than that maximum.
|
||||
// This table specifies the base subtype for each weapon type. Negative
|
||||
// values here mean that the weapon cannot be found in the first N areas (so
|
||||
// -2, for example, means that the weapon never appears in Forest 1 or 2 at
|
||||
// all). Nonnegative values here mean the subtype can be found in all areas,
|
||||
// and specify the base subtype (usually in the range [0, 4]). The subtype
|
||||
// of weapon that actually appears depends on this value and a value from
|
||||
// the following table.
|
||||
/* 000C */ parray<int8_t, 0x0C> subtype_base_table;
|
||||
|
||||
// The area (floor) number is used in many places as well. Unlike the normal
|
||||
// area numbers, which start with Pioneer 2, the area numbers in this
|
||||
// structure start with Forest 1, and boss areas are treated as the first area
|
||||
// of the next section (so De Rol Le has Mines 1 drops, for example). Final
|
||||
// boss areas are treated as the last non-boss area (so Dark Falz boxes are
|
||||
// like Ruins 3 boxes). We refer to these adjusted area numbers as (area - 1).
|
||||
// This table specifies how many areas each weapon subtype appears in. For
|
||||
// example, if Sword (subtype 02, which is index 1 in this table and the
|
||||
// table above) has a subtype base of -2 and a subtype area lneght of 4,
|
||||
// then Sword items can be found when area - 1 is 2, 3, 4, or 5 (Cave 1
|
||||
// through Mine 1), and Gigush (the next sword subtype) can be found in Mine
|
||||
// 1 through Ruins 3.
|
||||
/* 0018 */ parray<uint8_t, 0x0C> subtype_area_length_table;
|
||||
|
||||
// This index probability table determines the types of non-rare weapons. The
|
||||
// indexes in this table correspond to the non-rare weapon types 01 through 0C
|
||||
// (Saber through Wand).
|
||||
/* 0000 */ parray<uint8_t, 0x0C> base_weapon_type_prob_table;
|
||||
// This index probability table specifies how likely each possible grind
|
||||
// value is. The table is indexed as [grind][subtype_area_index], where the
|
||||
// subtype area index is how many areas the player is beyond the first area
|
||||
// in which the subtype can first be found (clamped to [0, 3]). To continue
|
||||
// the example above, in Cave 3, subtype_area_index would be 2, since Swords
|
||||
// can first be found two areas earlier in Cave 1.
|
||||
// For example, this table could look like this:
|
||||
// [64 1E 19 14] // Chance of getting a grind +0
|
||||
// [00 1E 17 0F] // Chance of getting a grind +1
|
||||
// [00 14 14 0E] // Chance of getting a grind +2
|
||||
// ...
|
||||
// C1 C2 C3 M1 // (Episode 1 area values from the example for reference)
|
||||
/* 0024 */ parray<parray<uint8_t, 4>, 9> grind_prob_tables;
|
||||
|
||||
// This table specifies the base subtype for each weapon type. Negative values
|
||||
// here mean that the weapon cannot be found in the first N areas (so -2, for
|
||||
// example, means that the weapon never appears in Forest 1 or 2 at all).
|
||||
// Nonnegative values here mean the subtype can be found in all areas, and
|
||||
// specify the base subtype (usually in the range [0, 4]). The subtype of
|
||||
// weapon that actually appears depends on this value and a value from the
|
||||
// following table.
|
||||
/* 000C */ parray<int8_t, 0x0C> subtype_base_table;
|
||||
// This array specifies the chance that a rare weapon will have each
|
||||
// possible bonus value. This is indexed as [(bonus_value - 10 / 5)][spec],
|
||||
// so the first row refers the probability of getting a -10% bonus, the next
|
||||
// row is the chance of getting -5%, etc., all the way up to +100%. For
|
||||
// non-rare items, spec is determined randomly based on the following field;
|
||||
// for rare items, spec is always 5.
|
||||
/* 0048 */ parray<parray<U16T, 6>, 0x17> bonus_value_prob_tables;
|
||||
|
||||
// This table specifies how many areas each weapon subtype appears in. For
|
||||
// example, if Sword (subtype 02, which is index 1 in this table and the table
|
||||
// above) has a subtype base of -2 and a subtype area lneght of 4, then Sword
|
||||
// items can be found when area - 1 is 2, 3, 4, or 5 (Cave 1 through Mine 1),
|
||||
// and Gigush (the next sword subtype) can be found in Mine 1 through Ruins 3.
|
||||
/* 0018 */ parray<uint8_t, 0x0C> subtype_area_length_table;
|
||||
// This array specifies the value of spec to be used in the above lookup for
|
||||
// non-rare items. This is NOT an index probability table; this is a direct
|
||||
// lookup with indexes [bonus_index][area - 1]. A value of 0xFF in any byte
|
||||
// of this array prevents any weapon from having a bonus in that slot.
|
||||
// For example, the array might look like this:
|
||||
// [00 00 00 01 01 01 01 02 02 02]
|
||||
// [FF FF FF 00 00 00 01 01 01 01]
|
||||
// [FF FF FF FF FF FF FF FF FF 00]
|
||||
// F1 F2 C1 C2 C3 M1 M2 R1 R2 R3 // (Episode 1 areas, for reference)
|
||||
// In this example, spec is 0, 1, or 2 in all cases where a weapon can have
|
||||
// a bonus. In Forest 1 and 2 and Cave 1, weapons may have at most one
|
||||
// bonus; in all other areas except Ruins 3, they can have at most two
|
||||
// bonuses, and in Ruins 3, they can have up to three bonuses.
|
||||
/* 015C */ parray<parray<uint8_t, 10>, 3> nonrare_bonus_prob_spec;
|
||||
|
||||
// This index probability table specifies how likely each possible grind value
|
||||
// is. The table is indexed as [grind][subtype_area_index], where the subtype
|
||||
// area index is how many areas the player is beyond the first area in which
|
||||
// the subtype can first be found (clamped to [0, 3]). To continue the example
|
||||
// above, in Cave 3, subtype_area_index would be 2, since Swords can first be
|
||||
// found two areas earlier in Cave 1.
|
||||
// For example, this table could look like this:
|
||||
// [64 1E 19 14] // Chance of getting a grind +0
|
||||
// [00 1E 17 0F] // Chance of getting a grind +1
|
||||
// [00 14 14 0E] // Chance of getting a grind +2
|
||||
// ...
|
||||
// C1 C2 C3 M1 // (Episode 1 area values from the example, for reference)
|
||||
/* 0024 */ parray<parray<uint8_t, 4>, 9> grind_prob_tables;
|
||||
// This array specifies the chance that a weapon will have each bonus type.
|
||||
// The table is indexed as [bonus_type][area - 1] for non-rare items; for
|
||||
// rare items, a random value in the range [0, 9] is used instead of
|
||||
// (area - 1).
|
||||
// For example, the table might look like this:
|
||||
// [46 46 3F 3E 3E 3D 3C 3C 3A 3A] // Chance of getting no bonus
|
||||
// [14 14 0A 0A 09 02 02 04 05 05] // Chance of getting Native bonus
|
||||
// [0A 0A 12 11 11 09 09 08 08 08] // Chance of getting A.Beast bonus
|
||||
// [00 00 09 0A 0B 13 12 08 09 09] // Chance of getting Machine bonus
|
||||
// [00 00 00 01 01 08 0A 13 13 13] // Chance of getting Dark bonus
|
||||
// [00 00 00 00 00 01 01 01 01 01] // Chance of getting Hit bonus
|
||||
// F1 F2 C1 C2 C3 M1 M2 R1 R2 R3 // (Episode 1 areas, for reference)
|
||||
/* 017A */ parray<parray<uint8_t, 10>, 6> bonus_type_prob_tables;
|
||||
|
||||
// This array specifies the chance that a rare weapon will have each possible
|
||||
// bonus value. The table is indexed as [(bonus_value - 10 / 5)][spec], so the
|
||||
// first row refers the probability of getting a -10% bonus, the next row is
|
||||
// the chance of getting -5%, etc., all the way up to +100%. For non-rare
|
||||
// items, spec is determined randomly based on the following field; for rare
|
||||
// items, spec is always 5.
|
||||
/* 0048 */ parray<parray<U16T, 6>, 0x17> bonus_value_prob_tables;
|
||||
// This array (indexed by area - 1) specifies a multiplier of used in
|
||||
// special ability determination. It seems this uses the star values from
|
||||
// ItemPMT, but not yet clear exactly in what way.
|
||||
// TODO: Figure out exactly what this does. Anchor: 80106FEC
|
||||
/* 01B6 */ parray<uint8_t, 0x0A> special_mult;
|
||||
|
||||
// This array specifies the value of spec to be used in the above lookup for
|
||||
// non-rare items. This is NOT an index probability table; this is a direct
|
||||
// lookup with indexes [bonus_index][area - 1]. A value of 0xFF in any byte of
|
||||
// this array prevents any weapon from having a bonus in that slot.
|
||||
// For example, the array might look like this:
|
||||
// [00 00 00 01 01 01 01 02 02 02]
|
||||
// [FF FF FF 00 00 00 01 01 01 01]
|
||||
// [FF FF FF FF FF FF FF FF FF 00]
|
||||
// F1 F2 C1 C2 C3 M1 M2 R1 R2 R3 // (Episode 1 areas, for reference)
|
||||
// In this example, spec is 0, 1, or 2 in all cases where a weapon can have a
|
||||
// bonus. In Forest 1 and 2 and Cave 1, weapons may have at most one bonus; in
|
||||
// all other areas except Ruins 3, they can have at most two bonuses, and in
|
||||
// Ruins 3, they can have up to three bonuses.
|
||||
/* 015C */ parray<parray<uint8_t, 10>, 3> nonrare_bonus_prob_spec;
|
||||
// This array (indexed by area - 1) specifies the probability that any
|
||||
// non-rare weapon will have a special ability.
|
||||
/* 01C0 */ parray<uint8_t, 0x0A> special_percent;
|
||||
|
||||
// This array specifies the chance that a weapon will have each bonus type.
|
||||
// The table is indexed as [bonus_type][area - 1] for non-rare items; for rare
|
||||
// items, a random value in the range [0, 9] is used instead of (area - 1).
|
||||
// For example, the table might look like this:
|
||||
// [46 46 3F 3E 3E 3D 3C 3C 3A 3A] // Chance of getting no bonus
|
||||
// [14 14 0A 0A 09 02 02 04 05 05] // Chance of getting Native bonus
|
||||
// [0A 0A 12 11 11 09 09 08 08 08] // Chance of getting A.Beast bonus
|
||||
// [00 00 09 0A 0B 13 12 08 09 09] // Chance of getting Machine bonus
|
||||
// [00 00 00 01 01 08 0A 13 13 13] // Chance of getting Dark bonus
|
||||
// [00 00 00 00 00 01 01 01 01 01] // Chance of getting Hit bonus
|
||||
// F1 F2 C1 C2 C3 M1 M2 R1 R2 R3 // (Episode 1 areas, for reference)
|
||||
/* 017A */ parray<parray<uint8_t, 10>, 6> bonus_type_prob_tables;
|
||||
// TODO: Figure out exactly how this table is used. Anchor: 80106D34
|
||||
/* 01CA */ parray<uint8_t, 0x05> armor_shield_type_index_prob_table;
|
||||
|
||||
// This array (indexed by area - 1) specifies a multiplier of used in special
|
||||
// ability determination. It seems this uses the star values from ItemPMT, but
|
||||
// not yet clear exactly in what way.
|
||||
// TODO: Figure out exactly what this does. Anchor: 80106FEC
|
||||
/* 01B6 */ parray<uint8_t, 0x0A> special_mult;
|
||||
// This index probability table specifies how common each possible slot
|
||||
// count is for armor drops.
|
||||
/* 01CF */ parray<uint8_t, 0x05> armor_slot_count_prob_table;
|
||||
|
||||
// This array (indexed by area - 1) specifies the probability that any
|
||||
// non-rare weapon will have a special ability.
|
||||
/* 01C0 */ parray<uint8_t, 0x0A> special_percent;
|
||||
// These values specify maximum indexes into another array which is
|
||||
// generated at runtime. The values here are multiplied by a random float in
|
||||
// the range [0, n] to look up the value in the secondary array, which is
|
||||
// what ends up determining the unit type.
|
||||
// TODO: Figure out and document the exact logic here. Anchor: 80106364
|
||||
/* 01D4 */ parray<uint8_t, 0x0A> unit_maxes;
|
||||
|
||||
// TODO: Figure out exactly how this table is used. Anchor: 80106D34
|
||||
/* 01CA */ parray<uint8_t, 0x05> armor_shield_type_index_prob_table;
|
||||
// This index probability table is indexed by [tool_class][area - 1]. The
|
||||
// tool class refers to an entry in ItemPMT, which links it to the actual
|
||||
// item code.
|
||||
/* 01DE */ parray<parray<U16T, 0x0A>, 0x1C> tool_class_prob_table;
|
||||
|
||||
// This index probability table specifies how common each possible slot count
|
||||
// is for armor drops.
|
||||
/* 01CF */ parray<uint8_t, 0x05> armor_slot_count_prob_table;
|
||||
// This index probability table determines how likely each technique is to
|
||||
// appear. The table is indexed as [technique_num][area - 1].
|
||||
/* 040E */ parray<parray<uint8_t, 0x0A>, 0x13> technique_index_prob_table;
|
||||
|
||||
// These values specify maximum indexes into another array which is generated
|
||||
// at runtime. The values here are multiplied by a random float in the range
|
||||
// [0, n] to look up the value in the secondary array, which is what ends up
|
||||
// determining the unit type.
|
||||
// TODO: Figure out and document the exact logic here. Anchor: 80106364
|
||||
/* 01D4 */ parray<uint8_t, 0x0A> unit_maxes;
|
||||
// This table specifies the ranges for technique disk levels. The table is
|
||||
// indexed as [technique_num][area - 1]. If either min or max in the range
|
||||
// is 0xFF, or if max < min, technique disks are not dropped for that
|
||||
// technique and area pair.
|
||||
/* 04CC */ parray<parray<Range<uint8_t>, 0x0A>, 0x13> technique_level_ranges;
|
||||
|
||||
// This index probability table is indexed by [tool_class][area - 1]. The tool
|
||||
// class refers to an entry in ItemPMT, which links it to the actual item
|
||||
// code.
|
||||
/* 01DE */ parray<parray<U16T, 0x0A>, 0x1C> tool_class_prob_table;
|
||||
// Each byte in this table (indexed by enemy_type) represents the percent
|
||||
// chance that the enemy drops anything at all. (This check is done after
|
||||
// the rare drop check, so it only applies to non-rare items.)
|
||||
/* 0648 */ parray<uint8_t, 0x64> enemy_type_drop_probs;
|
||||
|
||||
// This index probability table determines how likely each technique is to
|
||||
// appear. The table is indexed as [technique_num][area - 1].
|
||||
/* 040E */ parray<parray<uint8_t, 0x0A> 0x13> technique_index_prob_table;
|
||||
// This array (indexed by enemy_id) specifies the range of meseta values
|
||||
// that each enemy can drop.
|
||||
/* 06AC */ parray<Range<U16T>, 0x64> enemy_meseta_ranges;
|
||||
|
||||
// This table specifies the ranges for technique disk levels. The table is
|
||||
// indexed as [technique_num][area - 1]. If either min or max in the range is
|
||||
// 0xFF, or if max < min, technique disks are not dropped for that technique
|
||||
// and area pair.
|
||||
/* 04CC */ parray<parray<Range<uint8_t>, 0x0A>, 0x13> technique_level_ranges;
|
||||
// Each byte in this table (indexed by enemy_type) represents the class of
|
||||
// item that the enemy can drop. The values are:
|
||||
// 00 = weapon
|
||||
// 01 = armor
|
||||
// 02 = shield
|
||||
// 03 = unit
|
||||
// 04 = tool
|
||||
// 05 = meseta
|
||||
// Anything else = no item
|
||||
/* 083C */ parray<uint8_t, 0x64> enemy_item_classes;
|
||||
|
||||
// Each byte in this table (indexed by enemy_type) represents the percent
|
||||
// chance that the enemy drops anything at all. (This check is done after the
|
||||
// rare drop check, so it only applies to non-rare items.)
|
||||
/* 0648 */ parray<uint8_t, 0x64> enemy_type_drop_probs;
|
||||
// This table (indexed by area - 1) specifies the ranges of meseta values
|
||||
// that can drop from boxes.
|
||||
/* 08A0 */ parray<Range<U16T>, 0x0A> box_meseta_ranges;
|
||||
|
||||
// This array (indexed by enemy_id) specifies the range of meseta values that
|
||||
// each enemy can drop.
|
||||
/* 06AC */ parray<Range<U16T>, 0x64> enemy_meseta_ranges;
|
||||
// This index probability table determines which type of items drop from
|
||||
// boxes. The table is indexed as [item_class][area - 1], with item_class as
|
||||
// the result value (that is, in the example below, the game looks at a
|
||||
// single column and sums the values going down, then the chosen item class
|
||||
// is one of the row indexes based on the weight values in the column.) The
|
||||
// resulting item_class value has the same meaning as in enemy_item_classes
|
||||
// above.
|
||||
// For example, this array might look like the following:
|
||||
// [07 07 08 08 06 07 08 09 09 0A] // Chances per area of a weapon drop
|
||||
// [02 02 02 02 03 02 02 02 03 03] // Chances per area of an armor drop
|
||||
// [02 02 02 02 03 02 02 02 03 03] // Chances per area of a shield drop
|
||||
// [00 00 03 03 03 04 03 04 05 05] // Chances per area of a unit drop
|
||||
// [11 11 12 12 12 12 12 12 12 12] // Chances per area of a tool drop
|
||||
// [32 32 32 32 32 32 32 32 32 32] // Chances per area of a meseta drop
|
||||
// [16 16 11 11 11 11 11 0F 0C 0B] // Chances per area of an empty box
|
||||
// F1 F2 C1 C2 C3 M1 M2 R1 R2 R3 // (Episode 1 areas, for reference)
|
||||
/* 08C8 */ parray<parray<uint8_t, 10>, 7> box_item_class_prob_tables;
|
||||
|
||||
// Each byte in this table (indexed by enemy_type) represents the class of
|
||||
// item that the enemy can drop. The values are:
|
||||
// 00 = weapon
|
||||
// 01 = armor
|
||||
// 02 = shield
|
||||
// 03 = unit
|
||||
// 04 = tool
|
||||
// 05 = meseta
|
||||
// Anything else = no item
|
||||
/* 083C */ parray<uint8_t, 0x64> enemy_item_classes;
|
||||
/* 090E */ parray<uint8_t, 2> unused1;
|
||||
|
||||
// This table (indexed by area - 1) specifies the ranges of meseta values that
|
||||
// can drop from boxes.
|
||||
/* 08A0 */ parray<Range<U16T>, 0x0A> box_meseta_ranges;
|
||||
/* 0910 */ U32T base_weapon_type_prob_table_offset;
|
||||
/* 0914 */ U32T subtype_base_table_offset;
|
||||
/* 0918 */ U32T subtype_area_length_table_offset;
|
||||
/* 091C */ U32T grind_prob_tables_offset;
|
||||
/* 0920 */ U32T armor_shield_type_index_prob_table_offset;
|
||||
/* 0924 */ U32T armor_slot_count_prob_table_offset;
|
||||
/* 0928 */ U32T enemy_meseta_ranges_offset;
|
||||
/* 092C */ U32T enemy_type_drop_probs_offset;
|
||||
/* 0930 */ U32T enemy_item_classes_offset;
|
||||
/* 0934 */ U32T box_meseta_ranges_offset;
|
||||
/* 0938 */ U32T bonus_value_prob_tables_offset;
|
||||
/* 093C */ U32T nonrare_bonus_prob_spec_offset;
|
||||
/* 0940 */ U32T bonus_type_prob_tables_offset;
|
||||
/* 0944 */ U32T special_mult_offset;
|
||||
/* 0948 */ U32T special_percent_offset;
|
||||
/* 094C */ U32T tool_class_prob_table_offset;
|
||||
/* 0950 */ U32T technique_index_prob_table_offset;
|
||||
/* 0954 */ U32T technique_level_ranges_offset;
|
||||
/* 0958 */ uint8_t armor_or_shield_type_bias;
|
||||
/* 0959 */ parray<uint8_t, 3> unused2;
|
||||
/* 095C */ U32T unit_maxes_offset;
|
||||
/* 0960 */ U32T box_item_class_prob_tables_offset;
|
||||
/* 0964 */ U32T unused_offset2;
|
||||
/* 0968 */ U32T unused_offset3;
|
||||
/* 096C */ U32T unused_offset4;
|
||||
/* 0970 */ U32T unused_offset5;
|
||||
/* 0974 */ U32T unused_offset6;
|
||||
/* 0978 */ U32T unused_offset7;
|
||||
/* 097C */ U32T unused_offset8;
|
||||
/* 0980 */ U16T unknown_f1[0x20];
|
||||
/* 09C0 */ U32T unknown_f1_offset;
|
||||
/* 09C4 */ U32T unknown_f2[3];
|
||||
/* 09D0 */ U32T offset_table_offset;
|
||||
/* 09D4 */ U32T unknown_f3[3];
|
||||
/* 09E0 (end of structure) */
|
||||
|
||||
// This index probability table determines which type of items drop from
|
||||
// boxes. The table is indexed as [item_class][area - 1], with item_class as
|
||||
// the result value (that is, in the example below, the game looks at a single
|
||||
// column and sums the values going down, then the chosen item class is one of
|
||||
// the row indexes based on the weight values in the column.) The resulting
|
||||
// item_class value has the same meaning as in enemy_item_classes above.
|
||||
// For example, this array might look like the following:
|
||||
// [07 07 08 08 06 07 08 09 09 0A] // Chances per area of a weapon drop
|
||||
// [02 02 02 02 03 02 02 02 03 03] // Chances per area of an armor drop
|
||||
// [02 02 02 02 03 02 02 02 03 03] // Chances per area of a shield drop
|
||||
// [00 00 03 03 03 04 03 04 05 05] // Chances per area of a unit drop
|
||||
// [11 11 12 12 12 12 12 12 12 12] // Chances per area of a tool drop
|
||||
// [32 32 32 32 32 32 32 32 32 32] // Chances per area of a meseta drop
|
||||
// [16 16 11 11 11 11 11 0F 0C 0B] // Chances per area of an empty box
|
||||
// F1 F2 C1 C2 C3 M1 M2 R1 R2 R3 // (Episode 1 areas, for reference)
|
||||
/* 08C8 */ parray<parray<uint8_t, 10>, 7> box_item_class_prob_tables;
|
||||
void print(FILE* stream) const;
|
||||
} __attribute__((packed));
|
||||
|
||||
/* 0910 */ U32T offset_table[0x1C];
|
||||
/* 0980 */ U16T unknown_f1[0x20];
|
||||
/* 09C0 */ U32T unknown_f1_offset;
|
||||
/* 09C4 */ U32T unknown_f2[3];
|
||||
/* 09D0 */ U32T offset_table_offset;
|
||||
/* 09D4 */ U32T unknown_f3[3];
|
||||
/* 09E0 (end of structure) */
|
||||
} __attribute__((packed));
|
||||
CommonItemSet(std::shared_ptr<const std::string> data);
|
||||
|
||||
using BETable = Table<be_uint16_t, be_uint32_t>;
|
||||
using LETable = Table<le_uint16_t, le_uint32_t>;
|
||||
|
||||
const BETable& get_table(
|
||||
Episode episode, GameMode mode, uint8_t difficulty, uint8_t secid) const;
|
||||
|
||||
private:
|
||||
GSLArchive gsl;
|
||||
};
|
||||
|
||||
|
||||
|
||||
class RELFileSet {
|
||||
public:
|
||||
template <typename ValueT, typename WeightT = ValueT>
|
||||
struct WeightTableEntry {
|
||||
ValueT value;
|
||||
WeightT weight;
|
||||
} __attribute__((packed));
|
||||
|
||||
using WeightTableEntry8 = WeightTableEntry<uint8_t>;
|
||||
using WeightTableEntry32 = WeightTableEntry<be_uint32_t>;
|
||||
|
||||
protected:
|
||||
std::shared_ptr<const string> data;
|
||||
StringReader r;
|
||||
|
||||
struct TableSpec {
|
||||
be_uint32_t offset;
|
||||
uint8_t entries_per_table;
|
||||
parray<uint8_t, 3> unused;
|
||||
} __attribute__((packed));
|
||||
|
||||
RELFileSet(std::shared_ptr<const std::string> data);
|
||||
|
||||
template <typename T>
|
||||
std::pair<const T*, size_t> get_table(
|
||||
const TableSpec& spec, size_t index) const {
|
||||
const T* entries = &r.pget<T>(
|
||||
spec.offset + index * spec.entries_per_table * sizeof(T),
|
||||
spec.entries_per_table * sizeof(T));
|
||||
return make_pair(entries, spec.entries_per_table);
|
||||
}
|
||||
};
|
||||
|
||||
class ArmorRandomSet : public RELFileSet {
|
||||
public:
|
||||
// This class parses and accesses data from ArmorRandom.rel
|
||||
ArmorRandomSet(std::shared_ptr<const std::string> data);
|
||||
|
||||
std::pair<const WeightTableEntry8*, size_t> get_armor_table(size_t index) const;
|
||||
std::pair<const WeightTableEntry8*, size_t> get_shield_table(size_t index) const;
|
||||
std::pair<const WeightTableEntry8*, size_t> get_unit_table(size_t index) const;
|
||||
|
||||
private:
|
||||
const parray<TableSpec, 3>* tables;
|
||||
};
|
||||
|
||||
class ToolRandomSet : public RELFileSet {
|
||||
public:
|
||||
// This class parses and accesses data from ToolRandom.rel
|
||||
ToolRandomSet(std::shared_ptr<const std::string> data);
|
||||
|
||||
struct TechDiskLevelEntry {
|
||||
enum class Mode : uint8_t {
|
||||
LEVEL_1 = 0,
|
||||
PLAYER_LEVEL_DIVISOR = 1,
|
||||
RANDOM_IN_RANGE = 2,
|
||||
};
|
||||
Mode mode;
|
||||
uint8_t player_level_divisor_or_min_level;
|
||||
uint8_t max_level;
|
||||
} __attribute__((packed));
|
||||
|
||||
std::pair<const uint8_t*, size_t> get_common_recovery_table(size_t index) const;
|
||||
std::pair<const WeightTableEntry8*, size_t> get_rare_recovery_table(size_t index) const;
|
||||
std::pair<const WeightTableEntry8*, size_t> get_tech_disk_table(size_t index) const;
|
||||
std::pair<const TechDiskLevelEntry*, size_t> get_tech_disk_level_table(size_t index) const;
|
||||
|
||||
private:
|
||||
const TableSpec* common_recovery_table_spec;
|
||||
const TableSpec* rare_recovery_table_spec;
|
||||
const TableSpec* tech_disk_table_spec;
|
||||
const TableSpec* tech_disk_level_table_spec;
|
||||
};
|
||||
|
||||
class WeaponRandomSet : public RELFileSet {
|
||||
public:
|
||||
// This class parses and accesses data from WeaponRandom*.rel
|
||||
WeaponRandomSet(std::shared_ptr<const std::string> data);
|
||||
|
||||
struct RangeTableEntry {
|
||||
be_uint32_t min;
|
||||
be_uint32_t max;
|
||||
} __attribute__((packed));
|
||||
|
||||
std::pair<const WeightTableEntry8*, size_t> get_weapon_type_table(size_t index) const;
|
||||
const parray<WeightTableEntry32, 6>* get_bonus_type_table(size_t which, size_t index) const;
|
||||
const RangeTableEntry* get_bonus_range(size_t which, size_t index) const;
|
||||
const parray<WeightTableEntry32, 3>* get_special_mode_table(size_t index) const;
|
||||
const RangeTableEntry* get_standard_grind_range(size_t index) const;
|
||||
const RangeTableEntry* get_favored_grind_range(size_t index) const;
|
||||
|
||||
private:
|
||||
struct Offsets {
|
||||
be_uint32_t weapon_type_table; // [{c, o -> (table)}](10)
|
||||
be_uint32_t bonus_type_table1; // [[{u32 value, u32 weight}](6)](9)
|
||||
be_uint32_t bonus_type_table2; // [[{u32 value, u32 weight}](6)](9)
|
||||
be_uint32_t bonus_range_table1; // [{u32 min_index, u32 max_index}](9)
|
||||
be_uint32_t bonus_range_table2; // [{u32 min_index, u32 max_index}](9)
|
||||
be_uint32_t special_mode_table; // [[{u32 value, u32 weight}](3)](8)
|
||||
be_uint32_t standard_grind_range_table; // [{u32 min, u32 max}](6)
|
||||
be_uint32_t favored_grind_range_table; // [{u32 min, u32 max}](6)
|
||||
} __attribute__((packed));
|
||||
|
||||
const Offsets* offsets;
|
||||
};
|
||||
|
||||
+1598
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,193 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "CommonItemSet.hh"
|
||||
#include "RareItemSet.hh"
|
||||
#include "ItemParameterTable.hh"
|
||||
#include "StaticGameData.hh"
|
||||
#include "PSOEncryption.hh"
|
||||
|
||||
|
||||
|
||||
struct ItemDropSub {
|
||||
uint8_t override_area;
|
||||
};
|
||||
|
||||
class ItemCreator {
|
||||
public:
|
||||
struct Restrictions {
|
||||
// Note: The original code has a uint8_t enable_item_restrictions here; we
|
||||
// omit it because the caller can just pass a null pointer for this struct.
|
||||
// Note: The original code has many more fields in this structure; we only
|
||||
// include those which are used by the item creator. This structure is
|
||||
// probably actually the battle rules structure in the original game, but
|
||||
// many rules apply only client-side and newserv doesn't have to care about
|
||||
// their effects, so we omit those fields. The NORMAL and NORMAL2 modes
|
||||
// behave identically from the item creator's perspective, but may actually
|
||||
// mean "keep equipment" vs. "reset equipment" on the client side.
|
||||
enum class TechDiskMode {
|
||||
NORMAL = 0,
|
||||
FORBID_ALL = 1,
|
||||
LIMIT_LEVEL = 2,
|
||||
};
|
||||
enum class WeaponAndArmorMode {
|
||||
NORMAL = 0,
|
||||
NORMAL2 = 1,
|
||||
FORBID_ALL = 2,
|
||||
FORBID_RARES = 3,
|
||||
};
|
||||
enum class ToolMode {
|
||||
NORMAL = 0,
|
||||
NORMAL2 = 1,
|
||||
FORBID_ALL = 2,
|
||||
};
|
||||
enum class MesetaDropMode {
|
||||
NORMAL = 0,
|
||||
FORBID_ALL = 1,
|
||||
UNKNOWN = 2,
|
||||
};
|
||||
TechDiskMode tech_disk_mode;
|
||||
WeaponAndArmorMode weapon_and_armor_mode;
|
||||
bool forbid_mags;
|
||||
ToolMode tool_mode;
|
||||
MesetaDropMode meseta_drop_mode;
|
||||
bool forbid_scape_dolls;
|
||||
uint8_t max_tech_disk_level; // 0xFF = no maximum
|
||||
};
|
||||
|
||||
ItemCreator(
|
||||
std::shared_ptr<const CommonItemSet> common_item_set,
|
||||
std::shared_ptr<const RareItemSet> rare_item_set,
|
||||
std::shared_ptr<const ArmorRandomSet> armor_random_set,
|
||||
std::shared_ptr<const ToolRandomSet> tool_random_set,
|
||||
std::shared_ptr<const WeaponRandomSet> weapon_random_set,
|
||||
std::shared_ptr<const ItemParameterTable> item_parameter_table,
|
||||
Episode episode,
|
||||
GameMode mode,
|
||||
uint8_t difficulty,
|
||||
uint8_t section_id,
|
||||
uint32_t random_seed,
|
||||
std::shared_ptr<const Restrictions> restrictions = nullptr);
|
||||
~ItemCreator() = default;
|
||||
|
||||
ItemData on_monster_item_drop(uint32_t enemy_type, uint8_t area);
|
||||
ItemData on_box_item_drop(uint8_t area);
|
||||
|
||||
std::vector<ItemData> generate_armor_shop_contents(size_t player_level);
|
||||
std::vector<ItemData> generate_tool_shop_contents(size_t player_level);
|
||||
std::vector<ItemData> generate_weapon_shop_contents(size_t player_level);
|
||||
|
||||
private:
|
||||
PrefixedLogger log;
|
||||
Episode episode;
|
||||
GameMode mode;
|
||||
uint8_t difficulty;
|
||||
uint8_t section_id;
|
||||
std::shared_ptr<const CommonItemSet> common_item_set;
|
||||
std::shared_ptr<const RareItemSet> rare_item_set;
|
||||
std::shared_ptr<const ArmorRandomSet> armor_random_set;
|
||||
std::shared_ptr<const ToolRandomSet> tool_random_set;
|
||||
std::shared_ptr<const WeaponRandomSet> weapon_random_set;
|
||||
std::shared_ptr<const ItemParameterTable> item_parameter_table;
|
||||
const CommonItemSet::Table<be_uint16_t, be_uint32_t>* pt;
|
||||
const RareItemSet::Table* rt;
|
||||
std::shared_ptr<const Restrictions> restrictions;
|
||||
|
||||
std::shared_ptr<ItemDropSub> item_drop_sub;
|
||||
parray<uint8_t, 0x88> unit_weights_table1;
|
||||
parray<int8_t, 0x0D> unit_weights_table2;
|
||||
|
||||
// Note: The original implementation uses 17 different random states for some
|
||||
// reason. We forego that and use only one for simplicity.
|
||||
PSOV2Encryption random_crypt;
|
||||
|
||||
bool are_rare_drops_allowed() const;
|
||||
uint8_t normalize_area_number(uint8_t area) const;
|
||||
|
||||
ItemData on_monster_item_drop_with_norm_area(
|
||||
uint32_t enemy_type, uint8_t norm_area);
|
||||
ItemData on_box_item_drop_with_norm_area(uint8_t area_norm);
|
||||
|
||||
uint32_t rand_int(uint64_t max);
|
||||
float rand_float_0_1_from_crypt(size_t which);
|
||||
|
||||
template <size_t NumRanges>
|
||||
uint32_t choose_meseta_amount(
|
||||
const parray<CommonItemSet::Range<be_uint16_t>, NumRanges> ranges,
|
||||
size_t table_index);
|
||||
|
||||
bool should_allow_meseta_drops() const;
|
||||
|
||||
ItemData check_rare_spec_and_create_rare_enemy_item(uint32_t enemy_type);
|
||||
ItemData check_rare_specs_and_create_rare_box_item(uint8_t area_norm);
|
||||
ItemData check_rate_and_create_rare_item(
|
||||
const RareItemSet::Table::Drop& drop);
|
||||
|
||||
void generate_rare_weapon_bonuses(ItemData& item, uint32_t random_sample);
|
||||
void deduplicate_weapon_bonuses(ItemData& item) const;
|
||||
void set_item_unidentified_or_present_flags_if_special(ItemData& item) const;
|
||||
void set_item_unidentified_flag_if_challenge(ItemData& item) const;
|
||||
void set_tool_item_amount_to_1(ItemData& item) const;
|
||||
|
||||
void generate_common_item_variances(uint32_t norm_area, ItemData& item);
|
||||
void generate_common_armor_slots_and_bonuses(ItemData& item);
|
||||
void generate_common_armor_slot_count(ItemData& item);
|
||||
void generate_common_armor_or_shield_type_and_variances(
|
||||
char area_norm, ItemData& item);
|
||||
void generate_common_tool_variances(uint32_t area_norm, ItemData& item);
|
||||
uint8_t generate_tech_disk_level(uint32_t tech_num, uint32_t area_norm);
|
||||
void generate_common_tool_type(uint8_t tool_class, ItemData& item) const;
|
||||
void generate_common_mag_variances(ItemData& item) const;
|
||||
void generate_common_weapon_variances(uint8_t area_norm, ItemData& item);
|
||||
void generate_common_weapon_grind(ItemData& item,
|
||||
uint8_t offset_within_subtype_range);
|
||||
void generate_common_weapon_bonuses(ItemData& item, uint8_t area_norm);
|
||||
void generate_common_weapon_special(ItemData& item, uint8_t area_norm);
|
||||
uint8_t unknown_8011CA54(uint8_t det);
|
||||
uint8_t unknown_8011C63C(uint8_t det) const;
|
||||
void generate_unit_weights_tables();
|
||||
void generate_common_unit_variances(uint8_t det, ItemData& item);
|
||||
void choose_tech_disk_level_for_tool_shop(
|
||||
ItemData& item, size_t player_level, uint8_t tech_num_index);
|
||||
static void clear_tool_item_if_invalid(ItemData& item);
|
||||
void clear_item_if_restricted(ItemData& item) const;
|
||||
|
||||
static size_t get_table_index_for_armor_shop(size_t player_level);
|
||||
static bool shop_does_not_contain_duplicate_armor(
|
||||
const std::vector<ItemData>& shop, const ItemData& item);
|
||||
static bool shop_does_not_contain_duplicate_tech_disk(
|
||||
const std::vector<ItemData>& shop, const ItemData& item);
|
||||
static bool shop_does_not_contain_duplicate_or_too_many_similar_weapons(
|
||||
const std::vector<ItemData>& shop, const ItemData& item);
|
||||
static bool shop_does_not_contain_duplicate_item_by_primary_identifier(
|
||||
const std::vector<ItemData>& shop, const ItemData& item);
|
||||
void generate_armor_shop_armors(
|
||||
std::vector<ItemData>& shop, size_t player_level);
|
||||
void generate_armor_shop_shields(
|
||||
std::vector<ItemData>& shop, size_t player_level);
|
||||
void generate_armor_shop_units(
|
||||
std::vector<ItemData>& shop, size_t player_level);
|
||||
|
||||
static size_t get_table_index_for_tool_shop(size_t player_level);
|
||||
void generate_common_tool_shop_recovery_items(
|
||||
std::vector<ItemData>& shop, size_t player_level);
|
||||
void generate_rare_tool_shop_recovery_items(
|
||||
std::vector<ItemData>& shop, size_t player_level);
|
||||
void generate_tool_shop_tech_disks(
|
||||
std::vector<ItemData>& shop, size_t player_level);
|
||||
|
||||
void generate_weapon_shop_item_grind(ItemData& item, size_t player_level);
|
||||
void generate_weapon_shop_item_special(ItemData& item, size_t player_level);
|
||||
void generate_weapon_shop_item_bonus1(ItemData& item, size_t player_level);
|
||||
void generate_weapon_shop_item_bonus2(ItemData& item, size_t player_level);
|
||||
|
||||
template <typename IntT>
|
||||
IntT get_rand_from_weighted_tables(
|
||||
const IntT* tables, size_t offset, size_t num_values, size_t stride);
|
||||
template <typename IntT, size_t X>
|
||||
IntT get_rand_from_weighted_tables_1d(const parray<IntT, X>& tables);
|
||||
template <typename IntT, size_t X, size_t Y>
|
||||
IntT get_rand_from_weighted_tables_2d_vertical(
|
||||
const parray<parray<IntT, X>, Y>& tables, size_t offset);
|
||||
};
|
||||
+1443
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,92 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <phosg/Encoding.hh>
|
||||
|
||||
#include "Text.hh"
|
||||
|
||||
|
||||
|
||||
constexpr uint32_t MESETA_IDENTIFIER = 0x00040000;
|
||||
|
||||
struct ItemMagStats {
|
||||
uint16_t iq;
|
||||
uint16_t synchro;
|
||||
uint16_t def;
|
||||
uint16_t pow;
|
||||
uint16_t dex;
|
||||
uint16_t mind;
|
||||
uint8_t flags;
|
||||
uint8_t photon_blasts;
|
||||
uint8_t color;
|
||||
|
||||
ItemMagStats()
|
||||
: iq(0),
|
||||
synchro(40),
|
||||
def(500),
|
||||
pow(0),
|
||||
dex(0),
|
||||
mind(0),
|
||||
flags(0),
|
||||
photon_blasts(0),
|
||||
color(14) { }
|
||||
|
||||
inline uint16_t def_level() const {
|
||||
return this->def / 100;
|
||||
}
|
||||
inline uint16_t pow_level() const {
|
||||
return this->pow / 100;
|
||||
}
|
||||
inline uint16_t dex_level() const {
|
||||
return this->dex / 100;
|
||||
}
|
||||
inline uint16_t mind_level() const {
|
||||
return this->mind / 100;
|
||||
}
|
||||
inline uint16_t level() const {
|
||||
return this->def_level() + this->pow_level() + this->dex_level() + this->mind_level();
|
||||
}
|
||||
};
|
||||
|
||||
struct ItemData { // 0x14 bytes
|
||||
union {
|
||||
parray<uint8_t, 12> data1;
|
||||
parray<le_uint16_t, 6> data1w;
|
||||
parray<le_uint32_t, 3> data1d;
|
||||
} __attribute__((packed));
|
||||
le_uint32_t id;
|
||||
union {
|
||||
parray<uint8_t, 4> data2;
|
||||
parray<le_uint16_t, 2> data2w;
|
||||
le_uint32_t data2d;
|
||||
} __attribute__((packed));
|
||||
|
||||
ItemData();
|
||||
ItemData(const ItemData& other);
|
||||
ItemData& operator=(const ItemData& other);
|
||||
|
||||
bool operator==(const ItemData& other) const;
|
||||
bool operator!=(const ItemData& other) const;
|
||||
|
||||
void clear();
|
||||
|
||||
std::string name(bool include_color_codes) const;
|
||||
uint32_t primary_identifier() const;
|
||||
|
||||
bool is_stackable() const;
|
||||
size_t stack_size() const;
|
||||
size_t max_stack_size() const;
|
||||
|
||||
void assign_mag_stats(const ItemMagStats& mag);
|
||||
void clear_mag_stats();
|
||||
|
||||
void set_unidentified_or_present_flag(uint16_t v);
|
||||
void set_tool_item_amount(uint8_t amount);
|
||||
void set_armor_or_shield_defense_bonus(int16_t bonus);
|
||||
void set_common_armor_evasion_bonus(int16_t bonus);
|
||||
void set_item_unit_bonus(int16_t bonus);
|
||||
|
||||
bool empty() const;
|
||||
|
||||
static bool compare_for_sort(const ItemData& a, const ItemData& b);
|
||||
} __attribute__((packed));
|
||||
@@ -0,0 +1,195 @@
|
||||
#include "ItemParameterTable.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
|
||||
|
||||
ItemParameterTable::ItemParameterTable(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);
|
||||
}
|
||||
|
||||
const ItemParameterTable::Weapon& ItemParameterTable::get_weapon(
|
||||
uint8_t data1_1, uint8_t data1_2) const {
|
||||
if (data1_1 >= 0xED) {
|
||||
throw runtime_error("weapon ID out of range");
|
||||
}
|
||||
const auto& co = this->r.pget<CountAndOffset>(
|
||||
this->offsets->weapon_table + sizeof(CountAndOffset) * data1_1);
|
||||
if (data1_2 >= co.count) {
|
||||
throw runtime_error("weapon ID out of range");
|
||||
}
|
||||
return this->r.pget<Weapon>(co.offset + sizeof(Weapon) * data1_2);
|
||||
}
|
||||
|
||||
const ItemParameterTable::ArmorOrShield& ItemParameterTable::get_armor_or_shield(
|
||||
uint8_t data1_1, uint8_t data1_2) const {
|
||||
if ((data1_1 < 1) || (data1_1 > 2)) {
|
||||
throw runtime_error("armor/shield ID out of range");
|
||||
}
|
||||
const auto& co = this->r.pget<CountAndOffset>(
|
||||
this->offsets->armor_table + sizeof(CountAndOffset) * (data1_1 - 1));
|
||||
if (data1_2 >= co.count) {
|
||||
throw runtime_error("armor/shield ID out of range");
|
||||
}
|
||||
return this->r.pget<ArmorOrShield>(co.offset + sizeof(ArmorOrShield) * data1_2);
|
||||
}
|
||||
|
||||
const ItemParameterTable::Unit& ItemParameterTable::get_unit(
|
||||
uint8_t data1_2) const {
|
||||
const auto& co = this->r.pget<CountAndOffset>(this->offsets->unit_table);
|
||||
if (data1_2 >= co.count) {
|
||||
throw runtime_error("unit ID out of range");
|
||||
}
|
||||
return this->r.pget<Unit>(co.offset + sizeof(Unit) * data1_2);
|
||||
}
|
||||
|
||||
const ItemParameterTable::Tool& ItemParameterTable::get_tool(
|
||||
uint8_t data1_1, uint8_t data1_2) const {
|
||||
if (data1_1 > 0x1A) {
|
||||
throw runtime_error("tool ID out of range");
|
||||
}
|
||||
const auto& co = this->r.pget<CountAndOffset>(
|
||||
this->offsets->tool_table + sizeof(CountAndOffset) * data1_1);
|
||||
if (data1_2 >= co.count) {
|
||||
throw runtime_error("tool ID out of range");
|
||||
}
|
||||
return this->r.pget<Tool>(co.offset + sizeof(Tool) * data1_2);
|
||||
}
|
||||
|
||||
pair<uint8_t, uint8_t> ItemParameterTable::find_tool_by_class(
|
||||
uint8_t tool_class) const {
|
||||
const auto& cos = this->r.pget<parray<CountAndOffset, 0x18>>(
|
||||
this->offsets->tool_table);
|
||||
for (size_t z = 0; z < cos.size(); z++) {
|
||||
const auto& co = cos[z];
|
||||
const auto* defs = &this->r.pget<Tool>(co.offset, sizeof(Tool) * co.count);
|
||||
for (size_t y = 0; y < co.count; y++) {
|
||||
if (defs[y].base.id == tool_class) {
|
||||
return make_pair(z, y);
|
||||
}
|
||||
}
|
||||
}
|
||||
throw out_of_range("invalid tool class");
|
||||
}
|
||||
|
||||
const ItemParameterTable::Mag& ItemParameterTable::get_mag(
|
||||
uint8_t data1_1) const {
|
||||
const auto& co = this->r.pget<CountAndOffset>(this->offsets->mag_table);
|
||||
if (data1_1 >= co.count) {
|
||||
throw runtime_error("unit ID out of range");
|
||||
}
|
||||
return this->r.pget<Mag>(co.offset + sizeof(Mag) * data1_1);
|
||||
}
|
||||
|
||||
float ItemParameterTable::get_sale_divisor(uint8_t data1_0, uint8_t data1_1) const {
|
||||
if (data1_0 == 0) { // Weapon
|
||||
if (data1_1 < 0xED) {
|
||||
return this->r.pget_f32l(
|
||||
this->offsets->weapon_sale_divisor_table + data1_1 * sizeof(float));
|
||||
}
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
const auto& divisors = this->r.pget<NonWeaponSaleDivisors>(
|
||||
this->offsets->sale_divisor_table);
|
||||
if (data1_0 == 1) {
|
||||
switch (data1_1) {
|
||||
case 1:
|
||||
return divisors.armor_divisor;
|
||||
case 2:
|
||||
return divisors.shield_divisor;
|
||||
case 3:
|
||||
return divisors.unit_divisor;
|
||||
}
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
if (data1_0 == 2) {
|
||||
return divisors.mag_divisor;
|
||||
}
|
||||
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint8_t ItemParameterTable::get_max_tech_level(uint8_t char_class, uint8_t tech_num) const {
|
||||
if (char_class >= 12) {
|
||||
throw logic_error("invalid character class");
|
||||
}
|
||||
if (tech_num >= 19) {
|
||||
throw logic_error("invalid technique number");
|
||||
}
|
||||
return r.pget_u8(this->offsets->max_tech_level_table + tech_num * 12 + char_class);
|
||||
}
|
||||
|
||||
|
||||
|
||||
const ItemParameterTable::ItemBase& ItemParameterTable::get_item_definition(
|
||||
const ItemData& item) const {
|
||||
switch (item.data1[0]) {
|
||||
case 0:
|
||||
return this->get_weapon(item.data1[1], item.data1[2]).base;
|
||||
case 1:
|
||||
if (item.data1[1] == 3) {
|
||||
return this->get_unit(item.data1[2]).base;
|
||||
} else if ((item.data1[1] == 1) || (item.data1[1] == 2)) {
|
||||
return this->get_armor_or_shield(item.data1[1], item.data1[2]).base;
|
||||
}
|
||||
throw logic_error("invalid item");
|
||||
case 2:
|
||||
return this->get_mag(item.data1[1]).base;
|
||||
case 3:
|
||||
if (item.data1[1] == 2) {
|
||||
return this->get_tool(2, item.data1[4]).base;
|
||||
} else {
|
||||
return this->get_tool(item.data1[1], item.data1[2]).base;
|
||||
}
|
||||
throw logic_error("this should be impossible");
|
||||
case 4:
|
||||
throw logic_error("item is meseta and therefore has no definition");
|
||||
default:
|
||||
throw logic_error("invalid item");
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t ItemParameterTable::get_item_stars(const ItemData& item) const {
|
||||
if (item.data1[0] == 2) {
|
||||
return (item.data1[1] > 0x27) ? 12 : 0;
|
||||
} else if (item.data1[0] < 2) {
|
||||
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]);
|
||||
return (def.item_flag & 0x80) ? 12 : 0;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
bool ItemParameterTable::is_item_rare(const ItemData& item) const {
|
||||
return (this->get_item_stars(item) >= 9);
|
||||
}
|
||||
|
||||
bool ItemParameterTable::is_unsealable_item(const ItemData& item) const {
|
||||
const auto& co = this->r.pget<CountAndOffset>(this->offsets->unsealable_table);
|
||||
const auto* defs = &this->r.pget<UnsealableItem>(
|
||||
co.offset, co.count * sizeof(UnsealableItem));
|
||||
for (size_t z = 0; z < co.count; z++) {
|
||||
if ((defs[z].item[0] == item.data1[0]) &&
|
||||
(defs[z].item[1] == item.data1[1]) &&
|
||||
(defs[z].item[2] == item.data1[2])) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,239 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <phosg/Encoding.hh>
|
||||
|
||||
#include "Text.hh"
|
||||
#include "ItemData.hh"
|
||||
|
||||
|
||||
|
||||
class ItemParameterTable {
|
||||
public:
|
||||
struct ItemBase {
|
||||
le_uint32_t id;
|
||||
le_uint16_t type;
|
||||
le_uint16_t skin;
|
||||
le_uint32_t team_points;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct ArmorOrShield {
|
||||
ItemBase base;
|
||||
le_uint16_t dfp;
|
||||
le_uint16_t evp;
|
||||
uint8_t block_particle;
|
||||
uint8_t block_effect;
|
||||
uint8_t item_class;
|
||||
uint8_t unknown_a1;
|
||||
uint8_t required_level;
|
||||
uint8_t efr;
|
||||
uint8_t eth;
|
||||
uint8_t eic;
|
||||
uint8_t edk;
|
||||
uint8_t elt;
|
||||
uint8_t dfp_range;
|
||||
uint8_t evp_range;
|
||||
uint8_t stat_boost;
|
||||
uint8_t tech_boost;
|
||||
le_uint16_t unknown_a2;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct Unit {
|
||||
ItemBase base;
|
||||
le_uint16_t stat;
|
||||
le_uint16_t stat_amount;
|
||||
le_int16_t modifier_amount;
|
||||
parray<uint8_t, 2> unused;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct Mag {
|
||||
ItemBase base;
|
||||
uint8_t feed_table;
|
||||
uint8_t unknown_a1;
|
||||
uint8_t photon_blast;
|
||||
uint8_t activation;
|
||||
uint8_t on_pb_full;
|
||||
uint8_t on_low_hp;
|
||||
uint8_t on_death;
|
||||
uint8_t on_boss;
|
||||
uint8_t on_pb_full_flag;
|
||||
uint8_t on_low_hp_flag;
|
||||
uint8_t on_death_flag;
|
||||
uint8_t on_boss_flag;
|
||||
uint8_t item_class;
|
||||
parray<uint8_t, 3> unused;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct Tool {
|
||||
ItemBase base;
|
||||
le_uint16_t amount;
|
||||
le_uint16_t tech;
|
||||
le_int32_t cost;
|
||||
uint8_t item_flag;
|
||||
parray<uint8_t, 3> unused;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct Weapon {
|
||||
ItemBase base;
|
||||
uint8_t item_class;
|
||||
uint8_t unknown_a0;
|
||||
le_uint16_t atp_min;
|
||||
le_uint16_t atp_max;
|
||||
le_uint16_t atp_required;
|
||||
le_uint16_t mst_required;
|
||||
le_uint16_t ata_required;
|
||||
le_uint16_t mst;
|
||||
uint8_t max_grind;
|
||||
uint8_t photon;
|
||||
uint8_t special;
|
||||
uint8_t ata;
|
||||
uint8_t stat_boost;
|
||||
uint8_t projectile;
|
||||
int8_t trail1_x;
|
||||
int8_t trail1_y;
|
||||
int8_t trail2_x;
|
||||
int8_t trail2_y;
|
||||
int8_t color;
|
||||
uint8_t unknown_a1;
|
||||
uint8_t unknown_a2;
|
||||
uint8_t unknown_a3;
|
||||
uint8_t unknown_a4;
|
||||
uint8_t unknown_a5;
|
||||
uint8_t tech_boost;
|
||||
uint8_t combo_type;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct MagFeedResult {
|
||||
int8_t defense;
|
||||
int8_t power;
|
||||
int8_t dexterity;
|
||||
int8_t mind;
|
||||
int8_t iq;
|
||||
int8_t sync;
|
||||
parray<uint8_t, 2> unused;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct MagFeedResultsList {
|
||||
parray<MagFeedResult, 11> results;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct MagFeedResultsTable {
|
||||
parray<MagFeedResultsList, 8> table;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct ItemStarValue {
|
||||
uint8_t num_stars;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct Special {
|
||||
le_uint16_t type;
|
||||
le_uint16_t amount;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct StatBoost {
|
||||
uint8_t stat1;
|
||||
uint8_t stat2;
|
||||
le_uint16_t amount1;
|
||||
le_uint16_t amount2;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct MaxTechniqueLevels {
|
||||
// Indexed as [tech_num][char_class]
|
||||
parray<parray<uint8_t, 12>, 19> max_level;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct ItemCombination {
|
||||
parray<uint8_t, 3> used_item;
|
||||
parray<uint8_t, 3> equipped_item;
|
||||
parray<uint8_t, 3> result_item;
|
||||
uint8_t maglevel;
|
||||
uint8_t grind;
|
||||
uint8_t level;
|
||||
uint8_t char_class;
|
||||
parray<uint8_t, 3> unused;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct TechniqueBoost {
|
||||
le_uint32_t tech1;
|
||||
le_float boost1;
|
||||
le_uint32_t tech2;
|
||||
le_float boost2;
|
||||
le_uint32_t tech3;
|
||||
le_float boost3;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct EventItem {
|
||||
parray<uint8_t, 3> item;
|
||||
uint8_t probability;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct UnsealableItem {
|
||||
parray<uint8_t, 3> item;
|
||||
uint8_t unused;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct NonWeaponSaleDivisors {
|
||||
le_float armor_divisor;
|
||||
le_float shield_divisor;
|
||||
le_float unit_divisor;
|
||||
le_float mag_divisor;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct TableOffsets {
|
||||
/* 00 / 14884 */ le_uint32_t weapon_table; // -> [{count, offset -> [Weapon]}](0xED)
|
||||
/* 04 / 1478C */ le_uint32_t armor_table; // -> [{count, offset -> [ArmorOrShield]}](2; armors and shields)
|
||||
/* 08 / 1479C */ le_uint32_t unit_table; // -> {count, offset -> [Unit]} (last if out of range)
|
||||
/* 0C / 147AC */ le_uint32_t tool_table; // -> [{count, offset -> [Tool]}](0x1A) (last if out of range)
|
||||
/* 10 / 147A4 */ le_uint32_t mag_table; // -> {count, offset -> [Mag]}
|
||||
/* 14 / 0F4B8 */ le_uint32_t attack_animation_table; // -> [uint8_t](0xED)
|
||||
/* 18 / 0DE7C */ le_uint32_t photon_color_table; // -> [0x24-byte structs](0x20)
|
||||
/* 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)
|
||||
/* 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]}]
|
||||
/* 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]}]}
|
||||
/* 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;
|
||||
|
||||
const Weapon& get_weapon(uint8_t data1_1, uint8_t data1_2) const;
|
||||
const ArmorOrShield& get_armor_or_shield(uint8_t data1_1, uint8_t data1_2) const;
|
||||
const Unit& get_unit(uint8_t data1_2) const;
|
||||
const Tool& get_tool(uint8_t data1_1, uint8_t data1_2) const;
|
||||
std::pair<uint8_t, uint8_t> find_tool_by_class(uint8_t tool_class) const;
|
||||
const Mag& get_mag(uint8_t data1_1) const;
|
||||
float get_sale_divisor(uint8_t data1_0, uint8_t data1_1) const;
|
||||
const MagFeedResult& get_mag_feed_result(uint8_t table_index, uint8_t which) const;
|
||||
uint8_t get_item_stars(uint16_t slot) const;
|
||||
uint8_t get_max_tech_level(uint8_t char_class, uint8_t tech_num) const;
|
||||
|
||||
const ItemBase& get_item_definition(const ItemData& item) const;
|
||||
uint8_t get_item_stars(const ItemData& item) const;
|
||||
bool is_item_rare(const ItemData& item) const;
|
||||
bool is_unsealable_item(const ItemData& param_1) const;
|
||||
|
||||
private:
|
||||
std::shared_ptr<const std::string> data;
|
||||
StringReader r;
|
||||
const TableOffsets* offsets;
|
||||
};
|
||||
-375
@@ -8,15 +8,6 @@ using namespace std;
|
||||
|
||||
|
||||
|
||||
uint32_t random_int(shared_ptr<mt19937> rand, uint32_t min, uint32_t max) {
|
||||
uint32_t range = max - min + 1;
|
||||
return min + ((*rand)() % range);
|
||||
}
|
||||
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/* these items all need some kind of special handling that hasn't been implemented yet.
|
||||
|
||||
030B04 = TP Material (?)
|
||||
@@ -236,369 +227,3 @@ void player_use_item(shared_ptr<Client> c, size_t item_index) {
|
||||
c->game_data.player()->remove_item(item.data.id, 1, c->version() != GameVersion::BB);
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
CommonItemData::CommonItemData(
|
||||
vector<uint32_t>&& enemy_item_categories,
|
||||
vector<uint32_t>&& box_item_categories,
|
||||
vector<vector<uint8_t>>&& unit_types) :
|
||||
enemy_item_categories(move(enemy_item_categories)),
|
||||
box_item_categories(move(box_item_categories)),
|
||||
unit_types(move(unit_types)) {
|
||||
|
||||
// sanity check the values
|
||||
if (this->enemy_item_categories.size() != 8) {
|
||||
throw invalid_argument("enemy item categories is incorrect length");
|
||||
}
|
||||
if (this->box_item_categories.size() != 8) {
|
||||
throw invalid_argument("box item categories is incorrect length");
|
||||
}
|
||||
if (this->unit_types.size() != 4) {
|
||||
throw invalid_argument("unit types is incorrect length");
|
||||
}
|
||||
|
||||
{
|
||||
uint64_t sum = 0;
|
||||
for (uint32_t v : this->enemy_item_categories) {
|
||||
sum += v;
|
||||
}
|
||||
if (sum > 0xFFFFFFFF) {
|
||||
throw invalid_argument("enemy item category sum is too large");
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
uint64_t sum = 0;
|
||||
for (uint32_t v : this->box_item_categories) {
|
||||
sum += v;
|
||||
}
|
||||
if (sum > 0xFFFFFFFF) {
|
||||
throw invalid_argument("box item category sum is too large");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CommonItemCreator::CommonItemCreator(
|
||||
std::shared_ptr<const CommonItemData> data,
|
||||
std::shared_ptr<std::mt19937> random)
|
||||
: data(data), random(random) { }
|
||||
|
||||
int32_t CommonItemCreator::decide_item_type(bool is_box) const {
|
||||
uint32_t det = (*this->random)();
|
||||
|
||||
const auto& v = is_box ? this->data->box_item_categories : this->data->enemy_item_categories;
|
||||
for (size_t x = 0; x < v.size(); x++) {
|
||||
uint32_t probability = v.at(x);
|
||||
if (probability > det) {
|
||||
return x;
|
||||
}
|
||||
det -= probability;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
ItemData CommonItemCreator::create_drop_item(bool is_box, Episode episode,
|
||||
uint8_t difficulty, uint8_t area, uint8_t) const {
|
||||
// TODO: use the section ID (last argument) to vary drop frequencies appropriately
|
||||
// change the area if it's invalid (data for the bosses are actually in other areas)
|
||||
if (area > 10) {
|
||||
if (episode == Episode::EP1) {
|
||||
if (area == 11) {
|
||||
area = 3; // dragon
|
||||
} else if (area == 12) {
|
||||
area = 6; // de rol le
|
||||
} else if (area == 13) {
|
||||
area = 8; // vol opt
|
||||
} else if (area == 14) {
|
||||
area = 10; // dark falz
|
||||
} else {
|
||||
area = 1; // unknown area -> forest 1
|
||||
}
|
||||
} else if (episode == Episode::EP2) {
|
||||
if (area == 12) {
|
||||
area = 9; // gal gryphon
|
||||
} else if (area == 13) {
|
||||
area = 10; // olga flow
|
||||
} else if (area == 14) {
|
||||
area = 3; // barba ray
|
||||
} else if (area == 15) {
|
||||
area = 6; // gol dragon
|
||||
} else {
|
||||
area = 10; // tower
|
||||
}
|
||||
} else if (episode == Episode::EP4) {
|
||||
area = 1;
|
||||
}
|
||||
}
|
||||
|
||||
ItemData item;
|
||||
|
||||
// picks a random non-rare item type, then gives it appropriate random stats
|
||||
// modify some of the constants in this section to change the system's
|
||||
// parameters
|
||||
int32_t type = this->decide_item_type(is_box);
|
||||
switch (type) {
|
||||
case 0x00: // material
|
||||
item.data1[0] = 0x03;
|
||||
item.data1[1] = 0x0B;
|
||||
item.data1[2] = random_int(this->random, 0, 6);
|
||||
break;
|
||||
|
||||
case 0x01: // equipment
|
||||
switch (random_int(this->random, 0, 3)) {
|
||||
case 0x00: // weapon
|
||||
item.data1[1] = random_int(this->random, 1, 12); // random normal class
|
||||
item.data1[2] = difficulty + random_int(this->random, 0, 2); // special type
|
||||
if ((item.data1[1] > 0x09) && (item.data1[2] > 0x04)) {
|
||||
item.data1[2] = 0x04; // no special classes above 4
|
||||
}
|
||||
item.data1[4] = 0x80; // untekked
|
||||
if (item.data1[2] < 0x04) {
|
||||
item.data1[4] |= random_int(this->random, 0, 40); // give a special
|
||||
}
|
||||
for (size_t x = 0, y = 0; (x < 5) && (y < 3); x++) { // percentages
|
||||
if (random_int(this->random, 0, 10) == 1) { // 1/11 chance of getting each type of percentage
|
||||
item.data1[6 + (y * 2)] = x + 1;
|
||||
item.data1[7 + (y * 2)] = random_int(this->random, 0, 10) * 5;
|
||||
y++;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x01: // armor
|
||||
item.data1[0] = 0x01;
|
||||
item.data1[1] = 0x01;
|
||||
item.data1[2] = (6 * difficulty) + random_int(this->random, 0, ((area / 2) + 2) - 1); // standard type based on difficulty and area
|
||||
if (item.data1[2] > 0x17) {
|
||||
item.data1[2] = 0x17; // no standard types above 0x17
|
||||
}
|
||||
if (random_int(this->random, 0, 10) == 0) { // +/-
|
||||
item.data1[4] = random_int(this->random, 0, 5);
|
||||
item.data1[6] = random_int(this->random, 0, 2);
|
||||
}
|
||||
item.data1[5] = random_int(this->random, 0, 4); // slots
|
||||
break;
|
||||
|
||||
case 0x02: // shield
|
||||
item.data1[0] = 0x01;
|
||||
item.data1[1] = 0x02;
|
||||
item.data1[2] = (5 * difficulty) + random_int(this->random, 0, ((area / 2) + 2) - 1); // standard type based on difficulty and area
|
||||
if (item.data1[2] > 0x14) {
|
||||
item.data1[2] = 0x14; // no standard types above 0x14
|
||||
}
|
||||
if (random_int(this->random, 0, 10) == 0) { // +/-
|
||||
item.data1[4] = random_int(this->random, 0, 5);
|
||||
item.data1[6] = random_int(this->random, 0, 5);
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x03: { // unit
|
||||
const auto& type_table = this->data->unit_types.at(difficulty);
|
||||
uint8_t type = type_table[random_int(this->random, 0, type_table.size() - 1)];
|
||||
if (type == 0xFF) {
|
||||
throw out_of_range("no item dropped"); // 0xFF -> no item drops
|
||||
}
|
||||
item.data1[0] = 0x01;
|
||||
item.data1[1] = 0x03;
|
||||
item.data1[2] = type;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x02: // technique
|
||||
item.data1[0] = 0x03;
|
||||
item.data1[1] = 0x02;
|
||||
item.data1[4] = random_int(this->random, 0, 18); // tech type
|
||||
if ((item.data1[4] != 14) && (item.data1[4] != 17)) { // if not ryuker or reverser, give it a level
|
||||
if (item.data1[4] == 16) { // if not anti, give it a level between 1 and 30
|
||||
if (area > 3) {
|
||||
item.data1[2] = difficulty + random_int(this->random, 0, ((area - 1) / 2) - 1);
|
||||
} else {
|
||||
item.data1[2] = difficulty;
|
||||
}
|
||||
if (item.data1[2] > 6) {
|
||||
item.data1[2] = 6;
|
||||
}
|
||||
} else {
|
||||
item.data1[2] = (5 * difficulty) + random_int(this->random, 0, ((area * 3) / 2) - 1); // else between 1 and 7
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x03: // scape doll
|
||||
item.data1[0] = 0x03;
|
||||
item.data1[1] = 0x09;
|
||||
item.data1[2] = 0x00;
|
||||
break;
|
||||
|
||||
case 0x04: // grinder
|
||||
item.data1[0] = 0x03;
|
||||
item.data1[1] = 0x0A;
|
||||
item.data1[2] = random_int(this->random, 0, 2); // mono, di, tri
|
||||
break;
|
||||
|
||||
case 0x05: // consumable
|
||||
item.data1[0] = 0x03;
|
||||
item.data1[5] = 0x01;
|
||||
switch (random_int(this->random, 0, 2)) {
|
||||
case 0: // antidote / antiparalysis
|
||||
item.data1[1] = 6;
|
||||
item.data1[2] = random_int(this->random, 0, 1);
|
||||
break;
|
||||
|
||||
case 1: // telepipe / trap vision
|
||||
item.data1[1] = 7 + random_int(this->random, 0, 1);
|
||||
break;
|
||||
|
||||
case 2: // sol / moon / star atomizer
|
||||
item.data1[1] = 3 + random_int(this->random, 0, 2);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x06: // consumable
|
||||
item.data1[0] = 0x03;
|
||||
item.data1[5] = 0x01;
|
||||
item.data1[1] = random_int(this->random, 0, 1); // mate or fluid
|
||||
if (difficulty == 0) {
|
||||
item.data1[2] = random_int(this->random, 0, 1); // only mono and di on normal
|
||||
} else if (difficulty == 3) {
|
||||
item.data1[2] = random_int(this->random, 1, 2); // only di and tri on ultimate
|
||||
} else {
|
||||
item.data1[2] = random_int(this->random, 0, 2); // else, any of the three
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x07: // meseta
|
||||
item.data1[0] = 0x04;
|
||||
item.data2d = (90 * difficulty) + (random_int(this->random, 1, 20) * (area * 2)); // meseta amount
|
||||
break;
|
||||
|
||||
default:
|
||||
throw out_of_range("no item created");
|
||||
}
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
|
||||
ItemData CommonItemCreator::create_shop_item(uint8_t difficulty,
|
||||
uint8_t item_type) const {
|
||||
static const uint8_t max_percentages[4] = {20, 35, 45, 50};
|
||||
static const uint8_t max_quantity[4] = { 1, 1, 2, 2};
|
||||
static const uint8_t max_tech_level[4] = { 8, 15, 23, 30};
|
||||
static const uint8_t max_anti_level[4] = { 2, 4, 6, 7};
|
||||
|
||||
ItemData item;
|
||||
|
||||
item.data1[0] = item_type;
|
||||
while (item.data1[0] == 2) {
|
||||
item.data1[0] = rand() % 3;
|
||||
}
|
||||
switch (item.data1[0]) {
|
||||
case 0: { // weapon
|
||||
item.data1[1] = (rand() % 12) + 1;
|
||||
if (item.data1[1] > 9) {
|
||||
item.data1[2] = difficulty;
|
||||
} else {
|
||||
item.data1[2] = (rand() & 1) + difficulty;
|
||||
}
|
||||
|
||||
item.data1[3] = rand() % 11;
|
||||
item.data1[4] = rand() % 11;
|
||||
|
||||
size_t num_percentages = 0;
|
||||
for (size_t x = 0; (x < 5) && (num_percentages < 3); x++) {
|
||||
if ((rand() % 4) == 1) {
|
||||
item.data1[(num_percentages * 2) + 6] = x;
|
||||
item.data1[(num_percentages * 2) + 7] = rand() % (max_percentages[difficulty] + 1);
|
||||
num_percentages++;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 1: // armor
|
||||
item.data1[1] = 0;
|
||||
while (item.data1[1] == 0) {
|
||||
item.data1[1] = rand() & 3;
|
||||
}
|
||||
switch (item.data1[1]) {
|
||||
case 1:
|
||||
item.data1[2] = (rand() % 6) + (difficulty * 6);
|
||||
item.data1[5] = rand() % 5;
|
||||
break;
|
||||
case 2:
|
||||
item.data2[2] = (rand() % 6) + (difficulty * 5);
|
||||
*reinterpret_cast<short*>(&item.data1[6]) = (rand() % 9) - 4;
|
||||
*reinterpret_cast<short*>(&item.data1[9]) = (rand() % 9) - 4;
|
||||
break;
|
||||
case 3:
|
||||
item.data2[2] = rand() % 0x3B;
|
||||
*reinterpret_cast<short*>(&item.data1[7]) = (rand() % 5) - 4;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case 3: // tool
|
||||
item.data1[1] = rand() % 12;
|
||||
switch (item.data1[1]) {
|
||||
case 0:
|
||||
case 1:
|
||||
if (difficulty == 0) {
|
||||
item.data1[2] = 0;
|
||||
} else if (difficulty == 1) {
|
||||
item.data1[2] = rand() % 2;
|
||||
} else if (difficulty == 2) {
|
||||
item.data1[2] = (rand() % 2) + 1;
|
||||
} else if (difficulty == 3) {
|
||||
item.data1[2] = 2;
|
||||
}
|
||||
break;
|
||||
|
||||
case 6:
|
||||
item.data1[2] = rand() % 2;
|
||||
break;
|
||||
|
||||
case 10:
|
||||
item.data1[2] = rand() % 3;
|
||||
break;
|
||||
|
||||
case 11:
|
||||
item.data1[2] = rand() % 7;
|
||||
break;
|
||||
}
|
||||
|
||||
switch (item.data1[1]) {
|
||||
case 2:
|
||||
item.data1[4] = rand() % 19;
|
||||
switch (item.data1[4]) {
|
||||
case 14:
|
||||
case 17:
|
||||
item.data1[2] = 0; // reverser & ryuker always level 1
|
||||
break;
|
||||
case 16:
|
||||
item.data1[2] = rand() % max_anti_level[difficulty];
|
||||
break;
|
||||
default:
|
||||
item.data1[2] = rand() % max_tech_level[difficulty];
|
||||
}
|
||||
break;
|
||||
case 0:
|
||||
case 1:
|
||||
case 3:
|
||||
case 4:
|
||||
case 5:
|
||||
case 6:
|
||||
case 7:
|
||||
case 8:
|
||||
case 16:
|
||||
item.data1[5] = rand() % (max_quantity[difficulty] + 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
@@ -11,28 +11,3 @@
|
||||
|
||||
|
||||
void player_use_item(std::shared_ptr<Client> c, size_t item_index);
|
||||
|
||||
struct CommonItemData {
|
||||
std::vector<uint32_t> enemy_item_categories;
|
||||
std::vector<uint32_t> box_item_categories;
|
||||
std::vector<std::vector<uint8_t>> unit_types;
|
||||
|
||||
CommonItemData(
|
||||
std::vector<uint32_t>&& enemy_item_categories,
|
||||
std::vector<uint32_t>&& box_item_categories,
|
||||
std::vector<std::vector<uint8_t>>&& unit_types);
|
||||
};
|
||||
|
||||
struct CommonItemCreator {
|
||||
std::shared_ptr<const CommonItemData> data;
|
||||
std::shared_ptr<std::mt19937> random;
|
||||
|
||||
CommonItemCreator(
|
||||
std::shared_ptr<const CommonItemData> data,
|
||||
std::shared_ptr<std::mt19937> random);
|
||||
|
||||
int32_t decide_item_type(bool is_box) const;
|
||||
ItemData create_drop_item(bool is_box, Episode episode, uint8_t difficulty,
|
||||
uint8_t area, uint8_t section_id) const;
|
||||
ItemData create_shop_item(uint8_t difficulty, uint8_t shop_type) const;
|
||||
};
|
||||
|
||||
+2
-2
@@ -13,7 +13,7 @@
|
||||
#include "Client.hh"
|
||||
#include "Episode3/BattleRecord.hh"
|
||||
#include "Episode3/Server.hh"
|
||||
#include "Items.hh"
|
||||
#include "ItemCreator.hh"
|
||||
#include "Map.hh"
|
||||
#include "Player.hh"
|
||||
#include "Quest.hh"
|
||||
@@ -75,7 +75,7 @@ struct Lobby : public std::enable_shared_from_this<Lobby> {
|
||||
// This seed is also sent to the client for rare enemy generation
|
||||
uint32_t random_seed;
|
||||
std::shared_ptr<std::mt19937> random;
|
||||
std::shared_ptr<const CommonItemCreator> common_item_creator;
|
||||
std::shared_ptr<ItemCreator> item_creator;
|
||||
|
||||
// Ep3 stuff
|
||||
// There are three kinds of Episode 3 games. All of these types have the flag
|
||||
|
||||
+152
-13
@@ -4,6 +4,7 @@
|
||||
#include <string.h>
|
||||
|
||||
#include <phosg/Filesystem.hh>
|
||||
#include <phosg/Math.hh>
|
||||
#include <phosg/JSON.hh>
|
||||
#include <phosg/Network.hh>
|
||||
#include <phosg/Strings.hh>
|
||||
@@ -80,17 +81,6 @@ void populate_state_from_config(shared_ptr<ServerState> s,
|
||||
|
||||
s->set_port_configuration(parse_port_configuration(d.at("PortConfiguration")));
|
||||
|
||||
{
|
||||
auto enemy_categories = parse_int_vector<uint32_t>(d.at("CommonItemDropRates-Enemy"));
|
||||
auto box_categories = parse_int_vector<uint32_t>(d.at("CommonItemDropRates-Box"));
|
||||
vector<vector<uint8_t>> unit_types;
|
||||
for (const auto& item : d.at("CommonUnitTypes")->as_list()) {
|
||||
unit_types.emplace_back(parse_int_vector<uint8_t>(item));
|
||||
}
|
||||
s->common_item_data.reset(new CommonItemData(
|
||||
move(enemy_categories), move(box_categories), move(unit_types)));
|
||||
}
|
||||
|
||||
auto local_address_str = d.at("LocalAddress")->as_string();
|
||||
try {
|
||||
s->local_address = s->all_addresses.at(local_address_str);
|
||||
@@ -388,6 +378,8 @@ enum class Behavior {
|
||||
DECODE_QUEST_FILE,
|
||||
DECODE_SJIS,
|
||||
EXTRACT_GSL,
|
||||
FORMAT_ITEMRT_ENTRY,
|
||||
FORMAT_ITEMRT_REL,
|
||||
SHOW_EP3_DATA,
|
||||
PARSE_OBJECT_GRAPH,
|
||||
REPLAY_LOG,
|
||||
@@ -406,6 +398,8 @@ static bool behavior_takes_input_filename(Behavior b) {
|
||||
(b == Behavior::DECRYPT_TRIVIAL_DATA) ||
|
||||
(b == Behavior::DECODE_QUEST_FILE) ||
|
||||
(b == Behavior::DECODE_SJIS) ||
|
||||
(b == Behavior::FORMAT_ITEMRT_ENTRY) ||
|
||||
(b == Behavior::FORMAT_ITEMRT_REL) ||
|
||||
(b == Behavior::EXTRACT_GSL) ||
|
||||
(b == Behavior::PARSE_OBJECT_GRAPH) ||
|
||||
(b == Behavior::REPLAY_LOG);
|
||||
@@ -533,6 +527,10 @@ int main(int argc, char** argv) {
|
||||
quest_file_type = QuestFileFormat::QST;
|
||||
} else if (!strcmp(argv[x], "cat-client")) {
|
||||
behavior = Behavior::CAT_CLIENT;
|
||||
} else if (!strcmp(argv[x], "format-itemrt-entry")) {
|
||||
behavior = Behavior::FORMAT_ITEMRT_ENTRY;
|
||||
} else if (!strcmp(argv[x], "format-itemrt-rel")) {
|
||||
behavior = Behavior::FORMAT_ITEMRT_REL;
|
||||
} else if (!strcmp(argv[x], "show-ep3-data")) {
|
||||
behavior = Behavior::SHOW_EP3_DATA;
|
||||
} else if (!strcmp(argv[x], "parse-object-graph")) {
|
||||
@@ -873,6 +871,111 @@ int main(int argc, char** argv) {
|
||||
break;
|
||||
}
|
||||
|
||||
case Behavior::FORMAT_ITEMRT_ENTRY: {
|
||||
string data = read_input_data();
|
||||
if (data.size() < sizeof(RareItemSet::Table)) {
|
||||
throw runtime_error("input data too small");
|
||||
}
|
||||
const auto& table = *reinterpret_cast<const RareItemSet::Table*>(data.data());
|
||||
|
||||
auto format_drop = +[](const RareItemSet::Table::Drop& r) -> string {
|
||||
ItemData item;
|
||||
item.data1[0] = r.item_code[0];
|
||||
item.data1[1] = r.item_code[1];
|
||||
item.data1[2] = r.item_code[2];
|
||||
string name = item.name(false);
|
||||
|
||||
uint32_t expanded_probability = RareItemSet::expand_rate(r.probability);
|
||||
auto frac = reduce_fraction<uint64_t>(expanded_probability, 0x100000000);
|
||||
return string_printf("(%02hhX => %08" PRIX32 " => %" PRIu64 "/%" PRIu64 ") %02hhX%02hhX%02hhX (%s)",
|
||||
r.probability, expanded_probability, frac.first, frac.second, r.item_code[0], r.item_code[1], r.item_code[2], name.c_str());
|
||||
};
|
||||
|
||||
fprintf(stdout, "Monster rares:\n");
|
||||
for (size_t z = 0; z < 0x65; z++) {
|
||||
const auto& r = table.monster_rares[z];
|
||||
if (r.item_code[0] == 0 && r.item_code[1] == 0 && r.item_code[2] == 0) {
|
||||
continue;
|
||||
}
|
||||
string s = format_drop(r);
|
||||
fprintf(stdout, " %02zX: %s\n", z, s.c_str());
|
||||
}
|
||||
|
||||
fprintf(stdout, "Box rares:\n");
|
||||
for (size_t z = 0; z < 0x1E; z++) {
|
||||
const auto& r = table.box_rares[z];
|
||||
if (r.item_code[0] == 0 && r.item_code[1] == 0 && r.item_code[2] == 0) {
|
||||
continue;
|
||||
}
|
||||
string s = format_drop(r);
|
||||
fprintf(stdout, " %02zX: area %02hhX %s\n", z, table.box_areas[z], s.c_str());
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case Behavior::FORMAT_ITEMRT_REL: {
|
||||
shared_ptr<string> data(new string(read_input_data()));
|
||||
RELRareItemSet rs(data);
|
||||
|
||||
auto format_drop = +[](const RareItemSet::Table::Drop& r) -> string {
|
||||
ItemData item;
|
||||
item.data1[0] = r.item_code[0];
|
||||
item.data1[1] = r.item_code[1];
|
||||
item.data1[2] = r.item_code[2];
|
||||
string name = item.name(false);
|
||||
|
||||
uint32_t expanded_probability = RareItemSet::expand_rate(r.probability);
|
||||
auto frac = reduce_fraction<uint64_t>(expanded_probability, 0x100000000);
|
||||
return string_printf("(%02hhX => %08" PRIX32 " => %" PRIu64 "/%" PRIu64 ") %02hhX%02hhX%02hhX (%s)",
|
||||
r.probability, expanded_probability, frac.first, frac.second, r.item_code[0], r.item_code[1], r.item_code[2], name.c_str());
|
||||
};
|
||||
|
||||
auto print_collection = [&](GameMode mode, Episode episode, uint8_t difficulty, uint8_t section_id) -> void {
|
||||
const auto& table = rs.get_table(episode, mode, difficulty, section_id);
|
||||
|
||||
string secid_name = name_for_section_id(section_id);
|
||||
fprintf(stdout, "%s %s %s %s\n",
|
||||
name_for_mode(mode),
|
||||
name_for_episode(episode),
|
||||
name_for_difficulty(difficulty),
|
||||
secid_name.c_str());
|
||||
|
||||
fprintf(stdout, " Monster rares:\n");
|
||||
for (size_t z = 0; z < 0x65; z++) {
|
||||
const auto& r = table.monster_rares[z];
|
||||
if (r.item_code[0] == 0 && r.item_code[1] == 0 && r.item_code[2] == 0) {
|
||||
continue;
|
||||
}
|
||||
string s = format_drop(r);
|
||||
fprintf(stdout, " %02zX: %s\n", z, s.c_str());
|
||||
}
|
||||
|
||||
fprintf(stdout, " Box rares:\n");
|
||||
for (size_t z = 0; z < 0x1E; z++) {
|
||||
const auto& r = table.box_rares[z];
|
||||
if (r.item_code[0] == 0 && r.item_code[1] == 0 && r.item_code[2] == 0) {
|
||||
continue;
|
||||
}
|
||||
string s = format_drop(r);
|
||||
fprintf(stdout, " %02zX: area %02hhX %s\n", z, table.box_areas[z], s.c_str());
|
||||
}
|
||||
};
|
||||
|
||||
static const vector<Episode> episodes = {
|
||||
Episode::EP1,
|
||||
Episode::EP2,
|
||||
Episode::EP4,
|
||||
};
|
||||
for (Episode episode : episodes) {
|
||||
for (uint8_t difficulty = 0; difficulty < 4; difficulty++) {
|
||||
for (uint8_t section_id = 0; section_id < 10; section_id++) {
|
||||
print_collection(GameMode::NORMAL, episode, difficulty, section_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case Behavior::SHOW_EP3_DATA: {
|
||||
config_log.info("Collecting Episode 3 data");
|
||||
Episode3::DataIndex index("system/ep3", Episode3::BehaviorFlag::LOAD_CARD_TEXT);
|
||||
@@ -976,10 +1079,46 @@ int main(int argc, char** argv) {
|
||||
state->level_table.reset(new LevelTable(
|
||||
state->load_bb_file("PlyLevelTbl.prs"), true));
|
||||
|
||||
config_log.info("Loading rare table");
|
||||
state->rare_item_set.reset(new RareItemSet(
|
||||
config_log.info("Loading rare item table");
|
||||
state->rare_item_set.reset(new RELRareItemSet(
|
||||
state->load_bb_file("ItemRT.rel")));
|
||||
|
||||
// Note: These files don't exist in BB, so we use the GC versions of them
|
||||
// instead. This doesn't include Episode 4 of course, so we use Episode 1
|
||||
// parameters for Episode 4 implicitly.
|
||||
{
|
||||
config_log.info("Loading common item tables");
|
||||
shared_ptr<string> pt_data(new string(load_file(
|
||||
"system/blueburst/ItemPT_GC.gsl")));
|
||||
state->common_item_set.reset(new CommonItemSet(pt_data));
|
||||
|
||||
shared_ptr<string> armor_data(new string(load_file(
|
||||
"system/blueburst/ArmorRandom_GC.rel")));
|
||||
state->armor_random_set.reset(new ArmorRandomSet(armor_data));
|
||||
|
||||
shared_ptr<string> tool_data(new string(load_file(
|
||||
"system/blueburst/ToolRandom_GC.rel")));
|
||||
state->tool_random_set.reset(new ToolRandomSet(tool_data));
|
||||
|
||||
const char* filenames[4] = {
|
||||
"system/blueburst/WeaponRandomNormal_GC.rel",
|
||||
"system/blueburst/WeaponRandomHard_GC.rel",
|
||||
"system/blueburst/WeaponRandomVeryHard_GC.rel",
|
||||
"system/blueburst/WeaponRandomUltimate_GC.rel",
|
||||
};
|
||||
for (size_t z = 0; z < 4; z++) {
|
||||
shared_ptr<string> weapon_data(new string(load_file(filenames[z])));
|
||||
state->weapon_random_sets[z].reset(new WeaponRandomSet(weapon_data));
|
||||
}
|
||||
}
|
||||
|
||||
config_log.info("Loading item definition table");
|
||||
{
|
||||
shared_ptr<string> data(new string(prs_decompress(load_file(
|
||||
"system/blueburst/ItemPMT.prs"))));
|
||||
state->item_parameter_table.reset(new ItemParameterTable(data));
|
||||
}
|
||||
|
||||
config_log.info("Collecting Episode 3 data");
|
||||
state->ep3_data_index.reset(new Episode3::DataIndex(
|
||||
"system/ep3", state->ep3_behavior_flags));
|
||||
|
||||
+7
-35
@@ -8,6 +8,7 @@
|
||||
#include <phosg/Filesystem.hh>
|
||||
#include <phosg/Hash.hh>
|
||||
|
||||
#include "ItemData.hh"
|
||||
#include "FileContentsCache.hh"
|
||||
#include "Loggers.hh"
|
||||
#include "StaticGameData.hh"
|
||||
@@ -640,35 +641,6 @@ void PlayerLobbyDataBB::clear() {
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
constexpr uint32_t MESETA_IDENTIFIER = 0x00040000;
|
||||
|
||||
ItemData::ItemData() {
|
||||
this->clear();
|
||||
}
|
||||
|
||||
void ItemData::clear() {
|
||||
this->data1d[0] = 0;
|
||||
this->data1d[1] = 0;
|
||||
this->data1d[2] = 0;
|
||||
this->id = 0xFFFFFFFF;
|
||||
this->data2d = 0;
|
||||
}
|
||||
|
||||
uint32_t ItemData::primary_identifier() const {
|
||||
// The game treats any item starting with 04 as Meseta, and ignores the rest
|
||||
// of data1 (the value is in data2)
|
||||
if (this->data1[0] == 0x04) {
|
||||
return 0x00040000;
|
||||
}
|
||||
if (this->data1[0] == 0x03 && this->data1[1] == 0x02) {
|
||||
return 0x00030200; // Tech disk (data1[2] is level, so omit it)
|
||||
} else if (this->data1[0] == 0x02) {
|
||||
return 0x00020000 | (this->data1[1] << 8); // Mag
|
||||
} else {
|
||||
return (this->data1[0] << 16) | (this->data1[1] << 8) | this->data1[2];
|
||||
}
|
||||
}
|
||||
|
||||
PlayerInventoryItem::PlayerInventoryItem() {
|
||||
this->clear();
|
||||
}
|
||||
@@ -688,7 +660,7 @@ PlayerBankItem::PlayerBankItem() {
|
||||
|
||||
PlayerBankItem::PlayerBankItem(const PlayerInventoryItem& src)
|
||||
: data(src.data),
|
||||
amount(stack_size_for_item(this->data)),
|
||||
amount(this->data.stack_size()),
|
||||
show_flags(1) { }
|
||||
|
||||
void PlayerBankItem::clear() {
|
||||
@@ -723,7 +695,7 @@ void SavedPlayerDataBB::add_item(const PlayerInventoryItem& item) {
|
||||
}
|
||||
|
||||
// Handle combinable items
|
||||
size_t combine_max = stack_size_for_item(item.data);
|
||||
size_t combine_max = item.data.max_stack_size();
|
||||
if (combine_max > 1) {
|
||||
// Get the item index if there's already a stack of the same item in the
|
||||
// player's inventory
|
||||
@@ -764,7 +736,7 @@ void PlayerBank::add_item(const PlayerBankItem& item) {
|
||||
return;
|
||||
}
|
||||
|
||||
size_t combine_max = stack_size_for_item(item.data);
|
||||
size_t combine_max = item.data.max_stack_size();
|
||||
if (combine_max > 1) {
|
||||
size_t y;
|
||||
for (y = 0; y < this->num_items; y++) {
|
||||
@@ -818,7 +790,7 @@ PlayerInventoryItem SavedPlayerDataBB::remove_item(
|
||||
// then create a new item and reduce the amount of the existing stack. Note
|
||||
// that passing amount == 0 means to remove the entire stack, so this only
|
||||
// applies if amount is nonzero.
|
||||
if (amount && (stack_size_for_item(inventory_item.data) > 1) &&
|
||||
if (amount && (inventory_item.data.stack_size() > 1) &&
|
||||
(amount < inventory_item.data.data1[5])) {
|
||||
ret = inventory_item;
|
||||
ret.data.data1[5] = amount;
|
||||
@@ -855,7 +827,7 @@ PlayerBankItem PlayerBank::remove_item(uint32_t item_id, uint32_t amount) {
|
||||
size_t index = this->find_item(item_id);
|
||||
auto& bank_item = this->items[index];
|
||||
|
||||
if (amount && (stack_size_for_item(bank_item.data) > 1) &&
|
||||
if (amount && (bank_item.data.stack_size() > 1) &&
|
||||
(amount < bank_item.data.data1[5])) {
|
||||
ret = bank_item;
|
||||
ret.data.data1[5] = amount;
|
||||
@@ -897,7 +869,7 @@ void SavedPlayerDataBB::print_inventory(FILE* stream) const {
|
||||
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];
|
||||
auto name = name_for_item(item.data, false);
|
||||
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());
|
||||
}
|
||||
|
||||
+1
-19
@@ -9,31 +9,13 @@
|
||||
#include <phosg/Encoding.hh>
|
||||
|
||||
#include "LevelTable.hh"
|
||||
#include "ItemData.hh"
|
||||
#include "Version.hh"
|
||||
#include "Text.hh"
|
||||
#include "Episode3/DataIndex.hh"
|
||||
|
||||
|
||||
|
||||
struct ItemData { // 0x14 bytes
|
||||
union {
|
||||
uint8_t data1[12];
|
||||
le_uint16_t data1w[6];
|
||||
le_uint32_t data1d[3];
|
||||
} __attribute__((packed));
|
||||
le_uint32_t id;
|
||||
union {
|
||||
uint8_t data2[4];
|
||||
le_uint16_t data2w[2];
|
||||
le_uint32_t data2d;
|
||||
} __attribute__((packed));
|
||||
|
||||
ItemData();
|
||||
void clear();
|
||||
|
||||
uint32_t primary_identifier() const;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct PlayerBankItem;
|
||||
|
||||
struct PlayerInventoryItem { // 0x1C bytes
|
||||
|
||||
@@ -956,9 +956,9 @@ static HandlerResult S_6x(shared_ptr<ServerState>,
|
||||
sizeof(G_EnemyDropItemRequest_PC_V3_BB_6x60));
|
||||
session.next_drop_item.data.id = session.next_item_id++;
|
||||
send_drop_item(session.server_channel, session.next_drop_item.data,
|
||||
true, cmd.area, cmd.x, cmd.z, cmd.request_id);
|
||||
true, cmd.area, cmd.x, cmd.z, cmd.enemy_id);
|
||||
send_drop_item(session.client_channel, session.next_drop_item.data,
|
||||
true, cmd.area, cmd.x, cmd.z, cmd.request_id);
|
||||
true, cmd.area, cmd.x, cmd.z, cmd.enemy_id);
|
||||
session.next_drop_item.clear();
|
||||
return HandlerResult::Type::SUPPRESS;
|
||||
|
||||
|
||||
+53
-15
@@ -3,20 +3,66 @@
|
||||
#include <phosg/Filesystem.hh>
|
||||
#include <phosg/Random.hh>
|
||||
|
||||
#include "StaticGameData.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
|
||||
|
||||
RareItemSet::RareItemSet(shared_ptr<const string> data) : data(data) {
|
||||
// TODO: Actually parse the GSL here instead of treating it as a blob
|
||||
uint32_t RareItemSet::expand_rate(uint8_t pc) {
|
||||
int8_t shift = ((pc >> 3) & 0x1F) - 4;
|
||||
if (shift < 0) {
|
||||
shift = 0;
|
||||
}
|
||||
return ((2 << shift) * ((pc & 7) + 7));
|
||||
}
|
||||
|
||||
bool RareItemSet::sample(mt19937& random, uint8_t pc) {
|
||||
return (random() < RareItemSet::expand_rate(pc));
|
||||
}
|
||||
|
||||
|
||||
|
||||
GSLRareItemSet::GSLRareItemSet(shared_ptr<const string> data, bool is_big_endian)
|
||||
: gsl(data, is_big_endian) { }
|
||||
|
||||
const GSLRareItemSet::Table& GSLRareItemSet::get_table(
|
||||
Episode episode, GameMode mode, uint8_t difficulty, uint8_t secid) const {
|
||||
if (difficulty > 3) {
|
||||
throw logic_error("incorrect difficulty");
|
||||
}
|
||||
if (secid > 10) {
|
||||
throw logic_error("incorrect section id");
|
||||
}
|
||||
|
||||
if ((episode != Episode::EP1) && (episode != Episode::EP2)) {
|
||||
throw runtime_error("invalid episode");
|
||||
}
|
||||
|
||||
string filename = string_printf("ItemRT%s%s%c%1d.rel",
|
||||
((mode == GameMode::CHALLENGE) ? "c" : ""),
|
||||
((episode == Episode::EP2) ? "l" : ""),
|
||||
tolower(abbreviation_for_difficulty(difficulty)), // One of "nhvu"
|
||||
secid);
|
||||
auto entry = this->gsl.get(filename);
|
||||
if (entry.second < sizeof(Table)) {
|
||||
throw runtime_error(string_printf("table %s is too small", filename.c_str()));
|
||||
}
|
||||
return *reinterpret_cast<const Table*>(entry.first);
|
||||
}
|
||||
|
||||
|
||||
|
||||
RELRareItemSet::RELRareItemSet(shared_ptr<const string> data) : data(data) {
|
||||
if (this->data->size() != sizeof(Table) * 10 * 4 * 3) {
|
||||
throw runtime_error("data file size is incorrect");
|
||||
}
|
||||
this->tables = reinterpret_cast<const Table*>(this->data->data());
|
||||
}
|
||||
|
||||
const RareItemSet::Table& RareItemSet::get_table(
|
||||
Episode episode, uint8_t difficulty, uint8_t secid) const {
|
||||
const RELRareItemSet::Table& RELRareItemSet::get_table(
|
||||
Episode episode, GameMode mode, uint8_t difficulty, uint8_t secid) const {
|
||||
(void)mode; // TODO: Shouldn't we check for challenge mode somewhere?
|
||||
|
||||
if (difficulty > 3) {
|
||||
throw logic_error("incorrect difficulty");
|
||||
}
|
||||
@@ -39,14 +85,6 @@ const RareItemSet::Table& RareItemSet::get_table(
|
||||
throw invalid_argument("incorrect episode");
|
||||
}
|
||||
|
||||
return this->tables[(ep_index * 10 * 4) + (difficulty * 10) + secid];
|
||||
}
|
||||
|
||||
bool RareItemSet::sample(mt19937& random, uint8_t pc) {
|
||||
int8_t shift = ((pc >> 3) & 0x1F) - 4;
|
||||
if (shift < 0) {
|
||||
shift = 0;
|
||||
}
|
||||
uint32_t rate = ((2 << shift) * ((pc & 7) + 7));
|
||||
return (random() < rate);
|
||||
const auto* tables = reinterpret_cast<const Table*>(this->data->data());
|
||||
return tables[(ep_index * 10 * 4) + (difficulty * 10) + secid];
|
||||
}
|
||||
|
||||
+44
-8
@@ -7,34 +7,70 @@
|
||||
#include <random>
|
||||
|
||||
#include "StaticGameData.hh"
|
||||
#include "GSLArchive.hh"
|
||||
|
||||
|
||||
|
||||
class RareItemSet {
|
||||
public:
|
||||
struct Table {
|
||||
// 0x280 in size; describes one difficulty, section ID, and episode
|
||||
// TODO: It looks like this structure can actually vary. We see the offsets
|
||||
// 0194 and 01B2 in the unused section, along with the value 1E (number of
|
||||
// box rares). In PSOGC, these all appear to be the same size/format, but
|
||||
// that's probably not strictly required to be the case.
|
||||
// 0x280 in size; describes one difficulty, section ID, and episode
|
||||
struct Drop {
|
||||
uint8_t probability;
|
||||
uint8_t item_code[3];
|
||||
} __attribute__((packed));
|
||||
Drop monster_rares[0x65]; // 0000 - 0194 in file
|
||||
uint8_t box_areas[0x1E]; // 0194 - 01B2 in file
|
||||
Drop box_rares[0x1E]; // 01B2 - 022A in file
|
||||
uint8_t unused[0x56];
|
||||
/* 0000 */ parray<Drop, 0x65> monster_rares;
|
||||
/* 0194 */ parray<uint8_t, 0x1E> box_areas;
|
||||
/* 01B2 */ parray<Drop, 0x1E> box_rares;
|
||||
/* 022A */ parray<uint8_t, 2> unknown_a1;
|
||||
/* 022C */ be_uint32_t monster_rares_offset; // == 0x0000
|
||||
/* 0230 */ be_uint32_t box_count; // == 0x1E
|
||||
/* 0234 */ be_uint32_t box_areas_offset; // == 0x0194
|
||||
/* 0238 */ be_uint32_t box_rares_offset; // == 0x01B2
|
||||
/* 023C */ be_uint32_t unused_offset1;
|
||||
/* 0240 */ parray<be_uint16_t, 0x10> unknown_a2;
|
||||
/* 0260 */ be_uint32_t unknown_a2_offset;
|
||||
/* 0264 */ be_uint32_t unknown_a2_count;
|
||||
/* 0268 */ be_uint32_t unknown_a3;
|
||||
/* 026C */ be_uint32_t unknown_a4;
|
||||
/* 0270 */ be_uint32_t offset_table_offset; // == 0x022C
|
||||
/* 0274 */ parray<be_uint32_t, 3> unknown_a5;
|
||||
/* 0280 */
|
||||
} __attribute__((packed));
|
||||
|
||||
RareItemSet(std::shared_ptr<const std::string> data);
|
||||
virtual ~RareItemSet() = default;
|
||||
|
||||
const Table& get_table(Episode episode, uint8_t difficulty, uint8_t secid) const;
|
||||
virtual const Table& get_table(Episode episode, GameMode mode, uint8_t difficulty, uint8_t secid) const = 0;
|
||||
|
||||
static bool sample(std::mt19937& rand, uint8_t probability);
|
||||
static uint32_t expand_rate(uint8_t pc);
|
||||
|
||||
protected:
|
||||
RareItemSet() = default;
|
||||
};
|
||||
|
||||
|
||||
|
||||
class GSLRareItemSet : public RareItemSet {
|
||||
public:
|
||||
GSLRareItemSet(std::shared_ptr<const std::string> data, bool is_big_endian);
|
||||
virtual ~GSLRareItemSet() = default;
|
||||
virtual const Table& get_table(Episode episode, GameMode mode, uint8_t difficulty, uint8_t secid) const;
|
||||
|
||||
private:
|
||||
GSLArchive gsl;
|
||||
};
|
||||
|
||||
class RELRareItemSet : public RareItemSet {
|
||||
public:
|
||||
RELRareItemSet(std::shared_ptr<const std::string> data);
|
||||
virtual ~RELRareItemSet() = default;
|
||||
virtual const Table& get_table(Episode episode, GameMode mode, uint8_t difficulty, uint8_t secid) const;
|
||||
|
||||
private:
|
||||
std::shared_ptr<const std::string> data;
|
||||
const Table* tables;
|
||||
};
|
||||
|
||||
+18
-4
@@ -11,16 +11,17 @@
|
||||
#include <phosg/Strings.hh>
|
||||
#include <phosg/Time.hh>
|
||||
|
||||
#include "Loggers.hh"
|
||||
#include "ChatCommands.hh"
|
||||
#include "Episode3/Tournament.hh"
|
||||
#include "FileContentsCache.hh"
|
||||
#include "ItemCreator.hh"
|
||||
#include "Loggers.hh"
|
||||
#include "ProxyServer.hh"
|
||||
#include "PSOProtocol.hh"
|
||||
#include "ReceiveSubcommands.hh"
|
||||
#include "SendCommands.hh"
|
||||
#include "StaticGameData.hh"
|
||||
#include "Text.hh"
|
||||
#include "Episode3/Tournament.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
@@ -3189,8 +3190,21 @@ shared_ptr<Lobby> create_game_generic(
|
||||
game->battle_player = battle_player;
|
||||
battle_player->set_lobby(game);
|
||||
}
|
||||
game->common_item_creator.reset(new CommonItemCreator(
|
||||
s->common_item_data, game->random));
|
||||
if (game->version == GameVersion::BB) {
|
||||
// TODO: Use appropriate restrictions here if in battle mode
|
||||
game->item_creator.reset(new ItemCreator(
|
||||
s->common_item_set,
|
||||
s->rare_item_set,
|
||||
s->armor_random_set,
|
||||
s->tool_random_set,
|
||||
s->weapon_random_sets.at(game->difficulty),
|
||||
s->item_parameter_table,
|
||||
game->episode,
|
||||
game->mode,
|
||||
game->difficulty,
|
||||
game->section_id,
|
||||
game->random_seed));
|
||||
}
|
||||
game->event = Lobby::game_event_for_lobby_event(current_lobby->event);
|
||||
game->block = 0xFF;
|
||||
game->max_clients = (game->flags & Lobby::Flag::IS_SPECTATOR_TEAM) ? 12 : 4;
|
||||
|
||||
+49
-79
@@ -486,12 +486,12 @@ static void on_player_drop_item(shared_ptr<ServerState>,
|
||||
auto item = c->game_data.player()->remove_item(cmd.item_id, 0, c->version() != GameVersion::BB);
|
||||
l->add_item(item, cmd.area, cmd.x, cmd.z);
|
||||
|
||||
auto name = name_for_item(item.data, false);
|
||||
auto name = item.data.name(false);
|
||||
l->log.info("Player %hu dropped item %08" PRIX32 " (%s) at %hu:(%g, %g)",
|
||||
cmd.header.client_id.load(), cmd.item_id.load(), name.c_str(),
|
||||
cmd.area.load(), cmd.x.load(), cmd.z.load());
|
||||
if (c->options.debug) {
|
||||
string name = name_for_item(item.data, true);
|
||||
string name = item.data.name(true);
|
||||
send_text_message_printf(c, "$C5Items: drop %08" PRIX32 "\n%s",
|
||||
cmd.item_id.load(), name.c_str());
|
||||
}
|
||||
@@ -523,11 +523,11 @@ static void on_create_inventory_item(shared_ptr<ServerState>,
|
||||
item.data = cmd.item;
|
||||
c->game_data.player()->add_item(item);
|
||||
|
||||
auto name = name_for_item(item.data, false);
|
||||
auto name = item.data.name(false);
|
||||
l->log.info("Player %hu created inventory item %08" PRIX32 " (%s)",
|
||||
cmd.header.client_id.load(), cmd.item.id.load(), name.c_str());
|
||||
if (c->options.debug) {
|
||||
string name = name_for_item(item.data, true);
|
||||
string name = item.data.name(true);
|
||||
send_text_message_printf(c, "$C5Items: create %08" PRIX32 "\n%s",
|
||||
cmd.item.id.load(), name.c_str());
|
||||
}
|
||||
@@ -560,12 +560,12 @@ static void on_drop_partial_stack(shared_ptr<ServerState>,
|
||||
item.data = cmd.data;
|
||||
l->add_item(item, cmd.area, cmd.x, cmd.z);
|
||||
|
||||
auto name = name_for_item(item.data, false);
|
||||
auto name = item.data.name(false);
|
||||
l->log.info("Player %hu split stack to create ground item %08" PRIX32 " (%s) at %hu:(%g, %g)",
|
||||
cmd.header.client_id.load(), item.data.id.load(), name.c_str(),
|
||||
cmd.area.load(), cmd.x.load(), cmd.z.load());
|
||||
if (c->options.debug) {
|
||||
string name = name_for_item(item.data, true);
|
||||
string name = item.data.name(true);
|
||||
send_text_message_printf(c, "$C5Items: split %08" PRIX32 "\n%s",
|
||||
item.data.id.load(), name.c_str());
|
||||
}
|
||||
@@ -605,12 +605,12 @@ static void on_drop_partial_stack_bb(shared_ptr<ServerState>,
|
||||
|
||||
l->add_item(item, cmd.area, cmd.x, cmd.z);
|
||||
|
||||
auto name = name_for_item(item.data, false);
|
||||
auto name = item.data.name(false);
|
||||
l->log.info("Player %hu split stack %08" PRIX32 " (removed: %s) at %hu:(%g, %g)",
|
||||
cmd.header.client_id.load(), cmd.item_id.load(), name.c_str(),
|
||||
cmd.area.load(), cmd.x.load(), cmd.z.load());
|
||||
if (c->options.debug) {
|
||||
string name = name_for_item(item.data, true);
|
||||
string name = item.data.name(true);
|
||||
send_text_message_printf(c, "$C5Items: split/BB %08" PRIX32 "\n%s",
|
||||
cmd.item_id.load(), name.c_str());
|
||||
}
|
||||
@@ -642,11 +642,11 @@ static void on_buy_shop_item(shared_ptr<ServerState>,
|
||||
item.data = cmd.item;
|
||||
c->game_data.player()->add_item(item);
|
||||
|
||||
auto name = name_for_item(item.data, false);
|
||||
auto name = item.data.name(false);
|
||||
l->log.info("Player %hu bought item %08" PRIX32 " (%s) from shop",
|
||||
cmd.header.client_id.load(), item.data.id.load(), name.c_str());
|
||||
if (c->options.debug) {
|
||||
string name = name_for_item(item.data, true);
|
||||
string name = item.data.name(true);
|
||||
send_text_message_printf(c, "$C5Items: buy %08" PRIX32 "\n%s",
|
||||
item.data.id.load(), name.c_str());
|
||||
}
|
||||
@@ -676,11 +676,11 @@ static void on_box_or_enemy_item_drop(shared_ptr<ServerState>,
|
||||
item.data = cmd.data;
|
||||
l->add_item(item, cmd.area, cmd.x, cmd.z);
|
||||
|
||||
auto name = name_for_item(item.data, false);
|
||||
auto name = item.data.name(false);
|
||||
l->log.info("Leader created ground item %08" PRIX32 " (%s) at %hhu:(%g, %g)",
|
||||
item.data.id.load(), name.c_str(), cmd.area, cmd.x.load(), cmd.z.load());
|
||||
if (c->options.debug) {
|
||||
string name = name_for_item(item.data, true);
|
||||
string name = item.data.name(true);
|
||||
send_text_message_printf(c, "$C5Items: drop %08" PRIX32 "\n%s",
|
||||
item.data.id.load(), name.c_str());
|
||||
}
|
||||
@@ -712,11 +712,11 @@ static void on_pick_up_item(shared_ptr<ServerState>,
|
||||
auto item = l->remove_item(cmd.item_id);
|
||||
effective_c->game_data.player()->add_item(item);
|
||||
|
||||
auto name = name_for_item(item.data, false);
|
||||
auto name = item.data.name(false);
|
||||
l->log.info("Player %hu picked up %08" PRIX32 " (%s)",
|
||||
cmd.header.client_id.load(), cmd.item_id.load(), name.c_str());
|
||||
if (c->options.debug) {
|
||||
string name = name_for_item(item.data, true);
|
||||
string name = item.data.name(true);
|
||||
send_text_message_printf(c, "$C5Items: pick %08" PRIX32 "\n%s",
|
||||
cmd.item_id.load(), name.c_str());
|
||||
}
|
||||
@@ -744,11 +744,11 @@ static void on_pick_up_item_request(shared_ptr<ServerState>,
|
||||
auto item = l->remove_item(cmd.item_id);
|
||||
c->game_data.player()->add_item(item);
|
||||
|
||||
auto name = name_for_item(item.data, false);
|
||||
auto name = item.data.name(false);
|
||||
l->log.info("Player %hu picked up %08" PRIX32 " (%s)",
|
||||
cmd.header.client_id.load(), cmd.item_id.load(), name.c_str());
|
||||
if (c->options.debug) {
|
||||
string name = name_for_item(item.data, true);
|
||||
string name = item.data.name(true);
|
||||
send_text_message_printf(c, "$C5Items: pick/BB %08" PRIX32 "\n%s",
|
||||
cmd.item_id.load(), name.c_str());
|
||||
}
|
||||
@@ -802,8 +802,8 @@ static void on_use_item(shared_ptr<ServerState>,
|
||||
// Note: We do this weird scoping thing because player_use_item will
|
||||
// likely delete the item, which will break the reference here.
|
||||
const auto& item = c->game_data.player()->inventory.items[index].data;
|
||||
name = name_for_item(item, false);
|
||||
colored_name = name_for_item(item, true);
|
||||
name = item.name(false);
|
||||
colored_name = item.name(true);
|
||||
}
|
||||
player_use_item(c, index);
|
||||
|
||||
@@ -825,28 +825,32 @@ static void on_open_shop_bb_or_ep3_battle_subs(shared_ptr<ServerState> s,
|
||||
if (l->is_ep3()) {
|
||||
on_ep3_battle_subs(s, l, c, command, flag, data);
|
||||
|
||||
} else if (!l->common_item_creator.get()) {
|
||||
} else if (!l->item_creator.get()) {
|
||||
throw runtime_error("received shop subcommand without item creator present");
|
||||
|
||||
} else {
|
||||
const auto& cmd = check_size_sc<G_ShopContentsRequest_BB_6xB5>(data, 0x08);
|
||||
if ((l->version == GameVersion::BB) && l->is_game()) {
|
||||
size_t num_items = 9 + (rand() % 4);
|
||||
c->game_data.shop_contents.clear();
|
||||
while (c->game_data.shop_contents.size() < num_items) {
|
||||
ItemData item_data;
|
||||
if (cmd.shop_type == 0) { // tool shop
|
||||
item_data = l->common_item_creator->create_shop_item(l->difficulty, 3);
|
||||
} else if (cmd.shop_type == 1) { // weapon shop
|
||||
item_data = l->common_item_creator->create_shop_item(l->difficulty, 0);
|
||||
} else if (cmd.shop_type == 2) { // guards shop
|
||||
item_data = l->common_item_creator->create_shop_item(l->difficulty, 1);
|
||||
} else { // unknown shop... just leave it blank I guess
|
||||
break;
|
||||
}
|
||||
if (!l->item_creator) {
|
||||
throw logic_error("item creator missing from BB game");
|
||||
}
|
||||
|
||||
item_data.id = l->generate_item_id(c->lobby_client_id);
|
||||
c->game_data.shop_contents.emplace_back(item_data);
|
||||
size_t level = c->game_data.player()->disp.level + 1;
|
||||
switch (cmd.shop_type) {
|
||||
case 0:
|
||||
c->game_data.shop_contents = l->item_creator->generate_tool_shop_contents(level);
|
||||
break;
|
||||
case 1:
|
||||
c->game_data.shop_contents = l->item_creator->generate_weapon_shop_contents(level);
|
||||
break;
|
||||
case 2:
|
||||
c->game_data.shop_contents = l->item_creator->generate_armor_shop_contents(level);
|
||||
break;
|
||||
default:
|
||||
throw runtime_error("invalid shop type");
|
||||
}
|
||||
for (auto& item : c->game_data.shop_contents) {
|
||||
item.id = l->generate_item_id(c->lobby_client_id);
|
||||
}
|
||||
|
||||
send_shop(c, cmd.shop_type);
|
||||
@@ -946,7 +950,6 @@ static void on_sort_inventory_bb(shared_ptr<ServerState>,
|
||||
// EXP/Drop Item commands
|
||||
|
||||
static bool drop_item(
|
||||
std::shared_ptr<ServerState> s,
|
||||
std::shared_ptr<Lobby> l,
|
||||
int64_t enemy_id,
|
||||
uint8_t area,
|
||||
@@ -958,48 +961,15 @@ static bool drop_item(
|
||||
|
||||
// If the game is BB, run the rare + common drop logic
|
||||
if (l->version == GameVersion::BB) {
|
||||
if (!l->common_item_creator.get()) {
|
||||
if (!l->item_creator.get()) {
|
||||
throw runtime_error("received box drop subcommand without item creator present");
|
||||
}
|
||||
|
||||
const RareItemSet::Table::Drop* drop = nullptr;
|
||||
if (s->rare_item_set) {
|
||||
const auto& table = s->rare_item_set->get_table(
|
||||
l->episode, l->difficulty, l->section_id);
|
||||
if (enemy_id < 0) {
|
||||
for (size_t z = 0; z < 30; z++) {
|
||||
if (table.box_areas[z] != area) {
|
||||
continue;
|
||||
}
|
||||
if (RareItemSet::sample(*l->random, table.box_rares[z].probability)) {
|
||||
drop = &table.box_rares[z];
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if ((enemy_id <= 0x65) &&
|
||||
RareItemSet::sample(*l->random, table.monster_rares[enemy_id].probability)) {
|
||||
drop = &table.monster_rares[enemy_id];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (drop) {
|
||||
item.data.data1[0] = drop->item_code[0];
|
||||
item.data.data1[1] = drop->item_code[1];
|
||||
item.data.data1[2] = drop->item_code[2];
|
||||
// TODO: Add random percentages / modifiers
|
||||
if (item.data.data1d[0] == 0) {
|
||||
item.data.data1[4] |= 0x80; // Make it unidentified if it's a weapon
|
||||
}
|
||||
if (enemy_id >= 0) {
|
||||
item.data = l->item_creator->on_monster_item_drop(
|
||||
l->enemies.at(enemy_id).rt_index, area);
|
||||
} else {
|
||||
try {
|
||||
item.data = l->common_item_creator->create_drop_item(
|
||||
false, l->episode, l->difficulty, area, l->section_id);
|
||||
} catch (const out_of_range&) {
|
||||
// create_common_item throws this when it doesn't want to make an item
|
||||
return true;
|
||||
}
|
||||
item.data = l->item_creator->on_box_item_drop(area);
|
||||
}
|
||||
|
||||
// If the game is not BB, forward the request to the leader instead of
|
||||
@@ -1017,7 +987,7 @@ static bool drop_item(
|
||||
return true;
|
||||
}
|
||||
|
||||
static void on_enemy_drop_item_request(shared_ptr<ServerState> s,
|
||||
static void on_enemy_drop_item_request(shared_ptr<ServerState>,
|
||||
shared_ptr<Lobby> l, shared_ptr<Client> c, uint8_t command, uint8_t flag,
|
||||
const string& data) {
|
||||
if (!l->is_game()) {
|
||||
@@ -1027,12 +997,12 @@ static void on_enemy_drop_item_request(shared_ptr<ServerState> s,
|
||||
const auto& cmd = check_size_sc<G_EnemyDropItemRequest_DC_6x60>(data,
|
||||
sizeof(G_EnemyDropItemRequest_DC_6x60),
|
||||
sizeof(G_EnemyDropItemRequest_PC_V3_BB_6x60));
|
||||
if (!drop_item(s, l, cmd.enemy_id, cmd.area, cmd.x, cmd.z, cmd.request_id)) {
|
||||
if (!drop_item(l, cmd.enemy_id, cmd.area, cmd.x, cmd.z, cmd.enemy_id)) {
|
||||
forward_subcommand(l, c, command, flag, data);
|
||||
}
|
||||
}
|
||||
|
||||
static void on_box_drop_item_request(shared_ptr<ServerState> s,
|
||||
static void on_box_drop_item_request(shared_ptr<ServerState>,
|
||||
shared_ptr<Lobby> l, shared_ptr<Client> c, uint8_t command, uint8_t flag,
|
||||
const string& data) {
|
||||
if (!l->is_game()) {
|
||||
@@ -1040,7 +1010,7 @@ static void on_box_drop_item_request(shared_ptr<ServerState> s,
|
||||
}
|
||||
|
||||
const auto& cmd = check_size_sc<G_BoxItemDropRequest_6xA2>(data);
|
||||
if (!drop_item(s, l, -1, cmd.area, cmd.x, cmd.z, cmd.request_id)) {
|
||||
if (!drop_item(l, -1, cmd.area, cmd.x, cmd.z, cmd.request_id)) {
|
||||
forward_subcommand(l, c, command, flag, data);
|
||||
}
|
||||
}
|
||||
@@ -1215,7 +1185,7 @@ static void on_destroy_inventory_item(shared_ptr<ServerState>,
|
||||
if (l->flags & Lobby::Flag::ITEM_TRACKING_ENABLED) {
|
||||
auto item = c->game_data.player()->remove_item(
|
||||
cmd.item_id, cmd.amount, c->version() != GameVersion::BB);
|
||||
auto name = name_for_item(item.data, false);
|
||||
auto name = item.data.name(false);
|
||||
l->log.info("Inventory item %hu:%08" PRIX32 " destroyed (%s)",
|
||||
cmd.header.client_id.load(), cmd.item_id.load(), name.c_str());
|
||||
c->game_data.player()->print_inventory(stderr);
|
||||
@@ -1232,7 +1202,7 @@ static void on_destroy_ground_item(shared_ptr<ServerState>,
|
||||
}
|
||||
if (l->flags & Lobby::Flag::ITEM_TRACKING_ENABLED) {
|
||||
auto item = l->remove_item(cmd.item_id);
|
||||
auto name = name_for_item(item.data, false);
|
||||
auto name = item.data.name(false);
|
||||
l->log.info("Ground item %08" PRIX32 " destroyed (%s)", cmd.item_id.load(),
|
||||
name.c_str());
|
||||
forward_subcommand(l, c, command, flag, data);
|
||||
|
||||
+2
-2
@@ -765,14 +765,14 @@ session with ID 17205AE4, run the command `on 17205AE4 sc 1D 00 04 00`.\n\
|
||||
if (command_name == "set-next-item") {
|
||||
session->next_drop_item = item;
|
||||
|
||||
string name = name_for_item(session->next_drop_item.data, true);
|
||||
string name = session->next_drop_item.data.name(true);
|
||||
send_text_message(session->client_channel, u"$C7Next drop:\n" + decode_sjis(name));
|
||||
|
||||
} else {
|
||||
send_drop_stacked_item(session->client_channel, item.data, session->area, session->x, session->z);
|
||||
send_drop_stacked_item(session->server_channel, item.data, session->area, session->x, session->z);
|
||||
|
||||
string name = name_for_item(item.data, true);
|
||||
string name = item.data.name(true);
|
||||
send_text_message(session->client_channel, u"$C7Item created:\n" + decode_sjis(name));
|
||||
}
|
||||
|
||||
|
||||
+7
-1
@@ -20,6 +20,8 @@
|
||||
#include "Lobby.hh"
|
||||
#include "Menu.hh"
|
||||
#include "Quest.hh"
|
||||
#include "CommonItemSet.hh"
|
||||
#include "ItemParameterTable.hh"
|
||||
|
||||
|
||||
|
||||
@@ -64,9 +66,13 @@ struct ServerState {
|
||||
std::shared_ptr<const QuestIndex> quest_index;
|
||||
std::shared_ptr<const LevelTable> level_table;
|
||||
std::shared_ptr<const BattleParamsIndex> battle_params;
|
||||
std::shared_ptr<const CommonItemData> common_item_data;
|
||||
std::shared_ptr<const GSLArchive> bb_data_gsl;
|
||||
std::shared_ptr<const RareItemSet> rare_item_set;
|
||||
std::shared_ptr<const CommonItemSet> common_item_set;
|
||||
std::shared_ptr<const ArmorRandomSet> armor_random_set;
|
||||
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<Episode3::TournamentIndex> ep3_tournament_index;
|
||||
|
||||
|
||||
+1
-1292
File diff suppressed because it is too large
Load Diff
@@ -34,12 +34,10 @@ const char* abbreviation_for_mode(GameMode mode);
|
||||
|
||||
|
||||
|
||||
size_t stack_size_for_item(uint8_t data0, uint8_t data1);
|
||||
size_t stack_size_for_item(const ItemData& item);
|
||||
size_t max_stack_size_for_item(uint8_t data0, uint8_t data1);
|
||||
|
||||
extern const std::unordered_map<uint8_t, const char*> name_for_weapon_special;
|
||||
extern const std::unordered_map<uint8_t, const char*> name_for_s_rank_special;
|
||||
extern const std::unordered_map<uint32_t, const char*> name_for_primary_identifier;
|
||||
extern const vector<string> tech_id_to_name;
|
||||
extern const unordered_map<string, uint8_t> name_to_tech_id;
|
||||
|
||||
const std::string& name_for_technique(uint8_t tech);
|
||||
std::u16string u16name_for_technique(uint8_t tech);
|
||||
@@ -73,5 +71,3 @@ const char* name_for_difficulty(uint8_t difficulty);
|
||||
char abbreviation_for_difficulty(uint8_t difficulty);
|
||||
|
||||
char char_for_language_code(uint8_t language);
|
||||
|
||||
std::string name_for_item(const ItemData& item, bool include_color_codes);
|
||||
|
||||
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
@@ -1,154 +0,0 @@
|
||||
# Types of nonrare items that enemies drop.
|
||||
|
||||
# NORMAL MODE
|
||||
|
||||
# shield = 01
|
||||
# armor = 02
|
||||
# unit = 03
|
||||
# random = 04
|
||||
# unknown = 05
|
||||
# weapon = 06
|
||||
|
||||
Drop_Item_00_00 06 # booma
|
||||
Drop_Item_00_01 06 # gobooma
|
||||
Drop_Item_00_02 01 # gigobooma
|
||||
Drop_Item_00_03 02 # savage wolf
|
||||
Drop_Item_00_04 06 # barbarous wolf
|
||||
Drop_Item_00_05 02 # rag rappy
|
||||
Drop_Item_00_06 01 # al rappy
|
||||
Drop_Item_00_07 06 # mothmant
|
||||
Drop_Item_00_7A 05 # monest
|
||||
Drop_Item_00_08 06 # hildebear
|
||||
Drop_Item_00_09 06 # hildeblue
|
||||
Drop_Item_00_0A 04 # dragon
|
||||
Drop_Item_00_0B 06 # evil shark
|
||||
Drop_Item_00_0C 06 # pal shark
|
||||
Drop_Item_00_0D 03 # guil shark
|
||||
Drop_Item_00_0E 02 # poison lily
|
||||
Drop_Item_00_0F 03 # nar lily
|
||||
Drop_Item_00_10 06 # grass assassin
|
||||
Drop_Item_00_11 01 # nano dragon
|
||||
Drop_Item_00_12 02 # pofuilly slime
|
||||
Drop_Item_00_13 03 # pouilly slime
|
||||
Drop_Item_00_14 01 # pan arms
|
||||
Drop_Item_00_15 03 # migium
|
||||
Drop_Item_00_16 02 # hidoom
|
||||
Drop_Item_00_17 04 # de rol le
|
||||
Drop_Item_00_18 06 # gillchic
|
||||
Drop_Item_00_19 06 # dubchic
|
||||
Drop_Item_00_1A 01 # canadine
|
||||
Drop_Item_00_1B 02 # canane
|
||||
Drop_Item_00_1C 06 # sinow beat
|
||||
Drop_Item_00_1D 03 # sinow gold
|
||||
Drop_Item_00_1E 01 # garanz
|
||||
Drop_Item_00_1F 04 # vol opt
|
||||
Drop_Item_00_20 06 # dimenian
|
||||
Drop_Item_00_21 06 # la dimenian
|
||||
Drop_Item_00_22 02 # so dimenian
|
||||
Drop_Item_00_23 06 # claw
|
||||
Drop_Item_00_24 03 # bulclaw
|
||||
Drop_Item_00_25 06 # bulk
|
||||
Drop_Item_00_26 02 # delsaber
|
||||
Drop_Item_00_27 02 # dark belra
|
||||
Drop_Item_00_28 01 # dark gunner
|
||||
Drop_Item_00_29 01 # death dunner
|
||||
Drop_Item_00_2A 03 # chaos sorceror
|
||||
Drop_Item_00_2B 03 # chaos bringer
|
||||
Drop_Item_00_2C 04 # dark falz
|
||||
|
||||
Drop_Item_00_2E 06 # dimenian
|
||||
Drop_Item_00_2F 06 # la dimenian
|
||||
Drop_Item_00_30 01 # so dimenian
|
||||
Drop_Item_00_31 02 # rag rappy
|
||||
Drop_Item_00_32 01 # love rappy
|
||||
Drop_Item_00_33 04 # hallo rappy
|
||||
Drop_Item_00_34 04 # saint rappy
|
||||
Drop_Item_00_35 04 # egg rappy
|
||||
Drop_Item_00_36 06 # grass assassin
|
||||
Drop_Item_00_37 02 # poison lily
|
||||
Drop_Item_00_38 03 # nar lily
|
||||
Drop_Item_00_39 06 # mothmant
|
||||
Drop_Item_00_3A 06 # hildebear
|
||||
Drop_Item_00_3B 06 # hildeblue
|
||||
Drop_Item_00_3C 02 # dark belra
|
||||
Drop_Item_00_3D 04 # barba ray
|
||||
|
||||
Drop_Item_00_3E 02 # savage wolf
|
||||
Drop_Item_00_3F 06 # barbarous wolf
|
||||
Drop_Item_00_40 06 # gillchic
|
||||
Drop_Item_00_41 06 # dubchic
|
||||
Drop_Item_00_42 02 # delsaber
|
||||
Drop_Item_00_43 01 # pan arms
|
||||
Drop_Item_00_44 03 # migium
|
||||
Drop_Item_00_45 02 # hidoom
|
||||
Drop_Item_00_46 01 # garanz
|
||||
Drop_Item_00_47 03 # chaos sorceror
|
||||
Drop_Item_00_48 04 # gol dragon
|
||||
|
||||
Drop_Item_00_49 06 # merillia
|
||||
Drop_Item_00_4A 06 # meriltas
|
||||
Drop_Item_00_4B 06 # ul gibbon
|
||||
Drop_Item_00_4C 01 # zol gibbon
|
||||
Drop_Item_00_4D 06 # gee
|
||||
Drop_Item_00_4E 06 # sinow berill
|
||||
Drop_Item_00_4F 02 # sinow spigell
|
||||
Drop_Item_00_50 05 # mericarol
|
||||
Drop_Item_00_51 02 # merikle
|
||||
Drop_Item_00_52 01 # mericus
|
||||
Drop_Item_00_53 03 # gibbles
|
||||
Drop_Item_00_54 01 # gi gue
|
||||
Drop_Item_00_55 06 # del lily
|
||||
Drop_Item_00_56 02 # ill gill
|
||||
Drop_Item_00_57 03 # epsilon
|
||||
Drop_Item_00_58 04 # gal gryphon
|
||||
|
||||
Drop_Item_00_59 06 # dolmolm
|
||||
Drop_Item_00_5A 06 # dolmdarl
|
||||
Drop_Item_00_5B 03 # recobox
|
||||
Drop_Item_00_5C 06 # recon
|
||||
Drop_Item_00_5D 01 # deldepth
|
||||
Drop_Item_00_5E 06 # delbiter
|
||||
Drop_Item_00_5F 06 # morfos
|
||||
Drop_Item_00_60 06 # sinow zoa
|
||||
Drop_Item_00_61 02 # sinow zele
|
||||
Drop_Item_00_62 04 # olga flow
|
||||
|
||||
Drop_Item_00_64 05 # boota
|
||||
Drop_Item_00_65 05 # ze boota
|
||||
Drop_Item_00_66 05 # ba boota
|
||||
Drop_Item_00_67 05 # sand rappy
|
||||
Drop_Item_00_68 05 # del rappy
|
||||
Drop_Item_00_69 05 # satellite lizard
|
||||
Drop_Item_00_6A 05 # yowie
|
||||
Drop_Item_00_6B 05 # astark
|
||||
Drop_Item_00_6C 05 # zu
|
||||
Drop_Item_00_6D 05 # pazuzu
|
||||
Drop_Item_00_6E 05 # dorphon
|
||||
Drop_Item_00_6F 05 # dorphon eclair
|
||||
Drop_Item_00_70 05 # goran
|
||||
Drop_Item_00_71 05 # pyro goran
|
||||
Drop_Item_00_72 05 # goran detonator
|
||||
Drop_Item_00_73 05 # merissa a
|
||||
Drop_Item_00_74 05 # merissa aa
|
||||
Drop_Item_00_75 05 # girtablulu
|
||||
Drop_Item_00_76 05 # saint-million
|
||||
Drop_Item_00_77 05 # shambertin
|
||||
Drop_Item_00_78 05 # kondrieu
|
||||
|
||||
# HARD MODE
|
||||
|
||||
Drop_Item_01_30 02 # so dimenian
|
||||
Drop_Item_01_56 01 # ill gill
|
||||
|
||||
# VERY HARD MODE
|
||||
|
||||
Drop_Item_02_02 03 # gigobooma
|
||||
Drop_Item_02_04 01 # barbarous wolf 08
|
||||
Drop_Item_02_05 01 # rag rappy 05
|
||||
Drop_Item_02_31 01 # rag rappy
|
||||
Drop_Item_02_3F 01 # barbarous wolf
|
||||
Drop_Item_02_56 02 # ill gill
|
||||
|
||||
# ULTIMATE MODE
|
||||
|
||||
# no changes
|
||||
Binary file not shown.
@@ -316,63 +316,4 @@
|
||||
// be turned off here. This option has no effect on Blue Burst games - item
|
||||
// tracking is always enabled for them.
|
||||
"EnableItemTracking": true,
|
||||
|
||||
// Item drop rates for non-rare items in BB games. For each type (boxes or
|
||||
// enemies), all the categories must add up to a number less than 0x100000000.
|
||||
// Each number is a probability (out of 0x100000000) that the given item type
|
||||
// will appear.
|
||||
"CommonItemDropRates-Enemy": [
|
||||
0x03000000, // material
|
||||
0x20000000, // equipment
|
||||
0x06000000, // technique disk
|
||||
0x01800000, // scape doll
|
||||
0x06000000, // grinder
|
||||
0x10000000, // atomizers, etc.
|
||||
0x20000000, // mates/fluids
|
||||
0x40000000, // meseta
|
||||
],
|
||||
"CommonItemDropRates-Box": [
|
||||
0x00800000, // material
|
||||
0x20000000, // equipment
|
||||
0x01000000, // technique disk
|
||||
0x02000000, // scape doll
|
||||
0x08000000, // grinder
|
||||
0x10000000, // atomizers, etc.
|
||||
0x20000000, // mates/fluids
|
||||
0x80000000, // meseta
|
||||
],
|
||||
|
||||
// Unit drop rates for non-rare items in BB games. Each entry is an array of
|
||||
// unit types, one array per difficulty. Each entry in the array has an equal
|
||||
// probability of dropping. If a unit type is 0xFF, then no item will drop.
|
||||
"CommonUnitTypes": [
|
||||
// normal
|
||||
[0x00, 0x00, 0x00, 0x04, 0x04, 0x04, 0x08, 0x08, 0x08, 0x0C, 0x0C, 0x0C,
|
||||
0x10, 0x10, 0x10, 0x14, 0x14, 0x14, 0x18, 0x18, 0x18, 0x21, 0x21, 0x21,
|
||||
0x24, 0x24, 0x24, 0x27, 0x27, 0x27, 0x2A, 0x2A, 0x2A, 0xFF, 0xFF, 0xFF,
|
||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
||||
0xFF, 0xFF, 0xFF, 0xFF],
|
||||
// hard
|
||||
[0x01, 0x01, 0x05, 0x05, 0x09, 0x09, 0x0D, 0x0D, 0x11, 0x11, 0x15, 0x15,
|
||||
0x19, 0x19, 0x21, 0x21, 0x24, 0x24, 0x27, 0x27, 0x2A, 0x2A, 0x30, 0x30,
|
||||
0x33, 0x33, 0x36, 0x36, 0x39, 0x39, 0x3C, 0x3C, 0x3F, 0x3F, 0xFF, 0xFF,
|
||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
||||
0xFF, 0xFF, 0xFF, 0xFF],
|
||||
// very hard
|
||||
[0x02, 0x02, 0x06, 0x06, 0x0A, 0x0A, 0x0E, 0x0E, 0x12, 0x12, 0x16, 0x16,
|
||||
0x1A, 0x1A, 0x22, 0x22, 0x25, 0x25, 0x28, 0x28, 0x2B, 0x2B, 0x31, 0x31,
|
||||
0x34, 0x34, 0x37, 0x37, 0x3A, 0x3A, 0x3D, 0x3D, 0x3F, 0x3F, 0x41, 0x41,
|
||||
0x42, 0x42, 0x43, 0x43, 0x44, 0x44, 0x45, 0x45, 0x46, 0x46, 0x47, 0x47,
|
||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
||||
0xFF, 0xFF, 0xFF, 0xFF],
|
||||
// ultimate
|
||||
[0x02, 0x02, 0x06, 0x06, 0x0A, 0x0A, 0x0E, 0x0E, 0x12, 0x12, 0x16, 0x16,
|
||||
0x1A, 0x1A, 0x23, 0x23, 0x26, 0x26, 0x29, 0x29, 0x2C, 0x2C, 0x31, 0x31,
|
||||
0x34, 0x34, 0x37, 0x37, 0x3A, 0x3A, 0x3D, 0x3D, 0x3F, 0x3F, 0x41, 0x41,
|
||||
0x42, 0x42, 0x43, 0x43, 0x44, 0x44, 0x45, 0x45, 0x46, 0x46, 0x47, 0x47,
|
||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
||||
0xFF, 0xFF, 0xFF, 0xFF],
|
||||
],
|
||||
}
|
||||
Reference in New Issue
Block a user