implement server drop tables
This commit is contained in:
@@ -296,6 +296,7 @@ Some commands only work on the game server and not on the proxy server. The chat
|
||||
* `$maxlevel <level>`: Sets the maximum level for players to join the current game. (This only applies when joining; if a player joins and then levels up past this level during the game, they are not kicked out, but won't be able to rejoin if they leave.)
|
||||
* `$minlevel <level>`: Sets the minimum level for players to join the current game.
|
||||
* `$password <password>`: Sets the game's join password. To unlock the game, run `$password` with nothing after it.
|
||||
* `$raretable`: Switches between using the client's or the server's drop table. No effect on BB (the server's drop table is always used). The server's rare tables are defined in JSON files in the system/rare-tables directory.
|
||||
|
||||
* Episode 3 commands (game server only)
|
||||
* `$spec`: Toggles the allow spectators flag for Episode 3 games. If any players are spectating when this flag is disabled, they will be sent back to the lobby.
|
||||
|
||||
@@ -1236,6 +1236,21 @@ static void server_command_drop(shared_ptr<Client> c, const std::u16string&) {
|
||||
send_text_message_printf(l, "Drops %s", (l->flags & Lobby::Flag::DROPS_ENABLED) ? "enabled" : "disabled");
|
||||
}
|
||||
|
||||
static void server_command_raretable(shared_ptr<Client> c, const std::u16string&) {
|
||||
auto l = c->require_lobby();
|
||||
check_is_game(l, true);
|
||||
check_is_leader(l, c);
|
||||
if (l->base_version == GameVersion::BB) {
|
||||
send_text_message_printf(c, "Cannot use client\nrare table on BB");
|
||||
} else if (l->item_creator) {
|
||||
l->item_creator.reset();
|
||||
send_text_message_printf(l, "Game switched to\nclient rare tables");
|
||||
} else {
|
||||
l->create_item_creator();
|
||||
send_text_message_printf(l, "Game switched to\nserver rare tables");
|
||||
}
|
||||
}
|
||||
|
||||
static void server_command_item(shared_ptr<Client> c, const std::u16string& args) {
|
||||
auto s = c->require_server_state();
|
||||
auto l = c->require_lobby();
|
||||
@@ -1566,6 +1581,7 @@ static const unordered_map<u16string, ChatCommandDefinition> chat_commands({
|
||||
{u"$persist", {server_command_persist, nullptr}},
|
||||
{u"$playrec", {server_command_playrec, nullptr}},
|
||||
{u"$rand", {server_command_rand, proxy_command_rand}},
|
||||
{u"$raretable", {server_command_raretable, nullptr}},
|
||||
{u"$saverec", {server_command_saverec, nullptr}},
|
||||
{u"$sc", {server_command_send_client, proxy_command_send_client}},
|
||||
{u"$secid", {server_command_secid, proxy_command_secid}},
|
||||
|
||||
@@ -87,6 +87,7 @@ Client::Client(
|
||||
card_battle_table_seat_state(0),
|
||||
next_exp_value(0),
|
||||
can_chat(true),
|
||||
use_server_rare_tables(false),
|
||||
pending_bb_save_player_index(0),
|
||||
dol_base_addr(0) {
|
||||
this->last_switch_enabled_command.header.subcommand = 0;
|
||||
|
||||
@@ -172,6 +172,7 @@ struct Client : public std::enable_shared_from_this<Client> {
|
||||
uint32_t next_exp_value; // next EXP value to give
|
||||
G_SwitchStateChanged_6x05 last_switch_enabled_command;
|
||||
bool can_chat;
|
||||
bool use_server_rare_tables;
|
||||
std::string pending_bb_save_username;
|
||||
uint8_t pending_bb_save_player_index;
|
||||
std::deque<std::function<void(uint32_t, uint32_t)>> function_call_response_queue;
|
||||
|
||||
@@ -43,6 +43,33 @@ shared_ptr<ServerState> Lobby::require_server_state() const {
|
||||
return s;
|
||||
}
|
||||
|
||||
void Lobby::create_item_creator() {
|
||||
auto s = this->require_server_state();
|
||||
|
||||
shared_ptr<const RareItemSet> rare_item_set;
|
||||
if (this->base_version == GameVersion::BB) {
|
||||
rare_item_set = s->rare_item_sets.at("default-v4");
|
||||
} else if (this->base_version == GameVersion::GC || this->base_version == GameVersion::XB) {
|
||||
rare_item_set = s->rare_item_sets.at("default-v3");
|
||||
} else {
|
||||
// TODO: SHould there be a separate table for V1 eventually?
|
||||
rare_item_set = s->rare_item_sets.at("default-v2");
|
||||
}
|
||||
this->item_creator.reset(new ItemCreator(
|
||||
s->common_item_set,
|
||||
rare_item_set,
|
||||
s->armor_random_set,
|
||||
s->tool_random_set,
|
||||
s->weapon_random_sets.at(this->difficulty),
|
||||
s->tekker_adjustment_set,
|
||||
s->item_parameter_table,
|
||||
this->episode,
|
||||
(this->mode == GameMode::SOLO) ? GameMode::NORMAL : this->mode,
|
||||
this->difficulty,
|
||||
this->section_id,
|
||||
this->random_seed));
|
||||
}
|
||||
|
||||
void Lobby::create_ep3_server() {
|
||||
auto s = this->require_server_state();
|
||||
if (!this->ep3_server) {
|
||||
|
||||
+2
-1
@@ -18,7 +18,6 @@
|
||||
#include "Map.hh"
|
||||
#include "Player.hh"
|
||||
#include "Quest.hh"
|
||||
#include "RareItemSet.hh"
|
||||
#include "StaticGameData.hh"
|
||||
#include "Text.hh"
|
||||
|
||||
@@ -40,6 +39,7 @@ struct Lobby : public std::enable_shared_from_this<Lobby> {
|
||||
START_BATTLE_PLAYER_IMMEDIATELY = 0x00008000,
|
||||
DROPS_ENABLED = 0x00010000, // Does not affect BB
|
||||
IS_EP3_TRIAL = 0x00020000,
|
||||
USE_SERVER_RARE_TABLE = 0x00040000, // Does not affect BB
|
||||
|
||||
// Flags used only for lobbies
|
||||
PUBLIC = 0x01000000,
|
||||
@@ -124,6 +124,7 @@ struct Lobby : public std::enable_shared_from_this<Lobby> {
|
||||
Lobby& operator=(Lobby&&) = delete;
|
||||
|
||||
std::shared_ptr<ServerState> require_server_state() const;
|
||||
void create_item_creator();
|
||||
void create_ep3_server();
|
||||
|
||||
inline bool is_game() const {
|
||||
|
||||
+3
-15
@@ -3349,21 +3349,9 @@ shared_ptr<Lobby> create_game_generic(
|
||||
game->battle_player = battle_player;
|
||||
battle_player->set_lobby(game);
|
||||
}
|
||||
if (game->base_version == GameVersion::BB) {
|
||||
if ((game->base_version == GameVersion::BB) || (c->use_server_rare_tables)) {
|
||||
// TODO: Use appropriate restrictions here if in battle mode
|
||||
game->item_creator.reset(new ItemCreator(
|
||||
s->common_item_set,
|
||||
s->rare_item_set,
|
||||
s->armor_random_set,
|
||||
s->tool_random_set,
|
||||
s->weapon_random_sets.at(game->difficulty),
|
||||
s->tekker_adjustment_set,
|
||||
s->item_parameter_table,
|
||||
game->episode,
|
||||
(game->mode == GameMode::SOLO) ? GameMode::NORMAL : game->mode,
|
||||
game->difficulty,
|
||||
game->section_id,
|
||||
game->random_seed));
|
||||
game->create_item_creator();
|
||||
}
|
||||
game->event = Lobby::game_event_for_lobby_event(current_lobby->event);
|
||||
game->block = 0xFF;
|
||||
@@ -3461,7 +3449,7 @@ static void on_0C_C1_E7_EC(shared_ptr<Client> c, uint16_t command, uint32_t, con
|
||||
const auto& cmd = check_size_t<C_CreateGame_DCNTE<char>>(data);
|
||||
u16string name = decode_sjis(cmd.name);
|
||||
u16string password = decode_sjis(cmd.password);
|
||||
game = create_game_generic(s, c, name, password);
|
||||
game = create_game_generic(s, c, name, password, Episode::EP1, GameMode::NORMAL, 0, 0, true);
|
||||
|
||||
} else {
|
||||
const auto& cmd = check_size_t<C_CreateGame_DC_V3_0C_C1_Ep3_EC>(data);
|
||||
|
||||
+11
-16
@@ -1185,9 +1185,9 @@ static void on_entity_drop_item_request(shared_ptr<Client> c, uint8_t command, u
|
||||
return;
|
||||
}
|
||||
|
||||
// If the game is not BB, forward the request to the leader (if drops are
|
||||
// enabled, or just ignore it) instead of generating the item drop command
|
||||
if (l->base_version != GameVersion::BB) {
|
||||
// If there is no item creator (that is, the game is BB or has server rare
|
||||
// tables disabled), then forward the request to the leader
|
||||
if (!l->item_creator) {
|
||||
if (l->flags & Lobby::Flag::DROPS_ENABLED) {
|
||||
forward_subcommand(c, command, flag, data, size);
|
||||
}
|
||||
@@ -1208,10 +1208,6 @@ static void on_entity_drop_item_request(shared_ptr<Client> c, uint8_t command, u
|
||||
cmd.ignore_def = true;
|
||||
}
|
||||
|
||||
if (!l->item_creator.get()) {
|
||||
throw runtime_error("received box drop subcommand without item creator present");
|
||||
}
|
||||
|
||||
ItemData item;
|
||||
if (cmd.rt_index == 0x30) {
|
||||
if (cmd.ignore_def) {
|
||||
@@ -1220,16 +1216,15 @@ static void on_entity_drop_item_request(shared_ptr<Client> c, uint8_t command, u
|
||||
item = l->item_creator->on_specialized_box_item_drop(cmd.def[0], cmd.def[1], cmd.def[2]);
|
||||
}
|
||||
} else {
|
||||
if (!l->map) {
|
||||
throw runtime_error("game does not have a map loaded");
|
||||
if (l->map) {
|
||||
const auto& enemy = l->map->enemies.at(cmd.entity_id);
|
||||
uint32_t expected_rt_index = rare_table_index_for_enemy_type(enemy.type);
|
||||
if (cmd.rt_index != expected_rt_index) {
|
||||
c->log.warning("rt_index %02hhX from command does not match entity\'s expected index %02" PRIX32,
|
||||
cmd.rt_index, expected_rt_index);
|
||||
}
|
||||
}
|
||||
const auto& enemy = l->map->enemies.at(cmd.entity_id);
|
||||
uint32_t expected_rt_index = rare_table_index_for_enemy_type(enemy.type);
|
||||
if (cmd.rt_index != expected_rt_index) {
|
||||
c->log.warning("rt_index %02hhX from command does not match entity\'s expected index %02" PRIX32,
|
||||
cmd.rt_index, expected_rt_index);
|
||||
}
|
||||
item = l->item_creator->on_monster_item_drop(expected_rt_index, cmd.area);
|
||||
item = l->item_creator->on_monster_item_drop(cmd.rt_index, cmd.area);
|
||||
}
|
||||
item.id = l->generate_item_id(0xFF);
|
||||
|
||||
|
||||
+35
-9
@@ -861,15 +861,41 @@ void ServerState::load_level_table() {
|
||||
}
|
||||
|
||||
void ServerState::load_item_tables() {
|
||||
try {
|
||||
config_log.info("Loading JSON rare item table");
|
||||
auto json = JSON::parse(load_file("system/blueburst/rare-table.json"));
|
||||
this->rare_item_set.reset(new JSONRareItemSet(json));
|
||||
} catch (const exception& e) {
|
||||
config_log.info("Failed to load JSON rare item table: %s", e.what());
|
||||
config_log.info("Loading REL rare item table");
|
||||
this->rare_item_set.reset(new RELRareItemSet(
|
||||
this->load_bb_file("ItemRT.rel")));
|
||||
config_log.info("Loading rare item sets");
|
||||
for (const auto& filename : list_directory_sorted("system/rare-tables")) {
|
||||
string path = "system/rare-tables/" + filename;
|
||||
size_t ext_offset = filename.rfind('.');
|
||||
string basename = (ext_offset == string::npos) ? filename : filename.substr(0, ext_offset);
|
||||
|
||||
if (ends_with(filename, ".json")) {
|
||||
config_log.info("Loading JSON rare item table %s", filename.c_str());
|
||||
this->rare_item_sets.emplace(basename, new JSONRareItemSet(JSON::parse(load_file(path))));
|
||||
|
||||
} else if (ends_with(filename, ".afs")) {
|
||||
config_log.info("Loading AFS rare item table %s", filename.c_str());
|
||||
shared_ptr<string> data(new string(load_file(path)));
|
||||
this->rare_item_sets.emplace(basename, new AFSRareItemSet(data));
|
||||
|
||||
} else if (ends_with(filename, ".gsl")) {
|
||||
config_log.info("Loading GSL rare item table %s", filename.c_str());
|
||||
shared_ptr<string> data(new string(load_file(path)));
|
||||
this->rare_item_sets.emplace(basename, new GSLRareItemSet(data, false));
|
||||
|
||||
} else if (ends_with(filename, ".gslb")) {
|
||||
config_log.info("Loading GSL rare item table %s", filename.c_str());
|
||||
shared_ptr<string> data(new string(load_file(path)));
|
||||
this->rare_item_sets.emplace(basename, new GSLRareItemSet(data, true));
|
||||
|
||||
} else if (ends_with(filename, ".reg")) {
|
||||
config_log.info("Loading REL rare item table %s", filename.c_str());
|
||||
shared_ptr<string> data(new string(load_file(path)));
|
||||
this->rare_item_sets.emplace(basename, new RELRareItemSet(data));
|
||||
}
|
||||
}
|
||||
|
||||
if (!this->rare_item_sets.count("default-v4")) {
|
||||
config_log.info("default-v4 rare item set is not available; loading from BB data");
|
||||
this->rare_item_sets.emplace("default-v4", new RELRareItemSet(this->load_bb_file("ItemRT.rel")));
|
||||
}
|
||||
|
||||
// Note: These files don't exist in BB, so we use the GC versions of them
|
||||
|
||||
+1
-1
@@ -89,7 +89,7 @@ struct ServerState : public std::enable_shared_from_this<ServerState> {
|
||||
std::shared_ptr<const LevelTable> level_table;
|
||||
std::shared_ptr<const BattleParamsIndex> battle_params;
|
||||
std::shared_ptr<const GSLArchive> bb_data_gsl;
|
||||
std::shared_ptr<const RareItemSet> rare_item_set;
|
||||
std::unordered_map<std::string, std::shared_ptr<const RareItemSet>> rare_item_sets;
|
||||
std::shared_ptr<const CommonItemSet> common_item_set;
|
||||
std::shared_ptr<const ArmorRandomSet> armor_random_set;
|
||||
std::shared_ptr<const ToolRandomSet> tool_random_set;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user