From 4c735d055ef73d6c7ceba044fe72da01a5a0a156 Mon Sep 17 00:00:00 2001 From: Martin Michelsen Date: Sat, 3 Feb 2024 18:03:09 -0800 Subject: [PATCH] Ep3 NTE battles checkpoint 1 --- src/ChatCommands.cc | 4 +- src/CommandFormats.hh | 403 +++++++++++---------- src/Episode3/AssistServer.cc | 59 ++- src/Episode3/AssistServer.hh | 5 +- src/Episode3/BattleRecord.cc | 2 +- src/Episode3/Card.cc | 122 ++++--- src/Episode3/Card.hh | 2 +- src/Episode3/CardSpecial.cc | 151 +++++--- src/Episode3/CardSpecial.hh | 85 ++--- src/Episode3/DataIndexes.hh | 1 + src/Episode3/DeckState.cc | 13 +- src/Episode3/DeckState.hh | 2 +- src/Episode3/MapState.cc | 31 ++ src/Episode3/MapState.hh | 44 ++- src/Episode3/PlayerState.cc | 98 +++-- src/Episode3/PlayerState.hh | 22 +- src/Episode3/PlayerStateSubordinates.cc | 96 +++++ src/Episode3/PlayerStateSubordinates.hh | 47 +++ src/Episode3/RulerServer.cc | 4 +- src/Episode3/RulerServer.hh | 45 +-- src/Episode3/Server.cc | 457 +++++++++++++----------- src/Episode3/Server.hh | 26 +- src/Lobby.cc | 5 + src/Lobby.hh | 2 +- src/ProxyCommands.cc | 51 +-- src/ReceiveCommands.cc | 20 +- src/ReceiveSubcommands.cc | 8 +- src/ReplaySession.cc | 23 +- src/SendCommands.cc | 46 +-- src/ServerState.cc | 4 +- src/ServerState.hh | 6 +- 31 files changed, 1137 insertions(+), 747 deletions(-) diff --git a/src/ChatCommands.cc b/src/ChatCommands.cc index 4d6da846..66e749b9 100644 --- a/src/ChatCommands.cc +++ b/src/ChatCommands.cc @@ -547,14 +547,14 @@ static void server_command_auction(shared_ptr c, const std::string&) { check_license_flag(c, License::Flag::DEBUG); auto l = c->require_lobby(); if (l->is_game() && l->is_ep3()) { - G_InitiateCardAuction_GC_Ep3_6xB5x42 cmd; + G_InitiateCardAuction_Ep3_6xB5x42 cmd; cmd.header.sender_client_id = c->lobby_client_id; send_command_t(l, 0xC9, 0x00, cmd); } } static void proxy_command_auction(shared_ptr ses, const std::string&) { - G_InitiateCardAuction_GC_Ep3_6xB5x42 cmd; + G_InitiateCardAuction_Ep3_6xB5x42 cmd; cmd.header.sender_client_id = ses->lobby_client_id; ses->client_channel.send(0xC9, 0x00, &cmd, sizeof(cmd)); ses->server_channel.send(0xC9, 0x00, &cmd, sizeof(cmd)); diff --git a/src/CommandFormats.hh b/src/CommandFormats.hh index e2f1be88..6d64bc4f 100644 --- a/src/CommandFormats.hh +++ b/src/CommandFormats.hh @@ -1147,7 +1147,7 @@ struct C_CharacterData_V3_61_98 { /* 0678 */ // char auto_reply[...EOF]; } __attribute__((packed)); -struct C_CharacterData_GC_Ep3_61_98 { +struct C_CharacterData_Ep3_61_98 { /* 0000 */ PlayerInventory inventory; /* 034C */ PlayerDispDataDCPCV3 disp; /* 041C */ PlayerRecordsEntry_V3 records; @@ -1240,7 +1240,7 @@ struct S_JoinGame_GC_64 : S_JoinGame_DC_PC { parray unused; } __packed__; -struct S_JoinGame_GC_Ep3_64 : S_JoinGame_GC_64 { +struct S_JoinGame_Ep3_64 : S_JoinGame_GC_64 { // This field is only present if the game (and client) is Episode 3. Similarly // to lobby_data in the base struct, all four of these are always present and // they are filled in in slot positions. @@ -2247,7 +2247,7 @@ struct C_ExecuteCodeResult_B3 { // B7 (S->C): Rank update (Episode 3) -struct S_RankUpdate_GC_Ep3_B7 { +struct S_RankUpdate_Ep3_B7 { // If rank is not zero, the client sets its rank text to ":", // truncated to 11 characters. If rank is zero, the client uses rank_text // without modifying it. @@ -2278,7 +2278,7 @@ struct S_RankUpdate_GC_Ep3_B7 { // B9 (S->C): Update CARD lobby media (Episode 3) // This command is not valid on Episode 3 Trial Edition. -struct S_UpdateMediaHeader_GC_Ep3_B9 { +struct S_UpdateMediaHeader_Ep3_B9 { // Valid values for the type field: // 1: GVM file // 2: Unknown; probably BML file @@ -2358,13 +2358,13 @@ struct S_UpdateMediaHeader_GC_Ep3_B9 { // sent by client) // 04 = unknown (C->S; request_token must match the last token sent by client) -struct C_MesetaTransaction_GC_Ep3_BA { +struct C_MesetaTransaction_Ep3_BA { le_uint32_t transaction_num = 0; le_uint32_t value = 0; le_uint32_t request_token = 0; } __packed__; -struct S_MesetaTransaction_GC_Ep3_BA { +struct S_MesetaTransaction_Ep3_BA { le_uint32_t current_meseta = 0; le_uint32_t total_meseta_earned = 0; le_uint32_t request_token = 0; // Should match the token sent by the client @@ -2378,7 +2378,7 @@ struct S_MesetaTransaction_GC_Ep3_BA { // finished? We may never know... // header.flag is the number of valid match entries. -struct S_TournamentMatchInformation_GC_Ep3_BB { +struct S_TournamentMatchInformation_Ep3_BB { pstring tournament_name; struct TeamEntry { le_uint16_t win_count = 0; @@ -2577,7 +2577,7 @@ struct C_SetBlockedSenders_BB_C6 : C_SetBlockedSenders_C6<28> { // start time). If the client is not registered, the Check Tactics option is // disabled and the Status item has only two panes. -struct S_ConfirmTournamentEntry_GC_Ep3_CC { +struct S_ConfirmTournamentEntry_Ep3_CC { pstring tournament_name; le_uint16_t num_teams = 0; le_uint16_t players_per_team = 0; @@ -2829,7 +2829,7 @@ struct C_CreateChallengeModeAwardItem_BB_07DF { // respond to a 10 command with an E2 command. // header.flag is the count of filled-in entries. -struct S_TournamentList_GC_Ep3_E0 { +struct S_TournamentList_Ep3_E0 { struct Entry { le_uint32_t menu_id = 0; le_uint32_t item_id = 0; @@ -2874,7 +2874,7 @@ struct S_TournamentList_GC_Ep3_E0 { // used with the E3 command. See the E3 command for descriptions of what each // flag value means. -struct S_GameInformation_GC_Ep3_E1 { +struct S_GameInformation_Ep3_E1 { /* 0004 */ pstring game_name; struct PlayerEntry { pstring name; // From disp.name @@ -2925,7 +2925,7 @@ struct S_SystemFileCreated_00E1_BB { // password is the team password. The server should respond to that with a CC // command. -struct S_TournamentEntryList_GC_Ep3_E2 { +struct S_TournamentEntryList_Ep3_E2 { le_uint16_t players_per_team = 0; le_uint16_t unused = 0; struct Entry { @@ -2973,7 +2973,7 @@ struct S_TournamentEntryList_GC_Ep3_E2 { // The 00, 01, and 04 cases don't really make sense, because the E1 command is // more appropriate for inspecting non-tournament games. -struct S_TournamentGameDetails_GC_Ep3_E3 { +struct S_TournamentGameDetails_Ep3_E3 { // These fields are used only if the Rules pane is shown /* 0004 */ pstring name; /* 0024 */ pstring map_name; @@ -3025,13 +3025,13 @@ struct C_PlayerPreviewRequest_BB_E3 { // thereafter. // header.flag = seated state (1 = present, 0 = leaving) -struct C_CardBattleTableState_GC_Ep3_E4 { +struct C_CardBattleTableState_Ep3_E4 { le_uint16_t table_number = 0; le_uint16_t seat_number = 0; } __packed__; // header.flag = table number -struct S_CardBattleTableState_GC_Ep3_E4 { +struct S_CardBattleTableState_Ep3_E4 { struct Entry { // State values: // 0 = no player present @@ -3060,7 +3060,7 @@ struct S_PlayerPreview_NoPlayer_BB_00E4 { // E5 (C->S): Confirm CARD lobby battle table choice (Episode 3) // header.flag specifies whether the client answered "Yes" (1) or "No" (0). -struct S_CardBattleTableConfirmation_GC_Ep3_E5 { +struct S_CardBattleTableConfirmation_Ep3_E5 { le_uint16_t table_number = 0; le_uint16_t seat_number = 0; } __packed__; @@ -3081,7 +3081,7 @@ struct SC_PlayerPreview_CreateCharacter_BB_00E5 { // With header.flag == 1, this command is used for joining a tournament // spectator team. The following arguments are given in this form: -struct C_JoinSpectatorTeam_GC_Ep3_E6_Flag01 { +struct C_JoinSpectatorTeam_Ep3_E6_Flag01 { le_uint32_t menu_id = 0; le_uint32_t item_id = 0; } __packed__; @@ -3113,7 +3113,7 @@ struct S_ClientInit_BB_00E6 { // regular games. The server should be able to tell these cases apart by the // menu and/or item ID. -struct C_CreateSpectatorTeam_GC_Ep3_E7 { +struct C_CreateSpectatorTeam_Ep3_E7 { le_uint32_t menu_id = 0; le_uint32_t item_id = 0; pstring name; @@ -3137,7 +3137,7 @@ struct SC_SyncSaveFiles_BB_E7 { // The client will crash if leader_id == client_id. Presumably one of the // primary game's players should be the leader (this is what newserv does). -struct S_JoinSpectatorTeam_GC_Ep3_E8 { +struct S_JoinSpectatorTeam_Ep3_E8 { /* 0004 */ parray variations; // unused struct PlayerEntry { /* 0000 */ PlayerLobbyDataDCGC lobby_data; @@ -3261,7 +3261,7 @@ struct C_SwapGuildCardPositions_BB_0AE8 { // it's 1 (vs. any other value). There don't seem to be any in-game behavioral // differences though. -struct S_TimedMessageBoxHeader_GC_Ep3_EA { +struct S_TimedMessageBoxHeader_Ep3_EA { le_uint32_t duration = 0; // In frames; 30 frames = 1 second // Message data follows here (up to 0x1000 chars) } __packed__; @@ -3589,7 +3589,7 @@ struct C_UpdateChallengeRecords_BB_08ED { // 1&2 trade window; see the description of the D0 command above for details. // EE D0 (C->S): Begin trade -struct SC_TradeCards_GC_Ep3_EE_FlagD0_FlagD3 { +struct SC_TradeCards_Ep3_EE_FlagD0_FlagD3 { le_uint16_t target_client_id = 0; le_uint16_t entry_count = 0; struct Entry { @@ -3600,7 +3600,7 @@ struct SC_TradeCards_GC_Ep3_EE_FlagD0_FlagD3 { } __packed__; // EE D1 (S->C): Advance trade state -struct S_AdvanceCardTradeState_GC_Ep3_EE_FlagD1 { +struct S_AdvanceCardTradeState_Ep3_EE_FlagD1 { le_uint32_t unused = 0; } __packed__; @@ -3613,7 +3613,7 @@ struct S_AdvanceCardTradeState_GC_Ep3_EE_FlagD1 { // EE D4 (C->S): Trade failed // EE D4 (S->C): Trade complete -struct S_CardTradeComplete_GC_Ep3_EE_FlagD4 { +struct S_CardTradeComplete_Ep3_EE_FlagD4 { le_uint32_t success = 0; // 0 = failed, 1 = success, anything else = invalid } __packed__; @@ -3629,7 +3629,7 @@ struct S_CardTradeComplete_GC_Ep3_EE_FlagD4 { // EF (S->C): Start card auction (Episode 3) -struct S_StartCardAuction_GC_Ep3_EF { +struct S_StartCardAuction_Ep3_EF { le_uint16_t points_available = 0; le_uint16_t unused = 0; struct Entry { @@ -5569,14 +5569,14 @@ struct G_ShopContentsRequest_BB_6xB5 { // including the extended size field, is likely the reason for 6xB6 being a // separate subcommand from the other CARD battle subcommands.) -struct G_MapSubsubcommand_GC_Ep3_6xB6 { +struct G_MapSubsubcommand_Ep3_6xB6 { G_ExtendedHeader header; uint8_t subsubcommand = 0; // 0x40 or 0x41 parray unused; } __packed__; -struct G_MapList_GC_Ep3_6xB6x40 { - G_MapSubsubcommand_GC_Ep3_6xB6 header; +struct G_MapList_Ep3_6xB6x40 { + G_MapSubsubcommand_Ep3_6xB6 header; le_uint16_t compressed_data_size = 0; le_uint16_t unused = 0; // PRS-compressed map list data follows here. newserv generates this from the @@ -5584,8 +5584,8 @@ struct G_MapList_GC_Ep3_6xB6x40 { // and Episode3::MapIndex::get_compressed_map_list for details on the format. } __packed__; -struct G_MapData_GC_Ep3_6xB6x41 { - G_MapSubsubcommand_GC_Ep3_6xB6 header; +struct G_MapData_Ep3_6xB6x41 { + G_MapSubsubcommand_Ep3_6xB6 header; le_uint32_t map_number = 0; le_uint16_t compressed_data_size = 0; le_uint16_t unused = 0; @@ -5644,7 +5644,7 @@ struct G_IdentifyResult_BB_6xB9 { // 6xBA: Sync card trade state (Episode 3) // This command calls various member functions in TCardTradeServer. -struct G_SyncCardTradeState_GC_Ep3_6xBA { +struct G_SyncCardTradeState_Ep3_6xBA { G_ClientIDHeader header; le_uint16_t what = 0; // Low byte must be < 9; this indexes into a handler table le_uint16_t unknown_a2 = 0; @@ -5665,7 +5665,7 @@ struct G_AcceptItemIdentification_BB_6xBA { // client (what is properly bounds-checked). Find out the actual limits for // slot/args and make newserv enforce them. -struct G_SyncCardTradeState_GC_Ep3_6xBB { +struct G_SyncCardTradeState_Ep3_6xBB { G_ClientIDHeader header; le_uint16_t what = 0; // Must be < 5; this indexes into a jump table le_uint16_t slot = 0; @@ -5682,7 +5682,7 @@ struct G_SyncCardTradeState_GC_Ep3_6xBB { // command is smaller than 0x400 bytes, but uses the extended subcommand format // anyway (and uses the 6D command rather than 62). -struct G_CardCounts_GC_Ep3_6xBC { +struct G_CardCounts_Ep3_6xBC { G_UnusedHeader header; le_uint32_t size = 0; parray unknown_a1; @@ -5704,7 +5704,7 @@ struct G_BankContentsHeader_BB_6xBC { // Note: This structure does not have a normal header - the client ID field is // big-endian! -struct G_WordSelectDuringBattle_GC_Ep3_6xBD { +struct G_WordSelectDuringBattle_Ep3_6xBD { G_ClientIDHeader header; le_uint16_t unknown_a1 = 0; le_uint16_t unknown_a2 = 0; @@ -5731,7 +5731,7 @@ struct G_BankAction_BB_6xBD { // 6xBE: Sound chat (Episode 3; not Trial Edition) // This is the only subcommand ever sent with the CB command. -struct G_SoundChat_GC_Ep3_6xBE { +struct G_SoundChat_Ep3_6xBE { G_UnusedHeader header; le_uint32_t sound_id = 0; // Must be < 0x27 be_uint32_t unused = 0; @@ -5747,7 +5747,7 @@ struct G_CreateInventoryItem_BB_6xBE { // 6xBF: Change lobby music (Episode 3; not Trial Edition) -struct G_ChangeLobbyMusic_GC_Ep3_6xBF { +struct G_ChangeLobbyMusic_Ep3_6xBF { G_UnusedHeader header; le_uint32_t song_number = 0; // Must be < 0x34 } __packed__; @@ -6123,8 +6123,8 @@ struct G_SetMesetaSlotPrizeResult_BB_6xE3 { // 6xB4x02: Update hand and equips -struct G_UpdateHand_GC_Ep3_6xB4x02 { - /* 00 */ G_CardBattleCommandHeader header = {0xB4, sizeof(G_UpdateHand_GC_Ep3_6xB4x02) / 4, 0, 0x02, 0, 0, 0}; +struct G_UpdateHand_Ep3_6xB4x02 { + /* 00 */ G_CardBattleCommandHeader header = {0xB4, sizeof(G_UpdateHand_Ep3_6xB4x02) / 4, 0, 0x02, 0, 0, 0}; /* 08 */ le_uint16_t client_id = 0; /* 0A */ le_uint16_t unused = 0; /* 0C */ Episode3::HandAndEquipState state; @@ -6133,16 +6133,16 @@ struct G_UpdateHand_GC_Ep3_6xB4x02 { // 6xB4x03: Set state flags -struct G_SetStateFlags_GC_Ep3_6xB4x03 { - /* 00 */ G_CardBattleCommandHeader header = {0xB4, sizeof(G_SetStateFlags_GC_Ep3_6xB4x03) / 4, 0, 0x03, 0, 0, 0}; +struct G_SetStateFlags_Ep3_6xB4x03 { + /* 00 */ G_CardBattleCommandHeader header = {0xB4, sizeof(G_SetStateFlags_Ep3_6xB4x03) / 4, 0, 0x03, 0, 0, 0}; /* 08 */ Episode3::StateFlags state; /* 20 */ } __packed__; // 6xB4x04: Update SC/FC short statuses -struct G_UpdateShortStatuses_GC_Ep3_6xB4x04 { - /* 0000 */ G_CardBattleCommandHeader header = {0xB4, sizeof(G_UpdateShortStatuses_GC_Ep3_6xB4x04) / 4, 0, 0x04, 0, 0, 0}; +struct G_UpdateShortStatuses_Ep3_6xB4x04 { + /* 0000 */ G_CardBattleCommandHeader header = {0xB4, sizeof(G_UpdateShortStatuses_Ep3_6xB4x04) / 4, 0, 0x04, 0, 0, 0}; /* 0008 */ le_uint16_t client_id = 0; /* 000A */ le_uint16_t unused = 0; // The slots in this array have heterogeneous meanings. Specifically: @@ -6158,8 +6158,16 @@ struct G_UpdateShortStatuses_GC_Ep3_6xB4x04 { // TODO: This structure is different on Ep3 NTE because the Rules structure is // shorter. Define an appropriate structure for this. -struct G_UpdateMap_GC_Ep3_6xB4x05 { - /* 0000 */ G_CardBattleCommandHeader header = {0xB4, sizeof(G_UpdateMap_GC_Ep3_6xB4x05) / 4, 0, 0x05, 0, 0, 0}; +struct G_UpdateMap_Ep3NTE_6xB4x05 { + /* 0000 */ G_CardBattleCommandHeader header = {0xB4, sizeof(G_UpdateMap_Ep3NTE_6xB4x05) / 4, 0, 0x05, 0, 0, 0}; + /* 0008 */ Episode3::MapAndRulesStateTrial state; + /* 0138 */ uint8_t start_battle = 0; + /* 0139 */ parray unused; + /* 013C */ +} __packed__; + +struct G_UpdateMap_Ep3_6xB4x05 { + /* 0000 */ G_CardBattleCommandHeader header = {0xB4, sizeof(G_UpdateMap_Ep3_6xB4x05) / 4, 0, 0x05, 0, 0, 0}; /* 0008 */ Episode3::MapAndRulesState state; /* 0140 */ uint8_t start_battle = 0; /* 0141 */ parray unused; @@ -6168,16 +6176,16 @@ struct G_UpdateMap_GC_Ep3_6xB4x05 { // 6xB4x06: Apply condition effect -struct G_ApplyConditionEffect_GC_Ep3_6xB4x06 { - /* 00 */ G_CardBattleCommandHeader header = {0xB4, sizeof(G_ApplyConditionEffect_GC_Ep3_6xB4x06) / 4, 0, 0x06, 0, 0, 0}; +struct G_ApplyConditionEffect_Ep3_6xB4x06 { + /* 00 */ G_CardBattleCommandHeader header = {0xB4, sizeof(G_ApplyConditionEffect_Ep3_6xB4x06) / 4, 0, 0x06, 0, 0, 0}; /* 08 */ Episode3::EffectResult effect; /* 14 */ } __packed__; // 6xB4x07: Set battle decks -struct G_UpdateDecks_GC_Ep3_6xB4x07 { - /* 0000 */ G_CardBattleCommandHeader header = {0xB4, sizeof(G_UpdateDecks_GC_Ep3_6xB4x07) / 4, 0, 0x07, 0, 0, 0}; +struct G_UpdateDecks_Ep3_6xB4x07 { + /* 0000 */ G_CardBattleCommandHeader header = {0xB4, sizeof(G_UpdateDecks_Ep3_6xB4x07) / 4, 0, 0x07, 0, 0, 0}; /* 0008 */ parray entries_present; /* 000C */ parray entries; /* 016C */ @@ -6185,8 +6193,8 @@ struct G_UpdateDecks_GC_Ep3_6xB4x07 { // 6xB4x09: Set action state -struct G_SetActionState_GC_Ep3_6xB4x09 { - /* 00 */ G_CardBattleCommandHeader header = {0xB4, sizeof(G_SetActionState_GC_Ep3_6xB4x09) / 4, 0, 0x09, 0, 0, 0}; +struct G_SetActionState_Ep3_6xB4x09 { + /* 00 */ G_CardBattleCommandHeader header = {0xB4, sizeof(G_SetActionState_Ep3_6xB4x09) / 4, 0, 0x09, 0, 0, 0}; /* 08 */ le_uint16_t client_id = 0; /* 0A */ parray unknown_a1; /* 0C */ Episode3::ActionState state; @@ -6194,15 +6202,26 @@ struct G_SetActionState_GC_Ep3_6xB4x09 { } __packed__; // 6xB4x0A: Update action chain and metadata -// This command seems to be unused; Sega's server implementation never sends it. +// This command is used by Trial Edition. The final version sends 6xB4x4C, +// 6xB4x4D, and 6xB4x4E instead, but still has a handler for this command. -struct G_UpdateActionChainAndMetadata_GC_Ep3_6xB4x0A { - /* 0000 */ G_CardBattleCommandHeader header = {0xB4, sizeof(G_UpdateActionChainAndMetadata_GC_Ep3_6xB4x0A) / 4, 0, 0x0A, 0, 0, 0}; +struct G_UpdateActionChainAndMetadata_Ep3NTE_6xB4x0A { + /* 0000 */ G_CardBattleCommandHeader header = {0xB4, sizeof(G_UpdateActionChainAndMetadata_Ep3NTE_6xB4x0A) / 4, 0, 0x0A, 0, 0, 0}; /* 0008 */ le_uint16_t client_id = 0; // set_index must be 0xFF, or be in the range [0, 9]. If it's 0xFF, all nine // chains and metadatas are cleared for the client; otherwise, the provided // chain and metadata are copied into the slot specified by set_index. - /* 000A */ int8_t set_index = 0; + /* 000A */ int8_t index = 0; + /* 000B */ uint8_t unused = 0; + /* 000C */ Episode3::ActionChainWithCondsTrial chain; + /* 010C */ Episode3::ActionMetadata metadata; + /* 0180 */ +} __packed__; + +struct G_UpdateActionChainAndMetadata_Ep3_6xB4x0A { + /* 0000 */ G_CardBattleCommandHeader header = {0xB4, sizeof(G_UpdateActionChainAndMetadata_Ep3_6xB4x0A) / 4, 0, 0x0A, 0, 0, 0}; + /* 0008 */ le_uint16_t client_id = 0; + /* 000A */ int8_t index = 0; /* 000B */ uint8_t unused = 0; /* 000C */ Episode3::ActionChainWithConds chain; /* 010C */ Episode3::ActionMetadata metadata; @@ -6211,16 +6230,16 @@ struct G_UpdateActionChainAndMetadata_GC_Ep3_6xB4x0A { // 6xB3x0B / CAx0B: Redraw initial hand (immediately before battle) -struct G_RedrawInitialHand_GC_Ep3_6xB3x0B_CAx0B { - G_CardServerDataCommandHeader header = {0xB3, sizeof(G_RedrawInitialHand_GC_Ep3_6xB3x0B_CAx0B) / 4, 0, 0x0B, 0, 0, 0, 0, 0}; +struct G_RedrawInitialHand_Ep3_6xB3x0B_CAx0B { + G_CardServerDataCommandHeader header = {0xB3, sizeof(G_RedrawInitialHand_Ep3_6xB3x0B_CAx0B) / 4, 0, 0x0B, 0, 0, 0, 0, 0}; le_uint16_t client_id = 0; parray unused2; } __packed__; // 6xB3x0C / CAx0C: End initial redraw phase -struct G_EndInitialRedrawPhase_GC_Ep3_6xB3x0C_CAx0C { - G_CardServerDataCommandHeader header = {0xB3, sizeof(G_EndInitialRedrawPhase_GC_Ep3_6xB3x0C_CAx0C) / 4, 0, 0x0C, 0, 0, 0, 0, 0}; +struct G_EndInitialRedrawPhase_Ep3_6xB3x0C_CAx0C { + G_CardServerDataCommandHeader header = {0xB3, sizeof(G_EndInitialRedrawPhase_Ep3_6xB3x0C_CAx0C) / 4, 0, 0x0C, 0, 0, 0, 0, 0}; le_uint16_t client_id = 0; parray unused2; } __packed__; @@ -6230,24 +6249,24 @@ struct G_EndInitialRedrawPhase_GC_Ep3_6xB3x0C_CAx0C { // current phase. This command isn't used for ending the attack or defense // phases; for those phases, CAx12 and CAx28 are used instead. -struct G_EndNonAttackPhase_GC_Ep3_6xB3x0D_CAx0D { - G_CardServerDataCommandHeader header = {0xB3, sizeof(G_EndNonAttackPhase_GC_Ep3_6xB3x0D_CAx0D) / 4, 0, 0x0D, 0, 0, 0, 0, 0}; +struct G_EndNonAttackPhase_Ep3_6xB3x0D_CAx0D { + G_CardServerDataCommandHeader header = {0xB3, sizeof(G_EndNonAttackPhase_Ep3_6xB3x0D_CAx0D) / 4, 0, 0x0D, 0, 0, 0, 0, 0}; le_uint16_t client_id = 0; parray unused2; } __packed__; // 6xB3x0E / CAx0E: Discard card from hand -struct G_DiscardCardFromHand_GC_Ep3_6xB3x0E_CAx0E { - G_CardServerDataCommandHeader header = {0xB3, sizeof(G_DiscardCardFromHand_GC_Ep3_6xB3x0E_CAx0E) / 4, 0, 0x0E, 0, 0, 0, 0, 0}; +struct G_DiscardCardFromHand_Ep3_6xB3x0E_CAx0E { + G_CardServerDataCommandHeader header = {0xB3, sizeof(G_DiscardCardFromHand_Ep3_6xB3x0E_CAx0E) / 4, 0, 0x0E, 0, 0, 0, 0, 0}; le_uint16_t client_id = 0; le_uint16_t card_ref = 0xFFFF; } __packed__; // 6xB3x0F / CAx0F: Set card from hand -struct G_SetCardFromHand_GC_Ep3_6xB3x0F_CAx0F { - G_CardServerDataCommandHeader header = {0xB3, sizeof(G_SetCardFromHand_GC_Ep3_6xB3x0F_CAx0F) / 4, 0, 0x0F, 0, 0, 0, 0, 0}; +struct G_SetCardFromHand_Ep3_6xB3x0F_CAx0F { + G_CardServerDataCommandHeader header = {0xB3, sizeof(G_SetCardFromHand_Ep3_6xB3x0F_CAx0F) / 4, 0, 0x0F, 0, 0, 0, 0, 0}; le_uint16_t client_id = 0; le_uint16_t card_ref = 0xFFFF; le_uint16_t set_index = 0; @@ -6257,8 +6276,8 @@ struct G_SetCardFromHand_GC_Ep3_6xB3x0F_CAx0F { // 6xB3x10 / CAx10: Move field character -struct G_MoveFieldCharacter_GC_Ep3_6xB3x10_CAx10 { - G_CardServerDataCommandHeader header = {0xB3, sizeof(G_MoveFieldCharacter_GC_Ep3_6xB3x10_CAx10) / 4, 0, 0x10, 0, 0, 0, 0, 0}; +struct G_MoveFieldCharacter_Ep3_6xB3x10_CAx10 { + G_CardServerDataCommandHeader header = {0xB3, sizeof(G_MoveFieldCharacter_Ep3_6xB3x10_CAx10) / 4, 0, 0x10, 0, 0, 0, 0, 0}; le_uint16_t client_id = 0; le_uint16_t set_index = 0; Episode3::Location loc; @@ -6270,8 +6289,8 @@ struct G_MoveFieldCharacter_GC_Ep3_6xB3x10_CAx10 { // sent once for each attack (even if it includes multiple cards); in the // defense case, this command is sent once for each defense card. -struct G_EnqueueAttackOrDefense_GC_Ep3_6xB3x11_CAx11 { - G_CardServerDataCommandHeader header = {0xB3, sizeof(G_EnqueueAttackOrDefense_GC_Ep3_6xB3x11_CAx11) / 4, 0, 0x11, 0, 0, 0, 0, 0}; +struct G_EnqueueAttackOrDefense_Ep3_6xB3x11_CAx11 { + G_CardServerDataCommandHeader header = {0xB3, sizeof(G_EnqueueAttackOrDefense_Ep3_6xB3x11_CAx11) / 4, 0, 0x11, 0, 0, 0, 0, 0}; le_uint16_t client_id = 0; parray unused2; Episode3::ActionState entry; @@ -6281,24 +6300,24 @@ struct G_EnqueueAttackOrDefense_GC_Ep3_6xB3x11_CAx11 { // This command informs the server that the client is done playing attacks in // the current round. (In the defense phase, CAx28 is used instead.) -struct G_EndAttackList_GC_Ep3_6xB3x12_CAx12 { - G_CardServerDataCommandHeader header = {0xB3, sizeof(G_EndAttackList_GC_Ep3_6xB3x12_CAx12) / 4, 0, 0x12, 0, 0, 0, 0, 0}; +struct G_EndAttackList_Ep3_6xB3x12_CAx12 { + G_CardServerDataCommandHeader header = {0xB3, sizeof(G_EndAttackList_Ep3_6xB3x12_CAx12) / 4, 0, 0x12, 0, 0, 0, 0, 0}; le_uint16_t client_id = 0; parray unused2; } __packed__; // 6xB3x13 / CAx13: Set map state during setup -struct G_SetMapState_GC_Ep3_6xB3x13_CAx13 { - G_CardServerDataCommandHeader header = {0xB3, sizeof(G_SetMapState_GC_Ep3_6xB3x13_CAx13) / 4, 0, 0x13, 0, 0, 0, 0, 0}; +struct G_SetMapState_Ep3_6xB3x13_CAx13 { + G_CardServerDataCommandHeader header = {0xB3, sizeof(G_SetMapState_Ep3_6xB3x13_CAx13) / 4, 0, 0x13, 0, 0, 0, 0, 0}; Episode3::MapAndRulesState map_and_rules_state; Episode3::OverlayState overlay_state; } __packed__; // 6xB3x14 / CAx14: Set player deck during setup -struct G_SetPlayerDeck_GC_Ep3_6xB3x14_CAx14 { - G_CardServerDataCommandHeader header = {0xB3, sizeof(G_SetPlayerDeck_GC_Ep3_6xB3x14_CAx14) / 4, 0, 0x14, 0, 0, 0, 0, 0}; +struct G_SetPlayerDeck_Ep3_6xB3x14_CAx14 { + G_CardServerDataCommandHeader header = {0xB3, sizeof(G_SetPlayerDeck_Ep3_6xB3x14_CAx14) / 4, 0, 0x14, 0, 0, 0, 0, 0}; le_uint16_t client_id = 0; uint8_t is_cpu_player = 0; uint8_t unused2 = 0; @@ -6308,16 +6327,16 @@ struct G_SetPlayerDeck_GC_Ep3_6xB3x14_CAx14 { // 6xB3x15 / CAx15: Hard-reset server state // This command appears to be completely unused; the client never sends it. -struct G_HardResetServerState_GC_Ep3_6xB3x15_CAx15 { - G_CardServerDataCommandHeader header = {0xB3, sizeof(G_HardResetServerState_GC_Ep3_6xB3x15_CAx15) / 4, 0, 0x15, 0, 0, 0, 0, 0}; +struct G_HardResetServerState_Ep3_6xB3x15_CAx15 { + G_CardServerDataCommandHeader header = {0xB3, sizeof(G_HardResetServerState_Ep3_6xB3x15_CAx15) / 4, 0, 0x15, 0, 0, 0, 0, 0}; // No arguments } __packed__; // 6xB5x17: Unknown // TODO: Document this from Episode 3 client/server disassembly -struct G_Unknown_GC_Ep3_6xB5x17 { - G_CardBattleCommandHeader header = {0xB5, sizeof(G_Unknown_GC_Ep3_6xB5x17) / 4, 0, 0x17, 0, 0, 0}; +struct G_Unknown_Ep3_6xB5x17 { + G_CardBattleCommandHeader header = {0xB5, sizeof(G_Unknown_Ep3_6xB5x17) / 4, 0, 0x17, 0, 0, 0}; // No arguments } __packed__; @@ -6328,8 +6347,8 @@ struct G_Unknown_GC_Ep3_6xB5x17 { // not save when it receives this command, and instead returns directly to the // main menu. -struct G_ForceDisconnect_GC_Ep3_6xB5x1A { - G_CardBattleCommandHeader header = {0xB5, sizeof(G_ForceDisconnect_GC_Ep3_6xB5x1A) / 4, 0, 0x1A, 0, 0, 0}; +struct G_ForceDisconnect_Ep3_6xB5x1A { + G_CardBattleCommandHeader header = {0xB5, sizeof(G_ForceDisconnect_Ep3_6xB5x1A) / 4, 0, 0x1A, 0, 0, 0}; // No arguments } __packed__; @@ -6337,15 +6356,15 @@ struct G_ForceDisconnect_GC_Ep3_6xB5x1A { // Curiously, this command can be used during a non-setup phase; the server // should ignore the command's contents but still send a 6xB4x1C in response. -struct G_SetPlayerName_GC_Ep3_6xB3x1B_CAx1B { - G_CardServerDataCommandHeader header = {0xB3, sizeof(G_SetPlayerName_GC_Ep3_6xB3x1B_CAx1B) / 4, 0, 0x1B, 0, 0, 0, 0, 0}; +struct G_SetPlayerName_Ep3_6xB3x1B_CAx1B { + G_CardServerDataCommandHeader header = {0xB3, sizeof(G_SetPlayerName_Ep3_6xB3x1B_CAx1B) / 4, 0, 0x1B, 0, 0, 0, 0, 0}; Episode3::NameEntry entry; } __packed__; // 6xB4x1C: Set all player names -struct G_SetPlayerNames_GC_Ep3_6xB4x1C { - G_CardBattleCommandHeader header = {0xB4, sizeof(G_SetPlayerNames_GC_Ep3_6xB4x1C) / 4, 0, 0x1C, 0, 0, 0}; +struct G_SetPlayerNames_Ep3_6xB4x1C { + G_CardBattleCommandHeader header = {0xB4, sizeof(G_SetPlayerNames_Ep3_6xB4x1C) / 4, 0, 0x1C, 0, 0, 0}; parray entries; } __packed__; @@ -6354,14 +6373,14 @@ struct G_SetPlayerNames_GC_Ep3_6xB4x1C { // response to this command) that includes RegistrationPhase::BATTLE_STARTED and // a SetupPhase value other than REGISTRATION. -struct G_StartBattle_GC_Ep3_6xB3x1D_CAx1D { - G_CardServerDataCommandHeader header = {0xB3, sizeof(G_StartBattle_GC_Ep3_6xB3x1D_CAx1D) / 4, 0, 0x1D, 0, 0, 0, 0, 0}; +struct G_StartBattle_Ep3_6xB3x1D_CAx1D { + G_CardServerDataCommandHeader header = {0xB3, sizeof(G_StartBattle_Ep3_6xB3x1D_CAx1D) / 4, 0, 0x1D, 0, 0, 0, 0, 0}; } __packed__; // 6xB4x1E: Action result -struct G_ActionResult_GC_Ep3_6xB4x1E { - /* 00 */ G_CardBattleCommandHeader header = {0xB4, sizeof(G_ActionResult_GC_Ep3_6xB4x1E) / 4, 0, 0x1E, 0, 0, 0}; +struct G_ActionResult_Ep3_6xB4x1E { + /* 00 */ G_CardBattleCommandHeader header = {0xB4, sizeof(G_ActionResult_Ep3_6xB4x1E) / 4, 0, 0x1E, 0, 0, 0}; /* 08 */ be_uint32_t sequence_num = 0; /* 0C */ uint8_t error_code = 0; /* 0D */ uint8_t response_phase = 0; @@ -6373,8 +6392,8 @@ struct G_ActionResult_GC_Ep3_6xB4x1E { // This token is sent back in the context_token field of all CA commands from // the client. It seems Sega never used this functionality. -struct G_SetContextToken_GC_Ep3_6xB4x1F { - G_CardBattleCommandHeader header = {0xB4, sizeof(G_SetContextToken_GC_Ep3_6xB4x1F) / 4, 0, 0x1F, 0, 0, 0}; +struct G_SetContextToken_Ep3_6xB4x1F { + G_CardBattleCommandHeader header = {0xB4, sizeof(G_SetContextToken_Ep3_6xB4x1F) / 4, 0, 0x1F, 0, 0, 0}; // Note that this field is little-endian, but the corresponding context_token // field in G_CardServerDataCommandHeader is big-endian! le_uint32_t context_token = 0; @@ -6383,8 +6402,8 @@ struct G_SetContextToken_GC_Ep3_6xB4x1F { // 6xB5x20: Unknown // TODO: Document this from Episode 3 client/server disassembly -struct G_Unknown_GC_Ep3_6xB5x20 { - G_CardBattleCommandHeader header = {0xB5, sizeof(G_Unknown_GC_Ep3_6xB5x20) / 4, 0, 0x20, 0, 0, 0}; +struct G_Unknown_Ep3_6xB5x20 { + G_CardBattleCommandHeader header = {0xB5, sizeof(G_Unknown_Ep3_6xB5x20) / 4, 0, 0x20, 0, 0, 0}; le_uint32_t player_tag = 0x00010000; le_uint32_t guild_card_number = 0; uint8_t client_id = 0; @@ -6393,8 +6412,8 @@ struct G_Unknown_GC_Ep3_6xB5x20 { // 6xB3x21 / CAx21: End battle -struct G_EndBattle_GC_Ep3_6xB3x21_CAx21 { - G_CardServerDataCommandHeader header = {0xB3, sizeof(G_EndBattle_GC_Ep3_6xB3x21_CAx21) / 4, 0, 0x21, 0, 0, 0, 0, 0}; +struct G_EndBattle_Ep3_6xB3x21_CAx21 { + G_CardServerDataCommandHeader header = {0xB3, sizeof(G_EndBattle_Ep3_6xB3x21_CAx21) / 4, 0, 0x21, 0, 0, 0, 0, 0}; le_uint32_t unused2 = 0; } __packed__; @@ -6403,8 +6422,8 @@ struct G_EndBattle_GC_Ep3_6xB3x21_CAx21 { // command sets a flag on some data structure if it exists, but it appears that // that data structure is never allocated. -struct G_Unknown_GC_Ep3_6xB4x22 { - G_CardBattleCommandHeader header = {0xB4, sizeof(G_Unknown_GC_Ep3_6xB4x22) / 4, 0, 0x22, 0, 0, 0}; +struct G_Unknown_Ep3_6xB4x22 { + G_CardBattleCommandHeader header = {0xB4, sizeof(G_Unknown_Ep3_6xB4x22) / 4, 0, 0x22, 0, 0, 0}; // No arguments } __packed__; @@ -6412,8 +6431,8 @@ struct G_Unknown_GC_Ep3_6xB4x22 { // This command was actually sent by Sega's original servers, but it does // nothing on the client. -struct G_Unknown_GC_Ep3_6xB4x23 { - G_CardBattleCommandHeader header = {0xB4, sizeof(G_Unknown_GC_Ep3_6xB4x23) / 4, 0, 0x23, 0, 0, 0}; +struct G_Unknown_Ep3_6xB4x23 { + G_CardBattleCommandHeader header = {0xB4, sizeof(G_Unknown_Ep3_6xB4x23) / 4, 0, 0x23, 0, 0, 0}; uint8_t present = 0; // Handler expects this to be equal to 1 uint8_t client_id = 0; parray unused; @@ -6422,8 +6441,8 @@ struct G_Unknown_GC_Ep3_6xB4x23 { // 6xB5x27: Unknown // TODO: Document this from Episode 3 client/server disassembly -struct G_Unknown_GC_Ep3_6xB5x27 { - G_CardBattleCommandHeader header = {0xB5, sizeof(G_Unknown_GC_Ep3_6xB5x27) / 4, 0, 0x27, 0, 0, 0}; +struct G_Unknown_Ep3_6xB5x27 { + G_CardBattleCommandHeader header = {0xB5, sizeof(G_Unknown_Ep3_6xB5x27) / 4, 0, 0x27, 0, 0, 0}; // Note: This command uses header_b1 as well, which looks like another client // ID (it must be < 4, though it does not always match unknown_a1 below). le_uint32_t unknown_a1 = 0; // Probably client ID (must be < 4) @@ -6436,8 +6455,8 @@ struct G_Unknown_GC_Ep3_6xB5x27 { // This command informs the server that the client is done playing defense // cards. (In the attack phase, CAx12 is used instead.) -struct G_EndDefenseList_GC_Ep3_6xB3x28_CAx28 { - G_CardServerDataCommandHeader header = {0xB3, sizeof(G_EndDefenseList_GC_Ep3_6xB3x28_CAx28) / 4, 0, 0x28, 0, 0, 0, 0, 0}; +struct G_EndDefenseList_Ep3_6xB3x28_CAx28 { + G_CardServerDataCommandHeader header = {0xB3, sizeof(G_EndDefenseList_Ep3_6xB3x28_CAx28) / 4, 0, 0x28, 0, 0, 0, 0, 0}; uint8_t unused1 = 0; uint8_t client_id = 0; parray unused2; @@ -6447,8 +6466,8 @@ struct G_EndDefenseList_GC_Ep3_6xB3x28_CAx28 { // TODO: How is this different from 6xB4x09? It looks like the server never // sends this. -struct G_SetActionState_GC_Ep3_6xB4x29 { - /* 00 */ G_CardBattleCommandHeader header = {0xB4, sizeof(G_SetActionState_GC_Ep3_6xB4x29) / 4, 0, 0x29, 0, 0, 0}; +struct G_SetActionState_Ep3_6xB4x29 { + /* 00 */ G_CardBattleCommandHeader header = {0xB4, sizeof(G_SetActionState_Ep3_6xB4x29) / 4, 0, 0x29, 0, 0, 0}; /* 08 */ uint8_t unknown_a1 = 0; /* 09 */ parray unknown_a2; /* 0C */ Episode3::ActionState state; @@ -6458,8 +6477,8 @@ struct G_SetActionState_GC_Ep3_6xB4x29 { // 6xB4x2A: Unknown // TODO: Document this from Episode 3 client/server disassembly -struct G_Unknown_GC_Ep3_6xB4x2A { - G_CardBattleCommandHeader header = {0xB4, sizeof(G_Unknown_GC_Ep3_6xB4x2A) / 4, 0, 0x2A, 0, 0, 0}; +struct G_Unknown_Ep3_6xB4x2A { + G_CardBattleCommandHeader header = {0xB4, sizeof(G_Unknown_Ep3_6xB4x2A) / 4, 0, 0x2A, 0, 0, 0}; parray unknown_a1; le_uint16_t unknown_a2 = 0; parray unused; @@ -6469,16 +6488,16 @@ struct G_Unknown_GC_Ep3_6xB4x2A { // It seems Sega's servers completely ignored this command. The command name is // based on a debug message found nearby. -struct G_ExecLegacyCard_GC_Ep3_6xB3x2B_CAx2B { - G_CardServerDataCommandHeader header = {0xB3, sizeof(G_ExecLegacyCard_GC_Ep3_6xB3x2B_CAx2B) / 4, 0, 0x2B, 0, 0, 0, 0, 0}; +struct G_ExecLegacyCard_Ep3_6xB3x2B_CAx2B { + G_CardServerDataCommandHeader header = {0xB3, sizeof(G_ExecLegacyCard_Ep3_6xB3x2B_CAx2B) / 4, 0, 0x2B, 0, 0, 0, 0, 0}; le_uint16_t unused2 = 0; parray unused3; } __packed__; // 6xB4x2C: Unknown -struct G_Unknown_GC_Ep3_6xB4x2C { - /* 00 */ G_CardBattleCommandHeader header = {0xB4, sizeof(G_Unknown_GC_Ep3_6xB4x2C) / 4, 0, 0x2C, 0, 0, 0}; +struct G_Unknown_Ep3_6xB4x2C { + /* 00 */ G_CardBattleCommandHeader header = {0xB4, sizeof(G_Unknown_Ep3_6xB4x2C) / 4, 0, 0x2C, 0, 0, 0}; /* 08 */ uint8_t change_type = 0; /* 09 */ uint8_t client_id = 0; /* 0A */ parray card_refs; @@ -6490,8 +6509,8 @@ struct G_Unknown_GC_Ep3_6xB4x2C { // 6xB5x2D: Unknown // TODO: Document this from Episode 3 client/server disassembly -struct G_Unknown_GC_Ep3_6xB5x2D { - G_CardBattleCommandHeader header = {0xB5, sizeof(G_Unknown_GC_Ep3_6xB5x2D) / 4, 0, 0x2D, 0, 0, 0}; +struct G_Unknown_Ep3_6xB5x2D { + G_CardBattleCommandHeader header = {0xB5, sizeof(G_Unknown_Ep3_6xB5x2D) / 4, 0, 0x2D, 0, 0, 0}; // This array is indexed by client ID. When a client receives this command, it // sends a 6x70 command to itself. It's not clear what the function of this is // intended to be. @@ -6502,16 +6521,16 @@ struct G_Unknown_GC_Ep3_6xB5x2D { // 6xB5x2E: Notify other players that battle is about to end -struct G_BattleEndNotification_GC_Ep3_6xB5x2E { - G_CardBattleCommandHeader header = {0xB5, sizeof(G_BattleEndNotification_GC_Ep3_6xB5x2E) / 4, 0, 0x2E, 0, 0, 0}; +struct G_BattleEndNotification_Ep3_6xB5x2E { + G_CardBattleCommandHeader header = {0xB5, sizeof(G_BattleEndNotification_Ep3_6xB5x2E) / 4, 0, 0x2E, 0, 0, 0}; uint8_t unknown_a1 = 0; // Command ignored unless this is 0 or 1 parray unused; } __packed__; // 6xB5x2F: Set deck in battle setup menu -struct G_SetDeckInBattleSetupMenu_GC_Ep3_6xB5x2F { - G_CardBattleCommandHeader header = {0xB5, sizeof(G_SetDeckInBattleSetupMenu_GC_Ep3_6xB5x2F) / 4, 0, 0x2F, 0, 0, 0}; +struct G_SetDeckInBattleSetupMenu_Ep3_6xB5x2F { + G_CardBattleCommandHeader header = {0xB5, sizeof(G_SetDeckInBattleSetupMenu_Ep3_6xB5x2F) / 4, 0, 0x2F, 0, 0, 0}; parray unknown_a1; parray unknown_a2; @@ -6529,15 +6548,15 @@ struct G_SetDeckInBattleSetupMenu_GC_Ep3_6xB5x2F { // The client never sends this command, and when the client received this // command, it does nothing. -struct G_Unknown_GC_Ep3_6xB5x30 { - G_CardBattleCommandHeader header = {0xB5, sizeof(G_Unknown_GC_Ep3_6xB5x30) / 4, 0, 0x30, 0, 0, 0}; +struct G_Unknown_Ep3_6xB5x30 { + G_CardBattleCommandHeader header = {0xB5, sizeof(G_Unknown_Ep3_6xB5x30) / 4, 0, 0x30, 0, 0, 0}; // No arguments } __packed__; // 6xB5x31: Confirm deck selection -struct G_ConfirmDeckSelection_GC_Ep3_6xB5x31 { - G_CardBattleCommandHeader header = {0xB5, sizeof(G_ConfirmDeckSelection_GC_Ep3_6xB5x31) / 4, 0, 0x31, 0, 0, 0}; +struct G_ConfirmDeckSelection_Ep3_6xB5x31 { + G_CardBattleCommandHeader header = {0xB5, sizeof(G_ConfirmDeckSelection_Ep3_6xB5x31) / 4, 0, 0x31, 0, 0, 0}; // Note: This command uses header_b1 for... something. uint8_t unknown_a1 = 0; // Must be 0 or 1 uint8_t unknown_a2 = 0; // Must be < 4 @@ -6549,8 +6568,8 @@ struct G_ConfirmDeckSelection_GC_Ep3_6xB5x31 { // 6xB5x32: Move shared menu cursor -struct G_MoveSharedMenuCursor_GC_Ep3_6xB5x32 { - G_CardBattleCommandHeader header = {0xB5, sizeof(G_MoveSharedMenuCursor_GC_Ep3_6xB5x32) / 4, 0, 0x32, 0, 0, 0}; +struct G_MoveSharedMenuCursor_Ep3_6xB5x32 { + G_CardBattleCommandHeader header = {0xB5, sizeof(G_MoveSharedMenuCursor_Ep3_6xB5x32) / 4, 0, 0x32, 0, 0, 0}; le_uint16_t selected_item_index = 0xFFFF; le_uint16_t chosen_item_index = 0xFFFF; uint8_t unknown_a1 = 0; @@ -6563,8 +6582,8 @@ struct G_MoveSharedMenuCursor_GC_Ep3_6xB5x32 { // 6xB4x33: Subtract ally ATK points (e.g. for photon blast) -struct G_SubtractAllyATKPoints_GC_Ep3_6xB4x33 { - /* 00 */ G_CardBattleCommandHeader header = {0xB4, sizeof(G_SubtractAllyATKPoints_GC_Ep3_6xB4x33) / 4, 0, 0x33, 0, 0, 0}; +struct G_SubtractAllyATKPoints_Ep3_6xB4x33 { + /* 00 */ G_CardBattleCommandHeader header = {0xB4, sizeof(G_SubtractAllyATKPoints_Ep3_6xB4x33) / 4, 0, 0x33, 0, 0, 0}; /* 08 */ uint8_t client_id = 0; /* 09 */ uint8_t ally_cost = 0; /* 0A */ le_uint16_t card_ref = 0xFFFF; @@ -6573,8 +6592,8 @@ struct G_SubtractAllyATKPoints_GC_Ep3_6xB4x33 { // 6xB3x34 / CAx34: Photon blast request -struct G_PhotonBlastRequest_GC_Ep3_6xB3x34_CAx34 { - G_CardServerDataCommandHeader header = {0xB3, sizeof(G_PhotonBlastRequest_GC_Ep3_6xB3x34_CAx34) / 4, 0, 0x34, 0, 0, 0, 0, 0}; +struct G_PhotonBlastRequest_Ep3_6xB3x34_CAx34 { + G_CardServerDataCommandHeader header = {0xB3, sizeof(G_PhotonBlastRequest_Ep3_6xB3x34_CAx34) / 4, 0, 0x34, 0, 0, 0, 0, 0}; uint8_t ally_client_id = 0; uint8_t reason = 0; le_uint16_t card_ref = 0xFFFF; @@ -6582,8 +6601,8 @@ struct G_PhotonBlastRequest_GC_Ep3_6xB3x34_CAx34 { // 6xB4x35: Update photon blast status -struct G_PhotonBlastStatus_GC_Ep3_6xB4x35 { - /* 00 */ G_CardBattleCommandHeader header = {0xB4, sizeof(G_PhotonBlastStatus_GC_Ep3_6xB4x35) / 4, 0, 0x35, 0, 0, 0}; +struct G_PhotonBlastStatus_Ep3_6xB4x35 { + /* 00 */ G_CardBattleCommandHeader header = {0xB4, sizeof(G_PhotonBlastStatus_Ep3_6xB4x35) / 4, 0, 0x35, 0, 0, 0}; /* 08 */ uint8_t client_id = 0; /* 09 */ uint8_t accepted = 0; /* 0A */ le_uint16_t card_ref = 0xFFFF; @@ -6597,16 +6616,16 @@ struct G_PhotonBlastStatus_GC_Ep3_6xB4x35 { // this predicament appear to be either to disconnect (e.g. select Quit Game // from the pause menu) or receive an ED (force leave game) command. -struct G_RecreatePlayer_GC_Ep3_6xB5x36 { - G_CardBattleCommandHeader header = {0xB5, sizeof(G_RecreatePlayer_GC_Ep3_6xB5x36) / 4, 0, 0x36, 0, 0, 0}; +struct G_RecreatePlayer_Ep3_6xB5x36 { + G_CardBattleCommandHeader header = {0xB5, sizeof(G_RecreatePlayer_Ep3_6xB5x36) / 4, 0, 0x36, 0, 0, 0}; uint8_t client_id = 0; parray unused; } __packed__; // 6xB3x37 / CAx37: Ready to advance from starting rolls phase -struct G_AdvanceFromStartingRollsPhase_GC_Ep3_6xB3x37_CAx37 { - G_CardServerDataCommandHeader header = {0xB3, sizeof(G_AdvanceFromStartingRollsPhase_GC_Ep3_6xB3x37_CAx37) / 4, 0, 0x37, 0, 0, 0, 0, 0}; +struct G_AdvanceFromStartingRollsPhase_Ep3_6xB3x37_CAx37 { + G_CardServerDataCommandHeader header = {0xB3, sizeof(G_AdvanceFromStartingRollsPhase_Ep3_6xB3x37_CAx37) / 4, 0, 0x37, 0, 0, 0, 0, 0}; uint8_t client_id = 0; parray unused2; } __packed__; @@ -6617,19 +6636,23 @@ struct G_AdvanceFromStartingRollsPhase_GC_Ep3_6xB3x37_CAx37 { // the 6D command). This appears to be unused; it is likely superseded by the // CAx49 command. -struct G_CardCountsRequest_GC_Ep3_6xB5x38 { - G_CardBattleCommandHeader header = {0xB5, sizeof(G_CardCountsRequest_GC_Ep3_6xB5x38) / 4, 0, 0x38, 0, 0, 0}; +struct G_CardCountsRequest_Ep3_6xB5x38 { + G_CardBattleCommandHeader header = {0xB5, sizeof(G_CardCountsRequest_Ep3_6xB5x38) / 4, 0, 0x38, 0, 0, 0}; uint8_t requested_client_id = 0; uint8_t reply_to_client_id = 0; parray unused; } __packed__; // 6xB4x39: Update all player statistics -// TODO: This command is different on Ep3 NTE because the PlayerBattleStats -// structure is different. Document the format here. -struct G_UpdateAllPlayerStatistics_GC_Ep3_6xB4x39 { - /* 00 */ G_CardBattleCommandHeader header = {0xB4, sizeof(G_UpdateAllPlayerStatistics_GC_Ep3_6xB4x39) / 4, 0, 0x39, 0, 0, 0}; +struct G_UpdateAllPlayerStatistics_Ep3NTE_6xB4x39 { + /* 00 */ G_CardBattleCommandHeader header = {0xB4, sizeof(G_UpdateAllPlayerStatistics_Ep3NTE_6xB4x39) / 4, 0, 0x39, 0, 0, 0}; + /* 08 */ parray stats; + /* 58 */ +} __packed__; + +struct G_UpdateAllPlayerStatistics_Ep3_6xB4x39 { + /* 00 */ G_CardBattleCommandHeader header = {0xB4, sizeof(G_UpdateAllPlayerStatistics_Ep3_6xB4x39) / 4, 0, 0x39, 0, 0, 0}; /* 08 */ parray stats; /* A8 */ } __packed__; @@ -6638,8 +6661,8 @@ struct G_UpdateAllPlayerStatistics_GC_Ep3_6xB4x39 { // It seems Sega's servers completely ignored this command and used server-side // timing instead. newserv does the same. -struct G_OverallTimeLimitExpired_GC_Ep3_6xB3x3A_CAx3A { - G_CardServerDataCommandHeader header = {0xB3, sizeof(G_OverallTimeLimitExpired_GC_Ep3_6xB3x3A_CAx3A) / 4, 0, 0x3A, 0, 0, 0, 0, 0}; +struct G_OverallTimeLimitExpired_Ep3_6xB3x3A_CAx3A { + G_CardServerDataCommandHeader header = {0xB3, sizeof(G_OverallTimeLimitExpired_Ep3_6xB3x3A_CAx3A) / 4, 0, 0x3A, 0, 0, 0, 0, 0}; } __packed__; // 6xB4x3B: Load current environment @@ -6647,17 +6670,17 @@ struct G_OverallTimeLimitExpired_GC_Ep3_6xB3x3A_CAx3A { // battle. A 6xB4x05 and 6xB6x41 command shouldhave been sent before this, to // set the map state that should appear for the new spectator. -struct G_LoadCurrentEnvironment_GC_Ep3_6xB4x3B { - G_CardBattleCommandHeader header = {0xB4, sizeof(G_LoadCurrentEnvironment_GC_Ep3_6xB4x3B) / 4, 0, 0x3B, 0, 0, 0}; +struct G_LoadCurrentEnvironment_Ep3_6xB4x3B { + G_CardBattleCommandHeader header = {0xB4, sizeof(G_LoadCurrentEnvironment_Ep3_6xB4x3B) / 4, 0, 0x3B, 0, 0, 0}; parray unused; } __packed__; // 6xB5x3C: Set player substatus // This command sets the text that appears under the player's name in the HUD. -struct G_SetPlayerSubstatus_GC_Ep3_6xB5x3C { +struct G_SetPlayerSubstatus_Ep3_6xB5x3C { // Note: header.sender_client_id specifies which client's status to update - G_CardBattleCommandHeader header = {0xB5, sizeof(G_SetPlayerSubstatus_GC_Ep3_6xB5x3C) / 4, 0, 0x3C, 0, 0, 0}; + G_CardBattleCommandHeader header = {0xB5, sizeof(G_SetPlayerSubstatus_Ep3_6xB5x3C) / 4, 0, 0x3C, 0, 0, 0}; // Status values: // 00 (or any value not listed below) = (nothing) // 01 = Editing @@ -6671,8 +6694,8 @@ struct G_SetPlayerSubstatus_GC_Ep3_6xB5x3C { // This is sent before the counter sequence in a tournament game, to reserve the // player and COM slots and set the map number. -struct G_SetTournamentPlayerDecks_GC_Ep3_6xB4x3D { - G_CardBattleCommandHeader header = {0xB4, sizeof(G_SetTournamentPlayerDecks_GC_Ep3_6xB4x3D) / 4, 0, 0x3D, 0, 0, 0}; +struct G_SetTournamentPlayerDecks_Ep3_6xB4x3D { + G_CardBattleCommandHeader header = {0xB4, sizeof(G_SetTournamentPlayerDecks_Ep3_6xB4x3D) / 4, 0, 0x3D, 0, 0, 0}; Episode3::Rules rules; struct Entry { uint8_t type = 0; // 0 = no player, 1 = human, 2 = COM @@ -6695,8 +6718,8 @@ struct G_SetTournamentPlayerDecks_GC_Ep3_6xB4x3D { // 6xB5x3E: Make card auction bid -struct G_MakeCardAuctionBid_GC_Ep3_6xB5x3E { - G_CardBattleCommandHeader header = {0xB5, sizeof(G_MakeCardAuctionBid_GC_Ep3_6xB5x3E) / 4, 0, 0x3E, 0, 0, 0}; +struct G_MakeCardAuctionBid_Ep3_6xB5x3E { + G_CardBattleCommandHeader header = {0xB5, sizeof(G_MakeCardAuctionBid_Ep3_6xB5x3E) / 4, 0, 0x3E, 0, 0, 0}; // Note: This command uses header.unknown_a1 for the bidder's client ID. uint8_t card_index = 0; // Index of card in EF command uint8_t bid_value = 0; // 1-99 @@ -6708,8 +6731,8 @@ struct G_MakeCardAuctionBid_GC_Ep3_6xB5x3E { // specified in .client_id is able to control the menu; the other clients see // that player's actions but cannot control anything. -struct G_OpenBlockingMenu_GC_Ep3_6xB5x3F { - G_CardBattleCommandHeader header = {0xB5, sizeof(G_OpenBlockingMenu_GC_Ep3_6xB5x3F) / 4, 0, 0x3F, 0, 0, 0}; +struct G_OpenBlockingMenu_Ep3_6xB5x3F { + G_CardBattleCommandHeader header = {0xB5, sizeof(G_OpenBlockingMenu_Ep3_6xB5x3F) / 4, 0, 0x3F, 0, 0, 0}; // Menu type should be one of these values: // 0x01/0x02 = battle prep menu // 0x11 = card auction counter menu (join or cancel) @@ -6725,16 +6748,16 @@ struct G_OpenBlockingMenu_GC_Ep3_6xB5x3F { // 6xB3x40 / CAx40: Request map list // The server should respond with a 6xB6x40 command. -struct G_MapListRequest_GC_Ep3_6xB3x40_CAx40 { - G_CardServerDataCommandHeader header = {0xB3, sizeof(G_MapListRequest_GC_Ep3_6xB3x40_CAx40) / 4, 0, 0x40, 0, 0, 0, 0, 0}; +struct G_MapListRequest_Ep3_6xB3x40_CAx40 { + G_CardServerDataCommandHeader header = {0xB3, sizeof(G_MapListRequest_Ep3_6xB3x40_CAx40) / 4, 0, 0x40, 0, 0, 0, 0, 0}; } __packed__; // 6xB3x41 / CAx41: Request map data // The server should respond with a 6xB6x41 command containing the definition of // the specified map. -struct G_MapDataRequest_GC_Ep3_6xB3x41_CAx41 { - G_CardServerDataCommandHeader header = {0xB3, sizeof(G_MapDataRequest_GC_Ep3_6xB3x41_CAx41) / 4, 0, 0x41, 0, 0, 0, 0, 0}; +struct G_MapDataRequest_Ep3_6xB3x41_CAx41 { + G_CardServerDataCommandHeader header = {0xB3, sizeof(G_MapDataRequest_Ep3_6xB3x41_CAx41) / 4, 0, 0x41, 0, 0, 0, 0, 0}; le_uint32_t map_number = 0; } __packed__; @@ -6748,8 +6771,8 @@ struct G_MapDataRequest_GC_Ep3_6xB3x41_CAx41 { // 2. The client is in a game with 4 players. // 3. All clients are at the auction counter. -struct G_InitiateCardAuction_GC_Ep3_6xB5x42 { - G_CardBattleCommandHeader header = {0xB5, sizeof(G_InitiateCardAuction_GC_Ep3_6xB5x42) / 4, 0, 0x42, 0, 0, 0}; +struct G_InitiateCardAuction_Ep3_6xB5x42 { + G_CardBattleCommandHeader header = {0xB5, sizeof(G_InitiateCardAuction_Ep3_6xB5x42) / 4, 0, 0x42, 0, 0, 0}; // This command uses header.unknown_a1 (probably for the client's ID). } __packed__; @@ -6759,8 +6782,8 @@ struct G_InitiateCardAuction_GC_Ep3_6xB5x42 { // unimplemented or removed feature, or an earlier implementation of the card // trade window. -struct G_Unknown_GC_Ep3_6xB5x43 { - G_CardBattleCommandHeader header = {0xB5, sizeof(G_Unknown_GC_Ep3_6xB5x43) / 4, 0, 0x43, 0, 0, 0}; +struct G_Unknown_Ep3_6xB5x43 { + G_CardBattleCommandHeader header = {0xB5, sizeof(G_Unknown_Ep3_6xB5x43) / 4, 0, 0x43, 0, 0, 0}; struct Entry { // Both fields here are masked. To get the actual values used by the game, // XOR the values here with 0x39AB. @@ -6772,16 +6795,16 @@ struct G_Unknown_GC_Ep3_6xB5x43 { // 6xB5x44: Card auction bid summary -struct G_CardAuctionBidSummary_GC_Ep3_6xB5x44 { - G_CardBattleCommandHeader header = {0xB5, sizeof(G_CardAuctionBidSummary_GC_Ep3_6xB5x44) / 4, 0, 0x44, 0, 0, 0}; +struct G_CardAuctionBidSummary_Ep3_6xB5x44 { + G_CardBattleCommandHeader header = {0xB5, sizeof(G_CardAuctionBidSummary_Ep3_6xB5x44) / 4, 0, 0x44, 0, 0, 0}; // Note: This command uses header.unknown_a1 for the bidder's client ID. parray bids; // In same order as cards in the EF command } __packed__; // 6xB5x45: Card auction results -struct G_CardAuctionResults_GC_Ep3_6xB5x45 { - G_CardBattleCommandHeader header = {0xB5, sizeof(G_CardAuctionResults_GC_Ep3_6xB5x45) / 4, 0, 0x45, 0, 0, 0}; +struct G_CardAuctionResults_Ep3_6xB5x45 { + G_CardBattleCommandHeader header = {0xB5, sizeof(G_CardAuctionResults_Ep3_6xB5x45) / 4, 0, 0x45, 0, 0, 0}; // Note: This command uses header.unknown_a1 for the sender's client ID. // This array is indexed by [card_index][client_id], and contains the final // bid for each player on each card (or 0 if they did not bid on that card). @@ -6797,8 +6820,8 @@ struct G_CardAuctionResults_GC_Ep3_6xB5x45 { // recreating the TCardServer object (implemented in newserv's Episode3::Server) // in order to support multiple sequential battles in the same team. -struct G_ServerVersionStrings_GC_Ep3_6xB4x46 { - G_CardBattleCommandHeader header = {0xB4, sizeof(G_ServerVersionStrings_GC_Ep3_6xB4x46) / 4, 0, 0x46, 0, 0, 0}; +struct G_ServerVersionStrings_Ep3_6xB4x46 { + G_CardBattleCommandHeader header = {0xB4, sizeof(G_ServerVersionStrings_Ep3_6xB4x46) / 4, 0, 0x46, 0, 0, 0}; // In all of the examples (from Sega's servers) that I've seen of this // command, these fields have the following values: // version_signature = "[V1][FINAL2.0] 03/09/13 15:30 by K.Toya" @@ -6823,8 +6846,8 @@ struct G_ServerVersionStrings_GC_Ep3_6xB4x46 { le_uint32_t unused = 0; } __packed__; -struct G_ServerVersionStrings_GC_Ep3_NTE_6xB4x46 { - G_CardBattleCommandHeader header = {0xB4, sizeof(G_ServerVersionStrings_GC_Ep3_NTE_6xB4x46) / 4, 0, 0x46, 0, 0, 0}; +struct G_ServerVersionStrings_Ep3NTE_6xB4x46 { + G_CardBattleCommandHeader header = {0xB4, sizeof(G_ServerVersionStrings_Ep3NTE_6xB4x46) / 4, 0, 0x46, 0, 0, 0}; // Ep3 NTE uses the following strings: // "03/05/29 18:00 by K.Toya" pstring version_signature; @@ -6835,15 +6858,15 @@ struct G_ServerVersionStrings_GC_Ep3_NTE_6xB4x46 { // 6xB5x47: Set spectator's CARD level // header.sender_client_id is the spectator's client ID. -struct G_SetSpectatorCARDLevel_GC_Ep3_6xB5x47 { - G_CardBattleCommandHeader header = {0xB5, sizeof(G_SetSpectatorCARDLevel_GC_Ep3_6xB5x47) / 4, 0, 0x47, 0, 0, 0}; +struct G_SetSpectatorCARDLevel_Ep3_6xB5x47 { + G_CardBattleCommandHeader header = {0xB5, sizeof(G_SetSpectatorCARDLevel_Ep3_6xB5x47) / 4, 0, 0x47, 0, 0, 0}; le_uint32_t clv = 0; } __packed__; // 6xB3x48 / CAx48: End turn -struct G_EndTurn_GC_Ep3_6xB3x48_CAx48 { - G_CardServerDataCommandHeader header = {0xB3, sizeof(G_EndTurn_GC_Ep3_6xB3x48_CAx48) / 4, 0, 0x48, 0, 0, 0, 0, 0}; +struct G_EndTurn_Ep3_6xB3x48_CAx48 { + G_CardServerDataCommandHeader header = {0xB3, sizeof(G_EndTurn_Ep3_6xB3x48_CAx48) / 4, 0, 0x48, 0, 0, 0, 0, 0}; uint8_t client_id = 0; parray unused2; } __packed__; @@ -6860,8 +6883,8 @@ struct G_EndTurn_GC_Ep3_6xB3x48_CAx48 { // that callsite to implement one of the deck validity checks. // Episode 3 Trial Edition does not send this command. -struct G_CardCounts_GC_Ep3_6xB3x49_CAx49 { - G_CardServerDataCommandHeader header = {0xB3, sizeof(G_CardCounts_GC_Ep3_6xB3x49_CAx49) / 4, 0, 0x49, 0, 0, 0, 0, 0}; +struct G_CardCounts_Ep3_6xB3x49_CAx49 { + G_CardServerDataCommandHeader header = {0xB3, sizeof(G_CardCounts_Ep3_6xB3x49_CAx49) / 4, 0, 0x49, 0, 0, 0, 0, 0}; uint8_t basis = 0; parray unused; // This is encrypted with the trivial algorithm (see decrypt_trivial_gci_data) @@ -6873,8 +6896,8 @@ struct G_CardCounts_GC_Ep3_6xB3x49_CAx49 { // This command is not valid on Episode 3 Trial Edition. // TODO: Document this from Episode 3 client/server disassembly -struct G_Unknown_GC_Ep3_6xB4x4A { - G_CardBattleCommandHeader header = {0xB4, sizeof(G_Unknown_GC_Ep3_6xB4x4A) / 4, 0, 0x4A, 0, 0, 0}; +struct G_Unknown_Ep3_6xB4x4A { + G_CardBattleCommandHeader header = {0xB4, sizeof(G_Unknown_Ep3_6xB4x4A) / 4, 0, 0x4A, 0, 0, 0}; // Note: entry_count appears not to be bounds-checked; presumably the server // could send up to 0xFF entries, but those after the 8th would not be // byteswapped before the client handles them. @@ -6907,8 +6930,8 @@ struct G_Unknown_GC_Ep3_6xB4x4A { // lose_entries = {1, 0}, {-2, 0}, {-3, 0}, {-4, 0}, {-5, 0}, {-6, 0}, {-7, 0}, // {-10, -10}, {-30, -10}, {0, -15} -struct G_SetEXResultValues_GC_Ep3_6xB4x4B { - G_CardBattleCommandHeader header = {0xB4, sizeof(G_SetEXResultValues_GC_Ep3_6xB4x4B) / 4, 0, 0x4B, 0, 0, 0}; +struct G_SetEXResultValues_Ep3_6xB4x4B { + G_CardBattleCommandHeader header = {0xB4, sizeof(G_SetEXResultValues_Ep3_6xB4x4B) / 4, 0, 0x4B, 0, 0, 0}; struct Entry { le_int16_t threshold = 0; le_int16_t value = 0; @@ -6920,8 +6943,8 @@ struct G_SetEXResultValues_GC_Ep3_6xB4x4B { // 6xB4x4C: Update action chain // This command is not valid on Episode 3 Trial Edition. -struct G_UpdateActionChain_GC_Ep3_6xB4x4C { - G_CardBattleCommandHeader header = {0xB4, sizeof(G_UpdateActionChain_GC_Ep3_6xB4x4C) / 4, 0, 0x4C, 0, 0, 0}; +struct G_UpdateActionChain_Ep3_6xB4x4C { + G_CardBattleCommandHeader header = {0xB4, sizeof(G_UpdateActionChain_Ep3_6xB4x4C) / 4, 0, 0x4C, 0, 0, 0}; uint8_t client_id = 0; int8_t index = 0; parray unused; @@ -6931,8 +6954,8 @@ struct G_UpdateActionChain_GC_Ep3_6xB4x4C { // 6xB4x4D: Update action metadata // This command is not valid on Episode 3 Trial Edition. -struct G_UpdateActionMetadata_GC_Ep3_6xB4x4D { - G_CardBattleCommandHeader header = {0xB4, sizeof(G_UpdateActionMetadata_GC_Ep3_6xB4x4D) / 4, 0, 0x4D, 0, 0, 0}; +struct G_UpdateActionMetadata_Ep3_6xB4x4D { + G_CardBattleCommandHeader header = {0xB4, sizeof(G_UpdateActionMetadata_Ep3_6xB4x4D) / 4, 0, 0x4D, 0, 0, 0}; uint8_t client_id = 0; int8_t index = 0; parray unused; @@ -6942,8 +6965,8 @@ struct G_UpdateActionMetadata_GC_Ep3_6xB4x4D { // 6xB4x4E: Update card conditions // This command is not valid on Episode 3 Trial Edition. -struct G_UpdateCardConditions_GC_Ep3_6xB4x4E { - G_CardBattleCommandHeader header = {0xB4, sizeof(G_UpdateCardConditions_GC_Ep3_6xB4x4E) / 4, 0, 0x4E, 0, 0, 0}; +struct G_UpdateCardConditions_Ep3_6xB4x4E { + G_CardBattleCommandHeader header = {0xB4, sizeof(G_UpdateCardConditions_Ep3_6xB4x4E) / 4, 0, 0x4E, 0, 0, 0}; uint8_t client_id = 0; int8_t index = 0; parray unused; @@ -6953,8 +6976,8 @@ struct G_UpdateCardConditions_GC_Ep3_6xB4x4E { // 6xB4x4F: Clear set card conditions // This command is not valid on Episode 3 Trial Edition. -struct G_ClearSetCardConditions_GC_Ep3_6xB4x4F { - G_CardBattleCommandHeader header = {0xB4, sizeof(G_ClearSetCardConditions_GC_Ep3_6xB4x4F) / 4, 0, 0x4F, 0, 0, 0}; +struct G_ClearSetCardConditions_Ep3_6xB4x4F { + G_CardBattleCommandHeader header = {0xB4, sizeof(G_ClearSetCardConditions_Ep3_6xB4x4F) / 4, 0, 0x4F, 0, 0, 0}; uint8_t client_id = 0; uint8_t unused = 0; // For each 1 bit in this mask, the conditions of the corresponding card @@ -6967,8 +6990,8 @@ struct G_ClearSetCardConditions_GC_Ep3_6xB4x4F { // 6xB4x50: Set trap tile locations // This command is not valid on Episode 3 Trial Edition. -struct G_SetTrapTileLocations_GC_Ep3_6xB4x50 { - G_CardBattleCommandHeader header = {0xB4, sizeof(G_SetTrapTileLocations_GC_Ep3_6xB4x50) / 4, 0, 0x50, 0, 0, 0}; +struct G_SetTrapTileLocations_Ep3_6xB4x50 { + G_CardBattleCommandHeader header = {0xB4, sizeof(G_SetTrapTileLocations_Ep3_6xB4x50) / 4, 0, 0x50, 0, 0, 0}; // Each entry in this array corresponds to one of the 5 trap types, in order. // Each entry is an [x, y] pair; if that trap type is not present, its // location entry is FF FF. @@ -6983,8 +7006,8 @@ struct G_SetTrapTileLocations_GC_Ep3_6xB4x50 { // the StateFlags struct), then it will use this information to show the // tournament match result screen before the battle results screen. -struct G_TournamentMatchResult_GC_Ep3_6xB4x51 { - G_CardBattleCommandHeader header = {0xB4, sizeof(G_TournamentMatchResult_GC_Ep3_6xB4x51) / 4, 0, 0x51, 0, 0, 0}; +struct G_TournamentMatchResult_Ep3_6xB4x51 { + G_CardBattleCommandHeader header = {0xB4, sizeof(G_TournamentMatchResult_Ep3_6xB4x51) / 4, 0, 0x51, 0, 0, 0}; pstring match_description; struct NamesEntry { pstring team_name; @@ -7010,8 +7033,8 @@ struct G_TournamentMatchResult_GC_Ep3_6xB4x51 { // This is sent to all players in a game and all attached spectator teams when // any player joins or leaves any spectator team watching the same game. -struct G_SetGameMetadata_GC_Ep3_6xB4x52 { - G_CardBattleCommandHeader header = {0xB4, sizeof(G_SetGameMetadata_GC_Ep3_6xB4x52) / 4, 0, 0x52, 0, 0, 0}; +struct G_SetGameMetadata_Ep3_6xB4x52 { + G_CardBattleCommandHeader header = {0xB4, sizeof(G_SetGameMetadata_Ep3_6xB4x52) / 4, 0, 0x52, 0, 0, 0}; // This field appears before the slash in the spectators' HUD. Presumably this // is used to indicate how many spectators are in the current spectator team. // In the primary game (watched lobby), this is presumably unused. @@ -7045,8 +7068,8 @@ struct G_SetGameMetadata_GC_Ep3_6xB4x52 { // Note: It seems the client ignores everything in this structure; the command // handler just sets a global state flag and returns immediately. -struct G_RejectBattleStartRequest_GC_Ep3_6xB4x53 { - G_CardBattleCommandHeader header = {0xB4, sizeof(G_RejectBattleStartRequest_GC_Ep3_6xB4x53) / 4, 0, 0x53, 0, 0, 0}; +struct G_RejectBattleStartRequest_Ep3_6xB4x53 { + G_CardBattleCommandHeader header = {0xB4, sizeof(G_RejectBattleStartRequest_Ep3_6xB4x53) / 4, 0, 0x53, 0, 0, 0}; Episode3::SetupPhase setup_phase; Episode3::RegistrationPhase registration_phase; parray unused; diff --git a/src/Episode3/AssistServer.cc b/src/Episode3/AssistServer.cc index 00c57a13..86b16a67 100644 --- a/src/Episode3/AssistServer.cc +++ b/src/Episode3/AssistServer.cc @@ -6,21 +6,38 @@ using namespace std; namespace Episode3 { -// Note: This order matches the order that the cards are defined in the original -// code. This is relevant for consistency of results when choosing a random card -// (for God Whim). -const vector ALL_ASSIST_CARD_IDS = { - 0x0018, 0x0019, 0x001A, 0x00F5, 0x00F6, 0x00F7, 0x00F8, 0x00F9, 0x00FA, - 0x00FB, 0x00FC, 0x00FD, 0x00FE, 0x00FF, 0x0100, 0x0101, 0x0102, 0x0103, - 0x0104, 0x0105, 0x0106, 0x0107, 0x0108, 0x0109, 0x010A, 0x010B, 0x010C, - 0x010D, 0x010E, 0x010F, 0x0121, 0x0125, 0x0126, 0x0127, 0x0128, 0x0129, - 0x012A, 0x012B, 0x012C, 0x012D, 0x012E, 0x012F, 0x0130, 0x0131, 0x0132, - 0x0133, 0x0134, 0x0135, 0x0136, 0x0137, 0x0138, 0x0139, 0x013A, 0x013B, - 0x013C, 0x013D, 0x013E, 0x013F, 0x0140, 0x0141, 0x0142, 0x0143, 0x0144, - 0x0145, 0x0146, 0x0148, 0x014A, 0x014B, 0x014C, 0x014D, 0x014E, 0x023F, - 0x0240, 0x0241, 0x0242}; +const vector& all_assist_card_ids(bool is_trial) { + // Note: This order matches the order that the cards are defined in the original + // code. This is relevant for consistency of results when choosing a random card + // (for God Whim). + static const vector ALL_ASSIST_CARD_IDS_TRIAL = { + 0x00F5, 0x00F6, 0x00F7, 0x00F8, 0x00F9, 0x00FA, 0x00FB, 0x00FC, 0x00FD, + 0x00FE, 0x00FF, 0x0100, 0x0101, 0x0102, 0x0103, 0x0104, 0x0105, 0x0106, + 0x0107, 0x0108, 0x0109, 0x010A, 0x010B, 0x010C, 0x010D, 0x010E, 0x010F, + 0x0121, 0x0125, 0x0126, 0x0127, 0x0128, 0x0129, 0x012A, 0x012B, 0x012C, + 0x012D, 0x012E, 0x012F, 0x0130, 0x0131, 0x0132, 0x0133, 0x0134, 0x0135, + 0x0136, 0x0137, 0x0138, 0x0139, 0x013A, 0x013B, 0x013C, 0x013D, 0x013E, + 0x013F, 0x0140, 0x0141, 0x0142, 0x0143, 0x0144, 0x0145, 0x0146, 0x0148, + 0x014A, 0x014B, 0x014C, 0x014D, 0x014E, 0x023F, 0x0240, 0x0241, 0x0242}; + static const vector ALL_ASSIST_CARD_IDS_FINAL = { + 0x0018, 0x0019, 0x001A, 0x00F5, 0x00F6, 0x00F7, 0x00F8, 0x00F9, 0x00FA, + 0x00FB, 0x00FC, 0x00FD, 0x00FE, 0x00FF, 0x0100, 0x0101, 0x0102, 0x0103, + 0x0104, 0x0105, 0x0106, 0x0107, 0x0108, 0x0109, 0x010A, 0x010B, 0x010C, + 0x010D, 0x010E, 0x010F, 0x0121, 0x0125, 0x0126, 0x0127, 0x0128, 0x0129, + 0x012A, 0x012B, 0x012C, 0x012D, 0x012E, 0x012F, 0x0130, 0x0131, 0x0132, + 0x0133, 0x0134, 0x0135, 0x0136, 0x0137, 0x0138, 0x0139, 0x013A, 0x013B, + 0x013C, 0x013D, 0x013E, 0x013F, 0x0140, 0x0141, 0x0142, 0x0143, 0x0144, + 0x0145, 0x0146, 0x0148, 0x014A, 0x014B, 0x014C, 0x014D, 0x014E, 0x023F, + 0x0240, 0x0241, 0x0242}; + return is_trial ? ALL_ASSIST_CARD_IDS_TRIAL : ALL_ASSIST_CARD_IDS_FINAL; +} -AssistEffect assist_effect_number_for_card_id(uint16_t card_id) { +AssistEffect assist_effect_number_for_card_id(uint16_t card_id, bool is_trial) { + static const unordered_map card_id_to_effect_final_only({ + {0x0018, /* 0x0049 */ AssistEffect::DICE_FEVER_PLUS}, + {0x0019, /* 0x004A */ AssistEffect::RICH_PLUS}, + {0x001A, /* 0x004B */ AssistEffect::CHARITY_PLUS}, + }); static const unordered_map card_id_to_effect({ {0x00F5, /* 0x0001 */ AssistEffect::DICE_HALF}, {0x00F6, /* 0x0002 */ AssistEffect::DICE_PLUS_1}, @@ -94,15 +111,18 @@ AssistEffect assist_effect_number_for_card_id(uint16_t card_id) { {0x0240, /* 0x0046 */ AssistEffect::BOMB}, {0x0241, /* 0x0047 */ AssistEffect::SKIP_TURN}, {0x0242, /* 0x0048 */ AssistEffect::BATTLE_ROYALE}, - {0x0018, /* 0x0049 */ AssistEffect::DICE_FEVER_PLUS}, - {0x0019, /* 0x004A */ AssistEffect::RICH_PLUS}, - {0x001A, /* 0x004B */ AssistEffect::CHARITY_PLUS}, }); try { return card_id_to_effect.at(card_id); } catch (const out_of_range&) { - return AssistEffect::NONE; } + if (!is_trial) { + try { + return card_id_to_effect_final_only.at(card_id); + } catch (const out_of_range&) { + } + } + return AssistEffect::NONE; } AssistServer::AssistServer(shared_ptr server) @@ -224,6 +244,7 @@ AssistEffect AssistServer::get_active_assist_by_index(size_t index) const { } void AssistServer::populate_effects() { + bool is_trial = this->server()->options.is_trial(); for (size_t z = 0; z < 4; z++) { this->assist_card_defs[z] = nullptr; this->assist_effects[z] = AssistEffect::NONE; @@ -232,7 +253,7 @@ void AssistServer::populate_effects() { uint16_t card_id = hes->assist_card_id == 0xFFFF ? this->card_id_for_card_ref(hes->assist_card_ref) : hes->assist_card_id.load(); - this->assist_effects[z] = assist_effect_number_for_card_id(card_id); + this->assist_effects[z] = assist_effect_number_for_card_id(card_id, is_trial); if (this->assist_effects[z] != AssistEffect::NONE) { this->assist_card_defs[z] = this->definition_for_card_id(card_id); } diff --git a/src/Episode3/AssistServer.hh b/src/Episode3/AssistServer.hh index dd42b8fa..eb2fe454 100644 --- a/src/Episode3/AssistServer.hh +++ b/src/Episode3/AssistServer.hh @@ -13,9 +13,8 @@ namespace Episode3 { class Server; -extern const std::vector ALL_ASSIST_CARD_IDS; - -AssistEffect assist_effect_number_for_card_id(uint16_t card_id); +const std::vector& all_assist_card_ids(bool is_trial); +AssistEffect assist_effect_number_for_card_id(uint16_t card_id, bool is_trial); class AssistServer { public: diff --git a/src/Episode3/BattleRecord.cc b/src/Episode3/BattleRecord.cc index 519dfa14..3c9d2d21 100644 --- a/src/Episode3/BattleRecord.cc +++ b/src/Episode3/BattleRecord.cc @@ -191,7 +191,7 @@ bool BattleRecord::is_map_definition_event(const Event& ev) { if (ev.type == Event::Type::BATTLE_COMMAND) { auto& header = check_size_t(ev.data, 0xFFFF); if (header.subcommand == 0xB6) { - auto& header = check_size_t(ev.data, 0xFFFF); + auto& header = check_size_t(ev.data, 0xFFFF); if (header.subsubcommand == 0x41) { return true; } diff --git a/src/Episode3/Card.cc b/src/Episode3/Card.cc index a9cdc483..ebc85a5c 100644 --- a/src/Episode3/Card.cc +++ b/src/Episode3/Card.cc @@ -31,9 +31,12 @@ Card::Card( current_defense_power(0) {} void Card::init() { + auto s = this->server(); + auto ps = this->player_state(); + this->clear_action_chain_and_metadata_and_most_flags(); - this->team_id = this->player_state()->get_team_id(); - this->def_entry = this->server()->definition_for_card_id(this->card_id); + this->team_id = ps->get_team_id(); + this->def_entry = s->definition_for_card_id(this->card_id); if (!this->def_entry) { // The original implementation replaces the card ID and definition with 0009 // (Saber) if the SC is Hunters-side, and 0056 (Booma) if the SC is @@ -42,27 +45,39 @@ void Card::init() { // prevent it instead. throw runtime_error("card definition is missing"); } - this->sc_card_ref = this->player_state()->get_sc_card_ref(); - this->sc_def_entry = this->server()->definition_for_card_id( - this->player_state()->get_sc_card_id()); - this->sc_card_type = this->player_state()->get_sc_card_type(); - this->max_hp = this->def_entry->def.hp.stat; - this->current_hp = this->def_entry->def.hp.stat; + this->sc_card_ref = ps->get_sc_card_ref(); + this->sc_def_entry = s->definition_for_card_id(ps->get_sc_card_id()); + this->sc_card_type = ps->get_sc_card_type(); if (this->sc_card_ref == this->card_ref) { - int16_t rules_char_hp = this->server()->map_and_rules->rules.char_hp; - int16_t base_char_hp = (rules_char_hp == 0) ? 15 : rules_char_hp; - int16_t hp = clamp(base_char_hp + this->def_entry->def.hp.stat, 1, 99); - this->max_hp = hp; - this->current_hp = hp; + if (s->options.is_trial()) { + if (s->map_and_rules->rules.char_hp) { + this->max_hp = s->map_and_rules->rules.char_hp; + this->current_hp = s->map_and_rules->rules.char_hp; + } else { + this->max_hp = this->def_entry->def.hp.stat; + this->current_hp = this->def_entry->def.hp.stat; + } + } else { + int16_t rules_char_hp = s->map_and_rules->rules.char_hp; + int16_t base_char_hp = (rules_char_hp == 0) ? 15 : rules_char_hp; + int16_t hp = clamp(base_char_hp + this->def_entry->def.hp.stat, 1, 99); + this->max_hp = hp; + this->current_hp = hp; + } + } else { + this->max_hp = this->def_entry->def.hp.stat; + this->current_hp = this->def_entry->def.hp.stat; } this->ap = this->def_entry->def.ap.stat; this->tp = this->def_entry->def.tp.stat; - this->num_ally_fcs_destroyed_at_set_time = this->server()->team_num_ally_fcs_destroyed[this->team_id]; - this->num_cards_destroyed_by_team_at_set_time = this->server()->team_num_cards_destroyed[this->team_id]; + this->num_ally_fcs_destroyed_at_set_time = s->team_num_ally_fcs_destroyed[this->team_id]; + this->num_cards_destroyed_by_team_at_set_time = s->team_num_cards_destroyed[this->team_id]; this->action_chain.chain.card_ap = this->ap; this->action_chain.chain.card_tp = this->tp; - this->loc.direction = this->player_state()->start_facing_direction; - if (this->sc_card_ref != this->card_ref) { + this->loc.direction = ps->start_facing_direction; + // Ep3 NTE always sends 6xB4x0A at construction time; final only does for + // non-SC cards + if (s->options.is_trial() || (this->sc_card_ref != this->card_ref)) { this->send_6xB4x4E_4C_4D_if_needed(); } } @@ -245,7 +260,7 @@ bool Card::check_card_flag(uint32_t flags) const { void Card::commit_attack( int16_t damage, shared_ptr attacker_card, - G_ApplyConditionEffect_GC_Ep3_6xB4x06* cmd, + G_ApplyConditionEffect_Ep3_6xB4x06* cmd, size_t strike_number, int16_t* out_effective_damage) { auto log = this->server()->log_stack(string_printf("commit_attack(@%04hX #%04hX, @%04hX #%04hX => %hd (str%zu)): ", this->get_card_ref(), this->get_card_id(), attacker_card->get_card_ref(), attacker_card->get_card_id(), damage, strike_number)); @@ -303,7 +318,7 @@ void Card::commit_attack( log.debug("card destroyed"); } - G_ApplyConditionEffect_GC_Ep3_6xB4x06 cmd_to_send; + G_ApplyConditionEffect_Ep3_6xB4x06 cmd_to_send; if (cmd) { cmd_to_send = *cmd; } @@ -376,7 +391,7 @@ void Card::destroy_set_card(shared_ptr attacker_card) { sc_card->set_current_hp(hp - 1); sc_card->player_state()->stats.sc_damage_taken++; if (attacker_card && (attacker_card->team_id != this->team_id)) { - G_ApplyConditionEffect_GC_Ep3_6xB4x06 cmd; + G_ApplyConditionEffect_Ep3_6xB4x06 cmd; cmd.effect.flags = 0x41; cmd.effect.attacker_card_ref = attacker_card->card_ref; cmd.effect.target_card_ref = sc_card->card_ref; @@ -465,7 +480,7 @@ void Card::execute_attack(shared_ptr attacker_card) { log.debug("ap != 0 or flag 0x20 set; continuing..."); } - G_ApplyConditionEffect_GC_Ep3_6xB4x06 cmd; + G_ApplyConditionEffect_Ep3_6xB4x06 cmd; cmd.effect.flags = 0x01; cmd.effect.attacker_card_ref = attacker_card->card_ref; cmd.effect.target_card_ref = this->card_ref; @@ -618,7 +633,7 @@ int32_t Card::move_to_location(const Location& loc) { for (size_t warp_end = 0; warp_end < 2; warp_end++) { if ((this->server()->warp_positions[warp_type][warp_end][0] == this->loc.x) && (this->server()->warp_positions[warp_type][warp_end][1] == this->loc.y)) { - G_Unknown_GC_Ep3_6xB4x2C cmd; + G_Unknown_Ep3_6xB4x2C cmd; cmd.loc.x = this->loc.x; cmd.loc.y = this->loc.y; this->loc.x = this->server()->warp_positions[warp_type][warp_end ^ 1][0]; @@ -650,12 +665,14 @@ void Card::propagate_shared_hp_if_needed() { } void Card::send_6xB4x4E_4C_4D_if_needed(bool always_send) { + auto ps = this->player_state(); + ssize_t index = -1; - if (this->card_ref == this->player_state()->get_sc_card_ref()) { + if (this->card_ref == ps->get_sc_card_ref()) { index = 0; } else { for (size_t set_index = 0; set_index < 8; set_index++) { - if (this->card_ref == this->player_state()->get_set_ref(set_index)) { + if (this->card_ref == ps->get_set_ref(set_index)) { index = set_index + 1; break; } @@ -668,28 +685,44 @@ void Card::send_6xB4x4E_4C_4D_if_needed(bool always_send) { this->action_chain.chain.card_ap = this->ap; this->action_chain.chain.card_tp = this->tp; - this->send_6xB4x4E_if_needed(always_send); - auto& chain = this->player_state()->set_card_action_chains->at(index); - if (always_send || (chain != this->action_chain)) { + auto& chain = ps->set_card_action_chains->at(index); + auto& metadata = ps->set_card_action_metadatas->at(index); + + auto s = this->server(); + if (s->options.is_trial()) { chain = this->action_chain; - if (!this->server()->get_should_copy_prev_states_to_current_states()) { - G_UpdateActionChain_GC_Ep3_6xB4x4C cmd; - cmd.client_id = this->client_id; - cmd.index = index; - cmd.chain = this->action_chain.chain; - this->server()->send(cmd); - } - } - - auto& metadata = this->player_state()->set_card_action_metadatas->at(index); - if (always_send || (metadata != this->action_metadata)) { metadata = this->action_metadata; - G_UpdateActionMetadata_GC_Ep3_6xB4x4D cmd; + + G_UpdateActionChainAndMetadata_Ep3NTE_6xB4x0A cmd; cmd.client_id = this->client_id; cmd.index = index; + cmd.chain = this->action_chain; cmd.metadata = this->action_metadata; - this->server()->send(cmd); + s->send(cmd); + + } else { + this->send_6xB4x4E_if_needed(always_send); + + if (always_send || (chain != this->action_chain)) { + chain = this->action_chain; + if (!s->get_should_copy_prev_states_to_current_states()) { + G_UpdateActionChain_Ep3_6xB4x4C cmd; + cmd.client_id = this->client_id; + cmd.index = index; + cmd.chain = this->action_chain.chain; + s->send(cmd); + } + } + + if (always_send || (metadata != this->action_metadata)) { + metadata = this->action_metadata; + G_UpdateActionMetadata_Ep3_6xB4x4D cmd; + cmd.client_id = this->client_id; + cmd.index = index; + cmd.metadata = this->action_metadata; + s->send(cmd); + } } } @@ -712,7 +745,7 @@ void Card::send_6xB4x4E_if_needed(bool always_send) { if ((prev_conds != curr_conds) || (always_send != 0)) { prev_conds = curr_conds; if (!this->server()->get_should_copy_prev_states_to_current_states()) { - G_UpdateCardConditions_GC_Ep3_6xB4x4E cmd; + G_UpdateCardConditions_Ep3_6xB4x4E cmd; cmd.client_id = this->client_id; cmd.index = chain_index; cmd.conditions = this->action_chain.conditions; @@ -745,12 +778,13 @@ void Card::set_current_hp( } void Card::update_stats_on_destruction() { + auto s = this->server(); this->player_state()->num_destroyed_fcs++; - this->server()->team_num_ally_fcs_destroyed[this->team_id]++; - this->server()->team_num_cards_destroyed[this->team_id]++; + s->team_num_ally_fcs_destroyed[this->team_id]++; + s->team_num_cards_destroyed[this->team_id]++; for (size_t client_id = 0; client_id < 4; client_id++) { - auto other_ps = this->server()->player_states[client_id]; + auto other_ps = s->player_states[client_id]; if (other_ps && (other_ps->get_team_id() == this->team_id)) { auto card = other_ps->get_sc_card(); if (card) { diff --git a/src/Episode3/Card.hh b/src/Episode3/Card.hh index 3074546d..3b433d58 100644 --- a/src/Episode3/Card.hh +++ b/src/Episode3/Card.hh @@ -43,7 +43,7 @@ public: void commit_attack( int16_t damage, std::shared_ptr attacker_card, - G_ApplyConditionEffect_GC_Ep3_6xB4x06* cmd, + G_ApplyConditionEffect_Ep3_6xB4x06* cmd, size_t strike_number, int16_t* out_effective_damage); int16_t compute_defense_power_for_attacker_card( diff --git a/src/Episode3/CardSpecial.cc b/src/Episode3/CardSpecial.cc index 910a17bb..7b500322 100644 --- a/src/Episode3/CardSpecial.cc +++ b/src/Episode3/CardSpecial.cc @@ -63,7 +63,7 @@ void CardSpecial::AttackEnvStats::clear() { this->num_item_or_creature_cards_in_hand = 0; this->num_destroyed_ally_fcs = 0; this->target_team_num_set_cards = 0; - this->condition_giver_team_num_set_cards = 0; + this->non_target_team_num_set_cards = 0; this->num_native_creatures = 0; this->num_a_beast_creatures = 0; this->num_machine_creatures = 0; @@ -104,7 +104,7 @@ void CardSpecial::AttackEnvStats::print(FILE* stream) const { fprintf(stream, "(dm) effective_ap_if_not_tech = %" PRIu32 "\n", this->effective_ap_if_not_tech); fprintf(stream, "(dn) unknown_a1 = %" PRIu32 "\n", this->unknown_a1); fprintf(stream, "(edm) target_attack_bonus = %" PRIu32 "\n", this->target_attack_bonus); - fprintf(stream, "(ef) condition_giver_team_num_set_cards = %" PRIu32 "\n", this->condition_giver_team_num_set_cards); + fprintf(stream, "(ef) non_target_team_num_set_cards = %" PRIu32 "\n", this->non_target_team_num_set_cards); fprintf(stream, "(ehp) target_current_hp = %" PRIu32 "\n", this->target_current_hp); fprintf(stream, "(f) num_set_cards = %" PRIu32 "\n", this->num_set_cards); fprintf(stream, "(fdm) total_last_attack_damage = %" PRIu32 "\n", this->total_last_attack_damage); @@ -130,9 +130,7 @@ void CardSpecial::AttackEnvStats::print(FILE* stream) const { fprintf(stream, "(wd) num_cane_type_items = %" PRIu32 "\n", this->num_cane_type_items); } -CardSpecial::CardSpecial(shared_ptr server) - : w_server(server), - unknown_a2(0) {} +CardSpecial::CardSpecial(shared_ptr server) : w_server(server) {} shared_ptr CardSpecial::server() { auto s = this->w_server.lock(); @@ -229,7 +227,7 @@ void CardSpecial::adjust_dice_boost_if_team_has_condition_52( } for (size_t z = 0; z < 9; z++) { - if (!this->card_ref_has_ability_trap(card->action_chain.conditions[z]) && + if ((this->server()->options.is_trial() || !this->card_ref_has_ability_trap(card->action_chain.conditions[z])) && (card->action_chain.conditions[z].type == ConditionType::UNKNOWN_52)) { *inout_dice_boost = *inout_dice_boost * card->action_chain.conditions[z].value8; } @@ -313,7 +311,7 @@ bool CardSpecial::apply_defense_condition( if ((when == 2) && (defender_cond->type == ConditionType::GUOM) && (flags & 4)) { CardShortStatus stat = defender_card->get_short_status(); if (stat.card_flags & 4) { - G_ApplyConditionEffect_GC_Ep3_6xB4x06 cmd; + G_ApplyConditionEffect_Ep3_6xB4x06 cmd; cmd.effect.flags = 0x04; cmd.effect.attacker_card_ref = this->send_6xB4x06_if_card_ref_invalid(attacker_card_ref, 0x0E); cmd.effect.target_card_ref = defender_card->get_card_ref(); @@ -374,7 +372,7 @@ bool CardSpecial::apply_defense_condition( if (dice_roll.value_used_in_expr && !(original_cond_flags & 1) && !unknown_p8) { defender_cond->flags |= 1; - G_ApplyConditionEffect_GC_Ep3_6xB4x06 cmd; + G_ApplyConditionEffect_Ep3_6xB4x06 cmd; cmd.effect.flags = 0x08; cmd.effect.attacker_card_ref = this->send_6xB4x06_if_card_ref_invalid(attacker_card_ref, 0x10); cmd.effect.target_card_ref = defender_cond->card_ref; @@ -385,7 +383,7 @@ bool CardSpecial::apply_defense_condition( } else { if (defender_cond->type != ConditionType::NONE) { - G_ApplyConditionEffect_GC_Ep3_6xB4x06 cmd; + G_ApplyConditionEffect_Ep3_6xB4x06 cmd; cmd.effect.flags = 0x04; cmd.effect.attacker_card_ref = this->send_6xB4x06_if_card_ref_invalid(attacker_card_ref, 0x0D); cmd.effect.target_card_ref = defender_card->get_card_ref(); @@ -438,7 +436,8 @@ bool CardSpecial::apply_stat_deltas_to_all_cards_from_all_conditions_with_card_r } bool CardSpecial::apply_stat_deltas_to_card_from_condition_and_clear_cond(Condition& cond, shared_ptr card) { - auto log = this->server()->log_stack(string_printf("apply_stat_deltas_to_card_from_condition_and_clear_cond(@%04hX #%04hX): ", card->get_card_ref(), card->get_card_id())); + auto s = this->server(); + auto log = s->log_stack(string_printf("apply_stat_deltas_to_card_from_condition_and_clear_cond(@%04hX #%04hX): ", card->get_card_ref(), card->get_card_id())); string cond_str = cond.str(); log.debug("cond: %s", cond_str.c_str()); @@ -454,8 +453,10 @@ bool CardSpecial::apply_stat_deltas_to_card_from_condition_and_clear_cond(Condit int16_t ap = clamp(card->ap, -99, 99); int16_t tp = clamp(card->tp, -99, 99); log.debug("A_T_SWAP_0C: swapping AP (%hd) and TP (%hd)", ap, tp); - this->send_6xB4x06_for_stat_delta(card, cond_card_ref, 0xA0, tp - ap, 0, 0); - this->send_6xB4x06_for_stat_delta(card, cond_card_ref, 0x80, ap - tp, 0, 0); + if (!s->options.is_trial()) { + this->send_6xB4x06_for_stat_delta(card, cond_card_ref, 0xA0, tp - ap, 0, 0); + this->send_6xB4x06_for_stat_delta(card, cond_card_ref, 0x80, ap - tp, 0, 0); + } card->ap = tp; card->tp = ap; } else { @@ -468,8 +469,10 @@ bool CardSpecial::apply_stat_deltas_to_card_from_condition_and_clear_cond(Condit int16_t hp = clamp(card->get_current_hp(), -99, 99); if (hp != ap) { log.debug("A_H_SWAP: swapping AP (%hd) and HP (%hd)", ap, hp); - this->send_6xB4x06_for_stat_delta(card, cond_card_ref, 0xA0, hp - ap, 0, 0); - this->send_6xB4x06_for_stat_delta(card, cond_card_ref, 0x20, ap - hp, 0, 0); + if (!s->options.is_trial()) { + this->send_6xB4x06_for_stat_delta(card, cond_card_ref, 0xA0, hp - ap, 0, 0); + this->send_6xB4x06_for_stat_delta(card, cond_card_ref, 0x20, ap - hp, 0, 0); + } card->set_current_hp(ap, true, true); card->ap = hp; this->destroy_card_if_hp_zero(card, cond_card_ref); @@ -492,8 +495,9 @@ bool CardSpecial::apply_stat_deltas_to_card_from_condition_and_clear_cond(Condit // late-development intentional change. Condition* other_cond = nullptr; // return_null???(card, ConditionType::AP_OVERRIDE); if (!other_cond) { - this->send_6xB4x06_for_stat_delta( - card, cond_card_ref, 0xA0, -cond_value, 0, 0); + if (!s->options.is_trial()) { + this->send_6xB4x06_for_stat_delta(card, cond_card_ref, 0xA0, -cond_value, 0, 0); + } card->ap = max(card->ap - cond_value, 0); log.debug("AP_OVERRIDE: subtracting %hd from AP => %hd", cond_value, card->ap); } else { @@ -509,7 +513,9 @@ bool CardSpecial::apply_stat_deltas_to_card_from_condition_and_clear_cond(Condit // original code as well. Condition* other_cond = nullptr; // return_null???(card, ConditionType::TP_OVERRIDE) if (!other_cond) { - this->send_6xB4x06_for_stat_delta(card, cond_card_ref, 0x80, -cond_value, 0, 0); + if (!s->options.is_trial()) { + this->send_6xB4x06_for_stat_delta(card, cond_card_ref, 0x80, -cond_value, 0, 0); + } card->tp = max(card->tp - cond_value, 0); log.debug("TP_OVERRIDE: subtracting %hd from TP => %hd", cond_value, card->tp); } else { @@ -521,7 +527,9 @@ bool CardSpecial::apply_stat_deltas_to_card_from_condition_and_clear_cond(Condit break; case ConditionType::MISC_AP_BONUSES: if (cond_flags & 2) { - this->send_6xB4x06_for_stat_delta(card, cond_card_ref, 0xA0, -cond_value, 0, 0); + if (!s->options.is_trial()) { + this->send_6xB4x06_for_stat_delta(card, cond_card_ref, 0xA0, -cond_value, 0, 0); + } card->ap = max(card->ap - cond_value, 0); log.debug("MISC_AP_BONUSES: subtracting %hd from AP => %hd", cond_value, card->ap); } else { @@ -530,7 +538,9 @@ bool CardSpecial::apply_stat_deltas_to_card_from_condition_and_clear_cond(Condit break; case ConditionType::MISC_TP_BONUSES: if (cond_flags & 2) { - this->send_6xB4x06_for_stat_delta(card, cond_card_ref, 0x80, -cond_value, 0, 0); + if (!s->options.is_trial()) { + this->send_6xB4x06_for_stat_delta(card, cond_card_ref, 0x80, -cond_value, 0, 0); + } card->tp = max(card->tp - cond_value, 0); log.debug("MISC_TP_BONUSES: subtracting %hd from TP => %hd", cond_value, card->tp); } else { @@ -538,6 +548,9 @@ bool CardSpecial::apply_stat_deltas_to_card_from_condition_and_clear_cond(Condit } break; case ConditionType::AP_SILENCE: + if (!s->options.is_trial()) { + goto trial_unimplemented; + } if (cond_flags & 2) { this->send_6xB4x06_for_stat_delta(card, cond_card_ref, 0xA0, cond_value, 0, 0); card->ap = max(card->ap + cond_value, 0); @@ -547,6 +560,9 @@ bool CardSpecial::apply_stat_deltas_to_card_from_condition_and_clear_cond(Condit } break; case ConditionType::TP_SILENCE: + if (!s->options.is_trial()) { + goto trial_unimplemented; + } if (cond_flags & 2) { this->send_6xB4x06_for_stat_delta(card, cond_card_ref, 0x80, cond_value, 0, 0); card->tp = max(card->tp + cond_value, 0); @@ -555,6 +571,7 @@ bool CardSpecial::apply_stat_deltas_to_card_from_condition_and_clear_cond(Condit log.debug("TP_SILENCE: required flag is missing"); } break; + trial_unimplemented: default: log.debug("%s: no adjustments for condition type", name_for_condition_type(cond_type)); break; @@ -666,15 +683,15 @@ CardSpecial::AttackEnvStats CardSpecial::compute_attack_env_stats( const DiceRoll& dice_roll, uint16_t target_card_ref, uint16_t condition_giver_card_ref) { - auto log = this->server()->log_stack("compute_attack_env_stats: "); + auto s = this->server(); + auto log = s->log_stack("compute_attack_env_stats: "); string pa_str = pa.str(); log.debug("pa=%s, card=@%04hX #%04hX, dice_roll=%hhu, target=@%04hX, condition_giver=@%04hX", pa_str.c_str(), card->get_card_ref(), card->get_card_id(), dice_roll.value, target_card_ref, condition_giver_card_ref); - this->action_state = pa; - auto attacker_card = this->server()->card_for_set_card_ref(pa.attacker_card_ref); + auto attacker_card = s->card_for_set_card_ref(pa.attacker_card_ref); if (!attacker_card && (pa.original_attacker_card_ref != 0xFFFF)) { - attacker_card = this->server()->card_for_set_card_ref(pa.original_attacker_card_ref); + attacker_card = s->card_for_set_card_ref(pa.original_attacker_card_ref); log.debug("attacker=@%04hX #%04hX (from original)", attacker_card->get_card_ref(), attacker_card->get_card_id()); } else if (attacker_card) { log.debug("attacker=@%04hX #%04hX (from set)", attacker_card->get_card_ref(), attacker_card->get_card_id()); @@ -687,15 +704,15 @@ CardSpecial::AttackEnvStats CardSpecial::compute_attack_env_stats( auto ps = card->player_state(); log.debug("base ps = %hhu", ps->client_id); ast.num_set_cards = ps->count_set_cards(); - auto condition_giver_card = this->server()->card_for_set_card_ref(condition_giver_card_ref); - auto target_card = this->server()->card_for_set_card_ref(target_card_ref); + auto condition_giver_card = s->card_for_set_card_ref(condition_giver_card_ref); + auto target_card = s->card_for_set_card_ref(target_card_ref); if (!target_card) { target_card = condition_giver_card; } size_t ps_num_set_cards = 0; for (size_t z = 0; z < 4; z++) { - auto other_ps = this->server()->get_player_state(z); + auto other_ps = s->get_player_state(z); if (other_ps) { ps_num_set_cards += other_ps->count_set_cards(); } @@ -707,19 +724,19 @@ CardSpecial::AttackEnvStats CardSpecial::compute_attack_env_stats( : 0xFF; size_t target_team_num_set_cards = 0; - size_t condition_giver_team_num_set_cards = 0; + size_t non_target_team_num_set_cards = 0; for (size_t z = 0; z < 4; z++) { - auto other_ps = this->server()->get_player_state(z); + auto other_ps = s->get_player_state(z); if (other_ps) { if (target_card_team_id == other_ps->get_team_id()) { target_team_num_set_cards += other_ps->count_set_cards(); } else { - condition_giver_team_num_set_cards += other_ps->count_set_cards(); + non_target_team_num_set_cards += other_ps->count_set_cards(); } } } ast.target_team_num_set_cards = target_team_num_set_cards; - ast.condition_giver_team_num_set_cards = condition_giver_team_num_set_cards; + ast.non_target_team_num_set_cards = non_target_team_num_set_cards; ast.num_native_creatures = this->get_all_set_cards_by_team_and_class(CardClass::NATIVE_CREATURE, 0xFF, true).size(); ast.num_a_beast_creatures = this->get_all_set_cards_by_team_and_class(CardClass::A_BEAST_CREATURE, 0xFF, true).size(); @@ -736,14 +753,19 @@ CardSpecial::AttackEnvStats CardSpecial::compute_attack_env_stats( if (card_ref == 0xFFFF) { continue; } - auto ce = this->server()->definition_for_card_id(card_ref); + auto ce = s->definition_for_card_id(card_ref); if (ce && ((ce->def.type == CardType::ITEM) || (ce->def.type == CardType::CREATURE))) { num_item_or_creature_cards_in_hand++; } } ast.num_item_or_creature_cards_in_hand = num_item_or_creature_cards_in_hand; - ast.num_destroyed_ally_fcs = card->num_destroyed_ally_fcs; + if (s->options.is_trial()) { + ast.num_destroyed_ally_fcs = s->team_num_cards_destroyed[ps->get_team_id()] - card->num_ally_fcs_destroyed_at_set_time; + } else { + ast.num_destroyed_ally_fcs = card->num_destroyed_ally_fcs; + } + // Note: The original implementation has dice_roll as optional, but since it's // provided at all callsites, we require it (and hence don't check for nullptr // here) @@ -753,7 +775,7 @@ CardSpecial::AttackEnvStats CardSpecial::compute_attack_env_stats( ast.effective_tp = card->action_chain.chain.effective_tp; ast.current_hp = card->get_current_hp(); ast.max_hp = card->get_max_hp(); - ast.team_dice_bonus = card ? this->server()->team_dice_bonus[card->get_team_id()] : 0; + ast.team_dice_bonus = card ? s->team_dice_bonus[card->get_team_id()] : 0; ast.effective_ap_if_not_tech = (!attacker_card || (attacker_card->action_chain.chain.attack_medium == AttackMedium::TECH)) ? 0 @@ -802,8 +824,7 @@ CardSpecial::AttackEnvStats CardSpecial::compute_attack_env_stats( ast.action_cards_ap = 0; ast.action_cards_tp = 0; for (; (z < 9) && (pa.action_card_refs[z] != 0xFFFF); z++) { - this->unknown_a2 = pa.action_card_refs[z]; - auto ce = this->server()->definition_for_card_ref(pa.action_card_refs[z]); + auto ce = s->definition_for_card_ref(pa.action_card_refs[z]); if (ce) { if (ce->def.ap.type != CardDefinition::Stat::Type::MINUS_STAT) { ast.action_cards_ap += ce->def.ap.stat; @@ -1271,6 +1292,10 @@ size_t CardSpecial::count_cards_with_card_id_except_card_ref( vector> CardSpecial::get_all_set_cards_by_team_and_class( CardClass card_class, uint8_t team_id, bool exclude_destroyed_cards) const { + if (this->server()->options.is_trial()) { + team_id = 0xFF; + exclude_destroyed_cards = false; + } vector> ret; auto check_card = [&](shared_ptr card) -> void { if (card && @@ -1941,7 +1966,7 @@ bool CardSpecial::execute_effect( (cond.type == ConditionType::FREEZE) || (cond.type == ConditionType::UNKNOWN_1E) || (cond.type == ConditionType::DROP)) { - G_ApplyConditionEffect_GC_Ep3_6xB4x06 cmd; + G_ApplyConditionEffect_Ep3_6xB4x06 cmd; cmd.effect.flags = 0x04; cmd.effect.attacker_card_ref = this->send_6xB4x06_if_card_ref_invalid(attacker_card_ref, 0x0C); cmd.effect.target_card_ref = card->get_card_ref(); @@ -3254,7 +3279,7 @@ void CardSpecial::send_6xB4x06_for_exp_change( uint16_t attacker_card_ref, uint8_t dice_roll_value, bool unknown_p5) const { - G_ApplyConditionEffect_GC_Ep3_6xB4x06 cmd; + G_ApplyConditionEffect_Ep3_6xB4x06 cmd; cmd.effect.flags = 0x02; cmd.effect.attacker_card_ref = this->send_6xB4x06_if_card_ref_invalid(attacker_card_ref, 10); cmd.effect.target_card_ref = card->get_card_ref(); @@ -3274,27 +3299,29 @@ void CardSpecial::send_6xB4x06_for_exp_change( void CardSpecial::send_6xB4x06_for_card_destroyed( shared_ptr destroyed_card, uint16_t attacker_card_ref) const { - G_ApplyConditionEffect_GC_Ep3_6xB4x06 cmd; + auto s = this->server(); + G_ApplyConditionEffect_Ep3_6xB4x06 cmd; cmd.effect.flags = 0x04; cmd.effect.attacker_card_ref = this->send_6xB4x06_if_card_ref_invalid( attacker_card_ref, 0x13); cmd.effect.target_card_ref = destroyed_card->get_card_ref(); cmd.effect.value = 0; - cmd.effect.operation = 0x7E; + cmd.effect.operation = s->options.is_trial() ? 0x78 : 0x7E; this->server()->send(cmd); } uint16_t CardSpecial::send_6xB4x06_if_card_ref_invalid( uint16_t card_ref, int16_t value) const { - if (!this->server()->card_ref_is_empty_or_has_valid_card_id(card_ref)) { + auto s = this->server(); + if (!s->options.is_trial() && !s->card_ref_is_empty_or_has_valid_card_id(card_ref)) { if (value != 0) { - G_ApplyConditionEffect_GC_Ep3_6xB4x06 cmd; + G_ApplyConditionEffect_Ep3_6xB4x06 cmd; cmd.effect.flags = 0x04; cmd.effect.attacker_card_ref = 0xFFFF; cmd.effect.target_card_ref = 0xFFFF; cmd.effect.value = value; cmd.effect.operation = 0x7E; - this->server()->send(cmd); + s->send(cmd); } card_ref = 0xFFFF; } @@ -3323,7 +3350,7 @@ void CardSpecial::send_6xB4x06_for_stat_delta( } } - G_ApplyConditionEffect_GC_Ep3_6xB4x06 cmd; + G_ApplyConditionEffect_Ep3_6xB4x06 cmd; cmd.effect.flags = flags | 2; cmd.effect.attacker_card_ref = this->send_6xB4x06_if_card_ref_invalid(attacker_card_ref, 10); cmd.effect.target_card_ref = card->get_card_ref(); @@ -3611,7 +3638,7 @@ void CardSpecial::check_for_defense_interference( target_ps->unknown_a17++; - G_ApplyConditionEffect_GC_Ep3_6xB4x06 cmd; + G_ApplyConditionEffect_Ep3_6xB4x06 cmd; cmd.effect.flags = 0x04; cmd.effect.attacker_card_ref = this->send_6xB4x06_if_card_ref_invalid(attacker_card->get_card_ref(), 0x12); cmd.effect.target_card_ref = target_card->get_card_ref(); @@ -3631,26 +3658,32 @@ void CardSpecial::evaluate_and_apply_effects( uint16_t sc_card_ref, bool apply_defense_condition_to_all_cards, uint16_t apply_defense_condition_to_card_ref) { - auto log = this->server()->log_stack(string_printf("evaluate_and_apply_effects(%02hhX, @%04hX, @%04hX): ", when, set_card_ref, sc_card_ref)); + auto s = this->server(); + auto log = s->log_stack(string_printf("evaluate_and_apply_effects(%02hhX, @%04hX, @%04hX): ", when, set_card_ref, sc_card_ref)); { string as_str = as.str(); log.debug("when=%02hhX, set_card_ref=@%04hX, as=%s, sc_card_ref=@%04hX, apply_defense_condition_to_all_cards=%s, apply_defense_condition_to_card_ref=@%04hX", when, set_card_ref, as_str.c_str(), sc_card_ref, apply_defense_condition_to_all_cards ? "true" : "false", apply_defense_condition_to_card_ref); } - set_card_ref = this->send_6xB4x06_if_card_ref_invalid(set_card_ref, 1); + if (!s->options.is_trial()) { + set_card_ref = this->send_6xB4x06_if_card_ref_invalid(set_card_ref, 1); + } + auto ce = this->server()->definition_for_card_ref(set_card_ref); if (!ce) { log.debug("ce missing"); return; } + // TODO: NTE has an extra check here. Implement it. + uint16_t as_attacker_card_ref = this->send_6xB4x06_if_card_ref_invalid(as.attacker_card_ref, 2); if (as_attacker_card_ref == 0xFFFF) { as_attacker_card_ref = this->send_6xB4x06_if_card_ref_invalid(as.original_attacker_card_ref, 3); } - G_ApplyConditionEffect_GC_Ep3_6xB4x06 dice_cmd; + G_ApplyConditionEffect_Ep3_6xB4x06 dice_cmd; dice_cmd.effect.target_card_ref = set_card_ref; bool as_action_card_refs_contains_set_card_ref = false; bool as_action_card_refs_contains_duplicate_of_set_card = false; @@ -3659,7 +3692,7 @@ void CardSpecial::evaluate_and_apply_effects( as_action_card_refs_contains_set_card_ref = true; break; } - auto action_ce = this->server()->definition_for_card_ref(as.action_card_refs[z]); + auto action_ce = s->definition_for_card_ref(as.action_card_refs[z]); if (action_ce && (action_ce->def.card_id == ce->def.card_id)) { as_action_card_refs_contains_duplicate_of_set_card = true; } @@ -3667,12 +3700,12 @@ void CardSpecial::evaluate_and_apply_effects( bool unknown_v1 = as_action_card_refs_contains_duplicate_of_set_card && as_action_card_refs_contains_set_card_ref; - uint8_t random_percent = this->server() ? this->server()->get_random(99) : 0; + uint8_t random_percent = s->get_random(99); bool any_expr_used_dice_roll = false; DiceRoll dice_roll; uint8_t client_id = client_id_for_card_ref(dice_cmd.effect.target_card_ref); - auto set_card_ps = (client_id == 0xFF) ? nullptr : this->server()->player_states.at(client_id); + auto set_card_ps = (client_id == 0xFF) ? nullptr : s->player_states.at(client_id); dice_roll.value = 1; if (set_card_ps) { @@ -3705,7 +3738,8 @@ void CardSpecial::evaluate_and_apply_effects( string refs_str = refs_str_for_cards_vector(targeted_cards); effect_log.debug("targeted_cards=[%s]", refs_str.c_str()); bool all_targets_matched = false; - if (!targeted_cards.empty() && + if (!s->options.is_trial() && + !targeted_cards.empty() && ((card_effect.type == ConditionType::UNKNOWN_64) || (card_effect.type == ConditionType::MISC_DEFENSE_BONUSES) || (card_effect.type == ConditionType::MOSTLY_HALFGUARDS))) { @@ -3724,9 +3758,9 @@ void CardSpecial::evaluate_and_apply_effects( } } if (count == targeted_cards.size()) { - auto set_card = this->server()->card_for_set_card_ref(set_card_ref); + auto set_card = s->card_for_set_card_ref(set_card_ref); if (!set_card) { - set_card = this->server()->card_for_set_card_ref(sc_card_ref); + set_card = s->card_for_set_card_ref(sc_card_ref); } targeted_cards.clear(); if (set_card != nullptr) { @@ -3784,13 +3818,13 @@ void CardSpecial::evaluate_and_apply_effects( } if (applied_cond_index >= 0) { - G_ApplyConditionEffect_GC_Ep3_6xB4x06 cmd; + G_ApplyConditionEffect_Ep3_6xB4x06 cmd; cmd.effect.flags = 0x04; cmd.effect.attacker_card_ref = this->send_6xB4x06_if_card_ref_invalid(as_attacker_card_ref, 0x14); cmd.effect.target_card_ref = target_card->get_card_ref(); cmd.effect.value = (target_card->action_chain).conditions[applied_cond_index].remaining_turns; cmd.effect.operation = static_cast(card_effect.type); - this->server()->send(cmd); + s->send(cmd); // Note: The original code has this check outside of the if // (applied_cond_index >= 0) block, but this is a bug since @@ -3826,7 +3860,7 @@ void CardSpecial::evaluate_and_apply_effects( dice_cmd.effect.attacker_card_ref = this->send_6xB4x06_if_card_ref_invalid( as_attacker_card_ref, 0x15); dice_cmd.effect.dice_roll_value = dice_roll.value; - this->server()->send(dice_cmd); + s->send(dice_cmd); } } @@ -3903,7 +3937,7 @@ void CardSpecial::clear_invalid_conditions_on_card( if (cond.type != ConditionType::NONE) { if (!this->is_card_targeted_by_condition(cond, as, card)) { if (cond.type != ConditionType::NONE) { - G_ApplyConditionEffect_GC_Ep3_6xB4x06 cmd; + G_ApplyConditionEffect_Ep3_6xB4x06 cmd; cmd.effect.flags = 0x04; cmd.effect.attacker_card_ref = 0xFFFF; cmd.effect.target_card_ref = card->get_card_ref(); @@ -4350,7 +4384,6 @@ void CardSpecial::unknown_8024AAB8(const ActionState& as) { auto log = this->server()->log_stack("unknown_8024AAB8: "); string as_str = as.str(); log.debug("as=%s", as_str.c_str()); - this->unknown_action_state_a1 = as; for (size_t z = 0; (z < 8) && (as.action_card_refs[z] != 0xFFFF); z++) { uint16_t card_ref = this->send_6xB4x06_if_card_ref_invalid( @@ -4614,7 +4647,7 @@ void CardSpecial::check_for_attack_interference(shared_ptr unknown_p2) { ps->unknown_a16++; unknown_p2->action_chain.set_flags(0x100); - G_ApplyConditionEffect_GC_Ep3_6xB4x06 cmd; + G_ApplyConditionEffect_Ep3_6xB4x06 cmd; cmd.effect.flags = 0x04; cmd.effect.attacker_card_ref = this->send_6xB4x06_if_card_ref_invalid( unknown_p2->get_card_ref(), 0x11); diff --git a/src/Episode3/CardSpecial.hh b/src/Episode3/CardSpecial.hh index 720cc267..51c03935 100644 --- a/src/Episode3/CardSpecial.hh +++ b/src/Episode3/CardSpecial.hh @@ -45,45 +45,49 @@ public: }; struct AttackEnvStats { - uint32_t num_set_cards; // "f" in expr - uint32_t dice_roll_value1; // "d" in expr - uint32_t effective_ap; // "ap" in expr - uint32_t effective_tp; // "tp" in expr - uint32_t current_hp; // "hp" in expr - uint32_t max_hp; // "mhp" in expr - uint32_t effective_ap_if_not_tech; // "dm" in expr - uint32_t effective_ap_if_not_physical; // "tdm" in expr - uint32_t player_num_destroyed_fcs; // "tf" in expr - uint32_t player_num_atk_points; // "ac" in expr - uint32_t defined_max_hp; // "php" in expr - uint32_t dice_roll_value2; // "dc" in expr - uint32_t card_cost; // "cs" in expr - uint32_t total_num_set_cards; // "a" in expr - uint32_t action_cards_ap; // "kap" in expr - uint32_t action_cards_tp; // "ktp" in expr - uint32_t unknown_a1; // "dn" in expr - uint32_t num_item_or_creature_cards_in_hand; // "hf" in expr - uint32_t num_destroyed_ally_fcs; // "df" in expr - uint32_t target_team_num_set_cards; // "ff" in expr - uint32_t condition_giver_team_num_set_cards; // "ef" in expr - uint32_t num_native_creatures; // "bi" in expr - uint32_t num_a_beast_creatures; // "ab" in expr - uint32_t num_machine_creatures; // "mc" in expr - uint32_t num_dark_creatures; // "dk" in expr - uint32_t num_sword_type_items; // "sa" in expr - uint32_t num_gun_type_items; // "gn" in expr - uint32_t num_cane_type_items; // "wd" in expr - uint32_t effective_ap_if_not_tech2; // "tt" in expr - uint32_t team_dice_bonus; // "lv" in expr - uint32_t sc_effective_ap; // "adm" in expr - uint32_t attack_bonus; // "ddm" in expr - uint32_t num_sword_type_items_on_team; // "sat" in expr - uint32_t target_attack_bonus; // "edm" in expr - uint32_t last_attack_preliminary_damage; // "ldm" in expr - uint32_t last_attack_damage; // "rdm" in expr - uint32_t total_last_attack_damage; // "fdm" in expr - uint32_t last_attack_damage_count; // "ndm" in expr - uint32_t target_current_hp; // "ehp" in expr + /* 00 */ uint32_t num_set_cards; // "f" in expr + /* 04 */ uint32_t dice_roll_value1; // "d" in expr + /* 08 */ uint32_t effective_ap; // "ap" in expr + /* 0C */ uint32_t effective_tp; // "tp" in expr + /* 10 */ uint32_t current_hp; // "hp" in expr + /* 14 */ uint32_t max_hp; // "mhp" in expr + /* 18 */ uint32_t effective_ap_if_not_tech; // "dm" in expr + /* 1C */ uint32_t effective_ap_if_not_physical; // "tdm" in expr + /* 20 */ uint32_t player_num_destroyed_fcs; // "tf" in expr + /* 24 */ uint32_t player_num_atk_points; // "ac" in expr + /* 28 */ uint32_t defined_max_hp; // "php" in expr + /* 2C */ uint32_t dice_roll_value2; // "dc" in expr + /* 30 */ uint32_t card_cost; // "cs" in expr + /* 34 */ uint32_t total_num_set_cards; // "a" in expr + /* 38 */ uint32_t action_cards_ap; // "kap" in expr + /* 3C */ uint32_t action_cards_tp; // "ktp" in expr + /* 40 */ uint32_t unknown_a1; // "dn" in expr + /* 44 */ uint32_t num_item_or_creature_cards_in_hand; // "hf" in expr + /* 48 */ uint32_t num_destroyed_ally_fcs; // "df" in expr + /* 4C */ uint32_t target_team_num_set_cards; // "ff" in expr + /* 50 */ uint32_t non_target_team_num_set_cards; // "ef" in expr + /* 54 */ uint32_t num_native_creatures; // "bi" in expr + /* 58 */ uint32_t num_a_beast_creatures; // "ab" in expr + /* 5C */ uint32_t num_machine_creatures; // "mc" in expr + /* 60 */ uint32_t num_dark_creatures; // "dk" in expr + /* 64 */ uint32_t num_sword_type_items; // "sa" in expr + /* 68 */ uint32_t num_gun_type_items; // "gn" in expr + /* 6C */ uint32_t num_cane_type_items; // "wd" in expr + /* 70 */ uint32_t effective_ap_if_not_tech2; // "tt" in expr + /* 74 */ uint32_t team_dice_bonus; // "lv" in expr + /* 78 */ uint32_t sc_effective_ap; // "adm" in expr + // The following fields do not exist in Trial Edition. Because this struct + // is never sent to the client, we use the full struct even when playing + // Trial Edition, just for simplicity. + /* 7C */ uint32_t attack_bonus; // "ddm" in expr + /* 80 */ uint32_t num_sword_type_items_on_team; // "sat" in expr + /* 84 */ uint32_t target_attack_bonus; // "edm" in expr + /* 88 */ uint32_t last_attack_preliminary_damage; // "ldm" in expr + /* 8C */ uint32_t last_attack_damage; // "rdm" in expr + /* 90 */ uint32_t total_last_attack_damage; // "fdm" in expr + /* 94 */ uint32_t last_attack_damage_count; // "ndm" in expr + /* 98 */ uint32_t target_current_hp; // "ehp" in expr + /* 9C */ AttackEnvStats(); void clear(); @@ -333,9 +337,6 @@ public: private: std::weak_ptr w_server; - ActionState unknown_action_state_a1; - ActionState action_state; - uint16_t unknown_a2; }; } // namespace Episode3 diff --git a/src/Episode3/DataIndexes.hh b/src/Episode3/DataIndexes.hh index 6ca99763..76fa6bed 100644 --- a/src/Episode3/DataIndexes.hh +++ b/src/Episode3/DataIndexes.hh @@ -36,6 +36,7 @@ enum BehaviorFlag : uint32_t { DISABLE_MASKING = 0x00000080, DISABLE_INTERFERENCE = 0x00000100, ALLOW_NON_COM_INTERFERENCE = 0x00000200, + IS_TRIAL_EDITION = 0x00000400, }; enum class StatSwapType : uint8_t { diff --git a/src/Episode3/DeckState.cc b/src/Episode3/DeckState.cc index 7d18515c..5fcd9e75 100644 --- a/src/Episode3/DeckState.cc +++ b/src/Episode3/DeckState.cc @@ -88,15 +88,16 @@ bool DeckState::draw_card_by_ref(uint16_t card_ref) { return false; } - // If the card is discarded, then it should be before the draw index, and we - // can just change its state. if (this->entries[index].state == CardState::DISCARDED) { + // If the card is discarded, then it should be before the draw index, and we + // can just change its state. this->entries[index].state = CardState::IN_HAND; return true; - // If the card is still drawable, we need to move it so it's just in front of - // the draw index, then immediately draw it } else if (this->entries[index].state == CardState::DRAWABLE) { + // If the card is still drawable, we need to move it so it's just in front + // of the draw index, then immediately draw it. Ep3 NTE does not handle this + // case, but we do even when playing NTE. ssize_t ref_index; for (ref_index = this->card_refs.size(); ref_index >= 0; ref_index--) { if (this->card_refs[ref_index] == card_ref) { @@ -182,7 +183,7 @@ void DeckState::restart() { this->shuffle(); } -void DeckState::do_mulligan() { +void DeckState::do_mulligan(bool is_trial) { for (size_t z = 0; z < this->entries.size(); z++) { if (this->entries[z].state == CardState::DISCARDED) { this->entries[z].state = CardState::DRAWABLE; @@ -190,7 +191,7 @@ void DeckState::do_mulligan() { } this->draw_index = 1; - if (this->shuffle_enabled) { + if (is_trial || this->shuffle_enabled) { // Get the next 5 cards from the deck, and put the previous 5 cards after // them (so they will be shuffled back in). for (uint8_t z = 0; z < 5; z++) { diff --git a/src/Episode3/DeckState.hh b/src/Episode3/DeckState.hh index 8d9ddc21..ac0b9bfb 100644 --- a/src/Episode3/DeckState.hh +++ b/src/Episode3/DeckState.hh @@ -94,7 +94,7 @@ public: void restart(); void shuffle(); - void do_mulligan(); + void do_mulligan(bool is_trial); void print(FILE* stream, std::shared_ptr card_index = nullptr) const; diff --git a/src/Episode3/MapState.cc b/src/Episode3/MapState.cc index 5bc1e148..11ca93eb 100644 --- a/src/Episode3/MapState.cc +++ b/src/Episode3/MapState.cc @@ -68,6 +68,37 @@ void MapAndRulesState::clear_occupied_bit_for_tile(uint8_t x, uint8_t y) { this->map.tiles[y][x] &= 0xEF; } +MapAndRulesStateTrial::MapAndRulesStateTrial(const MapAndRulesState& state) + : map(state.map), + num_players(state.num_players), + unused1(state.unused1), + environment_number(state.environment_number), + num_players_per_team(state.num_players_per_team), + num_team0_players(state.num_team0_players), + unused2(state.unused2), + unused5(state.start_facing_directions), + unknown_a3(state.unused3), + map_number(state.map_number), + unused4(state.unused4), + rules(state.rules) {} + +MapAndRulesStateTrial::operator MapAndRulesState() const { + MapAndRulesState ret; + ret.map = this->map; + ret.num_players = this->num_players; + ret.unused1 = this->unused1; + ret.environment_number = this->environment_number; + ret.num_players_per_team = this->num_players_per_team; + ret.num_team0_players = this->num_team0_players; + ret.unused2 = this->unused2; + ret.start_facing_directions = this->unused5; + ret.unused3 = this->unknown_a3; + ret.map_number = this->map_number; + ret.unused4 = this->unused4; + ret.rules = this->rules; + return ret; +} + OverlayState::OverlayState() { this->clear(); } diff --git a/src/Episode3/MapState.hh b/src/Episode3/MapState.hh index a4e1bafd..0ef7a86d 100644 --- a/src/Episode3/MapState.hh +++ b/src/Episode3/MapState.hh @@ -10,8 +10,8 @@ namespace Episode3 { struct MapState { - /* 0000 */ le_uint16_t width; - /* 0002 */ le_uint16_t height; + /* 0000 */ le_uint16_t width = 0; + /* 0002 */ le_uint16_t height = 0; /* 0004 */ parray, 0x10> tiles; /* 0104 */ parray, 2> start_tile_definitions; /* 0110 */ @@ -24,16 +24,16 @@ struct MapState { struct MapAndRulesState { /* 0000 */ MapState map; - /* 0110 */ uint8_t num_players; - /* 0111 */ uint8_t unused1; - /* 0112 */ uint8_t environment_number; - /* 0113 */ uint8_t num_players_per_team; - /* 0114 */ uint8_t num_team0_players; - /* 0115 */ uint8_t unused2; - /* 0116 */ le_uint16_t start_facing_directions; - /* 0118 */ uint32_t unused3; - /* 011C */ le_uint32_t map_number; - /* 0120 */ uint32_t unused4; + /* 0110 */ uint8_t num_players = 0; + /* 0111 */ uint8_t unused1 = 0; + /* 0112 */ uint8_t environment_number = 0; + /* 0113 */ uint8_t num_players_per_team = 0; + /* 0114 */ uint8_t num_team0_players = 0; + /* 0115 */ uint8_t unused2 = 0; + /* 0116 */ le_uint16_t start_facing_directions = 0; + /* 0118 */ be_uint32_t unused3 = 0; + /* 011C */ le_uint32_t map_number = 0; + /* 0120 */ be_uint32_t unused4 = 0; /* 0124 */ Rules rules; /* 0138 */ @@ -47,6 +47,26 @@ struct MapAndRulesState { void clear_occupied_bit_for_tile(uint8_t x, uint8_t y); } __attribute__((packed)); +struct MapAndRulesStateTrial { + /* 0000 */ MapState map; + /* 0110 */ uint8_t num_players = 0; + /* 0111 */ uint8_t unused1 = 0; + /* 0112 */ uint8_t environment_number = 0; + /* 0113 */ uint8_t num_players_per_team = 0; + /* 0114 */ uint8_t num_team0_players = 0; + /* 0115 */ uint8_t unused2 = 0; + /* 0116 */ le_uint16_t unused5 = 0; + /* 0118 */ be_uint32_t unknown_a3 = 0; + /* 011C */ le_uint32_t map_number = 0; + /* 0120 */ be_uint32_t unused4 = 0; + /* 0124 */ RulesTrial rules; + /* 0130 */ + + MapAndRulesStateTrial() = default; + MapAndRulesStateTrial(const MapAndRulesState& state); + operator MapAndRulesState() const; +} __attribute__((packed)); + struct OverlayState { parray, 0x10> tiles; parray unused1; diff --git a/src/Episode3/PlayerState.cc b/src/Episode3/PlayerState.cc index 40b6aac9..2499c8f5 100644 --- a/src/Episode3/PlayerState.cc +++ b/src/Episode3/PlayerState.cc @@ -91,6 +91,10 @@ void PlayerState::init() { this->sc_card = make_shared(this->deck_state->sc_card_id(), this->sc_card_ref, this->client_id, s); this->sc_card->init(); this->draw_initial_hand(); + if (s->options.is_trial()) { + this->update_hand_and_equip_state_and_send_6xB4x02_if_needed(); + // TODO: NTE calls 80243310(1) here + } s->assist_server->hand_and_equip_states[this->client_id] = this->hand_and_equip; s->assist_server->card_short_statuses[this->client_id] = this->card_short_statuses; @@ -152,7 +156,7 @@ void PlayerState::apply_assist_card_effect_on_set( assist_card_id = s->card_id_for_card_ref(this->card_refs[6]); } - auto assist_effect = assist_effect_number_for_card_id(assist_card_id); + auto assist_effect = assist_effect_number_for_card_id(assist_card_id, s->options.is_trial()); if ((assist_effect == AssistEffect::RESISTANCE) || (assist_effect == AssistEffect::INDEPENDENT)) { this->assist_card_set_number = 0; @@ -415,7 +419,7 @@ void PlayerState::apply_dice_effects() { case AssistEffect::DICE_FEVER: for (size_t die_index = 0; die_index < 2; die_index++) { if (this->dice_results[die_index] > 0) { - this->dice_results[die_index] = 5; + this->dice_results[die_index] = s->options.is_trial() ? 6 : 5; } } break; @@ -434,6 +438,9 @@ void PlayerState::apply_dice_effects() { } break; case AssistEffect::DICE_FEVER_PLUS: + if (s->options.is_trial()) { + break; + } for (size_t die_index = 0; die_index < 2; die_index++) { if (this->dice_results[die_index] > 0) { this->dice_results[die_index] = 6; @@ -475,10 +482,18 @@ void PlayerState::compute_total_set_cards_cost() { size_t PlayerState::count_set_cards() const { size_t ret = 0; - for (size_t set_index = 0; set_index < 8; set_index++) { - auto card = this->set_cards[set_index]; - if (card && !(card->card_flags & 2)) { - ret++; + if (this->server()->options.is_trial()) { + for (size_t set_index = 0; set_index < 8; set_index++) { + if (this->card_refs[8 + set_index] != 0xFFFF) { + ret++; + } + } + } else { + for (size_t set_index = 0; set_index < 8; set_index++) { + auto card = this->set_cards[set_index]; + if (card && !(card->card_flags & 2)) { + ret++; + } } } return ret; @@ -559,7 +574,7 @@ void PlayerState::discard_and_redraw_hand() { this->discard_ref_from_hand(this->card_refs[0]); } - G_Unknown_GC_Ep3_6xB4x2C cmd; + G_Unknown_Ep3_6xB4x2C cmd; cmd.change_type = 3; cmd.client_id = this->client_id; cmd.card_refs.clear(0xFFFF); @@ -654,17 +669,21 @@ bool PlayerState::do_mulligan() { this->discard_ref_from_hand(this->card_refs[0]); } - G_Unknown_GC_Ep3_6xB4x2C cmd; - cmd.change_type = 3; - cmd.client_id = this->client_id; - cmd.card_refs.clear(0xFFFF); - cmd.unknown_a2.clear(0xFFFFFFFF); - s->send(cmd); + if (!s->options.is_trial()) { + G_Unknown_Ep3_6xB4x2C cmd; + cmd.change_type = 3; + cmd.client_id = this->client_id; + cmd.card_refs.clear(0xFFFF); + cmd.unknown_a2.clear(0xFFFFFFFF); + s->send(cmd); + } - this->deck_state->do_mulligan(); + this->deck_state->do_mulligan(s->options.is_trial()); this->draw_hand(5); - this->discard_log_card_refs.clear(0xFFFF); + if (!s->options.is_trial()) { + this->discard_log_card_refs.clear(0xFFFF); + } return true; } @@ -675,7 +694,7 @@ void PlayerState::draw_hand(ssize_t override_count) { size_t num_assists = s->assist_server->compute_num_assist_effects_for_client(this->client_id); for (size_t z = 0; z < num_assists; z++) { auto eff = s->assist_server->get_active_assist_by_index(z); - if (eff == AssistEffect::RICH_PLUS) { + if ((eff == AssistEffect::RICH_PLUS) && !s->options.is_trial()) { count = 4 - this->get_hand_size(); } else if (eff == AssistEffect::RICH) { count = 6 - this->get_hand_size(); @@ -694,7 +713,7 @@ void PlayerState::draw_hand(ssize_t override_count) { break; } } - if (s->get_setup_phase() == SetupPhase::MAIN_BATTLE) { + if (!s->options.is_trial() && (s->get_setup_phase() == SetupPhase::MAIN_BATTLE)) { this->stats.num_cards_drawn++; } } @@ -893,8 +912,7 @@ void PlayerState::log_discard(uint16_t card_ref, uint16_t reason) { this->discard_log_reasons[0] = reason; } -bool PlayerState::move_card_to_location_by_card_index( - size_t card_index, const Location& new_loc) { +bool PlayerState::move_card_to_location_by_card_index(size_t card_index, const Location& new_loc) { auto s = this->server(); shared_ptr card; @@ -1015,14 +1033,15 @@ void PlayerState::on_cards_destroyed() { void PlayerState::replace_all_set_assists_with_random_assists() { auto s = this->server(); + const auto& assist_card_ids = all_assist_card_ids(s->options.is_trial()); for (size_t client_id = 0; client_id < 4; client_id++) { auto other_ps = s->get_player_state(client_id); if (other_ps && ((other_ps->card_refs[6] != 0xFFFF) || (other_ps->set_assist_card_id != 0xFFFF))) { uint16_t card_id = 0x0130; while (card_id == 0x0130) { // God Whim - size_t index = s->get_random(ALL_ASSIST_CARD_IDS.size()); - card_id = ALL_ASSIST_CARD_IDS[index]; + size_t index = s->get_random(assist_card_ids.size()); + card_id = assist_card_ids[index]; if (!this->god_whim_can_use_hidden_cards) { auto ce = s->definition_for_card_id(card_id); if (!ce || ce->def.cannot_drop) { @@ -1171,7 +1190,7 @@ void PlayerState::send_set_card_updates(bool always_send) { } if (mask && !s->get_should_copy_prev_states_to_current_states()) { - G_ClearSetCardConditions_GC_Ep3_6xB4x4F cmd; + G_ClearSetCardConditions_Ep3_6xB4x4F cmd; cmd.client_id = this->client_id; cmd.clear_mask = mask; s->send(cmd); @@ -1307,7 +1326,7 @@ bool PlayerState::set_card_from_hand( this->update_hand_and_equip_state_and_send_6xB4x02_if_needed(); s->send_6xB4x05(); - G_Unknown_GC_Ep3_6xB4x4A cmd; + G_Unknown_Ep3_6xB4x4A cmd; cmd.card_refs.clear(0xFFFF); cmd.card_refs[0] = card_ref; cmd.client_id = this->client_id; @@ -1442,8 +1461,8 @@ void PlayerState::subtract_atk_points(uint8_t cost) { this->atk_points2 = min(this->atk_points, this->atk_points2_max); } -G_UpdateHand_GC_Ep3_6xB4x02 PlayerState::prepare_6xB4x02() const { - G_UpdateHand_GC_Ep3_6xB4x02 cmd; +G_UpdateHand_Ep3_6xB4x02 PlayerState::prepare_6xB4x02() const { + G_UpdateHand_Ep3_6xB4x02 cmd; cmd.client_id = this->client_id; cmd.state.dice_results = this->dice_results; cmd.state.atk_points = this->atk_points; @@ -1487,13 +1506,14 @@ void PlayerState::update_hand_and_equip_state_and_send_6xB4x02_if_needed( void PlayerState::set_random_assist_card_from_hand_for_free() { auto s = this->server(); + bool is_trial = s->options.is_trial(); vector candidate_card_refs; for (size_t hand_index = 0; hand_index < 6; hand_index++) { uint16_t card_ref = this->card_refs[hand_index]; auto ce = s->definition_for_card_ref(card_ref); if (ce && (ce->def.type == CardType::ASSIST) && - (assist_effect_number_for_card_id(ce->def.card_id) != AssistEffect::SQUEEZE)) { + (assist_effect_number_for_card_id(ce->def.card_id, is_trial) != AssistEffect::SQUEEZE)) { candidate_card_refs.emplace_back(card_ref); } } @@ -1506,8 +1526,8 @@ void PlayerState::set_random_assist_card_from_hand_for_free() { } } -G_UpdateShortStatuses_GC_Ep3_6xB4x04 PlayerState::prepare_6xB4x04() const { - G_UpdateShortStatuses_GC_Ep3_6xB4x04 cmd; +G_UpdateShortStatuses_Ep3_6xB4x04 PlayerState::prepare_6xB4x04() const { + G_UpdateShortStatuses_Ep3_6xB4x04 cmd; cmd.client_id = this->client_id; // Note: The original code calls memset to clear all the short status structs // at once. We don't do this because the default constructor has already @@ -1550,7 +1570,7 @@ void PlayerState::send_6xB4x04_if_needed(bool always_send) { if (always_send || (cmd.card_statuses != *this->card_short_statuses)) { auto s = this->server(); *this->card_short_statuses = cmd.card_statuses; - if (!s->get_should_copy_prev_states_to_current_states()) { + if (s->options.is_trial() || !s->get_should_copy_prev_states_to_current_states()) { s->send(cmd); } } @@ -1654,7 +1674,7 @@ bool PlayerState::set_action_cards_for_action_state(const ActionState& pa) { this->subtract_or_check_atk_or_def_points_for_action(pa, 1); if (action_type == ActionType::ATTACK) { - G_Unknown_GC_Ep3_6xB4x4A cmd; + G_Unknown_Ep3_6xB4x4A cmd; cmd.card_refs.clear(0xFFFF); cmd.client_id = this->client_id; cmd.round_num = s->get_round_num(); @@ -1693,7 +1713,7 @@ bool PlayerState::set_action_cards_for_action_state(const ActionState& pa) { } } else if (action_type == ActionType::DEFENSE) { - G_Unknown_GC_Ep3_6xB4x4A cmd; + G_Unknown_Ep3_6xB4x4A cmd; cmd.card_refs.clear(0xFFFF); cmd.client_id = this->client_id; cmd.round_num = s->get_round_num(); @@ -1777,14 +1797,26 @@ void PlayerState::handle_homesick_assist_effect_from_bomb(shared_ptr card) if (s->assist_server->get_active_assist_by_index(z) == AssistEffect::HOMESICK) { this->return_set_card_to_hand2(card_ref); this->log_discard(card_ref, 1); - this->set_cards[set_index]->card_flags |= 2; + // On NTE, the card is destroyed immediately + if (s->options.is_trial()) { + this->set_cards[set_index]->update_stats_on_destruction(); + this->set_cards[set_index].reset(); + } else { + this->set_cards[set_index]->card_flags |= 2; + } return; } } if (this->deck_state->set_card_ref_drawable_next(card_ref)) { this->log_discard(card_ref, 1); - this->set_cards[set_index]->card_flags |= 2; + // On NTE, the card is destroyed immediately + if (s->options.is_trial()) { + this->set_cards[set_index]->update_stats_on_destruction(); + this->set_cards[set_index].reset(); + } else { + this->set_cards[set_index]->card_flags |= 2; + } } } } diff --git a/src/Episode3/PlayerState.hh b/src/Episode3/PlayerState.hh index b0983b22..b5fbe663 100644 --- a/src/Episode3/PlayerState.hh +++ b/src/Episode3/PlayerState.hh @@ -58,8 +58,7 @@ public: void discard_all_attack_action_cards_from_hand(); void discard_all_item_and_creature_cards_from_hand(); void discard_and_redraw_hand(); - bool discard_card_or_add_to_draw_pile( - uint16_t card_ref, bool add_to_draw_pile); + bool discard_card_or_add_to_draw_pile(uint16_t card_ref, bool add_to_draw_pile); void discard_random_hand_card(); bool discard_ref_from_hand(uint16_t card_ref); void discard_set_assist_card(); @@ -76,8 +75,7 @@ public: const Location& loc, uint8_t target_team_id) const; uint8_t get_atk_points() const; - void get_short_status_for_card_index_in_hand( - size_t hand_index, CardShortStatus* stat) const; + void get_short_status_for_card_index_in_hand(size_t hand_index, CardShortStatus* stat) const; std::shared_ptr get_deck(); uint8_t get_def_points() const; uint8_t get_dice_result(size_t which) const; @@ -96,8 +94,7 @@ public: bool is_mulligan_allowed() const; bool is_team_turn() const; void log_discard(uint16_t card_ref, uint16_t reason); - bool move_card_to_location_by_card_index( - size_t card_index, const Location& new_loc); + bool move_card_to_location_by_card_index(size_t card_index, const Location& new_loc); void move_null_hand_refs_to_end(); void on_cards_destroyed(); void replace_all_set_assists_with_random_assists(); @@ -115,18 +112,15 @@ public: uint8_t assist_target_client_id, bool skip_error_checks_and_atk_sub); void set_initial_location(); - void set_map_occupied_bit_for_card_on_warp_tile( - std::shared_ptr card); + void set_map_occupied_bit_for_card_on_warp_tile(std::shared_ptr card); void set_map_occupied_bits_for_sc_and_creatures(); void subtract_def_points(uint8_t cost); - bool subtract_or_check_atk_or_def_points_for_action( - const ActionState& pa, bool deduct_points); + bool subtract_or_check_atk_or_def_points_for_action(const ActionState& pa, bool deduct_points); void subtract_atk_points(uint8_t cost); - G_UpdateHand_GC_Ep3_6xB4x02 prepare_6xB4x02() const; - void update_hand_and_equip_state_and_send_6xB4x02_if_needed( - bool always_send = false); + G_UpdateHand_Ep3_6xB4x02 prepare_6xB4x02() const; + void update_hand_and_equip_state_and_send_6xB4x02_if_needed(bool always_send = false); void set_random_assist_card_from_hand_for_free(); - G_UpdateShortStatuses_GC_Ep3_6xB4x04 prepare_6xB4x04() const; + G_UpdateShortStatuses_Ep3_6xB4x04 prepare_6xB4x04() const; void send_6xB4x04_if_needed(bool always_send = false); std::vector get_card_refs_within_range_from_all_players( const parray& range, diff --git a/src/Episode3/PlayerStateSubordinates.cc b/src/Episode3/PlayerStateSubordinates.cc index 10cf0d98..6453a195 100644 --- a/src/Episode3/PlayerStateSubordinates.cc +++ b/src/Episode3/PlayerStateSubordinates.cc @@ -481,6 +481,83 @@ bool ActionChainWithConds::can_apply_attack() const { return this->check_flag(4) ? false : (this->chain.target_card_ref_count != 0); } +/* 0000 */ int8_t effective_ap; +/* 0001 */ int8_t effective_tp; +/* 0002 */ int8_t ap_effect_bonus; +/* 0003 */ int8_t damage; +/* 0004 */ le_uint16_t acting_card_ref; +/* 0006 */ le_uint16_t unknown_card_ref_a3; +/* 0008 */ parray attack_action_card_refs; +/* 0018 */ uint8_t attack_action_card_ref_count; +/* 0019 */ AttackMedium attack_medium; +/* 001A */ uint8_t target_card_ref_count; +/* 001B */ ActionSubphase action_subphase; +/* 001C */ uint8_t strike_count; +/* 001D */ int8_t damage_multiplier; +/* 001E */ uint8_t attack_number; +/* 001F */ int8_t tp_effect_bonus; +/* 0020 */ uint8_t unused1; +/* 0021 */ uint8_t unused2; +/* 0022 */ int8_t card_ap; +/* 0023 */ int8_t card_tp; +/* 0024 */ le_uint32_t flags; +// The only difference between this structure and ActionChainWithConds is that +// these two fields have changed orders. +/* 0028 */ parray conditions; +/* 00B8 */ parray target_card_refs; +/* 0100 */ + +ActionChainWithCondsTrial::ActionChainWithCondsTrial(const ActionChainWithConds& src) + : effective_ap(src.chain.effective_ap), + effective_tp(src.chain.effective_tp), + ap_effect_bonus(src.chain.ap_effect_bonus), + damage(src.chain.damage), + acting_card_ref(src.chain.acting_card_ref), + unknown_card_ref_a3(src.chain.unknown_card_ref_a3), + attack_action_card_refs(src.chain.attack_action_card_refs), + attack_action_card_ref_count(src.chain.attack_action_card_ref_count), + attack_medium(src.chain.attack_medium), + target_card_ref_count(src.chain.target_card_ref_count), + action_subphase(src.chain.action_subphase), + strike_count(src.chain.strike_count), + damage_multiplier(src.chain.damage_multiplier), + attack_number(src.chain.attack_number), + tp_effect_bonus(src.chain.tp_effect_bonus), + unused1(src.chain.unused1), + unused2(src.chain.unused2), + card_ap(src.chain.card_ap), + card_tp(src.chain.card_tp), + flags(src.chain.flags), + conditions(src.conditions), + target_card_refs(src.chain.target_card_refs) {} + +ActionChainWithCondsTrial::operator ActionChainWithConds() const { + ActionChainWithConds ret; + ret.chain.effective_ap = this->effective_ap; + ret.chain.effective_tp = this->effective_tp; + ret.chain.ap_effect_bonus = this->ap_effect_bonus; + ret.chain.damage = this->damage; + ret.chain.acting_card_ref = this->acting_card_ref; + ret.chain.unknown_card_ref_a3 = this->unknown_card_ref_a3; + ret.chain.attack_action_card_refs = this->attack_action_card_refs; + ret.chain.attack_action_card_ref_count = this->attack_action_card_ref_count; + ret.chain.attack_medium = this->attack_medium; + ret.chain.target_card_ref_count = this->target_card_ref_count; + ret.chain.action_subphase = this->action_subphase; + ret.chain.strike_count = this->strike_count; + ret.chain.damage_multiplier = this->damage_multiplier; + ret.chain.attack_number = this->attack_number; + ret.chain.tp_effect_bonus = this->tp_effect_bonus; + ret.chain.unused1 = this->unused1; + ret.chain.unused2 = this->unused2; + ret.chain.card_ap = this->card_ap; + ret.chain.card_tp = this->card_tp; + ret.chain.flags = this->flags; + ret.chain.target_card_refs = this->target_card_refs; + ret.conditions = this->conditions; + return ret; +} + ActionMetadata::ActionMetadata() { this->clear(); } @@ -531,6 +608,8 @@ void ActionMetadata::clear() { this->action_subphase = ActionSubphase::INVALID_FF; this->defense_power = 0; this->defense_bonus = 0; + // TODO: Ep3 NTE doesn't set attack_bonus to zero here. Is the field just + // unused in NTE? this->attack_bonus = 0; this->flags = 0; this->target_card_refs.clear(0xFFFF); @@ -759,6 +838,23 @@ const char* PlayerBattleStats::name_for_rank(uint8_t rank) { return RANK_NAMES[rank]; } +PlayerBattleStatsTrial::PlayerBattleStatsTrial(const PlayerBattleStats& data) + : damage_given(data.damage_given.load()), + damage_taken(data.damage_taken.load()), + num_opponent_cards_destroyed(data.num_opponent_cards_destroyed.load()), + num_owned_cards_destroyed(data.num_owned_cards_destroyed.load()), + total_move_distance(data.total_move_distance.load()) {} + +PlayerBattleStatsTrial::operator PlayerBattleStats() const { + PlayerBattleStats ret; + ret.damage_given = this->damage_given.load(); + ret.damage_taken = this->damage_taken.load(); + ret.num_opponent_cards_destroyed = this->num_opponent_cards_destroyed.load(); + ret.num_owned_cards_destroyed = this->num_owned_cards_destroyed.load(); + ret.total_move_distance = this->total_move_distance.load(); + return ret; +} + static bool is_card_within_range( const parray& range, const Location& anchor_loc, diff --git a/src/Episode3/PlayerStateSubordinates.hh b/src/Episode3/PlayerStateSubordinates.hh index 6c752644..be946206 100644 --- a/src/Episode3/PlayerStateSubordinates.hh +++ b/src/Episode3/PlayerStateSubordinates.hh @@ -101,6 +101,8 @@ struct ActionState { } __attribute__((packed)); struct ActionChain { + // Note: Episode 3 Trial Edition has a different format for this structure. + // See ActionChainWithCondsTrial for details. /* 00 */ int8_t effective_ap; /* 01 */ int8_t effective_tp; /* 02 */ int8_t ap_effect_bonus; @@ -170,6 +172,38 @@ struct ActionChainWithConds { std::string str() const; } __attribute__((packed)); +struct ActionChainWithCondsTrial { + /* 0000 */ int8_t effective_ap; + /* 0001 */ int8_t effective_tp; + /* 0002 */ int8_t ap_effect_bonus; + /* 0003 */ int8_t damage; + /* 0004 */ le_uint16_t acting_card_ref; + /* 0006 */ le_uint16_t unknown_card_ref_a3; + /* 0008 */ parray attack_action_card_refs; + /* 0018 */ uint8_t attack_action_card_ref_count; + /* 0019 */ AttackMedium attack_medium; + /* 001A */ uint8_t target_card_ref_count; + /* 001B */ ActionSubphase action_subphase; + /* 001C */ uint8_t strike_count; + /* 001D */ int8_t damage_multiplier; + /* 001E */ uint8_t attack_number; + /* 001F */ int8_t tp_effect_bonus; + /* 0020 */ uint8_t unused1; + /* 0021 */ uint8_t unused2; + /* 0022 */ int8_t card_ap; + /* 0023 */ int8_t card_tp; + /* 0024 */ le_uint32_t flags; + // The only difference between this structure and ActionChainWithConds is that + // these two fields have changed orders. + /* 0028 */ parray conditions; + /* 00B8 */ parray target_card_refs; + /* 0100 */ + + ActionChainWithCondsTrial() = default; + ActionChainWithCondsTrial(const ActionChainWithConds& src); + operator ActionChainWithConds() const; +} __attribute__((packed)); + struct ActionMetadata { /* 00 */ le_uint16_t card_ref; /* 02 */ uint8_t target_card_ref_count; @@ -275,6 +309,19 @@ struct PlayerBattleStats { static const char* name_for_rank(uint8_t rank); } __attribute__((packed)); +struct PlayerBattleStatsTrial { + /* 00 */ le_uint32_t damage_given = 0; + /* 04 */ le_uint32_t damage_taken = 0; + /* 08 */ le_uint32_t num_opponent_cards_destroyed = 0; + /* 0C */ le_uint32_t num_owned_cards_destroyed = 0; + /* 10 */ le_uint32_t total_move_distance = 0; + /* 14 */ + + PlayerBattleStatsTrial() = default; + PlayerBattleStatsTrial(const PlayerBattleStats& data); + operator PlayerBattleStats() const; +} __attribute__((packed)); + std::vector get_card_refs_within_range( const parray& range, const Location& loc, diff --git a/src/Episode3/RulerServer.cc b/src/Episode3/RulerServer.cc index ac07f2ae..2727d44c 100644 --- a/src/Episode3/RulerServer.cc +++ b/src/Episode3/RulerServer.cc @@ -204,6 +204,8 @@ const ActionChainWithConds* RulerServer::action_chain_with_conds_for_card_ref( uint16_t card_ref) const { uint8_t client_id = client_id_for_card_ref(card_ref); if (client_id != 0xFF) { + // There appears to be a bug in Trial Edition: the bound on this loop is + // 0x10, not 9. for (size_t z = 0; z < 9; z++) { const auto* chain = &this->set_card_action_chains[client_id]->at(z); if (card_ref == chain->chain.acting_card_ref) { @@ -1672,7 +1674,7 @@ int32_t RulerServer::error_code_for_client_setting_card( } // Check for assists that can only be set on yourself - auto eff = assist_effect_number_for_card_id(ce->def.card_id); + auto eff = assist_effect_number_for_card_id(ce->def.card_id, this->server()->options.is_trial()); if (((eff == AssistEffect::LEGACY) || (eff == AssistEffect::EXCHANGE)) && (assist_target_client_id != 0xFF) && (assist_target_client_id != client_id_for_card_ref(card_ref))) { diff --git a/src/Episode3/RulerServer.hh b/src/Episode3/RulerServer.hh index 5b78b30f..bf939ffd 100644 --- a/src/Episode3/RulerServer.hh +++ b/src/Episode3/RulerServer.hh @@ -49,12 +49,9 @@ public: std::shared_ptr server(); std::shared_ptr server() const; - ActionChainWithConds* action_chain_with_conds_for_card_ref( - uint16_t card_ref); - const ActionChainWithConds* action_chain_with_conds_for_card_ref( - uint16_t card_ref) const; - bool any_attack_action_card_is_support_tech_or_support_pb( - const ActionState& pa) const; + ActionChainWithConds* action_chain_with_conds_for_card_ref(uint16_t card_ref); + const ActionChainWithConds* action_chain_with_conds_for_card_ref(uint16_t card_ref) const; + bool any_attack_action_card_is_support_tech_or_support_pb(const ActionState& pa) const; bool card_has_pierce_or_rampage( uint8_t client_id, ConditionType cond_type, @@ -63,27 +60,21 @@ public: uint16_t action_card_ref, uint8_t def_effect_index, AttackMedium attack_medium) const; - bool attack_action_has_rampage_and_not_pierce( - const ActionState& pa, uint16_t card_ref) const; - bool attack_action_has_pierce_and_not_rampage( - const ActionState& pa, uint8_t client_id); + bool attack_action_has_rampage_and_not_pierce(const ActionState& pa, uint16_t card_ref) const; + bool attack_action_has_pierce_and_not_rampage(const ActionState& pa, uint8_t client_id); bool card_exists_by_status(const CardShortStatus& stat) const; bool card_has_mighty_knuckle(uint32_t card_ref) const; uint16_t card_id_for_card_ref(uint16_t card_ref) const; static bool card_id_is_boss_sc(uint16_t card_id); static bool card_id_is_support_tech_or_support_pb(uint16_t card_id); bool card_ref_can_attack(uint16_t card_ref); - bool card_ref_can_move( - uint8_t client_id, uint16_t card_ref, bool ignore_atk_points) const; - bool card_ref_has_class_usability_condition( - uint16_t card_ref) const; + bool card_ref_can_move(uint8_t client_id, uint16_t card_ref, bool ignore_atk_points) const; + bool card_ref_has_class_usability_condition(uint16_t card_ref) const; bool card_ref_has_free_maneuver(uint16_t card_ref) const; bool card_ref_is_aerial(uint16_t card_ref) const; - bool card_ref_is_aerial_or_has_free_maneuver( - uint16_t card_ref) const; + bool card_ref_is_aerial_or_has_free_maneuver(uint16_t card_ref) const; bool card_ref_is_boss_sc(uint32_t card_ref) const; - bool card_ref_or_any_set_card_has_condition_46( - uint16_t card_ref) const; + bool card_ref_or_any_set_card_has_condition_46(uint16_t card_ref) const; bool card_ref_or_sc_has_fixed_range(uint16_t card_ref) const; bool check_move_path_and_get_cost( uint8_t client_id, @@ -123,14 +114,12 @@ public: uint16_t* out_effective_card_id, TargetMode* out_effective_target_mode, uint16_t* out_orig_card_ref) const; - size_t count_rampage_targets_for_attack( - const ActionState& pa, uint8_t client_id) const; + size_t count_rampage_targets_for_attack(const ActionState& pa, uint8_t client_id) const; bool defense_card_can_apply_to_attack( uint16_t defense_card_ref, uint16_t attacker_card_ref, uint16_t attacker_sc_card_ref) const; - bool defense_card_matches_any_attack_card_top_color( - const ActionState& pa) const; + bool defense_card_matches_any_attack_card_top_color(const ActionState& pa) const; std::shared_ptr definition_for_card_ref(uint16_t card_ref) const; int32_t error_code_for_client_setting_card( uint8_t client_id, @@ -157,17 +146,14 @@ public: size_t num_occupied_tiles, size_t num_vacant_tiles) const; uint16_t get_ally_sc_card_ref(uint16_t card_ref) const; - std::shared_ptr definition_for_card_id( - uint32_t card_id) const; + std::shared_ptr definition_for_card_id(uint32_t card_id) const; uint32_t get_card_id_with_effective_range( uint16_t card_ref, uint16_t card_id_override, TargetMode* out_target_mode) const; uint8_t get_card_ref_max_hp(uint16_t card_ref) const; bool get_creature_summon_area( uint8_t client_id, Location* out_loc, uint8_t* out_region_size) const; - std::shared_ptr get_hand_and_equip_state_for_client_id( - uint8_t client_id); - std::shared_ptr get_hand_and_equip_state_for_client_id( - uint8_t client_id) const; + std::shared_ptr get_hand_and_equip_state_for_client_id(uint8_t client_id); + std::shared_ptr get_hand_and_equip_state_for_client_id(uint8_t client_id) const; bool get_move_path_length_and_cost( uint32_t client_id, uint32_t card_ref, @@ -188,8 +174,7 @@ public: std::shared_ptr state_flags, std::shared_ptr assist_server); size_t max_move_distance_for_card_ref(uint32_t card_ref) const; - static void offsets_for_direction( - const Location& loc, int32_t* out_x_offset, int32_t* out_y_offset); + static void offsets_for_direction(const Location& loc, int32_t* out_x_offset, int32_t* out_y_offset); void register_player( uint8_t client_id, std::shared_ptr hes, diff --git a/src/Episode3/Server.cc b/src/Episode3/Server.cc index 8f726872..94ad6995 100644 --- a/src/Episode3/Server.cc +++ b/src/Episode3/Server.cc @@ -14,6 +14,8 @@ namespace Episode3 { // "[V1][FINAL2.0] 03/09/13 15:30 by K.Toya" static const char* VERSION_SIGNATURE = "newserv Ep3 based on [V1][FINAL2.0] 03/09/13 15:30 by K.Toya"; +static const char* VERSION_SIGNATURE_NTE = + "newserv Ep3 NTE based on 03/05/29 18:00 by K.Toya"; Server::PresenceEntry::PresenceEntry() { this->clear(); @@ -203,7 +205,7 @@ void Server::send(const void* data, size_t size, uint8_t command, bool enable_ma string masked_data; if (enable_masking && - (l->base_version != Version::GC_EP3_NTE) && + !this->options.is_trial() && !(this->options.behavior_flags & BehaviorFlag::DISABLE_MASKING) && (size >= 8)) { masked_data.assign(reinterpret_cast(data), size); @@ -233,18 +235,25 @@ void Server::send(const void* data, size_t size, uint8_t command, bool enable_ma void Server::send_6xB4x46() const { // Note: This function is not part of the original implementation; it was // factored out from its callsites in this file and the strings were changed. - G_ServerVersionStrings_GC_Ep3_6xB4x46 cmd46; - cmd46.version_signature.encode(VERSION_SIGNATURE, 1); - cmd46.date_str1.encode(format_time(this->options.card_index->definitions_mtime() * 1000000), 1); - string date_str2 = string_printf( - "Random:%08" PRIX32 "+%08" PRIX32, - this->options.random_crypt->seed(), - this->options.random_crypt->absolute_offset()); - if (this->last_chosen_map) { - date_str2 += string_printf(" Map:%08" PRIX32, this->last_chosen_map->map_number); + if (this->options.is_trial()) { + G_ServerVersionStrings_Ep3NTE_6xB4x46 cmd; + cmd.version_signature.encode(VERSION_SIGNATURE_NTE, 1); + cmd.date_str1.encode(format_time(this->options.card_index->definitions_mtime() * 1000000), 1); + this->send(cmd); + } else { + G_ServerVersionStrings_Ep3_6xB4x46 cmd; + cmd.version_signature.encode(VERSION_SIGNATURE, 1); + cmd.date_str1.encode(format_time(this->options.card_index->definitions_mtime() * 1000000), 1); + string date_str2 = string_printf( + "Random:%08" PRIX32 "+%08" PRIX32, + this->options.random_crypt->seed(), + this->options.random_crypt->absolute_offset()); + if (this->last_chosen_map) { + date_str2 += string_printf(" Map:%08" PRIX32, this->last_chosen_map->map_number); + } + cmd.date_str2.encode(date_str2, 1); + this->send(cmd); } - cmd46.date_str2.encode(date_str2, 1); - this->send(cmd46); } string Server::prepare_6xB6x41_map_definition(shared_ptr map, uint8_t language, bool is_trial) { @@ -253,8 +262,8 @@ string Server::prepare_6xB6x41_map_definition(shared_ptr ma const auto& compressed = vm->compressed(is_trial); StringWriter w; - uint32_t subcommand_size = (compressed.size() + sizeof(G_MapData_GC_Ep3_6xB6x41) + 3) & (~3); - w.put({{{{0xB6, 0, 0}, subcommand_size}, 0x41, {}}, vm->map->map_number.load(), compressed.size(), 0}); + uint32_t subcommand_size = (compressed.size() + sizeof(G_MapData_Ep3_6xB6x41) + 3) & (~3); + w.put({{{{0xB6, 0, 0}, subcommand_size}, 0x41, {}}, vm->map->map_number.load(), compressed.size(), 0}); w.write(compressed); return std::move(w.str()); } @@ -271,8 +280,7 @@ void Server::send_commands_for_joining_spectator(Channel& ch) const { } if (this->last_chosen_map) { - string data = this->prepare_6xB6x41_map_definition( - this->last_chosen_map, ch.language, (ch.version == Version::GC_EP3_NTE)); + string data = this->prepare_6xB6x41_map_definition(this->last_chosen_map, ch.language, this->options.is_trial()); this->log().info("Sending %c version of map %08" PRIX32, char_for_language_code(ch.language), this->last_chosen_map->map_number); ch.send(0x6C, 0x00, data); } @@ -286,10 +294,14 @@ void Server::send_commands_for_joining_spectator(Channel& ch) const { ch.send(0xC9, 0x00, ps->prepare_6xB4x04()); } } - { - G_UpdateMap_GC_Ep3_6xB4x05 cmd_05; - cmd_05.state = *this->map_and_rules; - ch.send(0xC9, 0x00, cmd_05); + if (ch.version == Version::GC_EP3_NTE) { + G_UpdateMap_Ep3NTE_6xB4x05 cmd; + cmd.state = *this->map_and_rules; + ch.send(0xC9, 0x00, cmd); + } else { + G_UpdateMap_Ep3_6xB4x05 cmd; + cmd.state = *this->map_and_rules; + ch.send(0xC9, 0x00, cmd); } // TODO: Sega does something like this; do we have to do this too? // for (uint8_t client_id = 0; client_id < 4; client_id++) { @@ -302,7 +314,7 @@ void Server::send_commands_for_joining_spectator(Channel& ch) const { ch.send(0xC9, 0x00, this->prepare_6xB4x1C_names_update()); ch.send(0xC9, 0x00, this->prepare_6xB4x50_trap_tile_locations()); { - G_LoadCurrentEnvironment_GC_Ep3_6xB4x3B cmd_3B; + G_LoadCurrentEnvironment_Ep3_6xB4x3B cmd_3B; ch.send(0xC9, 0x00, &cmd_3B, sizeof(cmd_3B)); } } @@ -740,7 +752,7 @@ void Server::dice_phase_after() { size_t num_assists = this->assist_server->compute_num_assist_effects_for_client(client_id); for (size_t z = 0; z < num_assists; z++) { auto eff = this->assist_server->get_active_assist_by_index(z); - if ((eff == AssistEffect::CHARITY_PLUS) || (eff == AssistEffect::CHARITY)) { + if ((eff == AssistEffect::CHARITY) || (!this->options.is_trial() && (eff == AssistEffect::CHARITY_PLUS))) { int16_t exp_delta = (eff == AssistEffect::CHARITY_PLUS) ? -1 : 1; for (size_t other_client_id = 0; other_client_id < 4; other_client_id++) { auto other_ps = this->player_states[other_client_id]; @@ -798,7 +810,8 @@ void Server::draw_phase_after() { } } - if (this->overall_time_expired || (this->round_num >= 1000)) { + // Apparently the hard limit of 1000 was added after NTE was released + if (this->overall_time_expired || (!this->options.is_trial() && (this->round_num >= 1000))) { bool no_winner_specified = true; for (size_t z = 0; z < 4; z++) { auto ps = this->player_states[z]; @@ -808,7 +821,12 @@ void Server::draw_phase_after() { } } if (no_winner_specified) { - this->compute_losing_team_id_and_add_winner_flags(0); + if (this->options.is_trial()) { + // TODO: This looks like an incomplete version of compute_losing_team_id_and_add_winner_flags; reconcile this + throw runtime_error("unimplemented NTE condition"); + } else { + this->compute_losing_team_id_and_add_winner_flags(0); + } } this->round_num--; this->set_battle_ended(); @@ -1009,106 +1027,108 @@ bool Server::is_registration_complete() const { } void Server::move_phase_after() { - for (size_t trap_type = 0; trap_type < 5; trap_type++) { - uint8_t trap_tile_index = this->chosen_trap_tile_index_of_type[trap_type]; - if (trap_tile_index == 0xFF) { - continue; - } - - bool should_trigger = false; - int16_t trap_x = this->trap_tile_locs[trap_type][trap_tile_index][0]; - int16_t trap_y = this->trap_tile_locs[trap_type][trap_tile_index][1]; - 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 && (sc_card->card_flags & 0x80) && - (sc_card->loc.x == trap_x) && (sc_card->loc.y == trap_y)) { - should_trigger = true; - break; - } + if (!this->options.is_trial()) { + for (size_t trap_type = 0; trap_type < 5; trap_type++) { + uint8_t trap_tile_index = this->chosen_trap_tile_index_of_type[trap_type]; + if (trap_tile_index == 0xFF) { + continue; } - } - if (!should_trigger) { - continue; - } - static const array, 5> default_trap_card_ids = { - // Red: Dice Fever, Heavy Fog, Muscular, Immortality, Snail Pace - vector{0x00F7, 0x010F, 0x012E, 0x013B, 0x013C}, - // Blue: Gold Rush, Charity, Requiem - vector{0x0131, 0x012B, 0x0133}, - // Purple: Powerless Rain, Trash 1, Empty Hand, Skip Draw - vector{0x00FA, 0x0125, 0x0126, 0x0137}, - // Green: Brave Wind, Homesick, Fly - vector{0x00FB, 0x014E, 0x0107}, - // Yellow: Dice+1, Battle Royale, Reverse Card, Giant Garden, Fix - vector{0x00F6, 0x0242, 0x014B, 0x0145, 0x012D}}; - - const vector* 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)]; - // } - 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())); - } - - if (trap_card_id != 0xFFFF) { + bool should_trigger = false; + int16_t trap_x = this->trap_tile_locs[trap_type][trap_tile_index][0]; + int16_t trap_y = this->trap_tile_locs[trap_type][trap_tile_index][1]; 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(trap_type); - cmd.unknown_a2[0] = trap_card_id; - cmd.unknown_a2[1] = 0xFFFFFFFF; - this->send(cmd); + if (sc_card && (sc_card->card_flags & 0x80) && + (sc_card->loc.x == trap_x) && (sc_card->loc.y == trap_y)) { + should_trigger = true; + break; } } } - } - - // Note: This is the original implementation: - // if (this->num_trap_tiles_of_type[trap_type] > 1) { - // uint8_t new_index = this->chosen_trap_tile_index_of_type[trap_type]; - // while (new_index == this->chosen_trap_tile_index_of_type[trap_type]) { - // new_index = this->get_random(this->num_trap_tiles_of_type[trap_type]); - // } - // this->chosen_trap_tile_index_of_type[trap_type] = new_index; - // this->send_6xB4x50(); - // } - // We instead use an implementation that consumes a constant amount of - // randomness per pass. - if (this->num_trap_tiles_of_type[trap_type] == 2) { - this->chosen_trap_tile_index_of_type[trap_type] ^= 1; - this->send_6xB4x50_trap_tile_locations(); - } else if (this->num_trap_tiles_of_type[trap_type] > 2) { - // Generate a new random index, but forbid it from matching the existing - // index - uint8_t new_index = this->get_random(this->num_trap_tiles_of_type[trap_type] - 1); - if (new_index >= this->chosen_trap_tile_index_of_type[trap_type]) { - new_index++; + if (!should_trigger) { + continue; + } + + static const array, 5> default_trap_card_ids = { + // Red: Dice Fever, Heavy Fog, Muscular, Immortality, Snail Pace + vector{0x00F7, 0x010F, 0x012E, 0x013B, 0x013C}, + // Blue: Gold Rush, Charity, Requiem + vector{0x0131, 0x012B, 0x0133}, + // Purple: Powerless Rain, Trash 1, Empty Hand, Skip Draw + vector{0x00FA, 0x0125, 0x0126, 0x0137}, + // Green: Brave Wind, Homesick, Fly + vector{0x00FB, 0x014E, 0x0107}, + // Yellow: Dice+1, Battle Royale, Reverse Card, Giant Garden, Fix + vector{0x00F6, 0x0242, 0x014B, 0x0145, 0x012D}}; + + const vector* 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)]; + // } + 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())); + } + + 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_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(trap_type); + cmd.unknown_a2[0] = trap_card_id; + cmd.unknown_a2[1] = 0xFFFFFFFF; + this->send(cmd); + } + } + } + } + + // Note: This is the original implementation: + // if (this->num_trap_tiles_of_type[trap_type] > 1) { + // uint8_t new_index = this->chosen_trap_tile_index_of_type[trap_type]; + // while (new_index == this->chosen_trap_tile_index_of_type[trap_type]) { + // new_index = this->get_random(this->num_trap_tiles_of_type[trap_type]); + // } + // this->chosen_trap_tile_index_of_type[trap_type] = new_index; + // this->send_6xB4x50(); + // } + // We instead use an implementation that consumes a constant amount of + // randomness per pass. + if (this->num_trap_tiles_of_type[trap_type] == 2) { + this->chosen_trap_tile_index_of_type[trap_type] ^= 1; + this->send_6xB4x50_trap_tile_locations(); + } else if (this->num_trap_tiles_of_type[trap_type] > 2) { + // Generate a new random index, but forbid it from matching the existing + // index + uint8_t new_index = this->get_random(this->num_trap_tiles_of_type[trap_type] - 1); + if (new_index >= this->chosen_trap_tile_index_of_type[trap_type]) { + new_index++; + } + this->chosen_trap_tile_index_of_type[trap_type] = new_index; + this->send_6xB4x50_trap_tile_locations(); } - this->chosen_trap_tile_index_of_type[trap_type] = new_index; - this->send_6xB4x50_trap_tile_locations(); } } @@ -1127,8 +1147,8 @@ void Server::action_phase_before() { } } -G_SetPlayerNames_GC_Ep3_6xB4x1C Server::prepare_6xB4x1C_names_update() const { - G_SetPlayerNames_GC_Ep3_6xB4x1C cmd; +G_SetPlayerNames_Ep3_6xB4x1C Server::prepare_6xB4x1C_names_update() const { + G_SetPlayerNames_Ep3_6xB4x1C cmd; cmd.entries = this->name_entries; return cmd; } @@ -1138,7 +1158,7 @@ void Server::send_6xB4x1C_names_update() { } int8_t Server::send_6xB4x33_remove_ally_atk_if_needed(const ActionState& pa) { - G_SubtractAllyATKPoints_GC_Ep3_6xB4x33 cmd; + G_SubtractAllyATKPoints_Ep3_6xB4x33 cmd; bool has_ally_cost = false; uint8_t ally_cost = 0; @@ -1194,8 +1214,8 @@ int8_t Server::send_6xB4x33_remove_ally_atk_if_needed(const ActionState& pa) { return 1; } -G_UpdateDecks_GC_Ep3_6xB4x07 Server::prepare_6xB4x07_decks_update() const { - G_UpdateDecks_GC_Ep3_6xB4x07 cmd07; +G_UpdateDecks_Ep3_6xB4x07 Server::prepare_6xB4x07_decks_update() const { + G_UpdateDecks_Ep3_6xB4x07 cmd07; for (size_t z = 0; z < 4; z++) { if (!this->check_presence_entry(z)) { cmd07.entries_present[z] = 0; @@ -1212,9 +1232,15 @@ G_UpdateDecks_GC_Ep3_6xB4x07 Server::prepare_6xB4x07_decks_update() const { void Server::send_all_state_updates() { this->send(this->prepare_6xB4x07_decks_update()); - G_UpdateMap_GC_Ep3_6xB4x05 cmd; - cmd.state = *this->map_and_rules; - this->send(cmd); + if (this->options.is_trial()) { + G_UpdateMap_Ep3NTE_6xB4x05 cmd; + cmd.state = *this->map_and_rules; + this->send(cmd); + } else { + G_UpdateMap_Ep3_6xB4x05 cmd; + cmd.state = *this->map_and_rules; + this->send(cmd); + } this->send_6xB4x02_for_all_players_if_needed(); } @@ -1535,10 +1561,17 @@ void Server::setup_and_start_battle() { this->update_battle_state_flags_and_send_6xB4x03_if_needed(true); this->send_6xB4x50_trap_tile_locations(); - G_UpdateMap_GC_Ep3_6xB4x05 cmd05; - cmd05.state = *this->map_and_rules; - cmd05.start_battle = 1; - this->send(cmd05); + if (this->options.is_trial()) { + G_UpdateMap_Ep3NTE_6xB4x05 cmd; + cmd.state = *this->map_and_rules; + cmd.start_battle = 1; + this->send(cmd); + } else { + G_UpdateMap_Ep3_6xB4x05 cmd; + cmd.state = *this->map_and_rules; + cmd.start_battle = 1; + this->send(cmd); + } this->battle_start_usecs = now(); @@ -1552,8 +1585,8 @@ void Server::setup_and_start_battle() { } } -G_SetStateFlags_GC_Ep3_6xB4x03 Server::prepare_6xB4x03() const { - G_SetStateFlags_GC_Ep3_6xB4x03 cmd; +G_SetStateFlags_Ep3_6xB4x03 Server::prepare_6xB4x03() const { + G_SetStateFlags_Ep3_6xB4x03 cmd; cmd.state.turn_num = this->round_num; cmd.state.battle_phase = this->battle_phase; cmd.state.current_team_turn1 = this->current_team_turn1; @@ -1579,7 +1612,7 @@ G_SetStateFlags_GC_Ep3_6xB4x03 Server::prepare_6xB4x03() const { } void Server::update_battle_state_flags_and_send_6xB4x03_if_needed(bool always_send) { - G_SetStateFlags_GC_Ep3_6xB4x03 cmd = this->prepare_6xB4x03(); + G_SetStateFlags_Ep3_6xB4x03 cmd = this->prepare_6xB4x03(); if (always_send || (*this->state_flags != cmd.state)) { *this->state_flags = cmd.state; this->send(cmd); @@ -1672,7 +1705,7 @@ void Server::on_server_data_input(shared_ptr sender_c, const string& dat } void Server::handle_CAx0B_mulligan_hand(shared_ptr, const string& data) { - const auto& in_cmd = check_size_t(data); + const auto& in_cmd = check_size_t(data); this->send_debug_command_received_message( in_cmd.client_id, in_cmd.header.subsubcommand, "REDRAW"); if (in_cmd.client_id >= 4) { @@ -1695,16 +1728,17 @@ void Server::handle_CAx0B_mulligan_hand(shared_ptr, const string& data) } } - G_ActionResult_GC_Ep3_6xB4x1E out_cmd; - out_cmd.sequence_num = in_cmd.header.sequence_num.load(); - out_cmd.error_code = error_code; - this->send(out_cmd); - - this->send_debug_message_if_error_code_nonzero(in_cmd.client_id, out_cmd.error_code); + if (!this->options.is_trial() || (error_code == 0)) { + G_ActionResult_Ep3_6xB4x1E out_cmd; + out_cmd.sequence_num = in_cmd.header.sequence_num.load(); + out_cmd.error_code = error_code; + this->send(out_cmd); + } + this->send_debug_message_if_error_code_nonzero(in_cmd.client_id, error_code); } void Server::handle_CAx0C_end_mulligan_phase(shared_ptr, const string& data) { - const auto& in_cmd = check_size_t(data); + const auto& in_cmd = check_size_t(data); this->send_debug_command_received_message( in_cmd.client_id, in_cmd.header.subsubcommand, "SETUP ADV 2"); if (in_cmd.client_id >= 4) { @@ -1721,7 +1755,11 @@ void Server::handle_CAx0C_end_mulligan_phase(shared_ptr, const string& d error_code = -0x78; } - G_ActionResult_GC_Ep3_6xB4x1E out_cmd_ack; + if (this->options.is_trial() && error_code) { + return; + } + + G_ActionResult_Ep3_6xB4x1E out_cmd_ack; out_cmd_ack.sequence_num = in_cmd.header.sequence_num; out_cmd_ack.response_phase = 1; this->send(out_cmd_ack); @@ -1748,7 +1786,7 @@ void Server::handle_CAx0C_end_mulligan_phase(shared_ptr, const string& d } } - G_ActionResult_GC_Ep3_6xB4x1E out_cmd_fin; + G_ActionResult_Ep3_6xB4x1E out_cmd_fin; out_cmd_fin.sequence_num = in_cmd.header.sequence_num; out_cmd_fin.response_phase = 2; out_cmd_fin.error_code = error_code; @@ -1758,28 +1796,27 @@ void Server::handle_CAx0C_end_mulligan_phase(shared_ptr, const string& d } void Server::handle_CAx0D_end_non_action_phase(shared_ptr, const string& data) { - const auto& in_cmd = check_size_t(data); - this->send_debug_command_received_message( - in_cmd.client_id, in_cmd.header.subsubcommand, "END PHASE"); + const auto& in_cmd = check_size_t(data); + this->send_debug_command_received_message(in_cmd.client_id, in_cmd.header.subsubcommand, "END PHASE"); if (in_cmd.client_id >= 4) { throw runtime_error("invalid client ID"); } - G_ActionResult_GC_Ep3_6xB4x1E out_cmd_ack; + G_ActionResult_Ep3_6xB4x1E out_cmd_ack; out_cmd_ack.sequence_num = in_cmd.header.sequence_num; out_cmd_ack.response_phase = 1; this->send(out_cmd_ack); this->set_client_id_ready_to_advance_phase(in_cmd.client_id); - G_ActionResult_GC_Ep3_6xB4x1E out_cmd_fin; + G_ActionResult_Ep3_6xB4x1E out_cmd_fin; out_cmd_fin.sequence_num = in_cmd.header.sequence_num; out_cmd_fin.response_phase = 2; this->send(out_cmd_fin); } void Server::handle_CAx0E_discard_card_from_hand(shared_ptr, const string& data) { - const auto& in_cmd = check_size_t(data); + const auto& in_cmd = check_size_t(data); this->send_debug_command_received_message( in_cmd.client_id, in_cmd.header.subsubcommand, "DISCARD"); if (in_cmd.client_id >= 4) { @@ -1809,18 +1846,18 @@ void Server::handle_CAx0E_discard_card_from_hand(shared_ptr, const strin } } - G_ActionResult_GC_Ep3_6xB4x1E out_cmd; - out_cmd.sequence_num = in_cmd.header.sequence_num; - out_cmd.error_code = error_code; - this->send(out_cmd); - - this->send_debug_message_if_error_code_nonzero(in_cmd.client_id, out_cmd.error_code); + if (!this->options.is_trial() || (error_code == 0)) { + G_ActionResult_Ep3_6xB4x1E out_cmd; + out_cmd.sequence_num = in_cmd.header.sequence_num; + out_cmd.error_code = error_code; + this->send(out_cmd); + } + this->send_debug_message_if_error_code_nonzero(in_cmd.client_id, error_code); } void Server::handle_CAx0F_set_card_from_hand(shared_ptr, const string& data) { - const auto& in_cmd = check_size_t(data); - this->send_debug_command_received_message( - in_cmd.client_id, in_cmd.header.subsubcommand, "SET FC"); + const auto& in_cmd = check_size_t(data); + this->send_debug_command_received_message(in_cmd.client_id, in_cmd.header.subsubcommand, "SET FC"); if (in_cmd.client_id >= 4) { throw runtime_error("invalid client ID"); } @@ -1836,6 +1873,7 @@ void Server::handle_CAx0F_set_card_from_hand(shared_ptr, const string& d error_code = -0x78; } + bool was_set = false; if (in_cmd.client_id >= 4) { error_code = -0x78; } @@ -1845,22 +1883,22 @@ void Server::handle_CAx0F_set_card_from_hand(shared_ptr, const string& d if (!ps) { this->ruler_server->error_code1 = -0x72; } else { - ps->set_card_from_hand(in_cmd.card_ref, in_cmd.set_index, &in_cmd.loc, in_cmd.assist_target_player, 0); + was_set = ps->set_card_from_hand(in_cmd.card_ref, in_cmd.set_index, &in_cmd.loc, in_cmd.assist_target_player, 0); } } else { this->ruler_server->error_code1 = error_code; } - G_ActionResult_GC_Ep3_6xB4x1E out_cmd; + G_ActionResult_Ep3_6xB4x1E out_cmd; out_cmd.sequence_num = in_cmd.header.sequence_num; - out_cmd.error_code = this->ruler_server->error_code1; + out_cmd.error_code = this->options.is_trial() ? (was_set ? 0 : 1) : this->ruler_server->error_code1; this->send(out_cmd); this->send_debug_message_if_error_code_nonzero(in_cmd.client_id, out_cmd.error_code); } void Server::handle_CAx10_move_fc_to_location(shared_ptr, const string& data) { - const auto& in_cmd = check_size_t(data); + const auto& in_cmd = check_size_t(data); this->send_debug_command_received_message( in_cmd.client_id, in_cmd.header.subsubcommand, "MOVE"); if (in_cmd.client_id >= 4) { @@ -1889,7 +1927,7 @@ void Server::handle_CAx10_move_fc_to_location(shared_ptr, const string& this->ruler_server->error_code2 = error_code; } - G_ActionResult_GC_Ep3_6xB4x1E out_cmd; + G_ActionResult_Ep3_6xB4x1E out_cmd; out_cmd.sequence_num = in_cmd.header.sequence_num; out_cmd.error_code = this->ruler_server->error_code2; this->send(out_cmd); @@ -1898,7 +1936,7 @@ void Server::handle_CAx10_move_fc_to_location(shared_ptr, const string& } void Server::handle_CAx11_enqueue_attack_or_defense(shared_ptr, const string& data) { - const auto& in_cmd = check_size_t(data); + const auto& in_cmd = check_size_t(data); this->send_debug_command_received_message( in_cmd.client_id, in_cmd.header.subsubcommand, "ENQUEUE ACT"); if (in_cmd.client_id >= 4) { @@ -1916,7 +1954,7 @@ void Server::handle_CAx11_enqueue_attack_or_defense(shared_ptr, const st this->ruler_server->error_code3 = 0; ActionState pa = in_cmd.entry; if (this->enqueue_attack_or_defense(in_cmd.client_id, &pa)) { - G_SetActionState_GC_Ep3_6xB4x09 out_cmd; + G_SetActionState_Ep3_6xB4x09 out_cmd; out_cmd.client_id = in_cmd.client_id; out_cmd.state = in_cmd.entry; this->send(out_cmd); @@ -1925,7 +1963,7 @@ void Server::handle_CAx11_enqueue_attack_or_defense(shared_ptr, const st this->ruler_server->error_code3 = error_code; } - G_ActionResult_GC_Ep3_6xB4x1E out_cmd; + G_ActionResult_Ep3_6xB4x1E out_cmd; out_cmd.sequence_num = in_cmd.header.sequence_num; out_cmd.error_code = this->ruler_server->error_code3; this->send(out_cmd); @@ -1934,7 +1972,7 @@ void Server::handle_CAx11_enqueue_attack_or_defense(shared_ptr, const st } void Server::handle_CAx12_end_attack_list(shared_ptr, const string& data) { - const auto& in_cmd = check_size_t(data); + const auto& in_cmd = check_size_t(data); this->send_debug_command_received_message( in_cmd.client_id, in_cmd.header.subsubcommand, "END ATK LIST"); if (in_cmd.client_id >= 4) { @@ -1949,7 +1987,7 @@ void Server::handle_CAx12_end_attack_list(shared_ptr, const string& data this->end_attack_list_for_client(in_cmd.client_id); } - G_ActionResult_GC_Ep3_6xB4x1E out_cmd; + G_ActionResult_Ep3_6xB4x1E out_cmd; out_cmd.sequence_num = in_cmd.header.sequence_num; this->send(out_cmd); @@ -1957,7 +1995,7 @@ void Server::handle_CAx12_end_attack_list(shared_ptr, const string& data } void Server::handle_CAx13_update_map_during_setup(shared_ptr, const string& data) { - const auto& in_cmd = check_size_t(data); + const auto& in_cmd = check_size_t(data); this->send_debug_command_received_message( in_cmd.header.subsubcommand, "UPDATE MAP"); @@ -1999,7 +2037,7 @@ void Server::handle_CAx13_update_map_during_setup(shared_ptr, const stri } void Server::handle_CAx14_update_deck_during_setup(shared_ptr, const string& data) { - const auto& in_cmd = check_size_t(data); + const auto& in_cmd = check_size_t(data); this->send_debug_command_received_message( in_cmd.client_id, in_cmd.header.subsubcommand, "UPDATE DECK"); @@ -2046,7 +2084,7 @@ void Server::handle_CAx14_update_deck_during_setup(shared_ptr, const str } void Server::handle_CAx15_unused_hard_reset_server_state(shared_ptr, const string& data) { - const auto& in_cmd = check_size_t(data); + const auto& in_cmd = check_size_t(data); this->send_debug_command_received_message( in_cmd.header.subsubcommand, "HARD RESET"); @@ -2064,7 +2102,7 @@ void Server::handle_CAx15_unused_hard_reset_server_state(shared_ptr, con } void Server::handle_CAx1B_update_player_name(shared_ptr, const string& data) { - const auto& in_cmd = check_size_t(data); + const auto& in_cmd = check_size_t(data); this->send_debug_command_received_message( in_cmd.entry.client_id, in_cmd.header.subsubcommand, "UPDATE NAME"); @@ -2088,7 +2126,7 @@ void Server::handle_CAx1B_update_player_name(shared_ptr, const string& d } } - G_SetPlayerNames_GC_Ep3_6xB4x1C out_cmd; + G_SetPlayerNames_Ep3_6xB4x1C out_cmd; for (size_t z = 0; z < 4; z++) { out_cmd.entries[z] = this->name_entries[z]; } @@ -2096,13 +2134,13 @@ void Server::handle_CAx1B_update_player_name(shared_ptr, const string& d } void Server::handle_CAx1D_start_battle(shared_ptr, const string& data) { - const auto& in_cmd = check_size_t(data); + const auto& in_cmd = check_size_t(data); this->send_debug_command_received_message( in_cmd.header.subsubcommand, "START BATTLE"); if (!this->battle_in_progress) { if (!this->update_registration_phase()) { - G_RejectBattleStartRequest_GC_Ep3_6xB4x53 out_cmd; + G_RejectBattleStartRequest_Ep3_6xB4x53 out_cmd; out_cmd.setup_phase = this->setup_phase; out_cmd.registration_phase = this->registration_phase; out_cmd.state = *this->map_and_rules; @@ -2121,7 +2159,7 @@ void Server::handle_CAx1D_start_battle(shared_ptr, const string& data) { } // Note: Sega's implementation doesn't set EX results values here; they // did it at game join time instead. We do it here for code simplicity. - if (l->ep3_ex_result_values) { + if ((l->base_version != Version::GC_EP3_NTE) && l->ep3_ex_result_values) { this->send(*l->ep3_ex_result_values); } } @@ -2133,7 +2171,7 @@ void Server::handle_CAx1D_start_battle(shared_ptr, const string& data) { } void Server::handle_CAx21_end_battle(shared_ptr, const string& data) { - const auto& in_cmd = check_size_t(data); + const auto& in_cmd = check_size_t(data); this->send_debug_command_received_message( in_cmd.header.subsubcommand, "END BATTLE"); if (this->setup_phase == SetupPhase::BATTLE_ENDED) { @@ -2148,14 +2186,14 @@ void Server::handle_CAx21_end_battle(shared_ptr, const string& data) { } void Server::handle_CAx28_end_defense_list(shared_ptr, const string& data) { - const auto& in_cmd = check_size_t(data); + const auto& in_cmd = check_size_t(data); this->send_debug_command_received_message( in_cmd.client_id, in_cmd.header.subsubcommand, "END DEF LIST"); if (in_cmd.client_id >= 4) { throw runtime_error("invalid client ID"); } - G_ActionResult_GC_Ep3_6xB4x1E out_cmd_ack; + G_ActionResult_Ep3_6xB4x1E out_cmd_ack; out_cmd_ack.sequence_num = in_cmd.header.sequence_num; out_cmd_ack.response_phase = 1; this->send(out_cmd_ack); @@ -2194,20 +2232,20 @@ void Server::handle_CAx28_end_defense_list(shared_ptr, const string& dat this->unknown_a10 = 0; } - G_ActionResult_GC_Ep3_6xB4x1E out_cmd_fin; + G_ActionResult_Ep3_6xB4x1E out_cmd_fin; out_cmd_fin.sequence_num = in_cmd.header.sequence_num; out_cmd_fin.response_phase = 2; this->send(out_cmd_fin); } void Server::handle_CAx2B_legacy_set_card(shared_ptr, const string& data) { - const auto& in_cmd = check_size_t(data); + const auto& in_cmd = check_size_t(data); this->send_debug_command_received_message(in_cmd.header.subsubcommand, "EXEC LEGACY"); // Sega's original implementation does nothing here, so we do nothing as well. } void Server::handle_CAx34_subtract_ally_atk_points(shared_ptr, const string& data) { - const auto& in_cmd = check_size_t(data); + const auto& in_cmd = check_size_t(data); uint8_t card_ref_client_id = client_id_for_card_ref(in_cmd.card_ref); this->send_debug_command_received_message( @@ -2237,7 +2275,7 @@ void Server::handle_CAx34_subtract_ally_atk_points(shared_ptr, const str } } if (accepted) { - G_PhotonBlastStatus_GC_Ep3_6xB4x35 out_cmd; + G_PhotonBlastStatus_Ep3_6xB4x35 out_cmd; out_cmd.accepted = 0; out_cmd.card_ref = in_cmd.card_ref; out_cmd.client_id = card_ref_client_id; @@ -2271,7 +2309,7 @@ void Server::handle_CAx34_subtract_ally_atk_points(shared_ptr, const str } this->has_done_pb[card_ref_client_id] = false; - G_PhotonBlastStatus_GC_Ep3_6xB4x35 out_cmd; + G_PhotonBlastStatus_Ep3_6xB4x35 out_cmd; out_cmd.client_id = card_ref_client_id; out_cmd.accepted = 1; out_cmd.card_ref = in_cmd.card_ref; @@ -2284,7 +2322,7 @@ void Server::handle_CAx34_subtract_ally_atk_points(shared_ptr, const str } void Server::handle_CAx37_client_ready_to_advance_from_starter_roll_phase(shared_ptr, const string& data) { - const auto& in_cmd = check_size_t(data); + const auto& in_cmd = check_size_t(data); this->send_debug_command_received_message( in_cmd.client_id, in_cmd.header.subsubcommand, "SETUP ADV 1"); if (in_cmd.client_id >= 4) { @@ -2314,14 +2352,14 @@ void Server::handle_CAx37_client_ready_to_advance_from_starter_roll_phase(shared } void Server::handle_CAx3A_time_limit_expired(shared_ptr, const string& data) { - const auto& in_cmd = check_size_t(data); + const auto& in_cmd = check_size_t(data); this->send_debug_command_received_message(in_cmd.header.subsubcommand, "TIME EXPIRED"); // We don't need to do anything here because the overall time limit is tracked // server-side instead. } void Server::handle_CAx40_map_list_request(shared_ptr sender_c, const string& data) { - const auto& in_cmd = check_size_t(data); + const auto& in_cmd = check_size_t(data); this->send_debug_command_received_message( in_cmd.header.subsubcommand, "MAP LIST"); @@ -2335,9 +2373,8 @@ void Server::handle_CAx40_map_list_request(shared_ptr sender_c, const st const auto& list_data = this->options.map_index->get_compressed_list(num_players, language); StringWriter w; - uint32_t subcommand_size = (list_data.size() + sizeof(G_MapList_GC_Ep3_6xB6x40) + 3) & (~3); - w.put( - G_MapList_GC_Ep3_6xB6x40{{{{0xB6, 0, 0}, subcommand_size}, 0x40, {}}, list_data.size(), 0}); + uint32_t subcommand_size = (list_data.size() + sizeof(G_MapList_Ep3_6xB6x40) + 3) & (~3); + w.put(G_MapList_Ep3_6xB6x40{{{{0xB6, 0, 0}, subcommand_size}, 0x40, {}}, list_data.size(), 0}); w.write(list_data); while (w.size() & 3) { w.put_u8(0); @@ -2360,7 +2397,7 @@ void Server::send_6xB6x41_to_all_clients() const { } if (map_commands_by_language[c->language()].empty()) { map_commands_by_language[c->language()] = this->prepare_6xB6x41_map_definition( - this->last_chosen_map, c->language(), (l->base_version == Version::GC_EP3_NTE)); + this->last_chosen_map, c->language(), this->options.is_trial()); } this->log().info("Sending %c version of map %08" PRIX32, char_for_language_code(c->language()), this->last_chosen_map->map_number); send_command(c, 0x6C, 0x00, map_commands_by_language[c->language()]); @@ -2394,7 +2431,7 @@ void Server::send_6xB6x41_to_all_clients() const { } void Server::handle_CAx41_map_request(shared_ptr, const string& data) { - const auto& cmd = check_size_t(data); + const auto& cmd = check_size_t(data); this->send_debug_command_received_message( cmd.header.subsubcommand, "MAP DATA"); @@ -2403,7 +2440,7 @@ void Server::handle_CAx41_map_request(shared_ptr, const string& data) { } void Server::handle_CAx48_end_turn(shared_ptr, const string& data) { - const auto& in_cmd = check_size_t(data); + const auto& in_cmd = check_size_t(data); this->send_debug_command_received_message( in_cmd.client_id, in_cmd.header.subsubcommand, "END TURN"); if (in_cmd.client_id >= 4) { @@ -2415,13 +2452,13 @@ void Server::handle_CAx48_end_turn(shared_ptr, const string& data) { ps->draw_hand(0); } - G_ActionResult_GC_Ep3_6xB4x1E out_cmd; + G_ActionResult_Ep3_6xB4x1E out_cmd; out_cmd.sequence_num = in_cmd.header.sequence_num; this->send(out_cmd); } void Server::handle_CAx49_card_counts(shared_ptr, const string& data) { - const auto& in_cmd = check_size_t(data); + const auto& in_cmd = check_size_t(data); this->send_debug_command_received_message( in_cmd.header.sender_client_id, in_cmd.header.subsubcommand, "CARD COUNTS"); @@ -2601,7 +2638,7 @@ void Server::unknown_8023EEF4() { log.debug("a14 (%" PRIu32 ") < num_pending_attacks_with_cards (%" PRIu32 ")", this->unknown_a14, this->num_pending_attacks_with_cards); this->defense_list_ended_for_client.clear(false); - G_SetActionState_GC_Ep3_6xB4x29 cmd; + G_SetActionState_Ep3_6xB4x29 cmd; cmd.unknown_a1 = this->unknown_a14; cmd.state = this->pending_attacks_with_cards[this->unknown_a14]; this->replace_targets_due_to_destruction_or_conditions(&cmd.state); @@ -2877,20 +2914,36 @@ vector> Server::const_cast_set_cards_v( } void Server::send_6xB4x39() const { - G_UpdateAllPlayerStatistics_GC_Ep3_6xB4x39 cmd; - for (size_t z = 0; z < 4; z++) { - if (this->player_states[z]) { - cmd.stats[z] = this->player_states[z]->stats; + if (this->options.is_trial()) { + G_UpdateAllPlayerStatistics_Ep3NTE_6xB4x39 cmd; + for (size_t z = 0; z < 4; z++) { + if (this->player_states[z]) { + cmd.stats[z] = this->player_states[z]->stats; + } } + this->send(cmd); + } else { + G_UpdateAllPlayerStatistics_Ep3_6xB4x39 cmd; + for (size_t z = 0; z < 4; z++) { + if (this->player_states[z]) { + cmd.stats[z] = this->player_states[z]->stats; + } + } + this->send(cmd); } - this->send(cmd); } void Server::send_6xB4x05() { this->compute_all_map_occupied_bits(); - G_UpdateMap_GC_Ep3_6xB4x05 cmd; - cmd.state = *this->map_and_rules; - this->send(cmd); + if (this->options.is_trial()) { + G_UpdateMap_Ep3NTE_6xB4x05 cmd; + cmd.state = *this->map_and_rules; + this->send(cmd); + } else { + G_UpdateMap_Ep3_6xB4x05 cmd; + cmd.state = *this->map_and_rules; + this->send(cmd); + } } void Server::send_6xB4x02_for_all_players_if_needed(bool always_send) { @@ -2902,8 +2955,8 @@ void Server::send_6xB4x02_for_all_players_if_needed(bool always_send) { } } -G_SetTrapTileLocations_GC_Ep3_6xB4x50 Server::prepare_6xB4x50_trap_tile_locations() const { - G_SetTrapTileLocations_GC_Ep3_6xB4x50 cmd; +G_SetTrapTileLocations_Ep3_6xB4x50 Server::prepare_6xB4x50_trap_tile_locations() const { + G_SetTrapTileLocations_Ep3_6xB4x50 cmd; for (size_t trap_type = 0; trap_type < 5; trap_type++) { uint8_t trap_index = this->chosen_trap_tile_index_of_type[trap_type]; if (trap_index != 0xFF) { diff --git a/src/Episode3/Server.hh b/src/Episode3/Server.hh index 9c1aba45..96f77535 100644 --- a/src/Episode3/Server.hh +++ b/src/Episode3/Server.hh @@ -74,6 +74,10 @@ public: std::shared_ptr random_crypt; std::shared_ptr tournament; std::array, 5> trap_card_ids; + + inline bool is_trial() const { + return (this->behavior_flags & BehaviorFlag::IS_TRIAL_EDITION); + } }; Server(std::shared_ptr lobby, Options&& options); ~Server() noexcept(false); @@ -102,7 +106,7 @@ public: if (cmd.header.size != sizeof(cmd) / 4) { throw std::logic_error("outbound command size field is incorrect"); } - if (cmd.header.subsubcommand == 0x06) { + if (!this->options.is_trial() && (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) { @@ -179,9 +183,8 @@ public: void move_phase_before(); void set_player_deck_valid(uint8_t client_id); void setup_and_start_battle(); - G_SetStateFlags_GC_Ep3_6xB4x03 prepare_6xB4x03() const; - void update_battle_state_flags_and_send_6xB4x03_if_needed( - bool always_send = false); + G_SetStateFlags_Ep3_6xB4x03 prepare_6xB4x03() const; + void update_battle_state_flags_and_send_6xB4x03_if_needed(bool always_send = false); bool update_registration_phase(); void on_server_data_input(std::shared_ptr sender_c, const std::string& data); void handle_CAx0B_mulligan_hand(std::shared_ptr sender_c, const std::string& data); @@ -209,12 +212,10 @@ public: void handle_CAx49_card_counts(std::shared_ptr sender_c, 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); + 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); + 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(); @@ -225,12 +226,11 @@ public: 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, uint8_t language, bool is_trial); + G_UpdateDecks_Ep3_6xB4x07 prepare_6xB4x07_decks_update() const; + G_SetPlayerNames_Ep3_6xB4x1C prepare_6xB4x1C_names_update() const; + static std::string prepare_6xB6x41_map_definition(std::shared_ptr map, uint8_t language, bool is_trial); void send_6xB6x41_to_all_clients() const; - G_SetTrapTileLocations_GC_Ep3_6xB4x50 prepare_6xB4x50_trap_tile_locations() const; + G_SetTrapTileLocations_Ep3_6xB4x50 prepare_6xB4x50_trap_tile_locations() const; std::vector> const_cast_set_cards_v( const std::vector>& cards); diff --git a/src/Lobby.cc b/src/Lobby.cc index 0c6d470d..e54fceee 100644 --- a/src/Lobby.cc +++ b/src/Lobby.cc @@ -438,6 +438,11 @@ void Lobby::create_ep3_server() { .tournament = tourn, .trap_card_ids = s->ep3_trap_card_ids, }; + if (this->base_version == Version::GC_EP3_NTE) { + options.behavior_flags |= Episode3::BehaviorFlag::IS_TRIAL_EDITION; + } else { + options.behavior_flags &= (~Episode3::BehaviorFlag::IS_TRIAL_EDITION); + } this->ep3_server = make_shared(this->shared_from_this(), std::move(options)); this->ep3_server->init(); } diff --git a/src/Lobby.hh b/src/Lobby.hh index edb6d8ae..1d629c1d 100644 --- a/src/Lobby.hh +++ b/src/Lobby.hh @@ -151,7 +151,7 @@ struct Lobby : public std::enable_shared_from_this { std::shared_ptr battle_record; // Not used in watcher games std::shared_ptr battle_player; // Only used in replay games std::shared_ptr tournament_match; - std::shared_ptr ep3_ex_result_values; + std::shared_ptr ep3_ex_result_values; // Lobby stuff uint8_t event; diff --git a/src/ProxyCommands.cc b/src/ProxyCommands.cc index 88275c09..2f7bafe1 100644 --- a/src/ProxyCommands.cc +++ b/src/ProxyCommands.cc @@ -838,7 +838,7 @@ constexpr on_command_t S_P_C4 = &S_C4; constexpr on_command_t S_B_C4 = &S_C4; static HandlerResult S_G_E4(shared_ptr ses, uint16_t, uint32_t, string& data) { - auto& cmd = check_size_t(data); + auto& cmd = check_size_t(data); bool modified = false; for (size_t x = 0; x < 4; x++) { if (cmd.entries[x].guild_card_number == ses->remote_guild_card_number) { @@ -960,9 +960,9 @@ static HandlerResult S_6x(shared_ptr ses, uint16_t, if (ses->config.check_flag(Client::Flag::PROXY_SAVE_FILES)) { if (is_ep3(ses->version()) && (data.size() >= 0x14)) { if (static_cast(data[0]) == 0xB6) { - const auto& header = check_size_t(data, 0xFFFF); + const auto& header = check_size_t(data, 0xFFFF); if (header.subsubcommand == 0x00000041) { - const auto& cmd = check_size_t(data, 0xFFFF); + const auto& cmd = check_size_t(data, 0xFFFF); string filename = string_printf("map%08" PRIX32 ".%" PRIu64 ".mnmd", cmd.map_number.load(), now()); string map_data = prs_decompress( @@ -994,18 +994,27 @@ static HandlerResult S_6x(shared_ptr ses, uint16_t, if (ses->config.check_flag(Client::Flag::PROXY_EP3_INFINITE_TIME_ENABLED) && (header.subcommand == 0xB4)) { if (header.subsubcommand == 0x3D) { - auto& cmd = check_size_t(data); + auto& cmd = check_size_t(data); if (cmd.rules.overall_time_limit || cmd.rules.phase_time_limit) { cmd.rules.overall_time_limit = 0; cmd.rules.phase_time_limit = 0; modified = true; } } else if (header.subsubcommand == 0x05) { - auto& cmd = check_size_t(data); - if (cmd.state.rules.overall_time_limit || cmd.state.rules.phase_time_limit) { - cmd.state.rules.overall_time_limit = 0; - cmd.state.rules.phase_time_limit = 0; - modified = true; + if (ses->version() == Version::GC_EP3_NTE) { + auto& cmd = check_size_t(data); + if (cmd.state.rules.overall_time_limit || cmd.state.rules.phase_time_limit) { + cmd.state.rules.overall_time_limit = 0; + cmd.state.rules.phase_time_limit = 0; + modified = true; + } + } else { + auto& cmd = check_size_t(data); + if (cmd.state.rules.overall_time_limit || cmd.state.rules.phase_time_limit) { + cmd.state.rules.overall_time_limit = 0; + cmd.state.rules.phase_time_limit = 0; + modified = true; + } } } } @@ -1064,7 +1073,7 @@ static HandlerResult S_6x(shared_ptr ses, uint16_t, if (data[4] == 0x1A) { return HandlerResult::Type::SUPPRESS; } else if (data[4] == 0x36) { - const auto& cmd = check_size_t(data); + const auto& cmd = check_size_t(data); if (ses->is_in_game && (cmd.client_id >= 4)) { return HandlerResult::Type::SUPPRESS; } @@ -1073,7 +1082,7 @@ static HandlerResult S_6x(shared_ptr ses, uint16_t, } else if ((static_cast(data[0]) == 0xBD) && ses->config.check_flag(Client::Flag::PROXY_EP3_UNMASK_WHISPERS) && is_ep3(ses->version())) { - auto& cmd = check_size_t(data); + auto& cmd = check_size_t(data); if (cmd.private_flags & (1 << ses->lobby_client_id)) { cmd.private_flags &= ~(1 << ses->lobby_client_id); modified = true; @@ -1112,7 +1121,7 @@ static HandlerResult C_GXB_61(shared_ptr ses, uint16 } else { C_CharacterData_V3_61_98* pd; if (flag == 4) { // Episode 3 - auto& ep3_pd = check_size_t(data); + auto& ep3_pd = check_size_t(data); if (ep3_pd.ep3_config.is_encrypted) { decrypt_trivial_gci_data( &ep3_pd.ep3_config.card_counts, @@ -1286,7 +1295,7 @@ static HandlerResult S_13_A7(shared_ptr ses, uint16_ static HandlerResult S_G_B7(shared_ptr ses, uint16_t, uint32_t, string& data) { if (is_ep3(ses->version())) { if (ses->config.check_flag(Client::Flag::PROXY_EP3_INFINITE_MESETA_ENABLED)) { - auto& cmd = check_size_t(data); + auto& cmd = check_size_t(data); if (cmd.current_meseta != 1000000) { cmd.current_meseta = 1000000; return HandlerResult::Type::MODIFIED; @@ -1328,7 +1337,7 @@ static HandlerResult S_G_B8(shared_ptr ses, uint16_t static HandlerResult S_G_B9(shared_ptr ses, uint16_t, uint32_t, string& data) { if (ses->config.check_flag(Client::Flag::PROXY_SAVE_FILES)) { try { - const auto& header = check_size_t(data, 0xFFFF); + const auto& header = check_size_t(data, 0xFFFF); if (data.size() - sizeof(header) < header.size) { throw runtime_error("Media data size extends beyond end of command; not saving file"); @@ -1363,8 +1372,8 @@ static HandlerResult S_G_B9(shared_ptr ses, uint16_t static HandlerResult S_G_EF(shared_ptr ses, uint16_t, uint32_t, string& data) { if (is_ep3(ses->version())) { if (ses->config.check_flag(Client::Flag::PROXY_EP3_INFINITE_MESETA_ENABLED)) { - auto& cmd = check_size_t(data, - offsetof(S_StartCardAuction_GC_Ep3_EF, unused), 0xFFFF); + auto& cmd = check_size_t(data, + offsetof(S_StartCardAuction_Ep3_EF, unused), 0xFFFF); if (cmd.points_available != 0x7FFF) { cmd.points_available = 0x7FFF; return HandlerResult::Type::MODIFIED; @@ -1382,7 +1391,7 @@ static HandlerResult S_B_EF(shared_ptr, uint16_t, ui static HandlerResult S_G_BA(shared_ptr ses, uint16_t, uint32_t, string& data) { if (ses->config.check_flag(Client::Flag::PROXY_EP3_INFINITE_MESETA_ENABLED)) { - auto& cmd = check_size_t(data); + auto& cmd = check_size_t(data); if (cmd.current_meseta != 1000000) { cmd.current_meseta = 1000000; return HandlerResult::Type::MODIFIED; @@ -1477,10 +1486,10 @@ constexpr on_command_t S_B_65_67_68 = &S_65_67_68_EB; template static HandlerResult S_64(shared_ptr ses, uint16_t, uint32_t flag, string& data) { CmdT* cmd; - S_JoinGame_GC_Ep3_64* cmd_ep3 = nullptr; + S_JoinGame_Ep3_64* cmd_ep3 = nullptr; if (ses->sub_version >= 0x40) { - cmd = &check_size_t(data, sizeof(S_JoinGame_GC_Ep3_64)); - cmd_ep3 = &check_size_t(data); + cmd = &check_size_t(data, sizeof(S_JoinGame_Ep3_64)); + cmd_ep3 = &check_size_t(data); } else if (ses->version() == Version::XB_V3) { // Schtserv doesn't send the unknown_a1 field in this command, and we don't // use it here, so we allow it to be omitted. @@ -1541,7 +1550,7 @@ constexpr on_command_t S_X_64 = &S_64; constexpr on_command_t S_B_64 = &S_64; static HandlerResult S_E8(shared_ptr ses, uint16_t, uint32_t, string& data) { - auto& cmd = check_size_t(data); + auto& cmd = check_size_t(data); ses->clear_lobby_players(12); ses->floor = 0; diff --git a/src/ReceiveCommands.cc b/src/ReceiveCommands.cc index 36821c52..fb426be8 100644 --- a/src/ReceiveCommands.cc +++ b/src/ReceiveCommands.cc @@ -1228,7 +1228,7 @@ static void on_B7_Ep3(shared_ptr c, uint16_t, uint32_t, string& data) { } static void on_BA_Ep3(shared_ptr c, uint16_t command, uint32_t, string& data) { - const auto& in_cmd = check_size_t(data); + const auto& in_cmd = check_size_t(data); auto s = c->require_server_state(); auto l = c->lobby.lock(); bool is_lobby = l && !l->is_game(); @@ -1252,7 +1252,7 @@ static void on_BA_Ep3(shared_ptr c, uint16_t command, uint32_t, string& total_meseta_earned = c->license->ep3_total_meseta_earned; } - S_MesetaTransaction_GC_Ep3_BA out_cmd = {current_meseta, total_meseta_earned, in_cmd.request_token}; + S_MesetaTransaction_Ep3_BA out_cmd = {current_meseta, total_meseta_earned, in_cmd.request_token}; send_command(c, command, 0x03, &out_cmd, sizeof(out_cmd)); } @@ -1281,7 +1281,7 @@ static bool add_next_game_client(shared_ptr l) { auto s = c->require_server_state(); if (tourn) { - G_SetStateFlags_GC_Ep3_6xB4x03 state_cmd; + G_SetStateFlags_Ep3_6xB4x03 state_cmd; state_cmd.state.turn_num = 1; state_cmd.state.battle_phase = Episode3::BattlePhase::INVALID_00; state_cmd.state.current_team_turn1 = 0xFF; @@ -1498,7 +1498,7 @@ static void on_ep3_battle_table_state_updated(shared_ptr l, int16_t table } static void on_E4_Ep3(shared_ptr c, uint16_t, uint32_t flag, string& data) { - const auto& cmd = check_size_t(data); + const auto& cmd = check_size_t(data); auto l = c->require_lobby(); if (cmd.seat_number >= 4) { @@ -1539,7 +1539,7 @@ static void on_E4_Ep3(shared_ptr c, uint16_t, uint32_t flag, string& dat } static void on_E5_Ep3(shared_ptr c, uint16_t, uint32_t flag, string& data) { - check_size_t(data); + check_size_t(data); auto l = c->require_lobby(); if (l->is_game() || !l->is_ep3()) { throw runtime_error("battle table command sent in non-CARD lobby"); @@ -3032,7 +3032,7 @@ static void on_61_98(shared_ptr c, uint16_t command, uint32_t flag, stri if (!is_ep3(c->version())) { throw runtime_error("non-Episode 3 client sent Episode 3 player data"); } - const auto* cmd3 = &check_size_t(data); + const auto* cmd3 = &check_size_t(data); c->ep3_config = make_shared(cmd3->ep3_config); cmd = reinterpret_cast(cmd3); if (c->config.specific_version == 0x33000000) { @@ -4646,7 +4646,7 @@ static void on_EE_Ep3(shared_ptr c, uint16_t, uint32_t flag, string& dat } if (flag == 0xD0) { - auto& cmd = check_size_t(data); + auto& cmd = check_size_t(data); if (c->pending_card_trade) { throw runtime_error("player started a card trade when one is already pending"); @@ -4678,7 +4678,7 @@ static void on_EE_Ep3(shared_ptr c, uint16_t, uint32_t flag, string& dat // See the description of the D0 command in CommandFormats.hh for more // information on how this sequence is supposed to work. (The EE D0 command // is analogous to Episodes 1&2's D0 command.) - S_AdvanceCardTradeState_GC_Ep3_EE_FlagD1 resp = {0}; + S_AdvanceCardTradeState_Ep3_EE_FlagD1 resp = {0}; send_command_t(target_c, 0xEE, 0xD1, resp); if (target_c->pending_card_trade) { send_command_t(c, 0xEE, 0xD1, resp); @@ -4703,7 +4703,7 @@ static void on_EE_Ep3(shared_ptr c, uint16_t, uint32_t flag, string& dat if (target_c->pending_card_trade->confirmed) { send_execute_card_trade(c, target_c->pending_card_trade->card_to_count); send_execute_card_trade(target_c, c->pending_card_trade->card_to_count); - S_CardTradeComplete_GC_Ep3_EE_FlagD4 resp = {1}; + S_CardTradeComplete_Ep3_EE_FlagD4 resp = {1}; send_command_t(c, 0xEE, 0xD4, resp); send_command_t(target_c, 0xEE, 0xD4, resp); c->pending_card_trade.reset(); @@ -4719,7 +4719,7 @@ static void on_EE_Ep3(shared_ptr c, uint16_t, uint32_t flag, string& dat } uint8_t other_client_id = c->pending_card_trade->other_client_id; c->pending_card_trade.reset(); - S_CardTradeComplete_GC_Ep3_EE_FlagD4 resp = {0}; + S_CardTradeComplete_Ep3_EE_FlagD4 resp = {0}; send_command_t(c, 0xEE, 0xD4, resp); // Cancel the other side of the trade too, if it's open diff --git a/src/ReceiveSubcommands.cc b/src/ReceiveSubcommands.cc index ec9fbd91..2119f637 100644 --- a/src/ReceiveSubcommands.cc +++ b/src/ReceiveSubcommands.cc @@ -954,7 +954,7 @@ static void on_ep3_battle_subs(shared_ptr c, uint8_t command, uint8_t fl if (header.subsubcommand == 0x1A) { return; } else if (header.subsubcommand == 0x36) { - const auto& cmd = check_size_t(data, size); + const auto& cmd = check_size_t(data, size); if (l->is_game() && (cmd.client_id >= 4)) { return; } @@ -2015,9 +2015,9 @@ static void on_ep3_private_word_select_bb_bank_action(shared_ptr c, uint } else if (is_ep3(c->version())) { - const auto& cmd = check_size_t(data, size); - G_WordSelectDuringBattle_GC_Ep3_6xBD masked_cmd = { - {0xBD, sizeof(G_WordSelectDuringBattle_GC_Ep3_6xBD) >> 2, cmd.header.client_id}, + const auto& cmd = check_size_t(data, size); + G_WordSelectDuringBattle_Ep3_6xBD masked_cmd = { + {0xBD, sizeof(G_WordSelectDuringBattle_Ep3_6xBD) >> 2, cmd.header.client_id}, 0x0001, 0x0001, // "Please use the Whispers function." diff --git a/src/ReplaySession.cc b/src/ReplaySession.cc index 9b3bd7a4..c170d904 100644 --- a/src/ReplaySession.cc +++ b/src/ReplaySession.cc @@ -298,13 +298,13 @@ void ReplaySession::apply_default_mask(shared_ptr ev) { auto& mask = check_size_t(mask_data, mask_size); mask.variations.clear(0); } else { - auto& mask = check_size_t(mask_data, mask_size, sizeof(S_JoinGame_GC_Ep3_64)); + auto& mask = check_size_t(mask_data, mask_size, sizeof(S_JoinGame_Ep3_64)); mask.variations.clear(0); mask.rare_seed = 0; for (size_t offset = sizeof(S_JoinGame_GC_64) + - offsetof(S_JoinGame_GC_Ep3_64::Ep3PlayerEntry, disp.visual.name_color_checksum); + offsetof(S_JoinGame_Ep3_64::Ep3PlayerEntry, disp.visual.name_color_checksum); offset + 4 <= mask_size; - offset += sizeof(S_JoinGame_GC_Ep3_64::Ep3PlayerEntry)) { + offset += sizeof(S_JoinGame_Ep3_64::Ep3PlayerEntry)) { *reinterpret_cast(reinterpret_cast(mask_data) + offset) = 0; } } @@ -349,7 +349,7 @@ void ReplaySession::apply_default_mask(shared_ptr ev) { break; case 0xE8: if (is_gc(version)) { - auto& mask = check_size_t(mask_data, mask_size); + auto& mask = check_size_t(mask_data, mask_size); mask.rare_seed = 0; for (size_t z = 0; z < 4; z++) { mask.players[z].disp.visual.name_color_checksum = 0; @@ -365,9 +365,12 @@ void ReplaySession::apply_default_mask(shared_ptr ev) { } break; case 0xC9: - if (mask_size == 0xCC) { - auto& mask = check_size_t( - mask_data, mask_size); + if (mask_size == sizeof(G_ServerVersionStrings_Ep3NTE_6xB4x46)) { + auto& mask = check_size_t(mask_data, mask_size); + mask.version_signature.clear(0); + mask.date_str1.clear(0); + } else if (mask_size == sizeof(G_ServerVersionStrings_Ep3_6xB4x46)) { + auto& mask = check_size_t(mask_data, mask_size); mask.version_signature.clear(0); mask.date_str1.clear(0); mask.date_str2.clear(0); @@ -375,15 +378,15 @@ void ReplaySession::apply_default_mask(shared_ptr ev) { break; case 0x6C: if (is_gc(version) && mask_size >= 0x14) { - const auto& cmd = check_size_t(cmd_data, cmd_size, 0xFFFF); + const auto& cmd = check_size_t(cmd_data, cmd_size, 0xFFFF); if ((cmd.header.header.basic_header.subcommand == 0xB6) && (cmd.header.subsubcommand == 0x40)) { check_size_t(ev->mask, 0xFFFF).size = 0; - auto& mask = check_size_t(mask_data, mask_size, 0xFFFF); + auto& mask = check_size_t(mask_data, mask_size, 0xFFFF); mask.header.header.size = 0; mask.compressed_data_size = 0; ev->allow_size_disparity = true; - for (size_t z = sizeof(PSOCommandHeaderDCV3) + sizeof(G_MapList_GC_Ep3_6xB6x40); z < ev->mask.size(); z++) { + for (size_t z = sizeof(PSOCommandHeaderDCV3) + sizeof(G_MapList_Ep3_6xB6x40); z < ev->mask.size(); z++) { ev->mask[z] = 0; } } diff --git a/src/SendCommands.cc b/src/SendCommands.cc index 525d0e6d..9a7b2cae 100644 --- a/src/SendCommands.cc +++ b/src/SendCommands.cc @@ -801,7 +801,7 @@ void send_ep3_timed_message_box(Channel& ch, uint32_t frames, const string& mess return; } StringWriter w; - w.put({frames}); + w.put({frames}); w.write(encoded); w.put_u8(0); while (w.size() & 3) { @@ -1706,7 +1706,7 @@ static void send_join_spectator_team(shared_ptr c, shared_ptr l) auto s = c->require_server_state(); - S_JoinSpectatorTeam_GC_Ep3_E8 cmd; + S_JoinSpectatorTeam_Ep3_E8 cmd; cmd.variations.clear(0); cmd.client_id = c->lobby_client_id; @@ -1901,7 +1901,7 @@ void send_join_game(shared_ptr c, shared_ptr l) { } case Version::GC_EP3_NTE: case Version::GC_EP3: { - S_JoinGame_GC_Ep3_64 cmd; + S_JoinGame_Ep3_64 cmd; size_t player_count = populate_v3_cmd(cmd); auto s = c->require_server_state(); for (size_t x = 0; x < 4; x++) { @@ -2280,7 +2280,7 @@ void send_execute_card_trade(shared_ptr c, const vector max_entries) { throw logic_error("too many items in execute card trade command"); @@ -2405,7 +2405,7 @@ void send_warp(shared_ptr l, uint32_t floor, bool is_private) { } void send_ep3_change_music(Channel& ch, uint32_t song) { - G_ChangeLobbyMusic_GC_Ep3_6xBF cmd = {{0xBF, 0x02, 0}, song}; + G_ChangeLobbyMusic_Ep3_6xBF cmd = {{0xBF, 0x02, 0}, song}; ch.send(0x60, 0x00, cmd); } @@ -2758,7 +2758,7 @@ void send_ep3_media_update( uint32_t which, const string& compressed_data) { StringWriter w; - w.put({type, which, compressed_data.size(), 0}); + w.put({type, which, compressed_data.size(), 0}); w.write(compressed_data); while (w.size() & 3) { w.put_u8(0); @@ -2770,12 +2770,12 @@ void send_ep3_rank_update(shared_ptr c) { auto s = c->require_server_state(); uint32_t current_meseta = s->ep3_infinite_meseta ? 1000000 : c->license->ep3_current_meseta; uint32_t total_meseta_earned = s->ep3_infinite_meseta ? 1000000 : c->license->ep3_total_meseta_earned; - S_RankUpdate_GC_Ep3_B7 cmd = {0, {}, current_meseta, total_meseta_earned, 0xFFFFFFFF}; + S_RankUpdate_Ep3_B7 cmd = {0, {}, current_meseta, total_meseta_earned, 0xFFFFFFFF}; send_command_t(c, 0xB7, 0x00, cmd); } void send_ep3_card_battle_table_state(shared_ptr l, uint16_t table_number) { - S_CardBattleTableState_GC_Ep3_E4 cmd; + S_CardBattleTableState_Ep3_E4 cmd; for (size_t z = 0; z < 4; z++) { cmd.entries[z].state = 0; cmd.entries[z].unknown_a1 = 0; @@ -2806,7 +2806,7 @@ void send_ep3_card_battle_table_state(shared_ptr l, uint16_t table_number } void send_ep3_set_context_token(shared_ptr c, uint32_t context_token) { - G_SetContextToken_GC_Ep3_6xB4x1F cmd; + G_SetContextToken_Ep3_6xB4x1F cmd; cmd.context_token = context_token; send_command_t(c, 0xC9, 0x00, cmd); } @@ -2818,7 +2818,7 @@ void send_ep3_confirm_tournament_entry( throw runtime_error("cannot send tournament entry command to Episode 3 Trial Edition client"); } - S_ConfirmTournamentEntry_GC_Ep3_CC cmd; + S_ConfirmTournamentEntry_Ep3_CC cmd; if (tourn) { auto s = c->require_server_state(); cmd.tournament_name.encode(tourn->get_name(), c->language()); @@ -2842,7 +2842,7 @@ void send_ep3_tournament_list( bool is_for_spectator_team_create) { auto s = c->require_server_state(); - S_TournamentList_GC_Ep3_E0 cmd; + S_TournamentList_Ep3_E0 cmd; size_t z = 0; for (const auto& it : s->ep3_tournament_index->all_tournaments()) { const auto& tourn = it.second; @@ -2884,7 +2884,7 @@ void send_ep3_tournament_entry_list( shared_ptr c, shared_ptr tourn, bool is_for_spectator_team_create) { - S_TournamentEntryList_GC_Ep3_E2 cmd; + S_TournamentEntryList_Ep3_E2 cmd; cmd.players_per_team = (tourn->get_flags() & Episode3::Tournament::Flag::IS_2V2) ? 2 : 1; size_t z = 0; for (const auto& team : tourn->all_teams()) { @@ -2914,7 +2914,7 @@ void send_ep3_tournament_entry_list( void send_ep3_tournament_details( shared_ptr c, shared_ptr tourn) { - S_TournamentGameDetails_GC_Ep3_E3 cmd; + S_TournamentGameDetails_Ep3_E3 cmd; auto vm = tourn->get_map()->version(c->language()); cmd.name.encode(tourn->get_name(), c->language()); cmd.map_name.encode(vm->map->name.decode(vm->language), c->language()); @@ -2955,7 +2955,7 @@ void send_ep3_game_details(shared_ptr c, shared_ptr l) { auto tourn = tourn_match ? tourn_match->tournament.lock() : nullptr; if (tourn) { - S_TournamentGameDetails_GC_Ep3_E3 cmd; + S_TournamentGameDetails_Ep3_E3 cmd; cmd.name.encode(l->name, c->language()); auto vm = tourn->get_map()->version(c->language()); @@ -2974,7 +2974,7 @@ void send_ep3_game_details(shared_ptr c, shared_ptr l) { if (primary_lobby) { auto serial_number_to_client = primary_lobby->clients_by_serial_number(); - auto describe_team = [&](S_TournamentGameDetails_GC_Ep3_E3::TeamEntry& team_entry, shared_ptr team) -> void { + auto describe_team = [&](S_TournamentGameDetails_Ep3_E3::TeamEntry& team_entry, shared_ptr team) -> void { team_entry.team_name.encode(team->name, c->language()); for (size_t z = 0; z < team->players.size(); z++) { auto& entry = team_entry.players[z]; @@ -3014,7 +3014,7 @@ void send_ep3_game_details(shared_ptr c, shared_ptr l) { send_command_t(c, 0xE3, flag, cmd); } else { - S_GameInformation_GC_Ep3_E1 cmd; + S_GameInformation_Ep3_E1 cmd; cmd.game_name.encode(l->name, c->language()); if (primary_lobby) { size_t num_players = 0; @@ -3043,7 +3043,7 @@ void send_ep3_game_details(shared_ptr c, shared_ptr l) { // spectator count in the info window object. To account for this, we send // a mostly-blank E3 to set the spectator count, followed by an E1 with // the correct data. - S_TournamentGameDetails_GC_Ep3_E3 cmd_E3; + S_TournamentGameDetails_Ep3_E3 cmd_E3; cmd_E3.num_spectators = num_spectators; send_command_t(c, 0xE3, 0x04, cmd_E3); @@ -3073,7 +3073,7 @@ void send_ep3_set_tournament_player_decks(shared_ptr c) { throw runtime_error("tournament is deleted"); } - G_SetTournamentPlayerDecks_GC_Ep3_6xB4x3D cmd; + G_SetTournamentPlayerDecks_Ep3_6xB4x3D cmd; cmd.rules = tourn->get_rules(); cmd.map_number = tourn->get_map()->map_number; cmd.player_slot = 0xFF; @@ -3139,7 +3139,7 @@ void send_ep3_tournament_match_result(shared_ptr l, uint32_t meseta_rewar if (!lc) { continue; } - auto write_player_names = [&](G_TournamentMatchResult_GC_Ep3_6xB4x51::NamesEntry& entry, shared_ptr team) -> void { + auto write_player_names = [&](G_TournamentMatchResult_Ep3_6xB4x51::NamesEntry& entry, shared_ptr team) -> void { for (size_t z = 0; z < team->players.size(); z++) { const auto& player = team->players[z]; if (player.is_human()) { @@ -3155,7 +3155,7 @@ void send_ep3_tournament_match_result(shared_ptr l, uint32_t meseta_rewar } }; - G_TournamentMatchResult_GC_Ep3_6xB4x51 cmd; + G_TournamentMatchResult_Ep3_6xB4x51 cmd; cmd.match_description.encode((match == tourn->get_final_match()) ? string_printf("(%s) Final match", tourn->get_name().c_str()) : string_printf("(%s) Round %zu", tourn->get_name().c_str(), match->round_num), @@ -3197,7 +3197,7 @@ void send_ep3_update_game_metadata(shared_ptr l) { auto s = l->require_server_state(); { - G_SetGameMetadata_GC_Ep3_6xB4x52 cmd; + G_SetGameMetadata_Ep3_6xB4x52 cmd; cmd.total_spectators = total_spectators; if ((l->base_version != Version::GC_EP3_NTE) && !(s->ep3_behavior_flags & Episode3::BehaviorFlag::DISABLE_MASKING)) { @@ -3230,7 +3230,7 @@ void send_ep3_update_game_metadata(shared_ptr l) { } add_color_inplace(text); for (auto watcher_l : l->watcher_lobbies) { - G_SetGameMetadata_GC_Ep3_6xB4x52 cmd; + G_SetGameMetadata_Ep3_6xB4x52 cmd; cmd.local_spectators = 0; for (auto c : watcher_l->clients) { cmd.local_spectators += (c.get() != nullptr); @@ -3513,7 +3513,7 @@ void send_ep3_card_auction(shared_ptr l) { ? s->ep3_card_index_trial : s->ep3_card_index; - S_StartCardAuction_GC_Ep3_EF cmd; + S_StartCardAuction_Ep3_EF cmd; cmd.points_available = s->ep3_card_auction_points; for (size_t z = 0; z < num_cards; z++) { uint64_t v = random_object() % distribution_size; diff --git a/src/ServerState.cc b/src/ServerState.cc index f546d91a..b3b0b202 100644 --- a/src/ServerState.cc +++ b/src/ServerState.cc @@ -845,8 +845,8 @@ void ServerState::load_config() { } { - auto parse_ep3_ex_result_cmd = [&](const JSON& src) -> shared_ptr { - auto ret = make_shared(); + auto parse_ep3_ex_result_cmd = [&](const JSON& src) -> shared_ptr { + auto ret = make_shared(); const auto& win_json = src.at("Win"); for (size_t z = 0; z < min(win_json.size(), 10); z++) { ret->win_entries[z].threshold = win_json.at(z).at(0).as_int(); diff --git a/src/ServerState.hh b/src/ServerState.hh index 8e081941..3a6b153f 100644 --- a/src/ServerState.hh +++ b/src/ServerState.hh @@ -129,9 +129,9 @@ struct ServerState : public std::enable_shared_from_this { std::shared_ptr ep3_card_index_trial; std::shared_ptr ep3_map_index; std::shared_ptr ep3_com_deck_index; - std::shared_ptr ep3_default_ex_values; - std::shared_ptr ep3_tournament_ex_values; - std::shared_ptr ep3_tournament_final_round_ex_values; + std::shared_ptr ep3_default_ex_values; + std::shared_ptr ep3_tournament_ex_values; + std::shared_ptr ep3_tournament_final_round_ex_values; std::shared_ptr quest_category_index; std::shared_ptr default_quest_index; std::shared_ptr ep3_download_quest_index;