paavohtl Claude Opus 4.6 (1M context) commited on
Commit
e1d5d02
·
1 Parent(s): d45f977

refactor: eliminate pointer arithmetic in weapon.rs — typed params throughout

Browse files

- Rename DDGame.hurry_flag → version_flag_4 (confirmed via Ghidra as game
logic version byte, not a hurry flag)
- Replace raw fire_params pointer math with struct field access
(collision_radius.0, _fp_04) with polymorphism comments
- Type local_struct as *const WeaponSpawnData in projectile_fire_impl,
use named fields (initial_speed_x/y) instead of array indexing
- Type all naked asm bridge params: *mut CTaskWorm, *const WeaponFireParams,
*const WeaponSpawnData, *const u8, *const () instead of u32
- Simplify fire_weapon_special: remove params_38 u32 arg, compute
params_38_ptr inside from entry pointer
- Type CTaskArrow/CTaskMissile ctor calls with proper pointer params
- Remove duplicate section header comments

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

crates/openwa-core/src/engine/ddgame.rs CHANGED
@@ -313,8 +313,9 @@ pub struct DDGame {
313
  /// 0x7E3F: Version flag byte 3 (set by InitVersionFlags).
314
  /// Passed to is_super_weapon as the version/mode parameter.
315
  pub version_flag_3: u8,
316
- /// 0x7E40: Fast-forward hurry flag (byte).
317
- pub hurry_flag: u8,
 
318
  /// 0x7E41-0x7E9F: Unknown
319
  pub _unknown_7e41: [u8; 0x7EA0 - 0x7E41],
320
 
 
313
  /// 0x7E3F: Version flag byte 3 (set by InitVersionFlags).
314
  /// Passed to is_super_weapon as the version/mode parameter.
315
  pub version_flag_3: u8,
316
+ /// 0x7E40: Game logic version byte 4 (set by InitVersionFlags).
317
+ /// Used in FireWeapon__MailMineMole to gate vtable call behavior by version.
318
+ pub version_flag_4: u8,
319
  /// 0x7E41-0x7E9F: Unknown
320
  pub _unknown_7e41: [u8; 0x7EA0 - 0x7E41],
321
 
crates/openwa-wormkit/src/replacements/weapon.rs CHANGED
@@ -17,7 +17,8 @@ use core::sync::atomic::{AtomicU32, Ordering};
17
 
18
  use openwa_core::address::va;
19
  use openwa_core::engine::ddgame::{self, TeamArenaRef};
20
- use openwa_core::game::weapon::{WeaponEntry, WeaponFireParams};
 
21
  use openwa_core::game::Weapon;
22
  use openwa_core::log::log_line;
23
  use openwa_core::task::Task;
@@ -195,7 +196,7 @@ unsafe extern "C" fn trampoline_fire_weapon() {
195
  /// Completion flag at worm+0x3C (CGameTask.subclass_data[12]).
196
  /// Params pointer at entry+0x3C (WeaponEntry.fire_complete) — different object, same offset.
197
  unsafe extern "cdecl" fn fire_weapon_impl(
198
- entry: *const WeaponEntry, local_struct: u32, worm: *mut CTaskWorm,
199
  ) {
200
  use openwa_core::rebase::rb;
201
 
@@ -203,8 +204,6 @@ unsafe extern "cdecl" fn fire_weapon_impl(
203
  let subtype_34 = (*entry).fire_subtype_34;
204
  let subtype_38 = (*entry).fire_subtype_38;
205
  let fire_params = &raw const (*entry).fire_params;
206
- let w = worm as u32;
207
-
208
  // Log weapon fire
209
  let weapon_id = (*worm).selected_weapon;
210
  let weapon_name = Weapon::try_from(weapon_id)
@@ -219,25 +218,25 @@ unsafe extern "cdecl" fn fire_weapon_impl(
219
 
220
  match weapon_type {
221
  1 => match subtype_38 {
222
- 1 => call_fire_placed_explosive(w, fire_params, local_struct, rb(0x51EC80)), // PlacedExplosive
223
- 2 => call_fire_stdcall3(w, fire_params, local_struct, rb(0x51DFB0)), // Projectile
224
- 3 => call_fire_thiscall2(w, fire_params, local_struct, rb(0x51E0F0)), // CreateWeaponProjectile
225
- 4 => call_fire_thiscall2(w, fire_params, local_struct, rb(0x51ED90)), // CreateArrow (Shotgun/Longbow)
226
  _ => {}
227
  },
228
  2 => match subtype_38 {
229
- 1 => call_fire_stdcall3(w, fire_params, local_struct, rb(0x51E1C0)), // RopeType1
230
- 2 => call_fire_thiscall2(w, fire_params, local_struct, rb(0x51E0F0)), // CreateWeaponProjectile
231
- 3 => call_fire_stdcall3(w, fire_params, local_struct, rb(0x51E240)), // RopeType3
232
  _ => {}
233
  },
234
  3 => {
 
235
  let subtype_34_ptr = &raw const (*entry).fire_subtype_34 as *const WeaponFireParams;
236
- call_fire_stdcall3(w, subtype_34_ptr, local_struct, rb(0x51E2C0));
237
  }
238
  4 => {
239
- let subtype_38_ptr = &raw const (*entry).fire_subtype_38 as u32;
240
- fire_weapon_special(subtype_34, subtype_38_ptr, worm, local_struct, entry);
241
  }
242
  _ => {}
243
  }
@@ -245,14 +244,6 @@ unsafe extern "cdecl" fn fire_weapon_impl(
245
  (*worm).set_fire_complete(1);
246
  }
247
 
248
- // ── Sub-function bridges ────────────────────────────────────
249
- // All preserve ESI=worm for usercall sub-functions.
250
-
251
- // ── Sub-function bridges ────────────────────────────────────
252
- // All bridges set ESI=worm AND EDI=worm before calling, since
253
- // sub-functions are usercall and read both registers implicitly.
254
- // Stack offsets are carefully calculated for each bridge.
255
-
256
  // ── Sub-function bridges ────────────────────────────────────
257
  // All bridges save/restore ESI+EDI, set ESI=EDI=worm, then call.
258
  // This preserves LLVM's callee-saved registers while providing
@@ -262,7 +253,7 @@ unsafe extern "cdecl" fn fire_weapon_impl(
262
  /// Args: (worm, fire_params, local_struct, addr).
263
  #[unsafe(naked)]
264
  unsafe extern "C" fn call_fire_placed_explosive(
265
- _worm: u32, _fire_params: *const WeaponFireParams, _local_struct: u32, _addr: u32,
266
  ) {
267
  core::arch::naked_asm!(
268
  "push ebx",
@@ -285,7 +276,7 @@ unsafe extern "C" fn call_fire_placed_explosive(
285
  /// Bridge: Projectile/Rope/Grenade — stdcall(worm, fire_params, local_struct), RET 0xC.
286
  #[unsafe(naked)]
287
  unsafe extern "C" fn call_fire_stdcall3(
288
- _worm: u32, _fire_params: *const WeaponFireParams, _local: u32, _addr: u32,
289
  ) {
290
  core::arch::naked_asm!(
291
  "push ebx",
@@ -308,7 +299,7 @@ unsafe extern "C" fn call_fire_stdcall3(
308
  /// Bridge: CreateWeaponProjectile — thiscall(ECX=worm, fire_params, local_struct), RET 0x8.
309
  #[unsafe(naked)]
310
  unsafe extern "C" fn call_fire_thiscall2(
311
- _worm: u32, _fire_params: *const WeaponFireParams, _local: u32, _addr: u32,
312
  ) {
313
  core::arch::naked_asm!(
314
  "push ebx",
@@ -333,7 +324,7 @@ unsafe extern "C" fn call_fire_thiscall2(
333
  #[unsafe(naked)]
334
  #[allow(dead_code)]
335
  unsafe extern "C" fn call_fire_stdcall2(
336
- _worm: u32, _fire_params: *const WeaponFireParams, _local: u32, _addr: u32,
337
  ) {
338
  core::arch::naked_asm!(
339
  "push ebx",
@@ -352,51 +343,50 @@ unsafe extern "C" fn call_fire_stdcall2(
352
  );
353
  }
354
 
355
- /// Type 4 (special) weapon dispatch.
356
  /// Type 4 (special) weapon dispatch.
357
  ///
358
  /// EAX at entry = weapon entry pointer (unchanged from type-4 switch).
359
  /// Some handlers explicitly set EAX=worm or EAX=*(worm+0x44).
360
  /// Handlers without explicit MOV EAX inherit the entry pointer.
361
  unsafe fn fire_weapon_special(
362
- subtype: i32, params_38: u32, worm: *mut CTaskWorm, local_struct: u32,
363
- entry: *const WeaponEntry,
364
  ) {
365
  use openwa_core::rebase::rb;
366
- let w = worm as u32;
367
- let e = entry as u32;
 
368
 
369
  match subtype {
370
- 1 => fire_worm_vtable_0xe(w, 0x6C), // Blowtorch
371
- 2 => call_fire_usercall(e, w, rb(0x51E3E0)), // Drill (EAX=entry)
372
- 3 => call_fire_stdcall3(w, params_38 as *const WeaponFireParams, local_struct, rb(0x51E350)), // Girder
373
- 4 => fire_worm_vtable_0xe(w, 0x6D), // Baseball Bat
374
- 5 => fire_worm_vtable_0xe(w, 0x75), // Fire Punch
375
- 6 => fire_worm_vtable_0xe(w, 0x70), // Dragon Ball
376
- 8 => fire_worm_vtable_0xe(w, 0x6E), // Kamikaze
377
- 9 => call_fire_usercall_stdcall1(e, w, local_struct, rb(0x51E480)), // Prod (EAX=entry)
378
- 10 => call_fire_usercall(w, w, rb(0x51E710)), // Air Strike (EAX=worm)
379
- 11 => fire_worm_vtable_0xe(w, 0x71), // Scales of Justice
380
- 13 => fire_send_team_message(worm, 0x2B), // Napalm Strike
381
- 14 => fire_mail_mine_mole(worm), // Mail/Mine/Mole (pure Rust)
382
  16 => {
383
  // Teleport: MOV EAX,[ESI+0x44] (worm state) before check
384
  let worm_state = (*worm).state();
385
- let result = call_fire_usercall_ret(worm_state, w, rb(0x516930));
386
  if result != 0 {
387
- call_fire_usercall(result as u32, w, rb(0x51EB00));
388
  } else {
389
- fire_worm_vtable_0xe(w, 0x74);
390
  }
391
  }
392
- 17 => call_fire_usercall(w, w, rb(0x51E920)), // Freeze (EAX=worm)
393
- 18 => fire_worm_vtable_0xe(w, 0x72), // Suicide Bomber
394
- 19 => fire_skip_go(worm, entry), // Skip Go (pure Rust)
395
- 20 => fire_surrender(worm), // Surrender (pure Rust)
396
- 21 => fire_select_worm(worm), // Select Worm (pure Rust)
397
- 22 => call_fire_usercall(e, w, rb(0x51EC30)), // Jet Pack (EAX=entry)
398
- 23 => fire_worm_vtable_0xe(w, 0x78), // Magic Bullet
399
- 24 => call_fire_usercall(e, w, rb(0x51EA60)), // Low Grav (EAX=entry)
400
  _ => {}
401
  }
402
  }
@@ -517,7 +507,7 @@ unsafe fn fire_surrender(worm: *mut CTaskWorm) {
517
  unsafe fn fire_mail_mine_mole(worm: *mut CTaskWorm) {
518
  use openwa_core::engine::ddgame::TeamArenaRef;
519
  let ddgame = (*worm).ddgame();
520
- let version = *((ddgame as *const u8).add(0x7E40));
521
  let worm_state = (*worm).state();
522
 
523
  let should_call_vtable = version < 2
@@ -525,7 +515,7 @@ unsafe fn fire_mail_mine_mole(worm: *mut CTaskWorm) {
525
  && (worm_state == 0x7D || (worm_state == 0x78 && version < 8)));
526
 
527
  if should_call_vtable {
528
- fire_worm_vtable_0xe(worm as u32, 0x65);
529
  }
530
 
531
  fire_send_team_message(worm, 0x28);
@@ -543,7 +533,7 @@ unsafe fn fire_mail_mine_mole(worm: *mut CTaskWorm) {
543
  /// Args: (spawn_data, worm, fire_params, addr).
544
  #[unsafe(naked)]
545
  unsafe extern "C" fn call_projectile_fire_single(
546
- _spawn_data: u32, _worm: u32, _fire_params: u32, _addr: u32,
547
  ) {
548
  core::arch::naked_asm!(
549
  "push ebx",
@@ -566,7 +556,7 @@ unsafe extern "C" fn call_projectile_fire_single(
566
  /// Args: (this, parent, fire_params, spawn_data, ctor_addr).
567
  #[unsafe(naked)]
568
  unsafe extern "C" fn call_missile_ctor(
569
- _this: u32, _parent: u32, _fire_params: u32, _spawn_data: u32, _addr: u32,
570
  ) {
571
  core::arch::naked_asm!(
572
  "push ebx",
@@ -589,7 +579,7 @@ unsafe extern "C" fn call_missile_ctor(
589
  /// Bridge: usercall(EAX=eax_val, ESI=worm, EDI=worm), plain RET.
590
  /// Args: (eax_val, worm, addr). Saves/restores ESI+EDI. Uses EBX to hold addr.
591
  #[unsafe(naked)]
592
- unsafe extern "C" fn call_fire_usercall(_eax: u32, _worm: u32, _addr: u32) {
593
  core::arch::naked_asm!(
594
  "push ebx",
595
  "push esi",
@@ -608,7 +598,7 @@ unsafe extern "C" fn call_fire_usercall(_eax: u32, _worm: u32, _addr: u32) {
608
 
609
  /// Bridge: usercall(EAX=eax_val, ESI=worm, EDI=worm), plain RET, returns EAX.
610
  #[unsafe(naked)]
611
- unsafe extern "C" fn call_fire_usercall_ret(_eax: u32, _worm: u32, _addr: u32) -> i32 {
612
  core::arch::naked_asm!(
613
  "push ebx",
614
  "push esi",
@@ -628,7 +618,7 @@ unsafe extern "C" fn call_fire_usercall_ret(_eax: u32, _worm: u32, _addr: u32) -
628
  /// Bridge: usercall(EAX=eax_val, ESI=worm, EDI=worm) + stdcall(1 param), RET 0x4.
629
  #[unsafe(naked)]
630
  unsafe extern "C" fn call_fire_usercall_stdcall1(
631
- _eax: u32, _worm: u32, _param: u32, _addr: u32,
632
  ) {
633
  core::arch::naked_asm!(
634
  "push ebx",
@@ -651,7 +641,7 @@ unsafe extern "C" fn call_fire_usercall_stdcall1(
651
  /// Original: `(**(code **)(*worm + 0x38))(msg_id)`.
652
  /// Uses naked bridge to avoid LLVM stack tracking issues with push/RET mismatch.
653
  #[unsafe(naked)]
654
- unsafe extern "C" fn fire_worm_vtable_0xe(_worm: u32, _msg_id: u32) {
655
  core::arch::naked_asm!(
656
  "push ebx",
657
  "push esi",
@@ -706,7 +696,7 @@ unsafe extern "C" fn trampoline_create_weapon_projectile() {
706
  /// Original: 0x51E0F0. Allocates CTaskMissile (0x40C bytes), looks up
707
  /// parent CTaskTurnGame via SharedData, calls the original constructor.
708
  unsafe extern "cdecl" fn create_weapon_projectile_impl(
709
- worm: *mut CTaskWorm, fire_params: *const WeaponFireParams, local_struct: u32,
710
  ) {
711
  use openwa_core::rebase::rb;
712
  use openwa_core::task::{SharedDataTable, Task};
@@ -736,9 +726,9 @@ unsafe extern "cdecl" fn create_weapon_projectile_impl(
736
  // Call original CTaskMissile::Constructor
737
  // thiscall(ECX=buffer, parent, fire_params, local_struct), RET 0xC
738
  call_missile_ctor(
739
- buffer as u32,
740
- parent as u32,
741
- fire_params as u32,
742
  local_struct,
743
  rb(va::CTASK_MISSILE_CTOR),
744
  );
@@ -780,23 +770,23 @@ unsafe extern "C" fn trampoline_projectile_fire() {
780
  /// e. Write rotated velocity into local spawn data
781
  /// f. Call ProjectileFire_Single(loop_counter, fire_params) with EDI=spawn_data
782
  unsafe extern "cdecl" fn projectile_fire_impl(
783
- worm: *mut CTaskWorm, fire_params: *const WeaponFireParams, local_struct: *const u32,
784
  ) {
785
  use openwa_core::rebase::rb;
786
 
787
- let fire_params_raw = fire_params as *const i32;
788
- let shot_count = *fire_params_raw.add(3); // fire_params+0xC = projectile count per trigger
 
789
  if shot_count <= 0 {
790
  return;
791
  }
792
 
793
- // Copy 11 DWORDs from local_struct to local spawn data
794
- let mut spawn_data = [0u32; 11];
795
- core::ptr::copy_nonoverlapping(local_struct, spawn_data.as_mut_ptr(), 11);
796
 
797
- // Read template velocity from local_struct (fields [4] and [5])
798
- let template_speed_x = *local_struct.add(4) as i32;
799
- let template_speed_y = *local_struct.add(5) as i32;
800
 
801
  // Trig table: sin at SIN_TABLE, cos at SIN_TABLE + 256*4
802
  let sin_table = rb(va::SIN_TABLE) as *const i32;
@@ -809,8 +799,9 @@ unsafe extern "cdecl" fn projectile_fire_impl(
809
  let rng = ddgame.advance_rng();
810
 
811
  // Compute spread angle: ((rng_low16 - 0x8000) * spread_param) / 360
 
812
  let rng_centered = (rng & 0xFFFF) as i32 - 0x8000;
813
- let spread_param = *fire_params_raw.add(4); // fire_params+0x10
814
  let angle = (rng_centered * spread_param) / 360;
815
 
816
  // Cos/sin table lookup with linear interpolation
@@ -836,14 +827,14 @@ unsafe extern "cdecl" fn projectile_fire_impl(
836
  + fixed_mul(cos_val, template_speed_y);
837
 
838
  // Write rotated velocity into spawn data
839
- spawn_data[4] = speed_x as u32;
840
- spawn_data[5] = speed_y as u32;
841
 
842
  // Call ProjectileFire_Single(worm, fire_params) with EDI=&spawn_data
843
  call_projectile_fire_single(
844
- spawn_data.as_ptr() as u32,
845
- worm as u32,
846
- fire_params as u32,
847
  rb(va::PROJECTILE_FIRE_SINGLE),
848
  );
849
  }
@@ -879,7 +870,7 @@ unsafe extern "C" fn trampoline_create_arrow() {
879
  }
880
 
881
  unsafe extern "cdecl" fn create_arrow_impl(
882
- worm: *mut CTaskWorm, fire_params: *const WeaponFireParams, local_struct: u32,
883
  ) {
884
  use openwa_core::rebase::rb;
885
  use openwa_core::task::{SharedDataTable, Task};
@@ -905,9 +896,9 @@ unsafe extern "cdecl" fn create_arrow_impl(
905
  core::ptr::write_bytes(buffer, 0, 0x148);
906
 
907
  // CTaskArrow::Constructor — stdcall(this, parent, fire_params, local_struct), RET 0x10
908
- let ctor: unsafe extern "stdcall" fn(u32, u32, u32, u32) =
909
  core::mem::transmute(rb(va::CTASK_ARROW_CTOR));
910
- ctor(buffer as u32, parent as u32, fire_params as u32, local_struct);
911
 
912
  let _ = log_line(&format!(
913
  "[Weapon] CreateArrow: worm=0x{:08X} arrow=0x{:08X}",
 
17
 
18
  use openwa_core::address::va;
19
  use openwa_core::engine::ddgame::{self, TeamArenaRef};
20
+ use openwa_core::fixed::Fixed;
21
+ 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;
 
196
  /// Completion flag at worm+0x3C (CGameTask.subclass_data[12]).
197
  /// Params pointer at entry+0x3C (WeaponEntry.fire_complete) — different object, same offset.
198
  unsafe extern "cdecl" fn fire_weapon_impl(
199
+ entry: *const WeaponEntry, local_struct: *const u8, worm: *mut CTaskWorm,
200
  ) {
201
  use openwa_core::rebase::rb;
202
 
 
204
  let subtype_34 = (*entry).fire_subtype_34;
205
  let subtype_38 = (*entry).fire_subtype_38;
206
  let fire_params = &raw const (*entry).fire_params;
 
 
207
  // Log weapon fire
208
  let weapon_id = (*worm).selected_weapon;
209
  let weapon_name = Weapon::try_from(weapon_id)
 
218
 
219
  match weapon_type {
220
  1 => match subtype_38 {
221
+ 1 => call_fire_placed_explosive(worm, fire_params, local_struct, rb(0x51EC80)), // PlacedExplosive
222
+ 2 => call_fire_stdcall3(worm, fire_params, local_struct, rb(0x51DFB0)), // Projectile
223
+ 3 => call_fire_thiscall2(worm, fire_params, local_struct, rb(0x51E0F0)), // CreateWeaponProjectile
224
+ 4 => call_fire_thiscall2(worm, fire_params, local_struct, rb(0x51ED90)), // CreateArrow (Shotgun/Longbow)
225
  _ => {}
226
  },
227
  2 => match subtype_38 {
228
+ 1 => call_fire_stdcall3(worm, fire_params, local_struct, rb(0x51E1C0)), // RopeType1
229
+ 2 => call_fire_thiscall2(worm, fire_params, local_struct, rb(0x51E0F0)), // CreateWeaponProjectile
230
+ 3 => call_fire_stdcall3(worm, fire_params, local_struct, rb(0x51E240)), // RopeType3
231
  _ => {}
232
  },
233
  3 => {
234
+ // StrikeFire takes a pointer to the subtype_34 field (reinterpreted as fire params)
235
  let subtype_34_ptr = &raw const (*entry).fire_subtype_34 as *const WeaponFireParams;
236
+ call_fire_stdcall3(worm, subtype_34_ptr, local_struct, rb(0x51E2C0));
237
  }
238
  4 => {
239
+ fire_weapon_special(subtype_34, entry, worm, local_struct);
 
240
  }
241
  _ => {}
242
  }
 
244
  (*worm).set_fire_complete(1);
245
  }
246
 
 
 
 
 
 
 
 
 
247
  // ── Sub-function bridges ────────────────────────────────────
248
  // All bridges save/restore ESI+EDI, set ESI=EDI=worm, then call.
249
  // This preserves LLVM's callee-saved registers while providing
 
253
  /// Args: (worm, fire_params, local_struct, addr).
254
  #[unsafe(naked)]
255
  unsafe extern "C" fn call_fire_placed_explosive(
256
+ _worm: *mut CTaskWorm, _fire_params: *const WeaponFireParams, _local_struct: *const u8, _addr: u32,
257
  ) {
258
  core::arch::naked_asm!(
259
  "push ebx",
 
276
  /// Bridge: Projectile/Rope/Grenade — stdcall(worm, fire_params, local_struct), RET 0xC.
277
  #[unsafe(naked)]
278
  unsafe extern "C" fn call_fire_stdcall3(
279
+ _worm: *mut CTaskWorm, _fire_params: *const WeaponFireParams, _local: *const u8, _addr: u32,
280
  ) {
281
  core::arch::naked_asm!(
282
  "push ebx",
 
299
  /// Bridge: CreateWeaponProjectile — thiscall(ECX=worm, fire_params, local_struct), RET 0x8.
300
  #[unsafe(naked)]
301
  unsafe extern "C" fn call_fire_thiscall2(
302
+ _worm: *mut CTaskWorm, _fire_params: *const WeaponFireParams, _local: *const u8, _addr: u32,
303
  ) {
304
  core::arch::naked_asm!(
305
  "push ebx",
 
324
  #[unsafe(naked)]
325
  #[allow(dead_code)]
326
  unsafe extern "C" fn call_fire_stdcall2(
327
+ _worm: *mut CTaskWorm, _fire_params: *const WeaponFireParams, _local: *const u8, _addr: u32,
328
  ) {
329
  core::arch::naked_asm!(
330
  "push ebx",
 
343
  );
344
  }
345
 
 
346
  /// Type 4 (special) weapon dispatch.
347
  ///
348
  /// EAX at entry = weapon entry pointer (unchanged from type-4 switch).
349
  /// Some handlers explicitly set EAX=worm or EAX=*(worm+0x44).
350
  /// Handlers without explicit MOV EAX inherit the entry pointer.
351
  unsafe fn fire_weapon_special(
352
+ subtype: i32, entry: *const WeaponEntry, worm: *mut CTaskWorm, local_struct: *const u8,
 
353
  ) {
354
  use openwa_core::rebase::rb;
355
+
356
+ // Pointer to fire_subtype_38 field, reinterpreted as fire params pointer for Girder
357
+ let params_38_ptr = &raw const (*entry).fire_subtype_38 as *const WeaponFireParams;
358
 
359
  match subtype {
360
+ 1 => fire_worm_vtable_0xe(worm, 0x6C), // Blowtorch
361
+ 2 => call_fire_usercall(entry as *const (), worm, rb(0x51E3E0)), // Drill (EAX=entry)
362
+ 3 => call_fire_stdcall3(worm, params_38_ptr, local_struct, rb(0x51E350)), // Girder
363
+ 4 => fire_worm_vtable_0xe(worm, 0x6D), // Baseball Bat
364
+ 5 => fire_worm_vtable_0xe(worm, 0x75), // Fire Punch
365
+ 6 => fire_worm_vtable_0xe(worm, 0x70), // Dragon Ball
366
+ 8 => fire_worm_vtable_0xe(worm, 0x6E), // Kamikaze
367
+ 9 => call_fire_usercall_stdcall1(entry as *const (), worm, local_struct, rb(0x51E480)), // Prod (EAX=entry)
368
+ 10 => call_fire_usercall(worm as *const (), worm, rb(0x51E710)), // Air Strike (EAX=worm)
369
+ 11 => fire_worm_vtable_0xe(worm, 0x71), // Scales of Justice
370
+ 13 => fire_send_team_message(worm, 0x2B), // Napalm Strike
371
+ 14 => fire_mail_mine_mole(worm), // Mail/Mine/Mole (pure Rust)
372
  16 => {
373
  // Teleport: MOV EAX,[ESI+0x44] (worm state) before check
374
  let worm_state = (*worm).state();
375
+ let result = call_fire_usercall_ret(worm_state as *const (), worm, rb(0x516930));
376
  if result != 0 {
377
+ call_fire_usercall(result as *const (), worm, rb(0x51EB00));
378
  } else {
379
+ fire_worm_vtable_0xe(worm, 0x74);
380
  }
381
  }
382
+ 17 => call_fire_usercall(worm as *const (), worm, rb(0x51E920)), // Freeze (EAX=worm)
383
+ 18 => fire_worm_vtable_0xe(worm, 0x72), // Suicide Bomber
384
+ 19 => fire_skip_go(worm, entry), // Skip Go (pure Rust)
385
+ 20 => fire_surrender(worm), // Surrender (pure Rust)
386
+ 21 => fire_select_worm(worm), // Select Worm (pure Rust)
387
+ 22 => call_fire_usercall(entry as *const (), worm, rb(0x51EC30)), // Jet Pack (EAX=entry)
388
+ 23 => fire_worm_vtable_0xe(worm, 0x78), // Magic Bullet
389
+ 24 => call_fire_usercall(entry as *const (), worm, rb(0x51EA60)), // Low Grav (EAX=entry)
390
  _ => {}
391
  }
392
  }
 
507
  unsafe fn fire_mail_mine_mole(worm: *mut CTaskWorm) {
508
  use openwa_core::engine::ddgame::TeamArenaRef;
509
  let ddgame = (*worm).ddgame();
510
+ let version = (*ddgame).version_flag_4;
511
  let worm_state = (*worm).state();
512
 
513
  let should_call_vtable = version < 2
 
515
  && (worm_state == 0x7D || (worm_state == 0x78 && version < 8)));
516
 
517
  if should_call_vtable {
518
+ fire_worm_vtable_0xe(worm, 0x65);
519
  }
520
 
521
  fire_send_team_message(worm, 0x28);
 
533
  /// Args: (spawn_data, worm, fire_params, addr).
534
  #[unsafe(naked)]
535
  unsafe extern "C" fn call_projectile_fire_single(
536
+ _spawn_data: *const WeaponSpawnData, _worm: *mut CTaskWorm, _fire_params: *const WeaponFireParams, _addr: u32,
537
  ) {
538
  core::arch::naked_asm!(
539
  "push ebx",
 
556
  /// Args: (this, parent, fire_params, spawn_data, ctor_addr).
557
  #[unsafe(naked)]
558
  unsafe extern "C" fn call_missile_ctor(
559
+ _this: *mut u8, _parent: *mut u8, _fire_params: *const WeaponFireParams, _spawn_data: *const u8, _addr: u32,
560
  ) {
561
  core::arch::naked_asm!(
562
  "push ebx",
 
579
  /// Bridge: usercall(EAX=eax_val, ESI=worm, EDI=worm), plain RET.
580
  /// Args: (eax_val, worm, addr). Saves/restores ESI+EDI. Uses EBX to hold addr.
581
  #[unsafe(naked)]
582
+ unsafe extern "C" fn call_fire_usercall(_eax: *const (), _worm: *mut CTaskWorm, _addr: u32) {
583
  core::arch::naked_asm!(
584
  "push ebx",
585
  "push esi",
 
598
 
599
  /// Bridge: usercall(EAX=eax_val, ESI=worm, EDI=worm), plain RET, returns EAX.
600
  #[unsafe(naked)]
601
+ unsafe extern "C" fn call_fire_usercall_ret(_eax: *const (), _worm: *mut CTaskWorm, _addr: u32) -> i32 {
602
  core::arch::naked_asm!(
603
  "push ebx",
604
  "push esi",
 
618
  /// Bridge: usercall(EAX=eax_val, ESI=worm, EDI=worm) + stdcall(1 param), RET 0x4.
619
  #[unsafe(naked)]
620
  unsafe extern "C" fn call_fire_usercall_stdcall1(
621
+ _eax: *const (), _worm: *mut CTaskWorm, _param: *const u8, _addr: u32,
622
  ) {
623
  core::arch::naked_asm!(
624
  "push ebx",
 
641
  /// Original: `(**(code **)(*worm + 0x38))(msg_id)`.
642
  /// Uses naked bridge to avoid LLVM stack tracking issues with push/RET mismatch.
643
  #[unsafe(naked)]
644
+ unsafe extern "C" fn fire_worm_vtable_0xe(_worm: *mut CTaskWorm, _msg_id: u32) {
645
  core::arch::naked_asm!(
646
  "push ebx",
647
  "push esi",
 
696
  /// Original: 0x51E0F0. Allocates CTaskMissile (0x40C bytes), looks up
697
  /// parent CTaskTurnGame via SharedData, calls the original constructor.
698
  unsafe extern "cdecl" fn create_weapon_projectile_impl(
699
+ worm: *mut CTaskWorm, fire_params: *const WeaponFireParams, local_struct: *const u8,
700
  ) {
701
  use openwa_core::rebase::rb;
702
  use openwa_core::task::{SharedDataTable, Task};
 
726
  // Call original CTaskMissile::Constructor
727
  // thiscall(ECX=buffer, parent, fire_params, local_struct), RET 0xC
728
  call_missile_ctor(
729
+ buffer,
730
+ parent as *mut u8,
731
+ fire_params,
732
  local_struct,
733
  rb(va::CTASK_MISSILE_CTOR),
734
  );
 
770
  /// e. Write rotated velocity into local spawn data
771
  /// f. Call ProjectileFire_Single(loop_counter, fire_params) with EDI=spawn_data
772
  unsafe extern "cdecl" fn projectile_fire_impl(
773
+ worm: *mut CTaskWorm, fire_params: *const WeaponFireParams, local_struct: *const WeaponSpawnData,
774
  ) {
775
  use openwa_core::rebase::rb;
776
 
777
+ let params = &*fire_params;
778
+ // collision_radius field is polymorphic — for ProjectileFire it holds the shot count
779
+ let shot_count = params.collision_radius.0;
780
  if shot_count <= 0 {
781
  return;
782
  }
783
 
784
+ // Copy spawn template from caller's stack buffer
785
+ let mut spawn_data = *local_struct;
 
786
 
787
+ // Read template velocity (will be rotated per-shot)
788
+ let template_speed_x = spawn_data.initial_speed_x.0;
789
+ let template_speed_y = spawn_data.initial_speed_y.0;
790
 
791
  // Trig table: sin at SIN_TABLE, cos at SIN_TABLE + 256*4
792
  let sin_table = rb(va::SIN_TABLE) as *const i32;
 
799
  let rng = ddgame.advance_rng();
800
 
801
  // Compute spread angle: ((rng_low16 - 0x8000) * spread_param) / 360
802
+ // _fp_04 field is polymorphic — for ProjectileFire it holds spread angle
803
  let rng_centered = (rng & 0xFFFF) as i32 - 0x8000;
804
+ let spread_param = params._fp_04;
805
  let angle = (rng_centered * spread_param) / 360;
806
 
807
  // Cos/sin table lookup with linear interpolation
 
827
  + fixed_mul(cos_val, template_speed_y);
828
 
829
  // Write rotated velocity into spawn data
830
+ spawn_data.initial_speed_x = Fixed(speed_x);
831
+ spawn_data.initial_speed_y = Fixed(speed_y);
832
 
833
  // Call ProjectileFire_Single(worm, fire_params) with EDI=&spawn_data
834
  call_projectile_fire_single(
835
+ &raw const spawn_data,
836
+ worm,
837
+ fire_params,
838
  rb(va::PROJECTILE_FIRE_SINGLE),
839
  );
840
  }
 
870
  }
871
 
872
  unsafe extern "cdecl" fn create_arrow_impl(
873
+ worm: *mut CTaskWorm, fire_params: *const WeaponFireParams, local_struct: *const u8,
874
  ) {
875
  use openwa_core::rebase::rb;
876
  use openwa_core::task::{SharedDataTable, Task};
 
896
  core::ptr::write_bytes(buffer, 0, 0x148);
897
 
898
  // CTaskArrow::Constructor — stdcall(this, parent, fire_params, local_struct), RET 0x10
899
+ let ctor: unsafe extern "stdcall" fn(*mut u8, *mut u8, *const WeaponFireParams, *const u8) =
900
  core::mem::transmute(rb(va::CTASK_ARROW_CTOR));
901
+ ctor(buffer, parent as *mut u8, fire_params, local_struct);
902
 
903
  let _ = log_line(&format!(
904
  "[Weapon] CreateArrow: worm=0x{:08X} arrow=0x{:08X}",