paavohtl Claude Opus 4.6 (1M context) commited on
Commit
654bf33
Β·
1 Parent(s): 07b71e5

refactor: add weapon fire type enums, use Fixed for aim/scale params

Browse files

Add FireType, FireMethod, and SpecialFireSubtype enums to weapon.rs
with TryFrom<i32>. All weapon dispatch matches in weapon.rs and
weapon_release.rs now use typed enum variants instead of raw integers.

Rename fire_subtype_34 β†’ special_subtype, fire_subtype_38 β†’ fire_method
in WeaponEntry for clarity.

Use Fixed type for aim direction params and landscape_scale field.
Add Mul<i32> for Fixed (integer scaling without shift). Remove
fixed_mul_shrd β€” superseded by Fixed's Mul impl. Spawn offset
calculation now reads as: aim_dir_x * scale * 0x18.

Rename param_1/param_2 β†’ spawn_x/spawn_y, param_3/param_4 β†’
aim_dir_x/aim_dir_y based on caller analysis (FUN_0051cb60 computes
adjusted worm position + aim direction from angle).

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

crates/openwa-core/src/fixed.rs CHANGED
@@ -72,6 +72,15 @@ impl Mul for Fixed {
72
  }
73
  }
74
 
 
 
 
 
 
 
 
 
 
75
  impl Neg for Fixed {
76
  type Output = Self;
77
  #[inline]
 
72
  }
73
  }
74
 
