allow trap cards to be customized
This commit is contained in:
+2
-2
@@ -1338,8 +1338,8 @@ static void server_command_ep3_infinite_time(shared_ptr<Client> c, const std::u1
|
||||
return;
|
||||
}
|
||||
|
||||
l->ep3_server->behavior_flags ^= Episode3::BehaviorFlag::DISABLE_TIME_LIMITS;
|
||||
bool infinite_time_enabled = (l->ep3_server->behavior_flags & Episode3::BehaviorFlag::DISABLE_TIME_LIMITS);
|
||||
l->ep3_server->options.behavior_flags ^= Episode3::BehaviorFlag::DISABLE_TIME_LIMITS;
|
||||
bool infinite_time_enabled = (l->ep3_server->options.behavior_flags & Episode3::BehaviorFlag::DISABLE_TIME_LIMITS);
|
||||
send_text_message(l, infinite_time_enabled ? u"$C6Infinite time enabled" : u"$C6Infinite time disabled");
|
||||
}
|
||||
|
||||
|
||||
+17
-17
@@ -897,7 +897,7 @@ shared_ptr<Card> CardSpecial::compute_replaced_target_based_on_conditions(
|
||||
// the Gifoie card's ID (00D9) for compute_effective_range.
|
||||
// TODO: We should fix this so it doesn't rely on a fixed card definition.
|
||||
parray<uint8_t, 9 * 9> range;
|
||||
compute_effective_range(range, this->server()->card_index, 0x00D9, target_card_loc, this->server()->map_and_rules);
|
||||
compute_effective_range(range, this->server()->options.card_index, 0x00D9, target_card_loc, this->server()->map_and_rules);
|
||||
auto card_refs_in_parry_range = target_ps->get_all_cards_within_range(
|
||||
range, target_card_loc, 0xFF);
|
||||
|
||||
@@ -2651,7 +2651,7 @@ vector<shared_ptr<const Card>> CardSpecial::get_targeted_cards_for_condition(
|
||||
if (ce && ps) {
|
||||
uint16_t range_card_id = this->get_card_id_with_effective_range(card1, ce->def.card_id, card2);
|
||||
parray<uint8_t, 9 * 9> range;
|
||||
compute_effective_range(range, this->server()->card_index, range_card_id, card1_loc, this->server()->map_and_rules);
|
||||
compute_effective_range(range, this->server()->options.card_index, range_card_id, card1_loc, this->server()->map_and_rules);
|
||||
add_card_refs(ps->get_card_refs_within_range_from_all_players(range, card1_loc, CardType::ITEM));
|
||||
}
|
||||
}
|
||||
@@ -2661,7 +2661,7 @@ vector<shared_ptr<const Card>> CardSpecial::get_targeted_cards_for_condition(
|
||||
if (ce && ps) {
|
||||
uint16_t range_card_id = this->get_card_id_with_effective_range(card1, ce->def.card_id, card2);
|
||||
parray<uint8_t, 9 * 9> range;
|
||||
compute_effective_range(range, this->server()->card_index, range_card_id, card1_loc, this->server()->map_and_rules);
|
||||
compute_effective_range(range, this->server()->options.card_index, range_card_id, card1_loc, this->server()->map_and_rules);
|
||||
add_card_refs(ps->get_all_cards_within_range(range, card1_loc, card1->get_team_id()));
|
||||
}
|
||||
}
|
||||
@@ -2701,7 +2701,7 @@ vector<shared_ptr<const Card>> CardSpecial::get_targeted_cards_for_condition(
|
||||
if (ce && ps) {
|
||||
uint16_t range_card_id = this->get_card_id_with_effective_range(card1, ce->def.card_id, card2);
|
||||
parray<uint8_t, 9 * 9> range;
|
||||
compute_effective_range(range, this->server()->card_index, range_card_id, card1_loc, this->server()->map_and_rules);
|
||||
compute_effective_range(range, this->server()->options.card_index, range_card_id, card1_loc, this->server()->map_and_rules);
|
||||
add_card_refs(ps->get_all_cards_within_range(range, card1_loc, card1->get_team_id()));
|
||||
}
|
||||
}
|
||||
@@ -2771,7 +2771,7 @@ vector<shared_ptr<const Card>> CardSpecial::get_targeted_cards_for_condition(
|
||||
// should fix this eventually.
|
||||
uint16_t range_card_id = this->get_card_id_with_effective_range(card1, 0x00D9, card2);
|
||||
parray<uint8_t, 9 * 9> range;
|
||||
compute_effective_range(range, this->server()->card_index, range_card_id, card1_loc, this->server()->map_and_rules);
|
||||
compute_effective_range(range, this->server()->options.card_index, range_card_id, card1_loc, this->server()->map_and_rules);
|
||||
auto result_card_refs = ps->get_all_cards_within_range(range, card1_loc, card1->get_team_id());
|
||||
for (uint16_t result_card_ref : result_card_refs) {
|
||||
auto result_card = this->server()->card_for_set_card_ref(result_card_ref);
|
||||
@@ -2797,7 +2797,7 @@ vector<shared_ptr<const Card>> CardSpecial::get_targeted_cards_for_condition(
|
||||
uint16_t range_card_id = this->get_card_id_with_effective_range(card1, 0x00D9, card2);
|
||||
log23.debug("effective range card ID is #%04hX", range_card_id);
|
||||
parray<uint8_t, 9 * 9> range;
|
||||
compute_effective_range(range, this->server()->card_index, range_card_id, card1_loc, this->server()->map_and_rules, &log23);
|
||||
compute_effective_range(range, this->server()->options.card_index, range_card_id, card1_loc, this->server()->map_and_rules, &log23);
|
||||
auto result_card_refs = ps->get_all_cards_within_range(range, card1_loc, 0xFF);
|
||||
log23.debug("%zu result card refs", result_card_refs.size());
|
||||
for (uint16_t result_card_ref : result_card_refs) {
|
||||
@@ -2874,7 +2874,7 @@ vector<shared_ptr<const Card>> CardSpecial::get_targeted_cards_for_condition(
|
||||
// TODO: Again with the Gifoie hardcoding...
|
||||
uint16_t range_card_id = this->get_card_id_with_effective_range(card1, 0x00D9, card2);
|
||||
parray<uint8_t, 9 * 9> range;
|
||||
compute_effective_range(range, this->server()->card_index, range_card_id, card1_loc, this->server()->map_and_rules);
|
||||
compute_effective_range(range, this->server()->options.card_index, range_card_id, card1_loc, this->server()->map_and_rules);
|
||||
auto result_card_refs = ps->get_all_cards_within_range(range, card1_loc, 0xFF);
|
||||
for (uint16_t result_card_ref : result_card_refs) {
|
||||
auto result_card = this->server()->card_for_set_card_ref(result_card_ref);
|
||||
@@ -2928,7 +2928,7 @@ vector<shared_ptr<const Card>> CardSpecial::get_targeted_cards_for_condition(
|
||||
// TODO: Yet another Gifoie hardcode location :(
|
||||
uint16_t range_card_id = this->get_card_id_with_effective_range(card1, 0x00D9, card2);
|
||||
parray<uint8_t, 9 * 9> range;
|
||||
compute_effective_range(range, this->server()->card_index, range_card_id, card1_loc, this->server()->map_and_rules);
|
||||
compute_effective_range(range, this->server()->options.card_index, range_card_id, card1_loc, this->server()->map_and_rules);
|
||||
auto result_card_refs = ps->get_all_cards_within_range(range, card1_loc, card1->get_team_id());
|
||||
for (uint16_t result_card_ref : result_card_refs) {
|
||||
auto result_card = this->server()->card_for_set_card_ref(result_card_ref);
|
||||
@@ -2955,7 +2955,7 @@ vector<shared_ptr<const Card>> CardSpecial::get_targeted_cards_for_condition(
|
||||
// TODO: Sigh. Gifoie again.
|
||||
uint16_t range_card_id = this->get_card_id_with_effective_range(card1, 0x00D9, card2);
|
||||
parray<uint8_t, 9 * 9> range;
|
||||
compute_effective_range(range, this->server()->card_index, range_card_id, card1_loc, this->server()->map_and_rules);
|
||||
compute_effective_range(range, this->server()->options.card_index, range_card_id, card1_loc, this->server()->map_and_rules);
|
||||
auto result_card_refs = ps->get_all_cards_within_range(range, card1_loc, 0xFF);
|
||||
for (uint16_t result_card_ref : result_card_refs) {
|
||||
auto result_card = this->server()->card_for_set_card_ref(result_card_ref);
|
||||
@@ -3051,7 +3051,7 @@ vector<shared_ptr<const Card>> CardSpecial::get_targeted_cards_for_condition(
|
||||
// Slay instead of Gifoie
|
||||
uint16_t range_card_id = this->get_card_id_with_effective_range(card1, 0x009C, card2);
|
||||
parray<uint8_t, 9 * 9> range;
|
||||
compute_effective_range(range, this->server()->card_index, range_card_id, card1_loc, this->server()->map_and_rules);
|
||||
compute_effective_range(range, this->server()->options.card_index, range_card_id, card1_loc, this->server()->map_and_rules);
|
||||
auto result_card_refs = ps->get_all_cards_within_range(range, card1_loc, 0xFF);
|
||||
for (uint16_t result_card_ref : result_card_refs) {
|
||||
auto result_card = this->server()->card_for_set_card_ref(result_card_ref);
|
||||
@@ -3080,7 +3080,7 @@ vector<shared_ptr<const Card>> CardSpecial::get_targeted_cards_for_condition(
|
||||
// TODO: Sigh. Gifoie. Sigh.
|
||||
uint16_t range_card_id = this->get_card_id_with_effective_range(card1, 0x00D9, card2);
|
||||
parray<uint8_t, 9 * 9> range;
|
||||
compute_effective_range(range, this->server()->card_index, range_card_id, card1_loc, this->server()->map_and_rules);
|
||||
compute_effective_range(range, this->server()->options.card_index, range_card_id, card1_loc, this->server()->map_and_rules);
|
||||
auto result_card_refs = ps->get_all_cards_within_range(range, card1_loc, 0xFF);
|
||||
for (uint16_t result_card_ref : result_card_refs) {
|
||||
auto result_card = this->server()->card_for_set_card_ref(result_card_ref);
|
||||
@@ -3117,7 +3117,7 @@ vector<shared_ptr<const Card>> CardSpecial::get_targeted_cards_for_condition(
|
||||
// TODO: One more Gifoie here.
|
||||
uint16_t range_card_id = this->get_card_id_with_effective_range(card1, 0x00D9, card2);
|
||||
parray<uint8_t, 9 * 9> range;
|
||||
compute_effective_range(range, this->server()->card_index, range_card_id, card1_loc, this->server()->map_and_rules);
|
||||
compute_effective_range(range, this->server()->options.card_index, range_card_id, card1_loc, this->server()->map_and_rules);
|
||||
auto result_card_refs = ps->get_all_cards_within_range(range, card1_loc, card1->get_team_id());
|
||||
for (uint16_t result_card_ref : result_card_refs) {
|
||||
auto result_card = this->server()->card_for_set_card_ref(result_card_ref);
|
||||
@@ -3541,7 +3541,7 @@ void CardSpecial::check_for_defense_interference(
|
||||
shared_ptr<Card> target_card,
|
||||
int16_t* inout_unknown_p4) {
|
||||
// Note: This check is not part of the original implementation.
|
||||
if (this->server()->behavior_flags & BehaviorFlag::DISABLE_INTERFERENCE) {
|
||||
if (this->server()->options.behavior_flags & BehaviorFlag::DISABLE_INTERFERENCE) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -3574,7 +3574,7 @@ void CardSpecial::check_for_defense_interference(
|
||||
}
|
||||
|
||||
auto ally_hes = this->server()->ruler_server->get_hand_and_equip_state_for_client_id(target_ally_client_id);
|
||||
if (!ally_hes || (!(this->server()->behavior_flags & BehaviorFlag::ALLOW_NON_COM_INTERFERENCE) && !ally_hes->is_cpu_player)) {
|
||||
if (!ally_hes || (!(this->server()->options.behavior_flags & BehaviorFlag::ALLOW_NON_COM_INTERFERENCE) && !ally_hes->is_cpu_player)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -4317,7 +4317,7 @@ vector<shared_ptr<const Card>> CardSpecial::filter_cards_by_range(
|
||||
// TODO: Remove hardcoded card ID here (Earthquake)
|
||||
uint16_t card_id = this->get_card_id_with_effective_range(card1, 0x00ED, card2);
|
||||
parray<uint8_t, 9 * 9> range;
|
||||
compute_effective_range(range, this->server()->card_index, card_id, card1_loc, this->server()->map_and_rules);
|
||||
compute_effective_range(range, this->server()->options.card_index, card_id, card1_loc, this->server()->map_and_rules);
|
||||
auto card_refs_in_range = ps->get_card_refs_within_range_from_all_players(range, card1_loc, CardType::ITEM);
|
||||
|
||||
for (auto card : cards) {
|
||||
@@ -4539,7 +4539,7 @@ void CardSpecial::unknown_8024A9D8(const ActionState& pa, uint16_t action_card_r
|
||||
|
||||
void CardSpecial::check_for_attack_interference(shared_ptr<Card> unknown_p2) {
|
||||
// Note: This check is not part of the original implementation.
|
||||
if (this->server()->behavior_flags & BehaviorFlag::DISABLE_INTERFERENCE) {
|
||||
if (this->server()->options.behavior_flags & BehaviorFlag::DISABLE_INTERFERENCE) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -4568,7 +4568,7 @@ void CardSpecial::check_for_attack_interference(shared_ptr<Card> unknown_p2) {
|
||||
}
|
||||
|
||||
auto ally_hes = this->server()->ruler_server->get_hand_and_equip_state_for_client_id(ally_client_id);
|
||||
if (!ally_hes || (!(this->server()->behavior_flags & BehaviorFlag::ALLOW_NON_COM_INTERFERENCE) && !ally_hes->is_cpu_player)) {
|
||||
if (!ally_hes || (!(this->server()->options.behavior_flags & BehaviorFlag::ALLOW_NON_COM_INTERFERENCE) && !ally_hes->is_cpu_player)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -51,7 +51,7 @@ void PlayerState::init() {
|
||||
this->deck_state.reset(new DeckState(
|
||||
this->client_id,
|
||||
s->deck_entries[client_id]->card_ids,
|
||||
s->random_crypt));
|
||||
s->options.random_crypt));
|
||||
if (s->map_and_rules->rules.disable_deck_shuffle) {
|
||||
this->deck_state->disable_shuffle();
|
||||
}
|
||||
|
||||
+68
-63
@@ -24,19 +24,10 @@ void Server::PresenceEntry::clear() {
|
||||
this->is_cpu_player = 0;
|
||||
}
|
||||
|
||||
Server::Server(shared_ptr<Lobby> lobby,
|
||||
shared_ptr<const CardIndex> card_index,
|
||||
shared_ptr<const MapIndex> map_index,
|
||||
uint32_t behavior_flags,
|
||||
shared_ptr<PSOLFGEncryption> random_crypt,
|
||||
shared_ptr<const Tournament> tournament)
|
||||
Server::Server(shared_ptr<Lobby> lobby, Options&& options)
|
||||
: lobby(lobby),
|
||||
card_index(card_index),
|
||||
map_index(map_index),
|
||||
behavior_flags(behavior_flags),
|
||||
random_crypt(random_crypt),
|
||||
last_chosen_map(tournament ? tournament->get_map() : nullptr),
|
||||
tournament(tournament),
|
||||
options(std::move(options)),
|
||||
last_chosen_map(this->options.tournament ? this->options.tournament->get_map() : nullptr),
|
||||
tournament_match_result_sent(false),
|
||||
override_environment_number(0xFF),
|
||||
battle_finished(false),
|
||||
@@ -204,7 +195,7 @@ void Server::send(const void* data, size_t size) const {
|
||||
}
|
||||
|
||||
string masked_data;
|
||||
if (!(this->behavior_flags & BehaviorFlag::DISABLE_MASKING)) {
|
||||
if (!(this->options.behavior_flags & BehaviorFlag::DISABLE_MASKING)) {
|
||||
if (size >= 8) {
|
||||
masked_data.assign(reinterpret_cast<const char*>(data), size);
|
||||
uint8_t mask_key = (random_object<uint32_t>() % 0xFF) + 1;
|
||||
@@ -238,12 +229,16 @@ void Server::send_6xB4x46() const {
|
||||
|
||||
G_ServerVersionStrings_GC_Ep3_6xB4x46 cmd46;
|
||||
cmd46.version_signature = VERSION_SIGNATURE;
|
||||
cmd46.date_str1 = format_time(this->card_index->definitions_mtime() * 1000000);
|
||||
cmd46.date_str1 = format_time(this->options.card_index->definitions_mtime() * 1000000);
|
||||
string date_str2 = string_printf(
|
||||
"Lobby:%08" PRIX32 " Random:%08" PRIX32 "+%08" PRIX32,
|
||||
l->lobby_id,
|
||||
this->options.random_crypt->seed(),
|
||||
this->options.random_crypt->absolute_offset());
|
||||
if (this->last_chosen_map) {
|
||||
cmd46.date_str2 = string_printf("Lobby:%08" PRIX32 " Random:%08" PRIX32 "+%08" PRIX32 " Map:%08" PRIX32, l->lobby_id, this->random_crypt->seed(), this->random_crypt->absolute_offset(), this->last_chosen_map->map.map_number.load());
|
||||
} else {
|
||||
cmd46.date_str2 = string_printf("Lobby:%08" PRIX32 " Random:%08" PRIX32 "+%08" PRIX32, l->lobby_id, this->random_crypt->seed(), this->random_crypt->absolute_offset());
|
||||
date_str2 += string_printf(" Map:%08" PRIX32, this->last_chosen_map->map.map_number.load());
|
||||
}
|
||||
cmd46.date_str2 = date_str2;
|
||||
this->send(cmd46);
|
||||
}
|
||||
|
||||
@@ -307,7 +302,7 @@ void Server::send_commands_for_joining_spectator(Channel& c, bool is_trial) cons
|
||||
|
||||
__attribute__((format(printf, 2, 3))) void Server::send_debug_message_printf(const char* fmt, ...) const {
|
||||
auto l = this->lobby.lock();
|
||||
if (l && (this->behavior_flags & Episode3::BehaviorFlag::ENABLE_STATUS_MESSAGES)) {
|
||||
if (l && (this->options.behavior_flags & Episode3::BehaviorFlag::ENABLE_STATUS_MESSAGES)) {
|
||||
va_list va;
|
||||
va_start(va, fmt);
|
||||
std::string buf = string_vprintf(fmt, va);
|
||||
@@ -408,7 +403,7 @@ void Server::draw_phase_before() {
|
||||
|
||||
shared_ptr<const CardIndex::CardEntry> Server::definition_for_card_ref(uint16_t card_ref) const {
|
||||
try {
|
||||
return this->card_index->definition_for_id(this->card_id_for_card_ref(card_ref));
|
||||
return this->options.card_index->definition_for_id(this->card_id_for_card_ref(card_ref));
|
||||
} catch (const out_of_range&) {
|
||||
return nullptr;
|
||||
}
|
||||
@@ -667,7 +662,7 @@ void Server::copy_player_states_to_prev_states() {
|
||||
|
||||
shared_ptr<const CardIndex::CardEntry> Server::definition_for_card_id(uint16_t card_id) const {
|
||||
try {
|
||||
return this->card_index->definition_for_id(card_id);
|
||||
return this->options.card_index->definition_for_id(card_id);
|
||||
} catch (const out_of_range&) {
|
||||
return nullptr;
|
||||
}
|
||||
@@ -971,12 +966,12 @@ uint32_t Server::get_random(uint32_t max) {
|
||||
// The original implementation was essentially:
|
||||
// return (static_cast<double>(this->random_crypt->next() >> 16) / 65536.0) * max
|
||||
// This is unnecessarily complicated, so we instead just do this:
|
||||
return this->random_crypt->next() % max;
|
||||
return this->options.random_crypt->next() % max;
|
||||
}
|
||||
|
||||
float Server::get_random_float_0_1() {
|
||||
// This lacks some precision, but matches the original implementation.
|
||||
return (static_cast<double>(this->random_crypt->next() >> 16) / 65536.0);
|
||||
return (static_cast<double>(this->options.random_crypt->next() >> 16) / 65536.0);
|
||||
}
|
||||
|
||||
uint32_t Server::get_round_num() const {
|
||||
@@ -1020,45 +1015,55 @@ void Server::move_phase_after() {
|
||||
continue;
|
||||
}
|
||||
|
||||
static const uint16_t TRAP_CARD_IDS[5][5] = {
|
||||
// Dice Fever, Heavy Fog, Muscular, Immortality, Snail Pace
|
||||
{0x00F7, 0x010F, 0x012E, 0x013B, 0x013C},
|
||||
// Gold Rush, Charity, Requiem
|
||||
{0x0131, 0x012B, 0x0133, 0x0000, 0x0000},
|
||||
// Powerless Rain, Trash 1, Empty Hand, Skip Draw
|
||||
{0x00FA, 0x0125, 0x0126, 0x0137, 0x0000},
|
||||
// Brave Wind, Homesick, Fly
|
||||
{0x00FB, 0x014E, 0x0107, 0x0000, 0x0000},
|
||||
// Dice+1, Battle Royale, Reverse Card, Giant Garden, Fix
|
||||
{0x00F6, 0x0242, 0x014B, 0x0145, 0x012D}};
|
||||
static size_t TRAP_CARD_ID_COUNTS[5] = {5, 3, 4, 3, 5};
|
||||
static const array<vector<uint16_t>, 5> default_trap_card_ids = {
|
||||
// Red: Dice Fever, Heavy Fog, Muscular, Immortality, Snail Pace
|
||||
vector<uint16_t>{0x00F7, 0x010F, 0x012E, 0x013B, 0x013C},
|
||||
// Blue: Gold Rush, Charity, Requiem
|
||||
vector<uint16_t>{0x0131, 0x012B, 0x0133},
|
||||
// Purple: Powerless Rain, Trash 1, Empty Hand, Skip Draw
|
||||
vector<uint16_t>{0x00FA, 0x0125, 0x0126, 0x0137},
|
||||
// Green: Brave Wind, Homesick, Fly
|
||||
vector<uint16_t>{0x00FB, 0x014E, 0x0107},
|
||||
// Yellow: Dice+1, Battle Royale, Reverse Card, Giant Garden, Fix
|
||||
vector<uint16_t>{0x00F6, 0x0242, 0x014B, 0x0145, 0x012D}};
|
||||
|
||||
const vector<uint16_t>* trap_card_ids = &this->options.trap_card_ids.at(trap_type);
|
||||
if (trap_card_ids->empty()) {
|
||||
trap_card_ids = &default_trap_card_ids.at(trap_type);
|
||||
}
|
||||
|
||||
// This is the original implementation. We do something smarter instead.
|
||||
// uint16_t trap_card_id = 0;
|
||||
// while (trap_card_id == 0) {
|
||||
// trap_card_id = TRAP_CARD_IDS[trap_type][this->get_random(5)];
|
||||
// }
|
||||
size_t trap_card_id_index = this->get_random(TRAP_CARD_ID_COUNTS[trap_type]);
|
||||
uint16_t trap_card_id = TRAP_CARD_IDS[trap_type][trap_card_id_index];
|
||||
uint16_t trap_card_id = 0xFFFF;
|
||||
if (trap_card_ids->size() == 1) {
|
||||
trap_card_id = trap_card_ids->at(0);
|
||||
} else if (trap_card_ids->size() > 1) {
|
||||
trap_card_id = trap_card_ids->at(this->get_random(trap_card_ids->size()));
|
||||
}
|
||||
|
||||
for (size_t client_id = 0; client_id < 4; client_id++) {
|
||||
auto ps = this->player_states[client_id];
|
||||
if (ps) {
|
||||
auto sc_card = ps->get_sc_card();
|
||||
if (sc_card &&
|
||||
(abs(sc_card->loc.x - trap_x) < 2) &&
|
||||
(abs(sc_card->loc.y - trap_y) < 2) &&
|
||||
ps->replace_assist_card_by_id(trap_card_id)) {
|
||||
G_Unknown_GC_Ep3_6xB4x2C cmd;
|
||||
cmd.change_type = 0x01;
|
||||
cmd.client_id = client_id;
|
||||
cmd.card_refs.clear(0xFFFF);
|
||||
cmd.loc.x = trap_x;
|
||||
cmd.loc.y = trap_y;
|
||||
cmd.loc.direction = static_cast<Direction>(trap_type);
|
||||
cmd.unknown_a2[0] = trap_card_id;
|
||||
cmd.unknown_a2[1] = 0xFFFFFFFF;
|
||||
this->send(cmd);
|
||||
if (trap_card_id != 0xFFFF) {
|
||||
for (size_t client_id = 0; client_id < 4; client_id++) {
|
||||
auto ps = this->player_states[client_id];
|
||||
if (ps) {
|
||||
auto sc_card = ps->get_sc_card();
|
||||
if (sc_card &&
|
||||
(abs(sc_card->loc.x - trap_x) < 2) &&
|
||||
(abs(sc_card->loc.y - trap_y) < 2) &&
|
||||
ps->replace_assist_card_by_id(trap_card_id)) {
|
||||
G_Unknown_GC_Ep3_6xB4x2C cmd;
|
||||
cmd.change_type = 0x01;
|
||||
cmd.client_id = client_id;
|
||||
cmd.card_refs.clear(0xFFFF);
|
||||
cmd.loc.x = trap_x;
|
||||
cmd.loc.y = trap_y;
|
||||
cmd.loc.direction = static_cast<Direction>(trap_type);
|
||||
cmd.unknown_a2[0] = trap_card_id;
|
||||
cmd.unknown_a2[1] = 0xFFFFFFFF;
|
||||
this->send(cmd);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1543,7 +1548,7 @@ G_SetStateFlags_GC_Ep3_6xB4x03 Server::prepare_6xB4x03() const {
|
||||
cmd.state.team_dice_boost[0] = this->team_dice_boost[0];
|
||||
cmd.state.team_dice_boost[1] = this->team_dice_boost[1];
|
||||
cmd.state.first_team_turn = this->first_team_turn;
|
||||
cmd.state.tournament_flag = this->tournament ? 1 : 0;
|
||||
cmd.state.tournament_flag = this->options.tournament ? 1 : 0;
|
||||
for (size_t z = 0; z < 4; z++) {
|
||||
auto ps = this->player_states[z];
|
||||
if (!ps) {
|
||||
@@ -1952,8 +1957,8 @@ void Server::handle_CAx13_update_map_during_setup(const string& data) {
|
||||
|
||||
// If this match is part of a tournament, ignore the rules sent by the
|
||||
// client and use the tournament rules instead.
|
||||
if (this->tournament) {
|
||||
this->map_and_rules->rules = this->tournament->get_rules();
|
||||
if (this->options.tournament) {
|
||||
this->map_and_rules->rules = this->options.tournament->get_rules();
|
||||
}
|
||||
|
||||
if (this->override_environment_number != 0xFF) {
|
||||
@@ -1961,7 +1966,7 @@ void Server::handle_CAx13_update_map_during_setup(const string& data) {
|
||||
this->override_environment_number = 0xFF;
|
||||
}
|
||||
this->overlay_state = in_cmd.overlay_state;
|
||||
if (this->behavior_flags & BehaviorFlag::DISABLE_TIME_LIMITS) {
|
||||
if (this->options.behavior_flags & BehaviorFlag::DISABLE_TIME_LIMITS) {
|
||||
this->map_and_rules->rules.overall_time_limit = 0;
|
||||
this->map_and_rules->rules.phase_time_limit = 0;
|
||||
}
|
||||
@@ -1989,9 +1994,9 @@ void Server::handle_CAx14_update_deck_during_setup(const string& data) {
|
||||
}
|
||||
DeckEntry entry = in_cmd.entry;
|
||||
int32_t verify_error = 0;
|
||||
if (!(this->behavior_flags & BehaviorFlag::SKIP_DECK_VERIFY)) {
|
||||
if (!(this->options.behavior_flags & BehaviorFlag::SKIP_DECK_VERIFY)) {
|
||||
// Note: Sega's original implementation doesn't use the card counts here
|
||||
if (this->behavior_flags & BehaviorFlag::IGNORE_CARD_COUNTS) {
|
||||
if (this->options.behavior_flags & BehaviorFlag::IGNORE_CARD_COUNTS) {
|
||||
verify_error = this->ruler_server->verify_deck(entry.card_ids);
|
||||
} else {
|
||||
verify_error = this->ruler_server->verify_deck(entry.card_ids,
|
||||
@@ -2001,7 +2006,7 @@ void Server::handle_CAx14_update_deck_during_setup(const string& data) {
|
||||
if (verify_error) {
|
||||
throw runtime_error(string_printf("invalid deck: -0x%" PRIX32, verify_error));
|
||||
}
|
||||
if (!(this->behavior_flags & BehaviorFlag::SKIP_D1_D2_REPLACE)) {
|
||||
if (!(this->options.behavior_flags & BehaviorFlag::SKIP_D1_D2_REPLACE)) {
|
||||
this->ruler_server->replace_D1_D2_rank_cards_with_Attack(entry.card_ids);
|
||||
}
|
||||
*this->deck_entries[in_cmd.client_id] = in_cmd.entry;
|
||||
@@ -2309,7 +2314,7 @@ void Server::handle_CAx40_map_list_request(const string& data) {
|
||||
throw runtime_error("lobby is deleted");
|
||||
}
|
||||
|
||||
const auto& list_data = this->map_index->get_compressed_list(l->count_clients());
|
||||
const auto& list_data = this->options.map_index->get_compressed_list(l->count_clients());
|
||||
|
||||
StringWriter w;
|
||||
uint32_t subcommand_size = (list_data.size() + sizeof(G_MapList_GC_Ep3_6xB6x40) + 3) & (~3);
|
||||
@@ -2337,7 +2342,7 @@ void Server::handle_CAx41_map_request(const string& data) {
|
||||
throw runtime_error("lobby is deleted");
|
||||
}
|
||||
|
||||
this->last_chosen_map = this->map_index->definition_for_number(cmd.map_number);
|
||||
this->last_chosen_map = this->options.map_index->definition_for_number(cmd.map_number);
|
||||
auto out_cmd = this->prepare_6xB6x41_map_definition(this->last_chosen_map, l->flags & Lobby::Flag::IS_EP3_TRIAL);
|
||||
send_command(l, 0x6C, 0x00, out_cmd);
|
||||
for (auto watcher_l : l->watcher_lobbies) {
|
||||
|
||||
+10
-11
@@ -66,12 +66,15 @@ class Server : public std::enable_shared_from_this<Server> {
|
||||
// it appears that that command is never sent by the client, so we combine
|
||||
// the two classes into one in our implementation.
|
||||
public:
|
||||
Server(std::shared_ptr<Lobby> lobby,
|
||||
std::shared_ptr<const CardIndex> card_index,
|
||||
std::shared_ptr<const MapIndex> map_index,
|
||||
uint32_t behavior_flags,
|
||||
std::shared_ptr<PSOLFGEncryption> random_crypt,
|
||||
std::shared_ptr<const Tournament> tournament);
|
||||
struct Options {
|
||||
std::shared_ptr<const CardIndex> card_index;
|
||||
std::shared_ptr<const MapIndex> map_index;
|
||||
uint32_t behavior_flags;
|
||||
std::shared_ptr<PSOLFGEncryption> random_crypt;
|
||||
std::shared_ptr<const Tournament> tournament;
|
||||
std::array<std::vector<uint16_t>, 5> trap_card_ids;
|
||||
};
|
||||
Server(std::shared_ptr<Lobby> lobby, Options&& options);
|
||||
~Server() noexcept(false);
|
||||
void init();
|
||||
|
||||
@@ -236,12 +239,8 @@ private:
|
||||
public:
|
||||
// These fields are not part of the original implementation
|
||||
std::weak_ptr<Lobby> lobby;
|
||||
std::shared_ptr<const CardIndex> card_index;
|
||||
std::shared_ptr<const MapIndex> map_index;
|
||||
uint32_t behavior_flags;
|
||||
std::shared_ptr<PSOLFGEncryption> random_crypt;
|
||||
Options options;
|
||||
std::shared_ptr<const MapIndex::MapEntry> last_chosen_map;
|
||||
std::shared_ptr<const Tournament> tournament;
|
||||
bool tournament_match_result_sent;
|
||||
uint8_t override_environment_number;
|
||||
mutable std::deque<StackLogger*> logger_stack;
|
||||
|
||||
@@ -1303,13 +1303,15 @@ static void on_CA_Ep3(shared_ptr<Client> c, uint16_t, uint32_t, const string& da
|
||||
}
|
||||
auto tourn = l->tournament_match ? l->tournament_match->tournament.lock() : nullptr;
|
||||
bool is_trial = (l->flags & Lobby::Flag::IS_EP3_TRIAL);
|
||||
l->ep3_server = make_shared<Episode3::Server>(
|
||||
l,
|
||||
is_trial ? s->ep3_card_index_trial : s->ep3_card_index,
|
||||
s->ep3_map_index,
|
||||
s->ep3_behavior_flags,
|
||||
l->random_crypt,
|
||||
tourn);
|
||||
Episode3::Server::Options options = {
|
||||
.card_index = is_trial ? s->ep3_card_index_trial : s->ep3_card_index,
|
||||
.map_index = s->ep3_map_index,
|
||||
.behavior_flags = s->ep3_behavior_flags,
|
||||
.random_crypt = l->random_crypt,
|
||||
.tournament = tourn,
|
||||
.trap_card_ids = s->ep3_trap_card_ids,
|
||||
};
|
||||
l->ep3_server = make_shared<Episode3::Server>(l, std::move(options));
|
||||
l->ep3_server->init();
|
||||
|
||||
if (s->ep3_behavior_flags & Episode3::BehaviorFlag::ENABLE_STATUS_MESSAGES) {
|
||||
|
||||
+1
-1
@@ -296,7 +296,7 @@ Proxy session commands:\n\
|
||||
} else if (type == "config") {
|
||||
auto config_json = this->state->load_config();
|
||||
this->state->parse_config(config_json, true);
|
||||
this->state->resolve_ep3_card_auction_pool();
|
||||
this->state->resolve_ep3_card_names();
|
||||
} else {
|
||||
throw invalid_argument("incorrect data type");
|
||||
}
|
||||
|
||||
+37
-4
@@ -101,7 +101,7 @@ void ServerState::init() {
|
||||
this->load_level_table();
|
||||
this->load_item_tables();
|
||||
this->load_ep3_data();
|
||||
this->resolve_ep3_card_auction_pool();
|
||||
this->resolve_ep3_card_names();
|
||||
this->load_quest_index();
|
||||
this->compile_functions();
|
||||
this->load_dol_files();
|
||||
@@ -589,6 +589,20 @@ void ServerState::parse_config(const JSON& json, bool is_reload) {
|
||||
.card_name = it.first});
|
||||
}
|
||||
|
||||
const auto& ep3_trap_cards_json = json.get("Episode3TrapCards", JSON::list()).as_list();
|
||||
if (!ep3_trap_cards_json.empty()) {
|
||||
if (ep3_trap_cards_json.size() != 5) {
|
||||
throw runtime_error("Episode3TrapCards must be a list of 5 lists");
|
||||
}
|
||||
this->ep3_trap_card_names.clear();
|
||||
for (const auto& trap_type_it : ep3_trap_cards_json) {
|
||||
auto& names = this->ep3_trap_card_names.emplace_back();
|
||||
for (const auto& card_it : trap_type_it->as_list()) {
|
||||
names.emplace_back(card_it->as_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!this->is_replay) {
|
||||
for (const auto& it : json.get("Episode3LobbyBanners", JSON::list()).as_list()) {
|
||||
Image img("system/ep3/banners/" + it->at(2).as_string());
|
||||
@@ -923,14 +937,33 @@ void ServerState::load_ep3_data() {
|
||||
config_log.info("Loaded Episode 3 tournament state");
|
||||
}
|
||||
|
||||
void ServerState::resolve_ep3_card_auction_pool() {
|
||||
config_log.info("Resolving Episode 3 card auction pool");
|
||||
void ServerState::resolve_ep3_card_names() {
|
||||
config_log.info("Resolving Episode 3 card names");
|
||||
for (auto& e : this->ep3_card_auction_pool) {
|
||||
try {
|
||||
const auto& card = this->ep3_card_index->definition_for_name_normalized(e.card_name);
|
||||
e.card_id = card->def.card_id;
|
||||
} catch (const out_of_range&) {
|
||||
throw runtime_error(string_printf("Ep3 card \"%s\" does not exist", e.card_name.c_str()));
|
||||
throw runtime_error(string_printf("Ep3 card \"%s\" in auction pool does not exist", e.card_name.c_str()));
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t z = 0; z < this->ep3_trap_card_ids.size(); z++) {
|
||||
auto& ids = this->ep3_trap_card_ids[z];
|
||||
ids.clear();
|
||||
if (z < this->ep3_trap_card_names.size()) {
|
||||
auto& names = this->ep3_trap_card_names[z];
|
||||
for (const auto& name : names) {
|
||||
try {
|
||||
const auto& card = this->ep3_card_index->definition_for_name_normalized(name);
|
||||
if (card->def.type != Episode3::CardType::ASSIST) {
|
||||
throw runtime_error(string_printf("Ep3 card \"%s\" in trap card list is not an assist card", name.c_str()));
|
||||
}
|
||||
ids.emplace_back(card->def.card_id);
|
||||
} catch (const out_of_range&) {
|
||||
throw runtime_error(string_printf("Ep3 card \"%s\" in trap card list does not exist", name.c_str()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+3
-1
@@ -109,6 +109,8 @@ struct ServerState : public std::enable_shared_from_this<ServerState> {
|
||||
std::string card_name;
|
||||
};
|
||||
std::vector<CardAuctionPoolEntry> ep3_card_auction_pool;
|
||||
std::vector<std::vector<std::string>> ep3_trap_card_names;
|
||||
std::array<std::vector<uint16_t>, 5> ep3_trap_card_ids;
|
||||
struct Ep3LobbyBannerEntry {
|
||||
uint32_t type = 1;
|
||||
uint32_t which; // See B9 documentation in CommandFormats.hh
|
||||
@@ -207,7 +209,7 @@ struct ServerState : public std::enable_shared_from_this<ServerState> {
|
||||
void load_level_table();
|
||||
void load_item_tables();
|
||||
void load_ep3_data();
|
||||
void resolve_ep3_card_auction_pool();
|
||||
void resolve_ep3_card_names();
|
||||
void load_quest_index();
|
||||
void compile_functions();
|
||||
void load_dol_files();
|
||||
|
||||
@@ -322,6 +322,17 @@
|
||||
// 0x0200 => Allow interference even when neither player is a COM
|
||||
"Episode3BehaviorFlags": 0x0002,
|
||||
|
||||
// Trap assist cards for each trap type in Episode 3 battles. These are the
|
||||
// default values used offline, but you can change the trap types online here.
|
||||
// Only assist cards may be used as trap cards.
|
||||
"Episode3TrapCards": [
|
||||
["Dice Fever", "Heavy Fog", "Muscular", "Immortality", "Snail Pace"], // Red
|
||||
["Gold Rush", "Charity", "Requiem"], // Blue
|
||||
["Powerless Rain", "Trash 1", "Empty Hand", "Skip Draw"], // Purple
|
||||
["Brave Wind", "Homesick", "Fly"], // Green
|
||||
["Dice+1", "Battle Royale", "Reverse Card", "Giant Garden", "Fix"], // Yellow
|
||||
],
|
||||
|
||||
// Episode 3 EX result values. This allows you to set the amount of EX players
|
||||
// will get for winning or losing online matches. Each set of numbers is a
|
||||
// list of thresholds; the first number in each pair is the level difference
|
||||
|
||||
Reference in New Issue
Block a user