paavohtl Claude Opus 4.6 (1M context) commited on
Commit
36caf33
·
1 Parent(s): cbc66c4

style: cargo fmt on weapon.rs

Browse files

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

crates/openwa-wormkit/src/replacements/weapon.rs CHANGED
@@ -23,8 +23,8 @@ use openwa_core::game::weapon::{WeaponEntry, WeaponFireParams, WeaponSpawnData};
23
  use openwa_core::game::Weapon;
24
  use openwa_core::log::log_line;
25
  use openwa_core::task::turn_game::CTaskTurnGame;
26
- use openwa_core::task::CTask;
27
  use openwa_core::task::worm::{CTaskWorm, WormState};
 
28
 
29
  use crate::hook::{self, usercall_trampoline};
30
  use crate::replacements::weapon_release::WeaponReleaseContext;
@@ -103,10 +103,12 @@ unsafe extern "cdecl" fn get_ammo_impl(
103
  }
104
 
105
  // SelectWorm (0x3B) requires >1 alive worm on the team
106
- if state.game_phase >= ddgame::GAME_PHASE_NORMAL_MIN && weapon_id == Weapon::SelectWorm as u32
107
- && count_alive_worms_impl(team_index, arena) == 0 {
108
- return 0;
109
- }
 
 
110
 
111
  state.get_ammo(alliance, wid) as u32
112
  }
