refactor: fully type CTaskCloud handler — no raw offsets
Browse filesReplace all raw pointer arithmetic in CTaskCloud::HandleMessage with
typed DDGame fields. Render path now uses RenderQueue::alloc directly
instead of delegating to original WA function.
New/renamed DDGame fields:
- render_phase (+0x764C): rendering phase, clouds render when == 5
- parallax_scale (+0x8150): renamed from crosshair_scale, also used
for cloud parallax X offset computation
- level_bound_min_x/max_x: changed from i32 to Fixed type
Cloud handler improvements:
- Uses TaskMessage enum (FrameFinish, RenderScene, SetWind)
- Uses Fixed arithmetic with from_int(128) for padding
- Calls RenderQueue::alloc::<DrawSpriteCmd> directly for rendering
- No ORIG_HANDLE_MESSAGE needed — fully self-contained
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
@@ -1,5 +1,6 @@
|
|
| 1 |
use crate::FieldRegistry;
|
| 2 |
use crate::audio::active_sound::ActiveSoundTable;
|
|
|
|
| 3 |
use crate::audio::dssound::DSSound;
|
| 4 |
use crate::audio::music::Music;
|
| 5 |
use crate::audio::speech::SpeechSlotTable;
|
|
@@ -254,8 +255,11 @@ pub struct DDGame {
|
|
| 254 |
/// 0x73B0: Render entry table (14 entries × 0x14 bytes).
|
| 255 |
/// First u32 of each entry zeroed by InitRenderIndices.
|
| 256 |
pub render_entries: [RenderEntry; 14],
|
| 257 |
-
/// 0x74C8-
|
| 258 |
-
pub _unknown_74c8: [u8;
|
|
|
|
|
|
|
|
|
|
| 259 |
|
| 260 |
/// 0x7650: Team index permutation maps (3 × 0x64 bytes).
|
| 261 |
/// Used for team-to-slot mapping (render order, turn order, display order).
|
|
@@ -268,10 +272,10 @@ pub struct DDGame {
|
|
| 268 |
/// 0x7784-0x779B: Unknown
|
| 269 |
pub _unknown_7784: [u8; 0x779C - 0x7784],
|
| 270 |
|
| 271 |
-
/// 0x779C: Level bound min X (
|
| 272 |
-
pub level_bound_min_x:
|
| 273 |
-
/// 0x77A0: Level bound max X (
|
| 274 |
-
pub level_bound_max_x:
|
| 275 |
/// 0x77A4: Level bound min Y (Fixed-point, same as min_x typically).
|
| 276 |
pub level_bound_min_y: i32,
|
| 277 |
/// 0x77A8: Level bound max Y (Fixed-point).
|
|
@@ -342,8 +346,10 @@ pub struct DDGame {
|
|
| 342 |
|
| 343 |
/// 0x8144-0x814F: Unknown
|
| 344 |
pub _unknown_8144: [u8; 0x8150 - 0x8144],
|
| 345 |
-
/// 0x8150:
|
| 346 |
-
|
|
|
|
|
|
|
| 347 |
|
| 348 |
/// 0x8154-0x818B: Unknown
|
| 349 |
pub _unknown_8154: [u8; 0x818C - 0x8154],
|
|
@@ -533,7 +539,7 @@ pub mod offsets {
|
|
| 533 |
pub const TURN_TIME_LIMIT: usize = 0x7EA8;
|
| 534 |
pub const SOUND_AVAILABLE: usize = 0x7EF8;
|
| 535 |
/// Scale factor used by DrawCrosshairLine (multiplied by 0x140000).
|
| 536 |
-
pub const
|
| 537 |
/// Turn status text (null-terminated ASCII, shown during gameplay).
|
| 538 |
pub const TURN_STATUS_TEXT: usize = 0x818C;
|
| 539 |
/// Checkpoint active flag.
|
|
|
|
| 1 |
use crate::FieldRegistry;
|
| 2 |
use crate::audio::active_sound::ActiveSoundTable;
|
| 3 |
+
use crate::fixed::Fixed;
|
| 4 |
use crate::audio::dssound::DSSound;
|
| 5 |
use crate::audio::music::Music;
|
| 6 |
use crate::audio::speech::SpeechSlotTable;
|
|
|
|
| 255 |
/// 0x73B0: Render entry table (14 entries × 0x14 bytes).
|
| 256 |
/// First u32 of each entry zeroed by InitRenderIndices.
|
| 257 |
pub render_entries: [RenderEntry; 14],
|
| 258 |
+
/// 0x74C8-0x764B: Unknown
|
| 259 |
+
pub _unknown_74c8: [u8; 0x764C - 0x74C8],
|
| 260 |
+
/// 0x764C: Rendering phase. Checked by CTaskCloud::HandleMessage:
|
| 261 |
+
/// clouds only render when this == 5 (in-game rendering active).
|
| 262 |
+
pub render_phase: i32,
|
| 263 |
|
| 264 |
/// 0x7650: Team index permutation maps (3 × 0x64 bytes).
|
| 265 |
/// Used for team-to-slot mapping (render order, turn order, display order).
|
|
|
|
| 272 |
/// 0x7784-0x779B: Unknown
|
| 273 |
pub _unknown_7784: [u8; 0x779C - 0x7784],
|
| 274 |
|
| 275 |
+
/// 0x779C: Level bound min X (Fixed16.16, negative = off-screen left).
|
| 276 |
+
pub level_bound_min_x: Fixed,
|
| 277 |
+
/// 0x77A0: Level bound max X (Fixed16.16).
|
| 278 |
+
pub level_bound_max_x: Fixed,
|
| 279 |
/// 0x77A4: Level bound min Y (Fixed-point, same as min_x typically).
|
| 280 |
pub level_bound_min_y: i32,
|
| 281 |
/// 0x77A8: Level bound max Y (Fixed-point).
|
|
|
|
| 346 |
|
| 347 |
/// 0x8144-0x814F: Unknown
|
| 348 |
pub _unknown_8144: [u8; 0x8150 - 0x8144],
|
| 349 |
+
/// 0x8150: Parallax/camera scale factor (Fixed-point multiplier).
|
| 350 |
+
/// Used by DrawCrosshairLine (multiplied by 0x140000) and
|
| 351 |
+
/// CTaskCloud render (parallax X offset = wind_speed * this >> 16).
|
| 352 |
+
pub parallax_scale: i32,
|
| 353 |
|
| 354 |
/// 0x8154-0x818B: Unknown
|
| 355 |
pub _unknown_8154: [u8; 0x818C - 0x8154],
|
|
|
|
| 539 |
pub const TURN_TIME_LIMIT: usize = 0x7EA8;
|
| 540 |
pub const SOUND_AVAILABLE: usize = 0x7EF8;
|
| 541 |
/// Scale factor used by DrawCrosshairLine (multiplied by 0x140000).
|
| 542 |
+
pub const PARALLAX_SCALE: usize = 0x8150;
|
| 543 |
/// Turn status text (null-terminated ASCII, shown during gameplay).
|
| 544 |
pub const TURN_STATUS_TEXT: usize = 0x818C;
|
| 545 |
/// Checkpoint active flag.
|
|
@@ -507,8 +507,8 @@ unsafe extern "cdecl" fn draw_crosshair_line_impl(task_ptr: u32) {
|
|
| 507 |
let sin_interp = trig_lookup(sin_table, angle);
|
| 508 |
let cos_interp = trig_lookup(cos_table, angle);
|
| 509 |
|
| 510 |
-
// Scale = fixed_mul(DDGame.
|
| 511 |
-
let scale = fixed_mul(ddgame.
|
| 512 |
|
| 513 |
// Endpoint = start + direction * scale
|
| 514 |
let mut endpoint_x = fixed_mul(sin_interp, scale).wrapping_add(start_x);
|
|
|
|
| 507 |
let sin_interp = trig_lookup(sin_table, angle);
|
| 508 |
let cos_interp = trig_lookup(cos_table, angle);
|
| 509 |
|
| 510 |
+
// Scale = fixed_mul(DDGame.parallax_scale, 0x140000) + task.aim_range_offset
|
| 511 |
+
let scale = fixed_mul(ddgame.parallax_scale, 0x14_0000) + task.aim_range_offset;
|
| 512 |
|
| 513 |
// Endpoint = start + direction * scale
|
| 514 |
let mut endpoint_x = fixed_mul(sin_interp, scale).wrapping_add(start_x);
|
|
@@ -3,18 +3,14 @@
|
|
| 3 |
//! Replaces CTaskCloud::HandleMessage (vtable slot 2).
|
| 4 |
//! Handles cloud position updates (wind parallax), rendering, and wind changes.
|
| 5 |
|
| 6 |
-
use core::sync::atomic::{AtomicU32, Ordering};
|
| 7 |
-
|
| 8 |
use openwa_core::address::va;
|
| 9 |
use openwa_core::fixed::Fixed;
|
| 10 |
use openwa_core::game::TaskMessage;
|
| 11 |
use openwa_core::log::log_line;
|
|
|
|
| 12 |
use openwa_core::task::cloud::CTaskCloud;
|
| 13 |
use openwa_core::task::{CTask, Task};
|
| 14 |
|
| 15 |
-
/// Original CTaskCloud::HandleMessage, saved for render call-through.
|
| 16 |
-
static ORIG_HANDLE_MESSAGE: AtomicU32 = AtomicU32::new(0);
|
| 17 |
-
|
| 18 |
/// CTaskCloud::HandleMessage replacement.
|
| 19 |
///
|
| 20 |
/// Handles three message types:
|
|
@@ -42,16 +38,16 @@ unsafe extern "thiscall" fn cloud_handle_message(
|
|
| 42 |
let wind = cloud.wind_accel.0;
|
| 43 |
cloud.pos_x = Fixed(cloud.pos_x.0 + cloud.vel_x.0 + wind * 10);
|
| 44 |
|
| 45 |
-
// Wrap X at landscape bounds (with
|
| 46 |
let ddgame = &*cloud.ddgame();
|
| 47 |
-
let
|
| 48 |
-
let level_left =
|
| 49 |
-
let level_right =
|
| 50 |
-
|
| 51 |
-
if cloud.pos_x
|
| 52 |
-
cloud.pos_x =
|
| 53 |
-
} else if cloud.pos_x
|
| 54 |
-
cloud.pos_x =
|
| 55 |
}
|
| 56 |
|
| 57 |
// Converge wind_accel toward wind_target (clamp step to ±0x147)
|
|
@@ -68,13 +64,27 @@ unsafe extern "thiscall" fn cloud_handle_message(
|
|
| 68 |
}
|
| 69 |
|
| 70 |
Ok(TaskMessage::RenderScene) => {
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 78 |
}
|
| 79 |
|
| 80 |
Ok(TaskMessage::SetWind) => {
|
|
@@ -96,7 +106,7 @@ pub fn install() -> Result<(), String> {
|
|
| 96 |
use openwa_core::vtable_replace;
|
| 97 |
|
| 98 |
vtable_replace!(openwa_core::task::cloud::CTaskCloudVTable, va::CTASK_CLOUD_VTABLE, {
|
| 99 |
-
handle_message
|
| 100 |
})?;
|
| 101 |
|
| 102 |
let _ = log_line("[Cloud] HandleMessage hooked via vtable_replace");
|
|
|
|
| 3 |
//! Replaces CTaskCloud::HandleMessage (vtable slot 2).
|
| 4 |
//! Handles cloud position updates (wind parallax), rendering, and wind changes.
|
| 5 |
|
|
|
|
|
|
|
| 6 |
use openwa_core::address::va;
|
| 7 |
use openwa_core::fixed::Fixed;
|
| 8 |
use openwa_core::game::TaskMessage;
|
| 9 |
use openwa_core::log::log_line;
|
| 10 |
+
use openwa_core::render::queue::{command_type, DrawSpriteCmd};
|
| 11 |
use openwa_core::task::cloud::CTaskCloud;
|
| 12 |
use openwa_core::task::{CTask, Task};
|
| 13 |
|
|
|
|
|
|
|
|
|
|
| 14 |
/// CTaskCloud::HandleMessage replacement.
|
| 15 |
///
|
| 16 |
/// Handles three message types:
|
|
|
|
| 38 |
let wind = cloud.wind_accel.0;
|
| 39 |
cloud.pos_x = Fixed(cloud.pos_x.0 + cloud.vel_x.0 + wind * 10);
|
| 40 |
|
| 41 |
+
// Wrap X at landscape bounds (with 128.0 Fixed padding)
|
| 42 |
let ddgame = &*cloud.ddgame();
|
| 43 |
+
let padding = Fixed::from_int(128);
|
| 44 |
+
let level_left = ddgame.level_bound_min_x - padding;
|
| 45 |
+
let level_right = ddgame.level_bound_max_x + padding;
|
| 46 |
+
|
| 47 |
+
if cloud.pos_x < level_left {
|
| 48 |
+
cloud.pos_x = level_right;
|
| 49 |
+
} else if cloud.pos_x > level_right {
|
| 50 |
+
cloud.pos_x = level_left;
|
| 51 |
}
|
| 52 |
|
| 53 |
// Converge wind_accel toward wind_target (clamp step to ±0x147)
|
|
|
|
| 64 |
}
|
| 65 |
|
| 66 |
Ok(TaskMessage::RenderScene) => {
|
| 67 |
+
let ddgame = &mut *cloud.ddgame();
|
| 68 |
+
|
| 69 |
+
// Only render when rendering phase == 5 (in-game rendering active)
|
| 70 |
+
if ddgame.render_phase == 5 {
|
| 71 |
+
// Compute parallax X offset: (vel_x + wind * 10) * parallax_scale
|
| 72 |
+
let scroll_speed = cloud.vel_x.0 + cloud.wind_accel.0 * 10;
|
| 73 |
+
let parallax_x = ((scroll_speed as i64 * ddgame.parallax_scale as i64) >> 16) as i32;
|
| 74 |
+
let x = parallax_x + cloud.pos_x.0;
|
| 75 |
+
|
| 76 |
+
let rq = &mut *ddgame.render_queue;
|
| 77 |
+
if let Some(entry) = rq.alloc::<DrawSpriteCmd>() {
|
| 78 |
+
*entry = DrawSpriteCmd {
|
| 79 |
+
command_type: command_type::DRAW_SPRITE_LOCAL,
|
| 80 |
+
layer: cloud.layer_depth.0 as u32,
|
| 81 |
+
x_pos: x as u32 & 0xFFFF0000,
|
| 82 |
+
y_pos: cloud.pos_y.0 as u32 & 0xFFFF0000,
|
| 83 |
+
sprite_id: cloud.sprite_id,
|
| 84 |
+
frame: 0,
|
| 85 |
+
};
|
| 86 |
+
}
|
| 87 |
+
}
|
| 88 |
}
|
| 89 |
|
| 90 |
Ok(TaskMessage::SetWind) => {
|
|
|
|
| 106 |
use openwa_core::vtable_replace;
|
| 107 |
|
| 108 |
vtable_replace!(openwa_core::task::cloud::CTaskCloudVTable, va::CTASK_CLOUD_VTABLE, {
|
| 109 |
+
handle_message => cloud_handle_message,
|
| 110 |
})?;
|
| 111 |
|
| 112 |
let _ = log_line("[Cloud] HandleMessage hooked via vtable_replace");
|