#pragma once #include #include #include "../Channel.hh" #include "../CommandFormats.hh" #include "../Text.hh" #include "AssistServer.hh" #include "CardSpecial.hh" #include "MapState.hh" #include "PlayerState.hh" #include "RulerServer.hh" struct Lobby; namespace Episode3 { /** * This implementation of Episode 3 battles (contained in all files in the * src/Episode3 directory, except for DataIndex.hh/cc) is derived from Sega's * original server implementation, reverse-engineered from the Episode 3 client * executable. The control flow, function breakdown, and structure definitions * in these files map very closely to how their server implementation was * written; notable differences (due to necessary environment differences or bug * fixes) are described in the comments therein. * * Some debugging functions have been added which are not part of the original * implementation. Notably, this applies to functions like debug message senders * and loggers and all str() functions. * * There are likely undiscovered bugs in this code, some originally written by * Sega, but more written by me as I manually transcribed and updated this code. */ // Class ownership levels (classes may only contain weak_ptrs, not shared_ptrs, // to classes at the same or higher level): // - ServerBase // - - Server // - - - RulerServer // - - - - AssistServer // - - - - CardSpecial // - - - - - StateFlags // - - - - - DeckEntry // - - - - - PlayerState // - - - - - - Card // - - - - - - - CardShortStatus // - - - - - - - DeckState // - - - - - - - HandAndEquipState // - - - - - - - MapAndRulesState / OverlayState // - - - - - - - - Everything within DataIndex class Server; class ServerBase : public std::enable_shared_from_this { public: ServerBase( std::shared_ptr lobby, std::shared_ptr data_index, uint32_t random_seed, std::shared_ptr map_if_tournament); void init(); void reset(); void recreate_server(); struct PresenceEntry { uint8_t player_present; uint8_t deck_valid; uint8_t is_cpu_player; PresenceEntry(); void clear(); } __attribute__((packed)); std::weak_ptr lobby; std::shared_ptr data_index; PrefixedLogger log; uint32_t random_seed; bool is_tournament; std::shared_ptr last_chosen_map; std::shared_ptr map_and_rules1; std::shared_ptr map_and_rules2; std::shared_ptr deck_entries[4]; std::shared_ptr server; parray presence_entries; uint8_t num_clients_present; parray name_entries; parray name_entries_valid; OverlayState overlay_state; parray, 4> client_card_counts; }; class Server : public std::enable_shared_from_this { public: explicit Server(std::shared_ptr base); void init(); std::shared_ptr base(); std::shared_ptr base() const; int8_t get_winner_team_id() const; template void send(const T& cmd) const { if (cmd.header.size != sizeof(cmd) / 4) { throw std::logic_error("outbound command size field is incorrect"); } if (cmd.header.subsubcommand == 0x06) { this->num_6xB4x06_commands_sent++; this->prev_num_6xB4x06_commands_sent = this->num_6xB4x06_commands_sent; if (this->num_6xB4x06_commands_sent > 0x100) { return; } } this->send(&cmd, cmd.header.size * 4); } void send(const void* data, size_t size) const; void send_commands_for_joining_spectator(Channel& ch) const; __attribute__((format(printf, 2, 3))) void log_debug(const char* fmt, ...) const; __attribute__((format(printf, 2, 3))) void send_debug_message_printf(const char* fmt, ...) const; __attribute__((format(printf, 2, 3))) void send_info_message_printf(const char* fmt, ...) const; void send_debug_command_received_message( uint8_t client_id, uint8_t subsubcommand, const char* description) const; void send_debug_command_received_message( uint8_t subsubcommand, const char* description) const; void send_debug_message_if_error_code_nonzero( uint8_t client_id, int32_t error_code) const; void send_6xB4x46() const; void add_team_exp(uint8_t team_id, int32_t exp); bool advance_battle_phase(); void action_phase_after(); void draw_phase_before(); std::shared_ptr definition_for_card_ref(uint16_t card_ref) const; std::shared_ptr card_for_set_card_ref(uint16_t card_ref); std::shared_ptr card_for_set_card_ref(uint16_t card_ref) const; uint16_t card_id_for_card_ref(uint16_t card_ref) const; bool card_ref_is_empty_or_has_valid_card_id(uint16_t card_ref) const; bool check_for_battle_end(); void check_for_destroyed_cards_and_send_6xB4x05_6xB4x02(); bool check_presence_entry(uint8_t client_id) const; void clear_player_flags_after_dice_phase(); void compute_all_map_occupied_bits(); void compute_team_dice_boost(uint8_t team_id); void copy_player_states_to_prev_states(); std::shared_ptr definition_for_card_id(uint16_t card_id) const; void destroy_cards_with_zero_hp(); void determine_first_team_turn(); void dice_phase_after(); void set_phase_before(); void draw_phase_after(); void dice_phase_before(); void end_attack_list_for_client(uint8_t client_id); void end_action_phase(); bool enqueue_attack_or_defense(uint8_t client_id, ActionState* pa); BattlePhase get_battle_phase() const; ActionSubphase get_current_action_subphase() const; uint8_t get_current_team_turn() const; std::shared_ptr get_player_state(uint8_t client_id); std::shared_ptr get_player_state(uint8_t client_id) const; uint32_t get_random(uint32_t max); float get_random_float_0_1(); uint32_t get_round_num() const; SetupPhase get_setup_phase() const; uint32_t get_should_copy_prev_states_to_current_states() const; bool is_registration_complete() const; void move_phase_after(); void action_phase_before(); void send_6xB4x1C_names_update(); int8_t send_6xB4x33_remove_ally_atk_if_needed(const ActionState& pa); void send_all_state_updates(); void send_set_card_updates_and_6xB4x04_if_needed(); void set_battle_ended(); void set_battle_started(); void set_client_id_ready_to_advance_phase(uint8_t client_id); void set_phase_after(); void move_phase_before(); void set_player_deck_valid(uint8_t client_id); void setup_and_start_battle(); void update_battle_state_flags_and_send_6xB4x03_if_needed( bool always_send = false); bool update_registration_phase(); void on_server_data_input(const std::string& data); void handle_6xB3x0B_mulligan_hand(const std::string& data); void handle_6xB3x0C_end_mulligan_phase(const std::string& data); void handle_6xB3x0D_end_non_action_phase(const std::string& data); void handle_6xB3x0E_discard_card_from_hand(const std::string& data); void handle_6xB3x0F_set_card_from_hand(const std::string& data); void handle_6xB3x10_move_fc_to_location(const std::string& data); void handle_6xB3x11_enqueue_attack_or_defense(const std::string& data); void handle_6xB3x12_end_attack_list(const std::string& data); void handle_6xB3x13_update_map_during_setup(const std::string& data); void handle_6xB3x14_update_deck_during_setup(const std::string& data); void handle_6xB3x15_unused_hard_reset_server_state(const std::string& data); void handle_6xB3x1B_update_player_name(const std::string& data); void handle_6xB3x1D_start_battle(const std::string& data); void handle_6xB3x21_end_battle(const std::string& data); void handle_6xB3x28_end_defense_list(const std::string& data); void handle_6xB3x2B_ignored(const std::string&); void handle_6xB3x34_subtract_ally_atk_points(const std::string& data); void handle_6xB3x37_client_ready_to_advance_from_starter_roll_phase(const std::string& data); void handle_6xB3x3A_ignored(const std::string& data); void handle_6xB3x40_map_list_request(const std::string& data); void handle_6xB3x41_map_request(const std::string& data); void handle_6xB3x48_end_turn(const std::string& data); void handle_6xB3x49_card_counts(const std::string& data); void compute_losing_team_id_and_add_winner_flags(uint32_t flags); uint32_t get_team_exp(uint8_t team_id) const; uint32_t send_6xB4x06_if_card_ref_invalid( uint16_t card_ref, int16_t negative_value); void unknown_8023EEF4(); void execute_bomb_assist_effect(); void replace_targets_due_to_destruction_or_conditions( ActionState* as); bool any_target_exists_for_attack(const ActionState& as); uint8_t get_current_team_turn2() const; void unknown_8023EE48(); void unknown_8023EE80(); void unknown_802402F4(); void send_6xB4x39() const; void send_6xB4x05(); // Recomputes the map occupied bits, so can't be const void send_6xB4x02_for_all_players_if_needed(bool always_send = false); void send_6xB4x50_trap_tile_locations() const; G_UpdateDecks_GC_Ep3_6xB4x07 prepare_6xB4x07_decks_update() const; G_SetPlayerNames_GC_Ep3_6xB4x1C prepare_6xB4x1C_names_update() const; static std::string prepare_6xB6x41_map_definition( std::shared_ptr map); G_SetTrapTileLocations_GC_Ep3_6xB4x50 prepare_6xB4x50_trap_tile_locations() const; std::vector> const_cast_set_cards_v( const std::vector>& cards); private: typedef void (Server::*handler_t)(const std::string&); static const std::unordered_map subcommand_handlers; std::weak_ptr w_base; public: bool tournament_match_result_sent; // Not part of original implementation uint32_t battle_finished; uint32_t battle_in_progress; uint32_t round_num; BattlePhase battle_phase; uint8_t first_team_turn; uint8_t current_team_turn1; SetupPhase setup_phase; RegistrationPhase registration_phase; ActionSubphase action_subphase; uint8_t current_team_turn2; ActionState pending_attacks[0x20]; uint32_t num_pending_attacks; parray client_done_enqueuing_attacks; parray player_ready_to_end_phase; std::shared_ptr random_crypt; uint32_t unknown_a10; uint32_t overall_time_expired; // Note: In the original implementation, this is a uint32_t and is measured in // seconds. In our environment, the simplest implementation uses now(), which // returns microseconds, so we use a uint64_t instead. uint64_t battle_start_usecs; uint32_t should_copy_prev_states_to_current_states; std::shared_ptr card_special; std::shared_ptr state_flags; std::shared_ptr player_states[4]; parray clients_done_in_mulligan_phase; uint32_t num_pending_attacks_with_cards; std::shared_ptr attack_cards[0x20]; ActionState pending_attacks_with_cards[0x20]; uint32_t unknown_a14; uint32_t unknown_a15; parray defense_list_ended_for_client; std::shared_ptr assist_server; uint16_t next_assist_card_set_number; std::shared_ptr ruler_server; parray, 2>, 5> warp_positions; // Array indexes are (type, end, x/y) parray team_exp; parray team_dice_boost; parray team_client_count; parray team_num_ally_fcs_destroyed; parray team_num_cards_destroyed; uint32_t hard_reset_flag; uint8_t tournament_flag; parray num_trap_tiles_of_type; parray chosen_trap_tile_index_of_type; parray, 8>, 5> trap_tile_locs; ActionState pb_action_states[4]; parray has_done_pb; parray, 4> has_done_pb_with_client; mutable uint32_t num_6xB4x06_commands_sent; mutable uint32_t prev_num_6xB4x06_commands_sent; }; } // namespace Episode3