PPC hit scrambles the secondary displays (phase-14, all three work items)
Restores the PPC's authentic secondary-display effect: an EnergyDamageType hit scrambles every secondary cockpit display for 0.8s while the main out-the-window view stays clean. Was fully specced (phase-14) with the engine half already present under the original VWE names; only the trigger and the visual were missing (the visual STUBBED since 2007). A -- TRIGGER (game/reconstructed/mech.cpp): in TakeDamageMessageHandler, at the binary's @0x4a03f3 position (after the cylinder resolve, before the burst loop, so ONCE per damage message) fire GetGaugeRenderer()->SpecialEffect(scrambleVideo, damageType*0.2f) on EnergyDamageType (==4). That type is authored on exactly the 14 PPC/ERPPC records, so the branch is structurally PPC-exclusive -- no weapon-class check. Duration DERIVED from the ordinal (4*0.2==0.8s), not a literal. BT_DMG_LOG prints [ppc-scramble]. B -- VISUAL (SVGA16::FunkyVideo, was the 2007 stub): FunkyVideo now arms scrambleActive; new SVGA16::ScrambleRowShift is a per-source-row horizontal shear+roll, read by BOTH DrawDevSurface (surround/dock) and ExpandPlaneToBGRA (glass windows, native + rotated radar), so all secondary surfaces shear together in source space and the main 3D view (separate timing chain) is untouched -- the modern stand-in for the VGA CRTC Horizontal-Total detune. Tunable: BT_SCRAMBLE_SHEAR (px/line, def 4), BT_SCRAMBLE_ROLL (px/sec, def 220); BT_SCRAMBLE_TEST=1 forces it on for tuning by eye. C -- NON-STACKING LATCH (L4GaugeRenderer::SpecialEffect): ignore the re-arm while scrambleVideoFlag is set (matches the binary's `modified` latch) -- a second PPC during the window no longer extends it. Was a divergence. Verified: Release links clean; surround boots + runs with the effect forced on, every secondary MFD + the radar shear while the out-the-window view stays clean, no crash (screenshot). Open (for the playtesters who filed the report): live PPC-fire confirmation + by-eye shear tuning -- k/roll are not recoverable from the binary. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -645,7 +645,7 @@ Total by −9** for 0.8 s — every secondary cockpit display loses horizontal s
|
||||
is untouched. `EnergyDamageType` is authored on **exactly the 14 PPC/ERPPC records and nothing
|
||||
else**, so this is structurally PPC-exclusive. It fires **once per damage message** (outside the
|
||||
burst loop). Full chain + addresses: [[gauges-hud]] §"PPC HIT = a deliberate CRTC horizontal-sync
|
||||
DETUNE"; port spec: `phases/phase-14-ppc-sync-distortion.md`. **Not yet implemented.**
|
||||
DETUNE"; port spec: `phases/phase-14-ppc-sync-distortion.md`. **Implemented 2026-08-06** (branch `ppc-sync-distortion`).
|
||||
|
||||
## (HISTORICAL — the gap as found 2026-07-29, superseded above) [T1]
|
||||
The authored crit machinery exists and is reconstructed — `Mech__DamageZone::CriticalHit @0049ccc4`
|
||||
|
||||
@@ -13,7 +13,7 @@ open_questions:
|
||||
- "Upper-MFD PRESET pages RESOLVED 2026-07-19 (Gitea #9): SetPresetMode table @0051dbf0 re-decoded (little-endian -> ModeMFD bits 0-14), per-MFD pod button banks identified from the .CTL dump, desktop J/K/L cycle wired"
|
||||
- "Always-active msg-4 records IDENTIFIED 2026-07-20 (glass input audit): 0x2C = Reservoir InjectCoolant (the flush button), 0x2F/0x2E/0x2D/0x2B/0x2A/0x29 = Condenser1-6 MoveValve, 0x1A-0x1D = GeneratorA-D ToggleGeneratorOnOff (@0050fb90; wired 2026-07-25, powersub.cpp); plus 0x13 = Mech DuckRequest (CROUCH -- COMPLETE 2026-08-06, [[locomotion]]), 0x28 = Mech BalanceCoolant, 0x12/0x14 = ThermalSight/Searchlight toggles (searchlight visuals done 2026-08-05) -- see pod-hardware.md + docs/GLASS_COCKPIT.md; statuses re-swept 2026-08-06"
|
||||
- "MP DEATHS resolved 2026-07-12 (observed-death tally + display clamp); remaining: verify multi-death tallies stay in sync across a long session (GAUGE_COMPOSITE.md)"
|
||||
- "PPC `scrambleVideo` NOT IMPLEMENTED (found 2026-08-06 [T1]): a PPC hit scrambles every secondary display for 0.8 s via a CRTC Horizontal-Total detune. Engine half already present under the original names (SpecialEffect/scrambleVideo, L4GREND.cpp:806+832, called per frame at :370); missing = the trigger in Mech::TakeDamageMessageHandler (damageType==4) and the visual (SVGA16::FunkyVideo is STUBBED, L4VB16.cpp:6358). Also note our SpecialEffect re-arms the timeout on a second hit — the binary latches and does NOT stack. Spec: phases/phase-14-ppc-sync-distortion.md"
|
||||
- "PPC `scrambleVideo` IMPLEMENTED 2026-08-06 (branch ppc-sync-distortion): a PPC hit scrambles every secondary display for 0.8 s (modern per-scanline shear stand-in for the CRTC Horizontal-Total detune). Trigger in Mech::TakeDamageMessageHandler (damageType==4), visual in SVGA16::FunkyVideo/ScrambleRowShift (DrawDevSurface + ExpandPlaneToBGRA), non-stacking latch in L4GaugeRenderer::SpecialEffect. Screenshot-verified; live PPC-fire confirmation + by-eye shear tuning (BT_SCRAMBLE_SHEAR/ROLL) left to playtesters. Spec: phases/phase-14-ppc-sync-distortion.md"
|
||||
---
|
||||
|
||||
# Cockpit Gauges / MFD HUD
|
||||
@@ -758,11 +758,14 @@ pooled=0`), so the control chain is verified but final playback is not.
|
||||
|
||||
## PPC HIT = a deliberate CRTC horizontal-sync DETUNE on every secondary display (2026-08-06) [T1 disasm-verified]
|
||||
|
||||
**NOT YET IMPLEMENTED IN THE PORT.** Hand-off spec:
|
||||
`phases/phase-14-ppc-sync-distortion.md`. Reported by playtesters as "being hit
|
||||
by a PPC makes it look like all of the secondary CRTs were being degaussed" —
|
||||
main (VPX) view unaffected, PPC strikes only. Both observations are exactly
|
||||
what the binary does.
|
||||
**✅ IMPLEMENTED 2026-08-06** (branch `ppc-sync-distortion`; screenshot-verified —
|
||||
every secondary MFD + the radar shear together, the out-the-window view stays
|
||||
clean). Trigger + visual + non-stacking latch — see
|
||||
`phases/phase-14-ppc-sync-distortion.md` for the port details and the
|
||||
`BT_SCRAMBLE_*` tuning envs. Reported by playtesters as "being hit by a PPC makes
|
||||
it look like all of the secondary CRTs were being degaussed" — main (VPX) view
|
||||
unaffected, PPC strikes only. Both observations are exactly what the binary does.
|
||||
The disasm chain below is the ground truth the port was built from.
|
||||
|
||||
**The gate is the damage TYPE, and only the PPC has it.** A `BTL4.RES`
|
||||
subsystem census gives `damageType` 4 (`EnergyDamageType`) = **14 records,
|
||||
|
||||
@@ -812,7 +812,12 @@ void
|
||||
switch (type)
|
||||
{
|
||||
case scrambleVideo:
|
||||
if (graphicsDisplay != NULL)
|
||||
// NON-STACKING LATCH (phase-14 fidelity): the binary latches on `modified`
|
||||
// @0x4fe0fe -- a second PPC hit while the scramble is ALREADY live does NOT
|
||||
// re-save the (already detuned) value and does NOT extend the window. This
|
||||
// original unconditionally overwrote scrambleVideoTimeout, so rapid PPC fire
|
||||
// STACKED the effect -- a divergence from the binary. Ignore re-arm while live.
|
||||
if (graphicsDisplay != NULL && !scrambleVideoFlag)
|
||||
{
|
||||
scrambleVideoFlag = True;
|
||||
scrambleVideoTimeout = ((Scalar)Now()) + duration;
|
||||
|
||||
@@ -615,17 +615,24 @@ void SVGA16::DrawDevSurface(LPDIRECT3DDEVICE9 device, int slot, int mask, int pa
|
||||
Word *source = pixelBuffer.Data.MapPointer;
|
||||
Word *dest = (Word*)rect.pBits;
|
||||
int postRowIncrement = (rect.Pitch / 2) - w;
|
||||
// PPC sync-scramble (phase-14): read each SOURCE row with a horizontal wrap
|
||||
// (ScrambleRowShift == 0 unless FunkyVideo is armed), so the surface tears
|
||||
// like a monitor that has lost horizontal lock. Indexed read (srcRow[sx])
|
||||
// instead of the linear source++.
|
||||
if (monoTint < 0)
|
||||
{
|
||||
// PALETTE surface (sec/radar) -- palette-LUT expand (== SVGA16::Update case 0).
|
||||
SVGA16Palette *pal = &palette[paletteID];
|
||||
for (int y = 0; y < h; y++)
|
||||
{
|
||||
Word *srcRow = source + y * w;
|
||||
int sh = ScrambleRowShift(y, w);
|
||||
for (int x = 0; x < w; x++)
|
||||
{
|
||||
PaletteTriplet *pe = &(pal->paletteData.Color[*source & mask]);
|
||||
int sx = x + sh; if (sx >= w) sx -= w;
|
||||
PaletteTriplet *pe = &(pal->paletteData.Color[srcRow[sx] & mask]);
|
||||
*dest = ((pe->Red >> 3) << 11) | ((pe->Green >> 2) << 5) | (pe->Blue >> 3);
|
||||
dest++; source++;
|
||||
dest++;
|
||||
}
|
||||
dest += postRowIncrement;
|
||||
}
|
||||
@@ -637,10 +644,13 @@ void SVGA16::DrawDevSurface(LPDIRECT3DDEVICE9 device, int slot, int mask, int pa
|
||||
Word tint = (Word) monoTint;
|
||||
for (int y = 0; y < h; y++)
|
||||
{
|
||||
Word *srcRow = source + y * w;
|
||||
int sh = ScrambleRowShift(y, w);
|
||||
for (int x = 0; x < w; x++)
|
||||
{
|
||||
*dest = (*source & mask) ? tint : 0;
|
||||
dest++; source++;
|
||||
int sx = x + sh; if (sx >= w) sx -= w;
|
||||
*dest = (srcRow[sx] & mask) ? tint : 0;
|
||||
dest++;
|
||||
}
|
||||
dest += postRowIncrement;
|
||||
}
|
||||
@@ -719,9 +729,11 @@ void SVGA16::ExpandPlaneToBGRA(int mask, int paletteID, int monoTint, int rotate
|
||||
{
|
||||
Word *src = base + y * w;
|
||||
unsigned long *d = dst + y * w;
|
||||
int sh = ScrambleRowShift(y, w); // PPC scramble (0 unless armed)
|
||||
for (int x = 0; x < w; x++)
|
||||
{
|
||||
Word s = src[x];
|
||||
int sx = x + sh; if (sx >= w) sx -= w;
|
||||
Word s = src[sx];
|
||||
if (monoTint < 0)
|
||||
{
|
||||
PaletteTriplet *pe = &(pal->paletteData.Color[s & mask]);
|
||||
@@ -742,6 +754,13 @@ void SVGA16::ExpandPlaneToBGRA(int mask, int paletteID, int monoTint, int rotate
|
||||
// 90-degree rotation: output is transposed (ow = h, oh = w). rotate 3 = CW,
|
||||
// rotate 1 = CCW (the DrawDevSurface convention; BT_GAUGE_SEC_ROT picks it).
|
||||
int ow = h, oh = w;
|
||||
// PPC scramble: precompute the per-SOURCE-ROW shift once. sy is used per output
|
||||
// pixel and ScrambleRowShift reads the clock, so it must not be called per pixel.
|
||||
// The shear stays in SOURCE space (keyed on sy), so the rotation displays an
|
||||
// authentically-sheared raster.
|
||||
int rowSh[512];
|
||||
int shRows = (scrambleActive && h <= 512) ? h : 0;
|
||||
for (int i = 0; i < shRows; i++) rowSh[i] = ScrambleRowShift(i, w);
|
||||
for (int oy = 0; oy < oh; oy++)
|
||||
{
|
||||
unsigned long *d = dst + oy * ow;
|
||||
@@ -758,6 +777,7 @@ void SVGA16::ExpandPlaneToBGRA(int mask, int paletteID, int monoTint, int rotate
|
||||
sx = w - 1 - oy;
|
||||
sy = ox;
|
||||
}
|
||||
if (shRows) { sx += rowSh[sy]; if (sx >= w) sx -= w; }
|
||||
Word s = base[sy * w + sx];
|
||||
if (monoTint < 0)
|
||||
{
|
||||
@@ -5395,6 +5415,7 @@ SVGA16::SVGA16(
|
||||
BuildWindows(init_width,init_height,windowed, secondaryIndex, aux1Index, aux2Index);
|
||||
for (int _i = 0; _i < 10; _i++) // DEV-COMPOSITE: lazily created on first surface draw
|
||||
mDevSurfaceTex[_i] = NULL;
|
||||
scrambleActive = False; // PPC sync-scramble (phase-14) -- armed by FunkyVideo
|
||||
//STUBBED: VIDEO RB 1/15/07
|
||||
# if defined(DEBUG)
|
||||
Tell("SVGA16::SVGA16()\n");
|
||||
@@ -6354,13 +6375,54 @@ void
|
||||
Check_Fpu();
|
||||
}
|
||||
|
||||
//
|
||||
// PPC sync-scramble (phase-14). The original (`//STUBBED: VIDEO RB 1/15/07`)
|
||||
// detuned the VGA CRTC Horizontal Total by -9 so every secondary monitor lost
|
||||
// horizontal lock for 0.8 s. There is no CRTC here, so FunkyVideo just arms /
|
||||
// disarms a flag; the per-surface expansions (DrawDevSurface for the surround,
|
||||
// ExpandPlaneToBGRA for the glass windows) shear the pixelBuffer read while it is
|
||||
// set. The 0.8 s window + non-stacking latch stay in L4GaugeRenderer.
|
||||
//
|
||||
void
|
||||
SVGA16::FunkyVideo(Logical on_off)
|
||||
{
|
||||
//STUBBED: VIDEO RB 1/15/07
|
||||
//Check(this);
|
||||
//SVGAFunkyVideo(on_off);
|
||||
//Check_Fpu();
|
||||
scrambleActive = on_off;
|
||||
}
|
||||
|
||||
//
|
||||
// Per-SOURCE-ROW horizontal shift for the scramble. Model (phase-14 §4):
|
||||
// shift(y,t) = ( y*shear + roll*t ) mod width
|
||||
// a constant per-line shear (the CRTC detune offset every scanline by a fixed
|
||||
// amount -> a diagonal tear) plus a time roll (the monitor never re-locked, so
|
||||
// the tear drifts). The physically-derived shear is ~9% of a line, but the
|
||||
// on-screen result depended on each pod monitor's H-sync PLL -- not recoverable,
|
||||
// so the default is a legible moderate tear; tune by eye against the playtesters
|
||||
// who filed the report (BT_SCRAMBLE_SHEAR px/line, BT_SCRAMBLE_ROLL px/sec).
|
||||
//
|
||||
int
|
||||
SVGA16::ScrambleRowShift(int srcRow, int width) const
|
||||
{
|
||||
// BT_SCRAMBLE_TEST forces the effect always-on so the shear can be TUNED by eye
|
||||
// (adjust BT_SCRAMBLE_SHEAR / BT_SCRAMBLE_ROLL and relaunch) without waiting to
|
||||
// be hit by a PPC. Off by default.
|
||||
static int sTest = -1;
|
||||
if (sTest < 0) sTest = (getenv("BT_SCRAMBLE_TEST") != NULL) ? 1 : 0;
|
||||
if ((!scrambleActive && !sTest) || width <= 0)
|
||||
return 0;
|
||||
|
||||
static double shearPx = -1.0, rollPx = -1.0;
|
||||
if (shearPx < 0.0)
|
||||
{
|
||||
const char *e = getenv("BT_SCRAMBLE_SHEAR");
|
||||
const char *r = getenv("BT_SCRAMBLE_ROLL");
|
||||
shearPx = (e != NULL) ? atof(e) : 4.0; // px/line (aggressive ~9%: width*0.09)
|
||||
rollPx = (r != NULL) ? atof(r) : 220.0; // px/sec drift
|
||||
}
|
||||
double roll = rollPx * ((double)GetTickCount() * 0.001);
|
||||
long s = (long)((double)srcRow * shearPx + roll);
|
||||
s %= width;
|
||||
if (s < 0) s += width;
|
||||
return (int)s;
|
||||
}
|
||||
|
||||
//########################################################################
|
||||
|
||||
@@ -334,6 +334,20 @@ public:
|
||||
void
|
||||
FunkyVideo(Logical on_off);
|
||||
|
||||
// PPC sync-scramble (phase-14): the modern stand-in for the VGA CRTC
|
||||
// Horizontal-Total detune. FunkyVideo(on) sets scrambleActive; every
|
||||
// gauge-surface expansion (DrawDevSurface for the surround, ExpandPlaneToBGRA
|
||||
// for the glass windows) then reads the pixelBuffer with a per-SOURCE-ROW
|
||||
// horizontal shift so all secondary displays shear together -- authentic by
|
||||
// construction (one VGA's detuned sync drives them all). Returns the source-x
|
||||
// offset for a source row, 0 when not scrambling. Tunable by eye
|
||||
// (BT_SCRAMBLE_SHEAR px/line, BT_SCRAMBLE_ROLL px/sec) -- the exact look
|
||||
// depended on each pod monitor's H-sync PLL and is not recoverable.
|
||||
int
|
||||
ScrambleRowShift(int srcRow, int width) const;
|
||||
Logical
|
||||
scrambleActive;
|
||||
|
||||
protected:
|
||||
|
||||
void BuildWindows(unsigned int width, unsigned int height, bool windowed, int *secondaryIndex, int *aux1Gauge, int *aux2Gauge);
|
||||
|
||||
@@ -82,6 +82,7 @@
|
||||
// The torso twist is reached via a BRIDGE (BTGetTorsoTwist, defined in torso.cpp)
|
||||
// for the same reason.
|
||||
#include "dmgtable.hpp"
|
||||
#include <GAUGREND.hpp> // GaugeRenderer::SpecialEffect (virtual) -- PPC scramble trigger
|
||||
extern Scalar BTGetTorsoTwist(Subsystem *torso); // torso.cpp (Torso complete there)
|
||||
// heat-bank ambient bridge (heatfamily_reslice.cpp, AggregateHeatSink complete
|
||||
// there) -- mech.cpp cannot include the subsystem headers (local-stub collision)
|
||||
@@ -1122,6 +1123,31 @@ void
|
||||
}
|
||||
}
|
||||
reportZone = message->damageZone; // post-resolve (@0x4a0396 write)
|
||||
|
||||
//
|
||||
// @0x4a03f3 [T1] -- PPC/ERPPC SECONDARY-DISPLAY SCRAMBLE. EnergyDamageType
|
||||
// (==4) is authored on exactly the 14 PPC/ERPPC subsystem records and nothing
|
||||
// else, so this branch is structurally PPC-exclusive -- NO explicit weapon
|
||||
// class check (the data authorship IS the gate). It sits OUTSIDE the burst
|
||||
// loop below, so it fires ONCE per damage message, not per burst. The
|
||||
// duration is DERIVED from the type ordinal, not a literal: (Scalar)4 * 0.2 ==
|
||||
// 0.8s (binary loads long double 0.2 @0x4a0c08, fmulp). SpecialEffect ->
|
||||
// L4GaugeRenderer scrambles every SECONDARY cockpit display (SVGA16 sync
|
||||
// detune, FunkyVideo) for that window; the main 3D view is on an independent
|
||||
// timing chain and is left clean. Spec: phases/phase-14-ppc-sync-distortion.md.
|
||||
//
|
||||
if (message->damageData.damageType == Damage::EnergyDamageType)
|
||||
{
|
||||
GaugeRenderer *gauges =
|
||||
(application != 0) ? application->GetGaugeRenderer() : 0; // APP.h:355 -- named, not application+0x4c
|
||||
if (gauges != 0) // the binary null-guards the renderer too (@0x4a0405)
|
||||
gauges->SpecialEffect(GaugeRenderer::scrambleVideo,
|
||||
(Scalar)message->damageData.damageType * 0.2f);
|
||||
if (BTEnvOn("BT_DMG_LOG", 0))
|
||||
DEBUG_STREAM << "[ppc-scramble] EnergyDamageType hit -> scrambleVideo for "
|
||||
<< ((Scalar)message->damageData.damageType * 0.2f) << "s\n" << std::flush;
|
||||
}
|
||||
|
||||
//
|
||||
// #80 -- the faithful application loop (binary @0x4a0423-0x4a04d8), which
|
||||
// REPLACES the engine-base single application. Three things the base
|
||||
|
||||
@@ -3,11 +3,34 @@
|
||||
**Goal:** restore the PPC's authentic secondary effect — a PPC strike scrambles
|
||||
**every secondary cockpit display for 0.8 s**, leaving the main view untouched.
|
||||
|
||||
**Status:** NOT STARTED. Spec only. Discovered 2026-08-06 by disassembly of the
|
||||
shipped `BTL4OPT.EXE` (md5 `a97075bcb5634d13263e9ad5a2b96fd0`) after playtesters
|
||||
reported *"being hit by a PPC makes it look like all of the secondary CRTs were
|
||||
being degaussed."* Full findings: `context/gauges-hud.md` §"PPC HIT = a
|
||||
deliberate CRTC horizontal-sync DETUNE"; cross-ref in `context/combat-damage.md`.
|
||||
**Status:** ✅ IMPLEMENTED 2026-08-06 (branch `ppc-sync-distortion`) [T2 —
|
||||
visual screenshot-verified, trigger by construction]. Discovered 2026-08-06 by
|
||||
disassembly of the shipped `BTL4OPT.EXE` (md5 `a97075bcb5634d13263e9ad5a2b96fd0`)
|
||||
after playtesters reported *"being hit by a PPC makes it look like all of the
|
||||
secondary CRTs were being degaussed."* Full findings: `context/gauges-hud.md`
|
||||
§"PPC HIT = a deliberate CRTC horizontal-sync DETUNE"; cross-ref in
|
||||
`context/combat-damage.md`.
|
||||
|
||||
**What shipped (all three work items):**
|
||||
- **A — trigger:** `game/reconstructed/mech.cpp`, `Mech::TakeDamageMessageHandler`
|
||||
@0x4a03f3 position (after the cylinder resolve, before the burst loop) — fires
|
||||
`GetGaugeRenderer()->SpecialEffect(scrambleVideo, damageType*0.2f)` once per
|
||||
EnergyDamageType message. `BT_DMG_LOG` prints `[ppc-scramble]`.
|
||||
- **B — visual:** `SVGA16::FunkyVideo` (was the 2007 stub) now arms `scrambleActive`;
|
||||
`SVGA16::ScrambleRowShift` (new) is the per-source-row horizontal shear, read by
|
||||
BOTH `DrawDevSurface` (surround/dock) and `ExpandPlaneToBGRA` (glass windows), so
|
||||
all secondary surfaces + the rotated radar shear together and the main 3D view
|
||||
(a separate timing chain) is untouched. Tunable: `BT_SCRAMBLE_SHEAR` (px/line,
|
||||
default 4), `BT_SCRAMBLE_ROLL` (px/sec, default 220). **`BT_SCRAMBLE_TEST=1`
|
||||
forces it always-on for tuning by eye without waiting for a PPC hit.**
|
||||
- **C — non-stacking latch:** `L4GaugeRenderer::SpecialEffect` now ignores the
|
||||
re-arm while `scrambleVideoFlag` is set (matches the binary's `modified` latch);
|
||||
a second PPC during the window no longer extends it.
|
||||
- Verified: Release links clean; surround boots + runs with the effect forced on,
|
||||
every secondary MFD + the radar shear while the out-the-window view stays clean,
|
||||
no crash. **Open:** live PPC-fire confirmation (one arm per hit, non-stacking
|
||||
across two hits) + the by-eye shear tuning belong to the playtesters who filed
|
||||
the report — `k`/`roll` are not recoverable from the binary.
|
||||
|
||||
**Good news up front:** the engine half already exists in our tree under the
|
||||
**original VWE names** (`SpecialEffect` / `scrambleVideo` / `FunkyVideo`). Only
|
||||
|
||||
Reference in New Issue
Block a user