paavohtl Claude Opus 4.6 (1M context) commited on
Commit
78418c5
Β·
1 Parent(s): ed7ea8c

feat: port Freeze weapon to pure Rust

Browse files

Port FireWeapon__Freeze (0x51E920) β€” plays freeze sound at worm position,
creates landscape freeze overlay via PCLandscape vtable[5], and increments
WormEntry counters (turn_action +3, effect_counter +10).

Sound positioning uses the DDGame sound queue entry directly after
PlaySoundGlobal queues the sound effect.

Add freeze_nuke_armageddon replay test (2Γ— Freeze, plus Nuke/Armageddon).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

crates/openwa-wormkit/src/replacements/weapon.rs CHANGED
@@ -391,8 +391,8 @@ unsafe fn fire_weapon_special(
391
  (*worm).set_state(WormState::TeleportCancelled_Maybe);
392
  }
393
  }
394
- // Freeze (EAX=worm)
395
- 17 => call_fire_usercall(worm as *const (), worm, rb(0x51E920)),
396
  // Suicide Bomber
397
  18 => (*worm).set_state(WormState::SuicideBomber),
398
  // Skip Go (pure Rust)
@@ -555,6 +555,72 @@ unsafe fn fire_mail_mine_mole(worm: *mut CTaskWorm) {
555
  entry.turn_action_counter_Maybe += 7;
556
  }
557
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
558
  // ── SpecialImpact wrapper ─────────────────────────────────
559
 
560
  /// Typed wrapper for SpecialImpact (0x5193D0).
 
391
  (*worm).set_state(WormState::TeleportCancelled_Maybe);
392
  }
393
  }
394
+ // Freeze (pure Rust)
395
+ 17 => fire_freeze(worm),
396
  // Suicide Bomber
397
  18 => (*worm).set_state(WormState::SuicideBomber),
398
  // Skip Go (pure Rust)
 
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
561
+ /// landscape and increments worm counters. The visual/counter path only runs
562
+ /// when CTaskWorm+0x2EC (freeze_target_team) is nonzero.
563
+ ///
564
+ /// Convention: usercall(EAX=worm, ESI=worm, EDI=worm), plain RET.
565
+ unsafe fn fire_freeze(worm: *mut CTaskWorm) {
566
+ use openwa_core::rebase::rb;
567
+
568
+ let ddgame = (*worm).ddgame();
569
+ let game_version = (*(*ddgame).game_info).game_version;
570
+
571
+ // Read freeze position and target from CTaskWorm fields
572
+ let worm_raw = worm as *const u8;
573
+ let freeze_x = *(worm_raw.add(0x2E0) as *const i32);
574
+ let freeze_y = *(worm_raw.add(0x2E4) as *const i32);
575
+ let freeze_target = *(worm_raw.add(0x2EC) as *const i32);
576
+
577
+ // Choose sound: 0x70 if freeze has target or old version, else 0x73
578
+ let sound_id: u32 = if freeze_target != 0 || game_version < 0x21 {
579
+ 0x70
580
+ } else {
581
+ 0x73
582
+ };
583
+
584
+ // Call PlaySoundGlobal β€” thiscall(ECX=worm, sound_id, 3, 0x10000, 0x10000)
585
+ type PlaySoundGlobalFn = unsafe extern "thiscall" fn(*mut CTaskWorm, u32, i32, i32, i32) -> i32;
586
+ let play_sound: PlaySoundGlobalFn = core::mem::transmute(rb(va::PLAY_SOUND_GLOBAL));
587
+ let result = play_sound(worm, sound_id, 3, 0x10000, 0x10000);
588
+
589
+ // If sound was queued, set its position to the freeze location
590
+ if result != 0 {
591
+ let queue_count = (*ddgame).sound_queue_count;
592
+ if queue_count > 0 {
593
+ let entry = &mut (*ddgame).sound_queue[(queue_count - 1) as usize];
594
+ entry.is_local = 1;
595
+ entry.secondary_vtable = 0;
596
+ entry.pos_x = freeze_x as u32;
597
+ entry.pos_y = freeze_y as u32;
598
+ }
599
+ }
600
+
601
+ // If freeze has a target, apply the visual effect and update counters
602
+ if freeze_target != 0 {
603
+ // Call PCLandscape vtable[5] to create freeze overlay
604
+ let landscape = (*ddgame).landscape as *mut u8;
605
+ let landscape_vt = *(landscape as *const *const usize);
606
+ let freeze_visual: unsafe extern "thiscall" fn(*mut u8, i32, i32, *mut u8, *mut u8) =
607
+ core::mem::transmute(*landscape_vt.add(5));
608
+ let sprite1 = (*ddgame).sprite_cache_2[freeze_target as usize];
609
+ let sprite2 = (*ddgame).sprite_cache_2[19 + freeze_target as usize];
610
+ freeze_visual(landscape, freeze_x >> 16, freeze_y >> 16, sprite1, sprite2);
611
+
612
+ // Increment WormEntry counters
613
+ let arena = openwa_core::engine::ddgame::TeamArenaRef::from_ptr(
614
+ &raw mut (*ddgame).team_arena,
615
+ );
616
+ let team_index = (*worm).team_index as usize;
617
+ let worm_index = (*worm).worm_index as usize;
618
+ let entry = arena.team_worm_mut(team_index, worm_index);
619
+ entry.turn_action_counter_Maybe += 3;
620
+ entry.effect_counter_04_Maybe += 10;
621
+ }
622
+ }
623
+
624
  // ── SpecialImpact wrapper ─────────────────────────────────
625
 
626
  /// Typed wrapper for SpecialImpact (0x5193D0).
testdata/replays/freeze_nuke_armageddon.WAgame ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:0c40f3581f382865d4b8e7f29970084e3bba185d594be18638d529db2a3d4a3b
3
+ size 14457
testdata/replays/freeze_nuke_armageddon_expected.log ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:e238485ecb1c48d34e8d48aecae2f9dca02532b521339870f6e8e8fc63dc4135
3
+ size 2051