use system randomness by default unless overridden
This commit is contained in:
@@ -26,3 +26,4 @@
|
|||||||
|
|
||||||
- Test all quest item subcommands
|
- Test all quest item subcommands
|
||||||
- Figure out why Pouilly Slime EXP doesn't work
|
- Figure out why Pouilly Slime EXP doesn't work
|
||||||
|
- Make server-specified rare enemies work with maps loaded by the proxy
|
||||||
|
|||||||
@@ -298,22 +298,22 @@ struct ProbabilityTable {
|
|||||||
return this->items[--this->count];
|
return this->items[--this->count];
|
||||||
}
|
}
|
||||||
|
|
||||||
void shuffle(PSOLFGEncryption& random_crypt) {
|
void shuffle(std::shared_ptr<PSOLFGEncryption> opt_rand_crypt) {
|
||||||
for (size_t z = 1; z < this->count; z++) {
|
for (size_t z = 1; z < this->count; z++) {
|
||||||
size_t other_z = random_crypt.next() % (z + 1);
|
size_t other_z = random_from_optional_crypt(opt_rand_crypt) % (z + 1);
|
||||||
ItemT t = this->items[z];
|
ItemT t = this->items[z];
|
||||||
this->items[z] = this->items[other_z];
|
this->items[z] = this->items[other_z];
|
||||||
this->items[other_z] = t;
|
this->items[other_z] = t;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ItemT sample(PSOLFGEncryption& random_crypt) const {
|
ItemT sample(std::shared_ptr<PSOLFGEncryption> opt_rand_crypt) const {
|
||||||
if (this->count == 0) {
|
if (this->count == 0) {
|
||||||
throw std::runtime_error("sample from empty probability table");
|
throw std::runtime_error("sample from empty probability table");
|
||||||
} else if (this->count == 1) {
|
} else if (this->count == 1) {
|
||||||
return this->items[0];
|
return this->items[0];
|
||||||
} else {
|
} else {
|
||||||
return this->items[random_crypt.next() % this->count];
|
return this->items[random_from_optional_crypt(opt_rand_crypt) % this->count];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -205,8 +205,8 @@ void DeckState::do_mulligan(bool is_nte) {
|
|||||||
size_t max = this->num_drawable_cards() - 5;
|
size_t max = this->num_drawable_cards() - 5;
|
||||||
uint8_t base_index = this->draw_index + 5;
|
uint8_t base_index = this->draw_index + 5;
|
||||||
for (size_t z = 0; z < this->card_refs.size(); z++) {
|
for (size_t z = 0; z < this->card_refs.size(); z++) {
|
||||||
uint8_t index1 = this->random_crypt->next() % max;
|
uint8_t index1 = random_from_optional_crypt(this->opt_rand_crypt) % max;
|
||||||
uint8_t index2 = this->random_crypt->next() % max;
|
uint8_t index2 = random_from_optional_crypt(this->opt_rand_crypt) % max;
|
||||||
uint16_t temp_ref = this->card_refs[base_index + index1];
|
uint16_t temp_ref = this->card_refs[base_index + index1];
|
||||||
this->card_refs[base_index + index1] = this->card_refs[base_index + index2];
|
this->card_refs[base_index + index1] = this->card_refs[base_index + index2];
|
||||||
this->card_refs[base_index + index2] = temp_ref;
|
this->card_refs[base_index + index2] = temp_ref;
|
||||||
@@ -265,8 +265,8 @@ void DeckState::shuffle() {
|
|||||||
// instead swap each item with another random item (possibly itself) that
|
// instead swap each item with another random item (possibly itself) that
|
||||||
// doesn't appear earlier than it in the array, but this is not what Sega
|
// doesn't appear earlier than it in the array, but this is not what Sega
|
||||||
// did.
|
// did.
|
||||||
uint8_t index1 = this->draw_index + this->random_crypt->next() % max;
|
uint8_t index1 = this->draw_index + random_from_optional_crypt(this->opt_rand_crypt) % max;
|
||||||
uint8_t index2 = this->draw_index + this->random_crypt->next() % max;
|
uint8_t index2 = this->draw_index + random_from_optional_crypt(this->opt_rand_crypt) % max;
|
||||||
uint16_t temp_ref = this->card_refs[index1];
|
uint16_t temp_ref = this->card_refs[index1];
|
||||||
this->card_refs[index1] = this->card_refs[index2];
|
this->card_refs[index1] = this->card_refs[index2];
|
||||||
this->card_refs[index2] = temp_ref;
|
this->card_refs[index2] = temp_ref;
|
||||||
|
|||||||
@@ -57,13 +57,13 @@ public:
|
|||||||
DeckState(
|
DeckState(
|
||||||
uint8_t client_id,
|
uint8_t client_id,
|
||||||
const parray<CardIDT, 0x1F>& card_ids,
|
const parray<CardIDT, 0x1F>& card_ids,
|
||||||
std::shared_ptr<PSOLFGEncryption> random_crypt)
|
std::shared_ptr<PSOLFGEncryption> opt_rand_crypt)
|
||||||
: client_id(client_id),
|
: client_id(client_id),
|
||||||
draw_index(1),
|
draw_index(1),
|
||||||
card_ref_base(this->client_id << 8),
|
card_ref_base(this->client_id << 8),
|
||||||
shuffle_enabled(true),
|
shuffle_enabled(true),
|
||||||
loop_enabled(true),
|
loop_enabled(true),
|
||||||
random_crypt(random_crypt) {
|
opt_rand_crypt(opt_rand_crypt) {
|
||||||
for (size_t z = 0; z < card_ids.size(); z++) {
|
for (size_t z = 0; z < card_ids.size(); z++) {
|
||||||
auto& e = this->entries[z];
|
auto& e = this->entries[z];
|
||||||
e.card_id = card_ids[z];
|
e.card_id = card_ids[z];
|
||||||
@@ -112,7 +112,7 @@ private:
|
|||||||
parray<CardEntry, 31> entries;
|
parray<CardEntry, 31> entries;
|
||||||
parray<uint16_t, 31> card_refs;
|
parray<uint16_t, 31> card_refs;
|
||||||
|
|
||||||
std::shared_ptr<PSOLFGEncryption> random_crypt;
|
std::shared_ptr<PSOLFGEncryption> opt_rand_crypt;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Episode3
|
} // namespace Episode3
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ void PlayerState::init() {
|
|||||||
throw logic_error("replacing a player state object is not permitted");
|
throw logic_error("replacing a player state object is not permitted");
|
||||||
}
|
}
|
||||||
|
|
||||||
this->deck_state = make_shared<DeckState>(this->client_id, s->deck_entries[client_id]->card_ids, s->options.random_crypt);
|
this->deck_state = make_shared<DeckState>(this->client_id, s->deck_entries[client_id]->card_ids, s->options.opt_rand_crypt);
|
||||||
if (s->map_and_rules->rules.disable_deck_shuffle) {
|
if (s->map_and_rules->rules.disable_deck_shuffle) {
|
||||||
this->deck_state->disable_shuffle();
|
this->deck_state->disable_shuffle();
|
||||||
}
|
}
|
||||||
|
|||||||
+17
-12
@@ -99,10 +99,10 @@ void Server::init() {
|
|||||||
this->card_special = make_shared<CardSpecial>(this->shared_from_this());
|
this->card_special = make_shared<CardSpecial>(this->shared_from_this());
|
||||||
|
|
||||||
// Note: The original implementation calls the default PSOV2Encryption
|
// Note: The original implementation calls the default PSOV2Encryption
|
||||||
// constructor for random_crypt, which just uses 0 as the seed. It then
|
// constructor for opt_rand_crypt, which just uses 0 as the seed. It then
|
||||||
// re-seeds the generator later. We instead expect the caller to provide a
|
// re-seeds the generator later. We instead expect the caller to provide a
|
||||||
// seeded generator, and we don't re-seed it at all.
|
// seeded generator, and we don't re-seed it at all.
|
||||||
// this->random_crypt = make_shared<PSOV2Encryption>(0);
|
// this->opt_rand_crypt = make_shared<PSOV2Encryption>(0);
|
||||||
|
|
||||||
this->state_flags = make_shared<StateFlags>();
|
this->state_flags = make_shared<StateFlags>();
|
||||||
|
|
||||||
@@ -273,10 +273,15 @@ void Server::send_6xB4x46() const {
|
|||||||
G_ServerVersionStrings_Ep3_6xB4x46 cmd;
|
G_ServerVersionStrings_Ep3_6xB4x46 cmd;
|
||||||
cmd.version_signature.encode(VERSION_SIGNATURE, 1);
|
cmd.version_signature.encode(VERSION_SIGNATURE, 1);
|
||||||
cmd.date_str1.encode(format_time(this->options.card_index->definitions_mtime() * 1000000), 1);
|
cmd.date_str1.encode(format_time(this->options.card_index->definitions_mtime() * 1000000), 1);
|
||||||
string date_str2 = string_printf(
|
string date_str2;
|
||||||
"Random:%08" PRIX32 "+%08" PRIX32,
|
if (this->options.opt_rand_crypt) {
|
||||||
this->options.random_crypt->seed(),
|
date_str2 = string_printf(
|
||||||
this->options.random_crypt->absolute_offset());
|
"Random:%08" PRIX32 "+%08" PRIX32,
|
||||||
|
this->options.opt_rand_crypt->seed(),
|
||||||
|
this->options.opt_rand_crypt->absolute_offset());
|
||||||
|
} else {
|
||||||
|
date_str2 = "Random:<SYS>";
|
||||||
|
}
|
||||||
if (this->last_chosen_map) {
|
if (this->last_chosen_map) {
|
||||||
date_str2 += string_printf(" Map:%08" PRIX32, this->last_chosen_map->map_number);
|
date_str2 += string_printf(" Map:%08" PRIX32, this->last_chosen_map->map_number);
|
||||||
}
|
}
|
||||||
@@ -1080,14 +1085,14 @@ shared_ptr<const PlayerState> Server::get_player_state(uint8_t client_id) const
|
|||||||
|
|
||||||
uint32_t Server::get_random(uint32_t max) {
|
uint32_t Server::get_random(uint32_t max) {
|
||||||
// The original implementation was essentially:
|
// The original implementation was essentially:
|
||||||
// return (static_cast<double>(this->random_crypt->next() >> 16) / 65536.0) * max
|
// return (static_cast<double>(this->opt_rand_crypt->next() >> 16) / 65536.0) * max
|
||||||
// This is unnecessarily complicated, so we instead just do this:
|
// This is unnecessarily complicated and imprecise, so we instead just do:
|
||||||
return this->options.random_crypt->next() % max;
|
return random_from_optional_crypt(this->options.opt_rand_crypt) % max;
|
||||||
}
|
}
|
||||||
|
|
||||||
float Server::get_random_float_0_1() {
|
float Server::get_random_float_0_1() {
|
||||||
// This lacks some precision, but matches the original implementation.
|
// This lacks some precision, but matches the original implementation.
|
||||||
return (static_cast<double>(this->options.random_crypt->next() >> 16) / 65536.0);
|
return (static_cast<double>(random_from_optional_crypt(this->options.opt_rand_crypt) >> 16) / 65536.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t Server::get_round_num() const {
|
uint32_t Server::get_round_num() const {
|
||||||
@@ -1544,8 +1549,8 @@ void Server::setup_and_start_battle() {
|
|||||||
|
|
||||||
this->setup_phase = SetupPhase::STARTER_ROLLS;
|
this->setup_phase = SetupPhase::STARTER_ROLLS;
|
||||||
|
|
||||||
// Note: This is where original implementation re-seeds random_crypt (it uses
|
// Note: This is where original implementation re-seeds opt_rand_crypt (it
|
||||||
// time() as the seed value).
|
// uses time() as the seed value).
|
||||||
|
|
||||||
for (size_t z = 0; z < 4; z++) {
|
for (size_t z = 0; z < 4; z++) {
|
||||||
if (!this->check_presence_entry(z)) {
|
if (!this->check_presence_entry(z)) {
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ public:
|
|||||||
std::shared_ptr<const CardIndex> card_index;
|
std::shared_ptr<const CardIndex> card_index;
|
||||||
std::shared_ptr<const MapIndex> map_index;
|
std::shared_ptr<const MapIndex> map_index;
|
||||||
uint32_t behavior_flags;
|
uint32_t behavior_flags;
|
||||||
std::shared_ptr<PSOLFGEncryption> random_crypt;
|
std::shared_ptr<PSOLFGEncryption> opt_rand_crypt;
|
||||||
std::shared_ptr<const Tournament> tournament;
|
std::shared_ptr<const Tournament> tournament;
|
||||||
std::array<std::vector<uint16_t>, 5> trap_card_ids;
|
std::array<std::vector<uint16_t>, 5> trap_card_ids;
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -643,8 +643,8 @@ JSON HTTPServer::generate_lobby_json(shared_ptr<const Lobby> l) const {
|
|||||||
}
|
}
|
||||||
auto battle_state_json = JSON::dict({
|
auto battle_state_json = JSON::dict({
|
||||||
{"BehaviorFlags", ep3s->options.behavior_flags},
|
{"BehaviorFlags", ep3s->options.behavior_flags},
|
||||||
{"RandomSeed", ep3s->options.random_crypt ? ep3s->options.random_crypt->seed() : JSON(nullptr)},
|
{"RandomSeed", ep3s->options.opt_rand_crypt ? ep3s->options.opt_rand_crypt->seed() : JSON(nullptr)},
|
||||||
{"RandomOffset", ep3s->options.random_crypt ? ep3s->options.random_crypt->absolute_offset() : JSON(nullptr)},
|
{"RandomOffset", ep3s->options.opt_rand_crypt ? ep3s->options.opt_rand_crypt->absolute_offset() : JSON(nullptr)},
|
||||||
{"Tournament", ep3s->options.tournament ? ep3s->options.tournament->json() : nullptr},
|
{"Tournament", ep3s->options.tournament ? ep3s->options.tournament->json() : nullptr},
|
||||||
{"MapNumber", ep3s->last_chosen_map ? ep3s->last_chosen_map->map_number : JSON(nullptr)},
|
{"MapNumber", ep3s->last_chosen_map ? ep3s->last_chosen_map->map_number : JSON(nullptr)},
|
||||||
{"EnvironmentNumber", ep3s->map_and_rules ? ep3s->map_and_rules->environment_number : JSON(nullptr)},
|
{"EnvironmentNumber", ep3s->map_and_rules ? ep3s->map_and_rules->environment_number : JSON(nullptr)},
|
||||||
|
|||||||
+31
-30
@@ -23,7 +23,7 @@ ItemCreator::ItemCreator(
|
|||||||
GameMode mode,
|
GameMode mode,
|
||||||
uint8_t difficulty,
|
uint8_t difficulty,
|
||||||
uint8_t section_id,
|
uint8_t section_id,
|
||||||
uint32_t random_seed,
|
std::shared_ptr<PSOLFGEncryption> opt_rand_crypt,
|
||||||
shared_ptr<const BattleRules> restrictions)
|
shared_ptr<const BattleRules> restrictions)
|
||||||
: log(string_printf("[ItemCreator:%s/%s/%s/%c/%hhu] ", name_for_enum(stack_limits->version), abbreviation_for_episode(episode), abbreviation_for_mode(mode), abbreviation_for_difficulty(difficulty), section_id), lobby_log.min_level),
|
: log(string_printf("[ItemCreator:%s/%s/%s/%c/%hhu] ", name_for_enum(stack_limits->version), abbreviation_for_episode(episode), abbreviation_for_mode(mode), abbreviation_for_difficulty(difficulty), section_id), lobby_log.min_level),
|
||||||
version(stack_limits->version),
|
version(stack_limits->version),
|
||||||
@@ -40,17 +40,12 @@ ItemCreator::ItemCreator(
|
|||||||
item_parameter_table(item_parameter_table),
|
item_parameter_table(item_parameter_table),
|
||||||
pt(common_item_set->get_table(this->episode, this->mode, this->difficulty, this->section_id)),
|
pt(common_item_set->get_table(this->episode, this->mode, this->difficulty, this->section_id)),
|
||||||
restrictions(restrictions),
|
restrictions(restrictions),
|
||||||
random_crypt(random_seed) {
|
opt_rand_crypt(opt_rand_crypt ? make_shared<PSOV2Encryption>(opt_rand_crypt->seed()) : nullptr) {
|
||||||
this->generate_unit_stars_tables();
|
this->generate_unit_stars_tables();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ItemCreator::set_random_state(uint32_t seed, uint32_t absolute_offset) {
|
void ItemCreator::set_random_crypt(shared_ptr<PSOLFGEncryption> new_random_crypt) {
|
||||||
if ((this->random_crypt.seed() != seed) || (this->random_crypt.absolute_offset() > absolute_offset)) {
|
this->opt_rand_crypt = new_random_crypt;
|
||||||
this->random_crypt = PSOV2Encryption(seed);
|
|
||||||
}
|
|
||||||
while (this->random_crypt.absolute_offset() < absolute_offset) {
|
|
||||||
this->random_crypt.next();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ItemCreator::are_rare_drops_allowed() const {
|
bool ItemCreator::are_rare_drops_allowed() const {
|
||||||
@@ -141,8 +136,11 @@ ItemCreator::DropResult ItemCreator::on_monster_item_drop(uint32_t enemy_type, u
|
|||||||
}
|
}
|
||||||
|
|
||||||
ItemCreator::DropResult ItemCreator::on_box_item_drop_with_area_norm(uint8_t area_norm) {
|
ItemCreator::DropResult ItemCreator::on_box_item_drop_with_area_norm(uint8_t area_norm) {
|
||||||
this->log.info("Box drop checks for area_norm %02hhX; random state: %08" PRIX32 " %08" PRIX32,
|
this->log.info("Box drop checks for area_norm %02hhX", area_norm);
|
||||||
area_norm, this->random_crypt.seed(), this->random_crypt.absolute_offset());
|
if (this->opt_rand_crypt) {
|
||||||
|
this->log.info("Random state: %08" PRIX32 " %08" PRIX32,
|
||||||
|
this->opt_rand_crypt->seed(), this->opt_rand_crypt->absolute_offset());
|
||||||
|
}
|
||||||
DropResult res;
|
DropResult res;
|
||||||
res.item = this->check_rare_specs_and_create_rare_box_item(area_norm);
|
res.item = this->check_rare_specs_and_create_rare_box_item(area_norm);
|
||||||
if (!res.item.empty()) {
|
if (!res.item.empty()) {
|
||||||
@@ -189,7 +187,11 @@ ItemCreator::DropResult ItemCreator::on_monster_item_drop_with_area_norm(uint32_
|
|||||||
this->log.warning("Invalid enemy type: %" PRIX32, enemy_type);
|
this->log.warning("Invalid enemy type: %" PRIX32, enemy_type);
|
||||||
return DropResult();
|
return DropResult();
|
||||||
}
|
}
|
||||||
this->log.info("Enemy type: %" PRIX32 "; random state: %08" PRIX32 " %08" PRIX32, enemy_type, this->random_crypt.seed(), this->random_crypt.absolute_offset());
|
this->log.info("Enemy type: %" PRIX32 "", enemy_type);
|
||||||
|
if (this->opt_rand_crypt) {
|
||||||
|
this->log.info("Random state: %08" PRIX32 " %08" PRIX32,
|
||||||
|
this->opt_rand_crypt->seed(), this->opt_rand_crypt->absolute_offset());
|
||||||
|
}
|
||||||
|
|
||||||
uint8_t type_drop_prob = this->pt->enemy_type_drop_probs.at(enemy_type);
|
uint8_t type_drop_prob = this->pt->enemy_type_drop_probs.at(enemy_type);
|
||||||
uint8_t drop_sample = this->rand_int(100);
|
uint8_t drop_sample = this->rand_int(100);
|
||||||
@@ -281,12 +283,12 @@ ItemData ItemCreator::check_rare_specs_and_create_rare_box_item(uint8_t area_nor
|
|||||||
}
|
}
|
||||||
|
|
||||||
uint32_t ItemCreator::rand_int(uint64_t max) {
|
uint32_t ItemCreator::rand_int(uint64_t max) {
|
||||||
return this->random_crypt.next() % max;
|
return random_from_optional_crypt(this->opt_rand_crypt) % max;
|
||||||
}
|
}
|
||||||
|
|
||||||
float ItemCreator::rand_float_0_1_from_crypt() {
|
float ItemCreator::rand_float_0_1_from_crypt() {
|
||||||
// This lacks some precision, but matches the original implementation.
|
// This lacks some precision, but matches the original implementation.
|
||||||
return (static_cast<double>(this->random_crypt.next() >> 16) / 65536.0);
|
return (static_cast<double>(random_from_optional_crypt(this->opt_rand_crypt) >> 16) / 65536.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <size_t NumRanges>
|
template <size_t NumRanges>
|
||||||
@@ -706,9 +708,9 @@ void ItemCreator::generate_common_mag_variances(ItemData& item) {
|
|||||||
if (is_pre_v1(this->version)) {
|
if (is_pre_v1(this->version)) {
|
||||||
item.data2[3] = 0x00;
|
item.data2[3] = 0x00;
|
||||||
} else if (is_v1_or_v2(this->version)) {
|
} else if (is_v1_or_v2(this->version)) {
|
||||||
item.data2[3] = this->random_crypt.next() % 0x0E;
|
item.data2[3] = random_from_optional_crypt(this->opt_rand_crypt) % 0x0E;
|
||||||
} else {
|
} else {
|
||||||
item.data2[3] = this->random_crypt.next() % 0x12;
|
item.data2[3] = random_from_optional_crypt(this->opt_rand_crypt) % 0x12;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1018,8 +1020,7 @@ bool ItemCreator::shop_does_not_contain_duplicate_item_by_data1_0_1_2(
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ItemCreator::generate_armor_shop_armors(
|
void ItemCreator::generate_armor_shop_armors(vector<ItemData>& shop, size_t player_level) {
|
||||||
vector<ItemData>& shop, size_t player_level) {
|
|
||||||
size_t num_items;
|
size_t num_items;
|
||||||
if (player_level < 11) {
|
if (player_level < 11) {
|
||||||
num_items = 4;
|
num_items = 4;
|
||||||
@@ -1039,7 +1040,7 @@ void ItemCreator::generate_armor_shop_armors(
|
|||||||
pt.push(src_table.first[z].value);
|
pt.push(src_table.first[z].value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pt.shuffle(this->random_crypt);
|
pt.shuffle(this->opt_rand_crypt);
|
||||||
|
|
||||||
for (size_t items_generated = 0; items_generated < num_items;) {
|
for (size_t items_generated = 0; items_generated < num_items;) {
|
||||||
ItemData item;
|
ItemData item;
|
||||||
@@ -1083,7 +1084,7 @@ void ItemCreator::generate_armor_shop_shields(vector<ItemData>& shop, size_t pla
|
|||||||
pt.push(src_table.first[z].value);
|
pt.push(src_table.first[z].value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pt.shuffle(this->random_crypt);
|
pt.shuffle(this->opt_rand_crypt);
|
||||||
|
|
||||||
for (size_t items_generated = 0; items_generated < num_items;) {
|
for (size_t items_generated = 0; items_generated < num_items;) {
|
||||||
ItemData item;
|
ItemData item;
|
||||||
@@ -1126,7 +1127,7 @@ void ItemCreator::generate_armor_shop_units(vector<ItemData>& shop, size_t playe
|
|||||||
pt.push(src_table.first[z].value);
|
pt.push(src_table.first[z].value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pt.shuffle(this->random_crypt);
|
pt.shuffle(this->opt_rand_crypt);
|
||||||
|
|
||||||
for (size_t items_generated = 0; items_generated < num_items;) {
|
for (size_t items_generated = 0; items_generated < num_items;) {
|
||||||
ItemData item;
|
ItemData item;
|
||||||
@@ -1229,7 +1230,7 @@ void ItemCreator::generate_rare_tool_shop_recovery_items(
|
|||||||
pt.push(e.value);
|
pt.push(e.value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pt.shuffle(this->random_crypt);
|
pt.shuffle(this->opt_rand_crypt);
|
||||||
|
|
||||||
size_t effective_num_items = num_items;
|
size_t effective_num_items = num_items;
|
||||||
size_t items_generated = 0;
|
size_t items_generated = 0;
|
||||||
@@ -1272,7 +1273,7 @@ void ItemCreator::generate_tool_shop_tech_disks(vector<ItemData>& shop, size_t p
|
|||||||
pt.push(e.value);
|
pt.push(e.value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pt.shuffle(this->random_crypt);
|
pt.shuffle(this->opt_rand_crypt);
|
||||||
|
|
||||||
static const array<uint8_t, 0x13> tech_num_map = {
|
static const array<uint8_t, 0x13> tech_num_map = {
|
||||||
0x00, 0x03, 0x06, 0x0F, 0x10, 0x0D, 0x0A, 0x0B, 0x0C, 0x01, 0x04, 0x07,
|
0x00, 0x03, 0x06, 0x0F, 0x10, 0x0D, 0x0A, 0x0B, 0x0C, 0x01, 0x04, 0x07,
|
||||||
@@ -1373,7 +1374,7 @@ vector<ItemData> ItemCreator::generate_weapon_shop_contents(size_t player_level)
|
|||||||
pt.push(e.value);
|
pt.push(e.value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pt.shuffle(this->random_crypt);
|
pt.shuffle(this->opt_rand_crypt);
|
||||||
|
|
||||||
vector<ItemData> shop;
|
vector<ItemData> shop;
|
||||||
while (shop.size() < num_items) {
|
while (shop.size() < num_items) {
|
||||||
@@ -1573,7 +1574,7 @@ void ItemCreator::generate_weapon_shop_item_special(ItemData& item, size_t playe
|
|||||||
|
|
||||||
// Note: The original code shuffles pt and then pops a single value from it.
|
// Note: The original code shuffles pt and then pops a single value from it.
|
||||||
// For simplicity, we just sample a single value (and don't pop it) instead.
|
// For simplicity, we just sample a single value (and don't pop it) instead.
|
||||||
switch (pt.sample(this->random_crypt)) {
|
switch (pt.sample(this->opt_rand_crypt)) {
|
||||||
case 0:
|
case 0:
|
||||||
item.data1[4] = 0;
|
item.data1[4] = 0;
|
||||||
break;
|
break;
|
||||||
@@ -1625,7 +1626,7 @@ void ItemCreator::generate_weapon_shop_item_bonus1(
|
|||||||
|
|
||||||
// Note: The original code shuffles pt and then pops a single value from it.
|
// Note: The original code shuffles pt and then pops a single value from it.
|
||||||
// For simplicity, we just sample a single value (and don't pop it) instead.
|
// For simplicity, we just sample a single value (and don't pop it) instead.
|
||||||
item.data1[6] = pt.sample(this->random_crypt);
|
item.data1[6] = pt.sample(this->opt_rand_crypt);
|
||||||
if (item.data1[6] == 0) {
|
if (item.data1[6] == 0) {
|
||||||
item.data1[7] = 0;
|
item.data1[7] = 0;
|
||||||
|
|
||||||
@@ -1666,7 +1667,7 @@ void ItemCreator::generate_weapon_shop_item_bonus2(ItemData& item, size_t player
|
|||||||
pt.push(e.value);
|
pt.push(e.value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pt.shuffle(this->random_crypt);
|
pt.shuffle(this->opt_rand_crypt);
|
||||||
|
|
||||||
do {
|
do {
|
||||||
item.data1[8] = pt.pop();
|
item.data1[8] = pt.pop();
|
||||||
@@ -1752,7 +1753,7 @@ ssize_t ItemCreator::apply_tekker_deltas(ItemData& item, uint8_t section_id) {
|
|||||||
// Adjust the weapon's special
|
// Adjust the weapon's special
|
||||||
{
|
{
|
||||||
const auto& prob_table = this->tekker_adjustment_set->get_special_upgrade_prob_table(section_id, favored);
|
const auto& prob_table = this->tekker_adjustment_set->get_special_upgrade_prob_table(section_id, favored);
|
||||||
uint8_t delta_index = prob_table.sample(this->random_crypt);
|
uint8_t delta_index = prob_table.sample(this->opt_rand_crypt);
|
||||||
int8_t delta = delta_table.at(delta_index);
|
int8_t delta = delta_table.at(delta_index);
|
||||||
this->log.info("(Special) Delta index %hhu, delta %hhd", delta_index, delta);
|
this->log.info("(Special) Delta index %hhu, delta %hhd", delta_index, delta);
|
||||||
// Note: The original code checks specifically for -1 and +1 here, but the
|
// Note: The original code checks specifically for -1 and +1 here, but the
|
||||||
@@ -1788,7 +1789,7 @@ ssize_t ItemCreator::apply_tekker_deltas(ItemData& item, uint8_t section_id) {
|
|||||||
if (!this->item_parameter_table->is_item_rare(item)) {
|
if (!this->item_parameter_table->is_item_rare(item)) {
|
||||||
const auto& weapon_def = this->item_parameter_table->get_weapon(item.data1[1], item.data1[2]);
|
const auto& weapon_def = this->item_parameter_table->get_weapon(item.data1[1], item.data1[2]);
|
||||||
const auto& prob_table = this->tekker_adjustment_set->get_grind_delta_prob_table(section_id, favored);
|
const auto& prob_table = this->tekker_adjustment_set->get_grind_delta_prob_table(section_id, favored);
|
||||||
uint8_t delta_index = prob_table.sample(this->random_crypt);
|
uint8_t delta_index = prob_table.sample(this->opt_rand_crypt);
|
||||||
int8_t delta = delta_table.at(delta_index);
|
int8_t delta = delta_table.at(delta_index);
|
||||||
this->log.info("(Grind) Delta index %hhu, delta %hhd", delta_index, delta);
|
this->log.info("(Grind) Delta index %hhu, delta %hhd", delta_index, delta);
|
||||||
int16_t new_grind = static_cast<int16_t>(item.data1[3]) + static_cast<int16_t>(delta);
|
int16_t new_grind = static_cast<int16_t>(item.data1[3]) + static_cast<int16_t>(delta);
|
||||||
@@ -1804,7 +1805,7 @@ ssize_t ItemCreator::apply_tekker_deltas(ItemData& item, uint8_t section_id) {
|
|||||||
const auto& prob_table = this->tekker_adjustment_set->get_bonus_delta_prob_table(section_id, favored);
|
const auto& prob_table = this->tekker_adjustment_set->get_bonus_delta_prob_table(section_id, favored);
|
||||||
// Note: The original code really does use the same delta for all three
|
// Note: The original code really does use the same delta for all three
|
||||||
// bonuses.
|
// bonuses.
|
||||||
uint8_t delta_index = prob_table.sample(this->random_crypt);
|
uint8_t delta_index = prob_table.sample(this->opt_rand_crypt);
|
||||||
int8_t delta = delta_table.at(delta_index);
|
int8_t delta = delta_table.at(delta_index);
|
||||||
this->log.info("(Bonuses) Delta index %hhu, delta %hhd", delta_index, delta);
|
this->log.info("(Bonuses) Delta index %hhu, delta %hhd", delta_index, delta);
|
||||||
// Note: The original code doesn't check if there's actually a bonus in each
|
// Note: The original code doesn't check if there's actually a bonus in each
|
||||||
|
|||||||
+3
-3
@@ -24,11 +24,11 @@ public:
|
|||||||
GameMode mode,
|
GameMode mode,
|
||||||
uint8_t difficulty,
|
uint8_t difficulty,
|
||||||
uint8_t section_id,
|
uint8_t section_id,
|
||||||
uint32_t random_seed,
|
std::shared_ptr<PSOLFGEncryption> opt_rand_crypt,
|
||||||
std::shared_ptr<const BattleRules> restrictions = nullptr);
|
std::shared_ptr<const BattleRules> restrictions = nullptr);
|
||||||
~ItemCreator() = default;
|
~ItemCreator() = default;
|
||||||
|
|
||||||
void set_random_state(uint32_t seed, uint32_t absolute_offset);
|
void set_random_crypt(std::shared_ptr<PSOLFGEncryption> new_random_crypt);
|
||||||
|
|
||||||
struct DropResult {
|
struct DropResult {
|
||||||
ItemData item;
|
ItemData item;
|
||||||
@@ -78,7 +78,7 @@ private:
|
|||||||
|
|
||||||
// Note: The original implementation uses 17 different random states for some
|
// Note: The original implementation uses 17 different random states for some
|
||||||
// reason. We forego that and use only one for simplicity.
|
// reason. We forego that and use only one for simplicity.
|
||||||
PSOV2Encryption random_crypt;
|
std::shared_ptr<PSOLFGEncryption> opt_rand_crypt;
|
||||||
|
|
||||||
inline bool is_v3() const {
|
inline bool is_v3() const {
|
||||||
return !is_v1_or_v2(this->version);
|
return !is_v1_or_v2(this->version);
|
||||||
|
|||||||
+2
-2
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
|
||||||
void player_use_item(shared_ptr<Client> c, size_t item_index, shared_ptr<PSOLFGEncryption> random_crypt) {
|
void player_use_item(shared_ptr<Client> c, size_t item_index, shared_ptr<PSOLFGEncryption> opt_rand_crypt) {
|
||||||
auto s = c->require_server_state();
|
auto s = c->require_server_state();
|
||||||
|
|
||||||
// On PC (and presumably DC), the client sends a 6x29 after this to delete the
|
// On PC (and presumably DC), the client sends a 6x29 after this to delete the
|
||||||
@@ -177,7 +177,7 @@ void player_use_item(shared_ptr<Client> c, size_t item_index, shared_ptr<PSOLFGE
|
|||||||
if (sum == 0) {
|
if (sum == 0) {
|
||||||
throw runtime_error("no unwrap results available for event");
|
throw runtime_error("no unwrap results available for event");
|
||||||
}
|
}
|
||||||
size_t det = random_crypt->next() % sum;
|
size_t det = random_from_optional_crypt(opt_rand_crypt) % sum;
|
||||||
for (size_t z = 0; z < table.second; z++) {
|
for (size_t z = 0; z < table.second; z++) {
|
||||||
const auto& entry = table.first[z];
|
const auto& entry = table.first[z];
|
||||||
if (det > entry.probability) {
|
if (det > entry.probability) {
|
||||||
|
|||||||
+1
-1
@@ -12,7 +12,7 @@
|
|||||||
#include "ServerState.hh"
|
#include "ServerState.hh"
|
||||||
#include "StaticGameData.hh"
|
#include "StaticGameData.hh"
|
||||||
|
|
||||||
void player_use_item(std::shared_ptr<Client> c, size_t item_index, std::shared_ptr<PSOLFGEncryption> random_crypt);
|
void player_use_item(std::shared_ptr<Client> c, size_t item_index, std::shared_ptr<PSOLFGEncryption> opt_rand_crypt);
|
||||||
void player_feed_mag(std::shared_ptr<Client> c, size_t mag_item_index, size_t fed_item_index);
|
void player_feed_mag(std::shared_ptr<Client> c, size_t mag_item_index, size_t fed_item_index);
|
||||||
|
|
||||||
void apply_mag_feed_result(
|
void apply_mag_feed_result(
|
||||||
|
|||||||
+29
-11
@@ -257,7 +257,7 @@ void Lobby::create_item_creator() {
|
|||||||
(this->mode == GameMode::SOLO) ? GameMode::NORMAL : this->mode,
|
(this->mode == GameMode::SOLO) ? GameMode::NORMAL : this->mode,
|
||||||
this->difficulty,
|
this->difficulty,
|
||||||
this->section_id,
|
this->section_id,
|
||||||
this->random_seed,
|
this->opt_rand_crypt,
|
||||||
this->quest ? this->quest->battle_rules : nullptr);
|
this->quest ? this->quest->battle_rules : nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -268,9 +268,10 @@ shared_ptr<Map> Lobby::load_maps(
|
|||||||
uint8_t event,
|
uint8_t event,
|
||||||
uint32_t lobby_id,
|
uint32_t lobby_id,
|
||||||
shared_ptr<const Map::RareEnemyRates> rare_rates,
|
shared_ptr<const Map::RareEnemyRates> rare_rates,
|
||||||
shared_ptr<PSOLFGEncryption> random_crypt,
|
uint32_t random_seed,
|
||||||
|
shared_ptr<PSOLFGEncryption> opt_rand_crypt,
|
||||||
shared_ptr<const string> quest_dat_contents_decompressed) {
|
shared_ptr<const string> quest_dat_contents_decompressed) {
|
||||||
auto map = make_shared<Map>(version, lobby_id, random_crypt);
|
auto map = make_shared<Map>(version, lobby_id, random_seed, opt_rand_crypt);
|
||||||
map->add_enemies_and_objects_from_quest_data(
|
map->add_enemies_and_objects_from_quest_data(
|
||||||
episode,
|
episode,
|
||||||
difficulty,
|
difficulty,
|
||||||
@@ -291,12 +292,26 @@ shared_ptr<Map> Lobby::load_maps(
|
|||||||
shared_ptr<const SetDataTableBase> sdt,
|
shared_ptr<const SetDataTableBase> sdt,
|
||||||
function<shared_ptr<const string>(Version, const string&)> get_file_data,
|
function<shared_ptr<const string>(Version, const string&)> get_file_data,
|
||||||
shared_ptr<const Map::RareEnemyRates> rare_rates,
|
shared_ptr<const Map::RareEnemyRates> rare_rates,
|
||||||
shared_ptr<PSOLFGEncryption> random_crypt,
|
uint32_t random_seed,
|
||||||
|
shared_ptr<PSOLFGEncryption> opt_rand_crypt,
|
||||||
const parray<le_uint32_t, 0x20>& variations,
|
const parray<le_uint32_t, 0x20>& variations,
|
||||||
const PrefixedLogger* log) {
|
const PrefixedLogger* log) {
|
||||||
auto enemy_filenames = sdt->map_filenames_for_variations(variations, episode, mode, true);
|
auto enemy_filenames = sdt->map_filenames_for_variations(variations, episode, mode, true);
|
||||||
auto object_filenames = sdt->map_filenames_for_variations(variations, episode, mode, false);
|
auto object_filenames = sdt->map_filenames_for_variations(variations, episode, mode, false);
|
||||||
return Lobby::load_maps(enemy_filenames, object_filenames, version, episode, mode, difficulty, event, lobby_id, get_file_data, rare_rates, random_crypt, log);
|
return Lobby::load_maps(
|
||||||
|
enemy_filenames,
|
||||||
|
object_filenames,
|
||||||
|
version,
|
||||||
|
episode,
|
||||||
|
mode,
|
||||||
|
difficulty,
|
||||||
|
event,
|
||||||
|
lobby_id,
|
||||||
|
get_file_data,
|
||||||
|
rare_rates,
|
||||||
|
random_seed,
|
||||||
|
opt_rand_crypt,
|
||||||
|
log);
|
||||||
}
|
}
|
||||||
|
|
||||||
shared_ptr<Map> Lobby::load_maps(
|
shared_ptr<Map> Lobby::load_maps(
|
||||||
@@ -310,9 +325,10 @@ shared_ptr<Map> Lobby::load_maps(
|
|||||||
uint32_t lobby_id,
|
uint32_t lobby_id,
|
||||||
function<shared_ptr<const string>(Version, const string&)> get_file_data,
|
function<shared_ptr<const string>(Version, const string&)> get_file_data,
|
||||||
shared_ptr<const Map::RareEnemyRates> rare_rates,
|
shared_ptr<const Map::RareEnemyRates> rare_rates,
|
||||||
shared_ptr<PSOLFGEncryption> random_crypt,
|
uint32_t rare_seed,
|
||||||
|
shared_ptr<PSOLFGEncryption> opt_rand_crypt,
|
||||||
const PrefixedLogger* log) {
|
const PrefixedLogger* log) {
|
||||||
auto map = make_shared<Map>(version, lobby_id, random_crypt);
|
auto map = make_shared<Map>(version, lobby_id, rare_seed, opt_rand_crypt);
|
||||||
|
|
||||||
// Don't load free-roam maps in Challenge mode, since players can't go to
|
// Don't load free-roam maps in Challenge mode, since players can't go to
|
||||||
// Ragol without a quest loaded
|
// Ragol without a quest loaded
|
||||||
@@ -384,7 +400,8 @@ void Lobby::load_maps() {
|
|||||||
this->event,
|
this->event,
|
||||||
this->lobby_id,
|
this->lobby_id,
|
||||||
rare_rates,
|
rare_rates,
|
||||||
this->random_crypt,
|
this->random_seed,
|
||||||
|
this->opt_rand_crypt,
|
||||||
vq->dat_contents_decompressed);
|
vq->dat_contents_decompressed);
|
||||||
|
|
||||||
} else if (this->mode != GameMode::CHALLENGE) {
|
} else if (this->mode != GameMode::CHALLENGE) {
|
||||||
@@ -399,12 +416,13 @@ void Lobby::load_maps() {
|
|||||||
s->set_data_table(this->base_version, this->episode, this->mode, this->difficulty),
|
s->set_data_table(this->base_version, this->episode, this->mode, this->difficulty),
|
||||||
bind(&ServerState::load_map_file, s.get(), placeholders::_1, placeholders::_2),
|
bind(&ServerState::load_map_file, s.get(), placeholders::_1, placeholders::_2),
|
||||||
rare_rates,
|
rare_rates,
|
||||||
this->random_crypt,
|
this->random_seed,
|
||||||
|
this->opt_rand_crypt,
|
||||||
this->variations,
|
this->variations,
|
||||||
&this->log);
|
&this->log);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
this->map = make_shared<Map>(this->base_version, this->lobby_id, this->random_crypt);
|
this->map = make_shared<Map>(this->base_version, this->lobby_id, this->random_seed, this->opt_rand_crypt);
|
||||||
}
|
}
|
||||||
|
|
||||||
this->log.info("Generated objects list (%zu entries):", this->map->objects.size());
|
this->log.info("Generated objects list (%zu entries):", this->map->objects.size());
|
||||||
@@ -434,7 +452,7 @@ void Lobby::create_ep3_server() {
|
|||||||
.card_index = is_nte ? s->ep3_card_index_trial : s->ep3_card_index,
|
.card_index = is_nte ? s->ep3_card_index_trial : s->ep3_card_index,
|
||||||
.map_index = s->ep3_map_index,
|
.map_index = s->ep3_map_index,
|
||||||
.behavior_flags = s->ep3_behavior_flags,
|
.behavior_flags = s->ep3_behavior_flags,
|
||||||
.random_crypt = this->random_crypt,
|
.opt_rand_crypt = this->opt_rand_crypt,
|
||||||
.tournament = tourn,
|
.tournament = tourn,
|
||||||
.trap_card_ids = s->ep3_trap_card_ids,
|
.trap_card_ids = s->ep3_trap_card_ids,
|
||||||
};
|
};
|
||||||
|
|||||||
+7
-4
@@ -117,7 +117,7 @@ struct Lobby : public std::enable_shared_from_this<Lobby> {
|
|||||||
std::string name;
|
std::string name;
|
||||||
// This seed is also sent to the client for rare enemy generation
|
// This seed is also sent to the client for rare enemy generation
|
||||||
uint32_t random_seed;
|
uint32_t random_seed;
|
||||||
std::shared_ptr<PSOLFGEncryption> random_crypt;
|
std::shared_ptr<PSOLFGEncryption> opt_rand_crypt;
|
||||||
uint8_t allowed_drop_modes;
|
uint8_t allowed_drop_modes;
|
||||||
DropMode drop_mode;
|
DropMode drop_mode;
|
||||||
std::shared_ptr<ItemCreator> item_creator;
|
std::shared_ptr<ItemCreator> item_creator;
|
||||||
@@ -202,7 +202,8 @@ struct Lobby : public std::enable_shared_from_this<Lobby> {
|
|||||||
uint8_t event,
|
uint8_t event,
|
||||||
uint32_t lobby_id,
|
uint32_t lobby_id,
|
||||||
std::shared_ptr<const Map::RareEnemyRates> rare_rates,
|
std::shared_ptr<const Map::RareEnemyRates> rare_rates,
|
||||||
std::shared_ptr<PSOLFGEncryption> random_crypt,
|
uint32_t random_seed,
|
||||||
|
std::shared_ptr<PSOLFGEncryption> opt_rand_crypt,
|
||||||
std::shared_ptr<const std::string> quest_dat_contents_decompressed);
|
std::shared_ptr<const std::string> quest_dat_contents_decompressed);
|
||||||
static std::shared_ptr<Map> load_maps(
|
static std::shared_ptr<Map> load_maps(
|
||||||
Version version,
|
Version version,
|
||||||
@@ -214,7 +215,8 @@ struct Lobby : public std::enable_shared_from_this<Lobby> {
|
|||||||
std::shared_ptr<const SetDataTableBase> sdt,
|
std::shared_ptr<const SetDataTableBase> sdt,
|
||||||
std::function<std::shared_ptr<const std::string>(Version, const std::string&)> get_file_data,
|
std::function<std::shared_ptr<const std::string>(Version, const std::string&)> get_file_data,
|
||||||
std::shared_ptr<const Map::RareEnemyRates> rare_rates,
|
std::shared_ptr<const Map::RareEnemyRates> rare_rates,
|
||||||
std::shared_ptr<PSOLFGEncryption> random_crypt,
|
uint32_t random_seed,
|
||||||
|
std::shared_ptr<PSOLFGEncryption> opt_rand_crypt,
|
||||||
const parray<le_uint32_t, 0x20>& variations,
|
const parray<le_uint32_t, 0x20>& variations,
|
||||||
const PrefixedLogger* log = nullptr);
|
const PrefixedLogger* log = nullptr);
|
||||||
static std::shared_ptr<Map> load_maps(
|
static std::shared_ptr<Map> load_maps(
|
||||||
@@ -228,7 +230,8 @@ struct Lobby : public std::enable_shared_from_this<Lobby> {
|
|||||||
uint32_t lobby_id,
|
uint32_t lobby_id,
|
||||||
std::function<std::shared_ptr<const std::string>(Version, const std::string&)> get_file_data,
|
std::function<std::shared_ptr<const std::string>(Version, const std::string&)> get_file_data,
|
||||||
std::shared_ptr<const Map::RareEnemyRates> rare_rates,
|
std::shared_ptr<const Map::RareEnemyRates> rare_rates,
|
||||||
std::shared_ptr<PSOLFGEncryption> random_crypt,
|
uint32_t random_seed,
|
||||||
|
std::shared_ptr<PSOLFGEncryption> opt_rand_crypt,
|
||||||
const PrefixedLogger* log = nullptr);
|
const PrefixedLogger* log = nullptr);
|
||||||
void load_maps();
|
void load_maps();
|
||||||
void create_ep3_server();
|
void create_ep3_server();
|
||||||
|
|||||||
+5
-3
@@ -2133,7 +2133,8 @@ Action a_find_rare_enemy_seeds(
|
|||||||
if (!vq->dat_contents_decompressed) {
|
if (!vq->dat_contents_decompressed) {
|
||||||
throw runtime_error("quest does not have DAT data");
|
throw runtime_error("quest does not have DAT data");
|
||||||
}
|
}
|
||||||
map = Lobby::load_maps(version, episode, difficulty, 0, 0, rare_rates, random_crypt, vq->dat_contents_decompressed);
|
map = Lobby::load_maps(
|
||||||
|
version, episode, difficulty, 0, 0, rare_rates, seed, random_crypt, vq->dat_contents_decompressed);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
generate_variations_deprecated(variations, random_crypt, version, episode, (mode == GameMode::SOLO));
|
generate_variations_deprecated(variations, random_crypt, version, episode, (mode == GameMode::SOLO));
|
||||||
@@ -2147,6 +2148,7 @@ Action a_find_rare_enemy_seeds(
|
|||||||
s->set_data_table(version, episode, mode, difficulty),
|
s->set_data_table(version, episode, mode, difficulty),
|
||||||
bind(&ServerState::load_map_file, s.get(), placeholders::_1, placeholders::_2),
|
bind(&ServerState::load_map_file, s.get(), placeholders::_1, placeholders::_2),
|
||||||
rare_rates,
|
rare_rates,
|
||||||
|
seed,
|
||||||
random_crypt,
|
random_crypt,
|
||||||
variations);
|
variations);
|
||||||
}
|
}
|
||||||
@@ -2295,12 +2297,12 @@ Action a_replay_ep3_battle_commands(
|
|||||||
s->load_ep3_cards(false);
|
s->load_ep3_cards(false);
|
||||||
s->load_ep3_maps(false);
|
s->load_ep3_maps(false);
|
||||||
|
|
||||||
auto random_crypt = make_shared<PSOV2Encryption>(args.get<uint32_t>("seed", 0, Arguments::IntFormat::HEX));
|
auto opt_rand_crypt = make_shared<PSOV2Encryption>(args.get<uint32_t>("seed", 0, Arguments::IntFormat::HEX));
|
||||||
Episode3::Server::Options options = {
|
Episode3::Server::Options options = {
|
||||||
.card_index = s->ep3_card_index,
|
.card_index = s->ep3_card_index,
|
||||||
.map_index = s->ep3_map_index,
|
.map_index = s->ep3_map_index,
|
||||||
.behavior_flags = 0x0092,
|
.behavior_flags = 0x0092,
|
||||||
.random_crypt = random_crypt,
|
.opt_rand_crypt = opt_rand_crypt,
|
||||||
.tournament = nullptr,
|
.tournament = nullptr,
|
||||||
.trap_card_ids = {},
|
.trap_card_ids = {},
|
||||||
};
|
};
|
||||||
|
|||||||
+9
-8
@@ -689,10 +689,11 @@ string Map::Object::str() const {
|
|||||||
this->item_drop_checked ? "true" : "false");
|
this->item_drop_checked ? "true" : "false");
|
||||||
}
|
}
|
||||||
|
|
||||||
Map::Map(Version version, uint32_t lobby_id, std::shared_ptr<PSOLFGEncryption> random_crypt)
|
Map::Map(Version version, uint32_t lobby_id, uint32_t rare_seed, std::shared_ptr<PSOLFGEncryption> opt_rand_crypt)
|
||||||
: log(string_printf("[Lobby:%08" PRIX32 ":map] ", lobby_id), lobby_log.min_level),
|
: log(string_printf("[Lobby:%08" PRIX32 ":map] ", lobby_id), lobby_log.min_level),
|
||||||
version(version),
|
version(version),
|
||||||
random_crypt(random_crypt) {}
|
rare_seed(rare_seed),
|
||||||
|
opt_rand_crypt(opt_rand_crypt) {}
|
||||||
|
|
||||||
void Map::clear() {
|
void Map::clear() {
|
||||||
this->objects.clear();
|
this->objects.clear();
|
||||||
@@ -735,7 +736,7 @@ bool Map::check_and_log_rare_enemy(bool default_is_rare, uint32_t rare_rate) {
|
|||||||
// versions, we must match the client's logic, even though it's more
|
// versions, we must match the client's logic, even though it's more
|
||||||
// computationally expensive.
|
// computationally expensive.
|
||||||
if (this->version == Version::BB_V4) {
|
if (this->version == Version::BB_V4) {
|
||||||
if ((this->rare_enemy_indexes.size() < 0x10) && (this->random_crypt->next() < rare_rate)) {
|
if ((this->rare_enemy_indexes.size() < 0x10) && (random_from_optional_crypt(this->opt_rand_crypt) < rare_rate)) {
|
||||||
this->rare_enemy_indexes.emplace_back(this->enemies.size());
|
this->rare_enemy_indexes.emplace_back(this->enemies.size());
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -744,7 +745,7 @@ bool Map::check_and_log_rare_enemy(bool default_is_rare, uint32_t rare_rate) {
|
|||||||
// TODO: We only need the first value from this crypt, so it's unfortunate
|
// TODO: We only need the first value from this crypt, so it's unfortunate
|
||||||
// that we have to initialize the entire thing. Find a way to make this
|
// that we have to initialize the entire thing. Find a way to make this
|
||||||
// faster.
|
// faster.
|
||||||
PSOV2Encryption crypt(this->random_crypt->seed() + 0x1000 + this->enemies.size());
|
PSOV2Encryption crypt(this->rare_seed + 0x1000 + this->enemies.size());
|
||||||
float det = (static_cast<float>((crypt.next() >> 16) & 0xFFFF) / 65536.0f);
|
float det = (static_cast<float>((crypt.next() >> 16) & 0xFFFF) / 65536.0f);
|
||||||
// On v1 and v2 (and GC NTE), the rare rate is 0.1% instead of 0.2%.
|
// On v1 and v2 (and GC NTE), the rare rate is 0.1% instead of 0.2%.
|
||||||
float threshold = is_v1_or_v2(this->version) ? 0.001f : 0.002f;
|
float threshold = is_v1_or_v2(this->version) ? 0.001f : 0.002f;
|
||||||
@@ -1534,7 +1535,7 @@ void Map::add_enemies_and_objects_from_quest_data(
|
|||||||
const auto& random_enemy_locations_header = r.pget<SectionHeader>(floor_sections.random_enemy_locations);
|
const auto& random_enemy_locations_header = r.pget<SectionHeader>(floor_sections.random_enemy_locations);
|
||||||
const auto& random_enemy_definitions_header = r.pget<SectionHeader>(floor_sections.random_enemy_definitions);
|
const auto& random_enemy_definitions_header = r.pget<SectionHeader>(floor_sections.random_enemy_definitions);
|
||||||
if (!random_state) {
|
if (!random_state) {
|
||||||
random_state = make_shared<DATParserRandomState>(this->random_crypt->seed());
|
random_state = make_shared<DATParserRandomState>(this->rare_seed);
|
||||||
}
|
}
|
||||||
this->add_random_enemies_from_map_data(
|
this->add_random_enemies_from_map_data(
|
||||||
episode,
|
episode,
|
||||||
@@ -1640,12 +1641,12 @@ string Map::disassemble_quest_data(const void* data, size_t size) {
|
|||||||
SetDataTableBase::SetDataTableBase(Version version) : version(version) {}
|
SetDataTableBase::SetDataTableBase(Version version) : version(version) {}
|
||||||
|
|
||||||
parray<le_uint32_t, 0x20> SetDataTableBase::generate_variations(
|
parray<le_uint32_t, 0x20> SetDataTableBase::generate_variations(
|
||||||
Episode episode, bool is_solo, std::shared_ptr<PSOLFGEncryption> random_crypt) const {
|
Episode episode, bool is_solo, std::shared_ptr<PSOLFGEncryption> opt_rand_crypt) const {
|
||||||
parray<le_uint32_t, 0x20> ret;
|
parray<le_uint32_t, 0x20> ret;
|
||||||
for (size_t floor = 0; floor < 0x10; floor++) {
|
for (size_t floor = 0; floor < 0x10; floor++) {
|
||||||
auto num_vars = this->num_free_roam_variations_for_floor(episode, is_solo, floor);
|
auto num_vars = this->num_free_roam_variations_for_floor(episode, is_solo, floor);
|
||||||
ret[floor * 2] = (num_vars.first > 1) ? (random_crypt->next() % num_vars.first) : 0;
|
ret[floor * 2] = (num_vars.first > 1) ? (random_from_optional_crypt(opt_rand_crypt) % num_vars.first) : 0;
|
||||||
ret[floor * 2 + 1] = (num_vars.second > 1) ? (random_crypt->next() % num_vars.second) : 0;
|
ret[floor * 2 + 1] = (num_vars.second > 1) ? (random_from_optional_crypt(opt_rand_crypt) % num_vars.second) : 0;
|
||||||
}
|
}
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|||||||
+7
-3
@@ -256,7 +256,7 @@ struct Map {
|
|||||||
void generate_shuffled_location_table(const Map::RandomEnemyLocationsHeader& header, StringReader r, uint16_t section);
|
void generate_shuffled_location_table(const Map::RandomEnemyLocationsHeader& header, StringReader r, uint16_t section);
|
||||||
};
|
};
|
||||||
|
|
||||||
Map(Version version, uint32_t lobby_id, std::shared_ptr<PSOLFGEncryption> random_crypt);
|
Map(Version version, uint32_t lobby_id, uint32_t rare_seed, std::shared_ptr<PSOLFGEncryption> opt_rand_crypt);
|
||||||
~Map() = default;
|
~Map() = default;
|
||||||
|
|
||||||
void clear();
|
void clear();
|
||||||
@@ -315,7 +315,8 @@ struct Map {
|
|||||||
|
|
||||||
PrefixedLogger log;
|
PrefixedLogger log;
|
||||||
Version version;
|
Version version;
|
||||||
std::shared_ptr<PSOLFGEncryption> random_crypt;
|
uint32_t rare_seed;
|
||||||
|
std::shared_ptr<PSOLFGEncryption> opt_rand_crypt;
|
||||||
std::vector<Object> objects;
|
std::vector<Object> objects;
|
||||||
std::vector<Enemy> enemies;
|
std::vector<Enemy> enemies;
|
||||||
std::vector<size_t> rare_enemy_indexes;
|
std::vector<size_t> rare_enemy_indexes;
|
||||||
@@ -325,7 +326,10 @@ class SetDataTableBase {
|
|||||||
public:
|
public:
|
||||||
virtual ~SetDataTableBase() = default;
|
virtual ~SetDataTableBase() = default;
|
||||||
|
|
||||||
parray<le_uint32_t, 0x20> generate_variations(Episode episode, bool is_solo, std::shared_ptr<PSOLFGEncryption> random_crypt) const;
|
parray<le_uint32_t, 0x20> generate_variations(
|
||||||
|
Episode episode,
|
||||||
|
bool is_solo,
|
||||||
|
std::shared_ptr<PSOLFGEncryption> opt_rand_crypt = nullptr) const;
|
||||||
virtual std::pair<uint32_t, uint32_t> num_available_variations_for_floor(Episode episode, uint8_t floor) const = 0;
|
virtual std::pair<uint32_t, uint32_t> num_available_variations_for_floor(Episode episode, uint8_t floor) const = 0;
|
||||||
virtual std::pair<uint32_t, uint32_t> num_free_roam_variations_for_floor(Episode episode, bool is_solo, uint8_t floor) const = 0;
|
virtual std::pair<uint32_t, uint32_t> num_free_roam_variations_for_floor(Episode episode, bool is_solo, uint8_t floor) const = 0;
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <phosg/Encoding.hh>
|
#include <phosg/Encoding.hh>
|
||||||
|
#include <phosg/Random.hh>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
@@ -342,3 +343,7 @@ std::string encrypt_pr2_data(const std::string& data, size_t decompressed_size,
|
|||||||
}
|
}
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline uint32_t random_from_optional_crypt(std::shared_ptr<PSOLFGEncryption> random_crypt) {
|
||||||
|
return random_crypt ? random_crypt->next() : random_object<uint32_t>();
|
||||||
|
}
|
||||||
|
|||||||
@@ -1347,6 +1347,7 @@ static HandlerResult S_13_A7(shared_ptr<ProxyServer::LinkedSession> ses, uint16_
|
|||||||
ses->lobby_event,
|
ses->lobby_event,
|
||||||
ses->id,
|
ses->id,
|
||||||
Map::DEFAULT_RARE_ENEMIES,
|
Map::DEFAULT_RARE_ENEMIES,
|
||||||
|
ses->lobby_random_seed,
|
||||||
make_shared<PSOV2Encryption>(ses->lobby_random_seed),
|
make_shared<PSOV2Encryption>(ses->lobby_random_seed),
|
||||||
quest_dat_data);
|
quest_dat_data);
|
||||||
}
|
}
|
||||||
@@ -1656,6 +1657,7 @@ static HandlerResult S_64(shared_ptr<ProxyServer::LinkedSession> ses, uint16_t,
|
|||||||
s->set_data_table(ses->version(), ses->lobby_episode, ses->lobby_mode, ses->lobby_difficulty),
|
s->set_data_table(ses->version(), ses->lobby_episode, ses->lobby_mode, ses->lobby_difficulty),
|
||||||
bind(&ServerState::load_map_file, s.get(), placeholders::_1, placeholders::_2),
|
bind(&ServerState::load_map_file, s.get(), placeholders::_1, placeholders::_2),
|
||||||
Map::DEFAULT_RARE_ENEMIES,
|
Map::DEFAULT_RARE_ENEMIES,
|
||||||
|
ses->lobby_random_seed,
|
||||||
make_shared<PSOV2Encryption>(ses->lobby_random_seed),
|
make_shared<PSOV2Encryption>(ses->lobby_random_seed),
|
||||||
cmd->variations,
|
cmd->variations,
|
||||||
&ses->log);
|
&ses->log);
|
||||||
|
|||||||
+4
-4
@@ -771,9 +771,9 @@ void ProxyServer::LinkedSession::set_drop_mode(DropMode new_mode) {
|
|||||||
default:
|
default:
|
||||||
throw logic_error("invalid lobby base version");
|
throw logic_error("invalid lobby base version");
|
||||||
}
|
}
|
||||||
uint32_t random_seed = this->config.check_flag(Client::Flag::USE_OVERRIDE_RANDOM_SEED)
|
auto opt_rand_crypt = this->config.check_flag(Client::Flag::USE_OVERRIDE_RANDOM_SEED)
|
||||||
? this->config.override_random_seed
|
? make_shared<PSOV2Encryption>(this->config.override_random_seed)
|
||||||
: this->lobby_random_seed;
|
: nullptr;
|
||||||
this->item_creator = make_shared<ItemCreator>(
|
this->item_creator = make_shared<ItemCreator>(
|
||||||
common_item_set,
|
common_item_set,
|
||||||
rare_item_set,
|
rare_item_set,
|
||||||
@@ -787,7 +787,7 @@ void ProxyServer::LinkedSession::set_drop_mode(DropMode new_mode) {
|
|||||||
(this->lobby_mode == GameMode::SOLO) ? GameMode::NORMAL : this->lobby_mode,
|
(this->lobby_mode == GameMode::SOLO) ? GameMode::NORMAL : this->lobby_mode,
|
||||||
this->lobby_difficulty,
|
this->lobby_difficulty,
|
||||||
this->lobby_section_id,
|
this->lobby_section_id,
|
||||||
random_seed,
|
opt_rand_crypt,
|
||||||
// TODO: Can we get battle rules here somehow?
|
// TODO: Can we get battle rules here somehow?
|
||||||
nullptr);
|
nullptr);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -4147,8 +4147,8 @@ shared_ptr<Lobby> create_game_generic(
|
|||||||
game->difficulty = difficulty;
|
game->difficulty = difficulty;
|
||||||
if (c->config.check_flag(Client::Flag::USE_OVERRIDE_RANDOM_SEED)) {
|
if (c->config.check_flag(Client::Flag::USE_OVERRIDE_RANDOM_SEED)) {
|
||||||
game->random_seed = c->config.override_random_seed;
|
game->random_seed = c->config.override_random_seed;
|
||||||
|
game->opt_rand_crypt = make_shared<PSOV2Encryption>(game->random_seed);
|
||||||
}
|
}
|
||||||
game->random_crypt = make_shared<PSOV2Encryption>(game->random_seed);
|
|
||||||
if (battle_player) {
|
if (battle_player) {
|
||||||
game->battle_player = battle_player;
|
game->battle_player = battle_player;
|
||||||
battle_player->set_lobby(game);
|
battle_player->set_lobby(game);
|
||||||
@@ -4239,14 +4239,14 @@ shared_ptr<Lobby> create_game_generic(
|
|||||||
// GC NTE ignores the passed-in variations and always uses all zeroes
|
// GC NTE ignores the passed-in variations and always uses all zeroes
|
||||||
if (game->base_version != Version::GC_NTE) {
|
if (game->base_version != Version::GC_NTE) {
|
||||||
auto sdt = s->set_data_table(game->base_version, game->episode, game->mode, game->difficulty);
|
auto sdt = s->set_data_table(game->base_version, game->episode, game->mode, game->difficulty);
|
||||||
game->variations = sdt->generate_variations(game->episode, is_solo, game->random_crypt);
|
game->variations = sdt->generate_variations(game->episode, is_solo, game->opt_rand_crypt);
|
||||||
} else {
|
} else {
|
||||||
game->variations.clear(0);
|
game->variations.clear(0);
|
||||||
}
|
}
|
||||||
game->load_maps();
|
game->load_maps();
|
||||||
} else {
|
} else {
|
||||||
game->variations.clear(0);
|
game->variations.clear(0);
|
||||||
game->map = make_shared<Map>(game->base_version, game->lobby_id, game->random_crypt);
|
game->map = make_shared<Map>(game->base_version, game->lobby_id, game->random_seed, game->opt_rand_crypt);
|
||||||
}
|
}
|
||||||
|
|
||||||
return game;
|
return game;
|
||||||
|
|||||||
@@ -1803,7 +1803,7 @@ static void on_use_item(
|
|||||||
const auto& item = p->inventory.items[index].data;
|
const auto& item = p->inventory.items[index].data;
|
||||||
name = s->describe_item(c->version(), item, false);
|
name = s->describe_item(c->version(), item, false);
|
||||||
}
|
}
|
||||||
player_use_item(c, index, l->random_crypt);
|
player_use_item(c, index, l->opt_rand_crypt);
|
||||||
|
|
||||||
if (l->log.should_log(LogLevel::INFO)) {
|
if (l->log.should_log(LogLevel::INFO)) {
|
||||||
l->log.info("Player %hhu used item %hu:%08" PRIX32 " (%s)",
|
l->log.info("Player %hhu used item %hu:%08" PRIX32 " (%s)",
|
||||||
@@ -3531,7 +3531,7 @@ static void on_secret_lottery_ticket_exchange_bb(shared_ptr<Client> c, uint8_t,
|
|||||||
|
|
||||||
ItemData item = (s->secret_lottery_results.size() == 1)
|
ItemData item = (s->secret_lottery_results.size() == 1)
|
||||||
? s->secret_lottery_results[0]
|
? s->secret_lottery_results[0]
|
||||||
: s->secret_lottery_results[l->random_crypt->next() % s->secret_lottery_results.size()];
|
: s->secret_lottery_results[random_from_optional_crypt(l->opt_rand_crypt) % s->secret_lottery_results.size()];
|
||||||
item.enforce_min_stack_size(limits);
|
item.enforce_min_stack_size(limits);
|
||||||
item.id = l->generate_item_id(c->lobby_client_id);
|
item.id = l->generate_item_id(c->lobby_client_id);
|
||||||
p->add_item(item, limits);
|
p->add_item(item, limits);
|
||||||
@@ -3547,7 +3547,7 @@ static void on_secret_lottery_ticket_exchange_bb(shared_ptr<Client> c, uint8_t,
|
|||||||
out_cmd.unknown_a3.clear(1);
|
out_cmd.unknown_a3.clear(1);
|
||||||
} else {
|
} else {
|
||||||
for (size_t z = 0; z < out_cmd.unknown_a3.size(); z++) {
|
for (size_t z = 0; z < out_cmd.unknown_a3.size(); z++) {
|
||||||
out_cmd.unknown_a3[z] = l->random_crypt->next() % s->secret_lottery_results.size();
|
out_cmd.unknown_a3[z] = random_from_optional_crypt(l->opt_rand_crypt) % s->secret_lottery_results.size();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
send_command_t(c, 0x24, (slt_index >= 0) ? 0 : 1, out_cmd);
|
send_command_t(c, 0x24, (slt_index >= 0) ? 0 : 1, out_cmd);
|
||||||
@@ -3578,7 +3578,7 @@ static void on_quest_F95E_result_bb(shared_ptr<Client> c, uint8_t, uint8_t, void
|
|||||||
if (results.empty()) {
|
if (results.empty()) {
|
||||||
throw runtime_error("invalid result type");
|
throw runtime_error("invalid result type");
|
||||||
}
|
}
|
||||||
ItemData item = (results.size() == 1) ? results[0] : results[l->random_crypt->next() % results.size()];
|
ItemData item = (results.size() == 1) ? results[0] : results[random_from_optional_crypt(l->opt_rand_crypt) % results.size()];
|
||||||
if (item.data1[0] == 0x04) { // Meseta
|
if (item.data1[0] == 0x04) { // Meseta
|
||||||
// TODO: What is the right amount of Meseta to use here? Presumably it
|
// TODO: What is the right amount of Meseta to use here? Presumably it
|
||||||
// should be random within a certain range, but it's not obvious what
|
// should be random within a certain range, but it's not obvious what
|
||||||
@@ -3656,10 +3656,10 @@ static void on_quest_F960_result_bb(shared_ptr<Client> c, uint8_t, uint8_t, void
|
|||||||
size_t tier = cmd.result_tier - num_failures;
|
size_t tier = cmd.result_tier - num_failures;
|
||||||
const auto& results = s->quest_F960_success_results.at(tier);
|
const auto& results = s->quest_F960_success_results.at(tier);
|
||||||
uint64_t probability = results.base_probability + num_failures * results.probability_upgrade;
|
uint64_t probability = results.base_probability + num_failures * results.probability_upgrade;
|
||||||
if (l->random_crypt->next() <= probability) {
|
if (random_from_optional_crypt(l->opt_rand_crypt) <= probability) {
|
||||||
c->log.info("Tier %zu yielded a prize", tier);
|
c->log.info("Tier %zu yielded a prize", tier);
|
||||||
const auto& result_items = results.results.at(weekday);
|
const auto& result_items = results.results.at(weekday);
|
||||||
item = result_items[l->random_crypt->next() % result_items.size()];
|
item = result_items[random_from_optional_crypt(l->opt_rand_crypt) % result_items.size()];
|
||||||
break;
|
break;
|
||||||
} else {
|
} else {
|
||||||
c->log.info("Tier %zu did not yield a prize", tier);
|
c->log.info("Tier %zu did not yield a prize", tier);
|
||||||
@@ -3668,7 +3668,7 @@ static void on_quest_F960_result_bb(shared_ptr<Client> c, uint8_t, uint8_t, void
|
|||||||
if (item.empty()) {
|
if (item.empty()) {
|
||||||
c->log.info("Choosing result from failure tier");
|
c->log.info("Choosing result from failure tier");
|
||||||
const auto& result_items = s->quest_F960_failure_results.results.at(weekday);
|
const auto& result_items = s->quest_F960_failure_results.results.at(weekday);
|
||||||
item = result_items[l->random_crypt->next() % result_items.size()];
|
item = result_items[random_from_optional_crypt(l->opt_rand_crypt) % result_items.size()];
|
||||||
}
|
}
|
||||||
if (item.empty()) {
|
if (item.empty()) {
|
||||||
throw runtime_error("no item produced, even from failure tier");
|
throw runtime_error("no item produced, even from failure tier");
|
||||||
|
|||||||
Reference in New Issue
Block a user