using Content.Server._DV.CosmicCult.EntitySystems; using Content.Server.Actions; using Content.Server.AlertLevel; using Content.Server.Audio; using Content.Server.Chat.Systems; using Content.Server.GameTicking.Events; using Content.Server.Pinpointer; using Content.Server.Popups; using Content.Server.Station.Systems; using Content.Shared._DV.CosmicCult.Components; using Content.Shared._DV.CosmicCult; using Content.Server._EE.Radio; using Content.Shared.Alert; using Content.Shared.DoAfter; using Content.Shared.Examine; using Content.Shared.Eye; using Content.Shared.Hands; using Content.Shared.Inventory.Events; using Content.Shared.Movement.Systems; using Content.Shared.StatusEffect; using Robust.Server.GameObjects; using Robust.Shared.Audio.Systems; using Robust.Shared.EntitySerialization.Systems; using Robust.Shared.EntitySerialization; using Robust.Shared.Prototypes; using Robust.Shared.Random; using Robust.Shared.Timing; using Robust.Shared.Utility; using Content.Shared.Popups; using Content.Shared.Radio; using Content.Server.Radio.Components; using Content.Shared.IdentityManagement.Components; namespace Content.Server._DV.CosmicCult; public sealed partial class CosmicCultSystem : SharedCosmicCultSystem { [Dependency] private readonly ActionsSystem _actions = default!; [Dependency] private readonly AlertLevelSystem _alert = default!; [Dependency] private readonly AlertsSystem _alerts = default!; [Dependency] private readonly AppearanceSystem _appearance = default!; [Dependency] private readonly ChatSystem _chatSystem = default!; [Dependency] private readonly CosmicCorruptingSystem _corrupting = default!; [Dependency] private readonly CosmicCultRuleSystem _cultRule = default!; [Dependency] private readonly IGameTiming _timing = default!; [Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly MapLoaderSystem _mapLoader = default!; [Dependency] private readonly MonumentSystem _monument = default!; [Dependency] private readonly MovementSpeedModifierSystem _movementSpeed = default!; [Dependency] private readonly NavMapSystem _navMap = default!; [Dependency] private readonly PopupSystem _popup = default!; [Dependency] private readonly ServerGlobalSoundSystem _sound = default!; [Dependency] private readonly SharedAudioSystem _audio = default!; [Dependency] private readonly SharedDoAfterSystem _doAfter = default!; [Dependency] private readonly SharedEyeSystem _eye = default!; [Dependency] private readonly SharedMapSystem _map = default!; [Dependency] private readonly SharedUserInterfaceSystem _ui = default!; [Dependency] private readonly StationSystem _station = default!; [Dependency] private readonly StatusEffectsSystem _statusEffects = default!; private readonly ResPath _mapPath = new("Maps/_DV/Nonstations/cosmicvoid.yml"); private static readonly EntProtoId CosmicEchoVfx = "CosmicEchoVfx"; private static readonly ProtoId EntropicDegen = "EntropicDegen"; private static readonly ProtoId CosmicRadio = "CosmicRadio"; public override void Initialize() { base.Initialize(); SubscribeLocalEvent(OnRoundStart); SubscribeLocalEvent(OnStartCultist); SubscribeLocalEvent(OnStartCultLead); SubscribeLocalEvent(OnGetVisMask); SubscribeLocalEvent(OnGotEquipped); SubscribeLocalEvent(OnGotUnequipped); SubscribeLocalEvent(OnGotHeld); SubscribeLocalEvent(OnGotUnheld); SubscribeLocalEvent(OnStartInfluenceStride); SubscribeLocalEvent(OnEndInfluenceStride); SubscribeLocalEvent(OnRefreshMoveSpeed); SubscribeLocalEvent(OnStartImposition); SubscribeLocalEvent(OnEndImposition); SubscribeLocalEvent(OnImpositionMoveSpeed); SubscribeLocalEvent(OnCosmicCultExamined); SubscribeLocalEvent(OnSubtleMarkExamined); SubscribeLocalEvent(OnTransmitterChannelsChangedCult, after: new[] { typeof(IntrinsicRadioKeySystem) }); SubscribeFinale(); //Hook up the cosmic cult finale system } public void MalignEcho(Entity uid) { if (_cultRule.AssociatedGamerule(uid) is not { } cult) return; if (cult.Comp.CurrentTier > 1 && !_random.Prob(0.5f)) Spawn(CosmicEchoVfx, Transform(uid).Coordinates); } #region Housekeeping // Rogue Ascendants use this too, which are generalized MidRoundAntags, so we keep the map around. If you're porting cosmic cult, and do not want rogue ascendants, feel free to move this into selective usage akin to NukeOps base. /// /// Creates the Cosmic Void pocket dimension map. /// private void OnRoundStart(RoundStartingEvent ev) { if (_mapLoader.TryLoadMap(_mapPath, out var map, out _, new DeserializationOptions { InitializeMaps = true })) _map.SetPaused(map.Value.Comp.MapId, false); } private void OnCosmicCultExamined(Entity ent, ref ExaminedEvent args) { args.PushMarkup(Loc.GetString(EntitySeesCult(args.Examiner) ? ent.Comp.CultistText : ent.Comp.OthersText)); } private void OnSubtleMarkExamined(Entity ent, ref ExaminedEvent args) { var ev = new SeeIdentityAttemptEvent(); RaiseLocalEvent(ent, ev); if (ev.TotalCoverage.HasFlag(IdentityBlockerCoverage.EYES)) return; args.PushMarkup(Loc.GetString(ent.Comp.ExamineText)); } #endregion #region Init Cult /// /// Add the starting powers to the cultist. /// private void OnStartCultist(Entity uid, ref ComponentInit args) { foreach (var actionId in uid.Comp.CosmicCultActions) { var actionEnt = _actions.AddAction(uid, actionId); uid.Comp.ActionEntities.Add(actionEnt); } _eye.RefreshVisibilityMask(uid.Owner); _alerts.ShowAlert(uid, uid.Comp.EntropyAlert); } /// /// Add the Monument summon action to the cult lead. /// private void OnStartCultLead(Entity uid, ref ComponentInit args) { _actions.AddAction(uid, ref uid.Comp.CosmicMonumentPlaceActionEntity, uid.Comp.CosmicMonumentPlaceAction, uid); } private void OnGetVisMask(Entity uid, ref GetVisMaskEvent args) { args.VisibilityMask |= (int)VisibilityFlags.CosmicCultMonument; } /// /// Called by Cosmic Siphon. Increments the Cult's global objective tracker. /// #endregion #region Equipment Pickup private void OnGotEquipped(Entity ent, ref GotEquippedEvent args) { if (!EntityIsCultist(args.Equipee)) _statusEffects.TryAddStatusEffect(args.Equipee, EntropicDegen, TimeSpan.FromDays(1), true); // TimeSpan.MaxValue causes a crash here, so we use FromDays(1) instead. } private void OnGotUnequipped(Entity ent, ref GotUnequippedEvent args) { if (!EntityIsCultist(args.Equipee)) _statusEffects.TryRemoveStatusEffect(args.Equipee, EntropicDegen); } private void OnGotHeld(Entity ent, ref GotEquippedHandEvent args) { if (!EntityIsCultist(args.User)) { _statusEffects.TryAddStatusEffect(args.User, EntropicDegen, TimeSpan.FromDays(1), true); _popup.PopupEntity(Loc.GetString("cosmiccult-gear-pickup", ("ITEM", args.Equipped)), args.User, args.User, PopupType.MediumCaution); } } private void OnGotUnheld(Entity ent, ref GotUnequippedHandEvent args) { if (!EntityIsCultist(args.User)) _statusEffects.TryRemoveStatusEffect(args.User, EntropicDegen); } #endregion #region Movespeed private void OnStartInfluenceStride(Entity uid, ref ComponentInit args) // i wish movespeed was easier to work with { _movementSpeed.RefreshMovementSpeedModifiers(uid); } private void OnEndInfluenceStride(Entity uid, ref ComponentRemove args) // that movespeed applies more-or-less correctly { _movementSpeed.RefreshMovementSpeedModifiers(uid); } private void OnStartImposition(Entity uid, ref ComponentInit args) // these functions just make sure { _movementSpeed.RefreshMovementSpeedModifiers(uid); EnsureComp(uid).CultistText = "cosmic-examine-text-malignecho"; } private void OnEndImposition(Entity uid, ref ComponentRemove args) // as various cosmic cult effects get added and removed { _movementSpeed.RefreshMovementSpeedModifiers(uid); RemComp(uid); } private void OnRefreshMoveSpeed(EntityUid uid, InfluenceStrideComponent comp, RefreshMovementSpeedModifiersEvent args) { args.ModifySpeed(1.1f, 1.1f); } private void OnImpositionMoveSpeed(EntityUid uid, CosmicImposingComponent comp, RefreshMovementSpeedModifiersEvent args) { args.ModifySpeed(0.65f, 0.65f); } #endregion /// /// Edge Case to handle IPCs losing astral murmur after panel operations. /// private void OnTransmitterChannelsChangedCult(EntityUid uid, CosmicCultComponent component, EncryptionChannelsChangedEvent args) { if (!TryComp(uid, out IntrinsicRadioTransmitterComponent? transmitter) || !TryComp(uid, out ActiveRadioComponent? activeRadio)) return; if (transmitter.Channels.Contains(CosmicRadio) && activeRadio.Channels.Contains(CosmicRadio)) return; transmitter.Channels.Add(CosmicRadio); activeRadio.Channels.Add(CosmicRadio); } }