Eye damage (#10262)
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
using Content.Client.Verbs;
|
||||
using Content.Shared.Eye.Blinding;
|
||||
using Content.Shared.Examine;
|
||||
using Content.Shared.IdentityManagement;
|
||||
using Content.Shared.Input;
|
||||
@@ -116,7 +117,7 @@ namespace Content.Client.Examine
|
||||
// Tooltips coming in from the server generally prioritize
|
||||
// opening at the old tooltip rather than the cursor/another entity,
|
||||
// since there's probably one open already if it's coming in from the server.
|
||||
OpenTooltip(player.Value, ev.EntityUid, ev.CenterAtCursor, ev.OpenAtOldTooltip);
|
||||
OpenTooltip(player.Value, ev.EntityUid, ev.CenterAtCursor, ev.OpenAtOldTooltip, ev.KnowTarget);
|
||||
UpdateTooltipInfo(player.Value, ev.EntityUid, ev.Message, ev.Verbs);
|
||||
}
|
||||
|
||||
@@ -131,7 +132,7 @@ namespace Content.Client.Examine
|
||||
/// not fill it with information. This is done when the server sends examine info/verbs,
|
||||
/// or immediately if it's entirely clientside.
|
||||
/// </summary>
|
||||
public void OpenTooltip(EntityUid player, EntityUid target, bool centeredOnCursor=true, bool openAtOldTooltip=true)
|
||||
public void OpenTooltip(EntityUid player, EntityUid target, bool centeredOnCursor=true, bool openAtOldTooltip=true, bool knowTarget = true)
|
||||
{
|
||||
// Close any examine tooltip that might already be opened
|
||||
// Before we do that, save its position. We'll prioritize opening any new popups there if
|
||||
@@ -191,11 +192,22 @@ namespace Content.Client.Examine
|
||||
});
|
||||
}
|
||||
|
||||
hBox.AddChild(new Label
|
||||
if (knowTarget)
|
||||
{
|
||||
Text = Identity.Name(target, EntityManager, player),
|
||||
HorizontalExpand = true,
|
||||
});
|
||||
hBox.AddChild(new Label
|
||||
{
|
||||
Text = Identity.Name(target, EntityManager, player),
|
||||
HorizontalExpand = true,
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
hBox.AddChild(new Label
|
||||
{
|
||||
Text = "???",
|
||||
HorizontalExpand = true,
|
||||
});
|
||||
}
|
||||
|
||||
panel.Measure(Vector2.Infinity);
|
||||
var size = Vector2.ComponentMax((minWidth, 0), panel.DesiredSize);
|
||||
@@ -294,7 +306,13 @@ namespace Content.Client.Examine
|
||||
return;
|
||||
|
||||
FormattedMessage message;
|
||||
OpenTooltip(playerEnt.Value, entity, centeredOnCursor, false);
|
||||
|
||||
// Basically this just predicts that we can't make out the entity if we have poor vision.
|
||||
var canSeeClearly = true;
|
||||
if (HasComp<BlurryVisionComponent>(playerEnt))
|
||||
canSeeClearly = false;
|
||||
|
||||
OpenTooltip(playerEnt.Value, entity, centeredOnCursor, false, knowTarget: canSeeClearly);
|
||||
if (entity.IsClientSide())
|
||||
{
|
||||
message = GetExamineText(entity, playerEnt);
|
||||
|
||||
68
Content.Client/Eye/Blinding/BlurryVisionOverlay.cs
Normal file
68
Content.Client/Eye/Blinding/BlurryVisionOverlay.cs
Normal file
@@ -0,0 +1,68 @@
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Content.Shared.Eye.Blinding;
|
||||
|
||||
namespace Content.Client.Eye.Blinding
|
||||
{
|
||||
public sealed class BlurryVisionOverlay : Overlay
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
|
||||
public override bool RequestScreenTexture => true;
|
||||
public override OverlaySpace Space => OverlaySpace.WorldSpace;
|
||||
private readonly ShaderInstance _blurryVisionXShader;
|
||||
private readonly ShaderInstance _blurryVisionYShader;
|
||||
private BlurryVisionComponent _blurryVisionComponent = default!;
|
||||
|
||||
public BlurryVisionOverlay()
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
_blurryVisionXShader = _prototypeManager.Index<ShaderPrototype>("BlurryVisionX").InstanceUnique();
|
||||
_blurryVisionYShader = _prototypeManager.Index<ShaderPrototype>("BlurryVisionY").InstanceUnique();
|
||||
}
|
||||
|
||||
protected override bool BeforeDraw(in OverlayDrawArgs args)
|
||||
{
|
||||
var playerEntity = _playerManager.LocalPlayer?.ControlledEntity;
|
||||
|
||||
if (playerEntity == null)
|
||||
return false;
|
||||
|
||||
if (!_entityManager.TryGetComponent<BlurryVisionComponent>(playerEntity, out var blurComp))
|
||||
return false;
|
||||
|
||||
if (!blurComp.Active)
|
||||
return false;
|
||||
|
||||
if (_entityManager.TryGetComponent<BlindableComponent>(playerEntity, out var blindComp)
|
||||
&& blindComp.Sources > 0)
|
||||
return false;
|
||||
|
||||
_blurryVisionComponent = blurComp;
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override void Draw(in OverlayDrawArgs args)
|
||||
{
|
||||
if (ScreenTexture == null)
|
||||
return;
|
||||
|
||||
_blurryVisionXShader?.SetParameter("SCREEN_TEXTURE", ScreenTexture);
|
||||
_blurryVisionXShader?.SetParameter("BLUR_AMOUNT", (_blurryVisionComponent.Magnitude / 10));
|
||||
_blurryVisionYShader?.SetParameter("SCREEN_TEXTURE", ScreenTexture);
|
||||
_blurryVisionYShader?.SetParameter("BLUR_AMOUNT", (_blurryVisionComponent.Magnitude / 10));
|
||||
|
||||
var worldHandle = args.WorldHandle;
|
||||
var viewport = args.WorldBounds;
|
||||
worldHandle.SetTransform(Matrix3.Identity);
|
||||
worldHandle.UseShader(_blurryVisionXShader);
|
||||
worldHandle.DrawRect(viewport, Color.White);
|
||||
worldHandle.UseShader(_blurryVisionYShader);
|
||||
worldHandle.DrawRect(viewport, Color.White);
|
||||
}
|
||||
}
|
||||
}
|
||||
61
Content.Client/Eye/Blinding/BlurryVisionSystem.cs
Normal file
61
Content.Client/Eye/Blinding/BlurryVisionSystem.cs
Normal file
@@ -0,0 +1,61 @@
|
||||
using Content.Shared.Eye.Blinding;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Client.Eye.Blinding;
|
||||
|
||||
public sealed class BlurryVisionSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IPlayerManager _player = default!;
|
||||
[Dependency] private readonly IOverlayManager _overlayMan = default!;
|
||||
private BlurryVisionOverlay _overlay = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<BlurryVisionComponent, ComponentInit>(OnBlurryInit);
|
||||
SubscribeLocalEvent<BlurryVisionComponent, ComponentShutdown>(OnBlurryShutdown);
|
||||
|
||||
SubscribeLocalEvent<BlurryVisionComponent, PlayerAttachedEvent>(OnPlayerAttached);
|
||||
SubscribeLocalEvent<BlurryVisionComponent, PlayerDetachedEvent>(OnPlayerDetached);
|
||||
|
||||
SubscribeLocalEvent<BlurryVisionComponent, ComponentHandleState>(OnHandleState);
|
||||
|
||||
_overlay = new();
|
||||
}
|
||||
|
||||
private void OnPlayerAttached(EntityUid uid, BlurryVisionComponent component, PlayerAttachedEvent args)
|
||||
{
|
||||
_overlayMan.AddOverlay(_overlay);
|
||||
}
|
||||
|
||||
private void OnPlayerDetached(EntityUid uid, BlurryVisionComponent component, PlayerDetachedEvent args)
|
||||
{
|
||||
_overlayMan.RemoveOverlay(_overlay);
|
||||
}
|
||||
|
||||
private void OnBlurryInit(EntityUid uid, BlurryVisionComponent component, ComponentInit args)
|
||||
{
|
||||
if (_player.LocalPlayer?.ControlledEntity == uid)
|
||||
_overlayMan.AddOverlay(_overlay);
|
||||
}
|
||||
|
||||
private void OnBlurryShutdown(EntityUid uid, BlurryVisionComponent component, ComponentShutdown args)
|
||||
{
|
||||
if (_player.LocalPlayer?.ControlledEntity == uid)
|
||||
{
|
||||
_overlayMan.RemoveOverlay(_overlay);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnHandleState(EntityUid uid, BlurryVisionComponent component, ref ComponentHandleState args)
|
||||
{
|
||||
if (args.Current is not BlurryVisionComponentState state)
|
||||
return;
|
||||
|
||||
component.Magnitude = state.Magnitude;
|
||||
}
|
||||
}
|
||||
23
Content.Server/Chemistry/ReagentEffects/ChemHealEyeDamage.cs
Normal file
23
Content.Server/Chemistry/ReagentEffects/ChemHealEyeDamage.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
using Content.Shared.Chemistry.Reagent;
|
||||
using Content.Shared.Eye.Blinding;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace Content.Server.Chemistry.ReagentEffects
|
||||
{
|
||||
/// <summary>
|
||||
/// Heal eye damage (or deal)
|
||||
/// </summary>
|
||||
[UsedImplicitly]
|
||||
public sealed class ChemHealEyeDamage : ReagentEffect
|
||||
{
|
||||
/// <summary>
|
||||
/// Add or remove eye damage?
|
||||
[DataField("add")]
|
||||
public bool Add = false;
|
||||
|
||||
public override void Effect(ReagentEffectArgs args)
|
||||
{
|
||||
EntitySystem.Get<SharedBlindingSystem>().AdjustEyeDamage(args.SolutionEntity, Add);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,10 +16,14 @@ namespace Content.Server.Examine
|
||||
|
||||
private static readonly FormattedMessage _entityNotFoundMessage;
|
||||
|
||||
private static readonly FormattedMessage _entityOutOfRangeMessage;
|
||||
|
||||
static ExamineSystem()
|
||||
{
|
||||
_entityNotFoundMessage = new FormattedMessage();
|
||||
_entityNotFoundMessage.AddText(Loc.GetString("examine-system-entity-does-not-exist"));
|
||||
_entityOutOfRangeMessage = new FormattedMessage();
|
||||
_entityOutOfRangeMessage.AddText(Loc.GetString("examine-system-cant-see-entity"));
|
||||
}
|
||||
|
||||
public override void Initialize()
|
||||
@@ -54,14 +58,20 @@ namespace Content.Server.Examine
|
||||
var channel = player.ConnectedClient;
|
||||
|
||||
if (session.AttachedEntity is not {Valid: true} playerEnt
|
||||
|| !EntityManager.EntityExists(request.EntityUid)
|
||||
|| !CanExamine(playerEnt, request.EntityUid))
|
||||
|| !EntityManager.EntityExists(request.EntityUid))
|
||||
{
|
||||
RaiseNetworkEvent(new ExamineSystemMessages.ExamineInfoResponseMessage(
|
||||
request.EntityUid, _entityNotFoundMessage), channel);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!CanExamine(playerEnt, request.EntityUid))
|
||||
{
|
||||
RaiseNetworkEvent(new ExamineSystemMessages.ExamineInfoResponseMessage(
|
||||
request.EntityUid, _entityOutOfRangeMessage, knowTarget: false), channel);
|
||||
return;
|
||||
}
|
||||
|
||||
SortedSet<Verb>? verbs = null;
|
||||
if (request.GetVerbs)
|
||||
verbs = _verbSystem.GetLocalVerbs(request.EntityUid, playerEnt, typeof(ExamineVerb));
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
using Content.Shared.Eye.Blinding.EyeProtection; // why aren't tools predicted 🙂
|
||||
using Content.Shared.Eye.Blinding;
|
||||
using Content.Shared.StatusEffect;
|
||||
using Content.Shared.Clothing.Components;
|
||||
using Content.Shared.Inventory;
|
||||
using Content.Shared.Inventory.Events;
|
||||
using Content.Server.Tools;
|
||||
|
||||
namespace Content.Server.Eye.Blinding.EyeProtection
|
||||
{
|
||||
public sealed class EyeProtectionSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly StatusEffectsSystem _statusEffectsSystem = default!;
|
||||
[Dependency] private readonly SharedBlindingSystem _blindingSystem = default!;
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<RequiresEyeProtectionComponent, ToolUseAttemptEvent>(OnUseAttempt);
|
||||
SubscribeLocalEvent<RequiresEyeProtectionComponent, WelderToggledEvent>(OnWelderToggled);
|
||||
|
||||
SubscribeLocalEvent<EyeProtectionComponent, GotEquippedEvent>(OnEquipped);
|
||||
SubscribeLocalEvent<EyeProtectionComponent, GotUnequippedEvent>(OnUnequipped);
|
||||
}
|
||||
|
||||
private void OnUseAttempt(EntityUid uid, RequiresEyeProtectionComponent component, ToolUseAttemptEvent args)
|
||||
{
|
||||
if (!component.Toggled)
|
||||
return;
|
||||
|
||||
if (!TryComp<StatusEffectsComponent>(args.User, out var status) || !TryComp<BlindableComponent>(args.User, out var blindable))
|
||||
return;
|
||||
|
||||
if (blindable.Sources > 0)
|
||||
return;
|
||||
|
||||
float statusTime = (float) component.StatusEffectTime.TotalSeconds - blindable.BlindResistance;
|
||||
|
||||
if (statusTime <= 0)
|
||||
return;
|
||||
|
||||
var statusTimeSpan = TimeSpan.FromSeconds(statusTime * (blindable.EyeDamage + 1));
|
||||
// Add permanent eye damage if they had zero protection, also scale their temporary blindness by how much they already accumulated.
|
||||
if (_statusEffectsSystem.TryAddStatusEffect(args.User, SharedBlindingSystem.BlindingStatusEffect, statusTimeSpan, false, "TemporaryBlindness") && blindable.BlindResistance <= 0)
|
||||
_blindingSystem.AdjustEyeDamage(args.User, true, blindable);
|
||||
}
|
||||
private void OnWelderToggled(EntityUid uid, RequiresEyeProtectionComponent component, WelderToggledEvent args)
|
||||
{
|
||||
component.Toggled = args.WelderOn;
|
||||
}
|
||||
|
||||
private void OnEquipped(EntityUid uid, EyeProtectionComponent component, GotEquippedEvent args)
|
||||
{
|
||||
if (!TryComp<SharedClothingComponent>(uid, out var clothing) || clothing.Slots == SlotFlags.PREVENTEQUIP)
|
||||
return;
|
||||
|
||||
if (!clothing.Slots.HasFlag(args.SlotFlags))
|
||||
return;
|
||||
|
||||
component.IsActive = true;
|
||||
if (!TryComp<BlindableComponent>(args.Equipee, out var blindComp))
|
||||
return;
|
||||
|
||||
blindComp.BlindResistance += (float) component.ProtectionTime.TotalSeconds;
|
||||
}
|
||||
|
||||
private void OnUnequipped(EntityUid uid, EyeProtectionComponent component, GotUnequippedEvent args)
|
||||
{
|
||||
if (!component.IsActive)
|
||||
return;
|
||||
component.IsActive = false;
|
||||
if (!TryComp<BlindableComponent>(args.Equipee, out var blindComp))
|
||||
return;
|
||||
|
||||
blindComp.BlindResistance -= (float) component.ProtectionTime.TotalSeconds;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -104,6 +104,9 @@ namespace Content.Server.Tools
|
||||
|
||||
welder.Lit = true;
|
||||
|
||||
var ev = new WelderToggledEvent(true);
|
||||
RaiseLocalEvent(welder.Owner, ev, false);
|
||||
|
||||
if(item != null)
|
||||
_itemSystem.SetHeldPrefix(uid, "on", item);
|
||||
|
||||
@@ -140,6 +143,9 @@ namespace Content.Server.Tools
|
||||
|
||||
welder.Lit = false;
|
||||
|
||||
var ev = new WelderToggledEvent(false);
|
||||
RaiseLocalEvent(welder.Owner, ev, false);
|
||||
|
||||
// TODO: Make all this use visualizers.
|
||||
if (item != null)
|
||||
_itemSystem.SetHeldPrefix(uid, "off", item);
|
||||
@@ -332,4 +338,14 @@ namespace Content.Server.Tools
|
||||
_welderTimer -= WelderUpdateTimer;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class WelderToggledEvent : EntityEventArgs
|
||||
{
|
||||
public bool WelderOn;
|
||||
|
||||
public WelderToggledEvent(bool welderOn)
|
||||
{
|
||||
WelderOn = welderOn;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,14 +31,17 @@ namespace Content.Shared.Examine
|
||||
public readonly bool CenterAtCursor;
|
||||
public readonly bool OpenAtOldTooltip;
|
||||
|
||||
public readonly bool KnowTarget;
|
||||
|
||||
public ExamineInfoResponseMessage(EntityUid entityUid, FormattedMessage message, List<Verb>? verbs=null,
|
||||
bool centerAtCursor=true, bool openAtOldTooltip=true)
|
||||
bool centerAtCursor=true, bool openAtOldTooltip=true, bool knowTarget = true)
|
||||
{
|
||||
EntityUid = entityUid;
|
||||
Message = message;
|
||||
Verbs = verbs;
|
||||
CenterAtCursor = centerAtCursor;
|
||||
OpenAtOldTooltip = openAtOldTooltip;
|
||||
KnowTarget = knowTarget;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,6 +101,12 @@ namespace Content.Shared.Examine
|
||||
return DeadExamineRange;
|
||||
else if (MobStateSystem.IsCritical(examiner, mobState) || (TryComp<BlindableComponent>(examiner, out var blind) && blind.Sources > 0))
|
||||
return CritExamineRange;
|
||||
|
||||
else if (TryComp<BlurryVisionComponent>(examiner, out var blurry) && blurry.Magnitude != 0)
|
||||
{
|
||||
float range = ExamineRange - (2 * (8 - blurry.Magnitude));
|
||||
return Math.Clamp(range, 2, 16);
|
||||
}
|
||||
}
|
||||
return ExamineRange;
|
||||
}
|
||||
|
||||
@@ -12,6 +12,24 @@ namespace Content.Shared.Eye.Blinding
|
||||
[DataField("sources")]
|
||||
public int Sources = 0;
|
||||
|
||||
/// <summary>
|
||||
/// How many seconds will be subtracted from each attempt to add blindness to us?
|
||||
/// </summary>
|
||||
[DataField("blindResistance")]
|
||||
public float BlindResistance = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Replace with actual eye damage after bobby I guess
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public int EyeDamage = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Whether eye damage has accumulated enough to blind them.
|
||||
/// <summary>
|
||||
[ViewVariables]
|
||||
public bool EyeTooDamaged = false;
|
||||
|
||||
/// <description>
|
||||
/// Used to ensure that this doesn't break with sandbox or admin tools.
|
||||
/// This is not "enabled/disabled".
|
||||
|
||||
14
Content.Shared/Eye/Blinding/BlurryVisionComponent.cs
Normal file
14
Content.Shared/Eye/Blinding/BlurryVisionComponent.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared.Eye.Blinding
|
||||
{
|
||||
[RegisterComponent]
|
||||
[NetworkedComponent]
|
||||
public sealed class BlurryVisionComponent : Component
|
||||
{
|
||||
[DataField("mangitude")]
|
||||
public float Magnitude = 1f;
|
||||
|
||||
public bool Active => Magnitude < 10f;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
namespace Content.Shared.Eye.Blinding.EyeProtection
|
||||
{
|
||||
/// <summary>
|
||||
/// For welding masks, sunglasses, etc.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed class EyeProtectionComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// How many seconds to subtract from the status effect. If it's greater than the source
|
||||
/// of blindness, do not blind.
|
||||
/// </summary>
|
||||
[DataField("protectionTime")]
|
||||
public TimeSpan ProtectionTime = TimeSpan.FromSeconds(10);
|
||||
|
||||
public bool IsActive = false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
namespace Content.Shared.Eye.Blinding.EyeProtection
|
||||
{
|
||||
/// <summary>
|
||||
/// For tools like welders that will damage your eyes when you use them.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed class RequiresEyeProtectionComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// How long to apply temporary blindness to the user.
|
||||
/// </summary>
|
||||
[DataField("statusEffectTime")]
|
||||
public TimeSpan StatusEffectTime = TimeSpan.FromSeconds(10);
|
||||
|
||||
/// <summary>
|
||||
/// You probably want to turn this on in yaml if it's something always on and not a welder.
|
||||
/// </summary>
|
||||
[DataField("toggled")]
|
||||
public bool Toggled = false;
|
||||
}
|
||||
}
|
||||
@@ -1,18 +1,28 @@
|
||||
using Content.Shared.Clothing.Components;
|
||||
using Content.Shared.Inventory.Events;
|
||||
using Content.Shared.Inventory;
|
||||
using Content.Shared.Item;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Serialization;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace Content.Shared.Eye.Blinding
|
||||
{
|
||||
public sealed class SharedBlindingSystem : EntitySystem
|
||||
{
|
||||
public const string BlindingStatusEffect = "TemporaryBlindness";
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<BlindfoldComponent, GotEquippedEvent>(OnEquipped);
|
||||
SubscribeLocalEvent<BlindfoldComponent, GotUnequippedEvent>(OnUnequipped);
|
||||
|
||||
SubscribeLocalEvent<VisionCorrectionComponent, GotEquippedEvent>(OnGlassesEquipped);
|
||||
SubscribeLocalEvent<VisionCorrectionComponent, GotUnequippedEvent>(OnGlassesUnequipped);
|
||||
|
||||
SubscribeLocalEvent<BlurryVisionComponent, ComponentGetState>(OnGetState);
|
||||
|
||||
SubscribeLocalEvent<TemporaryBlindnessComponent, ComponentInit>(OnInit);
|
||||
SubscribeLocalEvent<TemporaryBlindnessComponent, ComponentShutdown>(OnShutdown);
|
||||
}
|
||||
|
||||
private void OnEquipped(EntityUid uid, BlindfoldComponent component, GotEquippedEvent args)
|
||||
@@ -39,6 +49,46 @@ namespace Content.Shared.Eye.Blinding
|
||||
AdjustBlindSources(args.Equipee, false, blindComp);
|
||||
}
|
||||
|
||||
private void OnGlassesEquipped(EntityUid uid, VisionCorrectionComponent component, GotEquippedEvent args)
|
||||
{
|
||||
if (!TryComp<SharedClothingComponent>(uid, out var clothing) || clothing.Slots == SlotFlags.PREVENTEQUIP) // we live in a society
|
||||
return;
|
||||
// Is the clothing in its actual slot?
|
||||
if (!clothing.Slots.HasFlag(args.SlotFlags))
|
||||
return;
|
||||
|
||||
if (!TryComp<BlurryVisionComponent>(args.Equipee, out var blur))
|
||||
return;
|
||||
|
||||
component.IsActive = true;
|
||||
blur.Magnitude += component.VisionBonus;
|
||||
blur.Dirty();
|
||||
}
|
||||
|
||||
private void OnGlassesUnequipped(EntityUid uid, VisionCorrectionComponent component, GotUnequippedEvent args)
|
||||
{
|
||||
if (!component.IsActive || !TryComp<BlurryVisionComponent>(args.Equipee, out var blur))
|
||||
return;
|
||||
component.IsActive = false;
|
||||
blur.Magnitude -= component.VisionBonus;
|
||||
blur.Dirty();
|
||||
}
|
||||
|
||||
private void OnGetState(EntityUid uid, BlurryVisionComponent component, ref ComponentGetState args)
|
||||
{
|
||||
args.State = new BlurryVisionComponentState(component.Magnitude);
|
||||
}
|
||||
|
||||
private void OnInit(EntityUid uid, TemporaryBlindnessComponent component, ComponentInit args)
|
||||
{
|
||||
AdjustBlindSources(uid, true);
|
||||
}
|
||||
|
||||
private void OnShutdown(EntityUid uid, TemporaryBlindnessComponent component, ComponentShutdown args)
|
||||
{
|
||||
AdjustBlindSources(uid, false);
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
public void AdjustBlindSources(EntityUid uid, bool Add, BlindableComponent? blindable = null)
|
||||
{
|
||||
@@ -52,6 +102,56 @@ namespace Content.Shared.Eye.Blinding
|
||||
{
|
||||
blindable.Sources--;
|
||||
}
|
||||
|
||||
blindable.Sources = Math.Max(blindable.Sources, 0);
|
||||
}
|
||||
|
||||
public void AdjustEyeDamage(EntityUid uid, bool add, BlindableComponent? blindable = null)
|
||||
{
|
||||
if (!Resolve(uid, ref blindable, false))
|
||||
return;
|
||||
|
||||
if (add)
|
||||
{
|
||||
blindable.EyeDamage++;
|
||||
} else
|
||||
{
|
||||
blindable.EyeDamage--;
|
||||
}
|
||||
|
||||
if (blindable.EyeDamage > 0)
|
||||
{
|
||||
var blurry = EnsureComp<BlurryVisionComponent>(uid);
|
||||
blurry.Magnitude = (9 - blindable.EyeDamage);
|
||||
blurry.Dirty();
|
||||
} else
|
||||
{
|
||||
RemComp<BlurryVisionComponent>(uid);
|
||||
}
|
||||
|
||||
if (!blindable.EyeTooDamaged && blindable.EyeDamage >= 8)
|
||||
{
|
||||
blindable.EyeTooDamaged = true;
|
||||
AdjustBlindSources(uid, true, blindable);
|
||||
}
|
||||
if (blindable.EyeTooDamaged && blindable.EyeDamage < 8)
|
||||
{
|
||||
blindable.EyeTooDamaged = false;
|
||||
AdjustBlindSources(uid, false, blindable);
|
||||
}
|
||||
|
||||
blindable.EyeDamage = Math.Clamp(blindable.EyeDamage, 0, 8);
|
||||
}
|
||||
}
|
||||
|
||||
// I have no idea why blurry vision needs this but blindness doesn't
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class BlurryVisionComponentState : ComponentState
|
||||
{
|
||||
public float Magnitude;
|
||||
public BlurryVisionComponentState(float magnitude)
|
||||
{
|
||||
Magnitude = magnitude;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
11
Content.Shared/Eye/Blinding/TemporaryBlindnessComponent.cs
Normal file
11
Content.Shared/Eye/Blinding/TemporaryBlindnessComponent.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared.Eye.Blinding
|
||||
{
|
||||
/// <summary>
|
||||
/// Blind status effect.
|
||||
/// </summary>
|
||||
[NetworkedComponent, RegisterComponent]
|
||||
public sealed class TemporaryBlindnessComponent : Component
|
||||
{}
|
||||
}
|
||||
15
Content.Shared/Eye/Blinding/VisionCorrectionComponent.cs
Normal file
15
Content.Shared/Eye/Blinding/VisionCorrectionComponent.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared.Eye.Blinding
|
||||
{
|
||||
[RegisterComponent]
|
||||
[NetworkedComponent]
|
||||
public sealed class VisionCorrectionComponent : Component
|
||||
{
|
||||
[ViewVariables]
|
||||
public bool IsActive = false;
|
||||
|
||||
[DataField("visionBonus")]
|
||||
public float VisionBonus = 3f;
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
examine-system-entity-does-not-exist = That entity doesn't exist
|
||||
|
||||
examine-system-cant-see-entity = You can't make out whatever that is.
|
||||
|
||||
examine-verb-name = Basic
|
||||
|
||||
examinable-anchored = It is anchored to the floor
|
||||
|
||||
@@ -85,5 +85,8 @@ reagent-desc-omnizine = A soothing milky liquid with an iridescent gleam. A well
|
||||
reagent-name-ultravasculine = ultravasculine
|
||||
reagent-desc-ultravasculine = Rapidly flushes toxins from the body, but places some stress on the veins. Do not overdose.
|
||||
|
||||
reagent-name-oculine = oculine
|
||||
reagent-desc-oculine = Heals eye damage.
|
||||
|
||||
reagent-name-ethylredoxrazine = ethylredoxrazine
|
||||
reagent-desc-ethylredoxrazine = Neutralises the effects of alcohol in the blood stream. Though it is commonly needed, it is rarely requested.
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
spriteName: engivend
|
||||
startingInventory:
|
||||
ClothingEyesGlassesMeson: 4
|
||||
ClothingHeadHatWelding: 4
|
||||
Multitool: 4
|
||||
PowerCellMedium: 5
|
||||
ClothingHandsGlovesColorYellow: 6
|
||||
|
||||
@@ -65,17 +65,14 @@
|
||||
- type: entity
|
||||
parent: ClothingEyesBase
|
||||
id: ClothingEyesGlassesMeson
|
||||
name: optical meson scanners
|
||||
description: The pinnacle of modern science, wallhacks in real life
|
||||
name: engineering goggles #less confusion
|
||||
description: Green-tinted goggles using a proprietary polymer that provides protection from eye damage of all types.
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: Clothing/Eyes/Glasses/meson.rsi
|
||||
- type: Clothing
|
||||
sprite: Clothing/Eyes/Glasses/meson.rsi
|
||||
- type: Armor
|
||||
modifiers:
|
||||
coefficients:
|
||||
Radiation: 0.5
|
||||
- type: EyeProtection
|
||||
|
||||
- type: entity
|
||||
parent: ClothingEyesBase
|
||||
@@ -87,6 +84,7 @@
|
||||
sprite: Clothing/Eyes/Glasses/glasses.rsi
|
||||
- type: Clothing
|
||||
sprite: Clothing/Eyes/Glasses/glasses.rsi
|
||||
- type: VisionCorrection
|
||||
|
||||
- type: entity
|
||||
parent: ClothingEyesBase
|
||||
@@ -99,6 +97,8 @@
|
||||
- type: Clothing
|
||||
sprite: Clothing/Eyes/Glasses/sunglasses.rsi
|
||||
- type: FlashImmunity
|
||||
- type: EyeProtection
|
||||
protectionTime: 5
|
||||
|
||||
- type: entity
|
||||
parent: ClothingEyesBase
|
||||
@@ -111,6 +111,8 @@
|
||||
- type: Clothing
|
||||
sprite: Clothing/Eyes/Glasses/secglasses.rsi
|
||||
- type: FlashImmunity
|
||||
- type: EyeProtection
|
||||
protectionTime: 5
|
||||
|
||||
#Make a scanner category when these actually function and we get the trayson
|
||||
- type: entity
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
sprite: Clothing/Eyes/Misc/eyepatch.rsi
|
||||
- type: Clothing
|
||||
sprite: Clothing/Eyes/Misc/eyepatch.rsi
|
||||
- type: EyeProtection
|
||||
protectionTime: 5
|
||||
|
||||
- type: entity
|
||||
parent: ClothingEyesBase
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
- type: IngestionBlocker
|
||||
- type: FlashImmunity
|
||||
- type: IdentityBlocker
|
||||
- type: EyeProtection
|
||||
|
||||
- type: entity
|
||||
parent: WeldingMaskBase
|
||||
|
||||
@@ -36,6 +36,8 @@
|
||||
- type: Clothing
|
||||
sprite: Clothing/Mask/gassyndicate.rsi
|
||||
- type: FlashImmunity
|
||||
- type: EyeProtection
|
||||
protectionTime: 5
|
||||
|
||||
- type: entity
|
||||
parent: ClothingMaskGas
|
||||
|
||||
@@ -112,6 +112,7 @@
|
||||
- Stutter
|
||||
- Electrocution
|
||||
- ForcedSleep
|
||||
- TemporaryBlindness
|
||||
- type: Body
|
||||
template: AnimalTemplate
|
||||
preset: AnimalPreset
|
||||
@@ -179,6 +180,7 @@
|
||||
- Stutter
|
||||
- Electrocution
|
||||
- ForcedSleep
|
||||
- TemporaryBlindness
|
||||
- type: ThermalRegulator
|
||||
metabolismHeat: 800
|
||||
radiatedHeat: 100
|
||||
|
||||
@@ -80,6 +80,7 @@
|
||||
- PressureImmunity
|
||||
- Muted
|
||||
- ForcedSleep
|
||||
- TemporaryBlindness
|
||||
- type: DiseaseCarrier
|
||||
- type: Blindable
|
||||
# Other
|
||||
|
||||
@@ -108,6 +108,17 @@
|
||||
- state: carrot
|
||||
- type: SliceableFood
|
||||
slice: FoodCakeCarrotSlice
|
||||
- type: SolutionContainerManager
|
||||
solutions:
|
||||
food:
|
||||
maxVol: 30
|
||||
reagents:
|
||||
- ReagentId: JuiceCarrot
|
||||
Quantity: 15
|
||||
- ReagentId: Sugar
|
||||
Quantity: 5
|
||||
- ReagentId: Vitamin
|
||||
Quantity: 5
|
||||
|
||||
- type: entity
|
||||
name: slice of carrot cake
|
||||
@@ -120,6 +131,18 @@
|
||||
- state: plate-small
|
||||
- state: plate-slice-shading
|
||||
- state: carrot-slice
|
||||
- type: SolutionContainerManager
|
||||
solutions:
|
||||
food:
|
||||
maxVol: 6
|
||||
reagents:
|
||||
- ReagentId: JuiceCarrot
|
||||
Quantity: 3
|
||||
- ReagentId: Sugar
|
||||
Quantity: 1
|
||||
- ReagentId: Vitamin
|
||||
Quantity: 1
|
||||
|
||||
# Tastes like sweetness, cake, carrot.
|
||||
|
||||
- type: entity
|
||||
|
||||
@@ -265,5 +265,7 @@
|
||||
reagents:
|
||||
- ReagentId: Nutriment
|
||||
Quantity: 2
|
||||
- ReagentId: JuiceCarrot
|
||||
Quantity: 1
|
||||
- type: Sprite
|
||||
state: dink
|
||||
|
||||
@@ -74,7 +74,8 @@
|
||||
- type: Sprite
|
||||
layers:
|
||||
- state: margherita-slice
|
||||
# Tastes like crust, tomato, cheese.
|
||||
- type: SliceableFood
|
||||
slice: FoodPizzaMeatSlice
|
||||
|
||||
- type: entity
|
||||
name: meat pizza
|
||||
@@ -133,6 +134,17 @@
|
||||
- state: vegetable
|
||||
- type: SliceableFood
|
||||
slice: FoodPizzaVegetableSlice
|
||||
- type: SolutionContainerManager
|
||||
solutions:
|
||||
food:
|
||||
maxVol: 40
|
||||
reagents:
|
||||
- ReagentId: Nutriment
|
||||
Quantity: 25
|
||||
- ReagentId: JuiceCarrot
|
||||
Quantity: 5
|
||||
- ReagentId: Vitamin
|
||||
Quantity: 5
|
||||
|
||||
- type: entity
|
||||
name: slice of vegetable pizza
|
||||
@@ -143,6 +155,18 @@
|
||||
- type: Sprite
|
||||
layers:
|
||||
- state: vegetable-slice
|
||||
- type: SolutionContainerManager
|
||||
solutions:
|
||||
food:
|
||||
maxVol: 40
|
||||
reagents:
|
||||
- ReagentId: Nutriment
|
||||
Quantity: 4
|
||||
- ReagentId: JuiceCarrot
|
||||
Quantity: 1
|
||||
- ReagentId: Vitamin
|
||||
Quantity: 1
|
||||
|
||||
# Tastes like crust, tomato, cheese, carrot.
|
||||
|
||||
- type: entity
|
||||
|
||||
@@ -61,6 +61,14 @@
|
||||
components:
|
||||
- type: Sprite
|
||||
state: fries-carrot
|
||||
netsync: false
|
||||
- type: SolutionContainerManager
|
||||
solutions:
|
||||
food:
|
||||
maxVol: 26
|
||||
reagents:
|
||||
- ReagentId: JuiceCarrot
|
||||
Quantity: 20
|
||||
# Tastes like carrots, salt.
|
||||
|
||||
- type: entity
|
||||
|
||||
@@ -228,7 +228,7 @@
|
||||
food:
|
||||
maxVol: 9
|
||||
reagents:
|
||||
- ReagentId: Nutriment
|
||||
- ReagentId: JuiceCarrot
|
||||
Quantity: 5
|
||||
- ReagentId: Vitamin
|
||||
Quantity: 4
|
||||
|
||||
@@ -50,6 +50,7 @@
|
||||
radius: 1.5
|
||||
color: orange
|
||||
- type: Appearance
|
||||
- type: RequiresEyeProtection
|
||||
|
||||
- type: entity
|
||||
name: industrial welding tool
|
||||
|
||||
@@ -86,7 +86,7 @@
|
||||
growthStages: 3
|
||||
waterConsumption: 6
|
||||
chemicals:
|
||||
Nutriment:
|
||||
JuiceCarrot:
|
||||
Min: 1
|
||||
Max: 5
|
||||
PotencyDivisor: 20
|
||||
|
||||
@@ -57,6 +57,17 @@
|
||||
desc: reagent-desc-juice-carrot
|
||||
physicalDesc: reagent-physical-desc-crisp
|
||||
color: "#FF8820"
|
||||
metabolisms:
|
||||
Drink:
|
||||
effects:
|
||||
- !type:SatiateThirst
|
||||
factor: 2
|
||||
- !type:AdjustReagent
|
||||
reagent: Oculine
|
||||
amount: 0.15
|
||||
- !type:AdjustReagent
|
||||
reagent: Nutriment
|
||||
amount: 0.5
|
||||
|
||||
- type: reagent
|
||||
id: JuiceGrape
|
||||
|
||||
@@ -658,3 +658,14 @@
|
||||
reagent: Ultravasculine
|
||||
amount: 0.5
|
||||
|
||||
- type: reagent
|
||||
id: Oculine
|
||||
name: reagent-name-oculine
|
||||
desc: reagent-desc-oculine
|
||||
group: Medicine
|
||||
physicalDesc: reagent-physical-desc-translucent
|
||||
color: "#404040"
|
||||
metabolisms:
|
||||
Medicine:
|
||||
effects:
|
||||
- !type:ChemHealEyeDamage
|
||||
|
||||
@@ -323,6 +323,20 @@
|
||||
products:
|
||||
Ultravasculine: 2
|
||||
|
||||
- type: reaction
|
||||
id: Oculine
|
||||
reactants:
|
||||
TableSalt:
|
||||
amount: 1
|
||||
Blood:
|
||||
amount: 1
|
||||
Carbon:
|
||||
amount: 1
|
||||
Hydrogen:
|
||||
amount: 1
|
||||
products:
|
||||
Oculine: 4
|
||||
|
||||
- type: reaction
|
||||
id: Siderlac
|
||||
reactants:
|
||||
|
||||
@@ -55,3 +55,13 @@
|
||||
pixelSize: 32, 32
|
||||
alphaCutoff: 0
|
||||
removeTransparency: false
|
||||
|
||||
- type: shader
|
||||
id: BlurryVisionX
|
||||
kind: source
|
||||
path: "/Textures/Shaders/blurryx.swsl"
|
||||
|
||||
- type: shader
|
||||
id: BlurryVisionY
|
||||
kind: source
|
||||
path: "/Textures/Shaders/blurryy.swsl"
|
||||
|
||||
@@ -48,3 +48,5 @@
|
||||
- type: statusEffect
|
||||
id: ForcedSleep #I.e., they will not wake on damage or similar
|
||||
|
||||
- type: statusEffect
|
||||
id: TemporaryBlindness
|
||||
|
||||
16
Resources/Textures/Shaders/blurryx.swsl
Normal file
16
Resources/Textures/Shaders/blurryx.swsl
Normal file
@@ -0,0 +1,16 @@
|
||||
uniform sampler2D SCREEN_TEXTURE;
|
||||
uniform highp float BLUR_AMOUNT;
|
||||
|
||||
void fragment() {
|
||||
highp vec3 col = texture(SCREEN_TEXTURE, UV).xyz * BLUR_AMOUNT;
|
||||
highp vec4 color = zTexture(UV);
|
||||
col += texture(SCREEN_TEXTURE, UV + vec2(SCREEN_PIXEL_SIZE.x, 0.0)).xyz * 0.15;
|
||||
col += texture(SCREEN_TEXTURE, UV + vec2(-SCREEN_PIXEL_SIZE.x, 0.0)).xyz * 0.15;
|
||||
col += texture(SCREEN_TEXTURE, UV + vec2(2.0 * SCREEN_PIXEL_SIZE.x, 0.0)).xyz * 0.12;
|
||||
col += texture(SCREEN_TEXTURE, UV + vec2(2.0 * -SCREEN_PIXEL_SIZE.x, 0.0)).xyz * 0.12;
|
||||
col += texture(SCREEN_TEXTURE, UV + vec2(3.0 * SCREEN_PIXEL_SIZE.x, 0.0)).xyz * 0.09;
|
||||
col += texture(SCREEN_TEXTURE, UV + vec2(3.0 * -SCREEN_PIXEL_SIZE.x, 0.0)).xyz * 0.09;
|
||||
col += texture(SCREEN_TEXTURE, UV + vec2(4.0 * SCREEN_PIXEL_SIZE.x, 0.0)).xyz * 0.05;
|
||||
col += texture(SCREEN_TEXTURE, UV + vec2(4.0 * -SCREEN_PIXEL_SIZE.x, 0.0)).xyz * 0.05;
|
||||
COLOR = vec4(vec3(col), color.a);
|
||||
}
|
||||
16
Resources/Textures/Shaders/blurryy.swsl
Normal file
16
Resources/Textures/Shaders/blurryy.swsl
Normal file
@@ -0,0 +1,16 @@
|
||||
uniform sampler2D SCREEN_TEXTURE;
|
||||
uniform highp float BLUR_AMOUNT;
|
||||
|
||||
void fragment() {
|
||||
highp vec3 col = texture(SCREEN_TEXTURE, UV).xyz * BLUR_AMOUNT;
|
||||
highp vec4 color = zTexture(UV);
|
||||
col += texture(SCREEN_TEXTURE, UV + vec2(0.0, SCREEN_PIXEL_SIZE.y)).xyz * 0.15;
|
||||
col += texture(SCREEN_TEXTURE, UV + vec2(0.0, -SCREEN_PIXEL_SIZE.y)).xyz * 0.15;
|
||||
col += texture(SCREEN_TEXTURE, UV + vec2(0.0, 2.0 * SCREEN_PIXEL_SIZE.y)).xyz * 0.12;
|
||||
col += texture(SCREEN_TEXTURE, UV + vec2(0.0, 2.0 * -SCREEN_PIXEL_SIZE.y)).xyz * 0.12;
|
||||
col += texture(SCREEN_TEXTURE, UV + vec2(0.0, 3.0 * SCREEN_PIXEL_SIZE.y)).xyz * 0.09;
|
||||
col += texture(SCREEN_TEXTURE, UV + vec2(0.0, 3.0 * -SCREEN_PIXEL_SIZE.y)).xyz * 0.09;
|
||||
col += texture(SCREEN_TEXTURE, UV + vec2(0.0, 4.0 * SCREEN_PIXEL_SIZE.y)).xyz * 0.05;
|
||||
col += texture(SCREEN_TEXTURE, UV + vec2(0.0, 4.0 * -SCREEN_PIXEL_SIZE.y)).xyz * 0.05;
|
||||
COLOR = vec4(vec3(col), color.a);
|
||||
}
|
||||
Reference in New Issue
Block a user