From 57f3e1e5f21830985d88b340279c0bf8e807f7dc Mon Sep 17 00:00:00 2001 From: James Osborne Date: Fri, 22 May 2026 21:49:38 -0400 Subject: [PATCH] Lock BB test characters to test ship --- src/ReceiveCommands.cc | 82 ++++++++++++++++++++++-------------------- src/ServerState.cc | 1 + src/ServerState.hh | 1 + 3 files changed, 45 insertions(+), 39 deletions(-) diff --git a/src/ReceiveCommands.cc b/src/ReceiveCommands.cc index 7328c6b1..72f30602 100644 --- a/src/ReceiveCommands.cc +++ b/src/ReceiveCommands.cc @@ -34,12 +34,8 @@ const char* QUEST_BARRIER_DISCONNECT_HOOK_NAME = "quest_barrier"; const char* ADD_NEXT_CLIENT_DISCONNECT_HOOK_NAME = "add_next_game_client"; -static string bb_test_taint_filename(shared_ptr c) { - return c->character_filename() + ".test-tainted"; -} - -static string bb_test_taint_grandfather_filename(shared_ptr c) { - return c->character_filename() + ".grandfathered-before-test-taint"; +static string bb_test_filename(shared_ptr c) { + return c->character_filename() + ".test"; } static bool file_exists_for_bb_taint(const string& filename) { @@ -47,28 +43,44 @@ static bool file_exists_for_bb_taint(const string& filename) { return f.good(); } -static bool bb_character_is_test_tainted(shared_ptr c) { - return file_exists_for_bb_taint(bb_test_taint_filename(c)); +static bool bb_character_is_test(shared_ptr c) { + return file_exists_for_bb_taint(bb_test_filename(c)); } -static bool bb_character_is_test_taint_grandfathered(shared_ptr c) { - return file_exists_for_bb_taint(bb_test_taint_grandfather_filename(c)); -} - -static bool mark_bb_character_test_tainted(shared_ptr c) { - string filename = bb_test_taint_filename(c); +static bool mark_bb_character_test(shared_ptr c) { + string filename = bb_test_filename(c); ofstream f(filename, ios::out | ios::trunc); if (!f.good()) { return false; } - f << "status=test-tainted\n"; - f << "reason=entered-test-ship\n"; - f << "account_id=" << c->login->account->account_id << "\n"; + uint32_t account_id = 0; + if (c->login && c->login->account) { + account_id = c->login->account->account_id; + } + + f << "status=test\n"; + f << "reason=created-on-test-ship\n"; + f << "account_id=" << account_id << "\n"; f << "character_file=" << c->character_filename() << "\n"; return f.good(); } +static bool enforce_bb_test_ship_lock(shared_ptr c, bool current_ship_is_test) { + if (current_ship_is_test) { + if (!bb_character_is_test(c)) { + send_message_box(c, "$C6Only Test characters can enter Test.\n\n$C7Create a new character on the Test ship."); + return false; + } + } else { + if (bb_character_is_test(c)) { + send_message_box(c, "$C6This BB character is locked to Test.\n\n$C7Test characters cannot enter public ships."); + return false; + } + } + + return true; +} static string bb_hardcore_filename(shared_ptr c) { return c->character_filename() + ".hardcore"; @@ -127,9 +139,7 @@ static bool enforce_bb_hardcore_ship_lock(shared_ptr c, bool current_shi } if (current_ship_is_hardcore) { - if (bb_character_is_hardcore_ineligible(c) || - bb_character_is_test_tainted(c) || - bb_character_is_test_taint_grandfathered(c)) { + if (bb_character_is_hardcore_ineligible(c)) { if (!bb_character_is_hardcore_ineligible(c)) { mark_bb_character_hardcore_ineligible(c); } @@ -2851,14 +2861,6 @@ static asio::awaitable on_10_main_menu(shared_ptr c, uint32_t item break; } - // PSO Peeps alpha test: entering Test taints only non-grandfathered BB characters. - // Existing characters were grandfathered before this feature was enabled. - if (!bb_character_is_test_taint_grandfathered(c)) { - if (!mark_bb_character_test_tainted(c)) { - send_message_box(c, "$C6Could not mark this character for Test access.\n\n$C7Please report this."); - break; - } - } send_reconnect(c, s->connect_address_for_client(c), 19345); break; @@ -2880,13 +2882,6 @@ static asio::awaitable on_10_main_menu(shared_ptr c, uint32_t item break; } - // PSO Peeps dev/test isolation: entering Dev taints only non-grandfathered BB characters. - if (!bb_character_is_test_taint_grandfathered(c)) { - if (!mark_bb_character_test_tainted(c)) { - send_message_box(c, "$C6Could not mark this character for Dev access.\n\n$C7Please report this."); - break; - } - } send_reconnect(c, s->connect_address_for_client(c), 19445); break; @@ -2908,8 +2903,8 @@ static asio::awaitable on_10_main_menu(shared_ptr c, uint32_t item break; } - if (bb_character_is_test_tainted(c)) { - send_message_box(c, "$C6This character has been used outside of Vanilla and cannot enter Vanilla.\n\n$C7Use Live/Test with this character, or create a fresh Vanilla character."); + if (bb_character_is_test(c)) { + send_message_box(c, "$C6This BB character is locked to Test.\n\n$C7Test characters cannot enter public ships."); c->channel->disconnect(); break; } @@ -2937,8 +2932,7 @@ static asio::awaitable on_10_main_menu(shared_ptr c, uint32_t item } if (bb_character_is_hardcore_ineligible(c) || - bb_character_is_test_tainted(c) || - bb_character_is_test_taint_grandfathered(c)) { + bb_character_is_test(c)) { if (!bb_character_is_hardcore_ineligible(c)) { mark_bb_character_hardcore_ineligible(c); } @@ -4165,6 +4159,10 @@ static asio::awaitable on_E3_BB(shared_ptr c, Channel::Message& ms c->bb_bank_character_index = cmd.character_index; auto s = c->require_server_state(); + if (!enforce_bb_test_ship_lock(c, s->enable_test_mode)) { + c->unload_character(false); + co_return; + } if (!enforce_bb_hardcore_ship_lock(c, s->enable_hardcore_mode)) { c->unload_character(false); co_return; @@ -4417,6 +4415,12 @@ static asio::awaitable on_E5_BB(shared_ptr c, Channel::Message& ms try { auto s = c->require_server_state(); c->create_character_file(c->login->account->account_id, c->language(), cmd.preview, s->level_table(c->version())); + if (s->enable_test_mode) { + if (!mark_bb_character_test(c)) { + throw runtime_error("could not mark new character as Test"); + } + c->log.info_f("Marked BB character as Test: {}", c->character_filename()); + } } catch (const exception& e) { send_message_box(c, std::format("$C6New character could not be created:\n{}", e.what())); should_send_approve = false; diff --git a/src/ServerState.cc b/src/ServerState.cc index b100feb3..6c5c1868 100644 --- a/src/ServerState.cc +++ b/src/ServerState.cc @@ -957,6 +957,7 @@ void ServerState::load_config_early() { this->enable_bb_ship_selection_menu = this->config_json->get_bool("EnableBBShipSelectionMenu", false); this->enable_blueballz = this->config_json->get_bool("EnableBlueballz", false); this->enable_hardcore_mode = this->config_json->get_bool("EnableHardcoreMode", false); + this->enable_test_mode = this->config_json->get_bool("EnableTestMode", false); this->blueballz_max_tier = std::min(10, std::max(0, this->config_json->get_int("BlueballzMaxTier", 10))); this->blueballz_unlocked_tier_v2 = std::min( this->blueballz_max_tier, diff --git a/src/ServerState.hh b/src/ServerState.hh index b27f5a1a..5c300b44 100644 --- a/src/ServerState.hh +++ b/src/ServerState.hh @@ -178,6 +178,7 @@ struct ServerState : public std::enable_shared_from_this { bool enable_blueballz = false; int64_t blueballz_enemy_hp_scale_tier = -1; // -1 = disabled; 0..10 = scale BB enemy HP in stream files bool enable_hardcore_mode = false; + bool enable_test_mode = false; int8_t blueballz_max_tier = 10; int8_t blueballz_unlocked_tier_v2 = 0; int8_t blueballz_unlocked_tier_v3 = 0;