Files
crystall-punk-14/Content.Server/_CP14/Religion/CP14ReligionSystem.cs
Red 81e044758f Magical vision skill (#1467)
* fix firewave error spamming

* basic magic traces entities

* Add magical vision system and mana trace entities

Introduces the magical vision mechanic, including the CP14MagicVisionComponent, marker entities, and related systems for tracking and displaying magical traces. Adds new actions, skill integration, localization strings, and icons for magical vision and trace markers. Magic traces are now spawned on spell use and mob state changes, with directional pointers and localized descriptions.

* Show time passed for magic vision markers

Adds a display of the time elapsed since a magic vision marker was spawned, using a localized string. Updates English and Russian locale files with the new 'cp14-magic-vision-timed-past' entry.

* aura imprints

* Update critical and death messages for inclusivity

Revised the 'critical' and 'dead' messages in both English and Russian locale files to use more inclusive language, replacing references to 'magical creature' with 'someone'.

* Move magic vision spawn on mob state change to server

Transferred the logic for spawning magic vision markers on mob state changes (Critical/Dead) from CP14SharedMagicVisionSystem to CP14AuraImprintSystem. This centralizes the event handling on the server side. Also increased the duration of magic vision markers for spell usage from 20 to 50 seconds.

* Integrate magic vision with visibility mask system

Added a CP14MagicVision flag to VisibilityFlags and updated the magic vision and religion systems to use the visibility mask system. Magic vision markers now use the Visibility component, and visibility is refreshed when relevant components are added or removed. Removed client-side toggling logic in favor of server-driven visibility.

* drowsiness overlay

* noir shader

* sfx design
2025-06-26 13:49:53 +03:00

299 lines
9.8 KiB
C#

using Content.Server._CP14.MagicEnergy;
using Content.Server.Chat.Managers;
using Content.Server.Chat.Systems;
using Content.Server.Speech;
using Content.Shared._CP14.MagicEnergy.Components;
using Content.Shared._CP14.Religion.Components;
using Content.Shared._CP14.Religion.Prototypes;
using Content.Shared._CP14.Religion.Systems;
using Content.Shared.Chat;
using Content.Shared.Eye;
using Content.Shared.FixedPoint;
using Robust.Server.GameObjects;
using Robust.Server.GameStates;
using Robust.Shared.Network;
using Robust.Shared.Player;
using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
namespace Content.Server._CP14.Religion;
public sealed partial class CP14ReligionGodSystem : CP14SharedReligionGodSystem
{
[Dependency] private readonly IChatManager _chat = default!;
[Dependency] private readonly ChatSystem _chatSys = default!;
[Dependency] private readonly PvsOverrideSystem _pvs = default!;
[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<CP14ReligionEntityComponent> _godQuery;
/// <summary>
/// If ReligionObserver receives a radius higher than this value, this entity will automatically be placed in PvsOverride for the god in order to function correctly outside of the player's PVS.
/// </summary>
/// <remarks> Maybe there is a variable for the distance outside the screen in PVS, I don't know. This number works best</remarks>
private const float ObservationOverrideRadius = 6.5f;
public override void Initialize()
{
base.Initialize();
InitializeUI();
_godQuery = GetEntityQuery<CP14ReligionEntityComponent>();
SubscribeLocalEvent<CP14ReligionObserverComponent, ComponentInit>(OnObserverInit);
SubscribeLocalEvent<CP14ReligionObserverComponent, AfterAutoHandleStateEvent>(OnObserverHandleState);
SubscribeLocalEvent<CP14ReligionEntityComponent, ComponentInit>(OnGodInit);
SubscribeLocalEvent<CP14ReligionEntityComponent, ComponentShutdown>(OnGodShutdown);
SubscribeLocalEvent<CP14ReligionEntityComponent, PlayerAttachedEvent>(OnPlayerAttached);
SubscribeLocalEvent<CP14ReligionEntityComponent, PlayerDetachedEvent>(OnPlayerDetached);
SubscribeLocalEvent<CP14ReligionSpeakerComponent, CP14SpokeAttemptEvent>(OnSpokeAttempt);
SubscribeLocalEvent<ExpandICChatRecipientsEvent>(OnExpandRecipients);
SubscribeLocalEvent<CP14ReligionAltarComponent, ListenEvent>(OnListen);
SubscribeLocalEvent<CP14ReligionEntityComponent, GetVisMaskEvent>(OnGetVis);
}
private void OnGetVis(Entity<CP14ReligionEntityComponent> ent, ref GetVisMaskEvent args)
{
args.VisibilityMask |= (int)VisibilityFlags.Ghost;
}
private void OnExpandRecipients(ExpandICChatRecipientsEvent ev)
{
foreach (var recipient in ev.Recipients)
{
var recipientEntity = recipient.Key.AttachedEntity;
if (!_godQuery.TryComp(recipientEntity, out var god) || god.Religion is null)
continue;
if (!InVision(ev.Source, (recipientEntity.Value, god)))
{
// If the recipient is not in vision, we don't want to send them the message.
ev.Recipients.Remove(recipient.Key);
}
}
}
public override void Update(float frameTime)
{
base.Update(frameTime);
var query = EntityQueryEnumerator<CP14ReligionFollowerComponent, CP14MagicEnergyContainerComponent>();
while (query.MoveNext(out var uid, out var follower, out var energy))
{
if (follower.NextUpdateTime >= _gameTiming.CurTime)
continue;
if (follower.Religion is null)
continue;
follower.NextUpdateTime = _gameTiming.CurTime + TimeSpan.FromSeconds(follower.ManaTransferDelay);
foreach (var god in GetGods(follower.Religion.Value))
{
_magicEnergy.TransferEnergy((uid, energy),
god.Owner,
follower.EnergyToGodTransfer,
out _,
out _,
safe: true);
}
}
}
private void OnSpokeAttempt(Entity<CP14ReligionSpeakerComponent> ent, ref CP14SpokeAttemptEvent args)
{
args.Cancel();
if (!_godQuery.TryComp(ent, out var god) || god.Religion is null)
return;
if (ent.Comp.RestrictedReligionZone && !InVision(ent, (ent, god)))
return;
_magicEnergy.ChangeEnergy(ent.Owner, -ent.Comp.ManaCost, out _, out _);
var speaker = Spawn(ent.Comp.Speaker);
_transform.DropNextTo(speaker, ent.Owner);
var message = args.Message;
var type = args.Type;
Timer.Spawn(333,
() =>
{
_chatSys.TrySendInGameICMessage(speaker,
message,
type,
ChatTransmitRange.Normal,
nameOverride: MetaData(ent).EntityName,
ignoreActionBlocker: true);
});
}
private void OnObserverHandleState(Entity<CP14ReligionObserverComponent> ent, ref AfterAutoHandleStateEvent args)
{
var query = EntityQueryEnumerator<CP14ReligionEntityComponent>();
while (query.MoveNext(out var uid, out var god))
{
UpdatePvsOverrides(new Entity<CP14ReligionEntityComponent>(uid, god));
}
}
private void OnObserverInit(Entity<CP14ReligionObserverComponent> ent, ref ComponentInit args)
{
if (ent.Comp.Religion is null)
return;
var gods = GetGods(ent.Comp.Religion.Value);
foreach (var god in gods)
{
UpdatePvsOverrides(god);
}
}
private void OnGodInit(Entity<CP14ReligionEntityComponent> ent, ref ComponentInit args)
{
AddPvsOverrides(ent);
_eye.RefreshVisibilityMask(ent.Owner);
}
private void OnGodShutdown(Entity<CP14ReligionEntityComponent> ent, ref ComponentShutdown args)
{
RemovePvsOverrides(ent);
_eye.RefreshVisibilityMask(ent.Owner);
}
private void OnPlayerAttached(Entity<CP14ReligionEntityComponent> ent, ref PlayerAttachedEvent args)
{
AddPvsOverrides(ent);
}
private void OnPlayerDetached(Entity<CP14ReligionEntityComponent> ent, ref PlayerDetachedEvent args)
{
RemovePvsOverrides(ent);
}
private void OnListen(Entity<CP14ReligionAltarComponent> ent, ref ListenEvent args)
{
if (ent.Comp.Religion is null)
return;
var wrappedMessage =
Loc.GetString("cp14-altar-wrapped-message",
("name", MetaData(args.Source).EntityName),
("msg", args.Message));
SendMessageToGods(ent.Comp.Religion.Value, wrappedMessage, args.Source);
}
public override void SendMessageToGods(ProtoId<CP14ReligionPrototype> religion, string msg, EntityUid source)
{
var gods = GetGods(religion);
HashSet<INetChannel> channels = new();
foreach (var god in gods)
{
if (!TryComp<ActorComponent>(god, out var godActor))
continue;
channels.Add(godActor.PlayerSession.Channel);
}
_chat.ChatMessageToMany(ChatChannel.Notifications,
msg,
msg,
source,
false,
true,
channels,
colorOverride: Color.Aqua);
}
public FixedPoint2 GetFollowerPercentage(Entity<CP14ReligionEntityComponent> god)
{
FixedPoint2 total = 0;
FixedPoint2 followers = 0;
var allHumans = Mind.GetAliveHumans();
foreach (var human in allHumans)
{
total += 1;
if (!TryComp<CP14ReligionFollowerComponent>(human.Comp.CurrentEntity, out var relFollower))
continue;
if (relFollower.Religion != god.Comp.Religion)
continue;
followers += 1;
}
if (total == 0)
return 0f;
return followers / total;
}
private void AddPvsOverrides(Entity<CP14ReligionEntityComponent> ent)
{
if (ent.Comp.Religion is null)
return;
if (!TryComp<ActorComponent>(ent, out var actor))
return;
ent.Comp.Session = actor.PlayerSession;
var query = EntityQueryEnumerator<CP14ReligionObserverComponent>();
while (query.MoveNext(out var uid, out var observer))
{
if (!observer.Active)
continue;
if (observer.Radius <= ObservationOverrideRadius)
continue;
ent.Comp.PvsOverridedObservers.Add(uid);
_pvs.AddSessionOverride(uid, actor.PlayerSession);
}
}
private void RemovePvsOverrides(Entity<CP14ReligionEntityComponent> ent)
{
if (ent.Comp.Religion is null)
return;
if (ent.Comp.Session is null)
return;
foreach (var observer in ent.Comp.PvsOverridedObservers)
{
_pvs.RemoveSessionOverride(observer, ent.Comp.Session);
}
ent.Comp.Session = null;
ent.Comp.PvsOverridedObservers.Clear();
}
private void UpdatePvsOverrides(Entity<CP14ReligionEntityComponent> ent)
{
if (ent.Comp.Session is null)
return;
RemovePvsOverrides(ent);
AddPvsOverrides(ent);
}
}
public sealed class CP14SpokeAttemptEvent(string message, InGameICChatType type, ICommonSession? player)
: CancellableEntityEventArgs
{
public string Message = message;
public InGameICChatType Type = type;
public ICommonSession? Player = player;
}