feat: add WormState enum — typed worm state machine values
Browse filesAdd 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:
|
| 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(
|
| 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(
|
| 368 |
// Fire Punch
|
| 369 |
-
5 => (*worm).set_state(
|
| 370 |
// Dragon Ball
|
| 371 |
-
6 => (*worm).set_state(
|
| 372 |
// Kamikaze
|
| 373 |
-
8 => (*worm).set_state(
|
| 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(
|
| 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 |
-
|
|
|
|
| 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(
|
| 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(
|
| 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 ==
|
|
|
|
| 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 ==
|
|
|
|
| 542 |
|
| 543 |
if should_call_vtable {
|
| 544 |
-
(*worm).set_state(
|
| 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);
|