paavohtl Claude Opus 4.6 (1M context) commited on
Commit
2a09823
·
1 Parent(s): 0572750

feat: add WormState enum — typed worm state machine values

Browse files

Add repr(u8) WormState enum with all known states from CTaskWorm::HandleMessage
(0x510B40) decompilation and weapon fire dispatch analysis:
- Idle/Active states (0x64-0x68)
- Weapon fire states (0x6C-0x75: Blowtorch, Bat, Kamikaze, etc.)
- Aiming/charging states (0x77-0x7E)
- Dying/dead states (0x7F-0x8B)

Update CTaskWormVTable::set_state to take WormState parameter.
Replace all hex literals in weapon.rs with enum variants.

Source: Ghidra decompilation of HandleMessage switch cases +
SetState call sites across weapon fire dispatch.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

crates/openwa-core/src/task/mod.rs CHANGED
@@ -25,7 +25,7 @@ pub use overlays::{BungeeTrailTask, WeaponAimTask};
25
  pub use supply_crate::{CTaskCrate, CTaskCrateVTable};
26
  pub use team::{CTaskTeam, CTaskTeamVTable};
27
  pub use turn_game::{CTaskTurnGame, CTaskTurnGameVTable, TurnGameCtx};
28
- pub use worm::{CTaskWorm, CTaskWormVTable};
29
 
30
  // Task trait impls — safe access to CTask base regardless of inheritance depth.
31
  // CTask<V> impl is in base.rs (blanket impl).
 
25
  pub use supply_crate::{CTaskCrate, CTaskCrateVTable};
26
  pub use team::{CTaskTeam, CTaskTeamVTable};
27
  pub use turn_game::{CTaskTurnGame, CTaskTurnGameVTable, TurnGameCtx};
28
+ pub use worm::{CTaskWorm, CTaskWormVTable, WormState};
29
 
30
  // Task trait impls — safe access to CTask base regardless of inheritance depth.
31
  // CTask<V> impl is in base.rs (blanket impl).
crates/openwa-core/src/task/worm.rs CHANGED
@@ -3,6 +3,98 @@ use super::game_task::CGameTask;
3
  use crate::game::weapon::WeaponEntry;
4
  use crate::FieldRegistry;
