replace $defrange with $dicerange

This commit is contained in:
Martin Michelsen
2024-02-10 14:29:37 -08:00
parent a312191ced
commit 093ba1fd38
6 changed files with 170 additions and 40 deletions
+1 -1
View File
@@ -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 <min>-<max>`: 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 <what>`: Shows a statistic about your player or team in the current battle. `<what>` 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 <name>`: Saves the recording of the last battle.
+50 -23
View File
@@ -1817,7 +1817,7 @@ static void server_command_ep3_infinite_time(shared_ptr<Client> 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<Client> c, const std::string& args) {
static void server_command_ep3_set_dice_range(shared_ptr<Client> 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<Client> 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<uint8_t>(def_dice_range >> 4), static_cast<uint8_t>(def_dice_range & 0x0F));
}
if (atk_dice_range_2v1) {
send_text_message_printf(l, "$C7ATK (1p in 2v1): $C6%hhu-%hhu",
static_cast<uint8_t>(atk_dice_range_2v1 >> 4), static_cast<uint8_t>(atk_dice_range_2v1 & 0x0F));
}
if (def_dice_range_2v1) {
send_text_message_printf(l, "$C7DEF (1p in 2v1): $C6%hhu-%hhu",
static_cast<uint8_t>(def_dice_range_2v1 >> 4), static_cast<uint8_t>(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<string, ChatCommandDefinition> 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}},
+93 -4
View File
@@ -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<bool>(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,
+8 -1
View File
@@ -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<uint8_t, 6> 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<uint8_t, 4> 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));
+6 -4
View File
@@ -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;
}
+12 -7
View File
@@ -185,14 +185,19 @@ static void forward_subcommand(shared_ptr<Client> 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<const char*>(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<const char*>(data_to_send), size_to_send);
} else {
send_command(lc, command, flag, data_to_send, size_to_send);
}
}
};