paavohtl Claude Sonnet 4.6 commited on
Commit
f2e565c
·
1 Parent(s): 32d632e

feat: add CTaskFire struct and debug inspector support

Browse files

- address.rs: CTASK_FIRE_VTABLE = 0x00669DD8
- task.rs: CTaskFire (0xD8 bytes, extends CTask) with spawn_x/y, timer,
burn_rate, spread_counter, lifetime, slot_index fields
- app.rs: add CTaskFire to KNOWN_VTABLES; add inspector section showing
key fields (position, timer, burn rate, lifetime, slot)

Vtable labeled in Ghidra: CTaskFire__Init, __Free, __HandleMessage,
__ProcessFrame all renamed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

crates/openwa-core/src/address.rs CHANGED
@@ -73,6 +73,8 @@ pub mod va {
73
  pub const CTASK_CLOUD_VTABLE: u32 = 0x0066_9D38;
74
  /// CTaskSeaBubble vtable - water bubble particle (constructor 0x554FE0)
75
  pub const CTASK_SEA_BUBBLE_VTABLE: u32 = 0x0066_9E88;
 
 
76
  /// Sprite vtable (0x70-byte objects, 8 entries)
77
  pub const SPRITE_VTABLE: u32 = 0x0066_418C;
78
 
 
73
  pub const CTASK_CLOUD_VTABLE: u32 = 0x0066_9D38;
74
  /// CTaskSeaBubble vtable - water bubble particle (constructor 0x554FE0)
75
  pub const CTASK_SEA_BUBBLE_VTABLE: u32 = 0x0066_9E88;
76
+ /// CTaskFire vtable - fire/flame entity (constructor 0x54F4C0, 0xD8 bytes)
77
+ pub const CTASK_FIRE_VTABLE: u32 = 0x0066_9DD8;
78
  /// Sprite vtable (0x70-byte objects, 8 entries)
79
  pub const SPRITE_VTABLE: u32 = 0x0066_418C;
80
 
crates/openwa-core/src/task.rs CHANGED
@@ -566,6 +566,51 @@ impl CTaskOilDrum {
566
  }
567
  }
568
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
569
  // ============================================================
570
  // Derived task overlays — for accessing task-specific fields
571
  // beyond or within CGameTask that differ per task type.
 
566
  }
567
  }
568
 
569
+ /// Fire/flame entity task.
570
+ ///
571
+ /// Extends CTask (not CGameTask) — no physics body.
572
+ /// class_type = 0x18. Allocated 0xD8 bytes.
573
+ /// Constructor: CTaskFire__Constructor (0x54F4C0).
574
+ /// vtable: CTaskFire__vtable (0x00669DD8).
575
+ ///
576
+ /// One CTaskFire is spawned per flame sprite. The `timer` field starts
577
+ /// at 0xFFFF and counts down each frame; when it reaches zero the fire
578
+ /// dies. `lifetime` at +0xB1 is a signed byte: 0xFF (= -1i8) means alive,
579
+ /// 0 means the task is being destroyed.
580
+ #[repr(C)]
581
+ pub struct CTaskFire {
582
+ /// 0x00-0x2F: CTask base
583
+ pub base: CTask,
584
+ /// 0x30: spread counter (incremented while fire is spreading)
585
+ pub spread_counter: i32,
586
+ /// 0x34: frame countdown; starts at 0xFFFF, decrements each ProcessFrame
587
+ pub timer: i32,
588
+ /// 0x38: random seed / initial offset for sprite variation
589
+ pub rand_offset: u32,
590
+ /// 0x3C: burn rate / intensity (higher = more damage per frame)
591
+ pub burn_rate: u32,
592
+ pub _unknown_40: u32,
593
+ /// 0x44: spawn X position (Fixed 16.16)
594
+ pub spawn_x: crate::fixed::Fixed,
595
+ /// 0x48: spawn Y position (Fixed 16.16)
596
+ pub spawn_y: crate::fixed::Fixed,
597
+ pub _unknown_4c: [u8; 0x24],
598
+ /// 0x70: absolute tick (frame counter) when this flame was spawned
599
+ pub spawn_time: u32,
600
+ pub _unknown_74: u32,
601
+ /// 0x78-0xA7: per-frame spawn parameter table (12 DWORDs)
602
+ pub spawn_params: [u32; 12],
603
+ /// 0xA8: slot index in the fire-object pool
604
+ pub slot_index: u32,
605
+ pub _unknown_ac: u32,
606
+ pub _flags_b0: u8,
607
+ /// 0xB1: lifetime byte; -1 (0xFF as i8) = alive, 0 = dying/dead
608
+ pub lifetime: i8,
609
+ pub _unknown_b2: [u8; 0x26],
610
+ }
611
+
612
+ const _: () = assert!(core::mem::size_of::<CTaskFire>() == 0xD8);
613
+
614
  // ============================================================