5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6
  crate::define_addresses! {
7
  class "CTaskWorm" {
8
  /// CTaskWorm vtable
@@ -61,7 +153,7 @@ pub struct CTaskWormVTable {
61
  pub on_killed: fn(this: *mut CTaskWorm),
62
  /// SetState — worm state machine; handles all state transitions
63
  #[slot(14)]
64
- pub set_state: fn(this: *mut CTaskWorm, state: u32),
65
  /// CheckPendingAction — if field +0xBC is set, calls SetState(0x73)
66
  #[slot(15)]
67
  pub check_pending_action: fn(this: *mut CTaskWorm),
@@ -194,10 +286,7 @@ bind_CTaskWormVTable!(CTaskWorm, base.base.vtable);
194
 
195
  impl CTaskWorm {
196
  /// Returns the worm's current state code (lives at offset +0x44, inside
197
- /// `base.subclass_data`).
198
- ///
199
- /// Known states: `0x65`=idle, `0x67`=active turn, `0x7F`=drowning,
200
- /// `0x80`=hurt, `0x81`/`0x86`=dead, `0x87`=dead variant, `0x8B`=unknown.
201
  pub fn state(&self) -> u32 {
202
  unsafe { *((self as *const CTaskWorm as *const u8).add(0x44) as *const u32) }
203
  }
 
3
  use crate::game::weapon::WeaponEntry;
4
  use crate::FieldRegistry;
5
 
6
+ /// Worm state machine states.
7
+ ///
8
+ /// CTaskWorm's `SetState` (vtable slot 14) transitions between these.
9
+ /// The state byte lives at CTaskWorm+0x44 (inside `base.subclass_data`).
10
+ /// Also stored in WormEntry.state in the TeamArenaState.
11
+ ///
12
+ /// States 0x68..=0x8A are the "weapon/action active" range — checked by
13
+ /// `(state - 0x68) < 0x23` in HandleMessage. States 0x80+ are dying/dead.
14
+ /// Names are best guesses from behavioral observation and disassembly.
15
+ ///
16
+ /// Source: CTaskWorm::HandleMessage (0x510B40) decompilation + weapon fire dispatch.
17
+ #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
18
+ #[repr(u8)]
19
+ #[allow(non_camel_case_types)]
20
+ pub enum WormState {
21
+ /// Transitional state checked by CheckWormState0x64 (0x5228D0).
22
+ /// Appears briefly during turn transitions.
23
+ Transitional = 0x64,
24
+ /// Idle — not this worm's turn. Also used by MailMineMole to re-enter idle.
25
+ Idle = 0x65,
26
+ /// Idle variant — grouped with 0x65/0x67 in HandleMessage switch cases.
27
+ IdleVariant_Maybe = 0x66,
28
+ /// Active turn — this worm is currently being controlled.
29
+ Active = 0x67,
30
+ /// Active variant — observed in WormEntry. Start of weapon/action range.
31
+ ActiveVariant_Maybe = 0x68,
32
+ // 0x69–0x6B: unknown (within weapon/action range)
33
+ /// Blowtorch — using the blowtorch weapon.
34
+ Blowtorch = 0x6C,
35
+ /// Baseball Bat — swinging the baseball bat. Frequently checked in HandleMessage.
36
+ BaseballBat = 0x6D,
37
+ /// Kamikaze — performing kamikaze attack.
38
+ Kamikaze = 0x6E,
39
+ // 0x6F: unknown
40
+ /// Dragon Ball — performing dragon ball attack.
41
+ DragonBall = 0x70,
42
+ /// Scales of Justice — using scales of justice.
43
+ ScalesOfJustice = 0x71,
44
+ /// Suicide Bomber — performing suicide bomber attack.
45
+ SuicideBomber = 0x72,
46
+ /// Weapon charging — entered from aiming states (0x7B, 0x7C) before release.
47
+ /// Also set by CheckPendingAction when field +0xBC is nonzero.
48
+ WeaponCharging_Maybe = 0x73,
49
+ /// Teleport cancelled — teleport failed or was denied.
50
+ TeleportCancelled_Maybe = 0x74,
51
+ /// Fire Punch — performing fire punch attack.
52
+ FirePunch = 0x75,
53
+ // 0x76: unknown
54
+ /// Weapon selected — entered via SelectCursor (msg 0x24) from idle/active states.
55
+ /// HandleMessage sets param[0xa7]=-1 when already in this state.
56
+ WeaponSelected_Maybe = 0x77,
57
+ /// Weapon aimed — post-select state. Teleport check accepts this.
58
+ /// Also used for Magic Bullet weapon fire.
59
+ WeaponAimed_Maybe = 0x78,
60
+ // 0x79–0x7A: unknown
61
+ /// Aiming with angle — entered for aimed weapons. Sets angle params.
62
+ /// Teleport check accepts this. Transitions to 0x73 on fire.
63
+ AimingAngle_Maybe = 0x7B,
64
+ /// Rope swinging — IsNotOnRope checks `state != 0x7C`.
65
+ /// Transitions to 0x73 on fire.
66
+ RopeSwinging = 0x7C,
67
+ /// Pre-fire variant — MailMineMole version check uses this.
68
+ /// Transitions to 0x7E or 0x65 depending on FUN_004fb580.
69
+ PreFire_Maybe = 0x7D,
70
+ /// Post-fire / special movement — entered from 0x78 and 0x7D
71
+ /// when FUN_004fb580 returns nonzero.
72
+ PostFire_Maybe = 0x7E,
73
+ /// Drowning — worm fell in water.
74
+ Drowning = 0x7F,
75
+ /// Hurt — worm took damage.
76
+ Hurt = 0x80,
77
+ /// Dead variant 1.
78
+ Dead1 = 0x81,
79
+ /// Dying variant 1 — checked alongside 0x83 in HandleMessage.
80
+ Dying1_Maybe = 0x82,
81
+ /// Dying variant 2 — checked alongside 0x82 in HandleMessage.
82
+ Dying2_Maybe = 0x83,
83
+ // 0x84–0x85: unknown
84
+ /// Dead — set by Surrender (msg 0x29). Frequently checked in HandleMessage.
85
+ Dead = 0x86,
86
+ /// Dead variant 3.
87
+ Dead3 = 0x87,
88
+ /// Unknown — grouped with idle states (0x65/0x66/0x67/0x8B) in HandleMessage.
89
+ Unknown_0x88 = 0x88,
90
+ /// Dying/special animation (from WormEntry state documentation).
91
+ DyingAnimation_Maybe = 0x89,
92
+ // 0x8A: end of weapon/action range
93
+ /// Unknown state checked in CTaskTeam handlers.
94
+ /// Grouped with idle states in HandleMessage switch cases.
95
+ Unknown_0x8B = 0x8B,
96
+ }
97
+
98
  crate::define_addresses! {
99
  class "CTaskWorm" {
100
  /// CTaskWorm vtable
 
153
  pub on_killed: fn(this: *mut CTaskWorm),
154
  /// SetState — worm state machine; handles all state transitions
155
  #[slot(14)]
156
+ pub set_state: fn(this: *mut CTaskWorm, state: WormState),
157
  /// CheckPendingAction — if field +0xBC is set, calls SetState(0x73)
158
  #[slot(15)]
159
  pub check_pending_action: fn(this: *mut CTaskWorm),
 
286
 
287
  impl CTaskWorm {
288
  /// Returns the worm's current state code (lives at offset +0x44, inside
289
+ /// `base.subclass_data`). See [`WormState`] for known values.
 
 
 
290
  pub fn state(&self) -> u32 {
291
  unsafe { *((self as *const CTaskWorm as *const u8).add(0x44) as *const u32) }
292
  }
crates/openwa-wormkit/src/replacements/weapon.rs CHANGED
@@ -22,7 +22,7 @@ use openwa_core::game::weapon::{WeaponEntry, WeaponFireParams, WeaponSpawnData};
22
  use openwa_core::game::Weapon;
23
  use openwa_core::log::log_line;
24
  use openwa_core::task::Task;
25
- use openwa_core::task::worm::CTaskWorm;
26
 
27
  use crate::hook::{self, usercall_trampoline};
28
 
@@ -358,25 +358,25 @@ unsafe fn fire_weapon_special(
358
 
359
  match subtype {
360
  // Blowtorch
361
- 1 => (*worm).set_state(0x6C),
362
  // Pneumatic Drill (pure Rust)
363
  2 => fire_drill(worm, local_struct),
364
  // Girder
365
  3 => call_fire_stdcall3(worm, params_38_ptr, local_struct, rb(0x51E350)),
366
  // Baseball Bat
367
- 4 => (*worm).set_state(0x6D),
368
  // Fire Punch
369
- 5 => (*worm).set_state(0x75),
370
  // Dragon Ball
371
- 6 => (*worm).set_state(0x70),
372
  // Kamikaze
373
- 8 => (*worm).set_state(0x6E),
374
  // Prod (pure Rust)
375
  9 => fire_prod(worm, local_struct),
376
  // Air Strike (EAX=worm)
377
  10 => call_fire_usercall(worm as *const (), worm, rb(0x51E710)),
378
  // Scales of Justice
379
- 11 => (*worm).set_state(0x71),
380
  // Napalm Strike
381
  13 => fire_send_team_message(worm, 0x2B),
382
  // Mail/Mine/Mole (pure Rust)
@@ -387,13 +387,14 @@ unsafe fn fire_weapon_special(
387
  if can_teleport(worm_state) {
388
  call_fire_usercall(worm_state as *const (), worm, rb(0x51EB00));
389
  } else {
390
- (*worm).set_state(0x74);
 
391
  }
392
  }
393
  // Freeze (EAX=worm)
394
  17 => call_fire_usercall(worm as *const (), worm, rb(0x51E920)),
395
  // Suicide Bomber
396
- 18 => (*worm).set_state(0x72),
397
  // Skip Go (pure Rust)
398
  19 => fire_skip_go(worm, entry),
399
  // Surrender (pure Rust)
@@ -403,7 +404,7 @@ unsafe fn fire_weapon_special(
403
  // Jet Pack (EAX=entry)
404
  22 => call_fire_usercall(entry as *const (), worm, rb(0x51EC30)),
405
  // Magic Bullet
406
- 23 => (*worm).set_state(0x78),
407
  // Low Gravity (EAX=entry)
408
  24 => call_fire_usercall(entry as *const (), worm, rb(0x51EA60)),
409
  _ => {}
@@ -412,9 +413,9 @@ unsafe fn fire_weapon_special(
412
 
413
  /// Teleport validity check — pure Rust port of 0x516930.
414
  /// Returns true if the worm's current state allows teleportation.
415
- /// Valid states: 0x78 (idle-aim), 0x7B-0x7D (pre-fire range).
416
  fn can_teleport(state: u32) -> bool {
417
- state == 0x78 || (0x7B..=0x7D).contains(&state)
 
418
  }
419
 
420
  // ── Pure Rust fire handlers (no bridge needed) ──────────────
@@ -538,10 +539,11 @@ unsafe fn fire_mail_mine_mole(worm: *mut CTaskWorm) {
538
 
539
  let should_call_vtable = version < 2
540
  || (version >= 5
541
- && (worm_state == 0x7D || (worm_state == 0x78 && version < 8)));
 
542
 
543
  if should_call_vtable {
544
- (*worm).set_state(0x65);
545
  }
546
 
547
  fire_send_team_message(worm, 0x28);
 
22
  use openwa_core::game::Weapon;
23
  use openwa_core::log::log_line;
24
  use openwa_core::task::Task;
25
+ use openwa_core::task::worm::{CTaskWorm, WormState};
26
 
27
  use crate::hook::{self, usercall_trampoline};
28
 
 
358
 
359
  match subtype {
360
  // Blowtorch
361
+ 1 => (*worm).set_state(WormState::Blowtorch),
362
  // Pneumatic Drill (pure Rust)
363
  2 => fire_drill(worm, local_struct),
364
  // Girder
365
  3 => call_fire_stdcall3(worm, params_38_ptr, local_struct, rb(0x51E350)),
366
  // Baseball Bat
367
+ 4 => (*worm).set_state(WormState::BaseballBat),
368
  // Fire Punch
369
+ 5 => (*worm).set_state(WormState::FirePunch),
370
  // Dragon Ball
371
+ 6 => (*worm).set_state(WormState::DragonBall),
372
  // Kamikaze
373
+ 8 => (*worm).set_state(WormState::Kamikaze),
374
  // Prod (pure Rust)
375
  9 => fire_prod(worm, local_struct),
376
  // Air Strike (EAX=worm)
377
  10 => call_fire_usercall(worm as *const (), worm, rb(0x51E710)),
378
  // Scales of Justice
379
+ 11 => (*worm).set_state(WormState::ScalesOfJustice),
380
  // Napalm Strike
381
  13 => fire_send_team_message(worm, 0x2B),
382
  // Mail/Mine/Mole (pure Rust)
 
387
  if can_teleport(worm_state) {
388
  call_fire_usercall(worm_state as *const (), worm, rb(0x51EB00));
389
  } else {
390
+ // Teleport denied — return to cancelled state
391
+ (*worm).set_state(WormState::TeleportCancelled_Maybe);
392
  }
393
  }
394
  // Freeze (EAX=worm)
395
  17 => call_fire_usercall(worm as *const (), worm, rb(0x51E920)),
396
  // Suicide Bomber
397
+ 18 => (*worm).set_state(WormState::SuicideBomber),
398
  // Skip Go (pure Rust)
399
  19 => fire_skip_go(worm, entry),
400
  // Surrender (pure Rust)
 
404
  // Jet Pack (EAX=entry)
405
  22 => call_fire_usercall(entry as *const (), worm, rb(0x51EC30)),
406
  // Magic Bullet
407
+ 23 => (*worm).set_state(WormState::WeaponAimed_Maybe),
408
  // Low Gravity (EAX=entry)
409
  24 => call_fire_usercall(entry as *const (), worm, rb(0x51EA60)),
410
  _ => {}
 
413
 
414
  /// Teleport validity check — pure Rust port of 0x516930.
415
  /// Returns true if the worm's current state allows teleportation.
 
416
  fn can_teleport(state: u32) -> bool {
417
+ state == WormState::WeaponAimed_Maybe as u32
418
+ || (WormState::AimingAngle_Maybe as u32..=WormState::PreFire_Maybe as u32).contains(&state)
419
  }
420
 
421
  // ── Pure Rust fire handlers (no bridge needed) ──────────────
 
539
 
540
  let should_call_vtable = version < 2
541
  || (version >= 5
542
+ && (worm_state == WormState::PreFire_Maybe as u32
543
+ || (worm_state == WormState::WeaponAimed_Maybe as u32 && version < 8)));
544
 
545
  if should_call_vtable {
546
+ (*worm).set_state(WormState::Idle);
547
  }
548
 
549
  fire_send_team_message(worm, 0x28);