paavohtl Claude Opus 4.6 (1M context) commited on
Commit
e85f37c
Β·
1 Parent(s): f5fbbeb

refactor: eliminate raw pointer arithmetic in weapon_release.rs

Browse files

Replace 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-0x72EB: Unknown
211
- pub _unknown_72e0: [u8; 0x72EC - 0x72E0],
 
 
 
 
 
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, SharedDataTable, Task};
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 ddgame = w.ddgame() as *mut u8;
111
- *(ddgame.add(0x72E4) as *mut u32) = 0x0E;
112
- let mut offset = 0u32;
113
- while (offset as i32) < 0x118 {
114
- *(ddgame.add(0x73B0).add(offset as usize) as *mut u32) = 0;
115
- offset += 0x14;
116
  }
117
- *(ddgame.add(0x739C) as *mut u32) = 0;
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 ddgame_ptr = w.ddgame();
189
- let ddgame_raw = ddgame_ptr as *const u8;
190
- let ptr_at_24 = *(ddgame_raw.add(0x24) as *const *const u8);
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 table = SharedDataTable::from_task((*worm).as_task_ptr());
231
- let team_entity = table.lookup(0, 0x14);
232
- if !team_entity.is_null() {
233
- let vtable = *(team_entity as *const *const usize);
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 ddgame = (*worm).ddgame() as *mut u8;
252
  let team_id = (*worm).team_index;
253
  let worm_id = (*worm).worm_index;
254
- let current_weapon_byte = *ddgame.add(0x7E3F);
255
-
256
- if is_super_weapon(weapon_id, current_weapon_byte) {
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
- let counter = ddgame
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
- let counter = ddgame
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
- let counter = ddgame
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
- // Team sound from DDGame+0x7768 + team_id * 0xF0
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
- let sound_id = *((&raw const (*entry).fire_params as *const u8).add(4)
364
- as *const u32);
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 = *((&raw const *ddgame as *const u8).add(0x528) as *const *const u8);
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();