refactor: add TeamWeaponSlots/WeaponSlots structs, replace flat array
Browse filesReplace 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]
|
| 416 |
-
(*ddgame).team_arena.weapon_slots[
|
| 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
|
| 1770 |
-
/// `weapon_slots[alliance
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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:
|
| 1787 |
-
///
|
| 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:
|
| 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
|
| 1852 |
-
pub fn get_ammo(&self,
|
| 1853 |
-
self.weapon_slots[
|
| 1854 |
}
|
| 1855 |
|
| 1856 |
-
/// Get mutable reference to ammo count
|
| 1857 |
-
pub fn ammo_mut(&mut self,
|
| 1858 |
-
&mut self.weapon_slots[
|
| 1859 |
}
|
| 1860 |
|
| 1861 |
-
/// Get delay flag for a weapon
|
| 1862 |
-
|
| 1863 |
-
|
| 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
|
| 1986 |
let alliance_id = self.team_header(team_index).weapon_alliance as usize;
|
| 1987 |
-
alliance_id
|
| 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
|
| 34 |
let state = arena.state_mut();
|
| 35 |
-
let ammo = state.get_ammo(
|
| 36 |
if ammo >= 0 {
|
| 37 |
if amount < 0 {
|
| 38 |
-
*state.ammo_mut(
|
| 39 |
} else {
|
| 40 |
-
*state.ammo_mut(
|
| 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
|
| 56 |
let state = arena.state_mut();
|
| 57 |
-
let ammo = state.get_ammo(
|
| 58 |
if ammo > 0 {
|
| 59 |
-
*state.ammo_mut(
|
| 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
|
| 78 |
let state = arena.state();
|
| 79 |
|
| 80 |
// Check weapon delay
|
| 81 |
-
if state.get_delay(
|
| 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(
|
| 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;
|