feat: port Girder weapon to pure Rust
Browse filesPort FireWeapon__Girder (0x51E350) — allocates CTaskFireBall (0xA4 bytes),
copies 7 DWORDs from fire_params, calls CTaskFireBall::Constructor via
naked asm bridge (usercall EAX=parent + 9 stack params, RET 0x24).
Same allocation+constructor pattern as CreateWeaponProjectile and CreateArrow.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
crates/openwa-wormkit/src/replacements/weapon.rs
CHANGED
|
@@ -361,8 +361,8 @@ unsafe fn fire_weapon_special(
|
|
| 361 |
1 => (*worm).set_state(WormState::Blowtorch),
|
| 362 |
// Pneumatic Drill (pure Rust)
|
| 363 |
2 => fire_drill(worm, local_struct),
|
| 364 |
-
// Girder
|
| 365 |
-
3 =>
|
| 366 |
// Baseball Bat
|
| 367 |
4 => (*worm).set_state(WormState::BaseballBat),
|
| 368 |
// Fire Punch
|
|
@@ -555,6 +555,74 @@ unsafe fn fire_mail_mine_mole(worm: *mut CTaskWorm) {
|
|
| 555 |
entry.turn_action_counter_Maybe += 7;
|
| 556 |
}
|
| 557 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 558 |
/// Freeze (subtype 17) — pure Rust port of 0x51E920.
|
| 559 |
///
|
| 560 |
/// Plays a freeze sound, optionally creates a freeze visual overlay on the
|
|
|
|
| 361 |
1 => (*worm).set_state(WormState::Blowtorch),
|
| 362 |
// Pneumatic Drill (pure Rust)
|
| 363 |
2 => fire_drill(worm, local_struct),
|
| 364 |
+
// Girder (pure Rust)
|
| 365 |
+
3 => fire_girder(worm, params_38_ptr, local_struct),
|
| 366 |
// Baseball Bat
|
| 367 |
4 => (*worm).set_state(WormState::BaseballBat),
|
| 368 |
// Fire Punch
|
|
|
|
| 555 |
entry.turn_action_counter_Maybe += 7;
|
| 556 |
}
|
| 557 |
|
| 558 |
+
/// Girder (type 4 subtype 3) — pure Rust port of 0x51E350.
|
| 559 |
+
///
|
| 560 |
+
/// Allocates a CTaskFireBall (0xA4 bytes), copies 7 DWORDs from fire_params
|
| 561 |
+
/// as constructor arguments, and calls the constructor. The constructor handles
|
| 562 |
+
/// actually placing the girder on the landscape.
|
| 563 |
+
///
|
| 564 |
+
/// Convention: stdcall(worm, fire_params, local_struct), RET 0xC.
|
| 565 |
+
unsafe fn fire_girder(
|
| 566 |
+
worm: *mut CTaskWorm, fire_params: *const WeaponFireParams, local_struct: *const u8,
|
| 567 |
+
) {
|
| 568 |
+
use openwa_core::rebase::rb;
|
| 569 |
+
use openwa_core::task::{SharedDataTable, Task};
|
| 570 |
+
use openwa_core::wa_alloc::wa_malloc;
|
| 571 |
+
|
| 572 |
+
// Look up parent task via SharedData (same key as CreateWeaponProjectile)
|
| 573 |
+
let table = SharedDataTable::from_task((*worm).as_task_ptr());
|
| 574 |
+
let parent = table.lookup(0, 0x19);
|
| 575 |
+
|
| 576 |
+
// Allocate CTaskFireBall (0xA4 bytes), zero first 0x84
|
| 577 |
+
let buffer = wa_malloc(0xA4);
|
| 578 |
+
if buffer.is_null() {
|
| 579 |
+
return;
|
| 580 |
+
}
|
| 581 |
+
core::ptr::write_bytes(buffer, 0, 0x84);
|
| 582 |
+
|
| 583 |
+
// CTaskFireBall::Constructor — usercall(EAX=parent) +
|
| 584 |
+
// stdcall(this, 7×fire_param DWORDs, local_struct), RET 0x24.
|
| 585 |
+
// Copy 7 DWORDs from fire_params onto the stack via the naked bridge.
|
| 586 |
+
call_fireball_ctor(buffer, parent as *mut u8, fire_params, local_struct, rb(0x550890));
|
| 587 |
+
}
|
| 588 |
+
|
| 589 |
+
/// Bridge: CTaskFireBall::Constructor — usercall(EAX=parent) +
|
| 590 |
+
/// stdcall(this, 7 DWORDs from fire_params, local_struct), RET 0x24.
|
| 591 |
+
///
|
| 592 |
+
/// The original copies 7 DWORDs from fire_params onto the stack via REP MOVSD.
|
| 593 |
+
/// We replicate this by pushing them individually in reverse order.
|
| 594 |
+
#[unsafe(naked)]
|
| 595 |
+
unsafe extern "C" fn call_fireball_ctor(
|
| 596 |
+
_this: *mut u8, _parent: *mut u8, _fire_params: *const WeaponFireParams,
|
| 597 |
+
_local_struct: *const u8, _addr: u32,
|
| 598 |
+
) {
|
| 599 |
+
core::arch::naked_asm!(
|
| 600 |
+
"push ebx",
|
| 601 |
+
"push esi",
|
| 602 |
+
"push edi",
|
| 603 |
+
// Stack: 3 saves(12) + ret(4) = 16 to first arg
|
| 604 |
+
// Args: this=+16, parent=+20, fire_params=+24, local_struct=+28, addr=+32
|
| 605 |
+
"mov esi, [esp+24]", // ESI = fire_params
|
| 606 |
+
"mov eax, [esp+20]", // EAX = parent (usercall)
|
| 607 |
+
"mov ebx, [esp+32]", // EBX = ctor address
|
| 608 |
+
// Push constructor args in reverse: local_struct, 7 DWORDs, this
|
| 609 |
+
"push [esp+28]", // local_struct (B+28 → stack pos 1)
|
| 610 |
+
"push [esi+24]", // fire_params[6]
|
| 611 |
+
"push [esi+20]", // fire_params[5]
|
| 612 |
+
"push [esi+16]", // fire_params[4]
|
| 613 |
+
"push [esi+12]", // fire_params[3]
|
| 614 |
+
"push [esi+8]", // fire_params[2]
|
| 615 |
+
"push [esi+4]", // fire_params[1]
|
| 616 |
+
"push [esi]", // fire_params[0] (B-28 → stack pos 8)
|
| 617 |
+
"push [esp+48]", // this: B-32+48 = B+16 ✓
|
| 618 |
+
"call ebx", // RET 0x24 cleans 9 params
|
| 619 |
+
"pop edi",
|
| 620 |
+
"pop esi",
|
| 621 |
+
"pop ebx",
|
| 622 |
+
"ret",
|
| 623 |
+
);
|
| 624 |
+
}
|
| 625 |
+
|
| 626 |
/// Freeze (subtype 17) — pure Rust port of 0x51E920.
|
| 627 |
///
|
| 628 |
/// Plays a freeze sound, optionally creates a freeze visual overlay on the
|