refactor: eliminate raw pointer arithmetic in weapon_release.rs
Browse filesReplace DDGame raw offset accesses with named struct fields and methods:
- render_slot_count (0x72E4), render_entries, render_state_flag
- game_info, game_state_stream, version_flag_3 (direct field access)
- team_sound_id() method for 0x7768+team*0xF0 sound table
- weapon_stat_counter() method for 0x40CC-0x40D8 indexed counters
- fire_params.spread for type-4 subtype-4 sound ID
Replace raw vtable dispatch for HandleMessage with typed
lookup_team_task() + CTaskTeam::handle_message() bind macro.
Make lookup_team_task pub(crate) for cross-module reuse.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
crates/openwa-core/src/engine/ddgame.rs
CHANGED
|
@@ -207,8 +207,13 @@ pub struct DDGame {
|
|
| 207 |
pub game_speed: i32,
|
| 208 |
/// 0x72DC: Game speed target (Fixed-point, 0x10000 = 1.0x).
|
| 209 |
pub game_speed_target: i32,
|
| 210 |
-
/// 0x72E0-
|
| 211 |
-
pub _unknown_72e0:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 212 |
/// 0x72EC: RNG state word 1 (changes every frame).
|
| 213 |
pub rng_state_1: u32,
|
| 214 |
/// 0x72F0: RNG state word 2 (changes every frame).
|
|
@@ -437,6 +442,39 @@ impl DDGame {
|
|
| 437 |
rng
|
| 438 |
}
|
| 439 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 440 |
/// Show the "too many objects" warning on the HUD.
|
| 441 |
///
|
| 442 |
/// Sets `hud_status_code = 6` and loads string resource 0x70F into
|
|
|
|
| 207 |
pub game_speed: i32,
|
| 208 |
/// 0x72DC: Game speed target (Fixed-point, 0x10000 = 1.0x).
|
| 209 |
pub game_speed_target: i32,
|
| 210 |
+
/// 0x72E0-0x72E3: Unknown
|
| 211 |
+
pub _unknown_72e0: u32,
|
| 212 |
+
/// 0x72E4: Active render/weapon slot count. Reset to 14 (0x0E) by WeaponRelease
|
| 213 |
+
/// sync check, matching the render_entries array size.
|
| 214 |
+
pub render_slot_count: u32,
|
| 215 |
+
/// 0x72E8-0x72EB: Unknown
|
| 216 |
+
pub _unknown_72e8: u32,
|
| 217 |
/// 0x72EC: RNG state word 1 (changes every frame).
|
| 218 |
pub rng_state_1: u32,
|
| 219 |
/// 0x72F0: RNG state word 2 (changes every frame).
|
|
|
|
| 442 |
rng
|
| 443 |
}
|
| 444 |
|
| 445 |
+
/// Read a per-team sound ID from the team sound table at DDGame+0x7768.
|
| 446 |
+
///
|
| 447 |
+
/// The table has stride 0xF0 per team (240-byte per-team config blocks).
|
| 448 |
+
/// The u32 at the base of each block is a sound ID used for type-2 (rope)
|
| 449 |
+
/// weapon release sounds.
|
| 450 |
+
///
|
| 451 |
+
/// # Safety
|
| 452 |
+
/// `team_id` must be a valid team index (0β5).
|
| 453 |
+
pub unsafe fn team_sound_id(&self, team_id: u32) -> u32 {
|
| 454 |
+
let base = (self as *const DDGame as *const u8).add(0x7768);
|
| 455 |
+
*(base.add((team_id as usize) * 0xF0) as *const u32)
|
| 456 |
+
}
|
| 457 |
+
|
| 458 |
+
/// Get a mutable pointer to a per-team/per-worm weapon stat counter.
|
| 459 |
+
///
|
| 460 |
+
/// Four counters exist at DDGame base offsets 0x40CC, 0x40D0, 0x40D4, 0x40D8,
|
| 461 |
+
/// indexed by `team_id * 0x51C + worm_id * 0x9C` (same stride as FullTeamBlock
|
| 462 |
+
/// and WormEntry). These track weapon usage stats during gameplay.
|
| 463 |
+
///
|
| 464 |
+
/// # Safety
|
| 465 |
+
/// `team_id` and `worm_id` must be valid indices.
|
| 466 |
+
pub unsafe fn weapon_stat_counter(
|
| 467 |
+
&mut self,
|
| 468 |
+
team_id: u32,
|
| 469 |
+
worm_id: u32,
|
| 470 |
+
base_offset: usize,
|
| 471 |
+
) -> *mut i32 {
|
| 472 |
+
(self as *mut DDGame as *mut u8)
|
| 473 |
+
.add(base_offset)
|
| 474 |
+
.add((team_id as usize) * 0x51C)
|
| 475 |
+
.add((worm_id as usize) * 0x9C) as *mut i32
|
| 476 |
+
}
|
| 477 |
+
|
| 478 |
/// Show the "too many objects" warning on the HUD.
|
| 479 |
///
|
| 480 |
/// Sets `hud_status_code = 6` and loads string resource 0x70F into
|
crates/openwa-wormkit/src/replacements/weapon.rs
CHANGED
|
@@ -422,7 +422,7 @@ fn can_fire_subtype16(state: u32) -> bool {
|
|
| 422 |
/// Look up the CTaskTeam entity for a worm via SharedData.
|
| 423 |
///
|
| 424 |
/// Returns null if not found.
|
| 425 |
-
unsafe fn lookup_team_task(worm: *const CTaskWorm) -> *mut openwa_core::task::CTaskTeam {
|
| 426 |
use openwa_core::task::{SharedDataTable, Task};
|
| 427 |
|
| 428 |
let table = SharedDataTable::from_task((*worm).as_task_ptr());
|
|
|
|
| 422 |
/// Look up the CTaskTeam entity for a worm via SharedData.
|
| 423 |
///
|
| 424 |
/// Returns null if not found.
|
| 425 |
+
pub(crate) unsafe fn lookup_team_task(worm: *const CTaskWorm) -> *mut openwa_core::task::CTaskTeam {
|
| 426 |
use openwa_core::task::{SharedDataTable, Task};
|
| 427 |
|
| 428 |
let table = SharedDataTable::from_task((*worm).as_task_ptr());
|
crates/openwa-wormkit/src/replacements/weapon_release.rs
CHANGED
|
@@ -9,7 +9,7 @@ use openwa_core::address::va;
|
|
| 9 |
use openwa_core::log::log_line;
|
| 10 |
use openwa_core::rebase::rb;
|
| 11 |
use openwa_core::task::worm::CTaskWorm;
|
| 12 |
-
use openwa_core::task::{CGameTask,
|
| 13 |
|
| 14 |
use crate::hook::{self, usercall_trampoline};
|
| 15 |
use crate::replacements::{sound, weapon};
|
|
@@ -107,14 +107,12 @@ unsafe extern "cdecl" fn weapon_release_impl(
|
|
| 107 |
|
| 108 |
// ββ 1. Sync check βββββββββββββββββββββββββββββββββββββββ
|
| 109 |
if w.fire_sync_frame_1 == w.fire_sync_frame_2 {
|
| 110 |
-
let
|
| 111 |
-
|
| 112 |
-
|
| 113 |
-
|
| 114 |
-
*(ddgame.add(0x73B0).add(offset as usize) as *mut u32) = 0;
|
| 115 |
-
offset += 0x14;
|
| 116 |
}
|
| 117 |
-
|
| 118 |
}
|
| 119 |
|
| 120 |
// ββ 2. Populate context fields ββββββββββββββββββββββββββ
|
|
@@ -185,11 +183,9 @@ unsafe extern "cdecl" fn weapon_release_impl(
|
|
| 185 |
}
|
| 186 |
|
| 187 |
// ββ 5. Network timing βββββββββββββββββββββββββββββββββββ
|
| 188 |
-
let
|
| 189 |
-
let
|
| 190 |
-
let
|
| 191 |
-
let is_network = *ptr_at_24.add(0xD9D0);
|
| 192 |
-
let fe_version = *ptr_at_24.add(0xD9B1);
|
| 193 |
|
| 194 |
let mut adjust = 0i32;
|
| 195 |
let max_clients = if is_network == 0 {
|
|
@@ -227,20 +223,10 @@ unsafe extern "cdecl" fn weapon_release_impl(
|
|
| 227 |
);
|
| 228 |
write_u32(&mut msg_buf, 0x1C, weapon_id);
|
| 229 |
|
| 230 |
-
let
|
| 231 |
-
|
| 232 |
-
|
| 233 |
-
|
| 234 |
-
let handle_msg: unsafe extern "thiscall" fn(
|
| 235 |
-
*mut u8,
|
| 236 |
-
*mut u8,
|
| 237 |
-
u32,
|
| 238 |
-
u32,
|
| 239 |
-
*const u8,
|
| 240 |
-
) = core::mem::transmute(*vtable.add(2));
|
| 241 |
-
handle_msg(
|
| 242 |
-
team_entity as *mut u8,
|
| 243 |
-
(*worm).as_task_ptr_mut() as *mut u8,
|
| 244 |
0x49,
|
| 245 |
0x408,
|
| 246 |
msg_buf.as_ptr(),
|
|
@@ -248,42 +234,25 @@ unsafe extern "cdecl" fn weapon_release_impl(
|
|
| 248 |
}
|
| 249 |
|
| 250 |
// ββ 8. Weapon stat counters βββββββββββββββββββββββββββββ
|
| 251 |
-
let
|
| 252 |
let team_id = (*worm).team_index;
|
| 253 |
let worm_id = (*worm).worm_index;
|
| 254 |
-
|
| 255 |
-
|
| 256 |
-
|
| 257 |
-
let counter = ddgame
|
| 258 |
-
.add(0x40D8)
|
| 259 |
-
.add((team_id * 0x51C) as usize)
|
| 260 |
-
.add((worm_id * 0x9C) as usize) as *mut i32;
|
| 261 |
-
*counter += 1;
|
| 262 |
}
|
| 263 |
|
| 264 |
// Range 0x3E..=0x46
|
| 265 |
if weapon_id.wrapping_sub(0x3E) <= 8 {
|
| 266 |
-
|
| 267 |
-
.add(0x40D4)
|
| 268 |
-
.add((team_id * 0x51C) as usize)
|
| 269 |
-
.add((worm_id * 0x9C) as usize) as *mut i32;
|
| 270 |
-
*counter += 1;
|
| 271 |
}
|
| 272 |
|
| 273 |
if is_weapon_category_a(weapon_id) {
|
| 274 |
-
|
| 275 |
-
.add(0x40D0)
|
| 276 |
-
.add((team_id * 0x51C) as usize)
|
| 277 |
-
.add((worm_id * 0x9C) as usize) as *mut i32;
|
| 278 |
-
*counter += 1;
|
| 279 |
}
|
| 280 |
|
| 281 |
if is_weapon_category_b(weapon_id) {
|
| 282 |
-
|
| 283 |
-
.add(0x40CC)
|
| 284 |
-
.add((team_id * 0x51C) as usize)
|
| 285 |
-
.add((worm_id * 0x9C) as usize) as *mut i32;
|
| 286 |
-
*counter += 1;
|
| 287 |
}
|
| 288 |
|
| 289 |
// ββ 9. Sound dispatch + 10. Visual effect βββββββββββββββ
|
|
@@ -342,10 +311,7 @@ unsafe extern "cdecl" fn weapon_release_impl(
|
|
| 342 |
}
|
| 343 |
2 => {
|
| 344 |
if w._unknown_2cc == 0 || w._unknown_2c8 == 1 {
|
| 345 |
-
|
| 346 |
-
let ddgame = w.ddgame() as *const u8;
|
| 347 |
-
let team_sound_offset = (team_id as usize) * 0xF0 + 0x7768;
|
| 348 |
-
let team_sound = *(ddgame.add(team_sound_offset) as *const u32);
|
| 349 |
sound::play_sound_local(task, team_sound, 3, 0x10000, 0x10000);
|
| 350 |
}
|
| 351 |
}
|
|
@@ -360,8 +326,8 @@ unsafe extern "cdecl" fn weapon_release_impl(
|
|
| 360 |
sound::play_sound_local(task, sound_id, 3, 0x10000, 0x10000);
|
| 361 |
}
|
| 362 |
4 => {
|
| 363 |
-
|
| 364 |
-
|
| 365 |
sound::play_sound_local(task, sound_id, 3, 0x10000, 0x10000);
|
| 366 |
}
|
| 367 |
10 => {
|
|
@@ -383,7 +349,7 @@ unsafe extern "cdecl" fn weapon_release_impl(
|
|
| 383 |
// ββ 10. Visual effect (if triggered by sound dispatch) ββ
|
| 384 |
if do_effect {
|
| 385 |
let ddgame = &mut *(*worm).ddgame();
|
| 386 |
-
let gfx_handler =
|
| 387 |
let palette = *(gfx_handler.add(0x22C) as *const u32);
|
| 388 |
|
| 389 |
let rng1 = ddgame.advance_effect_rng();
|
|
|
|
| 9 |
use openwa_core::log::log_line;
|
| 10 |
use openwa_core::rebase::rb;
|
| 11 |
use openwa_core::task::worm::CTaskWorm;
|
| 12 |
+
use openwa_core::task::{CGameTask, Task};
|
| 13 |
|
| 14 |
use crate::hook::{self, usercall_trampoline};
|
| 15 |
use crate::replacements::{sound, weapon};
|
|
|
|
| 107 |
|
| 108 |
// ββ 1. Sync check βββββββββββββββββββββββββββββββββββββββ
|
| 109 |
if w.fire_sync_frame_1 == w.fire_sync_frame_2 {
|
| 110 |
+
let g = &mut *w.ddgame();
|
| 111 |
+
g.render_slot_count = 0x0E;
|
| 112 |
+
for entry in &mut g.render_entries {
|
| 113 |
+
entry.active = 0;
|
|
|
|
|
|
|
| 114 |
}
|
| 115 |
+
g.render_state_flag = 0;
|
| 116 |
}
|
| 117 |
|
| 118 |
// ββ 2. Populate context fields ββββββββββββββββββββββββββ
|
|
|
|
| 183 |
}
|
| 184 |
|
| 185 |
// ββ 5. Network timing βββββββββββββββββββββββββββββββββββ
|
| 186 |
+
let game_info = (*w.ddgame()).game_info as *const u8;
|
| 187 |
+
let is_network = *game_info.add(0xD9D0);
|
| 188 |
+
let fe_version = *game_info.add(0xD9B1);
|
|
|
|
|
|
|
| 189 |
|
| 190 |
let mut adjust = 0i32;
|
| 191 |
let max_clients = if is_network == 0 {
|
|
|
|
| 223 |
);
|
| 224 |
write_u32(&mut msg_buf, 0x1C, weapon_id);
|
| 225 |
|
| 226 |
+
let team = weapon::lookup_team_task(worm);
|
| 227 |
+
if !team.is_null() {
|
| 228 |
+
(*team).handle_message(
|
| 229 |
+
(*worm).as_task_ptr_mut(),
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 230 |
0x49,
|
| 231 |
0x408,
|
| 232 |
msg_buf.as_ptr(),
|
|
|
|
| 234 |
}
|
| 235 |
|
| 236 |
// ββ 8. Weapon stat counters βββββββββββββββββββββββββββββ
|
| 237 |
+
let g = &mut *(*worm).ddgame();
|
| 238 |
let team_id = (*worm).team_index;
|
| 239 |
let worm_id = (*worm).worm_index;
|
| 240 |
+
|
| 241 |
+
if is_super_weapon(weapon_id, g.version_flag_3) {
|
| 242 |
+
*g.weapon_stat_counter(team_id, worm_id, 0x40D8) += 1;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 243 |
}
|
| 244 |
|
| 245 |
// Range 0x3E..=0x46
|
| 246 |
if weapon_id.wrapping_sub(0x3E) <= 8 {
|
| 247 |
+
*g.weapon_stat_counter(team_id, worm_id, 0x40D4) += 1;
|
|
|
|
|
|
|
|
|
|
|
|
|
| 248 |
}
|
| 249 |
|
| 250 |
if is_weapon_category_a(weapon_id) {
|
| 251 |
+
*g.weapon_stat_counter(team_id, worm_id, 0x40D0) += 1;
|
|
|
|
|
|
|
|
|
|
|
|
|
| 252 |
}
|
| 253 |
|
| 254 |
if is_weapon_category_b(weapon_id) {
|
| 255 |
+
*g.weapon_stat_counter(team_id, worm_id, 0x40CC) += 1;
|
|
|
|
|
|
|
|
|
|
|
|
|
| 256 |
}
|
| 257 |
|
| 258 |
// ββ 9. Sound dispatch + 10. Visual effect βββββββββββββββ
|
|
|
|
| 311 |
}
|
| 312 |
2 => {
|
| 313 |
if w._unknown_2cc == 0 || w._unknown_2c8 == 1 {
|
| 314 |
+
let team_sound = (*w.ddgame()).team_sound_id(team_id);
|
|
|
|
|
|
|
|
|
|
| 315 |
sound::play_sound_local(task, team_sound, 3, 0x10000, 0x10000);
|
| 316 |
}
|
| 317 |
}
|
|
|
|
| 326 |
sound::play_sound_local(task, sound_id, 3, 0x10000, 0x10000);
|
| 327 |
}
|
| 328 |
4 => {
|
| 329 |
+
// Sound ID at entry+0x40 = fire_params.spread (polymorphic use)
|
| 330 |
+
let sound_id = (*entry).fire_params.spread as u32;
|
| 331 |
sound::play_sound_local(task, sound_id, 3, 0x10000, 0x10000);
|
| 332 |
}
|
| 333 |
10 => {
|
|
|
|
| 349 |
// ββ 10. Visual effect (if triggered by sound dispatch) ββ
|
| 350 |
if do_effect {
|
| 351 |
let ddgame = &mut *(*worm).ddgame();
|
| 352 |
+
let gfx_handler = ddgame.game_state_stream as *const u8;
|
| 353 |
let palette = *(gfx_handler.add(0x22C) as *const u32);
|
| 354 |
|
| 355 |
let rng1 = ddgame.advance_effect_rng();
|