refine many subcommand formats
This commit is contained in:
+1
-1
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user