diff --git a/src/CommandFormats.hh b/src/CommandFormats.hh index 38157da8..c9459e47 100644 --- a/src/CommandFormats.hh +++ b/src/CommandFormats.hh @@ -4069,8 +4069,10 @@ struct G_SymbolChat_6x07 { // equal to 0x1000, so any valid enemy ID would be far outside the array's // range. newserv unconditionally blocks this command because it appears never // to be used, and the array write is not bounds-checked, so it could be used -// to cause undefined behavior on other clients. It seems that this broken -// logic predates even DC NTE. +// to cause undefined behavior on other clients. It seems that this logic +// predates even DC NTE; it's likely that this was part of the implementation +// of enemy states before entity IDs and the standard 0xB50-entry array of +// states were introduced. struct G_LegacyKillEnemy_6x09 { G_EntityIDHeader header; @@ -4455,11 +4457,11 @@ struct G_UseMedicalCenter_6x31 { G_ClientIDHeader header; } __packed_ws__(G_UseMedicalCenter_6x31, 4); -// 6x32: Revive player (Medical Center) +// 6x32: Revive all players (Medical Center) -struct G_MedicalCenterRevivePlayer_6x32 { +struct G_MedicalCenterReviveAllPlayers_6x32 { G_UnusedHeader header; -} __packed_ws__(G_MedicalCenterRevivePlayer_6x32, 4); +} __packed_ws__(G_MedicalCenterReviveAllPlayers_6x32, 4); // 6x33: Revive player (with Moon Atomizer) (protected on V3/V4) @@ -4692,7 +4694,7 @@ struct G_PlayerDied_6x4D { le_uint32_t death_flags = 0; // Same as 6x70's death_flags field } __packed_ws__(G_PlayerDied_6x4D, 8); -// 6x4E: Player is dead can be revived (protected on GC NTE/V3/V4) +// 6x4E: Player can be revived (protected on GC NTE/V3/V4) // This command creates the particle effect that Reverser and Moon Atomizers // can target. @@ -4707,7 +4709,9 @@ struct G_PlayerRevived_6x4F { } __packed_ws__(G_PlayerRevived_6x4F, 4); // 6x50: Switch interaction (protected on V3/V4) -// If UDP mode is enabled, this command is sent via UDP. +// If UDP mode is enabled, this command is sent via UDP. This command doesn't +// actually do anything with the switch; it just sets the player's animation +// state. 6x05 is used to set the switch flag if needed. struct G_SwitchInteraction_6x50 { G_ClientIDHeader header; @@ -4804,12 +4808,11 @@ struct G_PickUpItemRequest_6x5A { // This command has a handler, but it does nothing, even on DC NTE. // 6x5C: Destroy floor item -// Same format as 6x63. It appears this version should not be used because it +// Same format as 6x63. It appears this command should not be used because it // removes the item from the floor just like 6x63 does, but 6x5C doesn't call // the item's destructor. // 6x5D: Drop meseta or stacked item -// On DC NTE, this command has the same format, but is subcommand 6x4F instead. struct G_DropStackedItem_DC_6x5D { G_ClientIDHeader header; @@ -4898,10 +4901,10 @@ struct G_DestroyFloorItem_6x5C_6x63 { } __packed_ws__(G_DestroyFloorItem_6x5C_6x63, 0x0C); // 6x64: Unused (not valid on Episode 3) -// This command has a handler, but it does nothing even on DC NTE. +// This command has a handler, but it does nothing, even on DC NTE. // 6x65: Unused (not valid on Episode 3) -// This command has a handler, but it does nothing even on DC NTE. +// This command has a handler, but it does nothing, even on DC NTE. // 6x66: Use star atomizer @@ -5057,6 +5060,9 @@ struct G_SyncSetFlagState_6x6E_Decompressed { } __packed_ws__(G_SyncSetFlagState_6x6E_Decompressed, 8); // 6x6F: Set quest flags (used while loading into game) +// On Episode 3, this command sets the seq vars instead. However, the client +// never sends this, since seq vars don't need to be synced to the entire game +// in online play. struct G_SetQuestFlags_DCv1_6x6F { G_UnusedHeader header; @@ -5068,6 +5074,11 @@ struct G_SetQuestFlags_V2_V3_6x6F { QuestFlags quest_flags; } __packed_ws__(G_SetQuestFlags_V2_V3_6x6F, 0x204); +struct G_SetSeqVars_Ep3_6x6F { + G_UnusedHeader header; + Ep3SeqVars seq_vars; +} __packed_ws__(G_SetSeqVars_Ep3_6x6F, 0x404); + struct G_SetQuestFlags_BB_6x6F { G_UnusedHeader header; QuestFlags quest_flags; @@ -5248,6 +5259,7 @@ check_struct_size(G_WordSelect_6x74, 0x20); check_struct_size(G_WordSelectBE_6x74, 0x20); // 6x75: Update quest flag +// This command does nothing on Episode 3. struct G_UpdateQuestFlag_DC_PC_6x75 { G_UnusedHeader header; @@ -5272,6 +5284,7 @@ struct G_SetEntitySetFlags_6x76 { // 6x77: Sync quest register // This is sent by the client when an opcode D9 is executed within a quest. +// This command does nothing on Episode 3. struct G_SyncQuestRegister_6x77 { G_UnusedHeader header; diff --git a/src/PlayerSubordinates.cc b/src/PlayerSubordinates.cc index 8fbaf383..fc1e0da1 100644 --- a/src/PlayerSubordinates.cc +++ b/src/PlayerSubordinates.cc @@ -309,22 +309,7 @@ PlayerRecordsChallengeBB::operator PlayerRecordsChallengePC() const { return ret; } -QuestFlagsV1& QuestFlagsV1::operator=(const QuestFlags& other) { - this->data[0] = other.data[0]; - this->data[1] = other.data[1]; - this->data[2] = other.data[2]; - return *this; -} - -QuestFlagsV1::operator QuestFlags() const { - QuestFlags ret; - ret.data[0] = this->data[0]; - ret.data[1] = this->data[1]; - ret.data[2] = this->data[2]; - return ret; -} - -const QuestFlagsForDifficulty QuestFlagsForDifficulty::BB_QUEST_FLAG_APPLY_MASK{{ +const QuestFlagsForDifficulty BB_QUEST_FLAG_APPLY_MASK{{ // clang-format off /* 0000 */ 0x00, 0x3F, 0xFF, 0xE3, 0xE0, 0xFF, 0xFF, 0x00, /* 0040 */ 0x03, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, diff --git a/src/PlayerSubordinates.hh b/src/PlayerSubordinates.hh index 22cfb592..01cea0b4 100644 --- a/src/PlayerSubordinates.hh +++ b/src/PlayerSubordinates.hh @@ -884,10 +884,25 @@ inline PlayerDispDataBB convert_player_disp_data( return src; } -struct QuestFlagsForDifficulty { - static const QuestFlagsForDifficulty BB_QUEST_FLAG_APPLY_MASK; +template +struct FlagsArray { + parray> 3)> data = 0; - parray data; + FlagsArray() = default; + FlagsArray(const FlagsArray& other) = default; + FlagsArray(FlagsArray&& other) = default; + FlagsArray& operator=(const FlagsArray& other) = default; + FlagsArray& operator=(FlagsArray&& other) = default; + + FlagsArray(std::initializer_list init_items) : data(init_items) {} + + template + explicit FlagsArray(const FlagsArray& other) : data(other.data) {} + template + FlagsArray& operator=(const FlagsArray& other) { + this->data = other.data; + return *this; + } inline bool get(uint16_t flag_index) const { size_t byte_index = flag_index >> 3; @@ -904,6 +919,7 @@ struct QuestFlagsForDifficulty { uint8_t mask = 0x80 >> (flag_index & 7); this->data[byte_index] &= (~mask); } + inline void update_all(bool set) { if (set) { this->data.clear(0xFF); @@ -911,57 +927,66 @@ struct QuestFlagsForDifficulty { this->data.clear(0x00); } } -} __packed_ws__(QuestFlagsForDifficulty, 0x80); +} __attribute__((packed)); -struct QuestFlags { - parray data; +template +struct FlagsTable { + parray, NumTables> data; - inline QuestFlagsForDifficulty& for_difficulty(Difficulty difficulty) { - return this->data[static_cast(difficulty)]; - } - inline const QuestFlagsForDifficulty& for_difficulty(Difficulty difficulty) const { - return this->data[static_cast(difficulty)]; + FlagsTable() = default; + FlagsTable(const FlagsTable& other) = default; + FlagsTable(FlagsTable&& other) = default; + FlagsTable& operator=(const FlagsTable& other) = default; + FlagsTable& operator=(FlagsTable&& other) = default; + + template + explicit FlagsTable(const FlagsTable& other) : data(other.data) {} + template + FlagsTable& operator=(const FlagsTable& other) { + this->data = other.data; + return *this; } - inline bool get(Difficulty difficulty, uint16_t flag_index) const { - return this->for_difficulty(difficulty).get(flag_index); + inline FlagsArray& array(TableIndexT which) { + return this->data[static_cast(which)]; } - inline void set(Difficulty difficulty, uint16_t flag_index) { - this->for_difficulty(difficulty).set(flag_index); + inline const FlagsArray& array(TableIndexT which) const { + return this->data[static_cast(which)]; } - inline void clear(Difficulty difficulty, uint16_t flag_index) { - this->for_difficulty(difficulty).clear(flag_index); + + inline bool get(TableIndexT array_index, size_t flag_index) const { + return this->array(array_index).get(flag_index); } - inline void update_all(Difficulty difficulty, bool set) { - this->for_difficulty(difficulty).update_all(set); + inline void set(TableIndexT array_index, size_t flag_index) { + this->array(array_index).set(flag_index); + } + inline void clear(TableIndexT array_index, size_t flag_index) { + this->array(array_index).clear(flag_index); + } + inline void update_all(TableIndexT array_index, bool set) { + this->array(array_index).update_all(set); } inline void update_all(bool set) { - for (Difficulty difficulty : ALL_DIFFICULTIES_V234) { - this->update_all(difficulty, set); + for (size_t z = 0; z < this->data.size(); z++) { + this->update_all(z, set); } } -} __packed_ws__(QuestFlags, 0x200); +} __attribute__((packed)); -struct QuestFlagsV1 { - parray data; +using QuestFlagsForDifficulty = FlagsArray<0x400>; +using QuestFlagsV1 = FlagsTable<0x400, 3, Difficulty>; +using QuestFlags = FlagsTable<0x400, 4, Difficulty>; +using Ep3SeqVars = FlagsArray<0x2000>; +using SwitchFlagsV1 = FlagsTable<0x100, 0x10>; +using SwitchFlags = FlagsTable<0x100, 0x12>; +static_assert(sizeof(QuestFlagsForDifficulty) == 0x80); +static_assert(sizeof(QuestFlagsV1) == 0x180); +static_assert(sizeof(QuestFlags) == 0x200); +static_assert(sizeof(Ep3SeqVars) == 0x400); +static_assert(sizeof(SwitchFlagsV1) == 0x200); +static_assert(sizeof(SwitchFlags) == 0x240); - QuestFlagsV1& operator=(const QuestFlags& other); - operator QuestFlags() const; -} __packed_ws__(QuestFlagsV1, 0x180); - -struct SwitchFlags { - parray, 0x12> data; - - inline bool get(uint8_t floor, uint16_t flag_num) const { - return this->data[floor][flag_num >> 3] & (0x80 >> (flag_num & 7)); - } - inline void set(uint8_t floor, uint16_t flag_num) { - this->data[floor][flag_num >> 3] |= (0x80 >> (flag_num & 7)); - } - inline void clear(uint8_t floor, uint16_t flag_num) { - this->data[floor][flag_num >> 3] &= ~(0x80 >> (flag_num & 7)); - } -} __packed_ws__(SwitchFlags, 0x240); +extern const QuestFlagsForDifficulty BB_QUEST_FLAG_APPLY_MASK; struct BattleRules { enum class TechDiskMode : uint8_t { diff --git a/src/ReceiveCommands.cc b/src/ReceiveCommands.cc index 966c8f56..ce63bff1 100644 --- a/src/ReceiveCommands.cc +++ b/src/ReceiveCommands.cc @@ -4737,7 +4737,7 @@ shared_ptr create_game_generic( if (quest_flag_rewrites && !quest_flag_rewrites->empty()) { IntegralExpression::Env env = { - .flags = &p->quest_flags.for_difficulty(difficulty), + .flags = &p->quest_flags.array(difficulty), .challenge_records = &p->challenge_records, .team = creator_c->team(), .num_players = 1, diff --git a/src/ReceiveSubcommands.cc b/src/ReceiveSubcommands.cc index 540516f1..e14b725b 100644 --- a/src/ReceiveSubcommands.cc +++ b/src/ReceiveSubcommands.cc @@ -694,7 +694,7 @@ static asio::awaitable on_sync_joining_player_compressed_state(shared_ptr< // player), it's not surprising that no one noticed this. But it does // mean we have to check switch_flags_r.eof() here. for (size_t z = 0; (z < 0x20) && !switch_flags_r.eof(); z++) { - uint8_t& l_flags = l->switch_flags->data[floor][z]; + uint8_t& l_flags = l->switch_flags->array(floor).data[z]; uint8_t r_flags = switch_flags_r.get_u8(); if (l_flags != r_flags) { l->log.warning_f("Switch flags do not match at floor {:02X} byte {:02X} (expected {:02X}, received {:02X})", diff --git a/src/SaveFileFormats.hh b/src/SaveFileFormats.hh index 6968ed1d..9c127f2d 100644 --- a/src/SaveFileFormats.hh +++ b/src/SaveFileFormats.hh @@ -681,7 +681,7 @@ struct PSOGCEp3NTECharacter { /* 0430:0014 */ be_uint32_t save_count = 1; /* 0434:0018 */ pstring ppp_username; /* 0450:0034 */ pstring ppp_password; - /* 0460:0044 */ parray seq_vars; + /* 0460:0044 */ Ep3SeqVars seq_vars; /* 0860:0444 */ be_uint32_t death_count = 0; /* 0864:0448 */ PlayerBank200BE bank; /* 1B2C:1710 */ GuildCardGCBE guild_card; @@ -722,7 +722,7 @@ struct PSOGCEp3CharacterFile { // NPC decks are unlocked, and whether the player has a VIP card or not. // Logically, this structure maps to quest_flags in other versions, but is // a different size. - /* 0460:0044 */ parray seq_vars; + /* 0460:0044 */ Ep3SeqVars seq_vars; /* 0860:0444 */ be_uint32_t death_count = 0; // Curiously, Episode 3 characters do have item banks, but there are only 4 // item slots. Presumably Sega didn't completely remove the bank in Ep3 diff --git a/src/SendCommands.cc b/src/SendCommands.cc index 1f616229..82f9b8d8 100644 --- a/src/SendCommands.cc +++ b/src/SendCommands.cc @@ -2956,8 +2956,8 @@ void send_game_flag_state_t(shared_ptr c) { if ((difficulty != l->difficulty) && !use_v3_cmd) { continue; } - const auto& diff_flags = l->quest_flag_values->for_difficulty(difficulty); - const auto& diff_known_flags = l->quest_flags_known->for_difficulty(difficulty); + const auto& diff_flags = l->quest_flag_values->array(difficulty); + const auto& diff_known_flags = l->quest_flags_known->array(difficulty); for (uint8_t z = 0; z < diff_known_flags.data.size(); z++) { uint8_t known_flags = diff_known_flags.data[z]; if (!known_flags) { diff --git a/src/Text.hh b/src/Text.hh index 7ae0646d..de6fab10 100644 --- a/src/Text.hh +++ b/src/Text.hh @@ -243,7 +243,11 @@ struct parray { this->items[x] = s.items[x]; } for (; x < Count; x++) { - this->items[x] = 0; + if constexpr (std::is_integral_v) { + this->items[x] = 0; + } else { + this->items[x] = ItemT(); + } } } else { for (size_t x = 0; x < Count; x++) {