UI/OMRScopeHUD.razor
@using System;
@using System.Globalization;
@using System.Linq;
@using Sandbox;
@using Sandbox.UI;
@inherits PanelComponent
<root>
@if ( ShouldShowScope )
{
<div class="pip-stage" style="@ScopeStageStyle">
<div class="pip-peripheral-dim" style="@PeripheralDimStyle"></div>
<div class="pip-lens" style="@LensStyle">
<OMRScopeFeedPanel class="pip-feed" style="@FeedStyle" SourceTexture=@ScopeTexture />
<div class="pip-lens-transmission" style="@LensTransmissionStyle"></div>
<div class="pip-lens-tint" style="@LensTintStyle"></div>
<div class="pip-lens-vignette" style="@LensVignetteStyle"></div>
<div class="pip-lens-reflection" style="@LensReflectionStyle"></div>
@if ( ShowScreenSpaceRing )
{
<div class="scope-ring" style="@ScopeRingStyle"></div>
}
@if ( ShowReticle )
{
<OMRScopeReticlePanel class="scope-reticle"
ReticleStyle=@(Profile?.SimpleReticleStyle ?? OMRScopeReticleStyle.FineCross)
ReticleColor=@(Profile?.SimpleReticleColor ?? Color.Black)
Thickness=@(Profile?.SimpleReticleThickness ?? 1.5f)
Gap=@(Profile?.SimpleReticleGap ?? 8f)
LineLength=@(Profile?.SimpleReticleLineLength ?? 72f)
DotSize=@(Profile?.SimpleReticleDotSize ?? 3f)
ReticleOpacity=@ReticleOpacity
OutlineWidth=@(Profile?.SimpleReticleOutlineWidth ?? 0f)
OutlineOpacity=@(Profile?.SimpleReticleOutlineOpacity ?? 0f) />
}
</div>
</div>
}
</root>
@code
{
private OMRScopePiPCamera _pipCamera;
private readonly OMRSceneReferences _sceneReferences = new();
private OMRSceneReferences SceneReferences
{
get { _sceneReferences.Refresh( Scene ); return _sceneReferences; }
}
private PlayerState LocalPlayerState => SceneReferences.LocalPlayer;
private BaseInventoryComponent LocalInventory => SceneReferences.LocalInventory;
private IWeaponOpticState LocalOpticState =>
LocalInventory?.ActiveItem as IWeaponOpticState;
private OMROpticProfile Profile =>
LocalOpticState?.ActiveOpticProfile;
private Texture ScopeTexture =>
PiPCamera?.OutputTexture;
private OMRScopePiPCamera PiPCamera
{
get
{
if ( _pipCamera is not null && _pipCamera.IsValid )
return _pipCamera;
_pipCamera =
Scene?
.GetAllComponents<OMRScopePiPCamera>()
.FirstOrDefault(
x =>
x is not null &&
x.IsValid
);
return _pipCamera;
}
}
private RoundManager LocalRoundManager => SceneReferences.Rounds;
private bool IsGameplayContext
{
get
{
if ( PauseMenu.IsAnyPauseOpen )
return false;
RoundManager roundManager = LocalRoundManager;
if ( roundManager is null )
return false;
return roundManager.Mode switch
{
SessionMode.Sandbox => true,
SessionMode.Match => roundManager.Phase == RoundPhase.Fighting,
SessionMode.Menu => LocalPlayerState?.PreMatchState == PreMatchPlayerState.Warmup,
_ => false
};
}
}
private bool UsesScreenSpacePiPOverlay
{
get
{
if ( Profile is null || !Profile.IsValid )
return false;
return Profile.PresentationMode == OMROpticPresentationMode.PictureInPicture ||
(
Profile.PresentationMode == OMROpticPresentationMode.ModelLensPictureInPicture &&
Profile.UseScreenSpaceHybridOverlay
);
}
}
private bool IsHybridModelLensOverlay =>
Profile is not null &&
Profile.IsValid &&
Profile.PresentationMode == OMROpticPresentationMode.ModelLensPictureInPicture &&
Profile.UseScreenSpaceHybridOverlay;
private bool ShowReticle =>
Profile?.ShowSimpleReticle ?? true;
private float ReticleOpacity =>
Profile is null || !Profile.IsValid
? 1f
: MathX.Lerp(
MathX.Clamp( Profile.ReticleHipOpacity, 0f, 1f ),
MathX.Clamp( Profile.ReticleAdsOpacity, 0f, 1f ),
LensClarityBlend
);
private bool ShouldShowScope =>
IsGameplayContext &&
LocalOpticState is not null &&
Profile is not null &&
Profile.IsValid &&
UsesScreenSpacePiPOverlay &&
ScopePresentationBlend > 0.001f &&
PiPCamera is not null &&
PiPCamera.IsRendering;
private float ScopePresentationBlend
{
get
{
if ( LocalOpticState is null || Profile is null )
return 0f;
float start;
float full;
if ( IsHybridModelLensOverlay )
{
start = MathX.Clamp( Profile.HybridOverlayFadeStart, 0f, 1f );
full = MathX.Clamp( Profile.HybridOverlayFadeFull, start, 1f );
}
else
{
start = MathX.Clamp( Profile.ScopePresentationThreshold, 0f, 1f );
full = 1f;
}
if ( full <= start + 0.0001f )
return LocalOpticState.AimFraction >= full ? 1f : 0f;
return MathX.Clamp(
(LocalOpticState.AimFraction - start) / (full - start),
0f,
1f
);
}
}
private string ScopeStageStyle =>
$"opacity: {Css( ScopePresentationBlend )};";
private string PeripheralDimStyle
{
get
{
float opacity = MathX.Clamp(
Profile?.PiPPeripheralDimOpacity ?? 0.12f,
0f,
0.85f
);
return $"background-color: rgba(0, 0, 0, {Css( opacity )});";
}
}
private float DiameterVh
{
get
{
float authored = IsHybridModelLensOverlay
? Profile?.HybridOverlayDiameterVh ?? 40f
: Profile?.PiPLensDiameterVh ?? 64f;
return MathX.Clamp( authored, 10f, 95f );
}
}
private string LensStyle =>
$"width: {Css( DiameterVh )}vh; " +
$"height: {Css( DiameterVh )}vh;";
private float FeedZoomScale =>
MathX.Clamp( PiPCamera?.PresentationZoomScale ?? 1f, 1f, 20f );
private string FeedStyle =>
$"transform:scale({Css( FeedZoomScale )},{Css( FeedZoomScale )});";
private float LensClarityBlend
{
get
{
if ( Profile is null || !Profile.IsValid || !Profile.EnableLensSimulation )
return 1f;
float start = MathX.Clamp( Profile.LensClarityStart, 0f, 1f );
float full = MathX.Clamp( Profile.LensClarityFull, start, 1f );
if ( full <= start + 0.0001f )
return LocalOpticState?.AimFraction >= full ? 1f : 0f;
return MathX.Clamp(
((LocalOpticState?.AimFraction ?? 0f) - start) / (full - start),
0f,
1f
);
}
}
private float OpticalLerp( float hip, float ads ) =>
MathX.Lerp( hip, ads, LensClarityBlend );
private string LensTransmissionStyle
{
get
{
if ( Profile is null || !Profile.IsValid || !Profile.EnableLensSimulation )
return "opacity:0;";
// Do not put a CSS filter directly on OMRScopeFeedPanel. The panel draws a
// live GPU render target in OnDraw; forcing that through an off-screen UI
// filter pass proved unreliable and could collapse the lens to a flat gray
// surface. Simulate light transmission with a simple black veil here, while
// blur/saturation/contrast remain authored for the eventual lens shader.
float brightness = MathX.Clamp(
OpticalLerp( Profile.LensHipBrightness, Profile.LensAdsBrightness ),
0f,
1f
);
float opacity = 1f - brightness;
return $"background-color:rgba(0,0,0,1);opacity:{Css( opacity )};";
}
}
private string LensTintStyle
{
get
{
if ( Profile is null || !Profile.IsValid || !Profile.EnableLensSimulation )
return "opacity:0;";
float opacity = OpticalLerp( Profile.LensHipTintOpacity, Profile.LensAdsTintOpacity );
return $"background-color:{Profile.LensTintColor.Rgba};opacity:{Css( opacity )};";
}
}
private string LensVignetteStyle
{
get
{
if ( Profile is null || !Profile.IsValid || !Profile.EnableLensSimulation )
return "opacity:0;";
float darkness = OpticalLerp( Profile.LensHipEdgeDarkening, Profile.LensAdsEdgeDarkening );
return $"background-image:radial-gradient(rgba(0,0,0,0),rgba(0,0,0,{Css( darkness )}));";
}
}
private string LensReflectionStyle
{
get
{
if ( Profile is null || !Profile.IsValid || !Profile.EnableLensSimulation )
return "opacity:0;";
float opacity = OpticalLerp( Profile.LensHipReflectionOpacity, Profile.LensAdsReflectionOpacity );
return $"opacity:{Css( opacity )};";
}
}
private float RingThickness =>
MathF.Max( Profile?.PiPRingThickness ?? 3f, 0f );
private bool ShowScreenSpaceRing =>
!IsHybridModelLensOverlay || (Profile?.HybridOverlayDrawRing ?? false);
private string ScopeRingStyle
{
get
{
Color ringColor = Profile?.PiPRingColor ?? Color.Black;
return $"border: {Css( RingThickness )}px solid {ringColor.Rgba};";
}
}
private static string Css( float value ) =>
value.ToString(
"0.###",
CultureInfo.InvariantCulture
);
protected override int BuildHash()
{
HashCode hash = new();
IWeaponOpticState optic = LocalOpticState;
OMROpticProfile profile = Profile;
Texture texture = PiPCamera?.OutputTexture;
hash.Add( PauseMenu.IsAnyPauseOpen );
hash.Add( optic?.IsScopePresentationActive ?? false );
hash.Add( optic?.AimFraction ?? 0f );
hash.Add( PiPCamera?.IsRendering ?? false );
hash.Add( texture?.Index ?? -1 );
hash.Add( PiPCamera?.PresentationZoomScale ?? 1f );
if ( profile is null || !profile.IsValid )
return hash.ToHashCode();
hash.Add( profile.PresentationMode );
hash.Add( profile.UseScreenSpaceHybridOverlay );
hash.Add( profile.HybridOverlayDiameterVh );
hash.Add( profile.HybridOverlayDrawRing );
hash.Add( profile.HybridOverlayFadeStart );
hash.Add( profile.HybridOverlayFadeFull );
hash.Add( profile.PiPLensDiameterVh );
hash.Add( profile.PiPPeripheralDimOpacity );
hash.Add( profile.PiPRingThickness );
hash.Add( profile.PiPRingColor );
hash.Add( profile.EnableLensSimulation );
hash.Add( profile.LensClarityStart );
hash.Add( profile.LensClarityFull );
hash.Add( profile.LensHipBlur );
hash.Add( profile.LensAdsBlur );
hash.Add( profile.LensHipBrightness );
hash.Add( profile.LensAdsBrightness );
hash.Add( profile.LensHipSaturation );
hash.Add( profile.LensAdsSaturation );
hash.Add( profile.LensHipContrast );
hash.Add( profile.LensAdsContrast );
hash.Add( profile.LensTintColor );
hash.Add( profile.LensHipTintOpacity );
hash.Add( profile.LensAdsTintOpacity );
hash.Add( profile.LensHipEdgeDarkening );
hash.Add( profile.LensAdsEdgeDarkening );
hash.Add( profile.LensHipReflectionOpacity );
hash.Add( profile.LensAdsReflectionOpacity );
hash.Add( profile.ShowSimpleReticle );
hash.Add( profile.ReticleHipOpacity );
hash.Add( profile.ReticleAdsOpacity );
hash.Add( profile.SimpleReticleOutlineWidth );
hash.Add( profile.SimpleReticleOutlineOpacity );
hash.Add( profile.SimpleReticleStyle );
hash.Add( profile.SimpleReticleColor );
hash.Add( profile.SimpleReticleThickness );
hash.Add( profile.SimpleReticleGap );
hash.Add( profile.SimpleReticleLineLength );
hash.Add( profile.SimpleReticleDotSize );
return hash.ToHashCode();
}
}