241 lines
11 KiB
C#
241 lines
11 KiB
C#
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<StatusEffectPrototype> EntropicDegen = "EntropicDegen";
|
|
private static readonly ProtoId<RadioChannelPrototype> CosmicRadio = "CosmicRadio";
|
|
|
|
public override void Initialize()
|
|
{
|
|
base.Initialize();
|
|
|
|
SubscribeLocalEvent<RoundStartingEvent>(OnRoundStart);
|
|
|
|
SubscribeLocalEvent<CosmicCultComponent, ComponentInit>(OnStartCultist);
|
|
SubscribeLocalEvent<CosmicCultLeadComponent, ComponentInit>(OnStartCultLead);
|
|
SubscribeLocalEvent<CosmicCultComponent, GetVisMaskEvent>(OnGetVisMask);
|
|
|
|
SubscribeLocalEvent<CosmicEquipmentComponent, GotEquippedEvent>(OnGotEquipped);
|
|
SubscribeLocalEvent<CosmicEquipmentComponent, GotUnequippedEvent>(OnGotUnequipped);
|
|
SubscribeLocalEvent<CosmicEquipmentComponent, GotEquippedHandEvent>(OnGotHeld);
|
|
SubscribeLocalEvent<CosmicEquipmentComponent, GotUnequippedHandEvent>(OnGotUnheld);
|
|
|
|
SubscribeLocalEvent<InfluenceStrideComponent, ComponentInit>(OnStartInfluenceStride);
|
|
SubscribeLocalEvent<InfluenceStrideComponent, ComponentRemove>(OnEndInfluenceStride);
|
|
SubscribeLocalEvent<InfluenceStrideComponent, RefreshMovementSpeedModifiersEvent>(OnRefreshMoveSpeed);
|
|
SubscribeLocalEvent<CosmicImposingComponent, ComponentInit>(OnStartImposition);
|
|
SubscribeLocalEvent<CosmicImposingComponent, ComponentRemove>(OnEndImposition);
|
|
SubscribeLocalEvent<CosmicImposingComponent, RefreshMovementSpeedModifiersEvent>(OnImpositionMoveSpeed);
|
|
|
|
SubscribeLocalEvent<CosmicCultExamineComponent, ExaminedEvent>(OnCosmicCultExamined);
|
|
|
|
SubscribeLocalEvent<CosmicSubtleMarkComponent, ExaminedEvent>(OnSubtleMarkExamined);
|
|
SubscribeLocalEvent<CosmicCultComponent, EncryptionChannelsChangedEvent>(OnTransmitterChannelsChangedCult, after: new[] { typeof(IntrinsicRadioKeySystem) });
|
|
|
|
SubscribeFinale(); //Hook up the cosmic cult finale system
|
|
}
|
|
|
|
public void MalignEcho(Entity<CosmicCultComponent> 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.
|
|
/// <summary>
|
|
/// Creates the Cosmic Void pocket dimension map.
|
|
/// </summary>
|
|
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<CosmicCultExamineComponent> ent, ref ExaminedEvent args)
|
|
{
|
|
args.PushMarkup(Loc.GetString(EntitySeesCult(args.Examiner) ? ent.Comp.CultistText : ent.Comp.OthersText));
|
|
}
|
|
|
|
private void OnSubtleMarkExamined(Entity<CosmicSubtleMarkComponent> 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
|
|
/// <summary>
|
|
/// Add the starting powers to the cultist.
|
|
/// </summary>
|
|
private void OnStartCultist(Entity<CosmicCultComponent> 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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Add the Monument summon action to the cult lead.
|
|
/// </summary>
|
|
private void OnStartCultLead(Entity<CosmicCultLeadComponent> uid, ref ComponentInit args)
|
|
{
|
|
_actions.AddAction(uid, ref uid.Comp.CosmicMonumentPlaceActionEntity, uid.Comp.CosmicMonumentPlaceAction, uid);
|
|
}
|
|
|
|
private void OnGetVisMask(Entity<CosmicCultComponent> uid, ref GetVisMaskEvent args)
|
|
{
|
|
args.VisibilityMask |= (int)VisibilityFlags.CosmicCultMonument;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Called by Cosmic Siphon. Increments the Cult's global objective tracker.
|
|
/// </summary>
|
|
#endregion
|
|
|
|
#region Equipment Pickup
|
|
private void OnGotEquipped(Entity<CosmicEquipmentComponent> ent, ref GotEquippedEvent args)
|
|
{
|
|
if (!EntityIsCultist(args.Equipee))
|
|
_statusEffects.TryAddStatusEffect<CosmicEntropyDebuffComponent>(args.Equipee, EntropicDegen, TimeSpan.FromDays(1), true); // TimeSpan.MaxValue causes a crash here, so we use FromDays(1) instead.
|
|
}
|
|
|
|
private void OnGotUnequipped(Entity<CosmicEquipmentComponent> ent, ref GotUnequippedEvent args)
|
|
{
|
|
if (!EntityIsCultist(args.Equipee))
|
|
_statusEffects.TryRemoveStatusEffect(args.Equipee, EntropicDegen);
|
|
}
|
|
private void OnGotHeld(Entity<CosmicEquipmentComponent> ent, ref GotEquippedHandEvent args)
|
|
{
|
|
if (!EntityIsCultist(args.User))
|
|
{
|
|
_statusEffects.TryAddStatusEffect<CosmicEntropyDebuffComponent>(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<CosmicEquipmentComponent> ent, ref GotUnequippedHandEvent args)
|
|
{
|
|
if (!EntityIsCultist(args.User))
|
|
_statusEffects.TryRemoveStatusEffect(args.User, EntropicDegen);
|
|
}
|
|
#endregion
|
|
|
|
#region Movespeed
|
|
private void OnStartInfluenceStride(Entity<InfluenceStrideComponent> uid, ref ComponentInit args) // i wish movespeed was easier to work with
|
|
{
|
|
_movementSpeed.RefreshMovementSpeedModifiers(uid);
|
|
}
|
|
private void OnEndInfluenceStride(Entity<InfluenceStrideComponent> uid, ref ComponentRemove args) // that movespeed applies more-or-less correctly
|
|
{
|
|
_movementSpeed.RefreshMovementSpeedModifiers(uid);
|
|
}
|
|
private void OnStartImposition(Entity<CosmicImposingComponent> uid, ref ComponentInit args) // these functions just make sure
|
|
{
|
|
_movementSpeed.RefreshMovementSpeedModifiers(uid);
|
|
EnsureComp<CosmicCultExamineComponent>(uid).CultistText = "cosmic-examine-text-malignecho";
|
|
}
|
|
private void OnEndImposition(Entity<CosmicImposingComponent> uid, ref ComponentRemove args) // as various cosmic cult effects get added and removed
|
|
{
|
|
_movementSpeed.RefreshMovementSpeedModifiers(uid);
|
|
RemComp<CosmicCultExamineComponent>(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
|
|
|
|
/// <summary>
|
|
/// Edge Case to handle IPCs losing astral murmur after panel operations.
|
|
/// </summary>
|
|
private void OnTransmitterChannelsChangedCult(EntityUid uid, CosmicCultComponent component, EncryptionChannelsChangedEvent args)
|
|
{
|
|
if (!TryComp<IntrinsicRadioTransmitterComponent>(uid, out IntrinsicRadioTransmitterComponent? transmitter) || !TryComp<ActiveRadioComponent>(uid, out ActiveRadioComponent? activeRadio))
|
|
return;
|
|
|
|
if (transmitter.Channels.Contains(CosmicRadio) && activeRadio.Channels.Contains(CosmicRadio))
|
|
return;
|
|
|
|
transmitter.Channels.Add(CosmicRadio);
|
|
activeRadio.Channels.Add(CosmicRadio);
|
|
|
|
|
|
}
|
|
|
|
}
|