add ability to specify separate DEF dice range

This commit is contained in:
Martin Michelsen
2023-09-22 21:59:33 -07:00
parent be0e616df7
commit 6e522459ae
7 changed files with 179 additions and 51 deletions
+1
View File
@@ -287,6 +287,7 @@ Some commands only work on the game server and not on the proxy server. The chat
* Episode 3 commands (game server only) * 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. * `$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). * `$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 the ATK dice; the 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.
* `$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`. * `$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. * `$surrender`: Causes your team to immediately lose the current battle.
* `$saverec <name>`: Saves the recording of the last battle. * `$saverec <name>`: Saves the recording of the last battle.
+49
View File
@@ -1280,6 +1280,54 @@ static void server_command_ep3_infinite_time(shared_ptr<Client> c, const std::u1
send_text_message(l, infinite_time_enabled ? u"$C6Infinite time enabled" : u"$C6Infinite time disabled"); send_text_message(l, infinite_time_enabled ? u"$C6Infinite time enabled" : u"$C6Infinite time disabled");
} }
static void server_command_ep3_set_def_dice_range(shared_ptr<Client> c, const std::u16string& args) {
auto l = c->require_lobby();
check_is_game(l, true);
check_is_ep3(c, true);
check_is_leader(l, c);
if (l->episode != Episode::EP3) {
throw logic_error("non-Ep3 client in Ep3 game");
}
if (!l->ep3_server) {
send_text_message(c, u"$C6Episode 3 server\nis not initialized");
return;
}
if (l->ep3_server->setup_phase != Episode3::SetupPhase::REGISTRATION) {
send_text_message(c, u"$C6Battle is already\nin progress");
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(encode_sjis(args), '-');
if (tokens.size() == 1) {
min_dice = stoul(tokens[0]);
max_dice = min_dice;
} else if (tokens.size() == 2) {
min_dice = stoul(tokens[0]);
max_dice = stoul(tokens[1]);
} else {
send_text_message(c, u"$C6Specify DEF dice\nrange as MIN-MAX");
return;
}
if (min_dice == 0 || min_dice > 9 || max_dice == 0 || max_dice > 9) {
send_text_message(c, u"$C6DEF dice must be\nin range 1-9");
return;
}
if (min_dice > max_dice) {
uint8_t t = min_dice;
min_dice = max_dice;
max_dice = t;
}
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);
}
}
static void server_command_ep3_unset_field_character(shared_ptr<Client> c, const std::u16string& args) { static void server_command_ep3_unset_field_character(shared_ptr<Client> c, const std::u16string& args) {
auto s = c->require_server_state(); auto s = c->require_server_state();
auto l = c->require_lobby(); auto l = c->require_lobby();
@@ -1429,6 +1477,7 @@ static const unordered_map<u16string, ChatCommandDefinition> chat_commands({
{u"$bbchar", {server_command_convert_char_to_bb, nullptr}}, {u"$bbchar", {server_command_convert_char_to_bb, nullptr}},
{u"$cheat", {server_command_cheat, nullptr}}, {u"$cheat", {server_command_cheat, nullptr}},
{u"$debug", {server_command_debug, nullptr}}, {u"$debug", {server_command_debug, nullptr}},
{u"$defrange", {server_command_ep3_set_def_dice_range, nullptr}},
{u"$drop", {server_command_drop, nullptr}}, {u"$drop", {server_command_drop, nullptr}},
{u"$edit", {server_command_edit, nullptr}}, {u"$edit", {server_command_edit, nullptr}},
{u"$event", {server_command_lobby_event, proxy_command_lobby_event}}, {u"$event", {server_command_lobby_event, proxy_command_lobby_event}},
+44 -5
View File
@@ -1172,10 +1172,6 @@ void PlayerConfig::encrypt(uint8_t basis) {
this->basis = basis; this->basis = basis;
} }
Rules::Rules() {
this->clear();
}
Rules::Rules(const JSON& json) { Rules::Rules(const JSON& json) {
this->clear(); this->clear();
this->overall_time_limit = json.get_int("overall_time_limit", this->overall_time_limit); this->overall_time_limit = json.get_int("overall_time_limit", this->overall_time_limit);
@@ -1191,6 +1187,9 @@ Rules::Rules(const JSON& json) {
this->disable_dialogue = json.get_bool("disable_dialogue", this->disable_dialogue); 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->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); 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);
} }
JSON Rules::json() const { JSON Rules::json() const {
@@ -1208,6 +1207,8 @@ JSON Rules::json() const {
{"disable_dialogue", static_cast<bool>(this->disable_dialogue)}, {"disable_dialogue", static_cast<bool>(this->disable_dialogue)},
{"dice_exchange_mode", name_for_enum(this->dice_exchange_mode)}, {"dice_exchange_mode", name_for_enum(this->dice_exchange_mode)},
{"disable_dice_boost", static_cast<bool>(this->disable_dice_boost)}, {"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)},
}); });
} }
@@ -1234,7 +1235,9 @@ void Rules::clear() {
this->disable_dialogue = 0; this->disable_dialogue = 0;
this->dice_exchange_mode = DiceExchangeMode::HIGH_ATK; this->dice_exchange_mode = DiceExchangeMode::HIGH_ATK;
this->disable_dice_boost = 0; this->disable_dice_boost = 0;
this->unused.clear(0); this->def_dice_range = 0;
this->unused1 = 0;
this->unused2 = 0;
} }
string Rules::str() const { string Rules::str() const {
@@ -1281,6 +1284,17 @@ string Rules::str() const {
tokens.emplace_back(string_printf("max_dice=%hhu", this->max_dice)); tokens.emplace_back(string_printf("max_dice=%hhu", this->max_dice));
} }
if (this->min_def_dice() == 0) {
tokens.emplace_back("min_def_dice=(default)");
} else {
tokens.emplace_back(string_printf("min_def_dice=%hhu", this->min_def_dice()));
}
if (this->max_def_dice() == 0) {
tokens.emplace_back("max_def_dice=(default)");
} else {
tokens.emplace_back(string_printf("max_def_dice=%hhu", this->max_def_dice()));
}
switch (this->dice_exchange_mode) { switch (this->dice_exchange_mode) {
case DiceExchangeMode::HIGH_ATK: case DiceExchangeMode::HIGH_ATK:
tokens.emplace_back("dice_exchange=high-atk"); tokens.emplace_back("dice_exchange=high-atk");
@@ -1743,6 +1757,23 @@ bool Rules::check_and_reset_invalid_fields() {
this->max_dice = t; this->max_dice = t;
ret = true; 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) {
min_def_dice = 0;
ret = true;
}
if (max_def_dice > 9) {
max_def_dice = 0;
ret = true;
}
if ((min_def_dice != 0) && (max_def_dice != 0) && (max_def_dice < min_def_dice)) {
uint8_t t = min_def_dice;
min_def_dice = max_def_dice;
max_def_dice = t;
ret = true;
}
this->def_dice_range = ((min_def_dice << 4) & 0xF0) | (max_def_dice & 0x0F);
if (this->disable_deck_shuffle > 1) { if (this->disable_deck_shuffle > 1) {
this->disable_deck_shuffle = 0; this->disable_deck_shuffle = 0;
ret = true; ret = true;
@@ -1778,6 +1809,14 @@ bool Rules::check_and_reset_invalid_fields() {
return ret; return ret;
} }
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;
}
CardIndex::CardIndex(const string& filename, const string& decompressed_filename, const string& text_filename) { CardIndex::CardIndex(const string& filename, const string& decompressed_filename, const string& text_filename) {
unordered_map<uint32_t, vector<string>> card_tags; unordered_map<uint32_t, vector<string>> card_tags;
unordered_map<uint32_t, string> card_text; unordered_map<uint32_t, string> card_text;
+22 -15
View File
@@ -864,20 +864,24 @@ struct Rules {
// When this structure is used in a map/quest definition, FF in any of these // When this structure is used in a map/quest definition, FF in any of these
// fields means the user is allowed to override it. Any non-FF fields are // fields means the user is allowed to override it. Any non-FF fields are
// fixed for the map/quest and cannot be overridden. // fixed for the map/quest and cannot be overridden.
/* 00 */ uint8_t overall_time_limit; // In increments of 5 mins; 0 = unlimited /* 00 */ uint8_t overall_time_limit = 0; // In increments of 5 mins; 0 = unlimited
/* 01 */ uint8_t phase_time_limit; // In seconds; 0 = unlimited /* 01 */ uint8_t phase_time_limit = 0; // In seconds; 0 = unlimited
/* 02 */ AllowedCards allowed_cards; /* 02 */ AllowedCards allowed_cards = AllowedCards::ALL;
/* 03 */ uint8_t min_dice; // 0 = default (1) /* 03 */ uint8_t min_dice = 1; // 0 = default (1)
/* 04 */ uint8_t max_dice; // 0 = default (6) /* 04 */ uint8_t max_dice = 6; // 0 = default (6)
/* 05 */ uint8_t disable_deck_shuffle; // 0 = shuffle on, 1 = off /* 05 */ uint8_t disable_deck_shuffle = 0; // 0 = shuffle on, 1 = off
/* 06 */ uint8_t disable_deck_loop; // 0 = loop on, 1 = off /* 06 */ uint8_t disable_deck_loop = 0; // 0 = loop on, 1 = off
/* 07 */ uint8_t char_hp; /* 07 */ uint8_t char_hp = 15;
/* 08 */ HPType hp_type; /* 08 */ HPType hp_type = HPType::DEFEAT_PLAYER;
/* 09 */ uint8_t no_assist_cards; // 1 = assist cards disallowed /* 09 */ uint8_t no_assist_cards = 0; // 1 = assist cards disallowed
/* 0A */ uint8_t disable_dialogue; // 0 = dialogue on, 1 = dialogue off /* 0A */ uint8_t disable_dialogue = 0; // 0 = dialogue on, 1 = dialogue off
/* 0B */ DiceExchangeMode dice_exchange_mode; /* 0B */ DiceExchangeMode dice_exchange_mode = DiceExchangeMode::HIGH_ATK;
/* 0C */ uint8_t disable_dice_boost; // 0 = dice boost on, 1 = off /* 0C */ uint8_t disable_dice_boost = 0; // 0 = dice boost on, 1 = off
/* 0D */ parray<uint8_t, 3> unused; // 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 */ uint8_t unused1 = 0;
/* 0F */ uint8_t unused2 = 0;
/* 10 */ /* 10 */
// Annoyingly, this structure is a different size in Episode 3 Trial Edition. // Annoyingly, this structure is a different size in Episode 3 Trial Edition.
@@ -886,7 +890,7 @@ struct Rules {
// clients. It'd be nice to support Trial Edition battles, but that would // clients. It'd be nice to support Trial Edition battles, but that would
// likely be more work than it's worth. // likely be more work than it's worth.
Rules(); Rules() = default;
explicit Rules(const JSON& json); explicit Rules(const JSON& json);
JSON json() const; JSON json() const;
bool operator==(const Rules& other) const; bool operator==(const Rules& other) const;
@@ -897,6 +901,9 @@ struct Rules {
bool check_invalid_fields() const; bool check_invalid_fields() const;
bool check_and_reset_invalid_fields(); bool check_and_reset_invalid_fields();
uint8_t min_def_dice() const;
uint8_t max_def_dice() const;
std::string str() const; std::string str() const;
} __attribute__((packed)); } __attribute__((packed));
+38 -26
View File
@@ -1826,32 +1826,44 @@ void PlayerState::roll_main_dice() {
auto s = this->server(); auto s = this->server();
const auto& rules = s->map_and_rules->rules; const auto& rules = s->map_and_rules->rules;
uint8_t min_dice = rules.min_dice; uint8_t min_atk_dice = rules.min_dice;
uint8_t max_dice = rules.max_dice; uint8_t max_atk_dice = rules.max_dice;
if (min_dice == 0) { if (min_atk_dice == 0) {
min_dice = 1; min_atk_dice = 1;
} }
if (max_dice == 0) { if (max_atk_dice == 0) {
max_dice = 6; max_atk_dice = 6;
} }
if (max_atk_dice < min_atk_dice) {
if (max_dice < min_dice) { uint8_t t = max_atk_dice;
uint8_t t = max_dice; max_atk_dice = min_atk_dice;
max_dice = min_dice; min_atk_dice = t;
min_dice = t;
} }
uint8_t atk_dice_range_width = (max_atk_dice - min_atk_dice) + 1;
uint8_t dice_range_width = (max_dice - min_dice) + 1; if (atk_dice_range_width < 2) {
if (dice_range_width < 2) { this->dice_results[0] = min_atk_dice;
this->dice_results[0] = min_dice;
this->dice_results[1] = min_dice;
this->atk_points = min_dice;
this->def_points = min_dice;
} else { } else {
this->dice_results[0] = min_dice + s->get_random(dice_range_width); this->dice_results[0] = min_atk_dice + s->get_random(atk_dice_range_width);
this->dice_results[1] = min_dice + s->get_random(dice_range_width); }
this->atk_points = this->dice_results[0];
this->def_points = this->dice_results[1]; 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;
if (min_def_dice == 0) {
min_def_dice = 1;
}
if (max_def_dice == 0) {
max_def_dice = 6;
}
if (max_def_dice < min_def_dice) {
uint8_t t = max_def_dice;
max_def_dice = min_def_dice;
min_def_dice = t;
}
uint8_t def_dice_range_width = (max_def_dice - min_def_dice) + 1;
if (def_dice_range_width < 2) {
this->dice_results[1] = min_def_dice;
} else {
this->dice_results[1] = min_def_dice + s->get_random(def_dice_range_width);
} }
bool should_exchange = false; bool should_exchange = false;
@@ -1862,12 +1874,12 @@ void PlayerState::roll_main_dice() {
} }
if (!should_exchange) { if (!should_exchange) {
this->atk_points = (short)(char)this->dice_results[0]; this->atk_points = this->dice_results[0];
this->def_points = (short)(char)this->dice_results[1]; this->def_points = this->dice_results[1];
this->assist_flags &= (~AssistFlag::DICE_WERE_EXCHANGED); this->assist_flags &= (~AssistFlag::DICE_WERE_EXCHANGED);
} else { } else {
this->atk_points = (short)(char)this->dice_results[1]; this->atk_points = this->dice_results[1];
this->def_points = (short)(char)this->dice_results[0]; this->def_points = this->dice_results[0];
this->assist_flags |= AssistFlag::DICE_WERE_EXCHANGED; this->assist_flags |= AssistFlag::DICE_WERE_EXCHANGED;
} }
+6
View File
@@ -1932,7 +1932,13 @@ void Server::handle_CAx13_update_map_during_setup(const string& data) {
(this->map_and_rules->num_players == 0) && (this->map_and_rules->num_players == 0) &&
(this->registration_phase != RegistrationPhase::REGISTERED) && (this->registration_phase != RegistrationPhase::REGISTERED) &&
(this->registration_phase != RegistrationPhase::BATTLE_STARTED)) { (this->registration_phase != RegistrationPhase::BATTLE_STARTED)) {
// newserv's extended rules are stored in unused parts of the Rules struct,
// and clients will probably overwrite them with zeroes if we allow them to.
// So, we preserve the extended rules manually here.
uint8_t def_dice_range = this->map_and_rules->rules.def_dice_range;
*this->map_and_rules = in_cmd.map_and_rules_state; *this->map_and_rules = in_cmd.map_and_rules_state;
this->map_and_rules->rules.def_dice_range = def_dice_range;
if (this->override_environment_number != 0xFF) { if (this->override_environment_number != 0xFF) {
this->map_and_rules->environment_number = this->override_environment_number; this->map_and_rules->environment_number = this->override_environment_number;
this->override_environment_number = 0xFF; this->override_environment_number = 0xFF;
+19 -5
View File
@@ -166,6 +166,8 @@ Server commands:\n\
resize: If the tournament is less than half full when it starts, reduce\n\ resize: If the tournament is less than half full when it starts, reduce\n\
the number of rounds to fit the existing entries\n\ the number of rounds to fit the existing entries\n\
dice=MIN-MAX: Set minimum and maximum dice rolls\n\ dice=MIN-MAX: Set minimum and maximum dice rolls\n\
dice=MIN-MAX:MIN-MAX: Set minimum and maximum dice rolls for ATK and DEF\n\
dice separately\n\
overall-time-limit=N: Set battle time limit (in multiples of 5 minutes)\n\ overall-time-limit=N: Set battle time limit (in multiples of 5 minutes)\n\
phase-time-limit=N: Set phase time limit (in seconds)\n\ phase-time-limit=N: Set phase time limit (in seconds)\n\
allowed-cards=ALL/N/NR/NRS: Set rarities of allowed cards\n\ allowed-cards=ALL/N/NR/NRS: Set rarities of allowed cards\n\
@@ -479,12 +481,24 @@ Proxy session commands:\n\
} else if (token == "resize") { } else if (token == "resize") {
flags |= Episode3::Tournament::Flag::RESIZE_ON_START; flags |= Episode3::Tournament::Flag::RESIZE_ON_START;
} else if (starts_with(token, "dice=")) { } else if (starts_with(token, "dice=")) {
auto subtokens = split(token.substr(5), '-'); auto subtokens = split(token.substr(5), ':');
if (subtokens.size() != 2) { if (subtokens.size() == 1) {
throw runtime_error("dice option must be of the form dice=X-Y"); rules.def_dice_range = 0x00;
} else if (subtokens.size() == 2) {
auto subsubtokens = split(subtokens[1], '-');
if (subsubtokens.size() != 2) {
throw runtime_error("dice option must be of the form dice=A-B or dice=A-B:C-D");
}
rules.def_dice_range = ((stoul(subsubtokens[0]) << 4) & 0xF0) | (stoul(subsubtokens[1]) & 0x0F);
} else {
throw runtime_error("dice option must be of the form dice=A-B or dice=A-B:C-D");
} }
rules.min_dice = stoul(subtokens[0]); auto subsubtokens = split(subtokens[0], '-');
rules.max_dice = stoul(subtokens[1]); if (subsubtokens.size() != 2) {
throw runtime_error("dice option must be of the form dice=A-B or dice=A-B:C-D");
}
rules.min_dice = stoul(subsubtokens[0]);
rules.max_dice = stoul(subsubtokens[1]);
} else if (starts_with(token, "overall-time-limit=")) { } else if (starts_with(token, "overall-time-limit=")) {
uint32_t limit = stoul(token.substr(19)); uint32_t limit = stoul(token.substr(19));
if (limit > 600) { if (limit > 600) {