feat: add arrow sprites, DisplayGfx, CoordList, weapon sprites
Browse filesImplement remaining constructor sections in create_ddgame():
- Arrow sprite loading loop (32 iterations) with GfxDir bridges
- Arrow collision SpriteRegions
- Arrow GfxDir objects (conditional on secondary gfxdir)
- DisplayGfx at DDGame+0x138
- CoordList at DDGame+0x50C (allocation, population TODO)
- Weapon sprite loading (2x DDGame__LoadWeaponSprites_Maybe)
- Diagnostic log lines at key milestones
Remaining TODO: gradient images, layer/fill sprites, HUD, display
finalization (~200 lines of original 887).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
crates/openwa-core/src/engine/ddgame.rs
CHANGED
|
@@ -5,6 +5,7 @@ use crate::engine::ddgame_wrapper::DDGameWrapper;
|
|
| 5 |
use crate::engine::game_info::GameInfo;
|
| 6 |
use crate::input::keyboard::DDKeyboard;
|
| 7 |
use crate::audio::dssound::DSSound;
|
|
|
|
| 8 |
use crate::render::landscape::PCLandscape;
|
| 9 |
use crate::audio::music::Music;
|
| 10 |
use crate::display::palette::Palette;
|
|
@@ -420,6 +421,8 @@ pub unsafe fn create_ddgame(
|
|
| 420 |
game_info: *mut GameInfo,
|
| 421 |
network_ecx: u32, // implicit ECX from caller
|
| 422 |
) -> *mut DDGame {
|
|
|
|
|
|
|
| 423 |
// ββ 1. Allocate and zero-fill (matches: memset(piVar3, 0, 0x98B8)) ββ
|
| 424 |
let ddgame = wa_malloc(0x98B8) as *mut DDGame;
|
| 425 |
if ddgame.is_null() {
|
|
@@ -471,6 +474,11 @@ pub unsafe fn create_ddgame(
|
|
| 471 |
}
|
| 472 |
}
|
| 473 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 474 |
// ββ 9. InitVersionFlags β sets DDGame+0x7E2E/0x7E2F/0x7E3F ββ
|
| 475 |
call_init_version_flags(wrapper);
|
| 476 |
|
|
@@ -536,6 +544,10 @@ unsafe fn init_graphics_and_resources(
|
|
| 536 |
|
| 537 |
let headless_offset = if f914 != 0 { 1u32 } else { 0u32 };
|
| 538 |
(*wrapper).gfx_mode = if gfx_loaded_idx.wrapping_sub(headless_offset) < 2 { 1 } else { 0 };
|
|
|
|
|
|
|
|
|
|
|
|
|
| 539 |
|
| 540 |
// ββ GfxHandler #2 (conditional) ββ
|
| 541 |
let d778 = *(game_info as *const u8).add(0xD778).cast::<i32>();
|
|
@@ -693,6 +705,8 @@ unsafe fn init_graphics_and_resources(
|
|
| 693 |
}
|
| 694 |
}
|
| 695 |
|
|
|
|
|
|
|
| 696 |
// ββ GfxResource (used by PCLandscape and later by gradients) ββ
|
| 697 |
let gfx_resource_create: unsafe extern "thiscall" fn(*mut u8, *mut u8) -> *mut u8 =
|
| 698 |
core::mem::transmute(rb(va::GFX_RESOURCE_CREATE) as usize);
|
|
@@ -743,6 +757,10 @@ unsafe fn init_graphics_and_resources(
|
|
| 743 |
}
|
| 744 |
};
|
| 745 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 746 |
// ββ TaskStateMachine at DDGame+0x380 (alloc 0x2C) ββ
|
| 747 |
{
|
| 748 |
let tsm = wa_malloc(0x2C);
|
|
@@ -804,16 +822,193 @@ unsafe fn init_graphics_and_resources(
|
|
| 804 |
release(gfx_resource);
|
| 805 |
}
|
| 806 |
|
|
|
|
|
|
|
| 807 |
// ββ Arrow sprites + collision regions (32 iterations) ββ
|
| 808 |
-
|
| 809 |
-
|
| 810 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 811 |
// ββ Display finalization ββ
|
| 812 |
//
|
| 813 |
-
// These remaining sections (~
|
| 814 |
-
//
|
| 815 |
-
// layer management.
|
| 816 |
-
//
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 817 |
}
|
| 818 |
|
| 819 |
/// Bridge to SpriteRegion__Constructor (0x57DB20).
|
|
|
|
| 5 |
use crate::engine::game_info::GameInfo;
|
| 6 |
use crate::input::keyboard::DDKeyboard;
|
| 7 |
use crate::audio::dssound::DSSound;
|
| 8 |
+
use crate::log::log_line;
|
| 9 |
use crate::render::landscape::PCLandscape;
|
| 10 |
use crate::audio::music::Music;
|
| 11 |
use crate::display::palette::Palette;
|
|
|
|
| 421 |
game_info: *mut GameInfo,
|
| 422 |
network_ecx: u32, // implicit ECX from caller
|
| 423 |
) -> *mut DDGame {
|
| 424 |
+
let _ = log_line("[DDGame] create_ddgame: begin");
|
| 425 |
+
|
| 426 |
// ββ 1. Allocate and zero-fill (matches: memset(piVar3, 0, 0x98B8)) ββ
|
| 427 |
let ddgame = wa_malloc(0x98B8) as *mut DDGame;
|
| 428 |
if ddgame.is_null() {
|
|
|
|
| 474 |
}
|
| 475 |
}
|
| 476 |
|
| 477 |
+
let _ = log_line(&format!(
|
| 478 |
+
"[DDGame] create_ddgame: alloc=0x{:08X}, headless={}, sound=0x{:08X}",
|
| 479 |
+
ddgame as u32, is_headless, sound as u32,
|
| 480 |
+
));
|
| 481 |
+
|
| 482 |
// ββ 9. InitVersionFlags β sets DDGame+0x7E2E/0x7E2F/0x7E3F ββ
|
| 483 |
call_init_version_flags(wrapper);
|
| 484 |
|
|
|
|
| 544 |
|
| 545 |
let headless_offset = if f914 != 0 { 1u32 } else { 0u32 };
|
| 546 |
(*wrapper).gfx_mode = if gfx_loaded_idx.wrapping_sub(headless_offset) < 2 { 1 } else { 0 };
|
| 547 |
+
let _ = log_line(&format!(
|
| 548 |
+
"[DDGame] GfxHandler #1 loaded (idx={}), gfx_mode={}",
|
| 549 |
+
gfx_loaded_idx, (*wrapper).gfx_mode,
|
| 550 |
+
));
|
| 551 |
|
| 552 |
// ββ GfxHandler #2 (conditional) ββ
|
| 553 |
let d778 = *(game_info as *const u8).add(0xD778).cast::<i32>();
|
|
|
|
| 705 |
}
|
| 706 |
}
|
| 707 |
|
| 708 |
+
let _ = log_line("[DDGame] audio init done");
|
| 709 |
+
|
| 710 |
// ββ GfxResource (used by PCLandscape and later by gradients) ββ
|
| 711 |
let gfx_resource_create: unsafe extern "thiscall" fn(*mut u8, *mut u8) -> *mut u8 =
|
| 712 |
core::mem::transmute(rb(va::GFX_RESOURCE_CREATE) as usize);
|
|
|
|
| 757 |
}
|
| 758 |
};
|
| 759 |
|
| 760 |
+
let _ = log_line(&format!(
|
| 761 |
+
"[DDGame] PCLandscape=0x{:08X}", landscape as u32,
|
| 762 |
+
));
|
| 763 |
+
|
| 764 |
// ββ TaskStateMachine at DDGame+0x380 (alloc 0x2C) ββ
|
| 765 |
{
|
| 766 |
let tsm = wa_malloc(0x2C);
|
|
|
|
| 822 |
release(gfx_resource);
|
| 823 |
}
|
| 824 |
|
| 825 |
+
let _ = log_line("[DDGame] SpriteRegions + TSM done");
|
| 826 |
+
|
| 827 |
// ββ Arrow sprites + collision regions (32 iterations) ββ
|
| 828 |
+
{
|
| 829 |
+
let gfx_handler = (*wrapper)._field_4c0;
|
| 830 |
+
let gfx_find_entry = rb(va::GFX_DIR_FIND_ENTRY) as u32;
|
| 831 |
+
let gfx_load_image = rb(va::GFX_DIR_LOAD_IMAGE) as u32;
|
| 832 |
+
let fun_4f5f80 = rb(0x4F5F80) as u32;
|
| 833 |
+
let _sprintf = rb(0x5D125A) as u32;
|
| 834 |
+
|
| 835 |
+
for i in 0u32..32 {
|
| 836 |
+
// Format "arrow%02u.img\0" into stack buffer
|
| 837 |
+
let mut name_buf = *b"arrow00.img\0\0\0\0\0";
|
| 838 |
+
name_buf[5] = b'0' + (i / 10) as u8;
|
| 839 |
+
name_buf[6] = b'0' + (i % 10) as u8;
|
| 840 |
+
|
| 841 |
+
// Display vtable[5](1) β set active layer
|
| 842 |
+
{
|
| 843 |
+
let disp_vt = *((*ddgame).display as *const *const u32);
|
| 844 |
+
let set_layer: unsafe extern "thiscall" fn(*mut DDDisplay, i32) =
|
| 845 |
+
core::mem::transmute(*disp_vt.add(5));
|
| 846 |
+
set_layer((*ddgame).display, 1);
|
| 847 |
+
}
|
| 848 |
+
|
| 849 |
+
// GfxDir__FindEntry: usercall(EAX=name) + stdcall(gfx_handler), RET 0x4
|
| 850 |
+
let entry: *mut u8;
|
| 851 |
+
core::arch::asm!(
|
| 852 |
+
"push {handler}",
|
| 853 |
+
"call {find}",
|
| 854 |
+
handler = in(reg) gfx_handler,
|
| 855 |
+
find = in(reg) gfx_find_entry,
|
| 856 |
+
inlateout("eax") name_buf.as_ptr() => entry,
|
| 857 |
+
out("ecx") _,
|
| 858 |
+
out("edx") _,
|
| 859 |
+
clobber_abi("C"),
|
| 860 |
+
);
|
| 861 |
+
|
| 862 |
+
let sprite: *mut u8;
|
| 863 |
+
if !entry.is_null() {
|
| 864 |
+
// Try gfx_handler->vtable[2](entry->field_4)
|
| 865 |
+
let gfx_vt = *(gfx_handler as *const *const u32);
|
| 866 |
+
let load_cached: unsafe extern "thiscall" fn(*mut u8, u32) -> *mut u8 =
|
| 867 |
+
core::mem::transmute(*gfx_vt.add(2));
|
| 868 |
+
let cached = load_cached(gfx_handler, *(entry.add(4) as *const u32));
|
| 869 |
+
if !cached.is_null() {
|
| 870 |
+
sprite = cached;
|
| 871 |
+
} else {
|
| 872 |
+
// Fallback to GfxDir__LoadImage
|
| 873 |
+
sprite = call_gfx_load_and_wrap(
|
| 874 |
+
name_buf.as_ptr(), gfx_load_image, fun_4f5f80,
|
| 875 |
+
);
|
| 876 |
+
}
|
| 877 |
+
} else {
|
| 878 |
+
sprite = call_gfx_load_and_wrap(
|
| 879 |
+
name_buf.as_ptr(), gfx_load_image, fun_4f5f80,
|
| 880 |
+
);
|
| 881 |
+
}
|
| 882 |
+
|
| 883 |
+
// Store arrow sprite at DDGame+0x38+i*4
|
| 884 |
+
(*ddgame).arrow_sprites[i as usize] = sprite;
|
| 885 |
+
|
| 886 |
+
// Calculate collision region dimensions from sprite
|
| 887 |
+
if !sprite.is_null() {
|
| 888 |
+
let sprite_w = *(sprite.add(0x14) as *const i32);
|
| 889 |
+
let sprite_h = *(sprite.add(0x18) as *const i32);
|
| 890 |
+
let half_w = (sprite_w / 2 - 10).max(0);
|
| 891 |
+
let half_h = (sprite_h / 2 - 10).max(0);
|
| 892 |
+
|
| 893 |
+
// Create SpriteRegion for collision
|
| 894 |
+
let alloc = wa_malloc(0x9C);
|
| 895 |
+
core::ptr::write_bytes(alloc, 0, 0x9C);
|
| 896 |
+
let region = if !alloc.is_null() {
|
| 897 |
+
call_sprite_region_ctor(alloc, 0, 0, 0, half_w as u32, 0, half_h as u32, 0)
|
| 898 |
+
} else {
|
| 899 |
+
core::ptr::null_mut()
|
| 900 |
+
};
|
| 901 |
+
(*ddgame).arrow_collision_regions[i as usize] = region;
|
| 902 |
+
}
|
| 903 |
+
|
| 904 |
+
// Arrow GfxDir (conditional on secondary gfxdir)
|
| 905 |
+
if !(*ddgame).secondary_gfxdir.is_null() {
|
| 906 |
+
let gfx_resource_create: unsafe extern "thiscall" fn(*mut u8, *mut u8) -> *mut u8 =
|
| 907 |
+
core::mem::transmute(rb(va::GFX_RESOURCE_CREATE) as usize);
|
| 908 |
+
(*ddgame).arrow_gfxdirs[i as usize] =
|
| 909 |
+
gfx_resource_create(gfx_handler, core::ptr::null_mut());
|
| 910 |
+
}
|
| 911 |
+
}
|
| 912 |
+
}
|
| 913 |
+
|
| 914 |
+
let _ = log_line("[DDGame] arrow sprites done");
|
| 915 |
+
|
| 916 |
+
// ββ DisplayGfx at DDGame+0x138 ββ
|
| 917 |
+
{
|
| 918 |
+
let tsm = wa_malloc(0x2C);
|
| 919 |
+
core::ptr::write_bytes(tsm, 0, 0x2C);
|
| 920 |
+
if !tsm.is_null() {
|
| 921 |
+
let tsm_init: unsafe extern "fastcall" fn(u32, u32) =
|
| 922 |
+
core::mem::transmute(rb(va::TASK_STATE_MACHINE_INIT) as usize);
|
| 923 |
+
tsm_init(0, 0);
|
| 924 |
+
*(tsm as *mut u32) = rb(0x6641F8); // DisplayGfx__vtable
|
| 925 |
+
}
|
| 926 |
+
(*ddgame).display_gfx = tsm;
|
| 927 |
+
}
|
| 928 |
+
|
| 929 |
+
// ββ CoordList at DDGame+0x50C (capacity 600, 0x12C0 buffer) ββ
|
| 930 |
+
{
|
| 931 |
+
let cl = wa_malloc(12) as *mut u32;
|
| 932 |
+
*cl = 0; // count
|
| 933 |
+
*cl.add(1) = 600; // capacity
|
| 934 |
+
let data = wa_malloc(0x12C0);
|
| 935 |
+
core::ptr::write_bytes(data, 0, 0x12C0);
|
| 936 |
+
*cl.add(2) = data as u32;
|
| 937 |
+
(*ddgame).coord_list = cl as *mut u8;
|
| 938 |
+
|
| 939 |
+
// TODO: populate coord_list from landscape data (complex loop)
|
| 940 |
+
}
|
| 941 |
+
|
| 942 |
+
// ββ Weapon sprites (FUN_005717A0 Γ2) ββ
|
| 943 |
+
{
|
| 944 |
+
let load_weapon_sprites: unsafe extern "C" fn() =
|
| 945 |
+
core::mem::transmute(rb(va::DDGAME_LOAD_WEAPON_SPRITES) as usize);
|
| 946 |
+
load_weapon_sprites();
|
| 947 |
+
load_weapon_sprites();
|
| 948 |
+
}
|
| 949 |
+
|
| 950 |
+
let _ = log_line("[DDGame] weapon sprites done");
|
| 951 |
+
|
| 952 |
+
// ββ Gradient images, layer sprites, fill image, HUD ββ
|
| 953 |
// ββ Display finalization ββ
|
| 954 |
//
|
| 955 |
+
// These remaining sections (~200 lines) involve complex gradient
|
| 956 |
+
// computation, sprite loading with GfxDir lookups, and display
|
| 957 |
+
// layer management. Will be added in subsequent commits.
|
| 958 |
+
//
|
| 959 |
+
// TODO: layer.spr, back.spr, debris.spr loading
|
| 960 |
+
// TODO: gradient image processing (largest remaining section)
|
| 961 |
+
// TODO: fill.img β fill_pixel at DDGame+0x7338
|
| 962 |
+
// TODO: HUD_LoadWeaponSprites_Maybe
|
| 963 |
+
// TODO: Close GfxHandlers
|
| 964 |
+
// TODO: Display finalization (layer visibility)
|
| 965 |
+
}
|
| 966 |
+
|
| 967 |
+
/// Helper: load image via GfxDir__LoadImage + wrap with FUN_004F5F80.
|
| 968 |
+
/// Used by arrow sprite loop when GfxDir__FindEntry fails.
|
| 969 |
+
#[cfg(target_arch = "x86")]
|
| 970 |
+
unsafe fn call_gfx_load_and_wrap(
|
| 971 |
+
name: *const u8,
|
| 972 |
+
gfx_load_image_addr: u32,
|
| 973 |
+
fun_4f5f80_addr: u32,
|
| 974 |
+
) -> *mut u8 {
|
| 975 |
+
// GfxDir__LoadImage: usercall(EAX=name?), stdcall(1 stack param), RET 0x4
|
| 976 |
+
let image: *mut u8;
|
| 977 |
+
core::arch::asm!(
|
| 978 |
+
"push {name}",
|
| 979 |
+
"call {load}",
|
| 980 |
+
name = in(reg) name,
|
| 981 |
+
load = in(reg) gfx_load_image_addr,
|
| 982 |
+
out("eax") image,
|
| 983 |
+
out("ecx") _,
|
| 984 |
+
out("edx") _,
|
| 985 |
+
clobber_abi("C"),
|
| 986 |
+
);
|
| 987 |
+
if image.is_null() {
|
| 988 |
+
return core::ptr::null_mut();
|
| 989 |
+
}
|
| 990 |
+
// FUN_004F5F80(display_gfx_context, image, 1)
|
| 991 |
+
let result: *mut u8;
|
| 992 |
+
core::arch::asm!(
|
| 993 |
+
"push 1",
|
| 994 |
+
"push {img}",
|
| 995 |
+
"push {ctx}",
|
| 996 |
+
"call {wrap}",
|
| 997 |
+
"add esp, 12",
|
| 998 |
+
img = in(reg) image,
|
| 999 |
+
ctx = in(reg) 0u32, // display_gfx context, often 0
|
| 1000 |
+
wrap = in(reg) fun_4f5f80_addr,
|
| 1001 |
+
out("eax") result,
|
| 1002 |
+
out("ecx") _,
|
| 1003 |
+
out("edx") _,
|
| 1004 |
+
clobber_abi("C"),
|
| 1005 |
+
);
|
| 1006 |
+
// Release the raw image
|
| 1007 |
+
let image_vt = *(image as *const *const u32);
|
| 1008 |
+
let destroy: unsafe extern "thiscall" fn(*mut u8, u32) =
|
| 1009 |
+
core::mem::transmute(*image_vt);
|
| 1010 |
+
destroy(image, 1);
|
| 1011 |
+
result
|
| 1012 |
}
|
| 1013 |
|
| 1014 |
/// Bridge to SpriteRegion__Constructor (0x57DB20).
|