From 14c0dd06f5d0c660fba86d19b42b692e6731ffe1 Mon Sep 17 00:00:00 2001 From: arcattack Date: Thu, 9 Jul 2026 08:31:16 -0500 Subject: [PATCH] Three-trigger keyboard weapon groups: 1=lasers 2=PPCs 3=missiles (task #43) Interim pod-like grouping ahead of the authentic ConfigureMappables/ ChooseButton mapper channels (decomp-anchored in open-questions): - Three fire channels like three pod buttons: gBTWeaponTrigger (lasers, key 1 or SPACE), gBTPPCTrigger (PPCs, key 2), gBTMissileTrigger (missiles, key 3 or CTRL). Keyboard only per user (mouse buttons removed). - EmitterSimulation picks its channel by classID (PPC 3028 vs Emitter 3016); BT_AUTOFIRE holds all three. - The BT_BEAM_LOG sampler modulus 30 aliased to ONE weapon under the new synchronized 5-beam volleys (5 | 30) -> 31 (coprime). Verified: all 5 energy weapons fire (31 samples each), aimed hits land, kill completes. Co-Authored-By: Claude Fable 5 --- context/build-and-run.md | 3 +- game/reconstructed/emitter.cpp | 11 +++++-- game/reconstructed/mech4.cpp | 57 +++++++++++++++++++--------------- 3 files changed, 42 insertions(+), 29 deletions(-) diff --git a/context/build-and-run.md b/context/build-and-run.md index 3d57e70..e242ba4 100644 --- a/context/build-and-run.md +++ b/context/build-and-run.md @@ -39,7 +39,8 @@ run\run.cmd [EGG] # default DEV.EGG; cd's to content\ and runs btl4.exe -eg - **cwd MUST be `content\`** — the engine resolves `BTL4.RES`, `VIDEO\`, `BTDPL.INI`, eggs relative to cwd (the `loadTables` gotcha: `L4VIDEO.cpp:849` `fopen("VIDEO\\REPLACEMATS.tbl")` is relative + unchecked → fread on NULL if cwd is wrong). Logs to `btl4.log` in `content\` (or `BT_LOG=`). [T2] -- Interactive: **WASD** drive, **Space/Ctrl** fire, **X** all-stop. Default egg = `DEV.EGG` +- Interactive: **WASD** drive; weapon groups (keyboard, task #43) **1/Space** = lasers, **2** = + PPCs, **3/Ctrl** = missiles; **X** all-stop; **V** cockpit/chase view. Default egg = `DEV.EGG` (map=grass, time=day). Swap mech via the egg's `vehicle=` (bhk1/madcat/…). The code path is mech-agnostic (verified — Mad Cat booted + fought with zero code changes). [T2] diff --git a/game/reconstructed/emitter.cpp b/game/reconstructed/emitter.cpp index bce823a..c08d50d 100644 --- a/game/reconstructed/emitter.cpp +++ b/game/reconstructed/emitter.cpp @@ -267,12 +267,17 @@ void PoweredSubsystem::PoweredSubsystemSimulation(time_slice); // E8 (bring-up trigger): write the weapon's fireImpulse from the player fire input - // (the controls mapper is bypassed). gBTWeaponTrigger is pulsed per-FRAME by mech4 + // (the controls mapper is bypassed). The triggers are pulsed per-FRAME by mech4 // (1,0,1,0...) so CheckFireEdge sees clean rising edges for repeated auto-fire under a // held/forced trigger. HasActiveTarget (E3) limits firing to the mech with a locked // target (the player) -- enemy emitters read this too but never fire (no target). - extern int gBTWeaponTrigger; - fireImpulse = (Scalar)gBTWeaponTrigger; + // WEAPON GROUPS (task #43, interim keyboard split standing in for the authentic + // ConfigureMappables/ChooseButton mapper channels): lasers (0xBC8=3016) and PPCs + // (0xBD4=3028) read SEPARATE fire channels, like two pod buttons. + extern int gBTWeaponTrigger; // channel 1: lasers + extern int gBTPPCTrigger; // channel 2: PPCs + fireImpulse = (Scalar)(((int)GetClassID() == 3028) ? gBTPPCTrigger + : gBTWeaponTrigger); Logical fireEdge = CheckFireEdge(); // @004b9608 targetWithinRange = UpdateTargeting(); // @004b9bdc -> this+0x34c diff --git a/game/reconstructed/mech4.cpp b/game/reconstructed/mech4.cpp index 783dc55..13b1fc4 100644 --- a/game/reconstructed/mech4.cpp +++ b/game/reconstructed/mech4.cpp @@ -618,12 +618,17 @@ static int gShotCount = 0; static Scalar gBlockCooldown = 0.0f; // seconds since the last blocking contact (counts down) static const Scalar kBlockHysteresis = 0.4f; -// E8 weapon trigger (bring-up): pulsed per player frame; read by EmitterSimulation -// (emitter.cpp) so the real weapon's CheckFireEdge sees rising edges. Non-static -// (the emitter externs it). -int gBTWeaponTrigger = 0; -int gBTMissileTrigger = 0; // the split missile-fire channel (CTRL) -static int gBTMissileKey = 0; // raw key state (set by the keyboard poll) +// E8 weapon triggers (bring-up): pulsed per player frame; read by the weapon +// sims so CheckFireEdge sees rising edges. Non-static (the weapons extern +// them). WEAPON GROUPS (task #43): three KEYBOARD fire channels -- an interim +// pod-like split standing in for the authentic ConfigureMappables/ChooseButton +// mapper channels: 1/SPACE = lasers, 2 = PPCs, 3/CTRL = missiles. +int gBTWeaponTrigger = 0; // channel 1: lasers (key 1 / SPACE) +int gBTPPCTrigger = 0; // channel 2: PPCs (key 2) +int gBTMissileTrigger = 0; // channel 3: missiles (key 3 / CTRL) +static int gBTLaserKey = 0; // raw key states (set by the keyboard poll) +static int gBTPPCKey = 0; +static int gBTMissileKey = 0; // Damage: each shot dispatches a REAL Entity::TakeDamageMessage to the target. Now // that the damage zones are constructed (mech.cpp Pass-3 zone build), the engine base @@ -1278,14 +1283,16 @@ void gBTDrive.keyBack = focused && ((pAsync('S') | pAsync(0x28 /*VK_DOWN*/)) & dn) ? 1 : 0; gBTDrive.keyLeft = focused && ((pAsync('A') | pAsync(0x25 /*VK_LEFT*/)) & dn) ? 1 : 0; gBTDrive.keyRight = focused && ((pAsync('D') | pAsync(0x27 /*VK_RIGHT*/)) & dn) ? 1 : 0; - // SPLIT WEAPON CONTROLS (interim, toward the real controls-mapper - // weapon groups): SPACE / LMB = the energy weapons (lasers), - // CTRL / RMB = the missile launchers. (The mouse buttons pair - // with the mouse-slewed reticle -- task #36.) - gBTDrive.fire = focused && ((pAsync(0x20 /*VK_SPACE*/) | - pAsync(0x01 /*VK_LBUTTON*/)) & dn) ? 1 : 0; - gBTMissileKey = focused && ((pAsync(0x11 /*VK_CONTROL*/) | - pAsync(0x02 /*VK_RBUTTON*/)) & dn) ? 1 : 0; + // WEAPON GROUPS (task #43, KEYBOARD only per user): three fire + // channels like three pod buttons -- 1/SPACE = lasers, 2 = PPCs, + // 3/CTRL = missiles. (Interim; the authentic system is the + // ConfigureMappables/ChooseButton mapper channels.) + gBTLaserKey = focused && ((pAsync('1') | pAsync(0x20 /*VK_SPACE*/)) & dn) ? 1 : 0; + gBTPPCKey = focused && (pAsync('2') & dn) ? 1 : 0; + gBTMissileKey = focused && ((pAsync('3') | pAsync(0x11 /*VK_CONTROL*/)) & dn) ? 1 : 0; + // gBTDrive.fire = "any weapon trigger down" (feeds the bring-up + // damage dispatcher + the beam-visual keepalive) + gBTDrive.fire = (gBTLaserKey || gBTPPCKey || gBTMissileKey) ? 1 : 0; static int sPrevX = 0; const int xNow = focused && (pAsync('X') & dn) ? 1 : 0; if (xNow && !sPrevX) gBTDrive.allStop = 1; // edge -> one all-stop @@ -2338,7 +2345,7 @@ void BTPushBeamKind(mz.x, mz.y, mz.z, bend.x, bend.y, bend.z, tint, ttl1, 1.0f /* natural model width */, isPPC ? 1 : 0 /* ppc.bgf : ermlaser.bgf */); - if (getenv("BT_BEAM_LOG") && (s_beamStateLog++ % 30) == 0) + if (getenv("BT_BEAM_LOG") && (s_beamStateLog++ % 31) == 0) // 31: coprime with the 5-beam volley (a %30 sampler aliased to one weapon) DEBUG_STREAM << "[beam] " << (isPPC ? "PPC" : "laser") << " #" << wi << " mz=(" << mz.x << "," << mz.y << "," << mz.z << ") end=(" << bend.x << "," << bend.y << "," << bend.z @@ -2577,17 +2584,17 @@ void const int fireWanted = gBTDrive.fireForced || gBTDrive.fire; - // E8: pulse the weapon trigger per frame (1,0,1,0...) so the real Emitter's - // CheckFireEdge sees clean rising edges for repeated auto-fire. Read by - // EmitterSimulation. AUTHENTIC gating: the trigger passes through and the - // weapon's OWN HasActiveTarget gate decides (Emitter::FireWeapon fires only - // with a designated target -- part_013.c:7758; no aim/arc test exists). - // targetInArc is the explicit BT_FIRE_ARC presentation clamp (default true). - extern int gBTWeaponTrigger; - gBTWeaponTrigger = (fireWanted && targetInArc) ? (gBTWeaponTrigger ? 0 : 1) : 0; - // the missile channel: CTRL / RMB (or the autofire harness) -- same edge pulse - extern int gBTMissileTrigger; + // E8: pulse the three fire channels per frame (1,0,1,0...) so each + // weapon sim's CheckFireEdge sees clean rising edges. AUTHENTIC + // gating: the trigger passes through and the weapon's OWN + // HasActiveTarget gate decides (FireWeapon -- part_013.c:7758; no + // aim/arc test exists). targetInArc is the explicit BT_FIRE_ARC + // presentation clamp (default true). BT_AUTOFIRE holds all three. + const int laserWanted = gBTDrive.fireForced || gBTLaserKey; + const int ppcWanted = gBTDrive.fireForced || gBTPPCKey; const int missileWanted = gBTDrive.fireForced || gBTMissileKey; + gBTWeaponTrigger = (laserWanted && targetInArc) ? (gBTWeaponTrigger ? 0 : 1) : 0; + gBTPPCTrigger = (ppcWanted && targetInArc) ? (gBTPPCTrigger ? 0 : 1) : 0; gBTMissileTrigger = (missileWanted && targetInArc) ? (gBTMissileTrigger ? 0 : 1) : 0; // Resolve the "explode" effect resource once.