refine many subcommand formats

This commit is contained in:
Martin Michelsen
2025-01-26 09:47:19 -08:00
parent 65a1b97093
commit 78b7bfac70
9 changed files with 528 additions and 321 deletions
+1 -1
View File
@@ -250,7 +250,7 @@ public:
std::unique_ptr<PendingItemTrade> pending_item_trade;
std::unique_ptr<PendingCardTrade> pending_card_trade;
uint32_t telepipe_lobby_id;
G_SetTelepipeState_6x68 telepipe_state;
TelepipeState telepipe_state;
std::shared_ptr<Episode3::PlayerConfig> ep3_config; // Null for non-Ep3
int8_t bb_character_index;
ItemData bb_identify_result;
+143 -132
View File
@@ -3834,7 +3834,7 @@ struct S_SetShutdownCommand_BB_01EF {
// are laid out similarly as above. These structs start with G_ to indicate that
// they are (usually) bidirectional, and are (usually) generated by clients and
// consumed by clients. Generally in newserv source, these commands are referred
// to as (for example) 6x02, etc., referencing the fact that they are almost
// to as (for example) 6x0B, etc., referencing the fact that they are almost
// always sent via a command starting with the hex digit 6.
// All game subcommands have the same header format, which is one of:
@@ -3849,11 +3849,15 @@ struct S_SetShutdownCommand_BB_01EF {
// Multiple subcommands may be sent in the same 6x command. It seems the client
// never sends commands like this, but newserv generates commands containing
// multiple subcommands in some situations (for example, the implementation of
// infinite HP does this).
// infinite HP does this). All versions of the client support this behavior.
// If any subcommand or group thereof is longer than 0x400 bytes, the 6C or 6D
// commands must be used. The 60 and 62 commands exhibit undefined behavior if
// command must be used. The 60 and 62 commands exhibit undefined behavior if
// this limit is exceeded.
// Many subcommands have different numbers on DC NTE and 11/2000, or simply
// don't exist on those versions. See the subcommand_definitions table in
// ReceiveSubcommands.cc for a full listing.
// Some subcommands are "protected" on V3 and later (not including GC NTE);
// these commands are blocked by the client if they affect the local player. If
// a V3 or later client receives a protected subcommand that would affect its
@@ -3994,23 +3998,27 @@ struct G_SymbolChat_6x07 {
// 6x08: Invalid subcommand
// 6x09: Unknown
// header.entity_id is expected to be an enemy ID.
// header.entity_id is expected to be an enemy ID, but is also expected to be
// in the range [0x00, 0x80) since it writes to an array of 0x80 entries. This
// duality makes no sense because enemy IDs are greater than or equal to
// 0x1000, so any valid enemy ID would be far outside the array's range, and
// the write is not bounds-checked. For this reason, newserv unconditionally
// blocks this command.
struct G_Unknown_6x09 {
G_EntityIDHeader header;
} __packed_ws__(G_Unknown_6x09, 4);
// 6x0A: Update enemy state
// In Ultimate mode, the low 7 bits of game_flags are ignored, and 6x9C is used
// to update those instead.
template <bool BE>
struct G_UpdateEnemyStateT_6x0A {
G_EntityIDHeader header;
le_uint16_t enemy_index = 0; // [0, 0xB50)
le_uint16_t total_damage = 0;
// Flags:
// 00000400 - should play hit animation
// 00000800 - is dead
typename std::conditional_t<BE, be_uint32_t, le_uint32_t> flags = 0;
typename std::conditional_t<BE, be_uint32_t, le_uint32_t> game_flags = 0;
} __packed__;
using G_UpdateEnemyState_GC_6x0A = G_UpdateEnemyStateT_6x0A<true>;
using G_UpdateEnemyState_DC_PC_XB_BB_6x0A = G_UpdateEnemyStateT_6x0A<false>;
@@ -4060,25 +4068,25 @@ struct G_RemoveStatusEffect_6x0D {
le_uint32_t unused = 0;
} __packed_ws__(G_RemoveStatusEffect_6x0D, 0x0C);
// 6x0E: Unknown (protected on V3/V4)
// 6x0E: Clear all negative status effects (protected on V3/V4)
// It seems that the client never sends this command.
struct G_Unknown_6x0E {
struct G_ClearNegativeStatusEffects_6x0E {
G_ClientIDHeader header;
} __packed_ws__(G_Unknown_6x0E, 4);
} __packed_ws__(G_ClearNegativeStatusEffects_6x0E, 4);
// 6x0F: Invalid subcommand
// 6x10: Unknown (not valid on Episode 3)
// 6x10: Dragon boss actions (not valid on Episode 3)
// 6x11: Dragon boss actions (not valid on Episode 3)
// It seems that the client never sends these commands.
struct G_Unknown_6x10_6x11_6x14 {
struct G_DragonBossActions_6x10_6x11 {
G_EntityIDHeader header;
le_uint16_t unknown_a2 = 0;
le_uint16_t unknown_a3 = 0;
le_uint32_t unknown_a4 = 0;
} __packed_ws__(G_Unknown_6x10_6x11_6x14, 0x0C);
// 6x11: Unknown (not valid on Episode 3)
// Same format as 6x10
} __packed_ws__(G_DragonBossActions_6x10_6x11, 0x0C);
// 6x12: Dragon boss actions (not valid on Episode 3)
@@ -4105,7 +4113,13 @@ struct G_DeRolLeBossActions_6x13 {
} __packed_ws__(G_DeRolLeBossActions_6x13, 8);
// 6x14: De Rol Le boss actions (not valid on Episode 3)
// Same format as 6x10
struct G_DeRolLeBossActions_6x14 {
G_EntityIDHeader header;
le_uint16_t unknown_a2 = 0;
le_uint16_t unknown_a3 = 0;
le_uint32_t unknown_a4 = 0;
} __packed_ws__(G_DeRolLeBossActions_6x14, 0x0C);
// 6x15: Vol Opt boss actions (not valid on Episode 3)
@@ -4188,7 +4202,7 @@ struct G_SetPosition_6x20 {
G_ClientIDHeader header;
le_int32_t floor = 0;
VectorXYZF pos;
le_uint32_t unknown_a1 = 0;
le_uint32_t angle_y = 0;
} __packed_ws__(G_SetPosition_6x20, 0x18);
// 6x21: Inter-level warp (protected on V3/V4)
@@ -4210,7 +4224,7 @@ struct G_SetPlayerVisibility_6x22_6x23 {
struct G_TeleportPlayer_6x24 {
G_ClientIDHeader header;
le_uint32_t unknown_a1 = 0;
le_uint32_t angle_y = 0;
VectorXYZF pos;
} __packed_ws__(G_TeleportPlayer_6x24, 0x14);
@@ -4266,8 +4280,9 @@ struct G_DropItem_6x2A {
VectorXYZF pos;
} __packed_ws__(G_DropItem_6x2A, 0x18);
// 6x2B: Create item in inventory (e.g. via tekker or bank withdraw) (protected on V3/V4)
// 6x2B: Create item in inventory (tekker/bank) (protected on V3/V4)
// On BB, the 6xBE command is used instead of 6x2B to create inventory items.
// If equip_item is nonzero, the item is equipped immediately.
struct G_CreateInventoryItem_DC_6x2B {
G_ClientIDHeader header;
@@ -4276,19 +4291,22 @@ struct G_CreateInventoryItem_DC_6x2B {
struct G_CreateInventoryItem_PC_V3_BB_6x2B : G_CreateInventoryItem_DC_6x2B {
uint8_t unused1 = 0;
uint8_t unknown_a2 = 0; // Does something with equipped items, but logic isn't the same as 6x25
uint8_t equip_item = 0;
parray<uint8_t, 2> unused2 = 0;
} __packed_ws__(G_CreateInventoryItem_PC_V3_BB_6x2B, 0x1C);
// 6x2C: Talk to NPC (protected on V3/V4)
// This updates G_6x70_NPCTalkState in the TObjPlayer struct, but the format is
// not the same. The names here match the fields in G_6x70_NPCTalkState, hence
// the unusual numbering.
struct G_TalkToNPC_6x2C {
G_ClientIDHeader header;
le_uint16_t unknown_a1 = 0;
le_uint16_t unknown_a2 = 0;
le_float unknown_a3 = 0.0f;
le_float unknown_a4 = 0.0f;
le_float unknown_a5 = 0.0f;
le_float unknown_a6 = 0.0f;
le_float unknown_a4 = 0.0f;
} __packed_ws__(G_TalkToNPC_6x2C, 0x14);
// 6x2D: Done talking to NPC (protected on V3/V4)
@@ -4308,9 +4326,9 @@ struct G_SetOrClearPlayerFlags_6x2E {
// 6x2F: Change player HP
struct G_ChangePlayerHP_6x2F {
G_UnusedHeader header;
le_uint32_t type = 0; // 0 = set HP, 1 = add/subtract HP, 2 = add/sub fixed HP
le_uint16_t amount = 0;
G_ClientIDHeader header;
le_uint32_t type = 0; // 0 = set HP, 1 = add/subtract HP, 2 = reset to max HP
le_int16_t amount = 0;
le_uint16_t client_id = 0;
} __packed_ws__(G_ChangePlayerHP_6x2F, 0x0C);
@@ -4355,12 +4373,19 @@ struct G_RevivePlayer_6x33 {
} __packed_ws__(G_RevivePlayer_6x33, 8);
// 6x34: Unknown
// This subcommand is completely ignored (at least, by PSO GC).
// This subcommand is ignored by all versions of PSO.
// 6x35: Invalid subcommand
// 6x36: Unknown (supported; game only)
// This subcommand is completely ignored (at least, by PSO GC).
// This subcommand is completely ignored on V3.
// TODO: It is not ignored on V2, and presumably earlier versions as well.
// Figure out what it does. It's something related to PBs.
struct G_Unknown_6x36 {
// The parameter seems to be expected to be in the range [0, 12).
G_ParameterHeader header;
} __packed_ws__(G_Unknown_6x36, 4);
// 6x37: Photon blast (protected on V3/V4)
@@ -4379,22 +4404,26 @@ struct G_DonateToPhotonBlast_6x38 {
} __packed_ws__(G_DonateToPhotonBlast_6x38, 8);
// 6x39: Photon blast ready (protected on V3/V4)
// This is sent when a player's PB meter reaches 100.
struct G_PhotonBlastReady_6x38 {
struct G_SetPhotonBlastReadyFlag_6x39 {
G_ClientIDHeader header;
} __packed_ws__(G_PhotonBlastReady_6x38, 4);
} __packed_ws__(G_SetPhotonBlastReadyFlag_6x39, 4);
// 6x3A: Unknown (supported; game only) (protected on V3/V4)
// 6x3A: Clear photon blast ready flag (protected on V3/V4)
// This is sent when a player's PB meter drops below 100. This can happen if
// a player donates to a PB instead of joining it.
struct G_Unknown_6x3A {
struct G_ClearPhotonBlastReadyFlag_6x3A {
G_ClientIDHeader header;
} __packed_ws__(G_Unknown_6x3A, 4);
} __packed_ws__(G_ClearPhotonBlastReadyFlag_6x3A, 4);
// 6x3B: Unknown (supported; lobby & game) (protected on V3/V4)
// 6x3B: Clear temporary photon blast state flags (protected on V3/V4)
// This is usually sent when a player changes floors.
struct G_Unknown_6x3B {
struct G_ClearTemporaryPhotonBlastStateFlags_6x3B {
G_ClientIDHeader header;
} __packed_ws__(G_Unknown_6x3B, 4);
} __packed_ws__(G_ClearTemporaryPhotonBlastStateFlags_6x3B, 4);
// 6x3C: Unknown (DCv1 and earlier)
// This command has a handler, but it does nothing, even on DC NTE.
@@ -4405,7 +4434,7 @@ struct G_Unknown_6x3B {
struct G_StopAtPosition_6x3E {
G_ClientIDHeader header;
le_uint16_t unknown_a1 = 0;
le_uint16_t is_near_enemy = 0; // Sets game_flag 0x00100000 (v3)
le_uint16_t angle = 0;
le_int16_t floor = 0;
le_int16_t room = 0;
@@ -4416,7 +4445,7 @@ struct G_StopAtPosition_6x3E {
struct G_SetPosition_6x3F {
G_ClientIDHeader header;
le_uint16_t unknown_a1 = 0;
le_uint16_t is_near_enemy = 0; // Sets game_flag 0x00100000 (v3)
le_uint16_t angle = 0;
le_int16_t floor = 0;
le_int16_t room = 0;
@@ -4480,9 +4509,9 @@ struct G_CastTechnique_6x47 {
uint8_t unused = 0; // Must not be negative
// Note: The level here isn't the actual tech level that was cast, if the
// actual level is > 15. In that case, a 6x8D is sent first, which contains
// the additional level which is added to this level at cast time. They
// probably did this for legacy reasons when dealing with v1/v2
// compatibility, and never cleaned it up.
// the additional level which is added to this level at cast time. Sega
// probably did this as part of implementing v1/v2 compatibility and never
// cleaned it up.
uint8_t level = 0;
uint8_t target_count = 0; // Must be in [0, 10]
// The client may send a shorter command if not all of these are used.
@@ -4533,7 +4562,7 @@ struct G_HitByEnemy_6x4B_6x4C {
struct G_PlayerDied_6x4D {
G_ClientIDHeader header;
le_uint32_t unknown_a1 = 0;
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 V3/V4)
@@ -4553,7 +4582,7 @@ struct G_PlayerRevived_6x4F {
struct G_SwitchInteraction_6x50 {
G_ClientIDHeader header;
le_uint32_t unknown_a1 = 0;
le_uint32_t angle_y = 0;
} __packed_ws__(G_SwitchInteraction_6x50, 8);
// 6x51: Set player angle
@@ -4597,8 +4626,8 @@ struct G_Unknown_6x54 {
struct G_IntraMapWarp_6x55 {
G_ClientIDHeader header;
le_uint32_t unknown_a1 = 0;
VectorXYZF pos1;
VectorXYZF pos2;
VectorXYZF pos;
VectorXYZF unknown_a2;
} __packed_ws__(G_IntraMapWarp_6x55, 0x20);
// 6x56: Set player position and angle (protected on V3/V4)
@@ -4656,7 +4685,7 @@ struct G_PickUpItemRequest_6x5A {
struct G_DropStackedItem_DC_6x5D {
G_ClientIDHeader header;
le_uint16_t floor = 0;
le_uint16_t unknown_a2 = 0; // Corresponds to FloorItem::unknown_a2
le_uint16_t room_id = 0;
VectorXZF pos;
ItemData item_data;
} __packed_ws__(G_DropStackedItem_DC_6x5D, 0x24);
@@ -4679,7 +4708,7 @@ struct FloorItem {
/* 01 */ uint8_t source_type = 0; // 1 = enemy, 2 = box
/* 02 */ le_uint16_t entity_index = 0; // < 0x0B50 if source_type == 1; otherwise < 0x0BA0
/* 04 */ VectorXZF pos;
/* 0C */ le_uint16_t unknown_a2 = 0;
/* 0C */ le_uint16_t room_id = 0;
// The drop number is scoped to the floor and increments by 1 each time an
// item is dropped. The last item dropped in each floor has drop_number equal
// to total_items_dropped_per_floor[floor - 1] - 1.
@@ -4763,13 +4792,7 @@ struct G_TriggerSetEvent_6x67 {
struct G_SetTelepipeState_6x68 {
G_UnusedHeader header;
le_uint16_t client_id2 = 0;
le_uint16_t floor = 0;
le_uint16_t unknown_b1 = 0;
uint8_t unknown_b2 = 0;
uint8_t unknown_b3 = 0;
VectorXYZF pos;
le_uint32_t unknown_a3 = 0;
TelepipeState state;
} __packed_ws__(G_SetTelepipeState_6x68, 0x1C);
// 6x69: NPC control
@@ -4915,7 +4938,7 @@ struct G_SetQuestFlags_V2_V3_6x6F {
struct G_SetQuestFlags_BB_6x6F {
G_UnusedHeader header;
QuestFlags quest_flags;
// If use_apply_mask is 1, only the flags set in bb_quest_flag_apply_mask
// If use_apply_mask is 1, only the flags set in BB_QUEST_FLAG_APPLY_MASK
// (in PlayerSubordinates.cc) are overwritten on the receiving client's end.
// The client always sends this with use_apply_mask = 1.
le_uint32_t use_apply_mask = 1;
@@ -4927,33 +4950,11 @@ struct G_SetQuestFlags_BB_6x6F {
// structure also includes transient state (e.g. current HP).
struct G_6x70_Sub_Telepipe {
/* 00 */ le_uint16_t owner_client_id = 0xFFFF;
/* 02 */ le_uint16_t floor = 0;
/* 04 */ le_uint32_t unknown_a1 = 0;
/* 08 */ VectorXYZF pos;
/* 14 */ le_uint32_t unknown_a3 = 0;
/* 00 */ TelepipeState state;
/* 18 */ le_uint32_t unknown_a4 = 0x0000FFFF;
/* 1C */
} __packed_ws__(G_6x70_Sub_Telepipe, 0x1C);
struct G_6x70_Sub_UnknownA1 {
// This is used in all versions of this command except DCNTE and 11/2000.
/* 00 */ le_uint16_t unknown_a1 = 0;
/* 02 */ le_uint16_t unknown_a2 = 0;
/* 04 */ le_uint32_t unknown_a3 = 0;
/* 08 */ le_float unknown_a4 = 0.0f;
/* 0C */ le_uint32_t unknown_a5 = 0;
/* 10 */ le_uint32_t unknown_a6 = 0;
/* 14 */
} __packed_ws__(G_6x70_Sub_UnknownA1, 0x14);
struct G_6x70_StatusEffectState {
/* 00 */ le_uint32_t effect_type = 0;
/* 04 */ le_float multiplier = 0.0f;
/* 08 */ le_uint32_t remaining_frames = 0;
/* 0C */
} __packed_ws__(G_6x70_StatusEffectState, 0x0C);
struct G_6x70_Base_DCNTE {
/* 0000 */ le_uint16_t client_id = 0;
/* 0002 */ le_uint16_t room_id = 0;
@@ -4974,10 +4975,10 @@ struct G_SyncPlayerDispAndInventory_DCNTE_6x70 {
/* 0030 */ le_uint32_t unknown_a5 = 0;
/* 0034 */ le_uint32_t unknown_a6 = 0;
/* 0038 */ G_6x70_Sub_Telepipe telepipe;
/* 0054 */ le_uint32_t unknown_a8 = 0;
/* 0058 */ parray<uint8_t, 0x10> unknown_a9;
/* 0054 */ le_uint32_t death_flags = 0;
/* 0058 */ NPCTalkState_DCProtos npc_talk_state;
/* 0068 */ le_uint32_t area = 0;
/* 006C */ le_uint32_t flags2 = 0;
/* 006C */ le_uint32_t game_flags = 0;
/* 0070 */ PlayerVisualConfig visual;
/* 00C0 */ PlayerStats stats;
/* 00E4 */ le_uint32_t num_items = 0;
@@ -4993,10 +4994,10 @@ struct G_SyncPlayerDispAndInventory_DC112000_6x70 {
/* 0032 */ le_uint16_t bonus_tp_from_materials = 0;
/* 0034 */ parray<uint8_t, 0x10> unknown_a5;
/* 0044 */ G_6x70_Sub_Telepipe telepipe;
/* 0060 */ le_uint32_t unknown_a8 = 0;
/* 0064 */ parray<uint8_t, 0x10> unknown_a9;
/* 0060 */ le_uint32_t death_flags = 0;
/* 0064 */ NPCTalkState_DCProtos npc_talk_state;
/* 0074 */ le_uint32_t area = 0;
/* 0078 */ le_uint32_t flags2 = 0;
/* 0078 */ le_uint32_t game_flags = 0;
/* 007C */ PlayerVisualConfig visual;
/* 00CC */ PlayerStats stats;
/* 00F0 */ le_uint32_t num_items = 0;
@@ -5008,21 +5009,21 @@ struct G_6x70_Base_V1 {
/* 0000 */ G_6x70_Base_DCNTE base;
/* 0024 */ le_uint16_t bonus_hp_from_materials = 0;
/* 0026 */ le_uint16_t bonus_tp_from_materials = 0;
/* 0028 */ G_6x70_StatusEffectState permanent_status_effect;
/* 0034 */ G_6x70_StatusEffectState temporary_status_effect;
/* 0040 */ G_6x70_StatusEffectState attack_status_effect;
/* 004C */ G_6x70_StatusEffectState defense_status_effect;
/* 0058 */ G_6x70_StatusEffectState unused_status_effect;
/* 0028 */ StatusEffectState permanent_status_effect;
/* 0034 */ StatusEffectState temporary_status_effect;
/* 0040 */ StatusEffectState attack_status_effect;
/* 004C */ StatusEffectState defense_status_effect;
/* 0058 */ StatusEffectState unused_status_effect;
/* 0064 */ le_uint32_t language = 0;
/* 0068 */ le_uint32_t player_tag = 0;
/* 006C */ le_uint32_t guild_card_number = 0;
/* 0070 */ le_uint32_t unknown_a6 = 0; // Probably battle-related (assigned together with battle_team_number)
/* 0074 */ le_uint32_t battle_team_number = 0;
/* 0078 */ G_6x70_Sub_Telepipe telepipe;
/* 0094 */ le_uint32_t unknown_a8 = 0;
/* 0098 */ G_6x70_Sub_UnknownA1 unknown_a9;
/* 0094 */ le_uint32_t death_flags = 0; // Only a few bits are used. 4 = player is dead
/* 0098 */ NPCTalkState npc_talk_state;
/* 00AC */ le_uint32_t area = 0;
/* 00B0 */ le_uint32_t flags2 = 0;
/* 00B0 */ le_uint32_t game_flags = 0;
/* 00B4 */ parray<uint8_t, 0x14> technique_levels_v1 = 0xFF; // Last byte is uninitialized
/* 00C8 */ PlayerVisualConfig visual;
/* 0118 */
@@ -5166,23 +5167,22 @@ struct G_GogoBall_6x79 {
G_UnusedHeader header;
le_uint32_t unknown_a1 = 0;
le_uint32_t unknown_a2 = 0;
le_float unknown_a3 = 0.0f;
le_float unknown_a4 = 0.0f;
VectorXZF ball_pos;
uint8_t unknown_a5 = 0;
parray<uint8_t, 3> unused;
} __packed_ws__(G_GogoBall_6x79, 0x18);
// 6x7A: Unknown (protected on V3/V4)
// 6x7A: Enable Stealth Suit effect (protected on V3/V4)
struct G_Unknown_6x7A {
struct G_EnableStealthSuitEffect_6x7A {
G_ClientIDHeader header;
} __packed_ws__(G_Unknown_6x7A, 4);
} __packed_ws__(G_EnableStealthSuitEffect_6x7A, 4);
// 6x7B: Unknown (protected on V3/V4)
// 6x7B: Disable Stealth Suit effect (protected on V3/V4)
struct G_Unknown_6x7B {
struct G_DisableStealthSuitEffect_6x7B {
G_ClientIDHeader header;
} __packed_ws__(G_Unknown_6x7B, 4);
} __packed_ws__(G_DisableStealthSuitEffect_6x7B, 4);
// 6x7C: Set Challenge records (not valid on Episode 3)
@@ -5225,7 +5225,7 @@ struct G_SetBattleModeData_6x7D {
} __packed_ws__(G_SetBattleModeData_6x7D, 0x18);
// 6x7E: Unknown (not valid on Episode 3)
// This subcommand is completely ignored (at least, by PSO GC).
// This subcommand is valid on DC 08/2001 (v2) and later, but it does nothing.
// 6x7F: Battle scores and places (not valid on Episode 3)
@@ -5292,26 +5292,29 @@ struct G_VolOptBossActions_6x84 {
le_uint16_t unused = 0;
} __packed_ws__(G_VolOptBossActions_6x84, 0x10);
// 6x85: Unknown (supported; game only; not valid on Episode 3)
// 6x85: Vol Opt phase 2 boss actions (not valid on Episode 3)
struct G_Unknown_6x85 {
struct G_VolOptPhase2BossActions_6x85 {
G_UnusedHeader header;
le_uint16_t unknown_a1 = 0; // Command is ignored unless this is 0
parray<le_uint16_t, 7> unknown_a2; // Only the first 3 appear to be used
} __packed_ws__(G_Unknown_6x85, 0x14);
} __packed_ws__(G_VolOptPhase2BossActions_6x85, 0x14);
// 6x86: Hit destructible object (not valid on Episode 3)
// This command only has an effect for TOVS2Wall01 objects.
struct G_HitDestructibleObject_6x86 : G_UpdateObjectState_6x0B {
le_uint16_t unknown_a3 = 0;
le_uint16_t unknown_a4 = 0;
le_uint16_t is_destroyed = 0;
le_uint16_t remaining_hits = 0;
} __packed_ws__(G_HitDestructibleObject_6x86, 0x10);
// 6x87: Shrink player (protected on V3/V4)
// This command is sent when the F838 quest opcode is executed.
struct G_ShrinkPlayer_6x87 {
G_ClientIDHeader header;
le_float unknown_a1 = 0.0f;
// This field contains the value set by the preceding F8A7 quest opcode.
le_float size = 1.0f;
} __packed_ws__(G_ShrinkPlayer_6x87, 8);
// 6x88: Restore shrunken player (protected on V3/V4)
@@ -5381,24 +5384,24 @@ struct G_SetPlayerBattleTeam_6x90 {
le_uint32_t team_number = 0; // 0 or 1
} __packed_ws__(G_SetPlayerBattleTeam_6x90, 8);
// 6x91: Unknown (supported; game only)
// TODO: Deals with TOAttackableCol objects. Figure out exactly what it does.
// 6x91: Hit destructible object
// This command only has an effect for TOAttackableCol objects.
struct G_UpdateAttackableColState_6x91 : G_UpdateObjectState_6x0B {
le_uint16_t unknown_a3 = 0;
le_uint16_t unknown_a4 = 0;
le_uint16_t is_destroyed = 0;
le_uint16_t remaining_hits = 0;
le_uint16_t switch_flag_num = 0;
uint8_t should_set = 0; // The switch flag is only set if this is equal to 1; otherwise it's cleared
uint8_t should_set = 0; // The switch flag is only set if this is equal to 1
uint8_t floor = 0;
} __packed_ws__(G_UpdateAttackableColState_6x91, 0x14);
// 6x92: Unknown (not valid on Episode 3)
// This does something with the TObjOnlineEndingHexMove object. TODO: Figure
// out exactly what.
// TODO: It looks like this sets the heights of players in the online victory
// screen? Figure out if this is actually what it does.
struct G_Unknown_6x92 {
G_UnusedHeader header;
le_uint32_t unknown_a1 = 0;
le_uint32_t client_id = 0;
le_float unknown_a2 = 0.0f;
} __packed_ws__(G_Unknown_6x92, 0x0C);
@@ -5480,14 +5483,16 @@ struct G_LevelUpAllTechniques_6x9B {
parray<uint8_t, 3> unused;
} __packed_ws__(G_LevelUpAllTechniques_6x9B, 8);
// 6x9C: Unknown (supported; game only; not valid on Episode 3)
// This command only has an effect in Ultimate mode.
// TODO: Figure out what this does.
// 6x9C: Set enemy low game flags (not valid on Episode 3)
// This command only has an effect in Ultimate mode. It sets the low 7 bits of
// game_flags (those that match 0x0000003F).
struct G_Unknown_6x9C {
struct G_SetEnemyLowGameFlagsUltimate_6x9C {
G_EntityIDHeader header;
le_uint32_t unknown_a1 = 0;
} __packed_ws__(G_Unknown_6x9C, 8);
// A virtual function is called on the enemy if low_game_flags is equal to
// any of 0x02, 0x04, 0x10, or 0x20.
le_uint32_t low_game_flags = 0;
} __packed_ws__(G_SetEnemyLowGameFlagsUltimate_6x9C, 8);
// 6x9D: Set dead flag (Challenge mode; not valid on Episode 3)
// This command causes the specified client to appear in the dead players list
@@ -5641,13 +5646,12 @@ struct G_Unknown_GCNTE_6xAB {
struct G_CreateLobbyChair_6xAB {
G_ClientIDHeader header;
le_uint16_t unknown_a1 = 0;
le_uint16_t unknown_a2 = 0;
le_uint16_t unused = 0;
le_uint16_t flags = 0; // Only the low two bits are used
} __packed_ws__(G_CreateLobbyChair_6xAB, 8);
// 6xAC: Unknown (not valid on pre-V3) (protected on V3/V4)
// This command appears to be different on GC NTE than on any other version. It
// also seems that no version (except perhaps GC NTE) ever sends this command.
// 6xAC: Unknown (not valid on pre-V3)
// This command appears to be different on GC NTE than on any other version.
struct G_Unknown_GCNTE_6xAC {
G_EntityIDHeader header;
@@ -5656,11 +5660,17 @@ struct G_Unknown_GCNTE_6xAC {
le_uint32_t unknown_a3 = 0;
} __packed_ws__(G_Unknown_GCNTE_6xAC, 0x0C);
struct G_Unknown_6xAC {
// 6xAC: Delete multiple inventory items (protected on V3/V4)
// This command appears to delete multiple items from a player's inventory.
// It's not clear when or why this would be used; the client never sends it.
// The disassembly also is somewhat confusing, and it's not clear that it even
// works properly.
struct G_DeleteMultipleInventoryItems_6xAC {
G_ClientIDHeader header;
le_uint32_t num_items = 0;
parray<le_uint32_t, 0x1E> item_ids;
} __packed_ws__(G_Unknown_6xAC, 0x80);
} __packed_ws__(G_DeleteMultipleInventoryItems_6xAC, 0x80);
// 6xAD: Olga Flow subordinate boss actions (not valid on pre-V3, Episode 3, or
// GC Trial Edition)
@@ -5704,14 +5714,15 @@ struct G_MoveLobbyChair_6xB0 {
// 6xB2: Play sound from player (not valid on pre-V3 or GC Trial Edition)
// This command is sent when a snapshot is taken on PSO GC, but it can be used
// to play any sound, centered on the local player. If localize is FFFF, then
// the sound is not centered on the local player and is just played globally.
// to play any sound. The sound is centered on the local player (even if the
// local player does not match client_id), or if client_id is FFFF, it is not
// localized at all and it just played globally.
struct G_PlaySoundFromPlayer_6xB2 {
G_UnusedHeader header;
uint8_t floor = 0;
uint8_t unused = 0;
le_uint16_t localize = 0;
le_uint16_t client_id = 0;
le_uint32_t sound_id = 0; // 0x00051720 = camera shutter sound
} __packed_ws__(G_PlaySoundFromPlayer_6xB2, 0x0C);
+109 -32
View File
@@ -1230,21 +1230,22 @@ MapFile::MapFile(std::shared_ptr<const std::string> data) {
throw runtime_error("section floor number too large");
}
size_t section_offset = r.where();
switch (header.type()) {
case SectionHeader::Type::OBJECT_SETS:
this->set_object_sets_for_floor(header.floor, r.getv(header.data_size), header.data_size);
this->set_object_sets_for_floor(header.floor, section_offset, r.getv(header.data_size), header.data_size);
break;
case SectionHeader::Type::ENEMY_SETS:
this->set_enemy_sets_for_floor(header.floor, r.getv(header.data_size), header.data_size);
this->set_enemy_sets_for_floor(header.floor, section_offset, r.getv(header.data_size), header.data_size);
break;
case SectionHeader::Type::EVENTS:
this->set_events_for_floor(header.floor, r.getv(header.data_size), header.data_size, true);
this->set_events_for_floor(header.floor, section_offset, r.getv(header.data_size), header.data_size, true);
break;
case SectionHeader::Type::RANDOM_ENEMY_LOCATIONS:
this->set_random_enemy_locations_for_floor(header.floor, r.getv(header.data_size), header.data_size);
this->set_random_enemy_locations_for_floor(header.floor, section_offset, r.getv(header.data_size), header.data_size);
break;
case SectionHeader::Type::RANDOM_ENEMY_DEFINITIONS:
this->set_random_enemy_definitions_for_floor(header.floor, r.getv(header.data_size), header.data_size);
this->set_random_enemy_definitions_for_floor(header.floor, section_offset, r.getv(header.data_size), header.data_size);
break;
default:
throw runtime_error("invalid section type");
@@ -1260,15 +1261,15 @@ MapFile::MapFile(
std::shared_ptr<const std::string> events_data) {
if (objects_data) {
this->link_data(objects_data);
this->set_object_sets_for_floor(floor, objects_data->data(), objects_data->size());
this->set_object_sets_for_floor(floor, 0, objects_data->data(), objects_data->size());
}
if (enemies_data) {
this->link_data(enemies_data);
this->set_enemy_sets_for_floor(floor, enemies_data->data(), enemies_data->size());
this->set_enemy_sets_for_floor(floor, 0, enemies_data->data(), enemies_data->size());
}
if (events_data) {
this->link_data(events_data);
this->set_events_for_floor(floor, events_data->data(), events_data->size(), false);
this->set_events_for_floor(floor, 0, events_data->data(), events_data->size(), false);
}
this->compute_floor_start_indexes();
}
@@ -1282,7 +1283,7 @@ void MapFile::link_data(std::shared_ptr<const string> data) {
}
}
void MapFile::set_object_sets_for_floor(uint8_t floor, const void* data, size_t size) {
void MapFile::set_object_sets_for_floor(uint8_t floor, size_t file_offset, const void* data, size_t size) {
auto& floor_sections = this->sections_for_floor.at(floor);
if (floor_sections.object_sets) {
throw runtime_error("multiple object sets sections for same floor");
@@ -1292,9 +1293,11 @@ void MapFile::set_object_sets_for_floor(uint8_t floor, const void* data, size_t
}
floor_sections.object_sets = reinterpret_cast<const ObjectSetEntry*>(data);
floor_sections.object_set_count = size / sizeof(ObjectSetEntry);
floor_sections.object_sets_file_offset = file_offset;
floor_sections.object_sets_file_size = size;
}
void MapFile::set_enemy_sets_for_floor(uint8_t floor, const void* data, size_t size) {
void MapFile::set_enemy_sets_for_floor(uint8_t floor, size_t file_offset, const void* data, size_t size) {
auto& floor_sections = this->sections_for_floor.at(floor);
if (floor_sections.enemy_sets) {
throw runtime_error("multiple enemy sets sections for same floor");
@@ -1307,9 +1310,11 @@ void MapFile::set_enemy_sets_for_floor(uint8_t floor, const void* data, size_t s
}
floor_sections.enemy_sets = reinterpret_cast<const EnemySetEntry*>(data);
floor_sections.enemy_set_count = size / sizeof(EnemySetEntry);
floor_sections.enemy_sets_file_offset = file_offset;
floor_sections.enemy_sets_file_size = size;
}
void MapFile::set_events_for_floor(uint8_t floor, const void* data, size_t size, bool allow_evt2) {
void MapFile::set_events_for_floor(uint8_t floor, size_t file_offset, const void* data, size_t size, bool allow_evt2) {
auto& floor_sections = this->sections_for_floor.at(floor);
if (floor_sections.events_data || floor_sections.events1 || floor_sections.events2 || floor_sections.event_action_stream) {
throw runtime_error("multiple events sections for same floor");
@@ -1317,6 +1322,9 @@ void MapFile::set_events_for_floor(uint8_t floor, const void* data, size_t size,
floor_sections.events_data = data;
floor_sections.events_data_size = size;
floor_sections.events_file_offset = file_offset;
floor_sections.events_file_size = size;
phosg::StringReader r(data, size);
const auto& events_header = r.get<EventsSectionHeader>();
floor_sections.event_count = events_header.entry_count;
@@ -1339,7 +1347,7 @@ void MapFile::set_events_for_floor(uint8_t floor, const void* data, size_t size,
events_header.action_stream_offset, floor_sections.event_action_stream_bytes);
}
void MapFile::set_random_enemy_locations_for_floor(uint8_t floor, const void* data, size_t size) {
void MapFile::set_random_enemy_locations_for_floor(uint8_t floor, size_t file_offset, const void* data, size_t size) {
auto& floor_sections = this->sections_for_floor.at(floor);
if (floor_sections.random_enemy_locations_data) {
throw runtime_error("multiple random enemy locations sections for same floor");
@@ -1347,10 +1355,12 @@ void MapFile::set_random_enemy_locations_for_floor(uint8_t floor, const void* da
floor_sections.random_enemy_locations_data = data;
floor_sections.random_enemy_locations_data_size = size;
floor_sections.random_enemy_locations_file_offset = file_offset;
floor_sections.random_enemy_locations_file_size = size;
this->has_any_random_sections = true;
}
void MapFile::set_random_enemy_definitions_for_floor(uint8_t floor, const void* data, size_t size) {
void MapFile::set_random_enemy_definitions_for_floor(uint8_t floor, size_t file_offset, const void* data, size_t size) {
auto& floor_sections = this->sections_for_floor.at(floor);
if (floor_sections.random_enemy_definitions_data) {
throw runtime_error("multiple random enemy locations sections for same floor");
@@ -1358,6 +1368,8 @@ void MapFile::set_random_enemy_definitions_for_floor(uint8_t floor, const void*
floor_sections.random_enemy_definitions_data = data;
floor_sections.random_enemy_definitions_data_size = size;
floor_sections.random_enemy_definitions_file_offset = file_offset;
floor_sections.random_enemy_definitions_file_size = size;
this->has_any_random_sections = true;
}
@@ -1383,15 +1395,15 @@ std::shared_ptr<MapFile> MapFile::materialize_random_sections(uint32_t random_se
const auto& this_sf = this->sections_for_floor[floor];
if (this_sf.object_sets) {
new_map->set_object_sets_for_floor(floor, this_sf.object_sets, this_sf.object_set_count * sizeof(ObjectSetEntry));
new_map->set_object_sets_for_floor(floor, 0, this_sf.object_sets, this_sf.object_set_count * sizeof(ObjectSetEntry));
}
if (this_sf.enemy_sets) {
new_map->set_enemy_sets_for_floor(floor, this_sf.enemy_sets, this_sf.enemy_set_count * sizeof(EnemySetEntry));
new_map->set_enemy_sets_for_floor(floor, 0, this_sf.enemy_sets, this_sf.enemy_set_count * sizeof(EnemySetEntry));
}
if (this_sf.events1) {
new_map->set_events_for_floor(floor, this_sf.events_data, this_sf.events_data_size, false);
new_map->set_events_for_floor(floor, 0, this_sf.events_data, this_sf.events_data_size, false);
} else if (this_sf.events2) {
if (!this_sf.random_enemy_locations_data || !this_sf.random_enemy_definitions_data) {
throw runtime_error("cannot materialize random enemies; evt2 section present but one or both random data sections are missing");
@@ -1547,11 +1559,11 @@ std::shared_ptr<MapFile> MapFile::materialize_random_sections(uint32_t random_se
auto enemy_sets_sec_data = make_shared<string>(std::move(enemy_sets_w.str()));
new_map->link_data(enemy_sets_sec_data);
new_map->set_enemy_sets_for_floor(floor, enemy_sets_sec_data->data(), enemy_sets_sec_data->size());
new_map->set_enemy_sets_for_floor(floor, 0, enemy_sets_sec_data->data(), enemy_sets_sec_data->size());
auto events1_sec_data = make_shared<string>(std::move(events1_sec_w.str()));
new_map->link_data(events1_sec_data);
new_map->set_events_for_floor(floor, events1_sec_data->data(), events1_sec_data->size(), false);
new_map->set_events_for_floor(floor, 0, events1_sec_data->data(), events1_sec_data->size(), false);
}
}
@@ -1670,44 +1682,60 @@ string MapFile::disassemble() const {
phosg::StringReader as_r(sf.event_action_stream, sf.event_action_stream_bytes);
if (sf.object_sets) {
ret.emplace_back(phosg::string_printf(".object_sets %hhu", floor));
ret.emplace_back(phosg::string_printf(".object_sets %hhu /* 0x%zX in file; 0x%zX bytes */",
floor, sf.object_sets_file_offset, sf.object_sets_file_size));
for (size_t z = 0; z < sf.object_set_count; z++) {
size_t k_id = z + sf.first_object_set_index;
ret.emplace_back(phosg::string_printf("/* K-%03zX */ ", k_id) + sf.object_sets[z].str());
}
}
if (sf.enemy_sets) {
ret.emplace_back(phosg::string_printf(".enemy_sets %hhu", floor));
ret.emplace_back(phosg::string_printf(".enemy_sets %hhu /* 0x%zX in file; 0x%zX bytes */",
floor, sf.enemy_sets_file_offset, sf.enemy_sets_file_size));
for (size_t z = 0; z < sf.enemy_set_count; z++) {
size_t s_id = z + sf.first_enemy_set_index;
ret.emplace_back(phosg::string_printf("/* S-%03zX */ ", s_id) + sf.enemy_sets[z].str());
}
}
if (sf.events1) {
ret.emplace_back(phosg::string_printf(".events %hhu", floor));
ret.emplace_back(phosg::string_printf(".events %hhu /* 0x%zX in file; 0x%zX bytes; 0x%zX bytes in action stream */",
floor, sf.events_file_offset, sf.events_file_size, sf.event_action_stream_bytes));
for (size_t z = 0; z < sf.event_count; z++) {
const auto& ev = sf.events2[z];
const auto& ev = sf.events1[z];
size_t w_id = z + sf.first_event_set_index;
ret.emplace_back(phosg::string_printf("/* W-%03zX */ ", w_id) + ev.str());
if (ev.action_stream_offset >= sf.event_action_stream_bytes) {
ret.emplace_back(phosg::string_printf(
" // WARNING: Event action stream offset (0x%" PRIX32 ") is outside of this section",
ev.action_stream_offset.load()));
}
size_t as_size = as_r.size() - ev.action_stream_offset;
ret.emplace_back(this->disassemble_action_stream(as_r.pgetv(ev.action_stream_offset, as_size), as_size));
}
}
if (sf.events2) {
ret.emplace_back(phosg::string_printf(".random_events %hhu", floor));
ret.emplace_back(phosg::string_printf(".random_events %hhu /* 0x%zX in file; 0x%zX bytes; 0x%zX bytes in action stream */",
floor, sf.events_file_offset, sf.events_file_size, sf.event_action_stream_bytes));
for (size_t z = 0; z < sf.event_count; z++) {
const auto& ev = sf.events2[z];
ret.emplace_back(phosg::string_printf("/* index %zu */", z) + ev.str());
if (ev.action_stream_offset >= sf.event_action_stream_bytes) {
ret.emplace_back(phosg::string_printf(
" // WARNING: Event action stream offset (0x%" PRIX32 ") is outside of this section",
ev.action_stream_offset.load()));
}
size_t as_size = as_r.size() - ev.action_stream_offset;
ret.emplace_back(this->disassemble_action_stream(as_r.pgetv(ev.action_stream_offset, as_size), as_size));
}
}
if (sf.random_enemy_locations_data) {
ret.emplace_back(phosg::string_printf(".random_enemy_locations %hhu", floor));
ret.emplace_back(phosg::string_printf(".random_enemy_locations %hhu /* 0x%zX in file; 0x%zX bytes */",
floor, sf.random_enemy_locations_file_offset, sf.random_enemy_locations_file_size));
ret.emplace_back(phosg::format_data(sf.random_enemy_locations_data, sf.random_enemy_locations_data_size));
}
if (sf.random_enemy_definitions_data) {
ret.emplace_back(phosg::string_printf(".random_enemy_definitions %hhu", floor));
ret.emplace_back(phosg::string_printf(".random_enemy_definitions %hhu /* 0x%zX in file; 0x%zX bytes */",
floor, sf.random_enemy_definitions_file_offset, sf.random_enemy_definitions_file_size));
ret.emplace_back(phosg::format_data(sf.random_enemy_definitions_data, sf.random_enemy_definitions_data_size));
}
}
@@ -2415,7 +2443,10 @@ void SuperMap::link_event_version(
const void* map_file_action_stream,
size_t map_file_action_stream_size) {
if (entry->action_stream_offset >= map_file_action_stream_size) {
throw runtime_error("event entry action stream offset is beyond end of action stream");
string s = entry->str();
throw runtime_error(phosg::string_printf(
"action stream offset 0x%" PRIX32 " is beyond end of action stream (0x%zX) for event %s",
entry->action_stream_offset.load(), map_file_action_stream_size, s.c_str()));
}
const void* ev_action_stream_start = reinterpret_cast<const uint8_t*>(map_file_action_stream) +
entry->action_stream_offset;
@@ -3257,6 +3288,40 @@ uint32_t MapState::RareEnemyRates::for_enemy_type(EnemyType type) const {
const shared_ptr<const MapState::RareEnemyRates> MapState::NO_RARE_ENEMIES = make_shared<MapState::RareEnemyRates>(0, 0);
const shared_ptr<const MapState::RareEnemyRates> MapState::DEFAULT_RARE_ENEMIES = make_shared<MapState::RareEnemyRates>(0x0083126E, 0x1999999A);
uint32_t MapState::EnemyState::convert_game_flags(uint32_t game_flags, bool to_v3) {
// The format of game_flags was changed significantly between v2 and v3, and
// not accounting for this results in odd effects like other characters not
// appearing when joining a game. Unfortunately, some bits were deleted on v3
// and other bits were added, so it doesn't suffice to simply store the most
// complete format of this field - we have to be able to convert between the
// two.
// Bits on v2: ?IHCBAzy xwvutsrq ponmlkji hgfedcba
// Bits on v3: ?IHGFEDC BAzyxwvu srqponkj hgfedcba
// The bits ilmt were removed in v3 and the bits to their left were shifted
// right. The bits DEFG were added in v3 and do not exist on v2.
// Known meanings for these bits:
// o = is dead
// n = should play hit animation
// y = is near enemy
// H = is enemy?
// I = is object? (some entities have both H and I set though)
// It could be that the flags 0x70000000 are actually a 3-bit integer rather
// than individual flags. TODO: Investigate this.
if (to_v3) {
return (game_flags & 0xE00000FF) |
((game_flags & 0x00000600) >> 1) |
((game_flags & 0x0007E000) >> 3) |
((game_flags & 0x1FF00000) >> 4);
} else {
return (game_flags & 0xE00000FF) |
((game_flags << 1) & 0x00000600) |
((game_flags << 3) & 0x0007E000) |
((game_flags << 4) & 0x1FF00000);
}
}
MapState::EntityIterator::EntityIterator(MapState* map_state, Version version, bool at_end)
: map_state(map_state),
version(version),
@@ -3755,6 +3820,7 @@ void MapState::import_object_states_from_sync(
void MapState::import_enemy_states_from_sync(Version from_version, const SyncEnemyStateEntry* entries, size_t entry_count) {
this->log.info("Importing enemy state from sync command");
size_t enemy_index = 0;
bool is_v3 = !is_v1_or_v2(from_version);
for (const auto& fc : this->floor_config_entries) {
if (!fc.super_map) {
continue;
@@ -3775,10 +3841,15 @@ void MapState::import_enemy_states_from_sync(Version from_version, const SyncEne
if (ene_st->super_ene != ene) {
throw logic_error("super enemy link is incorrect");
}
if (ene_st->game_flags != entry.flags) {
this->log.warning("(%04zX => E-%03zX) Flags from client (%08" PRIX32 ") do not match game flags from map (%08" PRIX32 ")",
enemy_index, ene_st->e_id, entry.flags.load(), ene_st->game_flags);
ene_st->game_flags = entry.flags;
if (ene_st->get_game_flags(is_v3) != entry.flags) {
this->log.warning("(%04zX => E-%03zX) Flags from client (%08" PRIX32 "(%s)) do not match game flags from map (%08" PRIX32 "(%s))",
enemy_index,
ene_st->e_id,
entry.flags.load(),
is_v3 ? "v3" : "v2",
ene_st->game_flags,
(ene_st->server_flags & MapState::EnemyState::Flag::GAME_FLAGS_IS_V3) ? "v3" : "v2");
ene_st->set_game_flags(entry.flags, !is_v1_or_v2(from_version));
}
if (ene_st->total_damage != entry.total_damage) {
this->log.warning("(%04zX => E-%03zX) Total damage from client (%hu) does not match total damage from map (%hu)",
@@ -4107,8 +4178,14 @@ void MapState::print(FILE* stream) const {
}
}
string ene_str = ene_st->super_ene->str();
fprintf(stream, " %s total_damage=%04hX rare_flags=%04hX game_flags=%08" PRIX32 " set_flags=%04hX server_flags=%02hhX\n",
ene_str.c_str(), ene_st->total_damage, ene_st->rare_flags, ene_st->game_flags, ene_st->set_flags, ene_st->server_flags);
fprintf(stream, " %s total_damage=%04hX rare_flags=%04hX game_flags=%08" PRIX32 "(%s) set_flags=%04hX server_flags=%04hX\n",
ene_str.c_str(),
ene_st->total_damage,
ene_st->rare_flags,
ene_st->game_flags,
(ene_st->server_flags & MapState::EnemyState::Flag::GAME_FLAGS_IS_V3) ? "v3" : "v2",
ene_st->set_flags,
ene_st->server_flags);
}
if (this->bb_rare_enemy_indexes.empty()) {
+41 -11
View File
@@ -337,14 +337,20 @@ public:
};
struct FloorSections {
size_t object_sets_file_offset = 0;
size_t object_sets_file_size = 0;
const ObjectSetEntry* object_sets = nullptr;
size_t object_set_count = 0;
size_t first_object_set_index = 0;
size_t enemy_sets_file_offset = 0;
size_t enemy_sets_file_size = 0;
const EnemySetEntry* enemy_sets = nullptr;
size_t enemy_set_count = 0;
size_t first_enemy_set_index = 0;
size_t events_file_offset = 0;
size_t events_file_size = 0;
const void* events_data = nullptr;
size_t events_data_size = 0;
const Event1Entry* events1 = nullptr;
@@ -354,9 +360,13 @@ public:
size_t event_action_stream_bytes = 0;
size_t first_event_set_index = 0;
size_t random_enemy_locations_file_offset = 0;
size_t random_enemy_locations_file_size = 0;
const void* random_enemy_locations_data = nullptr;
size_t random_enemy_locations_data_size = 0;
size_t random_enemy_definitions_file_offset = 0;
size_t random_enemy_definitions_file_size = 0;
const void* random_enemy_definitions_data = nullptr;
size_t random_enemy_definitions_data_size = 0;
};
@@ -412,11 +422,11 @@ public:
protected:
void link_data(std::shared_ptr<const std::string> data);
void set_object_sets_for_floor(uint8_t floor, const void* data, size_t size);
void set_enemy_sets_for_floor(uint8_t floor, const void* data, size_t size);
void set_events_for_floor(uint8_t floor, const void* data, size_t size, bool allow_evt2);
void set_random_enemy_locations_for_floor(uint8_t floor, const void* data, size_t size);
void set_random_enemy_definitions_for_floor(uint8_t floor, const void* data, size_t size);
void set_object_sets_for_floor(uint8_t floor, size_t file_offset, const void* data, size_t size);
void set_enemy_sets_for_floor(uint8_t floor, size_t file_offset, const void* data, size_t size);
void set_events_for_floor(uint8_t floor, size_t file_offset, const void* data, size_t size, bool allow_evt2);
void set_random_enemy_locations_for_floor(uint8_t floor, size_t file_offset, const void* data, size_t size);
void set_random_enemy_definitions_for_floor(uint8_t floor, size_t file_offset, const void* data, size_t size);
void compute_floor_start_indexes();
@@ -668,11 +678,12 @@ public:
struct EnemyState {
std::shared_ptr<const SuperMap::Enemy> super_ene;
enum Flag {
LAST_HIT_MASK = 0x03,
EXP_GIVEN = 0x04,
ITEM_DROPPED = 0x08,
ALL_HITS_MASK_FIRST = 0x10,
ALL_HITS_MASK = 0xF0,
LAST_HIT_MASK = 0x0003,
EXP_GIVEN = 0x0004,
ITEM_DROPPED = 0x0008,
ALL_HITS_MASK_FIRST = 0x0010,
ALL_HITS_MASK = 0x00F0,
GAME_FLAGS_IS_V3 = 0x0100,
};
size_t e_id = 0;
size_t set_id = 0;
@@ -680,7 +691,9 @@ public:
uint16_t rare_flags = 0;
uint32_t game_flags = 0; // From 6x0A
uint16_t set_flags = 0; // Only used if super_ene->child_index == 0
uint8_t server_flags = 0;
uint16_t server_flags = 0;
static uint32_t convert_game_flags(uint32_t game_flags, bool to_v3);
inline void reset() {
this->total_damage = 0;
@@ -690,6 +703,23 @@ public:
this->server_flags = 0;
}
inline void set_game_flags(uint32_t game_flags, bool is_v3) {
this->game_flags = game_flags;
if (is_v3) {
this->server_flags |= Flag::GAME_FLAGS_IS_V3;
} else {
this->server_flags &= ~Flag::GAME_FLAGS_IS_V3;
}
}
inline uint32_t get_game_flags(bool is_v3) const {
bool flags_is_v3 = (this->server_flags & Flag::GAME_FLAGS_IS_V3);
if (flags_is_v3 == is_v3) {
return this->game_flags;
} else {
return this->convert_game_flags(this->game_flags, is_v3);
}
}
inline bool is_rare(Version version) const {
return (((rare_flags >> static_cast<size_t>(version)) & 1) ||
((version == Version::BB_V4) ? this->super_ene->is_default_rare_bb : this->super_ene->is_default_rare_v123));
+60 -42
View File
@@ -324,6 +324,50 @@ QuestFlagsV1::operator QuestFlags() const {
return ret;
}
const QuestFlagsForDifficulty 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,
/* 0080 */ 0x00, 0x00, 0x01, 0xE0, 0x00, 0x00, 0x00, 0x00,
/* 00C0 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
/* 0100 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFC, 0x00,
/* 0140 */ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
/* 0180 */ 0xFE, 0x00, 0x7F, 0xFE, 0x0F, 0xFF, 0xFF, 0x80,
/* 01C0 */ 0x3F, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x0F, 0xFF,
/* 0200 */ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00,
/* 0240 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
/* 0280 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07,
/* 02C0 */ 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
/* 0300 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
/* 0340 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
/* 0380 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
/* 03C0 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// clang-format on
// The flags in the above mask are:
// 000A 000B 000C 000D 000E 000F 0010 0011 0012 0013 0014 0015 0016 0017
// 0018 0019 001A 001E 001F 0020 0021 0022 0028 0029 002A 002B 002C 002D
// 002E 002F 0030 0031 0032 0033 0034 0035 0036 0037 0046 0047 0048 0049
// 004A 004B 004C 004D 004E 004F 0050 0051 0052 0053 0054 0055 0056 0057
// 0058 0059 005A 005B 005C 005D 005E 005F 0060 0061 0062 0063 0097 0098
// 0099 009A 012D 012E 012F 0130 0131 0132 0133 0134 0135 0140 0141 0142
// 0143 0144 0145 0146 0147 0148 0149 014A 014B 014C 014D 014E 014F 0150
// 0151 0152 0153 0154 0155 0156 0157 0158 0159 015A 015B 015C 015D 015E
// 015F 0160 0161 0162 0163 0164 0165 0166 0167 0168 0169 016A 016B 016C
// 016D 016E 016F 0170 0171 0172 0173 0174 0175 0176 0177 0178 0179 017A
// 017B 017C 017D 017E 017F 0180 0181 0182 0183 0184 0185 0186 0191 0192
// 0193 0194 0195 0196 0197 0198 0199 019A 019B 019C 019D 019E 01A4 01A5
// 01A6 01A7 01A8 01A9 01AA 01AB 01AC 01AD 01AE 01AF 01B0 01B1 01B2 01B3
// 01B4 01B5 01B6 01B7 01B8 01C2 01C3 01C4 01C5 01C6 01C7 01C8 01C9 01CA
// 01CB 01CC 01CD 01CE 01CF 01D0 01D1 01D2 01D3 01D4 01D5 01D6 01F4 01F5
// 01F6 01F7 01F8 01F9 01FA 01FB 01FC 01FD 01FE 01FF 0200 0201 0202 0203
// 0204 0205 0206 0207 0208 0209 020A 020B 020C 020D 020E 020F 0210 0211
// 0212 0213 0214 0215 0216 0217 0218 0219 021A 021B 021C 021D 021E 021F
// 0220 0221 0222 0223 0224 0225 0226 0227 0228 0229 022A 022B 022C 022D
// 022E 022F 0230 0231 0232 0233 0234 0235 02BD 02BE 02BF 02C0 02C1 02C2
// 02C3 02C4
}};
BattleRules::BattleRules(const phosg::JSON& json) {
static const phosg::JSON empty_list = phosg::JSON::list();
@@ -699,46 +743,20 @@ const ChallengeTemplateDefinition& get_challenge_template_definition(Version ver
}
}
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,
/* 0080 */ 0x00, 0x00, 0x01, 0xE0, 0x00, 0x00, 0x00, 0x00,
/* 00C0 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
/* 0100 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFC, 0x00,
/* 0140 */ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
/* 0180 */ 0xFE, 0x00, 0x7F, 0xFE, 0x0F, 0xFF, 0xFF, 0x80,
/* 01C0 */ 0x3F, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x0F, 0xFF,
/* 0200 */ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00,
/* 0240 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
/* 0280 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07,
/* 02C0 */ 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
/* 0300 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
/* 0340 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
/* 0380 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
/* 03C0 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// clang-format on
NPCTalkState::NPCTalkState(const NPCTalkState_DCProtos& proto)
: unknown_a1(proto.unknown_a1),
unknown_a2(proto.unknown_a2),
unknown_a3(0),
unknown_a4(proto.unknown_a4),
unknown_a5(proto.unknown_a5),
unknown_a6(proto.unknown_a6) {}
// The flags in the above mask are:
// 000A 000B 000C 000D 000E 000F 0010 0011 0012 0013 0014 0015 0016 0017
// 0018 0019 001A 001E 001F 0020 0021 0022 0028 0029 002A 002B 002C 002D
// 002E 002F 0030 0031 0032 0033 0034 0035 0036 0037 0046 0047 0048 0049
// 004A 004B 004C 004D 004E 004F 0050 0051 0052 0053 0054 0055 0056 0057
// 0058 0059 005A 005B 005C 005D 005E 005F 0060 0061 0062 0063 0097 0098
// 0099 009A 012D 012E 012F 0130 0131 0132 0133 0134 0135 0140 0141 0142
// 0143 0144 0145 0146 0147 0148 0149 014A 014B 014C 014D 014E 014F 0150
// 0151 0152 0153 0154 0155 0156 0157 0158 0159 015A 015B 015C 015D 015E
// 015F 0160 0161 0162 0163 0164 0165 0166 0167 0168 0169 016A 016B 016C
// 016D 016E 016F 0170 0171 0172 0173 0174 0175 0176 0177 0178 0179 017A
// 017B 017C 017D 017E 017F 0180 0181 0182 0183 0184 0185 0186 0191 0192
// 0193 0194 0195 0196 0197 0198 0199 019A 019B 019C 019D 019E 01A4 01A5
// 01A6 01A7 01A8 01A9 01AA 01AB 01AC 01AD 01AE 01AF 01B0 01B1 01B2 01B3
// 01B4 01B5 01B6 01B7 01B8 01C2 01C3 01C4 01C5 01C6 01C7 01C8 01C9 01CA
// 01CB 01CC 01CD 01CE 01CF 01D0 01D1 01D2 01D3 01D4 01D5 01D6 01F4 01F5
// 01F6 01F7 01F8 01F9 01FA 01FB 01FC 01FD 01FE 01FF 0200 0201 0202 0203
// 0204 0205 0206 0207 0208 0209 020A 020B 020C 020D 020E 020F 0210 0211
// 0212 0213 0214 0215 0216 0217 0218 0219 021A 021B 021C 021D 021E 021F
// 0220 0221 0222 0223 0224 0225 0226 0227 0228 0229 022A 022B 022C 022D
// 022E 022F 0230 0231 0232 0233 0234 0235 02BD 02BE 02BF 02C0 02C1 02C2
// 02C3 02C4
}};
NPCTalkState::operator NPCTalkState_DCProtos() const {
NPCTalkState_DCProtos ret;
ret.unknown_a1 = this->unknown_a1;
ret.unknown_a2 = this->unknown_a2;
ret.unknown_a4 = this->unknown_a4;
ret.unknown_a5 = this->unknown_a5;
ret.unknown_a6 = this->unknown_a6;
return ret;
}
+44 -1
View File
@@ -11,6 +11,7 @@
#include <vector>
#include "ChoiceSearch.hh"
#include "CommonFileFormats.hh"
#include "FileContentsCache.hh"
#include "ItemData.hh"
#include "LevelTable.hh"
@@ -777,6 +778,8 @@ inline PlayerDispDataBB convert_player_disp_data<PlayerDispDataBB>(
}
struct QuestFlagsForDifficulty {
static const QuestFlagsForDifficulty BB_QUEST_FLAG_APPLY_MASK;
parray<uint8_t, 0x80> data;
inline bool get(uint16_t flag_index) const {
@@ -995,4 +998,44 @@ using SymbolChatBE = SymbolChatT<true>;
check_struct_size(SymbolChat, 0x3C);
check_struct_size(SymbolChatBE, 0x3C);
extern const QuestFlagsForDifficulty bb_quest_flag_apply_mask;
struct TelepipeState {
/* 00 */ le_uint16_t owner_client_id = 0xFFFF;
/* 02 */ le_uint16_t floor = 0;
/* 04 */ le_uint32_t room_id = 0;
/* 08 */ VectorXYZF pos;
/* 14 */ le_uint32_t angle_y = 0;
/* 18 */
} __packed_ws__(TelepipeState, 0x18);
struct NPCTalkState_DCProtos {
// This is used in all versions of this command except DCNTE and 11/2000.
/* 00 */ le_uint16_t unknown_a1 = 0;
/* 02 */ le_uint16_t unknown_a2 = 0;
// unknown_a3 is missing in this format, unlike the v1+ format below
/* 04 */ le_float unknown_a4 = 0.0f;
/* 08 */ le_float unknown_a5 = 0.0f;
/* 0C */ le_float unknown_a6 = 0.0f;
/* 10 */
} __packed_ws__(NPCTalkState_DCProtos, 0x10);
struct NPCTalkState {
// This is used in all versions of this command except DCNTE and 11/2000.
/* 00 */ le_uint16_t unknown_a1 = 0;
/* 02 */ le_uint16_t unknown_a2 = 0;
/* 04 */ le_uint32_t unknown_a3 = 0;
/* 08 */ le_float unknown_a4 = 0.0f;
/* 0C */ le_float unknown_a5 = 0.0f;
/* 10 */ le_float unknown_a6 = 0.0f;
/* 14 */
NPCTalkState() = default;
NPCTalkState(const NPCTalkState_DCProtos& proto);
operator NPCTalkState_DCProtos() const;
} __packed_ws__(NPCTalkState, 0x14);
struct StatusEffectState {
/* 00 */ le_uint32_t effect_type = 0;
/* 04 */ le_float multiplier = 0.0f;
/* 08 */ le_uint32_t remaining_frames = 0;
/* 0C */
} __packed_ws__(StatusEffectState, 0x0C);
+114 -82
View File
@@ -788,11 +788,11 @@ Parsed6x70Data::Parsed6x70Data(
unknown_a6(0),
battle_team_number(0),
telepipe(cmd.telepipe),
unknown_a8(cmd.unknown_a8),
unknown_a9_nte_112000(cmd.unknown_a9),
death_flags(cmd.death_flags),
npc_talk_state(cmd.npc_talk_state),
area(cmd.area),
flags2_value(cmd.flags2),
flags2_is_v3(false),
game_flags(cmd.game_flags),
game_flags_is_v3(false),
visual(cmd.visual),
stats(cmd.stats),
num_items(cmd.num_items),
@@ -824,11 +824,11 @@ Parsed6x70Data::Parsed6x70Data(
unknown_a6(0),
battle_team_number(0),
telepipe(cmd.telepipe),
unknown_a8(cmd.unknown_a8),
unknown_a9_nte_112000(cmd.unknown_a9),
death_flags(cmd.death_flags),
npc_talk_state(cmd.npc_talk_state),
area(cmd.area),
flags2_value(cmd.flags2),
flags2_is_v3(false),
game_flags(cmd.game_flags),
game_flags_is_v3(false),
visual(cmd.visual),
stats(cmd.stats),
num_items(cmd.num_items),
@@ -860,7 +860,7 @@ Parsed6x70Data::Parsed6x70Data(
Version from_version,
bool from_client_customization)
: Parsed6x70Data(cmd.base, guild_card_number, from_version, from_client_customization) {
this->flags2_is_v3 = true;
this->game_flags_is_v3 = true;
this->stats = cmd.stats;
this->num_items = cmd.num_items;
this->items = cmd.items;
@@ -876,7 +876,7 @@ Parsed6x70Data::Parsed6x70Data(
Version from_version,
bool from_client_customization)
: Parsed6x70Data(cmd.base, guild_card_number, from_version, from_client_customization) {
this->flags2_is_v3 = true;
this->game_flags_is_v3 = true;
this->stats = cmd.stats;
this->num_items = cmd.num_items;
this->items = cmd.items;
@@ -892,7 +892,7 @@ Parsed6x70Data::Parsed6x70Data(
Version from_version,
bool from_client_customization)
: Parsed6x70Data(cmd.base, guild_card_number, from_version, from_client_customization) {
this->flags2_is_v3 = true;
this->game_flags_is_v3 = true;
this->stats = cmd.stats;
this->num_items = cmd.num_items;
this->items = cmd.items;
@@ -909,10 +909,10 @@ G_SyncPlayerDispAndInventory_DCNTE_6x70 Parsed6x70Data::as_dc_nte(shared_ptr<Ser
ret.unknown_a5 = this->unknown_a5_nte;
ret.unknown_a6 = this->unknown_a6;
ret.telepipe = this->telepipe;
ret.unknown_a8 = this->unknown_a8;
ret.unknown_a9 = this->unknown_a9_nte_112000;
ret.death_flags = this->death_flags;
ret.npc_talk_state = this->npc_talk_state;
ret.area = this->area;
ret.flags2 = this->get_flags2(false);
ret.game_flags = this->get_game_flags(false);
ret.visual = this->visual;
ret.stats = this->stats;
ret.num_items = this->num_items;
@@ -937,10 +937,10 @@ G_SyncPlayerDispAndInventory_DC112000_6x70 Parsed6x70Data::as_dc_112000(shared_p
ret.bonus_tp_from_materials = this->bonus_tp_from_materials;
ret.unknown_a5 = this->unknown_a5_112000;
ret.telepipe = this->telepipe;
ret.unknown_a8 = this->unknown_a8;
ret.unknown_a9 = this->unknown_a9_nte_112000;
ret.death_flags = this->death_flags;
ret.npc_talk_state = this->npc_talk_state;
ret.area = this->area;
ret.flags2 = this->get_flags2(false);
ret.game_flags = this->get_game_flags(false);
ret.visual = this->visual;
ret.stats = this->stats;
ret.num_items = this->num_items;
@@ -1099,11 +1099,11 @@ Parsed6x70Data::Parsed6x70Data(
unknown_a6(base.unknown_a6),
battle_team_number(base.battle_team_number),
telepipe(base.telepipe),
unknown_a8(base.unknown_a8),
unknown_a9_final(base.unknown_a9),
death_flags(base.death_flags),
npc_talk_state(base.npc_talk_state),
area(base.area),
flags2_value(base.flags2),
flags2_is_v3(!is_v1_or_v2(from_version)),
game_flags(base.game_flags),
game_flags_is_v3(!is_v1_or_v2(from_version)),
technique_levels_v1(base.technique_levels_v1),
visual(base.visual) {}
@@ -1123,42 +1123,19 @@ G_6x70_Base_V1 Parsed6x70Data::base_v1(bool is_v3) const {
ret.unknown_a6 = this->unknown_a6;
ret.battle_team_number = this->battle_team_number;
ret.telepipe = this->telepipe;
ret.unknown_a8 = this->unknown_a8;
ret.unknown_a9 = this->unknown_a9_final;
ret.death_flags = this->death_flags;
ret.npc_talk_state = this->npc_talk_state;
ret.area = this->area;
ret.flags2 = this->get_flags2(is_v3);
ret.game_flags = this->get_game_flags(is_v3);
ret.technique_levels_v1 = this->technique_levels_v1;
ret.visual = this->visual;
return ret;
}
uint32_t Parsed6x70Data::get_flags2(bool is_v3) const {
// The format of flags2 was changed significantly between v2 and v3, and not
// accounting for this means that sometimes other characters won't appear
// when joining a game. Unfortunately, some bits were deleted on v3 and other
// bits were added, so it doesn't suffice to simply store the most complete
// format of this field - we have to be able to convert between the two.
// Bits on v2: ---CBAzy xwvutsrq ponmlkji hgfedcba
// Bits on v3: ---GFEDC BAzyxwvu srqponkj hgfedcba
// The bits ilmt were removed in v3 and the bits to their left were shifted
// right. The bits DEFG were added in v3 and do not exist on v2.
if (is_v3 == this->flags2_is_v3) {
return this->flags2_value;
} else if (!this->flags2_is_v3) { // Convert v2 -> v3
return (
(this->flags2_value & 0x000000FF) |
((this->flags2_value & 0x00000600) >> 1) |
((this->flags2_value & 0x0007E000) >> 3) |
((this->flags2_value & 0x1FF00000) >> 4));
} else { // Convert v3 -> v2
return (
(this->flags2_value & 0x000000FF) |
((this->flags2_value << 1) & 0x00000600) |
((this->flags2_value << 3) & 0x0007E000) |
((this->flags2_value << 4) & 0x1FF00000));
}
uint32_t Parsed6x70Data::get_game_flags(bool is_v3) const {
return (this->game_flags_is_v3 == is_v3)
? this->game_flags
: MapState::EnemyState::convert_game_flags(this->game_flags, is_v3);
}
static void on_sync_joining_player_disp_and_inventory(
@@ -3338,16 +3315,42 @@ static void on_trigger_set_event(shared_ptr<Client> c, uint8_t command, uint8_t
forward_subcommand(c, command, flag, data, size);
}
static void on_update_telepipe_state(shared_ptr<Client> c, uint8_t command, uint8_t flag, void* data, size_t size) {
static inline uint32_t bswap32_high16(uint32_t v) {
return ((v >> 8) & 0x00FF0000) | ((v << 8) & 0xFF000000) | (v & 0x0000FFFF);
}
static void on_update_telepipe_state(shared_ptr<Client> c, uint8_t command, uint8_t, void* data, size_t size) {
if (c->lobby_client_id > 3) {
throw logic_error("client ID is above 3");
}
if (command_is_private(command)) {
return;
}
auto l = c->require_lobby();
if (!l->is_game()) {
return;
}
c->telepipe_state = check_size_t<G_SetTelepipeState_6x68>(data, size);
auto& cmd = check_size_t<G_SetTelepipeState_6x68>(data, size);
c->telepipe_state = cmd.state;
c->telepipe_lobby_id = l->lobby_id;
forward_subcommand(c, command, flag, data, size);
// See the comments in G_SetTelepipeState_6x68 in CommandsFormats.hh for
// why we have to do this
if (is_big_endian(c->version())) {
c->telepipe_state.room_id = bswap32_high16(phosg::bswap32(c->telepipe_state.room_id));
}
for (auto lc : l->clients) {
if (lc && (lc != c)) {
if (is_big_endian(lc->version())) {
cmd.state.room_id = phosg::bswap32(bswap32_high16(c->telepipe_state.room_id));
} else {
cmd.state.room_id = c->telepipe_state.room_id;
}
send_command_t(lc, 0x60, 0x00, cmd);
}
}
}
static void on_update_enemy_state(shared_ptr<Client> c, uint8_t command, uint8_t, void* data, size_t size) {
@@ -3367,36 +3370,64 @@ static void on_update_enemy_state(shared_ptr<Client> c, uint8_t command, uint8_t
if ((cmd.enemy_index & 0xF000) || (cmd.header.entity_id != (cmd.enemy_index | 0x1000))) {
throw runtime_error("mismatched enemy id/index");
}
bool is_v3 = !is_v1_or_v2(c->version());
auto ene_st = l->map_state->enemy_state_for_index(c->version(), c->floor, cmd.enemy_index);
ene_st->game_flags = is_big_endian(c->version()) ? bswap32(cmd.flags) : cmd.flags.load();
uint32_t src_flags = is_big_endian(c->version()) ? bswap32(cmd.game_flags) : cmd.game_flags.load();
if (l->difficulty == 3) {
src_flags = (src_flags & 0xFFFFFFC0) | (ene_st->get_game_flags(is_v3) & 0x0000003F);
}
ene_st->set_game_flags(src_flags, is_v3);
ene_st->total_damage = cmd.total_damage;
ene_st->set_last_hit_by_client_id(c->lobby_client_id);
l->log.info("E-%03zX updated to damage=%hu game_flags=%08" PRIX32,
ene_st->e_id, ene_st->total_damage, ene_st->game_flags);
l->log.info("E-%03zX updated to damage=%hu game_flags=%08" PRIX32 " (%s)",
ene_st->e_id,
ene_st->total_damage,
ene_st->game_flags,
(ene_st->server_flags & MapState::EnemyState::Flag::GAME_FLAGS_IS_V3) ? "v3" : "v2");
G_UpdateEnemyState_GC_6x0A sw_cmd = {
{cmd.header.subcommand, cmd.header.size, cmd.header.entity_id},
cmd.enemy_index, cmd.total_damage, cmd.flags.load()};
bool sender_is_be = is_big_endian(c->version());
for (auto lc : l->clients) {
if (lc && (lc != c)) {
if (is_big_endian(lc->version()) == sender_is_be) {
cmd.enemy_index = l->map_state->index_for_enemy_state(lc->version(), ene_st);
if (cmd.enemy_index != 0xFFFF) {
cmd.header.entity_id = 0x1000 | cmd.enemy_index;
send_command_t(lc, 0x60, 0x00, cmd);
}
} else {
sw_cmd.enemy_index = l->map_state->index_for_enemy_state(lc->version(), ene_st);
if (sw_cmd.enemy_index != 0xFFFF) {
sw_cmd.header.entity_id = 0x1000 | sw_cmd.enemy_index;
send_command_t(lc, 0x60, 0x00, sw_cmd);
}
cmd.enemy_index = l->map_state->index_for_enemy_state(lc->version(), ene_st);
if (cmd.enemy_index != 0xFFFF) {
cmd.header.entity_id = 0x1000 | cmd.enemy_index;
uint32_t game_flags = ene_st->get_game_flags(!is_v1_or_v2(lc->version()));
cmd.game_flags = is_big_endian(lc->version()) ? phosg::bswap32(game_flags) : game_flags;
send_command_t(lc, 0x60, 0x00, cmd);
}
}
}
}
static void on_set_enemy_low_game_flags_ultimate(
shared_ptr<Client> c, uint8_t command, uint8_t flag, void* data, size_t size) {
auto& cmd = check_size_t<G_SetEnemyLowGameFlagsUltimate_6x9C>(data, size);
if (command_is_private(command) ||
(cmd.header.entity_id < 0x1000) ||
(cmd.header.entity_id >= 0x4000) ||
(cmd.low_game_flags & 0xFFFFFFC0) ||
(c->lobby_client_id > 3)) {
return;
}
auto l = c->require_lobby();
if (!l->is_game() || (l->difficulty != 3)) {
return;
}
bool is_v3 = !is_v1_or_v2(c->version());
auto ene_st = l->map_state->enemy_state_for_index(c->version(), c->floor, cmd.header.entity_id - 0x1000);
uint32_t game_flags = ene_st->get_game_flags(is_v3);
if (!(game_flags & cmd.low_game_flags)) {
ene_st->set_game_flags(game_flags | cmd.low_game_flags, is_v3);
l->log.info("E-%03zX updated to game_flags=%08" PRIX32 " (%s)",
ene_st->e_id,
ene_st->game_flags,
(ene_st->server_flags & MapState::EnemyState::Flag::GAME_FLAGS_IS_V3) ? "v3" : "v2");
}
forward_subcommand_with_entity_id_transcode_t<G_SetEnemyLowGameFlagsUltimate_6x9C>(c, command, flag, data, size);
}
template <typename CmdT>
static void on_update_object_state_t(shared_ptr<Client> c, uint8_t command, uint8_t, void* data, size_t size) {
auto& cmd = check_size_t<CmdT>(data, size);
@@ -3501,7 +3532,7 @@ static void on_battle_scores(shared_ptr<Client> c, uint8_t command, uint8_t, voi
}
}
static void on_dragon_actions(shared_ptr<Client> c, uint8_t command, uint8_t, void* data, size_t size) {
static void on_dragon_actions_6x12(shared_ptr<Client> c, uint8_t command, uint8_t, void* data, size_t size) {
auto& cmd = check_size_t<G_DragonBossActions_DC_PC_XB_BB_6x12>(data, size);
if (command_is_private(command)) {
@@ -5045,6 +5076,7 @@ static void on_write_quest_counter_bb(shared_ptr<Client> c, uint8_t, uint8_t, vo
constexpr uint8_t NONE = 0x00;
const SubcommandDefinition subcommand_definitions[0x100] = {
// {DC NTE, 11/2000, all other versions, handler}
/* 6x00 */ {0x00, 0x00, 0x00, on_invalid},
/* 6x01 */ {0x01, 0x01, 0x01, on_invalid},
/* 6x02 */ {0x02, 0x02, 0x02, forward_subcommand_m},
@@ -5054,18 +5086,18 @@ const SubcommandDefinition subcommand_definitions[0x100] = {
/* 6x06 */ {0x06, 0x06, 0x06, on_send_guild_card},
/* 6x07 */ {0x07, 0x07, 0x07, on_symbol_chat, SDF::ALWAYS_FORWARD_TO_WATCHERS},
/* 6x08 */ {0x08, 0x08, 0x08, on_invalid},
/* 6x09 */ {0x09, 0x09, 0x09, forward_subcommand_with_entity_id_transcode_t<G_Unknown_6x09>},
/* 6x09 */ {0x09, 0x09, 0x09, on_invalid}, // See notes in CommandFormats.hh
/* 6x0A */ {0x0A, 0x0A, 0x0A, on_update_enemy_state},
/* 6x0B */ {0x0B, 0x0B, 0x0B, on_update_object_state_t<G_UpdateObjectState_6x0B>},
/* 6x0C */ {0x0C, 0x0C, 0x0C, on_received_condition},
/* 6x0D */ {NONE, NONE, 0x0D, on_forward_check_game},
/* 6x0E */ {NONE, NONE, 0x0E, on_forward_check_game},
/* 6x0E */ {NONE, NONE, 0x0E, forward_subcommand_with_entity_id_transcode_t<G_ClearNegativeStatusEffects_6x0E>},
/* 6x0F */ {NONE, NONE, 0x0F, on_invalid},
/* 6x10 */ {0x0E, 0x0E, 0x10, forward_subcommand_with_entity_id_transcode_t<G_Unknown_6x10_6x11_6x14>},
/* 6x11 */ {0x0F, 0x0F, 0x11, forward_subcommand_with_entity_id_transcode_t<G_Unknown_6x10_6x11_6x14>},
/* 6x12 */ {0x10, 0x10, 0x12, on_dragon_actions},
/* 6x10 */ {0x0E, 0x0E, 0x10, forward_subcommand_with_entity_id_transcode_t<G_DragonBossActions_6x10_6x11>},
/* 6x11 */ {0x0F, 0x0F, 0x11, forward_subcommand_with_entity_id_transcode_t<G_DragonBossActions_6x10_6x11>},
/* 6x12 */ {0x10, 0x10, 0x12, on_dragon_actions_6x12},
/* 6x13 */ {0x11, 0x11, 0x13, forward_subcommand_with_entity_id_transcode_t<G_DeRolLeBossActions_6x13>},
/* 6x14 */ {0x12, 0x12, 0x14, forward_subcommand_with_entity_id_transcode_t<G_Unknown_6x10_6x11_6x14>},
/* 6x14 */ {0x12, 0x12, 0x14, forward_subcommand_with_entity_id_transcode_t<G_DeRolLeBossActions_6x14>},
/* 6x15 */ {0x13, 0x13, 0x15, forward_subcommand_with_entity_id_transcode_t<G_VolOptBossActions_6x15>},
/* 6x16 */ {0x14, 0x14, 0x16, forward_subcommand_with_entity_id_transcode_t<G_VolOptBossActions_6x16>},
/* 6x17 */ {0x15, 0x15, 0x17, forward_subcommand_with_entity_id_transcode_t<G_VolOpt2BossActions_6x17>},
@@ -5179,7 +5211,7 @@ const SubcommandDefinition subcommand_definitions[0x100] = {
/* 6x83 */ {NONE, NONE, 0x83, on_forward_check_game},
/* 6x84 */ {NONE, NONE, 0x84, on_forward_check_game},
/* 6x85 */ {NONE, NONE, 0x85, on_forward_check_game},
/* 6x86 */ {NONE, NONE, 0x86, forward_subcommand_with_entity_id_transcode_t<G_HitDestructibleObject_6x86>},
/* 6x86 */ {NONE, NONE, 0x86, on_update_object_state_t<G_HitDestructibleObject_6x86>},
/* 6x87 */ {NONE, NONE, 0x87, on_forward_check_game},
/* 6x88 */ {NONE, NONE, 0x88, on_forward_check_game},
/* 6x89 */ {NONE, NONE, 0x89, forward_subcommand_with_entity_id_transcode_t<G_SetKillerEntityID_6x89, false, offsetof(G_SetKillerEntityID_6x89, killer_entity_id)>},
@@ -5201,7 +5233,7 @@ const SubcommandDefinition subcommand_definitions[0x100] = {
/* 6x99 */ {NONE, NONE, 0x99, on_forward_check_game},
/* 6x9A */ {NONE, NONE, 0x9A, on_forward_check_game_client},
/* 6x9B */ {NONE, NONE, 0x9B, on_forward_check_game},
/* 6x9C */ {NONE, NONE, 0x9C, forward_subcommand_with_entity_id_transcode_t<G_Unknown_6x9C>},
/* 6x9C */ {NONE, NONE, 0x9C, on_set_enemy_low_game_flags_ultimate},
/* 6x9D */ {NONE, NONE, 0x9D, on_forward_check_game},
/* 6x9E */ {NONE, NONE, 0x9E, forward_subcommand_m},
/* 6x9F */ {NONE, NONE, 0x9F, forward_subcommand_with_entity_id_transcode_t<G_GalGryphonBossActions_6x9F>},
+10 -11
View File
@@ -50,23 +50,22 @@ public:
uint16_t bonus_hp_from_materials = 0;
uint16_t bonus_tp_from_materials = 0;
parray<uint8_t, 0x10> unknown_a5_112000;
G_6x70_StatusEffectState permanent_status_effect;
G_6x70_StatusEffectState temporary_status_effect;
G_6x70_StatusEffectState attack_status_effect;
G_6x70_StatusEffectState defense_status_effect;
G_6x70_StatusEffectState unused_status_effect;
StatusEffectState permanent_status_effect;
StatusEffectState temporary_status_effect;
StatusEffectState attack_status_effect;
StatusEffectState defense_status_effect;
StatusEffectState unused_status_effect;
uint32_t language = 0;
uint32_t player_tag = 0;
uint32_t guild_card_number = 0;
uint32_t unknown_a6 = 0;
uint32_t battle_team_number = 0;
G_6x70_Sub_Telepipe telepipe;
uint32_t unknown_a8 = 0;
parray<uint8_t, 0x10> unknown_a9_nte_112000;
G_6x70_Sub_UnknownA1 unknown_a9_final;
uint32_t death_flags = 0;
NPCTalkState npc_talk_state;
uint32_t area = 0;
uint32_t flags2_value = 0;
bool flags2_is_v3 = false;
uint32_t game_flags = 0;
bool game_flags_is_v3 = false;
parray<uint8_t, 0x14> technique_levels_v1 = 0xFF;
PlayerVisualConfig visual;
std::string name;
@@ -127,7 +126,7 @@ protected:
Version from_version,
bool from_client_customization);
G_6x70_Base_V1 base_v1(bool is_v3) const;
uint32_t get_flags2(bool is_v3) const;
uint32_t get_game_flags(bool is_v3) const;
};
bool validate_6xBB(G_SyncCardTradeServerState_Ep3_6xBB& cmd);
+6 -9
View File
@@ -649,7 +649,7 @@ void send_guild_card_chunk_bb(shared_ptr<Client> c, size_t chunk_index) {
S_GuildCardFileChunk_02DC cmd;
size_t data_size = min<size_t>(sizeof(PSOBBGuildCardFile) - chunk_offset, sizeof(cmd.data));
cmd.unknown = 0;
cmd.unknown_a1 = 0;
cmd.chunk_index = chunk_index;
cmd.data.assign_range(
reinterpret_cast<const uint8_t*>(c->guild_card_file().get()) + chunk_offset,
@@ -2672,7 +2672,7 @@ void send_game_item_state(shared_ptr<Client> c) {
fi.source_type = 0;
fi.entity_index = 0xFFFF;
fi.pos = item->pos;
fi.unknown_a2 = 0;
fi.room_id = 0;
fi.drop_number = (floor == 0) ? 0xFFFF : (decompressed_header.next_drop_number_per_floor.at(floor - 1)++);
fi.item = item->data;
fi.item.encode_for_version(c->version(), s->item_parameter_table_for_encode(c->version()));
@@ -2736,9 +2736,10 @@ void send_game_enemy_state(shared_ptr<Client> c) {
auto s = c->require_server_state();
vector<SyncEnemyStateEntry> entries;
bool is_v3 = !is_v1_or_v2(c->version());
for (auto ene_st : l->map_state->iter_enemy_states(c->version())) {
auto& entry = entries.emplace_back();
entry.flags = ene_st->game_flags;
entry.flags = ene_st->get_game_flags(is_v3);
entry.item_drop_id = (ene_st->server_flags & MapState::EnemyState::Flag::ITEM_DROPPED)
? 0xFFFF
: (0xCA0 + l->map_state->index_for_enemy_state(c->version(), ene_st));
@@ -2921,11 +2922,7 @@ void send_game_player_state(shared_ptr<Client> to_c, shared_ptr<Client> from_c,
auto to_l = to_c->lobby.lock();
if (to_l && (from_c->telepipe_lobby_id == to_l->lobby_id)) {
to_send.telepipe.owner_client_id = from_c->telepipe_state.client_id2;
to_send.telepipe.floor = from_c->telepipe_state.floor;
to_send.telepipe.unknown_a1 = from_c->telepipe_state.unknown_b3;
to_send.telepipe.pos = from_c->telepipe_state.pos;
to_send.telepipe.unknown_a3 = from_c->telepipe_state.unknown_a3;
to_send.telepipe.state = from_c->telepipe_state;
}
if (apply_overrides) {
@@ -3040,7 +3037,7 @@ void send_create_inventory_item_to_client(shared_ptr<Client> c, uint8_t client_i
cmd.header.client_id = client_id;
cmd.item_data = item;
cmd.unused1 = 0;
cmd.unknown_a2 = 0;
cmd.equip_item = 0;
cmd.unused2.clear(0);
send_command_t(c, 0x60, 0x00, cmd);
}