refactor: add weapon fire type enums, use Fixed for aim/scale params
Browse filesAdd 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>
|
@@ -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]
|
|
@@ -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
|
| 187 |
/// +0x38: Fire subtype for weapon types 1 (projectile) and 2 (rope).
|
| 188 |
-
pub
|
| 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,
|
|
@@ -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:
|
| 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
|
|
@@ -201,9 +201,8 @@ pub(crate) unsafe extern "cdecl" fn fire_weapon_impl(
|
|
| 201 |
) {
|
| 202 |
use openwa_core::rebase::rb;
|
| 203 |
|
| 204 |
-
let
|
| 205 |
-
let
|
| 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,
|
| 216 |
));
|
| 217 |
|
| 218 |
CTaskWorm::set_fire_complete_raw(worm, 0);
|
| 219 |
|
| 220 |
-
|
| 221 |
-
|
| 222 |
-
|
| 223 |
-
|
| 224 |
-
|
| 225 |
-
|
|
|
|
| 226 |
_ => {}
|
| 227 |
},
|
| 228 |
-
|
| 229 |
-
|
| 230 |
-
|
| 231 |
-
|
| 232 |
_ => {}
|
| 233 |
},
|
| 234 |
-
|
| 235 |
// StrikeFire takes a pointer to the subtype_34 field (reinterpreted as fire params)
|
| 236 |
-
let subtype_34_ptr = &raw const (*entry).
|
| 237 |
call_fire_stdcall3(worm, subtype_34_ptr, local_struct, rb(0x51E2C0));
|
| 238 |
}
|
| 239 |
-
|
| 240 |
-
fire_weapon_special(
|
| 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
|
| 358 |
-
let params_38_ptr = &raw const (*entry).
|
| 359 |
-
|
| 360 |
-
match subtype {
|
| 361 |
-
|
| 362 |
-
|
| 363 |
-
|
| 364 |
-
|
| 365 |
-
|
| 366 |
-
|
| 367 |
-
|
| 368 |
-
|
| 369 |
-
|
| 370 |
-
|
| 371 |
-
|
| 372 |
-
|
| 373 |
-
|
| 374 |
-
|
| 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 |
-
|
| 395 |
-
|
| 396 |
-
|
| 397 |
-
|
| 398 |
-
|
| 399 |
-
|
| 400 |
-
|
| 401 |
-
|
| 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]=
|
| 625 |
if !team.is_null() {
|
| 626 |
let mut buf = [0u8; 0x40C];
|
| 627 |
-
buf[0..4].copy_from_slice(&entry.
|
| 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.
|
| 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.
|
| 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);
|
|
@@ -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 |
-
|
| 34 |
-
|
| 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 |
-
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
|
| 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 |
-
|
| 100 |
-
|
| 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.
|
| 127 |
-
ctx.
|
| 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
|
| 133 |
-
let
|
| 134 |
|
| 135 |
-
|
| 136 |
-
let landscape_scale = w.landscape_scale;
|
| 137 |
|
| 138 |
-
|
| 139 |
-
|
| 140 |
-
|
| 141 |
-
|
| 142 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 143 |
}
|
| 144 |
-
|
| 145 |
-
// Falls through to
|
| 146 |
-
|
| 147 |
-
|
| 148 |
}
|
| 149 |
-
|
| 150 |
-
|
| 151 |
-
|
| 152 |
}
|
| 153 |
-
|
| 154 |
-
|
| 155 |
-
|
| 156 |
}
|
| 157 |
_ => {}
|
| 158 |
},
|
| 159 |
-
|
| 160 |
-
|
| 161 |
-
|
| 162 |
-
// Special Y adjustment for
|
| 163 |
-
if w.state() ==
|
| 164 |
-
|
| 165 |
}
|
| 166 |
}
|
| 167 |
-
|
| 168 |
-
|
| 169 |
-
|
| 170 |
}
|
| 171 |
-
|
| 172 |
-
if (
|
| 173 |
-
|
| 174 |
-
|
| 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 |
-
|
| 274 |
-
match (*entry).
|
| 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 |
-
|
| 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 |
-
|
| 324 |
-
match (*entry).
|
| 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).
|
| 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,
|
| 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 |
|