use GC logic for BB nonrare item drop generation and shops

This commit is contained in:
Martin Michelsen
2023-03-07 23:16:42 -08:00
parent 6cdbc3e8e0
commit 838e53a91e
36 changed files with 4577 additions and 2270 deletions
+4 -4
View File
@@ -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));
}
}
+2 -2
View File
@@ -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;
+140
View File
@@ -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
View File
@@ -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
View File
File diff suppressed because it is too large Load Diff
+193
View File
@@ -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
View File
File diff suppressed because it is too large Load Diff
+92
View File
@@ -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));
+195
View File
@@ -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;
}
+239
View File
@@ -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
View File
@@ -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;
}
-25
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
+2 -2
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
File diff suppressed because it is too large Load Diff
+3 -7
View File
@@ -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);