diff --git a/Content.Client/_CorvaxNext/Overlays/BaseSwitchableOverlay.cs b/Content.Client/_CorvaxNext/Overlays/BaseSwitchableOverlay.cs new file mode 100644 index 00000000000..5910ae4a836 --- /dev/null +++ b/Content.Client/_CorvaxNext/Overlays/BaseSwitchableOverlay.cs @@ -0,0 +1,47 @@ +using Content.Shared._CorvaxNext.Overlays; +using Robust.Client.Graphics; +using Robust.Client.Player; +using Robust.Shared.Enums; +using Robust.Shared.Prototypes; +using System.Numerics; + +namespace Content.Client._CorvaxNext.Overlays; + +public class BaseSwitchableOverlay : Overlay + where TComp : SwitchableOverlayComponent +{ + [Dependency] private readonly IPrototypeManager _prototype = default!; + [Dependency] private readonly IPlayerManager _player = default!; + [Dependency] private readonly IEntityManager _entity = default!; + + public override bool RequestScreenTexture => true; + public override OverlaySpace Space => OverlaySpace.WorldSpace; + private readonly ShaderInstance _shader; + + public BaseSwitchableOverlay() + { + IoCManager.InjectDependencies(this); + _shader = _prototype.Index("NightVision").Instance().Duplicate(); + } + + protected override void Draw(in OverlayDrawArgs args) + { + if (ScreenTexture is null + || _player.LocalEntity is null + || !_entity.TryGetComponent(_player.LocalEntity.Value, out var component) + || !component.IsActive) + return; + + _shader.SetParameter("SCREEN_TEXTURE", ScreenTexture); + _shader.SetParameter("tint", component.Tint); + _shader.SetParameter("luminance_threshold", component.Strength); + _shader.SetParameter("noise_amount", component.Noise); + + var worldHandle = args.WorldHandle; + + worldHandle.SetTransform(Matrix3x2.Identity); + worldHandle.UseShader(_shader); + worldHandle.DrawRect(args.WorldBounds, component.Color); + worldHandle.UseShader(null); + } +} diff --git a/Content.Client/_CorvaxNext/Overlays/NightVisionSystem.cs b/Content.Client/_CorvaxNext/Overlays/NightVisionSystem.cs new file mode 100644 index 00000000000..1b8909a1540 --- /dev/null +++ b/Content.Client/_CorvaxNext/Overlays/NightVisionSystem.cs @@ -0,0 +1,85 @@ +using Content.Shared._CorvaxNext.Overlays; +using Content.Shared.GameTicking; +using Robust.Client.Graphics; +using Robust.Client.Player; +using Robust.Shared.Player; + +namespace Content.Client._CorvaxNext.Overlays; + +public sealed class NightVisionSystem : SwitchableOverlaySystem +{ + [Dependency] private readonly IPlayerManager _player = default!; + [Dependency] private readonly IOverlayManager _overlayMan = default!; + [Dependency] private readonly ILightManager _lightManager = default!; + + private BaseSwitchableOverlay _overlay = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnPlayerAttached); + SubscribeLocalEvent(OnPlayerDetached); + SubscribeLocalEvent(OnRestart); + + _overlay = new BaseSwitchableOverlay(); + } + + private void OnPlayerAttached(EntityUid uid, NightVisionComponent component, PlayerAttachedEvent args) + { + if (!component.IsActive) + return; + + UpdateVision(args.Player, component.IsActive); + } + + private void OnPlayerDetached(EntityUid uid, NightVisionComponent component, PlayerDetachedEvent args) + { + UpdateVision(args.Player, false); + } + + private void OnRestart(RoundRestartCleanupEvent ev) + { + _overlayMan.RemoveOverlay(_overlay); + _lightManager.DrawLighting = true; + } + + protected override void UpdateVision(EntityUid uid, bool active) + { + if (_player.LocalSession?.AttachedEntity != uid) + return; + + UpdateOverlay(active); + UpdateNightVision(active); + } + + private void UpdateVision(ICommonSession player, bool active) + { + if (_player.LocalSession != player) + return; + + UpdateOverlay(active); + UpdateNightVision(active); + } + + private void UpdateNightVision(bool active) + { + _lightManager.DrawLighting = !active; + } + + private void UpdateOverlay(bool active) + { + if (_player.LocalEntity == null) + { + _overlayMan.RemoveOverlay(_overlay); + return; + } + + active |= TryComp(_player.LocalEntity.Value, out var component) && component.IsActive; + + if (active) + _overlayMan.AddOverlay(_overlay); + else + _overlayMan.RemoveOverlay(_overlay); + } +} diff --git a/Content.Client/_CorvaxNext/Overlays/ThermalVisionOverlay.cs b/Content.Client/_CorvaxNext/Overlays/ThermalVisionOverlay.cs new file mode 100644 index 00000000000..6d7018a73b5 --- /dev/null +++ b/Content.Client/_CorvaxNext/Overlays/ThermalVisionOverlay.cs @@ -0,0 +1,130 @@ +using System.Linq; +using System.Numerics; +using Content.Client.Stealth; +using Content.Shared._CorvaxNext.Overlays; +using Content.Shared.Body.Components; +using Content.Shared.Stealth.Components; +using Robust.Client.GameObjects; +using Robust.Client.Graphics; +using Robust.Client.Player; +using Robust.Shared.Enums; +using Robust.Shared.Map; + +namespace Content.Client._CorvaxNext.Overlays; + +public sealed class ThermalVisionOverlay : Overlay +{ + [Dependency] private readonly IEntityManager _entity = default!; + [Dependency] private readonly IPlayerManager _players = default!; + + private readonly TransformSystem _transform; + private readonly OccluderSystem _occluder; + private readonly StealthSystem _stealth; + private readonly ContainerSystem _container; + + public override bool RequestScreenTexture => true; + public override OverlaySpace Space => OverlaySpace.WorldSpace; + + private readonly List _entries = []; + + public ThermalVisionOverlay() + { + IoCManager.InjectDependencies(this); + + _container = _entity.System(); + _transform = _entity.System(); + _occluder = _entity.System(); + _stealth = _entity.System(); + + ZIndex = -1; + } + + protected override void Draw(in OverlayDrawArgs args) + { + if (ScreenTexture is null + || _players.LocalEntity is null + || !_entity.TryGetComponent(_players.LocalEntity.Value, out var component) + || !component.IsActive) + return; + + var worldHandle = args.WorldHandle; + var eye = args.Viewport.Eye; + + if (eye is null) + return; + + var mapId = eye.Position.MapId; + var eyeRot = eye.Rotation; + + _entries.Clear(); + var entities = _entity.EntityQueryEnumerator(); + while (entities.MoveNext(out var uid, out var body, out var sprite, out var xform)) + { + if (!CanSee(uid, sprite, body)) + continue; + + var entity = uid; + + if (_container.TryGetOuterContainer(uid, xform, out var container)) + { + var owner = container.Owner; + if (_entity.TryGetComponent(owner, out var ownerSprite) + && _entity.TryGetComponent(owner, out var ownerXform)) + { + entity = owner; + sprite = ownerSprite; + xform = ownerXform; + } + } + + if (_entries.Any(e => e.Ent.Item1 == entity)) + continue; + + _entries.Add(new ThermalVisionRenderEntry((entity, sprite, xform, body), mapId, eyeRot)); + } + + foreach (var entry in _entries) + { + Render(entry.Ent, entry.Map, worldHandle, entry.EyeRot); + } + + worldHandle.SetTransform(Matrix3x2.Identity); + } + + private void Render(Entity ent, + MapId? map, + DrawingHandleWorld handle, + Angle eyeRot) + { + var (uid, sprite, xform, body) = ent; + if (xform.MapID != map || HasOccluders(uid) || !CanSee(uid, sprite, body)) + return; + + var position = _transform.GetWorldPosition(xform); + var rotation = _transform.GetWorldRotation(xform); + + sprite.Render(handle, eyeRot, rotation, position: position); + } + + private bool CanSee(EntityUid uid, SpriteComponent sprite, BodyComponent body) + { + return sprite.Visible + && !_entity.HasComponent(uid) + && (!_entity.TryGetComponent(uid, out StealthComponent? stealth) + || _stealth.GetVisibility(uid, stealth) > 0.5f); + } + + private bool HasOccluders(EntityUid uid) + { + var mapCoordinates = _transform.GetMapCoordinates(uid); + var occluders = _occluder.QueryAabb(mapCoordinates.MapId, + Box2.CenteredAround(mapCoordinates.Position, new Vector2(0.3f, 0.3f))); + + return occluders.Any(o => o.Component.Enabled); + } +} + +public record struct ThermalVisionRenderEntry( + (EntityUid, SpriteComponent, TransformComponent, BodyComponent) Ent, + MapId? Map, + Angle EyeRot); diff --git a/Content.Client/_CorvaxNext/Overlays/ThermalVisionSystem.cs b/Content.Client/_CorvaxNext/Overlays/ThermalVisionSystem.cs new file mode 100644 index 00000000000..ce3713c4be3 --- /dev/null +++ b/Content.Client/_CorvaxNext/Overlays/ThermalVisionSystem.cs @@ -0,0 +1,81 @@ +using Content.Shared._CorvaxNext.Overlays; +using Content.Shared.GameTicking; +using Robust.Client.Graphics; +using Robust.Client.Player; +using Robust.Shared.Player; + +namespace Content.Client._CorvaxNext.Overlays; + +public sealed class ThermalVisionSystem : SwitchableOverlaySystem +{ + [Dependency] private readonly IPlayerManager _player = default!; + [Dependency] private readonly IOverlayManager _overlayMan = default!; + + private ThermalVisionOverlay _thermalOverlay = default!; + private BaseSwitchableOverlay _overlay = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnPlayerAttached); + SubscribeLocalEvent(OnPlayerDetached); + SubscribeLocalEvent(OnRestart); + + _thermalOverlay = new ThermalVisionOverlay(); + _overlay = new BaseSwitchableOverlay(); + } + + private void OnPlayerAttached(EntityUid uid, ThermalVisionComponent component, PlayerAttachedEvent args) + { + if (!component.IsActive) + return; + + UpdateVision(args.Player, component.IsActive); + } + + private void OnPlayerDetached(EntityUid uid, ThermalVisionComponent component, PlayerDetachedEvent args) + { + UpdateVision(args.Player, false); + } + + private void OnRestart(RoundRestartCleanupEvent ev) + { + _overlayMan.RemoveOverlay(_thermalOverlay); + _overlayMan.RemoveOverlay(_overlay); + } + + protected override void UpdateVision(EntityUid uid, bool active) + { + if (_player.LocalSession?.AttachedEntity != uid) + return; + + UpdateOverlay(active, _thermalOverlay); + UpdateOverlay(active, _overlay); + } + + private void UpdateVision(ICommonSession player, bool active) + { + if (_player.LocalSession != player) + return; + + UpdateOverlay(active, _thermalOverlay); + UpdateOverlay(active, _overlay); + } + + private void UpdateOverlay(bool active, Overlay overlay) + { + if (_player.LocalEntity == null) + { + _overlayMan.RemoveOverlay(overlay); + return; + } + + active |= TryComp(_player.LocalEntity.Value, out var component) && component.IsActive; + + if (active) + _overlayMan.AddOverlay(overlay); + else + _overlayMan.RemoveOverlay(overlay); + } +} diff --git a/Content.Server/_CorvaxNext/Overlays/NightVisionSystem.cs b/Content.Server/_CorvaxNext/Overlays/NightVisionSystem.cs new file mode 100644 index 00000000000..b38deb2d0b7 --- /dev/null +++ b/Content.Server/_CorvaxNext/Overlays/NightVisionSystem.cs @@ -0,0 +1,5 @@ +using Content.Shared._CorvaxNext.Overlays; + +namespace Content.Server._CorvaxNext.Overlays; + +public sealed class NightVisionSystem : SwitchableOverlaySystem; diff --git a/Content.Server/_CorvaxNext/Overlays/ThermalVisionSystem.cs b/Content.Server/_CorvaxNext/Overlays/ThermalVisionSystem.cs new file mode 100644 index 00000000000..6cffd8314d9 --- /dev/null +++ b/Content.Server/_CorvaxNext/Overlays/ThermalVisionSystem.cs @@ -0,0 +1,5 @@ +using Content.Shared._CorvaxNext.Overlays; + +namespace Content.Server._CorvaxNext.Overlays; + +public sealed class ThermalVisionSystem : SwitchableOverlaySystem; diff --git a/Content.Shared/_CorvaxNext/Clothing/Components/ClothingGrantComponentComponent.cs b/Content.Shared/_CorvaxNext/Clothing/Components/ClothingGrantComponentComponent.cs new file mode 100644 index 00000000000..ff2e2a2cbca --- /dev/null +++ b/Content.Shared/_CorvaxNext/Clothing/Components/ClothingGrantComponentComponent.cs @@ -0,0 +1,14 @@ +using Robust.Shared.Prototypes; + +namespace Content.Shared._CorvaxNext.Clothing +{ + [RegisterComponent] + public sealed partial class ClothingGrantComponentComponent : Component + { + [DataField("component")] + [AlwaysPushInheritance] + public ComponentRegistry Components { get; private set; } = new(); + + public bool IsActive = false; + } +} diff --git a/Content.Shared/_CorvaxNext/Clothing/Components/ClothingGrantTagComponent.cs b/Content.Shared/_CorvaxNext/Clothing/Components/ClothingGrantTagComponent.cs new file mode 100644 index 00000000000..27190765295 --- /dev/null +++ b/Content.Shared/_CorvaxNext/Clothing/Components/ClothingGrantTagComponent.cs @@ -0,0 +1,10 @@ +namespace Content.Shared._CorvaxNext.Clothing; + +[RegisterComponent] +public sealed partial class ClothingGrantTagComponent : Component +{ + [DataField("tag")] + public string Tag = ""; + + public bool IsActive = false; +} diff --git a/Content.Shared/_CorvaxNext/Clothing/Systems/ClothingGrantingSystem.cs b/Content.Shared/_CorvaxNext/Clothing/Systems/ClothingGrantingSystem.cs new file mode 100644 index 00000000000..97044a34030 --- /dev/null +++ b/Content.Shared/_CorvaxNext/Clothing/Systems/ClothingGrantingSystem.cs @@ -0,0 +1,92 @@ +using Content.Shared.Clothing.Components; +using Content.Shared.Inventory.Events; +using Robust.Shared.Serialization.Manager; +using Content.Shared.Tag; + +namespace Content.Shared._CorvaxNext.Clothing; + +public sealed class ClothingGrantingSystem : EntitySystem +{ + [Dependency] private readonly IComponentFactory _componentFactory = default!; + [Dependency] private readonly ISerializationManager _serializationManager = default!; + [Dependency] private readonly TagSystem _tagSystem = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnCompEquip); + SubscribeLocalEvent(OnCompUnequip); + + SubscribeLocalEvent(OnTagEquip); + SubscribeLocalEvent(OnTagUnequip); + } + + private void OnCompEquip(EntityUid uid, ClothingGrantComponentComponent component, GotEquippedEvent args) + { + if (!TryComp(uid, out var clothing)) return; + + if (!clothing.Slots.HasFlag(args.SlotFlags)) return; + + if (component.Components.Count > 1) + { + Logger.Error("Although a component registry supports multiple components, we cannot bookkeep more than 1 component for ClothingGrantComponent at this time."); + return; + } + + foreach (var (name, data) in component.Components) + { + var newComp = (Component) _componentFactory.GetComponent(name); + + if (HasComp(args.Equipee, newComp.GetType())) + continue; + + newComp.Owner = args.Equipee; + + var temp = (object) newComp; + _serializationManager.CopyTo(data.Component, ref temp); + EntityManager.AddComponent(args.Equipee, (Component)temp!); + + component.IsActive = true; + } + } + + private void OnCompUnequip(EntityUid uid, ClothingGrantComponentComponent component, GotUnequippedEvent args) + { + if (!component.IsActive) return; + + foreach (var (name, data) in component.Components) + { + var newComp = (Component) _componentFactory.GetComponent(name); + + RemComp(args.Equipee, newComp.GetType()); + } + + component.IsActive = false; + } + + + private void OnTagEquip(EntityUid uid, ClothingGrantTagComponent component, GotEquippedEvent args) + { + if (!TryComp(uid, out var clothing)) + return; + + if (!clothing.Slots.HasFlag(args.SlotFlags)) + return; + + EnsureComp(args.Equipee); + _tagSystem.AddTag(args.Equipee, component.Tag); + + component.IsActive = true; + } + + private void OnTagUnequip(EntityUid uid, ClothingGrantTagComponent component, GotUnequippedEvent args) + { + if (!component.IsActive) + return; + + _tagSystem.RemoveTag(args.Equipee, component.Tag); + + component.IsActive = false; + } +} diff --git a/Content.Shared/_CorvaxNext/Overlays/BaseOverlayComponent.cs b/Content.Shared/_CorvaxNext/Overlays/BaseOverlayComponent.cs new file mode 100644 index 00000000000..4d5ad8b4727 --- /dev/null +++ b/Content.Shared/_CorvaxNext/Overlays/BaseOverlayComponent.cs @@ -0,0 +1,16 @@ +namespace Content.Shared._CorvaxNext.Overlays; + +public abstract partial class BaseOverlayComponent : Component +{ + [DataField, ViewVariables(VVAccess.ReadOnly)] + public virtual Vector3 Tint { get; set; } = new(0.3f, 0.3f, 0.3f); + + [DataField, ViewVariables(VVAccess.ReadOnly)] + public virtual float Strength { get; set; } = 2f; + + [DataField, ViewVariables(VVAccess.ReadOnly)] + public virtual float Noise { get; set; } = 0.5f; + + [DataField, ViewVariables(VVAccess.ReadOnly)] + public virtual Color Color { get; set; } = Color.White; +} diff --git a/Content.Shared/_CorvaxNext/Overlays/NightVisionComponent.cs b/Content.Shared/_CorvaxNext/Overlays/NightVisionComponent.cs new file mode 100644 index 00000000000..084f7448568 --- /dev/null +++ b/Content.Shared/_CorvaxNext/Overlays/NightVisionComponent.cs @@ -0,0 +1,15 @@ +using Content.Shared.Actions; +using Robust.Shared.Audio; +using Robust.Shared.GameStates; + +namespace Content.Shared._CorvaxNext.Overlays; + +[RegisterComponent, NetworkedComponent] +public sealed partial class NightVisionComponent : SwitchableOverlayComponent +{ + public override string? ToggleAction { get; set; } = "ToggleNightVision"; + + public override Color Color { get; set; } = Color.FromHex("#98FB98"); +} + +public sealed partial class ToggleNightVisionEvent : InstantActionEvent; diff --git a/Content.Shared/_CorvaxNext/Overlays/SwitchableOverlayComponent.cs b/Content.Shared/_CorvaxNext/Overlays/SwitchableOverlayComponent.cs new file mode 100644 index 00000000000..89a99a283f0 --- /dev/null +++ b/Content.Shared/_CorvaxNext/Overlays/SwitchableOverlayComponent.cs @@ -0,0 +1,24 @@ +using Robust.Shared.Audio; +using Robust.Shared.GameStates; + +namespace Content.Shared._CorvaxNext.Overlays; + +public abstract partial class SwitchableOverlayComponent : BaseOverlayComponent +{ + [DataField, AutoNetworkedField] + public bool IsActive = true; + + [DataField] + public virtual SoundSpecifier? ActivateSound { get; set; } = + new SoundPathSpecifier("/Audio/_CorvaxNext/Items/Goggles/activate.ogg"); + + [DataField] + public virtual SoundSpecifier? DeactivateSound { get; set; } = + new SoundPathSpecifier("/Audio/_CorvaxNext/Items/Goggles/deactivate.ogg"); + + [DataField] + public virtual string? ToggleAction { get; set; } + + [ViewVariables] + public EntityUid? ToggleActionEntity; +} diff --git a/Content.Shared/_CorvaxNext/Overlays/SwitchableOverlaySystem.cs b/Content.Shared/_CorvaxNext/Overlays/SwitchableOverlaySystem.cs new file mode 100644 index 00000000000..062fa177264 --- /dev/null +++ b/Content.Shared/_CorvaxNext/Overlays/SwitchableOverlaySystem.cs @@ -0,0 +1,52 @@ +using Content.Shared.Actions; +using Robust.Shared.Audio.Systems; +using Robust.Shared.Network; +using Robust.Shared.Player; +using Robust.Shared.Timing; + +namespace Content.Shared._CorvaxNext.Overlays; + +public abstract class SwitchableOverlaySystem : EntitySystem + where TComp : SwitchableOverlayComponent + where TEvent : InstantActionEvent +{ + [Dependency] private readonly SharedAudioSystem _audio = default!; + [Dependency] private readonly SharedActionsSystem _actions = default!; + [Dependency] private readonly IGameTiming _timing = default!; + [Dependency] private readonly INetManager _net = default!; + + public override void Initialize() + { + SubscribeLocalEvent(OnToggle); + SubscribeLocalEvent(OnInit); + SubscribeLocalEvent(OnRemove); + } + + private void OnRemove(EntityUid uid, TComp component, ComponentRemove args) + { + _actions.RemoveAction(uid, component.ToggleActionEntity); + UpdateVision(uid, false); + } + + private void OnInit(EntityUid uid, TComp component, ComponentInit args) + { + _actions.AddAction(uid, ref component.ToggleActionEntity, component.ToggleAction); + UpdateVision(uid, component.IsActive); + } + + protected virtual void UpdateVision(EntityUid uid, bool active) { } + + private void OnToggle(EntityUid uid, TComp component, TEvent args) + { + if (!_timing.IsFirstTimePredicted) + return; + + component.IsActive = !component.IsActive; + + if (_net.IsClient) + _audio.PlayEntity(component.IsActive ? component.ActivateSound : component.DeactivateSound, Filter.Local(), uid, false); + + args.Handled = true; + UpdateVision(uid, component.IsActive); + } +} diff --git a/Content.Shared/_CorvaxNext/Overlays/ThermalInvisibilityComponent.cs b/Content.Shared/_CorvaxNext/Overlays/ThermalInvisibilityComponent.cs new file mode 100644 index 00000000000..924fa8ef63f --- /dev/null +++ b/Content.Shared/_CorvaxNext/Overlays/ThermalInvisibilityComponent.cs @@ -0,0 +1,7 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared._CorvaxNext.Overlays; + +[RegisterComponent, NetworkedComponent] +public sealed partial class ThermalInvisibilityComponent : SwitchableOverlayComponent; + diff --git a/Content.Shared/_CorvaxNext/Overlays/ThermalVisionComponent.cs b/Content.Shared/_CorvaxNext/Overlays/ThermalVisionComponent.cs new file mode 100644 index 00000000000..65eb94d9221 --- /dev/null +++ b/Content.Shared/_CorvaxNext/Overlays/ThermalVisionComponent.cs @@ -0,0 +1,14 @@ +using Content.Shared.Actions; +using Robust.Shared.GameStates; + +namespace Content.Shared._CorvaxNext.Overlays; + +[RegisterComponent, NetworkedComponent] +public sealed partial class ThermalVisionComponent : SwitchableOverlayComponent +{ + public override string? ToggleAction { get; set; } = "ToggleThermalVision"; + + public override Color Color { get; set; } = Color.FromHex("#C26E4A"); +} + +public sealed partial class ToggleThermalVisionEvent : InstantActionEvent; diff --git a/Resources/Audio/_CorvaxNext/Items/Goggles/activate.ogg b/Resources/Audio/_CorvaxNext/Items/Goggles/activate.ogg new file mode 100644 index 00000000000..96cdb288fe0 Binary files /dev/null and b/Resources/Audio/_CorvaxNext/Items/Goggles/activate.ogg differ diff --git a/Resources/Audio/_CorvaxNext/Items/Goggles/attributions.yml b/Resources/Audio/_CorvaxNext/Items/Goggles/attributions.yml new file mode 100644 index 00000000000..7b1121f5423 --- /dev/null +++ b/Resources/Audio/_CorvaxNext/Items/Goggles/attributions.yml @@ -0,0 +1,9 @@ +- files: ["activate.ogg"] + license: "CC-BY-NC-SA-3.0" + copyright: "Taken from TGstation" + source: "https://github.com/tgstation/tgstation" + +- files: ["deactivate.ogg"] + license: "CC-BY-NC-SA-3.0" + copyright: "Taken from TGstation" + source: "https://github.com/tgstation/tgstation" \ No newline at end of file diff --git a/Resources/Audio/_CorvaxNext/Items/Goggles/deactivate.ogg b/Resources/Audio/_CorvaxNext/Items/Goggles/deactivate.ogg new file mode 100644 index 00000000000..e1e8f4fd82f Binary files /dev/null and b/Resources/Audio/_CorvaxNext/Items/Goggles/deactivate.ogg differ diff --git a/Resources/Locale/en-US/_corvaxnext/store/uplink-catalog.ftl b/Resources/Locale/en-US/_corvaxnext/store/uplink-catalog.ftl new file mode 100644 index 00000000000..3b1db5115ee --- /dev/null +++ b/Resources/Locale/en-US/_corvaxnext/store/uplink-catalog.ftl @@ -0,0 +1,5 @@ +uplink-night-vision-name = Night vision goggles +uplink-night-vision-desc = They allow you to see in the dark, all while looking like normal sunglasses! + +uplink-thermal-vision-name = Thermal vision goggles +uplink-thermal-vision-desc = They allow you to see living creatures regardless of obstacles, all while looking like normal sunglasses! \ No newline at end of file diff --git a/Resources/Locale/ru-RU/_CorvaxNext/entities/clothing/eyes/goggles.ftl b/Resources/Locale/ru-RU/_CorvaxNext/entities/clothing/eyes/goggles.ftl new file mode 100644 index 00000000000..6287842baf6 --- /dev/null +++ b/Resources/Locale/ru-RU/_CorvaxNext/entities/clothing/eyes/goggles.ftl @@ -0,0 +1,15 @@ +# Night Vision Goggles +ent-ClothingEyesNightVisionGoggles = прибор ночного видения + .desc = Теперь ты можешь видеть в темноте! +ent-ClothingEyesNightVisionGogglesSyndie = { ent-ClothingEyesNightVisionGoggles } + .desc = { ent-ClothingEyesNightVisionGoggles.desc } +ent-ClothingEyesNightVisionGogglesNukie = { ent-ClothingEyesNightVisionGoggles } + .desc = { ent-ClothingEyesNightVisionGoggles.desc } + +# Thermal Vision Goggles +ent-ClothingEyesThermalVisionGoggles = прибор термального видения + .desc = Теперь ты можешь видеть всех! +ent-ClothingEyesThermalVisionGogglesSyndie = { ent-ClothingEyesThermalVisionGoggles } + .desc = { ent-ClothingEyesThermalVisionGoggles.desc } +ent-ClothingEyesThermalVisionGogglesNukie = { ent-ClothingEyesThermalVisionGoggles } + .desc = { ent-ClothingEyesThermalVisionGoggles.desc } \ No newline at end of file diff --git a/Resources/Locale/ru-RU/_CorvaxNext/prototypes/actions/types.ftl b/Resources/Locale/ru-RU/_CorvaxNext/prototypes/actions/types.ftl new file mode 100644 index 00000000000..ca88b9a6afa --- /dev/null +++ b/Resources/Locale/ru-RU/_CorvaxNext/prototypes/actions/types.ftl @@ -0,0 +1,5 @@ +ent-ToggleNightVision = Переключить ночное зрение + .desc = Переключает ночное зрение. + +ent-ToggleThermalVision = Переключить термальное зрение + .desc = Переключает термальное зрение. \ No newline at end of file diff --git a/Resources/Locale/ru-RU/_CorvaxNext/store/uplink-catalog.ftl b/Resources/Locale/ru-RU/_CorvaxNext/store/uplink-catalog.ftl new file mode 100644 index 00000000000..c91985a04b6 --- /dev/null +++ b/Resources/Locale/ru-RU/_CorvaxNext/store/uplink-catalog.ftl @@ -0,0 +1,5 @@ +uplink-night-vision-name = Прибор ночного видения +uplink-night-vision-desc = Позволяет вам видеть в темноте, при этом выглядя как обычные солнцезащитные очки! + +uplink-thermal-vision-name = Прибор термального видения +uplink-thermal-vision-desc = Позволяет вам видеть живых существ независимо от преград, при этом выглядя как обычные солнцезащитные очки! \ No newline at end of file diff --git a/Resources/Prototypes/Entities/Mobs/Cyborgs/base_borg_chassis.yml b/Resources/Prototypes/Entities/Mobs/Cyborgs/base_borg_chassis.yml index be32887c3f0..e8b942241d3 100644 --- a/Resources/Prototypes/Entities/Mobs/Cyborgs/base_borg_chassis.yml +++ b/Resources/Prototypes/Entities/Mobs/Cyborgs/base_borg_chassis.yml @@ -6,6 +6,7 @@ save: false abstract: true components: + - type: ThermalInvisibility #Corvax-Next-ThermalVision - type: Reactive groups: Acidic: [Touch] diff --git a/Resources/Prototypes/Entities/Mobs/Player/admin_ghost.yml b/Resources/Prototypes/Entities/Mobs/Player/admin_ghost.yml index c102b4714e3..de0959ebcb1 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/admin_ghost.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/admin_ghost.yml @@ -4,6 +4,7 @@ name: admin observer categories: [ HideSpawnMenu ] components: + - type: ThermalInvisibility #Corvax-Next-ThermalVision - type: ContentEye maxZoom: 8.916104, 8.916104 - type: Tag diff --git a/Resources/Prototypes/Entities/Structures/Machines/lathe.yml b/Resources/Prototypes/Entities/Structures/Machines/lathe.yml index 55d881d1a95..97e8618ba2a 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/lathe.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/lathe.yml @@ -354,9 +354,11 @@ - FauxTileAstroSnow - OreBagOfHolding - DeviceQuantumSpinInverter - - EnergyScalpel # _CorvaxNext: surgery Change - - EnergyCautery # _CorvaxNext: surgery Change - - AdvancedRetractor # _CorvaxNext: surgery Change + # Corvax-Next Start + - EnergyScalpel + - EnergyCautery + - AdvancedRetractor + # Corvax-Next End - type: EmagLatheRecipes emagDynamicRecipes: - BoxBeanbag diff --git a/Resources/Prototypes/_CorvaxNext/Actions/types.yml b/Resources/Prototypes/_CorvaxNext/Actions/types.yml new file mode 100644 index 00000000000..f8c817185c8 --- /dev/null +++ b/Resources/Prototypes/_CorvaxNext/Actions/types.yml @@ -0,0 +1,25 @@ +- type: entity + id: ToggleNightVision + name: Switch night vision + description: Switches night vision + components: + - type: InstantAction + itemIconStyle: BigAction + priority: -20 + icon: + sprite: _CorvaxNext/Clothing/Eyes/Goggles/nightvision.rsi + state: icon + event: !type:ToggleNightVisionEvent + +- type: entity + id: ToggleThermalVision + name: Switch Thermal vision + description: Switches Thermal vision + components: + - type: InstantAction + itemIconStyle: BigAction + priority: -20 + icon: + sprite: _CorvaxNext/Clothing/Eyes/Goggles/thermal.rsi + state: icon + event: !type:ToggleThermalVisionEvent \ No newline at end of file diff --git a/Resources/Prototypes/_CorvaxNext/Catalog/uplink_catalog.yml b/Resources/Prototypes/_CorvaxNext/Catalog/uplink_catalog.yml new file mode 100644 index 00000000000..49ca58473e5 --- /dev/null +++ b/Resources/Prototypes/_CorvaxNext/Catalog/uplink_catalog.yml @@ -0,0 +1,63 @@ +# Night Vision + +- type: listing + id: UplinkNightGoggles + name: uplink-night-vision-name + description: uplink-night-vision-desc + productEntity: ClothingEyesNightVisionGogglesSyndie + cost: + Telecrystal: 6 + categories: + - UplinkWearables + conditions: + - !type:StoreWhitelistCondition + blacklist: + tags: + - NukeOpsUplink + +- type: listing + id: UplinkNightGogglesNukie + name: uplink-night-vision-name + description: uplink-night-vision-desc + productEntity: ClothingEyesNightVisionGogglesNukie + cost: + Telecrystal: 10 + categories: + - UplinkWearables + conditions: + - !type:StoreWhitelistCondition + whitelist: + tags: + - NukeOpsUplink + +# Thermal Vision + +- type: listing + id: UplinkThermalGoggles + name: uplink-thermal-vision-name + description: uplink-thermal-vision-desc + productEntity: ClothingEyesThermalVisionGogglesSyndie + cost: + Telecrystal: 8 + categories: + - UplinkWearables + conditions: + - !type:StoreWhitelistCondition + blacklist: + tags: + - NukeOpsUplink + +- type: listing + id: UplinkThermalGogglesNukie + name: uplink-thermal-vision-name + description: uplink-thermal-vision-desc + productEntity: ClothingEyesThermalVisionGogglesNukie + cost: + Telecrystal: 12 + categories: + - UplinkWearables + conditions: + - !type:StoreWhitelistCondition + whitelist: + tags: + - NukeOpsUplink diff --git a/Resources/Prototypes/_CorvaxNext/Entities/Clothing/Eyes/goggles.yml b/Resources/Prototypes/_CorvaxNext/Entities/Clothing/Eyes/goggles.yml new file mode 100644 index 00000000000..318139adff5 --- /dev/null +++ b/Resources/Prototypes/_CorvaxNext/Entities/Clothing/Eyes/goggles.yml @@ -0,0 +1,71 @@ +# Night Vision Goggles + +- type: entity + parent: ClothingEyesBase + id: ClothingEyesNightVisionGoggles + name: night vision goggles + description: Now you can see in the dark! + components: + - type: Sprite + sprite: _CorvaxNext/Clothing/Eyes/Goggles/nightvision.rsi + - type: Clothing + sprite: _CorvaxNext/Clothing/Eyes/Goggles/nightvision.rsi + - type: ClothingGrantComponent + component: + - type: NightVision + +- type: entity + parent: ClothingEyesNightVisionGoggles + id: ClothingEyesNightVisionGogglesSyndie + suffix: "Chameleon" + components: + - type: ChameleonClothing + slot: [eyes] + default: ClothingEyesGlassesSunglasses + - type: UserInterface + interfaces: + enum.ChameleonUiKey.Key: + type: ChameleonBoundUserInterface + +- type: entity + parent: [ClothingEyesNightVisionGogglesSyndie, ShowSecurityIcons] + id: ClothingEyesNightVisionGogglesNukie + suffix: "Chameleon, NukeOps" + components: + - type: ShowSyndicateIcons + +# Thermal Vision Goggles + +- type: entity + parent: ClothingEyesBase + id: ClothingEyesThermalVisionGoggles + name: thermal vision goggles + description: Now you can see everyone! + components: + - type: Sprite + sprite: _CorvaxNext/Clothing/Eyes/Goggles/thermal.rsi + - type: Clothing + sprite: _CorvaxNext/Clothing/Eyes/Goggles/thermal.rsi + - type: ClothingGrantComponent + component: + - type: ThermalVision + +- type: entity + parent: ClothingEyesThermalVisionGoggles + id: ClothingEyesThermalVisionGogglesSyndie + suffix: "Chameleon" + components: + - type: ChameleonClothing + slot: [eyes] + default: ClothingEyesGlassesSunglasses + - type: UserInterface + interfaces: + enum.ChameleonUiKey.Key: + type: ChameleonBoundUserInterface + +- type: entity + parent: [ClothingEyesThermalVisionGogglesSyndie, ShowSecurityIcons] + id: ClothingEyesThermalVisionGogglesNukie + suffix: "Chameleon, NukeOps" + components: + - type: ShowSyndicateIcons \ No newline at end of file diff --git a/Resources/Prototypes/_CorvaxNext/Research/experimental.yml b/Resources/Prototypes/_CorvaxNext/Research/experimental.yml index 0d69e6428ee..e949c566f56 100644 --- a/Resources/Prototypes/_CorvaxNext/Research/experimental.yml +++ b/Resources/Prototypes/_CorvaxNext/Research/experimental.yml @@ -1,3 +1,5 @@ +# Tier 2 + - type: technology id: BluespaceMining name: research-technology-bluespace-mining diff --git a/Resources/Prototypes/_CorvaxNext/Shaders/shaders.yml b/Resources/Prototypes/_CorvaxNext/Shaders/shaders.yml new file mode 100644 index 00000000000..09733c85e20 --- /dev/null +++ b/Resources/Prototypes/_CorvaxNext/Shaders/shaders.yml @@ -0,0 +1,4 @@ +- type: shader + id: NightVision + kind: source + path: "/Textures/_CorvaxNext/Shaders/nightvision.swsl" \ No newline at end of file diff --git a/Resources/Textures/_CorvaxNext/Clothing/Eyes/Goggles/nightvision.rsi/equipped-EYES-off.png b/Resources/Textures/_CorvaxNext/Clothing/Eyes/Goggles/nightvision.rsi/equipped-EYES-off.png new file mode 100644 index 00000000000..b63f30fc713 Binary files /dev/null and b/Resources/Textures/_CorvaxNext/Clothing/Eyes/Goggles/nightvision.rsi/equipped-EYES-off.png differ diff --git a/Resources/Textures/_CorvaxNext/Clothing/Eyes/Goggles/nightvision.rsi/equipped-EYES.png b/Resources/Textures/_CorvaxNext/Clothing/Eyes/Goggles/nightvision.rsi/equipped-EYES.png new file mode 100644 index 00000000000..199c44122bb Binary files /dev/null and b/Resources/Textures/_CorvaxNext/Clothing/Eyes/Goggles/nightvision.rsi/equipped-EYES.png differ diff --git a/Resources/Textures/_CorvaxNext/Clothing/Eyes/Goggles/nightvision.rsi/icon.png b/Resources/Textures/_CorvaxNext/Clothing/Eyes/Goggles/nightvision.rsi/icon.png new file mode 100644 index 00000000000..bf770f70f89 Binary files /dev/null and b/Resources/Textures/_CorvaxNext/Clothing/Eyes/Goggles/nightvision.rsi/icon.png differ diff --git a/Resources/Textures/_CorvaxNext/Clothing/Eyes/Goggles/nightvision.rsi/inhand-left.png b/Resources/Textures/_CorvaxNext/Clothing/Eyes/Goggles/nightvision.rsi/inhand-left.png new file mode 100644 index 00000000000..995b37471b3 Binary files /dev/null and b/Resources/Textures/_CorvaxNext/Clothing/Eyes/Goggles/nightvision.rsi/inhand-left.png differ diff --git a/Resources/Textures/_CorvaxNext/Clothing/Eyes/Goggles/nightvision.rsi/inhand-right.png b/Resources/Textures/_CorvaxNext/Clothing/Eyes/Goggles/nightvision.rsi/inhand-right.png new file mode 100644 index 00000000000..c3efa67f837 Binary files /dev/null and b/Resources/Textures/_CorvaxNext/Clothing/Eyes/Goggles/nightvision.rsi/inhand-right.png differ diff --git a/Resources/Textures/_CorvaxNext/Clothing/Eyes/Goggles/nightvision.rsi/meta.json b/Resources/Textures/_CorvaxNext/Clothing/Eyes/Goggles/nightvision.rsi/meta.json new file mode 100644 index 00000000000..987b20b9af7 --- /dev/null +++ b/Resources/Textures/_CorvaxNext/Clothing/Eyes/Goggles/nightvision.rsi/meta.json @@ -0,0 +1,48 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from tgstation at https://github.com/tgstation/tgstation", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "equipped-EYES", + "directions": 4, + "delays": [ + [ + 0.1, + 0.1 + ], + [ + 0.1, + 0.1 + ], + [ + 0.1, + 0.1 + ], + [ + 0.1, + 0.1 + ] + ] + }, + { + "name": "equipped-EYES-off", + "directions": 4 + }, + { + "name": "icon" + }, + { + "name": "inhand-left", + "directions": 4 + }, + { + "name": "inhand-right", + "directions": 4 + } + ] +} diff --git a/Resources/Textures/_CorvaxNext/Clothing/Eyes/Goggles/thermal.rsi/equipped-EYES.png b/Resources/Textures/_CorvaxNext/Clothing/Eyes/Goggles/thermal.rsi/equipped-EYES.png new file mode 100644 index 00000000000..9bef0a8c05f Binary files /dev/null and b/Resources/Textures/_CorvaxNext/Clothing/Eyes/Goggles/thermal.rsi/equipped-EYES.png differ diff --git a/Resources/Textures/_CorvaxNext/Clothing/Eyes/Goggles/thermal.rsi/icon.png b/Resources/Textures/_CorvaxNext/Clothing/Eyes/Goggles/thermal.rsi/icon.png new file mode 100644 index 00000000000..3d5f8ef9b65 Binary files /dev/null and b/Resources/Textures/_CorvaxNext/Clothing/Eyes/Goggles/thermal.rsi/icon.png differ diff --git a/Resources/Textures/_CorvaxNext/Clothing/Eyes/Goggles/thermal.rsi/inhand-left.png b/Resources/Textures/_CorvaxNext/Clothing/Eyes/Goggles/thermal.rsi/inhand-left.png new file mode 100644 index 00000000000..bf67e35d402 Binary files /dev/null and b/Resources/Textures/_CorvaxNext/Clothing/Eyes/Goggles/thermal.rsi/inhand-left.png differ diff --git a/Resources/Textures/_CorvaxNext/Clothing/Eyes/Goggles/thermal.rsi/inhand-right.png b/Resources/Textures/_CorvaxNext/Clothing/Eyes/Goggles/thermal.rsi/inhand-right.png new file mode 100644 index 00000000000..4ede078291d Binary files /dev/null and b/Resources/Textures/_CorvaxNext/Clothing/Eyes/Goggles/thermal.rsi/inhand-right.png differ diff --git a/Resources/Textures/_CorvaxNext/Clothing/Eyes/Goggles/thermal.rsi/meta.json b/Resources/Textures/_CorvaxNext/Clothing/Eyes/Goggles/thermal.rsi/meta.json new file mode 100644 index 00000000000..205508acfa7 --- /dev/null +++ b/Resources/Textures/_CorvaxNext/Clothing/Eyes/Goggles/thermal.rsi/meta.json @@ -0,0 +1,26 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from tgstation at https://github.com/tgstation/tgstation", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "icon" + }, + { + "name": "equipped-EYES", + "directions": 4 + }, + { + "name": "inhand-left", + "directions": 4 + }, + { + "name": "inhand-right", + "directions": 4 + } + ] +} diff --git a/Resources/Textures/_CorvaxNext/Shaders/nightvision.swsl b/Resources/Textures/_CorvaxNext/Shaders/nightvision.swsl new file mode 100644 index 00000000000..655d725a0d5 --- /dev/null +++ b/Resources/Textures/_CorvaxNext/Shaders/nightvision.swsl @@ -0,0 +1,40 @@ +light_mode unshaded; + +uniform sampler2D SCREEN_TEXTURE; +uniform highp vec3 tint; // Colour of the tint +uniform highp float luminance_threshold; // number between 0 and 1 +uniform highp float noise_amount; // number between 0 and 1 + +lowp float rand (lowp vec2 n) { + return 0.5 + 0.5 * fract (sin (dot (n.xy, vec2 (12.9898, 78.233)))* 43758.5453); +} + +void fragment() { + + highp vec4 color = zTextureSpec(SCREEN_TEXTURE, FRAGCOORD.xy * SCREEN_PIXEL_SIZE); + + // convert color to grayscale using luminance + highp float grey = dot(color.rgb, vec3(0.298, 0.5882, 0.1137)); + + // calculate local threshold + highp float threshold = grey * luminance_threshold; + + // amplify low luminance parts + if (grey < threshold) { + grey += (threshold - grey) * 0.5; + if (grey > 1.0) { + grey = 1.0; + } + } + + // apply night vision color tint + color.rgb = mix(color.rgb, tint, grey); + + // add some noise for realism + lowp float noise = rand(FRAGCOORD.xy + TIME) * noise_amount / 10.0; + color.rgb += noise; + + color.rgb = clamp(color.rgb, 0.0, 1.0); + + COLOR = color; +} \ No newline at end of file