make BB games deterministic for replays
This commit is contained in:
@@ -1,19 +1,16 @@
|
||||
## General
|
||||
|
||||
- Encapsulate BB server-side random state and make replays deterministic
|
||||
- Write a simple status API
|
||||
- Implement per-game logging
|
||||
- Make reloading happen on separate threads so compression doesn't block active clients
|
||||
- Implement decrypt/encrypt actions for VMS files
|
||||
- Make UI strings localizable (e.g. entries in menus, welcome message, etc.)
|
||||
- Figure out what causes the corruption message on PC proxy sessions and fix it
|
||||
- Add an idle connection timeout for proxy sessions
|
||||
- Look into JP heart symbol bug on Linux
|
||||
|
||||
## Episode 3
|
||||
|
||||
- Enforce tournament deck restrictions (e.g. rank checks, No Assist option) when populating COMs at tournament start time
|
||||
- Add support for recording battles on the proxy server (both in primary and spectator teams)
|
||||
- Make `reload licenses` not vulnerable to online players' licenses overwriting licenses on disk somehow
|
||||
- Implement ranks (based on total Meseta earned)
|
||||
|
||||
|
||||
+1
-1
@@ -1617,7 +1617,7 @@ static void proxy_command_item(shared_ptr<ProxyServer::LinkedSession> ses, const
|
||||
bool set_drop = (!args.empty() && (args[0] == '!'));
|
||||
|
||||
ItemData item = s->item_name_index->parse_item_description(ses->version(), (set_drop ? args.substr(1) : args));
|
||||
item.id = random_object<uint32_t>();
|
||||
item.id = random_object<uint32_t>() | 0x80000000;
|
||||
|
||||
if (set_drop) {
|
||||
ses->next_drop_item = item;
|
||||
|
||||
+2
-4
@@ -2,13 +2,11 @@
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include <phosg/Random.hh>
|
||||
|
||||
#include "SendCommands.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
void player_use_item(shared_ptr<Client> c, size_t item_index) {
|
||||
void player_use_item(shared_ptr<Client> c, size_t item_index, shared_ptr<PSOLFGEncryption> random_crypt) {
|
||||
auto s = c->require_server_state();
|
||||
|
||||
// On PC (and presumably DC), the client sends a 6x29 after this to delete the
|
||||
@@ -179,7 +177,7 @@ void player_use_item(shared_ptr<Client> c, size_t item_index) {
|
||||
if (sum == 0) {
|
||||
throw runtime_error("no unwrap results available for event");
|
||||
}
|
||||
size_t det = random_object<size_t>() % sum;
|
||||
size_t det = random_crypt->next() % sum;
|
||||
for (size_t z = 0; z < table.second; z++) {
|
||||
const auto& entry = table.first[z];
|
||||
if (det > entry.probability) {
|
||||
|
||||
+2
-1
@@ -6,8 +6,9 @@
|
||||
#include <random>
|
||||
|
||||
#include "Client.hh"
|
||||
#include "PSOEncryption.hh"
|
||||
#include "ServerState.hh"
|
||||
#include "StaticGameData.hh"
|
||||
|
||||
void player_use_item(std::shared_ptr<Client> c, size_t item_index);
|
||||
void player_use_item(std::shared_ptr<Client> c, size_t item_index, std::shared_ptr<PSOLFGEncryption> random_crypt);
|
||||
void player_feed_mag(std::shared_ptr<Client> c, size_t mag_item_index, size_t fed_item_index);
|
||||
|
||||
+1
-1
@@ -254,7 +254,7 @@ void Lobby::create_item_creator() {
|
||||
|
||||
void Lobby::load_maps() {
|
||||
auto s = this->require_server_state();
|
||||
this->map = make_shared<Map>(this->lobby_id);
|
||||
this->map = make_shared<Map>(this->lobby_id, this->random_crypt);
|
||||
|
||||
if (this->quest) {
|
||||
auto leader_c = this->clients.at(this->leader_id);
|
||||
|
||||
+4
-3
@@ -131,8 +131,9 @@ string Map::Object::str(shared_ptr<const ItemNameIndex> name_index) const {
|
||||
}
|
||||
}
|
||||
|
||||
Map::Map(uint32_t lobby_id)
|
||||
: log(string_printf("[Lobby:%08" PRIX32 ":map] ", lobby_id), lobby_log.min_level) {}
|
||||
Map::Map(uint32_t lobby_id, std::shared_ptr<PSOLFGEncryption> random_crypt)
|
||||
: log(string_printf("[Lobby:%08" PRIX32 ":map] ", lobby_id), lobby_log.min_level),
|
||||
random_crypt(random_crypt) {}
|
||||
|
||||
void Map::clear() {
|
||||
this->objects.clear();
|
||||
@@ -167,7 +168,7 @@ bool Map::check_and_log_rare_enemy(bool default_is_rare, uint32_t rare_rate) {
|
||||
if (default_is_rare) {
|
||||
return true;
|
||||
}
|
||||
if ((this->rare_enemy_indexes.size() < 0x10) && (random_object<uint32_t>() < rare_rate)) {
|
||||
if ((this->rare_enemy_indexes.size() < 0x10) && (this->random_crypt->next() < rare_rate)) {
|
||||
this->rare_enemy_indexes.emplace_back(this->enemies.size());
|
||||
return true;
|
||||
}
|
||||
|
||||
+2
-1
@@ -253,7 +253,7 @@ struct Map {
|
||||
void generate_shuffled_location_table(const Map::RandomEnemyLocationsHeader& header, StringReader r, uint16_t section);
|
||||
};
|
||||
|
||||
explicit Map(uint32_t lobby_id);
|
||||
Map(uint32_t lobby_id, std::shared_ptr<PSOLFGEncryption> random_crypt);
|
||||
~Map() = default;
|
||||
|
||||
void clear();
|
||||
@@ -309,6 +309,7 @@ struct Map {
|
||||
static std::string disassemble_quest_data(const void* data, size_t size);
|
||||
|
||||
PrefixedLogger log;
|
||||
std::shared_ptr<PSOLFGEncryption> random_crypt;
|
||||
std::vector<Object> objects;
|
||||
std::vector<Enemy> enemies;
|
||||
std::vector<size_t> rare_enemy_indexes;
|
||||
|
||||
@@ -1453,7 +1453,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);
|
||||
player_use_item(c, index, l->random_crypt);
|
||||
|
||||
if (l->log.should_log(LogLevel::INFO)) {
|
||||
l->log.info("Player %hhu used item %hu:%08" PRIX32 " (%s)",
|
||||
@@ -2803,7 +2803,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[random_object<uint32_t>() % s->secret_lottery_results.size()];
|
||||
: s->secret_lottery_results[l->random_crypt->next() % s->secret_lottery_results.size()];
|
||||
item.enforce_min_stack_size();
|
||||
item.id = l->generate_item_id(c->lobby_client_id);
|
||||
p->add_item(item);
|
||||
@@ -2819,7 +2819,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] = random_object<uint32_t>() % s->secret_lottery_results.size();
|
||||
out_cmd.unknown_a3[z] = l->random_crypt->next() % s->secret_lottery_results.size();
|
||||
}
|
||||
}
|
||||
send_command_t(c, 0x24, (slt_index >= 0) ? 0 : 1, out_cmd);
|
||||
@@ -2849,7 +2849,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[random_object<uint32_t>() % results.size()];
|
||||
ItemData item = (results.size() == 1) ? results[0] : results[l->random_crypt->next() % 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
|
||||
@@ -2926,10 +2926,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 (random_object<uint32_t>() <= probability) {
|
||||
if (l->random_crypt->next() <= probability) {
|
||||
c->log.info("Tier %zu yielded a prize", tier);
|
||||
const auto& result_items = results.results.at(weekday);
|
||||
item = result_items[random_object<uint32_t>() % result_items.size()];
|
||||
item = result_items[l->random_crypt->next() % result_items.size()];
|
||||
break;
|
||||
} else {
|
||||
c->log.info("Tier %zu did not yield a prize", tier);
|
||||
@@ -2938,7 +2938,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[random_object<uint32_t>() % result_items.size()];
|
||||
item = result_items[l->random_crypt->next() % result_items.size()];
|
||||
}
|
||||
if (item.empty()) {
|
||||
throw runtime_error("no item produced, even from failure tier");
|
||||
|
||||
+2
-1
@@ -501,11 +501,12 @@ void send_pc_console_split_reconnect(shared_ptr<Client> c, uint32_t address,
|
||||
}
|
||||
|
||||
void send_client_init_bb(shared_ptr<Client> c, uint32_t error_code) {
|
||||
auto team = c->team();
|
||||
S_ClientInit_BB_00E6 cmd;
|
||||
cmd.error_code = error_code;
|
||||
cmd.player_tag = 0x00010000;
|
||||
cmd.guild_card_number = c->license->serial_number;
|
||||
cmd.team_id = static_cast<uint32_t>(random_object<uint32_t>());
|
||||
cmd.team_id = team ? team->team_id : 0;
|
||||
c->config.serialize_into(cmd.client_config);
|
||||
cmd.can_create_team = 1;
|
||||
cmd.episode_4_unlocked = 1;
|
||||
|
||||
+1
-1
@@ -811,7 +811,7 @@ Proxy session commands:\n\
|
||||
|
||||
auto s = ses->require_server_state();
|
||||
ItemData item = s->item_name_index->parse_item_description(ses->version(), command_args);
|
||||
item.id = random_object<uint32_t>();
|
||||
item.id = random_object<uint32_t>() | 0x80000000;
|
||||
|
||||
if (command_name == "set-next-item") {
|
||||
ses->next_drop_item = item;
|
||||
|
||||
Reference in New Issue
Block a user