refactor: rename team arena types for clarity
Browse files- FullTeamBlock → TeamBlock (nothing partial about it)
- TeamBlockSlot0 → TeamSlot0 (shorter, follows from TeamBlock)
- TeamBlockHeader → TeamHeader (shorter)
- _teams_region → team_blocks_region (public, used field)
- _worm_overlap → worm_overlap (used field)
- _metadata → trailer (it's a 0x3C trailing region)
Runtime hex dump confirmed the existing struct layout is correct:
header(0x9C union) + worms[7](0x9C each) + trailer(0x3C) = 0x51C.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
crates/openwa-core/src/engine/ddgame.rs
CHANGED
|
@@ -1428,17 +1428,17 @@ pub mod offsets {
|
|
| 1428 |
/// GetAmmo/AddAmmo/SubtractAmmo.
|
| 1429 |
pub const TEAM_ARENA_STATE: usize = 0x4628;
|
| 1430 |
|
| 1431 |
-
// === Team block array (7 ×
|
| 1432 |
/// Start of team block array within DDGame (7 blocks, stride 0x51C).
|
| 1433 |
/// Derived: entry_ptr(team=0) - 0x598 = 0x4628 - 0x598 = 0x4090.
|
| 1434 |
/// Runtime-confirmed: block[0] is zeroed preamble, blocks[1-6] hold team data.
|
| 1435 |
pub const TEAM_BLOCKS: usize = 0x4090;
|
| 1436 |
|
| 1437 |
-
/// Byte offset from TeamArenaState base back to
|
| 1438 |
-
/// `blocks_ptr = (tws_base as *const c_char).sub(ARENA_TO_BLOCKS) as *const
|
| 1439 |
///
|
| 1440 |
/// entry_ptr(0) = DDGame+0x4628 = TEAM_BLOCKS + 0x598.
|
| 1441 |
-
/// 0x598 = sizeof(
|
| 1442 |
pub const ARENA_TO_BLOCKS: usize = 0x598;
|
| 1443 |
|
| 1444 |
// === FUN_00526120 init offsets (stride 0x194, 10 entries) ===
|
|
@@ -1636,7 +1636,7 @@ const _: () = assert!(core::mem::size_of::<SoundQueueEntry>() == 0x24);
|
|
| 1636 |
///
|
| 1637 |
/// WA supports up to 8 playable worms per team. The original code accesses
|
| 1638 |
/// worms via raw pointer arithmetic from the team entry pointer, using
|
| 1639 |
-
/// stride 0x9C. This means the 8th worm crosses the
|
| 1640 |
/// into the next block's header slot — see `TeamArenaRef::team_worm()`.
|
| 1641 |
///
|
| 1642 |
/// Field offsets confirmed by runtime memory dump (validator DLL):
|
|
@@ -1675,7 +1675,7 @@ pub struct WormEntry {
|
|
| 1675 |
|
| 1676 |
const _: () = assert!(core::mem::size_of::<WormEntry>() == 0x9C);
|
| 1677 |
|
| 1678 |
-
/// Team-level metadata stored at slot 0 of each
|
| 1679 |
///
|
| 1680 |
/// This struct overlays the same memory as a WormEntry but interprets the
|
| 1681 |
/// high offsets (0x6C+) as team metadata rather than worm data. The low
|
|
@@ -1685,9 +1685,9 @@ const _: () = assert!(core::mem::size_of::<WormEntry>() == 0x9C);
|
|
| 1685 |
/// Accessed via `TeamArenaRef::team_header()` and `team_header_b()`.
|
| 1686 |
#[repr(C)]
|
| 1687 |
#[derive(Clone, Copy)]
|
| 1688 |
-
pub struct
|
| 1689 |
/// 0x00-0x5F: Opaque — may hold 8th worm data from previous team.
|
| 1690 |
-
pub
|
| 1691 |
/// 0x60-0x6B: Unknown padding.
|
| 1692 |
pub _unknown_60: [u8; 0x0C],
|
| 1693 |
/// 0x6C: Team eliminated flag (nonzero = eliminated).
|
|
@@ -1710,27 +1710,27 @@ pub struct TeamBlockHeader {
|
|
| 1710 |
pub _unknown_98: [u8; 4],
|
| 1711 |
}
|
| 1712 |
|
| 1713 |
-
const _: () = assert!(core::mem::size_of::<
|
| 1714 |
|
| 1715 |
-
/// Union for slot 0 of a
|
| 1716 |
///
|
| 1717 |
/// This slot is dual-purpose: its high offsets store team metadata
|
| 1718 |
-
/// (`
|
| 1719 |
/// of the previous team (`WormEntry`). The two uses don't conflict
|
| 1720 |
/// because worm data occupies 0x00-0x5F and header data starts at 0x6C.
|
| 1721 |
#[repr(C)]
|
| 1722 |
#[derive(Clone, Copy)]
|
| 1723 |
-
pub union
|
| 1724 |
/// View as worm data (used when the previous team has 8 worms).
|
| 1725 |
pub worm: core::mem::ManuallyDrop<WormEntry>,
|
| 1726 |
/// View as team metadata (eliminated, alliance, worm_count, etc.).
|
| 1727 |
-
pub team: core::mem::ManuallyDrop<
|
| 1728 |
}
|
| 1729 |
|
| 1730 |
/// Full per-team data block (0x51C bytes, 7 blocks in DDGame).
|
| 1731 |
///
|
| 1732 |
-
/// Each block starts with a `
|
| 1733 |
-
/// dual purpose: its high offsets hold team metadata (`
|
| 1734 |
/// while its low offsets may contain the 8th worm of the previous team.
|
| 1735 |
/// The remaining 7 worm slots follow, then 0x3C bytes of metadata.
|
| 1736 |
///
|
|
@@ -1739,19 +1739,19 @@ pub union TeamBlockSlot0 {
|
|
| 1739 |
/// which uses raw pointer arithmetic and naturally crosses block boundaries.
|
| 1740 |
///
|
| 1741 |
/// **Header access**: Use `TeamArenaRef::team_header(idx)` to get the
|
| 1742 |
-
/// `
|
| 1743 |
#[repr(C)]
|
| 1744 |
-
pub struct
|
| 1745 |
-
/// 0x000-0x09B: Header slot (union of
|
| 1746 |
/// Team metadata at high offsets; may hold 8th worm data at low offsets.
|
| 1747 |
-
pub header:
|
| 1748 |
/// 0x09C-0x4DF: 7 worm entries (slots 1-7, stride 0x9C)
|
| 1749 |
pub worms: [WormEntry; 7],
|
| 1750 |
/// 0x4E0-0x51B: Team metadata (0x3C bytes)
|
| 1751 |
-
pub
|
| 1752 |
}
|
| 1753 |
|
| 1754 |
-
const _: () = assert!(core::mem::size_of::<
|
| 1755 |
|
| 1756 |
/// Team arena state area within DDGame (at DDGame + 0x4628).
|
| 1757 |
///
|
|
@@ -1793,12 +1793,12 @@ pub struct TeamArenaState {
|
|
| 1793 |
/// This region contains 7 team entries at stride 0x51C (1-indexed, index 0
|
| 1794 |
/// is preamble). The original WA.exe accesses team data via raw pointer
|
| 1795 |
/// arithmetic relative to the arena base. In our Rust code, team data is
|
| 1796 |
-
/// accessed through `TeamArenaRef`, `
|
| 1797 |
/// so this region is treated as opaque padding.
|
| 1798 |
///
|
| 1799 |
/// The 7th entry (team 6, 1-indexed) only has 8 bytes before team_count;
|
| 1800 |
/// the rest overlaps with weapon_slots.
|
| 1801 |
-
pub
|
| 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.
|
|
@@ -1885,7 +1885,7 @@ impl TeamArenaState {
|
|
| 1885 |
///
|
| 1886 |
/// Wraps the raw `base: u32` from trampoline register captures and provides
|
| 1887 |
/// accessor methods that encapsulate the backward pointer arithmetic to reach
|
| 1888 |
-
///
|
| 1889 |
/// the TeamArenaState in DDGame memory.
|
| 1890 |
///
|
| 1891 |
/// # Safety
|
|
@@ -1922,10 +1922,10 @@ impl TeamArenaRef {
|
|
| 1922 |
&mut *(self.base as *mut TeamArenaState)
|
| 1923 |
}
|
| 1924 |
|
| 1925 |
-
/// Get pointer to the
|
| 1926 |
#[inline]
|
| 1927 |
-
pub unsafe fn blocks(&self) -> *const
|
| 1928 |
-
self.base.sub(offsets::ARENA_TO_BLOCKS) as *const
|
| 1929 |
}
|
| 1930 |
|
| 1931 |
/// Get the team header (metadata) for a team.
|
|
@@ -1933,7 +1933,7 @@ impl TeamArenaRef {
|
|
| 1933 |
/// Returns `&block[team_idx+1].header.team`, which holds team metadata:
|
| 1934 |
/// worm_count, eliminated flag, weapon_alliance, team_name.
|
| 1935 |
#[inline]
|
| 1936 |
-
pub unsafe fn team_header(&self, team_idx: usize) -> &
|
| 1937 |
&(*self.blocks().add(team_idx + 1)).header.team
|
| 1938 |
}
|
| 1939 |
|
|
@@ -1941,7 +1941,7 @@ impl TeamArenaRef {
|
|
| 1941 |
///
|
| 1942 |
/// Uses raw pointer arithmetic matching the original WA code:
|
| 1943 |
/// `base + team_idx * 0x51C + worm_num * 0x9C - 0x598`.
|
| 1944 |
-
/// This naturally crosses
|
| 1945 |
/// since the 8th worm's early fields (state, health) spill into the
|
| 1946 |
/// next block's header slot. The header metadata lives at high offsets
|
| 1947 |
/// (0x6C+) that don't conflict with worm data (0x00-0x5F).
|
|
@@ -1964,7 +1964,7 @@ impl TeamArenaRef {
|
|
| 1964 |
/// **Note**: For accessing worms, prefer `team_worm()` which handles
|
| 1965 |
/// 8-worm teams correctly via cross-boundary pointer arithmetic.
|
| 1966 |
#[inline]
|
| 1967 |
-
pub unsafe fn team_and_header(&self, team_idx: usize) -> (&
|
| 1968 |
let blocks = self.blocks();
|
| 1969 |
let block = &*blocks.add(team_idx);
|
| 1970 |
let header = &(*blocks.add(team_idx + 1)).header.team;
|
|
@@ -1976,14 +1976,14 @@ impl TeamArenaRef {
|
|
| 1976 |
/// Pattern B indexes from block[i+2] for 0-indexed team `i`:
|
| 1977 |
/// `base + 0x510 + i*0x51C` = `blocks[i+2].header.team + 0x70`.
|
| 1978 |
#[inline]
|
| 1979 |
-
pub unsafe fn team_header_b(&self, team_idx: usize) -> &
|
| 1980 |
&(*self.blocks().add(team_idx + 2)).header.team
|
| 1981 |
}
|
| 1982 |
|
| 1983 |
/// Get mutable team header for Pattern B access.
|
| 1984 |
#[inline]
|
| 1985 |
-
pub unsafe fn team_header_b_mut(&self, team_idx: usize) -> &mut
|
| 1986 |
-
&mut (*(self.blocks() as *mut
|
| 1987 |
.header
|
| 1988 |
.team
|
| 1989 |
}
|
|
|
|
| 1428 |
/// GetAmmo/AddAmmo/SubtractAmmo.
|
| 1429 |
pub const TEAM_ARENA_STATE: usize = 0x4628;
|
| 1430 |
|
| 1431 |
+
// === Team block array (7 × TeamBlock, stride 0x51C) ===
|
| 1432 |
/// Start of team block array within DDGame (7 blocks, stride 0x51C).
|
| 1433 |
/// Derived: entry_ptr(team=0) - 0x598 = 0x4628 - 0x598 = 0x4090.
|
| 1434 |
/// Runtime-confirmed: block[0] is zeroed preamble, blocks[1-6] hold team data.
|
| 1435 |
pub const TEAM_BLOCKS: usize = 0x4090;
|
| 1436 |
|
| 1437 |
+
/// Byte offset from TeamArenaState base back to TeamBlock array start.
|
| 1438 |
+
/// `blocks_ptr = (tws_base as *const c_char).sub(ARENA_TO_BLOCKS) as *const TeamBlock`
|
| 1439 |
///
|
| 1440 |
/// entry_ptr(0) = DDGame+0x4628 = TEAM_BLOCKS + 0x598.
|
| 1441 |
+
/// 0x598 = sizeof(TeamBlock) + 0x7C = one block + offset into TeamHeader.
|
| 1442 |
pub const ARENA_TO_BLOCKS: usize = 0x598;
|
| 1443 |
|
| 1444 |
// === FUN_00526120 init offsets (stride 0x194, 10 entries) ===
|
|
|
|
| 1636 |
///
|
| 1637 |
/// WA supports up to 8 playable worms per team. The original code accesses
|
| 1638 |
/// worms via raw pointer arithmetic from the team entry pointer, using
|
| 1639 |
+
/// stride 0x9C. This means the 8th worm crosses the TeamBlock boundary
|
| 1640 |
/// into the next block's header slot — see `TeamArenaRef::team_worm()`.
|
| 1641 |
///
|
| 1642 |
/// Field offsets confirmed by runtime memory dump (validator DLL):
|
|
|
|
| 1675 |
|
| 1676 |
const _: () = assert!(core::mem::size_of::<WormEntry>() == 0x9C);
|
| 1677 |
|
| 1678 |
+
/// Team-level metadata stored at slot 0 of each TeamBlock (0x9C bytes).
|
| 1679 |
///
|
| 1680 |
/// This struct overlays the same memory as a WormEntry but interprets the
|
| 1681 |
/// high offsets (0x6C+) as team metadata rather than worm data. The low
|
|
|
|
| 1685 |
/// Accessed via `TeamArenaRef::team_header()` and `team_header_b()`.
|
| 1686 |
#[repr(C)]
|
| 1687 |
#[derive(Clone, Copy)]
|
| 1688 |
+
pub struct TeamHeader {
|
| 1689 |
/// 0x00-0x5F: Opaque — may hold 8th worm data from previous team.
|
| 1690 |
+
pub worm_overlap: [u8; 0x60],
|
| 1691 |
/// 0x60-0x6B: Unknown padding.
|
| 1692 |
pub _unknown_60: [u8; 0x0C],
|
| 1693 |
/// 0x6C: Team eliminated flag (nonzero = eliminated).
|
|
|
|
| 1710 |
pub _unknown_98: [u8; 4],
|
| 1711 |
}
|
| 1712 |
|
| 1713 |
+
const _: () = assert!(core::mem::size_of::<TeamHeader>() == 0x9C);
|
| 1714 |
|
| 1715 |
+
/// Union for slot 0 of a TeamBlock.
|
| 1716 |
///
|
| 1717 |
/// This slot is dual-purpose: its high offsets store team metadata
|
| 1718 |
+
/// (`TeamHeader`), while its low offsets may contain the 8th worm
|
| 1719 |
/// of the previous team (`WormEntry`). The two uses don't conflict
|
| 1720 |
/// because worm data occupies 0x00-0x5F and header data starts at 0x6C.
|
| 1721 |
#[repr(C)]
|
| 1722 |
#[derive(Clone, Copy)]
|
| 1723 |
+
pub union TeamSlot0 {
|
| 1724 |
/// View as worm data (used when the previous team has 8 worms).
|
| 1725 |
pub worm: core::mem::ManuallyDrop<WormEntry>,
|
| 1726 |
/// View as team metadata (eliminated, alliance, worm_count, etc.).
|
| 1727 |
+
pub team: core::mem::ManuallyDrop<TeamHeader>,
|
| 1728 |
}
|
| 1729 |
|
| 1730 |
/// Full per-team data block (0x51C bytes, 7 blocks in DDGame).
|
| 1731 |
///
|
| 1732 |
+
/// Each block starts with a `TeamSlot0` union (0x9C bytes) that serves
|
| 1733 |
+
/// dual purpose: its high offsets hold team metadata (`TeamHeader`),
|
| 1734 |
/// while its low offsets may contain the 8th worm of the previous team.
|
| 1735 |
/// The remaining 7 worm slots follow, then 0x3C bytes of metadata.
|
| 1736 |
///
|
|
|
|
| 1739 |
/// which uses raw pointer arithmetic and naturally crosses block boundaries.
|
| 1740 |
///
|
| 1741 |
/// **Header access**: Use `TeamArenaRef::team_header(idx)` to get the
|
| 1742 |
+
/// `TeamHeader` for a team (reads from `blocks[idx+1].header.team`).
|
| 1743 |
#[repr(C)]
|
| 1744 |
+
pub struct TeamBlock {
|
| 1745 |
+
/// 0x000-0x09B: Header slot (union of TeamHeader and WormEntry).
|
| 1746 |
/// Team metadata at high offsets; may hold 8th worm data at low offsets.
|
| 1747 |
+
pub header: TeamSlot0,
|
| 1748 |
/// 0x09C-0x4DF: 7 worm entries (slots 1-7, stride 0x9C)
|
| 1749 |
pub worms: [WormEntry; 7],
|
| 1750 |
/// 0x4E0-0x51B: Team metadata (0x3C bytes)
|
| 1751 |
+
pub trailer: [u8; 0x3C],
|
| 1752 |
}
|
| 1753 |
|
| 1754 |
+
const _: () = assert!(core::mem::size_of::<TeamBlock>() == 0x51C);
|
| 1755 |
|
| 1756 |
/// Team arena state area within DDGame (at DDGame + 0x4628).
|
| 1757 |
///
|
|
|
|
| 1793 |
/// This region contains 7 team entries at stride 0x51C (1-indexed, index 0
|
| 1794 |
/// is preamble). The original WA.exe accesses team data via raw pointer
|
| 1795 |
/// arithmetic relative to the arena base. In our Rust code, team data is
|
| 1796 |
+
/// accessed through `TeamArenaRef`, `TeamBlock`, and `TeamHeader`,
|
| 1797 |
/// so this region is treated as opaque padding.
|
| 1798 |
///
|
| 1799 |
/// The 7th entry (team 6, 1-indexed) only has 8 bytes before team_count;
|
| 1800 |
/// the rest overlaps with weapon_slots.
|
| 1801 |
+
pub team_blocks_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.
|
|
|
|
| 1885 |
///
|
| 1886 |
/// Wraps the raw `base: u32` from trampoline register captures and provides
|
| 1887 |
/// accessor methods that encapsulate the backward pointer arithmetic to reach
|
| 1888 |
+
/// TeamBlock worm data. The TeamBlock array lives 0x598 bytes before
|
| 1889 |
/// the TeamArenaState in DDGame memory.
|
| 1890 |
///
|
| 1891 |
/// # Safety
|
|
|
|
| 1922 |
&mut *(self.base as *mut TeamArenaState)
|
| 1923 |
}
|
| 1924 |
|
| 1925 |
+
/// Get pointer to the TeamBlock array base.
|
| 1926 |
#[inline]
|
| 1927 |
+
pub unsafe fn blocks(&self) -> *const TeamBlock {
|
| 1928 |
+
self.base.sub(offsets::ARENA_TO_BLOCKS) as *const TeamBlock
|
| 1929 |
}
|
| 1930 |
|
| 1931 |
/// Get the team header (metadata) for a team.
|
|
|
|
| 1933 |
/// Returns `&block[team_idx+1].header.team`, which holds team metadata:
|
| 1934 |
/// worm_count, eliminated flag, weapon_alliance, team_name.
|
| 1935 |
#[inline]
|
| 1936 |
+
pub unsafe fn team_header(&self, team_idx: usize) -> &TeamHeader {
|
| 1937 |
&(*self.blocks().add(team_idx + 1)).header.team
|
| 1938 |
}
|
| 1939 |
|
|
|
|
| 1941 |
///
|
| 1942 |
/// Uses raw pointer arithmetic matching the original WA code:
|
| 1943 |
/// `base + team_idx * 0x51C + worm_num * 0x9C - 0x598`.
|
| 1944 |
+
/// This naturally crosses TeamBlock boundaries when worm_num = 8,
|
| 1945 |
/// since the 8th worm's early fields (state, health) spill into the
|
| 1946 |
/// next block's header slot. The header metadata lives at high offsets
|
| 1947 |
/// (0x6C+) that don't conflict with worm data (0x00-0x5F).
|
|
|
|
| 1964 |
/// **Note**: For accessing worms, prefer `team_worm()` which handles
|
| 1965 |
/// 8-worm teams correctly via cross-boundary pointer arithmetic.
|
| 1966 |
#[inline]
|
| 1967 |
+
pub unsafe fn team_and_header(&self, team_idx: usize) -> (&TeamBlock, &TeamHeader) {
|
| 1968 |
let blocks = self.blocks();
|
| 1969 |
let block = &*blocks.add(team_idx);
|
| 1970 |
let header = &(*blocks.add(team_idx + 1)).header.team;
|
|
|
|
| 1976 |
/// Pattern B indexes from block[i+2] for 0-indexed team `i`:
|
| 1977 |
/// `base + 0x510 + i*0x51C` = `blocks[i+2].header.team + 0x70`.
|
| 1978 |
#[inline]
|
| 1979 |
+
pub unsafe fn team_header_b(&self, team_idx: usize) -> &TeamHeader {
|
| 1980 |
&(*self.blocks().add(team_idx + 2)).header.team
|
| 1981 |
}
|
| 1982 |
|
| 1983 |
/// Get mutable team header for Pattern B access.
|
| 1984 |
#[inline]
|
| 1985 |
+
pub unsafe fn team_header_b_mut(&self, team_idx: usize) -> &mut TeamHeader {
|
| 1986 |
+
&mut (*(self.blocks() as *mut TeamBlock).add(team_idx + 2))
|
| 1987 |
.header
|
| 1988 |
.team
|
| 1989 |
}
|
crates/openwa-core/src/engine/mod.rs
CHANGED
|
@@ -9,8 +9,8 @@ pub mod net_bridge;
|
|
| 9 |
pub mod net_wrapper;
|
| 10 |
|
| 11 |
pub use ddgame::{
|
| 12 |
-
DDGame,
|
| 13 |
-
|
| 14 |
GAME_PHASE_SUDDEN_DEATH,
|
| 15 |
};
|
| 16 |
pub use ddgame_wrapper::DDGameWrapper;
|
|
|
|
| 9 |
pub mod net_wrapper;
|
| 10 |
|
| 11 |
pub use ddgame::{
|
| 12 |
+
DDGame, RenderEntry, SoundQueueEntry, TeamArenaRef, TeamArenaState, TeamBlock,
|
| 13 |
+
TeamHeader, TeamIndexMap, TeamSlot0, WormEntry, GAME_PHASE_NORMAL_MIN,
|
| 14 |
GAME_PHASE_SUDDEN_DEATH,
|
| 15 |
};
|
| 16 |
pub use ddgame_wrapper::DDGameWrapper;
|
crates/openwa-wormkit/src/replacements/weapon.rs
CHANGED
|
@@ -409,7 +409,7 @@ unsafe fn fire_skip_go(worm: *const CTaskWorm, entry: *const WeaponEntry) {
|
|
| 409 |
let bit = 1u32 << bit_index;
|
| 410 |
|
| 411 |
// First DWORD of each team's region in TeamArenaState (stride 0x51C)
|
| 412 |
-
let flags_ptr = ddgame.team_arena.
|
| 413 |
[team_index * 0x51C..][..4]
|
| 414 |
.as_ptr() as *mut u32;
|
| 415 |
let flags = *flags_ptr;
|
|
|
|
| 409 |
let bit = 1u32 << bit_index;
|
| 410 |
|
| 411 |
// First DWORD of each team's region in TeamArenaState (stride 0x51C)
|
| 412 |
+
let flags_ptr = ddgame.team_arena.team_blocks_region
|
| 413 |
[team_index * 0x51C..][..4]
|
| 414 |
.as_ptr() as *mut u32;
|
| 415 |
let flags = *flags_ptr;
|