diff --git a/Content.Client/Entry/EntryPoint.cs b/Content.Client/Entry/EntryPoint.cs index 066eafb053..e91874432f 100644 --- a/Content.Client/Entry/EntryPoint.cs +++ b/Content.Client/Entry/EntryPoint.cs @@ -5,6 +5,7 @@ using Content.Client.CharacterInterface; using Content.Client.Chat.Managers; using Content.Client.EscapeMenu; using Content.Client.Eui; +using Content.Client.Eye.Blinding; using Content.Client.Flash; using Content.Client.GhostKick; using Content.Client.HUD; diff --git a/Content.Client/Eye/Blinding/BlindOverlay.cs b/Content.Client/Eye/Blinding/BlindOverlay.cs new file mode 100644 index 0000000000..a1a9b5c86c --- /dev/null +++ b/Content.Client/Eye/Blinding/BlindOverlay.cs @@ -0,0 +1,80 @@ +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 BlindOverlay : Overlay + { + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + [Dependency] private readonly IPlayerManager _playerManager = default!; + [Dependency] IEntityManager _entityManager = default!; + [Dependency] ILightManager _lightManager = default!; + + + public override bool RequestScreenTexture => true; + public override OverlaySpace Space => OverlaySpace.WorldSpace; + private readonly ShaderInstance _greyscaleShader; + private readonly ShaderInstance _circleMaskShader; + + private BlindableComponent _blindableComponent = default!; + + public BlindOverlay() + { + IoCManager.InjectDependencies(this); + _greyscaleShader = _prototypeManager.Index("GreyscaleFullscreen").InstanceUnique(); + _circleMaskShader = _prototypeManager.Index("CircleMask").InstanceUnique(); + } + protected override bool BeforeDraw(in OverlayDrawArgs args) + { + var playerEntity = _playerManager.LocalPlayer?.ControlledEntity; + + if (playerEntity == null) + return false; + + if (!_entityManager.TryGetComponent(playerEntity, out var blindComp)) + return false; + + _blindableComponent = blindComp; + + var blind = _blindableComponent.Sources > 0; + + if (!blind && _blindableComponent.LightSetup) // Turn FOV back on if we can see again + { + _lightManager.Enabled = true; + _blindableComponent.LightSetup = false; + _blindableComponent.GraceFrame = true; + return true; + } + + return blind; + } + + protected override void Draw(in OverlayDrawArgs args) + { + if (ScreenTexture == null) + return; + + if (!_blindableComponent.GraceFrame) + { + _blindableComponent.LightSetup = true; // Ok we touched the lights + _lightManager.Enabled = false; + } else + { + _blindableComponent.GraceFrame = false; + } + + _greyscaleShader?.SetParameter("SCREEN_TEXTURE", ScreenTexture); + + var worldHandle = args.WorldHandle; + var viewport = args.WorldBounds; + worldHandle.SetTransform(Matrix3.Identity); + worldHandle.UseShader(_greyscaleShader); + worldHandle.DrawRect(viewport, Color.White); + worldHandle.UseShader(_circleMaskShader); + worldHandle.DrawRect(viewport, Color.White); + } + } +} diff --git a/Content.Client/Eye/Blinding/BlindingSystem.cs b/Content.Client/Eye/Blinding/BlindingSystem.cs new file mode 100644 index 0000000000..842286006b --- /dev/null +++ b/Content.Client/Eye/Blinding/BlindingSystem.cs @@ -0,0 +1,55 @@ + +using Content.Shared.Eye.Blinding; +using Robust.Client.GameObjects; +using Robust.Client.Graphics; +using Robust.Client.Player; + +namespace Content.Client.Eye.Blinding; + +public sealed class BlindingSystem : EntitySystem +{ + [Dependency] private readonly IPlayerManager _player = default!; + [Dependency] private readonly IOverlayManager _overlayMan = default!; + [Dependency] ILightManager _lightManager = default!; + + + private BlindOverlay _overlay = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnBlindInit); + SubscribeLocalEvent(OnBlindShutdown); + + SubscribeLocalEvent(OnPlayerAttached); + SubscribeLocalEvent(OnPlayerDetached); + + _overlay = new(); + } + + private void OnPlayerAttached(EntityUid uid, BlindableComponent component, PlayerAttachedEvent args) + { + _overlayMan.AddOverlay(_overlay); + } + + private void OnPlayerDetached(EntityUid uid, BlindableComponent component, PlayerDetachedEvent args) + { + _overlayMan.RemoveOverlay(_overlay); + _lightManager.Enabled = true; + } + + private void OnBlindInit(EntityUid uid, BlindableComponent component, ComponentInit args) + { + if (_player.LocalPlayer?.ControlledEntity == uid) + _overlayMan.AddOverlay(_overlay); + } + + private void OnBlindShutdown(EntityUid uid, BlindableComponent component, ComponentShutdown args) + { + if (_player.LocalPlayer?.ControlledEntity == uid) + { + _overlayMan.RemoveOverlay(_overlay); + } + } +} diff --git a/Content.Shared/Examine/ExamineSystemShared.cs b/Content.Shared/Examine/ExamineSystemShared.cs index 78d8662344..48f98da212 100644 --- a/Content.Shared/Examine/ExamineSystemShared.cs +++ b/Content.Shared/Examine/ExamineSystemShared.cs @@ -2,6 +2,7 @@ using System.Linq; using Content.Shared.DragDrop; using Content.Shared.Interaction; using Content.Shared.MobState.Components; +using Content.Shared.Eye.Blinding; using Content.Shared.MobState.EntitySystems; using JetBrains.Annotations; using Robust.Shared.Containers; @@ -98,7 +99,7 @@ namespace Content.Shared.Examine { if (MobStateSystem.IsDead(examiner, mobState)) return DeadExamineRange; - else if (MobStateSystem.IsCritical(examiner, mobState)) + else if (MobStateSystem.IsCritical(examiner, mobState) || (TryComp(examiner, out var blind) && blind.Sources > 0)) return CritExamineRange; } return ExamineRange; diff --git a/Content.Shared/Eye/Blinding/BlindableComponent.cs b/Content.Shared/Eye/Blinding/BlindableComponent.cs new file mode 100644 index 0000000000..01a5dc64bd --- /dev/null +++ b/Content.Shared/Eye/Blinding/BlindableComponent.cs @@ -0,0 +1,26 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Eye.Blinding +{ + [RegisterComponent] + [NetworkedComponent] + public sealed class BlindableComponent : Component + { + /// + /// How many sources of blindness are affecting us? + /// + [DataField("sources")] + public int Sources = 0; + + /// + /// Used to ensure that this doesn't break with sandbox or admin tools. + /// This is not "enabled/disabled". + /// + public bool LightSetup = false; + + /// + /// Gives an extra frame of blindness to reenable light manager during + /// + public bool GraceFrame = false; + } +} diff --git a/Content.Shared/Eye/Blinding/BlindfoldComponent.cs b/Content.Shared/Eye/Blinding/BlindfoldComponent.cs new file mode 100644 index 0000000000..d1bdce423a --- /dev/null +++ b/Content.Shared/Eye/Blinding/BlindfoldComponent.cs @@ -0,0 +1,12 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Eye.Blinding +{ + [RegisterComponent] + [NetworkedComponent] + public sealed class BlindfoldComponent : Component + { + [ViewVariables] + public bool IsActive = false; + } +} diff --git a/Content.Shared/Eye/Blinding/SharedBlindingSystem.cs b/Content.Shared/Eye/Blinding/SharedBlindingSystem.cs new file mode 100644 index 0000000000..70a26f8a22 --- /dev/null +++ b/Content.Shared/Eye/Blinding/SharedBlindingSystem.cs @@ -0,0 +1,40 @@ +using Content.Shared.Inventory.Events; +using Content.Shared.Inventory; +using Content.Shared.Item; + +namespace Content.Shared.Eye.Blinding +{ + public sealed class SharedBlindingSystem : EntitySystem + { + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnEquipped); + SubscribeLocalEvent(OnUnequipped); + } + + private void OnEquipped(EntityUid uid, BlindfoldComponent component, GotEquippedEvent args) + { + if (!TryComp(uid, out var clothing) || clothing.SlotFlags == SlotFlags.PREVENTEQUIP) // we live in a society + return; + // Is the clothing in its actual slot? + if (!clothing.SlotFlags.HasFlag(args.SlotFlags)) + return; + + component.IsActive = true; + if (!TryComp(args.Equipee, out var blindComp)) + return; + blindComp.Sources++; + } + + private void OnUnequipped(EntityUid uid, BlindfoldComponent component, GotUnequippedEvent args) + { + if (!component.IsActive) + return; + component.IsActive = false; + if (!TryComp(args.Equipee, out var blindComp)) + return; + blindComp.Sources--; + } + } +} diff --git a/Resources/Prototypes/Catalog/Fills/Crates/salvage.yml b/Resources/Prototypes/Catalog/Fills/Crates/salvage.yml index 5c89172ae1..ca6dbd7467 100644 --- a/Resources/Prototypes/Catalog/Fills/Crates/salvage.yml +++ b/Resources/Prototypes/Catalog/Fills/Crates/salvage.yml @@ -74,6 +74,8 @@ prob: 0.001 - id: WeaponRevolverInspector prob: 0.001 + - id: ClothingShoesBootsMagBlinding + prob: 0.001 # - Skub - id: Skub prob: 0.001 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Inventories/secdrobe.yml b/Resources/Prototypes/Catalog/VendingMachines/Inventories/secdrobe.yml index fd9ec3d703..9bd0ca4d57 100644 --- a/Resources/Prototypes/Catalog/VendingMachines/Inventories/secdrobe.yml +++ b/Resources/Prototypes/Catalog/VendingMachines/Inventories/secdrobe.yml @@ -17,3 +17,4 @@ ClothingUniformJumpsuitSecBlue: 3 ClothingHeadsetSecurity: 3 ClothingOuterWinterSec: 2 + ClothingEyesBlindfold: 1 diff --git a/Resources/Prototypes/Catalog/VendingMachines/Inventories/theater.yml b/Resources/Prototypes/Catalog/VendingMachines/Inventories/theater.yml index 4cb7dd5f44..2a15090326 100644 --- a/Resources/Prototypes/Catalog/VendingMachines/Inventories/theater.yml +++ b/Resources/Prototypes/Catalog/VendingMachines/Inventories/theater.yml @@ -26,5 +26,6 @@ Gohei: 2 ClothingHeadPaperSack: 2 ClothingHeadPaperSackSmile: 2 + ClothingEyesBlindfold: 1 emaggedInventory: ClothingShoesBling: 1 diff --git a/Resources/Prototypes/Entities/Clothing/Eyes/misc.yml b/Resources/Prototypes/Entities/Clothing/Eyes/misc.yml index 2d0634d589..ec91b8cadd 100644 --- a/Resources/Prototypes/Entities/Clothing/Eyes/misc.yml +++ b/Resources/Prototypes/Entities/Clothing/Eyes/misc.yml @@ -8,3 +8,18 @@ sprite: Clothing/Eyes/Misc/eyepatch.rsi - type: Clothing sprite: Clothing/Eyes/Misc/eyepatch.rsi + +- type: entity + parent: ClothingEyesBase + id: ClothingEyesBlindfold + name: blindfold + description: The bind leading the blind. + components: + - type: Sprite + sprite: Clothing/Eyes/Misc/blindfold.rsi + - type: Clothing + sprite: Clothing/Eyes/Misc/blindfold.rsi + - type: Blindfold + - type: Construction + graph: Blindfold + node: blindfold diff --git a/Resources/Prototypes/Entities/Clothing/Shoes/magboots.yml b/Resources/Prototypes/Entities/Clothing/Shoes/magboots.yml index ab567ccd99..a327fc4712 100644 --- a/Resources/Prototypes/Entities/Clothing/Shoes/magboots.yml +++ b/Resources/Prototypes/Entities/Clothing/Shoes/magboots.yml @@ -49,6 +49,20 @@ - type: StaticPrice price: 750 +- type: entity + parent: ClothingShoesBootsMag + id: ClothingShoesBootsMagBlinding + name: magboots of blinding speed + description: These would look fetching on a fetcher like you. + components: + - type: ClothingSpeedModifier + walkModifier: 2.5 #PVS isn't too much of an issue when you are blind... + sprintModifier: 2.5 + enabled: false + - type: StaticPrice + price: 3000 + - type: Blindfold + - type: entity parent: ClothingShoesBase id: ClothingShoesBootsMagSyndie diff --git a/Resources/Prototypes/Entities/Markers/Spawners/Random/maintenance.yml b/Resources/Prototypes/Entities/Markers/Spawners/Random/maintenance.yml index a221cf0f66..f07b342aaf 100644 --- a/Resources/Prototypes/Entities/Markers/Spawners/Random/maintenance.yml +++ b/Resources/Prototypes/Entities/Markers/Spawners/Random/maintenance.yml @@ -103,5 +103,6 @@ - WeaponFlareGun - Spear - LidSalami + - ClothingEyesBlindfold chance: 0.6 offset: 0.0 diff --git a/Resources/Prototypes/Entities/Mobs/Species/human.yml b/Resources/Prototypes/Entities/Mobs/Species/human.yml index dc6ca2b16c..6ba8964a9e 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/human.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/human.yml @@ -82,6 +82,7 @@ - PressureImmunity - Muted - type: DiseaseCarrier + - type: Blindable # Other - type: Inventory - type: Clickable diff --git a/Resources/Prototypes/Recipes/Crafting/Graphs/improvised/blindfold.yml b/Resources/Prototypes/Recipes/Crafting/Graphs/improvised/blindfold.yml new file mode 100644 index 0000000000..0b02d32530 --- /dev/null +++ b/Resources/Prototypes/Recipes/Crafting/Graphs/improvised/blindfold.yml @@ -0,0 +1,13 @@ +- type: constructionGraph + id: Blindfold + start: start + graph: + - node: start + edges: + - to: blindfold + steps: + - material: Cloth + amount: 3 + doAfter: 10 + - node: blindfold + entity: ClothingEyesBlindfold diff --git a/Resources/Prototypes/Recipes/Crafting/improvised.yml b/Resources/Prototypes/Recipes/Crafting/improvised.yml index 4116442fa6..322d610662 100644 --- a/Resources/Prototypes/Recipes/Crafting/improvised.yml +++ b/Resources/Prototypes/Recipes/Crafting/improvised.yml @@ -47,3 +47,16 @@ icon: sprite: Objects/Specific/Medical/medical.rsi state: gauze + +- type: construction + name: blindfold + id: blindfold + graph: Blindfold + startNode: start + targetNode: blindfold + category: Tools + objectType: Item + description: Better hope everyone turns a blind eye to you crafting this sussy item... + icon: + sprite: Clothing/Eyes/Misc/blindfold.rsi + state: icon diff --git a/Resources/Prototypes/Shaders/greyscale.yml b/Resources/Prototypes/Shaders/greyscale.yml index 176893ab89..290f5ead5a 100644 --- a/Resources/Prototypes/Shaders/greyscale.yml +++ b/Resources/Prototypes/Shaders/greyscale.yml @@ -2,3 +2,8 @@ id: Greyscale kind: source path: "/Textures/Shaders/greyscale.swsl" + +- type: shader + id: GreyscaleFullscreen + kind: source + path: "/Textures/Shaders/greyscale_fullscreen.swsl" diff --git a/Resources/Textures/Clothing/Eyes/Misc/blindfold.rsi/equipped-EYES.png b/Resources/Textures/Clothing/Eyes/Misc/blindfold.rsi/equipped-EYES.png new file mode 100644 index 0000000000..36b670c4a3 Binary files /dev/null and b/Resources/Textures/Clothing/Eyes/Misc/blindfold.rsi/equipped-EYES.png differ diff --git a/Resources/Textures/Clothing/Eyes/Misc/blindfold.rsi/icon.png b/Resources/Textures/Clothing/Eyes/Misc/blindfold.rsi/icon.png new file mode 100644 index 0000000000..1be79ed80a Binary files /dev/null and b/Resources/Textures/Clothing/Eyes/Misc/blindfold.rsi/icon.png differ diff --git a/Resources/Textures/Clothing/Eyes/Misc/blindfold.rsi/meta.json b/Resources/Textures/Clothing/Eyes/Misc/blindfold.rsi/meta.json new file mode 100644 index 0000000000..a89a8a1240 --- /dev/null +++ b/Resources/Textures/Clothing/Eyes/Misc/blindfold.rsi/meta.json @@ -0,0 +1,18 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from tgstation at https://github.com/tgstation/tgstation/commit/40d89d11ea4a5cb81d61dc1018b46f4e7d32c62a", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "icon" + }, + { + "name": "equipped-EYES", + "directions": 4 + } + ] +} diff --git a/Resources/Textures/Shaders/greyscale_fullscreen.swsl b/Resources/Textures/Shaders/greyscale_fullscreen.swsl new file mode 100644 index 0000000000..40c0f72b59 --- /dev/null +++ b/Resources/Textures/Shaders/greyscale_fullscreen.swsl @@ -0,0 +1,9 @@ +uniform sampler2D SCREEN_TEXTURE; + +void fragment() { + highp vec4 color = zTextureSpec(SCREEN_TEXTURE, UV); + + highp float grey = dot(color.rgb, vec3(0.299, 0.587, 0.114)); + + COLOR = vec4(vec3(grey), color.a); +}