use system randomness by default unless overridden

This commit is contained in:
Martin Michelsen
2024-02-23 23:50:03 -08:00
parent 7dc5a02a83
commit 294d180e68
22 changed files with 148 additions and 106 deletions
+1
View File
@@ -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
+4 -4
View File
@@ -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];
} }
} }
}; };
+4 -4
View File
@@ -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;
+3 -3
View File
@@ -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
+1 -1
View File
@@ -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
View File
@@ -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)) {
+1 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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>();
}
+2
View File
@@ -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
View File
@@ -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 {
+3 -3
View File
@@ -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;
+7 -7
View File
@@ -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");