Download crates/openwa-core/src/task/worm.rs from nmarshall/wormz-a: direct link, hf CLI and curl.
- Browser
- Download file 23.6 kB
-
https://huggingface.co/nmarshall/wormz-a/resolve/main/crates/openwa-core/src/task/worm.rs
- Command line
-
hf download hf://nmarshall/wormz-a/crates/openwa-core/src/task/worm.rs
-
curl -L -o worm.rs https://huggingface.co/nmarshall/wormz-a/resolve/main/crates/openwa-core/src/task/worm.rs
23.6 kB
| use super::base::CTask; | |
| use super::game_task::CGameTask; | |
| use crate::fixed::Fixed; | |
| use crate::game::weapon::WeaponEntry; | |
| use crate::game::Weapon; | |
| use crate::FieldRegistry; | |
| /// Worm state machine states. | |
| /// | |
| /// CTaskWorm's `SetState` (vtable slot 14) transitions between these. | |
| /// The state byte lives at CTaskWorm+0x44 (inside `base.subclass_data`). | |
| /// Also stored in WormEntry.state in the TeamArena. | |
| /// | |
| /// States 0x68..=0x8A are the "weapon/action active" range β checked by | |
| /// `(state - 0x68) < 0x23` in HandleMessage. States 0x80+ are dying/dead. | |
| /// Names are best guesses from behavioral observation and disassembly. | |
| /// | |
| /// Source: CTaskWorm::HandleMessage (0x510B40) decompilation + weapon fire dispatch. | |
| pub enum WormState { | |
| /// Transitional state checked by CheckWormState0x64 (0x5228D0). | |
| /// Appears briefly during turn transitions. | |
| Transitional = 0x64, | |
| /// Idle β not this worm's turn. Also used by MailMineMole to re-enter idle. | |
| Idle = 0x65, | |
| /// Idle variant β grouped with 0x65/0x67 in HandleMessage switch cases. | |
| IdleVariant_Maybe = 0x66, | |
| /// Active turn β this worm is currently being controlled. | |
| Active = 0x67, | |
| /// Active variant β observed in WormEntry. Start of weapon/action range. | |
| ActiveVariant_Maybe = 0x68, | |
| /// Unknown (within weapon/action range). | |
| Unknown_0x69 = 0x69, | |
| /// Unknown (within weapon/action range). | |
| Unknown_0x6A = 0x6A, | |
| /// Unknown (within weapon/action range). | |
| Unknown_0x6B = 0x6B, | |
| /// Fire Punch β performing fire punch attack (sub34=1). | |
| FirePunch = 0x6C, | |
| /// Kamikaze β performing kamikaze attack (sub34=4). Frequently checked in HandleMessage. | |
| Kamikaze = 0x6D, | |
| /// Pneumatic Drill β using the pneumatic drill (sub34=8). | |
| PneumaticDrill = 0x6E, | |
| /// Air Strike pending β set when air strike fires with `_unknown_208 == 0`. | |
| AirStrikePending_Maybe = 0x6F, | |
| /// Unknown β sub34=6 has no replay test log data. | |
| Unknown_0x70 = 0x70, | |
| /// Blowtorch β using the blowtorch weapon (sub34=11). | |
| Blowtorch = 0x71, | |
| /// Unknown β sub34=18 has no replay test log data. | |
| Unknown_0x72 = 0x72, | |
| /// Weapon charging β entered from aiming states (0x7B, 0x7C) before release. | |
| /// Also set by CheckPendingAction when field +0xBC is nonzero. | |
| WeaponCharging_Maybe = 0x73, | |
| /// Teleport cancelled β teleport failed or was denied. | |
| TeleportCancelled_Maybe = 0x74, | |
| /// Suicide Bomber β performing suicide bomber attack (sub34=5). | |
| SuicideBomber = 0x75, | |
| /// Unknown (within weapon/action range). | |
| Unknown_0x76 = 0x76, | |
| /// Weapon selected β entered via SelectCursor (msg 0x24) from idle/active states. | |
| /// HandleMessage sets param[0xa7]=-1 when already in this state. | |
| WeaponSelected_Maybe = 0x77, | |
| /// Weapon aimed β post-select state. Teleport check accepts this. | |
| /// Also used for Magic Bullet weapon fire. | |
| WeaponAimed_Maybe = 0x78, | |
| /// Unknown. Checked in WeaponRelease spawn offset (type 2, Y adjustment when state == 0x79). | |
| Unknown_0x79 = 0x79, | |
| /// Unknown. | |
| Unknown_0x7A = 0x7A, | |
| /// Aiming with angle β entered for aimed weapons. Sets angle params. | |
| /// Teleport check accepts this. Transitions to 0x73 on fire. | |
| AimingAngle_Maybe = 0x7B, | |
| /// Rope swinging β IsNotOnRope checks `state != 0x7C`. | |
| /// Transitions to 0x73 on fire. | |
| RopeSwinging = 0x7C, | |
| /// Pre-fire variant β MailMineMole version check uses this. | |
| /// Transitions to 0x7E or 0x65 depending on FUN_004fb580. | |
| PreFire_Maybe = 0x7D, | |
| /// Post-fire / special movement β entered from 0x78 and 0x7D | |
| /// when FUN_004fb580 returns nonzero. | |
| PostFire_Maybe = 0x7E, | |
| /// Drowning β worm fell in water. | |
| Drowning = 0x7F, | |
| /// Hurt β worm took damage. | |
| Hurt = 0x80, | |
| /// Dead variant 1. | |
| Dead1 = 0x81, | |
| /// Dying variant 1 β checked alongside 0x83 in HandleMessage. | |
| Dying1_Maybe = 0x82, | |
| /// Dying variant 2 β checked alongside 0x82 in HandleMessage. | |
| Dying2_Maybe = 0x83, | |
| /// Unknown. | |
| Unknown_0x84 = 0x84, | |
| /// Unknown. | |
| Unknown_0x85 = 0x85, | |
| /// Dead β set by Surrender (msg 0x29). Frequently checked in HandleMessage. | |
| Dead = 0x86, | |
| /// Dead variant 3. | |
| Dead3 = 0x87, | |
| /// Unknown β grouped with idle states (0x65/0x66/0x67/0x8B) in HandleMessage. | |
| Unknown_0x88 = 0x88, | |
| /// Dying/special animation (from WormEntry state documentation). | |
| DyingAnimation_Maybe = 0x89, | |
| /// Unknown. End of weapon/action range. | |
| Unknown_0x8A = 0x8A, | |
| /// Unknown state checked in CTaskTeam handlers. | |
| /// Grouped with idle states in HandleMessage switch cases. | |
| Unknown_0x8B = 0x8B, | |
| } | |
| impl WormState {} | |
| /// Raw-pointer methods for CTaskWorm. | |
| /// | |
| /// `set_state_raw` and other vtable methods are now auto-generated by the | |
| /// `bind_CTaskWormVTable!` macro. Only non-vtable raw helpers remain here. | |
| impl CTaskWorm { | |
| /// Set the weapon fire completion flag at +0x3C without creating `&mut self`. | |
| pub unsafe fn set_fire_complete_raw(this: *mut CTaskWorm, value: i32) { | |
| let base = this as *mut u8; | |
| core::ptr::write(base.add(0x3C) as *mut i32, value); | |
| } | |
| /// Fire subtype 1 at CGameTask+0x34 (subclass_data[4..8]). | |
| /// Air strike temporarily swaps this with `_unknown_190` during position update. | |
| pub unsafe fn fire_subtype_1(this: *const CTaskWorm) -> i32 { | |
| i32::from_ne_bytes((&(*this).base.subclass_data)[4..8].try_into().unwrap()) | |
| } | |
| /// Set fire subtype 1 without creating `&mut self` (avoids LLVM noalias violations). | |
| pub unsafe fn set_fire_subtype_1_raw(this: *mut CTaskWorm, value: i32) { | |
| (&mut (*this).base.subclass_data)[4..8].copy_from_slice(&value.to_ne_bytes()); | |
| } | |
| /// Set the action field at CGameTask+0x48 (subclass_data[0x18..0x1C]). | |
| /// Cleared after air strike completion alongside `_unknown_208` / `_unknown_19x`. | |
| pub unsafe fn set_action_field_raw(this: *mut CTaskWorm, value: i32) { | |
| (&mut (*this).base.subclass_data)[0x18..0x1C].copy_from_slice(&value.to_ne_bytes()); | |
| } | |
| } | |
| crate::define_addresses! { | |
| class "CTaskWorm" { | |
| /// CTaskWorm vtable | |
| vtable CTASK_WORM_VTABLE = 0x0066_44C8; | |
| /// CTaskWorm constructor | |
| ctor CTASK_WORM_CONSTRUCTOR = 0x0050_BFB0; | |
| } | |
| } | |
| /// Virtual method table for CTaskWorm (vtable at 0x6644C8, 20 slots). | |
| /// | |
| /// CTaskWorm overrides 14 of the 20 inherited CTask/CGameTask slots; | |
| /// 6 slots pass through unchanged. Slot layout by vtable byte offset: | |
| /// | |
| /// ```text | |
| /// Slot Offset Name Base class | |
| /// ---- ------ ------------------- ---------- | |
| /// 0 0x00 WriteReplayState CTask (overridden) | |
| /// 1 0x04 Free CTask (overridden) | |
| /// 2 0x08 HandleMessage CTask (overridden) | |
| /// 3 0x0C GetEntityData CTask (overridden) | |
| /// 4 0x10 (stub, returns 0) CTask β inherited | |
| /// 5 0x14 ProcessChildren CTask β inherited | |
| /// 6 0x18 ProcessFrame CTask β inherited | |
| /// 7 0x1C OnContactEntity CGameTask (overridden) | |
| /// 8 0x20 OnWormPush CGameTask (overridden) | |
| /// 9 0x24 OnLandBounce CGameTask (overridden) | |
| /// 10 0x28 OnLandSlide CGameTask? (new) | |
| /// 11 0x2C OnSink CGameTask? (new) | |
| /// 12 0x30 (inherited) CGameTask | |
| /// 13 0x34 OnKilled CGameTask (overridden) | |
| /// 14 0x38 SetState CTaskWorm (new) | |
| /// 15 0x3C CheckPendingAction CTaskWorm (new) | |
| /// 16 0x40 IsNotOnRope CTaskWorm (new) | |
| /// 17 0x44 (inherited) CGameTask | |
| /// 18 0x48 GetTeamIndex CTaskWorm (new) | |
| /// 19 0x4C (inherited) CGameTask | |
| /// ``` | |
| pub struct CTaskWormVTable { | |
| /// WriteReplayState β serializes worm state to a replay stream | |
| pub write_replay_state: fn(this: *mut CTaskWorm, stream: *mut u8), | |
| /// Free β calls inner destructor, then `_free(this)` if flags & 1 | |
| pub free: fn(this: *mut CTaskWorm, flags: u8) -> *mut CTaskWorm, | |
| /// HandleMessage β processes all TaskMessages sent to this worm | |
| pub handle_message: | |
| fn(this: *mut CTaskWorm, sender: *mut CTask, msg_type: u32, size: u32, data: *const u8), | |
| /// GetEntityData β returns worm data by query code | |
| pub get_entity_data: fn(this: *mut CTaskWorm, query: u32, param: u32, out: *mut u32) -> u32, | |
| // Slots 4-6: Inherited CTask stubs (auto-filled as usize) | |
| /// OnContactEntity β handles physical contact with another entity | |
| pub on_contact_entity: fn(this: *mut CTaskWorm, other: *mut CGameTask, flags: u32) -> u32, | |
| /// OnWormPush β post-contact worm-worm push impulse | |
| pub on_worm_push: fn(this: *mut CTaskWorm, other: *mut CGameTask, flags: u32) -> u32, | |
| /// OnLandBounce β worm lands on terrain; plays thud sound, bounce physics | |
| pub on_land_bounce: fn(this: *mut CTaskWorm), | |
| /// OnLandSlide β secondary landing callback; sliding/friction physics | |
| pub on_land_slide: fn(this: *mut CTaskWorm), | |
| /// OnSink β worm sinks in water/acid; transitions to drowning state | |
| pub on_sink: fn(this: *mut CTaskWorm, dx: i32, dy: i32) -> u32, | |
| // Slot 12: Inherited (auto-filled) | |
| /// OnKilled β worm death; plays death sound, transitions to dead state | |
| pub on_killed: fn(this: *mut CTaskWorm), | |
| /// SetState β worm state machine; handles all state transitions | |
| pub set_state: fn(this: *mut CTaskWorm, state: WormState), | |
| /// CheckPendingAction β if field +0xBC is set, calls SetState(0x73) | |
| pub check_pending_action: fn(this: *mut CTaskWorm), | |
| /// IsNotOnRope β returns true if worm state != 0x7C (rope-swinging) | |
| pub is_not_on_rope: fn(this: *const CTaskWorm) -> bool, | |
| // Slot 17: Inherited (auto-filled) | |
| /// GetTeamIndex β returns worm's team index (field +0xFC) | |
| pub get_team_index: fn(this: *const CTaskWorm) -> u32, | |
| // Slot 19: Inherited (auto-filled) | |
| } | |
| /// Worm entity task β the primary playable character in WA. | |
| /// | |
| /// Extends CGameTask (0xFC bytes) with worm identity, physics overrides, and | |
| /// per-worm state. Total size: 0x3FC bytes. | |
| /// | |
| /// Constructor: 0x50BFB0 (stdcall, 5 params): | |
| /// this, parent_task, team_index, worm_index, init_data_ptr | |
| /// | |
| /// Vtable at 0x6644C8. Class type byte: 0x12. | |
| /// | |
| /// # Important fields in the CGameTask base | |
| /// The worm state field lives at **offset +0x44** (inside `base.subclass_data`). | |
| /// Use [`CTaskWorm::state`] to read it without pointer arithmetic. | |
| /// | |
| /// Source: Ghidra decompilation of 0x50BFB0, vtable analysis of 0x6644C8, | |
| /// wkJellyWorm CTaskWorm.h | |
| pub struct CTaskWorm { | |
| /// 0x00β0xFB: CGameTask base (position, velocity, sound emitter, etc.) | |
| pub base: CGameTask<*const CTaskWormVTable>, | |
| /// 0xFC: Team index (0-based); 3rd constructor param | |
| pub team_index: u32, | |
| /// 0x100: Worm index within team (0-based); 4th constructor param | |
| pub worm_index: u32, | |
| /// 0x104: Unknown flag (checked in OnContactEntity) | |
| pub _unknown_104: u32, | |
| /// 0x108β0x10F: Unknown | |
| pub _unknown_108: [u8; 8], | |
| /// 0x110β0x137: Ten u32s copied from spawn init_data (5th constructor param) | |
| pub spawn_params: [u32; 10], | |
| /// 0x138β0x143: Unknown | |
| pub _unknown_138: [u8; 0x0C], | |
| /// 0x144: Poison source bitmask β tracks which alliances have poisoned this worm. | |
| /// PoisonWorm handler (msg 0x51): `poison_source_mask |= alliance_bit`. | |
| /// Prevents double-poisoning from the same source. | |
| pub poison_source_mask: u32, | |
| /// 0x148: Poison damage per turn. 0 = not poisoned. | |
| /// PoisonWorm handler: `poison_damage += msg_data[0]`. | |
| /// ApplyPoison handler (msg 0x3E): subtracts this from health each turn. | |
| pub poison_damage: i32, | |
| /// 0x14Cβ0x14F: Unknown | |
| pub _unknown_14c: [u8; 4], | |
| /// 0x150: Unknown (slot 9 in GetEntityData query 0x7D4 output) | |
| pub _unknown_150: u32, | |
| /// 0x154: Unknown (rope-related; cleared in some SetState transitions) | |
| pub _unknown_154: u32, | |
| /// 0x158: Worm pool slot index in DDGame (assigned from pool at construction) | |
| pub slot_id: u32, | |
| /// 0x15Cβ0x163: Unknown | |
| pub _unknown_15c: [u8; 0x164 - 0x15C], | |
| /// 0x164: Frames the worm has stayed stationary (no movement). Resets on movement. | |
| pub stationary_frames: u32, | |
| /// 0x168β0x16F: Unknown | |
| pub _unknown_168: [u8; 0x170 - 0x168], | |
| /// 0x170: Currently selected weapon ID. | |
| pub selected_weapon: Weapon, | |
| /// 0x174β0x177: Unknown | |
| pub _unknown_174: [u8; 4], | |
| /// 0x178: Display health (animated toward target). Used for health bar interpolation. | |
| /// Stored as `00 00 XX 00` where XX is health β actual layout is u16 at +0x17A. | |
| pub display_health_raw: u32, | |
| /// 0x17C: Target health (matches WormEntry.health). Same byte layout as display_health. | |
| pub target_health_raw: u32, | |
| /// 0x180β0x18F: Unknown | |
| pub _unknown_180: [u8; 0x190 - 0x180], | |
| /// 0x190: Secondary position/mode field. Air strike temporarily swaps this with +0x34. | |
| pub _unknown_190: i32, | |
| /// 0x194β0x197: Unknown | |
| pub _unknown_194: [u8; 4], | |
| /// 0x198: Cleared after air strike fire. | |
| pub _unknown_198: i32, | |
| /// 0x19C: Cleared after air strike fire. | |
| pub _unknown_19c: i32, | |
| /// 0x1A0β0x1A7: Unknown | |
| pub _unknown_1a0: [u8; 0x1A8 - 0x1A0], | |
| /// 0x1A8: Facing direction copy. -1 = left, +1 = right (same as +0x3DC). | |
| pub facing_direction_2: i32, | |
| /// 0x1AC: Inverted facing direction. +1 = left, -1 = right. | |
| pub facing_direction_inv: i32, | |
| /// 0x1B0β0x1EB: Unknown | |
| pub _unknown_1b0: [u8; 0x1EC - 0x1B0], | |
| /// 0x1EC: Movement streak counter. Increases ~once per second while moving | |
| /// in one direction. Resets to 0 when movement resumes after a stop. | |
| /// Set to -1 when the worm is blocked (e.g. hits a wall). | |
| pub movement_streak: i32, | |
| /// 0x1F0β0x207: Unknown | |
| pub _unknown_1f0: [u8; 0x208 - 0x1F0], | |
| /// 0x208: Action/mode flag. Checked in air strike (must be 0 to fire). | |
| /// Cleared after air strike completes. | |
| pub _unknown_208: i32, | |
| /// 0x20Cβ0x247: Unknown | |
| pub _unknown_20c: [u8; 0x248 - 0x20C], | |
| /// 0x248: Landscape scale factor for spawn offset calculation (Fixed 16.16). | |
| /// Used by WeaponRelease to convert aim params to world-space projectile offsets. | |
| pub landscape_scale: Fixed, | |
| /// 0x24C: Aim angle (fixed-point 16.16, range 0..0x10000 = 0..360 degrees). | |
| pub aim_angle: u32, | |
| /// 0x250β0x267: Unknown | |
| pub _unknown_250: [u8; 0x268 - 0x250], | |
| /// 0x268: Show aiming cursor flag (nonzero = cursor visible). | |
| pub show_cursor: u32, | |
| /// 0x26Cβ0x283: Unknown | |
| pub _unknown_26c: [u8; 0x284 - 0x26C], | |
| /// 0x284: Shot/aim data 1 (slot 5 in GetEntityData query 0x7D4). | |
| /// For weapons 0x22/0x24 (Teleport/Freeze), copied from shot_data_2 on first fire. | |
| /// Sent in HandleMessage 0x49 buffer during WeaponRelease. | |
| pub shot_data_1: u32, | |
| /// 0x288: Shot/aim data 2 (slot 6 in GetEntityData query 0x7D4). | |
| pub shot_data_2: u32, | |
| /// 0x28Cβ0x28F: Unknown | |
| pub _unknown_28c: [u8; 4], | |
| /// 0x290: Fire sync frame counter 1. Compared with fire_sync_frame_2 | |
| /// in WeaponRelease; when equal, weapon slot table is reset. | |
| pub fire_sync_frame_1: i32, | |
| /// 0x294: Fire sync frame counter 2. | |
| pub fire_sync_frame_2: i32, | |
| /// 0x298β0x2BB: Unknown | |
| pub _unknown_298: [u8; 0x2BC - 0x298], | |
| /// 0x2BC: Network client index. Compared against max client count | |
| /// (5 or 10 depending on FE version) to compute network delay. | |
| pub network_client_index: i32, | |
| /// 0x2C0: Difficulty level (0 = easier/30-frame delay, 1 = harder/60-frame delay). | |
| pub difficulty_level: i32, | |
| /// 0x2C4β0x2C7: Unknown | |
| pub _unknown_2c4: [u8; 4], | |
| /// 0x2C8: Network/sound condition flag. In type 2 (rope) sound dispatch, | |
| /// sound plays only when this is 0 OR when it equals 1. | |
| pub _unknown_2c8: i32, | |
| /// 0x2CC: Network flag. Nonzero = network mode active. | |
| /// Used in sound dispatch conditions and HandleMessage 0x49 buffer. | |
| pub _unknown_2cc: i32, | |
| /// 0x2D0β0x2DF: Unknown | |
| pub _unknown_2d0: [u8; 0x2E0 - 0x2D0], | |
| /// 0x2E0: Weapon parameter 1. Polymorphic per weapon: | |
| /// - WeaponRelease: ammo_per_turn (copied to release context) | |
| /// - Air Strike: fire position X | |
| /// - Freeze: freeze effect X position | |
| pub weapon_param_1: i32, | |
| /// 0x2E4: Weapon parameter 2. Polymorphic per weapon: | |
| /// - WeaponRelease: ammo_per_slot (copied to release context) | |
| /// - Air Strike: fire position Y | |
| /// - Freeze: freeze effect Y position | |
| pub weapon_param_2: i32, | |
| /// 0x2E8β0x2EB: Unknown | |
| pub _unknown_2e8: [u8; 4], | |
| /// 0x2EC: Weapon parameter 3 / launch count. | |
| /// - Weapons 0x22/0x24: checked == 0 to copy shot_data_2 β shot_data_1 | |
| /// - Freeze: freeze target entity ID | |
| pub weapon_param_3: i32, | |
| /// 0x2F0: Worm name, null-terminated (max 17 chars, from spawn init_data+3) | |
| pub worm_name: [u8; 0x11], | |
| /// 0x301: Country / team name from scheme, null-terminated (max 17 chars) | |
| pub country_name: [u8; 0x11], | |
| /// 0x312: Health display string, null-terminated ASCII (e.g. "100", "88"). | |
| /// Updated when health changes for the floating health number display. | |
| pub health_text: [u8; 0x09], | |
| /// 0x31B: Poison damage display string, null-terminated ASCII (e.g. "5", ""). | |
| /// Shown as the green poison damage number above the worm. | |
| pub poison_text: [u8; 0x09], | |
| /// 0x324β0x333: Unknown | |
| pub _unknown_324: [u8; 0x334 - 0x324], | |
| /// 0x334: Facing direction copy. -1 = left, +1 = right (same as +0x3DC). | |
| pub facing_direction_3: i32, | |
| /// 0x338β0x367: Unknown | |
| pub _unknown_338: [u8; 0x368 - 0x338], | |
| /// 0x368: Animator / controller object (dispatched via vtable for state animations) | |
| pub animator: *mut u8, | |
| /// 0x36C: Active weapon entry pointer. Points to `&WeaponTable.entries[selected_weapon]`. | |
| /// Contains fire type (+0x30), subtypes (+0x34/+0x38), and completion flag (+0x3C). | |
| /// Used by WeaponRelease: `MOV EAX, [EDI+0x36C]` before calling FireWeapon. | |
| pub active_weapon_entry: *mut WeaponEntry, | |
| /// 0x370β0x3AF: Unknown (rope anchor, weapon-specific data, etc.) | |
| pub _unknown_370: [u8; 0x3B0 - 0x370], | |
| /// 0x3B0: Streaming sound handle. Nonzero when a worm sound effect | |
| /// (e.g., weapon charge-up) is actively playing. PlayWormSound stores the | |
| /// new handle here; StopWormSound clears it. | |
| pub sound_handle: i32, | |
| /// 0x3B4: Secondary sound handle, used by FUN_00515020 (teleport/weapon sounds). | |
| /// Same stop/play semantics as `sound_handle` but a separate channel. | |
| pub sound_handle_2: i32, | |
| /// 0x3B8β0x3DB: Unknown | |
| pub _unknown_3b8: [u8; 0x3DC - 0x3B8], | |
| /// 0x3DC: Facing direction. -1 = facing left, +1 = facing right. | |
| pub facing_direction: i32, | |
| /// 0x3E0β0x3E3: Unknown | |
| pub _unknown_3e0: u32, | |
| /// 0x3E4: Input: aim up key held (nonzero = adjusting aim upward). | |
| pub input_aim_up: u32, | |
| /// 0x3E8: Input: aim down key held (nonzero = adjusting aim downward). | |
| pub input_aim_down: u32, | |
| /// 0x3EC: Input: move left key held (nonzero = worm walking left). | |
| pub input_move_left: u32, | |
| /// 0x3F0: Input: move right key held (nonzero = worm walking right). | |
| pub input_move_right: u32, | |
| /// 0x3F4β0x3FB: Unknown | |
| pub _unknown_3f4: [u8; 0x3FC - 0x3F4], | |
| } | |
| const _: () = assert!(core::mem::size_of::<CTaskWorm>() == 0x3FC); | |
| // Generate typed vtable method wrappers: handle_message(), on_contact_entity(), etc. | |
| bind_CTaskWormVTable!(CTaskWorm, base.base.vtable); | |
| impl CTaskWorm { | |
| /// Returns the worm's current state code (lives at offset +0x44, inside | |
| /// `base.subclass_data`). See [`WormState`] for known values. | |
| pub fn state(&self) -> u32 { | |
| u32::from_ne_bytes(self.base.subclass_data[0x14..0x18].try_into().unwrap()) | |
| } | |
| pub fn is_in_state(&self, state: WormState) -> bool { | |
| self.state() == state as u32 | |
| } | |
| // Weapon fire dispatch state: | |
| // - Fire type/subtypes live in the WeaponEntry (via active_weapon_entry at +0x36C) | |
| // - Completion flag lives in CGameTask.subclass_data[12] (this object, at +0x3C) | |
| /// Weapon fire completion flag at CGameTask+0x3C (subclass_data[12]). | |
| /// Set to 0 before FireWeapon dispatch, 1 after. | |
| pub fn fire_complete(&self) -> i32 { | |
| i32::from_ne_bytes(self.base.subclass_data[12..16].try_into().unwrap()) | |
| } | |
| /// Set the weapon fire completion flag. | |
| pub fn set_fire_complete(&mut self, value: i32) { | |
| self.base.subclass_data[12..16].copy_from_slice(&value.to_ne_bytes()); | |
| } | |
| // vtable() method is now provided by bind_CTaskWormVTable! macro above. | |
| } | |
| // ββ Snapshot impl ββββββββββββββββββββββββββββββββββββββββββ | |
| impl crate::snapshot::Snapshot for CTaskWorm { | |
| unsafe fn write_snapshot( | |
| &self, | |
| w: &mut dyn core::fmt::Write, | |
| indent: usize, | |
| ) -> core::fmt::Result { | |
| use crate::snapshot::{write_indent, write_raw_region}; | |
| let i = indent; | |
| let b = &self.base; // CGameTask | |
| write_indent(w, i)?; | |
| writeln!(w, "pos = ({}, {})", b.pos_x, b.pos_y)?; | |
| write_indent(w, i)?; | |
| writeln!(w, "speed = ({}, {})", b.speed_x, b.speed_y)?; | |
| write_indent(w, i)?; | |
| writeln!(w, "angle = {}", b.angle)?; | |
| write_indent(w, i)?; | |
| writeln!(w, "team_index = {}", self.team_index)?; | |
| write_indent(w, i)?; | |
| writeln!(w, "worm_index = {}", self.worm_index)?; | |
| write_indent(w, i)?; | |
| writeln!(w, "slot_id = {}", self.slot_id)?; | |
| write_indent(w, i)?; | |
| writeln!(w, "selected_weapon = {:?}", self.selected_weapon)?; | |
| write_indent(w, i)?; | |
| writeln!(w, "facing = {}", self.facing_direction)?; | |
| write_indent(w, i)?; | |
| writeln!(w, "aim_angle = 0x{:08X}", self.aim_angle)?; | |
| write_indent(w, i)?; | |
| write!(w, "spawn_params =")?; | |
| for v in &self.spawn_params { | |
| write!(w, " {:08X}", v)?; | |
| } | |
| writeln!(w)?; | |
| // Raw dump of unknown regions | |
| write_indent(w, i)?; | |
| writeln!(w, "_unknown_1f0 ({} bytes):", self._unknown_1f0.len())?; | |
| write_raw_region( | |
| w, | |
| self._unknown_1f0.as_ptr(), | |
| self._unknown_1f0.len(), | |
| i + 1, | |
| )?; | |
| write_indent(w, i)?; | |
| writeln!(w, "_unknown_28c ({} bytes):", self._unknown_28c.len())?; | |
| write_raw_region( | |
| w, | |
| self._unknown_28c.as_ptr(), | |
| self._unknown_28c.len(), | |
| i + 1, | |
| )?; | |
| write_indent(w, i)?; | |
| writeln!(w, "_unknown_370 ({} bytes):", self._unknown_370.len())?; | |
| write_raw_region( | |
| w, | |
| self._unknown_370.as_ptr(), | |
| self._unknown_370.len(), | |
| i + 1, | |
| )?; | |
| Ok(()) | |
| } | |
| } | |