75
+ /// Scale a Fixed value by a plain integer (no shift β€” just `raw * n`).
76
+ impl Mul<i32> for Fixed {
77
+ type Output = Self;
78
+ #[inline]
79
+ fn mul(self, rhs: i32) -> Self {
80
+ Self(self.0 * rhs)
81
+ }
82
+ }
83
+
84
  impl Neg for Fixed {
85
  type Output = Self;
86
  #[inline]
crates/openwa-core/src/game/weapon.rs CHANGED
@@ -143,6 +143,130 @@ const _: () = assert!(core::mem::size_of::<WeaponSpawnData>() == 0x2C);
143
 
144
  /// Per-weapon data entry in the weapon table (0x1D0 = 464 bytes).
145
  ///
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
146
  /// 71 standard entries (indices 0..70), matching the `Weapon` enum.
147
  /// Source: wkJellyWorm/src/CustomWeapons.h (WeaponStruct).
148
  ///
@@ -183,9 +307,9 @@ pub struct WeaponEntry {
183
  /// Read by FireWeapon to dispatch to the correct handler.
184
  pub fire_type: i32,
185
  /// +0x34: Fire subtype for weapon types 3 (grenade/mortar) and 4 (special).
186
- pub fire_subtype_34: i32,
187
  /// +0x38: Fire subtype for weapon types 1 (projectile) and 2 (rope).
188
- pub fire_subtype_38: i32,
189
  /// +0x3C: Fire parameters sub-structure. Pointer to this field is passed
190
  /// to fire sub-functions (PlacedExplosive, Projectile, CreateWeaponProjectile, etc.).
191
  pub fire_params: WeaponFireParams,
 
143
 
144
  /// Per-weapon data entry in the weapon table (0x1D0 = 464 bytes).
145
  ///
146
+ /// Top-level weapon fire type (WeaponEntry+0x30).
147
+ ///
148
+ /// Determines which sub-function handles the weapon fire in FireWeapon dispatch.
149
+ #[derive(Debug, Clone, Copy, PartialEq, Eq)]
150
+ #[repr(i32)]
151
+ pub enum FireType {
152
+ /// Projectile weapons (Bazooka, Grenade, Shotgun, etc.).
153
+ /// Sub-dispatched by `fire_method`.
154
+ Projectile = 1,
155
+ /// Rope-based weapons (Ninja Rope, Bungee).
156
+ /// Sub-dispatched by `fire_method`.
157
+ Rope = 2,
158
+ /// Strike weapons (Air Strike, Napalm Strike, Mail Strike, etc.).
159
+ /// Uses `special_subtype` as parameter data (not a subtype selector).
160
+ Strike = 3,
161
+ /// Special weapons (melee, utility, powerups).
162
+ /// Sub-dispatched by `special_subtype`.
163
+ Special = 4,
164
+ }
165
+
166
+ impl TryFrom<i32> for FireType {
167
+ type Error = i32;
168
+ fn try_from(v: i32) -> Result<Self, i32> {
169
+ match v {
170
+ 1 => Ok(Self::Projectile),
171
+ 2 => Ok(Self::Rope),
172
+ 3 => Ok(Self::Strike),
173
+ 4 => Ok(Self::Special),
174
+ _ => Err(v),
175
+ }
176
+ }
177
+ }
178
+
179
+ /// Fire method for projectile (type 1) and rope (type 2) weapons (WeaponEntry+0x38).
180
+ ///
181
+ /// Selects which sub-function creates the projectile or rope entity.
182
+ #[derive(Debug, Clone, Copy, PartialEq, Eq)]
183
+ #[repr(i32)]
184
+ pub enum FireMethod {
185
+ /// PlacedExplosive: usercall, places mine/dynamite at worm position.
186
+ PlacedExplosive = 1,
187
+ /// ProjectileFire: stdcall, fires projectile with spread/rotation.
188
+ ProjectileFire = 2,
189
+ /// CreateWeaponProjectile: thiscall, allocates CTaskMissile.
190
+ CreateWeaponProjectile = 3,
191
+ /// CreateArrow: thiscall, allocates CTaskArrow (Shotgun, Longbow).
192
+ CreateArrow = 4,
193
+ }
194
+
195
+ impl TryFrom<i32> for FireMethod {
196
+ type Error = i32;
197
+ fn try_from(v: i32) -> Result<Self, i32> {
198
+ match v {
199
+ 1 => Ok(Self::PlacedExplosive),
200
+ 2 => Ok(Self::ProjectileFire),
201
+ 3 => Ok(Self::CreateWeaponProjectile),
202
+ 4 => Ok(Self::CreateArrow),
203
+ _ => Err(v),
204
+ }
205
+ }
206
+ }
207
+
208
+ /// Special weapon subtype (WeaponEntry+0x34, when fire_type == Special).
209
+ ///
210
+ /// Each value corresponds to a specific weapon's fire handler.
211
+ #[derive(Debug, Clone, Copy, PartialEq, Eq)]
212
+ #[repr(i32)]
213
+ pub enum SpecialFireSubtype {
214
+ Blowtorch = 1,
215
+ PneumaticDrill = 2,
216
+ Girder = 3,
217
+ BaseballBat = 4,
218
+ FirePunch = 5,
219
+ DragonBall = 6,
220
+ // 7: unknown
221
+ Kamikaze = 8,
222
+ Prod = 9,
223
+ AirStrike = 10,
224
+ ScalesOfJustice = 11,
225
+ // 12: unknown
226
+ NapalmStrike = 13,
227
+ MailMineMole = 14,
228
+ // 15: unknown
229
+ IndianNuclearTest = 16,
230
+ Freeze = 17,
231
+ SuicideBomber = 18,
232
+ SkipGo = 19,
233
+ Surrender = 20,
234
+ SelectWorm = 21,
235
+ JetPack = 22,
236
+ MagicBullet = 23,
237
+ LowGravity = 24,
238
+ }
239
+
240
+ impl TryFrom<i32> for SpecialFireSubtype {
241
+ type Error = i32;
242
+ fn try_from(v: i32) -> Result<Self, i32> {
243
+ match v {
244
+ 1 => Ok(Self::Blowtorch),
245
+ 2 => Ok(Self::PneumaticDrill),
246
+ 3 => Ok(Self::Girder),
247
+ 4 => Ok(Self::BaseballBat),
248
+ 5 => Ok(Self::FirePunch),
249
+ 6 => Ok(Self::DragonBall),
250
+ 8 => Ok(Self::Kamikaze),
251
+ 9 => Ok(Self::Prod),
252
+ 10 => Ok(Self::AirStrike),
253
+ 11 => Ok(Self::ScalesOfJustice),
254
+ 13 => Ok(Self::NapalmStrike),
255
+ 14 => Ok(Self::MailMineMole),
256
+ 16 => Ok(Self::IndianNuclearTest),
257
+ 17 => Ok(Self::Freeze),
258
+ 18 => Ok(Self::SuicideBomber),
259
+ 19 => Ok(Self::SkipGo),
260
+ 20 => Ok(Self::Surrender),
261
+ 21 => Ok(Self::SelectWorm),
262
+ 22 => Ok(Self::JetPack),
263
+ 23 => Ok(Self::MagicBullet),
264
+ 24 => Ok(Self::LowGravity),
265
+ _ => Err(v),
266
+ }
267
+ }
268
+ }
269
+
270
  /// 71 standard entries (indices 0..70), matching the `Weapon` enum.
271
  /// Source: wkJellyWorm/src/CustomWeapons.h (WeaponStruct).
272
  ///
 
307
  /// Read by FireWeapon to dispatch to the correct handler.
308
  pub fire_type: i32,
309
  /// +0x34: Fire subtype for weapon types 3 (grenade/mortar) and 4 (special).
310
+ pub special_subtype: i32,
311
  /// +0x38: Fire subtype for weapon types 1 (projectile) and 2 (rope).
312
+ pub fire_method: i32,
313
  /// +0x3C: Fire parameters sub-structure. Pointer to this field is passed
314
  /// to fire sub-functions (PlacedExplosive, Projectile, CreateWeaponProjectile, etc.).
315
  pub fire_params: WeaponFireParams,
crates/openwa-core/src/task/worm.rs CHANGED
@@ -1,5 +1,6 @@
1
  use super::base::CTask;
2
  use super::game_task::CGameTask;
 
3
  use crate::game::weapon::WeaponEntry;
4
  use crate::FieldRegistry;
5
 
@@ -314,9 +315,9 @@ pub struct CTaskWorm {
314
  pub _unknown_208: i32,
315
  /// 0x20C–0x247: Unknown
316
  pub _unknown_20c: [u8; 0x248 - 0x20C],
317
- /// 0x248: Landscape scale factor for spawn offset calculation.
318
  /// Used by WeaponRelease to convert aim params to world-space projectile offsets.
319
- pub landscape_scale: i32,
320
  /// 0x24C: Aim angle (fixed-point 16.16, range 0..0x10000 = 0..360 degrees).
321
  pub aim_angle: u32,
322
  /// 0x250–0x267: Unknown
 
1
  use super::base::CTask;
2
  use super::game_task::CGameTask;
3
+ use crate::fixed::Fixed;
4
  use crate::game::weapon::WeaponEntry;
5
  use crate::FieldRegistry;
6
 
 
315
  pub _unknown_208: i32,
316
  /// 0x20C–0x247: Unknown
317
  pub _unknown_20c: [u8; 0x248 - 0x20C],
318
+ /// 0x248: Landscape scale factor for spawn offset calculation (Fixed 16.16).
319
  /// Used by WeaponRelease to convert aim params to world-space projectile offsets.
320
+ pub landscape_scale: Fixed,
321
  /// 0x24C: Aim angle (fixed-point 16.16, range 0..0x10000 = 0..360 degrees).
322
  pub aim_angle: u32,
323
  /// 0x250–0x267: Unknown
crates/openwa-wormkit/src/replacements/weapon.rs CHANGED
@@ -201,9 +201,8 @@ pub(crate) unsafe extern "cdecl" fn fire_weapon_impl(
201
  ) {
202
  use openwa_core::rebase::rb;
203
 
204
- let weapon_type = (*entry).fire_type;
205
- let subtype_34 = (*entry).fire_subtype_34;
206
- let subtype_38 = (*entry).fire_subtype_38;
207
  let fire_params = &raw const (*entry).fire_params;
208
  // Log weapon fire
209
  let weapon_id = (*worm).selected_weapon;
@@ -212,32 +211,33 @@ pub(crate) unsafe extern "cdecl" fn fire_weapon_impl(
212
  .unwrap_or_else(|id| format!("Unknown({})", id));
213
  let _ = log_line(&format!(
214
  "[Weapon] FireWeapon: {} (id={}) type={} sub34={} sub38={}",
215
- weapon_name, weapon_id, weapon_type, subtype_34, subtype_38
216
  ));
217
 
218
  CTaskWorm::set_fire_complete_raw(worm, 0);
219
 
220
- match weapon_type {
221
- 1 => match subtype_38 {
222
- 1 => call_fire_placed_explosive(worm, fire_params, local_struct, rb(0x51EC80)), // PlacedExplosive
223
- 2 => call_fire_stdcall3(worm, fire_params, local_struct, rb(0x51DFB0)), // Projectile
224
- 3 => call_fire_thiscall2(worm, fire_params, local_struct, rb(0x51E0F0)), // CreateWeaponProjectile
225
- 4 => call_fire_thiscall2(worm, fire_params, local_struct, rb(0x51ED90)), // CreateArrow (Shotgun/Longbow)
 
226
  _ => {}
227
  },
228
- 2 => match subtype_38 {
229
- 1 => call_fire_stdcall3(worm, fire_params, local_struct, rb(0x51E1C0)), // RopeType1
230
- 2 => call_fire_thiscall2(worm, fire_params, local_struct, rb(0x51E0F0)), // CreateWeaponProjectile
231
- 3 => call_fire_stdcall3(worm, fire_params, local_struct, rb(0x51E240)), // RopeType3
232
  _ => {}
233
  },
234
- 3 => {
235
  // StrikeFire takes a pointer to the subtype_34 field (reinterpreted as fire params)
236
- let subtype_34_ptr = &raw const (*entry).fire_subtype_34 as *const WeaponFireParams;
237
  call_fire_stdcall3(worm, subtype_34_ptr, local_struct, rb(0x51E2C0));
238
  }
239
- 4 => {
240
- fire_weapon_special(subtype_34, entry, worm, local_struct);
241
  }
242
  _ => {}
243
  }
@@ -352,61 +352,40 @@ unsafe extern "C" fn call_fire_stdcall2(
352
  unsafe fn fire_weapon_special(
353
  subtype: i32, entry: *const WeaponEntry, worm: *mut CTaskWorm, local_struct: *const u8,
354
  ) {
 
355
  use openwa_core::rebase::rb;
356
 
357
- // Pointer to fire_subtype_38 field, reinterpreted as fire params pointer for Girder
358
- let params_38_ptr = &raw const (*entry).fire_subtype_38 as *const WeaponFireParams;
359
-
360
- match subtype {
361
- // Blowtorch
362
- 1 => CTaskWorm::set_state_raw(worm,WormState::Blowtorch),
363
- // Pneumatic Drill (pure Rust)
364
- 2 => fire_drill(worm, local_struct),
365
- // Girder (pure Rust)
366
- 3 => fire_girder(worm, params_38_ptr, local_struct),
367
- // Baseball Bat
368
- 4 => CTaskWorm::set_state_raw(worm,WormState::BaseballBat),
369
- // Fire Punch
370
- 5 => CTaskWorm::set_state_raw(worm,WormState::FirePunch),
371
- // Dragon Ball
372
- 6 => CTaskWorm::set_state_raw(worm,WormState::DragonBall),
373
- // Kamikaze
374
- 8 => CTaskWorm::set_state_raw(worm,WormState::Kamikaze),
375
- // Prod (pure Rust)
376
- 9 => fire_prod(worm, local_struct),
377
- // Air Strike (pure Rust)
378
- 10 => fire_air_strike(worm),
379
- // Scales of Justice
380
- 11 => CTaskWorm::set_state_raw(worm,WormState::ScalesOfJustice),
381
- // Napalm Strike
382
- 13 => fire_send_team_message(worm, 0x2B),
383
- // Mail/Mine/Mole (pure Rust)
384
- 14 => fire_mail_mine_mole(worm),
385
- // Indian Nuclear Test: check worm state, then detonate or cancel
386
- 16 => {
387
- let worm_state = (*worm).state();
388
- if can_fire_subtype16(worm_state) {
389
  fire_indian_nuclear_test(worm);
390
  } else {
391
- CTaskWorm::set_state_raw(worm,WormState::TeleportCancelled_Maybe);
392
  }
393
  }
394
- // Freeze (pure Rust)
395
- 17 => fire_freeze(worm),
396
- // Suicide Bomber
397
- 18 => CTaskWorm::set_state_raw(worm,WormState::SuicideBomber),
398
- // Skip Go (pure Rust)
399
- 19 => fire_skip_go(worm, entry),
400
- // Surrender (pure Rust)
401
- 20 => fire_surrender(worm),
402
- // Select Worm (pure Rust)
403
- 21 => fire_select_worm(worm),
404
- // Jet Pack (pure Rust)
405
- 22 => fire_jet_pack(worm),
406
- // Magic Bullet
407
- 23 => CTaskWorm::set_state_raw(worm,WormState::WeaponAimed_Maybe),
408
- // Low Gravity β€” kept on bridge pending codegen UB investigation
409
- 24 => call_fire_usercall(entry as *const (), worm, rb(0x51EA60)),
410
  _ => {}
411
  }
412
  }
@@ -621,10 +600,10 @@ unsafe fn fire_indian_nuclear_test(worm: *mut CTaskWorm) {
621
  let entry = &*(*worm).active_weapon_entry;
622
  let team = lookup_team_task(worm);
623
 
624
- // Message 0x59 (RaiseWater): buf[0]=fire_subtype_38, buf[4]=8
625
  if !team.is_null() {
626
  let mut buf = [0u8; 0x40C];
627
- buf[0..4].copy_from_slice(&entry.fire_subtype_38.to_ne_bytes());
628
  buf[4..8].copy_from_slice(&8i32.to_ne_bytes());
629
  (*team).handle_message((*worm).as_task_ptr_mut(), 0x59, 0x408, buf.as_ptr());
630
  }
@@ -1059,7 +1038,7 @@ unsafe fn fire_drill(worm: *mut CTaskWorm, local_struct: *const u8) {
1059
  let version = (*ddgame).version_flag_4;
1060
  let entry = &*(*worm).active_weapon_entry;
1061
  let shot_count = entry.fire_params.shot_count;
1062
- let weapon_type = entry.fire_subtype_38;
1063
  let facing = (*worm).facing_direction_2;
1064
 
1065
  // Cast local_struct to WeaponSpawnData for field access (offsets match)
@@ -1092,7 +1071,7 @@ unsafe fn fire_prod(worm: *mut CTaskWorm, local_struct: *const u8) {
1092
  let entry = &*(*worm).active_weapon_entry;
1093
  let shot_count = entry.fire_params.shot_count;
1094
  let spread = entry.fire_params.spread;
1095
- let weapon_type = entry.fire_subtype_38;
1096
  let facing = (*worm).facing_direction_2;
1097
 
1098
  let spawn = &*(local_struct as *const WeaponSpawnData);
 
201
  ) {
202
  use openwa_core::rebase::rb;
203
 
204
+ let fire_type = (*entry).fire_type;
205
+ let fire_method = (*entry).fire_method;
 
206
  let fire_params = &raw const (*entry).fire_params;
207
  // Log weapon fire
208
  let weapon_id = (*worm).selected_weapon;
 
211
  .unwrap_or_else(|id| format!("Unknown({})", id));
212
  let _ = log_line(&format!(
213
  "[Weapon] FireWeapon: {} (id={}) type={} sub34={} sub38={}",
214
+ weapon_name, weapon_id, fire_type, (*entry).special_subtype, fire_method
215
  ));
216
 
217
  CTaskWorm::set_fire_complete_raw(worm, 0);
218
 
219
+ use openwa_core::game::weapon::{FireType, FireMethod, SpecialFireSubtype};
220
+ match FireType::try_from(fire_type) {
221
+ Ok(FireType::Projectile) => match FireMethod::try_from(fire_method) {
222
+ Ok(FireMethod::PlacedExplosive) => call_fire_placed_explosive(worm, fire_params, local_struct, rb(0x51EC80)),
223
+ Ok(FireMethod::ProjectileFire) => call_fire_stdcall3(worm, fire_params, local_struct, rb(0x51DFB0)),
224
+ Ok(FireMethod::CreateWeaponProjectile) => call_fire_thiscall2(worm, fire_params, local_struct, rb(0x51E0F0)),
225
+ Ok(FireMethod::CreateArrow) => call_fire_thiscall2(worm, fire_params, local_struct, rb(0x51ED90)),
226
  _ => {}
227
  },
228
+ Ok(FireType::Rope) => match FireMethod::try_from(fire_method) {
229
+ Ok(FireMethod::PlacedExplosive) => call_fire_stdcall3(worm, fire_params, local_struct, rb(0x51E1C0)), // RopeType1
230
+ Ok(FireMethod::ProjectileFire) => call_fire_thiscall2(worm, fire_params, local_struct, rb(0x51E0F0)),
231
+ Ok(FireMethod::CreateWeaponProjectile) => call_fire_stdcall3(worm, fire_params, local_struct, rb(0x51E240)), // RopeType3
232
  _ => {}
233
  },
234
+ Ok(FireType::Strike) => {
235
  // StrikeFire takes a pointer to the subtype_34 field (reinterpreted as fire params)
236
+ let subtype_34_ptr = &raw const (*entry).special_subtype as *const WeaponFireParams;
237
  call_fire_stdcall3(worm, subtype_34_ptr, local_struct, rb(0x51E2C0));
238
  }
239
+ Ok(FireType::Special) => {
240
+ fire_weapon_special((*entry).special_subtype, entry, worm, local_struct);
241
  }
242
  _ => {}
243
  }
 
352
  unsafe fn fire_weapon_special(
353
  subtype: i32, entry: *const WeaponEntry, worm: *mut CTaskWorm, local_struct: *const u8,
354
  ) {
355
+ use openwa_core::game::weapon::SpecialFireSubtype as S;
356
  use openwa_core::rebase::rb;
357
 
358
+ // Pointer to fire_method field, reinterpreted as fire params pointer for Girder
359
+ let params_38_ptr = &raw const (*entry).fire_method as *const WeaponFireParams;
360
+
361
+ match S::try_from(subtype) {
362
+ Ok(S::Blowtorch) => CTaskWorm::set_state_raw(worm, WormState::Blowtorch),
363
+ Ok(S::PneumaticDrill) => fire_drill(worm, local_struct),
364
+ Ok(S::Girder) => fire_girder(worm, params_38_ptr, local_struct),
365
+ Ok(S::BaseballBat) => CTaskWorm::set_state_raw(worm, WormState::BaseballBat),
366
+ Ok(S::FirePunch) => CTaskWorm::set_state_raw(worm, WormState::FirePunch),
367
+ Ok(S::DragonBall) => CTaskWorm::set_state_raw(worm, WormState::DragonBall),
368
+ Ok(S::Kamikaze) => CTaskWorm::set_state_raw(worm, WormState::Kamikaze),
369
+ Ok(S::Prod) => fire_prod(worm, local_struct),
370
+ Ok(S::AirStrike) => fire_air_strike(worm),
371
+ Ok(S::ScalesOfJustice) => CTaskWorm::set_state_raw(worm, WormState::ScalesOfJustice),
372
+ Ok(S::NapalmStrike) => fire_send_team_message(worm, 0x2B),
373
+ Ok(S::MailMineMole) => fire_mail_mine_mole(worm),
374
+ Ok(S::IndianNuclearTest) => {
375
+ if can_fire_subtype16((*worm).state()) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
376
  fire_indian_nuclear_test(worm);
377
  } else {
378
+ CTaskWorm::set_state_raw(worm, WormState::TeleportCancelled_Maybe);
379
  }
380
  }
381
+ Ok(S::Freeze) => fire_freeze(worm),
382
+ Ok(S::SuicideBomber) => CTaskWorm::set_state_raw(worm, WormState::SuicideBomber),
383
+ Ok(S::SkipGo) => fire_skip_go(worm, entry),
384
+ Ok(S::Surrender) => fire_surrender(worm),
385
+ Ok(S::SelectWorm) => fire_select_worm(worm),
386
+ Ok(S::JetPack) => fire_jet_pack(worm),
387
+ Ok(S::MagicBullet) => CTaskWorm::set_state_raw(worm, WormState::WeaponAimed_Maybe),
388
+ Ok(S::LowGravity) => call_fire_usercall(entry as *const (), worm, rb(0x51EA60)),
 
 
 
 
 
 
 
 
389
  _ => {}
390
  }
391
  }
 
600
  let entry = &*(*worm).active_weapon_entry;
601
  let team = lookup_team_task(worm);
602
 
603
+ // Message 0x59 (RaiseWater): buf[0]=fire_method, buf[4]=8
604
  if !team.is_null() {
605
  let mut buf = [0u8; 0x40C];
606
+ buf[0..4].copy_from_slice(&entry.fire_method.to_ne_bytes());
607
  buf[4..8].copy_from_slice(&8i32.to_ne_bytes());
608
  (*team).handle_message((*worm).as_task_ptr_mut(), 0x59, 0x408, buf.as_ptr());
609
  }
 
1038
  let version = (*ddgame).version_flag_4;
1039
  let entry = &*(*worm).active_weapon_entry;
1040
  let shot_count = entry.fire_params.shot_count;
1041
+ let weapon_type = entry.fire_method;
1042
  let facing = (*worm).facing_direction_2;
1043
 
1044
  // Cast local_struct to WeaponSpawnData for field access (offsets match)
 
1071
  let entry = &*(*worm).active_weapon_entry;
1072
  let shot_count = entry.fire_params.shot_count;
1073
  let spread = entry.fire_params.spread;
1074
+ let weapon_type = entry.fire_method;
1075
  let facing = (*worm).facing_direction_2;
1076
 
1077
  let spawn = &*(local_struct as *const WeaponSpawnData);
crates/openwa-wormkit/src/replacements/weapon_release.rs CHANGED
@@ -10,7 +10,7 @@ use openwa_core::audio::SoundId;
10
  use openwa_core::fixed::Fixed;
11
  use openwa_core::log::log_line;
12
  use openwa_core::rebase::rb;
13
- use openwa_core::task::worm::CTaskWorm;
14
  use openwa_core::task::{CGameTask, Task};
15
 
16
  use crate::hook::{self, usercall_trampoline};
@@ -30,8 +30,8 @@ usercall_trampoline!(fn trampoline_weapon_release; impl_fn = weapon_release_impl
30
  struct WeaponReleaseContext {
31
  team_id: u32,
32
  worm_id: u32,
33
- param_1: u32,
34
- param_2: u32,
35
  spawn_offset_x: i32,
36
  spawn_offset_y: i32,
37
  ammo_per_turn: u32,
@@ -70,25 +70,14 @@ fn is_weapon_category_b(weapon_id: u32) -> bool {
70
  matches!(weapon_id, 0x1C | 0x2C | 0x2F | 0x32)
71
  }
72
 
73
- // ── Fixed-point multiply ────────────────────────────────────
74
-
75
- /// Fixed-point 16.16 multiply: (a * b) >> 16, using SHRD like the original.
76
- /// Matches the `IMUL + SHRD EAX,EDX,0x10` pattern in the disassembly.
77
- #[inline(always)]
78
- fn fixed_mul_shrd(a: i32, b: i32) -> i32 {
79
- let product = (a as i64) * (b as i64);
80
- // SHRD EAX,EDX,0x10: shift the full 64-bit result right by 16
81
- (product >> 16) as i32
82
- }
83
-
84
  // ── Main implementation ─────────────────────────────────────
85
 
86
  unsafe extern "cdecl" fn weapon_release_impl(
87
  worm: *mut CTaskWorm,
88
- param_1: u32,
89
- param_2: u32,
90
- param_3: i32,
91
- param_4: i32,
92
  ) {
93
  let w = &*worm;
94
 
@@ -96,8 +85,8 @@ unsafe extern "cdecl" fn weapon_release_impl(
96
  let mut ctx = WeaponReleaseContext {
97
  team_id: 0,
98
  worm_id: 0,
99
- param_1: 0,
100
- param_2: 0,
101
  spawn_offset_x: 0,
102
  spawn_offset_y: 0,
103
  ammo_per_turn: 0,
@@ -123,59 +112,64 @@ unsafe extern "cdecl" fn weapon_release_impl(
123
  ctx.team_id = w.team_index;
124
  ctx.worm_id = w.worm_index;
125
  ctx.ammo_per_turn = w.weapon_param_1 as u32;
126
- ctx.param_2 = param_2;
127
- ctx.param_1 = param_1;
128
  ctx.ammo_per_slot = w.weapon_param_2 as u32;
129
 
130
  let entry = w.active_weapon_entry;
131
  let fire_type = (*entry).fire_type;
132
- let fire_subtype_34 = (*entry).fire_subtype_34;
133
- let fire_subtype_38 = (*entry).fire_subtype_38;
134
 
135
- // ── 3. Spawn offset calculation ─────────────────────────
136
- let landscape_scale = w.landscape_scale;
137
 
138
- match fire_type {
139
- 1 => match fire_subtype_38 {
140
- 1 => {
141
- ctx.spawn_offset_x = param_3 * 0x18;
142
- ctx.spawn_offset_y = param_4 * 0x18;
 
 
 
 
143
  }
144
- 2 => {
145
- // Falls through to type 3 (passthrough)
146
- ctx.spawn_offset_x = param_3;
147
- ctx.spawn_offset_y = param_4;
148
  }
149
- 3 => {
150
- ctx.spawn_offset_x = fixed_mul_shrd(param_3, landscape_scale) * 0x18;
151
- ctx.spawn_offset_y = fixed_mul_shrd(param_4, landscape_scale) * 0x18;
152
  }
153
- 4 => {
154
- ctx.spawn_offset_x = param_3 * 0x14;
155
- ctx.spawn_offset_y = param_4 * 0x14;
156
  }
157
  _ => {}
158
  },
159
- 2 => {
160
- ctx.spawn_offset_x = fixed_mul_shrd(param_3, landscape_scale) * 0x18;
161
- ctx.spawn_offset_y = fixed_mul_shrd(param_4, landscape_scale) * 0x18;
162
- // Special Y adjustment for angle 0x79
163
- if w.state() == 0x79 {
164
- ctx.spawn_offset_y += w.base.speed_y.0;
165
  }
166
  }
167
- 3 => {
168
- ctx.spawn_offset_x = param_3;
169
- ctx.spawn_offset_y = param_4;
170
  }
171
- 4 => {
172
- if (fire_subtype_34 as u32).wrapping_sub(1) < 0x18 {
173
- ctx.spawn_offset_x = param_3;
174
- ctx.spawn_offset_y = param_4;
175
  }
176
  }
177
  _ => {}
178
  }
 
 
179
 
180
  // ── 4. Delay ────────────────────────────────────────────
181
  if w.difficulty_level == 0 {
@@ -264,14 +258,13 @@ unsafe extern "cdecl" fn weapon_release_impl(
264
 
265
  let w = &*worm; // re-borrow after mutation above
266
  let entry = w.active_weapon_entry;
267
- let fire_type = (*entry).fire_type;
268
 
269
  let play_worm_sound_addr = rb(va::PLAY_WORM_SOUND);
270
  let stop_worm_sound_addr = rb(va::STOP_WORM_SOUND);
271
 
272
- match fire_type {
273
- 1 => {
274
- match (*entry).fire_subtype_34 {
275
  1 => {
276
  if w.sound_handle == 0 {
277
  call_play_worm_sound(worm, 0x1004E, 0x10000, play_worm_sound_addr);
@@ -311,7 +304,7 @@ unsafe extern "cdecl" fn weapon_release_impl(
311
  _ => {}
312
  }
313
  }
314
- 2 => {
315
  if w._unknown_2cc == 0 || w._unknown_2c8 == 1 {
316
  let team_sound_raw = (*w.ddgame()).team_sound_id(team_id);
317
  if let Ok(sid) = SoundId::try_from(team_sound_raw) {
@@ -319,16 +312,16 @@ unsafe extern "cdecl" fn weapon_release_impl(
319
  }
320
  }
321
  }
322
- // Type 3: no sound
323
- 4 => {
324
- match (*entry).fire_subtype_34 {
325
  2 => {
326
  sound::play_sound_local(
327
  task, SoundId::BaseballBatRelease, 3, Fixed::ONE, Fixed::ONE,
328
  );
329
  }
330
  3 => {
331
- if let Ok(sid) = SoundId::try_from((*entry).fire_subtype_38 as u32) {
332
  sound::play_sound_local(task, sid, 3, Fixed::ONE, Fixed::ONE);
333
  }
334
  }
@@ -385,7 +378,7 @@ unsafe extern "cdecl" fn weapon_release_impl(
385
 
386
  let _ = log_line(&format!(
387
  "[WeaponRelease] worm=0x{:08X} weapon={} type={} sub34={} sub38={}",
388
- worm as u32, weapon_id, fire_type, fire_subtype_34, fire_subtype_38,
389
  ));
390
  }
391
 
 
10
  use openwa_core::fixed::Fixed;
11
  use openwa_core::log::log_line;
12
  use openwa_core::rebase::rb;
13
+ use openwa_core::task::worm::{CTaskWorm, WormState};
14
  use openwa_core::task::{CGameTask, Task};
15
 
16
  use crate::hook::{self, usercall_trampoline};
 
30
  struct WeaponReleaseContext {
31
  team_id: u32,
32
  worm_id: u32,
33
+ spawn_x: u32,
34
+ spawn_y: u32,
35
  spawn_offset_x: i32,
36
  spawn_offset_y: i32,
37
  ammo_per_turn: u32,
 
70
  matches!(weapon_id, 0x1C | 0x2C | 0x2F | 0x32)
71
  }
72
 
 
 
 
 
 
 
 
 
 
 
 
73
  // ── Main implementation ─────────────────────────────────────
74
 
75
  unsafe extern "cdecl" fn weapon_release_impl(
76
  worm: *mut CTaskWorm,
77
+ spawn_x: u32,
78
+ spawn_y: u32,
79
+ aim_dir_x: Fixed,
80
+ aim_dir_y: Fixed,
81
  ) {
82
  let w = &*worm;
83
 
 
85
  let mut ctx = WeaponReleaseContext {
86
  team_id: 0,
87
  worm_id: 0,
88
+ spawn_x: 0,
89
+ spawn_y: 0,
90
  spawn_offset_x: 0,
91
  spawn_offset_y: 0,
92
  ammo_per_turn: 0,
 
112
  ctx.team_id = w.team_index;
113
  ctx.worm_id = w.worm_index;
114
  ctx.ammo_per_turn = w.weapon_param_1 as u32;
115
+ ctx.spawn_x = spawn_x;
116
+ ctx.spawn_y = spawn_y;
117
  ctx.ammo_per_slot = w.weapon_param_2 as u32;
118
 
119
  let entry = w.active_weapon_entry;
120
  let fire_type = (*entry).fire_type;
121
+ let special_subtype = (*entry).special_subtype;
122
+ let fire_method = (*entry).fire_method;
123
 
124
+ use openwa_core::game::weapon::{FireType, FireMethod};
 
125
 
126
+ // ── 3. Spawn offset calculation ─────────────────────────
127
+ let scale = w.landscape_scale;
128
+
129
+ let (mut offset_x, mut offset_y) = (Fixed::ZERO, Fixed::ZERO);
130
+ match FireType::try_from(fire_type) {
131
+ Ok(FireType::Projectile) => match FireMethod::try_from(fire_method) {
132
+ Ok(FireMethod::PlacedExplosive) => {
133
+ offset_x = aim_dir_x * 0x18;
134
+ offset_y = aim_dir_y * 0x18;
135
  }
136
+ Ok(FireMethod::ProjectileFire) => {
137
+ // Falls through to Strike (passthrough)
138
+ offset_x = aim_dir_x;
139
+ offset_y = aim_dir_y;
140
  }
141
+ Ok(FireMethod::CreateWeaponProjectile) => {
142
+ offset_x = aim_dir_x * scale * 0x18;
143
+ offset_y = aim_dir_y * scale * 0x18;
144
  }
145
+ Ok(FireMethod::CreateArrow) => {
146
+ offset_x = aim_dir_x * 0x14;
147
+ offset_y = aim_dir_y * 0x14;
148
  }
149
  _ => {}
150
  },
151
+ Ok(FireType::Rope) => {
152
+ offset_x = aim_dir_x * scale * 0x18;
153
+ offset_y = aim_dir_y * scale * 0x18;
154
+ // Special Y adjustment for state 0x79
155
+ if w.state() == WormState::Unknown_0x79 as u32 {
156
+ offset_y += w.base.speed_y;
157
  }
158
  }
159
+ Ok(FireType::Strike) => {
160
+ offset_x = aim_dir_x;
161
+ offset_y = aim_dir_y;
162
  }
163
+ Ok(FireType::Special) => {
164
+ if (special_subtype as u32).wrapping_sub(1) < 0x18 {
165
+ offset_x = aim_dir_x;
166
+ offset_y = aim_dir_y;
167
  }
168
  }
169
  _ => {}
170
  }
171
+ ctx.spawn_offset_x = offset_x.0;
172
+ ctx.spawn_offset_y = offset_y.0;
173
 
174
  // ── 4. Delay ────────────────────────────────────────────
175
  if w.difficulty_level == 0 {
 
258
 
259
  let w = &*worm; // re-borrow after mutation above
260
  let entry = w.active_weapon_entry;
 
261
 
262
  let play_worm_sound_addr = rb(va::PLAY_WORM_SOUND);
263
  let stop_worm_sound_addr = rb(va::STOP_WORM_SOUND);
264
 
265
+ match FireType::try_from((*entry).fire_type) {
266
+ Ok(FireType::Projectile) => {
267
+ match (*entry).special_subtype {
268
  1 => {
269
  if w.sound_handle == 0 {
270
  call_play_worm_sound(worm, 0x1004E, 0x10000, play_worm_sound_addr);
 
304
  _ => {}
305
  }
306
  }
307
+ Ok(FireType::Rope) => {
308
  if w._unknown_2cc == 0 || w._unknown_2c8 == 1 {
309
  let team_sound_raw = (*w.ddgame()).team_sound_id(team_id);
310
  if let Ok(sid) = SoundId::try_from(team_sound_raw) {
 
312
  }
313
  }
314
  }
315
+ // Type 3 (Strike): no sound
316
+ Ok(FireType::Special) => {
317
+ match (*entry).special_subtype {
318
  2 => {
319
  sound::play_sound_local(
320
  task, SoundId::BaseballBatRelease, 3, Fixed::ONE, Fixed::ONE,
321
  );
322
  }
323
  3 => {
324
+ if let Ok(sid) = SoundId::try_from((*entry).fire_method as u32) {
325
  sound::play_sound_local(task, sid, 3, Fixed::ONE, Fixed::ONE);
326
  }
327
  }
 
378
 
379
  let _ = log_line(&format!(
380
  "[WeaponRelease] worm=0x{:08X} weapon={} type={} sub34={} sub38={}",
381
+ worm as u32, weapon_id, fire_type, special_subtype, fire_method,
382
  ));
383
  }
384