diff --git a/README.md b/README.md index 3cdd043f..078e4cb5 100644 --- a/README.md +++ b/README.md @@ -418,7 +418,7 @@ Some commands only work on the game server and not on the proxy server. The chat * 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. * `$inftime`: Toggles infinite-time mode. Must be used before starting a battle. If infinite-time mode is enabled, the overall and per-phase time limits will be disabled regardless of the values chosen during battle setup. After completing a battle, infinite-time mode is reset to the server's default value (which can be set in Episode3BehaviorFlags in config.json). - * `$defrange -`: Sets the DEF dice range for the next battle. If this is used, the dice range set during battle rules setup will apply only to ATK dice; DEF dice will use this range instead. Assist cards and other dice effects will still apply. Dice exchange also still applies if it is enabled. + * `$dicerange [d:L-H] [1:L-H] [a1:L-H] [d1:L-H]`: Sets override dice ranges for the next battle. The min and max dice values from the rules setup menu always apply to the ATK dice, but you can specify a different range for the DEF dice with `d:2-4` (for example). The `1:` override applies to the 1-player team in a 2v1 game (so you would set the 2-player team's desired dice range in the rules menu). You can also specify the 1-player team's ATK and DEF ranges separately with the `a1:` and `d1:` overrides. * `$stat `: Shows a statistic about your player or team in the current battle. `` can be `duration`, `fcs-destroyed`, `cards-destroyed`, `damage-given`, `damage-taken`, `opp-cards-destroyed`, `own-cards-destroyed`, `move-distance`, `cards-set`, `fcs-set`, `attack-actions-set`, `techs-set`, `assists-set`, `defenses-self`, `defenses-ally`, `cards-drawn`, `max-attack-damage`, `max-combo`, `attacks-given`, `attacks-taken`, `sc-damage`, `damage-defended`, or `rank`. * `$surrender`: Causes your team to immediately lose the current battle. * `$saverec `: Saves the recording of the last battle. diff --git a/src/ChatCommands.cc b/src/ChatCommands.cc index 296c6809..a88da976 100644 --- a/src/ChatCommands.cc +++ b/src/ChatCommands.cc @@ -1817,7 +1817,7 @@ static void server_command_ep3_infinite_time(shared_ptr c, const std::st send_text_message(l, infinite_time_enabled ? "$C6Infinite time enabled" : "$C6Infinite time disabled"); } -static void server_command_ep3_set_def_dice_range(shared_ptr c, const std::string& args) { +static void server_command_ep3_set_dice_range(shared_ptr c, const std::string& args) { auto l = c->require_lobby(); check_is_game(l, true); check_is_ep3(c, true); @@ -1835,37 +1835,64 @@ static void server_command_ep3_set_def_dice_range(shared_ptr c, const st return; } if (l->tournament_match) { - send_text_message(c, "$C6Cannot override\nDEF range in a\ntournament"); + send_text_message(c, "$C6Cannot override\ndice ranges in a\ntournament"); return; } - if (args.empty()) { - l->ep3_server->map_and_rules->rules.def_dice_range = 0; - send_text_message_printf(l, "$C6DEF dice range\nset to default"); - } else { - uint8_t min_dice, max_dice; - auto tokens = split(args, '-'); + auto parse_dice_range = +[](const string& spec) -> uint8_t { + auto tokens = split(spec, '-'); if (tokens.size() == 1) { - min_dice = stoul(tokens[0]); - max_dice = min_dice; + uint8_t v = stoull(spec); + return (v << 4) | (v & 0x0F); } else if (tokens.size() == 2) { - min_dice = stoul(tokens[0]); - max_dice = stoul(tokens[1]); + return (stoull(tokens[0]) << 4) | (stoull(tokens[1]) & 0x0F); } else { - send_text_message(c, "$C6Specify DEF dice\nrange as MIN-MAX"); + throw runtime_error("invalid dice spec format"); + } + }; + + uint8_t def_dice_range = 0; + uint8_t atk_dice_range_2v1 = 0; + uint8_t def_dice_range_2v1 = 0; + for (const auto& spec : split(args, ' ')) { + auto tokens = split(spec, ':'); + if (tokens.size() != 2) { + send_text_message(c, "$C6Invalid dice spec\nformat"); return; } - if (min_dice == 0 || min_dice > 9 || max_dice == 0 || max_dice > 9) { - send_text_message(c, "$C6DEF dice must be\nin range 1-9"); - return; + if (tokens[0] == "d") { + def_dice_range = parse_dice_range(tokens[1]); + } else if (tokens[0] == "1") { + atk_dice_range_2v1 = parse_dice_range(tokens[1]); + def_dice_range_2v1 = atk_dice_range_2v1; + } else if (tokens[0] == "a1") { + atk_dice_range_2v1 = parse_dice_range(tokens[1]); + } else if (tokens[0] == "d1") { + def_dice_range_2v1 = parse_dice_range(tokens[1]); } - if (min_dice > max_dice) { - uint8_t t = min_dice; - min_dice = max_dice; - max_dice = t; + } + + auto& rules = l->ep3_server->map_and_rules->rules; + rules.def_dice_range = def_dice_range; + rules.atk_dice_range_2v1 = atk_dice_range_2v1; + rules.def_dice_range_2v1 = def_dice_range_2v1; + + if (!def_dice_range || !atk_dice_range_2v1 || !def_dice_range_2v1) { + send_text_message_printf(l, "$C7Dice ranges reset\nto defaults"); + } else { + send_text_message_printf(l, "$C7Dice ranges changed:"); + if (def_dice_range) { + send_text_message_printf(l, "$C7DEF: $C6%hhu-%hhu", + static_cast(def_dice_range >> 4), static_cast(def_dice_range & 0x0F)); + } + if (atk_dice_range_2v1) { + send_text_message_printf(l, "$C7ATK (1p in 2v1): $C6%hhu-%hhu", + static_cast(atk_dice_range_2v1 >> 4), static_cast(atk_dice_range_2v1 & 0x0F)); + } + if (def_dice_range_2v1) { + send_text_message_printf(l, "$C7DEF (1p in 2v1): $C6%hhu-%hhu", + static_cast(def_dice_range_2v1 >> 4), static_cast(def_dice_range_2v1 & 0x0F)); } - l->ep3_server->map_and_rules->rules.def_dice_range = ((min_dice << 4) & 0xF0) | (max_dice & 0x0F); - send_text_message_printf(l, "$C6DEF dice range\nset to %hhu-%hhu", min_dice, max_dice); } } @@ -2067,7 +2094,7 @@ static const unordered_map chat_commands({ {"$bbchar", {server_command_bbchar, nullptr}}, {"$cheat", {server_command_cheat, nullptr}}, {"$debug", {server_command_debug, nullptr}}, - {"$defrange", {server_command_ep3_set_def_dice_range, nullptr}}, + {"$dicerange", {server_command_ep3_set_dice_range, nullptr}}, {"$dropmode", {server_command_dropmode, nullptr}}, {"$edit", {server_command_edit, nullptr}}, {"$ep3battledebug", {server_command_enable_ep3_battle_debug_menu, nullptr}}, diff --git a/src/Episode3/DataIndexes.cc b/src/Episode3/DataIndexes.cc index 67d2f83d..f6a4f845 100644 --- a/src/Episode3/DataIndexes.cc +++ b/src/Episode3/DataIndexes.cc @@ -1273,9 +1273,15 @@ Rules::Rules(const JSON& json) { this->disable_dialogue = json.get_bool("disable_dialogue", this->disable_dialogue); this->dice_exchange_mode = json.get_enum("dice_exchange_mode", this->dice_exchange_mode); this->disable_dice_boost = json.get_bool("disable_dice_boost", this->disable_dice_boost); - uint8_t min_def_dice = json.get_int("min_def_dice", (this->def_dice_range >> 4) & 0x0F); - uint8_t max_def_dice = json.get_int("max_def_dice", this->def_dice_range & 0x0F); - this->def_dice_range = ((min_def_dice << 4) & 0xF0) | (max_def_dice & 0x0F); + uint8_t min_dice = json.get_int("min_def_dice", (this->def_dice_range >> 4) & 0x0F); + uint8_t max_dice = json.get_int("max_def_dice", this->def_dice_range & 0x0F); + this->def_dice_range = ((min_dice << 4) & 0xF0) | (max_dice & 0x0F); + min_dice = json.get_int("min_atk_dice_2v1", (this->atk_dice_range_2v1 >> 4) & 0x0F); + max_dice = json.get_int("max_atk_dice_2v1", this->atk_dice_range_2v1 & 0x0F); + this->atk_dice_range_2v1 = ((min_dice << 4) & 0xF0) | (max_dice & 0x0F); + min_dice = json.get_int("min_def_dice_2v1", (this->def_dice_range_2v1 >> 4) & 0x0F); + max_dice = json.get_int("max_def_dice_2v1", this->def_dice_range_2v1 & 0x0F); + this->def_dice_range_2v1 = ((min_dice << 4) & 0xF0) | (max_dice & 0x0F); } JSON Rules::json() const { @@ -1295,6 +1301,10 @@ JSON Rules::json() const { {"disable_dice_boost", static_cast(this->disable_dice_boost)}, {"min_def_dice", ((this->def_dice_range >> 4) & 0x0F)}, {"max_def_dice", (this->def_dice_range & 0x0F)}, + {"min_atk_dice_2v1", ((this->atk_dice_range_2v1 >> 4) & 0x0F)}, + {"max_atk_dice_2v1", (this->atk_dice_range_2v1 & 0x0F)}, + {"min_def_dice_2v1", ((this->def_dice_range_2v1 >> 4) & 0x0F)}, + {"max_def_dice_2v1", (this->def_dice_range_2v1 & 0x0F)}, }); } @@ -1380,6 +1390,28 @@ string Rules::str() const { tokens.emplace_back(string_printf("max_def_dice=%hhu", this->max_def_dice())); } + if (this->min_atk_dice_2v1() == 0) { + tokens.emplace_back("min_atk_dice_2v1=(default)"); + } else { + tokens.emplace_back(string_printf("min_atk_dice_2v1=%hhu", this->min_atk_dice_2v1())); + } + if (this->max_atk_dice_2v1() == 0) { + tokens.emplace_back("max_atk_dice_2v1=(default)"); + } else { + tokens.emplace_back(string_printf("max_atk_dice_2v1=%hhu", this->max_atk_dice_2v1())); + } + + if (this->min_def_dice_2v1() == 0) { + tokens.emplace_back("min_def_dice_2v1=(default)"); + } else { + tokens.emplace_back(string_printf("min_def_dice_2v1=%hhu", this->min_def_dice_2v1())); + } + if (this->max_def_dice_2v1() == 0) { + tokens.emplace_back("max_def_dice_2v1=(default)"); + } else { + tokens.emplace_back(string_printf("max_def_dice_2v1=%hhu", this->max_def_dice_2v1())); + } + switch (this->dice_exchange_mode) { case DiceExchangeMode::HIGH_ATK: tokens.emplace_back("dice_exchange=high-atk"); @@ -2061,6 +2093,7 @@ bool Rules::check_and_reset_invalid_fields() { this->max_dice = t; ret = true; } + uint8_t min_def_dice = this->min_def_dice(); uint8_t max_def_dice = this->max_def_dice(); if (min_def_dice > 9) { @@ -2082,6 +2115,51 @@ bool Rules::check_and_reset_invalid_fields() { this->disable_deck_shuffle = 0; ret = true; } + + uint8_t min_atk_dice_2v1 = this->min_atk_dice_2v1(); + uint8_t max_atk_dice_2v1 = this->max_atk_dice_2v1(); + if (min_atk_dice_2v1 > 9) { + min_atk_dice_2v1 = 0; + ret = true; + } + if (max_atk_dice_2v1 > 9) { + max_atk_dice_2v1 = 0; + ret = true; + } + if ((min_atk_dice_2v1 != 0) && (max_atk_dice_2v1 != 0) && (max_atk_dice_2v1 < min_atk_dice_2v1)) { + uint8_t t = min_atk_dice_2v1; + min_atk_dice_2v1 = max_atk_dice_2v1; + max_atk_dice_2v1 = t; + ret = true; + } + this->def_dice_range = ((min_atk_dice_2v1 << 4) & 0xF0) | (max_atk_dice_2v1 & 0x0F); + if (this->disable_deck_shuffle > 1) { + this->disable_deck_shuffle = 0; + ret = true; + } + + uint8_t min_def_dice_2v1 = this->min_def_dice_2v1(); + uint8_t max_def_dice_2v1 = this->max_def_dice_2v1(); + if (min_def_dice_2v1 > 9) { + min_def_dice_2v1 = 0; + ret = true; + } + if (max_def_dice_2v1 > 9) { + max_def_dice_2v1 = 0; + ret = true; + } + if ((min_def_dice_2v1 != 0) && (max_def_dice_2v1 != 0) && (max_def_dice_2v1 < min_def_dice_2v1)) { + uint8_t t = min_def_dice_2v1; + min_def_dice_2v1 = max_def_dice_2v1; + max_def_dice_2v1 = t; + ret = true; + } + this->def_dice_range = ((min_def_dice_2v1 << 4) & 0xF0) | (max_def_dice_2v1 & 0x0F); + if (this->disable_deck_shuffle > 1) { + this->disable_deck_shuffle = 0; + ret = true; + } + if (this->disable_deck_loop > 1) { this->disable_deck_loop = 0; ret = true; @@ -2116,10 +2194,21 @@ bool Rules::check_and_reset_invalid_fields() { uint8_t Rules::min_def_dice() const { return (this->def_dice_range >> 4) & 0x0F; } - uint8_t Rules::max_def_dice() const { return this->def_dice_range & 0x0F; } +uint8_t Rules::min_atk_dice_2v1() const { + return (this->atk_dice_range_2v1 >> 4) & 0x0F; +} +uint8_t Rules::max_atk_dice_2v1() const { + return this->atk_dice_range_2v1 & 0x0F; +} +uint8_t Rules::min_def_dice_2v1() const { + return (this->def_dice_range_2v1 >> 4) & 0x0F; +} +uint8_t Rules::max_def_dice_2v1() const { + return this->def_dice_range_2v1 & 0x0F; +} CardIndex::CardIndex( const string& filename, diff --git a/src/Episode3/DataIndexes.hh b/src/Episode3/DataIndexes.hh index 6c841429..f6c2ceaa 100644 --- a/src/Episode3/DataIndexes.hh +++ b/src/Episode3/DataIndexes.hh @@ -953,7 +953,10 @@ struct Rules { // NOTE: The following fields are unused in PSO's implementation, but newserv // uses them to implement extended rules. /* 0D */ uint8_t def_dice_range = 0; // High 4 bits = min, low 4 = max - /* 0E */ parray unused; + // These fields specify override dice ranges for the 1-player team in 2v1 + /* 0E */ uint8_t atk_dice_range_2v1 = 0; // High 4 bits = min, low 4 = max + /* 0F */ uint8_t def_dice_range_2v1 = 0; // High 4 bits = min, low 4 = max + /* 10 */ parray unused; /* 14 */ // Annoyingly, this structure is a different size in Episode 3 Trial Edition. @@ -975,6 +978,10 @@ struct Rules { uint8_t min_def_dice() const; uint8_t max_def_dice() const; + uint8_t min_atk_dice_2v1() const; + uint8_t max_atk_dice_2v1() const; + uint8_t min_def_dice_2v1() const; + uint8_t max_def_dice_2v1() const; std::string str() const; } __attribute__((packed)); diff --git a/src/Episode3/PlayerState.cc b/src/Episode3/PlayerState.cc index 5af59723..667deb31 100644 --- a/src/Episode3/PlayerState.cc +++ b/src/Episode3/PlayerState.cc @@ -1960,8 +1960,10 @@ void PlayerState::roll_main_dice_or_apply_after_effects() { // the non-NTE logic and assign the dice ranges at battle start time to yield // the NTE behavior. (See RulesTrial in DataIndexes.cc for how this is done.) - uint8_t min_atk_dice = rules.min_dice; - uint8_t max_atk_dice = rules.max_dice; + bool is_1p_2v1 = (s->team_client_count.at(this->get_team_id()) < s->team_client_count[this->get_team_id() ^ 1]); + + uint8_t min_atk_dice = (is_1p_2v1 && rules.min_atk_dice_2v1()) ? rules.min_atk_dice_2v1() : rules.min_dice; + uint8_t max_atk_dice = (is_1p_2v1 && rules.max_atk_dice_2v1()) ? rules.max_atk_dice_2v1() : rules.max_dice; if (min_atk_dice == 0) { min_atk_dice = 1; } @@ -1980,8 +1982,8 @@ void PlayerState::roll_main_dice_or_apply_after_effects() { this->dice_results[0] = min_atk_dice + s->get_random(atk_dice_range_width); } - uint8_t min_def_dice = rules.min_def_dice() ? rules.min_def_dice() : rules.min_dice; - uint8_t max_def_dice = rules.max_def_dice() ? rules.max_def_dice() : rules.max_dice; + uint8_t min_def_dice = (is_1p_2v1 && rules.min_def_dice_2v1()) ? rules.min_def_dice_2v1() : (rules.min_def_dice() ? rules.min_def_dice() : rules.min_dice); + uint8_t max_def_dice = (is_1p_2v1 && rules.max_def_dice_2v1()) ? rules.max_def_dice_2v1() : (rules.max_def_dice() ? rules.max_def_dice() : rules.max_dice); if (min_def_dice == 0) { min_def_dice = 1; } diff --git a/src/ReceiveSubcommands.cc b/src/ReceiveSubcommands.cc index da8e7278..fb4818fc 100644 --- a/src/ReceiveSubcommands.cc +++ b/src/ReceiveSubcommands.cc @@ -185,14 +185,19 @@ static void forward_subcommand(shared_ptr c, uint8_t command, uint8_t fl if (!data_to_send || !size_to_send) { lc->log.info("Command cannot be translated to client\'s version"); - } else if ((def_flags & SDF::USE_JOIN_COMMAND_QUEUE) && lc->game_join_command_queue) { - lc->log.info("Client not ready to receive join commands; adding to queue"); - auto& cmd = lc->game_join_command_queue->emplace_back(); - cmd.command = command; - cmd.flag = flag; - cmd.data.assign(reinterpret_cast(data_to_send), size_to_send); } else { - send_command(lc, command, flag, data_to_send, size_to_send); + if ((command == 0xCB) && (lc->version() == Version::GC_EP3_NTE)) { + command = 0xC9; + } + if ((def_flags & SDF::USE_JOIN_COMMAND_QUEUE) && lc->game_join_command_queue) { + lc->log.info("Client not ready to receive join commands; adding to queue"); + auto& cmd = lc->game_join_command_queue->emplace_back(); + cmd.command = command; + cmd.flag = flag; + cmd.data.assign(reinterpret_cast(data_to_send), size_to_send); + } else { + send_command(lc, command, flag, data_to_send, size_to_send); + } } };