World-pick targeting: 0x388 = whatever is downrange -- mech, terrain, or nothing (task #41)
User video evidence (lasers firing at nothing) + the colleague's
torso-locked report + the binary all reconcile into one model:
The boresight pick hits the WORLD, and the target slot mech+0x388 holds
whatever entity is downrange:
- enemy mech under the boresight -> aimed target (hull point -> STEP-6
zone under the boresight, hotbox + lock ring, damage);
- else the TERRAIN downrange (BTGroundRayHit to 1200) -> the beam and
missiles fire at the scenery ("firing at nothing", as in the pod
videos), range caret reads the ground distance (authentic :5639), no
damage;
- else (sky) -> no target, and the weapon's own double 0x388 gate
refuses the discharge.
Binary evidence for non-mech targets: HudSimulation :5620 explicitly
handles a target WITHOUT damage zones (target->0x120 == 0) -- dead code
if only mechs were ever targeted. The pick is automatic every frame
(0x388: 11 reads / 0 direct stores in CODE).
Implementation: gBTTerrainEntity captured in MakeEntityRenderables
(Terrain::GetClassDerivations); mech4 pick = enemy PickRayHit else
ground ray -> terrain entity + ground point; damage/hotbox/lock only
for the mech pick; the task-40 enemy auto-converge REMOVED (facing away
now fires at the scenery, not magically at the enemy -- the user's
complaint). Verified all three states headless (BT_AIM 0,0 / .6,.5 /
0,-.8): aimed zone hits / terrain beams + 0 damage / no discharge.
Fourth (and evidence-complete) targeting model iteration; KB updated.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
60ed54e008
commit
e30a61a62f
+24
-21
@@ -58,27 +58,30 @@ basis via `BTSetAimCamera`, the render loop publishes proj._11/_22 + backbuffer
|
||||
`Mech::PickRayHit` (world ray → local frame → `BoundingBox::HitBy` slab test vs the collision
|
||||
template's ExtentBox → world hit point).
|
||||
|
||||
**FIRING NEEDS A TARGET BUT NOT A MANUAL LOCK / PINPOINT AIM (task #40, binary-verified [T1]).**
|
||||
The weapon fire path is DOUBLY gated on `mech+0x388 != 0`: `Emitter::EmitterSimulation`
|
||||
(`FUN_004baa88` :7689) only calls FireWeapon with a target, AND `Emitter::FireWeapon`
|
||||
(`FUN_004bace8` :7727) wraps its ENTIRE body (incl. `beamFlag`=+0x46c=1 :7749) in the same check —
|
||||
so **no target ⇒ no beam, ever** (you cannot fire into truly empty space). BUT a capstone scan of
|
||||
the whole CODE section finds **11 reads of `+0x388` and ZERO direct stores** — the target is
|
||||
written INDIRECTLY (a message/selector via a `this`-pointer), i.e. the game **AUTO-ACQUIRES** the
|
||||
target for you; there is no manual "lock to fire." The spinning-ring **LOCK** (HudSimulation
|
||||
5619-5634) is a SEPARATE, stricter state layered on top (host-zone < 0.75 damage AND targeted-zone
|
||||
< 1.0). **Port model:** `mech+0x388` = the enemy whenever it is ALIVE (`!IsMechDestroyed()`) — the
|
||||
pod's auto-target — so firing works freely with the enemy merely present, NO pinpoint needed
|
||||
(verified: `BT_AIM="0.5 0.2"` off-hull → fires + lands damage; before this fix off-hull dealt 0).
|
||||
A pinpoint hull hit (`PickRayHit` → `hotTarget`) UPGRADES the shot: `mech+0x37c` = the picked hull
|
||||
point → STEP-6 resolves the zone under the crosshair (aimed fire; center-aim → head-band zone 13),
|
||||
and lights the lock ring. Off-hull, `0x37c` = the target's centre mass (body hits, near-centre
|
||||
zone). (⚠ THREE earlier cuts were wrong and are corrected: a STICKY designation — locked forever;
|
||||
a MOUSE cursor slew — a free cursor the pod never had; and a PINPOINT-required fire gate — you had
|
||||
to boresight the hull exactly to fire at all. All from over-reading the RP-shared `Reticle` struct;
|
||||
removed.) The HUD draws the aim group at the boresight ([0x9a] translate), the designator hotbox at
|
||||
the target's projected point (subB9 hot / subB8 designated, `BTProjectHotBox`/`BTProjectToReticle`),
|
||||
and the edge arrows when off-screen/behind. `BT_FIRE_ARC` is now an EXPLICIT OPT-IN presentation clamp (unset = authentic
|
||||
**THE WORLD-PICK TARGET MODEL (task #41, the reconciliation — binary + pod-video evidence).**
|
||||
The target slot `mech+0x388` holds **whatever entity is downrange of the torso boresight** — a
|
||||
mech OR a WORLD entity (terrain/mesa/building). Evidence: (a) the weapon fire path is DOUBLY
|
||||
gated on `0x388 != 0` (`FUN_004baa88` :7689 AND `FUN_004bace8` :7727 wrap everything incl.
|
||||
`beamFlag`=+0x46c :7749 — no target ⇒ no discharge [T1]); (b) pod demo VIDEOS show lasers firing
|
||||
with no mech targeted — reconciled by (c): HudSimulation :5620 **explicitly handles a target
|
||||
WITHOUT damage zones** (`target->0x120 == 0`) — dead code unless non-mech entities get targeted;
|
||||
and (d) `0x388` has **11 reads / ZERO direct stores** across CODE (capstone scan) — the pick is
|
||||
written indirectly, AUTOMATICALLY, every frame; never a manual player lock. So: **fire at the
|
||||
enemy under your boresight → aimed zone damage; fire anywhere else → the beam hits the scenery
|
||||
downrange ("firing at nothing"); only a true sky shot (nothing within range) refuses to
|
||||
discharge.** The spinning-ring **LOCK** (HudSimulation 5619-5634) is a SEPARATE, stricter state
|
||||
(mech target only + host-zone < 0.75 damage + targeted-zone < 1.0). **Port implementation:** the
|
||||
boresight ray tests the enemy's collision box (`PickRayHit` → aimed hull point → STEP-6 zone
|
||||
under the boresight + hotbox + lock ring), else the terrain (`BTGroundRayHit` to 1200 →
|
||||
`gBTTerrainEntity` + the ground point — beam/missiles fly to the scenery, NO damage dispatch;
|
||||
the range caret authentically reads the ground distance, :5639), else no target (sky — the
|
||||
double gate refuses). Verified all three states (`BT_AIM` 0,0 / 0.6,0.5 / 0,−0.8). (⚠ This is
|
||||
the FOURTH targeting model iteration; the first three — sticky lock, mouse cursor, enemy
|
||||
auto-converge — all over-read the RP-shared `Reticle` struct or under-read the pick. The
|
||||
world-pick model is the one that fits ALL the evidence.) The HUD draws the aim group at the
|
||||
boresight ([0x9a] translate), the designator hotbox at the mech target's projected point (subB9
|
||||
hot / subB8 designated, `BTProjectHotBox`/`BTProjectToReticle`), and the edge arrows when
|
||||
off-screen/behind. `BT_FIRE_ARC` is now an EXPLICIT OPT-IN presentation clamp (unset = authentic
|
||||
no-arc). The old hardwired lock, the ±30°-default cone, and the projectile path's gEnemyMech
|
||||
fallback are all REMOVED. LMB fires lasers / RMB missiles (with SPACE/CTRL). ⚠ A view-selection
|
||||
bug was fixed en route: every
|
||||
|
||||
@@ -58,11 +58,19 @@
|
||||
#if !defined(APP_HPP)
|
||||
# include <app.hpp>
|
||||
#endif
|
||||
#if !defined(TERRAIN_HPP)
|
||||
# include <terrain.hpp> // Terrain::GetClassDerivations (world-pick target, task #41)
|
||||
#endif
|
||||
|
||||
#include <string.h>
|
||||
#include <math.h>
|
||||
#include <time.h> // clock() -- the threat-trail ages (task #37)
|
||||
|
||||
// WORLD-PICK TARGET (task #41): a live Terrain entity mech4 cites in
|
||||
// mech+0x388 when the boresight pick lands on the ground (the binary's target
|
||||
// slot holds non-mech world entities). Captured in MakeEntityRenderables.
|
||||
Entity *gBTTerrainEntity = 0;
|
||||
|
||||
|
||||
//
|
||||
// Material-name substitution placeholders. Mirrors RPL4VideoRenderer's
|
||||
@@ -205,6 +213,19 @@ void
|
||||
// `this_instance` in the CulturalIcon/Landmark arm has been fixed in
|
||||
// L4VIDEO.cpp.
|
||||
//
|
||||
// WORLD-PICK TARGET support (task #41): the boresight pick can hit
|
||||
// TERRAIN (the binary's target slot holds non-mech world entities --
|
||||
// HudSimulation part_013.c:5620 explicitly handles a target without
|
||||
// damage zones). Capture a live Terrain entity for mech4's ground
|
||||
// pick to cite in mech+0x388 (the pick POINT carries the geometry;
|
||||
// which specific instance matters only to damage, which never routes
|
||||
// to terrain).
|
||||
{
|
||||
extern Entity *gBTTerrainEntity;
|
||||
if (gBTTerrainEntity == 0
|
||||
&& entity->IsDerivedFrom(*Terrain::GetClassDerivations()))
|
||||
gBTTerrainEntity = entity;
|
||||
}
|
||||
DPLRenderer::MakeEntityRenderables(
|
||||
entity, model_resource, view_type);
|
||||
break;
|
||||
|
||||
@@ -2286,38 +2286,62 @@ void
|
||||
}
|
||||
}
|
||||
|
||||
// --- TARGETING (task #36 -- the AUTHENTIC Reticle acquisition,
|
||||
// engine/MUNGA/RETICLE.h [T0]): the pilot slews the crosshair
|
||||
// (gBTAimX/Y); a pick ray through it tests the enemy's collision
|
||||
// volume; the mech UNDER the crosshair becomes the designated target
|
||||
// (sticky -- it persists when the crosshair drifts off, marked by
|
||||
// the HUD designator; re-hover to refresh, burial clears it). The
|
||||
// designation feeds the engine-Entity target slots the whole weapon
|
||||
// path reads (target position @0x37c, target entity @0x388, zone
|
||||
// @0x38c). While the crosshair is ON the target ("hot"), the aim
|
||||
// point is the PICKED HULL POINT -- the STEP-6 cylinder lookup then
|
||||
// resolves damage to the zone under the crosshair (aimed fire).
|
||||
Entity *hotTarget = 0; // mech under the crosshair NOW
|
||||
// --- TARGETING: the WORLD-PICK model (task #41, the reconciliation of
|
||||
// ALL the evidence). The boresight ray picks whatever is DOWNRANGE:
|
||||
// 1. the enemy mech's collision volume -> aimed target (hull point
|
||||
// -> STEP-6 zone under the boresight; hotbox + lock ring);
|
||||
// 2. else the TERRAIN (BTGroundRayHit) -> the ground point becomes
|
||||
// the target -- the beam fires at the scenery ("firing at
|
||||
// nothing", seen in the pod demo videos), no damage;
|
||||
// 3. else (sky, nothing within range) -> NO target, and the
|
||||
// weapon's own double gate (FUN_004baa88:7689 + FUN_004bace8:
|
||||
// 7727 -- both require mech+0x388 != 0) refuses the discharge.
|
||||
// Binary evidence for non-mech targets: HudSimulation :5620
|
||||
// explicitly handles a target WITHOUT damage zones (target->0x120
|
||||
// == 0) -- dead code if only mechs were ever targeted. The pick is
|
||||
// AUTOMATIC every frame (0x388 has 11 reads / 0 direct stores in
|
||||
// CODE -- written indirectly, never a manual player lock).
|
||||
Entity *hotTarget = 0; // the enemy mech under the boresight
|
||||
Point3D hotPoint; // picked world point on its hull
|
||||
static int gAimNoRay = 0, gAimNoPick = 0, gAimHits = 0; // 1Hz diagnostics
|
||||
Entity *pickTarget = 0; // what the boresight ray hit (mech/terrain)
|
||||
Point3D pickPoint; // where it hit
|
||||
static int gAimNoRay = 0, gAimGround = 0, gAimHits = 0; // 1Hz diagnostics
|
||||
{
|
||||
extern int BTGetAimRay(float rx, float ry, float outStart[3], float outDir[3]);
|
||||
extern Entity *gBTTerrainEntity; // captured by MakeEntityRenderables
|
||||
float rs[3], rd[3];
|
||||
if (!BTGetAimRay(gBTAimX, gBTAimY, rs, rd))
|
||||
{
|
||||
++gAimNoRay;
|
||||
}
|
||||
else if (gEnemyMech != 0)
|
||||
else
|
||||
{
|
||||
Point3D rayStart(rs[0], rs[1], rs[2]);
|
||||
Vector3D rayDir(rd[0], rd[1], rd[2]);
|
||||
if (((Mech *)gEnemyMech)->PickRayHit(rayStart, rayDir, 4000.0f, &hotPoint))
|
||||
if (gEnemyMech != 0 && !((Mech *)gEnemyMech)->IsMechDestroyed()
|
||||
&& ((Mech *)gEnemyMech)->PickRayHit(rayStart, rayDir, 4000.0f, &hotPoint))
|
||||
{
|
||||
hotTarget = gEnemyMech;
|
||||
hotTarget = gEnemyMech;
|
||||
pickTarget = gEnemyMech;
|
||||
pickPoint = hotPoint;
|
||||
++gAimHits;
|
||||
}
|
||||
else
|
||||
++gAimNoPick;
|
||||
{
|
||||
// the ground downrange of the guns (max = the pod's HUD
|
||||
// range scale; past it the shot is a sky shot -> no target)
|
||||
extern bool BTGroundRayHit(float,float,float, float,float,float,
|
||||
float, float*,float*,float*);
|
||||
float hx, hy, hz;
|
||||
if (gBTTerrainEntity != 0 &&
|
||||
BTGroundRayHit(rs[0], rs[1], rs[2], rd[0], rd[1], rd[2],
|
||||
1200.0f, &hx, &hy, &hz))
|
||||
{
|
||||
pickTarget = gBTTerrainEntity;
|
||||
pickPoint.x = hx; pickPoint.y = hy; pickPoint.z = hz;
|
||||
++gAimGround;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The Reticle struct (the mech's TargetReticle attribute): position,
|
||||
@@ -2325,53 +2349,44 @@ void
|
||||
// damage delivery (the authentic percent-table roll, STEP 6).
|
||||
targetReticle.reticlePosition.x = gBTAimX;
|
||||
targetReticle.reticlePosition.y = gBTAimY;
|
||||
targetReticle.targetEntity = hotTarget;
|
||||
targetReticle.targetEntity = pickTarget;
|
||||
targetReticle.targetDamageZone = -1;
|
||||
if (hotTarget != 0)
|
||||
targetReticle.rayIntersection = hotPoint;
|
||||
if (pickTarget != 0)
|
||||
targetReticle.rayIntersection = pickPoint;
|
||||
|
||||
// TARGET (mech+0x388) is AUTO-ACQUIRED, not manually locked (task #40,
|
||||
// binary-confirmed): the weapon's fire path (Emitter FUN_004baa88 :7689
|
||||
// + FireWeapon FUN_004bace8 :7727) is DOUBLY gated on mech+0x388 != 0 --
|
||||
// no target, NO beam, ever -- but 0x388 is WRITTEN INDIRECTLY (a
|
||||
// disasm scan of the whole CODE section finds 11 reads and ZERO direct
|
||||
// stores), i.e. the game AUTO-SELECTS the target for you; you never
|
||||
// manually "lock" to fire. The spinning-ring LOCK (HudSimulation
|
||||
// 5619-5634) is a SEPARATE, stricter state layered on top. So: 0x388
|
||||
// = the enemy whenever it is alive (the pod's auto-target); firing
|
||||
// needs only that, NOT a pinpoint boresight hit. A pinpoint hull hit
|
||||
// (hotTarget) UPGRADES the shot to aimed-zone damage + the lock ring.
|
||||
Entity *autoTarget =
|
||||
(gEnemyMech != 0 && !((Mech *)gEnemyMech)->IsMechDestroyed())
|
||||
? gEnemyMech : 0;
|
||||
if (autoTarget != 0)
|
||||
// the engine-Entity target slots the whole weapon path reads
|
||||
if (pickTarget != 0)
|
||||
{
|
||||
MECH_TARGET_ENTITY(this) = autoTarget;
|
||||
MECH_TARGET_ENTITY(this) = pickTarget;
|
||||
MECH_TARGET_SUBIDX(this) = -1;
|
||||
if (hotTarget != 0)
|
||||
MECH_TARGET_POS(this) = hotPoint; // aimed: the picked hull point
|
||||
else
|
||||
{
|
||||
// no pinpoint pick -> converge on the target's centre mass
|
||||
// (the beam still fires; the zone resolves near-centre).
|
||||
Point3D cm = ((Mech *)autoTarget)->localOrigin.linearPosition;
|
||||
cm.y += kMuzzleHeight;
|
||||
MECH_TARGET_POS(this) = cm;
|
||||
}
|
||||
MECH_TARGET_POS(this) = pickPoint; // beam endpoint = the pick
|
||||
}
|
||||
else
|
||||
{
|
||||
MECH_TARGET_ENTITY(this) = 0; // no living enemy -> no target
|
||||
MECH_TARGET_ENTITY(this) = 0; // sky: no target, no discharge
|
||||
MECH_TARGET_SUBIDX(this) = -1;
|
||||
}
|
||||
}
|
||||
|
||||
// HUD feeds: the range caret + the hotbox (world point + state) + the
|
||||
// recovered-Execute instruments (compass, twist tape, group mask).
|
||||
// The range caret tracks the PICK (authentic: :5639 computes it from
|
||||
// mech+0x37c whatever the target is -- a terrain pick reads the ground
|
||||
// distance); the hotbox + lock exist ONLY for a mech target (:5620 --
|
||||
// a target with no damage zones gets neither).
|
||||
{
|
||||
extern void BTSetHudTargetRange(Scalar range);
|
||||
Entity *des = MECH_TARGET_ENTITY(this);
|
||||
if (des != 0)
|
||||
if (des != 0 && des != hotTarget)
|
||||
{
|
||||
// terrain pick: range to the ground point; no hotbox, no ring.
|
||||
Point3D tp = MECH_TARGET_POS(this);
|
||||
float hddx = tp.x - localOrigin.linearPosition.x;
|
||||
float hddz = tp.z - localOrigin.linearPosition.z;
|
||||
BTSetHudTargetRange((Scalar)sqrtf(hddx*hddx + hddz*hddz));
|
||||
gBTHudLockState = 0;
|
||||
}
|
||||
else if (des != 0)
|
||||
{
|
||||
Mech *dm = (Mech *)des;
|
||||
Point3D dp = dm->localOrigin.linearPosition;
|
||||
@@ -2426,7 +2441,8 @@ void
|
||||
{
|
||||
Point3D enemyPos = ((Mech *)gEnemyMech)->localOrigin.linearPosition;
|
||||
|
||||
const int designated = (MECH_TARGET_ENTITY(this) != 0);
|
||||
// damage routes ONLY when the boresight pick is the enemy mech
|
||||
const int designated = (hotTarget != 0);
|
||||
|
||||
float ddx = enemyPos.x - localOrigin.linearPosition.x;
|
||||
float ddy = enemyPos.y - localOrigin.linearPosition.y;
|
||||
@@ -2438,14 +2454,15 @@ void
|
||||
{
|
||||
gTargetLogAccum = 0.0f;
|
||||
DEBUG_STREAM << "[target] aim=(" << gBTAimX << "," << gBTAimY << ")"
|
||||
<< (hotTarget != 0 ? " HOT-aimed (zone under crosshair)"
|
||||
: (designated ? " auto-target (fire freely, body hits)" : " no living target"))
|
||||
<< (hotTarget != 0 ? " ENEMY under boresight (aimed)"
|
||||
: (pickTarget != 0 ? " terrain downrange (beam at scenery)"
|
||||
: " sky (no target, no discharge)"))
|
||||
<< " range=" << range
|
||||
<< (range <= kWeaponRange ? " IN RANGE" : "")
|
||||
<< " [aimedHits=" << gAimHits << " noRay=" << gAimNoRay
|
||||
<< " offHull=" << gAimNoPick << "]"
|
||||
<< " [enemyPicks=" << gAimHits << " groundPicks=" << gAimGround
|
||||
<< " noRay=" << gAimNoRay << "]"
|
||||
<< "\n" << std::flush;
|
||||
gAimHits = 0; gAimNoRay = 0; gAimNoPick = 0;
|
||||
gAimHits = 0; gAimNoRay = 0; gAimGround = 0;
|
||||
}
|
||||
|
||||
// --- FIRING (bring-up): on the trigger, with a target in range and the
|
||||
@@ -2502,29 +2519,14 @@ void
|
||||
gFireCooldown = kFireCooldown;
|
||||
++gShotCount;
|
||||
|
||||
// Impact point (task #36): while the crosshair is ON the target the
|
||||
// shot lands at the PICKED HULL POINT -- the STEP-6 cylinder lookup
|
||||
// resolves damage to the zone under the crosshair (aimed fire: leg
|
||||
// shots hit legs, torso shots hit the torso). Off-crosshair (the
|
||||
// sticky designation converging), the shot enters on the surface
|
||||
// toward the shooter at chest height (the unaimed synth point --
|
||||
// exactly the axis point would be angularly degenerate for the
|
||||
// cylinder sector lookup).
|
||||
Point3D impact;
|
||||
if (hotTarget != 0)
|
||||
{
|
||||
impact = hotPoint;
|
||||
}
|
||||
else
|
||||
{
|
||||
impact = enemyPos;
|
||||
if (range > 1e-3f)
|
||||
{
|
||||
impact.x -= (ddx / range) * 3.0f; // ~torso radius toward shooter
|
||||
impact.z -= (ddz / range) * 3.0f;
|
||||
}
|
||||
impact.y += kMuzzleHeight; // chest height (beam aim height)
|
||||
}
|
||||
// Impact point (tasks #36/#41): damage happens ONLY when the enemy
|
||||
// is under the boresight (designated == hotTarget != 0, checked in
|
||||
// the gate above), so the shot always lands at the PICKED HULL
|
||||
// POINT -- the STEP-6 cylinder lookup resolves damage to the zone
|
||||
// under the boresight (aimed fire: leg shots hit legs, torso shots
|
||||
// the torso). Terrain picks never reach here (beam + scenery
|
||||
// impact only, no damage dispatch).
|
||||
Point3D impact = hotPoint;
|
||||
|
||||
Origin exp_origin = ((Mech *)gEnemyMech)->localOrigin;
|
||||
exp_origin.linearPosition = impact; // at the hit point
|
||||
|
||||
@@ -620,13 +620,18 @@ void
|
||||
// reconstruction; the byte-exact world-entity Projectile is blocked by the 2007 engine
|
||||
// Entity base mismatch -- see BTPushProjectile / mech4.cpp). Speed = |launchVelocity|.
|
||||
char *o = (char *)owner; // inherited MechSubsystem::owner (the Mech)
|
||||
void *target = (o != 0) ? *(void **)(o + 0x388) : 0; // owner's locked target entity (null in bring-up)
|
||||
Point3D targetPos = (o != 0) ? *(Point3D *)(o + 0x37c) : muzzle; // owner target point
|
||||
// The owner's target slots (raw 0x388/0x37c -- the same raw offsets mech4's
|
||||
// targeting block writes; a consistent producer/consumer pair on our
|
||||
// compiled object). Task #41: the slots hold the WORLD PICK -- the enemy
|
||||
// mech under the boresight, or the terrain downrange (a missile fired at
|
||||
// the scenery flies to the ground point and detonates without damage), or
|
||||
// null at the sky (BTPushProjectile refuses; the weapon's own 0x388 gate
|
||||
// wouldn't have fired either).
|
||||
void *target = (o != 0) ? *(void **)(o + 0x388) : 0;
|
||||
Point3D targetPos = (o != 0) ? *(Point3D *)(o + 0x37c) : muzzle;
|
||||
Scalar speed = (Scalar)sqrtf(launchVelocity.x*launchVelocity.x
|
||||
+ launchVelocity.y*launchVelocity.y
|
||||
+ launchVelocity.z*launchVelocity.z);
|
||||
// Always launch -- BTPushProjectile falls back to gEnemyMech when the owner's target
|
||||
// slot isn't populated (bring-up).
|
||||
BTPushProjectile(muzzle, o, target, targetPos, speed, damageData.damageAmount);
|
||||
|
||||
simulationFlags |= 0x1; // replication-dirty
|
||||
|
||||
Reference in New Issue
Block a user