615
  // Derived task overlays — for accessing task-specific fields
616
  // beyond or within CGameTask that differ per task type.
crates/openwa-debugui/src/app.rs CHANGED
@@ -3,7 +3,7 @@ use openwa_core::address::va;
3
  use openwa_core::ddgame::DDGame;
4
  use openwa_core::ddgame_wrapper::DDGameWrapper;
5
  use openwa_core::rebase::rb;
6
- use openwa_core::task::{CTask, CTaskCloud, CTaskMine, CTaskOilDrum, CTaskWorm};
7
 
8
  use crate::log;
9
 
@@ -25,6 +25,7 @@ const KNOWN_VTABLES: &[(u32, &str)] = &[
25
  (va::CTASK_OILDRUM_VTABLE, "CTaskOilDrum"),
26
  (va::CTASK_CLOUD_VTABLE, "CTaskCloud"),
27
  (va::CTASK_SEA_BUBBLE_VTABLE, "CTaskSeaBubble"),
 
28
  ];
29
 
30
  /// Vtables of entities that are created/destroyed every frame (particles,
@@ -459,6 +460,24 @@ impl DebugApp {
459
  });
460
  }
461
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
462
  // --- CTaskWorm-specific fields ---
463
  if name == "CTaskWorm" || (*task).class_type == openwa_core::class_type::ClassType::Worm {
464
  let worm = addr as *const CTaskWorm;
 
3
  use openwa_core::ddgame::DDGame;
4
  use openwa_core::ddgame_wrapper::DDGameWrapper;
5
  use openwa_core::rebase::rb;
6
+ use openwa_core::task::{CTask, CTaskCloud, CTaskFire, CTaskMine, CTaskOilDrum, CTaskWorm};
7
 
8
  use crate::log;
9
 
 
25
  (va::CTASK_OILDRUM_VTABLE, "CTaskOilDrum"),
26
  (va::CTASK_CLOUD_VTABLE, "CTaskCloud"),
27
  (va::CTASK_SEA_BUBBLE_VTABLE, "CTaskSeaBubble"),
28
+ (va::CTASK_FIRE_VTABLE, "CTaskFire"),
29
  ];
30
 
31
  /// Vtables of entities that are created/destroyed every frame (particles,
 
460
  });
461
  }
462
 
463
+ // --- CTaskFire-specific fields ---
464
+ if name == "CTaskFire" {
465
+ let fire = addr as *const CTaskFire;
466
+ egui::CollapsingHeader::new("CTaskFire")
467
+ .default_open(true)
468
+ .show(ui, |ui| {
469
+ egui::Grid::new("fire_grid").striped(true).show(ui, |ui| {
470
+ ui.label("spawn_x"); ui.label(format!("{:.1}", (*fire).spawn_x.to_f32())); ui.end_row();
471
+ ui.label("spawn_y"); ui.label(format!("{:.1}", (*fire).spawn_y.to_f32())); ui.end_row();
472
+ ui.label("timer"); ui.label(format!("{}", (*fire).timer)); ui.end_row();
473
+ ui.label("burn_rate"); ui.label(format!("{}", (*fire).burn_rate)); ui.end_row();
474
+ ui.label("spread_ctr"); ui.label(format!("{}", (*fire).spread_counter)); ui.end_row();
475
+ ui.label("lifetime"); ui.label(format!("{}", (*fire).lifetime)); ui.end_row();
476
+ ui.label("slot_index"); ui.label(format!("{}", (*fire).slot_index)); ui.end_row();
477
+ });
478
+ });
479
+ }
480
+
481
  // --- CTaskWorm-specific fields ---
482
  if name == "CTaskWorm" || (*task).class_type == openwa_core::class_type::ClassType::Worm {
483
  let worm = addr as *const CTaskWorm;