feat: port Skip Go to pure Rust, type fire_weapon_special params
Browse filesReplace Skip Go (0x51E8C0) bridge with pure Rust implementation β
reads team arena flags via typed DDGame/GameInfo/CTaskWorm fields,
toggles bit based on weapon entry's fire_params and game_version.
Type fire_weapon_special params as *mut CTaskWorm and *const WeaponEntry.
Use (*worm).state() accessor for Teleport check instead of raw +0x44.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
crates/openwa-wormkit/src/replacements/weapon.rs
CHANGED
|
@@ -232,7 +232,7 @@ unsafe extern "cdecl" fn fire_weapon_impl(
|
|
| 232 |
}
|
| 233 |
4 => {
|
| 234 |
let subtype_38_ptr = &raw const (*entry).fire_subtype_38 as u32;
|
| 235 |
-
fire_weapon_special(subtype_34, subtype_38_ptr,
|
| 236 |
}
|
| 237 |
_ => {}
|
| 238 |
}
|
|
@@ -349,47 +349,80 @@ unsafe extern "C" fn call_fire_stdcall2(
|
|
| 349 |
/// Some handlers explicitly set EAX=worm or EAX=*(worm+0x44).
|
| 350 |
/// Handlers without explicit MOV EAX inherit the entry pointer.
|
| 351 |
unsafe fn fire_weapon_special(
|
| 352 |
-
subtype: i32, params_38: u32, worm:
|
| 353 |
entry: *const WeaponEntry,
|
| 354 |
) {
|
| 355 |
use openwa_core::rebase::rb;
|
| 356 |
-
let
|
|
|
|
| 357 |
|
| 358 |
match subtype {
|
| 359 |
-
1 => fire_worm_vtable_0xe(
|
| 360 |
-
2 => call_fire_usercall(
|
| 361 |
-
3 => call_fire_stdcall3(
|
| 362 |
-
4 => fire_worm_vtable_0xe(
|
| 363 |
-
5 => fire_worm_vtable_0xe(
|
| 364 |
-
6 => fire_worm_vtable_0xe(
|
| 365 |
-
8 => fire_worm_vtable_0xe(
|
| 366 |
-
9 => call_fire_usercall_stdcall1(
|
| 367 |
-
10 => call_fire_usercall(
|
| 368 |
-
11 => fire_worm_vtable_0xe(
|
| 369 |
-
13 => call_fire_usercall(
|
| 370 |
-
14 => call_fire_usercall(
|
| 371 |
16 => {
|
| 372 |
-
// Teleport: MOV EAX,[ESI+0x44] (worm state
|
| 373 |
-
let worm_state =
|
| 374 |
-
let result = call_fire_usercall_ret(worm_state,
|
| 375 |
if result != 0 {
|
| 376 |
-
call_fire_usercall(result as u32,
|
| 377 |
} else {
|
| 378 |
-
fire_worm_vtable_0xe(
|
| 379 |
}
|
| 380 |
}
|
| 381 |
-
17 => call_fire_usercall(
|
| 382 |
-
18 => fire_worm_vtable_0xe(
|
| 383 |
-
19 =>
|
| 384 |
-
20 => call_fire_usercall(
|
| 385 |
-
21 => call_fire_usercall(
|
| 386 |
-
22 => call_fire_usercall(
|
| 387 |
-
23 => fire_worm_vtable_0xe(
|
| 388 |
-
24 => call_fire_usercall(
|
| 389 |
_ => {}
|
| 390 |
}
|
| 391 |
}
|
| 392 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 393 |
/// Bridge: usercall(EAX=eax_val, ESI=worm, EDI=worm), plain RET.
|
| 394 |
/// Args: (eax_val, worm, addr). Saves/restores ESI+EDI. Uses EBX to hold addr.
|
| 395 |
#[unsafe(naked)]
|
|
|
|
| 232 |
}
|
| 233 |
4 => {
|
| 234 |
let subtype_38_ptr = &raw const (*entry).fire_subtype_38 as u32;
|
| 235 |
+
fire_weapon_special(subtype_34, subtype_38_ptr, worm, local_struct, entry);
|
| 236 |
}
|
| 237 |
_ => {}
|
| 238 |
}
|
|
|
|
| 349 |
/// Some handlers explicitly set EAX=worm or EAX=*(worm+0x44).
|
| 350 |
/// Handlers without explicit MOV EAX inherit the entry pointer.
|
| 351 |
unsafe fn fire_weapon_special(
|
| 352 |
+
subtype: i32, params_38: u32, worm: *mut CTaskWorm, local_struct: u32,
|
| 353 |
entry: *const WeaponEntry,
|
| 354 |
) {
|
| 355 |
use openwa_core::rebase::rb;
|
| 356 |
+
let w = worm as u32;
|
| 357 |
+
let e = entry as u32;
|
| 358 |
|
| 359 |
match subtype {
|
| 360 |
+
1 => fire_worm_vtable_0xe(w, 0x6C), // Blowtorch
|
| 361 |
+
2 => call_fire_usercall(e, w, rb(0x51E3E0)), // Drill (EAX=entry)
|
| 362 |
+
3 => call_fire_stdcall3(w, params_38 as *const WeaponFireParams, local_struct, rb(0x51E350)), // Girder
|
| 363 |
+
4 => fire_worm_vtable_0xe(w, 0x6D), // Baseball Bat
|
| 364 |
+
5 => fire_worm_vtable_0xe(w, 0x75), // Fire Punch
|
| 365 |
+
6 => fire_worm_vtable_0xe(w, 0x70), // Dragon Ball
|
| 366 |
+
8 => fire_worm_vtable_0xe(w, 0x6E), // Kamikaze
|
| 367 |
+
9 => call_fire_usercall_stdcall1(e, w, local_struct, rb(0x51E480)), // Prod (EAX=entry)
|
| 368 |
+
10 => call_fire_usercall(w, w, rb(0x51E710)), // Air Strike (EAX=worm)
|
| 369 |
+
11 => fire_worm_vtable_0xe(w, 0x71), // Scales of Justice
|
| 370 |
+
13 => call_fire_usercall(e, w, rb(0x51E5C0)), // Napalm (EAX=entry)
|
| 371 |
+
14 => call_fire_usercall(w, w, rb(0x51E670)), // Mail/Mine/Mole (EAX=worm)
|
| 372 |
16 => {
|
| 373 |
+
// Teleport: MOV EAX,[ESI+0x44] (worm state) before check
|
| 374 |
+
let worm_state = (*worm).state();
|
| 375 |
+
let result = call_fire_usercall_ret(worm_state, w, rb(0x516930));
|
| 376 |
if result != 0 {
|
| 377 |
+
call_fire_usercall(result as u32, w, rb(0x51EB00));
|
| 378 |
} else {
|
| 379 |
+
fire_worm_vtable_0xe(w, 0x74);
|
| 380 |
}
|
| 381 |
}
|
| 382 |
+
17 => call_fire_usercall(w, w, rb(0x51E920)), // Freeze (EAX=worm)
|
| 383 |
+
18 => fire_worm_vtable_0xe(w, 0x72), // Suicide Bomber
|
| 384 |
+
19 => fire_skip_go(worm, entry), // Skip Go (pure Rust)
|
| 385 |
+
20 => call_fire_usercall(w, w, rb(0x51E600)), // Surrender (EAX=worm)
|
| 386 |
+
21 => call_fire_usercall(e, w, rb(0x51EBE0)), // Select Worm (EAX=entry)
|
| 387 |
+
22 => call_fire_usercall(e, w, rb(0x51EC30)), // Jet Pack (EAX=entry)
|
| 388 |
+
23 => fire_worm_vtable_0xe(w, 0x78), // Magic Bullet
|
| 389 |
+
24 => call_fire_usercall(e, w, rb(0x51EA60)), // Low Grav (EAX=entry)
|
| 390 |
_ => {}
|
| 391 |
}
|
| 392 |
}
|
| 393 |
|
| 394 |
+
// ββ Pure Rust fire handlers (no bridge needed) ββββββββββββββ
|
| 395 |
+
|
| 396 |
+
/// Skip Go (subtype 19) β pure Rust replacement for 0x51E8C0.
|
| 397 |
+
///
|
| 398 |
+
/// Toggles a bit in the team's arena state flags (DDGame+0x4628 + team*0x51C).
|
| 399 |
+
/// Bit position from weapon entry's fire_params field.
|
| 400 |
+
/// If game_version > 0x1C and bit already set: clears it. Otherwise: sets it.
|
| 401 |
+
unsafe fn fire_skip_go(worm: *const CTaskWorm, entry: *const WeaponEntry) {
|
| 402 |
+
use openwa_core::engine::DDGame;
|
| 403 |
+
use openwa_core::engine::GameInfo;
|
| 404 |
+
|
| 405 |
+
let ddgame = (*worm).base.base.ddgame as *const DDGame;
|
| 406 |
+
let game_info = (*ddgame).game_info as *const GameInfo;
|
| 407 |
+
let game_version = (*game_info).game_version;
|
| 408 |
+
let team_index = (*worm).team_index as usize;
|
| 409 |
+
|
| 410 |
+
let bit_index = (*entry).fire_params._data[0] & 0x1F;
|
| 411 |
+
let bit = 1u32 << bit_index;
|
| 412 |
+
|
| 413 |
+
// Team arena flags at DDGame+0x4628 + team_index * 0x51C
|
| 414 |
+
let flags_ptr = (ddgame as *mut u8).add(0x4628 + team_index * 0x51C) as *mut u32;
|
| 415 |
+
let flags = *flags_ptr;
|
| 416 |
+
|
| 417 |
+
if game_version > 0x1C && (flags & bit) != 0 {
|
| 418 |
+
*flags_ptr = flags & !bit; // clear
|
| 419 |
+
} else {
|
| 420 |
+
*flags_ptr = flags | bit; // set
|
| 421 |
+
}
|
| 422 |
+
}
|
| 423 |
+
|
| 424 |
+
// ββ Naked asm bridges βββββββββββββββββββββββββββββββββββββββ
|
| 425 |
+
|
| 426 |
/// Bridge: usercall(EAX=eax_val, ESI=worm, EDI=worm), plain RET.
|
| 427 |
/// Args: (eax_val, worm, addr). Saves/restores ESI+EDI. Uses EBX to hold addr.
|
| 428 |
#[unsafe(naked)]
|