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
- 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];
}
void shuffle(PSOLFGEncryption& random_crypt) {
void shuffle(std::shared_ptr<PSOLFGEncryption> opt_rand_crypt) {
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];
this->items[z] = this->items[other_z];
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) {
throw std::runtime_error("sample from empty probability table");
} else if (this->count == 1) {
return this->items[0];
} 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;
uint8_t base_index = this->draw_index + 5;
for (size_t z = 0; z < this->card_refs.size(); z++) {
uint8_t index1 = this->random_crypt->next() % max;
uint8_t index2 = this->random_crypt->next() % max;
uint8_t index1 = random_from_optional_crypt(this->opt_rand_crypt) % max;
uint8_t index2 = random_from_optional_crypt(this->opt_rand_crypt) % max;
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 + index2] = temp_ref;
@@ -265,8 +265,8 @@ void DeckState::shuffle() {
// 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
// did.
uint8_t index1 = this->draw_index + this->random_crypt->next() % max;
uint8_t index2 = 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 + random_from_optional_crypt(this->opt_rand_crypt) % max;
uint16_t temp_ref = this->card_refs[index1];
this->card_refs[index1] = this->card_refs[index2];
this->card_refs[index2] = temp_ref;
+3 -3
View File
@@ -57,13 +57,13 @@ public:
DeckState(
uint8_t client_id,
const parray<CardIDT, 0x1F>& card_ids,
std::shared_ptr<PSOLFGEncryption> random_crypt)
std::shared_ptr<PSOLFGEncryption> opt_rand_crypt)
: client_id(client_id),
draw_index(1),
card_ref_base(this->client_id << 8),
shuffle_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++) {
auto& e = this->entries[z];
e.card_id = card_ids[z];
@@ -112,7 +112,7 @@ private:
parray<CardEntry, 31> entries;
parray<uint16_t, 31> card_refs;
std::shared_ptr<PSOLFGEncryption> random_crypt;
std::shared_ptr<PSOLFGEncryption> opt_rand_crypt;
};
} // namespace Episode3
+1 -1
View File
@@ -49,7 +49,7 @@ void PlayerState::init() {
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) {
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());
// 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
// 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>();
@@ -273,10 +273,15 @@ void Server::send_6xB4x46() const {
G_ServerVersionStrings_Ep3_6xB4x46 cmd;
cmd.version_signature.encode(VERSION_SIGNATURE, 1);
cmd.date_str1.encode(format_time(this->options.card_index->definitions_mtime() * 1000000), 1);
string date_str2 = string_printf(
"Random:%08" PRIX32 "+%08" PRIX32,
this->options.random_crypt->seed(),
this->options.random_crypt->absolute_offset());
string date_str2;
if (this->options.opt_rand_crypt) {
date_str2 = string_printf(
"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) {
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) {
// The original implementation was essentially:
// return (static_cast<double>(this->random_crypt->next() >> 16) / 65536.0) * max
// This is unnecessarily complicated, so we instead just do this:
return this->options.random_crypt->next() % max;
// return (static_cast<double>(this->opt_rand_crypt->next() >> 16) / 65536.0) * max
// This is unnecessarily complicated and imprecise, so we instead just do:
return random_from_optional_crypt(this->options.opt_rand_crypt) % max;
}
float Server::get_random_float_0_1() {
// 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 {
@@ -1544,8 +1549,8 @@ void Server::setup_and_start_battle() {
this->setup_phase = SetupPhase::STARTER_ROLLS;
// Note: This is where original implementation re-seeds random_crypt (it uses
// time() as the seed value).
// Note: This is where original implementation re-seeds opt_rand_crypt (it
// uses time() as the seed value).
for (size_t z = 0; z < 4; 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 MapIndex> map_index;
uint32_t behavior_flags;
std::shared_ptr<PSOLFGEncryption> random_crypt;
std::shared_ptr<PSOLFGEncryption> opt_rand_crypt;
std::shared_ptr<const Tournament> tournament;
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({
{"BehaviorFlags", ep3s->options.behavior_flags},
{"RandomSeed", ep3s->options.random_crypt ? ep3s->options.random_crypt->seed() : JSON(nullptr)},
{"RandomOffset", ep3s->options.random_crypt ? ep3s->options.random_crypt->absolute_offset() : JSON(nullptr)},
{"RandomSeed", ep3s->options.opt_rand_crypt ? ep3s->options.opt_rand_crypt->seed() : 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},
{"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)},
+31 -30
View File
@@ -23,7 +23,7 @@ ItemCreator::ItemCreator(
GameMode mode,
uint8_t difficulty,
uint8_t section_id,
uint32_t random_seed,
std::shared_ptr<PSOLFGEncryption> opt_rand_crypt,
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),
version(stack_limits->version),
@@ -40,17 +40,12 @@ ItemCreator::ItemCreator(
item_parameter_table(item_parameter_table),
pt(common_item_set->get_table(this->episode, this->mode, this->difficulty, this->section_id)),
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();
}
void ItemCreator::set_random_state(uint32_t seed, uint32_t absolute_offset) {
if ((this->random_crypt.seed() != seed) || (this->random_crypt.absolute_offset() > absolute_offset)) {
this->random_crypt = PSOV2Encryption(seed);
}
while (this->random_crypt.absolute_offset() < absolute_offset) {
this->random_crypt.next();
}
void ItemCreator::set_random_crypt(shared_ptr<PSOLFGEncryption> new_random_crypt) {
this->opt_rand_crypt = new_random_crypt;
}
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) {
this->log.info("Box drop checks for area_norm %02hhX; random state: %08" PRIX32 " %08" PRIX32,
area_norm, this->random_crypt.seed(), this->random_crypt.absolute_offset());
this->log.info("Box drop checks for area_norm %02hhX", area_norm);
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;
res.item = this->check_rare_specs_and_create_rare_box_item(area_norm);
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);
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 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) {
return this->random_crypt.next() % max;
return random_from_optional_crypt(this->opt_rand_crypt) % max;
}
float ItemCreator::rand_float_0_1_from_crypt() {
// 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>
@@ -706,9 +708,9 @@ void ItemCreator::generate_common_mag_variances(ItemData& item) {
if (is_pre_v1(this->version)) {
item.data2[3] = 0x00;
} 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 {
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;
}
void ItemCreator::generate_armor_shop_armors(
vector<ItemData>& shop, size_t player_level) {
void ItemCreator::generate_armor_shop_armors(vector<ItemData>& shop, size_t player_level) {
size_t num_items;
if (player_level < 11) {
num_items = 4;
@@ -1039,7 +1040,7 @@ void ItemCreator::generate_armor_shop_armors(
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;) {
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.shuffle(this->random_crypt);
pt.shuffle(this->opt_rand_crypt);
for (size_t items_generated = 0; items_generated < num_items;) {
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.shuffle(this->random_crypt);
pt.shuffle(this->opt_rand_crypt);
for (size_t items_generated = 0; items_generated < num_items;) {
ItemData item;
@@ -1229,7 +1230,7 @@ void ItemCreator::generate_rare_tool_shop_recovery_items(
pt.push(e.value);
}
}
pt.shuffle(this->random_crypt);
pt.shuffle(this->opt_rand_crypt);
size_t effective_num_items = num_items;
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.shuffle(this->random_crypt);
pt.shuffle(this->opt_rand_crypt);
static const array<uint8_t, 0x13> tech_num_map = {
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.shuffle(this->random_crypt);
pt.shuffle(this->opt_rand_crypt);
vector<ItemData> shop;
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.
// 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:
item.data1[4] = 0;
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.
// 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) {
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.shuffle(this->random_crypt);
pt.shuffle(this->opt_rand_crypt);
do {
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
{
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);
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
@@ -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)) {
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);
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);
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);
@@ -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);
// Note: The original code really does use the same delta for all three
// 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);
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
+3 -3
View File
@@ -24,11 +24,11 @@ public:
GameMode mode,
uint8_t difficulty,
uint8_t section_id,
uint32_t random_seed,
std::shared_ptr<PSOLFGEncryption> opt_rand_crypt,
std::shared_ptr<const BattleRules> restrictions = nullptr);
~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 {
ItemData item;
@@ -78,7 +78,7 @@ private:
// Note: The original implementation uses 17 different random states for some
// 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 {
return !is_v1_or_v2(this->version);
+2 -2
View File
@@ -6,7 +6,7 @@
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();
// 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) {
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++) {
const auto& entry = table.first[z];
if (det > entry.probability) {
+1 -1
View File
@@ -12,7 +12,7 @@
#include "ServerState.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 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->difficulty,
this->section_id,
this->random_seed,
this->opt_rand_crypt,
this->quest ? this->quest->battle_rules : nullptr);
}
@@ -268,9 +268,10 @@ shared_ptr<Map> Lobby::load_maps(
uint8_t event,
uint32_t lobby_id,
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) {
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(
episode,
difficulty,
@@ -291,12 +292,26 @@ shared_ptr<Map> Lobby::load_maps(
shared_ptr<const SetDataTableBase> sdt,
function<shared_ptr<const string>(Version, const string&)> get_file_data,
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 PrefixedLogger* log) {
auto enemy_filenames = sdt->map_filenames_for_variations(variations, episode, mode, true);
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(
@@ -310,9 +325,10 @@ shared_ptr<Map> Lobby::load_maps(
uint32_t lobby_id,
function<shared_ptr<const string>(Version, const string&)> get_file_data,
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) {
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
// Ragol without a quest loaded
@@ -384,7 +400,8 @@ void Lobby::load_maps() {
this->event,
this->lobby_id,
rare_rates,
this->random_crypt,
this->random_seed,
this->opt_rand_crypt,
vq->dat_contents_decompressed);
} 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),
bind(&ServerState::load_map_file, s.get(), placeholders::_1, placeholders::_2),
rare_rates,
this->random_crypt,
this->random_seed,
this->opt_rand_crypt,
this->variations,
&this->log);
} 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());
@@ -434,7 +452,7 @@ void Lobby::create_ep3_server() {
.card_index = is_nte ? s->ep3_card_index_trial : s->ep3_card_index,
.map_index = s->ep3_map_index,
.behavior_flags = s->ep3_behavior_flags,
.random_crypt = this->random_crypt,
.opt_rand_crypt = this->opt_rand_crypt,
.tournament = tourn,
.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;
// This seed is also sent to the client for rare enemy generation
uint32_t random_seed;
std::shared_ptr<PSOLFGEncryption> random_crypt;
std::shared_ptr<PSOLFGEncryption> opt_rand_crypt;
uint8_t allowed_drop_modes;
DropMode drop_mode;
std::shared_ptr<ItemCreator> item_creator;
@@ -202,7 +202,8 @@ struct Lobby : public std::enable_shared_from_this<Lobby> {
uint8_t event,
uint32_t lobby_id,
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);
static std::shared_ptr<Map> load_maps(
Version version,
@@ -214,7 +215,8 @@ struct Lobby : public std::enable_shared_from_this<Lobby> {
std::shared_ptr<const SetDataTableBase> sdt,
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<PSOLFGEncryption> random_crypt,
uint32_t random_seed,
std::shared_ptr<PSOLFGEncryption> opt_rand_crypt,
const parray<le_uint32_t, 0x20>& variations,
const PrefixedLogger* log = nullptr);
static std::shared_ptr<Map> load_maps(
@@ -228,7 +230,8 @@ struct Lobby : public std::enable_shared_from_this<Lobby> {
uint32_t lobby_id,
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<PSOLFGEncryption> random_crypt,
uint32_t random_seed,
std::shared_ptr<PSOLFGEncryption> opt_rand_crypt,
const PrefixedLogger* log = nullptr);
void load_maps();
void create_ep3_server();
+5 -3
View File
@@ -2133,7 +2133,8 @@ Action a_find_rare_enemy_seeds(
if (!vq->dat_contents_decompressed) {
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 {
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),
bind(&ServerState::load_map_file, s.get(), placeholders::_1, placeholders::_2),
rare_rates,
seed,
random_crypt,
variations);
}
@@ -2295,12 +2297,12 @@ Action a_replay_ep3_battle_commands(
s->load_ep3_cards(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 = {
.card_index = s->ep3_card_index,
.map_index = s->ep3_map_index,
.behavior_flags = 0x0092,
.random_crypt = random_crypt,
.opt_rand_crypt = opt_rand_crypt,
.tournament = nullptr,
.trap_card_ids = {},
};
+9 -8
View File
@@ -689,10 +689,11 @@ string Map::Object::str() const {
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),
version(version),
random_crypt(random_crypt) {}
rare_seed(rare_seed),
opt_rand_crypt(opt_rand_crypt) {}
void Map::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
// computationally expensive.
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());
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
// that we have to initialize the entire thing. Find a way to make this
// 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);
// 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;
@@ -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_definitions_header = r.pget<SectionHeader>(floor_sections.random_enemy_definitions);
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(
episode,
@@ -1640,12 +1641,12 @@ string Map::disassemble_quest_data(const void* data, size_t size) {
SetDataTableBase::SetDataTableBase(Version version) : version(version) {}
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;
for (size_t floor = 0; floor < 0x10; 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 + 1] = (num_vars.second > 1) ? (random_crypt->next() % num_vars.second) : 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_from_optional_crypt(opt_rand_crypt) % num_vars.second) : 0;
}
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);
};
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;
void clear();
@@ -315,7 +315,8 @@ struct Map {
PrefixedLogger log;
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<Enemy> enemies;
std::vector<size_t> rare_enemy_indexes;
@@ -325,7 +326,10 @@ class SetDataTableBase {
public:
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_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 <phosg/Encoding.hh>
#include <phosg/Random.hh>
#include <stdexcept>
#include <string>
#include <vector>
@@ -342,3 +343,7 @@ std::string encrypt_pr2_data(const std::string& data, size_t decompressed_size,
}
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->id,
Map::DEFAULT_RARE_ENEMIES,
ses->lobby_random_seed,
make_shared<PSOV2Encryption>(ses->lobby_random_seed),
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),
bind(&ServerState::load_map_file, s.get(), placeholders::_1, placeholders::_2),
Map::DEFAULT_RARE_ENEMIES,
ses->lobby_random_seed,
make_shared<PSOV2Encryption>(ses->lobby_random_seed),
cmd->variations,
&ses->log);
+4 -4
View File
@@ -771,9 +771,9 @@ void ProxyServer::LinkedSession::set_drop_mode(DropMode new_mode) {
default:
throw logic_error("invalid lobby base version");
}
uint32_t random_seed = this->config.check_flag(Client::Flag::USE_OVERRIDE_RANDOM_SEED)
? this->config.override_random_seed
: this->lobby_random_seed;
auto opt_rand_crypt = this->config.check_flag(Client::Flag::USE_OVERRIDE_RANDOM_SEED)
? make_shared<PSOV2Encryption>(this->config.override_random_seed)
: nullptr;
this->item_creator = make_shared<ItemCreator>(
common_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_difficulty,
this->lobby_section_id,
random_seed,
opt_rand_crypt,
// TODO: Can we get battle rules here somehow?
nullptr);
} else {
+3 -3
View File
@@ -4147,8 +4147,8 @@ shared_ptr<Lobby> create_game_generic(
game->difficulty = difficulty;
if (c->config.check_flag(Client::Flag::USE_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) {
game->battle_player = battle_player;
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
if (game->base_version != Version::GC_NTE) {
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 {
game->variations.clear(0);
}
game->load_maps();
} else {
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;
+7 -7
View File
@@ -1803,7 +1803,7 @@ static void on_use_item(
const auto& item = p->inventory.items[index].data;
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)) {
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)
? 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.id = l->generate_item_id(c->lobby_client_id);
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);
} else {
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);
@@ -3578,7 +3578,7 @@ static void on_quest_F95E_result_bb(shared_ptr<Client> c, uint8_t, uint8_t, void
if (results.empty()) {
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
// 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
@@ -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;
const auto& results = s->quest_F960_success_results.at(tier);
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);
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;
} else {
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()) {
c->log.info("Choosing result from failure tier");
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()) {
throw runtime_error("no item produced, even from failure tier");