@@ -165,24 +167,42 @@ pub(crate) unsafe fn fire_weapon(
165
  let weapon = (*worm).selected_weapon;
166
  let _ = log_line(&format!(
167
  "[Weapon] FireWeapon: {:?} (id={}) type={} sub34={} sub38={}",
168
- weapon, weapon as u32, fire_type, (*entry).special_subtype, fire_method
 
 
 
 
169
  ));
170
 
171
  CTaskWorm::set_fire_complete_raw(worm, 0);
172
 
173
- use openwa_core::game::weapon::{FireType, FireMethod};
174
  match FireType::try_from(fire_type) {
175
  Ok(FireType::Projectile) => match FireMethod::try_from(fire_method) {
176
- Ok(FireMethod::PlacedExplosive) => call_fire_placed_explosive(worm, fire_params, ctx, rb(0x51EC80)),
177
- Ok(FireMethod::ProjectileFire) => call_fire_stdcall3(worm, fire_params, ctx, rb(0x51DFB0)),
178
- Ok(FireMethod::CreateWeaponProjectile) => call_fire_thiscall2(worm, fire_params, ctx, rb(0x51E0F0)),
179
- Ok(FireMethod::CreateArrow) => call_fire_thiscall2(worm, fire_params, ctx, rb(0x51ED90)),
 
 
 
 
 
 
 
 
180
  _ => {}
181
  },
182
  Ok(FireType::Rope) => match FireMethod::try_from(fire_method) {
183
- Ok(FireMethod::PlacedExplosive) => call_fire_stdcall3(worm, fire_params, ctx, rb(0x51E1C0)), // RopeType1
184
- Ok(FireMethod::ProjectileFire) => call_fire_thiscall2(worm, fire_params, ctx, rb(0x51E0F0)),
185
- Ok(FireMethod::CreateWeaponProjectile) => call_fire_stdcall3(worm, fire_params, ctx, rb(0x51E240)), // RopeType3
 
 
 
 
 
 
186
  _ => {}
187
  },
188
  Ok(FireType::Strike) => {
@@ -208,7 +228,10 @@ pub(crate) unsafe fn fire_weapon(
208
  /// Args: (worm, fire_params, ctx, addr).
209
  #[unsafe(naked)]
210
  unsafe extern "C" fn call_fire_placed_explosive(
211
- _worm: *mut CTaskWorm, _fire_params: *const WeaponFireParams, _ctx: *const WeaponReleaseContext, _addr: u32,
 
 
 
212
  ) {
213
  core::arch::naked_asm!(
214
  "push ebx",
@@ -216,10 +239,10 @@ unsafe extern "C" fn call_fire_placed_explosive(
216
  "push edi",
217
  // Set up usercall registers
218
  // Stack: 3 saves(12) + ret(4) = 16 to first arg
219
- "mov edx, [esp+16]", // EDX = worm
220
- "mov ecx, [esp+24]", // ECX = local_struct
221
- "mov ebx, [esp+28]", // addr
222
- "push [esp+20]", // fire_params (stack param)
223
  "call ebx",
224
  "pop edi",
225
  "pop esi",
@@ -231,18 +254,21 @@ unsafe extern "C" fn call_fire_placed_explosive(
231
  /// Bridge: Projectile/Rope/Grenade — stdcall(worm, fire_params, local_struct), RET 0xC.
232
  #[unsafe(naked)]
233
  unsafe extern "C" fn call_fire_stdcall3(
234
- _worm: *mut CTaskWorm, _fire_params: *const WeaponFireParams, _ctx: *const WeaponReleaseContext, _addr: u32,
 
 
 
235
  ) {
236
  core::arch::naked_asm!(
237
  "push ebx",
238
  "push esi",
239
  "push edi",
240
- "mov esi, [esp+16]", // worm (3 saves=12 + ret=4 = 16)
241
  "mov edi, [esp+16]",
242
- "mov ebx, [esp+28]", // addr (saves=12 + ret=4 + 3 args=12 = 28)
243
- "push [esp+24]", // local_struct
244
- "push [esp+24]", // params (shifted +4)
245
- "push [esp+24]", // worm (shifted +8)
246
  "call ebx",
247
  "pop edi",
248
  "pop esi",
@@ -254,18 +280,21 @@ unsafe extern "C" fn call_fire_stdcall3(
254
  /// Bridge: CreateWeaponProjectile — thiscall(ECX=worm, fire_params, local_struct), RET 0x8.
255
  #[unsafe(naked)]
256
  unsafe extern "C" fn call_fire_thiscall2(
257
- _worm: *mut CTaskWorm, _fire_params: *const WeaponFireParams, _ctx: *const WeaponReleaseContext, _addr: u32,
 
 
 
258
  ) {
259
  core::arch::naked_asm!(
260
  "push ebx",
261
  "push esi",
262
  "push edi",
263
- "mov esi, [esp+16]", // worm
264
  "mov edi, [esp+16]",
265
- "mov ecx, [esp+16]", // ECX = worm (this)
266
- "mov ebx, [esp+28]", // addr
267
- "push [esp+24]", // local_struct
268
- "push [esp+24]", // params (shifted +4)
269
  "call ebx",
270
  "pop edi",
271
  "pop esi",
@@ -279,17 +308,20 @@ unsafe extern "C" fn call_fire_thiscall2(
279
  #[unsafe(naked)]
280
  #[allow(dead_code)]
281
  unsafe extern "C" fn call_fire_stdcall2(
282
- _worm: *mut CTaskWorm, _fire_params: *const WeaponFireParams, _ctx: *const WeaponReleaseContext, _addr: u32,
 
 
 
283
  ) {
284
  core::arch::naked_asm!(
285
  "push ebx",
286
  "push esi",
287
  "push edi",
288
- "mov esi, [esp+16]", // worm
289
  "mov edi, [esp+16]",
290
- "mov ebx, [esp+28]", // addr
291
- "push [esp+24]", // local_struct
292
- "push [esp+24]", // params (shifted +4)
293
  "call ebx",
294
  "pop edi",
295
  "pop esi",
@@ -304,7 +336,10 @@ unsafe extern "C" fn call_fire_stdcall2(
304
  /// Some handlers explicitly set EAX=worm or EAX=*(worm+0x44).
305
  /// Handlers without explicit MOV EAX inherit the entry pointer.
306
  unsafe fn fire_weapon_special(
307
- subtype: i32, entry: *const WeaponEntry, worm: *mut CTaskWorm, ctx: *const WeaponReleaseContext,
 
 
 
308
  ) {
309
  use openwa_core::game::weapon::SpecialFireSubtype as S;
310
  use openwa_core::rebase::rb;
@@ -358,7 +393,9 @@ fn can_fire_subtype16(state: u32) -> bool {
358
  ///
359
  /// The entity at key (0, 0x14) is a CTaskTurnGame (inherits CTaskTeam).
360
  /// Returns null if not found.
361
- pub(crate) unsafe fn lookup_turn_game(worm: *const CTaskWorm) -> *mut openwa_core::task::CTaskTurnGame {
 
 
362
  use openwa_core::task::SharedDataTable;
363
 
364
  let table = SharedDataTable::from_task(worm as *const CTask);
@@ -408,7 +445,8 @@ unsafe fn fire_send_team_message(worm: *mut CTaskWorm, msg_type: u32) {
408
  let team_index = (*worm).team_index;
409
  buf[0..4].copy_from_slice(&team_index.to_ne_bytes());
410
 
411
- CTaskTurnGame::handle_message_raw(team,
 
412
  worm as *mut openwa_core::task::CTask,
413
  msg_type,
414
  4,
@@ -429,7 +467,8 @@ unsafe fn fire_select_worm(worm: *mut CTaskWorm) {
429
  buf[0..4].copy_from_slice(&8u32.to_ne_bytes());
430
  buf[4..8].copy_from_slice(&(*worm).team_index.to_ne_bytes());
431
 
432
- CTaskTurnGame::handle_message_raw(team,
 
433
  worm as *mut openwa_core::task::CTask,
434
  0x5D,
435
  0x408,
@@ -503,7 +542,7 @@ unsafe fn fire_mail_mine_mole(worm: *mut CTaskWorm) {
503
  || (worm_state == WormState::WeaponAimed_Maybe as u32 && version < 8)));
504
 
505
  if should_call_vtable {
506
- CTaskWorm::set_state_raw(worm,WormState::Idle);
507
  }
508
 
509
  fire_send_team_message(worm, 0x28);
@@ -527,7 +566,7 @@ unsafe fn fire_teleport(worm: *mut CTaskWorm) {
527
  use openwa_core::rebase::rb;
528
 
529
  if (*worm)._unknown_208 == 0 {
530
- CTaskWorm::set_state_raw(worm,WormState::AirStrikePending_Maybe);
531
  return;
532
  }
533
 
@@ -554,7 +593,7 @@ unsafe fn fire_teleport(worm: *mut CTaskWorm) {
554
  } else {
555
  WormState::Unknown_0x8B
556
  };
557
- CTaskWorm::set_state_raw(worm,new_state);
558
 
559
  // Clear action fields
560
  *(worm_raw.add(0x48) as *mut i32) = 0;
@@ -587,7 +626,13 @@ unsafe fn fire_nuclear_test(worm: *mut CTaskWorm) {
587
  let mut buf = [0u8; 0x40C];
588
  buf[0..4].copy_from_slice(&entry.fire_method.to_ne_bytes());
589
  buf[4..8].copy_from_slice(&8i32.to_ne_bytes());
590
- CTaskTurnGame::handle_message_raw(team,worm as *mut openwa_core::task::CTask, 0x59, 0x408, buf.as_ptr());
 
 
 
 
 
 
591
  }
592
 
593
  // Message 0x5A (NukeBlast): buf[0]=8
@@ -595,11 +640,23 @@ unsafe fn fire_nuclear_test(worm: *mut CTaskWorm) {
595
  if !team.is_null() {
596
  let mut buf = [0u8; 0x40C];
597
  buf[0..4].copy_from_slice(&8i32.to_ne_bytes());
598
- CTaskTurnGame::handle_message_raw(team,worm as *mut openwa_core::task::CTask, 0x5A, 0x408, buf.as_ptr());
 
 
 
 
 
 
599
  }
600
 
601
  // PlaySoundGlobal(IndianAnthem, 8, 0x10000, 0x10000)
602
- sound::queue_sound(ddgame, KnownSoundId::IndianAnthem.into(), 8, Fixed::ONE, Fixed::ONE);
 
 
 
 
 
 
603
 
604
  // Message 0x51 (PoisonWorm): buf[0]=shot_count, buf[4]=2, buf[8]=team_index
605
  let team = lookup_turn_game(worm);
@@ -608,29 +665,40 @@ unsafe fn fire_nuclear_test(worm: *mut CTaskWorm) {
608
  buf[0..4].copy_from_slice(&entry.fire_params.shot_count.to_ne_bytes());
609
  buf[4..8].copy_from_slice(&2i32.to_ne_bytes());
610
  buf[8..12].copy_from_slice(&(*worm).team_index.to_ne_bytes());
611
- CTaskTurnGame::handle_message_raw(team,worm as *mut openwa_core::task::CTask, 0x51, 0x408, buf.as_ptr());
 
 
 
 
 
 
612
  }
613
 
614
  // PlaySoundGlobal(NukeFlash, 5, 0x10000, 0x10000)
615
- sound::queue_sound(ddgame, KnownSoundId::NukeFlash.into(), 5, Fixed::ONE, Fixed::ONE);
 
 
 
 
 
 
616
  }
617
 
618
-
619
  /// Bridge: FUN_00547C30 — usercall(EAX=0x80000, ECX=x) + stdcall(y, 0,0,0, 600, 0x10000, 0x1999).
620
  #[unsafe(naked)]
621
  unsafe extern "C" fn call_spawn_effect(_x: i32, _y: i32, _addr: u32) {
622
  core::arch::naked_asm!(
623
  "push ebx",
624
  // Stack: 1 save(4) + ret(4) = 8 to first arg
625
- "mov ecx, [esp+8]", // x
626
- "mov ebx, [esp+16]", // addr
627
  "push 0x1999",
628
  "push 0x10000",
629
- "push 0x258", // 600
630
  "push 0",
631
  "push 0",
632
  "push 0",
633
- "push [esp+36]", // y (8 + 4 + 6*4 = 36)
634
  "mov eax, 0x80000",
635
  "call ebx",
636
  "pop ebx",
@@ -640,18 +708,16 @@ unsafe extern "C" fn call_spawn_effect(_x: i32, _y: i32, _addr: u32) {
640
 
641
  /// Bridge: FUN_004FE070 — usercall(ESI=worm, EDI=y) + stdcall(x). Plain RET.
642
  #[unsafe(naked)]
643
- unsafe extern "C" fn call_position_update(
644
- _worm: *mut CTaskWorm, _x: i32, _y: i32, _addr: u32,
645
- ) {
646
  core::arch::naked_asm!(
647
  "push ebx",
648
  "push esi",
649
  "push edi",
650
  // Stack: 3 saves(12) + ret(4) = 16 to first arg
651
- "mov esi, [esp+16]", // worm
652
- "mov edi, [esp+24]", // y
653
- "mov ebx, [esp+28]", // addr
654
- "push [esp+20]", // x
655
  "call ebx",
656
  "pop edi",
657
  "pop esi",
@@ -665,8 +731,8 @@ unsafe extern "C" fn call_position_update(
665
  unsafe extern "C" fn call_worm_landing_check(_worm: *mut CTaskWorm, _addr: u32) {
666
  core::arch::naked_asm!(
667
  "push esi",
668
- "mov esi, [esp+8]", // worm (1 save + ret = 8)
669
- "call [esp+12]", // addr (8 + 4 = 12)
670
  "pop esi",
671
  "ret",
672
  );
@@ -682,7 +748,8 @@ unsafe fn fire_scales_of_justice(worm: *mut CTaskWorm) {
682
  // Send message 0x5E to CTaskTurnGame
683
  let team = lookup_turn_game(worm);
684
  if !team.is_null() {
685
- CTaskTurnGame::handle_message_raw(team,
 
686
  worm as *mut openwa_core::task::CTask,
687
  0x5E,
688
  0,
@@ -695,7 +762,13 @@ unsafe fn fire_scales_of_justice(worm: *mut CTaskWorm) {
695
  // FUN_005480F0: usercall(EAX=-21) + stdcall(worm, result, 0x17, &worm_name)
696
  let ddgame = CTask::ddgame_raw(worm as *const CTask);
697
  let sound_val = call_get_sound_val((*ddgame).timer_obj, rb(0x53EC70));
698
- call_play_sound_usercall(worm, sound_val, 0x17, (*worm).worm_name.as_ptr(), rb(0x5480F0));
 
 
 
 
 
 
699
  }
700
 
701
  /// Bridge: FUN_0053EC70 — usercall(EDI=0x6CB) + stdcall(timer_obj). Returns EAX.
@@ -706,8 +779,8 @@ unsafe extern "C" fn call_get_sound_val(_timer_obj: *mut u8, _addr: u32) -> u32
706
  "push edi",
707
  // Stack: 2 saves(8) + ret(4) = 12 to first arg
708
  "mov edi, 0x6CB",
709
- "mov ebx, [esp+16]", // addr
710
- "push [esp+12]", // timer_obj
711
  "call ebx",
712
  "pop edi",
713
  "pop ebx",
@@ -718,7 +791,11 @@ unsafe extern "C" fn call_get_sound_val(_timer_obj: *mut u8, _addr: u32) -> u32
718
  /// Bridge: usercall(EAX=-21) + stdcall(4 params). Plain RET (callee doesn't clean).
719
  #[unsafe(naked)]
720
  unsafe extern "C" fn call_play_sound_usercall(
721
- _worm: *mut CTaskWorm, _sound_val: u32, _param3: u32, _name: *const u8, _addr: u32,
 
 
 
 
722
  ) {
723
  core::arch::naked_asm!(
724
  "push ebx",
@@ -756,7 +833,8 @@ unsafe fn fire_armageddon(worm: *mut CTaskWorm) {
756
  buf[0x0C..0x10].copy_from_slice(&((*worm).selected_weapon as u32).to_ne_bytes());
757
  buf[0x10..0x14].copy_from_slice(&(*worm).team_index.to_ne_bytes());
758
 
759
- CTaskTurnGame::handle_message_raw(team,
 
760
  worm as *mut openwa_core::task::CTask,
761
  0x5B,
762
  0x408,
@@ -785,16 +863,19 @@ unsafe fn fire_armageddon(worm: *mut CTaskWorm) {
785
  /// Bridge: usercall(ECX=half_x, EDX=half_y) + stdcall(worm), RET 0x4.
786
  #[unsafe(naked)]
787
  unsafe extern "C" fn call_set_gravity_center(
788
- _worm: *mut CTaskWorm, _half_x: i32, _half_y: i32, _addr: u32,
 
 
 
789
  ) {
790
  core::arch::naked_asm!(
791
  "push ebx",
792
  // Stack: 1 save(4) + ret(4) = 8 to first arg
793
- "mov ecx, [esp+12]", // half_x
794
- "mov edx, [esp+16]", // half_y
795
- "mov ebx, [esp+20]", // addr
796
- "push [esp+8]", // worm (shifted by 0 extra pushes before this)
797
- "call ebx", // RET 0x4 cleans worm
798
  "pop ebx",
799
  "ret",
800
  );
@@ -808,7 +889,9 @@ unsafe extern "C" fn call_set_gravity_center(
808
  ///
809
  /// Convention: stdcall(worm, fire_params, local_struct), RET 0xC.
810
  unsafe fn fire_dragon_ball(
811
- worm: *mut CTaskWorm, fire_params: *const WeaponFireParams, local_struct: *const u8,
 
 
812
  ) {
813
  use openwa_core::rebase::rb;
814
  use openwa_core::task::SharedDataTable;
@@ -828,7 +911,13 @@ unsafe fn fire_dragon_ball(
828
  // CTaskGirder::Constructor — usercall(EAX=parent) +
829
  // stdcall(this, 7×fire_param DWORDs, local_struct), RET 0x24.
830
  // Copy 7 DWORDs from fire_params onto the stack via the naked bridge.
831
- call_girder_ctor(buffer, parent as *mut u8, fire_params, local_struct, rb(0x550890));
 
 
 
 
 
 
832
  }
833
 
834
  /// Bridge: CTaskGirder::Constructor — usercall(EAX=parent) +
@@ -838,8 +927,11 @@ unsafe fn fire_dragon_ball(
838
  /// We replicate this by pushing them individually in reverse order.
839
  #[unsafe(naked)]
840
  unsafe extern "C" fn call_girder_ctor(
841
- _this: *mut u8, _parent: *mut u8, _fire_params: *const WeaponFireParams,
842
- _local_struct: *const u8, _addr: u32,
 
 
 
843
  ) {
844
  core::arch::naked_asm!(
845
  "push ebx",
@@ -847,20 +939,20 @@ unsafe extern "C" fn call_girder_ctor(
847
  "push edi",
848
  // Stack: 3 saves(12) + ret(4) = 16 to first arg
849
  // Args: this=+16, parent=+20, fire_params=+24, local_struct=+28, addr=+32
850
- "mov esi, [esp+24]", // ESI = fire_params
851
- "mov eax, [esp+20]", // EAX = parent (usercall)
852
- "mov ebx, [esp+32]", // EBX = ctor address
853
  // Push constructor args in reverse: local_struct, 7 DWORDs, this
854
- "push [esp+28]", // local_struct (B+28 → stack pos 1)
855
- "push [esi+24]", // fire_params[6]
856
- "push [esi+20]", // fire_params[5]
857
- "push [esi+16]", // fire_params[4]
858
- "push [esi+12]", // fire_params[3]
859
- "push [esi+8]", // fire_params[2]
860
- "push [esi+4]", // fire_params[1]
861
- "push [esi]", // fire_params[0] (B-28 → stack pos 8)
862
- "push [esp+48]", // this: B-32+48 = B+16 ✓
863
- "call ebx", // RET 0x24 cleans 9 params
864
  "pop edi",
865
  "pop esi",
866
  "pop ebx",
@@ -897,7 +989,6 @@ unsafe extern "C" fn call_fire_usercall(_eax: *const (), _worm: *mut CTaskWorm,
897
  /// Convention: usercall(EAX=worm, ESI=worm, EDI=worm), plain RET.
898
  unsafe fn fire_girder(worm: *mut CTaskWorm) {
899
  use crate::replacements::sound;
900
- use openwa_core::rebase::rb;
901
 
902
  let ddgame = CTask::ddgame_raw(worm as *const CTask);
903
  let game_version = (*(*ddgame).game_info).game_version;
@@ -933,9 +1024,8 @@ unsafe fn fire_girder(worm: *mut CTaskWorm) {
933
  girder_visual(landscape, girder_x >> 16, girder_y >> 16, sprite1, sprite2);
934
 
935
  // Increment WormEntry counters
936
- let arena = openwa_core::engine::ddgame::TeamArenaRef::from_ptr(
937
- &raw mut (*ddgame).team_arena,
938
- );
939
  let team_index = (*worm).team_index as usize;
940
  let worm_index = (*worm).worm_index as usize;
941
  let entry = arena.team_worm_mut(team_index, worm_index);
@@ -954,19 +1044,51 @@ unsafe fn fire_girder(worm: *mut CTaskWorm) {
954
  #[allow(clippy::too_many_arguments)]
955
  unsafe fn call_special_impact(
956
  worm: *mut CTaskWorm,
957
- x: i32, y: i32,
958
- radius_x: i32, radius_y: i32,
 
 
959
  weapon_type: i32,
960
- dx: i32, dy: i32,
961
- p8: i32, p9: i32, p10: i32,
962
- flags: u32, p12: i32,
 
 
 
 
963
  ) {
964
  use openwa_core::rebase::rb;
965
  type SpecialImpactFn = unsafe extern "stdcall" fn(
966
- *mut CTaskWorm, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, u32, i32,
 
 
 
 
 
 
 
 
 
 
 
 
967
  );
968
  let func: SpecialImpactFn = core::mem::transmute(rb(va::SPECIAL_IMPACT));
969
- func(worm, x, y, radius_x, radius_y, weapon_type, dx, dy, p8, p9, p10, flags, p12);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
970
  }
971
 
972
  /// Compute version-dependent flags for SpecialImpact.
@@ -976,8 +1098,12 @@ unsafe fn call_special_impact(
976
  /// - Base flags OR 0x10 if version >= 8
977
  fn special_impact_version_flags(base: u32, version: u8) -> u32 {
978
  let mut flags = base;
979
- if version >= 2 { flags |= 0x20; }
980
- if version >= 8 { flags |= 0x10; }
 
 
 
 
981
  flags
982
  }
983
 
@@ -1004,11 +1130,19 @@ unsafe fn fire_drill(worm: *mut CTaskWorm, local_struct: *const u8) {
1004
  let flags = special_impact_version_flags(0x21C4C, version);
1005
 
1006
  call_special_impact(
1007
- worm, x, y,
1008
- 0x1A_0000, 0x1E_0000,
1009
- weapon_type, dx, dy,
1010
- 6, 0x61, 0x51,
1011
- flags, 1,
 
 
 
 
 
 
 
 
1012
  );
1013
  }
1014
 
@@ -1060,11 +1194,19 @@ unsafe fn fire_prod(worm: *mut CTaskWorm, local_struct: *const u8) {
1060
  let flags = special_impact_version_flags(0xC4C, version);
1061
 
1062
  call_special_impact(
1063
- worm, x, y,
1064
- 0xC_0000, 0xC_0000,
1065
- weapon_type, dx, dy,
1066
- 0, 0, 0,
1067
- flags, 1,
 
 
 
 
 
 
 
 
1068
  );
1069
  }
1070
 
@@ -1074,18 +1216,21 @@ unsafe fn fire_prod(worm: *mut CTaskWorm, local_struct: *const u8) {
1074
  /// Args: (spawn_data, worm, fire_params, addr).
1075
  #[unsafe(naked)]
1076
  unsafe extern "C" fn call_projectile_fire_single(
1077
- _spawn_data: *const WeaponSpawnData, _worm: *mut CTaskWorm, _fire_params: *const WeaponFireParams, _addr: u32,
 
 
 
1078
  ) {
1079
  core::arch::naked_asm!(
1080
  "push ebx",
1081
  "push esi",
1082
  "push edi",
1083
  // Stack: 3 saves(12) + ret(4) = 16 to first arg
1084
- "mov edi, [esp+16]", // EDI = spawn_data
1085
- "mov ebx, [esp+28]", // addr
1086
- "push [esp+24]", // fire_params
1087
- "push [esp+24]", // worm (shifted +4)
1088
- "call ebx", // RET 0x8 cleans 2 params
1089
  "pop edi",
1090
  "pop esi",
1091
  "pop ebx",
@@ -1097,19 +1242,23 @@ unsafe extern "C" fn call_projectile_fire_single(
1097
  /// Args: (this, parent, fire_params, spawn_data, ctor_addr).
1098
  #[unsafe(naked)]
1099
  unsafe extern "C" fn call_missile_ctor(
1100
- _this: *mut u8, _parent: *mut u8, _fire_params: *const WeaponFireParams, _spawn_data: *const u8, _addr: u32,
 
 
 
 
1101
  ) {
1102
  core::arch::naked_asm!(
1103
  "push ebx",
1104
  "push esi",
1105
  "push edi",
1106
  // Stack: 3 saves(12) + ret(4) = 16 to first arg
1107
- "mov ecx, [esp+16]", // ECX = this (buffer)
1108
- "mov ebx, [esp+32]", // addr (16 + 4*4 args = 32)
1109
- "push [esp+28]", // spawn_data
1110
- "push [esp+28]", // fire_params (shifted +4)
1111
- "push [esp+28]", // parent (shifted +8)
1112
- "call ebx", // thiscall: RET 0xC cleans 3 params
1113
  "pop edi",
1114
  "pop esi",
1115
  "pop ebx",
@@ -1117,7 +1266,6 @@ unsafe extern "C" fn call_missile_ctor(
1117
  );
1118
  }
1119
 
1120
-
1121
  // ============================================================
1122
  // Passthrough hooks — fire sub-functions
1123
  // ============================================================
@@ -1154,7 +1302,9 @@ unsafe extern "C" fn trampoline_create_weapon_projectile() {
1154
  /// Original: 0x51E0F0. Allocates CTaskMissile (0x40C bytes), looks up
1155
  /// parent CTaskTurnGame via SharedData, calls the original constructor.
1156
  unsafe extern "cdecl" fn create_weapon_projectile_impl(
1157
- worm: *mut CTaskWorm, fire_params: *const WeaponFireParams, local_struct: *const u8,
 
 
1158
  ) {
1159
  use openwa_core::rebase::rb;
1160
  use openwa_core::task::SharedDataTable;
@@ -1228,7 +1378,9 @@ unsafe extern "C" fn trampoline_projectile_fire() {
1228
  /// e. Write rotated velocity into local spawn data
1229
  /// f. Call ProjectileFire_Single(loop_counter, fire_params) with EDI=spawn_data
1230
  unsafe extern "cdecl" fn projectile_fire_impl(
1231
- worm: *mut CTaskWorm, fire_params: *const WeaponFireParams, local_struct: *const WeaponSpawnData,
 
 
1232
  ) {
1233
  use openwa_core::rebase::rb;
1234
 
@@ -1279,10 +1431,8 @@ unsafe extern "cdecl" fn projectile_fire_impl(
1279
  // 2D rotation matrix:
1280
  // speed_x = cos * template_x + sin * template_y
1281
  // speed_y = -sin * template_x + cos * template_y
1282
- let speed_x = fixed_mul(cos_val, template_speed_x)
1283
- + fixed_mul(sin_val, template_speed_y);
1284
- let speed_y = fixed_mul(-sin_val, template_speed_x)
1285
- + fixed_mul(cos_val, template_speed_y);
1286
 
1287
  // Write rotated velocity into spawn data
1288
  spawn_data.initial_speed_x = Fixed(speed_x);
@@ -1328,7 +1478,9 @@ unsafe extern "C" fn trampoline_create_arrow() {
1328
  }
1329
 
1330
  unsafe extern "cdecl" fn create_arrow_impl(
1331
- worm: *mut CTaskWorm, fire_params: *const WeaponFireParams, local_struct: *const u8,
 
 
1332
  ) {
1333
  use openwa_core::rebase::rb;
1334
  use openwa_core::task::SharedDataTable;
@@ -1442,20 +1594,48 @@ pub fn install() -> Result<(), String> {
1442
  unsafe {
1443
  let _ = hook::install("AddAmmo", va::ADD_AMMO, trampoline_add_ammo as *const ())?;
1444
  let _ = hook::install("GetAmmo", va::GET_AMMO, trampoline_get_ammo as *const ())?;
1445
- let _ = hook::install("SubtractAmmo", va::SUBTRACT_AMMO, trampoline_subtract_ammo as *const ())?;
1446
- let _ = hook::install("CountAliveWorms", va::COUNT_ALIVE_WORMS, trampoline_count_alive_worms as *const ())?;
 
 
 
 
 
 
 
 
1447
  hook::install_trap!("FireWeapon", va::FIRE_WEAPON);
1448
 
1449
  // Passthrough hooks on fire sub-functions (log + call original)
1450
- let t = hook::install("CreateWeaponProjectile", va::CREATE_WEAPON_PROJECTILE, trampoline_create_weapon_projectile as *const ())?;
 
 
 
 
1451
  ORIG_CREATE_WEAPON_PROJECTILE.store(t as u32, Ordering::Relaxed);
1452
- let t = hook::install("ProjectileFire", va::PROJECTILE_FIRE, trampoline_projectile_fire as *const ())?;
 
 
 
 
1453
  ORIG_PROJECTILE_FIRE.store(t as u32, Ordering::Relaxed);
1454
- let t = hook::install("StrikeFire", va::STRIKE_FIRE, trampoline_strike_fire as *const ())?;
 
 
 
 
1455
  ORIG_STRIKE_FIRE.store(t as u32, Ordering::Relaxed);
1456
- let t = hook::install("PlacedExplosive", va::PLACED_EXPLOSIVE, trampoline_placed_explosive as *const ())?;
 
 
 
 
1457
  ORIG_PLACED_EXPLOSIVE.store(t as u32, Ordering::Relaxed);
1458
- let _ = hook::install("CreateArrow", va::CREATE_ARROW, trampoline_create_arrow as *const ())?;
 
 
 
 
1459
  }
1460
 
1461
  Ok(())
 
23
  use openwa_core::game::Weapon;
24
  use openwa_core::log::log_line;
25
  use openwa_core::task::turn_game::CTaskTurnGame;
 
26
  use openwa_core::task::worm::{CTaskWorm, WormState};
27
+ use openwa_core::task::CTask;
28
 
29
  use crate::hook::{self, usercall_trampoline};
30
  use crate::replacements::weapon_release::WeaponReleaseContext;
 
103
  }
104
 
105
  // SelectWorm (0x3B) requires >1 alive worm on the team
106
+ if state.game_phase >= ddgame::GAME_PHASE_NORMAL_MIN
107
+ && weapon_id == Weapon::SelectWorm as u32
108
+ && count_alive_worms_impl(team_index, arena) == 0
109
+ {
110
+ return 0;
111
+ }
112
 
113
  state.get_ammo(alliance, wid) as u32
114
  }
 
167
  let weapon = (*worm).selected_weapon;
168
  let _ = log_line(&format!(
169
  "[Weapon] FireWeapon: {:?} (id={}) type={} sub34={} sub38={}",
170
+ weapon,
171
+ weapon as u32,
172
+ fire_type,
173
+ (*entry).special_subtype,
174
+ fire_method
175
  ));
176
 
177
  CTaskWorm::set_fire_complete_raw(worm, 0);
178
 
179
+ use openwa_core::game::weapon::{FireMethod, FireType};
180
  match FireType::try_from(fire_type) {
181
  Ok(FireType::Projectile) => match FireMethod::try_from(fire_method) {
182
+ Ok(FireMethod::PlacedExplosive) => {
183
+ call_fire_placed_explosive(worm, fire_params, ctx, rb(0x51EC80))
184
+ }
185
+ Ok(FireMethod::ProjectileFire) => {
186
+ call_fire_stdcall3(worm, fire_params, ctx, rb(0x51DFB0))
187
+ }
188
+ Ok(FireMethod::CreateWeaponProjectile) => {
189
+ call_fire_thiscall2(worm, fire_params, ctx, rb(0x51E0F0))
190
+ }
191
+ Ok(FireMethod::CreateArrow) => {
192
+ call_fire_thiscall2(worm, fire_params, ctx, rb(0x51ED90))
193
+ }
194
  _ => {}
195
  },
196
  Ok(FireType::Rope) => match FireMethod::try_from(fire_method) {
197
+ Ok(FireMethod::PlacedExplosive) => {
198
+ call_fire_stdcall3(worm, fire_params, ctx, rb(0x51E1C0))
199
+ } // RopeType1
200
+ Ok(FireMethod::ProjectileFire) => {
201
+ call_fire_thiscall2(worm, fire_params, ctx, rb(0x51E0F0))
202
+ }
203
+ Ok(FireMethod::CreateWeaponProjectile) => {
204
+ call_fire_stdcall3(worm, fire_params, ctx, rb(0x51E240))
205
+ } // RopeType3
206
  _ => {}
207
  },
208
  Ok(FireType::Strike) => {
 
228
  /// Args: (worm, fire_params, ctx, addr).
229
  #[unsafe(naked)]
230
  unsafe extern "C" fn call_fire_placed_explosive(
231
+ _worm: *mut CTaskWorm,
232
+ _fire_params: *const WeaponFireParams,
233
+ _ctx: *const WeaponReleaseContext,
234
+ _addr: u32,
235
  ) {
236
  core::arch::naked_asm!(
237
  "push ebx",
 
239
  "push edi",
240
  // Set up usercall registers
241
  // Stack: 3 saves(12) + ret(4) = 16 to first arg
242
+ "mov edx, [esp+16]", // EDX = worm
243
+ "mov ecx, [esp+24]", // ECX = local_struct
244
+ "mov ebx, [esp+28]", // addr
245
+ "push [esp+20]", // fire_params (stack param)
246
  "call ebx",
247
  "pop edi",
248
  "pop esi",
 
254
  /// Bridge: Projectile/Rope/Grenade — stdcall(worm, fire_params, local_struct), RET 0xC.
255
  #[unsafe(naked)]
256
  unsafe extern "C" fn call_fire_stdcall3(
257
+ _worm: *mut CTaskWorm,
258
+ _fire_params: *const WeaponFireParams,
259
+ _ctx: *const WeaponReleaseContext,
260
+ _addr: u32,
261
  ) {
262
  core::arch::naked_asm!(
263
  "push ebx",
264
  "push esi",
265
  "push edi",
266
+ "mov esi, [esp+16]", // worm (3 saves=12 + ret=4 = 16)
267
  "mov edi, [esp+16]",
268
+ "mov ebx, [esp+28]", // addr (saves=12 + ret=4 + 3 args=12 = 28)
269
+ "push [esp+24]", // local_struct
270
+ "push [esp+24]", // params (shifted +4)
271
+ "push [esp+24]", // worm (shifted +8)
272
  "call ebx",
273
  "pop edi",
274
  "pop esi",
 
280
  /// Bridge: CreateWeaponProjectile — thiscall(ECX=worm, fire_params, local_struct), RET 0x8.
281
  #[unsafe(naked)]
282
  unsafe extern "C" fn call_fire_thiscall2(
283
+ _worm: *mut CTaskWorm,
284
+ _fire_params: *const WeaponFireParams,
285
+ _ctx: *const WeaponReleaseContext,
286
+ _addr: u32,
287
  ) {
288
  core::arch::naked_asm!(
289
  "push ebx",
290
  "push esi",
291
  "push edi",
292
+ "mov esi, [esp+16]", // worm
293
  "mov edi, [esp+16]",
294
+ "mov ecx, [esp+16]", // ECX = worm (this)
295
+ "mov ebx, [esp+28]", // addr
296
+ "push [esp+24]", // local_struct
297
+ "push [esp+24]", // params (shifted +4)
298
  "call ebx",
299
  "pop edi",
300
  "pop esi",
 
308
  #[unsafe(naked)]
309
  #[allow(dead_code)]
310
  unsafe extern "C" fn call_fire_stdcall2(
311
+ _worm: *mut CTaskWorm,
312
+ _fire_params: *const WeaponFireParams,
313
+ _ctx: *const WeaponReleaseContext,
314
+ _addr: u32,
315
  ) {
316
  core::arch::naked_asm!(
317
  "push ebx",
318
  "push esi",
319
  "push edi",
320
+ "mov esi, [esp+16]", // worm
321
  "mov edi, [esp+16]",
322
+ "mov ebx, [esp+28]", // addr
323
+ "push [esp+24]", // local_struct
324
+ "push [esp+24]", // params (shifted +4)
325
  "call ebx",
326
  "pop edi",
327
  "pop esi",
 
336
  /// Some handlers explicitly set EAX=worm or EAX=*(worm+0x44).
337
  /// Handlers without explicit MOV EAX inherit the entry pointer.
338
  unsafe fn fire_weapon_special(
339
+ subtype: i32,
340
+ entry: *const WeaponEntry,
341
+ worm: *mut CTaskWorm,
342
+ ctx: *const WeaponReleaseContext,
343
  ) {
344
  use openwa_core::game::weapon::SpecialFireSubtype as S;
345
  use openwa_core::rebase::rb;
 
393
  ///
394
  /// The entity at key (0, 0x14) is a CTaskTurnGame (inherits CTaskTeam).
395
  /// Returns null if not found.
396
+ pub(crate) unsafe fn lookup_turn_game(
397
+ worm: *const CTaskWorm,
398
+ ) -> *mut openwa_core::task::CTaskTurnGame {
399
  use openwa_core::task::SharedDataTable;
400
 
401
  let table = SharedDataTable::from_task(worm as *const CTask);
 
445
  let team_index = (*worm).team_index;
446
  buf[0..4].copy_from_slice(&team_index.to_ne_bytes());
447
 
448
+ CTaskTurnGame::handle_message_raw(
449
+ team,
450
  worm as *mut openwa_core::task::CTask,
451
  msg_type,
452
  4,
 
467
  buf[0..4].copy_from_slice(&8u32.to_ne_bytes());
468
  buf[4..8].copy_from_slice(&(*worm).team_index.to_ne_bytes());
469
 
470
+ CTaskTurnGame::handle_message_raw(
471
+ team,
472
  worm as *mut openwa_core::task::CTask,
473
  0x5D,
474
  0x408,
 
542
  || (worm_state == WormState::WeaponAimed_Maybe as u32 && version < 8)));
543
 
544
  if should_call_vtable {
545
+ CTaskWorm::set_state_raw(worm, WormState::Idle);
546
  }
547
 
548
  fire_send_team_message(worm, 0x28);
 
566
  use openwa_core::rebase::rb;
567
 
568
  if (*worm)._unknown_208 == 0 {
569
+ CTaskWorm::set_state_raw(worm, WormState::AirStrikePending_Maybe);
570
  return;
571
  }
572
 
 
593
  } else {
594
  WormState::Unknown_0x8B
595
  };
596
+ CTaskWorm::set_state_raw(worm, new_state);
597
 
598
  // Clear action fields
599
  *(worm_raw.add(0x48) as *mut i32) = 0;
 
626
  let mut buf = [0u8; 0x40C];
627
  buf[0..4].copy_from_slice(&entry.fire_method.to_ne_bytes());
628
  buf[4..8].copy_from_slice(&8i32.to_ne_bytes());
629
+ CTaskTurnGame::handle_message_raw(
630
+ team,
631
+ worm as *mut openwa_core::task::CTask,
632
+ 0x59,
633
+ 0x408,
634
+ buf.as_ptr(),
635
+ );
636
  }
637
 
638
  // Message 0x5A (NukeBlast): buf[0]=8
 
640
  if !team.is_null() {
641
  let mut buf = [0u8; 0x40C];
642
  buf[0..4].copy_from_slice(&8i32.to_ne_bytes());
643
+ CTaskTurnGame::handle_message_raw(
644
+ team,
645
+ worm as *mut openwa_core::task::CTask,
646
+ 0x5A,
647
+ 0x408,
648
+ buf.as_ptr(),
649
+ );
650
  }
651
 
652
  // PlaySoundGlobal(IndianAnthem, 8, 0x10000, 0x10000)
653
+ sound::queue_sound(
654
+ ddgame,
655
+ KnownSoundId::IndianAnthem.into(),
656
+ 8,
657
+ Fixed::ONE,
658
+ Fixed::ONE,
659
+ );
660
 
661
  // Message 0x51 (PoisonWorm): buf[0]=shot_count, buf[4]=2, buf[8]=team_index
662
  let team = lookup_turn_game(worm);
 
665
  buf[0..4].copy_from_slice(&entry.fire_params.shot_count.to_ne_bytes());
666
  buf[4..8].copy_from_slice(&2i32.to_ne_bytes());
667
  buf[8..12].copy_from_slice(&(*worm).team_index.to_ne_bytes());
668
+ CTaskTurnGame::handle_message_raw(
669
+ team,
670
+ worm as *mut openwa_core::task::CTask,
671
+ 0x51,
672
+ 0x408,
673
+ buf.as_ptr(),
674
+ );
675
  }
676
 
677
  // PlaySoundGlobal(NukeFlash, 5, 0x10000, 0x10000)
678
+ sound::queue_sound(
679
+ ddgame,
680
+ KnownSoundId::NukeFlash.into(),
681
+ 5,
682
+ Fixed::ONE,
683
+ Fixed::ONE,
684
+ );
685
  }
686
 
 
687
  /// Bridge: FUN_00547C30 — usercall(EAX=0x80000, ECX=x) + stdcall(y, 0,0,0, 600, 0x10000, 0x1999).
688
  #[unsafe(naked)]
689
  unsafe extern "C" fn call_spawn_effect(_x: i32, _y: i32, _addr: u32) {
690
  core::arch::naked_asm!(
691
  "push ebx",
692
  // Stack: 1 save(4) + ret(4) = 8 to first arg
693
+ "mov ecx, [esp+8]", // x
694
+ "mov ebx, [esp+16]", // addr
695
  "push 0x1999",
696
  "push 0x10000",
697
+ "push 0x258", // 600
698
  "push 0",
699
  "push 0",
700
  "push 0",
701
+ "push [esp+36]", // y (8 + 4 + 6*4 = 36)
702
  "mov eax, 0x80000",
703
  "call ebx",
704
  "pop ebx",
 
708
 
709
  /// Bridge: FUN_004FE070 — usercall(ESI=worm, EDI=y) + stdcall(x). Plain RET.
710
  #[unsafe(naked)]
711
+ unsafe extern "C" fn call_position_update(_worm: *mut CTaskWorm, _x: i32, _y: i32, _addr: u32) {
 
 
712
  core::arch::naked_asm!(
713
  "push ebx",
714
  "push esi",
715
  "push edi",
716
  // Stack: 3 saves(12) + ret(4) = 16 to first arg
717
+ "mov esi, [esp+16]", // worm
718
+ "mov edi, [esp+24]", // y
719
+ "mov ebx, [esp+28]", // addr
720
+ "push [esp+20]", // x
721
  "call ebx",
722
  "pop edi",
723
  "pop esi",
 
731
  unsafe extern "C" fn call_worm_landing_check(_worm: *mut CTaskWorm, _addr: u32) {
732
  core::arch::naked_asm!(
733
  "push esi",
734
+ "mov esi, [esp+8]", // worm (1 save + ret = 8)
735
+ "call [esp+12]", // addr (8 + 4 = 12)
736
  "pop esi",
737
  "ret",
738
  );
 
748
  // Send message 0x5E to CTaskTurnGame
749
  let team = lookup_turn_game(worm);
750
  if !team.is_null() {
751
+ CTaskTurnGame::handle_message_raw(
752
+ team,
753
  worm as *mut openwa_core::task::CTask,
754
  0x5E,
755
  0,
 
762
  // FUN_005480F0: usercall(EAX=-21) + stdcall(worm, result, 0x17, &worm_name)
763
  let ddgame = CTask::ddgame_raw(worm as *const CTask);
764
  let sound_val = call_get_sound_val((*ddgame).timer_obj, rb(0x53EC70));
765
+ call_play_sound_usercall(
766
+ worm,
767
+ sound_val,
768
+ 0x17,
769
+ (*worm).worm_name.as_ptr(),
770
+ rb(0x5480F0),
771
+ );
772
  }
773
 
774
  /// Bridge: FUN_0053EC70 — usercall(EDI=0x6CB) + stdcall(timer_obj). Returns EAX.
 
779
  "push edi",
780
  // Stack: 2 saves(8) + ret(4) = 12 to first arg
781
  "mov edi, 0x6CB",
782
+ "mov ebx, [esp+16]", // addr
783
+ "push [esp+12]", // timer_obj
784
  "call ebx",
785
  "pop edi",
786
  "pop ebx",
 
791
  /// Bridge: usercall(EAX=-21) + stdcall(4 params). Plain RET (callee doesn't clean).
792
  #[unsafe(naked)]
793
  unsafe extern "C" fn call_play_sound_usercall(
794
+ _worm: *mut CTaskWorm,
795
+ _sound_val: u32,
796
+ _param3: u32,
797
+ _name: *const u8,
798
+ _addr: u32,
799
  ) {
800
  core::arch::naked_asm!(
801
  "push ebx",
 
833
  buf[0x0C..0x10].copy_from_slice(&((*worm).selected_weapon as u32).to_ne_bytes());
834
  buf[0x10..0x14].copy_from_slice(&(*worm).team_index.to_ne_bytes());
835
 
836
+ CTaskTurnGame::handle_message_raw(
837
+ team,
838
  worm as *mut openwa_core::task::CTask,
839
  0x5B,
840
  0x408,
 
863
  /// Bridge: usercall(ECX=half_x, EDX=half_y) + stdcall(worm), RET 0x4.
864
  #[unsafe(naked)]
865
  unsafe extern "C" fn call_set_gravity_center(
866
+ _worm: *mut CTaskWorm,
867
+ _half_x: i32,
868
+ _half_y: i32,
869
+ _addr: u32,
870
  ) {
871
  core::arch::naked_asm!(
872
  "push ebx",
873
  // Stack: 1 save(4) + ret(4) = 8 to first arg
874
+ "mov ecx, [esp+12]", // half_x
875
+ "mov edx, [esp+16]", // half_y
876
+ "mov ebx, [esp+20]", // addr
877
+ "push [esp+8]", // worm (shifted by 0 extra pushes before this)
878
+ "call ebx", // RET 0x4 cleans worm
879
  "pop ebx",
880
  "ret",
881
  );
 
889
  ///
890
  /// Convention: stdcall(worm, fire_params, local_struct), RET 0xC.
891
  unsafe fn fire_dragon_ball(
892
+ worm: *mut CTaskWorm,
893
+ fire_params: *const WeaponFireParams,
894
+ local_struct: *const u8,
895
  ) {
896
  use openwa_core::rebase::rb;
897
  use openwa_core::task::SharedDataTable;
 
911
  // CTaskGirder::Constructor — usercall(EAX=parent) +
912
  // stdcall(this, 7×fire_param DWORDs, local_struct), RET 0x24.
913
  // Copy 7 DWORDs from fire_params onto the stack via the naked bridge.
914
+ call_girder_ctor(
915
+ buffer,
916
+ parent as *mut u8,
917
+ fire_params,
918
+ local_struct,
919
+ rb(0x550890),
920
+ );
921
  }
922
 
923
  /// Bridge: CTaskGirder::Constructor — usercall(EAX=parent) +
 
927
  /// We replicate this by pushing them individually in reverse order.
928
  #[unsafe(naked)]
929
  unsafe extern "C" fn call_girder_ctor(
930
+ _this: *mut u8,
931
+ _parent: *mut u8,
932
+ _fire_params: *const WeaponFireParams,
933
+ _local_struct: *const u8,
934
+ _addr: u32,
935
  ) {
936
  core::arch::naked_asm!(
937
  "push ebx",
 
939
  "push edi",
940
  // Stack: 3 saves(12) + ret(4) = 16 to first arg
941
  // Args: this=+16, parent=+20, fire_params=+24, local_struct=+28, addr=+32
942
+ "mov esi, [esp+24]", // ESI = fire_params
943
+ "mov eax, [esp+20]", // EAX = parent (usercall)
944
+ "mov ebx, [esp+32]", // EBX = ctor address
945
  // Push constructor args in reverse: local_struct, 7 DWORDs, this
946
+ "push [esp+28]", // local_struct (B+28 → stack pos 1)
947
+ "push [esi+24]", // fire_params[6]
948
+ "push [esi+20]", // fire_params[5]
949
+ "push [esi+16]", // fire_params[4]
950
+ "push [esi+12]", // fire_params[3]
951
+ "push [esi+8]", // fire_params[2]
952
+ "push [esi+4]", // fire_params[1]
953
+ "push [esi]", // fire_params[0] (B-28 → stack pos 8)
954
+ "push [esp+48]", // this: B-32+48 = B+16 ✓
955
+ "call ebx", // RET 0x24 cleans 9 params
956
  "pop edi",
957
  "pop esi",
958
  "pop ebx",
 
989
  /// Convention: usercall(EAX=worm, ESI=worm, EDI=worm), plain RET.
990
  unsafe fn fire_girder(worm: *mut CTaskWorm) {
991
  use crate::replacements::sound;
 
992
 
993
  let ddgame = CTask::ddgame_raw(worm as *const CTask);
994
  let game_version = (*(*ddgame).game_info).game_version;
 
1024
  girder_visual(landscape, girder_x >> 16, girder_y >> 16, sprite1, sprite2);
1025
 
1026
  // Increment WormEntry counters
1027
+ let arena =
1028
+ openwa_core::engine::ddgame::TeamArenaRef::from_ptr(&raw mut (*ddgame).team_arena);
 
1029
  let team_index = (*worm).team_index as usize;
1030
  let worm_index = (*worm).worm_index as usize;
1031
  let entry = arena.team_worm_mut(team_index, worm_index);
 
1044
  #[allow(clippy::too_many_arguments)]
1045
  unsafe fn call_special_impact(
1046
  worm: *mut CTaskWorm,
1047
+ x: i32,
1048
+ y: i32,
1049
+ radius_x: i32,
1050
+ radius_y: i32,
1051
  weapon_type: i32,
1052
+ dx: i32,
1053
+ dy: i32,
1054
+ p8: i32,
1055
+ p9: i32,
1056
+ p10: i32,
1057
+ flags: u32,
1058
+ p12: i32,
1059
  ) {
1060
  use openwa_core::rebase::rb;
1061
  type SpecialImpactFn = unsafe extern "stdcall" fn(
1062
+ *mut CTaskWorm,
1063
+ i32,
1064
+ i32,
1065
+ i32,
1066
+ i32,
1067
+ i32,
1068
+ i32,
1069
+ i32,
1070
+ i32,
1071
+ i32,
1072
+ i32,
1073
+ u32,
1074
+ i32,
1075
  );
1076
  let func: SpecialImpactFn = core::mem::transmute(rb(va::SPECIAL_IMPACT));
1077
+ func(
1078
+ worm,
1079
+ x,
1080
+ y,
1081
+ radius_x,
1082
+ radius_y,
1083
+ weapon_type,
1084
+ dx,
1085
+ dy,
1086
+ p8,
1087
+ p9,
1088
+ p10,
1089
+ flags,
1090
+ p12,
1091
+ );
1092
  }
1093
 
1094
  /// Compute version-dependent flags for SpecialImpact.
 
1098
  /// - Base flags OR 0x10 if version >= 8
1099
  fn special_impact_version_flags(base: u32, version: u8) -> u32 {
1100
  let mut flags = base;
1101
+ if version >= 2 {
1102
+ flags |= 0x20;
1103
+ }
1104
+ if version >= 8 {
1105
+ flags |= 0x10;
1106
+ }
1107
  flags
1108
  }
1109
 
 
1130
  let flags = special_impact_version_flags(0x21C4C, version);
1131
 
1132
  call_special_impact(
1133
+ worm,
1134
+ x,
1135
+ y,
1136
+ 0x1A_0000,
1137
+ 0x1E_0000,
1138
+ weapon_type,
1139
+ dx,
1140
+ dy,
1141
+ 6,
1142
+ 0x61,
1143
+ 0x51,
1144
+ flags,
1145
+ 1,
1146
  );
1147
  }
1148
 
 
1194
  let flags = special_impact_version_flags(0xC4C, version);
1195
 
1196
  call_special_impact(
1197
+ worm,
1198
+ x,
1199
+ y,
1200
+ 0xC_0000,
1201
+ 0xC_0000,
1202
+ weapon_type,
1203
+ dx,
1204
+ dy,
1205
+ 0,
1206
+ 0,
1207
+ 0,
1208
+ flags,
1209
+ 1,
1210
  );
1211
  }
1212
 
 
1216
  /// Args: (spawn_data, worm, fire_params, addr).
1217
  #[unsafe(naked)]
1218
  unsafe extern "C" fn call_projectile_fire_single(
1219
+ _spawn_data: *const WeaponSpawnData,
1220
+ _worm: *mut CTaskWorm,
1221
+ _fire_params: *const WeaponFireParams,
1222
+ _addr: u32,
1223
  ) {
1224
  core::arch::naked_asm!(
1225
  "push ebx",
1226
  "push esi",
1227
  "push edi",
1228
  // Stack: 3 saves(12) + ret(4) = 16 to first arg
1229
+ "mov edi, [esp+16]", // EDI = spawn_data
1230
+ "mov ebx, [esp+28]", // addr
1231
+ "push [esp+24]", // fire_params
1232
+ "push [esp+24]", // worm (shifted +4)
1233
+ "call ebx", // RET 0x8 cleans 2 params
1234
  "pop edi",
1235
  "pop esi",
1236
  "pop ebx",
 
1242
  /// Args: (this, parent, fire_params, spawn_data, ctor_addr).
1243
  #[unsafe(naked)]
1244
  unsafe extern "C" fn call_missile_ctor(
1245
+ _this: *mut u8,
1246
+ _parent: *mut u8,
1247
+ _fire_params: *const WeaponFireParams,
1248
+ _spawn_data: *const u8,
1249
+ _addr: u32,
1250
  ) {
1251
  core::arch::naked_asm!(
1252
  "push ebx",
1253
  "push esi",
1254
  "push edi",
1255
  // Stack: 3 saves(12) + ret(4) = 16 to first arg
1256
+ "mov ecx, [esp+16]", // ECX = this (buffer)
1257
+ "mov ebx, [esp+32]", // addr (16 + 4*4 args = 32)
1258
+ "push [esp+28]", // spawn_data
1259
+ "push [esp+28]", // fire_params (shifted +4)
1260
+ "push [esp+28]", // parent (shifted +8)
1261
+ "call ebx", // thiscall: RET 0xC cleans 3 params
1262
  "pop edi",
1263
  "pop esi",
1264
  "pop ebx",
 
1266
  );
1267
  }
1268
 
 
1269
  // ============================================================
1270
  // Passthrough hooks — fire sub-functions
1271
  // ============================================================
 
1302
  /// Original: 0x51E0F0. Allocates CTaskMissile (0x40C bytes), looks up
1303
  /// parent CTaskTurnGame via SharedData, calls the original constructor.
1304
  unsafe extern "cdecl" fn create_weapon_projectile_impl(
1305
+ worm: *mut CTaskWorm,
1306
+ fire_params: *const WeaponFireParams,
1307
+ local_struct: *const u8,
1308
  ) {
1309
  use openwa_core::rebase::rb;
1310
  use openwa_core::task::SharedDataTable;
 
1378
  /// e. Write rotated velocity into local spawn data
1379
  /// f. Call ProjectileFire_Single(loop_counter, fire_params) with EDI=spawn_data
1380
  unsafe extern "cdecl" fn projectile_fire_impl(
1381
+ worm: *mut CTaskWorm,
1382
+ fire_params: *const WeaponFireParams,
1383
+ local_struct: *const WeaponSpawnData,
1384
  ) {
1385
  use openwa_core::rebase::rb;
1386
 
 
1431
  // 2D rotation matrix:
1432
  // speed_x = cos * template_x + sin * template_y
1433
  // speed_y = -sin * template_x + cos * template_y
1434
+ let speed_x = fixed_mul(cos_val, template_speed_x) + fixed_mul(sin_val, template_speed_y);
1435
+ let speed_y = fixed_mul(-sin_val, template_speed_x) + fixed_mul(cos_val, template_speed_y);
 
 
1436
 
1437
  // Write rotated velocity into spawn data
1438
  spawn_data.initial_speed_x = Fixed(speed_x);
 
1478
  }
1479
 
1480
  unsafe extern "cdecl" fn create_arrow_impl(
1481
+ worm: *mut CTaskWorm,
1482
+ fire_params: *const WeaponFireParams,
1483
+ local_struct: *const u8,
1484
  ) {
1485
  use openwa_core::rebase::rb;
1486
  use openwa_core::task::SharedDataTable;
 
1594
  unsafe {
1595
  let _ = hook::install("AddAmmo", va::ADD_AMMO, trampoline_add_ammo as *const ())?;
1596
  let _ = hook::install("GetAmmo", va::GET_AMMO, trampoline_get_ammo as *const ())?;
1597
+ let _ = hook::install(
1598
+ "SubtractAmmo",
1599
+ va::SUBTRACT_AMMO,
1600
+ trampoline_subtract_ammo as *const (),
1601
+ )?;
1602
+ let _ = hook::install(
1603
+ "CountAliveWorms",
1604
+ va::COUNT_ALIVE_WORMS,
1605
+ trampoline_count_alive_worms as *const (),
1606
+ )?;
1607
  hook::install_trap!("FireWeapon", va::FIRE_WEAPON);
1608
 
1609
  // Passthrough hooks on fire sub-functions (log + call original)
1610
+ let t = hook::install(
1611
+ "CreateWeaponProjectile",
1612
+ va::CREATE_WEAPON_PROJECTILE,
1613
+ trampoline_create_weapon_projectile as *const (),
1614
+ )?;
1615
  ORIG_CREATE_WEAPON_PROJECTILE.store(t as u32, Ordering::Relaxed);
1616
+ let t = hook::install(
1617
+ "ProjectileFire",
1618
+ va::PROJECTILE_FIRE,
1619
+ trampoline_projectile_fire as *const (),
1620
+ )?;
1621
  ORIG_PROJECTILE_FIRE.store(t as u32, Ordering::Relaxed);
1622
+ let t = hook::install(
1623
+ "StrikeFire",
1624
+ va::STRIKE_FIRE,
1625
+ trampoline_strike_fire as *const (),
1626
+ )?;
1627
  ORIG_STRIKE_FIRE.store(t as u32, Ordering::Relaxed);
1628
+ let t = hook::install(
1629
+ "PlacedExplosive",
1630
+ va::PLACED_EXPLOSIVE,
1631
+ trampoline_placed_explosive as *const (),
1632
+ )?;
1633
  ORIG_PLACED_EXPLOSIVE.store(t as u32, Ordering::Relaxed);
1634
+ let _ = hook::install(
1635
+ "CreateArrow",
1636
+ va::CREATE_ARROW,
1637
+ trampoline_create_arrow as *const (),
1638
+ )?;
1639
  }
1640
 
1641
  Ok(())