paavohtl Claude Opus 4.6 (1M context) commited on
Commit
9ff76f4
·
1 Parent(s): 0c8bf83

refactor: add TeamWeaponSlots/WeaponSlots structs, replace flat array

Browse files

Replace flat weapon_slots: [i32; 852] with structured types:
- TeamWeaponSlots { ammo: [i32; 71], delay: [i32; 71] } (0x238 bytes)
- WeaponSlots { teams: [TeamWeaponSlots; 6] } (0xD50 bytes)

Update TeamArenaState accessors to take (alliance, weapon_id) instead
of flat index. Rename ammo_index → weapon_slot_key returning a tuple.

Update all callers in weapon hooks (GetAmmo, AddAmmo, SubtractAmmo).

Verified: headless PASS (byte-identical), headful 133+3 / 0 FAIL.

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

crates/openwa-core/src/engine/ddgame.rs CHANGED
@@ -412,8 +412,8 @@ pub unsafe fn ddgame_init_fields(ddgame: *mut DDGame) {
412
 
413
  // init_field_64d8 = TeamArenaState.team_count (arena+0x1EB0)
414
  (*ddgame).team_arena.team_count = 0;
415
- // init_field_72a4 = weapon_slots[754] (arena+0x2A7C)
416
- (*ddgame).team_arena.weapon_slots[754] = 0;
417
 
418
  // InitRenderIndices — original sets ESI = ddgame + 0x72D8, now uses typed DDGame ptr
419
  ddgame_init_render_indices(ddgame);
@@ -1766,8 +1766,26 @@ const _: () = assert!(core::mem::size_of::<FullTeamBlock>() == 0x51C);
1766
  /// The original code accesses these as two arrays at different base offsets
1767
  /// (0x1EB4 and 0x1FD0) using the same index `alliance_id * 142 + weapon_id`.
1768
  /// Since 0x1FD0 - 0x1EB4 = 71 * 4, this is equivalent to accessing
1769
- /// `weapon_slots[alliance * 142 + weapon]` for ammo and
1770
- /// `weapon_slots[alliance * 142 + 71 + weapon]` for delay.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1771
  #[repr(C)]
1772
  pub struct TeamArenaState {
1773
  /// 0x0000-0x1EAF: Per-team data region (opaque).
@@ -1783,12 +1801,11 @@ pub struct TeamArenaState {
1783
  pub _teams_region: [u8; 0x1EB0],
1784
  /// 0x1EB0: Number of teams in the game (used by team iteration loops)
1785
  pub team_count: i32,
1786
- /// 0x1EB4: Interleaved ammo/delay slots.
1787
- /// Per alliance: [ammo_0..ammo_70, delay_0..delay_70] = 142 i32 entries.
1788
- /// 6 alliances * 142 = 852 total entries.
1789
  /// Ammo: -1 = unlimited, 0 = none, >0 = count.
1790
  /// Delay: nonzero = weapon on delay.
1791
- pub weapon_slots: [i32; 852],
1792
  /// 0x2C04: Padding between weapon_slots end and game_mode_flag.
1793
  /// weapon_slots: 852 * 4 = 3408 = 0xD50. 0x1EB4 + 0xD50 = 0x2C04.
1794
  pub _pad_2c04: [u8; 8],
@@ -1848,20 +1865,19 @@ pub const GAME_PHASE_SUDDEN_DEATH: i32 = 0x1E4; // 484
1848
  pub const GAME_PHASE_NORMAL_MIN: i32 = -2;
1849
 
1850
  impl TeamArenaState {
1851
- /// Get ammo count for a weapon slot (by flat index).
1852
- pub fn get_ammo(&self, index: usize) -> i32 {
1853
- self.weapon_slots[index]
1854
  }
1855
 
1856
- /// Get mutable reference to ammo count for a weapon slot.
1857
- pub fn ammo_mut(&mut self, index: usize) -> &mut i32 {
1858
- &mut self.weapon_slots[index]
1859
  }
1860
 
1861
- /// Get delay flag for a weapon slot (by flat index).
1862
- /// Delay is at +71 offset from the ammo index within the same alliance block.
1863
- pub fn get_delay(&self, index: usize) -> i32 {
1864
- self.weapon_slots[index + 71]
1865
  }
1866
  }
1867
 
@@ -1981,9 +1997,10 @@ impl TeamArenaRef {
1981
  /// then 71 delay slots (stride 142 per alliance).
1982
  /// Ammo: `weapon_slots[alliance_id * 142 + weapon_id]`
1983
  /// Delay: `weapon_slots[alliance_id * 142 + 71 + weapon_id]`
 
1984
  #[inline]
1985
- pub unsafe fn ammo_index(&self, team_index: usize, weapon_id: u32) -> usize {
1986
  let alliance_id = self.team_header(team_index).weapon_alliance as usize;
1987
- alliance_id * 142 + weapon_id as usize
1988
  }
1989
  }
 
412
 
413
  // init_field_64d8 = TeamArenaState.team_count (arena+0x1EB0)
414
  (*ddgame).team_arena.team_count = 0;
415
+ // init_field_72a4 = weapon_slots flat[754] = alliance 5, ammo[44]
416
+ (*ddgame).team_arena.weapon_slots.teams[5].ammo[44] = 0;
417
 
418
  // InitRenderIndices — original sets ESI = ddgame + 0x72D8, now uses typed DDGame ptr
419
  ddgame_init_render_indices(ddgame);
 
1766
  /// The original code accesses these as two arrays at different base offsets
1767
  /// (0x1EB4 and 0x1FD0) using the same index `alliance_id * 142 + weapon_id`.
1768
  /// Since 0x1FD0 - 0x1EB4 = 71 * 4, this is equivalent to accessing
1769
+ /// `weapon_slots.teams[alliance].ammo[weapon]` and
1770
+ /// `weapon_slots.teams[alliance].delay[weapon]`.
1771
+
1772
+ /// Per-alliance weapon ammo and delay data (0x238 = 568 bytes per alliance).
1773
+ #[repr(C)]
1774
+ pub struct TeamWeaponSlots {
1775
+ /// Ammo counts per weapon (71 entries). -1 = unlimited, 0 = none, >0 = count.
1776
+ pub ammo: [i32; 71],
1777
+ /// Delay flags per weapon (71 entries). Nonzero = weapon on cooldown.
1778
+ pub delay: [i32; 71],
1779
+ }
1780
+ const _: () = assert!(core::mem::size_of::<TeamWeaponSlots>() == 0x238);
1781
+
1782
+ /// Weapon slot data for all 6 alliances (0xD50 = 3408 bytes total).
1783
+ #[repr(C)]
1784
+ pub struct WeaponSlots {
1785
+ pub teams: [TeamWeaponSlots; 6],
1786
+ }
1787
+ const _: () = assert!(core::mem::size_of::<WeaponSlots>() == 852 * 4);
1788
+
1789
  #[repr(C)]
1790
  pub struct TeamArenaState {
1791
  /// 0x0000-0x1EAF: Per-team data region (opaque).
 
1801
  pub _teams_region: [u8; 0x1EB0],
1802
  /// 0x1EB0: Number of teams in the game (used by team iteration loops)
1803
  pub team_count: i32,
1804
+ /// 0x1EB4: Per-alliance weapon ammo and delay data.
1805
+ /// 6 alliances, each with 71 ammo + 71 delay i32 entries.
 
1806
  /// Ammo: -1 = unlimited, 0 = none, >0 = count.
1807
  /// Delay: nonzero = weapon on delay.
1808
+ pub weapon_slots: WeaponSlots,
1809
  /// 0x2C04: Padding between weapon_slots end and game_mode_flag.
1810
  /// weapon_slots: 852 * 4 = 3408 = 0xD50. 0x1EB4 + 0xD50 = 0x2C04.
1811
  pub _pad_2c04: [u8; 8],
 
1865
  pub const GAME_PHASE_NORMAL_MIN: i32 = -2;
1866
 
1867
  impl TeamArenaState {
1868
+ /// Get ammo count for a weapon by alliance and weapon ID.
1869
+ pub fn get_ammo(&self, alliance: usize, weapon_id: usize) -> i32 {
1870
+ self.weapon_slots.teams[alliance].ammo[weapon_id]
1871
  }
1872
 
1873
+ /// Get mutable reference to ammo count.
1874
+ pub fn ammo_mut(&mut self, alliance: usize, weapon_id: usize) -> &mut i32 {
1875
+ &mut self.weapon_slots.teams[alliance].ammo[weapon_id]
1876
  }
1877
 
1878
+ /// Get delay flag for a weapon by alliance and weapon ID.
1879
+ pub fn get_delay(&self, alliance: usize, weapon_id: usize) -> i32 {
1880
+ self.weapon_slots.teams[alliance].delay[weapon_id]
 
1881
  }
1882
  }
1883
 
 
1997
  /// then 71 delay slots (stride 142 per alliance).
1998
  /// Ammo: `weapon_slots[alliance_id * 142 + weapon_id]`
1999
  /// Delay: `weapon_slots[alliance_id * 142 + 71 + weapon_id]`
2000
+ /// Returns (alliance_id, weapon_id) for accessing weapon slots.
2001
  #[inline]
2002
+ pub unsafe fn weapon_slot_key(&self, team_index: usize, weapon_id: u32) -> (usize, usize) {
2003
  let alliance_id = self.team_header(team_index).weapon_alliance as usize;
2004
+ (alliance_id, weapon_id as usize)
2005
  }
2006
  }
crates/openwa-wormkit/src/replacements/weapon.rs CHANGED
@@ -30,14 +30,14 @@ unsafe extern "cdecl" fn add_ammo_impl(
30
  arena: TeamArenaRef,
31
  weapon_id: u32,
32
  ) {
33
- let idx = arena.ammo_index(team_index as usize, weapon_id);
34
  let state = arena.state_mut();
35
- let ammo = state.get_ammo(idx);
36
  if ammo >= 0 {
37
  if amount < 0 {
38
- *state.ammo_mut(idx) = -1; // set unlimited
39
  } else {
40
- *state.ammo_mut(idx) = ammo + amount;
41
  }
42
  }
43
  }
@@ -52,11 +52,11 @@ usercall_trampoline!(fn trampoline_add_ammo; impl_fn = add_ammo_impl;
52
  // RET 0x4
53
 
54
  unsafe extern "cdecl" fn subtract_ammo_impl(team_index: u32, arena: TeamArenaRef, weapon_id: u32) {
55
- let idx = arena.ammo_index(team_index as usize, weapon_id);
56
  let state = arena.state_mut();
57
- let ammo = state.get_ammo(idx);
58
  if ammo > 0 {
59
- *state.ammo_mut(idx) = ammo - 1;
60
  }
61
  }
62
 
@@ -74,11 +74,11 @@ unsafe extern "cdecl" fn get_ammo_impl(
74
  arena: TeamArenaRef,
75
  weapon_id: u32,
76
  ) -> u32 {
77
- let idx = arena.ammo_index(team_index as usize, weapon_id);
78
  let state = arena.state();
79
 
80
  // Check weapon delay
81
- if state.get_delay(idx) != 0 {
82
  if state.game_mode_flag == 0 {
83
  return 0;
84
  }
@@ -98,7 +98,7 @@ unsafe extern "cdecl" fn get_ammo_impl(
98
  }
99
  }
100
 
101
- state.get_ammo(idx) as u32
102
  }
103
 
104
  usercall_trampoline!(fn trampoline_get_ammo; impl_fn = get_ammo_impl;
 
30
  arena: TeamArenaRef,
31
  weapon_id: u32,
32
  ) {
33
+ let (alliance, wid) = arena.weapon_slot_key(team_index as usize, weapon_id);
34
  let state = arena.state_mut();
35
+ let ammo = state.get_ammo(alliance, wid);
36
  if ammo >= 0 {
37
  if amount < 0 {
38
+ *state.ammo_mut(alliance, wid) = -1;
39
  } else {
40
+ *state.ammo_mut(alliance, wid) = ammo + amount;
41
  }
42
  }
43
  }
 
52
  // RET 0x4
53
 
54
  unsafe extern "cdecl" fn subtract_ammo_impl(team_index: u32, arena: TeamArenaRef, weapon_id: u32) {
55
+ let (alliance, wid) = arena.weapon_slot_key(team_index as usize, weapon_id);
56
  let state = arena.state_mut();
57
+ let ammo = state.get_ammo(alliance, wid);
58
  if ammo > 0 {
59
+ *state.ammo_mut(alliance, wid) = ammo - 1;
60
  }
61
  }
62
 
 
74
  arena: TeamArenaRef,
75
  weapon_id: u32,
76
  ) -> u32 {
77
+ let (alliance, wid) = arena.weapon_slot_key(team_index as usize, weapon_id);
78
  let state = arena.state();
79
 
80
  // Check weapon delay
81
+ if state.get_delay(alliance, wid) != 0 {
82
  if state.game_mode_flag == 0 {
83
  return 0;
84
  }
 
98
  }
99
  }
100
 
101
+ state.get_ammo(alliance, wid) as u32
102
  }
103
 
104
  usercall_trampoline!(fn trampoline_get_ammo; impl_fn = get_ammo_impl;