refactor: propagate types and named fields in game_state_init
Browse filesSplit _unknown blocks in DDGame (28 new fields) and GameInfo (17 new
fields) to replace raw pointer arithmetic with named struct access.
Retype game_speed, level bounds, and render_scale to Fixed. Change
game_info_sub parameters from *const u8 to *mut GameInfo across all
init sub-functions, eliminating the untyped pointer pattern.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
crates/openwa-core/src/engine/ddgame.rs
CHANGED
|
@@ -131,35 +131,67 @@ pub struct DDGame {
|
|
| 131 |
/// 0x54C: CTaskLand pointer (set by CTaskLand__InitLandscape at 0x5056F0).
|
| 132 |
/// The landscape/terrain task. Vtable at 0x664388.
|
| 133 |
pub task_land: *mut u8,
|
| 134 |
-
/// 0x550-
|
| 135 |
-
|
| 136 |
-
///
|
| 137 |
-
///
|
| 138 |
-
|
| 139 |
-
|
|
|
|
| 140 |
|
| 141 |
/// 0x5CC: Game frame counter. Incremented at end of each frame in
|
| 142 |
/// GameFrameDispatcher. Compared against GameInfo+0xF344 (sound start
|
| 143 |
/// threshold) by IsSoundSuppressed and DispatchGlobalSound.
|
| 144 |
pub frame_counter: i32,
|
| 145 |
|
| 146 |
-
/// 0x5D0
|
| 147 |
-
|
| 148 |
-
///
|
| 149 |
-
|
| 150 |
-
///
|
| 151 |
-
|
| 152 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 153 |
|
| 154 |
/// 0x2600-0x2DFF: Block of 0xFFFFFFFF values at runtime (512 i32 entries).
|
| 155 |
/// May be unused slots in a parallel table to the 0x600 permutation.
|
| 156 |
pub _unknown_2600: [u8; 0x2E00 - 0x2600],
|
| 157 |
|
| 158 |
-
/// 0x2E00-
|
| 159 |
///
|
| 160 |
/// Contains FUN_00526120 zeroed offsets at stride 0x194:
|
| 161 |
/// 0x379C, 0x3930, 0x3AC4, 0x3C58, 0x3DEC, 0x3F80, 0x4114, 0x42A8, 0x443C, 0x45D0
|
| 162 |
-
pub _unknown_2e00: [u8;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 163 |
|
| 164 |
/// 0x45EC: Game RNG state. Advanced each frame and by weapon spread:
|
| 165 |
/// `rng = (frame_counter + rng) * 0x19660D + 0x3C6EF35F`.
|
|
@@ -193,10 +225,10 @@ pub struct DDGame {
|
|
| 193 |
/// 0x72A8-0x72D7: Unknown
|
| 194 |
pub _unknown_72a8: [u8; 0x72D8 - 0x72A8],
|
| 195 |
|
| 196 |
-
/// 0x72D8: Game speed multiplier (
|
| 197 |
-
pub game_speed:
|
| 198 |
-
/// 0x72DC: Game speed target (
|
| 199 |
-
pub game_speed_target:
|
| 200 |
/// 0x72E0-0x72E3: Unknown
|
| 201 |
pub _unknown_72e0: u32,
|
| 202 |
/// 0x72E4: Active render/weapon slot count. Reset to 14 (0x0E) by WeaponRelease
|
|
@@ -237,8 +269,11 @@ pub struct DDGame {
|
|
| 237 |
pub _unknown_7344: [u8; 8],
|
| 238 |
/// 0x734C: Landscape dimension param (passed to PCLandscape vtable slot 6).
|
| 239 |
pub _field_734c: u32,
|
| 240 |
-
/// 0x7350-
|
| 241 |
-
pub _unknown_7350: [u8;
|
|
|
|
|
|
|
|
|
|
| 242 |
/// 0x7374: Unknown (zeroed by InitTurnState).
|
| 243 |
pub _field_7374: u32,
|
| 244 |
/// 0x7378: Unknown (zeroed by InitTurnState).
|
|
@@ -254,8 +289,12 @@ pub struct DDGame {
|
|
| 254 |
pub viewport_width_2: i32,
|
| 255 |
/// 0x738C: Viewport height max/duplicate (Fixed-point).
|
| 256 |
pub viewport_height_2: i32,
|
| 257 |
-
/// 0x7390
|
| 258 |
-
pub
|
|
|
|
|
|
|
|
|
|
|
|
|
| 259 |
/// 0x739C: Render state flag (zeroed by InitRenderIndices).
|
| 260 |
pub render_state_flag: u32,
|
| 261 |
|
|
@@ -271,8 +310,14 @@ pub struct DDGame {
|
|
| 271 |
/// 0x73B0: Render entry table (14 entries × 0x14 bytes).
|
| 272 |
/// First u32 of each entry zeroed by InitRenderIndices.
|
| 273 |
pub render_entries: [RenderEntry; 14],
|
| 274 |
-
/// 0x74C8-
|
| 275 |
-
pub _unknown_74c8: [u8;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 276 |
/// 0x764C: Rendering phase. Checked by CTaskCloud::HandleMessage:
|
| 277 |
/// clouds only render when this == 5 (in-game rendering active).
|
| 278 |
pub render_phase: i32,
|
|
@@ -289,28 +334,31 @@ pub struct DDGame {
|
|
| 289 |
pub _field_7784: u32,
|
| 290 |
/// 0x7788: Unknown (set from GameInfo+0xF362 during InitTurnState).
|
| 291 |
pub _field_7788: u32,
|
| 292 |
-
/// 0x778C: Unknown (set to Fixed 1.0
|
| 293 |
-
pub _field_778c:
|
| 294 |
/// 0x7790: Unknown (zeroed by InitTurnState).
|
| 295 |
pub _field_7790: u32,
|
| 296 |
-
/// 0x7794
|
| 297 |
-
pub
|
|
|
|
|
|
|
| 298 |
|
| 299 |
/// 0x779C: Level bound min X (Fixed16.16, negative = off-screen left).
|
| 300 |
pub level_bound_min_x: Fixed,
|
| 301 |
/// 0x77A0: Level bound max X (Fixed16.16).
|
| 302 |
pub level_bound_max_x: Fixed,
|
| 303 |
-
/// 0x77A4: Level bound min Y (
|
| 304 |
-
pub level_bound_min_y:
|
| 305 |
-
/// 0x77A8: Level bound max Y (
|
| 306 |
-
pub level_bound_max_y:
|
| 307 |
/// 0x77AC-0x77B7: Unknown
|
| 308 |
pub _unknown_77ac: [u8; 0x77B8 - 0x77AC],
|
| 309 |
/// 0x77B8: Level width for 3D sound distance computation (pixels, not fixed-point).
|
| 310 |
/// Read by ComputeDistanceParams, shifted left 16 before passing to Distance3D_Attenuation.
|
| 311 |
pub level_width_sound: i32,
|
| 312 |
-
/// 0x77BC
|
| 313 |
-
|
|
|
|
| 314 |
|
| 315 |
/// 0x77C0: Level width in pixels (set by PCLandscape constructor).
|
| 316 |
pub level_width: u32,
|
|
@@ -319,8 +367,10 @@ pub struct DDGame {
|
|
| 319 |
/// 0x77C8: Total pixels (width × height).
|
| 320 |
pub level_total_pixels: u32,
|
| 321 |
|
| 322 |
-
/// 0x77CC
|
| 323 |
-
pub
|
|
|
|
|
|
|
| 324 |
/// 0x77D4: Unknown (zeroed by InitTurnState).
|
| 325 |
pub _field_77d4: u32,
|
| 326 |
/// 0x77D8: Unknown (zeroed by InitTurnState).
|
|
@@ -714,9 +764,9 @@ impl crate::snapshot::Snapshot for DDGame {
|
|
| 714 |
write_indent(w, i)?;
|
| 715 |
writeln!(w, "frame_counter = {}", self.frame_counter)?;
|
| 716 |
write_indent(w, i)?;
|
| 717 |
-
writeln!(w, "game_speed = {}",
|
| 718 |
write_indent(w, i)?;
|
| 719 |
-
writeln!(w, "game_speed_target = {}",
|
| 720 |
write_indent(w, i)?;
|
| 721 |
writeln!(
|
| 722 |
w,
|
|
|
|
| 131 |
/// 0x54C: CTaskLand pointer (set by CTaskLand__InitLandscape at 0x5056F0).
|
| 132 |
/// The landscape/terrain task. Vtable at 0x664388.
|
| 133 |
pub task_land: *mut u8,
|
| 134 |
+
/// 0x550-0x5C3: Unknown.
|
| 135 |
+
pub _unknown_550: [u8; 0x5C4 - 0x550],
|
| 136 |
+
/// 0x5C4: Vector normalize function pointer. Selected by game version in InitGameState:
|
| 137 |
+
/// < 0x99 → VECTOR_NORMALIZE_SIMPLE (0x5755D0), >= 0x99 → VECTOR_NORMALIZE_OVERFLOW.
|
| 138 |
+
pub vector_normalize_fn: u32,
|
| 139 |
+
/// 0x5C8: Stack height config. Version-dependent: < -1 → 0x700, >= -1 → 0x800.
|
| 140 |
+
pub stack_height: u32,
|
| 141 |
|
| 142 |
/// 0x5CC: Game frame counter. Incremented at end of each frame in
|
| 143 |
/// GameFrameDispatcher. Compared against GameInfo+0xF344 (sound start
|
| 144 |
/// threshold) by IsSoundSuppressed and DispatchGlobalSound.
|
| 145 |
pub frame_counter: i32,
|
| 146 |
|
| 147 |
+
/// 0x5D0: Frame processing flag (set 0/1 by GameFrameEndProcessor, zeroed by InitGameState).
|
| 148 |
+
pub _field_5d0: u32,
|
| 149 |
+
/// 0x5D4: Unknown (zeroed by InitGameState).
|
| 150 |
+
pub _field_5d4: u32,
|
| 151 |
+
/// 0x5D8: Unknown (zeroed by InitGameState).
|
| 152 |
+
pub _field_5d8: u32,
|
| 153 |
+
/// 0x5DC: Unknown (zeroed by InitGameState).
|
| 154 |
+
pub _field_5dc: u32,
|
| 155 |
+
/// 0x5E0: Water level in pixels. Computed as `(100 - water_pct) * level_height / 100`.
|
| 156 |
+
pub water_level: i32,
|
| 157 |
+
/// 0x5E4: Water kill Y boundary (pixels). Derived from level_bound_max_y integer part + 0x28.
|
| 158 |
+
pub water_kill_y: i32,
|
| 159 |
+
/// 0x5E8: Initial water level (copy of water_level at init time).
|
| 160 |
+
pub water_level_initial: i32,
|
| 161 |
+
/// 0x5EC: Unknown (zeroed by InitGameState).
|
| 162 |
+
pub _field_5ec: u32,
|
| 163 |
+
/// 0x5F0: Unknown (set to 1 by InitGameState).
|
| 164 |
+
pub _field_5f0: u32,
|
| 165 |
+
/// 0x5F4: Unknown (set to 100 by InitGameState).
|
| 166 |
+
pub _field_5f4: u32,
|
| 167 |
+
/// 0x5F8: Unknown (zeroed by InitGameState).
|
| 168 |
+
pub _field_5f8: u32,
|
| 169 |
+
/// 0x5FC: Unknown (zeroed by InitGameState).
|
| 170 |
+
pub _field_5fc: u32,
|
| 171 |
+
/// 0x600-0x25FF: Identity permutation [0,1,2,...,~2048] — SpriteGfxTable.
|
| 172 |
+
pub _unknown_600: [u8; 0x2600 - 0x600],
|
| 173 |
|
| 174 |
/// 0x2600-0x2DFF: Block of 0xFFFFFFFF values at runtime (512 i32 entries).
|
| 175 |
/// May be unused slots in a parallel table to the 0x600 permutation.
|
| 176 |
pub _unknown_2600: [u8; 0x2E00 - 0x2600],
|
| 177 |
|
| 178 |
+
/// 0x2E00-0x45D3: Unknown (mostly zero at runtime).
|
| 179 |
///
|
| 180 |
/// Contains FUN_00526120 zeroed offsets at stride 0x194:
|
| 181 |
/// 0x379C, 0x3930, 0x3AC4, 0x3C58, 0x3DEC, 0x3F80, 0x4114, 0x42A8, 0x443C, 0x45D0
|
| 182 |
+
pub _unknown_2e00: [u8; 0x45D4 - 0x2E00],
|
| 183 |
+
/// 0x45D4: Terrain drop percentage A (from GameInfo+0xD955).
|
| 184 |
+
pub terrain_pct_a: u32,
|
| 185 |
+
/// 0x45D8: Terrain drop percentage B (from GameInfo+0xD958).
|
| 186 |
+
pub terrain_pct_b: u32,
|
| 187 |
+
/// 0x45DC: Terrain drop percentage C (from GameInfo+0xD957).
|
| 188 |
+
pub terrain_pct_c: u32,
|
| 189 |
+
/// 0x45E0: Unknown (zeroed by InitGameState version config).
|
| 190 |
+
pub _field_45e0: u32,
|
| 191 |
+
/// 0x45E4: Unknown (zeroed by InitGameState version config).
|
| 192 |
+
pub _field_45e4: u32,
|
| 193 |
+
/// 0x45E8: Unknown (zeroed by InitGameState version config).
|
| 194 |
+
pub _field_45e8: u32,
|
| 195 |
|
| 196 |
/// 0x45EC: Game RNG state. Advanced each frame and by weapon spread:
|
| 197 |
/// `rng = (frame_counter + rng) * 0x19660D + 0x3C6EF35F`.
|
|
|
|
| 225 |
/// 0x72A8-0x72D7: Unknown
|
| 226 |
pub _unknown_72a8: [u8; 0x72D8 - 0x72A8],
|
| 227 |
|
| 228 |
+
/// 0x72D8: Game speed multiplier (Fixed16.16, 1.0 = normal speed).
|
| 229 |
+
pub game_speed: Fixed,
|
| 230 |
+
/// 0x72DC: Game speed target (Fixed16.16, 1.0 = normal speed).
|
| 231 |
+
pub game_speed_target: Fixed,
|
| 232 |
/// 0x72E0-0x72E3: Unknown
|
| 233 |
pub _unknown_72e0: u32,
|
| 234 |
/// 0x72E4: Active render/weapon slot count. Reset to 14 (0x0E) by WeaponRelease
|
|
|
|
| 269 |
pub _unknown_7344: [u8; 8],
|
| 270 |
/// 0x734C: Landscape dimension param (passed to PCLandscape vtable slot 6).
|
| 271 |
pub _field_734c: u32,
|
| 272 |
+
/// 0x7350-0x736F: Unknown
|
| 273 |
+
pub _unknown_7350: [u8; 0x7370 - 0x7350],
|
| 274 |
+
/// 0x7370: Current team color index. Set from GameInfo+0xD924;
|
| 275 |
+
/// overridden to -1 (0xFFFFFFFF) if any CPU team is detected.
|
| 276 |
+
pub team_color: u32,
|
| 277 |
/// 0x7374: Unknown (zeroed by InitTurnState).
|
| 278 |
pub _field_7374: u32,
|
| 279 |
/// 0x7378: Unknown (zeroed by InitTurnState).
|
|
|
|
| 289 |
pub viewport_width_2: i32,
|
| 290 |
/// 0x738C: Viewport height max/duplicate (Fixed-point).
|
| 291 |
pub viewport_height_2: i32,
|
| 292 |
+
/// 0x7390: Unknown (zeroed by InitGameState).
|
| 293 |
+
pub _field_7390: u32,
|
| 294 |
+
/// 0x7394: Render scale factor (Fixed-point, 0x10000 = 1.0).
|
| 295 |
+
pub render_scale: Fixed,
|
| 296 |
+
/// 0x7398: Unknown (zeroed by InitGameState).
|
| 297 |
+
pub _field_7398: u32,
|
| 298 |
/// 0x739C: Render state flag (zeroed by InitRenderIndices).
|
| 299 |
pub render_state_flag: u32,
|
| 300 |
|
|
|
|
| 310 |
/// 0x73B0: Render entry table (14 entries × 0x14 bytes).
|
| 311 |
/// First u32 of each entry zeroed by InitRenderIndices.
|
| 312 |
pub render_entries: [RenderEntry; 14],
|
| 313 |
+
/// 0x74C8-0x763F: Unknown
|
| 314 |
+
pub _unknown_74c8: [u8; 0x7640 - 0x74C8],
|
| 315 |
+
/// 0x7640: Unknown (zeroed by InitGameState).
|
| 316 |
+
pub _field_7640: u32,
|
| 317 |
+
/// 0x7644: Unknown config (from GameInfo+0xF363).
|
| 318 |
+
pub _field_7644: u32,
|
| 319 |
+
/// 0x7648: Unknown config (from GameInfo+0xF364).
|
| 320 |
+
pub _field_7648: u32,
|
| 321 |
/// 0x764C: Rendering phase. Checked by CTaskCloud::HandleMessage:
|
| 322 |
/// clouds only render when this == 5 (in-game rendering active).
|
| 323 |
pub render_phase: i32,
|
|
|
|
| 334 |
pub _field_7784: u32,
|
| 335 |
/// 0x7788: Unknown (set from GameInfo+0xF362 during InitTurnState).
|
| 336 |
pub _field_7788: u32,
|
| 337 |
+
/// 0x778C: Unknown (set to Fixed 1.0 by InitTurnState).
|
| 338 |
+
pub _field_778c: Fixed,
|
| 339 |
/// 0x7790: Unknown (zeroed by InitTurnState).
|
| 340 |
pub _field_7790: u32,
|
| 341 |
+
/// 0x7794: Unknown (zeroed by InitGameState level bounds).
|
| 342 |
+
pub _field_7794: u32,
|
| 343 |
+
/// 0x7798: Unknown (zeroed by InitGameState level bounds).
|
| 344 |
+
pub _field_7798: u32,
|
| 345 |
|
| 346 |
/// 0x779C: Level bound min X (Fixed16.16, negative = off-screen left).
|
| 347 |
pub level_bound_min_x: Fixed,
|
| 348 |
/// 0x77A0: Level bound max X (Fixed16.16).
|
| 349 |
pub level_bound_max_x: Fixed,
|
| 350 |
+
/// 0x77A4: Level bound min Y (Fixed16.16).
|
| 351 |
+
pub level_bound_min_y: Fixed,
|
| 352 |
+
/// 0x77A8: Level bound max Y (Fixed16.16).
|
| 353 |
+
pub level_bound_max_y: Fixed,
|
| 354 |
/// 0x77AC-0x77B7: Unknown
|
| 355 |
pub _unknown_77ac: [u8; 0x77B8 - 0x77AC],
|
| 356 |
/// 0x77B8: Level width for 3D sound distance computation (pixels, not fixed-point).
|
| 357 |
/// Read by ComputeDistanceParams, shifted left 16 before passing to Distance3D_Attenuation.
|
| 358 |
pub level_width_sound: i32,
|
| 359 |
+
/// 0x77BC: Screen height in pixels (output from display vtable[1]).
|
| 360 |
+
/// Read by InitGameState for resolution-dependent layout.
|
| 361 |
+
pub screen_height_pixels: i32,
|
| 362 |
|
| 363 |
/// 0x77C0: Level width in pixels (set by PCLandscape constructor).
|
| 364 |
pub level_width: u32,
|
|
|
|
| 367 |
/// 0x77C8: Total pixels (width × height).
|
| 368 |
pub level_total_pixels: u32,
|
| 369 |
|
| 370 |
+
/// 0x77CC: Map boundary width. Default 0x30D4; updated to `level_w + 0x2954` for version > 0x32.
|
| 371 |
+
pub map_boundary_width: u32,
|
| 372 |
+
/// 0x77D0: Map boundary height. Default = level_height; updated to 0x2B8 for version > 0x32.
|
| 373 |
+
pub map_boundary_height: u32,
|
| 374 |
/// 0x77D4: Unknown (zeroed by InitTurnState).
|
| 375 |
pub _field_77d4: u32,
|
| 376 |
/// 0x77D8: Unknown (zeroed by InitTurnState).
|
|
|
|
| 764 |
write_indent(w, i)?;
|
| 765 |
writeln!(w, "frame_counter = {}", self.frame_counter)?;
|
| 766 |
write_indent(w, i)?;
|
| 767 |
+
writeln!(w, "game_speed = {}", self.game_speed)?;
|
| 768 |
write_indent(w, i)?;
|
| 769 |
+
writeln!(w, "game_speed_target = {}", self.game_speed_target)?;
|
| 770 |
write_indent(w, i)?;
|
| 771 |
writeln!(
|
| 772 |
w,
|
crates/openwa-core/src/engine/game_info.rs
CHANGED
|
@@ -35,13 +35,25 @@ pub struct GameInfo {
|
|
| 35 |
pub scoring_param_a: u16,
|
| 36 |
/// 0xD78A: Scoring parameter B (multiplied by 50 for initial score).
|
| 37 |
pub scoring_param_b: u16,
|
| 38 |
-
/// 0xD78C-
|
| 39 |
-
pub _unknown_d78c: [u8;
|
|
|
|
|
|
|
|
|
|
|
|
|
| 40 |
/// 0xD932: DoubleTurnTime availability threshold (u16).
|
| 41 |
/// If game_version > 0xD1 and this > 0x7FFF, DoubleTurnTime is disabled.
|
| 42 |
pub double_turn_time_threshold: u16,
|
| 43 |
-
/// 0xD934-
|
| 44 |
-
pub _unknown_d934: [u8;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 45 |
/// 0xD93C: Super weapon allowed flag. If 0, super weapons are
|
| 46 |
/// disabled (except when game_version < 0x2A).
|
| 47 |
pub super_weapon_allowed: u8,
|
|
@@ -61,21 +73,41 @@ pub struct GameInfo {
|
|
| 61 |
pub landscape_scheme_flag: u8,
|
| 62 |
/// 0xD94C: Donkey (weapon 0x36) disable flag.
|
| 63 |
pub donkey_disabled: u8,
|
| 64 |
-
/// 0xD94D-
|
| 65 |
-
pub _unknown_d94d: [u8;
|
|
|
|
|
|
|
| 66 |
/// 0xD956: When set, the AquaSheep slot is treated as SuperSheep instead.
|
| 67 |
pub aquasheep_is_supersheep: u8,
|
| 68 |
-
/// 0xD957
|
| 69 |
-
pub
|
|
|
|
|
|
|
| 70 |
/// 0xD959: Version-gated weapon restriction. If nonzero and game_version > 0x29,
|
| 71 |
/// returns -2 for unavailable weapons.
|
| 72 |
pub weapon_version_gate: u8,
|
| 73 |
-
/// 0xD95A-
|
| 74 |
-
pub _unknown_d95a: [u8;
|
| 75 |
-
///
|
| 76 |
-
pub
|
| 77 |
-
///
|
| 78 |
-
pub
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 79 |
/// 0xD9A2: Network weapon exception flag. When net_config_2 != 0,
|
| 80 |
/// weapons 10/0x37/0x38 are only disabled if this is also 0.
|
| 81 |
pub net_weapon_exception: u8,
|
|
@@ -96,8 +128,10 @@ pub struct GameInfo {
|
|
| 96 |
/// If nonzero, streaming audio subsystem is created in InitHardware.
|
| 97 |
pub speech_enabled: u8,
|
| 98 |
|
| 99 |
-
/// 0xDAA5-
|
| 100 |
-
pub _unknown_daa5: [u8;
|
|
|
|
|
|
|
| 101 |
|
| 102 |
/// 0xDAAC: Landscape data path (passed to PCLandscape constructor).
|
| 103 |
/// Points to a path string used for loading level terrain data.
|
|
@@ -115,8 +149,10 @@ pub struct GameInfo {
|
|
| 115 |
/// 0xDB08: Invisibility (weapon 0x42) mode flag (u32). Controls team-count
|
| 116 |
/// vs network_ecx check for availability.
|
| 117 |
pub invisibility_mode: u32,
|
| 118 |
-
/// 0xDB0C
|
| 119 |
-
pub
|
|
|
|
|
|
|
| 120 |
/// 0xDB1C: Replay map sub-type (first DWORD of first payload).
|
| 121 |
/// >= 1: map stored in playback.thm. Negative: inline map data.
|
| 122 |
pub replay_map_type: i32,
|
|
@@ -142,8 +178,10 @@ pub struct GameInfo {
|
|
| 142 |
pub _unknown_df60: [u8; 0xEF60 - 0xDF60],
|
| 143 |
/// 0xEF60: Cleared to 0 during replay loading.
|
| 144 |
pub replay_field_ef60: u32,
|
| 145 |
-
/// 0xEF64-
|
| 146 |
-
pub _unknown_ef64: [u8;
|
|
|
|
|
|
|
| 147 |
|
| 148 |
/// 0xF344: Sound start frame threshold (i32). Sound is suppressed when
|
| 149 |
/// DDGame.frame_counter < this value. Checked by IsSoundSuppressed and
|
|
@@ -154,12 +192,32 @@ pub struct GameInfo {
|
|
| 154 |
/// Checked by IsSoundSuppressed, DispatchGlobalSound, PlaySoundPooled_Direct.
|
| 155 |
pub sound_mute: u8,
|
| 156 |
|
| 157 |
-
/// 0xF349-
|
| 158 |
-
pub _unknown_f349: [u8;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 159 |
/// 0xF362: Unknown byte copied to DDGame+0x7788 during turn state init.
|
| 160 |
pub _field_f362: u8,
|
| 161 |
-
/// 0xF363
|
| 162 |
-
pub
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 163 |
|
| 164 |
/// 0xF374: Display flags passed to DisplayGfx::Init.
|
| 165 |
pub display_flags: u32,
|
|
@@ -250,6 +308,18 @@ pub struct GameInfo {
|
|
| 250 |
|
| 251 |
const _: () = assert!(core::mem::size_of::<GameInfo>() == 0xF91C);
|
| 252 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 253 |
struct HexU32s<'a>(&'a [u32]);
|
| 254 |
|
| 255 |
impl core::fmt::Debug for HexU32s<'_> {
|
|
|
|
| 35 |
pub scoring_param_a: u16,
|
| 36 |
/// 0xD78A: Scoring parameter B (multiplied by 50 for initial score).
|
| 37 |
pub scoring_param_b: u16,
|
| 38 |
+
/// 0xD78C-0xD923: Unknown
|
| 39 |
+
pub _unknown_d78c: [u8; 0xD924 - 0xD78C],
|
| 40 |
+
/// 0xD924: Starting team color index (u8). Copied to DDGame.team_color at init.
|
| 41 |
+
pub team_color_source: u8,
|
| 42 |
+
/// 0xD925-0xD931: Unknown
|
| 43 |
+
pub _unknown_d925: [u8; 0xD932 - 0xD925],
|
| 44 |
/// 0xD932: DoubleTurnTime availability threshold (u16).
|
| 45 |
/// If game_version > 0xD1 and this > 0x7FFF, DoubleTurnTime is disabled.
|
| 46 |
pub double_turn_time_threshold: u16,
|
| 47 |
+
/// 0xD934-0xD937: Unknown
|
| 48 |
+
pub _unknown_d934: [u8; 0xD938 - 0xD934],
|
| 49 |
+
/// 0xD938: Random crate drop percentage — land mines (u8, 0-100).
|
| 50 |
+
pub drop_pct_land: u8,
|
| 51 |
+
/// 0xD939: Random crate drop percentage — mines (u8, 0-100).
|
| 52 |
+
pub drop_pct_mine: u8,
|
| 53 |
+
/// 0xD93A: Random crate drop percentage — oil barrels (u8, 0-100).
|
| 54 |
+
pub drop_pct_barrel: u8,
|
| 55 |
+
/// 0xD93B: Unknown
|
| 56 |
+
pub _unknown_d93b: u8,
|
| 57 |
/// 0xD93C: Super weapon allowed flag. If 0, super weapons are
|
| 58 |
/// disabled (except when game_version < 0x2A).
|
| 59 |
pub super_weapon_allowed: u8,
|
|
|
|
| 73 |
pub landscape_scheme_flag: u8,
|
| 74 |
/// 0xD94C: Donkey (weapon 0x36) disable flag.
|
| 75 |
pub donkey_disabled: u8,
|
| 76 |
+
/// 0xD94D-0xD954: Unknown
|
| 77 |
+
pub _unknown_d94d: [u8; 0xD955 - 0xD94D],
|
| 78 |
+
/// 0xD955: Terrain drop config byte A. Copied to DDGame.terrain_pct_a.
|
| 79 |
+
pub terrain_cfg_a: u8,
|
| 80 |
/// 0xD956: When set, the AquaSheep slot is treated as SuperSheep instead.
|
| 81 |
pub aquasheep_is_supersheep: u8,
|
| 82 |
+
/// 0xD957: Terrain drop config byte C. Copied to DDGame.terrain_pct_c.
|
| 83 |
+
pub terrain_cfg_c: u8,
|
| 84 |
+
/// 0xD958: Terrain drop config byte B. Copied to DDGame.terrain_pct_b.
|
| 85 |
+
pub terrain_cfg_b: u8,
|
| 86 |
/// 0xD959: Version-gated weapon restriction. If nonzero and game_version > 0x29,
|
| 87 |
/// returns -2 for unavailable weapons.
|
| 88 |
pub weapon_version_gate: u8,
|
| 89 |
+
/// 0xD95A-0xD967: Unknown
|
| 90 |
+
pub _unknown_d95a: [u8; 0xD968 - 0xD95A],
|
| 91 |
+
/// 0xD968: Extended team count (u16). Used for buffer allocation sizing.
|
| 92 |
+
pub num_teams_alloc: u16,
|
| 93 |
+
/// 0xD96A: Extended terrain drop percentage (u8, remainder after land/mine/barrel).
|
| 94 |
+
pub ext_terrain_pct: u8,
|
| 95 |
+
/// 0xD96B: Extended terrain drop type (u8). 0 = auto-fill remainder.
|
| 96 |
+
pub ext_terrain_type: u8,
|
| 97 |
+
/// 0xD96C-0xD987: Unknown
|
| 98 |
+
pub _unknown_d96c: [u8; 0xD988 - 0xD96C],
|
| 99 |
+
/// 0xD988: Game speed config (Fixed16.16). Read as i32 for headful game_speed_target.
|
| 100 |
+
/// Note: high byte (0xD98B) overlaps with terrain_flag — use `terrain_flag` accessor.
|
| 101 |
+
pub game_speed_config: i32,
|
| 102 |
+
/// 0xD98C-0xD98F: Unknown
|
| 103 |
+
pub _unknown_d98c: [u8; 0xD990 - 0xD98C],
|
| 104 |
+
/// 0xD990: Team slot allocation count (u32). Used to size DDGame+0x514 array.
|
| 105 |
+
pub team_slot_count: u32,
|
| 106 |
+
/// 0xD994: Object slot allocation count (u32). Used to size DDGame+0x518 array
|
| 107 |
+
/// and buffer object allocation.
|
| 108 |
+
pub object_slot_count: u32,
|
| 109 |
+
/// 0xD998-0xD9A1: Unknown
|
| 110 |
+
pub _unknown_d998: [u8; 0xD9A2 - 0xD998],
|
| 111 |
/// 0xD9A2: Network weapon exception flag. When net_config_2 != 0,
|
| 112 |
/// weapons 10/0x37/0x38 are only disabled if this is also 0.
|
| 113 |
pub net_weapon_exception: u8,
|
|
|
|
| 128 |
/// If nonzero, streaming audio subsystem is created in InitHardware.
|
| 129 |
pub speech_enabled: u8,
|
| 130 |
|
| 131 |
+
/// 0xDAA5-0xDAA7: Unknown
|
| 132 |
+
pub _unknown_daa5: [u8; 0xDAA8 - 0xDAA5],
|
| 133 |
+
/// 0xDAA8: Turn percentage raw value (i32). Converted to fixed-point: `(val << 16) / 100`.
|
| 134 |
+
pub turn_percentage_raw: i32,
|
| 135 |
|
| 136 |
/// 0xDAAC: Landscape data path (passed to PCLandscape constructor).
|
| 137 |
/// Points to a path string used for loading level terrain data.
|
|
|
|
| 149 |
/// 0xDB08: Invisibility (weapon 0x42) mode flag (u32). Controls team-count
|
| 150 |
/// vs network_ecx check for availability.
|
| 151 |
pub invisibility_mode: u32,
|
| 152 |
+
/// 0xDB0C: Replay/network config flag (u8). Checked by InitGameState.
|
| 153 |
+
pub replay_config_flag: u8,
|
| 154 |
+
/// 0xDB0D-0xDB1B: Unknown
|
| 155 |
+
pub _unknown_db0d: [u8; 0xDB1C - 0xDB0D],
|
| 156 |
/// 0xDB1C: Replay map sub-type (first DWORD of first payload).
|
| 157 |
/// >= 1: map stored in playback.thm. Negative: inline map data.
|
| 158 |
pub replay_map_type: i32,
|
|
|
|
| 178 |
pub _unknown_df60: [u8; 0xEF60 - 0xDF60],
|
| 179 |
/// 0xEF60: Cleared to 0 during replay loading.
|
| 180 |
pub replay_field_ef60: u32,
|
| 181 |
+
/// 0xEF64-0xF33F: Unknown
|
| 182 |
+
pub _unknown_ef64: [u8; 0xF340 - 0xEF64],
|
| 183 |
+
/// 0xF340: State flag. InitGameState: `(this != 0) - 1` → wrapper+0xEC.
|
| 184 |
+
pub _field_f340: u32,
|
| 185 |
|
| 186 |
/// 0xF344: Sound start frame threshold (i32). Sound is suppressed when
|
| 187 |
/// DDGame.frame_counter < this value. Checked by IsSoundSuppressed and
|
|
|
|
| 192 |
/// Checked by IsSoundSuppressed, DispatchGlobalSound, PlaySoundPooled_Direct.
|
| 193 |
pub sound_mute: u8,
|
| 194 |
|
| 195 |
+
/// 0xF349-0xF34B: Unknown
|
| 196 |
+
pub _unknown_f349: [u8; 0xF34C - 0xF349],
|
| 197 |
+
/// 0xF34C: State field (i32). Set to -1 by InitGameState.
|
| 198 |
+
pub _field_f34c: i32,
|
| 199 |
+
/// 0xF350-0xF360: Unknown
|
| 200 |
+
pub _unknown_f350: [u8; 0xF361 - 0xF350],
|
| 201 |
+
/// 0xF361: Render phase config byte. Copied to DDGame.render_phase at init.
|
| 202 |
+
pub render_phase_cfg: u8,
|
| 203 |
/// 0xF362: Unknown byte copied to DDGame+0x7788 during turn state init.
|
| 204 |
pub _field_f362: u8,
|
| 205 |
+
/// 0xF363: Config byte. Copied to DDGame._field_7644 at init.
|
| 206 |
+
pub _field_f363: u8,
|
| 207 |
+
/// 0xF364: Config byte. Copied to DDGame._field_7648 at init.
|
| 208 |
+
pub _field_f364: u8,
|
| 209 |
+
/// 0xF365: Terrain config flag (u8). Bool-ified to wrapper+0x414.
|
| 210 |
+
pub _field_f365: u8,
|
| 211 |
+
/// 0xF366-0xF367: Unknown
|
| 212 |
+
pub _unknown_f366: [u8; 0xF368 - 0xF366],
|
| 213 |
+
/// 0xF368: Worm selection count config (i32). 0 = use default.
|
| 214 |
+
pub worm_select_cfg_a: i32,
|
| 215 |
+
/// 0xF36C: Worm selection count alt config (i32). -1 = use default (7).
|
| 216 |
+
pub worm_select_cfg_b: i32,
|
| 217 |
+
/// 0xF370: Display palette flag (u8). Bool-ified to palette+0x10 if headful.
|
| 218 |
+
pub _field_f370: u8,
|
| 219 |
+
/// 0xF371-0xF373: Unknown
|
| 220 |
+
pub _unknown_f371: [u8; 0xF374 - 0xF371],
|
| 221 |
|
| 222 |
/// 0xF374: Display flags passed to DisplayGfx::Init.
|
| 223 |
pub display_flags: u32,
|
|
|
|
| 308 |
|
| 309 |
const _: () = assert!(core::mem::size_of::<GameInfo>() == 0xF91C);
|
| 310 |
|
| 311 |
+
impl GameInfo {
|
| 312 |
+
/// Access terrain_flag at offset 0xD98B (high byte of game_speed_config).
|
| 313 |
+
/// Set from map object during replay loading: 0 = cavern terrain.
|
| 314 |
+
pub fn terrain_flag(&self) -> u8 {
|
| 315 |
+
(self.game_speed_config >> 24) as u8
|
| 316 |
+
}
|
| 317 |
+
/// Set terrain_flag (high byte of game_speed_config at 0xD98B).
|
| 318 |
+
pub fn set_terrain_flag(&mut self, val: u8) {
|
| 319 |
+
self.game_speed_config = (self.game_speed_config & 0x00FF_FFFF) | ((val as i32) << 24);
|
| 320 |
+
}
|
| 321 |
+
}
|
| 322 |
+
|
| 323 |
struct HexU32s<'a>(&'a [u32]);
|
| 324 |
|
| 325 |
impl core::fmt::Debug for HexU32s<'_> {
|
crates/openwa-core/src/engine/game_state_init.rs
CHANGED
|
@@ -7,6 +7,7 @@
|
|
| 7 |
use crate::engine::ddgame::DDGame;
|
| 8 |
use crate::engine::ddgame_wrapper::DDGameWrapper;
|
| 9 |
use crate::engine::game_info::GameInfo;
|
|
|
|
| 10 |
use crate::wa_alloc::wa_malloc;
|
| 11 |
|
| 12 |
/// Pure Rust implementation of SpriteGfxTable__Init (0x541620).
|
|
@@ -478,7 +479,7 @@ pub unsafe fn init_turn_state(wrapper: *mut u8) {
|
|
| 478 |
|
| 479 |
// DDGame._field_7788 = game_info._field_f362 (byte → u32)
|
| 480 |
(*ddgame)._field_7788 = (*game_info)._field_f362 as u32;
|
| 481 |
-
(*ddgame)._field_778c =
|
| 482 |
(*ddgame)._field_7790 = 0;
|
| 483 |
|
| 484 |
// Camera center: (level_width << 16) / 2, (level_height << 16) / 2
|
|
@@ -603,14 +604,14 @@ pub unsafe fn init_game_state(wrapper: *mut DDGameWrapper) {
|
|
| 603 |
let ddgame_raw = ddgame as *mut u8;
|
| 604 |
|
| 605 |
// ===== Copy replay mode flags from GameInfo =====
|
| 606 |
-
let
|
| 607 |
-
(*wrapper).replay_flag_a =
|
| 608 |
-
(*wrapper).replay_flag_b = *
|
| 609 |
|
| 610 |
// ===== SpriteGfxTable__Init =====
|
| 611 |
// ECX = DDGame+0x600, EDX = version-dependent count
|
| 612 |
{
|
| 613 |
-
let game_version =
|
| 614 |
let count: u32 = if game_version >= 0x3C { 0x400 } else { 0x100 };
|
| 615 |
sprite_gfx_table_init(ddgame_raw.add(0x600) as *mut u8, count);
|
| 616 |
}
|
|
@@ -702,7 +703,7 @@ pub unsafe fn init_game_state(wrapper: *mut DDGameWrapper) {
|
|
| 702 |
|
| 703 |
// ===== Allocate BufferObject (main_buffer at wrapper+0x0C) =====
|
| 704 |
{
|
| 705 |
-
let buf = allocate_buffer_object(ddgame_raw,
|
| 706 |
(*wrapper).main_buffer = buf;
|
| 707 |
}
|
| 708 |
|
|
@@ -725,7 +726,7 @@ pub unsafe fn init_game_state(wrapper: *mut DDGameWrapper) {
|
|
| 725 |
}
|
| 726 |
|
| 727 |
// ===== Allocate state buffer (wrapper+0x48) =====
|
| 728 |
-
(*wrapper).state_buffer = allocate_buffer_object(ddgame_raw,
|
| 729 |
|
| 730 |
// ===== Allocate statistics object (0xB94 bytes, wrapper+0x4C) =====
|
| 731 |
{
|
|
@@ -763,13 +764,12 @@ pub unsafe fn init_game_state(wrapper: *mut DDGameWrapper) {
|
|
| 763 |
|
| 764 |
// ===== Select vector normalize function based on game version =====
|
| 765 |
{
|
| 766 |
-
let game_version =
|
| 767 |
-
|
| 768 |
rb(va::VECTOR_NORMALIZE_SIMPLE)
|
| 769 |
} else {
|
| 770 |
rb(va::VECTOR_NORMALIZE_OVERFLOW)
|
| 771 |
};
|
| 772 |
-
*(ddgame_raw.add(0x5C4) as *mut u32) = func_ptr;
|
| 773 |
}
|
| 774 |
|
| 775 |
// ===== Initialize sentinel fields =====
|
|
@@ -789,7 +789,7 @@ pub unsafe fn init_game_state(wrapper: *mut DDGameWrapper) {
|
|
| 789 |
|
| 790 |
// ===== Resolution-dependent team render indices =====
|
| 791 |
{
|
| 792 |
-
let screen_height =
|
| 793 |
if screen_height < 0x2D0 {
|
| 794 |
// < 720px: smaller layout
|
| 795 |
*(w.add(0x2B0) as *mut i32) = 0xC;
|
|
@@ -831,8 +831,8 @@ pub unsafe fn init_game_state(wrapper: *mut DDGameWrapper) {
|
|
| 831 |
|
| 832 |
// ===== Team-to-slot mapping (conditional on network/replay mode) =====
|
| 833 |
if *(ddgame_raw.add(0x1C) as *const u32) != 0 || (*wrapper).replay_flag_a != 0 {
|
| 834 |
-
let team_count = *
|
| 835 |
-
let exclude_team =
|
| 836 |
let mut slot_idx = 0i32;
|
| 837 |
let pivar8_base = w.add(0x38C) as *mut i32;
|
| 838 |
let pivar7_base = w.add(0x3BC) as *mut i32;
|
|
@@ -866,11 +866,11 @@ pub unsafe fn init_game_state(wrapper: *mut DDGameWrapper) {
|
|
| 866 |
*(w.add(0x418) as *mut u32) = 0;
|
| 867 |
|
| 868 |
// Turn percentage: (game_info.turn_percentage << 16) / 100
|
| 869 |
-
let turn_pct_raw =
|
| 870 |
(*wrapper).turn_percentage = (turn_pct_raw << 16) / 100;
|
| 871 |
|
| 872 |
// ===== Display setup (headful only) =====
|
| 873 |
-
let is_headful =
|
| 874 |
|
| 875 |
if is_headful {
|
| 876 |
// The headful display initialization involves complex usercall patterns
|
|
@@ -878,17 +878,15 @@ pub unsafe fn init_game_state(wrapper: *mut DDGameWrapper) {
|
|
| 878 |
// ECX/EDX register params). For now, bridge to original display layer code
|
| 879 |
// via the existing ported DisplayGfx constructors.
|
| 880 |
// TODO: Port the headful display init with proper usercall bridges.
|
| 881 |
-
init_game_state_display(wrapper, w, ddgame_raw,
|
| 882 |
}
|
| 883 |
|
| 884 |
// ===== Worm selection count and terrain config =====
|
| 885 |
{
|
| 886 |
-
let
|
| 887 |
-
let worm_select_f368 = *(setup_data.add(0xF368) as *const i32);
|
| 888 |
let min_teams = *(w.add(0x2A8) as *const i32);
|
| 889 |
let team_count_cfg = *(w.add(0x2AC) as *const i32);
|
| 890 |
|
| 891 |
-
let mut val = worm_select_f368;
|
| 892 |
if val == 0 {
|
| 893 |
val = team_count_cfg;
|
| 894 |
} else if val < min_teams {
|
|
@@ -898,8 +896,7 @@ pub unsafe fn init_game_state(wrapper: *mut DDGameWrapper) {
|
|
| 898 |
}
|
| 899 |
*(w.add(0x2A0) as *mut i32) = val;
|
| 900 |
|
| 901 |
-
|
| 902 |
-
val = worm_select_f36c;
|
| 903 |
if val == -1 {
|
| 904 |
val = 7;
|
| 905 |
} else if val < min_teams {
|
|
@@ -909,12 +906,11 @@ pub unsafe fn init_game_state(wrapper: *mut DDGameWrapper) {
|
|
| 909 |
}
|
| 910 |
*(w.add(0x2A4) as *mut i32) = val;
|
| 911 |
|
| 912 |
-
*(w.add(0x414) as *mut u32) = (
|
| 913 |
|
| 914 |
if is_headful {
|
| 915 |
let palette_ptr = *(ddgame_raw as *const *mut u8);
|
| 916 |
-
*(palette_ptr.add(0x10) as *mut u32) =
|
| 917 |
-
(*(setup_data.add(0xF370) as *const u8) != 0) as u32;
|
| 918 |
}
|
| 919 |
}
|
| 920 |
|
|
@@ -942,19 +938,19 @@ pub unsafe fn init_game_state(wrapper: *mut DDGameWrapper) {
|
|
| 942 |
init_landscape_flags(wrapper as *mut u8);
|
| 943 |
|
| 944 |
// ===== Level bounds and camera initialization =====
|
| 945 |
-
init_game_state_level_bounds(ddgame_raw,
|
| 946 |
|
| 947 |
// ===== Display objects for HUD (headful only) =====
|
| 948 |
if is_headful {
|
| 949 |
-
init_game_state_hud_objects(wrapper, ddgame_raw,
|
| 950 |
}
|
| 951 |
|
| 952 |
// ===== Team name validation =====
|
| 953 |
-
init_game_state_team_names(ddgame_raw,
|
| 954 |
|
| 955 |
// ===== Weapon and team initialization =====
|
| 956 |
-
|
| 957 |
-
|
| 958 |
|
| 959 |
// InitWeaponTable
|
| 960 |
{
|
|
@@ -974,22 +970,22 @@ pub unsafe fn init_game_state(wrapper: *mut DDGameWrapper) {
|
|
| 974 |
}
|
| 975 |
|
| 976 |
// ===== Version-dependent stack/render config =====
|
| 977 |
-
init_game_state_version_config(ddgame_raw,
|
| 978 |
|
| 979 |
// ===== Water level and wind =====
|
| 980 |
-
init_game_state_water_level(
|
| 981 |
|
| 982 |
// ===== Random bag initialization =====
|
| 983 |
-
init_game_state_random_bags(ddgame_raw,
|
| 984 |
|
| 985 |
// ===== Turn state =====
|
| 986 |
init_turn_state(wrapper as *mut u8);
|
| 987 |
|
| 988 |
// ===== Weapon availability loop =====
|
| 989 |
-
init_game_state_weapon_avail(wrapper, ddgame_raw,
|
| 990 |
|
| 991 |
// ===== Team/object tracking arrays =====
|
| 992 |
-
init_game_state_tracking_arrays(ddgame_raw,
|
| 993 |
|
| 994 |
// ===== Statistics counters =====
|
| 995 |
for i in 0..10u32 {
|
|
@@ -1020,7 +1016,7 @@ pub unsafe fn init_game_state(wrapper: *mut DDGameWrapper) {
|
|
| 1020 |
|
| 1021 |
// ===== CTaskTurnGame constructor =====
|
| 1022 |
{
|
| 1023 |
-
let game_version =
|
| 1024 |
if game_version == -2 {
|
| 1025 |
// Online game: ECX = *net_bridge (dereferenced!) for the constructor
|
| 1026 |
let mem = wa_malloc(0x324);
|
|
@@ -1069,8 +1065,7 @@ pub unsafe fn init_game_state(wrapper: *mut DDGameWrapper) {
|
|
| 1069 |
{
|
| 1070 |
let replay_a = (*wrapper).replay_flag_a;
|
| 1071 |
let result = if replay_a == 0
|
| 1072 |
-
|| (*(
|
| 1073 |
-
&& *(w.add(0x49C) as *const i32) > 0xC)
|
| 1074 |
{
|
| 1075 |
0u32
|
| 1076 |
} else {
|
|
@@ -1084,10 +1079,9 @@ pub unsafe fn init_game_state(wrapper: *mut DDGameWrapper) {
|
|
| 1084 |
*(w.add(0xE0) as *mut u32) = 0;
|
| 1085 |
|
| 1086 |
// 0xEC: (game_info.f340 != 0) - 1 (i.e., 0 if nonzero, 0xFFFFFFFF if zero)
|
| 1087 |
-
*(w.add(0xEC) as *mut u32) =
|
| 1088 |
-
((*(game_info_sub.add(0xF340) as *const u32) != 0) as u32).wrapping_sub(1);
|
| 1089 |
|
| 1090 |
-
|
| 1091 |
(*wrapper).state_initialized = 1;
|
| 1092 |
|
| 1093 |
// ===== Serialize initial game state =====
|
|
@@ -1143,7 +1137,7 @@ pub unsafe fn init_game_state(wrapper: *mut DDGameWrapper) {
|
|
| 1143 |
|
| 1144 |
/// Allocate a BufferObject (0x48 bytes) with size computed from game setup.
|
| 1145 |
/// Used for main_buffer and state_buffer.
|
| 1146 |
-
unsafe fn allocate_buffer_object(ddgame_raw: *mut u8,
|
| 1147 |
use crate::address::va;
|
| 1148 |
use crate::rebase::rb;
|
| 1149 |
use crate::wa_alloc::wa_malloc;
|
|
@@ -1157,9 +1151,9 @@ unsafe fn allocate_buffer_object(ddgame_raw: *mut u8, game_info_sub: *const u8)
|
|
| 1157 |
return core::ptr::null_mut();
|
| 1158 |
}
|
| 1159 |
// Compute size: num_teams * 0x450 + 0x4F178 + num_objects * 0x70
|
| 1160 |
-
let num_teams =
|
| 1161 |
-
let num_objects =
|
| 1162 |
-
let size = num_teams * 0x450 + 0x4F178 + num_objects
|
| 1163 |
|
| 1164 |
let ctor: unsafe extern "stdcall" fn(*mut u32, u32, u32) -> *mut u8 =
|
| 1165 |
core::mem::transmute(rb(va::BUFFER_OBJECT_CONSTRUCTOR) as usize);
|
|
@@ -1300,7 +1294,7 @@ unsafe fn init_game_state_display(
|
|
| 1300 |
wrapper: *mut DDGameWrapper,
|
| 1301 |
w: *mut u8,
|
| 1302 |
ddgame_raw: *mut u8,
|
| 1303 |
-
|
| 1304 |
) {
|
| 1305 |
use crate::address::va;
|
| 1306 |
use crate::rebase::rb;
|
|
@@ -1329,7 +1323,7 @@ unsafe fn init_game_state_display(
|
|
| 1329 |
*(w.add(0x388) as *mut i32) = screen_height;
|
| 1330 |
|
| 1331 |
// Count active teams from slot_to_team array
|
| 1332 |
-
let team_count = *
|
| 1333 |
let count_active = |w: *mut u8, base: u32| -> u32 {
|
| 1334 |
let mut n = base;
|
| 1335 |
if *(w.add(0x3AC) as *const i32) >= 0 {
|
|
@@ -1532,34 +1526,34 @@ unsafe fn create_camera_object(
|
|
| 1532 |
}
|
| 1533 |
|
| 1534 |
/// Initialize level bounds and camera center positions.
|
| 1535 |
-
unsafe fn init_game_state_level_bounds(ddgame_raw: *mut u8,
|
| 1536 |
-
|
| 1537 |
-
|
| 1538 |
-
|
|
|
|
| 1539 |
|
| 1540 |
-
let is_cavern =
|
| 1541 |
if is_cavern == 0 {
|
| 1542 |
// Open-air level
|
| 1543 |
-
|
| 1544 |
-
|
| 1545 |
-
let level_width =
|
| 1546 |
-
|
| 1547 |
} else {
|
| 1548 |
// Cavern level
|
| 1549 |
-
let game_version =
|
| 1550 |
-
let bound = if game_version >= -1 {
|
| 1551 |
-
|
| 1552 |
-
|
| 1553 |
-
let level_width =
|
| 1554 |
-
|
| 1555 |
-
.wrapping_mul(0x10000)
|
| 1556 |
-
.wrapping_sub(bound);
|
| 1557 |
}
|
| 1558 |
|
| 1559 |
// Camera center initialization (4 viewports)
|
| 1560 |
for viewport_offset in [0x8CBCu32, 0x8CCCu32, 0x8CDCu32, 0x8CECu32] {
|
| 1561 |
-
let level_w =
|
| 1562 |
-
let level_h =
|
| 1563 |
let cx = (level_w << 16) / 2;
|
| 1564 |
let cy = (level_h * 0x10000) / 2;
|
| 1565 |
*(ddgame_raw.add(viewport_offset as usize + 0x18) as *mut i32) = cx;
|
|
@@ -1569,14 +1563,14 @@ unsafe fn init_game_state_level_bounds(ddgame_raw: *mut u8, game_info_sub: *cons
|
|
| 1569 |
}
|
| 1570 |
|
| 1571 |
// Map dimension fields
|
| 1572 |
-
|
| 1573 |
-
|
| 1574 |
|
| 1575 |
-
let game_version =
|
| 1576 |
if game_version > 0x32 {
|
| 1577 |
-
let level_w =
|
| 1578 |
-
|
| 1579 |
-
|
| 1580 |
}
|
| 1581 |
}
|
| 1582 |
|
|
@@ -1584,7 +1578,7 @@ unsafe fn init_game_state_level_bounds(ddgame_raw: *mut u8, game_info_sub: *cons
|
|
| 1584 |
unsafe fn init_game_state_hud_objects(
|
| 1585 |
_wrapper: *mut DDGameWrapper,
|
| 1586 |
ddgame_raw: *mut u8,
|
| 1587 |
-
|
| 1588 |
) {
|
| 1589 |
use crate::address::va;
|
| 1590 |
use crate::rebase::rb;
|
|
@@ -1632,18 +1626,21 @@ unsafe fn init_game_state_hud_objects(
|
|
| 1632 |
}
|
| 1633 |
|
| 1634 |
/// Validate team names — check for CPU team markers.
|
| 1635 |
-
unsafe fn init_game_state_team_names(ddgame_raw: *mut u8,
|
|
|
|
|
|
|
| 1636 |
// Set initial team color from game_info
|
| 1637 |
-
|
| 1638 |
|
| 1639 |
// Check team names for CPU marker (high bit set)
|
| 1640 |
-
let team_count =
|
|
|
|
| 1641 |
if team_count != 0 {
|
| 1642 |
for i in 0..team_count as usize {
|
| 1643 |
-
let name_byte = *(
|
| 1644 |
if name_byte >= 0x80 {
|
| 1645 |
// CPU team detected — set team color to -1
|
| 1646 |
-
|
| 1647 |
break;
|
| 1648 |
}
|
| 1649 |
}
|
|
@@ -1651,26 +1648,25 @@ unsafe fn init_game_state_team_names(ddgame_raw: *mut u8, game_info_sub: *const
|
|
| 1651 |
}
|
| 1652 |
|
| 1653 |
/// Version-dependent configuration (stack size, rendering constants).
|
| 1654 |
-
unsafe fn init_game_state_version_config(ddgame_raw: *mut u8,
|
| 1655 |
-
let
|
|
|
|
| 1656 |
|
| 1657 |
-
// Stack height:
|
| 1658 |
-
|
| 1659 |
-
// game_version >= -1: result = 0x800
|
| 1660 |
-
let stack_val: u32 = if game_version < -1 { 0x700 } else { 0x800 };
|
| 1661 |
-
*(ddgame_raw.add(0x5C8) as *mut u32) = stack_val;
|
| 1662 |
|
| 1663 |
// Copy RNG seed
|
| 1664 |
-
|
| 1665 |
-
|
|
|
|
| 1666 |
|
| 1667 |
// Zero various fields
|
| 1668 |
-
|
| 1669 |
-
|
| 1670 |
-
|
| 1671 |
-
|
| 1672 |
-
|
| 1673 |
-
|
| 1674 |
|
| 1675 |
// Zero counter arrays (6 entries each × 3 blocks)
|
| 1676 |
for block_base in [0x7EA8u32, 0x7EC0, 0x7ED8] {
|
|
@@ -1680,28 +1676,29 @@ unsafe fn init_game_state_version_config(ddgame_raw: *mut u8, game_info_sub: *co
|
|
| 1680 |
}
|
| 1681 |
|
| 1682 |
// Zero more fields
|
| 1683 |
-
|
| 1684 |
-
|
| 1685 |
-
|
| 1686 |
}
|
| 1687 |
|
| 1688 |
/// Water level and boundary calculations.
|
| 1689 |
-
unsafe fn init_game_state_water_level(
|
| 1690 |
-
// Water level: (100 -
|
| 1691 |
-
|
| 1692 |
-
let
|
| 1693 |
-
let
|
| 1694 |
-
|
|
|
|
| 1695 |
|
| 1696 |
// Clamp to 0 for newer versions
|
| 1697 |
-
let game_version =
|
| 1698 |
if water_level < 0 && game_version > 0x179 {
|
| 1699 |
-
|
| 1700 |
}
|
| 1701 |
|
| 1702 |
// Max Y boundary
|
| 1703 |
-
let water_val =
|
| 1704 |
-
let min_y =
|
| 1705 |
let max_y_candidate = (water_val + 0xA0) * 0x10000;
|
| 1706 |
let min_y_plus = min_y + 0x300_0000;
|
| 1707 |
let max_y = if max_y_candidate > min_y_plus {
|
|
@@ -1709,32 +1706,32 @@ unsafe fn init_game_state_water_level(ddgame_raw: *mut u8, game_info_sub: *const
|
|
| 1709 |
} else {
|
| 1710 |
min_y_plus
|
| 1711 |
};
|
| 1712 |
-
|
| 1713 |
|
| 1714 |
// Clamp for newer versions
|
| 1715 |
if game_version > 0x11E && (max_y >> 16) + 0x28 > 0x7FFF {
|
| 1716 |
-
|
| 1717 |
}
|
| 1718 |
|
| 1719 |
// Derived water fields
|
| 1720 |
-
|
| 1721 |
-
|
| 1722 |
-
|
| 1723 |
-
|
| 1724 |
|
| 1725 |
// Terrain type percentages
|
| 1726 |
-
|
| 1727 |
-
|
| 1728 |
-
|
| 1729 |
|
| 1730 |
// Game state flags
|
| 1731 |
-
|
| 1732 |
-
|
| 1733 |
-
|
| 1734 |
}
|
| 1735 |
|
| 1736 |
/// Random bag initialization (land, mine, barrel, weapon).
|
| 1737 |
-
unsafe fn init_game_state_random_bags(ddgame_raw: *mut u8,
|
| 1738 |
// Random bag at DDGame+0x360C (5 zeroes + 1 one)
|
| 1739 |
{
|
| 1740 |
let write_idx_ptr = ddgame_raw.add(0x379C) as *mut i32;
|
|
@@ -1754,38 +1751,38 @@ unsafe fn init_game_state_random_bags(ddgame_raw: *mut u8, game_info_sub: *const
|
|
| 1754 |
|
| 1755 |
// Terrain type percentages validation and clamping
|
| 1756 |
{
|
| 1757 |
-
let pct_land =
|
| 1758 |
-
let pct_mine =
|
| 1759 |
-
let pct_barrel =
|
| 1760 |
|
| 1761 |
let mut remaining = 100 - pct_land;
|
| 1762 |
if remaining < 0 {
|
| 1763 |
// Land > 100%, clamp all
|
| 1764 |
-
|
| 1765 |
-
|
| 1766 |
-
|
| 1767 |
remaining = 0;
|
| 1768 |
} else {
|
| 1769 |
remaining -= pct_mine;
|
| 1770 |
if remaining < 0 {
|
| 1771 |
-
|
| 1772 |
-
|
| 1773 |
remaining = 0;
|
| 1774 |
} else {
|
| 1775 |
remaining -= pct_barrel;
|
| 1776 |
if remaining < 0 {
|
| 1777 |
-
|
| 1778 |
}
|
| 1779 |
}
|
| 1780 |
}
|
| 1781 |
|
| 1782 |
// Extended terrain type from game_info
|
| 1783 |
-
let ext_type =
|
| 1784 |
-
let ext_pct =
|
| 1785 |
if ext_type == 0 {
|
| 1786 |
-
|
| 1787 |
} else if remaining < ext_pct {
|
| 1788 |
-
|
| 1789 |
}
|
| 1790 |
}
|
| 1791 |
|
|
@@ -1794,7 +1791,7 @@ unsafe fn init_game_state_random_bags(ddgame_raw: *mut u8, game_info_sub: *const
|
|
| 1794 |
let write_idx_ptr = ddgame_raw.add(0x4114) as *mut i32;
|
| 1795 |
|
| 1796 |
// Land entries (type 1)
|
| 1797 |
-
let count =
|
| 1798 |
let mut idx = *write_idx_ptr;
|
| 1799 |
if (idx as u32 + count) < 0x65 {
|
| 1800 |
for _ in 0..count {
|
|
@@ -1805,7 +1802,7 @@ unsafe fn init_game_state_random_bags(ddgame_raw: *mut u8, game_info_sub: *const
|
|
| 1805 |
}
|
| 1806 |
|
| 1807 |
// Mine entries (type 2)
|
| 1808 |
-
let count =
|
| 1809 |
idx = *write_idx_ptr;
|
| 1810 |
if (idx as u32 + count) < 0x65 {
|
| 1811 |
for _ in 0..count {
|
|
@@ -1816,7 +1813,7 @@ unsafe fn init_game_state_random_bags(ddgame_raw: *mut u8, game_info_sub: *const
|
|
| 1816 |
}
|
| 1817 |
|
| 1818 |
// Barrel entries (type 4)
|
| 1819 |
-
let count =
|
| 1820 |
idx = *write_idx_ptr;
|
| 1821 |
if (idx as u32 + count) < 0x65 {
|
| 1822 |
for _ in 0..count {
|
|
@@ -1827,7 +1824,7 @@ unsafe fn init_game_state_random_bags(ddgame_raw: *mut u8, game_info_sub: *const
|
|
| 1827 |
}
|
| 1828 |
|
| 1829 |
// Extended entries (type 0)
|
| 1830 |
-
let count =
|
| 1831 |
idx = *write_idx_ptr;
|
| 1832 |
if (idx as u32 + count) < 0x65 {
|
| 1833 |
for _ in 0..count {
|
|
@@ -1870,9 +1867,9 @@ unsafe fn init_game_state_random_bags(ddgame_raw: *mut u8, game_info_sub: *const
|
|
| 1870 |
unsafe fn init_game_state_weapon_avail(
|
| 1871 |
_wrapper: *mut DDGameWrapper,
|
| 1872 |
ddgame_raw: *mut u8,
|
| 1873 |
-
|
| 1874 |
) {
|
| 1875 |
-
let game_version =
|
| 1876 |
|
| 1877 |
for weapon_id in 1..0x47i32 {
|
| 1878 |
let avail = check_weapon_avail(ddgame_raw as *mut DDGame, weapon_id as u32);
|
|
@@ -1894,47 +1891,50 @@ unsafe fn init_game_state_weapon_avail(
|
|
| 1894 |
}
|
| 1895 |
|
| 1896 |
/// Allocate team/object tracking arrays and initialize the 0x51C object.
|
| 1897 |
-
unsafe fn init_game_state_tracking_arrays(ddgame_raw: *mut u8,
|
| 1898 |
use crate::wa_alloc::wa_malloc;
|
| 1899 |
|
|
|
|
|
|
|
| 1900 |
// Game speed
|
| 1901 |
-
|
| 1902 |
|
| 1903 |
-
let is_headful =
|
| 1904 |
if !is_headful {
|
| 1905 |
-
|
| 1906 |
} else {
|
| 1907 |
-
|
| 1908 |
}
|
| 1909 |
|
| 1910 |
// Network speed callback
|
| 1911 |
-
|
| 1912 |
-
|
|
|
|
| 1913 |
let vtable = *(snd_ptr as *const *const u32);
|
| 1914 |
let vmethod: unsafe extern "thiscall" fn(*const u8, u32, u32) =
|
| 1915 |
core::mem::transmute(*vtable.add(2));
|
| 1916 |
vmethod(
|
| 1917 |
snd_ptr,
|
| 1918 |
-
|
| 1919 |
-
|
| 1920 |
);
|
| 1921 |
}
|
| 1922 |
|
| 1923 |
// Misc fields
|
| 1924 |
-
|
| 1925 |
-
|
| 1926 |
-
|
| 1927 |
-
|
| 1928 |
-
|
| 1929 |
|
| 1930 |
// Render state
|
| 1931 |
-
|
| 1932 |
-
|
| 1933 |
-
|
| 1934 |
|
| 1935 |
// Allocate team tracking arrays (DDGame+0x514, DDGame+0x518)
|
| 1936 |
{
|
| 1937 |
-
let count_a =
|
| 1938 |
let size = count_a.wrapping_mul(4);
|
| 1939 |
let arr = wa_malloc(size);
|
| 1940 |
*(ddgame_raw.add(0x514) as *mut *mut u8) = arr;
|
|
@@ -1943,7 +1943,7 @@ unsafe fn init_game_state_tracking_arrays(ddgame_raw: *mut u8, game_info_sub: *c
|
|
| 1943 |
}
|
| 1944 |
}
|
| 1945 |
{
|
| 1946 |
-
let count_b =
|
| 1947 |
let size = count_b.wrapping_mul(4);
|
| 1948 |
let arr = wa_malloc(size);
|
| 1949 |
*(ddgame_raw.add(0x518) as *mut *mut u8) = arr;
|
|
|
|
| 7 |
use crate::engine::ddgame::DDGame;
|
| 8 |
use crate::engine::ddgame_wrapper::DDGameWrapper;
|
| 9 |
use crate::engine::game_info::GameInfo;
|
| 10 |
+
use crate::fixed::Fixed;
|
| 11 |
use crate::wa_alloc::wa_malloc;
|
| 12 |
|
| 13 |
/// Pure Rust implementation of SpriteGfxTable__Init (0x541620).
|
|
|
|
| 479 |
|
| 480 |
// DDGame._field_7788 = game_info._field_f362 (byte → u32)
|
| 481 |
(*ddgame)._field_7788 = (*game_info)._field_f362 as u32;
|
| 482 |
+
(*ddgame)._field_778c = Fixed::ONE;
|
| 483 |
(*ddgame)._field_7790 = 0;
|
| 484 |
|
| 485 |
// Camera center: (level_width << 16) / 2, (level_height << 16) / 2
|
|
|
|
| 604 |
let ddgame_raw = ddgame as *mut u8;
|
| 605 |
|
| 606 |
// ===== Copy replay mode flags from GameInfo =====
|
| 607 |
+
let game_info = (*ddgame).game_info;
|
| 608 |
+
(*wrapper).replay_flag_a = (*game_info).invisibility_mode as u8;
|
| 609 |
+
(*wrapper).replay_flag_b = ((*game_info).invisibility_mode >> 8) as u8;
|
| 610 |
|
| 611 |
// ===== SpriteGfxTable__Init =====
|
| 612 |
// ECX = DDGame+0x600, EDX = version-dependent count
|
| 613 |
{
|
| 614 |
+
let game_version = (*game_info).game_version;
|
| 615 |
let count: u32 = if game_version >= 0x3C { 0x400 } else { 0x100 };
|
| 616 |
sprite_gfx_table_init(ddgame_raw.add(0x600) as *mut u8, count);
|
| 617 |
}
|
|
|
|
| 703 |
|
| 704 |
// ===== Allocate BufferObject (main_buffer at wrapper+0x0C) =====
|
| 705 |
{
|
| 706 |
+
let buf = allocate_buffer_object(ddgame_raw, game_info);
|
| 707 |
(*wrapper).main_buffer = buf;
|
| 708 |
}
|
| 709 |
|
|
|
|
| 726 |
}
|
| 727 |
|
| 728 |
// ===== Allocate state buffer (wrapper+0x48) =====
|
| 729 |
+
(*wrapper).state_buffer = allocate_buffer_object(ddgame_raw, game_info);
|
| 730 |
|
| 731 |
// ===== Allocate statistics object (0xB94 bytes, wrapper+0x4C) =====
|
| 732 |
{
|
|
|
|
| 764 |
|
| 765 |
// ===== Select vector normalize function based on game version =====
|
| 766 |
{
|
| 767 |
+
let game_version = (*game_info).game_version;
|
| 768 |
+
(*ddgame).vector_normalize_fn = if game_version < 0x99 {
|
| 769 |
rb(va::VECTOR_NORMALIZE_SIMPLE)
|
| 770 |
} else {
|
| 771 |
rb(va::VECTOR_NORMALIZE_OVERFLOW)
|
| 772 |
};
|
|
|
|
| 773 |
}
|
| 774 |
|
| 775 |
// ===== Initialize sentinel fields =====
|
|
|
|
| 789 |
|
| 790 |
// ===== Resolution-dependent team render indices =====
|
| 791 |
{
|
| 792 |
+
let screen_height = (*ddgame).screen_height_pixels;
|
| 793 |
if screen_height < 0x2D0 {
|
| 794 |
// < 720px: smaller layout
|
| 795 |
*(w.add(0x2B0) as *mut i32) = 0xC;
|
|
|
|
| 831 |
|
| 832 |
// ===== Team-to-slot mapping (conditional on network/replay mode) =====
|
| 833 |
if *(ddgame_raw.add(0x1C) as *const u32) != 0 || (*wrapper).replay_flag_a != 0 {
|
| 834 |
+
let team_count = (*game_info).num_teams as i32;
|
| 835 |
+
let exclude_team = (*game_info).starting_team_index as i32;
|
| 836 |
let mut slot_idx = 0i32;
|
| 837 |
let pivar8_base = w.add(0x38C) as *mut i32;
|
| 838 |
let pivar7_base = w.add(0x3BC) as *mut i32;
|
|
|
|
| 866 |
*(w.add(0x418) as *mut u32) = 0;
|
| 867 |
|
| 868 |
// Turn percentage: (game_info.turn_percentage << 16) / 100
|
| 869 |
+
let turn_pct_raw = (*game_info).turn_percentage_raw;
|
| 870 |
(*wrapper).turn_percentage = (turn_pct_raw << 16) / 100;
|
| 871 |
|
| 872 |
// ===== Display setup (headful only) =====
|
| 873 |
+
let is_headful = (*ddgame).sound_available != 0;
|
| 874 |
|
| 875 |
if is_headful {
|
| 876 |
// The headful display initialization involves complex usercall patterns
|
|
|
|
| 878 |
// ECX/EDX register params). For now, bridge to original display layer code
|
| 879 |
// via the existing ported DisplayGfx constructors.
|
| 880 |
// TODO: Port the headful display init with proper usercall bridges.
|
| 881 |
+
init_game_state_display(wrapper, w, ddgame_raw, game_info);
|
| 882 |
}
|
| 883 |
|
| 884 |
// ===== Worm selection count and terrain config =====
|
| 885 |
{
|
| 886 |
+
let mut val = (*game_info).worm_select_cfg_a;
|
|
|
|
| 887 |
let min_teams = *(w.add(0x2A8) as *const i32);
|
| 888 |
let team_count_cfg = *(w.add(0x2AC) as *const i32);
|
| 889 |
|
|
|
|
| 890 |
if val == 0 {
|
| 891 |
val = team_count_cfg;
|
| 892 |
} else if val < min_teams {
|
|
|
|
| 896 |
}
|
| 897 |
*(w.add(0x2A0) as *mut i32) = val;
|
| 898 |
|
| 899 |
+
val = (*game_info).worm_select_cfg_b;
|
|
|
|
| 900 |
if val == -1 {
|
| 901 |
val = 7;
|
| 902 |
} else if val < min_teams {
|
|
|
|
| 906 |
}
|
| 907 |
*(w.add(0x2A4) as *mut i32) = val;
|
| 908 |
|
| 909 |
+
*(w.add(0x414) as *mut u32) = ((*game_info)._field_f365 != 0) as u32;
|
| 910 |
|
| 911 |
if is_headful {
|
| 912 |
let palette_ptr = *(ddgame_raw as *const *mut u8);
|
| 913 |
+
*(palette_ptr.add(0x10) as *mut u32) = ((*game_info)._field_f370 != 0) as u32;
|
|
|
|
| 914 |
}
|
| 915 |
}
|
| 916 |
|
|
|
|
| 938 |
init_landscape_flags(wrapper as *mut u8);
|
| 939 |
|
| 940 |
// ===== Level bounds and camera initialization =====
|
| 941 |
+
init_game_state_level_bounds(ddgame_raw, game_info);
|
| 942 |
|
| 943 |
// ===== Display objects for HUD (headful only) =====
|
| 944 |
if is_headful {
|
| 945 |
+
init_game_state_hud_objects(wrapper, ddgame_raw, game_info);
|
| 946 |
}
|
| 947 |
|
| 948 |
// ===== Team name validation =====
|
| 949 |
+
init_game_state_team_names(ddgame_raw, game_info);
|
| 950 |
|
| 951 |
// ===== Weapon and team initialization =====
|
| 952 |
+
(*ddgame).hud_status_code = 0;
|
| 953 |
+
(*ddgame).hud_status_text = core::ptr::null();
|
| 954 |
|
| 955 |
// InitWeaponTable
|
| 956 |
{
|
|
|
|
| 970 |
}
|
| 971 |
|
| 972 |
// ===== Version-dependent stack/render config =====
|
| 973 |
+
init_game_state_version_config(ddgame_raw, game_info);
|
| 974 |
|
| 975 |
// ===== Water level and wind =====
|
| 976 |
+
init_game_state_water_level(ddgame, game_info);
|
| 977 |
|
| 978 |
// ===== Random bag initialization =====
|
| 979 |
+
init_game_state_random_bags(ddgame_raw, game_info);
|
| 980 |
|
| 981 |
// ===== Turn state =====
|
| 982 |
init_turn_state(wrapper as *mut u8);
|
| 983 |
|
| 984 |
// ===== Weapon availability loop =====
|
| 985 |
+
init_game_state_weapon_avail(wrapper, ddgame_raw, game_info);
|
| 986 |
|
| 987 |
// ===== Team/object tracking arrays =====
|
| 988 |
+
init_game_state_tracking_arrays(ddgame_raw, game_info);
|
| 989 |
|
| 990 |
// ===== Statistics counters =====
|
| 991 |
for i in 0..10u32 {
|
|
|
|
| 1016 |
|
| 1017 |
// ===== CTaskTurnGame constructor =====
|
| 1018 |
{
|
| 1019 |
+
let game_version = (*game_info).game_version;
|
| 1020 |
if game_version == -2 {
|
| 1021 |
// Online game: ECX = *net_bridge (dereferenced!) for the constructor
|
| 1022 |
let mem = wa_malloc(0x324);
|
|
|
|
| 1065 |
{
|
| 1066 |
let replay_a = (*wrapper).replay_flag_a;
|
| 1067 |
let result = if replay_a == 0
|
| 1068 |
+
|| ((*game_info).replay_config_flag != 0 && *(w.add(0x49C) as *const i32) > 0xC)
|
|
|
|
| 1069 |
{
|
| 1070 |
0u32
|
| 1071 |
} else {
|
|
|
|
| 1079 |
*(w.add(0xE0) as *mut u32) = 0;
|
| 1080 |
|
| 1081 |
// 0xEC: (game_info.f340 != 0) - 1 (i.e., 0 if nonzero, 0xFFFFFFFF if zero)
|
| 1082 |
+
*(w.add(0xEC) as *mut u32) = (((*game_info)._field_f340 != 0) as u32).wrapping_sub(1);
|
|
|
|
| 1083 |
|
| 1084 |
+
(*game_info)._field_f34c = -1;
|
| 1085 |
(*wrapper).state_initialized = 1;
|
| 1086 |
|
| 1087 |
// ===== Serialize initial game state =====
|
|
|
|
| 1137 |
|
| 1138 |
/// Allocate a BufferObject (0x48 bytes) with size computed from game setup.
|
| 1139 |
/// Used for main_buffer and state_buffer.
|
| 1140 |
+
unsafe fn allocate_buffer_object(ddgame_raw: *mut u8, game_info: *const GameInfo) -> *mut u8 {
|
| 1141 |
use crate::address::va;
|
| 1142 |
use crate::rebase::rb;
|
| 1143 |
use crate::wa_alloc::wa_malloc;
|
|
|
|
| 1151 |
return core::ptr::null_mut();
|
| 1152 |
}
|
| 1153 |
// Compute size: num_teams * 0x450 + 0x4F178 + num_objects * 0x70
|
| 1154 |
+
let num_teams = (*game_info).num_teams_alloc as u32;
|
| 1155 |
+
let num_objects = (*game_info).object_slot_count;
|
| 1156 |
+
let size = num_teams * 0x450 + 0x4F178 + num_objects * 0x70;
|
| 1157 |
|
| 1158 |
let ctor: unsafe extern "stdcall" fn(*mut u32, u32, u32) -> *mut u8 =
|
| 1159 |
core::mem::transmute(rb(va::BUFFER_OBJECT_CONSTRUCTOR) as usize);
|
|
|
|
| 1294 |
wrapper: *mut DDGameWrapper,
|
| 1295 |
w: *mut u8,
|
| 1296 |
ddgame_raw: *mut u8,
|
| 1297 |
+
game_info: *const GameInfo,
|
| 1298 |
) {
|
| 1299 |
use crate::address::va;
|
| 1300 |
use crate::rebase::rb;
|
|
|
|
| 1323 |
*(w.add(0x388) as *mut i32) = screen_height;
|
| 1324 |
|
| 1325 |
// Count active teams from slot_to_team array
|
| 1326 |
+
let team_count = (*game_info).num_teams as u32;
|
| 1327 |
let count_active = |w: *mut u8, base: u32| -> u32 {
|
| 1328 |
let mut n = base;
|
| 1329 |
if *(w.add(0x3AC) as *const i32) >= 0 {
|
|
|
|
| 1526 |
}
|
| 1527 |
|
| 1528 |
/// Initialize level bounds and camera center positions.
|
| 1529 |
+
unsafe fn init_game_state_level_bounds(ddgame_raw: *mut u8, game_info: *const GameInfo) {
|
| 1530 |
+
let ddgame = ddgame_raw as *mut DDGame;
|
| 1531 |
+
|
| 1532 |
+
(*ddgame)._field_7794 = 0;
|
| 1533 |
+
(*ddgame)._field_7798 = 0;
|
| 1534 |
|
| 1535 |
+
let is_cavern = (*ddgame).level_width_raw as i32;
|
| 1536 |
if is_cavern == 0 {
|
| 1537 |
// Open-air level
|
| 1538 |
+
(*ddgame).level_bound_min_x = Fixed(0xF802_0000u32 as i32);
|
| 1539 |
+
(*ddgame).level_bound_min_y = Fixed(0xF802_0000u32 as i32);
|
| 1540 |
+
let level_width = (*ddgame).level_width as i32;
|
| 1541 |
+
(*ddgame).level_bound_max_x = Fixed((level_width + 0x7FE) * 0x10000);
|
| 1542 |
} else {
|
| 1543 |
// Cavern level
|
| 1544 |
+
let game_version = (*game_info).game_version;
|
| 1545 |
+
let bound = if game_version >= -1 { 0x20000i32 } else { 0i32 };
|
| 1546 |
+
(*ddgame).level_bound_min_x = Fixed(bound);
|
| 1547 |
+
(*ddgame).level_bound_min_y = Fixed(0x20000);
|
| 1548 |
+
let level_width = (*ddgame).level_width;
|
| 1549 |
+
(*ddgame).level_bound_max_x =
|
| 1550 |
+
Fixed((level_width.wrapping_mul(0x10000)).wrapping_sub(bound as u32) as i32);
|
|
|
|
| 1551 |
}
|
| 1552 |
|
| 1553 |
// Camera center initialization (4 viewports)
|
| 1554 |
for viewport_offset in [0x8CBCu32, 0x8CCCu32, 0x8CDCu32, 0x8CECu32] {
|
| 1555 |
+
let level_w = (*ddgame).level_width as i32;
|
| 1556 |
+
let level_h = (*ddgame).level_height as i32;
|
| 1557 |
let cx = (level_w << 16) / 2;
|
| 1558 |
let cy = (level_h * 0x10000) / 2;
|
| 1559 |
*(ddgame_raw.add(viewport_offset as usize + 0x18) as *mut i32) = cx;
|
|
|
|
| 1563 |
}
|
| 1564 |
|
| 1565 |
// Map dimension fields
|
| 1566 |
+
(*ddgame).map_boundary_width = 0x30D4;
|
| 1567 |
+
(*ddgame).map_boundary_height = (*ddgame).level_height;
|
| 1568 |
|
| 1569 |
+
let game_version = (*game_info).game_version;
|
| 1570 |
if game_version > 0x32 {
|
| 1571 |
+
let level_w = (*ddgame).level_width as i32;
|
| 1572 |
+
(*ddgame).map_boundary_width = (level_w + 0x2954) as u32;
|
| 1573 |
+
(*ddgame).map_boundary_height = 0x2B8;
|
| 1574 |
}
|
| 1575 |
}
|
| 1576 |
|
|
|
|
| 1578 |
unsafe fn init_game_state_hud_objects(
|
| 1579 |
_wrapper: *mut DDGameWrapper,
|
| 1580 |
ddgame_raw: *mut u8,
|
| 1581 |
+
_game_info: *const GameInfo,
|
| 1582 |
) {
|
| 1583 |
use crate::address::va;
|
| 1584 |
use crate::rebase::rb;
|
|
|
|
| 1626 |
}
|
| 1627 |
|
| 1628 |
/// Validate team names — check for CPU team markers.
|
| 1629 |
+
unsafe fn init_game_state_team_names(ddgame_raw: *mut u8, game_info: *mut GameInfo) {
|
| 1630 |
+
let ddgame = ddgame_raw as *mut DDGame;
|
| 1631 |
+
|
| 1632 |
// Set initial team color from game_info
|
| 1633 |
+
(*ddgame).team_color = (*game_info).team_color_source as u32;
|
| 1634 |
|
| 1635 |
// Check team names for CPU marker (high bit set)
|
| 1636 |
+
let team_count = (*game_info).speech_team_count;
|
| 1637 |
+
let gi_raw = game_info as *const u8;
|
| 1638 |
if team_count != 0 {
|
| 1639 |
for i in 0..team_count as usize {
|
| 1640 |
+
let name_byte = *(gi_raw.add(0x450 + i * 3000));
|
| 1641 |
if name_byte >= 0x80 {
|
| 1642 |
// CPU team detected — set team color to -1
|
| 1643 |
+
(*ddgame).team_color = 0xFFFF_FFFF;
|
| 1644 |
break;
|
| 1645 |
}
|
| 1646 |
}
|
|
|
|
| 1648 |
}
|
| 1649 |
|
| 1650 |
/// Version-dependent configuration (stack size, rendering constants).
|
| 1651 |
+
unsafe fn init_game_state_version_config(ddgame_raw: *mut u8, game_info: *const GameInfo) {
|
| 1652 |
+
let ddgame = ddgame_raw as *mut DDGame;
|
| 1653 |
+
let game_version = (*game_info).game_version;
|
| 1654 |
|
| 1655 |
+
// Stack height: game_version < -1 → 0x700, >= -1 → 0x800
|
| 1656 |
+
(*ddgame).stack_height = if game_version < -1 { 0x700 } else { 0x800 };
|
|
|
|
|
|
|
|
|
|
| 1657 |
|
| 1658 |
// Copy RNG seed
|
| 1659 |
+
let rng_seed = (*game_info).rng_seed;
|
| 1660 |
+
(*ddgame).game_rng = rng_seed;
|
| 1661 |
+
*(ddgame_raw.add(0x45F0) as *mut u32) = rng_seed;
|
| 1662 |
|
| 1663 |
// Zero various fields
|
| 1664 |
+
(*ddgame).frame_counter = 0;
|
| 1665 |
+
(*ddgame)._field_5d4 = 0;
|
| 1666 |
+
(*ddgame)._field_5d8 = 0;
|
| 1667 |
+
(*ddgame)._field_5dc = 0;
|
| 1668 |
+
(*ddgame)._field_7e4c = 0;
|
| 1669 |
+
(*ddgame)._field_5d0 = 0;
|
| 1670 |
|
| 1671 |
// Zero counter arrays (6 entries each × 3 blocks)
|
| 1672 |
for block_base in [0x7EA8u32, 0x7EC0, 0x7ED8] {
|
|
|
|
| 1676 |
}
|
| 1677 |
|
| 1678 |
// Zero more fields
|
| 1679 |
+
(*ddgame)._field_45e0 = 0;
|
| 1680 |
+
(*ddgame)._field_45e4 = 0;
|
| 1681 |
+
(*ddgame)._field_45e8 = 0;
|
| 1682 |
}
|
| 1683 |
|
| 1684 |
/// Water level and boundary calculations.
|
| 1685 |
+
unsafe fn init_game_state_water_level(ddgame: *mut DDGame, game_info: *mut GameInfo) {
|
| 1686 |
+
// Water level: (100 - level_height_raw) * level_height / 100
|
| 1687 |
+
// level_height_raw appears to encode a water height percentage here
|
| 1688 |
+
let height_raw = (*ddgame).level_height_raw as i32;
|
| 1689 |
+
let level_height = (*ddgame).level_height as i32;
|
| 1690 |
+
let water_level = ((100 - height_raw) * level_height) / 100;
|
| 1691 |
+
(*ddgame).water_level = water_level;
|
| 1692 |
|
| 1693 |
// Clamp to 0 for newer versions
|
| 1694 |
+
let game_version = (*game_info).game_version;
|
| 1695 |
if water_level < 0 && game_version > 0x179 {
|
| 1696 |
+
(*ddgame).water_level = 0;
|
| 1697 |
}
|
| 1698 |
|
| 1699 |
// Max Y boundary
|
| 1700 |
+
let water_val = (*ddgame).water_level;
|
| 1701 |
+
let min_y = (*ddgame).level_bound_min_y.0;
|
| 1702 |
let max_y_candidate = (water_val + 0xA0) * 0x10000;
|
| 1703 |
let min_y_plus = min_y + 0x300_0000;
|
| 1704 |
let max_y = if max_y_candidate > min_y_plus {
|
|
|
|
| 1706 |
} else {
|
| 1707 |
min_y_plus
|
| 1708 |
};
|
| 1709 |
+
(*ddgame).level_bound_max_y = Fixed(max_y);
|
| 1710 |
|
| 1711 |
// Clamp for newer versions
|
| 1712 |
if game_version > 0x11E && (max_y >> 16) + 0x28 > 0x7FFF {
|
| 1713 |
+
(*ddgame).level_bound_max_y = Fixed(0x7FD7_0000u32 as i32);
|
| 1714 |
}
|
| 1715 |
|
| 1716 |
// Derived water fields
|
| 1717 |
+
(*ddgame).water_kill_y = (*ddgame).level_bound_max_y.to_int() + 0x28;
|
| 1718 |
+
(*ddgame)._field_5f8 = 0;
|
| 1719 |
+
(*ddgame).water_level_initial = (*ddgame).water_level;
|
| 1720 |
+
(*ddgame)._field_5ec = 0;
|
| 1721 |
|
| 1722 |
// Terrain type percentages
|
| 1723 |
+
(*ddgame).terrain_pct_a = (*game_info).terrain_cfg_a as u32;
|
| 1724 |
+
(*ddgame).terrain_pct_b = (*game_info).terrain_cfg_b as u32;
|
| 1725 |
+
(*ddgame).terrain_pct_c = (*game_info).terrain_cfg_c as u32;
|
| 1726 |
|
| 1727 |
// Game state flags
|
| 1728 |
+
(*ddgame)._field_5f0 = 1;
|
| 1729 |
+
(*ddgame)._field_5f4 = 100;
|
| 1730 |
+
(*ddgame)._field_5fc = 0;
|
| 1731 |
}
|
| 1732 |
|
| 1733 |
/// Random bag initialization (land, mine, barrel, weapon).
|
| 1734 |
+
unsafe fn init_game_state_random_bags(ddgame_raw: *mut u8, game_info: *mut GameInfo) {
|
| 1735 |
// Random bag at DDGame+0x360C (5 zeroes + 1 one)
|
| 1736 |
{
|
| 1737 |
let write_idx_ptr = ddgame_raw.add(0x379C) as *mut i32;
|
|
|
|
| 1751 |
|
| 1752 |
// Terrain type percentages validation and clamping
|
| 1753 |
{
|
| 1754 |
+
let pct_land = (*game_info).drop_pct_land as i32;
|
| 1755 |
+
let pct_mine = (*game_info).drop_pct_mine as i32;
|
| 1756 |
+
let pct_barrel = (*game_info).drop_pct_barrel as i32;
|
| 1757 |
|
| 1758 |
let mut remaining = 100 - pct_land;
|
| 1759 |
if remaining < 0 {
|
| 1760 |
// Land > 100%, clamp all
|
| 1761 |
+
(*game_info).drop_pct_land = 100;
|
| 1762 |
+
(*game_info).drop_pct_mine = 0;
|
| 1763 |
+
(*game_info).drop_pct_barrel = 0;
|
| 1764 |
remaining = 0;
|
| 1765 |
} else {
|
| 1766 |
remaining -= pct_mine;
|
| 1767 |
if remaining < 0 {
|
| 1768 |
+
(*game_info).drop_pct_mine = (pct_mine + remaining) as u8;
|
| 1769 |
+
(*game_info).drop_pct_barrel = 0;
|
| 1770 |
remaining = 0;
|
| 1771 |
} else {
|
| 1772 |
remaining -= pct_barrel;
|
| 1773 |
if remaining < 0 {
|
| 1774 |
+
(*game_info).drop_pct_barrel = (pct_barrel + remaining) as u8;
|
| 1775 |
}
|
| 1776 |
}
|
| 1777 |
}
|
| 1778 |
|
| 1779 |
// Extended terrain type from game_info
|
| 1780 |
+
let ext_type = (*game_info).ext_terrain_type;
|
| 1781 |
+
let ext_pct = (*game_info).ext_terrain_pct as i32;
|
| 1782 |
if ext_type == 0 {
|
| 1783 |
+
(*game_info).ext_terrain_pct = remaining as u8;
|
| 1784 |
} else if remaining < ext_pct {
|
| 1785 |
+
(*game_info).ext_terrain_pct = 0;
|
| 1786 |
}
|
| 1787 |
}
|
| 1788 |
|
|
|
|
| 1791 |
let write_idx_ptr = ddgame_raw.add(0x4114) as *mut i32;
|
| 1792 |
|
| 1793 |
// Land entries (type 1)
|
| 1794 |
+
let count = (*game_info).drop_pct_land as u32;
|
| 1795 |
let mut idx = *write_idx_ptr;
|
| 1796 |
if (idx as u32 + count) < 0x65 {
|
| 1797 |
for _ in 0..count {
|
|
|
|
| 1802 |
}
|
| 1803 |
|
| 1804 |
// Mine entries (type 2)
|
| 1805 |
+
let count = (*game_info).drop_pct_mine as u32;
|
| 1806 |
idx = *write_idx_ptr;
|
| 1807 |
if (idx as u32 + count) < 0x65 {
|
| 1808 |
for _ in 0..count {
|
|
|
|
| 1813 |
}
|
| 1814 |
|
| 1815 |
// Barrel entries (type 4)
|
| 1816 |
+
let count = (*game_info).drop_pct_barrel as u32;
|
| 1817 |
idx = *write_idx_ptr;
|
| 1818 |
if (idx as u32 + count) < 0x65 {
|
| 1819 |
for _ in 0..count {
|
|
|
|
| 1824 |
}
|
| 1825 |
|
| 1826 |
// Extended entries (type 0)
|
| 1827 |
+
let count = (*game_info).ext_terrain_pct as u32;
|
| 1828 |
idx = *write_idx_ptr;
|
| 1829 |
if (idx as u32 + count) < 0x65 {
|
| 1830 |
for _ in 0..count {
|
|
|
|
| 1867 |
unsafe fn init_game_state_weapon_avail(
|
| 1868 |
_wrapper: *mut DDGameWrapper,
|
| 1869 |
ddgame_raw: *mut u8,
|
| 1870 |
+
game_info: *const GameInfo,
|
| 1871 |
) {
|
| 1872 |
+
let game_version = (*game_info).game_version;
|
| 1873 |
|
| 1874 |
for weapon_id in 1..0x47i32 {
|
| 1875 |
let avail = check_weapon_avail(ddgame_raw as *mut DDGame, weapon_id as u32);
|
|
|
|
| 1891 |
}
|
| 1892 |
|
| 1893 |
/// Allocate team/object tracking arrays and initialize the 0x51C object.
|
| 1894 |
+
unsafe fn init_game_state_tracking_arrays(ddgame_raw: *mut u8, game_info: *const GameInfo) {
|
| 1895 |
use crate::wa_alloc::wa_malloc;
|
| 1896 |
|
| 1897 |
+
let ddgame = ddgame_raw as *mut DDGame;
|
| 1898 |
+
|
| 1899 |
// Game speed
|
| 1900 |
+
(*ddgame).game_speed = Fixed::ONE;
|
| 1901 |
|
| 1902 |
+
let is_headful = (*ddgame).sound_available != 0;
|
| 1903 |
if !is_headful {
|
| 1904 |
+
(*ddgame).game_speed_target = Fixed(0x1000_0000);
|
| 1905 |
} else {
|
| 1906 |
+
(*ddgame).game_speed_target = Fixed((*game_info).game_speed_config);
|
| 1907 |
}
|
| 1908 |
|
| 1909 |
// Network speed callback
|
| 1910 |
+
let sound = (*ddgame).sound;
|
| 1911 |
+
if !sound.is_null() {
|
| 1912 |
+
let snd_ptr = sound as *const u8;
|
| 1913 |
let vtable = *(snd_ptr as *const *const u32);
|
| 1914 |
let vmethod: unsafe extern "thiscall" fn(*const u8, u32, u32) =
|
| 1915 |
core::mem::transmute(*vtable.add(2));
|
| 1916 |
vmethod(
|
| 1917 |
snd_ptr,
|
| 1918 |
+
(*ddgame).game_speed.0 as u32,
|
| 1919 |
+
(*ddgame).game_speed_target.0 as u32,
|
| 1920 |
);
|
| 1921 |
}
|
| 1922 |
|
| 1923 |
// Misc fields
|
| 1924 |
+
(*ddgame).sound_queue_count = 0;
|
| 1925 |
+
(*ddgame).render_phase = (*game_info).render_phase_cfg as i32;
|
| 1926 |
+
(*ddgame)._field_7640 = 0;
|
| 1927 |
+
(*ddgame)._field_7644 = (*game_info)._field_f363 as u32;
|
| 1928 |
+
(*ddgame)._field_7648 = (*game_info)._field_f364 as u32;
|
| 1929 |
|
| 1930 |
// Render state
|
| 1931 |
+
(*ddgame)._field_7390 = 0;
|
| 1932 |
+
(*ddgame).render_scale = Fixed::ONE;
|
| 1933 |
+
(*ddgame)._field_7398 = 0;
|
| 1934 |
|
| 1935 |
// Allocate team tracking arrays (DDGame+0x514, DDGame+0x518)
|
| 1936 |
{
|
| 1937 |
+
let count_a = (*game_info).team_slot_count;
|
| 1938 |
let size = count_a.wrapping_mul(4);
|
| 1939 |
let arr = wa_malloc(size);
|
| 1940 |
*(ddgame_raw.add(0x514) as *mut *mut u8) = arr;
|
|
|
|
| 1943 |
}
|
| 1944 |
}
|
| 1945 |
{
|
| 1946 |
+
let count_b = (*game_info).object_slot_count;
|
| 1947 |
let size = count_b.wrapping_mul(4);
|
| 1948 |
let arr = wa_malloc(size);
|
| 1949 |
*(ddgame_raw.add(0x518) as *mut *mut u8) = arr;
|
crates/openwa-dll/src/replacements/replay.rs
CHANGED
|
@@ -684,7 +684,7 @@ unsafe fn parse_and_write_v2plus(
|
|
| 684 |
call_usercall_esi(map as u32, rb(va::MAP_VIEW_COPY_INFO));
|
| 685 |
|
| 686 |
// Terrain flag: zero = cavern terrain
|
| 687 |
-
(*gi).
|
| 688 |
|
| 689 |
// Release map object
|
| 690 |
((*(*map).vtable).destructor)(map, 1);
|
|
|
|
| 684 |
call_usercall_esi(map as u32, rb(va::MAP_VIEW_COPY_INFO));
|
| 685 |
|
| 686 |
// Terrain flag: zero = cavern terrain
|
| 687 |
+
(*gi).set_terrain_flag(((*map).terrain_flag == 0) as u8);
|
| 688 |
|
| 689 |
// Release map object
|
| 690 |
((*(*map).vtable).destructor)(map, 1);
|