feat: add typed vtables to all CTask subclasses
Browse filesExtend the generic CTask<V>/CGameTask<V> vtable pattern to every task class:
New vtable definitions (via #[vtable] macro, with handle_message + process_frame):
- CTaskTurnGameVTable (0x669F70, 12 slots)
- CTaskFilterVTable (0x669DAC, 12 slots)
- CTaskCloudVTable (0x669D38, 12 slots)
- CTaskFireVTable (0x669DD8, 12 slots)
- CTaskMissileVTable (0x664438, 12 slots)
- CTaskArrowVTable (0x664198, 12 slots)
- CTaskMineVTable (0x6643E8, 12 slots)
- CTaskOilDrumVTable (0x664338, 12 slots)
- CTaskCrateVTable (0x664298, 12 slots)
Each struct now uses typed base: CTask<*const XxxVTable> or
CGameTask<*const XxxVTable>, with bind macro for method dispatch.
CGameTask made generic (V parameter with *const c_void default).
CTaskWorm updated to use CGameTask<*const CTaskWormVTable>.
FieldRegistry derive macro: improved generic substitution to handle
nested types like CTask<V> (recursive type AST walking).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- crates/openwa-core/src/task/arrow.rs +20 -1
- crates/openwa-core/src/task/cloud.rs +19 -1
- crates/openwa-core/src/task/filter.rs +20 -1
- crates/openwa-core/src/task/fire.rs +19 -1
- crates/openwa-core/src/task/game_task.rs +2 -2
- crates/openwa-core/src/task/mine_oil_drum.rs +39 -2
- crates/openwa-core/src/task/missile.rs +20 -1
- crates/openwa-core/src/task/mod.rs +8 -8
- crates/openwa-core/src/task/supply_crate.rs +20 -1
- crates/openwa-core/src/task/turn_game.rs +20 -1
- crates/openwa-core/src/task/worm.rs +5 -9
- crates/openwa-derive/src/lib.rs +59 -17
- crates/openwa-wormkit/src/replacements/weapon.rs +5 -5
|
@@ -1,3 +1,4 @@
|
|
|
|
|
| 1 |
use super::game_task::CGameTask;
|
| 2 |
use crate::FieldRegistry;
|
| 3 |
|
|
@@ -8,6 +9,21 @@ crate::define_addresses! {
|
|
| 8 |
}
|
| 9 |
}
|
| 10 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
/// Arrow/bullet projectile entity (Shotgun, Longbow).
|
| 12 |
///
|
| 13 |
/// Extends CGameTask (0xFC bytes). Allocated size: 0x168 bytes.
|
|
@@ -20,10 +36,13 @@ crate::define_addresses! {
|
|
| 20 |
#[repr(C)]
|
| 21 |
pub struct CTaskArrow {
|
| 22 |
/// 0x00–0xFB: CGameTask base (pos at 0x84/0x88, speed at 0x90/0x94).
|
| 23 |
-
pub base: CGameTask,
|
| 24 |
|
| 25 |
/// 0xFC–0x167: Arrow-specific fields. Layout unknown.
|
| 26 |
pub _unknown_fc: [u8; 0x168 - 0xFC],
|
| 27 |
}
|
| 28 |
|
| 29 |
const _: () = assert!(core::mem::size_of::<CTaskArrow>() == 0x168);
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
use super::base::CTask;
|
| 2 |
use super::game_task::CGameTask;
|
| 3 |
use crate::FieldRegistry;
|
| 4 |
|
|
|
|
| 9 |
}
|
| 10 |
}
|
| 11 |
|
| 12 |
+
/// CTaskArrow vtable — 12 slots. Extends CGameTask vtable with arrow behavior.
|
| 13 |
+
///
|
| 14 |
+
/// Vtable at Ghidra 0x664198.
|
| 15 |
+
#[openwa_core::vtable(size = 12, va = 0x0066_4198, class = "CTaskArrow")]
|
| 16 |
+
pub struct CTaskArrowVTable {
|
| 17 |
+
/// HandleMessage — processes arrow messages.
|
| 18 |
+
/// thiscall + 4 stack params, RET 0x10.
|
| 19 |
+
#[slot(2)]
|
| 20 |
+
pub handle_message: fn(this: *mut CTaskArrow, sender: *mut CTask, msg_type: u32, size: u32, data: *const u8),
|
| 21 |
+
/// ProcessFrame — per-frame arrow update.
|
| 22 |
+
/// thiscall + 1 stack param (flags), RET 0x4.
|
| 23 |
+
#[slot(7)]
|
| 24 |
+
pub process_frame: fn(this: *mut CTaskArrow, flags: u32),
|
| 25 |
+
}
|
| 26 |
+
|
| 27 |
/// Arrow/bullet projectile entity (Shotgun, Longbow).
|
| 28 |
///
|
| 29 |
/// Extends CGameTask (0xFC bytes). Allocated size: 0x168 bytes.
|
|
|
|
| 36 |
#[repr(C)]
|
| 37 |
pub struct CTaskArrow {
|
| 38 |
/// 0x00–0xFB: CGameTask base (pos at 0x84/0x88, speed at 0x90/0x94).
|
| 39 |
+
pub base: CGameTask<*const CTaskArrowVTable>,
|
| 40 |
|
| 41 |
/// 0xFC–0x167: Arrow-specific fields. Layout unknown.
|
| 42 |
pub _unknown_fc: [u8; 0x168 - 0xFC],
|
| 43 |
}
|
| 44 |
|
| 45 |
const _: () = assert!(core::mem::size_of::<CTaskArrow>() == 0x168);
|
| 46 |
+
|
| 47 |
+
// Generate typed vtable method wrappers: handle_message(), process_frame().
|
| 48 |
+
bind_CTaskArrowVTable!(CTaskArrow, base.base.vtable);
|
|
@@ -10,6 +10,21 @@ crate::define_addresses! {
|
|
| 10 |
}
|
| 11 |
}
|
| 12 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 13 |
/// Airstrike / weather cloud task.
|
| 14 |
///
|
| 15 |
/// Extends CTask directly (not CGameTask). Clouds drift horizontally with wind,
|
|
@@ -30,7 +45,7 @@ crate::define_addresses! {
|
|
| 30 |
#[repr(C)]
|
| 31 |
pub struct CTaskCloud {
|
| 32 |
/// 0x00–0x2F: CTask base
|
| 33 |
-
pub base: CTask,
|
| 34 |
/// 0x30: Parallax scroll layer depth (Fixed; 0x190000 = 25.0 at spawn,
|
| 35 |
/// decrements by 1 each cloud spawned in a batch)
|
| 36 |
pub layer_depth: Fixed,
|
|
@@ -55,3 +70,6 @@ pub struct CTaskCloud {
|
|
| 55 |
}
|
| 56 |
|
| 57 |
const _: () = assert!(core::mem::size_of::<CTaskCloud>() == 0x74);
|
|
|
|
|
|
|
|
|
|
|
|
| 10 |
}
|
| 11 |
}
|
| 12 |
|
| 13 |
+
/// CTaskCloud vtable — 12 slots. Extends CTask base (8 slots) with cloud behavior.
|
| 14 |
+
///
|
| 15 |
+
/// Vtable at Ghidra 0x669D38.
|
| 16 |
+
#[openwa_core::vtable(size = 12, va = 0x0066_9D38, class = "CTaskCloud")]
|
| 17 |
+
pub struct CTaskCloudVTable {
|
| 18 |
+
/// HandleMessage — processes cloud messages (wind updates, render).
|
| 19 |
+
/// thiscall + 4 stack params, RET 0x10.
|
| 20 |
+
#[slot(2)]
|
| 21 |
+
pub handle_message: fn(this: *mut CTaskCloud, sender: *mut CTask, msg_type: u32, size: u32, data: *const u8),
|
| 22 |
+
/// ProcessFrame — per-frame cloud update.
|
| 23 |
+
/// thiscall + 1 stack param (flags), RET 0x4.
|
| 24 |
+
#[slot(7)]
|
| 25 |
+
pub process_frame: fn(this: *mut CTaskCloud, flags: u32),
|
| 26 |
+
}
|
| 27 |
+
|
| 28 |
/// Airstrike / weather cloud task.
|
| 29 |
///
|
| 30 |
/// Extends CTask directly (not CGameTask). Clouds drift horizontally with wind,
|
|
|
|
| 45 |
#[repr(C)]
|
| 46 |
pub struct CTaskCloud {
|
| 47 |
/// 0x00–0x2F: CTask base
|
| 48 |
+
pub base: CTask<*const CTaskCloudVTable>,
|
| 49 |
/// 0x30: Parallax scroll layer depth (Fixed; 0x190000 = 25.0 at spawn,
|
| 50 |
/// decrements by 1 each cloud spawned in a batch)
|
| 51 |
pub layer_depth: Fixed,
|
|
|
|
| 70 |
}
|
| 71 |
|
| 72 |
const _: () = assert!(core::mem::size_of::<CTaskCloud>() == 0x74);
|
| 73 |
+
|
| 74 |
+
// Generate typed vtable method wrappers: handle_message(), process_frame().
|
| 75 |
+
bind_CTaskCloudVTable!(CTaskCloud, base.vtable);
|
|
@@ -9,6 +9,22 @@ crate::define_addresses! {
|
|
| 9 |
}
|
| 10 |
}
|
| 11 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 12 |
/// Message-subscription filter task — routes messages selectively to child tasks.
|
| 13 |
///
|
| 14 |
/// CTaskFilter is a CTask subclass that overrides HandleMessage to only forward
|
|
@@ -49,7 +65,7 @@ pub struct CTaskFilter {
|
|
| 49 |
/// - CTask+0x18 (`_unknown_18`): set to 0
|
| 50 |
/// - CTask+0x1C (`_unknown_1c`): set to `init_val_1c` constructor param
|
| 51 |
/// - CTask+0x20 (`_unknown_20`): set to 7 (task type / mode constant)
|
| 52 |
-
pub base: CTask,
|
| 53 |
/// 0x30–0x93: Boolean subscription table, indexed by message type ID (0–99).
|
| 54 |
///
|
| 55 |
/// `subscription_table[id] != 0` means this filter will forward messages of
|
|
@@ -61,3 +77,6 @@ pub struct CTaskFilter {
|
|
| 61 |
}
|
| 62 |
|
| 63 |
const _: () = assert!(core::mem::size_of::<CTaskFilter>() == 0xB4);
|
|
|
|
|
|
|
|
|
|
|
|
| 9 |
}
|
| 10 |
}
|
| 11 |
|
| 12 |
+
/// CTaskFilter vtable — 12 slots. Extends CTask base (8 slots) with filter behavior.
|
| 13 |
+
///
|
| 14 |
+
/// Vtable at Ghidra 0x669DAC. Slot 2 (HandleMessage) checks the subscription
|
| 15 |
+
/// table before forwarding messages to children.
|
| 16 |
+
#[openwa_core::vtable(size = 12, va = 0x0066_9DAC, class = "CTaskFilter")]
|
| 17 |
+
pub struct CTaskFilterVTable {
|
| 18 |
+
/// HandleMessage — checks subscription table, forwards matching messages.
|
| 19 |
+
/// thiscall + 4 stack params, RET 0x10.
|
| 20 |
+
#[slot(2)]
|
| 21 |
+
pub handle_message: fn(this: *mut CTaskFilter, sender: *mut CTask, msg_type: u32, size: u32, data: *const u8),
|
| 22 |
+
/// ProcessFrame — per-frame filter update.
|
| 23 |
+
/// thiscall + 1 stack param (flags), RET 0x4.
|
| 24 |
+
#[slot(7)]
|
| 25 |
+
pub process_frame: fn(this: *mut CTaskFilter, flags: u32),
|
| 26 |
+
}
|
| 27 |
+
|
| 28 |
/// Message-subscription filter task — routes messages selectively to child tasks.
|
| 29 |
///
|
| 30 |
/// CTaskFilter is a CTask subclass that overrides HandleMessage to only forward
|
|
|
|
| 65 |
/// - CTask+0x18 (`_unknown_18`): set to 0
|
| 66 |
/// - CTask+0x1C (`_unknown_1c`): set to `init_val_1c` constructor param
|
| 67 |
/// - CTask+0x20 (`_unknown_20`): set to 7 (task type / mode constant)
|
| 68 |
+
pub base: CTask<*const CTaskFilterVTable>,
|
| 69 |
/// 0x30–0x93: Boolean subscription table, indexed by message type ID (0–99).
|
| 70 |
///
|
| 71 |
/// `subscription_table[id] != 0` means this filter will forward messages of
|
|
|
|
| 77 |
}
|
| 78 |
|
| 79 |
const _: () = assert!(core::mem::size_of::<CTaskFilter>() == 0xB4);
|
| 80 |
+
|
| 81 |
+
// Generate typed vtable method wrappers: handle_message(), process_frame().
|
| 82 |
+
bind_CTaskFilterVTable!(CTaskFilter, base.vtable);
|
|
@@ -9,6 +9,21 @@ crate::define_addresses! {
|
|
| 9 |
}
|
| 10 |
}
|
| 11 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 12 |
/// Fire/flame entity task.
|
| 13 |
///
|
| 14 |
/// Extends CTask (not CGameTask) — no physics body.
|
|
@@ -24,7 +39,7 @@ crate::define_addresses! {
|
|
| 24 |
#[repr(C)]
|
| 25 |
pub struct CTaskFire {
|
| 26 |
/// 0x00-0x2F: CTask base
|
| 27 |
-
pub base: CTask,
|
| 28 |
/// 0x30: spread counter (incremented while fire is spreading)
|
| 29 |
pub spread_counter: i32,
|
| 30 |
/// 0x34: frame countdown; starts at 0xFFFF, decrements each ProcessFrame
|
|
@@ -54,3 +69,6 @@ pub struct CTaskFire {
|
|
| 54 |
}
|
| 55 |
|
| 56 |
const _: () = assert!(core::mem::size_of::<CTaskFire>() == 0xD8);
|
|
|
|
|
|
|
|
|
|
|
|
| 9 |
}
|
| 10 |
}
|
| 11 |
|
| 12 |
+
/// CTaskFire vtable — 12 slots. Extends CTask base (8 slots) with fire behavior.
|
| 13 |
+
///
|
| 14 |
+
/// Vtable at Ghidra 0x669DD8.
|
| 15 |
+
#[openwa_core::vtable(size = 12, va = 0x0066_9DD8, class = "CTaskFire")]
|
| 16 |
+
pub struct CTaskFireVTable {
|
| 17 |
+
/// HandleMessage — processes fire messages.
|
| 18 |
+
/// thiscall + 4 stack params, RET 0x10.
|
| 19 |
+
#[slot(2)]
|
| 20 |
+
pub handle_message: fn(this: *mut CTaskFire, sender: *mut CTask, msg_type: u32, size: u32, data: *const u8),
|
| 21 |
+
/// ProcessFrame — per-frame fire update (countdown, spread, damage).
|
| 22 |
+
/// thiscall + 1 stack param (flags), RET 0x4.
|
| 23 |
+
#[slot(7)]
|
| 24 |
+
pub process_frame: fn(this: *mut CTaskFire, flags: u32),
|
| 25 |
+
}
|
| 26 |
+
|
| 27 |
/// Fire/flame entity task.
|
| 28 |
///
|
| 29 |
/// Extends CTask (not CGameTask) — no physics body.
|
|
|
|
| 39 |
#[repr(C)]
|
| 40 |
pub struct CTaskFire {
|
| 41 |
/// 0x00-0x2F: CTask base
|
| 42 |
+
pub base: CTask<*const CTaskFireVTable>,
|
| 43 |
/// 0x30: spread counter (incremented while fire is spreading)
|
| 44 |
pub spread_counter: i32,
|
| 45 |
/// 0x34: frame countdown; starts at 0xFFFF, decrements each ProcessFrame
|
|
|
|
| 69 |
}
|
| 70 |
|
| 71 |
const _: () = assert!(core::mem::size_of::<CTaskFire>() == 0xD8);
|
| 72 |
+
|
| 73 |
+
// Generate typed vtable method wrappers: handle_message(), process_frame().
|
| 74 |
+
bind_CTaskFireVTable!(CTaskFire, base.vtable);
|
|
@@ -31,9 +31,9 @@ pub const CGAMETASK_SOUND_EMITTER_VT: u32 = SOUND_EMITTER_VTABLE;
|
|
| 31 |
/// Additional vtable (12 methods at offsets 0x1C-0x48 in vtable)
|
| 32 |
#[derive(FieldRegistry)]
|
| 33 |
#[repr(C)]
|
| 34 |
-
pub struct CGameTask {
|
| 35 |
/// 0x00-0x2F: Base CTask fields
|
| 36 |
-
pub base: CTask,
|
| 37 |
/// 0x30-0x83: Subclass-specific data (84 bytes). Each CGameTask derivative
|
| 38 |
/// uses this region differently:
|
| 39 |
/// - CTaskWorm: weapon fire state (+0x30 type, +0x34/+0x38 subtypes, +0x3C flag)
|
|
|
|
| 31 |
/// Additional vtable (12 methods at offsets 0x1C-0x48 in vtable)
|
| 32 |
#[derive(FieldRegistry)]
|
| 33 |
#[repr(C)]
|
| 34 |
+
pub struct CGameTask<V: 'static = *const core::ffi::c_void> {
|
| 35 |
/// 0x00-0x2F: Base CTask fields
|
| 36 |
+
pub base: CTask<V>,
|
| 37 |
/// 0x30-0x83: Subclass-specific data (84 bytes). Each CGameTask derivative
|
| 38 |
/// uses this region differently:
|
| 39 |
/// - CTaskWorm: weapon fire state (+0x30 type, +0x34/+0x38 subtypes, +0x3C flag)
|
|
@@ -1,3 +1,4 @@
|
|
|
|
|
| 1 |
use super::game_task::CGameTask;
|
| 2 |
use crate::FieldRegistry;
|
| 3 |
|
|
@@ -15,6 +16,36 @@ crate::define_addresses! {
|
|
| 15 |
}
|
| 16 |
}
|
| 17 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 18 |
/// Land mine entity task.
|
| 19 |
///
|
| 20 |
/// Extends CGameTask (0xFC bytes). Mines sit on the terrain and arm after
|
|
@@ -29,7 +60,7 @@ crate::define_addresses! {
|
|
| 29 |
#[repr(C)]
|
| 30 |
pub struct CTaskMine {
|
| 31 |
/// 0x00–0xFB: CGameTask base (pos at 0x84/0x88, speed at 0x90/0x94)
|
| 32 |
-
pub base: CGameTask,
|
| 33 |
/// 0xFC–0x10F: Unknown mine flags
|
| 34 |
pub _unknown_fc: [u8; 0x14],
|
| 35 |
/// 0x110: Object pool slot index (assigned from DDGame+0x3600 pool)
|
|
@@ -51,6 +82,9 @@ pub struct CTaskMine {
|
|
| 51 |
|
| 52 |
const _: () = assert!(core::mem::size_of::<CTaskMine>() == 0x128);
|
| 53 |
|
|
|
|
|
|
|
|
|
|
| 54 |
/// Exploding oil drum entity task.
|
| 55 |
///
|
| 56 |
/// Extends CGameTask (0xFC bytes). Oil drums roll on terrain and explode
|
|
@@ -65,7 +99,7 @@ const _: () = assert!(core::mem::size_of::<CTaskMine>() == 0x128);
|
|
| 65 |
#[repr(C)]
|
| 66 |
pub struct CTaskOilDrum {
|
| 67 |
/// 0x00–0xFB: CGameTask base (pos at 0x84/0x88, speed at 0x90/0x94)
|
| 68 |
-
pub base: CGameTask,
|
| 69 |
/// 0xFC: Triggered flag — set to 1 on first impact, starts smoke/fire
|
| 70 |
pub triggered: u32,
|
| 71 |
/// 0x100: Object pool slot index
|
|
@@ -80,6 +114,9 @@ pub struct CTaskOilDrum {
|
|
| 80 |
|
| 81 |
const _: () = assert!(core::mem::size_of::<CTaskOilDrum>() == 0x110);
|
| 82 |
|
|
|
|
|
|
|
|
|
|
| 83 |
impl CTaskOilDrum {
|
| 84 |
/// Returns true if the drum is on fire (flag at CGameTask+0xB0, inside _unknown_98).
|
| 85 |
///
|
|
|
|
| 1 |
+
use super::base::CTask;
|
| 2 |
use super::game_task::CGameTask;
|
| 3 |
use crate::FieldRegistry;
|
| 4 |
|
|
|
|
| 16 |
}
|
| 17 |
}
|
| 18 |
|
| 19 |
+
/// CTaskMine vtable — 12 slots. Extends CGameTask vtable with mine behavior.
|
| 20 |
+
///
|
| 21 |
+
/// Vtable at Ghidra 0x6643E8.
|
| 22 |
+
#[openwa_core::vtable(size = 12, va = 0x0066_43E8, class = "CTaskMine")]
|
| 23 |
+
pub struct CTaskMineVTable {
|
| 24 |
+
/// HandleMessage — processes mine messages (arm, trigger, detonate).
|
| 25 |
+
/// thiscall + 4 stack params, RET 0x10.
|
| 26 |
+
#[slot(2)]
|
| 27 |
+
pub handle_message: fn(this: *mut CTaskMine, sender: *mut CTask, msg_type: u32, size: u32, data: *const u8),
|
| 28 |
+
/// ProcessFrame — per-frame mine update.
|
| 29 |
+
/// thiscall + 1 stack param (flags), RET 0x4.
|
| 30 |
+
#[slot(7)]
|
| 31 |
+
pub process_frame: fn(this: *mut CTaskMine, flags: u32),
|
| 32 |
+
}
|
| 33 |
+
|
| 34 |
+
/// CTaskOilDrum vtable — 12 slots. Extends CGameTask vtable with oil drum behavior.
|
| 35 |
+
///
|
| 36 |
+
/// Vtable at Ghidra 0x664338.
|
| 37 |
+
#[openwa_core::vtable(size = 12, va = 0x0066_4338, class = "CTaskOilDrum")]
|
| 38 |
+
pub struct CTaskOilDrumVTable {
|
| 39 |
+
/// HandleMessage — processes oil drum messages.
|
| 40 |
+
/// thiscall + 4 stack params, RET 0x10.
|
| 41 |
+
#[slot(2)]
|
| 42 |
+
pub handle_message: fn(this: *mut CTaskOilDrum, sender: *mut CTask, msg_type: u32, size: u32, data: *const u8),
|
| 43 |
+
/// ProcessFrame — per-frame oil drum update.
|
| 44 |
+
/// thiscall + 1 stack param (flags), RET 0x4.
|
| 45 |
+
#[slot(7)]
|
| 46 |
+
pub process_frame: fn(this: *mut CTaskOilDrum, flags: u32),
|
| 47 |
+
}
|
| 48 |
+
|
| 49 |
/// Land mine entity task.
|
| 50 |
///
|
| 51 |
/// Extends CGameTask (0xFC bytes). Mines sit on the terrain and arm after
|
|
|
|
| 60 |
#[repr(C)]
|
| 61 |
pub struct CTaskMine {
|
| 62 |
/// 0x00–0xFB: CGameTask base (pos at 0x84/0x88, speed at 0x90/0x94)
|
| 63 |
+
pub base: CGameTask<*const CTaskMineVTable>,
|
| 64 |
/// 0xFC–0x10F: Unknown mine flags
|
| 65 |
pub _unknown_fc: [u8; 0x14],
|
| 66 |
/// 0x110: Object pool slot index (assigned from DDGame+0x3600 pool)
|
|
|
|
| 82 |
|
| 83 |
const _: () = assert!(core::mem::size_of::<CTaskMine>() == 0x128);
|
| 84 |
|
| 85 |
+
// Generate typed vtable method wrappers: handle_message(), process_frame().
|
| 86 |
+
bind_CTaskMineVTable!(CTaskMine, base.base.vtable);
|
| 87 |
+
|
| 88 |
/// Exploding oil drum entity task.
|
| 89 |
///
|
| 90 |
/// Extends CGameTask (0xFC bytes). Oil drums roll on terrain and explode
|
|
|
|
| 99 |
#[repr(C)]
|
| 100 |
pub struct CTaskOilDrum {
|
| 101 |
/// 0x00–0xFB: CGameTask base (pos at 0x84/0x88, speed at 0x90/0x94)
|
| 102 |
+
pub base: CGameTask<*const CTaskOilDrumVTable>,
|
| 103 |
/// 0xFC: Triggered flag — set to 1 on first impact, starts smoke/fire
|
| 104 |
pub triggered: u32,
|
| 105 |
/// 0x100: Object pool slot index
|
|
|
|
| 114 |
|
| 115 |
const _: () = assert!(core::mem::size_of::<CTaskOilDrum>() == 0x110);
|
| 116 |
|
| 117 |
+
// Generate typed vtable method wrappers: handle_message(), process_frame().
|
| 118 |
+
bind_CTaskOilDrumVTable!(CTaskOilDrum, base.base.vtable);
|
| 119 |
+
|
| 120 |
impl CTaskOilDrum {
|
| 121 |
/// Returns true if the drum is on fire (flag at CGameTask+0xB0, inside _unknown_98).
|
| 122 |
///
|
|
@@ -1,3 +1,4 @@
|
|
|
|
|
| 1 |
use super::game_task::CGameTask;
|
| 2 |
use crate::fixed::Fixed;
|
| 3 |
use crate::game::weapon::WeaponSpawnData;
|
|
@@ -11,6 +12,21 @@ crate::define_addresses! {
|
|
| 11 |
}
|
| 12 |
}
|
| 13 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 14 |
/// Projectile / missile entity task.
|
| 15 |
///
|
| 16 |
/// Extends CGameTask (0xFC bytes). One instance per airborne projectile
|
|
@@ -32,7 +48,7 @@ crate::define_addresses! {
|
|
| 32 |
#[repr(C)]
|
| 33 |
pub struct CTaskMissile {
|
| 34 |
/// 0x00–0xFB: CGameTask base (pos at 0x84/0x88, speed at 0x90/0x94).
|
| 35 |
-
pub base: CGameTask,
|
| 36 |
|
| 37 |
// ---- 0xFC–0x12F: missile init fields ----
|
| 38 |
/// 0xFC–0x10F: Unknown missile flags and state
|
|
@@ -128,6 +144,9 @@ pub struct CTaskMissile {
|
|
| 128 |
|
| 129 |
const _: () = assert!(core::mem::size_of::<CTaskMissile>() == 0x40C);
|
| 130 |
|
|
|
|
|
|
|
|
|
|
| 131 |
impl CTaskMissile {
|
| 132 |
/// Missile type from `render_data[0x17]` (= weapon_data[0x1A] for single shots).
|
| 133 |
///
|
|
|
|
| 1 |
+
use super::base::CTask;
|
| 2 |
use super::game_task::CGameTask;
|
| 3 |
use crate::fixed::Fixed;
|
| 4 |
use crate::game::weapon::WeaponSpawnData;
|
|
|
|
| 12 |
}
|
| 13 |
}
|
| 14 |
|
| 15 |
+
/// CTaskMissile vtable — 12 slots. Extends CGameTask vtable with missile behavior.
|
| 16 |
+
///
|
| 17 |
+
/// Vtable at Ghidra 0x664438.
|
| 18 |
+
#[openwa_core::vtable(size = 12, va = 0x0066_4438, class = "CTaskMissile")]
|
| 19 |
+
pub struct CTaskMissileVTable {
|
| 20 |
+
/// HandleMessage — processes missile messages.
|
| 21 |
+
/// thiscall + 4 stack params, RET 0x10.
|
| 22 |
+
#[slot(2)]
|
| 23 |
+
pub handle_message: fn(this: *mut CTaskMissile, sender: *mut CTask, msg_type: u32, size: u32, data: *const u8),
|
| 24 |
+
/// ProcessFrame — per-frame missile update (physics, homing, detonation).
|
| 25 |
+
/// thiscall + 1 stack param (flags), RET 0x4.
|
| 26 |
+
#[slot(7)]
|
| 27 |
+
pub process_frame: fn(this: *mut CTaskMissile, flags: u32),
|
| 28 |
+
}
|
| 29 |
+
|
| 30 |
/// Projectile / missile entity task.
|
| 31 |
///
|
| 32 |
/// Extends CGameTask (0xFC bytes). One instance per airborne projectile
|
|
|
|
| 48 |
#[repr(C)]
|
| 49 |
pub struct CTaskMissile {
|
| 50 |
/// 0x00–0xFB: CGameTask base (pos at 0x84/0x88, speed at 0x90/0x94).
|
| 51 |
+
pub base: CGameTask<*const CTaskMissileVTable>,
|
| 52 |
|
| 53 |
// ---- 0xFC–0x12F: missile init fields ----
|
| 54 |
/// 0xFC–0x10F: Unknown missile flags and state
|
|
|
|
| 144 |
|
| 145 |
const _: () = assert!(core::mem::size_of::<CTaskMissile>() == 0x40C);
|
| 146 |
|
| 147 |
+
// Generate typed vtable method wrappers: handle_message(), process_frame().
|
| 148 |
+
bind_CTaskMissileVTable!(CTaskMissile, base.base.vtable);
|
| 149 |
+
|
| 150 |
impl CTaskMissile {
|
| 151 |
/// Missile type from `render_data[0x17]` (= weapon_data[0x1A] for single shots).
|
| 152 |
///
|
|
@@ -13,16 +13,16 @@ pub mod team;
|
|
| 13 |
pub mod turn_game;
|
| 14 |
pub mod worm;
|
| 15 |
|
| 16 |
-
pub use arrow::CTaskArrow;
|
| 17 |
pub use base::{CTask, CTaskBfsIter, SharedDataIter, SharedDataNode, SharedDataTable};
|
| 18 |
-
pub use cloud::CTaskCloud;
|
| 19 |
-
pub use filter::CTaskFilter;
|
| 20 |
-
pub use fire::CTaskFire;
|
| 21 |
pub use game_task::{CGameTask, SoundEmitter, SoundEmitterVTable};
|
| 22 |
-
pub use mine_oil_drum::{CTaskMine, CTaskOilDrum};
|
| 23 |
-
pub use missile::{CTaskMissile, MissileType};
|
| 24 |
pub use overlays::{BungeeTrailTask, WeaponAimTask};
|
| 25 |
-
pub use supply_crate::CTaskCrate;
|
| 26 |
pub use team::{CTaskTeam, CTaskTeamVTable};
|
| 27 |
-
pub use turn_game::{CTaskTurnGame, TurnGameCtx};
|
| 28 |
pub use worm::{CTaskWorm, CTaskWormVTable};
|
|
|
|
| 13 |
pub mod turn_game;
|
| 14 |
pub mod worm;
|
| 15 |
|
| 16 |
+
pub use arrow::{CTaskArrow, CTaskArrowVTable};
|
| 17 |
pub use base::{CTask, CTaskBfsIter, SharedDataIter, SharedDataNode, SharedDataTable};
|
| 18 |
+
pub use cloud::{CTaskCloud, CTaskCloudVTable};
|
| 19 |
+
pub use filter::{CTaskFilter, CTaskFilterVTable};
|
| 20 |
+
pub use fire::{CTaskFire, CTaskFireVTable};
|
| 21 |
pub use game_task::{CGameTask, SoundEmitter, SoundEmitterVTable};
|
| 22 |
+
pub use mine_oil_drum::{CTaskMine, CTaskMineVTable, CTaskOilDrum, CTaskOilDrumVTable};
|
| 23 |
+
pub use missile::{CTaskMissile, CTaskMissileVTable, MissileType};
|
| 24 |
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};
|
|
@@ -1,3 +1,4 @@
|
|
|
|
|
| 1 |
use super::game_task::CGameTask;
|
| 2 |
use crate::FieldRegistry;
|
| 3 |
|
|
@@ -9,6 +10,21 @@ crate::define_addresses! {
|
|
| 9 |
}
|
| 10 |
}
|
| 11 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 12 |
/// Weapon/health/utility crate entity task.
|
| 13 |
///
|
| 14 |
/// Extends CGameTask (0xFC bytes). Crates fall from the sky with a parachute,
|
|
@@ -26,7 +42,7 @@ crate::define_addresses! {
|
|
| 26 |
#[repr(C)]
|
| 27 |
pub struct CTaskCrate {
|
| 28 |
/// 0x00–0xFB: CGameTask base (pos at 0x84/0x88, speed at 0x90/0x94)
|
| 29 |
-
pub base: CGameTask,
|
| 30 |
/// 0xFC: Unknown (zeroed by constructor)
|
| 31 |
pub _unknown_fc: u32,
|
| 32 |
/// 0x100: Object pool slot index (assigned from DDGame+0x3600 pool)
|
|
@@ -57,3 +73,6 @@ pub struct CTaskCrate {
|
|
| 57 |
}
|
| 58 |
|
| 59 |
const _: () = assert!(core::mem::size_of::<CTaskCrate>() == 0x4B0);
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
use super::base::CTask;
|
| 2 |
use super::game_task::CGameTask;
|
| 3 |
use crate::FieldRegistry;
|
| 4 |
|
|
|
|
| 10 |
}
|
| 11 |
}
|
| 12 |
|
| 13 |
+
/// CTaskCrate vtable — 12 slots. Extends CGameTask vtable with crate behavior.
|
| 14 |
+
///
|
| 15 |
+
/// Vtable at Ghidra 0x664298.
|
| 16 |
+
#[openwa_core::vtable(size = 12, va = 0x0066_4298, class = "CTaskCrate")]
|
| 17 |
+
pub struct CTaskCrateVTable {
|
| 18 |
+
/// HandleMessage — processes crate messages (collection, parachute, etc.).
|
| 19 |
+
/// thiscall + 4 stack params, RET 0x10.
|
| 20 |
+
#[slot(2)]
|
| 21 |
+
pub handle_message: fn(this: *mut CTaskCrate, sender: *mut CTask, msg_type: u32, size: u32, data: *const u8),
|
| 22 |
+
/// ProcessFrame — per-frame crate update.
|
| 23 |
+
/// thiscall + 1 stack param (flags), RET 0x4.
|
| 24 |
+
#[slot(7)]
|
| 25 |
+
pub process_frame: fn(this: *mut CTaskCrate, flags: u32),
|
| 26 |
+
}
|
| 27 |
+
|
| 28 |
/// Weapon/health/utility crate entity task.
|
| 29 |
///
|
| 30 |
/// Extends CGameTask (0xFC bytes). Crates fall from the sky with a parachute,
|
|
|
|
| 42 |
#[repr(C)]
|
| 43 |
pub struct CTaskCrate {
|
| 44 |
/// 0x00–0xFB: CGameTask base (pos at 0x84/0x88, speed at 0x90/0x94)
|
| 45 |
+
pub base: CGameTask<*const CTaskCrateVTable>,
|
| 46 |
/// 0xFC: Unknown (zeroed by constructor)
|
| 47 |
pub _unknown_fc: u32,
|
| 48 |
/// 0x100: Object pool slot index (assigned from DDGame+0x3600 pool)
|
|
|
|
| 73 |
}
|
| 74 |
|
| 75 |
const _: () = assert!(core::mem::size_of::<CTaskCrate>() == 0x4B0);
|
| 76 |
+
|
| 77 |
+
// Generate typed vtable method wrappers: handle_message(), process_frame().
|
| 78 |
+
bind_CTaskCrateVTable!(CTaskCrate, base.base.vtable);
|
|
@@ -89,6 +89,22 @@ pub struct TurnGameCtx {
|
|
| 89 |
|
| 90 |
const _: () = assert!(core::mem::size_of::<TurnGameCtx>() == 0xAC);
|
| 91 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 92 |
/// Root turn-controller task — one instance per game, parent of the entire entity tree.
|
| 93 |
///
|
| 94 |
/// Every worm, team, projectile, and environment task is a child (direct or indirect)
|
|
@@ -112,7 +128,7 @@ const _: () = assert!(core::mem::size_of::<TurnGameCtx>() == 0xAC);
|
|
| 112 |
#[repr(C)]
|
| 113 |
pub struct CTaskTurnGame {
|
| 114 |
/// 0x00-0x2F: CTask base (vtable, parent, children, shared_data, ddgame, …)
|
| 115 |
-
pub base: CTask,
|
| 116 |
/// 0x30–0xDB: Embedded `TurnGameCtx` sub-object (0xAC bytes).
|
| 117 |
/// See [`TurnGameCtx`] for field details.
|
| 118 |
pub game_ctx: TurnGameCtx,
|
|
@@ -189,3 +205,6 @@ pub struct CTaskTurnGame {
|
|
| 189 |
}
|
| 190 |
|
| 191 |
const _: () = assert!(core::mem::size_of::<CTaskTurnGame>() == 0x2E0);
|
|
|
|
|
|
|
|
|
|
|
|
| 89 |
|
| 90 |
const _: () = assert!(core::mem::size_of::<TurnGameCtx>() == 0xAC);
|
| 91 |
|
| 92 |
+
/// CTaskTurnGame vtable — 12 slots. Extends CTask base (8 slots) with turn-game behavior.
|
| 93 |
+
///
|
| 94 |
+
/// Vtable at Ghidra 0x669F70. Slot 2 (HandleMessage) is the main turn-flow
|
| 95 |
+
/// dispatcher (30+ message types).
|
| 96 |
+
#[openwa_core::vtable(size = 12, va = 0x0066_9F70, class = "CTaskTurnGame")]
|
| 97 |
+
pub struct CTaskTurnGameVTable {
|
| 98 |
+
/// HandleMessage — processes messages for turn flow control
|
| 99 |
+
/// thiscall + 4 stack params, RET 0x10.
|
| 100 |
+
#[slot(2)]
|
| 101 |
+
pub handle_message: fn(this: *mut CTaskTurnGame, sender: *mut CTask, msg_type: u32, size: u32, data: *const u8),
|
| 102 |
+
/// ProcessFrame — per-frame turn update.
|
| 103 |
+
/// thiscall + 1 stack param (flags), RET 0x4.
|
| 104 |
+
#[slot(7)]
|
| 105 |
+
pub process_frame: fn(this: *mut CTaskTurnGame, flags: u32),
|
| 106 |
+
}
|
| 107 |
+
|
| 108 |
/// Root turn-controller task — one instance per game, parent of the entire entity tree.
|
| 109 |
///
|
| 110 |
/// Every worm, team, projectile, and environment task is a child (direct or indirect)
|
|
|
|
| 128 |
#[repr(C)]
|
| 129 |
pub struct CTaskTurnGame {
|
| 130 |
/// 0x00-0x2F: CTask base (vtable, parent, children, shared_data, ddgame, …)
|
| 131 |
+
pub base: CTask<*const CTaskTurnGameVTable>,
|
| 132 |
/// 0x30–0xDB: Embedded `TurnGameCtx` sub-object (0xAC bytes).
|
| 133 |
/// See [`TurnGameCtx`] for field details.
|
| 134 |
pub game_ctx: TurnGameCtx,
|
|
|
|
| 205 |
}
|
| 206 |
|
| 207 |
const _: () = assert!(core::mem::size_of::<CTaskTurnGame>() == 0x2E0);
|
| 208 |
+
|
| 209 |
+
// Generate typed vtable method wrappers: handle_message(), process_frame(), etc.
|
| 210 |
+
bind_CTaskTurnGameVTable!(CTaskTurnGame, base.vtable);
|
|
@@ -95,7 +95,7 @@ pub struct CTaskWormVTable {
|
|
| 95 |
#[repr(C)]
|
| 96 |
pub struct CTaskWorm {
|
| 97 |
/// 0x00–0xFB: CGameTask base (position, velocity, sound emitter, etc.)
|
| 98 |
-
pub base: CGameTask,
|
| 99 |
|
| 100 |
/// 0xFC: Team index (0-based); 3rd constructor param
|
| 101 |
pub team_index: u32,
|
|
@@ -189,6 +189,9 @@ pub struct CTaskWorm {
|
|
| 189 |
|
| 190 |
const _: () = assert!(core::mem::size_of::<CTaskWorm>() == 0x3FC);
|
| 191 |
|
|
|
|
|
|
|
|
|
|
| 192 |
impl CTaskWorm {
|
| 193 |
/// Returns the worm's current state code (lives at offset +0x44, inside
|
| 194 |
/// `base.subclass_data`).
|
|
@@ -214,14 +217,7 @@ impl CTaskWorm {
|
|
| 214 |
self.base.subclass_data[12..16].copy_from_slice(&value.to_ne_bytes());
|
| 215 |
}
|
| 216 |
|
| 217 |
-
//
|
| 218 |
-
///
|
| 219 |
-
/// # Safety
|
| 220 |
-
/// The vtable pointer at offset 0 must be the genuine CTaskWorm vtable
|
| 221 |
-
/// (0x6644C8 in Ghidra, rebased at runtime).
|
| 222 |
-
pub unsafe fn vtable(&self) -> &'static CTaskWormVTable {
|
| 223 |
-
&*(self.base.base.vtable as *const CTaskWormVTable)
|
| 224 |
-
}
|
| 225 |
}
|
| 226 |
|
| 227 |
// ── Snapshot impl ──────────────────────────────────────────
|
|
|
|
| 95 |
#[repr(C)]
|
| 96 |
pub struct CTaskWorm {
|
| 97 |
/// 0x00–0xFB: CGameTask base (position, velocity, sound emitter, etc.)
|
| 98 |
+
pub base: CGameTask<*const CTaskWormVTable>,
|
| 99 |
|
| 100 |
/// 0xFC: Team index (0-based); 3rd constructor param
|
| 101 |
pub team_index: u32,
|
|
|
|
| 189 |
|
| 190 |
const _: () = assert!(core::mem::size_of::<CTaskWorm>() == 0x3FC);
|
| 191 |
|
| 192 |
+
// Generate typed vtable method wrappers: handle_message(), on_contact_entity(), etc.
|
| 193 |
+
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`).
|
|
|
|
| 217 |
self.base.subclass_data[12..16].copy_from_slice(&value.to_ne_bytes());
|
| 218 |
}
|
| 219 |
|
| 220 |
+
// vtable() method is now provided by bind_CTaskWormVTable! macro above.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 221 |
}
|
| 222 |
|
| 223 |
// ── Snapshot impl ──────────────────────────────────────────
|
|
@@ -586,24 +586,10 @@ pub fn derive_field_registry(input: TokenStream) -> TokenStream {
|
|
| 586 |
let doc = extract_doc_comment(&field.attrs);
|
| 587 |
let field_ty = &field.ty;
|
| 588 |
|
| 589 |
-
//
|
| 590 |
-
//
|
| 591 |
// Rust resolves with defaults applied.
|
| 592 |
-
let size_ty
|
| 593 |
-
if let syn::Type::Path(tp) = field_ty {
|
| 594 |
-
if tp.path.segments.len() == 1 {
|
| 595 |
-
let seg = &tp.path.segments[0];
|
| 596 |
-
if let Some(default) = generic_defaults.get(&seg.ident.to_string()) {
|
| 597 |
-
Box::new(default.clone())
|
| 598 |
-
} else {
|
| 599 |
-
Box::new(field_ty.clone())
|
| 600 |
-
}
|
| 601 |
-
} else {
|
| 602 |
-
Box::new(field_ty.clone())
|
| 603 |
-
}
|
| 604 |
-
} else {
|
| 605 |
-
Box::new(field_ty.clone())
|
| 606 |
-
};
|
| 607 |
|
| 608 |
// Determine ValueKind: check for #[field(kind = "...")] override, else infer
|
| 609 |
let kind = parse_field_kind_attr(&field.attrs)
|
|
@@ -734,3 +720,59 @@ fn value_kind_token(variant: &str) -> proc_macro2::TokenStream {
|
|
| 734 |
let ident = quote::format_ident!("{}", variant);
|
| 735 |
quote! { openwa_core::registry::ValueKind::#ident }
|
| 736 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 586 |
let doc = extract_doc_comment(&field.attrs);
|
| 587 |
let field_ty = &field.ty;
|
| 588 |
|
| 589 |
+
// Substitute generic type params (e.g., `V` or `CTask<V>`) with their
|
| 590 |
+
// defaults for size_of. offset_of uses the bare struct name which
|
| 591 |
// Rust resolves with defaults applied.
|
| 592 |
+
let size_ty = substitute_generics(field_ty, &generic_defaults);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 593 |
|
| 594 |
// Determine ValueKind: check for #[field(kind = "...")] override, else infer
|
| 595 |
let kind = parse_field_kind_attr(&field.attrs)
|
|
|
|
| 720 |
let ident = quote::format_ident!("{}", variant);
|
| 721 |
quote! { openwa_core::registry::ValueKind::#ident }
|
| 722 |
}
|
| 723 |
+
|
| 724 |
+
/// Recursively substitute generic type parameters with their defaults.
|
| 725 |
+
///
|
| 726 |
+
/// For example, given `CTask<V>` with defaults `{V → *const c_void}`,
|
| 727 |
+
/// produces `CTask<*const c_void>`. Handles nested generics and all
|
| 728 |
+
/// common type forms (paths, pointers, arrays, etc.).
|
| 729 |
+
fn substitute_generics(
|
| 730 |
+
ty: &syn::Type,
|
| 731 |
+
defaults: &std::collections::HashMap<String, syn::Type>,
|
| 732 |
+
) -> syn::Type {
|
| 733 |
+
if defaults.is_empty() {
|
| 734 |
+
return ty.clone();
|
| 735 |
+
}
|
| 736 |
+
|
| 737 |
+
match ty {
|
| 738 |
+
syn::Type::Path(tp) => {
|
| 739 |
+
// Check if the entire path is a generic param name (e.g., `V`)
|
| 740 |
+
if tp.qself.is_none() && tp.path.segments.len() == 1 {
|
| 741 |
+
let seg = &tp.path.segments[0];
|
| 742 |
+
if seg.arguments.is_empty() {
|
| 743 |
+
if let Some(default) = defaults.get(&seg.ident.to_string()) {
|
| 744 |
+
return default.clone();
|
| 745 |
+
}
|
| 746 |
+
}
|
| 747 |
+
}
|
| 748 |
+
// Recursively substitute within generic arguments (e.g., CTask<V>)
|
| 749 |
+
let mut tp = tp.clone();
|
| 750 |
+
for seg in &mut tp.path.segments {
|
| 751 |
+
if let syn::PathArguments::AngleBracketed(ref mut args) = seg.arguments {
|
| 752 |
+
for arg in &mut args.args {
|
| 753 |
+
if let syn::GenericArgument::Type(ref mut inner_ty) = arg {
|
| 754 |
+
*inner_ty = substitute_generics(inner_ty, defaults);
|
| 755 |
+
}
|
| 756 |
+
}
|
| 757 |
+
}
|
| 758 |
+
}
|
| 759 |
+
syn::Type::Path(tp)
|
| 760 |
+
}
|
| 761 |
+
syn::Type::Ptr(ptr) => {
|
| 762 |
+
let mut ptr = ptr.clone();
|
| 763 |
+
ptr.elem = Box::new(substitute_generics(&ptr.elem, defaults));
|
| 764 |
+
syn::Type::Ptr(ptr)
|
| 765 |
+
}
|
| 766 |
+
syn::Type::Array(arr) => {
|
| 767 |
+
let mut arr = arr.clone();
|
| 768 |
+
arr.elem = Box::new(substitute_generics(&arr.elem, defaults));
|
| 769 |
+
syn::Type::Array(arr)
|
| 770 |
+
}
|
| 771 |
+
syn::Type::Reference(r) => {
|
| 772 |
+
let mut r = r.clone();
|
| 773 |
+
r.elem = Box::new(substitute_generics(&r.elem, defaults));
|
| 774 |
+
syn::Type::Reference(r)
|
| 775 |
+
}
|
| 776 |
+
_ => ty.clone(),
|
| 777 |
+
}
|
| 778 |
+
}
|
|
@@ -408,7 +408,7 @@ unsafe fn fire_weapon_special(
|
|
| 408 |
unsafe fn lookup_team_task(worm: *const CTaskWorm) -> *mut openwa_core::task::CTaskTeam {
|
| 409 |
use openwa_core::task::SharedDataTable;
|
| 410 |
|
| 411 |
-
let task = &(*worm).base.base;
|
| 412 |
let table = SharedDataTable::from_task(task);
|
| 413 |
// CTaskTeam is registered with key (0, 0x14) in SharedData
|
| 414 |
table.lookup(0, 0x14) as *mut openwa_core::task::CTaskTeam
|
|
@@ -432,7 +432,7 @@ unsafe fn fire_send_team_message(worm: *mut CTaskWorm, msg_type: u32) {
|
|
| 432 |
buf[0..4].copy_from_slice(&team_index.to_ne_bytes());
|
| 433 |
|
| 434 |
(*team).handle_message(
|
| 435 |
-
&raw mut (*worm).base.base,
|
| 436 |
msg_type,
|
| 437 |
4,
|
| 438 |
buf.as_ptr(),
|
|
@@ -453,7 +453,7 @@ unsafe fn fire_select_worm(worm: *mut CTaskWorm) {
|
|
| 453 |
buf[4..8].copy_from_slice(&(*worm).team_index.to_ne_bytes());
|
| 454 |
|
| 455 |
(*team).handle_message(
|
| 456 |
-
&raw mut (*worm).base.base,
|
| 457 |
0x5D,
|
| 458 |
0x408,
|
| 459 |
buf.as_ptr(),
|
|
@@ -726,7 +726,7 @@ unsafe extern "cdecl" fn create_weapon_projectile_impl(
|
|
| 726 |
}
|
| 727 |
|
| 728 |
// Look up parent CTaskTurnGame via SharedData (key_esi=0, key_edi=0x19)
|
| 729 |
-
let table = SharedDataTable::from_task(task);
|
| 730 |
let parent = table.lookup(0, 0x19);
|
| 731 |
|
| 732 |
// Allocate CTaskMissile (0x40C bytes)
|
|
@@ -900,7 +900,7 @@ unsafe extern "cdecl" fn create_arrow_impl(
|
|
| 900 |
}
|
| 901 |
|
| 902 |
// Look up parent CTaskTurnGame via SharedData (key 0, 0x19)
|
| 903 |
-
let table = SharedDataTable::from_task(task);
|
| 904 |
let parent = table.lookup(0, 0x19);
|
| 905 |
|
| 906 |
// Allocate CTaskArrow (0x168 bytes)
|
|
|
|
| 408 |
unsafe fn lookup_team_task(worm: *const CTaskWorm) -> *mut openwa_core::task::CTaskTeam {
|
| 409 |
use openwa_core::task::SharedDataTable;
|
| 410 |
|
| 411 |
+
let task = &(*worm).base.base as *const _ as *const openwa_core::task::CTask;
|
| 412 |
let table = SharedDataTable::from_task(task);
|
| 413 |
// CTaskTeam is registered with key (0, 0x14) in SharedData
|
| 414 |
table.lookup(0, 0x14) as *mut openwa_core::task::CTaskTeam
|
|
|
|
| 432 |
buf[0..4].copy_from_slice(&team_index.to_ne_bytes());
|
| 433 |
|
| 434 |
(*team).handle_message(
|
| 435 |
+
&raw mut (*worm).base.base as *mut openwa_core::task::CTask,
|
| 436 |
msg_type,
|
| 437 |
4,
|
| 438 |
buf.as_ptr(),
|
|
|
|
| 453 |
buf[4..8].copy_from_slice(&(*worm).team_index.to_ne_bytes());
|
| 454 |
|
| 455 |
(*team).handle_message(
|
| 456 |
+
&raw mut (*worm).base.base as *mut openwa_core::task::CTask,
|
| 457 |
0x5D,
|
| 458 |
0x408,
|
| 459 |
buf.as_ptr(),
|
|
|
|
| 726 |
}
|
| 727 |
|
| 728 |
// Look up parent CTaskTurnGame via SharedData (key_esi=0, key_edi=0x19)
|
| 729 |
+
let table = SharedDataTable::from_task(task as *const _ as *const openwa_core::task::CTask);
|
| 730 |
let parent = table.lookup(0, 0x19);
|
| 731 |
|
| 732 |
// Allocate CTaskMissile (0x40C bytes)
|
|
|
|
| 900 |
}
|
| 901 |
|
| 902 |
// Look up parent CTaskTurnGame via SharedData (key 0, 0x19)
|
| 903 |
+
let table = SharedDataTable::from_task(task as *const _ as *const openwa_core::task::CTask);
|
| 904 |
let parent = table.lookup(0, 0x19);
|
| 905 |
|
| 906 |
// Allocate CTaskArrow (0x168 bytes)
|