paavohtl Claude Opus 4.6 (1M context) commited on
Commit
27b9967
Β·
1 Parent(s): fea4eab

feat: port Napalm Strike and Select Worm to pure Rust

Browse files

Add SharedDataTable::lookup() β€” pure Rust equivalent of FUN_004FDF90.
Looks up an entity by (key_esi, key_edi) in the 256-bucket hash table.

Port two weapon fire handlers using the new lookup:
- Napalm Strike (subtype 13): lookup CTaskTeam, send message 0x2B
- Select Worm (subtype 21): lookup CTaskTeam, send message 0x5D
with buf = [8, team_index]

Both share the fire_send_team_message() helper which handles the
SharedData lookup + HandleMessage vtable dispatch pattern.

Verified: headless PASS (byte-identical).

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

crates/openwa-core/src/task/base.rs CHANGED
@@ -139,6 +139,25 @@ impl SharedDataTable {
139
  h
140
  }
141
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
142
  /// Iterate all nodes across all 256 buckets.
143
  ///
144
  /// # Safety
 
139
  h
140
  }
141
 
142
+ /// Look up an entity by key pair. Returns the entity pointer, or null.
143
+ ///
144
+ /// Pure Rust equivalent of `FUN_004FDF90` (SharedData__Lookup).
145
+ /// fastcall(ECX=key_esi, EDX=key_edi, stack=task) in the original.
146
+ ///
147
+ /// # Safety
148
+ /// The table and all linked nodes must be valid.
149
+ pub unsafe fn lookup(&self, key_esi: u32, key_edi: u32) -> *mut u8 {
150
+ let bucket = Self::bucket_for(key_esi, key_edi) as usize;
151
+ let mut node = *self.buckets.add(bucket);
152
+ while !node.is_null() {
153
+ if (*node).key_edi == key_edi && (*node).key_esi == key_esi {
154
+ return (*node).entity;
155
+ }
156
+ node = (*node).next;
157
+ }
158
+ core::ptr::null_mut()
159
+ }
160
+
161
  /// Iterate all nodes across all 256 buckets.
162
  ///
163
  /// # Safety
