From 6a23e5da0ab0f722a9b31f005ffd24e64ecdfc96 Mon Sep 17 00:00:00 2001 From: Martin Michelsen Date: Wed, 18 Mar 2026 22:32:06 -0700 Subject: [PATCH] make some sense out of game_flags and player_flags --- README.md | 1 + notes/ar-codes.txt | 28 +++ src/ChatCommands.cc | 20 +- src/CommandFormats.hh | 230 +++++++++++------- src/Map.cc | 11 +- src/PlayerSubordinates.hh | 6 + src/ReceiveSubcommands.cc | 174 +++++++------ src/ReceiveSubcommands.hh | 10 +- src/StaticGameData.cc | 14 +- .../Debug/PlayerFlags.3OE1.patch.s | 77 ++++++ 10 files changed, 393 insertions(+), 178 deletions(-) create mode 100644 system/client-functions/Debug/PlayerFlags.3OE1.patch.s diff --git a/README.md b/README.md index cad9255d..2808ec2c 100644 --- a/README.md +++ b/README.md @@ -605,6 +605,7 @@ Some commands only work for clients not in proxy sessions. The chat commands are * `$swsetall`: Set all switch flags on your current floor. This unlocks all doors, disables all laser fences, triggers all light/poison switches, etc. * `$gc` (non-proxy only): Send your own Guild Card to yourself. * `$sc `: Send a command to yourself. + * `$scp `: Send a protected command to yourself. * `$ss `: Send a command to the remote server (if in a proxy session) or to the game server. * `$sb `: Send a command to yourself, and to the remote server or game server. * `$auction` (Episode 3 only): Bring up the CARD Auction menu, even if there are fewer than 4 players are in the game or you don't have a VIP card. diff --git a/notes/ar-codes.txt b/notes/ar-codes.txt index 962c63f4..aa365fd3 100644 --- a/notes/ar-codes.txt +++ b/notes/ar-codes.txt @@ -1074,3 +1074,31 @@ Enable quest board menu in free play (for use with the above code) 3OJ3 => 04262E44 38600001 3OJ4 => 04263EB8 38600001 3OP0 => 0426374C 38600001 + +All classes' footsteps sound like RAcast's +3OE0 => 041B3ED0 38600002 + 041B3ED4 4E800020 +3OE1 => 041B3ED0 38600002 + 041B3ED4 4E800020 +3OE2 => 041B4068 38600002 + 041B406C 4E800020 +3OJ2 => 041B3AE4 38600002 + 041B3AE8 4E800020 +3OJ3 => 041B3F38 38600002 + 041B3F3C 4E800020 +3OJ4 => 041B552C 38600002 + 041B5530 4E800020 +3OJ5 => 041B4004 38600002 + 041B4008 4E800020 +3OJT => 0420A120 38600002 + 0420A124 4E800020 +3OP0 => 041B4524 38600002 + 041B4528 4E800020 +3SE0 => 040D0378 38600002 + 040D037C 4E800020 +3SJ0 => 040D0394 38600002 + 040D0398 4E800020 +3SJT => 040D431C 38600002 + 040D4320 4E800020 +3SP0 => 040D07BC 38600002 + 040D07C0 4E800020 diff --git a/src/ChatCommands.cc b/src/ChatCommands.cc index a7452a3f..3469eafe 100644 --- a/src/ChatCommands.cc +++ b/src/ChatCommands.cc @@ -2349,14 +2349,18 @@ ChatCommandDefinition cc_saverec( co_return; }); -static asio::awaitable command_send_command(const Args& a, bool to_client, bool to_server) { +static asio::awaitable command_send_command(const Args& a, bool to_client, bool to_server, bool send_protected) { if (!a.c->proxy_session) { a.check_debug_enabled(); } string data = phosg::parse_data_string(a.text); data.resize((data.size() + 3) & (~3)); if (to_client) { - a.c->channel->send(data); + if (send_protected) { + co_await send_protected_command(a.c, data.data(), data.size(), false); + } else { + a.c->channel->send(data); + } } if (to_server) { if (a.c->proxy_session) { @@ -2371,13 +2375,19 @@ static asio::awaitable command_send_command(const Args& a, bool to_client, ChatCommandDefinition cc_sb( {"$sb"}, +[](const Args& a) -> asio::awaitable { - return command_send_command(a, true, true); + return command_send_command(a, true, true, false); }); ChatCommandDefinition cc_sc( {"$sc"}, +[](const Args& a) -> asio::awaitable { - return command_send_command(a, true, false); + return command_send_command(a, true, false, false); + }); + +ChatCommandDefinition cc_scp( + {"$scp"}, + +[](const Args& a) -> asio::awaitable { + return command_send_command(a, true, false, true); }); ChatCommandDefinition cc_secid( @@ -2571,7 +2581,7 @@ ChatCommandDefinition cc_spec( ChatCommandDefinition cc_ss( {"$ss"}, +[](const Args& a) -> asio::awaitable { - return command_send_command(a, false, true); + return command_send_command(a, false, true, false); }); ChatCommandDefinition cc_stat( diff --git a/src/CommandFormats.hh b/src/CommandFormats.hh index ea98c7a0..b3c13967 100644 --- a/src/CommandFormats.hh +++ b/src/CommandFormats.hh @@ -3723,6 +3723,49 @@ struct G_UpdateEnemyStateT_6x0A { G_EntityIDHeader header; le_uint16_t enemy_index = 0; // [0, 0xB50) le_uint16_t total_damage = 0; + // The bits in game_flags mean (all addresses in TODOs are for 3OE1): + // 00000001 = is poisoned + // 00000002 = is paralyzed + // 00000004 = is shocked + // 00000008 = is slow + // 00000010 = is confused + // 00000020 = is frozen + // 00000040 = cures and prevents all negative status effects + // 00000080 = appears to be unused (TODO: look for any usage of this flag) + // 00000100 = missed by attack (often set immediately before showing red "MISS" text) + // 00000200 = hit by attack (causes flinch for most enemies) + // 00000400 = last hit did damage greater than 25% of enemy's max HP (some enemies don't clear this) + // 00000800 = is dead (when set for most enemies, plays the death animation and then destroys the enemy) + // 00001000 = unknown (TODO: see TObjEnemyV8048ee80_v1C, TObjEnemyV8048ee80_v3B, TObjEneDolmOlm_v3B) + // 00002000 = unknown (TODO: see TObjGrass_v1E, 8011EA08, TObjEneIllGill_v1E, TObjEneIllGill_init) + // 00004000 = unknown (TODO: has status effect in slot 5; see TObjectV8047c128_v24_update_paralysis_effect; De Rol + // Le uses this; Vol Opt uses it too at TBoss3Volopt_update, TBoss3VoloptCore_update, + // TBoss3VoloptP01_update, TBoss3VolOptP02_update, TObjectV8047c128_v39, TObjectV8047c128_v38; related + // to paralysis somehow? see TObjectV8047c128_v24_update_paralysis_effect) + // 00008000 = immune to freeze (TODO: see TBoss3VolOptP02_init_inner; maybe other things use it too) + // 00010000 = unknown (TODO: see 801BA1F8) + // 00020000 = can't attack, cast techs, or use items (e.g. Vol Opt cage and Ruins falling traps set this) + // 00040000 = untargetable (e.g. TObjEneBeast sets this at construction time; it's cleared when it roars) + // 00080000 = appears to be unused (TODO: look for any usage of this flag) + // 00100000 = for players, is near enemy; for some enemies, is activated (not set if in idle animations, e.g. for + // TObjEneBeast and related classes) (unverified on v2) + // 00200000 = is attacking? (TODO: also set when TObjEneMoja is jumping though; also see TObjEnemyV8048ee80_v3C, + // TObjEneMerillLia_v3C, TObjEneSaver_v3A) + // 00400000 = unknown (TODO: see TOSensor_vF, 801CC358, 801CD224) + // 00800000 = affected by gravity? can be aerial? (wolves don't have this, perhaps their jumps are hardcoded; see + // TObjEnemyV8048ee80_v5B) (unverified on v2) + // 01000000 = immune to shock and freeze and paralysis (for Canadine/Mothmant/etc, only set when they're higher + // than player level; TODO: maybe other things too; also used by TObjPlayer, see 801B84B4; also has a + // meaning for weapons, see TItemWeapon_v10) (unverified on v2) + // 02000000 = invisible (also suppresses particles, some sounds, and hit/miss text) + // 04000000 = temporarily invincible (e.g. Dragon while it roars to advance to phase 2) (unverified on v2) + // 08000000 = unknown (TODO; see 80113384, TItemMag_v1B, TItemMag_v1A, 801182B0, TObjPlayer_render; probably + // graphical effects only) + // 10000000 = entity is player + // 20000000 = entity is enemy + // 40000000 = entity is object (some entities have both this and 20000000 set; this appears to make TWindowLockOn + // not show anything but the entity is still attackable, see TWindowLockOn_should_show_for_entity) + // 80000000 = entity is item typename std::conditional_t game_flags = 0; } __attribute__((packed)); using G_UpdateEnemyState_GC_6x0A = G_UpdateEnemyStateT_6x0A; @@ -4125,7 +4168,7 @@ struct G_Unknown_6x36 { struct G_PhotonBlast_6x37 { G_ClientIDHeader header; - le_uint16_t unknown_a1 = 0; + le_uint16_t amount = 0; // Amount of PB energy to expend (ignored by client upon receipt) le_uint16_t unused = 0; } __packed_ws__(G_PhotonBlast_6x37, 8); @@ -4212,7 +4255,9 @@ struct G_SetPosition_6x3F { struct G_WalkToPosition_6x40 { G_ClientIDHeader header; VectorXZF pos; - le_uint32_t action = 0; + // In the flags field, bit 00000008 is set if game_flag 00100000 is set (same as is_near_enemy in 6x3E and 6x3F). The + // meanings of the other bits are unknown. + le_uint32_t flags = 0; } __packed_ws__(G_WalkToPosition_6x40, 0x10); // 6x41: Move to position (v1) @@ -4290,8 +4335,8 @@ struct G_ShieldAttack_6x4A { G_ClientIDHeader header; } __packed_ws__(G_ShieldAttack_6x4A, 4); -// 6x4B: Hit by enemy (protected on GC NTE/V3/V4) -// 6x4C: Hit by enemy (protected on GC NTE/V3/V4) +// 6x4B: Minor hit by enemy (<= 25% of max HP; protected on GC NTE/V3/V4) +// 6x4C: Major hit by enemy (> 25% of max HP; protected on GC NTE/V3/V4) struct G_HitByEnemy_6x4B_6x4C { G_ClientIDHeader header; @@ -4393,7 +4438,7 @@ struct G_Unknown_6x57 { struct G_LobbyAnimation_6x58 { G_ClientIDHeader header; le_uint16_t animation_number = 0; - le_uint16_t unused = 0; + le_uint16_t flags = 0; // Only lowest bit appears to be used } __packed_ws__(G_LobbyAnimation_6x58, 8); // 6x59: Pick up item @@ -4688,7 +4733,7 @@ struct G_6x70_Sub_Telepipe { struct G_6x70_Base_DCNTE { /* 0000 */ le_uint16_t client_id = 0; /* 0002 */ le_uint16_t room_id = 0; - /* 0004 */ le_uint32_t flags1 = 0; + /* 0004 */ le_uint32_t game_flags = 0; /* 0008 */ VectorXYZF pos; /* 0014 */ VectorXYZI angle; /* 0020 */ le_uint16_t phase = 0; @@ -4696,41 +4741,39 @@ struct G_6x70_Base_DCNTE { } __packed_ws__(G_6x70_Base_DCNTE, 0x24); struct G_SyncPlayerDispAndInventory_DCNTE_6x70 { - // Offsets in this struct are relative to the overall command header - /* 0004 */ G_ExtendedHeaderT header = {{0x60, 0x00, 0x0000}, sizeof(G_SyncPlayerDispAndInventory_DCNTE_6x70)}; - /* 000C */ G_6x70_Base_DCNTE base; + /* 0000 */ G_ExtendedHeaderT header = {{0x60, 0x00, 0x0000}, sizeof(G_SyncPlayerDispAndInventory_DCNTE_6x70)}; + /* 0008 */ G_6x70_Base_DCNTE base; // The following two fields appear to contain uninitialized data - /* 0030 */ le_uint32_t unknown_a5 = 0; - /* 0034 */ le_uint32_t unknown_a6 = 0; - /* 0038 */ G_6x70_Sub_Telepipe telepipe; - /* 0054 */ le_uint32_t death_flags = 0; - /* 0058 */ PlayerHoldState_DCProtos hold_state; - /* 0068 */ le_uint32_t area = 0; - /* 006C */ le_uint32_t game_flags = 0; - /* 0070 */ PlayerVisualConfig visual; - /* 00C0 */ PlayerStats stats; - /* 00E4 */ le_uint32_t num_items = 0; - /* 00E8 */ parray items; - /* 0430 */ + /* 002C */ le_uint32_t unknown_a5 = 0; + /* 0030 */ le_uint32_t unknown_a6 = 0; + /* 0034 */ G_6x70_Sub_Telepipe telepipe; + /* 0050 */ le_uint32_t death_flags = 0; + /* 0054 */ PlayerHoldState_DCProtos hold_state; + /* 0064 */ le_uint32_t area = 0; + /* 0068 */ le_uint32_t player_flags = 0; + /* 006C */ PlayerVisualConfig visual; + /* 00BC */ PlayerStats stats; + /* 00E0 */ le_uint32_t num_items = 0; + /* 00E4 */ parray items; + /* 042C */ } __packed_ws__(G_SyncPlayerDispAndInventory_DCNTE_6x70, 0x42C); struct G_SyncPlayerDispAndInventory_DC112000_6x70 { - // Offsets in this struct are relative to the overall command header - /* 0004 */ G_ExtendedHeaderT header = {{0x67, 0x00, 0x0000}, sizeof(G_SyncPlayerDispAndInventory_DC112000_6x70)}; - /* 000C */ G_6x70_Base_DCNTE base; - /* 0030 */ le_uint16_t bonus_hp_from_materials = 0; - /* 0032 */ le_uint16_t bonus_tp_from_materials = 0; - /* 0034 */ parray unknown_a5; - /* 0044 */ G_6x70_Sub_Telepipe telepipe; - /* 0060 */ le_uint32_t death_flags = 0; - /* 0064 */ PlayerHoldState_DCProtos hold_state; - /* 0074 */ le_uint32_t area = 0; - /* 0078 */ le_uint32_t game_flags = 0; - /* 007C */ PlayerVisualConfig visual; - /* 00CC */ PlayerStats stats; - /* 00F0 */ le_uint32_t num_items = 0; - /* 00F4 */ parray items; - /* 043C */ + /* 0000 */ G_ExtendedHeaderT header = {{0x67, 0x00, 0x0000}, sizeof(G_SyncPlayerDispAndInventory_DC112000_6x70)}; + /* 0008 */ G_6x70_Base_DCNTE base; + /* 002C */ le_uint16_t bonus_hp_from_materials = 0; + /* 002E */ le_uint16_t bonus_tp_from_materials = 0; + /* 0030 */ parray unknown_a5; + /* 0040 */ G_6x70_Sub_Telepipe telepipe; + /* 005C */ le_uint32_t death_flags = 0; + /* 0060 */ PlayerHoldState_DCProtos hold_state; + /* 0070 */ le_uint32_t area = 0; + /* 0074 */ le_uint32_t player_flags = 0; + /* 0078 */ PlayerVisualConfig visual; + /* 00C8 */ PlayerStats stats; + /* 00EC */ le_uint32_t num_items = 0; + /* 00F0 */ parray items; + /* 0438 */ } __packed_ws__(G_SyncPlayerDispAndInventory_DC112000_6x70, 0x438); struct G_6x70_Base_V1 { @@ -4741,71 +4784,71 @@ struct G_6x70_Base_V1 { /* 0034 */ StatusEffectState temporary_status_effect; /* 0040 */ StatusEffectState attack_status_effect; /* 004C */ StatusEffectState defense_status_effect; - /* 0058 */ StatusEffectState unused_status_effect; + /* 0058 */ StatusEffectState unknown_a1_status_effect; /* 0064 */ le_uint32_t language32 = 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 death_flags = 0; // Only a few bits are used. 4 = player is dead + // Only a few bits appear to be used in death_flags. Known values: + // 00000001 = should drop weapon/item on death + // 00000002 = has automatic revival item (Scape Doll or Ragol Ring) + // 00000004 = unknown (TODO; causes client to send 6x31 instead of 6xA1 when revived) + /* 0094 */ le_uint32_t death_flags = 0; /* 0098 */ PlayerHoldState hold_state; /* 00AC */ le_uint32_t area = 0; - /* 00B0 */ le_uint32_t game_flags = 0; + /* 00B0 */ le_uint32_t player_flags = 0; /* 00B4 */ parray technique_levels_v1 = 0xFF; // Last byte is uninitialized /* 00C8 */ PlayerVisualConfig visual; /* 0118 */ } __packed_ws__(G_6x70_Base_V1, 0x118); struct G_SyncPlayerDispAndInventory_DC_PC_6x70 { - // Offsets in this struct are relative to the overall command header - /* 0004 */ G_ExtendedHeaderT header = {{0x70, 0x00, 0x0000}, sizeof(G_SyncPlayerDispAndInventory_DC_PC_6x70)}; - /* 000C */ G_6x70_Base_V1 base; - /* 0124 */ PlayerStats stats; - /* 0148 */ le_uint32_t num_items = 0; - /* 014C */ parray items; - /* 0494 */ + /* 0000 */ G_ExtendedHeaderT header = {{0x70, 0x00, 0x0000}, sizeof(G_SyncPlayerDispAndInventory_DC_PC_6x70)}; + /* 0008 */ G_6x70_Base_V1 base; + /* 0120 */ PlayerStats stats; + /* 0144 */ le_uint32_t num_items = 0; + /* 0148 */ parray items; + /* 0490 */ } __packed_ws__(G_SyncPlayerDispAndInventory_DC_PC_6x70, 0x490); -// GC NTE also uses this format. +// GC NTE also uses this format struct G_SyncPlayerDispAndInventory_GC_6x70 { - // Offsets in this struct are relative to the overall command header - /* 0004 */ G_ExtendedHeaderT header = {{0x70, 0x00, 0x0000}, sizeof(G_SyncPlayerDispAndInventory_GC_6x70)}; - /* 000C */ G_6x70_Base_V1 base; - /* 0124 */ PlayerStats stats; - /* 0148 */ le_uint32_t num_items = 0; - /* 014C */ parray items; - /* 0494 */ le_uint32_t floor = 0; - /* 0498 */ + /* 0000 */ G_ExtendedHeaderT header = {{0x70, 0x00, 0x0000}, sizeof(G_SyncPlayerDispAndInventory_GC_6x70)}; + /* 0008 */ G_6x70_Base_V1 base; + /* 0120 */ PlayerStats stats; + /* 0144 */ le_uint32_t num_items = 0; + /* 0148 */ parray items; + /* 0490 */ le_uint32_t floor = 0; + /* 0494 */ } __packed_ws__(G_SyncPlayerDispAndInventory_GC_6x70, 0x494); struct G_SyncPlayerDispAndInventory_XB_6x70 { - // Offsets in this struct are relative to the overall command header - /* 0004 */ G_ExtendedHeaderT header = {{0x70, 0x00, 0x0000}, sizeof(G_SyncPlayerDispAndInventory_XB_6x70)}; - /* 000C */ G_6x70_Base_V1 base; - /* 0124 */ PlayerStats stats; - /* 0148 */ le_uint32_t num_items = 0; - /* 014C */ parray items; - /* 0494 */ le_uint32_t floor = 0; - /* 0498 */ le_uint32_t xb_user_id_high = 0; - /* 049C */ le_uint32_t xb_user_id_low = 0; - /* 04A0 */ le_uint32_t unknown_a16 = 0; - /* 04A4 */ + /* 0000 */ G_ExtendedHeaderT header = {{0x70, 0x00, 0x0000}, sizeof(G_SyncPlayerDispAndInventory_XB_6x70)}; + /* 0008 */ G_6x70_Base_V1 base; + /* 0120 */ PlayerStats stats; + /* 0144 */ le_uint32_t num_items = 0; + /* 0148 */ parray items; + /* 0490 */ le_uint32_t floor = 0; + /* 0494 */ le_uint32_t xb_user_id_high = 0; + /* 0498 */ le_uint32_t xb_user_id_low = 0; + /* 049C */ le_uint32_t unknown_a16 = 0; + /* 04A0 */ } __packed_ws__(G_SyncPlayerDispAndInventory_XB_6x70, 0x4A0); struct G_SyncPlayerDispAndInventory_BB_6x70 { - // Offsets in this struct are relative to the overall command header - /* 0008 */ G_ExtendedHeaderT header = {{0x70, 0x00, 0x0000}, sizeof(G_SyncPlayerDispAndInventory_BB_6x70)}; - /* 0010 */ G_6x70_Base_V1 base; - /* 0128 */ pstring name; - /* 0148 */ PlayerStats stats; - /* 016C */ le_uint32_t num_items = 0; - /* 0170 */ parray items; - /* 04B8 */ le_uint32_t floor = 0; - /* 04BC */ le_uint32_t xb_user_id_high = 0; - /* 04C0 */ le_uint32_t xb_user_id_low = 0; - /* 04C4 */ le_uint32_t unknown_a16 = 0; - /* 04C8 */ + /* 0000 */ G_ExtendedHeaderT header = {{0x70, 0x00, 0x0000}, sizeof(G_SyncPlayerDispAndInventory_BB_6x70)}; + /* 0008 */ G_6x70_Base_V1 base; + /* 0120 */ pstring name; + /* 0140 */ PlayerStats stats; + /* 0164 */ le_uint32_t num_items = 0; + /* 0168 */ parray items; + /* 04B0 */ le_uint32_t floor = 0; + /* 04B4 */ le_uint32_t xb_user_id_high = 0; + /* 04B8 */ le_uint32_t xb_user_id_low = 0; + /* 04BC */ le_uint32_t unknown_a16 = 0; + /* 04C0 */ } __packed_ws__(G_SyncPlayerDispAndInventory_BB_6x70, 0x4C0); // 6x71: Unblock game join (used while loading into game) @@ -5007,6 +5050,8 @@ struct G_EnableDropWeaponOnDeath_6x82 { struct G_PlaceTrap_6x83 { G_ClientIDHeader header; le_uint16_t trap_type = 0; + // trap_index is actually the number of traps remaining for this client after setting this one (so it counts backward + // from their total trap count) le_uint16_t trap_index = 0; } __packed_ws__(G_PlaceTrap_6x83, 8); @@ -5205,13 +5250,18 @@ struct G_LevelUpAllTechniques_6x9B { parray unused; } __packed_ws__(G_LevelUpAllTechniques_6x9B, 8); -// 6x9C: Set enemy low game flags (not valid on Episode 3) -// This command only has an effect in Ultimate mode; it sets the low 6 bits of game_flags (those that match 0x3F). +// 6x9C: Set enemy status effect flags (not valid on Episode 3) +// This command only has an effect in Ultimate mode; it sets the low 6 bits of game_flags (those that match 0x3F). This +// essentially separates status effects from the 6x0A command. It's not clear why Sega did this only in Ultimate mode. struct G_SetEnemyLowGameFlagsUltimate_6x9C { G_EntityIDHeader header; - // 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; + // This field is expected to have one of these values (it should not actually be treated as a bit field): + // 0x00000002 = add paralysis status + // 0x00000004 = add shock status + // 0x00000010 = add confuse status + // 0x00000020 = add freeze status + le_uint32_t status_effect_flags = 0; } __packed_ws__(G_SetEnemyLowGameFlagsUltimate_6x9C, 8); // 6x9D: Set dead flag (Challenge mode; not valid on Episode 3) @@ -5409,18 +5459,12 @@ struct G_SetAnimationState_6xAE { } __packed_ws__(G_SetAnimationState_6xAE, 0x10); // 6xAF: Turn lobby chair (not valid on pre-V3 or GC Trial Edition) (protected on V3/V4) - -struct G_TurnLobbyChair_6xAF { - G_ClientIDHeader header; - le_uint32_t angle = 0; // In range [0x0000, 0xFFFF] -} __packed_ws__(G_TurnLobbyChair_6xAF, 8); - // 6xB0: Move lobby chair (not valid on pre-V3 or GC Trial Edition) (protected on V3/V4) -struct G_MoveLobbyChair_6xB0 { +struct G_TurnOrMoveLobbyChair_6xAF_6xB0 { G_ClientIDHeader header; - le_uint32_t unknown_a1 = 0; -} __packed_ws__(G_MoveLobbyChair_6xB0, 8); + le_uint32_t angle = 0; // In range [0x0000, 0xFFFF] +} __packed_ws__(G_TurnOrMoveLobbyChair_6xAF_6xB0, 8); // 6xB1: Unknown (not valid on pre-V3 or GC Trial Edition) // This subcommand is completely ignored. @@ -5686,7 +5730,7 @@ struct G_BankAction_BB_6xBD { G_UnusedHeader header; le_uint32_t item_id = 0; le_uint32_t meseta_amount = 0; - uint8_t action = 0; // 0 = deposit, 1 = take, 3 = done (close bank window) + uint8_t action = 0; // 0 = deposit, 1 = take, 2 = unknown (compact contents?), 3 = commit (close bank window) uint8_t item_amount = 0; le_uint16_t item_index = 0; // 0xFFFF = meseta } __packed_ws__(G_BankAction_BB_6xBD, 0x10); diff --git a/src/Map.cc b/src/Map.cc index e20e9523..9fde4c2c 100644 --- a/src/Map.cc +++ b/src/Map.cc @@ -1372,10 +1372,10 @@ static const vector dat_object_definitions({ // Item box. Params: // param1 = if positive, box is specialized to drop a specific item or type of item; if zero or negative, box - // drops any common item or none at all (and param3-6 are all ignored) - // param3 = if zero, then only data1[0-1] are used and the rest of the ItemData is cleared, then bonuses, grinds, - // etc. are applied to the item; if nonzero, the item is not randomized at all and drops exactly as specified - // in param4-6 + // drops any item (including box rares for the current floor), or nothing at all, and param3-6 are all ignored + // param3 = if zero, then only data1[0-1] (the high 2 bytes of param4) are used and the rest of the ItemData is + // cleared, then bonuses, grinds, etc. are applied to the item; if nonzero, the item is not randomized at all + // and drops exactly as specified in param4-6 // param4-6 = item definition (see below) // Not all fields in ItemData can be specified in the item definition here. The field order here does not match the // field order in ItemData! The item definition is encoded here as follows: @@ -2822,7 +2822,8 @@ static const vector dat_enemy_definitions({ // param2 = if less than 1, this is a Savage Wolf; otherwise it's a Barbarous Wolf {0x0043, F_V0_V4, 0x0000000000600006, "TObjEneBm5Wolf"}, - // Booma, Gobooma, or Gigobooma. Params: + // Booma, Gobooma, or Gigobooma. The activation radius is fixed and cannot be changed: 50 for Hunters, 100 for + // Rangers and Forces; the deactivation radius is 100 for Hunters and 150 for Rangers and Forces. Params: // param1 = TODO (fraction of max HP; see TObjEnemyV8048ee80_v5A) // param2 = idle walk radius (when there's no target, it will walk around its spawn location within this radius; // if this is zero, it stands still instead) diff --git a/src/PlayerSubordinates.hh b/src/PlayerSubordinates.hh index 1776a9f5..f838e6d6 100644 --- a/src/PlayerSubordinates.hh +++ b/src/PlayerSubordinates.hh @@ -64,6 +64,12 @@ struct PlayerVisualConfigT { // F = force, R = ranger, H = hunter // A = android, N = newman, M = human // f = female, m = male + // Enemies also have a class_flags field, though it isn't part of PlayerVisualConfig. The bits for enemies are: + // -------- -------- -------- ----DMAN + // D = Dark attribute + // M = Machine attribute + // A = Altered Beast attribute + // N = Native attribute /* 34 */ U32T class_flags = 0; /* 38 */ U16T costume = 0; /* 3A */ U16T skin = 0; diff --git a/src/ReceiveSubcommands.cc b/src/ReceiveSubcommands.cc index ffce5eae..136f1bba 100644 --- a/src/ReceiveSubcommands.cc +++ b/src/ReceiveSubcommands.cc @@ -397,7 +397,7 @@ asio::awaitable forward_subcommand_with_entity_id_transcode_t(shared_ptr -asio::awaitable forward_subcommand_with_entity_targets_transcode_and_track_hits_t( +asio::awaitable forward_subcommand_with_entity_targets_transcode_t( shared_ptr c, SubcommandMessage& msg) { // I'm lazy and this should never happen for item commands (since all players need to stay in sync) if (command_is_private(msg.command)) { @@ -430,16 +430,6 @@ asio::awaitable forward_subcommand_with_entity_targets_transcode_and_track if ((res.entity_id >= 0x1000) && (res.entity_id < 0x4000)) { auto ene_st = l->map_state->enemy_state_for_index(c->version(), res.entity_id - 0x1000); res.ene_st = ene_st; - - // Track hits for all resolved enemies - c->log.info_f("Claiming last hit on E-{:03X}", ene_st->e_id); - ene_st->set_last_hit_by_client_id(c->lobby_client_id); - if (ene_st->alias_target_ene_st) { - c->log.info_f("Claiming last hit on E-{:03X} (alias of E-{:03X})", - ene_st->alias_target_ene_st->e_id, ene_st->e_id); - ene_st->alias_target_ene_st->set_last_hit_by_client_id(c->lobby_client_id); - } - } else if ((res.entity_id >= 0x4000) && (res.entity_id < 0xFFFF)) { res.obj_st = l->map_state->object_state_for_index(c->version(), res.entity_id - 0x4000); } @@ -803,8 +793,8 @@ Parsed6x70Data::Parsed6x70Data( death_flags(cmd.death_flags), hold_state(cmd.hold_state), area(cmd.area), - game_flags(cmd.game_flags), - game_flags_is_v3(false), + player_flags(cmd.player_flags), + player_flags_is_v3(false), visual(cmd.visual), stats(cmd.stats), num_items(cmd.num_items), @@ -839,8 +829,8 @@ Parsed6x70Data::Parsed6x70Data( death_flags(cmd.death_flags), hold_state(cmd.hold_state), area(cmd.area), - game_flags(cmd.game_flags), - game_flags_is_v3(false), + player_flags(cmd.player_flags), + player_flags_is_v3(false), visual(cmd.visual), stats(cmd.stats), num_items(cmd.num_items), @@ -872,7 +862,7 @@ Parsed6x70Data::Parsed6x70Data( Version from_version, bool from_client_customization) : Parsed6x70Data(cmd.base, guild_card_number, from_version, from_client_customization) { - this->game_flags_is_v3 = true; + this->player_flags_is_v3 = true; this->stats = cmd.stats; this->num_items = cmd.num_items; this->items = cmd.items; @@ -888,7 +878,7 @@ Parsed6x70Data::Parsed6x70Data( Version from_version, bool from_client_customization) : Parsed6x70Data(cmd.base, guild_card_number, from_version, from_client_customization) { - this->game_flags_is_v3 = true; + this->player_flags_is_v3 = true; this->stats = cmd.stats; this->num_items = cmd.num_items; this->items = cmd.items; @@ -904,7 +894,7 @@ Parsed6x70Data::Parsed6x70Data( Version from_version, bool from_client_customization) : Parsed6x70Data(cmd.base, guild_card_number, from_version, from_client_customization) { - this->game_flags_is_v3 = true; + this->player_flags_is_v3 = true; this->stats = cmd.stats; this->num_items = cmd.num_items; this->items = cmd.items; @@ -924,7 +914,7 @@ G_SyncPlayerDispAndInventory_DCNTE_6x70 Parsed6x70Data::as_dc_nte(shared_ptrdeath_flags; ret.hold_state = this->hold_state; ret.area = this->area; - ret.game_flags = this->get_game_flags(false); + ret.player_flags = this->get_player_flags(false); ret.visual = this->visual; ret.stats = this->stats; ret.num_items = this->num_items; @@ -956,7 +946,7 @@ G_SyncPlayerDispAndInventory_DC112000_6x70 Parsed6x70Data::as_dc_112000(shared_p ret.death_flags = this->death_flags; ret.hold_state = this->hold_state; ret.area = this->area; - ret.game_flags = this->get_game_flags(false); + ret.player_flags = this->get_player_flags(false); ret.visual = this->visual; ret.stats = this->stats; ret.num_items = this->num_items; @@ -1112,7 +1102,7 @@ Parsed6x70Data::Parsed6x70Data( temporary_status_effect(base.temporary_status_effect), attack_status_effect(base.attack_status_effect), defense_status_effect(base.defense_status_effect), - unused_status_effect(base.unused_status_effect), + unknown_a1_status_effect(base.unknown_a1_status_effect), language(static_cast(base.language32.load())), player_tag(base.player_tag), guild_card_number(guild_card_number), // Ignore the client's GC# @@ -1122,8 +1112,8 @@ Parsed6x70Data::Parsed6x70Data( death_flags(base.death_flags), hold_state(base.hold_state), area(base.area), - game_flags(base.game_flags), - game_flags_is_v3(!is_v1_or_v2(from_version)), + player_flags(base.player_flags), + player_flags_is_v3(!is_v1_or_v2(from_version)), technique_levels_v1(base.technique_levels_v1), visual(base.visual) {} @@ -1136,7 +1126,7 @@ G_6x70_Base_V1 Parsed6x70Data::base_v1(bool is_v3) const { ret.temporary_status_effect = this->temporary_status_effect; ret.attack_status_effect = this->attack_status_effect; ret.defense_status_effect = this->defense_status_effect; - ret.unused_status_effect = this->unused_status_effect; + ret.unknown_a1_status_effect = this->unknown_a1_status_effect; ret.language32 = static_cast(this->language); ret.player_tag = this->player_tag; ret.guild_card_number = this->guild_card_number; @@ -1146,46 +1136,69 @@ G_6x70_Base_V1 Parsed6x70Data::base_v1(bool is_v3) const { ret.death_flags = this->death_flags; ret.hold_state = this->hold_state; ret.area = this->area; - ret.game_flags = this->get_game_flags(is_v3); + ret.player_flags = this->get_player_flags(is_v3); ret.technique_levels_v1 = this->technique_levels_v1; ret.visual = this->visual; return ret; } -uint32_t Parsed6x70Data::convert_game_flags(uint32_t game_flags, bool to_v3) { - // The format of game_flags for players 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: JIHCBAzy xwvutsrq ponmlkji hgfedcba - // Bits on v3: JIHGFEDC 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 so far: - // 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) - // J = is item +uint32_t Parsed6x70Data::convert_player_flags(uint32_t player_flags, bool to_v3) { + // The format of player_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. What's known about these bits (? indicates meaning/behavior is unverified): + // * V1/V2 * V3/V4 + // ? 00000001 00000001 = player hold is set (see notes on 6x2C and 6x2D in CommandFormats.hh) + // ? 00000002 ? 00000002 = unknown (TODO) + // ? 00000004 ? 00000004 = loading? (allows 6x3E to update room ID and near-enemy flag; TODO: what does this do?) + // ? 00000008 ? 00000008 = unknown (TODO) + // ? 00000010 00000010 = should send position update? (6x3E, 6x40, or 6x42) + // ? 00000020 ? 00000020 = unknown (TODO) + // ? 00000040 ? 00000040 = unknown (TODO) + // ? 00000080 ? 00000080 = seems to affect some particle effects? (TODO) + // ? 00000100 -------- = unknown (TODO) + // ? 00000200 00000100 = chat or pause menu is open (suppresses action palette) + // ? 00000400 ? 00000200 = unknown (TODO) + // ? 00000800 -------- = unknown (TODO) + // ? 00001000 -------- = unknown (TODO) + // ? 00002000 00000400 = action palette is disabled by p_action_disable or one of the TObjQuestColA* objects + // ? 00004000 00000800 = is about to return to Pioneer 2 after a death (TODO: also set by p_return_guild) + // ? 00008000 00001000 = cannot use telepipe / Ryuker (e.g. boss warps set this flag when the player is nearby) + // ? 00010000 ? 00002000 = unknown (TODO) + // ? 00020000 00004000 = is teleporting as a result of 6x24 (set only briefly after appearing at destination) + // ? 00040000 ? 00008000 = is dead NPC? (set by e.g. npc_crptalk_id when regsA[4] == 1) + // ? 00080000 -------- = unknown (TODO) + // ? 00100000 00010000 = has permanent trap vision (e.g. is android) + // ? 00200000 ? 00020000 = related to items; seems always set for the local player in P2 but not other players? + // ? 00400000 ? 00040000 = warping? (TODO: set by 6x21 and 6x22) + // ? 00800000 ? 00080000 = unknown (TODO) + // ? 01000000 ? 00100000 = unknown (TODO) + // ? 02000000 00200000 = is visible (set shortly after warping into a floor; remains set until next warp) + // ? 04000000 ? 00400000 = position is valid (therefore player can be rendered) + // ? 08000000 00800000 = player is invisible, but items are visible (TODO: is this used for Stealth Suit on BB?) + // ? 10000000 01000000 = if set, player does not drop weapon on death + // -------- ? 02000000 = used by TObjRoomId when param6 == 0x00010000 (TODO: figure out what this does) + // -------- 04000000 = is sitting in lobby chair + // -------- ? 08000000 = related to lobby chairs (TODO: see handle_6xAE) + // -------- 10000000 = using alternate lobby chair pose (X+B instead of X+A on GC, for example) if (to_v3) { - return (game_flags & 0xE00000FF) | - ((game_flags & 0x00000600) >> 1) | - ((game_flags & 0x0007E000) >> 3) | - ((game_flags & 0x1FF00000) >> 4); + return (player_flags & 0x000000FF) | + ((player_flags & 0x00000600) >> 1) | + ((player_flags & 0x0007E000) >> 3) | + ((player_flags & 0x1FF00000) >> 4); } else { - return (game_flags & 0xE00000FF) | - ((game_flags << 1) & 0x00000600) | - ((game_flags << 3) & 0x0007E000) | - ((game_flags << 4) & 0x1FF00000); + return (player_flags & 0x000000FF) | + ((player_flags << 1) & 0x00000600) | + ((player_flags << 3) & 0x0007E000) | + ((player_flags << 4) & 0x1FF00000); } } -uint32_t Parsed6x70Data::get_game_flags(bool is_v3) const { - return (this->game_flags_is_v3 == is_v3) - ? this->game_flags - : Parsed6x70Data::convert_game_flags(this->game_flags, is_v3); +uint32_t Parsed6x70Data::get_player_flags(bool is_v3) const { + return (this->player_flags_is_v3 == is_v3) + ? this->player_flags + : Parsed6x70Data::convert_player_flags(this->player_flags, is_v3); } static asio::awaitable on_sync_joining_player_disp_and_inventory( @@ -3435,17 +3448,29 @@ static asio::awaitable on_update_enemy_state(shared_ptr c, Subcomm } auto ene_st = l->map_state->enemy_state_for_index(c->version(), cmd.enemy_index); uint32_t src_flags = is_big_endian(c->version()) ? bswap32(cmd.game_flags) : cmd.game_flags.load(); + if (src_flags & 0xD0000000) { // Don't allow player, object, or item flags to be set + throw std::runtime_error("incorrect entity type flags in 6x0A command"); + } if (l->difficulty == Difficulty::ULTIMATE) { src_flags = (src_flags & 0xFFFFFFC0) | (ene_st->game_flags & 0x0000003F); } + + bool should_track_hit = (src_flags & 0x00000200); // "Enemy was hit" flag (see 6x0A comments in CommandFormats.hh) + if (should_track_hit) { + ene_st->set_last_hit_by_client_id(c->lobby_client_id); + } ene_st->game_flags = src_flags; ene_st->total_damage = cmd.total_damage; - l->log.info_f("E-{:03X} updated to damage={} game_flags={:08X}", ene_st->e_id, ene_st->total_damage, ene_st->game_flags); + l->log.info_f("E-{:03X} updated to damage={} game_flags={:08X}; last hit {}", + ene_st->e_id, ene_st->total_damage, ene_st->game_flags, should_track_hit ? "claimed" : "not claimed"); if (ene_st->alias_target_ene_st) { + if (should_track_hit) { + ene_st->alias_target_ene_st->set_last_hit_by_client_id(c->lobby_client_id); + } ene_st->alias_target_ene_st->game_flags = src_flags; ene_st->alias_target_ene_st->total_damage = cmd.total_damage; - l->log.info_f("Alias target E-{:03X} updated to damage={} game_flags={:08X}", - ene_st->alias_target_ene_st->e_id, ene_st->alias_target_ene_st->total_damage, ene_st->alias_target_ene_st->game_flags); + l->log.info_f("Alias target E-{:03X} updated to damage={} game_flags={:08X}; last hit {}", + ene_st->alias_target_ene_st->e_id, ene_st->alias_target_ene_st->total_damage, ene_st->alias_target_ene_st->game_flags, should_track_hit ? "claimed" : "not claimed"); } // TODO: It'd be nice if this worked on bosses too, but it seems we have to use each boss' specific state-syncing @@ -3493,8 +3518,18 @@ static asio::awaitable on_incr_enemy_damage(shared_ptr c, Subcomma cmd.total_damage_before_hit.load(), cmd.current_hp_before_hit.load(), cmd.max_hp.load()); + + if (cmd.hit_amount > 0) { + c->log.info_f("Claiming last hit on E-{:03X}", ene_st->e_id); + ene_st->set_last_hit_by_client_id(c->lobby_client_id); + } ene_st->total_damage = std::min(ene_st->total_damage + cmd.hit_amount, cmd.max_hp); if (ene_st->alias_target_ene_st) { + if (cmd.hit_amount > 0) { + c->log.info_f("Claiming last hit on E-{:03X} (alias of E-{:03X})", + ene_st->alias_target_ene_st->e_id, ene_st->e_id); + ene_st->alias_target_ene_st->set_last_hit_by_client_id(c->lobby_client_id); + } ene_st->alias_target_ene_st->total_damage = std::min( ene_st->alias_target_ene_st->total_damage + cmd.hit_amount, cmd.max_hp); } @@ -3502,13 +3537,13 @@ static asio::awaitable on_incr_enemy_damage(shared_ptr c, Subcomma co_await forward_subcommand_with_entity_id_transcode_t(c, msg); } -static asio::awaitable on_set_enemy_low_game_flags_ultimate(shared_ptr c, SubcommandMessage& msg) { +static asio::awaitable on_set_enemy_status_effect_flags_ultimate(shared_ptr c, SubcommandMessage& msg) { auto& cmd = msg.check_size_t(); if (command_is_private(msg.command) || (cmd.header.entity_id < 0x1000) || (cmd.header.entity_id >= 0x4000) || - (cmd.low_game_flags & 0xFFFFFFC0) || + (cmd.status_effect_flags & 0xFFFFFFC0) || (c->lobby_client_id > 3)) { co_return; } @@ -3518,12 +3553,12 @@ static asio::awaitable on_set_enemy_low_game_flags_ultimate(shared_ptrmap_state->enemy_state_for_index(c->version(), cmd.header.entity_id - 0x1000); - if (!(ene_st->game_flags & cmd.low_game_flags)) { - ene_st->game_flags |= cmd.low_game_flags; + if (!(ene_st->game_flags & cmd.status_effect_flags)) { + ene_st->game_flags |= cmd.status_effect_flags; l->log.info_f("E-{:03X} updated to game_flags={:08X}", ene_st->e_id, ene_st->game_flags); } - if (ene_st->alias_target_ene_st && !(ene_st->alias_target_ene_st->game_flags & cmd.low_game_flags)) { - ene_st->alias_target_ene_st->game_flags |= cmd.low_game_flags; + if (ene_st->alias_target_ene_st && !(ene_st->alias_target_ene_st->game_flags & cmd.status_effect_flags)) { + ene_st->alias_target_ene_st->game_flags |= cmd.status_effect_flags; l->log.info_f("Alias E-{:03X} updated to game_flags={:08X}", ene_st->alias_target_ene_st->e_id, ene_st->alias_target_ene_st->game_flags); } @@ -4062,8 +4097,11 @@ static asio::awaitable on_enemy_exp_request_bb(shared_ptr c, Subco // If the requesting player never hit this enemy, they are probably cheating; ignore the command. Also, each player // sends a 6xC8 if they ever hit the enemy; we only react to the first 6xC8 for each enemy (and give all relevant // players EXP then, if they deserve it). - if (!ene_st->ever_hit_by_client_id(c->lobby_client_id) || - (ene_st->server_flags & MapState::EnemyState::Flag::EXP_GIVEN)) { + if (!ene_st->ever_hit_by_client_id(c->lobby_client_id)) { + l->log.warning_f("The requesting player did not hit this enemy; ignoring request"); + co_return; + } + if (ene_st->server_flags & MapState::EnemyState::Flag::EXP_GIVEN) { l->log.info_f("EXP already given for this enemy; ignoring request"); co_return; } @@ -5506,10 +5544,10 @@ const vector subcommand_definitions{ /* 6x43 */ {0x3A, 0x3F, 0x43, on_forward_check_game_client}, /* 6x44 */ {0x3B, 0x40, 0x44, on_forward_check_game_client}, /* 6x45 */ {0x3C, 0x41, 0x45, on_forward_check_game_client}, - /* 6x46 */ {NONE, 0x42, 0x46, forward_subcommand_with_entity_targets_transcode_and_track_hits_t}, - /* 6x47 */ {0x3D, 0x43, 0x47, forward_subcommand_with_entity_targets_transcode_and_track_hits_t}, + /* 6x46 */ {NONE, 0x42, 0x46, forward_subcommand_with_entity_targets_transcode_t}, + /* 6x47 */ {0x3D, 0x43, 0x47, forward_subcommand_with_entity_targets_transcode_t}, /* 6x48 */ {NONE, NONE, 0x48, on_cast_technique_finished}, - /* 6x49 */ {0x3E, 0x44, 0x49, forward_subcommand_with_entity_targets_transcode_and_track_hits_t}, + /* 6x49 */ {0x3E, 0x44, 0x49, forward_subcommand_with_entity_targets_transcode_t}, /* 6x4A */ {0x3F, 0x45, 0x4A, on_change_hp}, /* 6x4B */ {0x40, 0x46, 0x4B, on_change_hp}, /* 6x4C */ {0x41, 0x47, 0x4C, on_change_hp}, @@ -5592,7 +5630,7 @@ const vector subcommand_definitions{ /* 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, on_set_enemy_low_game_flags_ultimate}, + /* 6x9C */ {NONE, NONE, 0x9C, on_set_enemy_status_effect_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}, diff --git a/src/ReceiveSubcommands.hh b/src/ReceiveSubcommands.hh index c4fad357..8931c7fd 100644 --- a/src/ReceiveSubcommands.hh +++ b/src/ReceiveSubcommands.hh @@ -52,7 +52,7 @@ public: StatusEffectState temporary_status_effect; StatusEffectState attack_status_effect; StatusEffectState defense_status_effect; - StatusEffectState unused_status_effect; + StatusEffectState unknown_a1_status_effect; // De Rol Le uses this? Language language = Language::JAPANESE; uint32_t player_tag = 0; uint32_t guild_card_number = 0; @@ -62,8 +62,8 @@ public: uint32_t death_flags = 0; PlayerHoldState hold_state; uint32_t area = 0; - uint32_t game_flags = 0; - bool game_flags_is_v3 = false; + uint32_t player_flags = 0; + bool player_flags_is_v3 = false; parray technique_levels_v1 = 0xFF; PlayerVisualConfig visual; std::string name; @@ -121,8 +121,8 @@ protected: Parsed6x70Data( const G_6x70_Base_V1& base, uint32_t guild_card_number, Version from_version, bool from_client_customization); G_6x70_Base_V1 base_v1(bool is_v3) const; - static uint32_t convert_game_flags(uint32_t game_flags, bool to_v3); - uint32_t get_game_flags(bool is_v3) const; + static uint32_t convert_player_flags(uint32_t player_flags, bool to_v3); + uint32_t get_player_flags(bool is_v3) const; }; bool validate_6xBB(G_SyncCardTradeServerState_Ep3_6xBB& cmd); diff --git a/src/StaticGameData.cc b/src/StaticGameData.cc index 0b0acdba..5d23fc28 100644 --- a/src/StaticGameData.cc +++ b/src/StaticGameData.cc @@ -293,8 +293,18 @@ uint8_t npc_for_name(const string& name, Version version) { const char* name_for_char_class(uint8_t cls) { static const array names = { - "HUmar", "HUnewearl", "HUcast", "RAmar", "RAcast", "RAcaseal", "FOmarl", "FOnewm", "FOnewearl", "HUcaseal", - "FOmar", "RAmarl"}; + /* 00 */ "HUmar", // 0 + /* 01 */ "HUnewearl", // 0 + /* 02 */ "HUcast", // 1 + /* 03 */ "RAmar", // 0 + /* 04 */ "RAcast", // 2 + /* 05 */ "RAcaseal", // 1 + /* 06 */ "FOmarl", // 0 + /* 07 */ "FOnewm", // 0 + /* 08 */ "FOnewearl", // 0 + /* 09 */ "HUcaseal", // 1 + /* 0A */ "FOmar", // 0 + /* 0B */ "RAmarl"}; // 0 try { return names.at(cls); } catch (const out_of_range&) { diff --git a/system/client-functions/Debug/PlayerFlags.3OE1.patch.s b/system/client-functions/Debug/PlayerFlags.3OE1.patch.s new file mode 100644 index 00000000..77f9a408 --- /dev/null +++ b/system/client-functions/Debug/PlayerFlags.3OE1.patch.s @@ -0,0 +1,77 @@ +.meta hide_from_patches_menu +.meta name="Player flags" +.meta description="" + +entry_ptr: +reloc0: + .offsetof start +start: + .include WriteCodeBlocksGC + + .label TObjPlayer_for_client_id, 0x801BA59C # [std](uint32_t client_id) + .label render_debug_printf, 0x803D4E3C # [std](uint32_t coords, const char* fmt, ...); + .label set_debug_text_color, 0x803D4990 # [](uint32_t color_argb); + .label hook_call, 0x80228A38 + .label hook_loc, 0x8000A000 + + .data hook_call + .data 4 + .address hook_call + b hook_loc + + .data hook_loc + .deltaof hook_start, hook_end + .address hook_loc +hook_start: + mflr r0 + stwu [r1 - 0x20], r1 + stw [r1 + 0x24], r0 + stw [r1 + 8], r30 + li r30, 0 + +hook_again: + li r6, 0 + mr r3, r30 + bl TObjPlayer_for_client_id + cmplwi r3, 0 + beq hook_player_missing + lwz r6, [r3 + 0x0334] # player_flags + lwz r3, [r13 - 0x5280] # local_client_id + cmp r3, r30 + bne hook_not_local_player + lis r3, 0xFFFF + ori r3, r3, 0x00FF + b hook_player_flags_ok +hook_not_local_player: + lis r3, 0xFFFF + ori r3, r3, 0xFFFF + b hook_player_flags_ok +hook_player_missing: + lis r3, 0xFF00 + ori r3, r3, 0x00FF +hook_player_flags_ok: + bl set_debug_text_color + + lis r3, 0x0002 + ori r3, r3, 0x0018 + add r3, r3, r30 + bl hook_get_fmt_string + .binary "Player %2d: %08X"00000000 +hook_get_fmt_string: + mflr r4 + mr r5, r30 + bl render_debug_printf + + addi r30, r30, 1 + cmplwi r30, 0x0C + blt hook_again + + lwz r30, [r1 + 8] + lwz r0, [r1 + 0x24] + addi r1, r1, 0x20 + mtlr r0 + blr +hook_end: + + .data 0 + .data 0