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 Content.Shared.Speech; 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 _godQuery; /// /// 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. /// /// Maybe there is a variable for the distance outside the screen in PVS, I don't know. This number works best private const float ObservationOverrideRadius = 6.5f; public override void Initialize() { base.Initialize(); InitializeUI(); _godQuery = GetEntityQuery(); SubscribeLocalEvent(OnObserverInit); SubscribeLocalEvent(OnObserverHandleState); SubscribeLocalEvent(OnGodInit); SubscribeLocalEvent(OnGodShutdown); SubscribeLocalEvent(OnPlayerAttached); SubscribeLocalEvent(OnPlayerDetached); SubscribeLocalEvent(OnSpokeAttempt); SubscribeLocalEvent(OnExpandRecipients); SubscribeLocalEvent(OnListen); SubscribeLocalEvent(OnGetVis); } private void OnGetVis(Entity 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(); 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 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 ent, ref AfterAutoHandleStateEvent args) { var query = EntityQueryEnumerator(); while (query.MoveNext(out var uid, out var god)) { UpdatePvsOverrides(new Entity(uid, god)); } } private void OnObserverInit(Entity 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 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) { AddPvsOverrides(ent); } private void OnPlayerDetached(Entity ent, ref PlayerDetachedEvent args) { RemovePvsOverrides(ent); } private void OnListen(Entity 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 religion, string msg, EntityUid source) { var gods = GetGods(religion); HashSet channels = new(); foreach (var god in gods) { if (!TryComp(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 god) { FixedPoint2 total = 0; FixedPoint2 followers = 0; var allHumans = Mind.GetAliveHumans(); foreach (var human in allHumans) { total += 1; if (!TryComp(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 ent) { if (ent.Comp.Religion is null) return; if (!TryComp(ent, out var actor)) return; ent.Comp.Session = actor.PlayerSession; var query = EntityQueryEnumerator(); 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 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 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; }