refactor: rename TeamArenaState to TeamArena, use raw pointers for soundness
Browse files- TeamArenaState β TeamArena; accessor methods now take *mut/*const Self
instead of returning &mut from &self (unsound aliasing)
- Move unrelated DDGame sub-structs out of team_arena.rs:
CoordEntry/CoordList β engine/coord.rs,
RenderEntry β render/mod.rs,
SoundQueueEntry β audio/sound_queue.rs
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- crates/openwa-core/src/audio/mod.rs +2 -0
- crates/openwa-core/src/audio/sound_ops.rs +2 -1
- crates/openwa-core/src/audio/sound_queue.rs +33 -0
- crates/openwa-core/src/engine/coord.rs +49 -0
- crates/openwa-core/src/engine/ddgame.rs +7 -7
- crates/openwa-core/src/engine/ddgame_constructor.rs +2 -2
- crates/openwa-core/src/engine/mod.rs +3 -2
- crates/openwa-core/src/engine/team_arena.rs +73 -199
- crates/openwa-core/src/engine/team_ops.rs +63 -72
- crates/openwa-core/src/game/weapon_fire.rs +36 -39
- crates/openwa-core/src/render/mod.rs +14 -0
- crates/openwa-core/src/task/team.rs +1 -1
- crates/openwa-core/src/task/turn_game.rs +2 -2
- crates/openwa-core/src/task/worm.rs +1 -1
- crates/openwa-dll/src/replacements/replay_test.rs +4 -7
- crates/openwa-dll/src/replacements/team.rs +13 -9
- crates/openwa-dll/src/replacements/weapon.rs +9 -5
- crates/openwa-dll/src/snapshot.rs +12 -8
crates/openwa-core/src/audio/mod.rs
CHANGED
|
@@ -4,6 +4,7 @@ pub mod known_sound_id;
|
|
| 4 |
pub mod music;
|
| 5 |
pub mod sound_id;
|
| 6 |
pub mod sound_ops;
|
|
|
|
| 7 |
pub mod speech;
|
| 8 |
pub mod speech_ops;
|
| 9 |
pub mod wav_player;
|
|
@@ -20,4 +21,5 @@ pub use dssound::{ChannelDescriptor, DSSound, DSSoundVtable};
|
|
| 20 |
pub use known_sound_id::KnownSoundId;
|
| 21 |
pub use music::{Music, MusicVtable, StreamingAudio};
|
| 22 |
pub use sound_id::SoundId;
|
|
|
|
| 23 |
pub use speech::{SpeechLineId, SpeechLineTableEntry, SpeechSlotTable, SPEECH_LINE_TABLE_COUNT};
|
|
|
|
| 4 |
pub mod music;
|
| 5 |
pub mod sound_id;
|
| 6 |
pub mod sound_ops;
|
| 7 |
+
pub mod sound_queue;
|
| 8 |
pub mod speech;
|
| 9 |
pub mod speech_ops;
|
| 10 |
pub mod wav_player;
|
|
|
|
| 21 |
pub use known_sound_id::KnownSoundId;
|
| 22 |
pub use music::{Music, MusicVtable, StreamingAudio};
|
| 23 |
pub use sound_id::SoundId;
|
| 24 |
+
pub use sound_queue::SoundQueueEntry;
|
| 25 |
pub use speech::{SpeechLineId, SpeechLineTableEntry, SpeechSlotTable, SPEECH_LINE_TABLE_COUNT};
|
crates/openwa-core/src/audio/sound_ops.rs
CHANGED
|
@@ -13,8 +13,9 @@
|
|
| 13 |
//! - DispatchGlobalSound (0x526270)
|
| 14 |
//! - PlaySoundPooled_Direct (0x546B50)
|
| 15 |
|
|
|
|
| 16 |
use crate::audio::{play_sound, play_sound_pooled, SoundId};
|
| 17 |
-
use crate::engine::{DDGame, DDGameWrapper
|
| 18 |
use crate::fixed::Fixed;
|
| 19 |
use crate::task::worm::CTaskWorm;
|
| 20 |
use crate::task::{CGameTask, CTask, SoundEmitter};
|
|
|
|
| 13 |
//! - DispatchGlobalSound (0x526270)
|
| 14 |
//! - PlaySoundPooled_Direct (0x546B50)
|
| 15 |
|
| 16 |
+
use crate::audio::SoundQueueEntry;
|
| 17 |
use crate::audio::{play_sound, play_sound_pooled, SoundId};
|
| 18 |
+
use crate::engine::{DDGame, DDGameWrapper};
|
| 19 |
use crate::fixed::Fixed;
|
| 20 |
use crate::task::worm::CTaskWorm;
|
| 21 |
use crate::task::{CGameTask, CTask, SoundEmitter};
|
crates/openwa-core/src/audio/sound_queue.rs
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// ============================================================
|
| 2 |
+
// Sound queue entry β 16 entries at DDGame + 0x7F00
|
| 3 |
+
// ============================================================
|
| 4 |
+
|
| 5 |
+
/// Sound queue entry (0x24 = 36 bytes, stride between consecutive entries).
|
| 6 |
+
///
|
| 7 |
+
/// DDGame maintains a 16-slot sound queue at offset 0x7F00. PlaySoundGlobal
|
| 8 |
+
/// appends entries; PlaySoundLocal additionally marks entries as local and
|
| 9 |
+
/// stores position via the task's secondary vtable.
|
| 10 |
+
#[repr(C)]
|
| 11 |
+
pub struct SoundQueueEntry {
|
| 12 |
+
/// Sound effect ID (SoundId enum value).
|
| 13 |
+
pub sound_id: u32,
|
| 14 |
+
/// Flags / priority (e.g. 3=weapon, 7=explosion).
|
| 15 |
+
pub flags: u32,
|
| 16 |
+
/// Volume (Fixed-point, 0x10000 = 1.0).
|
| 17 |
+
pub volume: u32,
|
| 18 |
+
/// Pitch (Fixed-point, 0x10000 = 1.0).
|
| 19 |
+
pub pitch: u32,
|
| 20 |
+
/// Reserved, always 0.
|
| 21 |
+
pub reserved: u32,
|
| 22 |
+
/// 0 = global, 1 = local (has position tracking).
|
| 23 |
+
pub is_local: u8,
|
| 24 |
+
pub _pad: [u8; 3],
|
| 25 |
+
/// Position X (filled by secondary vtable GetPosition for local sounds).
|
| 26 |
+
pub pos_x: u32,
|
| 27 |
+
/// Position Y (filled by secondary vtable GetPosition for local sounds).
|
| 28 |
+
pub pos_y: u32,
|
| 29 |
+
/// Pointer to task's secondary vtable (at task+0xE8) for position updates.
|
| 30 |
+
pub secondary_vtable: u32,
|
| 31 |
+
}
|
| 32 |
+
|
| 33 |
+
const _: () = assert!(core::mem::size_of::<SoundQueueEntry>() == 0x24);
|
crates/openwa-core/src/engine/coord.rs
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// ============================================================
|
| 2 |
+
// Coordinate types β DDGame sub-structs for screen and terrain coords
|
| 3 |
+
// ============================================================
|
| 4 |
+
|
| 5 |
+
/// Coordinate entry used in DDGame screen coordinate tables (stride 0x10).
|
| 6 |
+
///
|
| 7 |
+
/// InitFields zeroes x and y; at runtime they contain fixed-point screen
|
| 8 |
+
/// coordinates used for camera tracking and rendering regions.
|
| 9 |
+
#[repr(C)]
|
| 10 |
+
#[derive(Clone, Copy)]
|
| 11 |
+
pub struct CoordEntry {
|
| 12 |
+
pub x: i32,
|
| 13 |
+
pub y: i32,
|
| 14 |
+
pub _unknown: [u8; 8],
|
| 15 |
+
}
|
| 16 |
+
|
| 17 |
+
const _: () = assert!(core::mem::size_of::<CoordEntry>() == 0x10);
|
| 18 |
+
|
| 19 |
+
// ============================================================
|
| 20 |
+
// CoordList β dynamic array of packed terrain coordinates
|
| 21 |
+
// ============================================================
|
| 22 |
+
|
| 23 |
+
/// Packed terrain coordinate entry (8 bytes).
|
| 24 |
+
///
|
| 25 |
+
/// `coord` packs x and y as `x * 0x10000 + y` (fixed-point).
|
| 26 |
+
/// `flag` is always 1 for populated entries.
|
| 27 |
+
#[repr(C)]
|
| 28 |
+
#[derive(Clone, Copy)]
|
| 29 |
+
pub struct CoordListEntry {
|
| 30 |
+
pub coord: u32,
|
| 31 |
+
pub flag: u32,
|
| 32 |
+
}
|
| 33 |
+
const _: () = assert!(core::mem::size_of::<CoordListEntry>() == 8);
|
| 34 |
+
|
| 35 |
+
/// Dynamic coordinate array header (12 bytes).
|
| 36 |
+
///
|
| 37 |
+
/// Allocated at DDGame+0x50C during `init_graphics_and_resources`.
|
| 38 |
+
/// Data buffer is a separate allocation of `capacity * 8` bytes.
|
| 39 |
+
/// Used for terrain coordinate lookups (spawning, aiming, collision).
|
| 40 |
+
#[repr(C)]
|
| 41 |
+
pub struct CoordList {
|
| 42 |
+
/// Number of entries currently stored.
|
| 43 |
+
pub count: u32,
|
| 44 |
+
/// Maximum number of entries (600).
|
| 45 |
+
pub capacity: u32,
|
| 46 |
+
/// Pointer to the data buffer (`capacity * sizeof(CoordListEntry)` bytes).
|
| 47 |
+
pub data: *mut CoordListEntry,
|
| 48 |
+
}
|
| 49 |
+
const _: () = assert!(core::mem::size_of::<CoordList>() == 12);
|
crates/openwa-core/src/engine/ddgame.rs
CHANGED
|
@@ -2,19 +2,19 @@ use crate::audio::active_sound::ActiveSoundTable;
|
|
| 2 |
use crate::audio::dssound::DSSound;
|
| 3 |
use crate::audio::music::Music;
|
| 4 |
use crate::audio::speech::SpeechSlotTable;
|
|
|
|
| 5 |
use crate::bitgrid::{CollisionBitGrid, DisplayBitGrid};
|
| 6 |
use crate::display::dd_display::DDDisplay;
|
| 7 |
use crate::display::palette::Palette;
|
| 8 |
use crate::engine::game_info::GameInfo;
|
| 9 |
-
use crate::engine::{
|
| 10 |
-
CoordEntry, CoordList, RenderEntry, SoundQueueEntry, TeamArenaState, TeamIndexMap,
|
| 11 |
-
};
|
| 12 |
use crate::fixed::Fixed;
|
| 13 |
use crate::game::weapon::WeaponTable;
|
| 14 |
use crate::input::keyboard::DDKeyboard;
|
| 15 |
use crate::render::landscape::PCLandscape;
|
| 16 |
use crate::render::queue::RenderQueue;
|
| 17 |
use crate::render::turn_order::TurnOrderWidget;
|
|
|
|
| 18 |
use crate::FieldRegistry;
|
| 19 |
|
| 20 |
/// DDGame β the main game engine object.
|
|
@@ -183,7 +183,7 @@ pub struct DDGame {
|
|
| 183 |
/// 0x4628: Team arena state β per-team data, ammo, delays, alliance tracking.
|
| 184 |
/// Note: fields previously named init_field_64d8 (= team_count at arena+0x1EB0)
|
| 185 |
/// and init_field_72a4 (= weapon_slots entry at arena+0x2A7C) are inside this struct.
|
| 186 |
-
pub team_arena:
|
| 187 |
/// 0x7270-0x72A3: Unknown
|
| 188 |
pub _unknown_7270: [u8; 0x72A4 - 0x7270],
|
| 189 |
/// 0x72A4: Object pool counter. Incremented by +7 per CTaskMissile, +2 per CTaskArrow.
|
|
@@ -610,7 +610,7 @@ pub mod offsets {
|
|
| 610 |
pub const WEAPON_PANEL: usize = 0x548;
|
| 611 |
|
| 612 |
// === Team weapon state (DDGame + 0x4628) ===
|
| 613 |
-
/// Base of
|
| 614 |
/// Callers pass DDGame + TEAM_ARENA_STATE as base pointer to
|
| 615 |
/// GetAmmo/AddAmmo/SubtractAmmo.
|
| 616 |
pub const TEAM_ARENA_STATE: usize = 0x4628;
|
|
@@ -621,7 +621,7 @@ pub mod offsets {
|
|
| 621 |
/// Runtime-confirmed: block[0] is zeroed preamble, blocks[1-6] hold team data.
|
| 622 |
pub const TEAM_BLOCKS: usize = 0x4090;
|
| 623 |
|
| 624 |
-
/// Byte offset from
|
| 625 |
/// `blocks_ptr = (tws_base as *const c_char).sub(ARENA_TO_BLOCKS) as *const TeamBlock`
|
| 626 |
///
|
| 627 |
/// entry_ptr(0) = DDGame+0x4628 = TEAM_BLOCKS + 0x598.
|
|
@@ -772,7 +772,7 @@ impl crate::snapshot::Snapshot for DDGame {
|
|
| 772 |
write_indent(w, i)?;
|
| 773 |
writeln!(w, "task_land = {}", fmt_ptr(self.task_land))?;
|
| 774 |
|
| 775 |
-
//
|
| 776 |
write_indent(w, i)?;
|
| 777 |
writeln!(w, "team_arena.team_count = {}", self.team_arena.team_count)?;
|
| 778 |
write_indent(w, i)?;
|
|
|
|
| 2 |
use crate::audio::dssound::DSSound;
|
| 3 |
use crate::audio::music::Music;
|
| 4 |
use crate::audio::speech::SpeechSlotTable;
|
| 5 |
+
use crate::audio::SoundQueueEntry;
|
| 6 |
use crate::bitgrid::{CollisionBitGrid, DisplayBitGrid};
|
| 7 |
use crate::display::dd_display::DDDisplay;
|
| 8 |
use crate::display::palette::Palette;
|
| 9 |
use crate::engine::game_info::GameInfo;
|
| 10 |
+
use crate::engine::{CoordEntry, CoordList, TeamArena, TeamIndexMap};
|
|
|
|
|
|
|
| 11 |
use crate::fixed::Fixed;
|
| 12 |
use crate::game::weapon::WeaponTable;
|
| 13 |
use crate::input::keyboard::DDKeyboard;
|
| 14 |
use crate::render::landscape::PCLandscape;
|
| 15 |
use crate::render::queue::RenderQueue;
|
| 16 |
use crate::render::turn_order::TurnOrderWidget;
|
| 17 |
+
use crate::render::RenderEntry;
|
| 18 |
use crate::FieldRegistry;
|
| 19 |
|
| 20 |
/// DDGame β the main game engine object.
|
|
|
|
| 183 |
/// 0x4628: Team arena state β per-team data, ammo, delays, alliance tracking.
|
| 184 |
/// Note: fields previously named init_field_64d8 (= team_count at arena+0x1EB0)
|
| 185 |
/// and init_field_72a4 (= weapon_slots entry at arena+0x2A7C) are inside this struct.
|
| 186 |
+
pub team_arena: TeamArena,
|
| 187 |
/// 0x7270-0x72A3: Unknown
|
| 188 |
pub _unknown_7270: [u8; 0x72A4 - 0x7270],
|
| 189 |
/// 0x72A4: Object pool counter. Incremented by +7 per CTaskMissile, +2 per CTaskArrow.
|
|
|
|
| 610 |
pub const WEAPON_PANEL: usize = 0x548;
|
| 611 |
|
| 612 |
// === Team weapon state (DDGame + 0x4628) ===
|
| 613 |
+
/// Base of TeamArena sub-struct within DDGame.
|
| 614 |
/// Callers pass DDGame + TEAM_ARENA_STATE as base pointer to
|
| 615 |
/// GetAmmo/AddAmmo/SubtractAmmo.
|
| 616 |
pub const TEAM_ARENA_STATE: usize = 0x4628;
|
|
|
|
| 621 |
/// Runtime-confirmed: block[0] is zeroed preamble, blocks[1-6] hold team data.
|
| 622 |
pub const TEAM_BLOCKS: usize = 0x4090;
|
| 623 |
|
| 624 |
+
/// Byte offset from TeamArena base back to TeamBlock array start.
|
| 625 |
/// `blocks_ptr = (tws_base as *const c_char).sub(ARENA_TO_BLOCKS) as *const TeamBlock`
|
| 626 |
///
|
| 627 |
/// entry_ptr(0) = DDGame+0x4628 = TEAM_BLOCKS + 0x598.
|
|
|
|
| 772 |
write_indent(w, i)?;
|
| 773 |
writeln!(w, "task_land = {}", fmt_ptr(self.task_land))?;
|
| 774 |
|
| 775 |
+
// TeamArena summary
|
| 776 |
write_indent(w, i)?;
|
| 777 |
writeln!(w, "team_arena.team_count = {}", self.team_arena.team_count)?;
|
| 778 |
write_indent(w, i)?;
|
crates/openwa-core/src/engine/ddgame_constructor.rs
CHANGED
|
@@ -16,10 +16,10 @@ use crate::bitgrid::{BitGrid, BitGridBaseVtable, CollisionBitGrid, DisplayBitGri
|
|
| 16 |
use crate::display::dd_display::DDDisplay;
|
| 17 |
use crate::display::gradient::compute_complex_gradient;
|
| 18 |
use crate::display::palette::Palette;
|
|
|
|
| 19 |
use crate::engine::ddgame_wrapper::DDGameWrapper;
|
| 20 |
use crate::engine::game_info::GameInfo;
|
| 21 |
use crate::engine::net_bridge::NetBridge;
|
| 22 |
-
use crate::engine::team_arena::{CoordList, CoordListEntry};
|
| 23 |
use crate::input::keyboard::DDKeyboard;
|
| 24 |
use crate::rebase::rb;
|
| 25 |
use crate::render::gfx_dir::{
|
|
@@ -63,7 +63,7 @@ pub unsafe fn ddgame_init_fields(ddgame: *mut DDGame) {
|
|
| 63 |
*(base.add(off) as *mut u32) = 0;
|
| 64 |
}
|
| 65 |
|
| 66 |
-
// init_field_64d8 =
|
| 67 |
(*ddgame).team_arena.team_count = 0;
|
| 68 |
// init_field_72a4 = weapon_slots flat[754] = alliance 5, ammo[44]
|
| 69 |
(*ddgame).team_arena.weapon_slots.teams[5].ammo[44] = 0;
|
|
|
|
| 16 |
use crate::display::dd_display::DDDisplay;
|
| 17 |
use crate::display::gradient::compute_complex_gradient;
|
| 18 |
use crate::display::palette::Palette;
|
| 19 |
+
use crate::engine::coord::{CoordList, CoordListEntry};
|
| 20 |
use crate::engine::ddgame_wrapper::DDGameWrapper;
|
| 21 |
use crate::engine::game_info::GameInfo;
|
| 22 |
use crate::engine::net_bridge::NetBridge;
|
|
|
|
| 23 |
use crate::input::keyboard::DDKeyboard;
|
| 24 |
use crate::rebase::rb;
|
| 25 |
use crate::render::gfx_dir::{
|
|
|
|
| 63 |
*(base.add(off) as *mut u32) = 0;
|
| 64 |
}
|
| 65 |
|
| 66 |
+
// init_field_64d8 = TeamArena.team_count (arena+0x1EB0)
|
| 67 |
(*ddgame).team_arena.team_count = 0;
|
| 68 |
// init_field_72a4 = weapon_slots flat[754] = alliance 5, ammo[44]
|
| 69 |
(*ddgame).team_arena.weapon_slots.teams[5].ammo[44] = 0;
|
crates/openwa-core/src/engine/mod.rs
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
|
|
| 1 |
pub mod ddgame;
|
| 2 |
pub mod ddgame_constructor;
|
| 3 |
pub mod ddgame_load_fonts;
|
|
@@ -12,6 +13,7 @@ pub mod replay;
|
|
| 12 |
pub mod team_arena;
|
| 13 |
pub mod team_ops;
|
| 14 |
|
|
|
|
| 15 |
pub use ddgame::DDGame;
|
| 16 |
pub use ddgame_constructor::{
|
| 17 |
create_ddgame, ddgame_init_fields, ddgame_init_render_indices, display_layer_color_init,
|
|
@@ -23,7 +25,6 @@ pub use game_session::GameSession;
|
|
| 23 |
pub use game_timer::GameTimer;
|
| 24 |
pub use net_wrapper::DDNetGameWrapper;
|
| 25 |
pub use team_arena::{
|
| 26 |
-
|
| 27 |
-
TeamArenaState, TeamBlock, TeamHeader, TeamIndexMap, TeamSlot0, TeamWeaponSlots, WeaponSlots,
|
| 28 |
WormEntry, GAME_PHASE_NORMAL_MIN, GAME_PHASE_SUDDEN_DEATH,
|
| 29 |
};
|
|
|
|
| 1 |
+
pub mod coord;
|
| 2 |
pub mod ddgame;
|
| 3 |
pub mod ddgame_constructor;
|
| 4 |
pub mod ddgame_load_fonts;
|
|
|
|
| 13 |
pub mod team_arena;
|
| 14 |
pub mod team_ops;
|
| 15 |
|
| 16 |
+
pub use coord::{CoordEntry, CoordList, CoordListEntry};
|
| 17 |
pub use ddgame::DDGame;
|
| 18 |
pub use ddgame_constructor::{
|
| 19 |
create_ddgame, ddgame_init_fields, ddgame_init_render_indices, display_layer_color_init,
|
|
|
|
| 25 |
pub use game_timer::GameTimer;
|
| 26 |
pub use net_wrapper::DDNetGameWrapper;
|
| 27 |
pub use team_arena::{
|
| 28 |
+
TeamArena, TeamBlock, TeamHeader, TeamIndexMap, TeamSlot0, TeamWeaponSlots, WeaponSlots,
|
|
|
|
| 29 |
WormEntry, GAME_PHASE_NORMAL_MIN, GAME_PHASE_SUDDEN_DEATH,
|
| 30 |
};
|
crates/openwa-core/src/engine/team_arena.rs
CHANGED
|
@@ -2,26 +2,14 @@
|
|
| 2 |
// Team arena state β sub-struct at DDGame + 0x4628
|
| 3 |
// ============================================================
|
| 4 |
//
|
| 5 |
-
// Extracted from ddgame.rs: team/worm data structures,
|
| 6 |
// and related helper structs used as DDGame fields.
|
| 7 |
|
| 8 |
use core::ffi::CStr;
|
| 9 |
|
| 10 |
-
use
|
| 11 |
|
| 12 |
-
|
| 13 |
-
///
|
| 14 |
-
/// InitFields zeroes x and y; at runtime they contain fixed-point screen
|
| 15 |
-
/// coordinates used for camera tracking and rendering regions.
|
| 16 |
-
#[repr(C)]
|
| 17 |
-
#[derive(Clone, Copy)]
|
| 18 |
-
pub struct CoordEntry {
|
| 19 |
-
pub x: i32,
|
| 20 |
-
pub y: i32,
|
| 21 |
-
pub _unknown: [u8; 8],
|
| 22 |
-
}
|
| 23 |
-
|
| 24 |
-
const _: () = assert!(core::mem::size_of::<CoordEntry>() == 0x10);
|
| 25 |
|
| 26 |
/// Team index permutation map (0x64 = 100 bytes).
|
| 27 |
///
|
|
@@ -42,86 +30,6 @@ pub struct TeamIndexMap {
|
|
| 42 |
}
|
| 43 |
const _: () = assert!(core::mem::size_of::<TeamIndexMap>() == 0x64);
|
| 44 |
|
| 45 |
-
/// Render table entry (0x14 = 20 bytes).
|
| 46 |
-
///
|
| 47 |
-
/// 14 entries live at DDGame+0x73B0 (stride 0x14). Only the first u32
|
| 48 |
-
/// is zeroed during construction; the rest is uninitialized/unknown.
|
| 49 |
-
#[repr(C)]
|
| 50 |
-
#[derive(Clone, Copy)]
|
| 51 |
-
pub struct RenderEntry {
|
| 52 |
-
/// Active/state flag (zeroed on init).
|
| 53 |
-
pub active: u32,
|
| 54 |
-
/// Unknown data.
|
| 55 |
-
pub _unknown: [u8; 16],
|
| 56 |
-
}
|
| 57 |
-
const _: () = assert!(core::mem::size_of::<RenderEntry>() == 0x14);
|
| 58 |
-
|
| 59 |
-
// ============================================================
|
| 60 |
-
// CoordList β dynamic array of packed terrain coordinates
|
| 61 |
-
// ============================================================
|
| 62 |
-
|
| 63 |
-
/// Packed terrain coordinate entry (8 bytes).
|
| 64 |
-
///
|
| 65 |
-
/// `coord` packs x and y as `x * 0x10000 + y` (fixed-point).
|
| 66 |
-
/// `flag` is always 1 for populated entries.
|
| 67 |
-
#[repr(C)]
|
| 68 |
-
#[derive(Clone, Copy)]
|
| 69 |
-
pub struct CoordListEntry {
|
| 70 |
-
pub coord: u32,
|
| 71 |
-
pub flag: u32,
|
| 72 |
-
}
|
| 73 |
-
const _: () = assert!(core::mem::size_of::<CoordListEntry>() == 8);
|
| 74 |
-
|
| 75 |
-
/// Dynamic coordinate array header (12 bytes).
|
| 76 |
-
///
|
| 77 |
-
/// Allocated at DDGame+0x50C during `init_graphics_and_resources`.
|
| 78 |
-
/// Data buffer is a separate allocation of `capacity * 8` bytes.
|
| 79 |
-
/// Used for terrain coordinate lookups (spawning, aiming, collision).
|
| 80 |
-
#[repr(C)]
|
| 81 |
-
pub struct CoordList {
|
| 82 |
-
/// Number of entries currently stored.
|
| 83 |
-
pub count: u32,
|
| 84 |
-
/// Maximum number of entries (600).
|
| 85 |
-
pub capacity: u32,
|
| 86 |
-
/// Pointer to the data buffer (`capacity * sizeof(CoordListEntry)` bytes).
|
| 87 |
-
pub data: *mut CoordListEntry,
|
| 88 |
-
}
|
| 89 |
-
const _: () = assert!(core::mem::size_of::<CoordList>() == 12);
|
| 90 |
-
|
| 91 |
-
// ============================================================
|
| 92 |
-
// Sound queue entry β 16 entries at DDGame + 0x7F00
|
| 93 |
-
// ============================================================
|
| 94 |
-
|
| 95 |
-
/// Sound queue entry (0x24 = 36 bytes, stride between consecutive entries).
|
| 96 |
-
///
|
| 97 |
-
/// DDGame maintains a 16-slot sound queue at offset 0x7F00. PlaySoundGlobal
|
| 98 |
-
/// appends entries; PlaySoundLocal additionally marks entries as local and
|
| 99 |
-
/// stores position via the task's secondary vtable.
|
| 100 |
-
#[repr(C)]
|
| 101 |
-
pub struct SoundQueueEntry {
|
| 102 |
-
/// Sound effect ID (SoundId enum value).
|
| 103 |
-
pub sound_id: u32,
|
| 104 |
-
/// Flags / priority (e.g. 3=weapon, 7=explosion).
|
| 105 |
-
pub flags: u32,
|
| 106 |
-
/// Volume (Fixed-point, 0x10000 = 1.0).
|
| 107 |
-
pub volume: u32,
|
| 108 |
-
/// Pitch (Fixed-point, 0x10000 = 1.0).
|
| 109 |
-
pub pitch: u32,
|
| 110 |
-
/// Reserved, always 0.
|
| 111 |
-
pub reserved: u32,
|
| 112 |
-
/// 0 = global, 1 = local (has position tracking).
|
| 113 |
-
pub is_local: u8,
|
| 114 |
-
pub _pad: [u8; 3],
|
| 115 |
-
/// Position X (filled by secondary vtable GetPosition for local sounds).
|
| 116 |
-
pub pos_x: u32,
|
| 117 |
-
/// Position Y (filled by secondary vtable GetPosition for local sounds).
|
| 118 |
-
pub pos_y: u32,
|
| 119 |
-
/// Pointer to task's secondary vtable (at task+0xE8) for position updates.
|
| 120 |
-
pub secondary_vtable: u32,
|
| 121 |
-
}
|
| 122 |
-
|
| 123 |
-
const _: () = assert!(core::mem::size_of::<SoundQueueEntry>() == 0x24);
|
| 124 |
-
|
| 125 |
// ============================================================
|
| 126 |
// Per-worm and per-team block structs
|
| 127 |
// ============================================================
|
|
@@ -131,7 +39,7 @@ const _: () = assert!(core::mem::size_of::<SoundQueueEntry>() == 0x24);
|
|
| 131 |
/// WA supports up to 8 playable worms per team. The original code accesses
|
| 132 |
/// worms via raw pointer arithmetic from the team entry pointer, using
|
| 133 |
/// stride 0x9C. This means the 8th worm crosses the TeamBlock boundary
|
| 134 |
-
/// into the next block's header slot β see `
|
| 135 |
///
|
| 136 |
/// Field offsets confirmed by runtime memory dump (validator DLL):
|
| 137 |
/// - state at 0x00: 0x67 = active/selected, 0x65 = idle, 0x80+ = special
|
|
@@ -179,7 +87,7 @@ const _: () = assert!(core::mem::size_of::<WormEntry>() == 0x9C);
|
|
| 179 |
/// offsets (0x00-0x5F) may contain data from the previous team's 8th worm
|
| 180 |
/// when that team has 8 worms β they are treated as opaque padding.
|
| 181 |
///
|
| 182 |
-
/// Accessed via `
|
| 183 |
#[repr(C)]
|
| 184 |
#[derive(Clone, Copy)]
|
| 185 |
pub struct TeamHeader {
|
|
@@ -235,10 +143,10 @@ pub union TeamSlot0 {
|
|
| 235 |
/// The remaining 7 worm slots follow, then 0x3C bytes of metadata.
|
| 236 |
///
|
| 237 |
/// **Block indexing**: Block 0 is unused (preamble, all zeros). Actual team
|
| 238 |
-
/// data starts at block 1. Worms are accessed via `
|
| 239 |
/// which uses raw pointer arithmetic and naturally crosses block boundaries.
|
| 240 |
///
|
| 241 |
-
/// **Header access**: Use `
|
| 242 |
/// `TeamHeader` for a team (reads from `blocks[idx+1].header.team`).
|
| 243 |
#[repr(C)]
|
| 244 |
pub struct TeamBlock {
|
|
@@ -286,13 +194,13 @@ const _: () = assert!(core::mem::size_of::<WeaponSlots>() == 852 * 4);
|
|
| 286 |
/// `weapon_slots.teams[alliance].ammo[weapon]` and
|
| 287 |
/// `weapon_slots.teams[alliance].delay[weapon]`.
|
| 288 |
#[repr(C)]
|
| 289 |
-
pub struct
|
| 290 |
/// 0x0000-0x1EAF: Per-team data region (opaque).
|
| 291 |
///
|
| 292 |
/// This region contains 7 team entries at stride 0x51C (1-indexed, index 0
|
| 293 |
/// is preamble). The original WA.exe accesses team data via raw pointer
|
| 294 |
/// arithmetic relative to the arena base. In our Rust code, team data is
|
| 295 |
-
/// accessed through `
|
| 296 |
/// so this region is treated as opaque padding.
|
| 297 |
///
|
| 298 |
/// The 7th entry (team 6, 1-indexed) only has 8 bytes before team_count;
|
|
@@ -333,7 +241,7 @@ pub struct TeamArenaState {
|
|
| 333 |
pub last_active_alliance: i32,
|
| 334 |
}
|
| 335 |
|
| 336 |
-
const _: () = assert!(core::mem::size_of::<
|
| 337 |
|
| 338 |
/// Worm state constants and helpers.
|
| 339 |
/// TODO: Replace this module with the WormState enum
|
|
@@ -361,11 +269,11 @@ pub mod worm {
|
|
| 361 |
}
|
| 362 |
}
|
| 363 |
|
| 364 |
-
/// Game phase thresholds (stored in
|
| 365 |
pub const GAME_PHASE_SUDDEN_DEATH: i32 = 0x1E4; // 484
|
| 366 |
pub const GAME_PHASE_NORMAL_MIN: i32 = -2;
|
| 367 |
|
| 368 |
-
impl
|
| 369 |
/// Get ammo count for a weapon by alliance and weapon ID.
|
| 370 |
pub fn get_ammo(&self, alliance: usize, weapon_id: usize) -> i32 {
|
| 371 |
self.weapon_slots.teams[alliance].ammo[weapon_id]
|
|
@@ -380,82 +288,44 @@ impl TeamArenaState {
|
|
| 380 |
pub fn get_delay(&self, alliance: usize, weapon_id: usize) -> i32 {
|
| 381 |
self.weapon_slots.teams[alliance].delay[weapon_id]
|
| 382 |
}
|
| 383 |
-
}
|
| 384 |
|
| 385 |
-
//
|
| 386 |
-
///
|
| 387 |
-
/// Wraps the raw `base: u32` from trampoline register captures and provides
|
| 388 |
-
/// accessor methods that encapsulate the backward pointer arithmetic to reach
|
| 389 |
-
/// TeamBlock worm data. The TeamBlock array lives 0x598 bytes before
|
| 390 |
-
/// the TeamArenaState in DDGame memory.
|
| 391 |
-
///
|
| 392 |
-
/// # Safety
|
| 393 |
-
/// Must only be constructed from a valid DDGame + TEAM_ARENA_STATE pointer.
|
| 394 |
-
/// `repr(transparent)` ensures identical ABI to `*const u8` / `u32` on i686,
|
| 395 |
-
/// so it can be received directly from usercall trampoline register captures.
|
| 396 |
-
#[repr(transparent)]
|
| 397 |
-
#[derive(Clone, Copy)]
|
| 398 |
-
pub struct TeamArenaRef {
|
| 399 |
-
base: *mut u8,
|
| 400 |
-
}
|
| 401 |
|
| 402 |
-
|
| 403 |
-
/// Wrap a raw integer pointer (for usercall trampoline register captures).
|
| 404 |
///
|
| 405 |
-
///
|
| 406 |
-
/// `base` must point to a valid TeamArenaState (DDGame + 0x4628).
|
| 407 |
#[inline]
|
| 408 |
-
pub unsafe fn
|
| 409 |
-
|
| 410 |
-
base: base as *mut u8,
|
| 411 |
-
}
|
| 412 |
}
|
| 413 |
|
| 414 |
-
///
|
| 415 |
///
|
| 416 |
-
///
|
| 417 |
-
/// `arena` must point to a valid TeamArenaState within a live DDGame.
|
| 418 |
-
#[inline]
|
| 419 |
-
pub unsafe fn from_ptr(arena: *mut TeamArenaState) -> Self {
|
| 420 |
-
Self {
|
| 421 |
-
base: arena as *mut u8,
|
| 422 |
-
}
|
| 423 |
-
}
|
| 424 |
-
|
| 425 |
-
/// Access the TeamArenaState fields (read-only).
|
| 426 |
#[inline]
|
| 427 |
-
pub unsafe fn
|
| 428 |
-
|
| 429 |
}
|
| 430 |
|
| 431 |
-
///
|
| 432 |
-
#[inline]
|
| 433 |
-
pub unsafe fn state_mut(&self) -> &mut TeamArenaState {
|
| 434 |
-
&mut *(self.base as *mut TeamArenaState)
|
| 435 |
-
}
|
| 436 |
-
|
| 437 |
-
/// Get pointer to the TeamBlock array base.
|
| 438 |
-
#[inline]
|
| 439 |
-
pub unsafe fn blocks(&self) -> *mut TeamBlock {
|
| 440 |
-
self.base.sub(offsets::ARENA_TO_BLOCKS) as *mut TeamBlock
|
| 441 |
-
}
|
| 442 |
-
|
| 443 |
-
/// Get the team header (metadata) for a team.
|
| 444 |
///
|
| 445 |
-
/// Returns `
|
| 446 |
/// worm_count, eliminated flag, weapon_alliance, team_name.
|
| 447 |
#[inline]
|
| 448 |
-
pub unsafe fn
|
| 449 |
-
&(*
|
| 450 |
}
|
| 451 |
|
| 452 |
-
/// Get
|
|
|
|
|
|
|
|
|
|
| 453 |
#[inline]
|
| 454 |
-
pub unsafe fn
|
| 455 |
-
&
|
| 456 |
}
|
| 457 |
|
| 458 |
-
/// Get a playable worm entry by 1-indexed worm number (1..=8).
|
| 459 |
///
|
| 460 |
/// Uses raw pointer arithmetic matching the original WA code:
|
| 461 |
/// `base + team_idx * 0x51C + worm_num * 0x9C - 0x598`.
|
|
@@ -464,56 +334,60 @@ impl TeamArenaRef {
|
|
| 464 |
/// next block's header slot. The header metadata lives at high offsets
|
| 465 |
/// (0x6C+) that don't conflict with worm data (0x00-0x5F).
|
| 466 |
#[inline]
|
| 467 |
-
pub unsafe fn
|
| 468 |
-
|
| 469 |
-
|
| 470 |
-
|
| 471 |
-
|
| 472 |
-
|
| 473 |
-
|
| 474 |
}
|
| 475 |
|
| 476 |
-
|
| 477 |
-
pub unsafe fn
|
| 478 |
-
|
| 479 |
-
|
| 480 |
-
|
| 481 |
-
|
| 482 |
-
|
| 483 |
-
|
| 484 |
}
|
| 485 |
|
| 486 |
-
/// Get a team's block and its header in one call.
|
| 487 |
///
|
| 488 |
-
/// Returns `(
|
| 489 |
/// The block contains worm data, and the header (slot 0 of the next block)
|
| 490 |
/// holds team metadata (worm_count, eliminated flag).
|
| 491 |
///
|
| 492 |
/// **Note**: For accessing worms, prefer `team_worm()` which handles
|
| 493 |
/// 8-worm teams correctly via cross-boundary pointer arithmetic.
|
| 494 |
#[inline]
|
| 495 |
-
pub unsafe fn team_and_header(
|
| 496 |
-
|
| 497 |
-
|
| 498 |
-
|
|
|
|
|
|
|
|
|
|
| 499 |
(block, header)
|
| 500 |
}
|
| 501 |
|
| 502 |
-
/// Get team header for Pattern B access (alliance/active_worm at +0x70/+0x74).
|
| 503 |
///
|
| 504 |
/// Pattern B indexes from block[i+2] for 0-indexed team `i`:
|
| 505 |
/// `base + 0x510 + i*0x51C` = `blocks[i+2].header.team + 0x70`.
|
| 506 |
#[inline]
|
| 507 |
-
pub unsafe fn
|
| 508 |
-
&(*
|
| 509 |
}
|
| 510 |
|
| 511 |
-
/// Get
|
|
|
|
|
|
|
|
|
|
| 512 |
#[inline]
|
| 513 |
-
pub unsafe fn
|
| 514 |
-
&
|
| 515 |
-
.header
|
| 516 |
-
.team
|
| 517 |
}
|
| 518 |
|
| 519 |
/// Compute the flat index for ammo/delay table access.
|
|
@@ -521,21 +395,21 @@ impl TeamArenaRef {
|
|
| 521 |
/// Reads the weapon alliance ID from the team header for the given
|
| 522 |
/// 1-indexed team, then computes `alliance_id * 142 + weapon_id`.
|
| 523 |
///
|
| 524 |
-
///
|
| 525 |
-
/// then 71 delay slots (stride 142 per alliance).
|
| 526 |
-
/// Ammo: `weapon_slots[alliance_id * 142 + weapon_id]`
|
| 527 |
-
/// Delay: `weapon_slots[alliance_id * 142 + 71 + weapon_id]`
|
| 528 |
-
/// Returns (alliance_id, weapon_id) for accessing weapon slots.
|
| 529 |
#[inline]
|
| 530 |
-
pub unsafe fn weapon_slot_key(
|
| 531 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 532 |
(alliance_id, weapon_id as usize)
|
| 533 |
}
|
| 534 |
}
|
| 535 |
|
| 536 |
// ββ Snapshot impl ββββββββββββββββββββββββββββββββββββββββββ
|
| 537 |
|
| 538 |
-
impl
|
| 539 |
unsafe fn write_snapshot(
|
| 540 |
&self,
|
| 541 |
w: &mut dyn core::fmt::Write,
|
|
|
|
| 2 |
// Team arena state β sub-struct at DDGame + 0x4628
|
| 3 |
// ============================================================
|
| 4 |
//
|
| 5 |
+
// Extracted from ddgame.rs: team/worm data structures, TeamArena,
|
| 6 |
// and related helper structs used as DDGame fields.
|
| 7 |
|
| 8 |
use core::ffi::CStr;
|
| 9 |
|
| 10 |
+
use crate::snapshot::Snapshot;
|
| 11 |
|
| 12 |
+
use super::ddgame::offsets;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 13 |
|
| 14 |
/// Team index permutation map (0x64 = 100 bytes).
|
| 15 |
///
|
|
|
|
| 30 |
}
|
| 31 |
const _: () = assert!(core::mem::size_of::<TeamIndexMap>() == 0x64);
|
| 32 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
// ============================================================
|
| 34 |
// Per-worm and per-team block structs
|
| 35 |
// ============================================================
|
|
|
|
| 39 |
/// WA supports up to 8 playable worms per team. The original code accesses
|
| 40 |
/// worms via raw pointer arithmetic from the team entry pointer, using
|
| 41 |
/// stride 0x9C. This means the 8th worm crosses the TeamBlock boundary
|
| 42 |
+
/// into the next block's header slot β see `TeamArena::team_worm()`.
|
| 43 |
///
|
| 44 |
/// Field offsets confirmed by runtime memory dump (validator DLL):
|
| 45 |
/// - state at 0x00: 0x67 = active/selected, 0x65 = idle, 0x80+ = special
|
|
|
|
| 87 |
/// offsets (0x00-0x5F) may contain data from the previous team's 8th worm
|
| 88 |
/// when that team has 8 worms β they are treated as opaque padding.
|
| 89 |
///
|
| 90 |
+
/// Accessed via `TeamArena::team_header()` and `team_header_b()`.
|
| 91 |
#[repr(C)]
|
| 92 |
#[derive(Clone, Copy)]
|
| 93 |
pub struct TeamHeader {
|
|
|
|
| 143 |
/// The remaining 7 worm slots follow, then 0x3C bytes of metadata.
|
| 144 |
///
|
| 145 |
/// **Block indexing**: Block 0 is unused (preamble, all zeros). Actual team
|
| 146 |
+
/// data starts at block 1. Worms are accessed via `TeamArena::team_worm()`
|
| 147 |
/// which uses raw pointer arithmetic and naturally crosses block boundaries.
|
| 148 |
///
|
| 149 |
+
/// **Header access**: Use `TeamArena::team_header(idx)` to get the
|
| 150 |
/// `TeamHeader` for a team (reads from `blocks[idx+1].header.team`).
|
| 151 |
#[repr(C)]
|
| 152 |
pub struct TeamBlock {
|
|
|
|
| 194 |
/// `weapon_slots.teams[alliance].ammo[weapon]` and
|
| 195 |
/// `weapon_slots.teams[alliance].delay[weapon]`.
|
| 196 |
#[repr(C)]
|
| 197 |
+
pub struct TeamArena {
|
| 198 |
/// 0x0000-0x1EAF: Per-team data region (opaque).
|
| 199 |
///
|
| 200 |
/// This region contains 7 team entries at stride 0x51C (1-indexed, index 0
|
| 201 |
/// is preamble). The original WA.exe accesses team data via raw pointer
|
| 202 |
/// arithmetic relative to the arena base. In our Rust code, team data is
|
| 203 |
+
/// accessed through `TeamArena`, `TeamBlock`, and `TeamHeader`,
|
| 204 |
/// so this region is treated as opaque padding.
|
| 205 |
///
|
| 206 |
/// The 7th entry (team 6, 1-indexed) only has 8 bytes before team_count;
|
|
|
|
| 241 |
pub last_active_alliance: i32,
|
| 242 |
}
|
| 243 |
|
| 244 |
+
const _: () = assert!(core::mem::size_of::<TeamArena>() == 0x2C48);
|
| 245 |
|
| 246 |
/// Worm state constants and helpers.
|
| 247 |
/// TODO: Replace this module with the WormState enum
|
|
|
|
| 269 |
}
|
| 270 |
}
|
| 271 |
|
| 272 |
+
/// Game phase thresholds (stored in TeamArena::game_phase).
|
| 273 |
pub const GAME_PHASE_SUDDEN_DEATH: i32 = 0x1E4; // 484
|
| 274 |
pub const GAME_PHASE_NORMAL_MIN: i32 = -2;
|
| 275 |
|
| 276 |
+
impl TeamArena {
|
| 277 |
/// Get ammo count for a weapon by alliance and weapon ID.
|
| 278 |
pub fn get_ammo(&self, alliance: usize, weapon_id: usize) -> i32 {
|
| 279 |
self.weapon_slots.teams[alliance].ammo[weapon_id]
|
|
|
|
| 288 |
pub fn get_delay(&self, alliance: usize, weapon_id: usize) -> i32 {
|
| 289 |
self.weapon_slots.teams[alliance].delay[weapon_id]
|
| 290 |
}
|
|
|
|
| 291 |
|
| 292 |
+
// ββ Raw-pointer accessors (safe from noalias miscompilation) ββ
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 293 |
|
| 294 |
+
/// Get pointer to the TeamBlock array base.
|
|
|
|
| 295 |
///
|
| 296 |
+
/// The TeamBlock array lives 0x598 bytes before TeamArena in DDGame memory.
|
|
|
|
| 297 |
#[inline]
|
| 298 |
+
pub unsafe fn blocks_mut(this: *mut Self) -> *mut TeamBlock {
|
| 299 |
+
(this as *mut u8).sub(offsets::ARENA_TO_BLOCKS) as *mut TeamBlock
|
|
|
|
|
|
|
| 300 |
}
|
| 301 |
|
| 302 |
+
/// Get pointer to the TeamBlock array base (read-only).
|
| 303 |
///
|
| 304 |
+
/// The TeamBlock array lives 0x598 bytes before TeamArena in DDGame memory.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 305 |
#[inline]
|
| 306 |
+
pub unsafe fn blocks(this: *const Self) -> *const TeamBlock {
|
| 307 |
+
(this as *const u8).sub(offsets::ARENA_TO_BLOCKS) as *const TeamBlock
|
| 308 |
}
|
| 309 |
|
| 310 |
+
/// Get pointer to team header (metadata) for a team.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 311 |
///
|
| 312 |
+
/// Returns `blocks[team_idx+1].header.team`, which holds team metadata:
|
| 313 |
/// worm_count, eliminated flag, weapon_alliance, team_name.
|
| 314 |
#[inline]
|
| 315 |
+
pub unsafe fn team_header_mut(this: *mut Self, team_idx: usize) -> *mut TeamHeader {
|
| 316 |
+
&raw mut (*Self::blocks_mut(this).add(team_idx + 1)).header.team as *mut TeamHeader
|
| 317 |
}
|
| 318 |
|
| 319 |
+
/// Get pointer to team header (metadata) for a team.
|
| 320 |
+
///
|
| 321 |
+
/// Returns `blocks[team_idx+1].header.team`, which holds team metadata:
|
| 322 |
+
/// worm_count, eliminated flag, weapon_alliance, team_name.
|
| 323 |
#[inline]
|
| 324 |
+
pub unsafe fn team_header(this: *const Self, team_idx: usize) -> *const TeamHeader {
|
| 325 |
+
&raw const (*Self::blocks(this).add(team_idx + 1)).header.team as *const TeamHeader
|
| 326 |
}
|
| 327 |
|
| 328 |
+
/// Get pointer to a playable worm entry by 1-indexed worm number (1..=8).
|
| 329 |
///
|
| 330 |
/// Uses raw pointer arithmetic matching the original WA code:
|
| 331 |
/// `base + team_idx * 0x51C + worm_num * 0x9C - 0x598`.
|
|
|
|
| 334 |
/// next block's header slot. The header metadata lives at high offsets
|
| 335 |
/// (0x6C+) that don't conflict with worm data (0x00-0x5F).
|
| 336 |
#[inline]
|
| 337 |
+
pub unsafe fn team_worm_mut(
|
| 338 |
+
this: *mut Self,
|
| 339 |
+
team_idx: usize,
|
| 340 |
+
worm_num: usize,
|
| 341 |
+
) -> *mut WormEntry {
|
| 342 |
+
let base = this as *mut u8;
|
| 343 |
+
base.add(team_idx * 0x51C).add(worm_num * 0x9C).sub(0x598) as *mut WormEntry
|
| 344 |
}
|
| 345 |
|
| 346 |
+
#[inline]
|
| 347 |
+
pub unsafe fn team_worm(
|
| 348 |
+
this: *const Self,
|
| 349 |
+
team_idx: usize,
|
| 350 |
+
worm_num: usize,
|
| 351 |
+
) -> *const WormEntry {
|
| 352 |
+
let base = this as *const u8;
|
| 353 |
+
base.add(team_idx * 0x51C).add(worm_num * 0x9C).sub(0x598) as *const WormEntry
|
| 354 |
}
|
| 355 |
|
| 356 |
+
/// Get a team's block pointer and its header pointer in one call.
|
| 357 |
///
|
| 358 |
+
/// Returns `(blocks[team_idx], blocks[team_idx+1].header.team)`.
|
| 359 |
/// The block contains worm data, and the header (slot 0 of the next block)
|
| 360 |
/// holds team metadata (worm_count, eliminated flag).
|
| 361 |
///
|
| 362 |
/// **Note**: For accessing worms, prefer `team_worm()` which handles
|
| 363 |
/// 8-worm teams correctly via cross-boundary pointer arithmetic.
|
| 364 |
#[inline]
|
| 365 |
+
pub unsafe fn team_and_header(
|
| 366 |
+
this: *mut Self,
|
| 367 |
+
team_idx: usize,
|
| 368 |
+
) -> (*mut TeamBlock, *mut TeamHeader) {
|
| 369 |
+
let blocks = Self::blocks_mut(this);
|
| 370 |
+
let block = blocks.add(team_idx);
|
| 371 |
+
let header = &raw mut (*blocks.add(team_idx + 1)).header.team as *mut TeamHeader;
|
| 372 |
(block, header)
|
| 373 |
}
|
| 374 |
|
| 375 |
+
/// Get team header pointer for Pattern B access (alliance/active_worm at +0x70/+0x74).
|
| 376 |
///
|
| 377 |
/// Pattern B indexes from block[i+2] for 0-indexed team `i`:
|
| 378 |
/// `base + 0x510 + i*0x51C` = `blocks[i+2].header.team + 0x70`.
|
| 379 |
#[inline]
|
| 380 |
+
pub unsafe fn team_header_b_mut(this: *mut Self, team_idx: usize) -> *mut TeamHeader {
|
| 381 |
+
&raw mut (*Self::blocks_mut(this).add(team_idx + 2)).header.team as *mut TeamHeader
|
| 382 |
}
|
| 383 |
|
| 384 |
+
/// Get team header pointer for Pattern B access (alliance/active_worm at +0x70/+0x74).
|
| 385 |
+
///
|
| 386 |
+
/// Pattern B indexes from block[i+2] for 0-indexed team `i`:
|
| 387 |
+
/// `base + 0x510 + i*0x51C` = `blocks[i+2].header.team + 0x70`.
|
| 388 |
#[inline]
|
| 389 |
+
pub unsafe fn team_header_b(this: *const Self, team_idx: usize) -> *const TeamHeader {
|
| 390 |
+
&raw const (*Self::blocks(this).add(team_idx + 2)).header.team as *const TeamHeader
|
|
|
|
|
|
|
| 391 |
}
|
| 392 |
|
| 393 |
/// Compute the flat index for ammo/delay table access.
|
|
|
|
| 395 |
/// Reads the weapon alliance ID from the team header for the given
|
| 396 |
/// 1-indexed team, then computes `alliance_id * 142 + weapon_id`.
|
| 397 |
///
|
| 398 |
+
/// Returns (alliance_id, weapon_id) for accessing weapon_slots.
|
|
|
|
|
|
|
|
|
|
|
|
|
| 399 |
#[inline]
|
| 400 |
+
pub unsafe fn weapon_slot_key(
|
| 401 |
+
this: *const Self,
|
| 402 |
+
team_index: usize,
|
| 403 |
+
weapon_id: u32,
|
| 404 |
+
) -> (usize, usize) {
|
| 405 |
+
let alliance_id = (*Self::team_header(this, team_index)).weapon_alliance as usize;
|
| 406 |
(alliance_id, weapon_id as usize)
|
| 407 |
}
|
| 408 |
}
|
| 409 |
|
| 410 |
// ββ Snapshot impl ββββββββββββββββββββββββββββββββββββββββββ
|
| 411 |
|
| 412 |
+
impl Snapshot for WormEntry {
|
| 413 |
unsafe fn write_snapshot(
|
| 414 |
&self,
|
| 415 |
w: &mut dyn core::fmt::Write,
|
crates/openwa-core/src/engine/team_ops.rs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
| 1 |
//! Team and worm state accessor functions.
|
| 2 |
//!
|
| 3 |
-
//! Pure Rust reimplementations of WA.exe functions that access the
|
| 4 |
//! area (DDGame + 0x4628). Called from hook trampolines in openwa-dll.
|
| 5 |
//!
|
| 6 |
//! Original WA functions:
|
|
@@ -15,29 +15,28 @@
|
|
| 15 |
|
| 16 |
use crate::engine::team_arena::worm;
|
| 17 |
|
| 18 |
-
use super::team_arena::
|
| 19 |
|
| 20 |
/// Count teams by alliance membership β port of 0x522030.
|
| 21 |
///
|
| 22 |
/// Updates `state.active_team_count`, `same_alliance_count`, `enemy_team_count`.
|
| 23 |
/// Uses Pattern B header fields (+0x70/+0x74) for alliance and active_worm.
|
| 24 |
-
pub unsafe fn count_teams_by_alliance(arena:
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
let
|
| 33 |
-
let
|
| 34 |
-
let active_worm = header.active_worm;
|
| 35 |
if active_worm != 0 && team_alliance >= 0 {
|
| 36 |
-
|
| 37 |
if team_alliance == alliance_id {
|
| 38 |
-
|
| 39 |
} else {
|
| 40 |
-
|
| 41 |
}
|
| 42 |
}
|
| 43 |
}
|
|
@@ -46,17 +45,17 @@ pub unsafe fn count_teams_by_alliance(arena: TeamArenaRef, alliance_id: i32) {
|
|
| 46 |
/// Sum worm health for a team β port of 0x5224D0.
|
| 47 |
///
|
| 48 |
/// Returns 0 if the team is eliminated.
|
| 49 |
-
pub unsafe fn get_team_total_health(team_index: u32, arena:
|
| 50 |
-
let header =
|
| 51 |
|
| 52 |
-
if header.eliminated != 0 {
|
| 53 |
return 0;
|
| 54 |
}
|
| 55 |
|
| 56 |
-
let worm_count = header.worm_count;
|
| 57 |
let mut total = 0i32;
|
| 58 |
for w in 1..=worm_count as usize {
|
| 59 |
-
total +=
|
| 60 |
}
|
| 61 |
total
|
| 62 |
}
|
|
@@ -67,12 +66,10 @@ pub unsafe fn get_team_total_health(team_index: u32, arena: TeamArenaRef) -> i32
|
|
| 67 |
pub unsafe fn is_worm_in_special_state(
|
| 68 |
team_index: u32,
|
| 69 |
worm_index: u32,
|
| 70 |
-
arena:
|
| 71 |
) -> u32 {
|
| 72 |
if worm::is_special_state(
|
| 73 |
-
arena
|
| 74 |
-
.team_worm(team_index as usize, worm_index as usize)
|
| 75 |
-
.state,
|
| 76 |
) {
|
| 77 |
1
|
| 78 |
} else {
|
|
@@ -87,29 +84,27 @@ pub unsafe fn is_worm_in_special_state(
|
|
| 87 |
pub unsafe fn get_worm_position(
|
| 88 |
team_index: u32,
|
| 89 |
worm_index: u32,
|
| 90 |
-
arena:
|
| 91 |
out_x: *mut i32,
|
| 92 |
out_y: *mut i32,
|
| 93 |
) {
|
| 94 |
-
let worm =
|
| 95 |
-
*out_x = *(worm._unknown_90.as_ptr() as *const i32);
|
| 96 |
-
*out_y = *(worm._unknown_90.as_ptr().add(4) as *const i32);
|
| 97 |
}
|
| 98 |
|
| 99 |
/// Check if any worm on any team has state 0x64 β port of 0x5228D0.
|
| 100 |
///
|
| 101 |
/// Iterates real teams (1-indexed). 11 xrefs in gameplay code.
|
| 102 |
-
pub unsafe fn check_worm_state_0x64(arena:
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
|
| 106 |
-
let header = arena.team_header(i);
|
| 107 |
-
if header.eliminated != 0 {
|
| 108 |
continue;
|
| 109 |
}
|
| 110 |
-
let worm_count = header.worm_count;
|
| 111 |
for w in 1..=worm_count as usize {
|
| 112 |
-
if
|
| 113 |
return 1;
|
| 114 |
}
|
| 115 |
}
|
|
@@ -120,16 +115,16 @@ pub unsafe fn check_worm_state_0x64(arena: TeamArenaRef) -> u32 {
|
|
| 120 |
/// Check if any worm on a specific team has state 0x64 β port of 0x522930.
|
| 121 |
///
|
| 122 |
/// Per-team version. 1 xref (FUN_00556ad0).
|
| 123 |
-
pub unsafe fn check_team_worm_state_0x64(arena:
|
| 124 |
-
let header =
|
| 125 |
|
| 126 |
-
if header.eliminated != 0 {
|
| 127 |
return 0;
|
| 128 |
}
|
| 129 |
|
| 130 |
-
let worm_count = header.worm_count;
|
| 131 |
for w in 1..=worm_count as usize {
|
| 132 |
-
if
|
| 133 |
return 1;
|
| 134 |
}
|
| 135 |
}
|
|
@@ -139,17 +134,15 @@ pub unsafe fn check_team_worm_state_0x64(arena: TeamArenaRef, team_idx: u32) ->
|
|
| 139 |
/// Scan all teams for state 0x8b β port of 0x522970.
|
| 140 |
///
|
| 141 |
/// 1 xref (FUN_00557310).
|
| 142 |
-
pub unsafe fn check_any_worm_state_0x8b(arena:
|
| 143 |
-
|
| 144 |
-
|
| 145 |
-
|
| 146 |
-
let header = arena.team_header(i);
|
| 147 |
-
if header.eliminated != 0 {
|
| 148 |
continue;
|
| 149 |
}
|
| 150 |
-
let worm_count = header.worm_count;
|
| 151 |
for w in 1..=worm_count as usize {
|
| 152 |
-
if
|
| 153 |
return 1;
|
| 154 |
}
|
| 155 |
}
|
|
@@ -163,45 +156,43 @@ pub unsafe fn check_any_worm_state_0x8b(arena: TeamArenaRef) -> u32 {
|
|
| 163 |
/// Called on turn transitions and worm selection (Tab).
|
| 164 |
/// Updates active counters and records last_active_team/alliance.
|
| 165 |
/// Uses Pattern B headers (0-indexed from `team_idx - 1`).
|
| 166 |
-
pub unsafe fn set_active_worm(arena:
|
| 167 |
-
let state = arena.state_mut();
|
| 168 |
-
|
| 169 |
// Pattern B header: team_idx is 1-indexed, team_header_b is 0-indexed
|
| 170 |
// team_idx=1 β team_header_b(0) β blocks[2].header
|
| 171 |
-
let header =
|
| 172 |
|
| 173 |
if worm_index == 0 {
|
| 174 |
// Deactivate team
|
| 175 |
-
if header.active_worm != 0 {
|
| 176 |
-
|
| 177 |
-
let alliance = header.alliance;
|
| 178 |
if alliance >= 0 {
|
| 179 |
-
|
| 180 |
-
if alliance ==
|
| 181 |
-
|
| 182 |
} else {
|
| 183 |
-
|
| 184 |
}
|
| 185 |
}
|
| 186 |
-
|
| 187 |
}
|
| 188 |
} else {
|
| 189 |
// Activate team β only update counters if not already active
|
| 190 |
-
if header.active_worm == 0 {
|
| 191 |
-
|
| 192 |
-
|
| 193 |
-
let alliance = header.alliance;
|
| 194 |
-
|
| 195 |
if alliance >= 0 {
|
| 196 |
-
|
| 197 |
-
if alliance ==
|
| 198 |
-
|
| 199 |
} else {
|
| 200 |
-
|
| 201 |
}
|
| 202 |
}
|
| 203 |
}
|
| 204 |
// Always write worm_index (original writes ESI unconditionally)
|
| 205 |
-
|
| 206 |
}
|
| 207 |
}
|
|
|
|
| 1 |
//! Team and worm state accessor functions.
|
| 2 |
//!
|
| 3 |
+
//! Pure Rust reimplementations of WA.exe functions that access the TeamArena
|
| 4 |
//! area (DDGame + 0x4628). Called from hook trampolines in openwa-dll.
|
| 5 |
//!
|
| 6 |
//! Original WA functions:
|
|
|
|
| 15 |
|
| 16 |
use crate::engine::team_arena::worm;
|
| 17 |
|
| 18 |
+
use super::team_arena::TeamArena;
|
| 19 |
|
| 20 |
/// Count teams by alliance membership β port of 0x522030.
|
| 21 |
///
|
| 22 |
/// Updates `state.active_team_count`, `same_alliance_count`, `enemy_team_count`.
|
| 23 |
/// Uses Pattern B header fields (+0x70/+0x74) for alliance and active_worm.
|
| 24 |
+
pub unsafe fn count_teams_by_alliance(arena: *mut TeamArena, alliance_id: i32) {
|
| 25 |
+
(*arena).current_alliance = alliance_id;
|
| 26 |
+
(*arena).active_team_count = 0;
|
| 27 |
+
(*arena).same_alliance_count = 0;
|
| 28 |
+
(*arena).enemy_team_count = 0;
|
| 29 |
+
|
| 30 |
+
for i in 0..(*arena).team_count as usize {
|
| 31 |
+
let header = TeamArena::team_header_b(arena, i);
|
| 32 |
+
let team_alliance = (*header).alliance;
|
| 33 |
+
let active_worm = (*header).active_worm;
|
|
|
|
| 34 |
if active_worm != 0 && team_alliance >= 0 {
|
| 35 |
+
(*arena).active_team_count += 1;
|
| 36 |
if team_alliance == alliance_id {
|
| 37 |
+
(*arena).same_alliance_count += 1;
|
| 38 |
} else {
|
| 39 |
+
(*arena).enemy_team_count += 1;
|
| 40 |
}
|
| 41 |
}
|
| 42 |
}
|
|
|
|
| 45 |
/// Sum worm health for a team β port of 0x5224D0.
|
| 46 |
///
|
| 47 |
/// Returns 0 if the team is eliminated.
|
| 48 |
+
pub unsafe fn get_team_total_health(team_index: u32, arena: *const TeamArena) -> i32 {
|
| 49 |
+
let header = TeamArena::team_header(arena, team_index as usize);
|
| 50 |
|
| 51 |
+
if (*header).eliminated != 0 {
|
| 52 |
return 0;
|
| 53 |
}
|
| 54 |
|
| 55 |
+
let worm_count = (*header).worm_count;
|
| 56 |
let mut total = 0i32;
|
| 57 |
for w in 1..=worm_count as usize {
|
| 58 |
+
total += (*TeamArena::team_worm(arena, team_index as usize, w)).health;
|
| 59 |
}
|
| 60 |
total
|
| 61 |
}
|
|
|
|
| 66 |
pub unsafe fn is_worm_in_special_state(
|
| 67 |
team_index: u32,
|
| 68 |
worm_index: u32,
|
| 69 |
+
arena: *const TeamArena,
|
| 70 |
) -> u32 {
|
| 71 |
if worm::is_special_state(
|
| 72 |
+
(*TeamArena::team_worm(arena, team_index as usize, worm_index as usize)).state,
|
|
|
|
|
|
|
| 73 |
) {
|
| 74 |
1
|
| 75 |
} else {
|
|
|
|
| 84 |
pub unsafe fn get_worm_position(
|
| 85 |
team_index: u32,
|
| 86 |
worm_index: u32,
|
| 87 |
+
arena: *const TeamArena,
|
| 88 |
out_x: *mut i32,
|
| 89 |
out_y: *mut i32,
|
| 90 |
) {
|
| 91 |
+
let worm = TeamArena::team_worm(arena, team_index as usize, worm_index as usize);
|
| 92 |
+
*out_x = *((*worm)._unknown_90.as_ptr() as *const i32);
|
| 93 |
+
*out_y = *((*worm)._unknown_90.as_ptr().add(4) as *const i32);
|
| 94 |
}
|
| 95 |
|
| 96 |
/// Check if any worm on any team has state 0x64 β port of 0x5228D0.
|
| 97 |
///
|
| 98 |
/// Iterates real teams (1-indexed). 11 xrefs in gameplay code.
|
| 99 |
+
pub unsafe fn check_worm_state_0x64(arena: *const TeamArena) -> u32 {
|
| 100 |
+
for i in 1..=(*arena).team_count as usize {
|
| 101 |
+
let header = TeamArena::team_header(arena, i);
|
| 102 |
+
if (*header).eliminated != 0 {
|
|
|
|
|
|
|
| 103 |
continue;
|
| 104 |
}
|
| 105 |
+
let worm_count = (*header).worm_count;
|
| 106 |
for w in 1..=worm_count as usize {
|
| 107 |
+
if (*TeamArena::team_worm(arena, i, w)).state == 0x64 {
|
| 108 |
return 1;
|
| 109 |
}
|
| 110 |
}
|
|
|
|
| 115 |
/// Check if any worm on a specific team has state 0x64 β port of 0x522930.
|
| 116 |
///
|
| 117 |
/// Per-team version. 1 xref (FUN_00556ad0).
|
| 118 |
+
pub unsafe fn check_team_worm_state_0x64(arena: *const TeamArena, team_idx: u32) -> u32 {
|
| 119 |
+
let header = TeamArena::team_header(arena, team_idx as usize);
|
| 120 |
|
| 121 |
+
if (*header).eliminated != 0 {
|
| 122 |
return 0;
|
| 123 |
}
|
| 124 |
|
| 125 |
+
let worm_count = (*header).worm_count;
|
| 126 |
for w in 1..=worm_count as usize {
|
| 127 |
+
if (*TeamArena::team_worm(arena, team_idx as usize, w)).state == 0x64 {
|
| 128 |
return 1;
|
| 129 |
}
|
| 130 |
}
|
|
|
|
| 134 |
/// Scan all teams for state 0x8b β port of 0x522970.
|
| 135 |
///
|
| 136 |
/// 1 xref (FUN_00557310).
|
| 137 |
+
pub unsafe fn check_any_worm_state_0x8b(arena: *const TeamArena) -> u32 {
|
| 138 |
+
for i in 1..=(*arena).team_count as usize {
|
| 139 |
+
let header = TeamArena::team_header(arena, i);
|
| 140 |
+
if (*header).eliminated != 0 {
|
|
|
|
|
|
|
| 141 |
continue;
|
| 142 |
}
|
| 143 |
+
let worm_count = (*header).worm_count;
|
| 144 |
for w in 1..=worm_count as usize {
|
| 145 |
+
if (*TeamArena::team_worm(arena, i, w)).state == 0x8b {
|
| 146 |
return 1;
|
| 147 |
}
|
| 148 |
}
|
|
|
|
| 156 |
/// Called on turn transitions and worm selection (Tab).
|
| 157 |
/// Updates active counters and records last_active_team/alliance.
|
| 158 |
/// Uses Pattern B headers (0-indexed from `team_idx - 1`).
|
| 159 |
+
pub unsafe fn set_active_worm(arena: *mut TeamArena, team_idx: u32, worm_index: i32) {
|
|
|
|
|
|
|
| 160 |
// Pattern B header: team_idx is 1-indexed, team_header_b is 0-indexed
|
| 161 |
// team_idx=1 β team_header_b(0) β blocks[2].header
|
| 162 |
+
let header = TeamArena::team_header_b_mut(arena, team_idx as usize - 1);
|
| 163 |
|
| 164 |
if worm_index == 0 {
|
| 165 |
// Deactivate team
|
| 166 |
+
if (*header).active_worm != 0 {
|
| 167 |
+
(*arena).active_worm_count -= 1;
|
| 168 |
+
let alliance = (*header).alliance;
|
| 169 |
if alliance >= 0 {
|
| 170 |
+
(*arena).active_team_count -= 1;
|
| 171 |
+
if alliance == (*arena).current_alliance {
|
| 172 |
+
(*arena).same_alliance_count -= 1;
|
| 173 |
} else {
|
| 174 |
+
(*arena).enemy_team_count -= 1;
|
| 175 |
}
|
| 176 |
}
|
| 177 |
+
(*header).active_worm = 0;
|
| 178 |
}
|
| 179 |
} else {
|
| 180 |
// Activate team β only update counters if not already active
|
| 181 |
+
if (*header).active_worm == 0 {
|
| 182 |
+
(*arena).active_worm_count += 1;
|
| 183 |
+
(*arena).last_active_team = team_idx as i32;
|
| 184 |
+
let alliance = (*header).alliance;
|
| 185 |
+
(*arena).last_active_alliance = alliance;
|
| 186 |
if alliance >= 0 {
|
| 187 |
+
(*arena).active_team_count += 1;
|
| 188 |
+
if alliance == (*arena).current_alliance {
|
| 189 |
+
(*arena).same_alliance_count += 1;
|
| 190 |
} else {
|
| 191 |
+
(*arena).enemy_team_count += 1;
|
| 192 |
}
|
| 193 |
}
|
| 194 |
}
|
| 195 |
// Always write worm_index (original writes ESI unconditionally)
|
| 196 |
+
(*header).active_worm = worm_index;
|
| 197 |
}
|
| 198 |
}
|
crates/openwa-core/src/game/weapon_fire.rs
CHANGED
|
@@ -11,7 +11,7 @@
|
|
| 11 |
|
| 12 |
use crate::address::va;
|
| 13 |
use crate::audio::{KnownSoundId, SoundId};
|
| 14 |
-
use crate::engine::{
|
| 15 |
use crate::fixed::Fixed;
|
| 16 |
use crate::game::weapon::{WeaponEntry, WeaponFireParams, WeaponSpawnData};
|
| 17 |
use crate::game::Weapon;
|
|
@@ -48,15 +48,14 @@ const _: () = assert!(core::mem::size_of::<WeaponReleaseContext>() == 0x2C);
|
|
| 48 |
// __usercall: EAX = team_index, EDX = amount, [ESP+4] = team_info_base, [ESP+8] = weapon_id
|
| 49 |
// RET 0x8
|
| 50 |
|
| 51 |
-
pub unsafe fn add_ammo(team_index: u32, amount: i32, arena:
|
| 52 |
-
let (alliance, wid) =
|
| 53 |
-
let
|
| 54 |
-
let ammo = state.get_ammo(alliance, wid);
|
| 55 |
if ammo >= 0 {
|
| 56 |
if amount < 0 {
|
| 57 |
-
*
|
| 58 |
} else {
|
| 59 |
-
*
|
| 60 |
}
|
| 61 |
}
|
| 62 |
}
|
|
@@ -67,12 +66,11 @@ pub unsafe fn add_ammo(team_index: u32, amount: i32, arena: TeamArenaRef, weapon
|
|
| 67 |
// __usercall: EAX = team_index, ECX = team_info_base, [ESP+4] = weapon_id
|
| 68 |
// RET 0x4
|
| 69 |
|
| 70 |
-
pub unsafe fn subtract_ammo(team_index: u32, arena:
|
| 71 |
-
let (alliance, wid) =
|
| 72 |
-
let
|
| 73 |
-
let ammo = state.get_ammo(alliance, wid);
|
| 74 |
if ammo > 0 {
|
| 75 |
-
*
|
| 76 |
}
|
| 77 |
}
|
| 78 |
|
|
@@ -82,31 +80,30 @@ pub unsafe fn subtract_ammo(team_index: u32, arena: TeamArenaRef, weapon_id: u32
|
|
| 82 |
// __usercall: EAX = team_index, ESI = team_info_base, EDX = weapon_id
|
| 83 |
// plain RET, returns EAX = ammo count
|
| 84 |
|
| 85 |
-
pub unsafe fn get_ammo(team_index: u32, arena:
|
| 86 |
-
let (alliance, wid) =
|
| 87 |
-
let state = arena.state();
|
| 88 |
|
| 89 |
// Check weapon delay
|
| 90 |
-
if
|
| 91 |
-
if
|
| 92 |
return 0;
|
| 93 |
}
|
| 94 |
// In sudden death (phase >= 484), delayed weapons return 0
|
| 95 |
// unless it's Teleport (weapon 0x28)
|
| 96 |
-
if
|
| 97 |
return 0;
|
| 98 |
}
|
| 99 |
}
|
| 100 |
|
| 101 |
// SelectWorm (0x3B) requires >1 alive worm on the team
|
| 102 |
-
if
|
| 103 |
&& weapon_id == Weapon::SelectWorm as u32
|
| 104 |
&& count_alive_worms(team_index, arena) == 0
|
| 105 |
{
|
| 106 |
return 0;
|
| 107 |
}
|
| 108 |
|
| 109 |
-
|
| 110 |
}
|
| 111 |
|
| 112 |
// ============================================================
|
|
@@ -115,12 +112,12 @@ pub unsafe fn get_ammo(team_index: u32, arena: TeamArenaRef, weapon_id: u32) ->
|
|
| 115 |
// __usercall: EAX = team_index, ECX = base
|
| 116 |
// plain RET, returns EAX = bool (1 if >1 worm alive on team)
|
| 117 |
|
| 118 |
-
pub unsafe fn count_alive_worms(team_index: u32, arena:
|
| 119 |
-
let header =
|
| 120 |
-
let worm_count = header.worm_count;
|
| 121 |
let mut alive = 0i32;
|
| 122 |
for w in 1..=worm_count as usize {
|
| 123 |
-
if
|
| 124 |
alive += 1;
|
| 125 |
}
|
| 126 |
}
|
|
@@ -470,14 +467,14 @@ unsafe fn fire_skip_go(worm: *const CTaskWorm, entry: *const WeaponEntry) {
|
|
| 470 |
let bit_index = ((*entry).fire_params.shot_count & 0x1F) as u32;
|
| 471 |
let bit = 1u32 << bit_index;
|
| 472 |
|
| 473 |
-
let arena =
|
| 474 |
-
let header =
|
| 475 |
-
let flags = header.turn_action_flags;
|
| 476 |
|
| 477 |
if game_version > 0x1C && (flags & bit) != 0 {
|
| 478 |
-
header.turn_action_flags = flags & !bit;
|
| 479 |
} else {
|
| 480 |
-
header.turn_action_flags = flags | bit;
|
| 481 |
}
|
| 482 |
}
|
| 483 |
|
|
@@ -489,11 +486,11 @@ unsafe fn fire_freeze(worm: *mut CTaskWorm) {
|
|
| 489 |
fire_send_team_message(worm, 0x29);
|
| 490 |
|
| 491 |
let ddgame = CTask::ddgame_raw(worm as *const CTask);
|
| 492 |
-
let arena =
|
| 493 |
let team_index = (*worm).team_index as usize;
|
| 494 |
let worm_index = (*worm).worm_index as usize;
|
| 495 |
-
let entry =
|
| 496 |
-
entry.turn_action_counter_Maybe += 14;
|
| 497 |
}
|
| 498 |
|
| 499 |
/// Mail/Mine/Mole (subtype 14) β pure Rust replacement for 0x51E670.
|
|
@@ -524,11 +521,11 @@ unsafe fn fire_mail_mine_mole(worm: *mut CTaskWorm) {
|
|
| 524 |
|
| 525 |
fire_send_team_message(worm, 0x28);
|
| 526 |
|
| 527 |
-
let arena =
|
| 528 |
let team_index = (*worm).team_index as usize;
|
| 529 |
let worm_index = (*worm).worm_index as usize;
|
| 530 |
-
let entry =
|
| 531 |
-
entry.turn_action_counter_Maybe += 7;
|
| 532 |
}
|
| 533 |
|
| 534 |
/// Teleport (subtype 10) β pure Rust port of 0x51E710.
|
|
@@ -983,12 +980,12 @@ unsafe fn fire_girder(worm: *mut CTaskWorm) {
|
|
| 983 |
girder_visual(landscape, girder_x >> 16, girder_y >> 16, sprite1, sprite2);
|
| 984 |
|
| 985 |
// Increment WormEntry counters
|
| 986 |
-
let arena =
|
| 987 |
let team_index = (*worm).team_index as usize;
|
| 988 |
let worm_index = (*worm).worm_index as usize;
|
| 989 |
-
let entry =
|
| 990 |
-
entry.turn_action_counter_Maybe += 3;
|
| 991 |
-
entry.effect_counter_04_Maybe += 10;
|
| 992 |
}
|
| 993 |
}
|
| 994 |
|
|
|
|
| 11 |
|
| 12 |
use crate::address::va;
|
| 13 |
use crate::audio::{KnownSoundId, SoundId};
|
| 14 |
+
use crate::engine::{TeamArena, GAME_PHASE_NORMAL_MIN, GAME_PHASE_SUDDEN_DEATH};
|
| 15 |
use crate::fixed::Fixed;
|
| 16 |
use crate::game::weapon::{WeaponEntry, WeaponFireParams, WeaponSpawnData};
|
| 17 |
use crate::game::Weapon;
|
|
|
|
| 48 |
// __usercall: EAX = team_index, EDX = amount, [ESP+4] = team_info_base, [ESP+8] = weapon_id
|
| 49 |
// RET 0x8
|
| 50 |
|
| 51 |
+
pub unsafe fn add_ammo(team_index: u32, amount: i32, arena: *mut TeamArena, weapon_id: u32) {
|
| 52 |
+
let (alliance, wid) = TeamArena::weapon_slot_key(arena, team_index as usize, weapon_id);
|
| 53 |
+
let ammo = (*arena).get_ammo(alliance, wid);
|
|
|
|
| 54 |
if ammo >= 0 {
|
| 55 |
if amount < 0 {
|
| 56 |
+
*(*arena).ammo_mut(alliance, wid) = -1;
|
| 57 |
} else {
|
| 58 |
+
*(*arena).ammo_mut(alliance, wid) = ammo + amount;
|
| 59 |
}
|
| 60 |
}
|
| 61 |
}
|
|
|
|
| 66 |
// __usercall: EAX = team_index, ECX = team_info_base, [ESP+4] = weapon_id
|
| 67 |
// RET 0x4
|
| 68 |
|
| 69 |
+
pub unsafe fn subtract_ammo(team_index: u32, arena: *mut TeamArena, weapon_id: u32) {
|
| 70 |
+
let (alliance, wid) = TeamArena::weapon_slot_key(arena, team_index as usize, weapon_id);
|
| 71 |
+
let ammo = (*arena).get_ammo(alliance, wid);
|
|
|
|
| 72 |
if ammo > 0 {
|
| 73 |
+
*(*arena).ammo_mut(alliance, wid) = ammo - 1;
|
| 74 |
}
|
| 75 |
}
|
| 76 |
|
|
|
|
| 80 |
// __usercall: EAX = team_index, ESI = team_info_base, EDX = weapon_id
|
| 81 |
// plain RET, returns EAX = ammo count
|
| 82 |
|
| 83 |
+
pub unsafe fn get_ammo(team_index: u32, arena: *mut TeamArena, weapon_id: u32) -> u32 {
|
| 84 |
+
let (alliance, wid) = TeamArena::weapon_slot_key(arena, team_index as usize, weapon_id);
|
|
|
|
| 85 |
|
| 86 |
// Check weapon delay
|
| 87 |
+
if (*arena).get_delay(alliance, wid) != 0 {
|
| 88 |
+
if (*arena).game_mode_flag == 0 {
|
| 89 |
return 0;
|
| 90 |
}
|
| 91 |
// In sudden death (phase >= 484), delayed weapons return 0
|
| 92 |
// unless it's Teleport (weapon 0x28)
|
| 93 |
+
if (*arena).game_phase >= GAME_PHASE_SUDDEN_DEATH && weapon_id != Weapon::Teleport as u32 {
|
| 94 |
return 0;
|
| 95 |
}
|
| 96 |
}
|
| 97 |
|
| 98 |
// SelectWorm (0x3B) requires >1 alive worm on the team
|
| 99 |
+
if (*arena).game_phase >= GAME_PHASE_NORMAL_MIN
|
| 100 |
&& weapon_id == Weapon::SelectWorm as u32
|
| 101 |
&& count_alive_worms(team_index, arena) == 0
|
| 102 |
{
|
| 103 |
return 0;
|
| 104 |
}
|
| 105 |
|
| 106 |
+
(*arena).get_ammo(alliance, wid) as u32
|
| 107 |
}
|
| 108 |
|
| 109 |
// ============================================================
|
|
|
|
| 112 |
// __usercall: EAX = team_index, ECX = base
|
| 113 |
// plain RET, returns EAX = bool (1 if >1 worm alive on team)
|
| 114 |
|
| 115 |
+
pub unsafe fn count_alive_worms(team_index: u32, arena: *const TeamArena) -> u32 {
|
| 116 |
+
let header = TeamArena::team_header(arena, team_index as usize);
|
| 117 |
+
let worm_count = (*header).worm_count;
|
| 118 |
let mut alive = 0i32;
|
| 119 |
for w in 1..=worm_count as usize {
|
| 120 |
+
if (*TeamArena::team_worm(arena, team_index as usize, w)).health > 0 {
|
| 121 |
alive += 1;
|
| 122 |
}
|
| 123 |
}
|
|
|
|
| 467 |
let bit_index = ((*entry).fire_params.shot_count & 0x1F) as u32;
|
| 468 |
let bit = 1u32 << bit_index;
|
| 469 |
|
| 470 |
+
let arena = &raw mut (*ddgame).team_arena;
|
| 471 |
+
let header = TeamArena::team_header_mut(arena, team_index);
|
| 472 |
+
let flags = (*header).turn_action_flags;
|
| 473 |
|
| 474 |
if game_version > 0x1C && (flags & bit) != 0 {
|
| 475 |
+
(*header).turn_action_flags = flags & !bit;
|
| 476 |
} else {
|
| 477 |
+
(*header).turn_action_flags = flags | bit;
|
| 478 |
}
|
| 479 |
}
|
| 480 |
|
|
|
|
| 486 |
fire_send_team_message(worm, 0x29);
|
| 487 |
|
| 488 |
let ddgame = CTask::ddgame_raw(worm as *const CTask);
|
| 489 |
+
let arena = &raw mut (*ddgame).team_arena;
|
| 490 |
let team_index = (*worm).team_index as usize;
|
| 491 |
let worm_index = (*worm).worm_index as usize;
|
| 492 |
+
let entry = TeamArena::team_worm_mut(arena, team_index, worm_index);
|
| 493 |
+
(*entry).turn_action_counter_Maybe += 14;
|
| 494 |
}
|
| 495 |
|
| 496 |
/// Mail/Mine/Mole (subtype 14) β pure Rust replacement for 0x51E670.
|
|
|
|
| 521 |
|
| 522 |
fire_send_team_message(worm, 0x28);
|
| 523 |
|
| 524 |
+
let arena = &raw mut (*ddgame).team_arena;
|
| 525 |
let team_index = (*worm).team_index as usize;
|
| 526 |
let worm_index = (*worm).worm_index as usize;
|
| 527 |
+
let entry = TeamArena::team_worm_mut(arena, team_index, worm_index);
|
| 528 |
+
(*entry).turn_action_counter_Maybe += 7;
|
| 529 |
}
|
| 530 |
|
| 531 |
/// Teleport (subtype 10) β pure Rust port of 0x51E710.
|
|
|
|
| 980 |
girder_visual(landscape, girder_x >> 16, girder_y >> 16, sprite1, sprite2);
|
| 981 |
|
| 982 |
// Increment WormEntry counters
|
| 983 |
+
let arena = &raw mut (*ddgame).team_arena;
|
| 984 |
let team_index = (*worm).team_index as usize;
|
| 985 |
let worm_index = (*worm).worm_index as usize;
|
| 986 |
+
let entry = TeamArena::team_worm_mut(arena, team_index, worm_index);
|
| 987 |
+
(*entry).turn_action_counter_Maybe += 3;
|
| 988 |
+
(*entry).effect_counter_04_Maybe += 10;
|
| 989 |
}
|
| 990 |
}
|
| 991 |
|
crates/openwa-core/src/render/mod.rs
CHANGED
|
@@ -16,3 +16,17 @@ pub use sprite::{Sprite, SpriteFrame, SpriteId};
|
|
| 16 |
pub use turn_order::{
|
| 17 |
AnimatedItemList, TurnOrderAllianceGroup, TurnOrderTeamEntry, TurnOrderWidget,
|
| 18 |
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 16 |
pub use turn_order::{
|
| 17 |
AnimatedItemList, TurnOrderAllianceGroup, TurnOrderTeamEntry, TurnOrderWidget,
|
| 18 |
};
|
| 19 |
+
|
| 20 |
+
/// Render table entry (0x14 = 20 bytes).
|
| 21 |
+
///
|
| 22 |
+
/// 14 entries live at DDGame+0x73B0 (stride 0x14). Only the first u32
|
| 23 |
+
/// is zeroed during construction; the rest is uninitialized/unknown.
|
| 24 |
+
#[repr(C)]
|
| 25 |
+
#[derive(Clone, Copy)]
|
| 26 |
+
pub struct RenderEntry {
|
| 27 |
+
/// Active/state flag (zeroed on init).
|
| 28 |
+
pub active: u32,
|
| 29 |
+
/// Unknown data.
|
| 30 |
+
pub _unknown: [u8; 16],
|
| 31 |
+
}
|
| 32 |
+
const _: () = assert!(core::mem::size_of::<RenderEntry>() == 0x14);
|
crates/openwa-core/src/task/team.rs
CHANGED
|
@@ -66,7 +66,7 @@ pub struct CTaskTeam {
|
|
| 66 |
pub _secondary_vtable: u32,
|
| 67 |
/// 0x34: Unknown β observed to hold the same value as `team_index` in all runs.
|
| 68 |
pub _unknown_34: u32,
|
| 69 |
-
/// 0x38: Team number, **1-based**. Matches
|
| 70 |
pub team_index: u32,
|
| 71 |
/// 0x3C: Unknown β observed 0 for team 1, 1 for team 2.
|
| 72 |
pub _unknown_3c: u32,
|
|
|
|
| 66 |
pub _secondary_vtable: u32,
|
| 67 |
/// 0x34: Unknown β observed to hold the same value as `team_index` in all runs.
|
| 68 |
pub _unknown_34: u32,
|
| 69 |
+
/// 0x38: Team number, **1-based**. Matches TeamArena slot index.
|
| 70 |
pub team_index: u32,
|
| 71 |
/// 0x3C: Unknown β observed 0 for team 1, 1 for team 2.
|
| 72 |
pub _unknown_3c: u32,
|
crates/openwa-core/src/task/turn_game.rs
CHANGED
|
@@ -152,9 +152,9 @@ pub struct CTaskTurnGame {
|
|
| 152 |
pub current_team: u32,
|
| 153 |
/// 0x130: active worm index within the current team, **0-based** (stride 0x9C).
|
| 154 |
pub current_worm: u32,
|
| 155 |
-
/// 0x134: arena team index used for
|
| 156 |
pub arena_team: u32,
|
| 157 |
-
/// 0x138: arena worm index used for
|
| 158 |
pub arena_worm: u32,
|
| 159 |
pub _unknown_13c: u32,
|
| 160 |
/// 0x140: set to 1 at construction; role unclear.
|
|
|
|
| 152 |
pub current_team: u32,
|
| 153 |
/// 0x130: active worm index within the current team, **0-based** (stride 0x9C).
|
| 154 |
pub current_worm: u32,
|
| 155 |
+
/// 0x134: arena team index used for TeamArena lookups.
|
| 156 |
pub arena_team: u32,
|
| 157 |
+
/// 0x138: arena worm index used for TeamArena lookups.
|
| 158 |
pub arena_worm: u32,
|
| 159 |
pub _unknown_13c: u32,
|
| 160 |
/// 0x140: set to 1 at construction; role unclear.
|
crates/openwa-core/src/task/worm.rs
CHANGED
|
@@ -9,7 +9,7 @@ use crate::FieldRegistry;
|
|
| 9 |
///
|
| 10 |
/// CTaskWorm's `SetState` (vtable slot 14) transitions between these.
|
| 11 |
/// The state byte lives at CTaskWorm+0x44 (inside `base.subclass_data`).
|
| 12 |
-
/// Also stored in WormEntry.state in the
|
| 13 |
///
|
| 14 |
/// States 0x68..=0x8A are the "weapon/action active" range β checked by
|
| 15 |
/// `(state - 0x68) < 0x23` in HandleMessage. States 0x80+ are dying/dead.
|
|
|
|
| 9 |
///
|
| 10 |
/// CTaskWorm's `SetState` (vtable slot 14) transitions between these.
|
| 11 |
/// The state byte lives at CTaskWorm+0x44 (inside `base.subclass_data`).
|
| 12 |
+
/// Also stored in WormEntry.state in the TeamArena.
|
| 13 |
///
|
| 14 |
/// States 0x68..=0x8A are the "weapon/action active" range β checked by
|
| 15 |
/// `(state - 0x68) < 0x23` in HandleMessage. States 0x80+ are dying/dead.
|
crates/openwa-dll/src/replacements/replay_test.rs
CHANGED
|
@@ -13,10 +13,9 @@
|
|
| 13 |
use std::sync::atomic::{AtomicBool, AtomicI32, AtomicU32, Ordering};
|
| 14 |
|
| 15 |
use crate::log_line;
|
| 16 |
-
use openwa_core::engine::ddgame::offsets;
|
| 17 |
use openwa_core::engine::game_session;
|
| 18 |
use openwa_core::engine::DDGame;
|
| 19 |
-
use openwa_core::engine::
|
| 20 |
|
| 21 |
// ---------------------------------------------------------------------------
|
| 22 |
// Fast-forward
|
|
@@ -69,9 +68,8 @@ pub unsafe fn on_frame(frame: u32) {
|
|
| 69 |
///
|
| 70 |
/// Returns (alive_team_count, total_team_count).
|
| 71 |
unsafe fn count_alive_teams(ddgame: *const DDGame) -> (i32, i32) {
|
| 72 |
-
let
|
| 73 |
-
let
|
| 74 |
-
let team_count = arena.state().team_count;
|
| 75 |
if team_count <= 0 || team_count > 6 {
|
| 76 |
return (0, 0);
|
| 77 |
}
|
|
@@ -80,8 +78,7 @@ unsafe fn count_alive_teams(ddgame: *const DDGame) -> (i32, i32) {
|
|
| 80 |
for t in 1..=team_count as usize {
|
| 81 |
let mut team_has_alive = false;
|
| 82 |
for w in 1..=8usize {
|
| 83 |
-
|
| 84 |
-
if worm.health > 0 {
|
| 85 |
team_has_alive = true;
|
| 86 |
break;
|
| 87 |
}
|
|
|
|
| 13 |
use std::sync::atomic::{AtomicBool, AtomicI32, AtomicU32, Ordering};
|
| 14 |
|
| 15 |
use crate::log_line;
|
|
|
|
| 16 |
use openwa_core::engine::game_session;
|
| 17 |
use openwa_core::engine::DDGame;
|
| 18 |
+
use openwa_core::engine::TeamArena;
|
| 19 |
|
| 20 |
// ---------------------------------------------------------------------------
|
| 21 |
// Fast-forward
|
|
|
|
| 68 |
///
|
| 69 |
/// Returns (alive_team_count, total_team_count).
|
| 70 |
unsafe fn count_alive_teams(ddgame: *const DDGame) -> (i32, i32) {
|
| 71 |
+
let arena = &raw const (*ddgame).team_arena;
|
| 72 |
+
let team_count = (*arena).team_count;
|
|
|
|
| 73 |
if team_count <= 0 || team_count > 6 {
|
| 74 |
return (0, 0);
|
| 75 |
}
|
|
|
|
| 78 |
for t in 1..=team_count as usize {
|
| 79 |
let mut team_has_alive = false;
|
| 80 |
for w in 1..=8usize {
|
| 81 |
+
if (*TeamArena::team_worm(arena, t, w)).health > 0 {
|
|
|
|
| 82 |
team_has_alive = true;
|
| 83 |
break;
|
| 84 |
}
|
crates/openwa-dll/src/replacements/team.rs
CHANGED
|
@@ -4,13 +4,13 @@
|
|
| 4 |
//! This file contains only usercall trampolines and hook installation.
|
| 5 |
|
| 6 |
use openwa_core::engine::team_ops;
|
| 7 |
-
use openwa_core::{address::va, engine::
|
| 8 |
|
| 9 |
use crate::hook::{self, usercall_trampoline};
|
| 10 |
|
| 11 |
// ββ CountTeamsByAlliance (0x522030): usercall(EAX=base, EDI=alliance_id) ββ
|
| 12 |
|
| 13 |
-
unsafe extern "cdecl" fn count_teams_by_alliance_impl(arena:
|
| 14 |
team_ops::count_teams_by_alliance(arena, alliance_id);
|
| 15 |
}
|
| 16 |
|
|
@@ -19,7 +19,7 @@ usercall_trampoline!(fn trampoline_count_teams_by_alliance; impl_fn = count_team
|
|
| 19 |
|
| 20 |
// ββ GetTeamTotalHealth (0x5224D0): fastcall(ECX=team_index, EDX=base) ββ
|
| 21 |
|
| 22 |
-
unsafe extern "cdecl" fn get_team_total_health_impl(team_index: u32, arena:
|
| 23 |
team_ops::get_team_total_health(team_index, arena)
|
| 24 |
}
|
| 25 |
|
|
@@ -31,7 +31,7 @@ usercall_trampoline!(fn trampoline_get_team_total_health; impl_fn = get_team_tot
|
|
| 31 |
unsafe extern "cdecl" fn is_worm_in_special_state_impl(
|
| 32 |
team_index: u32,
|
| 33 |
worm_index: u32,
|
| 34 |
-
arena:
|
| 35 |
) -> u32 {
|
| 36 |
team_ops::is_worm_in_special_state(team_index, worm_index, arena)
|
| 37 |
}
|
|
@@ -44,7 +44,7 @@ usercall_trampoline!(fn trampoline_is_worm_in_special_state; impl_fn = is_worm_i
|
|
| 44 |
unsafe extern "cdecl" fn get_worm_position_impl(
|
| 45 |
team_index: u32,
|
| 46 |
worm_index: u32,
|
| 47 |
-
arena:
|
| 48 |
out_x: *mut i32,
|
| 49 |
out_y: *mut i32,
|
| 50 |
) {
|
|
@@ -56,7 +56,7 @@ usercall_trampoline!(fn trampoline_get_worm_position; impl_fn = get_worm_positio
|
|
| 56 |
|
| 57 |
// ββ CheckWormState0x64 (0x5228D0): usercall(EAX=base) ββ
|
| 58 |
|
| 59 |
-
unsafe extern "cdecl" fn check_worm_state_0x64_impl(arena:
|
| 60 |
team_ops::check_worm_state_0x64(arena)
|
| 61 |
}
|
| 62 |
|
|
@@ -66,7 +66,7 @@ usercall_trampoline!(fn trampoline_check_worm_state_0x64; impl_fn = check_worm_s
|
|
| 66 |
// ββ CheckTeamWormState0x64 (0x522930): usercall(ECX=base, EAX=team_idx) ββ
|
| 67 |
|
| 68 |
unsafe extern "cdecl" fn check_team_worm_state_0x64_impl(
|
| 69 |
-
arena:
|
| 70 |
team_idx: u32,
|
| 71 |
) -> u32 {
|
| 72 |
team_ops::check_team_worm_state_0x64(arena, team_idx)
|
|
@@ -77,7 +77,7 @@ usercall_trampoline!(fn trampoline_check_team_worm_state_0x64; impl_fn = check_t
|
|
| 77 |
|
| 78 |
// ββ CheckAnyWormState0x8b (0x522970): usercall(EAX=base) ββ
|
| 79 |
|
| 80 |
-
unsafe extern "cdecl" fn check_any_worm_state_0x8b_impl(arena:
|
| 81 |
team_ops::check_any_worm_state_0x8b(arena)
|
| 82 |
}
|
| 83 |
|
|
@@ -86,7 +86,11 @@ usercall_trampoline!(fn trampoline_check_any_worm_state_0x8b; impl_fn = check_an
|
|
| 86 |
|
| 87 |
// ββ SetActiveWorm_Maybe (0x522500): usercall(EAX=base, EDX=team_idx, ESI=worm) ββ
|
| 88 |
|
| 89 |
-
unsafe extern "cdecl" fn set_active_worm_impl(
|
|
|
|
|
|
|
|
|
|
|
|
|
| 90 |
team_ops::set_active_worm(arena, team_idx, worm_index);
|
| 91 |
}
|
| 92 |
|
|
|
|
| 4 |
//! This file contains only usercall trampolines and hook installation.
|
| 5 |
|
| 6 |
use openwa_core::engine::team_ops;
|
| 7 |
+
use openwa_core::{address::va, engine::TeamArena};
|
| 8 |
|
| 9 |
use crate::hook::{self, usercall_trampoline};
|
| 10 |
|
| 11 |
// ββ CountTeamsByAlliance (0x522030): usercall(EAX=base, EDI=alliance_id) ββ
|
| 12 |
|
| 13 |
+
unsafe extern "cdecl" fn count_teams_by_alliance_impl(arena: *mut TeamArena, alliance_id: i32) {
|
| 14 |
team_ops::count_teams_by_alliance(arena, alliance_id);
|
| 15 |
}
|
| 16 |
|
|
|
|
| 19 |
|
| 20 |
// ββ GetTeamTotalHealth (0x5224D0): fastcall(ECX=team_index, EDX=base) ββ
|
| 21 |
|
| 22 |
+
unsafe extern "cdecl" fn get_team_total_health_impl(team_index: u32, arena: *mut TeamArena) -> i32 {
|
| 23 |
team_ops::get_team_total_health(team_index, arena)
|
| 24 |
}
|
| 25 |
|
|
|
|
| 31 |
unsafe extern "cdecl" fn is_worm_in_special_state_impl(
|
| 32 |
team_index: u32,
|
| 33 |
worm_index: u32,
|
| 34 |
+
arena: *mut TeamArena,
|
| 35 |
) -> u32 {
|
| 36 |
team_ops::is_worm_in_special_state(team_index, worm_index, arena)
|
| 37 |
}
|
|
|
|
| 44 |
unsafe extern "cdecl" fn get_worm_position_impl(
|
| 45 |
team_index: u32,
|
| 46 |
worm_index: u32,
|
| 47 |
+
arena: *mut TeamArena,
|
| 48 |
out_x: *mut i32,
|
| 49 |
out_y: *mut i32,
|
| 50 |
) {
|
|
|
|
| 56 |
|
| 57 |
// ββ CheckWormState0x64 (0x5228D0): usercall(EAX=base) ββ
|
| 58 |
|
| 59 |
+
unsafe extern "cdecl" fn check_worm_state_0x64_impl(arena: *mut TeamArena) -> u32 {
|
| 60 |
team_ops::check_worm_state_0x64(arena)
|
| 61 |
}
|
| 62 |
|
|
|
|
| 66 |
// ββ CheckTeamWormState0x64 (0x522930): usercall(ECX=base, EAX=team_idx) ββ
|
| 67 |
|
| 68 |
unsafe extern "cdecl" fn check_team_worm_state_0x64_impl(
|
| 69 |
+
arena: *mut TeamArena,
|
| 70 |
team_idx: u32,
|
| 71 |
) -> u32 {
|
| 72 |
team_ops::check_team_worm_state_0x64(arena, team_idx)
|
|
|
|
| 77 |
|
| 78 |
// ββ CheckAnyWormState0x8b (0x522970): usercall(EAX=base) ββ
|
| 79 |
|
| 80 |
+
unsafe extern "cdecl" fn check_any_worm_state_0x8b_impl(arena: *mut TeamArena) -> u32 {
|
| 81 |
team_ops::check_any_worm_state_0x8b(arena)
|
| 82 |
}
|
| 83 |
|
|
|
|
| 86 |
|
| 87 |
// ββ SetActiveWorm_Maybe (0x522500): usercall(EAX=base, EDX=team_idx, ESI=worm) ββ
|
| 88 |
|
| 89 |
+
unsafe extern "cdecl" fn set_active_worm_impl(
|
| 90 |
+
arena: *mut TeamArena,
|
| 91 |
+
team_idx: u32,
|
| 92 |
+
worm_index: i32,
|
| 93 |
+
) {
|
| 94 |
team_ops::set_active_worm(arena, team_idx, worm_index);
|
| 95 |
}
|
| 96 |
|
crates/openwa-dll/src/replacements/weapon.rs
CHANGED
|
@@ -16,7 +16,7 @@ use openwa_core::game::weapon::WeaponFireParams;
|
|
| 16 |
use openwa_core::game::weapon_fire;
|
| 17 |
use openwa_core::log::log_line;
|
| 18 |
use openwa_core::task::worm::CTaskWorm;
|
| 19 |
-
use openwa_core::{address::va, engine::
|
| 20 |
|
| 21 |
use crate::hook::{self, usercall_trampoline};
|
| 22 |
|
|
@@ -25,7 +25,7 @@ use crate::hook::{self, usercall_trampoline};
|
|
| 25 |
unsafe extern "cdecl" fn add_ammo_impl(
|
| 26 |
team_index: u32,
|
| 27 |
amount: i32,
|
| 28 |
-
arena:
|
| 29 |
weapon_id: u32,
|
| 30 |
) {
|
| 31 |
weapon_fire::add_ammo(team_index, amount, arena, weapon_id);
|
|
@@ -36,7 +36,11 @@ usercall_trampoline!(fn trampoline_add_ammo; impl_fn = add_ammo_impl;
|
|
| 36 |
|
| 37 |
// ββ SubtractAmmo (0x522680): usercall(EAX=team, ECX=arena, stack=weapon_id) ββ
|
| 38 |
|
| 39 |
-
unsafe extern "cdecl" fn subtract_ammo_impl(
|
|
|
|
|
|
|
|
|
|
|
|
|
| 40 |
weapon_fire::subtract_ammo(team_index, arena, weapon_id);
|
| 41 |
}
|
| 42 |
|
|
@@ -47,7 +51,7 @@ usercall_trampoline!(fn trampoline_subtract_ammo; impl_fn = subtract_ammo_impl;
|
|
| 47 |
|
| 48 |
unsafe extern "cdecl" fn get_ammo_impl(
|
| 49 |
team_index: u32,
|
| 50 |
-
arena:
|
| 51 |
weapon_id: u32,
|
| 52 |
) -> u32 {
|
| 53 |
weapon_fire::get_ammo(team_index, arena, weapon_id)
|
|
@@ -58,7 +62,7 @@ usercall_trampoline!(fn trampoline_get_ammo; impl_fn = get_ammo_impl;
|
|
| 58 |
|
| 59 |
// ββ CountAliveWorms (0x5225A0): usercall(EAX=team, ECX=arena) ββ
|
| 60 |
|
| 61 |
-
unsafe extern "cdecl" fn count_alive_worms_impl(team_index: u32, arena:
|
| 62 |
weapon_fire::count_alive_worms(team_index, arena)
|
| 63 |
}
|
| 64 |
|
|
|
|
| 16 |
use openwa_core::game::weapon_fire;
|
| 17 |
use openwa_core::log::log_line;
|
| 18 |
use openwa_core::task::worm::CTaskWorm;
|
| 19 |
+
use openwa_core::{address::va, engine::TeamArena};
|
| 20 |
|
| 21 |
use crate::hook::{self, usercall_trampoline};
|
| 22 |
|
|
|
|
| 25 |
unsafe extern "cdecl" fn add_ammo_impl(
|
| 26 |
team_index: u32,
|
| 27 |
amount: i32,
|
| 28 |
+
arena: *mut TeamArena,
|
| 29 |
weapon_id: u32,
|
| 30 |
) {
|
| 31 |
weapon_fire::add_ammo(team_index, amount, arena, weapon_id);
|
|
|
|
| 36 |
|
| 37 |
// ββ SubtractAmmo (0x522680): usercall(EAX=team, ECX=arena, stack=weapon_id) ββ
|
| 38 |
|
| 39 |
+
unsafe extern "cdecl" fn subtract_ammo_impl(
|
| 40 |
+
team_index: u32,
|
| 41 |
+
arena: *mut TeamArena,
|
| 42 |
+
weapon_id: u32,
|
| 43 |
+
) {
|
| 44 |
weapon_fire::subtract_ammo(team_index, arena, weapon_id);
|
| 45 |
}
|
| 46 |
|
|
|
|
| 51 |
|
| 52 |
unsafe extern "cdecl" fn get_ammo_impl(
|
| 53 |
team_index: u32,
|
| 54 |
+
arena: *mut TeamArena,
|
| 55 |
weapon_id: u32,
|
| 56 |
) -> u32 {
|
| 57 |
weapon_fire::get_ammo(team_index, arena, weapon_id)
|
|
|
|
| 62 |
|
| 63 |
// ββ CountAliveWorms (0x5225A0): usercall(EAX=team, ECX=arena) ββ
|
| 64 |
|
| 65 |
+
unsafe extern "cdecl" fn count_alive_worms_impl(team_index: u32, arena: *mut TeamArena) -> u32 {
|
| 66 |
weapon_fire::count_alive_worms(team_index, arena)
|
| 67 |
}
|
| 68 |
|
crates/openwa-dll/src/snapshot.rs
CHANGED
|
@@ -19,7 +19,7 @@ use openwa_core::task::{CTask, CTaskBfsIter, CTaskMissile, CTaskWorm};
|
|
| 19 |
/// Must be called from the DLL while the game is paused (frame breakpoint).
|
| 20 |
#[cfg(target_arch = "x86")]
|
| 21 |
pub unsafe fn capture() -> String {
|
| 22 |
-
use openwa_core::engine::
|
| 23 |
|
| 24 |
let mut out = String::with_capacity(128 * 1024);
|
| 25 |
|
|
@@ -62,26 +62,30 @@ pub unsafe fn capture() -> String {
|
|
| 62 |
// ββ Team blocks + worm entries ββ
|
| 63 |
let team_count = (*ddgame).team_arena.team_count as usize;
|
| 64 |
let _ = writeln!(out, "[Teams] count={}", team_count);
|
| 65 |
-
let arena =
|
| 66 |
-
let blocks =
|
| 67 |
for t in 0..team_count {
|
| 68 |
-
let header =
|
| 69 |
let name =
|
| 70 |
-
core::ffi::CStr::from_ptr(header.team_name.as_ptr() as *const _).to_string_lossy();
|
| 71 |
let _ = writeln!(out, "\n [Team {}] \"{}\"", t, name);
|
| 72 |
let _ = writeln!(
|
| 73 |
out,
|
| 74 |
" eliminated={} alliance={} worm_count={} active_worm={}",
|
| 75 |
-
header.eliminated,
|
|
|
|
|
|
|
|
|
|
| 76 |
);
|
| 77 |
let _ = writeln!(
|
| 78 |
out,
|
| 79 |
" weapon_alliance={} turn_action_flags=0x{:08X}",
|
| 80 |
-
header.weapon_alliance,
|
|
|
|
| 81 |
);
|
| 82 |
|
| 83 |
// Worms are in block[t+1].worms[0..worm_count] (1-indexed blocks)
|
| 84 |
-
let worm_count = header.worm_count.max(0) as usize;
|
| 85 |
let block = &*blocks.add(t + 1);
|
| 86 |
for wi in 0..worm_count.min(7) {
|
| 87 |
let worm = &block.worms[wi];
|
|
|
|
| 19 |
/// Must be called from the DLL while the game is paused (frame breakpoint).
|
| 20 |
#[cfg(target_arch = "x86")]
|
| 21 |
pub unsafe fn capture() -> String {
|
| 22 |
+
use openwa_core::engine::TeamArena;
|
| 23 |
|
| 24 |
let mut out = String::with_capacity(128 * 1024);
|
| 25 |
|
|
|
|
| 62 |
// ββ Team blocks + worm entries ββ
|
| 63 |
let team_count = (*ddgame).team_arena.team_count as usize;
|
| 64 |
let _ = writeln!(out, "[Teams] count={}", team_count);
|
| 65 |
+
let arena = &raw mut (*ddgame).team_arena;
|
| 66 |
+
let blocks = TeamArena::blocks_mut(arena);
|
| 67 |
for t in 0..team_count {
|
| 68 |
+
let header = TeamArena::team_header_mut(arena, t);
|
| 69 |
let name =
|
| 70 |
+
core::ffi::CStr::from_ptr((*header).team_name.as_ptr() as *const _).to_string_lossy();
|
| 71 |
let _ = writeln!(out, "\n [Team {}] \"{}\"", t, name);
|
| 72 |
let _ = writeln!(
|
| 73 |
out,
|
| 74 |
" eliminated={} alliance={} worm_count={} active_worm={}",
|
| 75 |
+
(*header).eliminated,
|
| 76 |
+
(*header).alliance,
|
| 77 |
+
(*header).worm_count,
|
| 78 |
+
(*header).active_worm
|
| 79 |
);
|
| 80 |
let _ = writeln!(
|
| 81 |
out,
|
| 82 |
" weapon_alliance={} turn_action_flags=0x{:08X}",
|
| 83 |
+
(*header).weapon_alliance,
|
| 84 |
+
(*header).turn_action_flags
|
| 85 |
);
|
| 86 |
|
| 87 |
// Worms are in block[t+1].worms[0..worm_count] (1-indexed blocks)
|
| 88 |
+
let worm_count = (*header).worm_count.max(0) as usize;
|
| 89 |
let block = &*blocks.add(t + 1);
|
| 90 |
for wi in 0..worm_count.min(7) {
|
| 91 |
let worm = &block.worms[wi];
|