refactor: use Weapon enum for weapon IDs and classification
Browse filesChange CTaskWorm.selected_weapon from u32 to Weapon enum. Update
is_super_weapon, is_weapon_category_a, is_weapon_category_b to take
Weapon and match on named variants instead of hex magic numbers.
Replace raw 0x22/0x24 check with Weapon::Girder/GirderPack match.
Use SpecialFireSubtype variants in type-4 sound dispatch.
Idiomatic range check: Weapon::JetPack..=Weapon::CrateShower.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
crates/openwa-core/src/game/weapon.rs
CHANGED
|
@@ -5,7 +5,7 @@ use crate::fixed::Fixed;
|
|
| 5 |
/// Weapon types. Contiguous range 0-70.
|
| 6 |
///
|
| 7 |
/// Source: wkJellyWorm Constants.h
|
| 8 |
-
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
| 9 |
#[repr(u32)]
|
| 10 |
pub enum Weapon {
|
| 11 |
None = 0,
|
|
|
|
| 5 |
/// Weapon types. Contiguous range 0-70.
|
| 6 |
///
|
| 7 |
/// Source: wkJellyWorm Constants.h
|
| 8 |
+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd)]
|
| 9 |
#[repr(u32)]
|
| 10 |
pub enum Weapon {
|
| 11 |
None = 0,
|
crates/openwa-core/src/task/worm.rs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
| 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 |
|
|
@@ -278,7 +279,7 @@ pub struct CTaskWorm {
|
|
| 278 |
/// 0x168β0x16F: Unknown
|
| 279 |
pub _unknown_168: [u8; 0x170 - 0x168],
|
| 280 |
/// 0x170: Currently selected weapon ID.
|
| 281 |
-
pub selected_weapon:
|
| 282 |
/// 0x174β0x177: Unknown
|
| 283 |
pub _unknown_174: [u8; 4],
|
| 284 |
/// 0x178: Display health (animated toward target). Used for health bar interpolation.
|
|
@@ -467,7 +468,7 @@ impl crate::snapshot::Snapshot for CTaskWorm {
|
|
| 467 |
write_indent(w, i)?; writeln!(w, "team_index = {}", self.team_index)?;
|
| 468 |
write_indent(w, i)?; writeln!(w, "worm_index = {}", self.worm_index)?;
|
| 469 |
write_indent(w, i)?; writeln!(w, "slot_id = {}", self.slot_id)?;
|
| 470 |
-
write_indent(w, i)?; writeln!(w, "selected_weapon = {}", self.selected_weapon)?;
|
| 471 |
write_indent(w, i)?; writeln!(w, "facing = {}", self.facing_direction)?;
|
| 472 |
write_indent(w, i)?; writeln!(w, "aim_angle = 0x{:08X}", self.aim_angle)?;
|
| 473 |
|
|
|
|
| 1 |
use super::base::CTask;
|
| 2 |
use super::game_task::CGameTask;
|
| 3 |
use crate::fixed::Fixed;
|
| 4 |
+
use crate::game::Weapon;
|
| 5 |
use crate::game::weapon::WeaponEntry;
|
| 6 |
use crate::FieldRegistry;
|
| 7 |
|
|
|
|
| 279 |
/// 0x168β0x16F: Unknown
|
| 280 |
pub _unknown_168: [u8; 0x170 - 0x168],
|
| 281 |
/// 0x170: Currently selected weapon ID.
|
| 282 |
+
pub selected_weapon: Weapon,
|
| 283 |
/// 0x174β0x177: Unknown
|
| 284 |
pub _unknown_174: [u8; 4],
|
| 285 |
/// 0x178: Display health (animated toward target). Used for health bar interpolation.
|
|
|
|
| 468 |
write_indent(w, i)?; writeln!(w, "team_index = {}", self.team_index)?;
|
| 469 |
write_indent(w, i)?; writeln!(w, "worm_index = {}", self.worm_index)?;
|
| 470 |
write_indent(w, i)?; writeln!(w, "slot_id = {}", self.slot_id)?;
|
| 471 |
+
write_indent(w, i)?; writeln!(w, "selected_weapon = {:?}", self.selected_weapon)?;
|
| 472 |
write_indent(w, i)?; writeln!(w, "facing = {}", self.facing_direction)?;
|
| 473 |
write_indent(w, i)?; writeln!(w, "aim_angle = 0x{:08X}", self.aim_angle)?;
|
| 474 |
|
crates/openwa-wormkit/src/replacements/weapon.rs
CHANGED
|
@@ -205,18 +205,15 @@ pub(crate) unsafe extern "cdecl" fn fire_weapon_impl(
|
|
| 205 |
let fire_method = (*entry).fire_method;
|
| 206 |
let fire_params = &raw const (*entry).fire_params;
|
| 207 |
// Log weapon fire
|
| 208 |
-
let
|
| 209 |
-
let weapon_name = Weapon::try_from(weapon_id)
|
| 210 |
-
.map(|wp| format!("{:?}", wp))
|
| 211 |
-
.unwrap_or_else(|id| format!("Unknown({})", id));
|
| 212 |
let _ = log_line(&format!(
|
| 213 |
-
"[Weapon] FireWeapon: {} (id={}) type={} sub34={} sub38={}",
|
| 214 |
-
|
| 215 |
));
|
| 216 |
|
| 217 |
CTaskWorm::set_fire_complete_raw(worm, 0);
|
| 218 |
|
| 219 |
-
use openwa_core::game::weapon::{FireType, FireMethod
|
| 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)),
|
|
@@ -791,7 +788,7 @@ unsafe fn fire_low_gravity(worm: *mut CTaskWorm) {
|
|
| 791 |
// buf[0x04] = 100 (0x64), buf[0x08] = 166 (0xA6), buf[0x0C] = weapon_id, buf[0x10] = team_index
|
| 792 |
buf[0x04..0x08].copy_from_slice(&100i32.to_ne_bytes());
|
| 793 |
buf[0x08..0x0C].copy_from_slice(&166i32.to_ne_bytes());
|
| 794 |
-
buf[0x0C..0x10].copy_from_slice(&(*worm).selected_weapon.to_ne_bytes());
|
| 795 |
buf[0x10..0x14].copy_from_slice(&(*worm).team_index.to_ne_bytes());
|
| 796 |
|
| 797 |
(*team).handle_message(
|
|
|
|
| 205 |
let fire_method = (*entry).fire_method;
|
| 206 |
let fire_params = &raw const (*entry).fire_params;
|
| 207 |
// Log weapon fire
|
| 208 |
+
let weapon = (*worm).selected_weapon;
|
|
|
|
|
|
|
|
|
|
| 209 |
let _ = log_line(&format!(
|
| 210 |
+
"[Weapon] FireWeapon: {:?} (id={}) type={} sub34={} sub38={}",
|
| 211 |
+
weapon, weapon as u32, fire_type, (*entry).special_subtype, fire_method
|
| 212 |
));
|
| 213 |
|
| 214 |
CTaskWorm::set_fire_complete_raw(worm, 0);
|
| 215 |
|
| 216 |
+
use openwa_core::game::weapon::{FireType, FireMethod};
|
| 217 |
match FireType::try_from(fire_type) {
|
| 218 |
Ok(FireType::Projectile) => match FireMethod::try_from(fire_method) {
|
| 219 |
Ok(FireMethod::PlacedExplosive) => call_fire_placed_explosive(worm, fire_params, local_struct, rb(0x51EC80)),
|
|
|
|
| 788 |
// buf[0x04] = 100 (0x64), buf[0x08] = 166 (0xA6), buf[0x0C] = weapon_id, buf[0x10] = team_index
|
| 789 |
buf[0x04..0x08].copy_from_slice(&100i32.to_ne_bytes());
|
| 790 |
buf[0x08..0x0C].copy_from_slice(&166i32.to_ne_bytes());
|
| 791 |
+
buf[0x0C..0x10].copy_from_slice(&((*worm).selected_weapon as u32).to_ne_bytes());
|
| 792 |
buf[0x10..0x14].copy_from_slice(&(*worm).team_index.to_ne_bytes());
|
| 793 |
|
| 794 |
(*team).handle_message(
|
crates/openwa-wormkit/src/replacements/weapon_release.rs
CHANGED
|
@@ -8,6 +8,7 @@
|
|
| 8 |
use openwa_core::address::va;
|
| 9 |
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, WormState};
|
|
@@ -46,28 +47,58 @@ const _: () = assert!(core::mem::size_of::<WeaponReleaseContext>() == 0x2C);
|
|
| 46 |
// ββ Weapon category classifiers (pure Rust) βββββββββββββββββ
|
| 47 |
|
| 48 |
/// IsSuperWeapon (0x565960): returns true for "super weapon" IDs.
|
| 49 |
-
///
|
| 50 |
-
fn is_super_weapon(
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 57 |
}
|
| 58 |
|
| 59 |
-
/// FUN_005658C0: weapon category A β
|
| 60 |
-
fn is_weapon_category_a(
|
|
|
|
| 61 |
matches!(
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 65 |
)
|
| 66 |
}
|
| 67 |
|
| 68 |
-
/// FUN_00565920: weapon category B β
|
| 69 |
-
fn is_weapon_category_b(
|
| 70 |
-
|
|
|
|
| 71 |
}
|
| 72 |
|
| 73 |
// ββ Main implementation βββββββββββββββββββββββββββββββββββββ
|
|
@@ -198,9 +229,9 @@ unsafe extern "cdecl" fn weapon_release_impl(
|
|
| 198 |
ctx.network_delay = (client_idx + 1) * 1000;
|
| 199 |
}
|
| 200 |
|
| 201 |
-
// ββ 6.
|
| 202 |
-
let
|
| 203 |
-
if (
|
| 204 |
(*worm).shot_data_1 = w.shot_data_2;
|
| 205 |
}
|
| 206 |
|
|
@@ -217,7 +248,7 @@ unsafe extern "cdecl" fn weapon_release_impl(
|
|
| 217 |
0x18,
|
| 218 |
if w._unknown_2cc != 0 { 1 } else { 0 },
|
| 219 |
);
|
| 220 |
-
write_u32(&mut msg_buf, 0x1C,
|
| 221 |
|
| 222 |
let team = weapon::lookup_team_task(worm);
|
| 223 |
if !team.is_null() {
|
|
@@ -234,20 +265,20 @@ unsafe extern "cdecl" fn weapon_release_impl(
|
|
| 234 |
let team_id = (*worm).team_index;
|
| 235 |
let worm_id = (*worm).worm_index;
|
| 236 |
|
| 237 |
-
if is_super_weapon(
|
| 238 |
*g.weapon_stat_counter(team_id, worm_id, 0x40D8) += 1;
|
| 239 |
}
|
| 240 |
|
| 241 |
-
//
|
| 242 |
-
if
|
| 243 |
*g.weapon_stat_counter(team_id, worm_id, 0x40D4) += 1;
|
| 244 |
}
|
| 245 |
|
| 246 |
-
if is_weapon_category_a(
|
| 247 |
*g.weapon_stat_counter(team_id, worm_id, 0x40D0) += 1;
|
| 248 |
}
|
| 249 |
|
| 250 |
-
if is_weapon_category_b(
|
| 251 |
*g.weapon_stat_counter(team_id, worm_id, 0x40CC) += 1;
|
| 252 |
}
|
| 253 |
|
|
@@ -314,29 +345,30 @@ unsafe extern "cdecl" fn weapon_release_impl(
|
|
| 314 |
}
|
| 315 |
// Type 3 (Strike): no sound
|
| 316 |
Ok(FireType::Special) => {
|
| 317 |
-
|
| 318 |
-
|
|
|
|
| 319 |
sound::play_sound_local(
|
| 320 |
task, SoundId::BaseballBatRelease, 3, Fixed::ONE, Fixed::ONE,
|
| 321 |
);
|
| 322 |
}
|
| 323 |
-
|
| 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 |
}
|
| 328 |
-
|
| 329 |
-
// Sound ID
|
| 330 |
if let Ok(sid) = SoundId::try_from((*entry).fire_params.spread as u32) {
|
| 331 |
sound::play_sound_local(task, sid, 3, Fixed::ONE, Fixed::ONE);
|
| 332 |
}
|
| 333 |
}
|
| 334 |
-
|
| 335 |
if w._unknown_208 == 0 {
|
| 336 |
sound::play_sound_local(task, SoundId::Teleport, 3, Fixed::ONE, Fixed::ONE);
|
| 337 |
}
|
| 338 |
}
|
| 339 |
-
|
| 340 |
if w.sound_handle == 0 {
|
| 341 |
call_play_worm_sound(worm, 0x10035, 0x10000, play_worm_sound_addr);
|
| 342 |
}
|
|
@@ -377,8 +409,8 @@ unsafe extern "cdecl" fn weapon_release_impl(
|
|
| 377 |
weapon::fire_weapon_impl(entry, ctx_ptr, worm);
|
| 378 |
|
| 379 |
let _ = log_line(&format!(
|
| 380 |
-
"[WeaponRelease] worm=0x{:08X} weapon={} type={} sub34={} sub38={}",
|
| 381 |
-
worm as u32,
|
| 382 |
));
|
| 383 |
}
|
| 384 |
|
|
|
|
| 8 |
use openwa_core::address::va;
|
| 9 |
use openwa_core::audio::SoundId;
|
| 10 |
use openwa_core::fixed::Fixed;
|
| 11 |
+
use openwa_core::game::Weapon;
|
| 12 |
use openwa_core::log::log_line;
|
| 13 |
use openwa_core::rebase::rb;
|
| 14 |
use openwa_core::task::worm::{CTaskWorm, WormState};
|
|
|
|
| 47 |
// ββ Weapon category classifiers (pure Rust) βββββββββββββββββ
|
| 48 |
|
| 49 |
/// IsSuperWeapon (0x565960): returns true for "super weapon" IDs.
|
| 50 |
+
/// For SelectWorm, returns the DDGame+0x7E3F version flag (mode-dependent).
|
| 51 |
+
fn is_super_weapon(weapon: Weapon, ddgame_7e3f: u8) -> bool {
|
| 52 |
+
use Weapon::*;
|
| 53 |
+
matches!(
|
| 54 |
+
weapon,
|
| 55 |
+
Earthquake
|
| 56 |
+
| SuicideBomber
|
| 57 |
+
| MailStrike
|
| 58 |
+
| MineStrike
|
| 59 |
+
| MoleSquadron
|
| 60 |
+
| GirderPack
|
| 61 |
+
| ScalesOfJustice
|
| 62 |
+
| SuperBanana
|
| 63 |
+
| SalvationArmy
|
| 64 |
+
| MbBomb
|
| 65 |
+
| MingVase
|
| 66 |
+
| SheepStrike
|
| 67 |
+
| CarpetBomb
|
| 68 |
+
| Donkey
|
| 69 |
+
| NuclearTest
|
| 70 |
+
| Armageddon
|
| 71 |
+
| Freeze
|
| 72 |
+
| MagicBullet
|
| 73 |
+
) || (weapon == Weapon::SelectWorm && ddgame_7e3f != 0)
|
| 74 |
}
|
| 75 |
|
| 76 |
+
/// FUN_005658C0: weapon category A β homing/animal/special projectile weapons.
|
| 77 |
+
fn is_weapon_category_a(weapon: Weapon) -> bool {
|
| 78 |
+
use Weapon::*;
|
| 79 |
matches!(
|
| 80 |
+
weapon,
|
| 81 |
+
HomingPigeon
|
| 82 |
+
| SheepLauncher
|
| 83 |
+
| Sheep
|
| 84 |
+
| SuperSheep
|
| 85 |
+
| AquaSheep
|
| 86 |
+
| MoleBomb
|
| 87 |
+
| MoleSquadron
|
| 88 |
+
| SalvationArmy
|
| 89 |
+
| MbBomb
|
| 90 |
+
| Skunk
|
| 91 |
+
| SheepStrike
|
| 92 |
+
| MadCow
|
| 93 |
+
| OldWoman
|
| 94 |
+
| Donkey
|
| 95 |
)
|
| 96 |
}
|
| 97 |
|
| 98 |
+
/// FUN_00565920: weapon category B β fire/napalm weapons.
|
| 99 |
+
fn is_weapon_category_b(weapon: Weapon) -> bool {
|
| 100 |
+
use Weapon::*;
|
| 101 |
+
matches!(weapon, NapalmStrike | FlameThrower | PetrolBomb | SheepStrike)
|
| 102 |
}
|
| 103 |
|
| 104 |
// ββ Main implementation βββββββββββββββββββββββββββββββββββββ
|
|
|
|
| 229 |
ctx.network_delay = (client_idx + 1) * 1000;
|
| 230 |
}
|
| 231 |
|
| 232 |
+
// ββ 6. Girder/GirderPack special ββββββββββββββββββββββββ
|
| 233 |
+
let weapon = w.selected_weapon;
|
| 234 |
+
if matches!(weapon, Weapon::Girder | Weapon::GirderPack) && w.weapon_param_3 == 0 {
|
| 235 |
(*worm).shot_data_1 = w.shot_data_2;
|
| 236 |
}
|
| 237 |
|
|
|
|
| 248 |
0x18,
|
| 249 |
if w._unknown_2cc != 0 { 1 } else { 0 },
|
| 250 |
);
|
| 251 |
+
write_u32(&mut msg_buf, 0x1C, weapon as u32);
|
| 252 |
|
| 253 |
let team = weapon::lookup_team_task(worm);
|
| 254 |
if !team.is_null() {
|
|
|
|
| 265 |
let team_id = (*worm).team_index;
|
| 266 |
let worm_id = (*worm).worm_index;
|
| 267 |
|
| 268 |
+
if is_super_weapon(weapon, g.version_flag_3) {
|
| 269 |
*g.weapon_stat_counter(team_id, worm_id, 0x40D8) += 1;
|
| 270 |
}
|
| 271 |
|
| 272 |
+
// Powerup/utility weapons (JetPack..=CrateShower)
|
| 273 |
+
if (Weapon::JetPack..=Weapon::CrateShower).contains(&weapon) {
|
| 274 |
*g.weapon_stat_counter(team_id, worm_id, 0x40D4) += 1;
|
| 275 |
}
|
| 276 |
|
| 277 |
+
if is_weapon_category_a(weapon) {
|
| 278 |
*g.weapon_stat_counter(team_id, worm_id, 0x40D0) += 1;
|
| 279 |
}
|
| 280 |
|
| 281 |
+
if is_weapon_category_b(weapon) {
|
| 282 |
*g.weapon_stat_counter(team_id, worm_id, 0x40CC) += 1;
|
| 283 |
}
|
| 284 |
|
|
|
|
| 345 |
}
|
| 346 |
// Type 3 (Strike): no sound
|
| 347 |
Ok(FireType::Special) => {
|
| 348 |
+
use openwa_core::game::weapon::SpecialFireSubtype as S;
|
| 349 |
+
match S::try_from((*entry).special_subtype) {
|
| 350 |
+
Ok(S::PneumaticDrill) => {
|
| 351 |
sound::play_sound_local(
|
| 352 |
task, SoundId::BaseballBatRelease, 3, Fixed::ONE, Fixed::ONE,
|
| 353 |
);
|
| 354 |
}
|
| 355 |
+
Ok(S::Girder) => {
|
| 356 |
if let Ok(sid) = SoundId::try_from((*entry).fire_method as u32) {
|
| 357 |
sound::play_sound_local(task, sid, 3, Fixed::ONE, Fixed::ONE);
|
| 358 |
}
|
| 359 |
}
|
| 360 |
+
Ok(S::BaseballBat) => {
|
| 361 |
+
// Sound ID from fire_params.spread (polymorphic use of field)
|
| 362 |
if let Ok(sid) = SoundId::try_from((*entry).fire_params.spread as u32) {
|
| 363 |
sound::play_sound_local(task, sid, 3, Fixed::ONE, Fixed::ONE);
|
| 364 |
}
|
| 365 |
}
|
| 366 |
+
Ok(S::AirStrike) => {
|
| 367 |
if w._unknown_208 == 0 {
|
| 368 |
sound::play_sound_local(task, SoundId::Teleport, 3, Fixed::ONE, Fixed::ONE);
|
| 369 |
}
|
| 370 |
}
|
| 371 |
+
Ok(S::ScalesOfJustice) => {
|
| 372 |
if w.sound_handle == 0 {
|
| 373 |
call_play_worm_sound(worm, 0x10035, 0x10000, play_worm_sound_addr);
|
| 374 |
}
|
|
|
|
| 409 |
weapon::fire_weapon_impl(entry, ctx_ptr, worm);
|
| 410 |
|
| 411 |
let _ = log_line(&format!(
|
| 412 |
+
"[WeaponRelease] worm=0x{:08X} weapon={:?} type={} sub34={} sub38={}",
|
| 413 |
+
worm as u32, weapon, fire_type, special_subtype, fire_method,
|
| 414 |
));
|
| 415 |
}
|
| 416 |
|