crates/openwa-wormkit/src/replacements/weapon.rs CHANGED
@@ -367,7 +367,7 @@ unsafe fn fire_weapon_special(
367
  9 => call_fire_usercall_stdcall1(e, w, local_struct, rb(0x51E480)), // Prod (EAX=entry)
368
  10 => call_fire_usercall(w, w, rb(0x51E710)), // Air Strike (EAX=worm)
369
  11 => fire_worm_vtable_0xe(w, 0x71), // Scales of Justice
370
- 13 => call_fire_usercall(e, w, rb(0x51E5C0)), // Napalm (EAX=entry)
371
  14 => call_fire_usercall(w, w, rb(0x51E670)), // Mail/Mine/Mole (EAX=worm)
372
  16 => {
373
  // Teleport: MOV EAX,[ESI+0x44] (worm state) before check
@@ -383,7 +383,7 @@ unsafe fn fire_weapon_special(
383
  18 => fire_worm_vtable_0xe(w, 0x72), // Suicide Bomber
384
  19 => fire_skip_go(worm, entry), // Skip Go (pure Rust)
385
  20 => call_fire_usercall(w, w, rb(0x51E600)), // Surrender (EAX=worm)
386
- 21 => call_fire_usercall(e, w, rb(0x51EBE0)), // Select Worm (EAX=entry)
387
  22 => call_fire_usercall(e, w, rb(0x51EC30)), // Jet Pack (EAX=entry)
388
  23 => fire_worm_vtable_0xe(w, 0x78), // Magic Bullet
389
  24 => call_fire_usercall(e, w, rb(0x51EA60)), // Low Grav (EAX=entry)
@@ -393,6 +393,62 @@ unsafe fn fire_weapon_special(
393
 
394
  // ── Pure Rust fire handlers (no bridge needed) ──────────────
395
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
396
  /// Skip Go (subtype 19) β€” pure Rust replacement for 0x51E8C0.
397
  ///
398
  /// Toggles a bit in the team's `TeamHeader.turn_action_flags` (+0x7C).
 
367
  9 => call_fire_usercall_stdcall1(e, w, local_struct, rb(0x51E480)), // Prod (EAX=entry)
368
  10 => call_fire_usercall(w, w, rb(0x51E710)), // Air Strike (EAX=worm)
369
  11 => fire_worm_vtable_0xe(w, 0x71), // Scales of Justice
370
+ 13 => fire_send_team_message(worm, 0x2B), // Napalm Strike
371
  14 => call_fire_usercall(w, w, rb(0x51E670)), // Mail/Mine/Mole (EAX=worm)
372
  16 => {
373
  // Teleport: MOV EAX,[ESI+0x44] (worm state) before check
 
383
  18 => fire_worm_vtable_0xe(w, 0x72), // Suicide Bomber
384
  19 => fire_skip_go(worm, entry), // Skip Go (pure Rust)
385
  20 => call_fire_usercall(w, w, rb(0x51E600)), // Surrender (EAX=worm)
386
+ 21 => fire_select_worm(worm), // Select Worm (pure Rust)
387
  22 => call_fire_usercall(e, w, rb(0x51EC30)), // Jet Pack (EAX=entry)
388
  23 => fire_worm_vtable_0xe(w, 0x78), // Magic Bullet
389
  24 => call_fire_usercall(e, w, rb(0x51EA60)), // Low Grav (EAX=entry)
 
393
 
394
  // ── Pure Rust fire handlers (no bridge needed) ──────────────
395
 
396
+ /// Send a message to the worm's CTaskTeam entity via SharedData lookup.
397
+ ///
398
+ /// Pattern shared by Napalm Strike (msg 0x2B), Surrender (msg 0x29), etc.
399
+ /// Looks up entity with key (0, 0x14=CTaskTeam) in worm's shared data table,
400
+ /// then calls entity->vtable[2] (HandleMessage) with the given message type.
401
+ ///
402
+ /// The 0x40C-byte local buffer is passed as data pointer; team_index is written
403
+ /// at buf+0x08 to identify which team fired.
404
+ unsafe fn fire_send_team_message(worm: *mut CTaskWorm, msg_type: u32) {
405
+ use openwa_core::task::SharedDataTable;
406
+
407
+ let task = &(*worm).base.base; // CTask base
408
+ let table = SharedDataTable::from_task(task);
409
+
410
+ // Lookup CTaskTeam (key_esi=0, key_edi=0x14)
411
+ let team_entity = table.lookup(0, 0x14);
412
+ if team_entity.is_null() {
413
+ return;
414
+ }
415
+
416
+ // Prepare local buffer (0x40C bytes, mostly zero)
417
+ let mut buf = [0u8; 0x40C];
418
+ let team_index = (*worm).team_index;
419
+ buf[8..12].copy_from_slice(&team_index.to_ne_bytes());
420
+
421
+ // Call entity->vtable[2] = HandleMessage(sender=worm, msg, size=4, data=&buf)
422
+ let vtable = *(team_entity as *const u32);
423
+ let handle_message: unsafe extern "thiscall" fn(*mut u8, *mut CTaskWorm, u32, u32, *const u8) =
424
+ core::mem::transmute(*(vtable as *const u32).add(2));
425
+ handle_message(team_entity, worm, msg_type, 4, buf.as_ptr());
426
+ }
427
+
428
+ /// Select Worm (subtype 21) β€” pure Rust replacement for 0x51EBE0.
429
+ ///
430
+ /// Sends message 0x5D to CTaskTeam with buf = [8, team_index, ...].
431
+ unsafe fn fire_select_worm(worm: *mut CTaskWorm) {
432
+ use openwa_core::task::SharedDataTable;
433
+
434
+ let task = &(*worm).base.base;
435
+ let table = SharedDataTable::from_task(task);
436
+ let team_entity = table.lookup(0, 0x14);
437
+ if team_entity.is_null() {
438
+ return;
439
+ }
440
+
441
+ let mut buf = [0u8; 0x40C];
442
+ // buf[0..4] = 8 (constant), buf[4..8] = team_index
443
+ buf[0..4].copy_from_slice(&8u32.to_ne_bytes());
444
+ buf[4..8].copy_from_slice(&(*worm).team_index.to_ne_bytes());
445
+
446
+ let vtable = *(team_entity as *const u32);
447
+ let handle_message: unsafe extern "thiscall" fn(*mut u8, *mut CTaskWorm, u32, u32, *const u8) =
448
+ core::mem::transmute(*(vtable as *const u32).add(2));
449
+ handle_message(team_entity, worm, 0x5D, 0x408, buf.as_ptr());
450
+ }
451
+
452
  /// Skip Go (subtype 19) β€” pure Rust replacement for 0x51E8C0.
453
  ///
454
  /// Toggles a bit in the team's `TeamHeader.turn_action_flags` (+0x7C).