diff --git a/Content.Client/_CP14/MagicVision/CP14ClientMagicVisionSystem.cs b/Content.Client/_CP14/MagicVision/CP14ClientMagicVisionSystem.cs new file mode 100644 index 0000000000..7d840f09b4 --- /dev/null +++ b/Content.Client/_CP14/MagicVision/CP14ClientMagicVisionSystem.cs @@ -0,0 +1,159 @@ +using System.Numerics; +using Content.Shared._CP14.MagicVision; +using Content.Shared.Examine; +using Robust.Client.GameObjects; +using Robust.Client.Graphics; +using Robust.Client.Player; +using Robust.Client.Timing; +using Robust.Shared.Audio; +using Robust.Shared.Audio.Systems; +using Robust.Shared.Map; +using Robust.Shared.Player; +using Robust.Shared.Utility; + +namespace Content.Client._CP14.MagicVision; + +public sealed class CP14ClientMagicVisionSystem : CP14SharedMagicVisionSystem +{ + [Dependency] private readonly IClientGameTiming _timing = default!; + [Dependency] private readonly SpriteSystem _sprite = default!; + [Dependency] private readonly IPlayerManager _player = default!; + [Dependency] private readonly SharedTransformSystem _transform = default!; + [Dependency] private readonly IOverlayManager _overlayMan = default!; + [Dependency] private readonly SharedAudioSystem _audio = default!; + + private CP14MagicVisionOverlay? _overlay; + private CP14MagicVisionNoirOverlay? _overlay2; + + private TimeSpan _nextUpdate = TimeSpan.Zero; + + private SoundSpecifier _startSound = new SoundPathSpecifier(new ResPath("/Audio/Effects/eye_open.ogg")); + private SoundSpecifier _endSound = new SoundPathSpecifier(new ResPath("/Audio/Effects/eye_close.ogg")); + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnHandleStateMarker); + + SubscribeLocalEvent(OnPlayerAttached); + SubscribeLocalEvent(OnPlayerDetached); + + SubscribeLocalEvent(OnComponentInit); + SubscribeLocalEvent(OnComponentShutdown); + } + + private void OnComponentShutdown(Entity ent, ref ComponentShutdown args) + { + if (_player.LocalEntity != ent) + return; + if (_overlay != null) + { + _overlayMan.RemoveOverlay(_overlay); + _overlay = null; + } + if (_overlay2 != null) + { + _overlayMan.RemoveOverlay(_overlay2); + _overlay2 = null; + } + + _audio.PlayGlobal(_endSound, ent); + } + + private void OnComponentInit(Entity ent, ref ComponentInit args) + { + if (_player.LocalEntity != ent) + return; + + _overlay = new CP14MagicVisionOverlay(); + _overlayMan.AddOverlay(_overlay); + _overlay.StartOverlay = _timing.CurTime; + + _overlay2 = new CP14MagicVisionNoirOverlay(); + _overlayMan.AddOverlay(_overlay2); + + _audio.PlayGlobal(_startSound, ent); + } + + private void OnPlayerAttached(Entity ent, ref LocalPlayerAttachedEvent args) + { + _overlay = new CP14MagicVisionOverlay(); + _overlayMan.AddOverlay(_overlay); + _overlay.StartOverlay = _timing.CurTime; + + _overlay2 = new CP14MagicVisionNoirOverlay(); + _overlayMan.AddOverlay(_overlay2); + + _audio.PlayGlobal(_startSound, ent); + } + + private void OnPlayerDetached(Entity ent, ref LocalPlayerDetachedEvent args) + { + if (_overlay != null) + { + _overlayMan.RemoveOverlay(_overlay); + _overlay = null; + } + if (_overlay2 != null) + { + _overlayMan.RemoveOverlay(_overlay2); + _overlay2 = null; + } + _audio.PlayGlobal(_endSound, ent); + } + + protected override void OnExamined(Entity ent, ref ExaminedEvent args) + { + base.OnExamined(ent, ref args); + + if (ent.Comp.TargetCoordinates is null) + return; + + var originPosition = _transform.GetWorldPosition(ent); + var targetPosition = _transform.ToWorldPosition(ent.Comp.TargetCoordinates.Value); + + if ((targetPosition - originPosition).Length() < 0.5f) + return; + + Angle angle = new(targetPosition - originPosition); + + var pointer = Spawn(ent.Comp.PointerProto, new MapCoordinates(originPosition, _transform.GetMapId(Transform(ent).Coordinates))); + + _transform.SetWorldRotation(pointer, angle + Angle.FromDegrees(90)); + } + + private void OnHandleStateMarker(Entity ent, ref AfterAutoHandleStateEvent args) + { + if (!TryComp(ent, out var sprite)) + return; + if (ent.Comp.Icon is null) + return; + + var layer = _sprite.AddLayer(ent.Owner, ent.Comp.Icon); + sprite.LayerSetShader(layer, "unshaded"); + _sprite.LayerSetScale(ent.Owner, layer, new Vector2(0.5f, 0.5f)); + } + + public override void Update(float frameTime) + { + base.Update(frameTime); + + if (_timing.CurTime < _nextUpdate) + return; + + _nextUpdate = _timing.CurTime + TimeSpan.FromSeconds(0.5f); + var queryFade = EntityQueryEnumerator(); + while (queryFade.MoveNext(out var uid, out var fade, out var sprite)) + { + UpdateOpaque((uid, fade), sprite); + } + } + + private void UpdateOpaque(Entity ent, SpriteComponent sprite) + { + var progress = Math.Clamp((_timing.CurTime.TotalSeconds - ent.Comp.SpawnTime.TotalSeconds) / (ent.Comp.EndTime.TotalSeconds - ent.Comp.SpawnTime.TotalSeconds), 0, 1); + var alpha = 1 - progress; + _sprite.SetColor(ent.Owner, Color.White.WithAlpha((float)alpha)); + } +} diff --git a/Content.Client/_CP14/MagicVision/CP14MagicVisionNoirOverlay.cs b/Content.Client/_CP14/MagicVision/CP14MagicVisionNoirOverlay.cs new file mode 100644 index 0000000000..00a1de951f --- /dev/null +++ b/Content.Client/_CP14/MagicVision/CP14MagicVisionNoirOverlay.cs @@ -0,0 +1,33 @@ +using Robust.Client.Graphics; +using Robust.Shared.Enums; +using Robust.Shared.Prototypes; + +namespace Content.Client._CP14.MagicVision; + +public sealed class CP14MagicVisionNoirOverlay : Overlay +{ + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + + public override OverlaySpace Space => OverlaySpace.WorldSpace; + public override bool RequestScreenTexture => true; + private readonly ShaderInstance _noirShader; + + public CP14MagicVisionNoirOverlay() + { + IoCManager.InjectDependencies(this); + _noirShader = _prototypeManager.Index("CP14BlueNoir").InstanceUnique(); + ZIndex = 9; // draw this over the DamageOverlay, RainbowOverlay etc, but before the black and white shader + } + + protected override void Draw(in OverlayDrawArgs args) + { + if (ScreenTexture == null) + return; + + var handle = args.WorldHandle; + _noirShader.SetParameter("SCREEN_TEXTURE", ScreenTexture); + handle.UseShader(_noirShader); + handle.DrawRect(args.WorldBounds, Color.White); + handle.UseShader(null); + } +} diff --git a/Content.Client/_CP14/MagicVision/CP14MagicVisionOverlay.cs b/Content.Client/_CP14/MagicVision/CP14MagicVisionOverlay.cs new file mode 100644 index 0000000000..b49d714cec --- /dev/null +++ b/Content.Client/_CP14/MagicVision/CP14MagicVisionOverlay.cs @@ -0,0 +1,75 @@ +using Content.Shared._CP14.MagicVision; +using Robust.Client.Graphics; +using Robust.Client.Player; +using Robust.Shared.Enums; +using Robust.Shared.Prototypes; +using Robust.Shared.Timing; + +namespace Content.Client._CP14.MagicVision; + +public sealed class CP14MagicVisionOverlay : Overlay +{ + [Dependency] private readonly IEntityManager _entityManager = default!; + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + [Dependency] private readonly IPlayerManager _playerManager = default!; + [Dependency] private readonly IGameTiming _timing = default!; + + public override OverlaySpace Space => OverlaySpace.WorldSpace; + public override bool RequestScreenTexture => true; + + private readonly ShaderInstance _drowsinessShader; + + public float CurrentPower = 10.0f; + public TimeSpan StartOverlay = TimeSpan.Zero; // when the overlay started + + private const float PowerDivisor = 250.0f; + private const float Intensity = 0.2f; // for adjusting the visual scale + private float _visualScale = 0; // between 0 and 1 + + public CP14MagicVisionOverlay() + { + IoCManager.InjectDependencies(this); + _drowsinessShader = _prototypeManager.Index("Drowsiness").InstanceUnique(); + } + + protected override void FrameUpdate(FrameEventArgs args) + { + var playerEntity = _playerManager.LocalEntity; + + if (playerEntity == null) + return; + + if (!_entityManager.HasComponent(playerEntity)) + return; + + var curTime = _timing.CurTime; + var timeLeft = (float)(curTime - StartOverlay).TotalSeconds; + + CurrentPower = Math.Max(50f, 200f - (150f * Math.Min((float)(timeLeft / 3.0), 1.0f))); + } + + protected override bool BeforeDraw(in OverlayDrawArgs args) + { + if (!_entityManager.TryGetComponent(_playerManager.LocalEntity, out EyeComponent? eyeComp)) + return false; + + if (args.Viewport.Eye != eyeComp.Eye) + return false; + + _visualScale = Math.Clamp(CurrentPower / PowerDivisor, 0.0f, 1.0f); + return _visualScale > 0; + } + + protected override void Draw(in OverlayDrawArgs args) + { + if (ScreenTexture == null) + return; + + var handle = args.WorldHandle; + _drowsinessShader.SetParameter("SCREEN_TEXTURE", ScreenTexture); + _drowsinessShader.SetParameter("Strength", _visualScale * Intensity); + handle.UseShader(_drowsinessShader); + handle.DrawRect(args.WorldBounds, Color.White); + handle.UseShader(null); + } +} diff --git a/Content.Server/_CP14/AuraImprint/CP14AuraImprintSystem.cs b/Content.Server/_CP14/AuraImprint/CP14AuraImprintSystem.cs new file mode 100644 index 0000000000..98fef18151 --- /dev/null +++ b/Content.Server/_CP14/AuraImprint/CP14AuraImprintSystem.cs @@ -0,0 +1,69 @@ +using Content.Shared._CP14.AuraDNA; +using Content.Shared._CP14.MagicVision; +using Content.Shared.Mobs; +using Robust.Shared.Random; +using Robust.Shared.Utility; + +namespace Content.Server._CP14.AuraImprint; + +/// +/// This system handles the basic mechanics of spell use, such as doAfter, event invocation, and energy spending. +/// +public sealed partial class CP14AuraImprintSystem : CP14SharedAuraImprintSystem +{ + [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly CP14SharedMagicVisionSystem _vision = default!; + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnMapInit); + SubscribeLocalEvent(OnMobStateChanged); + } + + private void OnMapInit(Entity ent, ref MapInitEvent args) + { + ent.Comp.Imprint = GenerateAuraImprint(ent); + Dirty(ent); + } + + public string GenerateAuraImprint(Entity ent) + { + var letters = new[] { "ä", "ã", "ç", "ø", "ђ", "œ", "Ї", "Ћ", "ў", "ž", "Ћ", "ö", "є", "þ"}; + var imprint = string.Empty; + + for (var i = 0; i < ent.Comp.ImprintLength; i++) + { + imprint += letters[_random.Next(letters.Length)]; + } + + return $"[color={ent.Comp.ImprintColor.ToHex()}]{imprint}[/color]"; + } + + private void OnMobStateChanged(Entity ent, ref MobStateChangedEvent args) + { + switch (args.NewMobState) + { + case MobState.Critical: + { + _vision.SpawnMagicVision( + Transform(ent).Coordinates, + new SpriteSpecifier.Rsi(new ResPath("_CP14/Actions/Spells/misc.rsi"), "skull"), + Loc.GetString("cp14-magic-vision-crit"), + TimeSpan.FromMinutes(10), + ent); + break; + } + case MobState.Dead: + { + _vision.SpawnMagicVision( + Transform(ent).Coordinates, + new SpriteSpecifier.Rsi(new ResPath("_CP14/Actions/Spells/misc.rsi"), "skull_red"), + Loc.GetString("cp14-magic-vision-dead"), + TimeSpan.FromMinutes(10), + ent); + break; + } + } + } +} diff --git a/Content.Server/_CP14/MagicVision/CP14MagicVisionSystem.cs b/Content.Server/_CP14/MagicVision/CP14MagicVisionSystem.cs new file mode 100644 index 0000000000..4511765154 --- /dev/null +++ b/Content.Server/_CP14/MagicVision/CP14MagicVisionSystem.cs @@ -0,0 +1,54 @@ +using Content.Shared._CP14.MagicVision; +using Content.Shared.Eye; +using Robust.Shared.Timing; + +namespace Content.Server._CP14.MagicVision; + +public sealed class CP14MagicVisionSystem : CP14SharedMagicVisionSystem +{ + [Dependency] private readonly IGameTiming _timing = default!; + [Dependency] private readonly SharedEyeSystem _eye = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnMagicVisionToggle); + SubscribeLocalEvent(OnGetVisMask); + } + + private void OnGetVisMask(Entity ent, ref GetVisMaskEvent args) + { + args.VisibilityMask |= (int)VisibilityFlags.CP14MagicVision; + } + + private void OnMagicVisionToggle(Entity ent, ref CP14MagicVisionToggleActionEvent args) + { + if (!HasComp(ent)) + { + AddComp(ent); + } + else + { + RemComp(ent); + } + _eye.RefreshVisibilityMask(ent.Owner); + } + + public override void Update(float frameTime) + { + base.Update(frameTime); + + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var uid, out var marker)) + { + if (marker.EndTime == TimeSpan.Zero) + continue; + + if (_timing.CurTime < marker.EndTime) + continue; + + QueueDel(uid); + } + } +} diff --git a/Content.Server/_CP14/Religion/CP14ReligionSystem.cs b/Content.Server/_CP14/Religion/CP14ReligionSystem.cs index aa3f830a1f..5bac92fd75 100644 --- a/Content.Server/_CP14/Religion/CP14ReligionSystem.cs +++ b/Content.Server/_CP14/Religion/CP14ReligionSystem.cs @@ -26,6 +26,7 @@ public sealed partial class CP14ReligionGodSystem : CP14SharedReligionGodSystem [Dependency] private readonly CP14MagicEnergySystem _magicEnergy = default!; [Dependency] private readonly SharedTransformSystem _transform = default!; [Dependency] private readonly IGameTiming _gameTiming = default!; + [Dependency] private readonly SharedEyeSystem _eye = default!; private EntityQuery _godQuery; @@ -158,11 +159,13 @@ public sealed partial class CP14ReligionGodSystem : CP14SharedReligionGodSystem private void OnGodInit(Entity ent, ref ComponentInit args) { AddPvsOverrides(ent); + _eye.RefreshVisibilityMask(ent.Owner); } private void OnGodShutdown(Entity ent, ref ComponentShutdown args) { RemovePvsOverrides(ent); + _eye.RefreshVisibilityMask(ent.Owner); } private void OnPlayerAttached(Entity ent, ref PlayerAttachedEvent args) diff --git a/Content.Shared/Eye/VisibilityFlags.cs b/Content.Shared/Eye/VisibilityFlags.cs index 432e80dd58..eb034ad74a 100644 --- a/Content.Shared/Eye/VisibilityFlags.cs +++ b/Content.Shared/Eye/VisibilityFlags.cs @@ -10,5 +10,8 @@ namespace Content.Shared.Eye Normal = 1 << 0, Ghost = 1 << 1, Subfloor = 1 << 2, + //CP14 zone + CP14MagicVision = 1 << 3, + //CP14 zone end } } diff --git a/Content.Shared/_CP14/AuraDNA/CP14AuraImprintComponent.cs b/Content.Shared/_CP14/AuraDNA/CP14AuraImprintComponent.cs new file mode 100644 index 0000000000..5af990e8fd --- /dev/null +++ b/Content.Shared/_CP14/AuraDNA/CP14AuraImprintComponent.cs @@ -0,0 +1,19 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared._CP14.AuraDNA; + +/// +/// A component that stores a “blueprint” of the aura, unique to each mind. +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, Access(typeof(CP14SharedAuraImprintSystem))] +public sealed partial class CP14AuraImprintComponent : Component +{ + [DataField, AutoNetworkedField] + public string Imprint = string.Empty; + + [DataField] + public int ImprintLength = 8; + + [DataField] + public Color ImprintColor = Color.White; +} diff --git a/Content.Shared/_CP14/AuraDNA/CP14SharedAuraImprintSystem.cs b/Content.Shared/_CP14/AuraDNA/CP14SharedAuraImprintSystem.cs new file mode 100644 index 0000000000..30b95cfbe1 --- /dev/null +++ b/Content.Shared/_CP14/AuraDNA/CP14SharedAuraImprintSystem.cs @@ -0,0 +1,8 @@ +namespace Content.Shared._CP14.AuraDNA; + +/// +/// This system handles the basic mechanics of spell use, such as doAfter, event invocation, and energy spending. +/// +public abstract partial class CP14SharedAuraImprintSystem : EntitySystem +{ +} diff --git a/Content.Shared/_CP14/MagicSpell/CP14SharedMagicSystem.cs b/Content.Shared/_CP14/MagicSpell/CP14SharedMagicSystem.cs index cdffe4cb6b..47a966f9cd 100644 --- a/Content.Shared/_CP14/MagicSpell/CP14SharedMagicSystem.cs +++ b/Content.Shared/_CP14/MagicSpell/CP14SharedMagicSystem.cs @@ -1,11 +1,12 @@ -using System.Linq; using System.Text; using Content.Shared._CP14.MagicEnergy; using Content.Shared._CP14.MagicEnergy.Components; using Content.Shared._CP14.MagicSpell.Components; using Content.Shared._CP14.MagicSpell.Events; using Content.Shared._CP14.MagicSpell.Spells; +using Content.Shared._CP14.MagicVision; using Content.Shared.Actions; +using Content.Shared.Actions.Components; using Content.Shared.Damage.Systems; using Content.Shared.DoAfter; using Content.Shared.FixedPoint; @@ -32,6 +33,7 @@ public abstract partial class CP14SharedMagicSystem : EntitySystem [Dependency] private readonly MovementSpeedModifierSystem _movement = default!; [Dependency] private readonly SharedStaminaSystem _stamina = default!; [Dependency] private readonly IGameTiming _timing = default!; + [Dependency] private readonly CP14SharedMagicVisionSystem _magicVision = default!; private EntityQuery _magicContainerQuery; private EntityQuery _magicEffectQuery; @@ -162,6 +164,9 @@ public abstract partial class CP14SharedMagicSystem : EntitySystem private void CastTelegraphy(Entity ent, CP14SpellEffectBaseArgs args) { + if (!_timing.IsFirstTimePredicted) + return; + foreach (var effect in ent.Comp.TelegraphyEffects) { effect.Effect(EntityManager, args); @@ -170,6 +175,9 @@ public abstract partial class CP14SharedMagicSystem : EntitySystem private void CastSpell(Entity ent, CP14SpellEffectBaseArgs args) { + if (!_timing.IsFirstTimePredicted) + return; + var ev = new CP14MagicEffectConsumeResourceEvent(args.User); RaiseLocalEvent(ent, ref ev); @@ -177,6 +185,19 @@ public abstract partial class CP14SharedMagicSystem : EntitySystem { effect.Effect(EntityManager, args); } + + if (args.User is not null + && TryComp(ent, out var actionComp) + && TryComp(ent, out var manaCost)) + { + _magicVision.SpawnMagicVision( + Transform(args.User.Value).Coordinates, + actionComp.Icon, + Loc.GetString("cp14-magic-vision-used-spell", ("name", MetaData(ent).EntityName)), + TimeSpan.FromSeconds((float)manaCost.ManaCost * 50), + args.User, + args.Position); + } } protected FixedPoint2 CalculateManacost(Entity ent, EntityUid? caster) diff --git a/Content.Shared/_CP14/MagicVision/CP14MagicVisionComponent.cs b/Content.Shared/_CP14/MagicVision/CP14MagicVisionComponent.cs new file mode 100644 index 0000000000..08a7cac7b4 --- /dev/null +++ b/Content.Shared/_CP14/MagicVision/CP14MagicVisionComponent.cs @@ -0,0 +1,11 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared._CP14.MagicVision; + +/// +/// Allows to see magic vision trace entities +/// +[RegisterComponent, NetworkedComponent] +public sealed partial class CP14MagicVisionComponent : Component +{ +} diff --git a/Content.Shared/_CP14/MagicVision/CP14MagicVisionMarkerComponent.cs b/Content.Shared/_CP14/MagicVision/CP14MagicVisionMarkerComponent.cs new file mode 100644 index 0000000000..bafdf4388d --- /dev/null +++ b/Content.Shared/_CP14/MagicVision/CP14MagicVisionMarkerComponent.cs @@ -0,0 +1,31 @@ +using Robust.Shared.GameStates; +using Robust.Shared.Map; +using Robust.Shared.Prototypes; +using Robust.Shared.Utility; + +namespace Content.Shared._CP14.MagicVision; + +/// +/// Controls the visibility of this entity to the client, based on the length of time it has existed and the client's ability to see the magic +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState(true), AutoGenerateComponentPause] +public sealed partial class CP14MagicVisionMarkerComponent : Component +{ + [DataField, AutoPausedField, AutoNetworkedField] + public TimeSpan SpawnTime = TimeSpan.Zero; + + [DataField, AutoPausedField, AutoNetworkedField] + public TimeSpan EndTime = TimeSpan.Zero; + + [DataField, AutoNetworkedField] + public EntityCoordinates? TargetCoordinates; + + [DataField, AutoNetworkedField] + public SpriteSpecifier? Icon; + + [DataField] + public string? AuraImprint; + + [DataField, AutoNetworkedField] + public EntProtoId PointerProto = "CP14ManaVisionPointer"; +} diff --git a/Content.Shared/_CP14/MagicVision/CP14SharedMagicVisionSystem.cs b/Content.Shared/_CP14/MagicVision/CP14SharedMagicVisionSystem.cs new file mode 100644 index 0000000000..d107a7faa9 --- /dev/null +++ b/Content.Shared/_CP14/MagicVision/CP14SharedMagicVisionSystem.cs @@ -0,0 +1,83 @@ +using System.Text; +using Content.Shared._CP14.AuraDNA; +using Content.Shared.Actions; +using Content.Shared.Examine; +using Content.Shared.Mobs; +using Robust.Shared.Map; +using Robust.Shared.Prototypes; +using Robust.Shared.Timing; +using Robust.Shared.Utility; + +namespace Content.Shared._CP14.MagicVision; + +public abstract class CP14SharedMagicVisionSystem : EntitySystem +{ + [Dependency] private readonly IGameTiming _timing = default!; + [Dependency] private readonly MetaDataSystem _meta = default!; + + public readonly EntProtoId MagicTraceProto = "CP14MagicVisionMarker"; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnExamined); + SubscribeLocalEvent(OnAuraHolderExamine); + } + + private void OnAuraHolderExamine(Entity ent, ref ExaminedEvent args) + { + if (!HasComp(args.Examiner)) + return; + + args.PushMarkup($"{Loc.GetString("cp14-magic-vision-aura")} {ent.Comp.Imprint}"); + } + + protected virtual void OnExamined(Entity ent, ref ExaminedEvent args) + { + var sb = new StringBuilder(); + + var timePassed = _timing.CurTime - ent.Comp.SpawnTime; + sb.Append($"{Loc.GetString("cp14-magic-vision-timed-past")} {timePassed.Minutes}:{(timePassed.Seconds < 10 ? "0" : "")}{timePassed.Seconds}\n"); + + if (ent.Comp.AuraImprint is not null) + { + sb.Append($"{Loc.GetString("cp14-magic-vision-aura")} {ent.Comp.AuraImprint}"); + } + + args.AddMarkup(sb.ToString()); + } + + /// + /// Creates an invisible “magical trace” entity that can be seen with magical vision. + /// + /// Coordinates where the magic trail will be created + /// Magic trace icon + /// Description that can be obtained when examining the magical trace + /// Duration of the magical trace + /// Optional: The direction in which this trace “faces.” When studying the trace, + /// this direction can be seen in order to understand, for example, in which direction the spell was used. + public void SpawnMagicVision(EntityCoordinates position, SpriteSpecifier? icon, string description, TimeSpan duration, EntityUid? aura = null, EntityCoordinates? target = null) + { + var ent = PredictedSpawnAtPosition(MagicTraceProto, position); + var markerComp = EnsureComp(ent); + + markerComp.SpawnTime = _timing.CurTime; + markerComp.EndTime = _timing.CurTime + duration; + markerComp.TargetCoordinates = target; + markerComp.Icon = icon; + + if (aura is not null && TryComp(aura, out var auraImprint)) + { + markerComp.AuraImprint = auraImprint.Imprint; + } + + _meta.SetEntityDescription(ent, description); + + Dirty(ent, markerComp); + } +} + +public sealed partial class CP14MagicVisionToggleActionEvent : InstantActionEvent +{ +} diff --git a/Resources/Locale/en-US/_CP14/magicVision/vision.ftl b/Resources/Locale/en-US/_CP14/magicVision/vision.ftl new file mode 100644 index 0000000000..167808c20b --- /dev/null +++ b/Resources/Locale/en-US/_CP14/magicVision/vision.ftl @@ -0,0 +1,6 @@ +cp14-magic-vision-used-spell = The spell "{$name}" was used here. +cp14-magic-vision-crit = Someone here is in critical condition. +cp14-magic-vision-dead = Someone here has died. + +cp14-magic-vision-timed-past = Time has passed: +cp14-magic-vision-aura = Aura imprint: \ No newline at end of file diff --git a/Resources/Locale/ru-RU/_CP14/magicVision/vision.ftl b/Resources/Locale/ru-RU/_CP14/magicVision/vision.ftl new file mode 100644 index 0000000000..426a014138 --- /dev/null +++ b/Resources/Locale/ru-RU/_CP14/magicVision/vision.ftl @@ -0,0 +1,6 @@ +cp14-magic-vision-used-spell = Здесь было использовано заклинание "{$name}". +cp14-magic-vision-crit = Здесь кто-то упал в критическое состояние. +cp14-magic-vision-dead = Здесь кто-то умер. + +cp14-magic-vision-timed-past = Времени прошло: +cp14-magic-vision-aura = Слепок ауры: \ No newline at end of file diff --git a/Resources/Prototypes/_CP14/Entities/Actions/Spells/Fire/firewave.yml b/Resources/Prototypes/_CP14/Entities/Actions/Spells/Fire/firewave.yml index 8bce03ff96..f838d94c49 100644 --- a/Resources/Prototypes/_CP14/Entities/Actions/Spells/Fire/firewave.yml +++ b/Resources/Prototypes/_CP14/Entities/Actions/Spells/Fire/firewave.yml @@ -56,6 +56,12 @@ categories: [ HideSpawnMenu ] save: false components: + - type: Sprite + sprite: _CP14/Effects/fire.rsi + layers: + - state: small + visible: false + map: ["enum.EffectLayers.Unshaded"] - type: PointLight color: "#E25822" radius: 2.0 diff --git a/Resources/Prototypes/_CP14/Entities/Actions/Spells/Meta/mana_vision.yml b/Resources/Prototypes/_CP14/Entities/Actions/Spells/Meta/mana_vision.yml new file mode 100644 index 0000000000..f3be0c3a53 --- /dev/null +++ b/Resources/Prototypes/_CP14/Entities/Actions/Spells/Meta/mana_vision.yml @@ -0,0 +1,53 @@ +- type: entity + id: CP14ActionToggleMagicVision + parent: BaseMentalAction + name: Magical vision + description: You focus on magical flows to track recent events and scan the aura imprints of other living beings. + components: + - type: Action + useDelay: 5 + itemIconStyle: BigAction + checkCanInteract: false + sound: !type:SoundPathSpecifier + path: /Audio/Magic/rumble.ogg + icon: + sprite: _CP14/Actions/Spells/meta.rsi + state: magic_vision + - type: InstantAction + event: !type:CP14MagicVisionToggleActionEvent + +- type: entity + id: CP14ManaVisionPointer + name: pointer + categories: [ HideSpawnMenu ] + components: + - type: Sprite + color: "#42a4f5" + sprite: /Textures/_CP14/Effects/Magic/pointer.rsi + offset: 0, -1 + layers: + - state: pointer + shader: unshaded + drawdepth: LowFloors + - type: TimedDespawn + lifetime: 2 + - type: Tag + tags: + - HideContextMenu + +- type: entity + id: CP14MagicVisionMarker + categories: [ HideSpawnMenu ] + name: mana trace + components: + - type: Sprite + noRot: true + drawdepth: Effects + layers: + - state: sphere + sprite: /Textures/_CP14/Effects/Magic/pointer_sphere.rsi + shader: unshaded + color: "#42a4f5" + - type: Clickable + - type: Visibility + layer: 8 #magic vision only \ No newline at end of file diff --git a/Resources/Prototypes/_CP14/Entities/Markers/misc.yml b/Resources/Prototypes/_CP14/Entities/Markers/misc.yml index 39d364395e..d4a80a1643 100644 --- a/Resources/Prototypes/_CP14/Entities/Markers/misc.yml +++ b/Resources/Prototypes/_CP14/Entities/Markers/misc.yml @@ -9,5 +9,4 @@ - state: green - sprite: /Textures/_CP14/Structures/Dungeon/demiplan_rift.rsi state: pulse - - type: CP14DemiplaneRift - + - type: CP14DemiplaneRift \ No newline at end of file diff --git a/Resources/Prototypes/_CP14/Entities/Mobs/Species/base.yml b/Resources/Prototypes/_CP14/Entities/Mobs/Species/base.yml index 822bad3780..b4430b638c 100644 --- a/Resources/Prototypes/_CP14/Entities/Mobs/Species/base.yml +++ b/Resources/Prototypes/_CP14/Entities/Mobs/Species/base.yml @@ -232,6 +232,7 @@ Cold: 0.25 Bloodloss: 0.25 - type: CP14TradingReputation + - type: CP14AuraImprint - type: entity diff --git a/Resources/Prototypes/_CP14/Entities/Mobs/Species/carcat.yml b/Resources/Prototypes/_CP14/Entities/Mobs/Species/carcat.yml index b9961a9733..f911488875 100644 --- a/Resources/Prototypes/_CP14/Entities/Mobs/Species/carcat.yml +++ b/Resources/Prototypes/_CP14/Entities/Mobs/Species/carcat.yml @@ -42,6 +42,8 @@ - type: CP14NightVision #Night vision - type: FootstepModifier footstepSoundCollection: null # Silent footstep + - type: CP14AuraImprint + imprintColor: "#9e7e5d" - type: Inventory templateId: CP14Carcat # Cant wear shoes speciesId: carcat diff --git a/Resources/Prototypes/_CP14/Entities/Mobs/Species/dwarf.yml b/Resources/Prototypes/_CP14/Entities/Mobs/Species/dwarf.yml index 61c5df2263..ac942f9fd1 100644 --- a/Resources/Prototypes/_CP14/Entities/Mobs/Species/dwarf.yml +++ b/Resources/Prototypes/_CP14/Entities/Mobs/Species/dwarf.yml @@ -20,6 +20,8 @@ spawned: - id: CP14FoodMeatHuman amount: 5 + - type: CP14AuraImprint + imprintColor: "#91898d" - type: Body prototype: CP14Dwarf requiredLegs: 2 diff --git a/Resources/Prototypes/_CP14/Entities/Mobs/Species/elf.yml b/Resources/Prototypes/_CP14/Entities/Mobs/Species/elf.yml index 492fbbb1f6..68b88460ae 100644 --- a/Resources/Prototypes/_CP14/Entities/Mobs/Species/elf.yml +++ b/Resources/Prototypes/_CP14/Entities/Mobs/Species/elf.yml @@ -45,6 +45,9 @@ freeLearnedSkills: - MetamagicT1 - MetamagicT2 + - type: CP14AuraImprint + imprintLength: 10 #Long imprint hehe + imprintColor: "#42aaf5" - type: Inventory templateId: CP14Human displacements: diff --git a/Resources/Prototypes/_CP14/Entities/Mobs/Species/goblin.yml b/Resources/Prototypes/_CP14/Entities/Mobs/Species/goblin.yml index 7add9ee392..fbfe810979 100644 --- a/Resources/Prototypes/_CP14/Entities/Mobs/Species/goblin.yml +++ b/Resources/Prototypes/_CP14/Entities/Mobs/Species/goblin.yml @@ -128,6 +128,9 @@ - type: CP14MagicEnergyDraw #Cool x3 mana regen! energy: 1.5 delay: 3 + - type: CP14AuraImprint + imprintLength: 6 #Short imprint hehe + imprintColor: "#919e19" - type: Hands handDisplacement: sizeMaps: diff --git a/Resources/Prototypes/_CP14/Entities/Mobs/Species/silva.yml b/Resources/Prototypes/_CP14/Entities/Mobs/Species/silva.yml index f880521dcd..75430a7e01 100644 --- a/Resources/Prototypes/_CP14/Entities/Mobs/Species/silva.yml +++ b/Resources/Prototypes/_CP14/Entities/Mobs/Species/silva.yml @@ -83,6 +83,8 @@ - type: Body prototype: CP14Silva requiredLegs: 2 + - type: CP14AuraImprint + imprintColor: "#31cc3b" - type: Bloodstream bloodReagent: CP14BloodFlowerSap #Lol - type: Inventory diff --git a/Resources/Prototypes/_CP14/Entities/Mobs/Species/skeleton.yml b/Resources/Prototypes/_CP14/Entities/Mobs/Species/skeleton.yml index becd0b64d2..bc76715b6c 100644 --- a/Resources/Prototypes/_CP14/Entities/Mobs/Species/skeleton.yml +++ b/Resources/Prototypes/_CP14/Entities/Mobs/Species/skeleton.yml @@ -88,6 +88,8 @@ - MobMask layer: - MobLayer + - type: CP14AuraImprint + imprintColor: "#a81b5d" - type: Damageable damageContainer: CP14Biological damageModifierSet: CP14Skeleton diff --git a/Resources/Prototypes/_CP14/Entities/Mobs/Species/tiefling.yml b/Resources/Prototypes/_CP14/Entities/Mobs/Species/tiefling.yml index 6761754113..0d4ba160ba 100644 --- a/Resources/Prototypes/_CP14/Entities/Mobs/Species/tiefling.yml +++ b/Resources/Prototypes/_CP14/Entities/Mobs/Species/tiefling.yml @@ -44,6 +44,8 @@ damage: Heat: 1 #Magic regen from fire Cold: -1 + - type: CP14AuraImprint + imprintColor: "#e0762b" - type: CP14SkillStorage #Innate pyrokinetic freeLearnedSkills: - PyrokineticT1 diff --git a/Resources/Prototypes/_CP14/Shaders/shaders.yml b/Resources/Prototypes/_CP14/Shaders/shaders.yml index abe9eaf7b2..05142d28d8 100644 --- a/Resources/Prototypes/_CP14/Shaders/shaders.yml +++ b/Resources/Prototypes/_CP14/Shaders/shaders.yml @@ -23,4 +23,10 @@ - type: shader id: CP14ReligionVision kind: source - path: "/Textures/_CP14/Shaders/religion.swsl" \ No newline at end of file + path: "/Textures/_CP14/Shaders/religion.swsl" + + +- type: shader + id: CP14BlueNoir + kind: source + path: "/Textures/_CP14/Shaders/blue_noir.swsl" \ No newline at end of file diff --git a/Resources/Prototypes/_CP14/Skill/Basic/metamagic.yml b/Resources/Prototypes/_CP14/Skill/Basic/metamagic.yml index 09162fa08e..6d6457b3d6 100644 --- a/Resources/Prototypes/_CP14/Skill/Basic/metamagic.yml +++ b/Resources/Prototypes/_CP14/Skill/Basic/metamagic.yml @@ -138,6 +138,20 @@ - !type:NeedPrerequisite prerequisite: MetamagicT2 +- type: cp14Skill + id: CP14ActionToggleMagicVision + skillUiPosition: 6, 4 + tree: Metamagic + icon: + sprite: _CP14/Actions/Spells/meta.rsi + state: magic_vision + effects: + - !type:AddAction + action: CP14ActionToggleMagicVision + restrictions: + - !type:NeedPrerequisite + prerequisite: MetamagicT2 + # T3 - type: cp14Skill diff --git a/Resources/Textures/_CP14/Actions/Spells/meta.rsi/magic_vision.png b/Resources/Textures/_CP14/Actions/Spells/meta.rsi/magic_vision.png new file mode 100644 index 0000000000..638188faa1 Binary files /dev/null and b/Resources/Textures/_CP14/Actions/Spells/meta.rsi/magic_vision.png differ diff --git a/Resources/Textures/_CP14/Actions/Spells/meta.rsi/meta.json b/Resources/Textures/_CP14/Actions/Spells/meta.rsi/meta.json index 5f3ba98223..6f90d15b7a 100644 --- a/Resources/Textures/_CP14/Actions/Spells/meta.rsi/meta.json +++ b/Resources/Textures/_CP14/Actions/Spells/meta.rsi/meta.json @@ -24,6 +24,9 @@ }, { "name": "mana_trance" + }, + { + "name": "magic_vision" } ] } \ No newline at end of file diff --git a/Resources/Textures/_CP14/Actions/Spells/misc.rsi/meta.json b/Resources/Textures/_CP14/Actions/Spells/misc.rsi/meta.json index 1c0e5d594e..692d3c9e5e 100644 --- a/Resources/Textures/_CP14/Actions/Spells/misc.rsi/meta.json +++ b/Resources/Textures/_CP14/Actions/Spells/misc.rsi/meta.json @@ -12,6 +12,12 @@ }, { "name": "polymorph" + }, + { + "name": "skull" + }, + { + "name": "skull_red" } ] } \ No newline at end of file diff --git a/Resources/Textures/_CP14/Actions/Spells/misc.rsi/skull.png b/Resources/Textures/_CP14/Actions/Spells/misc.rsi/skull.png new file mode 100644 index 0000000000..38b5594566 Binary files /dev/null and b/Resources/Textures/_CP14/Actions/Spells/misc.rsi/skull.png differ diff --git a/Resources/Textures/_CP14/Actions/Spells/misc.rsi/skull_red.png b/Resources/Textures/_CP14/Actions/Spells/misc.rsi/skull_red.png new file mode 100644 index 0000000000..a5823d06e2 Binary files /dev/null and b/Resources/Textures/_CP14/Actions/Spells/misc.rsi/skull_red.png differ diff --git a/Resources/Textures/_CP14/Effects/Magic/pointer_sphere.rsi/meta.json b/Resources/Textures/_CP14/Effects/Magic/pointer_sphere.rsi/meta.json new file mode 100644 index 0000000000..8deee3c1c6 --- /dev/null +++ b/Resources/Textures/_CP14/Effects/Magic/pointer_sphere.rsi/meta.json @@ -0,0 +1,14 @@ +{ + "version": 1, + "size": { + "x": 32, + "y": 32 + }, + "license": "CC-BY-SA-4.0", + "copyright": "Created by TheShuEd", + "states": [ + { + "name": "sphere" + } + ] +} diff --git a/Resources/Textures/_CP14/Effects/Magic/pointer_sphere.rsi/sphere.png b/Resources/Textures/_CP14/Effects/Magic/pointer_sphere.rsi/sphere.png new file mode 100644 index 0000000000..5f669730c5 Binary files /dev/null and b/Resources/Textures/_CP14/Effects/Magic/pointer_sphere.rsi/sphere.png differ diff --git a/Resources/Textures/_CP14/Shaders/blue_noir.swsl b/Resources/Textures/_CP14/Shaders/blue_noir.swsl new file mode 100644 index 0000000000..6d04b4e057 --- /dev/null +++ b/Resources/Textures/_CP14/Shaders/blue_noir.swsl @@ -0,0 +1,43 @@ +uniform sampler2D SCREEN_TEXTURE; + +const highp float BlueHue = 210.0; +const highp float HueRange = 80.0; +const highp float MinSaturation = 0.1; + +// https://en.wikipedia.org/wiki/HSL_and_HSV#From_RGB +highp vec3 rgb2hsv(highp vec3 c) { + highp float xMax = max(c.r, max(c.g, c.b)); + highp float xMin = min(c.r, min(c.g, c.b)); + highp float delta = xMax - xMin; + + highp float hue = 0.0; + if (delta > 0.0) { + if (xMax == c.r) { + hue = mod((c.g - c.b) / delta, 6.0); + } else if (xMax == c.g) { + hue = (c.b - c.r) / delta + 2.0; + } else { + hue = (c.r - c.g) / delta + 4.0; + } + hue *= 60.0; + if (hue < 0.0) hue += 360.0; + } + + highp float sat = (xMax == 0.0) ? 0.0 : delta / xMax; + return vec3(hue, sat, xMax); +} + +void fragment() { + highp vec4 color = zTextureSpec(SCREEN_TEXTURE, UV); + highp vec3 gray = vec3(zGrayscale(color.rgb)); + highp vec3 hsv = rgb2hsv(color.rgb); + + bool is_blue = hsv.x > (BlueHue - HueRange) && hsv.x < (BlueHue + HueRange); + bool saturated_enough = hsv.y > MinSaturation; + + if (is_blue && saturated_enough) { + COLOR = color; + } else { + COLOR = vec4(gray, color.a); + } +}