The Cosmic Cult (#3288)
* asset upload * fixes * yml * guidebook, alert * yml fixes * yml you silly goose * yml and ft and just one tiny tiny cs * wtf is git doing i swear i already comitted these exact files * i can't believe it took 16 minutes to fail an enum * test fails begone. please. * ccvar namespace correction * ftl changes, namespace fixes again * wow actiongrant yml conflicts make me angrier than god * razor weapon dev * some requested changes * more requested changes * step one of who knows how many requested changes * grabbag of bugfixes * big refactorenning * the refactorings continue * bugfixes, some timering * ability system feedbacks * finish detimering * she feed on my back til i loop * ough * linq... * unity * mrrrp * meow * todo * mrp * woe rider mass cleanup be upon ye * ough * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Revert "woe rider mass cleanup be upon ye" This reverts commit 5e24490a66389c78efe969d16e43a7c5d52c1249. * 3 of 9 * omegadictionary begone * centralize is cosmic cult/sees cosmic cult checks * durations * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * florbular Co-authored-by: Tobias Berger <toby@tobot.dev> Signed-off-by: pathetic meowmeow <uhhadd@gmail.com> * feedback is stored in the git * yaml warrior * test fail real * dont specify the default --------- Signed-off-by: pathetic meowmeow <uhhadd@gmail.com> Co-authored-by: Janet Blackquill <uhhadd@gmail.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Tobias Berger <toby@tobot.dev>
This commit is contained in:
parent
1f7538bf21
commit
dad088edcb
|
|
@ -0,0 +1,189 @@
|
|||
using Content.Shared._DV.CosmicCult.Components;
|
||||
using Content.Shared._DV.CosmicCult;
|
||||
using Content.Shared.StatusIcon.Components;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Shared.Utility;
|
||||
using Content.Shared._DV.CosmicCult.Components.Examine;
|
||||
using System.Numerics;
|
||||
using Timer = Robust.Shared.Timing.Timer;
|
||||
using Robust.Client.Audio;
|
||||
using Robust.Shared.Audio;
|
||||
using Content.Client.Alerts;
|
||||
using Content.Client.UserInterface.Systems.Alerts.Controls;
|
||||
|
||||
namespace Content.Client._DV.CosmicCult;
|
||||
|
||||
public sealed partial class CosmicCultSystem : SharedCosmicCultSystem
|
||||
{
|
||||
[Dependency] private readonly AudioSystem _audio = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototype = default!;
|
||||
|
||||
private readonly ResPath _rsiPath = new("/Textures/_DV/CosmicCult/Effects/ability_siphonvfx.rsi");
|
||||
|
||||
private readonly SoundSpecifier _siphonSFX = new SoundPathSpecifier("/Audio/_DV/CosmicCult/ability_siphon.ogg");
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<RogueAscendedInfectionComponent, ComponentStartup>(OnAscendedInfectionAdded);
|
||||
SubscribeLocalEvent<RogueAscendedInfectionComponent, ComponentShutdown>(OnAscendedInfectionRemoved);
|
||||
|
||||
SubscribeLocalEvent<RogueAscendedAuraComponent, ComponentStartup>(OnAscendedAuraAdded);
|
||||
SubscribeLocalEvent<RogueAscendedAuraComponent, ComponentShutdown>(OnAscendedAuraRemoved);
|
||||
|
||||
SubscribeLocalEvent<CosmicStarMarkComponent, ComponentStartup>(OnCosmicStarMarkAdded);
|
||||
SubscribeLocalEvent<CosmicStarMarkComponent, ComponentShutdown>(OnCosmicStarMarkRemoved);
|
||||
|
||||
SubscribeLocalEvent<CosmicImposingComponent, ComponentStartup>(OnCosmicImpositionAdded);
|
||||
SubscribeLocalEvent<CosmicImposingComponent, ComponentShutdown>(OnCosmicImpositionRemoved);
|
||||
|
||||
SubscribeLocalEvent<CosmicCultComponent, GetStatusIconsEvent>(GetCosmicCultIcon);
|
||||
SubscribeLocalEvent<CosmicCultLeadComponent, GetStatusIconsEvent>(GetCosmicCultLeadIcon);
|
||||
SubscribeLocalEvent<CosmicBlankComponent, GetStatusIconsEvent>(GetCosmicSSDIcon);
|
||||
|
||||
SubscribeNetworkEvent<CosmicSiphonIndicatorEvent>(OnSiphon);
|
||||
SubscribeLocalEvent<CosmicCultComponent, UpdateAlertSpriteEvent>(OnUpdateAlert);
|
||||
}
|
||||
|
||||
#region Siphon Visuals
|
||||
private void OnSiphon(CosmicSiphonIndicatorEvent args)
|
||||
{
|
||||
var ent = GetEntity(args.Target);
|
||||
if (!TryComp<SpriteComponent>(ent, out var sprite))
|
||||
return;
|
||||
var layer = sprite.AddLayer(new SpriteSpecifier.Rsi(_rsiPath, "vfx"));
|
||||
sprite.LayerMapSet(CultSiphonedVisuals.Key, layer);
|
||||
sprite.LayerSetOffset(layer, new Vector2(0, 0.8f));
|
||||
sprite.LayerSetScale(layer, new Vector2(0.65f, 0.65f));
|
||||
sprite.LayerSetShader(layer, "unshaded");
|
||||
|
||||
Timer.Spawn(TimeSpan.FromSeconds(2), () => sprite.RemoveLayer(CultSiphonedVisuals.Key));
|
||||
_audio.PlayLocal(_siphonSFX, ent, ent, AudioParams.Default.WithVariation(0.1f));
|
||||
}
|
||||
|
||||
private void OnUpdateAlert(Entity<CosmicCultComponent> ent, ref UpdateAlertSpriteEvent args)
|
||||
{
|
||||
if (args.Alert.ID != ent.Comp.EntropyAlert)
|
||||
return;
|
||||
var entropy = Math.Clamp(ent.Comp.EntropyStored, 0, 14);
|
||||
var sprite = args.SpriteViewEnt.Comp;
|
||||
sprite.LayerSetState(AlertVisualLayers.Base, $"base{entropy}");
|
||||
sprite.LayerSetState(CultAlertVisualLayers.Counter, $"num{entropy}");
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Layer Additions
|
||||
private void OnAscendedInfectionAdded(Entity<RogueAscendedInfectionComponent> uid, ref ComponentStartup args)
|
||||
{
|
||||
if (!TryComp<SpriteComponent>(uid, out var sprite) || sprite.LayerMapTryGet(AscendedInfectionKey.Key, out _))
|
||||
return;
|
||||
|
||||
var layer = sprite.AddLayer(uid.Comp.Sprite);
|
||||
|
||||
sprite.LayerMapSet(AscendedInfectionKey.Key, layer);
|
||||
sprite.LayerSetShader(layer, "unshaded");
|
||||
}
|
||||
|
||||
private void OnAscendedAuraAdded(Entity<RogueAscendedAuraComponent> uid, ref ComponentStartup args)
|
||||
{
|
||||
if (!TryComp<SpriteComponent>(uid, out var sprite) || sprite.LayerMapTryGet(AscendedAuraKey.Key, out _))
|
||||
return;
|
||||
|
||||
var layer = sprite.AddLayer(uid.Comp.Sprite);
|
||||
|
||||
sprite.LayerMapSet(AscendedAuraKey.Key, layer);
|
||||
sprite.LayerSetShader(layer, "unshaded");
|
||||
}
|
||||
|
||||
private void OnCosmicStarMarkAdded(Entity<CosmicStarMarkComponent> uid, ref ComponentStartup args)
|
||||
{
|
||||
if (!TryComp<SpriteComponent>(uid, out var sprite) || sprite.LayerMapTryGet(CosmicRevealedKey.Key, out _))
|
||||
return;
|
||||
|
||||
var layer = sprite.AddLayer(uid.Comp.Sprite);
|
||||
sprite.LayerMapSet(CosmicRevealedKey.Key, layer);
|
||||
sprite.LayerSetShader(layer, "unshaded");
|
||||
|
||||
//offset the mark if the mob has an offset comp, needed for taller species like Thaven
|
||||
if (TryComp<CosmicStarMarkOffsetComponent>(uid, out var offset))
|
||||
{
|
||||
sprite.LayerSetOffset(CosmicRevealedKey.Key, offset.Offset);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnCosmicImpositionAdded(Entity<CosmicImposingComponent> uid, ref ComponentStartup args)
|
||||
{
|
||||
if (!TryComp<SpriteComponent>(uid, out var sprite) || sprite.LayerMapTryGet(CosmicImposingKey.Key, out _))
|
||||
return;
|
||||
|
||||
var layer = sprite.AddLayer(uid.Comp.Sprite);
|
||||
|
||||
sprite.LayerMapSet(CosmicImposingKey.Key, layer);
|
||||
sprite.LayerSetShader(layer, "unshaded");
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Layer Removals
|
||||
private void OnAscendedInfectionRemoved(Entity<RogueAscendedInfectionComponent> uid, ref ComponentShutdown args)
|
||||
{
|
||||
if (!TryComp<SpriteComponent>(uid, out var sprite))
|
||||
return;
|
||||
|
||||
sprite.RemoveLayer(AscendedInfectionKey.Key);
|
||||
}
|
||||
|
||||
private void OnAscendedAuraRemoved(Entity<RogueAscendedAuraComponent> uid, ref ComponentShutdown args)
|
||||
{
|
||||
if (!TryComp<SpriteComponent>(uid, out var sprite))
|
||||
return;
|
||||
|
||||
sprite.RemoveLayer(AscendedAuraKey.Key);
|
||||
}
|
||||
|
||||
private void OnCosmicStarMarkRemoved(Entity<CosmicStarMarkComponent> uid, ref ComponentShutdown args)
|
||||
{
|
||||
if (!TryComp<SpriteComponent>(uid, out var sprite))
|
||||
return;
|
||||
|
||||
sprite.RemoveLayer(CosmicRevealedKey.Key);
|
||||
}
|
||||
|
||||
private void OnCosmicImpositionRemoved(Entity<CosmicImposingComponent> uid, ref ComponentShutdown args)
|
||||
{
|
||||
if (!TryComp<SpriteComponent>(uid, out var sprite))
|
||||
return;
|
||||
|
||||
sprite.RemoveLayer(CosmicImposingKey.Key);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Icons
|
||||
private void GetCosmicCultIcon(Entity<CosmicCultComponent> ent, ref GetStatusIconsEvent args)
|
||||
{
|
||||
if (HasComp<CosmicCultLeadComponent>(ent))
|
||||
return;
|
||||
|
||||
if (_prototype.TryIndex(ent.Comp.StatusIcon, out var iconPrototype))
|
||||
args.StatusIcons.Add(iconPrototype);
|
||||
}
|
||||
|
||||
private void GetCosmicCultLeadIcon(Entity<CosmicCultLeadComponent> ent, ref GetStatusIconsEvent args)
|
||||
{
|
||||
if (_prototype.TryIndex(ent.Comp.StatusIcon, out var iconPrototype))
|
||||
args.StatusIcons.Add(iconPrototype);
|
||||
}
|
||||
|
||||
private void GetCosmicSSDIcon(Entity<CosmicBlankComponent> ent, ref GetStatusIconsEvent args)
|
||||
{
|
||||
if (_prototype.TryIndex(ent.Comp.StatusIcon, out var iconPrototype))
|
||||
args.StatusIcons.Add(iconPrototype);
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
|
||||
public enum CultSiphonedVisuals : byte
|
||||
{
|
||||
Key
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
using Content.Shared._DV.CosmicCult;
|
||||
|
||||
namespace Content.Client._DV.CosmicCult;
|
||||
|
||||
public sealed class MonumentSystem : SharedMonumentSystem;
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
using Robust.Client.GameObjects;
|
||||
using Content.Shared._DV.CosmicCult.Components;
|
||||
|
||||
namespace Content.Client._DV.CosmicCult;
|
||||
|
||||
/// <summary>
|
||||
/// Visualizer for The Monument of the Cosmic Cult.
|
||||
/// </summary>
|
||||
public sealed class MonumentVisualizerSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<MonumentComponent, AppearanceChangeEvent>(OnAppearanceChanged);
|
||||
}
|
||||
|
||||
private void OnAppearanceChanged(Entity<MonumentComponent> ent, ref AppearanceChangeEvent args)
|
||||
{
|
||||
if (args.Sprite == null)
|
||||
return;
|
||||
args.Sprite.LayerMapTryGet(MonumentVisualLayers.TransformLayer, out var transformLayer);
|
||||
args.Sprite.LayerMapTryGet(MonumentVisualLayers.MonumentLayer, out var baseLayer);
|
||||
_appearance.TryGetData<bool>(ent, MonumentVisuals.Transforming, out var transforming, args.Component);
|
||||
_appearance.TryGetData<bool>(ent, MonumentVisuals.Tier3, out var tier3, args.Component);
|
||||
if (!tier3)
|
||||
args.Sprite.LayerSetState(transformLayer, "transform-stage2");
|
||||
else
|
||||
args.Sprite.LayerSetState(transformLayer, "transform-stage3");
|
||||
if (transforming && HasComp<MonumentTransformingComponent>(ent))
|
||||
{
|
||||
args.Sprite.LayerSetAnimationTime(transformLayer, 0f);
|
||||
args.Sprite.LayerSetVisible(transformLayer, true);
|
||||
args.Sprite.LayerSetVisible(baseLayer, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
args.Sprite.LayerSetVisible(transformLayer, false);
|
||||
args.Sprite.LayerSetVisible(baseLayer, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
using Content.Client.Eui;
|
||||
|
||||
namespace Content.Client._DV.CosmicCult.UI;
|
||||
|
||||
public sealed class CosmicConvertedEui : BaseEui
|
||||
{
|
||||
private readonly CosmicConvertedMenu _menu;
|
||||
|
||||
public CosmicConvertedEui()
|
||||
{
|
||||
_menu = new CosmicConvertedMenu();
|
||||
}
|
||||
|
||||
public override void Opened()
|
||||
{
|
||||
_menu.OpenCentered();
|
||||
}
|
||||
|
||||
public override void Closed()
|
||||
{
|
||||
base.Closed();
|
||||
|
||||
_menu.Close();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
<controls:FancyWindow xmlns="https://spacestation14.io"
|
||||
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
|
||||
Title="{Loc 'cosmiccult-ui-converted-title'}">
|
||||
<BoxContainer Orientation="Vertical" Margin="10">
|
||||
<Label Text="{Loc 'cosmiccult-ui-converted-text-1'}" Margin="5" Align="Center"/>
|
||||
<Label Text="{Loc 'cosmiccult-ui-converted-text-2'}" Margin="5" Align="Center"/>
|
||||
<BoxContainer Orientation="Horizontal" Margin="5" Align="Center">
|
||||
<Button Name="ConfirmButton" Text="{Loc 'cosmiccult-ui-popup-confirm'}"/>
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
</controls:FancyWindow>
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
using Content.Client.UserInterface.Controls;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
|
||||
namespace Content.Client._DV.CosmicCult.UI;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class CosmicConvertedMenu : FancyWindow
|
||||
{
|
||||
public CosmicConvertedMenu()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
|
||||
ConfirmButton.OnPressed += _ => Close();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
<controls:FancyWindow xmlns="https://spacestation14.io"
|
||||
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
|
||||
Title="{Loc 'cosmiccult-ui-deconverted-title'}">
|
||||
<BoxContainer Orientation="Vertical" Margin="10">
|
||||
<Label Text="{Loc 'cosmiccult-ui-deconverted-text-1'}" Margin="5" Align="Center"/>
|
||||
<Label Text="{Loc 'cosmiccult-ui-deconverted-text-2'}" Margin="5" Align="Center"/>
|
||||
<BoxContainer Orientation="Horizontal" Margin="5" Align="Center">
|
||||
<Button Name="ConfirmButton" Text="{Loc 'cosmiccult-ui-popup-confirm'}"/>
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
</controls:FancyWindow>
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
using Content.Client.UserInterface.Controls;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
|
||||
namespace Content.Client._DV.CosmicCult.UI;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class CosmicDeconvertedMenu : FancyWindow
|
||||
{
|
||||
public CosmicDeconvertedMenu()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
|
||||
ConfirmButton.OnPressed += _ => Close();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
using Content.Client.Eui;
|
||||
|
||||
namespace Content.Client._DV.CosmicCult.UI;
|
||||
|
||||
public sealed class CosmicRoundStartEui : BaseEui
|
||||
{
|
||||
private readonly CosmicRoundStartMenu _menu;
|
||||
|
||||
public CosmicRoundStartEui()
|
||||
{
|
||||
_menu = new CosmicRoundStartMenu();
|
||||
}
|
||||
|
||||
public override void Opened()
|
||||
{
|
||||
_menu.OpenCentered();
|
||||
}
|
||||
|
||||
public override void Closed()
|
||||
{
|
||||
base.Closed();
|
||||
|
||||
_menu.Close();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
<controls:FancyWindow xmlns="https://spacestation14.io"
|
||||
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
|
||||
Title="{Loc 'cosmiccult-ui-roundstart-title'}">
|
||||
<BoxContainer Orientation="Vertical" Margin="10">
|
||||
<Label Text="{Loc 'cosmiccult-ui-roundstart-text-1'}" Margin="5" Align="Center"/>
|
||||
<Label Text="{Loc 'cosmiccult-ui-roundstart-text-2'}" Margin="5" Align="Center"/>
|
||||
<BoxContainer Orientation="Horizontal" Margin="5" Align="Center">
|
||||
<Button Name="ConfirmButton" Text="{Loc 'cosmiccult-ui-popup-confirm'}"/>
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
</controls:FancyWindow>
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
using Content.Client.UserInterface.Controls;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
|
||||
namespace Content.Client._DV.CosmicCult.UI;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class CosmicRoundStartMenu : FancyWindow
|
||||
{
|
||||
public CosmicRoundStartMenu()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
|
||||
ConfirmButton.OnPressed += _ => Close();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
using Content.Client.Eui;
|
||||
|
||||
namespace Content.Client._DV.CosmicCult.UI;
|
||||
|
||||
public sealed class DeconvertedCultistEui : BaseEui
|
||||
{
|
||||
private readonly CosmicDeconvertedMenu _menu;
|
||||
|
||||
public DeconvertedCultistEui()
|
||||
{
|
||||
_menu = new CosmicDeconvertedMenu();
|
||||
}
|
||||
|
||||
public override void Opened()
|
||||
{
|
||||
_menu.OpenCentered();
|
||||
}
|
||||
|
||||
public override void Closed()
|
||||
{
|
||||
base.Closed();
|
||||
|
||||
_menu.Close();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
<BoxContainer xmlns="https://spacestation14.io"
|
||||
Margin="10 10 10 0"
|
||||
HorizontalExpand="True"
|
||||
MinHeight="120"
|
||||
Name="InfluenceBox">
|
||||
<PanelContainer StyleClasses="AngleRect" HorizontalExpand="True">
|
||||
<BoxContainer Orientation="Vertical" HorizontalExpand="True">
|
||||
<!-- Header -->
|
||||
<BoxContainer Orientation="Horizontal" Margin="0 0 0 10">
|
||||
<TextureRect Name="InfluenceIcon" VerticalAlignment="Center" TextureScale="2 2" Stretch="KeepCentered" Margin="0 0 10 0" />
|
||||
<BoxContainer Orientation="Vertical" HorizontalExpand="True">
|
||||
<Label Name="Name" HorizontalAlignment="Left" FontColorOverride="#4CA7AD" />
|
||||
<PanelContainer StyleClasses="LowDivider" MinHeight="4" Margin="0 5 0 5" />
|
||||
<BoxContainer VerticalAlignment="Center">
|
||||
<Label Name="Status" HorizontalAlignment="Left" HorizontalExpand="True" />
|
||||
<Label Name="Type" HorizontalAlignment="Right" HorizontalExpand="True" />
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
<!-- Body -->
|
||||
<BoxContainer Orientation="Vertical" HorizontalExpand="True" Margin="10 0 0 0">
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<Label Name="CostText" Text="{Loc 'monument-interface-influences-cost'}" Margin="0 0 10 0" />
|
||||
<Label Name="Cost" HorizontalAlignment="Center" Text="73" FontColorOverride="#4CA7AD" />
|
||||
</BoxContainer>
|
||||
<RichTextLabel Name="Description" />
|
||||
<Button Name="GainButton" Text="{Loc 'monument-interface-influences-button-gain'}" HorizontalAlignment="Right" />
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
</PanelContainer>
|
||||
</BoxContainer>
|
||||
|
|
@ -0,0 +1,109 @@
|
|||
using Content.Shared._DV.CosmicCult.Components;
|
||||
using Content.Shared._DV.CosmicCult.Prototypes;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
|
||||
namespace Content.Client._DV.CosmicCult.UI.Monument;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class InfluenceUIBox : BoxContainer
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
|
||||
private readonly SpriteSystem _sprite;
|
||||
|
||||
public Action? OnGainButtonPressed;
|
||||
|
||||
public enum InfluenceUIBoxState
|
||||
{
|
||||
UnlockedAndEnoughEntropy = 0,
|
||||
UnlockedAndNotEnoughEntropy = 1,
|
||||
Owned = 2,
|
||||
Locked = 3,
|
||||
}
|
||||
|
||||
public readonly InfluenceUIBoxState State;
|
||||
public readonly InfluencePrototype Proto;
|
||||
|
||||
public InfluenceUIBox(InfluencePrototype influenceProto, InfluenceUIBoxState state)
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
IoCManager.InjectDependencies(this);
|
||||
_sprite = _entityManager.System<SpriteSystem>();
|
||||
GainButton.StyleClasses.Add("ButtonColorPurpleAndCool");
|
||||
|
||||
InfluenceIcon.Texture = _sprite.Frame0(influenceProto.Icon);
|
||||
Name.Text = Loc.GetString(influenceProto.Name);
|
||||
|
||||
State = state;
|
||||
Proto = influenceProto;
|
||||
|
||||
var availableEntropy = 0;
|
||||
if (_entityManager.TryGetComponent<CosmicCultComponent>(_playerManager.LocalEntity, out var cultComp))
|
||||
{
|
||||
availableEntropy = cultComp.EntropyBudget;
|
||||
}
|
||||
|
||||
switch (state)
|
||||
{
|
||||
case InfluenceUIBoxState.Owned:
|
||||
Status.Text = Loc.GetString("monument-interface-influences-owned");
|
||||
|
||||
GainButton.Disabled = true;
|
||||
GainButton.Modulate = Color.Green;
|
||||
GainButton.Label.Text = Loc.GetString("monument-interface-influences-purchased");
|
||||
GainButton.ToolTip = Loc.GetString("monument-interface-influences-owned-tooltip");
|
||||
|
||||
break;
|
||||
|
||||
case InfluenceUIBoxState.UnlockedAndEnoughEntropy:
|
||||
Status.Text = Loc.GetString("monument-interface-influences-unlocked");
|
||||
|
||||
GainButton.Disabled = false;
|
||||
|
||||
break;
|
||||
|
||||
case InfluenceUIBoxState.UnlockedAndNotEnoughEntropy:
|
||||
Status.Text = Loc.GetString("monument-interface-influences-unlocked");
|
||||
|
||||
GainButton.Disabled = false;
|
||||
GainButton.Modulate = Color.Gray;
|
||||
GainButton.ToolTip = Loc.GetString("monument-interface-influences-unlocked-not-enough-entropy-tooltip", ("entropy", influenceProto.Cost - availableEntropy));
|
||||
break;
|
||||
|
||||
case InfluenceUIBoxState.Locked:
|
||||
Status.Text = Loc.GetString("monument-interface-influences-locked");
|
||||
Status.FontColorOverride = Color.White;
|
||||
|
||||
GainButton.Disabled = true;
|
||||
GainButton.Modulate = Color.Gray;
|
||||
GainButton.Label.Text = Loc.GetString("monument-interface-influences-locked");
|
||||
GainButton.ToolTip = Loc.GetString("monument-interface-influences-locked-tooltip");
|
||||
|
||||
Name.FontColorOverride = Color.White;
|
||||
|
||||
InfluenceBox.Modulate = Color.Gray;
|
||||
InfluenceIcon.Modulate = Color.Gray;
|
||||
|
||||
Description.Modulate = Color.Gray;
|
||||
|
||||
Type.Modulate = Color.Gray;
|
||||
|
||||
CostText.Modulate = Color.Gray;
|
||||
|
||||
Cost.FontColorOverride = Color.Gray;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
Type.Text = Loc.GetString(influenceProto.InfluenceType);
|
||||
Cost.Text = influenceProto.Cost.ToString();
|
||||
Description.SetMessage(Loc.GetString(influenceProto.Description));
|
||||
|
||||
GainButton.OnPressed += _ => OnGainButtonPressed?.Invoke();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
using Content.Shared._DV.CosmicCult;
|
||||
using Content.Shared._DV.CosmicCult.Components;
|
||||
using Content.Shared._DV.CosmicCult.Prototypes;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Client._DV.CosmicCult.UI.Monument;
|
||||
|
||||
[UsedImplicitly]
|
||||
public sealed class MonumentBoundUserInterface(EntityUid owner, Enum uiKey) : BoundUserInterface(owner, uiKey)
|
||||
{
|
||||
[ViewVariables]
|
||||
private MonumentMenu? _menu;
|
||||
|
||||
protected override void Open()
|
||||
{
|
||||
base.Open();
|
||||
|
||||
_menu = this.CreateWindow<MonumentMenu>();
|
||||
|
||||
_menu.OnSelectGlyphButtonPressed += protoId => SendMessage(new GlyphSelectedMessage(protoId));
|
||||
_menu.OnRemoveGlyphButtonPressed += () => SendMessage(new GlyphRemovedMessage());
|
||||
|
||||
_menu.OnGainButtonPressed += OnInfluenceSelected;
|
||||
}
|
||||
|
||||
protected override void UpdateState(BoundUserInterfaceState state)
|
||||
{
|
||||
base.UpdateState(state);
|
||||
if (state is not MonumentBuiState buiState)
|
||||
return;
|
||||
|
||||
_menu?.UpdateState(buiState);
|
||||
}
|
||||
|
||||
private void OnInfluenceSelected(ProtoId<InfluencePrototype> selectedInfluence)
|
||||
{
|
||||
SendMessage(new InfluenceSelectedMessage(selectedInfluence));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
<controls:FancyWindow xmlns="https://spacestation14.io"
|
||||
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
|
||||
xmlns:graphics="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
|
||||
Title="{Loc 'monument-interface-title'}"
|
||||
MinSize="650 600"
|
||||
SetSize="750 620">
|
||||
<BoxContainer Orientation="Vertical" Margin="5 5 5 0">
|
||||
<!-- Header -->
|
||||
<BoxContainer Margin="7 10 7 0">
|
||||
<!-- Progress bar -->
|
||||
<ProgressBar Name="CultProgressBar" SetHeight="30" MaxValue="100" HorizontalExpand="True">
|
||||
<Label Name="ProgressBarPercentage" HorizontalAlignment="Center"/>
|
||||
</ProgressBar>
|
||||
</BoxContainer>
|
||||
<!-- Main body -->
|
||||
<BoxContainer Orientation="Horizontal" VerticalExpand="True" Margin="10 20 10 10">
|
||||
<!-- Left section -->
|
||||
<BoxContainer Name="LeftContainer" MinWidth="250" Orientation="Vertical" Margin="0 0 20 0">
|
||||
<!-- First section (Entropy) -->
|
||||
<BoxContainer Name="EntropyArea" HorizontalExpand="True" MinHeight="100" Orientation="Vertical" Margin="0 0 0 0">
|
||||
<Label HorizontalAlignment="Center" Text="{Loc 'monument-interface-entropy-title'}" />
|
||||
<PanelContainer HorizontalExpand="True" StyleClasses="LowDivider" Margin="0 0 0 10" />
|
||||
<TextureRect Name="MonumentIcon"
|
||||
TexturePath="/Textures/_DV/CosmicCult/Interface/mote_3.png"
|
||||
Stretch="KeepAspectCentered"
|
||||
VerticalAlignment="Center"
|
||||
MinSize="32 32"
|
||||
TextureScale="2 2"
|
||||
Margin="0 0 0 10" />
|
||||
<BoxContainer Orientation="Horizontal" HorizontalAlignment="Center">
|
||||
<Label HorizontalAlignment="Center" Text="{Loc 'monument-interface-entropy-available-label'}" Margin="0 0 5 0" StyleClasses="LabelSmall" />
|
||||
<Label HorizontalAlignment="Center" Name="AvailableEntropy" FontColorOverride="#4CA7AD" StyleClasses="LabelSmall" />
|
||||
</BoxContainer>
|
||||
<BoxContainer Orientation="Horizontal" Margin="0 5 0 0" HorizontalAlignment="Center">
|
||||
<Label HorizontalAlignment="Center" Text="{Loc 'monument-interface-entropy-next-stage-title'}" Margin="0 0 5 0" StyleClasses="LabelSmall" />
|
||||
<Label HorizontalAlignment="Center" Name="EntropyUntilNextStage" FontColorOverride="#4CA7AD" StyleClasses="LabelSmall" />
|
||||
</BoxContainer>
|
||||
<BoxContainer Orientation="Horizontal" Margin="0 5 0 0" HorizontalAlignment="Center">
|
||||
<Label HorizontalAlignment="Center" Text="{Loc 'monument-interface-entropy-seperator'}" Margin="0 0 5 0" StyleClasses="LabelSmall" />
|
||||
<Label HorizontalAlignment="Center" Name="OrSeperator" FontColorOverride="#4CA7AD" StyleClasses="LabelSmall" />
|
||||
</BoxContainer>
|
||||
<BoxContainer Orientation="Horizontal" HorizontalAlignment="Center">
|
||||
<Label HorizontalAlignment="Center" Text="{Loc 'monument-interface-entropy-crew-convert-title'}" Margin="0 0 5 0" StyleClasses="LabelSmall" />
|
||||
<Label HorizontalAlignment="Center" Name="CrewToConvertUntilNextStage" FontColorOverride="#4CA7AD" StyleClasses="LabelSmall" />
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
<!-- Second section (Glyphs) -->
|
||||
<BoxContainer Name="GlyphArea" HorizontalExpand="True" MinHeight="75" Orientation="Vertical" Margin="0 20 0 0">
|
||||
<Label HorizontalAlignment="Center" Text="{Loc 'monument-interface-glyphs-title'}" />
|
||||
<PanelContainer StyleClasses="LowDivider" />
|
||||
<!-- Filled out programatically -->
|
||||
<GridContainer HorizontalAlignment="Center" Name="GlyphContainer" Columns="3" Margin="0 5" />
|
||||
<Button Name="SelectGlyphButton" Text="{Loc 'monument-interface-glyphs-button-scribe'}" SetHeight="50" Margin="0 10 0 0" StyleClasses="ButtonColorPurpleAndCool" />
|
||||
<Button Name="RemoveGlyphButton" Text="{Loc 'monument-interface-glyphs-button-unscribe'}" SetHeight="50" Margin="0 10 0 0" StyleClasses="ButtonColorPurpleAndCool" />
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
<!-- Right section -->
|
||||
<BoxContainer VerticalExpand="True" HorizontalExpand="True" Orientation="Vertical">
|
||||
<BoxContainer VerticalExpand="True" HorizontalExpand="True" MinHeight="450" Orientation="Vertical" Margin="0 0 0 0">
|
||||
<Label HorizontalAlignment="Center" Text="{Loc 'monument-interface-influences-title'}" />
|
||||
<PanelContainer StyleClasses="LowDivider" Margin="0 0 0 0" />
|
||||
<!-- Influences -->
|
||||
<PanelContainer VerticalExpand="True" HorizontalExpand="True" Margin="0">
|
||||
<PanelContainer.PanelOverride>
|
||||
<graphics:StyleBoxFlat BackgroundColor="#1B1B1E" />
|
||||
</PanelContainer.PanelOverride>
|
||||
<!-- Filled out programatically -->
|
||||
<ScrollContainer HScrollEnabled="False" HorizontalExpand="True" VerticalExpand="True">
|
||||
<BoxContainer Name="InfluencesContainer" Orientation="Vertical" VerticalExpand="True" HorizontalExpand="True" />
|
||||
</ScrollContainer>
|
||||
</PanelContainer>
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
</controls:FancyWindow>
|
||||
|
|
@ -0,0 +1,186 @@
|
|||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Content.Client.Stylesheets;
|
||||
using Content.Client.UserInterface.Controls;
|
||||
using Content.Shared._DV.CCVars;
|
||||
using Content.Shared._DV.CosmicCult.Components;
|
||||
using Content.Shared._DV.CosmicCult.Prototypes;
|
||||
using Content.Shared._DV.CosmicCult;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Client._DV.CosmicCult.UI.Monument;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class MonumentMenu : FancyWindow
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _proto = default!;
|
||||
[Dependency] private readonly IEntityManager _ent = default!;
|
||||
[Dependency] private readonly IPlayerManager _player = default!;
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
|
||||
private readonly SpriteSystem _sprite;
|
||||
|
||||
// All glyph prototypes
|
||||
private readonly IEnumerable<GlyphPrototype> _glyphPrototypes;
|
||||
// All influence prototypes
|
||||
private readonly IEnumerable<InfluencePrototype> _influencePrototypes;
|
||||
private readonly ButtonGroup _glyphButtonGroup;
|
||||
private ProtoId<GlyphPrototype> _selectedGlyphProtoId = string.Empty;
|
||||
private HashSet<ProtoId<GlyphPrototype>> _unlockedGlyphProtoIds = [];
|
||||
public Action<ProtoId<GlyphPrototype>>? OnSelectGlyphButtonPressed;
|
||||
public Action? OnRemoveGlyphButtonPressed;
|
||||
|
||||
public Action<ProtoId<InfluencePrototype>>? OnGainButtonPressed;
|
||||
private int _entropyPerCultist = 0;
|
||||
|
||||
public MonumentMenu()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
IoCManager.InjectDependencies(this);
|
||||
_sprite = _ent.System<SpriteSystem>();
|
||||
|
||||
_glyphPrototypes = _proto.EnumeratePrototypes<GlyphPrototype>()
|
||||
.OrderBy(glyph => Loc.GetString(glyph.Name));
|
||||
_influencePrototypes = _proto.EnumeratePrototypes<InfluencePrototype>();
|
||||
|
||||
_glyphButtonGroup = new ButtonGroup();
|
||||
|
||||
RemoveGlyphButton.OnPressed += _ => OnRemoveGlyphButtonPressed?.Invoke();
|
||||
SelectGlyphButton.OnPressed += _ => OnSelectGlyphButtonPressed?.Invoke(_selectedGlyphProtoId);
|
||||
|
||||
_cfg.OnValueChanged(DCCVars.CosmicCultistEntropyValue, entropy =>
|
||||
{
|
||||
_entropyPerCultist = entropy;
|
||||
},
|
||||
invokeImmediately: true);
|
||||
}
|
||||
|
||||
public void UpdateState(MonumentBuiState state)
|
||||
{
|
||||
_selectedGlyphProtoId = state.SelectedGlyph;
|
||||
_unlockedGlyphProtoIds = state.UnlockedGlyphs;
|
||||
|
||||
CultProgressBar.BackgroundStyleBoxOverride = new StyleBoxFlat { BackgroundColor = new Color(15, 17, 30) };
|
||||
CultProgressBar.ForegroundStyleBoxOverride = new StyleBoxFlat { BackgroundColor = new Color(91, 62, 124) };
|
||||
|
||||
UpdateBar(state);
|
||||
UpdateEntropy(state);
|
||||
UpdateGlyphs();
|
||||
UpdateInfluences(state);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the progress bar
|
||||
/// </summary>
|
||||
private void UpdateBar(MonumentBuiState state)
|
||||
{
|
||||
var percentComplete = 100f * ((float)state.CurrentProgress / state.TargetProgress);
|
||||
|
||||
percentComplete = Math.Min(percentComplete, 100f);
|
||||
|
||||
CultProgressBar.Value = percentComplete;
|
||||
|
||||
ProgressBarPercentage.Text = Loc.GetString("monument-interface-progress-bar", ("percentage", percentComplete.ToString("0")));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the entropy fields
|
||||
/// </summary>
|
||||
private void UpdateEntropy(MonumentBuiState state)
|
||||
{
|
||||
var availableEntropy = -1;
|
||||
if (_ent.TryGetComponent<CosmicCultComponent>(_player.LocalEntity, out var cultComp))
|
||||
{
|
||||
availableEntropy = cultComp.EntropyBudget;
|
||||
}
|
||||
|
||||
var entropyToNextStage = Math.Max(state.TargetProgress - state.CurrentProgress, 0);
|
||||
var min = entropyToNextStage == 0 ? 0 : 1; //I have no idea what to call this. makes it so that it shows 0 crew for the final stage but at least one at all other times
|
||||
var crewToNextStage = (int)Math.Max(Math.Round((double)entropyToNextStage / _entropyPerCultist, MidpointRounding.ToPositiveInfinity), min); //force it to be at least one
|
||||
|
||||
AvailableEntropy.Text = Loc.GetString("monument-interface-entropy-value", ("infused", availableEntropy));
|
||||
EntropyUntilNextStage.Text = Loc.GetString("monument-interface-entropy-value", ("infused", entropyToNextStage.ToString()));
|
||||
CrewToConvertUntilNextStage.Text = crewToNextStage.ToString();
|
||||
}
|
||||
|
||||
// Update all the glyph buttons
|
||||
private void UpdateGlyphs()
|
||||
{
|
||||
GlyphContainer.RemoveAllChildren();
|
||||
foreach (var glyph in _glyphPrototypes)
|
||||
{
|
||||
var boxContainer = new BoxContainer();
|
||||
var unlocked = _unlockedGlyphProtoIds.Contains(glyph.ID);
|
||||
var button = new Button
|
||||
{
|
||||
HorizontalExpand = true,
|
||||
StyleClasses = { StyleBase.ButtonSquare },
|
||||
ToolTip = Loc.GetString(glyph.Tooltip),
|
||||
Group = _glyphButtonGroup,
|
||||
Pressed = glyph.ID == _selectedGlyphProtoId,
|
||||
Disabled = !unlocked,
|
||||
Modulate = !unlocked ? Color.Gray : Color.White,
|
||||
};
|
||||
button.OnPressed += _ => _selectedGlyphProtoId = glyph.ID;
|
||||
var glyphIcon = new TextureRect
|
||||
{
|
||||
Texture = _sprite.Frame0(glyph.Icon),
|
||||
TextureScale = new Vector2(2f, 2f),
|
||||
Stretch = TextureRect.StretchMode.KeepCentered,
|
||||
};
|
||||
button.AddChild(glyphIcon);
|
||||
boxContainer.AddChild(button);
|
||||
GlyphContainer.AddChild(boxContainer);
|
||||
}
|
||||
}
|
||||
|
||||
// Update all the influence thingies
|
||||
private void UpdateInfluences(MonumentBuiState state)
|
||||
{
|
||||
InfluencesContainer.RemoveAllChildren();
|
||||
|
||||
var influenceUIBoxes = new List<InfluenceUIBox>();
|
||||
foreach (var influence in _influencePrototypes)
|
||||
{
|
||||
var uiBoxState = GetUIBoxStateForInfluence(influence, state);
|
||||
var influenceBox = new InfluenceUIBox(influence, uiBoxState);
|
||||
influenceUIBoxes.Add(influenceBox);
|
||||
influenceBox.OnGainButtonPressed += () => OnGainButtonPressed?.Invoke(influence.ID);
|
||||
}
|
||||
|
||||
//sort the list of UI boxes by state (locked -> owned -> not enough entropy -> enough entropy)
|
||||
//then sort alphabetically within those categories
|
||||
foreach (var box in influenceUIBoxes.OrderBy(box => box.State).ThenBy(box => box.Proto.ID))
|
||||
{
|
||||
InfluencesContainer.AddChild(box);
|
||||
}
|
||||
}
|
||||
|
||||
private InfluenceUIBox.InfluenceUIBoxState GetUIBoxStateForInfluence(InfluencePrototype influence, MonumentBuiState state)
|
||||
{
|
||||
if (!_ent.TryGetComponent<CosmicCultComponent>(_player.LocalEntity, out var cultComp))
|
||||
return InfluenceUIBox.InfluenceUIBoxState.Locked; //early return with locked if there's somehow no cult comp
|
||||
|
||||
var unlocked = cultComp.UnlockedInfluences.Contains(influence.ID);
|
||||
var owned = cultComp.OwnedInfluences.Contains(influence);
|
||||
|
||||
//more verbose than it needs to be, but it reads nicer
|
||||
if (owned)
|
||||
return InfluenceUIBox.InfluenceUIBoxState.Owned;
|
||||
|
||||
if (unlocked)
|
||||
{
|
||||
//if it's unlocked, do we have enough entropy to buy it?
|
||||
return influence.Cost > cultComp.EntropyBudget ? InfluenceUIBox.InfluenceUIBoxState.UnlockedAndNotEnoughEntropy : InfluenceUIBox.InfluenceUIBoxState.UnlockedAndEnoughEntropy;
|
||||
}
|
||||
|
||||
return InfluenceUIBox.InfluenceUIBoxState.Locked;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,150 @@
|
|||
using System.Numerics;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Client._DV.CosmicCult.Visuals;
|
||||
|
||||
public sealed class MonumentPlacementPreviewOverlay : Overlay
|
||||
{
|
||||
private readonly IEntityManager _ent;
|
||||
private readonly IPlayerManager _player;
|
||||
private readonly SpriteSystem _sprite;
|
||||
private readonly SharedMapSystem _map;
|
||||
private readonly MonumentPlacementPreviewSystem _preview;
|
||||
private readonly IGameTiming _timing;
|
||||
public override OverlaySpace Space => OverlaySpace.WorldSpaceEntities;
|
||||
|
||||
private readonly ShaderInstance _saturationShader;
|
||||
private readonly ShaderInstance _unshadedShader;
|
||||
private readonly ShaderInstance _starsShader;
|
||||
|
||||
public bool LockPlacement = false;
|
||||
private EntityCoordinates _lastPos = new();
|
||||
|
||||
//for a slight fade in / out
|
||||
//ss14's formatting settings can take my needlessly public variables away from my cold, dead hands
|
||||
public float FadeInProgress = 0;
|
||||
public float FadeInTime = 0.25f;
|
||||
public bool FadingIn = true;
|
||||
|
||||
public float FadeOutProgress = 0;
|
||||
public float FadeOutTime = 0.25f;
|
||||
public bool FadingOut = false;
|
||||
|
||||
public float Alpha = 0;
|
||||
|
||||
private readonly SpriteSpecifier _mainTex;
|
||||
private readonly SpriteSpecifier _outlineTex;
|
||||
private readonly SpriteSpecifier _starTex;
|
||||
|
||||
//todo arbitrary sprite drawing overlay at some point
|
||||
//I don't want to have to make a new overlay for every "draw a sprite at x" thing
|
||||
//also I kinda want wrappers around the dog ass existing arbitrary rendering methods
|
||||
|
||||
//evil huge ctor because doing iocmanager stuff was killing the client for some reason
|
||||
public MonumentPlacementPreviewOverlay(IEntityManager entityManager, IPlayerManager playerManager, IPrototypeManager protoMan, IGameTiming timing, int tier)
|
||||
{
|
||||
_ent = entityManager;
|
||||
_player = playerManager;
|
||||
_sprite = _ent.System<SpriteSystem>();
|
||||
_map = _ent.System<SharedMapSystem>();
|
||||
_preview = _ent.System<MonumentPlacementPreviewSystem>();
|
||||
_timing = timing;
|
||||
|
||||
_saturationShader = protoMan.Index<ShaderPrototype>("SaturationShuffle").InstanceUnique();
|
||||
_saturationShader.SetParameter("tileSize", new Vector2(96, 96));
|
||||
_saturationShader.SetParameter("hsv", new Robust.Shared.Maths.Vector3(1.0f, 0.25f, 0.2f));
|
||||
|
||||
_starsShader = protoMan.Index<ShaderPrototype>("MonumentPulse").InstanceUnique();
|
||||
_starsShader.SetParameter("tileSize", new Vector2(96, 96));
|
||||
|
||||
_unshadedShader = protoMan.Index<ShaderPrototype>("unshaded").Instance(); //doesn't need a unique instance
|
||||
|
||||
ZIndex = (int) Shared.DrawDepth.DrawDepth.Mobs; //make the overlay render at the same depth as the actual sprite. might want to make it 1 lower if things get wierd with it.
|
||||
|
||||
//will fuck up if the wrong tier is passed in but it's not my problem if that happens
|
||||
_mainTex = new SpriteSpecifier.Rsi(new ResPath("_DV/CosmicCult/Tileset/monument.rsi"), $"stage{tier}");
|
||||
_outlineTex = new SpriteSpecifier.Rsi(new ResPath("_DV/CosmicCult/Tileset/monument.rsi"), $"stage{tier}-placement-ghost-1");
|
||||
_starTex = new SpriteSpecifier.Rsi(new ResPath("_DV/CosmicCult/Tileset/monument.rsi"), $"stage{tier}-placement-ghost-2");
|
||||
}
|
||||
|
||||
//this might get wierd if the player managed to leave the grid they put the monument on? theoretically not a concern because it can't be placed too close to space.
|
||||
//shouldn't crash due to the comp checks, though.
|
||||
protected override void Draw(in OverlayDrawArgs args)
|
||||
{
|
||||
if (!_ent.TryGetComponent<TransformComponent>(_player.LocalEntity, out var transformComp))
|
||||
return;
|
||||
|
||||
if (!_ent.TryGetComponent<MapGridComponent>(transformComp.GridUid, out var grid))
|
||||
return;
|
||||
|
||||
if (!_ent.TryGetComponent<TransformComponent>(transformComp.ParentUid, out var parentTransform))
|
||||
return;
|
||||
|
||||
var worldHandle = args.WorldHandle;
|
||||
|
||||
//make effects look more nicer
|
||||
var time = (float) _timing.FrameTime.TotalSeconds;
|
||||
//make the fade in / out progress
|
||||
if (FadingIn)
|
||||
{
|
||||
FadeInProgress += time;
|
||||
if (FadeInProgress >= FadeInTime)
|
||||
{
|
||||
FadingIn = false;
|
||||
FadeInProgress = FadeInTime;
|
||||
}
|
||||
Alpha = FadeInProgress / FadeInTime;
|
||||
}
|
||||
|
||||
if (FadingOut)
|
||||
{
|
||||
FadeOutProgress += time;
|
||||
if (FadeOutProgress >= FadeOutTime)
|
||||
{
|
||||
FadingOut = false;
|
||||
FadeOutProgress = FadeOutTime;
|
||||
}
|
||||
Alpha = 1 - FadeOutProgress / FadeOutTime;
|
||||
}
|
||||
|
||||
//have the outline's alpha slightly "breathe"
|
||||
var outlineAlphaModulate = 0.75f + 0.25f * (float) Math.Sin(_timing.CurTime.TotalSeconds);
|
||||
|
||||
//stuff to make the monument preview stick in place once the ability is confirmed
|
||||
Color color;
|
||||
if (!LockPlacement)
|
||||
{
|
||||
//set the colour based on if the target tile is valid or not
|
||||
color = _preview.VerifyPlacement(transformComp, out var snappedCoords) ? Color.White.WithAlpha(outlineAlphaModulate * Alpha) : Color.Gray.WithAlpha(outlineAlphaModulate * 0.5f * Alpha);
|
||||
_lastPos = snappedCoords; //update the position
|
||||
}
|
||||
else
|
||||
{
|
||||
//if the position is locked, then it has to be valid so always use the valid colour
|
||||
color = Color.White.WithAlpha(outlineAlphaModulate * Alpha);
|
||||
}
|
||||
|
||||
worldHandle.SetTransform(parentTransform.LocalMatrix);
|
||||
|
||||
//for the desaturated monument "shadow"
|
||||
worldHandle.UseShader(_saturationShader);
|
||||
worldHandle.DrawTexture(_sprite.Frame0(_mainTex), _lastPos.Position - new Vector2(1.5f, 0.5f), Color.White.WithAlpha(Alpha)); //needs the offset to render in the proper position. does not inherit the extra modulate
|
||||
|
||||
//for the outline to pop
|
||||
worldHandle.UseShader(_unshadedShader);
|
||||
worldHandle.DrawTexture(_sprite.Frame0(_outlineTex), _lastPos.Position - new Vector2(1.5f, 0.5f), color);
|
||||
|
||||
//some fancy schmancy things for the inside of the monument
|
||||
worldHandle.UseShader(_starsShader);
|
||||
worldHandle.DrawTexture(_sprite.Frame0(_starTex), _lastPos.Position - new Vector2(1.5f, 0.5f), color);
|
||||
worldHandle.UseShader(null);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,177 @@
|
|||
using System.Numerics;
|
||||
using System.Threading;
|
||||
using Content.Shared._DV.CosmicCult;
|
||||
using Content.Shared._DV.CosmicCult.Components;
|
||||
using Content.Shared.Actions;
|
||||
using Content.Shared.Actions.Events;
|
||||
using Content.Shared.Maps;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
using Timer = Robust.Shared.Timing.Timer;
|
||||
|
||||
namespace Content.Client._DV.CosmicCult.Visuals;
|
||||
|
||||
/// <summary>
|
||||
/// This handles rendering a preview of where the monument will be placed
|
||||
/// </summary>
|
||||
public sealed class MonumentPlacementPreviewSystem : EntitySystem
|
||||
{
|
||||
//most of these aren't used by this system, see MonumentPlacementPreviewOverlay for a note on why they're here
|
||||
[Dependency] private readonly EntityLookupSystem _lookup = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly IOverlayManager _overlay = default!;
|
||||
[Dependency] private readonly IPlayerManager _player = default!;
|
||||
[Dependency] private readonly IPrototypeManager _proto = default!;
|
||||
[Dependency] private readonly ITileDefinitionManager _tileDef = default!;
|
||||
[Dependency] private readonly SharedMapSystem _map = default!;
|
||||
[Dependency] private readonly TransformSystem _transform = default!;
|
||||
|
||||
private MonumentPlacementPreviewOverlay? _cachedOverlay;
|
||||
private CancellationTokenSource? _cancellationTokenSource;
|
||||
|
||||
private const int MinimumDistanceFromSpace = 3;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<MonumentPlacementPreviewComponent, ActionAttemptEvent>(OnAttemptMonumentPlacement);
|
||||
SubscribeLocalEvent<CosmicCultLeadComponent, EventCosmicPlaceMonument>(OnCosmicPlaceMonument);
|
||||
SubscribeLocalEvent<CosmicCultLeadComponent, EventCosmicMoveMonument>(OnCosmicMoveMonument);
|
||||
}
|
||||
|
||||
private void DoMonumentAnimation(EntityUid performer)
|
||||
{
|
||||
if (_cachedOverlay == null || _cancellationTokenSource == null)
|
||||
return;
|
||||
|
||||
if (!VerifyPlacement(Transform(performer), out _))
|
||||
return;
|
||||
|
||||
_cachedOverlay.LockPlacement = true;
|
||||
_cancellationTokenSource.Cancel(); //cancel the previous timer
|
||||
|
||||
//remove the overlay automatically after the primeTime expires
|
||||
//no cancellation token for this one as this'll never need to get cancelled afaik
|
||||
Timer.Spawn(TimeSpan.FromSeconds(3.8), //anim takes 3.8s, might want to have the ghost disappear earlier but eh
|
||||
() =>
|
||||
{
|
||||
_overlay.RemoveOverlay<MonumentPlacementPreviewOverlay>();
|
||||
_cachedOverlay = null;
|
||||
_cancellationTokenSource = null;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private void OnCosmicMoveMonument(Entity<CosmicCultLeadComponent> ent, ref EventCosmicMoveMonument args)
|
||||
{
|
||||
DoMonumentAnimation(args.Performer);
|
||||
}
|
||||
|
||||
private void OnCosmicPlaceMonument(Entity<CosmicCultLeadComponent> ent, ref EventCosmicPlaceMonument args)
|
||||
{
|
||||
DoMonumentAnimation(args.Performer);
|
||||
}
|
||||
|
||||
//duplicated from the ability check, minus the station check because that can't be done clientside afaik?
|
||||
//and no popups because they're done in the ability check as well
|
||||
public bool VerifyPlacement(TransformComponent xform, out EntityCoordinates outPos)
|
||||
{
|
||||
outPos = new EntityCoordinates();
|
||||
|
||||
//MAKE SURE WE'RE STANDING ON A GRID
|
||||
if (!TryComp(xform.GridUid, out MapGridComponent? grid))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
//CHECK IF IT'S BEING PLACED CHEESILY CLOSE TO SPACE
|
||||
var worldPos = _transform.GetWorldPosition(xform); //this is technically wrong but basically fine; if
|
||||
foreach (var tile in _map.GetTilesIntersecting(xform.GridUid.Value, grid, new Circle(worldPos, MinimumDistanceFromSpace)))
|
||||
{
|
||||
if (tile.IsSpace(_tileDef))
|
||||
return false;
|
||||
}
|
||||
|
||||
var localTile = _map.GetTileRef(xform.GridUid.Value, grid, xform.Coordinates);
|
||||
var targetIndices = localTile.GridIndices + new Vector2i(0, 1);
|
||||
var pos = _map.ToCenterCoordinates(xform.GridUid.Value, targetIndices, grid);
|
||||
outPos = pos;
|
||||
var box = new Box2(pos.Position + new Vector2(-1.4f, -0.4f), pos.Position + new Vector2(1.4f, 0.4f));
|
||||
|
||||
//CHECK FOR ENTITY AND ENVIRONMENTAL INTERSECTIONS
|
||||
if (_lookup.AnyLocalEntitiesIntersecting(xform.GridUid.Value, box, LookupFlags.Dynamic | LookupFlags.Static, _player.LocalEntity))
|
||||
return false;
|
||||
|
||||
//if all of those aren't false, return true
|
||||
return true;
|
||||
}
|
||||
|
||||
private void OnAttemptMonumentPlacement(Entity<MonumentPlacementPreviewComponent> ent, ref ActionAttemptEvent args)
|
||||
{
|
||||
if (!TryComp<ConfirmableActionComponent>(ent, out var confirmableAction))
|
||||
return; //return if the action somehow doesn't have a confirmableAction comp
|
||||
|
||||
//if we've already got a cached overlay, reset the timers & bump alpha back up to 1.
|
||||
//todo do that
|
||||
//should probably smoothly transition alpha back up to 1 but idrc (this will bother me a lot I'm lying) it's an incredibly specific thing that occurs in a .25s window at the end of a 10s wait
|
||||
//not a great solution but I'm not sure if a Real:tm: (also not entirely sure what a Real:tm: fix would be here tbh? hooking into ActionAttemptEvent?) fix would actually work here? need to investigate.
|
||||
if (_cachedOverlay != null && _cancellationTokenSource != null)
|
||||
{
|
||||
_cancellationTokenSource.Cancel();
|
||||
|
||||
_cancellationTokenSource = new CancellationTokenSource();
|
||||
StartTimers(confirmableAction, _cancellationTokenSource, _cachedOverlay);
|
||||
|
||||
if (_cachedOverlay.FadingOut) //if we're fading out
|
||||
{
|
||||
_cachedOverlay.FadingOut = false; //stop it
|
||||
|
||||
var progress = (1 - (_cachedOverlay.FadeOutProgress / _cachedOverlay.FadeOutTime)) * _cachedOverlay.FadeInTime; //set fade in progress to 1 - fade out progress (so 70% out becomes 30% in)
|
||||
_cachedOverlay.FadeInProgress = progress;
|
||||
_cachedOverlay.FadingIn = true; //start fading in again
|
||||
_cachedOverlay.FadeOutProgress = 0; //stop the fadeout entirely
|
||||
} //no need for a special fade in case as well, that can go as normal
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
_cancellationTokenSource = new CancellationTokenSource();
|
||||
//it's probably inefficient to make a new one every time, but this'll be happening like four times a round maybe
|
||||
//massive ctor because iocmanager hates me
|
||||
_cachedOverlay = new MonumentPlacementPreviewOverlay(EntityManager, _player, _proto, _timing, ent.Comp.Tier);
|
||||
_overlay.AddOverlay(_cachedOverlay);
|
||||
|
||||
StartTimers(confirmableAction, _cancellationTokenSource, _cachedOverlay);
|
||||
}
|
||||
|
||||
private void StartTimers(ConfirmableActionComponent comp, CancellationTokenSource tokenSource, MonumentPlacementPreviewOverlay overlay)
|
||||
{
|
||||
//remove the overlay automatically after the primeTime expires
|
||||
Timer.Spawn(comp.PrimeTime + comp.ConfirmDelay,
|
||||
() =>
|
||||
{
|
||||
_overlay.RemoveOverlay<MonumentPlacementPreviewOverlay>();
|
||||
_cachedOverlay = null;
|
||||
_cancellationTokenSource = null;
|
||||
},
|
||||
tokenSource.Token
|
||||
);
|
||||
|
||||
//start a timer to start the fade out as well, with the same cancellation token
|
||||
Timer.Spawn(comp.PrimeTime + comp.ConfirmDelay - TimeSpan.FromSeconds(overlay.FadeOutTime),
|
||||
() =>
|
||||
{
|
||||
overlay.FadingOut = true;
|
||||
},
|
||||
tokenSource.Token
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
"Cosmic Cult assets" refers to all digital images and digital audio published for the purposes of contribution towards the Cosmic Cult project, an antagonist designed for Space Station 14.
|
||||
"Cosmic Cult code" refers to all digital files containing C-Sharp code published for the purposes of contribution towards the Cosmic Cult project, an antagonist designed for Space Station 14.
|
||||
"Cosmic Cult localization" refers to all digital ".ftl" files containing language localization text published for the purposes of contribution towards the Cosmic Cult project, an antagonist designed for Space Station 14.
|
||||
|
||||
All Cosmic Cult assets, code, and localization may be utilized by and replicated to any Space Station 14 repository, regardless of repository licensing.
|
||||
Cosmic Cult assets must retain their CC-BY-SA-3.0 licenses, as dictated by said license.
|
||||
|
||||
Cosmic Cult assets, code, localization, and/or derivatives made from aforementioned are not to be used for the purpose of training "Artificial Intelligence(s)", "Neural Network(s)", "Large Language Model(s)", or any AI whatsoever.
|
||||
|
||||
By contributing to or utilizing assets from the Cosmic Cult project, you agree to comply with the statements described above.
|
||||
|
||||
- AftrLite
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
using Content.Server._DV.CosmicCult.Components; // DeltaV
|
||||
using Content.Server.Administration.Commands;
|
||||
using Content.Server.Antag;
|
||||
using Content.Server.GameTicking;
|
||||
|
|
@ -186,5 +187,22 @@ public sealed partial class AdminVerbSystem
|
|||
|
||||
if (HasComp<HumanoidAppearanceComponent>(args.Target)) // only humanoids can be cloned
|
||||
args.Verbs.Add(paradox);
|
||||
|
||||
// Begin DeltaV Additions
|
||||
var cosmicCultName = Loc.GetString("admin-verb-text-make-cosmiccultist");
|
||||
Verb cosmiccult = new()
|
||||
{
|
||||
Text = cosmicCultName,
|
||||
Category = VerbCategory.Antag,
|
||||
Icon = new SpriteSpecifier.Rsi(new("/Textures/_DV/CosmicCult/Icons/antag_icons.rsi"), "CosmicCult"),
|
||||
Act = () =>
|
||||
{
|
||||
_antag.ForceMakeAntag<CosmicCultRuleComponent>(targetPlayer, "CosmicCult");
|
||||
},
|
||||
Impact = LogImpact.High,
|
||||
Message = string.Join(": ", cosmicCultName, Loc.GetString("admin-verb-make-cosmiccultist")),
|
||||
};
|
||||
args.Verbs.Add(cosmiccult);
|
||||
// End DeltaV Additions
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ using Content.Shared.Mobs.Systems;
|
|||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Timing;
|
||||
using Content.Shared._DV.CosmicCult.Components; // DeltaV
|
||||
|
||||
|
||||
namespace Content.Server.Body.Systems;
|
||||
|
|
@ -77,14 +78,14 @@ public sealed class RespiratorSystem : EntitySystem
|
|||
if (_mobState.IsDead(uid) || HasComp<BreathingImmunityComponent>(uid)) // Shitmed: BreathingImmunity
|
||||
continue;
|
||||
|
||||
// Begin DeltaV Code: Addition:
|
||||
// Begin DeltaV Additions
|
||||
var organs = _bodySystem.GetBodyOrganEntityComps<LungComponent>((uid, body));
|
||||
var multiplier = -1f;
|
||||
foreach (var (_, lung, _) in organs)
|
||||
{
|
||||
multiplier *= lung.SaturationLoss;
|
||||
}
|
||||
// End DeltaV Code
|
||||
// End DeltaV Additions
|
||||
UpdateSaturation(uid, multiplier * (float) respirator.UpdateInterval.TotalSeconds, respirator); // DeltaV: use multiplier instead of negating
|
||||
|
||||
if (!_mobState.IsIncapacitated(uid) && !HasComp<DebrainedComponent>(uid)) // Shitmed Change - Cannot breathe in crit or when no brain.
|
||||
|
|
@ -104,6 +105,9 @@ public sealed class RespiratorSystem : EntitySystem
|
|||
|
||||
if (respirator.Saturation < respirator.SuffocationThreshold)
|
||||
{
|
||||
// DeltaV: Cosmic Cult - One line change but a refactor would be better. this is kinda cringe. Makes cultists gasp and respirate but not asphyxiate in space.
|
||||
if (TryComp<CosmicCultComponent>(uid, out var cultComponent) && !cultComponent.Respiration && !_mobState.IsIncapacitated(uid)) return;
|
||||
|
||||
if (_gameTiming.CurTime >= respirator.LastGaspEmoteTime + respirator.GaspEmoteCooldown)
|
||||
{
|
||||
respirator.LastGaspEmoteTime = _gameTiming.CurTime;
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ using Content.Server.Administration.Managers;
|
|||
using Content.Server.Chat.Managers;
|
||||
using Content.Server.GameTicking;
|
||||
using Content.Server.Maps;
|
||||
using Content.Shared._DV.CosmicCult.Components; // DeltaV - Cosmic Cult
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Database;
|
||||
|
|
@ -453,6 +454,14 @@ namespace Content.Server.Voting.Managers
|
|||
return false;
|
||||
}
|
||||
|
||||
// Begin DeltaV - Cosmic Cult
|
||||
if (eligibility == VoterEligibility.CosmicCult)
|
||||
{
|
||||
if (!_entityManager.HasComponent<CosmicCultComponent>(player.AttachedEntity))
|
||||
return false;
|
||||
}
|
||||
// End DeltaV - Cosmic Cult
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -550,7 +559,8 @@ namespace Content.Server.Voting.Managers
|
|||
All,
|
||||
Ghost, // Player needs to be a ghost
|
||||
GhostMinimumPlaytime, // Player needs to be a ghost, with a minimum playtime and deathtime as defined by votekick CCvars.
|
||||
MinimumPlaytime //Player needs to have a minimum playtime and deathtime as defined by votekick CCvars.
|
||||
MinimumPlaytime, //Player needs to have a minimum playtime and deathtime as defined by votekick CCvars.
|
||||
CosmicCult, // DeltaV - Player needs to be a cosmic cultist. Used by the cosmic cult gamemode.
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
|
|
|||
|
|
@ -0,0 +1,132 @@
|
|||
using System.Collections.Immutable;
|
||||
using Content.Server._DV.CosmicCult.Components;
|
||||
using Content.Server.Bible.Components;
|
||||
using Content.Server.Popups;
|
||||
using Content.Shared._DV.CosmicCult;
|
||||
using Content.Shared._DV.CosmicCult.Components;
|
||||
using Content.Shared._DV.CosmicCult.Components.Examine;
|
||||
using Content.Shared.DoAfter;
|
||||
using Content.Shared.Effects;
|
||||
using Content.Shared.IdentityManagement;
|
||||
using Content.Shared.Mind;
|
||||
using Content.Shared.Mind.Components;
|
||||
using Content.Shared.NPC;
|
||||
using Content.Shared.Stunnable;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Server._DV.CosmicCult.Abilities;
|
||||
|
||||
public sealed class CosmicBlankSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly CosmicCultSystem _cult = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly PopupSystem _popup = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
[Dependency] private readonly SharedColorFlashEffectSystem _color = default!;
|
||||
[Dependency] private readonly SharedCosmicCultSystem _cosmicCult = default!;
|
||||
[Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
|
||||
[Dependency] private readonly SharedMindSystem _mind = default!;
|
||||
[Dependency] private readonly SharedStunSystem _stun = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<CosmicCultComponent, EventCosmicBlank>(OnCosmicBlank);
|
||||
SubscribeLocalEvent<CosmicCultComponent, EventCosmicBlankDoAfter>(OnCosmicBlankDoAfter);
|
||||
}
|
||||
|
||||
private void OnCosmicBlank(Entity<CosmicCultComponent> uid, ref EventCosmicBlank args)
|
||||
{
|
||||
if (_cosmicCult.EntityIsCultist(args.Target) || HasComp<CosmicBlankComponent>(args.Target) || HasComp<BibleUserComponent>(args.Target) || HasComp<ActiveNPCComponent>(args.Target))
|
||||
{
|
||||
_popup.PopupEntity(Loc.GetString("cosmicability-generic-fail"), uid, uid);
|
||||
return;
|
||||
}
|
||||
if (args.Handled)
|
||||
return;
|
||||
|
||||
var doargs = new DoAfterArgs(EntityManager, uid, uid.Comp.CosmicBlankDelay, new EventCosmicBlankDoAfter(), uid, args.Target)
|
||||
{
|
||||
DistanceThreshold = 1.5f,
|
||||
Hidden = false,
|
||||
BreakOnDamage = true,
|
||||
BreakOnMove = true,
|
||||
BreakOnDropItem = true,
|
||||
};
|
||||
args.Handled = true;
|
||||
_doAfter.TryStartDoAfter(doargs);
|
||||
_popup.PopupEntity(Loc.GetString("cosmicability-blank-begin", ("target", Identity.Entity(uid, EntityManager))), uid, args.Target);
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
|
||||
var shuntQuery = EntityQueryEnumerator<InVoidComponent>();
|
||||
while (shuntQuery.MoveNext(out var uid, out var comp))
|
||||
{
|
||||
if (_timing.CurTime >= comp.ExitVoidTime)
|
||||
{
|
||||
if (!_mind.TryGetMind(uid, out var mindEnt, out var mind))
|
||||
continue;
|
||||
mind.PreventGhosting = false;
|
||||
_mind.TransferTo(mindEnt, comp.OriginalBody);
|
||||
RemComp<CosmicBlankComponent>(comp.OriginalBody);
|
||||
_popup.PopupEntity(Loc.GetString("cosmicability-blank-return"), comp.OriginalBody, comp.OriginalBody);
|
||||
QueueDel(uid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnCosmicBlankDoAfter(Entity<CosmicCultComponent> uid, ref EventCosmicBlankDoAfter args)
|
||||
{
|
||||
if (args.Args.Target is not { } target)
|
||||
return;
|
||||
if (args.Cancelled || args.Handled)
|
||||
return;
|
||||
args.Handled = true;
|
||||
|
||||
if (!TryComp<MindContainerComponent>(target, out var mindContainer) || !mindContainer.HasMind)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
EnsureComp<CosmicBlankComponent>(target);
|
||||
var examine = EnsureComp<CosmicCultExamineComponent>(target);
|
||||
examine.CultistText = "cosmic-examine-text-abilityblank";
|
||||
|
||||
_popup.PopupEntity(Loc.GetString("cosmicability-blank-success", ("target", Identity.Entity(target, EntityManager))), uid, uid);
|
||||
var tgtpos = Transform(target).Coordinates;
|
||||
var mindEnt = mindContainer.Mind.Value;
|
||||
var mind = Comp<MindComponent>(mindEnt);
|
||||
var comp = uid.Comp;
|
||||
mind.PreventGhosting = true;
|
||||
|
||||
var spawnPoints = EntityManager.GetAllComponents(typeof(CosmicVoidSpawnComponent)).ToImmutableList();
|
||||
if (spawnPoints.IsEmpty)
|
||||
{
|
||||
return;
|
||||
}
|
||||
_audio.PlayPvs(comp.BlankSFX, uid, AudioParams.Default.WithVolume(6f));
|
||||
Spawn(comp.BlankVFX, tgtpos);
|
||||
var newSpawn = _random.Pick(spawnPoints);
|
||||
var spawnTgt = Transform(newSpawn.Uid).Coordinates;
|
||||
var mobUid = Spawn(comp.SpawnWisp, spawnTgt);
|
||||
EnsureComp<InVoidComponent>(mobUid, out var inVoid);
|
||||
inVoid.OriginalBody = target;
|
||||
inVoid.ExitVoidTime = _timing.CurTime + comp.CosmicBlankDuration;
|
||||
_mind.TransferTo(mindEnt, mobUid);
|
||||
_stun.TryKnockdown(target, comp.CosmicBlankDuration + TimeSpan.FromSeconds(2), true);
|
||||
_popup.PopupEntity(Loc.GetString("cosmicability-blank-transfer"), mobUid, mobUid);
|
||||
_audio.PlayPvs(comp.BlankSFX, spawnTgt, AudioParams.Default.WithVolume(6f));
|
||||
_color.RaiseEffect(Color.CadetBlue, new List<EntityUid>() { target }, Filter.Pvs(target, entityManager: EntityManager));
|
||||
Spawn(comp.BlankVFX, spawnTgt);
|
||||
_cult.MalignEcho(uid);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
using Content.Server._DV.CosmicCult.Components;
|
||||
using Content.Server.Bible.Components;
|
||||
using Content.Server.Popups;
|
||||
using Content.Shared._DV.CosmicCult.Components;
|
||||
using Content.Shared._DV.CosmicCult;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Mindshield.Components;
|
||||
using Content.Shared.Mobs.Systems;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Stunnable;
|
||||
|
||||
namespace Content.Server._DV.CosmicCult.Abilities;
|
||||
|
||||
public sealed class CosmicConversionSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly CosmicCultRuleSystem _cultRule = default!;
|
||||
[Dependency] private readonly CosmicGlyphSystem _cosmicGlyph = default!;
|
||||
[Dependency] private readonly DamageableSystem _damageable = default!;
|
||||
[Dependency] private readonly MobStateSystem _mobState = default!;
|
||||
[Dependency] private readonly PopupSystem _popup = default!;
|
||||
[Dependency] private readonly SharedCosmicCultSystem _cosmicCult = default!;
|
||||
[Dependency] private readonly SharedStunSystem _stun = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<CosmicGlyphConversionComponent, TryActivateGlyphEvent>(OnConversionGlyph);
|
||||
}
|
||||
|
||||
private void OnConversionGlyph(Entity<CosmicGlyphConversionComponent> uid, ref TryActivateGlyphEvent args)
|
||||
{
|
||||
var possibleTargets = _cosmicGlyph.GetTargetsNearGlyph(uid, uid.Comp.ConversionRange, entity => !_cosmicCult.EntityIsCultist(entity));
|
||||
if (possibleTargets.Count == 0)
|
||||
{
|
||||
_popup.PopupEntity(Loc.GetString("cult-glyph-conditions-not-met"), uid, args.User);
|
||||
args.Cancel();
|
||||
return;
|
||||
}
|
||||
if (possibleTargets.Count > 1)
|
||||
{
|
||||
_popup.PopupEntity(Loc.GetString("cult-glyph-too-many-targets"), uid, args.User);
|
||||
args.Cancel();
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var target in possibleTargets)
|
||||
{
|
||||
if (_mobState.IsDead(target))
|
||||
{
|
||||
_popup.PopupEntity(Loc.GetString("cult-glyph-target-dead"), uid, args.User);
|
||||
args.Cancel();
|
||||
}
|
||||
else if (uid.Comp.NegateProtection == false && HasComp<BibleUserComponent>(target))
|
||||
{
|
||||
_popup.PopupEntity(Loc.GetString("cult-glyph-target-chaplain"), uid, args.User);
|
||||
args.Cancel();
|
||||
}
|
||||
else if (uid.Comp.NegateProtection == false && HasComp<MindShieldComponent>(target))
|
||||
{
|
||||
_popup.PopupEntity(Loc.GetString("cult-glyph-target-mindshield"), uid, args.User);
|
||||
args.Cancel();
|
||||
}
|
||||
else
|
||||
{
|
||||
_stun.TryStun(target, TimeSpan.FromSeconds(4f), false);
|
||||
_damageable.TryChangeDamage(target, uid.Comp.ConversionHeal * -1);
|
||||
_cultRule.CosmicConversion(uid, target);
|
||||
var finaleQuery = EntityQueryEnumerator<CosmicFinaleComponent>(); // Enumerator for The Monument's Finale
|
||||
while (finaleQuery.MoveNext(out var monument, out var comp) && comp.CurrentState == FinaleState.ActiveBuffer)
|
||||
{
|
||||
comp.BufferTimer -= TimeSpan.FromSeconds(45);
|
||||
_popup.PopupCoordinates(Loc.GetString("cosmiccult-finale-speedup"), Transform(monument).Coordinates, PopupType.Large);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
using System.Linq;
|
||||
using Content.Server.Bible.Components;
|
||||
using Content.Server.Flash;
|
||||
using Content.Server.Light.Components;
|
||||
using Content.Server.Light.EntitySystems;
|
||||
using Content.Shared._DV.CosmicCult;
|
||||
using Content.Shared._DV.CosmicCult.Components;
|
||||
using Content.Shared.Effects;
|
||||
using Content.Shared.Humanoid;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Mobs.Components;
|
||||
using Content.Shared.Physics;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Player;
|
||||
|
||||
namespace Content.Server._DV.CosmicCult.Abilities;
|
||||
|
||||
public sealed class CosmicGlareSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly CosmicCultSystem _cult = default!;
|
||||
[Dependency] private readonly EntityLookupSystem _lookup = default!;
|
||||
[Dependency] private readonly FlashSystem _flash = default!;
|
||||
[Dependency] private readonly PoweredLightSystem _poweredLight = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
[Dependency] private readonly SharedColorFlashEffectSystem _color = default!;
|
||||
[Dependency] private readonly SharedCosmicCultSystem _cosmicCult = default!;
|
||||
[Dependency] private readonly SharedInteractionSystem _interact = default!;
|
||||
|
||||
private HashSet<Entity<PoweredLightComponent>> _lights = [];
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<CosmicCultComponent, EventCosmicGlare>(OnCosmicGlare);
|
||||
}
|
||||
|
||||
private void OnCosmicGlare(Entity<CosmicCultComponent> uid, ref EventCosmicGlare args)
|
||||
{
|
||||
_audio.PlayPvs(uid.Comp.GlareSFX, uid);
|
||||
Spawn(uid.Comp.GlareVFX, Transform(uid).Coordinates);
|
||||
_cult.MalignEcho(uid);
|
||||
args.Handled = true;
|
||||
|
||||
_lights.Clear();
|
||||
_lookup.GetEntitiesInRange<PoweredLightComponent>(Transform(uid).Coordinates, uid.Comp.CosmicGlareRange, _lights);
|
||||
|
||||
foreach (var entity in _lights)
|
||||
_poweredLight.TryDestroyBulb(entity);
|
||||
|
||||
var targetFilter = Filter.Pvs(uid).RemoveWhere(player =>
|
||||
{
|
||||
if (player.AttachedEntity == null)
|
||||
return true;
|
||||
|
||||
var ent = player.AttachedEntity.Value;
|
||||
if (!HasComp<MobStateComponent>(ent) || !HasComp<HumanoidAppearanceComponent>(ent) || _cosmicCult.EntityIsCultist(ent) || HasComp<BibleUserComponent>(ent))
|
||||
return true;
|
||||
|
||||
return !_interact.InRangeUnobstructed((uid, Transform(uid)), (ent, Transform(ent)), range: 0, collisionMask: CollisionGroup.Impassable);
|
||||
});
|
||||
|
||||
var targets = new HashSet<NetEntity>(targetFilter.RemovePlayerByAttachedEntity(uid).Recipients.Select(ply => GetNetEntity(ply.AttachedEntity!.Value)));
|
||||
foreach (var target in targets)
|
||||
{
|
||||
_flash.Flash(GetEntity(target), uid, args.Action, (float)uid.Comp.CosmicGlareDuration.TotalMilliseconds, uid.Comp.CosmicGlarePenalty, false, false, uid.Comp.CosmicGlareStun);
|
||||
_color.RaiseEffect(Color.CadetBlue, new List<EntityUid>() { GetEntity(target) }, Filter.Pvs(GetEntity(target), entityManager: EntityManager));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
using Content.Shared._DV.CosmicCult;
|
||||
using Content.Shared._DV.CosmicCult.Components;
|
||||
using Content.Shared.Damage;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Server._DV.CosmicCult.Abilities;
|
||||
|
||||
public sealed class CosmicImpositionSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly CosmicCultSystem _cult = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<CosmicImposingComponent, BeforeDamageChangedEvent>(OnImpositionDamaged);
|
||||
SubscribeLocalEvent<CosmicCultComponent, EventCosmicImposition>(OnCosmicImposition);
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
|
||||
var query = EntityQueryEnumerator<CosmicImposingComponent>();
|
||||
while (query.MoveNext(out var uid, out var comp))
|
||||
{
|
||||
if (_timing.CurTime >= comp.Expiry)
|
||||
{
|
||||
RemComp(uid, comp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnCosmicImposition(Entity<CosmicCultComponent> uid, ref EventCosmicImposition args)
|
||||
{
|
||||
EnsureComp<CosmicImposingComponent>(uid, out var comp);
|
||||
comp.Expiry = _timing.CurTime + uid.Comp.CosmicImpositionDuration;
|
||||
Spawn(uid.Comp.ImpositionVFX, Transform(uid).Coordinates);
|
||||
args.Handled = true;
|
||||
_audio.PlayPvs(uid.Comp.ImpositionSFX, uid, AudioParams.Default.WithVariation(0.05f));
|
||||
_cult.MalignEcho(uid);
|
||||
}
|
||||
|
||||
private void OnImpositionDamaged(Entity<CosmicImposingComponent> uid, ref BeforeDamageChangedEvent args)
|
||||
{
|
||||
args.Cancelled = true;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
using Content.Server.Doors.Systems;
|
||||
using Content.Shared._DV.CosmicCult;
|
||||
using Content.Shared._DV.CosmicCult.Components;
|
||||
using Content.Shared.Doors.Components;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
|
||||
namespace Content.Server._DV.CosmicCult.Abilities;
|
||||
|
||||
public sealed class CosmicIngressSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly CosmicCultSystem _cult = default!;
|
||||
[Dependency] private readonly DoorSystem _door = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<CosmicCultComponent, EventCosmicIngress>(OnCosmicIngress);
|
||||
}
|
||||
|
||||
private void OnCosmicIngress(Entity<CosmicCultComponent> uid, ref EventCosmicIngress args)
|
||||
{
|
||||
var target = args.Target;
|
||||
if (args.Handled)
|
||||
return;
|
||||
|
||||
args.Handled = true;
|
||||
if (uid.Comp.CosmicEmpowered && TryComp<DoorBoltComponent>(target, out var doorBolt))
|
||||
_door.SetBoltsDown((target, doorBolt), false);
|
||||
_door.StartOpening(target);
|
||||
_audio.PlayPvs(uid.Comp.IngressSFX, uid);
|
||||
Spawn(uid.Comp.AbsorbVFX, Transform(target).Coordinates);
|
||||
_cult.MalignEcho(uid);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
using Content.Server.Bible.Components;
|
||||
using Content.Server.Polymorph.Systems;
|
||||
using Content.Server.Popups;
|
||||
using Content.Shared._DV.CosmicCult;
|
||||
using Content.Shared._DV.CosmicCult.Components;
|
||||
using Content.Shared._DV.CosmicCult.Components.Examine;
|
||||
using Content.Shared.Humanoid;
|
||||
using Content.Shared.IdentityManagement;
|
||||
using Content.Shared.Polymorph;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server._DV.CosmicCult.Abilities;
|
||||
|
||||
public sealed class CosmicLapseSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly CosmicCultSystem _cult = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototype = default!;
|
||||
[Dependency] private readonly PolymorphSystem _polymorph = default!;
|
||||
[Dependency] private readonly PopupSystem _popup = default!;
|
||||
|
||||
private static readonly ProtoId<PolymorphPrototype> HumanLapse = "CosmicLapseMobHuman";
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<CosmicCultComponent, EventCosmicLapse>(OnCosmicLapse);
|
||||
}
|
||||
|
||||
private void OnCosmicLapse(Entity<CosmicCultComponent> uid, ref EventCosmicLapse action)
|
||||
{
|
||||
if (action.Handled || HasComp<CosmicBlankComponent>(action.Target) || HasComp<CleanseCultComponent>(action.Target) || HasComp<BibleUserComponent>(action.Target))
|
||||
{
|
||||
_popup.PopupEntity(Loc.GetString("cosmicability-generic-fail"), uid, uid);
|
||||
return;
|
||||
}
|
||||
action.Handled = true;
|
||||
var tgtpos = Transform(action.Target).Coordinates;
|
||||
Spawn(uid.Comp.LapseVFX, tgtpos);
|
||||
_popup.PopupEntity(Loc.GetString("cosmicability-lapse-success", ("target", Identity.Entity(action.Target, EntityManager))), uid, uid);
|
||||
var species = Comp<HumanoidAppearanceComponent>(action.Target).Species;
|
||||
var polymorphId = "CosmicLapseMob" + species;
|
||||
|
||||
if (_prototype.HasIndex<PolymorphPrototype>(polymorphId))
|
||||
_polymorph.PolymorphEntity(action.Target, polymorphId);
|
||||
else
|
||||
_polymorph.PolymorphEntity(action.Target, HumanLapse);
|
||||
_cult.MalignEcho(uid);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,144 @@
|
|||
using System.Numerics;
|
||||
using Content.Server.Actions;
|
||||
using Content.Server.Popups;
|
||||
using Content.Server.Station.Components;
|
||||
using Content.Server.Station.Systems;
|
||||
using Content.Shared._DV.CosmicCult.Components;
|
||||
using Content.Shared._DV.CosmicCult;
|
||||
using Content.Shared.Maps;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server._DV.CosmicCult.Abilities;
|
||||
|
||||
public sealed class CosmicMonumentSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly ActionsSystem _actions = default!;
|
||||
[Dependency] private readonly CosmicCultRuleSystem _cultRule = default!;
|
||||
[Dependency] private readonly EntityLookupSystem _lookup = default!;
|
||||
[Dependency] private readonly ITileDefinitionManager _tileDef = default!;
|
||||
[Dependency] private readonly MonumentSystem _monument = default!;
|
||||
[Dependency] private readonly PopupSystem _popup = default!;
|
||||
[Dependency] private readonly SharedMapSystem _map = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||
[Dependency] private readonly StationSystem _station = default!;
|
||||
|
||||
private static readonly EntProtoId MonumentCollider = "MonumentCollider";
|
||||
private static readonly EntProtoId MonumentCosmicCultMoveEnd = "MonumentCosmicCultMoveEnd";
|
||||
private static readonly EntProtoId MonumentCosmicCultMoveStart = "MonumentCosmicCultMoveStart";
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<CosmicCultLeadComponent, EventCosmicPlaceMonument>(OnCosmicPlaceMonument);
|
||||
SubscribeLocalEvent<CosmicCultLeadComponent, EventCosmicMoveMonument>(OnCosmicMoveMonument);
|
||||
}
|
||||
|
||||
//todo attack this with a debugger at some point, it seems to un-prime before it should sometimes?
|
||||
//no idea why, might be something to do with verifying placement inside the action's execution instead of in an attemptEvent beforehand?
|
||||
//yeah it is - if the action is primed but fails at this step, then the action becomes un-primed but does not properly go through, requiring it to be primed again
|
||||
//works fine:tm: for now with a slightly jank fix on the client end of things, will probably want to dig deeper?
|
||||
//actually might not want to fix it?
|
||||
//I've got the client stuff working well & this works out to making the ghost stay up so long as you consistently try (& fail) to place the monument
|
||||
//guess I should ask for specific feedback for this one tiny feature?
|
||||
private void OnCosmicPlaceMonument(Entity<CosmicCultLeadComponent> uid, ref EventCosmicPlaceMonument args)
|
||||
{
|
||||
if (!VerifyPlacement(uid, out var pos))
|
||||
return;
|
||||
|
||||
_actions.RemoveAction(uid, uid.Comp.CosmicMonumentPlaceActionEntity);
|
||||
|
||||
Spawn(MonumentCollider, pos);
|
||||
var monument = Spawn(uid.Comp.MonumentPrototype, pos);
|
||||
|
||||
_cultRule.TransferCultAssociation(uid, monument);
|
||||
}
|
||||
|
||||
private void OnCosmicMoveMonument(Entity<CosmicCultLeadComponent> uid, ref EventCosmicMoveMonument args)
|
||||
{
|
||||
if (_cultRule.AssociatedGamerule(uid) is not {} cult)
|
||||
return;
|
||||
|
||||
if (!VerifyPlacement(uid, out var pos))
|
||||
return;
|
||||
|
||||
_actions.RemoveAction(uid, uid.Comp.CosmicMonumentMoveActionEntity);
|
||||
|
||||
//delete all old monument colliders for 100% safety
|
||||
var colliderQuery = EntityQueryEnumerator<MonumentCollisionComponent>();
|
||||
while (colliderQuery.MoveNext(out var collider, out _))
|
||||
{
|
||||
QueueDel(collider);
|
||||
}
|
||||
|
||||
//spawn the destination effect first because we only need one
|
||||
var destEnt = Spawn(MonumentCosmicCultMoveEnd, pos);
|
||||
var destComp = EnsureComp<MonumentMoveDestinationComponent>(destEnt);
|
||||
destComp.Monument = cult.Comp.MonumentInGame;
|
||||
var coords = Transform(cult.Comp.MonumentInGame).Coordinates;
|
||||
Spawn(MonumentCollider, pos); //spawn a new collider
|
||||
|
||||
Spawn(MonumentCosmicCultMoveStart, coords);
|
||||
Spawn(MonumentCollider, Transform(cult.Comp.MonumentInGame).Coordinates); //spawn a new collider
|
||||
|
||||
_monument.PhaseOutMonument(cult.Comp.MonumentInGame);
|
||||
destComp.PhaseInTimer = cult.Comp.MonumentInGame.Comp.PhaseOutTimer + TimeSpan.FromSeconds(0.75);
|
||||
}
|
||||
|
||||
//todo this can probably be mostly moved to shared but my brain isn't cooperating w/ that rn
|
||||
private bool VerifyPlacement(Entity<CosmicCultLeadComponent> uid, out EntityCoordinates outPos)
|
||||
{
|
||||
//MAKE SURE WE'RE STANDING ON A GRID
|
||||
var xform = Transform(uid);
|
||||
outPos = new EntityCoordinates();
|
||||
|
||||
if (!TryComp<MapGridComponent>(xform.GridUid, out var grid))
|
||||
{
|
||||
_popup.PopupEntity(Loc.GetString("cosmicability-monument-spawn-error-grid"), uid, uid);
|
||||
return false;
|
||||
}
|
||||
|
||||
var localTile = _map.GetTileRef(xform.GridUid.Value, grid, xform.Coordinates);
|
||||
var targetIndices = localTile.GridIndices + new Vector2i(0, 1);
|
||||
var pos = _map.ToCenterCoordinates(xform.GridUid.Value, targetIndices, grid);
|
||||
outPos = pos;
|
||||
var box = new Box2(pos.Position + new Vector2(-1.4f, -0.4f), pos.Position + new Vector2(1.4f, 0.4f));
|
||||
|
||||
//CHECK IF IT'S BEING PLACED CHEESILY CLOSE TO SPACE
|
||||
var spaceDistance = 3;
|
||||
var worldPos = _transform.GetWorldPosition(xform);
|
||||
foreach (var tile in _map.GetTilesIntersecting(xform.GridUid.Value, grid, new Circle(worldPos, spaceDistance)))
|
||||
{
|
||||
if (tile.IsSpace(_tileDef))
|
||||
{
|
||||
_popup.PopupEntity(Loc.GetString("cosmicability-monument-spawn-error-space", ("DISTANCE", spaceDistance)), uid, uid);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
//CHECK IF WE'RE ON THE STATION OR IF SOMEONE'S TRYING TO SNEAK THIS ONTO SOMETHING SMOL
|
||||
var station = _station.GetStationInMap(xform.MapID);
|
||||
|
||||
EntityUid? stationGrid = null;
|
||||
|
||||
if (TryComp<StationDataComponent>(station, out var stationData))
|
||||
stationGrid = _station.GetLargestGrid(stationData);
|
||||
|
||||
if (stationGrid is not null && stationGrid != xform.GridUid)
|
||||
{
|
||||
_popup.PopupEntity(Loc.GetString("cosmicability-monument-spawn-error-station"), uid, uid);
|
||||
return false;
|
||||
}
|
||||
|
||||
//CHECK FOR ENTITY AND ENVIRONMENTAL INTERSECTIONS
|
||||
if (_lookup.AnyLocalEntitiesIntersecting(xform.GridUid.Value, box, LookupFlags.Dynamic | LookupFlags.Static, uid))
|
||||
{
|
||||
_popup.PopupEntity(Loc.GetString("cosmicability-monument-spawn-error-intersection"), uid, uid);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
using System.Numerics;
|
||||
using Content.Server.Bible.Components;
|
||||
using Content.Shared._DV.CosmicCult.Components;
|
||||
using Content.Shared._DV.CosmicCult;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Effects;
|
||||
using Content.Shared.Mobs.Components;
|
||||
using Content.Shared.Stunnable;
|
||||
using Content.Shared.Weapons.Ranged.Systems;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Physics.Events;
|
||||
using Robust.Shared.Physics.Systems;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server._DV.CosmicCult.Abilities;
|
||||
|
||||
public sealed class CosmicNovaSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly CosmicCultSystem _cult = default!;
|
||||
[Dependency] private readonly DamageableSystem _damageable = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
[Dependency] private readonly SharedColorFlashEffectSystem _color = default!;
|
||||
[Dependency] private readonly SharedCosmicCultSystem _cosmicCult = default!;
|
||||
[Dependency] private readonly SharedGunSystem _gun = default!;
|
||||
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
|
||||
[Dependency] private readonly SharedStunSystem _stun = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||
|
||||
private static readonly EntProtoId Projectile = "ProjectileCosmicNova";
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<CosmicCultComponent, EventCosmicNova>(OnCosmicNova);
|
||||
SubscribeLocalEvent<CosmicAstralNovaComponent, StartCollideEvent>(OnNovaCollide);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This is the basic spell projectile code but updated to use non-obsolete functions, all so i can change the default projectile speed. Fuck.
|
||||
/// </summary>
|
||||
private void OnCosmicNova(Entity<CosmicCultComponent> uid, ref EventCosmicNova args)
|
||||
{
|
||||
var startPos = _transform.GetMapCoordinates(args.Performer);
|
||||
var targetPos = _transform.ToMapCoordinates(args.Target);
|
||||
var userVelocity = _physics.GetMapLinearVelocity(args.Performer);
|
||||
|
||||
var delta = targetPos.Position - startPos.Position;
|
||||
if (delta.EqualsApprox(Vector2.Zero))
|
||||
delta = new(.01f, 0);
|
||||
|
||||
args.Handled = true;
|
||||
var ent = Spawn(Projectile, startPos);
|
||||
_gun.ShootProjectile(ent, delta, userVelocity, args.Performer, args.Performer, 5f);
|
||||
_audio.PlayPvs(uid.Comp.NovaCastSFX, uid, AudioParams.Default.WithVariation(0.1f));
|
||||
_cult.MalignEcho(uid);
|
||||
}
|
||||
|
||||
private void OnNovaCollide(Entity<CosmicAstralNovaComponent> uid, ref StartCollideEvent args)
|
||||
{
|
||||
if (_cosmicCult.EntityIsCultist(args.OtherEntity) || HasComp<BibleUserComponent>(args.OtherEntity) || !HasComp<MobStateComponent>(args.OtherEntity))
|
||||
return;
|
||||
if (uid.Comp.DoStun)
|
||||
_stun.TryParalyze(args.OtherEntity, TimeSpan.FromSeconds(2f), false);
|
||||
_damageable.TryChangeDamage(args.OtherEntity, uid.Comp.CosmicNovaDamage); // This'll probably trigger two or three times because of how collision works. I'm not being lazy here, it's a feature (kinda /s)
|
||||
_color.RaiseEffect(Color.Red, new List<EntityUid>() { args.OtherEntity }, Filter.Pvs(args.OtherEntity, entityManager: EntityManager));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
using Content.Server._DV.CosmicCult.Components;
|
||||
using Content.Shared._DV.CosmicCult;
|
||||
using Content.Shared._DV.CosmicCult.Components;
|
||||
using Content.Shared._DV.CosmicCult.Components.Examine;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Mind;
|
||||
using Content.Shared.Stunnable;
|
||||
|
||||
namespace Content.Server._DV.CosmicCult.Abilities;
|
||||
|
||||
public sealed class CosmicReturnSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly DamageableSystem _damageable = default!;
|
||||
[Dependency] private readonly SharedMindSystem _mind = default!;
|
||||
[Dependency] private readonly SharedStunSystem _stun = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<CosmicAstralBodyComponent, EventCosmicReturn>(OnCosmicReturn);
|
||||
SubscribeLocalEvent<CosmicGlyphAstralProjectionComponent, TryActivateGlyphEvent>(OnAstralProjectionGlyph);
|
||||
}
|
||||
|
||||
private void OnAstralProjectionGlyph(Entity<CosmicGlyphAstralProjectionComponent> uid, ref TryActivateGlyphEvent args)
|
||||
{
|
||||
_damageable.TryChangeDamage(args.User, uid.Comp.ProjectionDamage, true);
|
||||
var projectionEnt = Spawn(uid.Comp.SpawnProjection, Transform(uid).Coordinates);
|
||||
if (_mind.TryGetMind(args.User, out var mindId, out var _))
|
||||
_mind.TransferTo(mindId, projectionEnt);
|
||||
EnsureComp<CosmicBlankComponent>(args.User);
|
||||
EnsureComp<CosmicAstralBodyComponent>(projectionEnt, out var astralComp);
|
||||
var mind = Comp<MindComponent>(mindId);
|
||||
mind.PreventGhosting = true;
|
||||
astralComp.OriginalBody = args.User;
|
||||
_stun.TryKnockdown(args.User, TimeSpan.FromSeconds(2), true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This action is exclusive to the Glyph-created Astral Projection, and allows the user to return to their original body.
|
||||
/// </summary>
|
||||
private void OnCosmicReturn(Entity<CosmicAstralBodyComponent> uid, ref EventCosmicReturn args)
|
||||
{
|
||||
if (_mind.TryGetMind(args.Performer, out var mindId, out var _))
|
||||
_mind.TransferTo(mindId, uid.Comp.OriginalBody);
|
||||
var mind = Comp<MindComponent>(mindId);
|
||||
mind.PreventGhosting = false;
|
||||
QueueDel(uid);
|
||||
RemComp<CosmicBlankComponent>(uid.Comp.OriginalBody);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,108 @@
|
|||
using Content.Server.Ghost;
|
||||
using Content.Server.Light.Components;
|
||||
using Content.Shared._DV.CosmicCult;
|
||||
using Content.Shared._DV.CosmicCult.Components;
|
||||
using Content.Shared.Alert;
|
||||
using Content.Shared.DoAfter;
|
||||
using Content.Shared.IdentityManagement;
|
||||
using Content.Shared.Mind;
|
||||
using Content.Shared.Mobs;
|
||||
using Content.Shared.Mobs.Components;
|
||||
using Content.Shared.NPC;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.StatusEffect;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
namespace Content.Server._DV.CosmicCult.Abilities;
|
||||
|
||||
public sealed class CosmicSiphonSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly AlertsSystem _alerts = default!;
|
||||
[Dependency] private readonly CosmicCultRuleSystem _cultRule = default!;
|
||||
[Dependency] private readonly EntityLookupSystem _lookup = default!;
|
||||
[Dependency] private readonly GhostSystem _ghost = default!;
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
|
||||
[Dependency] private readonly SharedMindSystem _mind = default!;
|
||||
[Dependency] private readonly SharedPopupSystem _popup = default!;
|
||||
[Dependency] private readonly StatusEffectsSystem _statusEffects = default!;
|
||||
[Dependency] private readonly CosmicCultSystem _cosmicCult = default!;
|
||||
|
||||
private readonly HashSet<Entity<PoweredLightComponent>> _lights = [];
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<CosmicCultComponent, EventCosmicSiphon>(OnCosmicSiphon);
|
||||
SubscribeLocalEvent<CosmicCultComponent, EventCosmicSiphonDoAfter>(OnCosmicSiphonDoAfter);
|
||||
}
|
||||
|
||||
private void OnCosmicSiphon(Entity<CosmicCultComponent> uid, ref EventCosmicSiphon args)
|
||||
{
|
||||
if (uid.Comp.EntropyStored >= uid.Comp.EntropyStoredCap)
|
||||
{
|
||||
_popup.PopupEntity(Loc.GetString("cosmicability-siphon-full"), uid, uid);
|
||||
return;
|
||||
}
|
||||
if (HasComp<ActiveNPCComponent>(args.Target) || TryComp<MobStateComponent>(args.Target, out var state) && state.CurrentState != MobState.Alive)
|
||||
{
|
||||
_popup.PopupEntity(Loc.GetString("cosmicability-siphon-fail", ("target", Identity.Entity(args.Target, EntityManager))), uid, uid);
|
||||
return;
|
||||
}
|
||||
if (args.Handled)
|
||||
return;
|
||||
|
||||
var doargs = new DoAfterArgs(EntityManager, uid, uid.Comp.CosmicSiphonDelay, new EventCosmicSiphonDoAfter(), uid, args.Target)
|
||||
{
|
||||
DistanceThreshold = 2f,
|
||||
Hidden = true,
|
||||
BreakOnHandChange = true,
|
||||
BreakOnDamage = true,
|
||||
BreakOnMove = true,
|
||||
BreakOnDropItem = true,
|
||||
};
|
||||
args.Handled = true;
|
||||
_doAfter.TryStartDoAfter(doargs);
|
||||
}
|
||||
|
||||
private void OnCosmicSiphonDoAfter(Entity<CosmicCultComponent> uid, ref EventCosmicSiphonDoAfter args)
|
||||
{
|
||||
if (args.Args.Target is not { } target)
|
||||
return;
|
||||
if (args.Cancelled || args.Handled)
|
||||
return;
|
||||
args.Handled = true;
|
||||
|
||||
if (_mind.TryGetMind(uid, out var _, out var mind) && mind.Session != null)
|
||||
RaiseNetworkEvent(new CosmicSiphonIndicatorEvent(GetNetEntity(target)), mind.Session);
|
||||
|
||||
uid.Comp.EntropyStored += uid.Comp.CosmicSiphonQuantity;
|
||||
uid.Comp.EntropyBudget += uid.Comp.CosmicSiphonQuantity;
|
||||
Dirty(uid, uid.Comp);
|
||||
|
||||
_statusEffects.TryAddStatusEffect<CosmicEntropyDebuffComponent>(target, "EntropicDegen", TimeSpan.FromSeconds(21), true);
|
||||
if (_cosmicCult.EntityIsCultist(target))
|
||||
{
|
||||
_popup.PopupEntity(Loc.GetString("cosmicability-siphon-cultist-success", ("target", Identity.Entity(target, EntityManager))), uid, uid);
|
||||
}
|
||||
else
|
||||
{
|
||||
_popup.PopupEntity(Loc.GetString("cosmicability-siphon-success", ("target", Identity.Entity(target, EntityManager))), uid, uid);
|
||||
_alerts.ShowAlert(uid, uid.Comp.EntropyAlert);
|
||||
_cultRule.IncrementCultObjectiveEntropy(uid);
|
||||
}
|
||||
|
||||
if (uid.Comp.CosmicEmpowered) // if you're empowered there's a 50% chance to flicker lights on siphon
|
||||
{
|
||||
_lights.Clear();
|
||||
_lookup.GetEntitiesInRange<PoweredLightComponent>(Transform(uid).Coordinates, 5, _lights, LookupFlags.StaticSundries);
|
||||
foreach (var light in _lights) // static range of 5. because.
|
||||
{
|
||||
if (!_random.Prob(0.5f))
|
||||
continue;
|
||||
_ghost.DoGhostBooEvent(light);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
using System.Linq;
|
||||
using Content.Shared._DV.CosmicCult.Components;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Whitelist;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
namespace Content.Server._DV.CosmicCult.Abilities;
|
||||
|
||||
public sealed class CosmicTransmuteSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly EntityLookupSystem _lookup = default!;
|
||||
[Dependency] private readonly EntityWhitelistSystem _entityWhitelist = default!;
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly SharedPopupSystem _popup = default!;
|
||||
|
||||
private readonly HashSet<EntityUid> _entities = [];
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<CosmicGlyphTransmuteComponent, TryActivateGlyphEvent>(OnTransmuteGlyph);
|
||||
}
|
||||
|
||||
private void OnTransmuteGlyph(Entity<CosmicGlyphTransmuteComponent> uid, ref TryActivateGlyphEvent args)
|
||||
{
|
||||
var tgtpos = Transform(uid).Coordinates;
|
||||
var possibleTargets = GatherEntities(uid);
|
||||
if (possibleTargets.Count == 0)
|
||||
{
|
||||
_popup.PopupEntity(Loc.GetString("cult-glyph-conditions-not-met"), uid, args.User);
|
||||
args.Cancel();
|
||||
return;
|
||||
}
|
||||
if (possibleTargets.Count > 1)
|
||||
{
|
||||
_popup.PopupEntity(Loc.GetString("cult-glyph-too-many-targets"), uid, args.User);
|
||||
args.Cancel();
|
||||
return;
|
||||
}
|
||||
|
||||
Spawn(_random.Pick(uid.Comp.Transmutations), tgtpos);
|
||||
QueueDel(possibleTargets.First());
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets all whitelisted entities near a glyph.
|
||||
/// </summary>
|
||||
private HashSet<EntityUid> GatherEntities(Entity<CosmicGlyphTransmuteComponent> ent)
|
||||
{
|
||||
_entities.Clear();
|
||||
_lookup.GetEntitiesInRange(Transform(ent).Coordinates, ent.Comp.TransmuteRange, _entities);
|
||||
_entities.RemoveWhere(item => !_entityWhitelist.IsValid(ent.Comp.Whitelist, item));
|
||||
return _entities;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
using Content.Shared._DV.CosmicCult.Components;
|
||||
using Content.Shared.EntityEffects;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server._DV.CosmicCult;
|
||||
|
||||
public sealed partial class CleanseCult : EntityEffect
|
||||
{
|
||||
protected override string? ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
|
||||
{
|
||||
return Loc.GetString("reagent-effect-guidebook-cleanse-cultist", ("chance", Probability));
|
||||
}
|
||||
|
||||
public override void Effect(EntityEffectBaseArgs args)
|
||||
{
|
||||
var entityManager = args.EntityManager;
|
||||
var uid = args.TargetEntity;
|
||||
if (entityManager.HasComponent<CosmicCultComponent>(uid) || entityManager.HasComponent<RogueAscendedInfectionComponent>(uid))
|
||||
entityManager.EnsureComponent<CleanseCultComponent>(uid); // We just slap them with the component and let the Deconversion system handle the rest.
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
using Content.Shared.Damage;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server._DV.CosmicCult.Components;
|
||||
|
||||
[RegisterComponent, AutoGenerateComponentState]
|
||||
[Access(typeof(DeconversionSystem))]
|
||||
public sealed partial class CleanseOnUseComponent : Component
|
||||
{
|
||||
[DataField] public TimeSpan UseTime = TimeSpan.FromSeconds(10);
|
||||
|
||||
[DataField] public SoundSpecifier SizzleSound = new SoundPathSpecifier("/Audio/Effects/lightburn.ogg");
|
||||
|
||||
[DataField] public SoundSpecifier CleanseSound = new SoundPathSpecifier("/Audio/_DV/CosmicCult/cleanse_deconversion.ogg");
|
||||
|
||||
[DataField] public SoundSpecifier MalignSound = new SoundPathSpecifier("/Audio/_DV/CosmicCult/glyph_trigger.ogg");
|
||||
|
||||
[DataField] public EntProtoId CleanseVFX = "CleanseEffectVFX";
|
||||
|
||||
[DataField] public EntProtoId MalignVFX = "CosmicGenericVFX";
|
||||
|
||||
[DataField] public bool Enabled = true;
|
||||
|
||||
/// <summary>
|
||||
/// When True allows an item to purge the Cosmic Cult's Malign Rifts onInteractInHand, utilized exclusively by the CosmicRiftSystem.
|
||||
/// </summary>
|
||||
[DataField] public bool CanPurge;
|
||||
|
||||
[DataField]
|
||||
public DamageSpecifier SelfDamage = new()
|
||||
{
|
||||
DamageDict = new() {
|
||||
{ "Caustic", 15 }
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
using Content.Server._DV.CosmicCult.Abilities;
|
||||
|
||||
namespace Content.Server._DV.CosmicCult.Components;
|
||||
|
||||
[RegisterComponent, Access(typeof(CosmicReturnSystem))]
|
||||
public sealed partial class CosmicAstralBodyComponent : Component
|
||||
{
|
||||
[DataField]
|
||||
public EntityUid OriginalBody;
|
||||
}
|
||||
|
|
@ -0,0 +1,115 @@
|
|||
using Content.Server._DV.CosmicCult.EntitySystems;
|
||||
using Content.Shared.Maps;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
|
||||
|
||||
namespace Content.Server._DV.CosmicCult.Components;
|
||||
|
||||
[RegisterComponent, Access(typeof(CosmicCorruptingSystem))]
|
||||
[AutoGenerateComponentPause]
|
||||
public sealed partial class CosmicCorruptingComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Our timer for corruption checks.
|
||||
/// </summary>
|
||||
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
|
||||
[AutoPausedField] public TimeSpan CorruptionTimer = default!;
|
||||
|
||||
/// <summary>
|
||||
/// the list of tiles that can be corrupted by this corruptor.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public HashSet<Vector2i> CorruptableTiles = [];
|
||||
|
||||
/// <summary>
|
||||
/// If this corruption source can move. if true, only corrupt the immediate area around it.
|
||||
/// Slightly hacky but works for our purposes.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public bool Mobile;
|
||||
|
||||
/// <summary>
|
||||
/// if this corruption source should floodfill through all corruptible tiles to initialise its corruptible tile set on activation.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public bool FloodFillStarting;
|
||||
|
||||
/// <summary>
|
||||
/// How many times has this corruption source ticked?
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public int CorruptionTicks;
|
||||
|
||||
/// <summary>
|
||||
/// The maximum amount of ticks this source can do.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public int CorruptionMaxTicks = 50;
|
||||
|
||||
/// <summary>
|
||||
/// The chance that a tile and/or wall is replaced.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float CorruptionChance = 0.51f;
|
||||
|
||||
/// <summary>
|
||||
/// The reduction applied to corruption chance every tick.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float CorruptionReduction;
|
||||
|
||||
/// <summary>
|
||||
/// Wether or not the CosmicCorruptingSystem should be running on this entity. use CosmicCorruptingSystem.Enable() instead of directly interacting with this variable.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public bool Enabled = true;
|
||||
|
||||
/// <summary>
|
||||
/// Wether or not the CosmicCorruptingSystem should spawn VFX when converting tiles and walls.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public bool UseVFX = true;
|
||||
|
||||
/// <summary>
|
||||
/// Wether or not the CosmicCorruptingSystem should ignore this component when it reaches max growth. Saves performance.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public bool AutoDisable = true;
|
||||
|
||||
/// <summary>
|
||||
/// How much time between tile corruptions.
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public TimeSpan CorruptionSpeed = TimeSpan.FromSeconds(6);
|
||||
|
||||
/// <summary>
|
||||
/// The tile we spawn when replacing a normal tile.
|
||||
/// </summary>
|
||||
[DataField] //not a dict like the entity conversion below because there's too many fucking tiles
|
||||
public ProtoId<ContentTileDefinition> ConversionTile = "FloorCosmicCorruption";
|
||||
|
||||
/// <summary>
|
||||
/// Dictionary for what entities to convert to which prototypes. Similar to CosmicCorruptibleComponent, but
|
||||
/// non-inheriting.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public Dictionary<EntProtoId, EntProtoId> EntityConversionDict = new Dictionary<EntProtoId, EntProtoId>()
|
||||
{
|
||||
{"Window", "WindowCosmicCult"},
|
||||
{"Table", "CosmicTable"},
|
||||
{"Chair", "CosmicChair"},
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// The VFX entity we spawn when corruption occurs.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public EntProtoId TileConvertVFX = "CosmicFloorSpawnVFX";
|
||||
|
||||
/// <summary>
|
||||
/// The VFX entity we spawn when walls get deleted.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public EntProtoId TileDisintegrateVFX = "CosmicGenericVFX";
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,137 @@
|
|||
using Content.Server.RoundEnd;
|
||||
using Content.Shared._DV.CosmicCult.Components;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
|
||||
|
||||
namespace Content.Server._DV.CosmicCult.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Component for the CosmicCultRuleSystem that should store gameplay info.
|
||||
/// </summary>
|
||||
[RegisterComponent, Access(typeof(CosmicCultRuleSystem))]
|
||||
[AutoGenerateComponentPause]
|
||||
public sealed partial class CosmicCultRuleComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// What happens if all of the cultists die.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public RoundEndBehavior RoundEndBehavior = RoundEndBehavior.ShuttleCall;
|
||||
|
||||
/// <summary>
|
||||
/// Sender for shuttle call.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public LocId RoundEndTextSender = "comms-console-announcement-title-centcom";
|
||||
|
||||
/// <summary>
|
||||
/// Text for shuttle call.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public LocId RoundEndTextShuttleCall = "cosmiccult-elimination-shuttle-call";
|
||||
|
||||
/// <summary>
|
||||
/// Text for announcement.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public LocId RoundEndTextAnnouncement = "cosmiccult-elimination-announcement";
|
||||
|
||||
/// <summary>
|
||||
/// Time for emergency shuttle arrival.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public TimeSpan EvacShuttleTime = TimeSpan.FromMinutes(5);
|
||||
|
||||
[DataField]
|
||||
public HashSet<EntityUid> Cultists = [];
|
||||
|
||||
[DataField]
|
||||
public bool WinLocked;
|
||||
|
||||
[DataField]
|
||||
public WinType WinType = WinType.CrewMinor;
|
||||
|
||||
/// <summary>
|
||||
/// The cult's monument
|
||||
/// </summary>
|
||||
public Entity<MonumentComponent> MonumentInGame;
|
||||
|
||||
/// <summary>
|
||||
/// The slow zone of the spawned monument
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public EntityUid MonumentSlowZone;
|
||||
|
||||
/// <summary>
|
||||
/// Current tier of the cult
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public int CurrentTier;
|
||||
|
||||
/// <summary>
|
||||
/// Amount of present crew
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public int TotalCrew;
|
||||
|
||||
/// <summary>
|
||||
/// Amount of cultists
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public int TotalCult;
|
||||
|
||||
/// <summary>
|
||||
/// Percentage of crew that have been converted into cultists
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public double PercentConverted;
|
||||
|
||||
/// <summary>
|
||||
/// How much entropy has been siphoned by the cult
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public int EntropySiphoned;
|
||||
|
||||
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), AutoPausedField]
|
||||
public TimeSpan? StewardVoteTimer;
|
||||
|
||||
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), AutoPausedField]
|
||||
public TimeSpan? PrepareFinaleTimer;
|
||||
|
||||
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), AutoPausedField]
|
||||
public TimeSpan? Tier3DelayTimer;
|
||||
|
||||
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), AutoPausedField]
|
||||
public TimeSpan? Tier2DelayTimer;
|
||||
}
|
||||
|
||||
public enum WinType : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// Cult complete win. The Cosmic Cult beckoned the final curtain call.
|
||||
/// </summary>
|
||||
CultComplete,
|
||||
/// <summary>
|
||||
/// Cult major win. The Monument reached Stage 3 and was fully empowered.
|
||||
/// </summary>
|
||||
CultMajor,
|
||||
/// <summary>
|
||||
/// Cult minor win. Even if the crew escaped, The Monument reached Stage 3.
|
||||
/// </summary>
|
||||
CultMinor,
|
||||
/// <summary>
|
||||
/// Neutral. The Monument didn't reach Stage 3, The crew escaped, but the Cult Leader also escaped.
|
||||
/// </summary>
|
||||
Neutral,
|
||||
/// <summary>
|
||||
/// Crew minor win. The monument didn't reach Stage 3, The crew escaped, and Cult leader was killed, deconverted, or left on the station.
|
||||
/// </summary>
|
||||
CrewMinor,
|
||||
/// <summary>
|
||||
/// Crew major win. The monument didn't reach Stage 3, The crew escaped, and the cult was killed.
|
||||
/// </summary>
|
||||
CrewMajor,
|
||||
/// <summary>
|
||||
/// Crew complete win. The cult was completely deconverted.
|
||||
/// </summary>
|
||||
CrewComplete,
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
namespace Content.Server.Objectives.Components;
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed partial class CosmicEntropyConditionComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// The amount of entropy this objective would like to be siphoned
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public int Siphoned;
|
||||
}
|
||||
|
|
@ -0,0 +1,89 @@
|
|||
using Content.Shared.Damage;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
|
||||
|
||||
namespace Content.Server._DV.CosmicCult.Components;
|
||||
|
||||
[RegisterComponent, AutoGenerateComponentPause]
|
||||
public sealed partial class CosmicFinaleComponent : Component
|
||||
{
|
||||
[DataField]
|
||||
public FinaleState CurrentState = FinaleState.Unavailable;
|
||||
|
||||
[DataField]
|
||||
public bool FinaleDelayStarted = false;
|
||||
|
||||
[DataField]
|
||||
public bool FinaleActive = false;
|
||||
|
||||
[DataField]
|
||||
public bool Occupied = false;
|
||||
|
||||
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), AutoPausedField]
|
||||
public TimeSpan FinaleTimer = default!;
|
||||
|
||||
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), AutoPausedField]
|
||||
public TimeSpan BufferTimer = default!;
|
||||
|
||||
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), AutoPausedField]
|
||||
public TimeSpan CultistsCheckTimer = default!;
|
||||
|
||||
[DataField, AutoNetworkedField]
|
||||
public TimeSpan BufferRemainingTime = TimeSpan.FromSeconds(300);
|
||||
|
||||
[DataField, AutoNetworkedField]
|
||||
public TimeSpan FinaleRemainingTime = TimeSpan.FromSeconds(126);
|
||||
|
||||
[DataField, AutoNetworkedField]
|
||||
public TimeSpan CheckWait = TimeSpan.FromSeconds(5);
|
||||
|
||||
[DataField]
|
||||
public SoundSpecifier CancelEventSound = new SoundPathSpecifier("/Audio/Misc/notice2.ogg");
|
||||
|
||||
[DataField]
|
||||
public TimeSpan FinaleSongLength;
|
||||
|
||||
[DataField]
|
||||
public TimeSpan SongLength;
|
||||
|
||||
[DataField]
|
||||
public SoundSpecifier? SelectedSong;
|
||||
|
||||
[DataField]
|
||||
public TimeSpan InteractionTime = TimeSpan.FromSeconds(8);
|
||||
|
||||
[DataField]
|
||||
public SoundSpecifier BufferMusic = new SoundPathSpecifier("/Audio/_DV/CosmicCult/premonition.ogg");
|
||||
|
||||
[DataField]
|
||||
public SoundSpecifier FinaleMusic = new SoundPathSpecifier("/Audio/_DV/CosmicCult/a_new_dawn.ogg");
|
||||
|
||||
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), AutoPausedField]
|
||||
public TimeSpan? SongTimer;
|
||||
|
||||
/// <summary>
|
||||
/// The degen that people suffer if they don't have mindshields, aren't a chaplain, or aren't cultists while the Finale is Available or Active. This feature is currently disabled.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public DamageSpecifier FinaleDegen = new()
|
||||
{
|
||||
DamageDict = new()
|
||||
{
|
||||
{ "Blunt", 2.25},
|
||||
{ "Cold", 2.25},
|
||||
{ "Radiation", 2.25},
|
||||
{ "Asphyxiation", 2.25}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public enum FinaleState : byte
|
||||
{
|
||||
Unavailable,
|
||||
ReadyBuffer,
|
||||
ReadyFinale,
|
||||
ActiveBuffer,
|
||||
ActiveFinale,
|
||||
Victory,
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server._DV.CosmicCult.Components;
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed partial class CosmicMalignRiftComponent : Component
|
||||
{
|
||||
[DataField]
|
||||
public bool Used;
|
||||
|
||||
[DataField]
|
||||
public bool Occupied;
|
||||
|
||||
[DataField]
|
||||
public EntProtoId PurgeVFX = "CleanseEffectVFX";
|
||||
|
||||
[DataField]
|
||||
public SoundSpecifier PurgeSound = new SoundPathSpecifier("/Audio/_DV/CosmicCult/cleanse_deconversion.ogg");
|
||||
|
||||
// [DataField]
|
||||
// public EntProtoId GrailID = "NullRodGrail"; // Not implemented at this time
|
||||
|
||||
[DataField]
|
||||
public TimeSpan BibleTime = TimeSpan.FromSeconds(35);
|
||||
|
||||
[DataField]
|
||||
public TimeSpan ChaplainTime = TimeSpan.FromSeconds(20);
|
||||
|
||||
[DataField]
|
||||
public TimeSpan AbsorbTime = TimeSpan.FromSeconds(35);
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
namespace Content.Server.Objectives.Components;
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed partial class CosmicTierConditionComponent : Component
|
||||
{
|
||||
[DataField]
|
||||
public int Tier;
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
namespace Content.Server.Objectives.Components;
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed partial class CosmicVictoryConditionComponent : Component
|
||||
{
|
||||
[DataField]
|
||||
public bool Victory;
|
||||
}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
namespace Content.Server._DV.CosmicCult.Components;
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed partial class CosmicVoidSpawnComponent : Component;
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
|
||||
|
||||
namespace Content.Server._DV.CosmicCult.Components;
|
||||
|
||||
[RegisterComponent]
|
||||
[AutoGenerateComponentPause]
|
||||
public sealed partial class InVoidComponent : Component
|
||||
{
|
||||
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
|
||||
[AutoPausedField]
|
||||
public TimeSpan ExitVoidTime = default!;
|
||||
|
||||
[DataField]
|
||||
public EntityUid OriginalBody;
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
using Content.Shared.Roles;
|
||||
|
||||
namespace Content.Server._DV.CosmicCult.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Added to mind role entities to tag that they are using the rogue ascended systems.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed partial class RogueAscendedRoleComponent : BaseMindRoleComponent;
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
namespace Content.Server._DV.CosmicCult.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Objective condition that requires the player to be a rogue ascended and corrupt other players' minds.
|
||||
/// Requires <see cref="NumberObjectiveComponent"/> to function.
|
||||
/// </summary>
|
||||
[RegisterComponent, Access(typeof(RogueAscendedObjectiveSystem))]
|
||||
public sealed partial class RogueInfectionConditionComponent : Component
|
||||
{
|
||||
[DataField]
|
||||
public int MindsCorrupted;
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
using Content.Server.EUI;
|
||||
|
||||
namespace Content.Server._DV.CosmicCult;
|
||||
|
||||
/// <summary>
|
||||
/// Does nothing on the server as this popup has no interactions
|
||||
/// </summary>
|
||||
public sealed class CosmicConvertedEui : BaseEui;
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
namespace Content.Server._DV.CosmicCult;
|
||||
|
||||
/// <summary>
|
||||
/// Associates an entity with a specific cosmic cult gamerule
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed partial class CosmicCultAssociatedRuleComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// The gamerule that this entity is associated with
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public EntityUid CultGamerule;
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
using Content.Server.Objectives.Components;
|
||||
using Content.Shared.Objectives.Components;
|
||||
|
||||
namespace Content.Server.Objectives.Systems;
|
||||
|
||||
public sealed class CosmicCultObjectiveSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly NumberObjectiveSystem _number = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<CosmicEntropyConditionComponent, ObjectiveGetProgressEvent>(OnGetEntropyProgress);
|
||||
SubscribeLocalEvent<CosmicTierConditionComponent, ObjectiveGetProgressEvent>(OnGetTierProgress);
|
||||
SubscribeLocalEvent<CosmicVictoryConditionComponent, ObjectiveGetProgressEvent>(OnGetVictoryProgress);
|
||||
}
|
||||
|
||||
private void OnGetEntropyProgress(Entity<CosmicEntropyConditionComponent> ent, ref ObjectiveGetProgressEvent args)
|
||||
{
|
||||
args.Progress = Progress(ent.Comp.Siphoned, _number.GetTarget(ent.Owner));
|
||||
}
|
||||
|
||||
private void OnGetTierProgress(Entity<CosmicTierConditionComponent> ent, ref ObjectiveGetProgressEvent args)
|
||||
{
|
||||
args.Progress = Progress(ent.Comp.Tier, _number.GetTarget(ent.Owner));
|
||||
}
|
||||
|
||||
private void OnGetVictoryProgress(Entity<CosmicVictoryConditionComponent> ent, ref ObjectiveGetProgressEvent args)
|
||||
{
|
||||
args.Progress = ent.Comp.Victory ? 1f : 0f;
|
||||
}
|
||||
|
||||
private static float Progress(int recruited, int target)
|
||||
{
|
||||
// prevent divide-by-zero
|
||||
if (target == 0)
|
||||
return 1f;
|
||||
|
||||
return MathF.Min(recruited / (float)target, 1f);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,748 @@
|
|||
using Content.Server._DV.CosmicCult.Components;
|
||||
using Content.Server.Actions;
|
||||
using Content.Server.Administration.Logs;
|
||||
using Content.Server.Antag;
|
||||
using Content.Server.Atmos.Components;
|
||||
using Content.Server.Audio;
|
||||
using Content.Server.Bible.Components;
|
||||
using Content.Server.Chat.Systems;
|
||||
using Content.Server.EUI;
|
||||
using Content.Server.GameTicking.Rules;
|
||||
using Content.Server.GameTicking;
|
||||
using Content.Server.Ghost;
|
||||
using Content.Server.Light.Components;
|
||||
using Content.Server.Objectives.Components;
|
||||
using Content.Server.Popups;
|
||||
using Content.Server.Radio.Components;
|
||||
using Content.Server.Roles;
|
||||
using Content.Server.RoundEnd;
|
||||
using Content.Server.Shuttles.Systems;
|
||||
using Content.Server.Voting.Managers;
|
||||
using Content.Server.Voting;
|
||||
using Content.Shared._DV.CCVars;
|
||||
using Content.Shared._DV.CosmicCult.Components.Examine;
|
||||
using Content.Shared._DV.CosmicCult.Components;
|
||||
using Content.Shared._DV.CosmicCult.Prototypes;
|
||||
using Content.Shared._DV.CosmicCult;
|
||||
using Content.Shared._DV.Roles;
|
||||
using Content.Shared.Alert;
|
||||
using Content.Shared.Audio;
|
||||
using Content.Shared.Body.Systems;
|
||||
using Content.Shared.Coordinates;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.GameTicking.Components;
|
||||
using Content.Shared.Humanoid;
|
||||
using Content.Shared.IdentityManagement;
|
||||
using Content.Shared.Mind.Components;
|
||||
using Content.Shared.Mind;
|
||||
using Content.Shared.Mobs.Components;
|
||||
using Content.Shared.Mobs;
|
||||
using Content.Shared.Movement.Systems;
|
||||
using Content.Shared.Parallax;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Roles;
|
||||
using Content.Shared.Stunnable;
|
||||
using Content.Shared.Temperature.Components;
|
||||
using Robust.Server.Audio;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Timing;
|
||||
using System.Linq;
|
||||
|
||||
namespace Content.Server._DV.CosmicCult;
|
||||
|
||||
/// <summary>
|
||||
/// Where all the main stuff for Cosmic Cultists happens.
|
||||
/// </summary>
|
||||
public sealed class CosmicCultRuleSystem : GameRuleSystem<CosmicCultRuleComponent>
|
||||
{
|
||||
[Dependency] private readonly ActionsSystem _actions = default!;
|
||||
[Dependency] private readonly AlertsSystem _alerts = default!;
|
||||
[Dependency] private readonly AntagSelectionSystem _antag = default!;
|
||||
[Dependency] private readonly AudioSystem _audio = default!;
|
||||
[Dependency] private readonly ChatSystem _chatSystem = default!;
|
||||
[Dependency] private readonly DamageableSystem _damage = default!;
|
||||
[Dependency] private readonly EmergencyShuttleSystem _emergency = default!;
|
||||
[Dependency] private readonly EuiManager _euiMan = default!;
|
||||
[Dependency] private readonly GhostSystem _ghost = default!;
|
||||
[Dependency] private readonly IAdminLogManager _adminLogger = default!;
|
||||
[Dependency] private readonly IConfigurationManager _config = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly IPlayerManager _playerMan = default!;
|
||||
[Dependency] private readonly IPrototypeManager _protoMan = default!;
|
||||
[Dependency] private readonly IRobustRandom _rand = default!;
|
||||
[Dependency] private readonly IVoteManager _votes = default!;
|
||||
[Dependency] private readonly MetaDataSystem _metaData = default!;
|
||||
[Dependency] private readonly MonumentSystem _monument = default!;
|
||||
[Dependency] private readonly MovementSpeedModifierSystem _movementSpeed = default!;
|
||||
[Dependency] private readonly PopupSystem _popup = default!;
|
||||
[Dependency] private readonly RoundEndSystem _roundEnd = default!;
|
||||
[Dependency] private readonly ServerGlobalSoundSystem _sound = default!;
|
||||
[Dependency] private readonly SharedBodySystem _body = default!;
|
||||
[Dependency] private readonly SharedEyeSystem _eye = default!;
|
||||
[Dependency] private readonly SharedMapSystem _map = default!;
|
||||
[Dependency] private readonly SharedMindSystem _mind = default!;
|
||||
[Dependency] private readonly SharedRoleSystem _role = default!;
|
||||
[Dependency] private readonly SharedStunSystem _stun = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||
[Dependency] private readonly SharedUserInterfaceSystem _ui = default!;
|
||||
[Dependency] private readonly VisibilitySystem _visibility = default!;
|
||||
|
||||
private ISawmill _sawmill = default!;
|
||||
|
||||
private TimeSpan _t3RevealDelay = default!;
|
||||
private TimeSpan _t2RevealDelay = default!;
|
||||
private TimeSpan _finaleDelay = default!;
|
||||
private TimeSpan _voteTimer = default!;
|
||||
|
||||
private readonly SoundSpecifier _briefingSound = new SoundPathSpecifier("/Audio/_DV/CosmicCult/antag_cosmic_briefing.ogg");
|
||||
private readonly SoundSpecifier _deconvertSound = new SoundPathSpecifier("/Audio/_DV/CosmicCult/antag_cosmic_deconvert.ogg");
|
||||
private readonly SoundSpecifier _tier3Sound = new SoundPathSpecifier("/Audio/_DV/CosmicCult/tier3.ogg");
|
||||
private readonly SoundSpecifier _tier2Sound = new SoundPathSpecifier("/Audio/_DV/CosmicCult/tier2.ogg");
|
||||
private readonly SoundSpecifier _monumentAlert = new SoundPathSpecifier("/Audio/_DV/CosmicCult/tier_up.ogg");
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
_sawmill = IoCManager.Resolve<ILogManager>().GetSawmill("cosmiccult");
|
||||
|
||||
SubscribeLocalEvent<GameRunLevelChangedEvent>(OnRunLevelChanged);
|
||||
SubscribeLocalEvent<CosmicCultAssociateRuleEvent>(OnAssociateRule);
|
||||
|
||||
SubscribeLocalEvent<CosmicCultRuleComponent, AfterAntagEntitySelectedEvent>(OnAntagSelect);
|
||||
|
||||
SubscribeLocalEvent<CosmicCultComponent, ComponentShutdown>(OnComponentShutdown);
|
||||
SubscribeLocalEvent<CosmicGodComponent, ComponentInit>(OnGodSpawn);
|
||||
SubscribeLocalEvent<CosmicCultComponent, MobStateChangedEvent>(OnMobStateChanged);
|
||||
|
||||
Subs.CVar(_config,
|
||||
DCCVars.CosmicCultT2RevealDelaySeconds,
|
||||
value => _t2RevealDelay = TimeSpan.FromSeconds(value),
|
||||
true);
|
||||
Subs.CVar(_config,
|
||||
DCCVars.CosmicCultT3RevealDelaySeconds,
|
||||
value => _t3RevealDelay = TimeSpan.FromSeconds(value),
|
||||
true);
|
||||
Subs.CVar(_config,
|
||||
DCCVars.CosmicCultFinaleDelaySeconds,
|
||||
value => _finaleDelay = TimeSpan.FromSeconds(value),
|
||||
true);
|
||||
Subs.CVar(_config,
|
||||
DCCVars.CosmicCultStewardVoteTimer,
|
||||
value => _voteTimer = TimeSpan.FromSeconds(value),
|
||||
true);
|
||||
}
|
||||
|
||||
#region Starting Events
|
||||
protected override void Started(EntityUid uid, CosmicCultRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args)
|
||||
{
|
||||
component.StewardVoteTimer = _timing.CurTime + TimeSpan.FromSeconds(10);
|
||||
}
|
||||
|
||||
protected override void ActiveTick(EntityUid uid, CosmicCultRuleComponent component, GameRuleComponent gameRule, float frameTime)
|
||||
{
|
||||
if (component.StewardVoteTimer is { } voteTimer && _timing.CurTime <= voteTimer)
|
||||
{
|
||||
component.StewardVoteTimer = null;
|
||||
StewardVote();
|
||||
}
|
||||
if (component.PrepareFinaleTimer is { } finalePrepTimer && _timing.CurTime <= finalePrepTimer)
|
||||
{
|
||||
component.PrepareFinaleTimer = null;
|
||||
|
||||
if (TryComp<CosmicFinaleComponent>(component.MonumentInGame, out var finaleComp))
|
||||
{
|
||||
_monument.ReadyFinale(component.MonumentInGame, finaleComp);
|
||||
UpdateCultData(component.MonumentInGame); //duplicated work but it looks nicer than calling updateAppearance on it's own
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (component.Tier3DelayTimer is { } tier3Timer && _timing.CurTime <= tier3Timer)
|
||||
{
|
||||
component.Tier3DelayTimer = null;
|
||||
|
||||
//do spooky things
|
||||
var query = EntityQueryEnumerator<CosmicCultComponent>();
|
||||
while (query.MoveNext(out var cultist, out var cultComp))
|
||||
{
|
||||
EnsureComp<CosmicStarMarkComponent>(cultist);
|
||||
}
|
||||
|
||||
var sender = Loc.GetString("cosmiccult-announcement-sender");
|
||||
var mapData = _map.GetMap(_transform.GetMapId(component.MonumentInGame.Owner.ToCoordinates()));
|
||||
_chatSystem.DispatchStationAnnouncement(component.MonumentInGame, Loc.GetString("cosmiccult-announce-tier3-progress"), null, false, null, Color.FromHex("#4cabb3"));
|
||||
_chatSystem.DispatchStationAnnouncement(component.MonumentInGame, Loc.GetString("cosmiccult-announce-tier3-warning"), null, false, null, Color.FromHex("#cae8e8"));
|
||||
_audio.PlayGlobal(_tier3Sound, Filter.Broadcast(), false, AudioParams.Default);
|
||||
|
||||
EnsureComp<ParallaxComponent>(mapData, out var parallax);
|
||||
parallax.Parallax = "CosmicFinaleParallax";
|
||||
Dirty(mapData, parallax);
|
||||
|
||||
EnsureComp<MapLightComponent>(mapData, out var mapLight);
|
||||
mapLight.AmbientLightColor = Color.FromHex("#210746");
|
||||
Dirty(mapData, mapLight);
|
||||
|
||||
var lights = EntityQueryEnumerator<PoweredLightComponent>();
|
||||
while (lights.MoveNext(out var light, out _))
|
||||
{
|
||||
if (!_rand.Prob(0.25f))
|
||||
continue;
|
||||
_ghost.DoGhostBooEvent(light);
|
||||
}
|
||||
|
||||
var collideQuery = EntityQueryEnumerator<MonumentCollisionComponent>();
|
||||
while (collideQuery.MoveNext(out var collideEnt, out var collideComp))
|
||||
{
|
||||
collideComp.HasCollision = true;
|
||||
Dirty(collideEnt, collideComp);
|
||||
}
|
||||
|
||||
if (TryComp<VisibilityComponent>(component.MonumentInGame, out var visComp))
|
||||
_visibility.SetLayer((component.MonumentInGame, visComp), 1);
|
||||
|
||||
component.MonumentSlowZone = Spawn("MonumentSlowZone", Transform(component.MonumentInGame).Coordinates); // spawn The Monument's slowing fixture entity that supresses non-cult / non-mindshielded / non-chaplain crew.
|
||||
_monument.SetCanTierUp(component.MonumentInGame, true);
|
||||
UpdateCultData(component.MonumentInGame); //instantly go up a tier if they manage it.
|
||||
_ui.SetUiState(component.MonumentInGame.Owner, MonumentKey.Key, new MonumentBuiState(component.MonumentInGame.Comp)); //not sure if this is needed but I'll be safe
|
||||
}
|
||||
if (component.Tier2DelayTimer is { } tier2Timer && _timing.CurTime <= tier2Timer)
|
||||
{
|
||||
component.Tier2DelayTimer = null;
|
||||
|
||||
//do spooky effects
|
||||
var sender = Loc.GetString("cosmiccult-announcement-sender");
|
||||
var mapData = _map.GetMap(_transform.GetMapId(component.MonumentInGame.Owner.ToCoordinates()));
|
||||
_chatSystem.DispatchStationAnnouncement(component.MonumentInGame, Loc.GetString("cosmiccult-announce-tier2-progress"), null, false, null, Color.FromHex("#4cabb3"));
|
||||
_chatSystem.DispatchStationAnnouncement(component.MonumentInGame, Loc.GetString("cosmiccult-announce-tier2-warning"), null, false, null, Color.FromHex("#cae8e8"));
|
||||
_audio.PlayGlobal(_tier2Sound, Filter.Broadcast(), false, AudioParams.Default);
|
||||
|
||||
for (var i = 0; i < Convert.ToInt16(component.TotalCrew / 4); i++) // spawn # malign rifts equal to 25% of the playercount
|
||||
{
|
||||
if (TryFindRandomTile(out var _, out var _, out var _, out var coords))
|
||||
{
|
||||
Spawn("CosmicMalignRift", coords);
|
||||
}
|
||||
}
|
||||
|
||||
var lights = EntityQueryEnumerator<PoweredLightComponent>();
|
||||
while (lights.MoveNext(out var light, out _))
|
||||
{
|
||||
if (!_rand.Prob(0.50f))
|
||||
continue;
|
||||
_ghost.DoGhostBooEvent(light);
|
||||
}
|
||||
|
||||
_monument.SetCanTierUp(component.MonumentInGame, true);
|
||||
UpdateCultData(component.MonumentInGame); //instantly go up a tier if they manage it
|
||||
_ui.SetUiState(component.MonumentInGame.Owner, MonumentKey.Key, new MonumentBuiState(component.MonumentInGame.Comp)); //not sure if this is needed but I'll be safe
|
||||
}
|
||||
}
|
||||
|
||||
private void StewardVote()
|
||||
{
|
||||
var cultists = new List<(string, EntityUid)>();
|
||||
|
||||
var cultQuery = EntityQueryEnumerator<CosmicCultComponent, MetaDataComponent>();
|
||||
while (cultQuery.MoveNext(out var cult, out _, out var metadata))
|
||||
{
|
||||
var playerInfo = metadata.EntityName;
|
||||
cultists.Add((playerInfo, cult));
|
||||
}
|
||||
|
||||
var options = new VoteOptions
|
||||
{
|
||||
DisplayVotes = false,
|
||||
Title = Loc.GetString("cosmiccult-vote-steward-title"),
|
||||
InitiatorText = Loc.GetString("cosmiccult-vote-steward-initiator"),
|
||||
Duration = _voteTimer,
|
||||
VoterEligibility = VoteManager.VoterEligibility.CosmicCult
|
||||
};
|
||||
|
||||
foreach (var (name, ent) in cultists)
|
||||
{
|
||||
options.Options.Add((Loc.GetString(name), ent));
|
||||
}
|
||||
|
||||
var vote = _votes.CreateVote(options);
|
||||
|
||||
vote.OnFinished += (_, args) =>
|
||||
{
|
||||
EntityUid picked;
|
||||
if (args.Winner == null)
|
||||
{
|
||||
picked = (EntityUid)_rand.Pick(args.Winners);
|
||||
}
|
||||
else
|
||||
{
|
||||
picked = (EntityUid)args.Winner;
|
||||
}
|
||||
EnsureComp<CosmicCultLeadComponent>(picked);
|
||||
_adminLogger.Add(LogType.Vote, LogImpact.Medium, $"Cult stewardship vote finished: {Identity.Entity(picked, EntityManager)} is now steward.");
|
||||
_antag.SendBriefing(picked, Loc.GetString("cosmiccult-vote-steward-briefing"), Color.FromHex("#4cabb3"), _monumentAlert);
|
||||
};
|
||||
}
|
||||
|
||||
private void OnAntagSelect(Entity<CosmicCultRuleComponent> uid, ref AfterAntagEntitySelectedEvent args)
|
||||
{
|
||||
TryStartCult(args.EntityUid, uid);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Round & Objectives
|
||||
|
||||
private void OnGodSpawn(Entity<CosmicGodComponent> uid, ref ComponentInit args)
|
||||
{
|
||||
var query = QueryActiveRules();
|
||||
|
||||
while (query.MoveNext(out var ruleUid, out _, out var cultRule, out _))
|
||||
{
|
||||
SetWinType((ruleUid, cultRule), WinType.CultComplete); //here's no coming back from this. Cult wins this round
|
||||
_roundEnd.EndRound(); //Woo game over yeaaaah
|
||||
foreach (var cultist in cultRule.Cultists)
|
||||
{
|
||||
if (TryComp<MobStateComponent>(cultist, out var state) && state.CurrentState != MobState.Dead)
|
||||
{
|
||||
if (!TryComp<MindContainerComponent>(cultist, out var mindContainer) || !mindContainer.HasMind)
|
||||
return;
|
||||
|
||||
var ascendant = Spawn("MobCosmicAstralAscended", Transform(cultist).Coordinates);
|
||||
_mind.TransferTo(mindContainer.Mind.Value, ascendant);
|
||||
_metaData.SetEntityName(ascendant, Loc.GetString("cosmiccult-astral-ascendant", ("name", cultist))); //Renames cultists' ascendant forms to "[CharacterName], Ascendant"
|
||||
_body.GibBody(cultist); // you don't need that body anymore
|
||||
}
|
||||
}
|
||||
QueueDel(cultRule.MonumentInGame); // The monument doesn't need to stick around postround! Into the bin with you.
|
||||
QueueDel(cultRule.MonumentSlowZone); // cease exist
|
||||
}
|
||||
}
|
||||
|
||||
private static void SetWinType(Entity<CosmicCultRuleComponent> ent, WinType type)
|
||||
{
|
||||
if (ent.Comp.WinLocked)
|
||||
return;
|
||||
ent.Comp.WinType = type;
|
||||
|
||||
if (type is WinType.CultComplete or WinType.CrewComplete) //Let's lock in our WinType to prevent us from setting a worse win if a better win's been achieved.
|
||||
ent.Comp.WinLocked = true;
|
||||
}
|
||||
|
||||
private void OnRunLevelChanged(GameRunLevelChangedEvent ev)
|
||||
{
|
||||
if (ev.New is not GameRunLevel.PostRound) //Are we moving to post-round?
|
||||
return;
|
||||
|
||||
var query = QueryActiveRules();
|
||||
while (query.MoveNext(out var uid, out _, out var cultRule, out _))
|
||||
{
|
||||
ConfirmWinState((uid, cultRule)); //If so, let's consult our Winconditions and set an appropriate WinType.
|
||||
}
|
||||
}
|
||||
|
||||
private bool CultistsAlive()
|
||||
{
|
||||
var query = EntityQueryEnumerator<CosmicCultComponent, MobStateComponent>();
|
||||
while (query.MoveNext(out _, out var comp, out var mob))
|
||||
{
|
||||
if (mob.Running && mob.CurrentState == MobState.Alive)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void OnMobStateChanged(Entity<CosmicCultComponent> ent, ref MobStateChangedEvent args)
|
||||
{
|
||||
if (CultistsAlive())
|
||||
return;
|
||||
|
||||
var query = QueryActiveRules();
|
||||
while (query.MoveNext(out var ruleUid, out _, out var ruleComp, out _))
|
||||
{
|
||||
ConfirmWinState((ruleUid, ruleComp));
|
||||
}
|
||||
}
|
||||
|
||||
private void ConfirmWinState(Entity<CosmicCultRuleComponent> ent)
|
||||
{
|
||||
var tier = ent.Comp.CurrentTier;
|
||||
var leaderAlive = false;
|
||||
var centcomm = _emergency.GetCentcommMaps();
|
||||
var wrapup = AllEntityQuery<CosmicCultComponent, TransformComponent>();
|
||||
while (wrapup.MoveNext(out var cultist, out _, out var cultistLocation))
|
||||
{
|
||||
if (cultistLocation.MapUid != null && centcomm.Contains(cultistLocation.MapUid.Value))
|
||||
{
|
||||
if (HasComp<CosmicCultLeadComponent>(cultist))
|
||||
leaderAlive = true;
|
||||
}
|
||||
}
|
||||
if (tier < 3 && leaderAlive)
|
||||
SetWinType(ent, WinType.Neutral); //The Monument isn't Tier 3, but the cult leader's alive and at Centcomm! a Neutral outcome
|
||||
var monument = AllEntityQuery<CosmicFinaleComponent>();
|
||||
while (monument.MoveNext(out var monumentUid, out var comp))
|
||||
{
|
||||
_sound.StopStationEventMusic(ent, StationEventMusicType.CosmicCult);
|
||||
if (tier == 3 && comp.CurrentState == FinaleState.Unavailable)
|
||||
{
|
||||
SetWinType(ent, WinType.CultMinor); //The crew escaped, and The Monument wasn't fully empowered. a small win
|
||||
}
|
||||
else if (comp.CurrentState != FinaleState.Unavailable)
|
||||
{
|
||||
SetWinType(ent, WinType.CultMajor); //Despite the crew's escape, The Finale is available or active. Major win
|
||||
}
|
||||
}
|
||||
|
||||
if (CultistsAlive())
|
||||
return; // There's still cultists alive! stop checking stuff
|
||||
|
||||
_roundEnd.DoRoundEndBehavior(ent.Comp.RoundEndBehavior, ent.Comp.EvacShuttleTime, ent.Comp.RoundEndTextSender, ent.Comp.RoundEndTextShuttleCall, ent.Comp.RoundEndTextAnnouncement);
|
||||
ent.Comp.RoundEndBehavior = RoundEndBehavior.Nothing; // prevent this being called multiple times.
|
||||
|
||||
var gameruleMonument = ent.Comp.MonumentInGame;
|
||||
if (TryComp<CosmicFinaleComponent>(gameruleMonument, out var finComp))
|
||||
{
|
||||
_monument.Disable(gameruleMonument);
|
||||
finComp.CurrentState = FinaleState.Unavailable;
|
||||
_popup.PopupCoordinates(Loc.GetString("cosmiccult-monument-powerdown"), Transform(gameruleMonument).Coordinates, PopupType.Large);
|
||||
_sound.StopStationEventMusic(gameruleMonument, StationEventMusicType.CosmicCult);
|
||||
_monument.UpdateMonumentAppearance(gameruleMonument, false);
|
||||
}
|
||||
|
||||
if (ent.Comp.TotalCult == 0)
|
||||
SetWinType(ent, WinType.CrewComplete); // No cultists registered! That means everyone got deconverted
|
||||
else
|
||||
SetWinType(ent, WinType.CrewMajor); // There's still cultists registered, but if we got here, that means they're all dead
|
||||
}
|
||||
|
||||
protected override void AppendRoundEndText(EntityUid uid,
|
||||
CosmicCultRuleComponent component,
|
||||
GameRuleComponent gameRule,
|
||||
ref RoundEndTextAppendEvent args)
|
||||
{
|
||||
var ftlKey = component.WinType.ToString().ToLower();
|
||||
var winType = Loc.GetString($"cosmiccult-roundend-{ftlKey}");
|
||||
var summaryText = Loc.GetString($"cosmiccult-summary-{ftlKey}");
|
||||
args.AddLine(winType);
|
||||
args.AddLine(summaryText);
|
||||
args.AddLine(Loc.GetString("cosmiccult-roundend-cultist-count", ("initialCount", component.TotalCult)));
|
||||
args.AddLine(Loc.GetString("cosmiccult-roundend-cultpop-count", ("count", component.PercentConverted)));
|
||||
args.AddLine(Loc.GetString("cosmiccult-roundend-entropy-count", ("count", component.EntropySiphoned)));
|
||||
args.AddLine(Loc.GetString("cosmiccult-roundend-monument-stage", ("stage", component.CurrentTier)));
|
||||
}
|
||||
|
||||
public void IncrementCultObjectiveEntropy(Entity<CosmicCultComponent> ent)
|
||||
{
|
||||
if (AssociatedGamerule(ent) is not { } cult)
|
||||
return;
|
||||
|
||||
cult.Comp.EntropySiphoned += ent.Comp.CosmicSiphonQuantity;
|
||||
var query = EntityQueryEnumerator<CosmicEntropyConditionComponent>();
|
||||
while (query.MoveNext(out _, out var entropyComp))
|
||||
{
|
||||
entropyComp.Siphoned = cult.Comp.EntropySiphoned;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
public void OnStartMonument(Entity<MonumentComponent> ent)
|
||||
{
|
||||
if (AssociatedGamerule(ent) is not { } cult)
|
||||
return;
|
||||
|
||||
cult.Comp.CurrentTier = 1;
|
||||
cult.Comp.MonumentInGame = ent; //Since there's only one Monument per round, let's store its UID for the rest of the round. Saves us on spamming enumerators.
|
||||
_monument.MonumentTier1(ent);
|
||||
UpdateCultData(ent);
|
||||
}
|
||||
|
||||
public void UpdateCultData(Entity<MonumentComponent> uid) // This runs every time Entropy is Inserted into The Monument, and every time a Cultist is Converted or Deconverted.
|
||||
{
|
||||
if (!TryComp<CosmicFinaleComponent>(uid, out var finaleComp))
|
||||
return;
|
||||
|
||||
if (AssociatedGamerule(uid) is not { } cult)
|
||||
return;
|
||||
|
||||
cult.Comp.TotalCrew = _playerMan.Sessions.Count(session => session.Status == SessionStatus.InGame && HasComp<HumanoidAppearanceComponent>(session.AttachedEntity));
|
||||
|
||||
#if DEBUG
|
||||
if (cult.Comp.TotalCrew < 25)
|
||||
cult.Comp.TotalCrew = 25;
|
||||
#endif
|
||||
|
||||
cult.Comp.PercentConverted = Math.Round((double)(100 * cult.Comp.TotalCult) / cult.Comp.TotalCrew);
|
||||
|
||||
//this can probably be somewhere else but
|
||||
_monument.UpdateMonumentReqsForTier(uid, cult.Comp.CurrentTier);
|
||||
_monument.UpdateMonumentProgress(uid, cult);
|
||||
|
||||
if (uid.Comp.CurrentProgress >= uid.Comp.TargetProgress && cult.Comp.CurrentTier == 3 && finaleComp.CurrentState == FinaleState.Unavailable)
|
||||
{
|
||||
if (!finaleComp.FinaleDelayStarted) //check if we've not already started the finale delay
|
||||
{
|
||||
finaleComp.FinaleDelayStarted = true; //set that we've started it
|
||||
//do everything else
|
||||
|
||||
var timer = _finaleDelay;
|
||||
var cultistQuery = EntityQueryEnumerator<CosmicCultComponent>();
|
||||
while (cultistQuery.MoveNext(out var cultist, out var cultistComp))
|
||||
{
|
||||
var mins = timer.Minutes;
|
||||
var secs = timer.Seconds;
|
||||
_antag.SendBriefing(cultist,
|
||||
Loc.GetString("cosmiccult-finale-autocall-briefing",
|
||||
("minutesandseconds", $"{mins} minutes and {secs} seconds")),
|
||||
Color.FromHex("#4cabb3"),
|
||||
_monumentAlert);
|
||||
}
|
||||
|
||||
cult.Comp.PrepareFinaleTimer = _timing.CurTime + timer;
|
||||
}
|
||||
}
|
||||
else if (finaleComp.CurrentState != FinaleState.Unavailable)
|
||||
_monument.SetTargetProgess(uid, uid.Comp.CurrentProgress);
|
||||
else if (uid.Comp.CurrentProgress >= uid.Comp.TargetProgress && cult.Comp.CurrentTier == 2 && uid.Comp.CanTierUp)
|
||||
{
|
||||
_monument.SetCanTierUp(uid, false);
|
||||
|
||||
var timer = _t3RevealDelay;
|
||||
var cultistQuery = EntityQueryEnumerator<CosmicCultComponent>();
|
||||
while (cultistQuery.MoveNext(out var cultist, out var cultistComp))
|
||||
{
|
||||
_antag.SendBriefing(cultist, Loc.GetString("cosmiccult-monument-stage3-briefing", ("time", _t3RevealDelay.Seconds)), Color.FromHex("#4cabb3"), _monumentAlert);
|
||||
}
|
||||
|
||||
_monument.MonumentTier3(uid);
|
||||
_monument.UpdateMonumentReqsForTier(uid, cult.Comp.CurrentTier);
|
||||
cult.Comp.CurrentTier = 3;
|
||||
|
||||
cult.Comp.Tier3DelayTimer = _timing.CurTime + timer;
|
||||
}
|
||||
else if (uid.Comp.CurrentProgress >= uid.Comp.TargetProgress && cult.Comp.CurrentTier == 1 && uid.Comp.CanTierUp)
|
||||
{
|
||||
_monument.SetCanTierUp(uid, false);
|
||||
|
||||
var cultistQuery = EntityQueryEnumerator<CosmicCultComponent>();
|
||||
while (cultistQuery.MoveNext(out var cultist, out var cultistComp))
|
||||
{
|
||||
_antag.SendBriefing(cultist, Loc.GetString("cosmiccult-monument-stage2-briefing", ("time", _t2RevealDelay.Seconds)), Color.FromHex("#4cabb3"), _monumentAlert);
|
||||
}
|
||||
|
||||
_monument.MonumentTier2(uid);
|
||||
cult.Comp.CurrentTier = 2;
|
||||
_monument.UpdateMonumentReqsForTier(uid, cult.Comp.CurrentTier);
|
||||
|
||||
cult.Comp.Tier2DelayTimer = _timing.CurTime + _t2RevealDelay;
|
||||
}
|
||||
|
||||
_monument.UpdateMonumentAppearance(uid, false);
|
||||
|
||||
Dirty(uid);
|
||||
_ui.SetUiState(uid.Owner, MonumentKey.Key, new MonumentBuiState(uid.Comp));
|
||||
}
|
||||
|
||||
#region De- & Conversion
|
||||
public void TryStartCult(EntityUid uid, Entity<CosmicCultRuleComponent> rule)
|
||||
{
|
||||
if (!_mind.TryGetMind(uid, out var mindId, out var mind))
|
||||
return;
|
||||
|
||||
EnsureComp<CosmicCultComponent>(uid, out var cultComp);
|
||||
EnsureComp<IntrinsicRadioReceiverComponent>(uid);
|
||||
EnsureComp<CosmicCultAssociatedRuleComponent>(uid, out var associatedComp);
|
||||
|
||||
associatedComp.CultGamerule = rule;
|
||||
|
||||
_role.MindAddRole(mindId, "MindRoleCosmicCult", mind, true);
|
||||
_role.MindHasRole<CosmicCultRoleComponent>(mindId, out var cosmicRole);
|
||||
|
||||
if (cosmicRole is not null)
|
||||
{
|
||||
EnsureComp<RoleBriefingComponent>(cosmicRole.Value.Owner);
|
||||
Comp<RoleBriefingComponent>(cosmicRole.Value.Owner).Briefing = Loc.GetString("objective-cosmiccult-charactermenu");
|
||||
}
|
||||
|
||||
_antag.SendBriefing(uid, Loc.GetString("cosmiccult-role-roundstart-fluff"), Color.FromHex("#4cabb3"), _briefingSound);
|
||||
_antag.SendBriefing(uid, Loc.GetString("cosmiccult-role-short-briefing"), Color.FromHex("#cae8e8"), null);
|
||||
|
||||
var transmitter = EnsureComp<IntrinsicRadioTransmitterComponent>(uid);
|
||||
var radio = EnsureComp<ActiveRadioComponent>(uid);
|
||||
radio.Channels.Add("CosmicRadio");
|
||||
transmitter.Channels.Add("CosmicRadio");
|
||||
|
||||
if (_mind.TryGetSession(mindId, out var session))
|
||||
{
|
||||
_euiMan.OpenEui(new CosmicRoundStartEui(), session);
|
||||
}
|
||||
|
||||
rule.Comp.TotalCult++;
|
||||
|
||||
cultComp.StoredDamageContainer = Comp<DamageableComponent>(uid).DamageContainerID!.Value; // nullable?
|
||||
|
||||
Dirty(uid, cultComp);
|
||||
|
||||
rule.Comp.Cultists.Add(uid);
|
||||
}
|
||||
|
||||
private void OnAssociateRule(ref CosmicCultAssociateRuleEvent args)
|
||||
{
|
||||
TransferCultAssociation(args.Originator, args.Target);
|
||||
if (TryComp<MonumentComponent>(args.Target, out var monument))
|
||||
{
|
||||
OnStartMonument((args.Target, monument));
|
||||
}
|
||||
}
|
||||
|
||||
public void TransferCultAssociation(EntityUid from, EntityUid to)
|
||||
{
|
||||
if (!TryComp<CosmicCultAssociatedRuleComponent>(from, out var source))
|
||||
return;
|
||||
|
||||
var destination = EnsureComp<CosmicCultAssociatedRuleComponent>(to);
|
||||
destination.CultGamerule = source.CultGamerule;
|
||||
}
|
||||
|
||||
public Entity<CosmicCultRuleComponent>? AssociatedGamerule(EntityUid uid)
|
||||
{
|
||||
if (!TryComp<CosmicCultAssociatedRuleComponent>(uid, out var associated))
|
||||
{
|
||||
_sawmill.Debug("{0} has no associated rule", uid);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!TryComp<CosmicCultRuleComponent>(associated.CultGamerule, out var cult))
|
||||
{
|
||||
_sawmill.Debug("Associated gamerule {0} is not a cult gamerule", associated.CultGamerule);
|
||||
return null;
|
||||
}
|
||||
|
||||
return (associated.CultGamerule, cult);
|
||||
}
|
||||
|
||||
public void CosmicConversion(EntityUid converter, EntityUid uid)
|
||||
{
|
||||
if (AssociatedGamerule(converter) is not { } cult)
|
||||
return;
|
||||
var cosmicGamerule = cult.Comp;
|
||||
|
||||
if (!_mind.TryGetMind(uid, out var mindId, out var mind))
|
||||
return;
|
||||
|
||||
_role.MindAddRole(mindId, "MindRoleCosmicCult", mind, true);
|
||||
_role.MindHasRole<CosmicCultRoleComponent>(mindId, out var cosmicRole);
|
||||
|
||||
if (cosmicRole is not null)
|
||||
{
|
||||
EnsureComp<RoleBriefingComponent>(cosmicRole.Value.Owner);
|
||||
Comp<RoleBriefingComponent>(cosmicRole.Value.Owner).Briefing = Loc.GetString("objective-cosmiccult-charactermenu");
|
||||
}
|
||||
|
||||
_antag.SendBriefing(mind.Session, Loc.GetString("cosmiccult-role-conversion-fluff"), Color.FromHex("#4cabb3"), _briefingSound);
|
||||
_antag.SendBriefing(uid, Loc.GetString("cosmiccult-role-short-briefing"), Color.FromHex("#cae8e8"), null);
|
||||
|
||||
var cultComp = EnsureComp<CosmicCultComponent>(uid);
|
||||
cultComp.EntropyBudget = 10; // pity balance
|
||||
cultComp.StoredDamageContainer = Comp<DamageableComponent>(uid).DamageContainerID!.Value;
|
||||
EnsureComp<IntrinsicRadioReceiverComponent>(uid);
|
||||
TransferCultAssociation(converter, uid);
|
||||
|
||||
if (cosmicGamerule.CurrentTier == 3)
|
||||
{
|
||||
_damage.SetDamageContainerID(uid, "BiologicalMetaphysical");
|
||||
cultComp.EntropyBudget = 48; // pity balance
|
||||
cultComp.Respiration = false;
|
||||
|
||||
foreach (var influenceProto in _protoMan.EnumeratePrototypes<InfluencePrototype>().Where(influenceProto => influenceProto.Tier == 3))
|
||||
{
|
||||
cultComp.UnlockedInfluences.Add(influenceProto.ID);
|
||||
}
|
||||
|
||||
EnsureComp<CosmicStarMarkComponent>(uid);
|
||||
EnsureComp<PressureImmunityComponent>(uid);
|
||||
EnsureComp<TemperatureImmunityComponent>(uid);
|
||||
}
|
||||
else if (cosmicGamerule.CurrentTier == 2)
|
||||
{
|
||||
cultComp.EntropyBudget = 26; // pity balance
|
||||
|
||||
foreach (var influenceProto in _protoMan.EnumeratePrototypes<InfluencePrototype>().Where(influenceProto => influenceProto.Tier == 2))
|
||||
{
|
||||
cultComp.UnlockedInfluences.Add(influenceProto.ID);
|
||||
}
|
||||
}
|
||||
|
||||
Dirty(uid, cultComp);
|
||||
|
||||
var transmitter = EnsureComp<IntrinsicRadioTransmitterComponent>(uid);
|
||||
var radio = EnsureComp<ActiveRadioComponent>(uid);
|
||||
radio.Channels = ["CosmicRadio"];
|
||||
transmitter.Channels = ["CosmicRadio"];
|
||||
|
||||
_mind.TryAddObjective(mindId, mind, "CosmicFinalityObjective");
|
||||
_mind.TryAddObjective(mindId, mind, "CosmicMonumentObjective");
|
||||
_mind.TryAddObjective(mindId, mind, "CosmicEntropyObjective");
|
||||
|
||||
if (_mind.TryGetSession(mindId, out var session))
|
||||
{
|
||||
_euiMan.OpenEui(new CosmicConvertedEui(), session);
|
||||
}
|
||||
|
||||
RemComp<BibleUserComponent>(uid);
|
||||
|
||||
cosmicGamerule.TotalCult++;
|
||||
cosmicGamerule.Cultists.Add(uid);
|
||||
|
||||
UpdateCultData(cosmicGamerule.MonumentInGame);
|
||||
}
|
||||
|
||||
private void OnComponentShutdown(Entity<CosmicCultComponent> uid, ref ComponentShutdown args)
|
||||
{
|
||||
if (AssociatedGamerule(uid) is not { } cult)
|
||||
return;
|
||||
var cosmicGamerule = cult.Comp;
|
||||
|
||||
_stun.TryKnockdown(uid, TimeSpan.FromSeconds(2), true);
|
||||
foreach (var actionEnt in uid.Comp.ActionEntities) _actions.RemoveAction(actionEnt);
|
||||
|
||||
if (TryComp<IntrinsicRadioTransmitterComponent>(uid, out var transmitter))
|
||||
transmitter.Channels.Remove("CosmicRadio");
|
||||
if (TryComp<ActiveRadioComponent>(uid, out var radio))
|
||||
radio.Channels.Remove("CosmicRadio");
|
||||
RemComp<CosmicCultLeadComponent>(uid);
|
||||
RemComp<InfluenceVitalityComponent>(uid);
|
||||
RemComp<InfluenceStrideComponent>(uid);
|
||||
RemComp<PressureImmunityComponent>(uid);
|
||||
RemComp<TemperatureImmunityComponent>(uid);
|
||||
RemComp<CosmicStarMarkComponent>(uid);
|
||||
_damage.SetDamageContainerID(uid.Owner, uid.Comp.StoredDamageContainer);
|
||||
_antag.SendBriefing(uid, Loc.GetString("cosmiccult-role-deconverted-fluff"), Color.FromHex("#4cabb3"), _deconvertSound);
|
||||
_antag.SendBriefing(uid, Loc.GetString("cosmiccult-role-deconverted-briefing"), Color.FromHex("#cae8e8"), null);
|
||||
|
||||
if (!_mind.TryGetMind(uid, out var mindId, out _) || !TryComp<MindComponent>(mindId, out var mindComp))
|
||||
return;
|
||||
|
||||
_mind.ClearObjectives(mindId, mindComp);
|
||||
_role.MindTryRemoveRole<CosmicCultRoleComponent>(mindId);
|
||||
_role.MindTryRemoveRole<RoleBriefingComponent>(mindId);
|
||||
if (_mind.TryGetSession(mindId, out var session))
|
||||
{
|
||||
_euiMan.OpenEui(new CosmicDeconvertedEui(), session);
|
||||
}
|
||||
_eye.SetVisibilityMask(uid, 1);
|
||||
_alerts.ClearAlert(uid, uid.Comp.EntropyAlert);
|
||||
cosmicGamerule.TotalCult--;
|
||||
cosmicGamerule.Cultists.Remove(uid);
|
||||
UpdateCultData(cosmicGamerule.MonumentInGame);
|
||||
_movementSpeed.RefreshMovementSpeedModifiers(uid);
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
|
|
@ -0,0 +1,167 @@
|
|||
using Content.Server._DV.CosmicCult.Components;
|
||||
using Content.Shared._DV.CosmicCult;
|
||||
using Content.Shared._DV.CosmicCult.Components;
|
||||
using Content.Shared.Audio;
|
||||
using Content.Shared.DoAfter;
|
||||
using Content.Shared.Humanoid;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.UserInterface;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Server._DV.CosmicCult;
|
||||
|
||||
public sealed partial class CosmicCultSystem : SharedCosmicCultSystem
|
||||
{
|
||||
/// <summary>
|
||||
/// Used to calculate when the finale song should start playing
|
||||
/// </summary>
|
||||
public void SubscribeFinale()
|
||||
{
|
||||
SubscribeLocalEvent<CosmicFinaleComponent, InteractHandEvent>(OnInteract);
|
||||
SubscribeLocalEvent<CosmicFinaleComponent, StartFinaleDoAfterEvent>(OnFinaleStartDoAfter);
|
||||
SubscribeLocalEvent<CosmicFinaleComponent, CancelFinaleDoAfterEvent>(OnFinaleCancelDoAfter);
|
||||
}
|
||||
|
||||
private void OnInteract(Entity<CosmicFinaleComponent> ent, ref InteractHandEvent args)
|
||||
{
|
||||
if (!HasComp<HumanoidAppearanceComponent>(args.User))
|
||||
return; // humanoids only!
|
||||
if (!EntityIsCultist(args.User) && !args.Handled && ent.Comp.FinaleActive)
|
||||
{
|
||||
ent.Comp.Occupied = true;
|
||||
var doargs = new DoAfterArgs(EntityManager, args.User, ent.Comp.InteractionTime, new CancelFinaleDoAfterEvent(), ent, ent)
|
||||
{
|
||||
DistanceThreshold = 1f, Hidden = false, BreakOnHandChange = true, BreakOnDamage = true, BreakOnMove = true
|
||||
};
|
||||
_popup.PopupEntity(Loc.GetString("cosmiccult-finale-cancel-begin"), args.User, args.User);
|
||||
_doAfter.TryStartDoAfter(doargs);
|
||||
args.Handled = true;
|
||||
}
|
||||
else if (EntityIsCultist(args.User) && !args.Handled && !ent.Comp.FinaleActive && ent.Comp.CurrentState != FinaleState.Unavailable)
|
||||
{
|
||||
ent.Comp.Occupied = true;
|
||||
var doargs = new DoAfterArgs(EntityManager, args.User, ent.Comp.InteractionTime, new StartFinaleDoAfterEvent(), ent, ent)
|
||||
{
|
||||
DistanceThreshold = 1f, Hidden = false, BreakOnHandChange = true, BreakOnDamage = true, BreakOnMove = true
|
||||
};
|
||||
_popup.PopupEntity(Loc.GetString("cosmiccult-finale-beckon-begin"), args.User, args.User);
|
||||
_doAfter.TryStartDoAfter(doargs);
|
||||
args.Handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnFinaleStartDoAfter(Entity<CosmicFinaleComponent> uid, ref StartFinaleDoAfterEvent args)
|
||||
{
|
||||
if (args.Args.Target == null || args.Cancelled || args.Handled)
|
||||
{
|
||||
uid.Comp.Occupied = false;
|
||||
return;
|
||||
}
|
||||
|
||||
_popup.PopupEntity(Loc.GetString("cosmiccult-finale-beckon-success"), args.Args.User, args.Args.User);
|
||||
StartFinale(uid);
|
||||
}
|
||||
|
||||
private void StartFinale(Entity<CosmicFinaleComponent> uid)
|
||||
{
|
||||
var comp = uid.Comp;
|
||||
var indicatedLocation = FormattedMessage.RemoveMarkupOrThrow(_navMap.GetNearestBeaconString((uid, Transform(uid))));
|
||||
|
||||
if (!TryComp<MonumentComponent>(uid, out var monument) || !TryComp<CosmicCorruptingComponent>(uid, out var corruptingComp))
|
||||
return;
|
||||
|
||||
if (uid.Comp.CurrentState == FinaleState.ReadyBuffer)
|
||||
{
|
||||
_corrupting.SetCorruptionTime((uid, corruptingComp), TimeSpan.FromSeconds(3));
|
||||
_appearance.SetData(uid, MonumentVisuals.FinaleReached, 2);
|
||||
comp.BufferTimer = _timing.CurTime + comp.BufferRemainingTime;
|
||||
comp.SelectedSong = comp.BufferMusic;
|
||||
_sound.DispatchStationEventMusic(uid, comp.SelectedSong, StationEventMusicType.CosmicCult);
|
||||
|
||||
_chatSystem.DispatchStationAnnouncement(uid,
|
||||
Loc.GetString("cosmiccult-finale-location", ("location", indicatedLocation)),
|
||||
null, false, null,
|
||||
Color.FromHex("#cae8e8"));
|
||||
|
||||
uid.Comp.CurrentState = FinaleState.ActiveBuffer;
|
||||
}
|
||||
else
|
||||
{
|
||||
_corrupting.SetCorruptionTime((uid, corruptingComp), TimeSpan.FromSeconds(1));
|
||||
_appearance.SetData(uid, MonumentVisuals.FinaleReached, 3);
|
||||
comp.FinaleTimer = _timing.CurTime + comp.FinaleRemainingTime;
|
||||
comp.SelectedSong = comp.FinaleMusic;
|
||||
_sound.DispatchStationEventMusic(uid, comp.SelectedSong, StationEventMusicType.CosmicCult);
|
||||
_chatSystem.DispatchStationAnnouncement(uid,
|
||||
Loc.GetString("cosmiccult-finale-location", ("location", indicatedLocation)),
|
||||
null, false, null,
|
||||
Color.FromHex("#cae8e8"));
|
||||
|
||||
uid.Comp.CurrentState = FinaleState.ActiveFinale;
|
||||
}
|
||||
|
||||
var stationUid = _station.GetStationInMap(Transform(uid).MapID);
|
||||
if (stationUid != null)
|
||||
{
|
||||
_alert.SetLevel(stationUid.Value, "octarine", true, true, true, true);
|
||||
}
|
||||
|
||||
if (TryComp<ActivatableUIComponent>(uid, out var uiComp))
|
||||
uiComp.Key = MonumentKey.Key; // wow! This is the laziest way to enable a UI ever!
|
||||
|
||||
_monument.Enable((uid, monument));
|
||||
comp.FinaleActive = true;
|
||||
|
||||
Dirty(uid, monument);
|
||||
_ui.SetUiState(uid.Owner, MonumentKey.Key, new MonumentBuiState(monument));
|
||||
}
|
||||
|
||||
private void OnFinaleCancelDoAfter(Entity<CosmicFinaleComponent> uid, ref CancelFinaleDoAfterEvent args)
|
||||
{
|
||||
var comp = uid.Comp;
|
||||
if (args.Args.Target is not {} target || args.Cancelled || args.Handled)
|
||||
{
|
||||
uid.Comp.Occupied = false;
|
||||
return;
|
||||
}
|
||||
|
||||
var stationUid = _station.GetOwningStation(uid);
|
||||
|
||||
if (stationUid != null)
|
||||
_alert.SetLevel(stationUid.Value, "green", true, true, true);
|
||||
|
||||
_sound.PlayGlobalOnStation(uid, _audio.ResolveSound(comp.CancelEventSound));
|
||||
_sound.StopStationEventMusic(uid, StationEventMusicType.CosmicCult);
|
||||
|
||||
if (uid.Comp.CurrentState == FinaleState.ActiveBuffer)
|
||||
{
|
||||
uid.Comp.CurrentState = FinaleState.ReadyBuffer;
|
||||
comp.BufferRemainingTime = comp.BufferTimer - _timing.CurTime + TimeSpan.FromSeconds(15);
|
||||
}
|
||||
else if (uid.Comp.CurrentState == FinaleState.ActiveFinale)
|
||||
{
|
||||
uid.Comp.CurrentState = FinaleState.ReadyFinale;
|
||||
}
|
||||
|
||||
if (TryComp<CosmicCorruptingComponent>(uid, out var corruptingComp))
|
||||
_corrupting.SetCorruptionTime((uid, corruptingComp), TimeSpan.FromSeconds(6));
|
||||
|
||||
if (TryComp<ActivatableUIComponent>(uid, out var uiComp))
|
||||
{
|
||||
_ui.CloseUi(uid.Owner, MonumentKey.Key);
|
||||
|
||||
uiComp.Key = null; //kazne called this the laziest way to disable a UI ever
|
||||
}
|
||||
|
||||
_appearance.SetData(uid, MonumentVisuals.FinaleReached, 1);
|
||||
|
||||
if (!TryComp<MonumentComponent>(target, out var monument))
|
||||
return;
|
||||
|
||||
_monument.Disable((uid, monument));
|
||||
comp.FinaleActive = false;
|
||||
|
||||
Dirty(target, monument);
|
||||
_ui.SetUiState(uid.Owner, MonumentKey.Key, new MonumentBuiState(monument));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,202 @@
|
|||
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.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;
|
||||
|
||||
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";
|
||||
|
||||
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);
|
||||
|
||||
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));
|
||||
}
|
||||
#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.MaxValue, true);
|
||||
}
|
||||
|
||||
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.MaxValue, true);
|
||||
}
|
||||
|
||||
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.05f, 1.05f);
|
||||
}
|
||||
private void OnImpositionMoveSpeed(EntityUid uid, CosmicImposingComponent comp, RefreshMovementSpeedModifiersEvent args)
|
||||
{
|
||||
args.ModifySpeed(0.55f, 0.55f);
|
||||
}
|
||||
#endregion
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
using Content.Server.EUI;
|
||||
|
||||
namespace Content.Server._DV.CosmicCult;
|
||||
|
||||
/// <summary>
|
||||
/// Does nothing on the server as this popup has no interactions
|
||||
/// </summary>
|
||||
public sealed class CosmicDeconvertedEui : BaseEui;
|
||||
|
|
@ -0,0 +1,107 @@
|
|||
using Content.Server.Popups;
|
||||
using Content.Shared._DV.CosmicCult.Components.Examine;
|
||||
using Content.Shared._DV.CosmicCult.Components;
|
||||
using Content.Shared._DV.CosmicCult;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Examine;
|
||||
using Content.Shared.Humanoid;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Mobs.Systems;
|
||||
using Robust.Server.Audio;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Containers;
|
||||
|
||||
namespace Content.Server._DV.CosmicCult;
|
||||
|
||||
public sealed class CosmicGlyphSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly DamageableSystem _damageable = default!;
|
||||
[Dependency] private readonly EntityLookupSystem _lookup = default!;
|
||||
[Dependency] private readonly PopupSystem _popup = default!;
|
||||
[Dependency] private readonly MobStateSystem _mobState = default!;
|
||||
[Dependency] private readonly AudioSystem _audio = default!;
|
||||
[Dependency] private readonly SharedContainerSystem _container = default!;
|
||||
[Dependency] private readonly SharedCosmicCultSystem _cosmicCult = default!;
|
||||
|
||||
private readonly HashSet<Entity<CosmicCultComponent>> _cultists = [];
|
||||
private readonly HashSet<Entity<HumanoidAppearanceComponent>> _humanoids = [];
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<CosmicGlyphComponent, ExaminedEvent>(OnExamine);
|
||||
SubscribeLocalEvent<CosmicGlyphComponent, ActivateInWorldEvent>(OnUseGlyph);
|
||||
}
|
||||
|
||||
#region Base trigger
|
||||
|
||||
private void OnExamine(Entity<CosmicGlyphComponent> uid, ref ExaminedEvent args)
|
||||
{
|
||||
if (_cosmicCult.EntityIsCultist(args.Examiner))
|
||||
{
|
||||
args.PushMarkup(Loc.GetString("cosmic-examine-glyph-cultcount", ("COUNT", uid.Comp.RequiredCultists)));
|
||||
}
|
||||
else
|
||||
{
|
||||
args.PushMarkup(Loc.GetString("cosmic-examine-text-glyphs"));
|
||||
}
|
||||
}
|
||||
|
||||
private void OnUseGlyph(Entity<CosmicGlyphComponent> uid, ref ActivateInWorldEvent args)
|
||||
{
|
||||
var tgtpos = Transform(uid).Coordinates;
|
||||
var userCoords = Transform(args.User).Coordinates;
|
||||
if (args.Handled || !userCoords.TryDistance(EntityManager, tgtpos, out var distance) || distance > uid.Comp.ActivationRange || !_cosmicCult.EntityIsCultist(args.User))
|
||||
return;
|
||||
var cultists = GatherCultists(uid, uid.Comp.ActivationRange);
|
||||
if (cultists.Count < uid.Comp.RequiredCultists)
|
||||
{
|
||||
_popup.PopupEntity(Loc.GetString("cult-glyph-not-enough-cultists"), uid, args.User);
|
||||
return;
|
||||
}
|
||||
|
||||
args.Handled = true;
|
||||
var tryInvokeEv = new TryActivateGlyphEvent(args.User, cultists);
|
||||
RaiseLocalEvent(uid, tryInvokeEv);
|
||||
if (tryInvokeEv.Cancelled)
|
||||
return;
|
||||
|
||||
var damage = uid.Comp.ActivationDamage / cultists.Count;
|
||||
foreach (var cultist in cultists)
|
||||
{
|
||||
_damageable.TryChangeDamage(cultist, damage, true);
|
||||
}
|
||||
|
||||
_audio.PlayPvs(uid.Comp.GylphSFX, tgtpos, AudioParams.Default.WithVolume(+1f));
|
||||
Spawn(uid.Comp.GylphVFX, tgtpos);
|
||||
QueueDel(uid);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Housekeeping
|
||||
/// <summary>
|
||||
/// Gets all cultists/constructs near a glyph.
|
||||
/// </summary>
|
||||
public HashSet<Entity<CosmicCultComponent>> GatherCultists(EntityUid uid, float range)
|
||||
{
|
||||
_lookup.GetEntitiesInRange<CosmicCultComponent>(Transform(uid).Coordinates, range, _cultists);
|
||||
_cultists.RemoveWhere(entity => !_mobState.IsAlive(entity) || _container.IsEntityInContainer(entity));
|
||||
return _cultists;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all the humanoids near a glyph.
|
||||
/// </summary>
|
||||
/// <param name="uid">The glyph.</param>
|
||||
/// <param name="range">Radius for a lookup.</param>
|
||||
/// <param name="exclude">Filter to exclude from return.</param>
|
||||
public HashSet<Entity<HumanoidAppearanceComponent>> GetTargetsNearGlyph(EntityUid uid, float range, Predicate<Entity<HumanoidAppearanceComponent>>? exclude = null)
|
||||
{
|
||||
_lookup.GetEntitiesInRange<HumanoidAppearanceComponent>(Transform(uid).Coordinates, range, _humanoids);
|
||||
if (exclude != null)
|
||||
_humanoids.RemoveWhere(exclude);
|
||||
_humanoids.RemoveWhere(target => HasComp<CosmicBlankComponent>(target) || HasComp<CosmicLapseComponent>(target)); // We never want these.
|
||||
|
||||
return _humanoids;
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
using Content.Server.EUI;
|
||||
|
||||
namespace Content.Server._DV.CosmicCult;
|
||||
|
||||
/// <summary>
|
||||
/// Does nothing on the server as this is an informational popup
|
||||
/// </summary>
|
||||
public sealed class CosmicRoundStartEui : BaseEui;
|
||||
|
|
@ -0,0 +1,136 @@
|
|||
using Content.Server._DV.CosmicCult.Components;
|
||||
using Content.Server.Bible.Components;
|
||||
using Content.Shared._DV.CosmicCult;
|
||||
using Content.Shared._DV.CosmicCult.Components;
|
||||
using Content.Shared._DV.CosmicCult.Components.Examine;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.DoAfter;
|
||||
using Content.Shared.IdentityManagement;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Jittering;
|
||||
using Content.Shared.Mobs.Systems;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Timing;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Server._DV.CosmicCult;
|
||||
|
||||
public sealed class DeconversionSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly DamageableSystem _damageable = default!;
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly MobStateSystem _mobState = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
[Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
|
||||
[Dependency] private readonly SharedJitteringSystem _jittering = default!;
|
||||
[Dependency] private readonly SharedPopupSystem _popup = default!;
|
||||
[Dependency] private readonly UseDelaySystem _delay = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<CleanseOnUseComponent, AfterInteractEvent>(OnAfterInteract);
|
||||
SubscribeLocalEvent<CleanseOnUseComponent, CleanseOnDoAfterEvent>(OnDoAfter);
|
||||
SubscribeLocalEvent<CleanseCultComponent, ComponentInit>(OnCompInit);
|
||||
}
|
||||
|
||||
private void OnCompInit(Entity<CleanseCultComponent> uid, ref ComponentInit args)
|
||||
{
|
||||
_jittering.DoJitter(uid.Owner, uid.Comp.CleanseDuration, true, 5, 20);
|
||||
uid.Comp.CleanseTime = _timing.CurTime + uid.Comp.CleanseDuration;
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
|
||||
var deconCultTimer = EntityQueryEnumerator<CleanseCultComponent>();
|
||||
while (deconCultTimer.MoveNext(out var uid, out var comp))
|
||||
{
|
||||
if (_timing.CurTime >= comp.CleanseTime && !HasComp<CosmicBlankComponent>(uid))
|
||||
{
|
||||
RemComp<CleanseCultComponent>(uid);
|
||||
DeconvertCultist(uid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnAfterInteract(Entity<CleanseOnUseComponent> uid, ref AfterInteractEvent args)
|
||||
{
|
||||
if (!TryComp(uid, out UseDelayComponent? useDelay) || _delay.IsDelayed((uid, useDelay)))
|
||||
return;
|
||||
if (!args.CanReach || !uid.Comp.Enabled || args.Target == null || _mobState.IsDead(args.Target.Value))
|
||||
return;
|
||||
|
||||
if (!HasComp<BibleUserComponent>(args.User))
|
||||
{
|
||||
_popup.PopupEntity(Loc.GetString("cleanse-item-sizzle", ("target", Identity.Entity(args.Used, EntityManager))), args.User, args.User);
|
||||
|
||||
_audio.PlayPvs(uid.Comp.SizzleSound, args.User);
|
||||
_damageable.TryChangeDamage(args.User, uid.Comp.SelfDamage, origin: uid);
|
||||
_delay.TryResetDelay((uid, useDelay));
|
||||
return;
|
||||
}
|
||||
|
||||
_popup.PopupEntity(Loc.GetString("cleanse-deconvert-attempt-begin", ("target", Identity.Entity(args.User, EntityManager))), args.User, args.Target.Value);
|
||||
_popup.PopupEntity(Loc.GetString("cleanse-deconvert-attempt-begin-user", ("target", Identity.Entity(args.Target.Value, EntityManager))), args.User, args.User);
|
||||
_doAfter.TryStartDoAfter(new DoAfterArgs(EntityManager, args.User, uid.Comp.UseTime, new CleanseOnDoAfterEvent(), uid, args.Target, uid)
|
||||
{
|
||||
BreakOnMove = true,
|
||||
BreakOnDamage = true,
|
||||
DistanceThreshold = 1.5f,
|
||||
RequireCanInteract = true,
|
||||
BlockDuplicate = true,
|
||||
CancelDuplicate = true,
|
||||
NeedHand = true,
|
||||
});
|
||||
}
|
||||
|
||||
private void OnDoAfter(Entity<CleanseOnUseComponent> uid, ref CleanseOnDoAfterEvent args)
|
||||
{
|
||||
var target = args.Args.Target;
|
||||
if (!TryComp(uid, out UseDelayComponent? useDelay) || args.Cancelled || args.Handled || target == null || _mobState.IsDead(target.Value))
|
||||
return;
|
||||
var targetPosition = Transform(target.Value).Coordinates;
|
||||
//TODO: This could be made more agnostic, but there's only one cult for now, and frankly, i'm so tired. This is easy to read and easy to modify code. Expand it at thine leisure.
|
||||
if (TryComp<CosmicCultComponent>(args.Target, out var comp) && comp.CosmicEmpowered)
|
||||
{
|
||||
Spawn(uid.Comp.MalignVFX, targetPosition);
|
||||
Spawn(uid.Comp.MalignVFX, Transform(args.User).Coordinates);
|
||||
EnsureComp<CleanseCultComponent>(target.Value, out var cleanse);
|
||||
cleanse.CleanseDuration = TimeSpan.FromSeconds(1);
|
||||
_audio.PlayPvs(uid.Comp.MalignSound, targetPosition, AudioParams.Default.WithVolume(2f));
|
||||
_damageable.TryChangeDamage(args.User, uid.Comp.SelfDamage, true);
|
||||
_popup.PopupEntity(Loc.GetString("cleanse-deconvert-attempt-success-empowered", ("target", Identity.Entity(target.Value, EntityManager))), args.User, args.User);
|
||||
}
|
||||
else if (TryComp<CosmicCultComponent>(target, out var cultComponent) && !cultComponent.CosmicEmpowered)
|
||||
{
|
||||
Spawn(uid.Comp.CleanseVFX, targetPosition);
|
||||
EnsureComp<CleanseCultComponent>(target.Value, out var cleanse);
|
||||
cleanse.CleanseDuration = TimeSpan.FromSeconds(1);
|
||||
_audio.PlayPvs(uid.Comp.CleanseSound, targetPosition, AudioParams.Default.WithVolume(4f));
|
||||
_popup.PopupEntity(Loc.GetString("cleanse-deconvert-attempt-success", ("target", Identity.Entity(target.Value, EntityManager))), args.User, args.User);
|
||||
}
|
||||
else if (HasComp<RogueAscendedInfectionComponent>(target))
|
||||
{
|
||||
Spawn(uid.Comp.CleanseVFX, targetPosition);
|
||||
RemComp<RogueAscendedInfectionComponent>(target.Value);
|
||||
_audio.PlayPvs(uid.Comp.CleanseSound, targetPosition, AudioParams.Default.WithVolume(4f));
|
||||
_popup.PopupEntity(Loc.GetString("cleanse-deconvert-attempt-success", ("target", Identity.Entity(target.Value, EntityManager))), args.User, args.User);
|
||||
}
|
||||
else
|
||||
{
|
||||
_popup.PopupEntity(Loc.GetString("cleanse-deconvert-attempt-notcorrupted", ("target", Identity.Entity(target.Value, EntityManager))), args.User, args.User);
|
||||
}
|
||||
_delay.TryResetDelay((uid, useDelay));
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private void DeconvertCultist(EntityUid uid)
|
||||
{
|
||||
RemComp<CosmicCultComponent>(uid);
|
||||
RemComp<RogueAscendedInfectionComponent>(uid);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,218 @@
|
|||
using System.Linq;
|
||||
using Content.Server._DV.CosmicCult.Components;
|
||||
using Content.Shared.Maps;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Server._DV.CosmicCult.EntitySystems;
|
||||
|
||||
public sealed class CosmicCorruptingSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly MapSystem _map = default!;
|
||||
|
||||
private readonly HashSet<Vector2i> _neighbourPositions =
|
||||
[
|
||||
new(-1, 1),
|
||||
new(0, 1),
|
||||
new(1, 1),
|
||||
new(-1, 0),
|
||||
new(0, 0),
|
||||
new(1, 0),
|
||||
new(-1, -1),
|
||||
new(0, -1),
|
||||
new(1, -1),
|
||||
];
|
||||
|
||||
[Dependency] private readonly IRobustRandom _rand = default!;
|
||||
[Dependency] private readonly TileSystem _tile = default!;
|
||||
[Dependency] private readonly ITileDefinitionManager _tileDefinition = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly TurfSystem _turfs = default!;
|
||||
|
||||
/// <remarks>
|
||||
/// this system is a mostly generic way of replacing tiles around an entity. the only hardcoded behaviour is secret
|
||||
/// walls -> malign doors, but that shouldn't be too hard to fix if this is needed for smth else later.
|
||||
/// </remarks>
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<CosmicCorruptingComponent, MapInitEvent>(OnMapInit);
|
||||
}
|
||||
|
||||
//when the entity spawns, add all neighbouring tiles to the corruptable list
|
||||
private void OnMapInit(Entity<CosmicCorruptingComponent> ent, ref MapInitEvent args)
|
||||
{
|
||||
RecalculateStartingTiles(ent);
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
var blanktimer = EntityQueryEnumerator<CosmicCorruptingComponent>();
|
||||
while (blanktimer.MoveNext(out var uid, out var comp))
|
||||
{
|
||||
if (comp.Enabled && _timing.CurTime >= comp.CorruptionTimer)
|
||||
{
|
||||
comp.CorruptionTimer = _timing.CurTime + comp.CorruptionSpeed;
|
||||
ConvertTiles((uid, comp));
|
||||
if (comp.CorruptionTicks <= comp.CorruptionMaxTicks)
|
||||
{
|
||||
comp.CorruptionTicks++;
|
||||
comp.CorruptionChance -= comp.CorruptionReduction;
|
||||
}
|
||||
|
||||
if (comp.CorruptionTicks >= comp.CorruptionMaxTicks && comp.AutoDisable)
|
||||
comp.Enabled =
|
||||
false; //maybe just remComp this? atm nothing re-enables a corruptor so that should be safe to do?
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ConvertTiles(Entity<CosmicCorruptingComponent> ent)
|
||||
{
|
||||
var xform = Transform(ent);
|
||||
if (xform.GridUid is not { } gridUid || !TryComp<MapGridComponent>(gridUid, out var mapGrid))
|
||||
return;
|
||||
|
||||
var convertTile = (ContentTileDefinition)_tileDefinition[ent.Comp.ConversionTile];
|
||||
|
||||
//if this is a mobile corruptor, reset the list of corruptable tiles every attempt.
|
||||
//not a super clean solution because I didn't account for the astral nova in the first rewrite but it works well enough for our purposes.
|
||||
if (ent.Comp.Mobile)
|
||||
RecalculateStartingTiles(ent);
|
||||
|
||||
//go over every corruptible tile
|
||||
foreach (var pos in
|
||||
new HashSet<Vector2i>(ent.Comp.CorruptableTiles)) //we love avoiding ConcurrentModificationExceptions
|
||||
{
|
||||
var tileRef = _map.GetTileRef((gridUid, mapGrid), pos);
|
||||
if (tileRef.Tile.TypeId == convertTile.TileId ||
|
||||
tileRef.Tile.IsEmpty) //if it's already corrupted (or space), remove it from the list and continue
|
||||
{
|
||||
ent.Comp.CorruptableTiles.Remove(pos);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (_rand.Prob(ent.Comp.CorruptionChance)) //if it rolls good
|
||||
{
|
||||
//replace & variantise the tile
|
||||
_tile.ReplaceTile(tileRef, convertTile);
|
||||
_tile.PickVariant(convertTile);
|
||||
|
||||
//then add the new neighbours as targets as long as they're not already corrupted
|
||||
foreach (var neighbourPos in _neighbourPositions)
|
||||
{
|
||||
var neighbourRef = _map.GetTileRef((gridUid, mapGrid), tileRef.GridIndices + neighbourPos);
|
||||
if (neighbourRef.Tile.TypeId == convertTile.TileId ||
|
||||
tileRef.Tile.IsEmpty) //ignore already corrupted (or space) tiles
|
||||
continue;
|
||||
|
||||
ent.Comp.CorruptableTiles.Add(neighbourRef.GridIndices);
|
||||
}
|
||||
|
||||
//corrupt anything that can be corrupted
|
||||
foreach (var convertedEnt in _map.GetAnchoredEntities((gridUid, mapGrid), pos).ToList())
|
||||
{
|
||||
var proto = Prototype(convertedEnt);
|
||||
if (ent.Comp.EntityConversionDict.TryGetValue(proto?.ID!, out var conversion))
|
||||
{
|
||||
Spawn(conversion, Transform(convertedEnt).Coordinates);
|
||||
QueueDel(convertedEnt);
|
||||
}
|
||||
}
|
||||
|
||||
//spawn the vfx if we should
|
||||
if (ent.Comp.UseVFX)
|
||||
Spawn(ent.Comp.TileConvertVFX, _turfs.GetTileCenter(tileRef));
|
||||
|
||||
ent.Comp.CorruptableTiles.Remove(pos);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#region API
|
||||
|
||||
public void SetCorruptionTime(Entity<CosmicCorruptingComponent> ent, TimeSpan time)
|
||||
{
|
||||
ent.Comp.CorruptionSpeed = time;
|
||||
}
|
||||
|
||||
public void Enable(Entity<CosmicCorruptingComponent> ent, bool recalculate = true)
|
||||
{
|
||||
ent.Comp.Enabled = true;
|
||||
|
||||
if (recalculate)
|
||||
RecalculateStartingTiles(ent);
|
||||
}
|
||||
|
||||
public void RecalculateStartingTiles(Entity<CosmicCorruptingComponent> ent)
|
||||
{
|
||||
ent.Comp.CorruptableTiles.Clear();
|
||||
|
||||
var xform = Transform(ent);
|
||||
|
||||
if (xform.GridUid is not { } gridUid || !TryComp<MapGridComponent>(gridUid, out var mapGrid))
|
||||
return;
|
||||
|
||||
var grid = (gridUid, mapGrid);
|
||||
var tile = _map.GetTileRef(grid, xform.Coordinates);
|
||||
|
||||
if (ent.Comp.FloodFillStarting) //todo make this async? it doesn't actually run that much though
|
||||
{
|
||||
var convertTile = (ContentTileDefinition)_tileDefinition[ent.Comp.ConversionTile];
|
||||
var visitedTiles = new HashSet<Vector2i>();
|
||||
var tilesToVisit = new HashSet<Vector2i> { tile.GridIndices };
|
||||
|
||||
var count = 0;
|
||||
|
||||
while (tilesToVisit.Count > 0)
|
||||
{
|
||||
//get the first tile in the list
|
||||
var currtile = tilesToVisit.First();
|
||||
count++;
|
||||
|
||||
//check every neighbouring tile
|
||||
foreach (var neighbourPos in _neighbourPositions)
|
||||
{
|
||||
var neighbourRef = _map.GetTileRef((gridUid, mapGrid), currtile + neighbourPos);
|
||||
|
||||
//if it's already been converted
|
||||
if (neighbourRef.Tile.TypeId == convertTile.TileId)
|
||||
{
|
||||
//and not already visited
|
||||
if (!visitedTiles.Contains(neighbourRef.GridIndices))
|
||||
tilesToVisit.Add(neighbourRef.GridIndices); //add it to the to visit list
|
||||
}
|
||||
else
|
||||
{
|
||||
//else, it's not been converted, so mark it as visited and add it to the corruptible tiles list
|
||||
//we don't care if the tile is empty, that'll get checked later
|
||||
visitedTiles.Add(neighbourRef.GridIndices);
|
||||
ent.Comp.CorruptableTiles.Add(neighbourRef.GridIndices);
|
||||
}
|
||||
}
|
||||
|
||||
//finally, mark the tile as visited and remove it from the toVisit list
|
||||
visitedTiles.Add(currtile);
|
||||
tilesToVisit.Remove(currtile);
|
||||
}
|
||||
|
||||
Log.Info($"floodfill tile recaulculation ran {count} times");
|
||||
}
|
||||
else
|
||||
{
|
||||
//add every neighbouring tile to the corruptable list
|
||||
//don't bother checking eligibility at this point because it'll get done later anyway
|
||||
foreach (var neighbourPos in _neighbourPositions)
|
||||
{
|
||||
var neighbourRef = _map.GetTileRef((gridUid, mapGrid), tile.GridIndices + neighbourPos);
|
||||
|
||||
ent.Comp.CorruptableTiles.Add(neighbourRef.GridIndices);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
using Content.Shared._DV.CosmicCult.Components;
|
||||
using Robust.Shared.Timing;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Popups;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
namespace Content.Server._DV.CosmicCult.EntitySystems;
|
||||
|
||||
/// <summary>
|
||||
/// Makes the person with this component take damage over time.
|
||||
/// Used for status effect.
|
||||
/// </summary>
|
||||
public sealed partial class CosmicEntropyDegenSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly DamageableSystem _damageable = default!;
|
||||
[Dependency] private readonly SharedPopupSystem _popup = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<CosmicEntropyDebuffComponent, ComponentStartup>(OnInit);
|
||||
}
|
||||
|
||||
private void OnInit(EntityUid uid, CosmicEntropyDebuffComponent comp, ref ComponentStartup args)
|
||||
{
|
||||
_damageable.TryChangeDamage(uid, comp.Degen, true, false);
|
||||
comp.CheckTimer = _timing.CurTime + comp.CheckWait;
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
|
||||
var query = EntityQueryEnumerator<CosmicEntropyDebuffComponent>();
|
||||
while (query.MoveNext(out var uid, out var component))
|
||||
{
|
||||
if (_timing.CurTime < component.CheckTimer)
|
||||
continue;
|
||||
component.CheckTimer = _timing.CurTime + component.CheckWait;
|
||||
_damageable.TryChangeDamage(uid, component.Degen, true, false);
|
||||
if (_random.Prob(component.PopupChance))
|
||||
_popup.PopupEntity(Loc.GetString("entropy-effect-numb"), uid, uid, PopupType.SmallCaution);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,162 @@
|
|||
using Content.Server._DV.CosmicCult.Components;
|
||||
using Content.Server.Atmos.Components;
|
||||
using Content.Server.Bible.Components;
|
||||
using Content.Server.Popups;
|
||||
using Content.Shared._DV.CosmicCult;
|
||||
using Content.Shared._DV.CosmicCult.Components;
|
||||
using Content.Shared.DoAfter;
|
||||
using Content.Shared.IdentityManagement;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Temperature.Components;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
|
||||
namespace Content.Server._DV.CosmicCult.EntitySystems;
|
||||
|
||||
public sealed class CosmicRiftSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
[Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
|
||||
[Dependency] private readonly PopupSystem _popup = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<CosmicMalignRiftComponent, InteractHandEvent>(OnInteract);
|
||||
SubscribeLocalEvent<CosmicMalignRiftComponent, InteractUsingEvent>(OnInteractUsing);
|
||||
SubscribeLocalEvent<CosmicCultComponent, EventAbsorbRiftDoAfter>(OnAbsorbDoAfter);
|
||||
SubscribeLocalEvent<CosmicMalignRiftComponent, EventPurgeRiftDoAfter>(OnPurgeDoAfter);
|
||||
}
|
||||
|
||||
private void OnInteract(Entity<CosmicMalignRiftComponent> uid, ref InteractHandEvent args)
|
||||
{
|
||||
if (args.Handled || uid.Comp.Occupied)
|
||||
{
|
||||
_popup.PopupEntity(Loc.GetString("cosmiccult-rift-inuse"), args.User, args.User);
|
||||
return;
|
||||
}
|
||||
|
||||
if (HasComp<BibleUserComponent>(args.User))
|
||||
{
|
||||
_popup.PopupEntity(Loc.GetString("cosmiccult-rift-chaplainoops"), args.User, args.User);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!TryComp<CosmicCultComponent>(args.User, out var cultist))
|
||||
{
|
||||
_popup.PopupEntity(Loc.GetString("cosmiccult-rift-invaliduser"), args.User, args.User);
|
||||
return;
|
||||
}
|
||||
|
||||
if (cultist.CosmicEmpowered)
|
||||
{
|
||||
_popup.PopupEntity(Loc.GetString("cosmiccult-rift-alreadyempowered"), args.User, args.User);
|
||||
return;
|
||||
}
|
||||
|
||||
args.Handled = true;
|
||||
uid.Comp.Occupied = true;
|
||||
_popup.PopupEntity(Loc.GetString("cosmiccult-rift-beginabsorb"), args.User, args.User);
|
||||
var doargs = new DoAfterArgs(EntityManager,
|
||||
args.User,
|
||||
uid.Comp.AbsorbTime,
|
||||
new EventAbsorbRiftDoAfter(),
|
||||
args.User,
|
||||
uid)
|
||||
{
|
||||
DistanceThreshold = 1.5f, Hidden = true, BreakOnDamage = true, BreakOnHandChange = true, BreakOnMove = true,
|
||||
MovementThreshold = 0.5f,
|
||||
};
|
||||
_doAfter.TryStartDoAfter(doargs);
|
||||
}
|
||||
|
||||
private void OnInteractUsing(Entity<CosmicMalignRiftComponent> uid, ref InteractUsingEvent args)
|
||||
{
|
||||
if (args.Handled || uid.Comp.Occupied)
|
||||
{
|
||||
_popup.PopupEntity(Loc.GetString("cosmiccult-rift-inuse"), args.User, args.User);
|
||||
return;
|
||||
}
|
||||
|
||||
if (HasComp<BibleComponent>(args.Used))
|
||||
{
|
||||
uid.Comp.Occupied = true;
|
||||
_popup.PopupEntity(Loc.GetString("cosmiccult-rift-beginpurge"), args.User, args.User);
|
||||
var doargs = new DoAfterArgs(EntityManager,
|
||||
args.User,
|
||||
uid.Comp.BibleTime,
|
||||
new EventPurgeRiftDoAfter(),
|
||||
uid,
|
||||
uid)
|
||||
{
|
||||
DistanceThreshold = 1.5f, Hidden = false, BreakOnDamage = true, BreakOnDropItem = true,
|
||||
BreakOnMove = true, MovementThreshold = 2f,
|
||||
};
|
||||
_doAfter.TryStartDoAfter(doargs);
|
||||
}
|
||||
else if (TryComp<CleanseOnUseComponent>(args.Used, out var comp) && comp.CanPurge)
|
||||
{
|
||||
uid.Comp.Occupied = true;
|
||||
_popup.PopupEntity(Loc.GetString("cosmiccult-rift-beginpurge"), args.User, args.User);
|
||||
var doargs = new DoAfterArgs(EntityManager,
|
||||
args.User,
|
||||
uid.Comp.ChaplainTime,
|
||||
new EventPurgeRiftDoAfter(),
|
||||
uid,
|
||||
uid)
|
||||
{
|
||||
DistanceThreshold = 1.5f, Hidden = false, BreakOnDamage = true, BreakOnDropItem = true,
|
||||
BreakOnMove = true, MovementThreshold = 2f,
|
||||
};
|
||||
_doAfter.TryStartDoAfter(doargs);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnAbsorbDoAfter(Entity<CosmicCultComponent> uid, ref EventAbsorbRiftDoAfter args)
|
||||
{
|
||||
var comp = uid.Comp;
|
||||
if (args.Args.Target is not { } target || args.Cancelled || args.Handled)
|
||||
{
|
||||
if (TryComp<CosmicMalignRiftComponent>(args.Args.Target, out var rift))
|
||||
rift.Occupied = false;
|
||||
return;
|
||||
}
|
||||
|
||||
args.Handled = true;
|
||||
var tgtpos = Transform(target).Coordinates;
|
||||
Spawn(uid.Comp.AbsorbVFX, tgtpos);
|
||||
comp.CosmicEmpowered = true;
|
||||
comp.CosmicSiphonQuantity = 2;
|
||||
comp.CosmicGlareRange = 10;
|
||||
comp.CosmicGlareDuration = TimeSpan.FromSeconds(10);
|
||||
comp.CosmicGlareStun = TimeSpan.FromSeconds(1);
|
||||
comp.CosmicImpositionDuration = TimeSpan.FromSeconds(7.2);
|
||||
comp.Respiration = false;
|
||||
EnsureComp<PressureImmunityComponent>(args.User);
|
||||
EnsureComp<TemperatureImmunityComponent>(args.User);
|
||||
_popup.PopupCoordinates(
|
||||
Loc.GetString("cosmiccult-rift-absorb", ("NAME", Identity.Entity(args.Args.User, EntityManager))),
|
||||
Transform(args.Args.User).Coordinates,
|
||||
PopupType.MediumCaution);
|
||||
QueueDel(target);
|
||||
}
|
||||
|
||||
private void OnPurgeDoAfter(Entity<CosmicMalignRiftComponent> uid, ref EventPurgeRiftDoAfter args)
|
||||
{
|
||||
if (args.Args.Target == null || args.Cancelled || args.Handled)
|
||||
{
|
||||
uid.Comp.Occupied = false;
|
||||
return;
|
||||
}
|
||||
|
||||
args.Handled = true;
|
||||
var tgtpos = Transform(uid).Coordinates;
|
||||
Spawn(uid.Comp.PurgeVFX, tgtpos);
|
||||
_audio.PlayPvs(uid.Comp.PurgeSound, args.User);
|
||||
_popup.PopupCoordinates(
|
||||
Loc.GetString("cosmiccult-rift-purge", ("NAME", Identity.Entity(args.Args.User, EntityManager))),
|
||||
Transform(args.Args.User).Coordinates,
|
||||
PopupType.Medium);
|
||||
QueueDel(uid);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,101 @@
|
|||
using Content.Server.Atmos.EntitySystems;
|
||||
using Content.Server.Atmos.Piping.Components;
|
||||
using Content.Server.Atmos.Piping.Unary.EntitySystems;
|
||||
using Content.Server.Audio;
|
||||
using Content.Server.Popups;
|
||||
using Content.Shared._DV.CosmicCult.Components;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Atmos.Piping.Unary.Components;
|
||||
using Robust.Server.GameObjects;
|
||||
|
||||
namespace Content.Server._DV.CosmicCult.EntitySystems;
|
||||
|
||||
public sealed class CosmicSpireSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly AmbientSoundSystem _ambient = default!;
|
||||
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
|
||||
[Dependency] private readonly AtmosphereSystem _atmos = default!;
|
||||
[Dependency] private readonly CosmicCultRuleSystem _cosmicRule = default!;
|
||||
[Dependency] private readonly SharedPointLightSystem _lights = default!;
|
||||
[Dependency] private readonly PopupSystem _popup = default!;
|
||||
[Dependency] private readonly GasVentScrubberSystem _scrub = default!;
|
||||
[Dependency] private readonly TransformSystem _transform = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<CosmicSpireComponent, AnchorStateChangedEvent>(OnAnchorChanged);
|
||||
SubscribeLocalEvent<CosmicSpireComponent, AtmosDeviceUpdateEvent>(OnDeviceUpdated);
|
||||
SubscribeLocalEvent<CosmicSpireComponent, GasAnalyzerScanEvent>(OnSpireAnalyzed);
|
||||
}
|
||||
|
||||
private void OnAnchorChanged(Entity<CosmicSpireComponent> ent, ref AnchorStateChangedEvent args)
|
||||
{
|
||||
if (args.Anchored)
|
||||
{
|
||||
ent.Comp.Enabled = true;
|
||||
UpdateSpireAppearance(ent, SpireStatus.On);
|
||||
}
|
||||
|
||||
if (!args.Anchored)
|
||||
{
|
||||
ent.Comp.Enabled = false;
|
||||
UpdateSpireAppearance(ent, SpireStatus.Off);
|
||||
}
|
||||
|
||||
_ambient.SetAmbience(ent, ent.Comp.Enabled);
|
||||
_lights.SetEnabled(ent, ent.Comp.Enabled);
|
||||
}
|
||||
|
||||
private void OnDeviceUpdated(Entity<CosmicSpireComponent> ent, ref AtmosDeviceUpdateEvent args)
|
||||
{
|
||||
if (!ent.Comp.Enabled)
|
||||
return;
|
||||
if (args.Grid is not { } grid)
|
||||
return;
|
||||
var timeDelta = args.dt;
|
||||
var position = _transform.GetGridTilePositionOrDefault(ent.Owner);
|
||||
var environment = _atmos.GetTileMixture(grid, args.Map, position, true);
|
||||
var running = Drain(timeDelta, ent, environment);
|
||||
if (!running)
|
||||
return;
|
||||
var enumerator = _atmos.GetAdjacentTileMixtures(grid, position, false, true);
|
||||
while (enumerator.MoveNext(out var adjacent))
|
||||
{
|
||||
Drain(timeDelta, ent, adjacent);
|
||||
}
|
||||
|
||||
if (ent.Comp.Storage.TotalMoles >= ent.Comp.DrainThreshHold)
|
||||
{
|
||||
_popup.PopupCoordinates(Loc.GetString("cosmiccult-spire-entropy"), Transform(ent).Coordinates);
|
||||
ent.Comp.Storage.Clear();
|
||||
Spawn(ent.Comp.SpawnVFX, Transform(ent).Coordinates);
|
||||
Spawn(ent.Comp.EntropyMote, Transform(ent).Coordinates);
|
||||
|
||||
if (_cosmicRule.AssociatedGamerule(ent) is not { } cult)
|
||||
return;
|
||||
cult.Comp.EntropySiphoned++;
|
||||
}
|
||||
}
|
||||
|
||||
private bool Drain(float timeDelta, Entity<CosmicSpireComponent> ent, GasMixture? tile)
|
||||
{
|
||||
return _scrub.Scrub(timeDelta,
|
||||
ent.Comp.DrainRate * _atmos.PumpSpeedup(),
|
||||
ScrubberPumpDirection.Scrubbing,
|
||||
ent.Comp.DrainGases,
|
||||
tile,
|
||||
ent.Comp.Storage);
|
||||
}
|
||||
|
||||
private void OnSpireAnalyzed(Entity<CosmicSpireComponent> ent, ref GasAnalyzerScanEvent args)
|
||||
{
|
||||
args.GasMixtures ??= [];
|
||||
args.GasMixtures.Add((Name(ent), ent.Comp.Storage));
|
||||
}
|
||||
|
||||
private void UpdateSpireAppearance(EntityUid uid, SpireStatus status)
|
||||
{
|
||||
_appearance.SetData(uid, SpireVisuals.Status, status);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,336 @@
|
|||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Content.Server._DV.CosmicCult.Components;
|
||||
using Content.Server._Impstation.Thaven;
|
||||
using Content.Server.Actions;
|
||||
using Content.Server.Antag;
|
||||
using Content.Server.Bible.Components;
|
||||
using Content.Server.Interaction;
|
||||
using Content.Server.Light.Components;
|
||||
using Content.Server.Light.EntitySystems;
|
||||
using Content.Server.Popups;
|
||||
using Content.Server.Station.Components;
|
||||
using Content.Server.Station.Systems;
|
||||
using Content.Shared._DV.CosmicCult;
|
||||
using Content.Shared._DV.CosmicCult.Components;
|
||||
using Content.Shared._DV.CosmicCult.Components.Examine;
|
||||
using Content.Shared._Impstation.Thaven.Components;
|
||||
using Content.Shared.Bed.Sleep;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Dataset;
|
||||
using Content.Shared.DoAfter;
|
||||
using Content.Shared.Effects;
|
||||
using Content.Shared.Humanoid;
|
||||
using Content.Shared.IdentityManagement;
|
||||
using Content.Shared.Mind;
|
||||
using Content.Shared.Mind.Components;
|
||||
using Content.Shared.Mobs;
|
||||
using Content.Shared.Mobs.Components;
|
||||
using Content.Shared.Nutrition;
|
||||
using Content.Shared.Physics;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Random;
|
||||
using Content.Shared.StatusEffect;
|
||||
using Content.Shared.Stunnable;
|
||||
using Content.Shared.Throwing;
|
||||
using Content.Shared.Weapons.Ranged.Systems;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Physics.Systems;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Server._DV.CosmicCult.EntitySystems;
|
||||
|
||||
// TODO: enable once cosmic cult is rolled out more
|
||||
public sealed class RogueAscendedSystem : EntitySystem
|
||||
{
|
||||
// [Dependency] private readonly ActionsSystem _actions = default!;
|
||||
// [Dependency] private readonly SharedStunSystem _stun = default!;
|
||||
// [Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
// [Dependency] private readonly PopupSystem _popup = default!;
|
||||
// [Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||
// [Dependency] private readonly SharedPhysicsSystem _physics = default!;
|
||||
// [Dependency] private readonly SharedGunSystem _gun = default!;
|
||||
// [Dependency] private readonly SharedColorFlashEffectSystem _color = default!;
|
||||
// [Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
|
||||
// [Dependency] private readonly DamageableSystem _damageable = default!;
|
||||
// [Dependency] private readonly InteractionSystem _interact = default!;
|
||||
// [Dependency] private readonly EntityLookupSystem _lookup = default!;
|
||||
// [Dependency] private readonly PoweredLightSystem _poweredLight = default!;
|
||||
// [Dependency] private readonly SharedMindSystem _mind = default!;
|
||||
// [Dependency] private readonly IRobustRandom _random = default!;
|
||||
// [Dependency] private readonly IGameTiming _timing = default!;
|
||||
// [Dependency] private readonly ThrowingSystem _throw = default!;
|
||||
// [Dependency] private readonly StationSystem _station = default!;
|
||||
// [Dependency] private readonly StatusEffectsSystem _statusEffects = default!;
|
||||
// [Dependency] private readonly AntagSelectionSystem _antag = default!;
|
||||
// [Dependency] private readonly ThavenMoodsSystem _moodSystem = default!; //impstation
|
||||
//
|
||||
// [ValidatePrototypeId<DatasetPrototype>]
|
||||
// private const string AscendantDataset = "ThavenMoodsAscendantInfection";
|
||||
// [DataField] private EntProtoId _spawnWisp = "MobCosmicWisp";
|
||||
// [DataField] private EntProtoId _blankVFX = "CosmicBlankAbilityVFX";
|
||||
// [DataField] private EntProtoId _glareVFX = "CosmicGlareAbilityVFX";
|
||||
// [DataField] private SoundSpecifier _blankSFX = new SoundPathSpecifier("/Audio/_DV/CosmicCult/ability_blank.ogg");
|
||||
// [DataField] private SoundSpecifier _ascendantSFX = new SoundPathSpecifier("/Audio/_DV/CosmicCult/ascendant_shunt.ogg");
|
||||
// [DataField] private SoundSpecifier _novaSFX = new SoundPathSpecifier("/Audio/_DV/CosmicCult/ability_nova_cast.ogg");
|
||||
//
|
||||
// public override void Initialize()
|
||||
// {
|
||||
// base.Initialize();
|
||||
// SubscribeLocalEvent<RogueAscendedComponent, ComponentInit>(OnSpawn);
|
||||
// SubscribeLocalEvent<RogueAscendedComponent, MobStateChangedEvent>(OnMobStateChanged);
|
||||
// SubscribeLocalEvent<RogueAscendedDendriteComponent, BeforeFullyEatenEvent>(OnDendriteConsumed);
|
||||
//
|
||||
// SubscribeLocalEvent<RogueAscendedComponent, EventRogueGrandShunt>(OnRogueShunt);
|
||||
// SubscribeLocalEvent<RogueAscendedComponent, EventRogueCosmicNova>(OnRogueNova);
|
||||
// SubscribeLocalEvent<HumanoidAppearanceComponent, EventRogueCosmicNova>(OnPlayerNova);
|
||||
//
|
||||
// SubscribeLocalEvent<RogueAscendedComponent, EventRogueInfection>(OnAttemptInfection);
|
||||
// SubscribeLocalEvent<RogueAscendedComponent, EventRogueSlumber>(OnAttemptSlumber);
|
||||
// SubscribeLocalEvent<RogueAscendedComponent, EventRogueInfectionDoAfter>(OnInfectionDoAfter);
|
||||
// SubscribeLocalEvent<RogueAscendedComponent, EventRogueSlumberDoAfter>(OnSlumberDoAfter);
|
||||
//
|
||||
// SubscribeLocalEvent<RogueAscendedInfectionComponent, ComponentShutdown>(OnInfectionCleansed);
|
||||
// }
|
||||
// #region Spawn
|
||||
// private void OnSpawn(Entity<RogueAscendedComponent> uid, ref ComponentInit args) // I WANT THIS DINGUS YEETED TOWARDS THE STATION AT MACH JESUS
|
||||
// {
|
||||
// var station = _station.GetStationInMap(Transform(uid).MapID);
|
||||
// if (TryComp<StationDataComponent>(station, out var stationData))
|
||||
// {
|
||||
// var stationGrid = _station.GetLargestGrid(stationData);
|
||||
// _throw.TryThrow(uid, Transform(stationGrid!.Value).Coordinates, baseThrowSpeed: 50, null, 0, 0, false, false, false, false, false);
|
||||
// }
|
||||
// }
|
||||
// #endregion
|
||||
//
|
||||
// #region Death
|
||||
// private void OnMobStateChanged(EntityUid uid, RogueAscendedComponent comp, MobStateChangedEvent args)
|
||||
// {
|
||||
// if (args.NewMobState == MobState.Alive)
|
||||
// return;
|
||||
// _audio.PlayPvs(comp.MobSound, uid);
|
||||
// }
|
||||
// #endregion
|
||||
//
|
||||
// #region Consume Dendrite
|
||||
// private void OnDendriteConsumed(Entity<RogueAscendedDendriteComponent> uid, ref BeforeFullyEatenEvent args)
|
||||
// {
|
||||
// if (!HasComp<HumanoidAppearanceComponent>(args.User) || HasComp<RogueAscendedAuraComponent>(args.User))
|
||||
// return; // if it ain't human, or already ate, nvm
|
||||
// if (TryComp<CosmicCultComponent>(args.User, out var cultComp))
|
||||
// {
|
||||
// cultComp.EntropyBudget += 30; //if they're a cultist, make them very very rich
|
||||
// cultComp.CosmicEmpowered = true; // also empower them, assuming they aren't already
|
||||
// return;
|
||||
// }
|
||||
// Spawn(uid.Comp.Vfx, Transform(args.User).Coordinates);
|
||||
// EnsureComp<RogueAscendedAuraComponent>(args.User, out var starMark);
|
||||
// _actions.AddAction(args.User, ref uid.Comp.RogueFoodActionEntity, uid.Comp.RogueFoodAction, args.User);
|
||||
// _audio.PlayPvs(uid.Comp.ActivateSfx, args.User);
|
||||
// _popup.PopupCoordinates(Loc.GetString("rogue-ascended-dendrite-eaten"), Transform(args.User).Coordinates, PopupType.Medium);
|
||||
// _color.RaiseEffect(Color.CadetBlue, new List<EntityUid>() { args.User }, Filter.Pvs(args.User, entityManager: EntityManager));
|
||||
// _stun.TryKnockdown(args.User, uid.Comp.StunTime, false);
|
||||
// Dirty(args.User, starMark);
|
||||
// }
|
||||
// #endregion
|
||||
//
|
||||
// #region Cleanse
|
||||
// private void OnInfectionCleansed(Entity<RogueAscendedInfectionComponent> uid, ref ComponentShutdown args)
|
||||
// {
|
||||
// if (uid.Comp.HadMoods)
|
||||
// {
|
||||
// EnsureComp<ThavenMoodsComponent>(uid, out var moodComp); // ensure it because we don't need another if()
|
||||
// _moodSystem.ToggleEmaggable((uid, moodComp)); // enable emagging again
|
||||
// _moodSystem.ToggleSharedMoods((uid, moodComp)); // enable shared moods
|
||||
// _moodSystem.ClearMoods((uid, moodComp)); // wipe those moods again
|
||||
// _moodSystem.TryAddRandomMood((uid, moodComp), false);
|
||||
// _moodSystem.TryAddRandomMood((uid, moodComp));
|
||||
// }
|
||||
// else
|
||||
// RemComp<ThavenMoodsComponent>(uid);
|
||||
// }
|
||||
// #endregion
|
||||
//
|
||||
// #region Ability - Slumber
|
||||
// private void OnAttemptSlumber(Entity<RogueAscendedComponent> uid, ref EventRogueSlumber args)
|
||||
// {
|
||||
// if (TryComp<MobStateComponent>(args.Target, out var state) && state.CurrentState != MobState.Alive)
|
||||
// {
|
||||
// _popup.PopupEntity(Loc.GetString("rogue-ascended-shatter-fail"), uid, uid);
|
||||
// return;
|
||||
// }
|
||||
// var doargs = new DoAfterArgs(EntityManager, uid, uid.Comp.RogueSlumberDoAfterTime, new EventRogueSlumberDoAfter(), uid, args.Target)
|
||||
// {
|
||||
// DistanceThreshold = 2f,
|
||||
// Hidden = false,
|
||||
// BreakOnDamage = true,
|
||||
// BreakOnMove = true,
|
||||
// BlockDuplicate = true,
|
||||
// };
|
||||
// args.Handled = true;
|
||||
// _doAfter.TryStartDoAfter(doargs);
|
||||
// }
|
||||
// private void OnSlumberDoAfter(Entity<RogueAscendedComponent> uid, ref EventRogueSlumberDoAfter args)
|
||||
// {
|
||||
// if (args.Cancelled || args.Target == null)
|
||||
// return;
|
||||
// var target = args.Target.Value;
|
||||
//
|
||||
// _statusEffects.TryAddStatusEffect<ForcedSleepingComponent>(target, "ForcedSleep", uid.Comp.RogueSlumberTime, false);
|
||||
// _audio.PlayPvs(uid.Comp.ShatterSfx, target);
|
||||
// args.Handled = true;
|
||||
// Spawn(uid.Comp.Vfx, Transform(target).Coordinates);
|
||||
// }
|
||||
//
|
||||
// #endregion
|
||||
//
|
||||
// #region Ability - Infection
|
||||
// private void OnAttemptInfection(Entity<RogueAscendedComponent> uid, ref EventRogueInfection args)
|
||||
// {
|
||||
// if (TryComp<MobStateComponent>(args.Target, out var state) && state.CurrentState != MobState.Alive && HasComp<ForcedSleepingComponent>(args.Target))
|
||||
// {
|
||||
// _popup.PopupEntity(Loc.GetString("rogue-ascended-infection-fail"), uid, uid);
|
||||
// return;
|
||||
// }
|
||||
// if (args.Handled || !TryComp<MindContainerComponent>(args.Target, out var mindContainer) || !mindContainer.HasMind)
|
||||
// {
|
||||
// _popup.PopupEntity(Loc.GetString("rogue-ascended-infection-error"), uid, uid);
|
||||
// return;
|
||||
// }
|
||||
// var doargs = new DoAfterArgs(EntityManager, uid, uid.Comp.RogueInfectionDoAfterTime, new EventRogueInfectionDoAfter(), uid, args.Target)
|
||||
// {
|
||||
// DistanceThreshold = 2f,
|
||||
// Hidden = false,
|
||||
// BreakOnDamage = true,
|
||||
// BreakOnMove = true,
|
||||
// BlockDuplicate = true,
|
||||
// };
|
||||
// args.Handled = true;
|
||||
// _doAfter.TryStartDoAfter(doargs);
|
||||
// _audio.PlayPvs(uid.Comp.MobSound, uid);
|
||||
// _popup.PopupCoordinates(Loc.GetString("rogue-ascended-infection-notification",
|
||||
// ("target", Identity.Entity(args.Target, EntityManager)),
|
||||
// ("user", Identity.Entity(args.Performer, EntityManager))),
|
||||
// Transform(uid).Coordinates, PopupType.LargeCaution);
|
||||
// }
|
||||
// private void OnInfectionDoAfter(Entity<RogueAscendedComponent> uid, ref EventRogueInfectionDoAfter args)
|
||||
// {
|
||||
// if (args.Cancelled || args.Target == null)
|
||||
// return;
|
||||
// var target = args.Target.Value;
|
||||
// EnsureComp<RogueAscendedInfectionComponent>(target, out var infectionComp);
|
||||
// if (HasComp<ThavenMoodsComponent>(target))
|
||||
// infectionComp.HadMoods = true; // make note that they already had moods
|
||||
// EnsureComp<ThavenMoodsComponent>(target, out var moodComp);
|
||||
// Spawn(uid.Comp.Vfx, Transform(target).Coordinates);
|
||||
//
|
||||
// _moodSystem.ToggleEmaggable((target, moodComp)); // can't emag an infected thavenmood
|
||||
// _moodSystem.ClearMoods((target, moodComp)); // wipe those moods
|
||||
// _moodSystem.ToggleSharedMoods((target, moodComp)); // disable shared moods
|
||||
// _moodSystem.TryAddRandomMood((target, moodComp), AscendantDataset, false); // we don't need to notify them twice
|
||||
// _moodSystem.TryAddRandomMood((target, moodComp), AscendantDataset);
|
||||
// Dirty(target, moodComp);
|
||||
//
|
||||
// _antag.SendBriefing(target, Loc.GetString("rogue-ascended-infection-briefing"), Color.FromHex("#4cabb3"), null);
|
||||
// _damageable.TryChangeDamage(target, uid.Comp.InfectionHeal * -1);
|
||||
//
|
||||
// _stun.TryStun(target, uid.Comp.StunTime, false);
|
||||
// _audio.PlayPvs(uid.Comp.InfectionSfx, target);
|
||||
//
|
||||
// if (_mind.TryGetObjectiveComp<RogueInfectionConditionComponent>(uid, out var obj))
|
||||
// obj.MindsCorrupted++;
|
||||
// } // the year is 2093. We invoke 5,922 systems and add 30,419 components to an entity. Beacuase.
|
||||
// #endregion
|
||||
//
|
||||
// #region Ability - Nova
|
||||
// private void CastNova(EntityUid uid, EventRogueCosmicNova args)
|
||||
// {
|
||||
// var startPos = _transform.GetMapCoordinates(args.Performer);
|
||||
// var targetPos = _transform.ToMapCoordinates(args.Target);
|
||||
// var userVelocity = _physics.GetMapLinearVelocity(args.Performer);
|
||||
//
|
||||
// var delta = targetPos.Position - startPos.Position;
|
||||
// if (delta.EqualsApprox(Vector2.Zero))
|
||||
// delta = new(.01f, 0);
|
||||
//
|
||||
// args.Handled = true;
|
||||
// var ent = Spawn("ProjectileRogueCosmicNova", startPos);
|
||||
// _gun.ShootProjectile(ent, delta, userVelocity, args.Performer, args.Performer, 5f);
|
||||
// _audio.PlayPvs(_novaSFX, uid, AudioParams.Default.WithVariation(0.1f));
|
||||
// }
|
||||
// private void OnRogueNova(Entity<RogueAscendedComponent> uid, ref EventRogueCosmicNova args)
|
||||
// {
|
||||
// CastNova(uid, args);
|
||||
// }
|
||||
// private void OnPlayerNova(Entity<HumanoidAppearanceComponent> uid, ref EventRogueCosmicNova args)
|
||||
// {
|
||||
// CastNova(uid, args);
|
||||
// }
|
||||
// #endregion
|
||||
//
|
||||
// #region Ability - GrandShunt
|
||||
// private void OnRogueShunt(Entity<RogueAscendedComponent> uid, ref EventRogueGrandShunt args)
|
||||
// {
|
||||
// args.Handled = true;
|
||||
// Spawn(_glareVFX, Transform(uid).Coordinates);
|
||||
// var entities = _lookup.GetEntitiesInRange(Transform(uid).Coordinates, 10);
|
||||
// entities.RemoveWhere(entity => !HasComp<PoweredLightComponent>(entity));
|
||||
// foreach (var entity in entities)
|
||||
// _poweredLight.TryDestroyBulb(entity);
|
||||
//
|
||||
// var targetFilter = Filter.Pvs(uid).RemoveWhere(player =>
|
||||
// {
|
||||
// if (player.AttachedEntity == null)
|
||||
// return true;
|
||||
// var ent = player.AttachedEntity.Value;
|
||||
// if (!HasComp<MobStateComponent>(ent) || !HasComp<HumanoidAppearanceComponent>(ent) || HasComp<BibleUserComponent>(ent))
|
||||
// return true;
|
||||
// return !_interact.InRangeUnobstructed((uid, Transform(uid)), (ent, Transform(ent)), range: 0, collisionMask: CollisionGroup.Impassable);
|
||||
// });
|
||||
// var targets = new HashSet<NetEntity>(targetFilter.RemovePlayerByAttachedEntity(uid).Recipients.Select(ply => GetNetEntity(ply.AttachedEntity!.Value)));
|
||||
// foreach (var target in targets)
|
||||
// {
|
||||
// var subject = GetEntity(target);
|
||||
// if (!TryComp<MindContainerComponent>(subject, out var mindContainer) || !mindContainer.HasMind)
|
||||
// {
|
||||
// return;
|
||||
// }
|
||||
// var tgtpos = Transform(subject).Coordinates;
|
||||
// var mindEnt = mindContainer.Mind.Value;
|
||||
// var mind = Comp<MindComponent>(mindEnt);
|
||||
// mind.PreventGhosting = true;
|
||||
//
|
||||
// var spawnPoints = EntityManager.GetAllComponents(typeof(CosmicVoidSpawnComponent)).ToImmutableList();
|
||||
// if (spawnPoints.IsEmpty)
|
||||
// {
|
||||
// return;
|
||||
// }
|
||||
// var newSpawn = _random.Pick(spawnPoints);
|
||||
// var spawnTgt = Transform(newSpawn.Uid).Coordinates;
|
||||
// var mobUid = Spawn(_spawnWisp, spawnTgt);
|
||||
//
|
||||
// EnsureComp<CosmicMarkBlankComponent>(subject);
|
||||
// EnsureComp<InVoidComponent>(mobUid, out var inVoid);
|
||||
//
|
||||
// inVoid.OriginalBody = subject;
|
||||
// inVoid.ExitVoidTime = _timing.CurTime + TimeSpan.FromSeconds(14);
|
||||
//
|
||||
// _mind.TransferTo(mindEnt, mobUid);
|
||||
// _stun.TryKnockdown(subject, TimeSpan.FromSeconds(16), true);
|
||||
// _popup.PopupEntity(Loc.GetString("cosmicability-blank-transfer"), mobUid, mobUid);
|
||||
// _audio.PlayLocal(_blankSFX, mobUid, mobUid, AudioParams.Default.WithVolume(6f));
|
||||
// _color.RaiseEffect(Color.CadetBlue, new List<EntityUid>() { subject }, Filter.Pvs(subject, entityManager: EntityManager));
|
||||
//
|
||||
// Spawn(_blankVFX, tgtpos);
|
||||
// Spawn(_blankVFX, spawnTgt);
|
||||
// }
|
||||
// _audio.PlayPvs(_ascendantSFX, uid, AudioParams.Default.WithVolume(6f));
|
||||
// }
|
||||
// #endregion
|
||||
}
|
||||
|
|
@ -0,0 +1,450 @@
|
|||
using System.Linq;
|
||||
using Content.Server._DV.CosmicCult.Components;
|
||||
using Content.Server._DV.CosmicCult.EntitySystems;
|
||||
using Content.Server.Actions;
|
||||
using Content.Server.Atmos.Components;
|
||||
using Content.Server.Audio;
|
||||
using Content.Server.Chat.Systems;
|
||||
using Content.Server.Objectives.Components;
|
||||
using Content.Shared._DV.CCVars;
|
||||
using Content.Shared._DV.CosmicCult;
|
||||
using Content.Shared._DV.CosmicCult.Components;
|
||||
using Content.Shared._DV.CosmicCult.Prototypes;
|
||||
using Content.Shared.Audio;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Stacks;
|
||||
using Content.Shared.Temperature.Components;
|
||||
using Content.Shared.UserInterface;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Server._DV.CosmicCult;
|
||||
|
||||
public sealed class MonumentSystem : SharedMonumentSystem
|
||||
{
|
||||
[Dependency] private readonly ActionsSystem _actions = default!;
|
||||
[Dependency] private readonly AppearanceSystem _appearance = default!;
|
||||
[Dependency] private readonly ChatSystem _chatSystem = default!;
|
||||
[Dependency] private readonly CosmicCorruptingSystem _corrupting = default!;
|
||||
[Dependency] private readonly CosmicCultRuleSystem _cosmicRule = default!;
|
||||
[Dependency] private readonly DamageableSystem _damage = default!;
|
||||
[Dependency] private readonly EntityLookupSystem _lookup = default!;
|
||||
[Dependency] private readonly IConfigurationManager _config = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly IPrototypeManager _protoMan = default!;
|
||||
[Dependency] private readonly ServerGlobalSoundSystem _sound = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
[Dependency] private readonly SharedMapSystem _map = default!;
|
||||
[Dependency] private readonly SharedPopupSystem _popup = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||
[Dependency] private readonly SharedUserInterfaceSystem _ui = default!;
|
||||
|
||||
private static readonly EntProtoId CosmicGod = "MobCosmicGodSpawn";
|
||||
private static readonly EntProtoId MonumentCollider = "MonumentCollider";
|
||||
|
||||
private EntityUid? _monumentStorageMap;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<MonumentComponent, InteractUsingEvent>(OnInfuseHeldEntropy);
|
||||
SubscribeLocalEvent<MonumentComponent, ActivateInWorldEvent>(OnInfuseEntropy);
|
||||
}
|
||||
|
||||
|
||||
public override void Update(float frameTime) // This Update() can fit so much functionality in it
|
||||
{
|
||||
base.Update(frameTime);
|
||||
|
||||
var finaleQuery = EntityQueryEnumerator<CosmicFinaleComponent, MonumentComponent>(); // Enumerator for The Monument's Finale
|
||||
while (finaleQuery.MoveNext(out var uid, out var comp, out var monuComp))
|
||||
{
|
||||
if (_timing.CurTime >= monuComp.CheckTimer)
|
||||
{
|
||||
var entities = _lookup.GetEntitiesInRange(Transform(uid).Coordinates, 10);
|
||||
entities.RemoveWhere(entity => !HasComp<InfluenceVitalityComponent>(entity));
|
||||
foreach (var entity in entities) _damage.TryChangeDamage(entity, monuComp.MonumentHealing * -1);
|
||||
monuComp.CheckTimer = _timing.CurTime + monuComp.CheckWait;
|
||||
}
|
||||
if (comp.SongTimer is { } time && _timing.CurTime >= time)
|
||||
{
|
||||
comp.SongTimer = null;
|
||||
if (comp.SelectedSong is { } song)
|
||||
_sound.DispatchStationEventMusic(uid, song, StationEventMusicType.CosmicCult);
|
||||
}
|
||||
|
||||
if (comp.CurrentState == FinaleState.ActiveBuffer && _timing.CurTime >= comp.BufferTimer) // swap everything over when buffer timer runs out
|
||||
{
|
||||
comp.CurrentState = FinaleState.ActiveFinale;
|
||||
comp.FinaleTimer = _timing.CurTime + comp.FinaleRemainingTime;
|
||||
comp.SelectedSong = comp.FinaleMusic;
|
||||
|
||||
_sound.StopStationEventMusic(uid, StationEventMusicType.CosmicCult);
|
||||
_appearance.SetData(uid, MonumentVisuals.FinaleReached, 3);
|
||||
_chatSystem.DispatchStationAnnouncement(uid, Loc.GetString("cosmiccult-announce-finale-warning"), null, false, null, Color.FromHex("#cae8e8"));
|
||||
|
||||
comp.SongTimer = _timing.CurTime + TimeSpan.FromSeconds(1);
|
||||
}
|
||||
else if (comp.CurrentState == FinaleState.ActiveFinale && _timing.CurTime >= comp.FinaleTimer) // trigger wincondition on time runout
|
||||
{
|
||||
var victoryQuery = EntityQueryEnumerator<CosmicVictoryConditionComponent>();
|
||||
while (victoryQuery.MoveNext(out _, out var victoryComp))
|
||||
{
|
||||
victoryComp.Victory = true;
|
||||
}
|
||||
|
||||
_sound.StopStationEventMusic(uid, StationEventMusicType.CosmicCult);
|
||||
Spawn(CosmicGod, Transform(uid).Coordinates);
|
||||
comp.CurrentState = FinaleState.Victory;
|
||||
}
|
||||
}
|
||||
|
||||
var monumentQuery = EntityQueryEnumerator<MonumentComponent>();
|
||||
while (monumentQuery.MoveNext(out var uid, out var comp))
|
||||
{
|
||||
if (comp.PhaseOutTimer is { } timer && _timing.CurTime >= timer)
|
||||
{
|
||||
OnMonumentPhaseOut((uid, comp));
|
||||
comp.PhaseOutTimer = null;
|
||||
}
|
||||
}
|
||||
|
||||
var destinationQuery = EntityQueryEnumerator<MonumentMoveDestinationComponent>();
|
||||
while (destinationQuery.MoveNext(out var uid, out var comp))
|
||||
{
|
||||
if (comp.PhaseInTimer is { } timer && _timing.CurTime >= timer)
|
||||
{
|
||||
OnMonumentPhaseIn((uid, comp));
|
||||
comp.PhaseInTimer = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnMonumentPhaseOut(Entity<MonumentComponent> ent)
|
||||
{
|
||||
//todo check if anything gets messed up by doing this to the monument?
|
||||
_transform.SetParent(ent, EnsureStorageMapExists());
|
||||
|
||||
if (ent.Comp.CurrentGlyph is not null) //delete the scribed glyph as well
|
||||
QueueDel(ent.Comp.CurrentGlyph);
|
||||
|
||||
//close the UI for everyone who has it open
|
||||
_ui.CloseUi(ent.Owner, MonumentKey.Key);
|
||||
}
|
||||
|
||||
private void OnMonumentPhaseIn(Entity<MonumentMoveDestinationComponent> ent)
|
||||
{
|
||||
var colliderQuery = EntityQueryEnumerator<MonumentCollisionComponent>();
|
||||
while (colliderQuery.MoveNext(out var collider, out _))
|
||||
{
|
||||
QueueDel(collider);
|
||||
}
|
||||
|
||||
if (ent.Comp.Monument is null)
|
||||
return;
|
||||
|
||||
var xform = Transform(ent);
|
||||
_transform.SetCoordinates(ent.Comp.Monument.Value, xform.Coordinates);
|
||||
_transform.AnchorEntity(ent.Comp.Monument.Value); //no idea if this does anything but let's be safe about it
|
||||
Spawn(MonumentCollider, xform.Coordinates);
|
||||
|
||||
if (TryComp<CosmicCorruptingComponent>(ent.Comp.Monument.Value, out var cosmicCorruptingComp))
|
||||
_corrupting.RecalculateStartingTiles((ent.Comp.Monument.Value, cosmicCorruptingComp));
|
||||
}
|
||||
|
||||
private EntityUid EnsureStorageMapExists()
|
||||
{
|
||||
if (_monumentStorageMap != null && Exists(_monumentStorageMap))
|
||||
return _monumentStorageMap.Value;
|
||||
|
||||
_monumentStorageMap = _map.CreateMap();
|
||||
_map.SetPaused(_monumentStorageMap.Value, true);
|
||||
return _monumentStorageMap.Value;
|
||||
}
|
||||
|
||||
public void PhaseOutMonument(Entity<MonumentComponent> ent)
|
||||
{
|
||||
ent.Comp.PhaseOutTimer = _timing.CurTime + TimeSpan.FromSeconds(0.45);
|
||||
}
|
||||
|
||||
public void UpdateMonumentProgress(Entity<MonumentComponent> ent, Entity<CosmicCultRuleComponent> cult)
|
||||
{
|
||||
ent.Comp.CurrentProgress = ent.Comp.TotalEntropy + cult.Comp.TotalCult * _config.GetCVar(DCCVars.CosmicCultistEntropyValue);
|
||||
}
|
||||
|
||||
private void OnInfuseEntropy(Entity<MonumentComponent> uid, ref ActivateInWorldEvent args)
|
||||
{
|
||||
if (!args.Complex)
|
||||
return;
|
||||
if (TryComp<CosmicCultComponent>(args.User, out var cultComp) && cultComp.EntropyStored > 0)
|
||||
{
|
||||
args.Handled = AddEntropy(uid, (args.User, cultComp));
|
||||
}
|
||||
}
|
||||
|
||||
private void OnInfuseHeldEntropy(Entity<MonumentComponent> uid, ref InteractUsingEvent args)
|
||||
{
|
||||
if (!HasComp<CosmicEntropyMoteComponent>(args.Used) || !TryComp<CosmicCultComponent>(args.User, out var cultComp) || !uid.Comp.Enabled || args.Handled)
|
||||
{
|
||||
_popup.PopupEntity(Loc.GetString("cosmiccult-entropy-unavailable"), args.User, args.User);
|
||||
return;
|
||||
}
|
||||
args.Handled = AddEntropy(uid, args.Used, (args.User, cultComp));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Method for adding the Cultist's internal Entropy to The Monument.
|
||||
/// </summary>
|
||||
private bool AddEntropy(Entity<MonumentComponent> monument, Entity<CosmicCultComponent> cultist)
|
||||
{
|
||||
_audio.PlayEntity(_audio.ResolveSound(monument.Comp.InfusionSFX), cultist, monument);
|
||||
_popup.PopupEntity(Loc.GetString("cosmiccult-entropy-inserted", ("count", cultist.Comp.EntropyStored)), cultist, cultist);
|
||||
monument.Comp.TotalEntropy += cultist.Comp.EntropyStored;
|
||||
cultist.Comp.EntropyStored = 0;
|
||||
Dirty(cultist, cultist.Comp);
|
||||
_cosmicRule.UpdateCultData(monument);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Method for adding itemized Entropy to The Monument.
|
||||
/// </summary>
|
||||
private bool AddEntropy(Entity<MonumentComponent> monument, EntityUid entropy, Entity<CosmicCultComponent> cultist)
|
||||
{
|
||||
var quant = TryComp<StackComponent>(entropy, out var stackComp) ? stackComp.Count : 1;
|
||||
monument.Comp.TotalEntropy += quant;
|
||||
cultist.Comp.EntropyBudget += quant;
|
||||
|
||||
Dirty(cultist, cultist.Comp);
|
||||
_cosmicRule.UpdateCultData(monument);
|
||||
|
||||
_popup.PopupEntity(Loc.GetString("cosmiccult-entropy-inserted", ("count", quant)), cultist, cultist);
|
||||
_audio.PlayEntity(_audio.ResolveSound(monument.Comp.InfusionSFX), cultist, monument);
|
||||
QueueDel(entropy);
|
||||
return true;
|
||||
}
|
||||
|
||||
public void UpdateMonumentAppearance(Entity<MonumentComponent> ent, bool tierUp) // this is kinda awful, but it works, and i've seen worse. improve it at thine leisure
|
||||
{
|
||||
if (_cosmicRule.AssociatedGamerule(ent) is not { } cult)
|
||||
return;
|
||||
if (!TryComp<CosmicFinaleComponent>(ent, out var finaleComp))
|
||||
return;
|
||||
_appearance.SetData(ent, MonumentVisuals.Monument, cult.Comp.CurrentTier);
|
||||
|
||||
switch (cult.Comp.CurrentTier)
|
||||
{
|
||||
case 3:
|
||||
_appearance.SetData(ent, MonumentVisuals.Tier3, true);
|
||||
break;
|
||||
case 2:
|
||||
_appearance.SetData(ent, MonumentVisuals.Tier3, false);
|
||||
break;
|
||||
}
|
||||
|
||||
if (tierUp)
|
||||
{
|
||||
var transformComp = EnsureComp<MonumentTransformingComponent>(ent);
|
||||
transformComp.EndTime = _timing.CurTime + ent.Comp.TransformTime;
|
||||
_appearance.SetData(ent, MonumentVisuals.Transforming, true);
|
||||
}
|
||||
|
||||
if (finaleComp.CurrentState != FinaleState.Unavailable)
|
||||
_appearance.SetData(ent, MonumentVisuals.FinaleReached, true);
|
||||
}
|
||||
|
||||
//note - these ar the thresholds for moving to the next tier
|
||||
//so t1 -> 2 needs 20% of the crew
|
||||
//t2 -> 3 needs 40%
|
||||
//and t3 -> finale needs an extra 20 entropy
|
||||
public void UpdateMonumentReqsForTier(Entity<MonumentComponent> monument, int tier)
|
||||
{
|
||||
if (_cosmicRule.AssociatedGamerule(monument) is not { } cult)
|
||||
return;
|
||||
|
||||
var numberOfCrewForTier3 = Math.Round((double)cult.Comp.TotalCrew / 100 * _config.GetCVar(DCCVars.CosmicCultTargetConversionPercent)); // 40% of current pop
|
||||
|
||||
switch (tier)
|
||||
{
|
||||
case 1:
|
||||
monument.Comp.ProgressOffset = 0;
|
||||
monument.Comp.TargetProgress = (int)(numberOfCrewForTier3 / 2 * _config.GetCVar(DCCVars.CosmicCultistEntropyValue));
|
||||
break;
|
||||
case 2:
|
||||
monument.Comp.ProgressOffset = (int)(numberOfCrewForTier3 / 2 * _config.GetCVar(DCCVars.CosmicCultistEntropyValue)); //reset the progress offset
|
||||
monument.Comp.TargetProgress = (int)(numberOfCrewForTier3 * _config.GetCVar(DCCVars.CosmicCultistEntropyValue));
|
||||
break;
|
||||
case 3:
|
||||
monument.Comp.ProgressOffset = (int)(numberOfCrewForTier3 * _config.GetCVar(DCCVars.CosmicCultistEntropyValue));
|
||||
monument.Comp.TargetProgress = (int)(numberOfCrewForTier3 * _config.GetCVar(DCCVars.CosmicCultistEntropyValue)); //removed offset; replaced with timer
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void SetCanTierUp(Entity<MonumentComponent> ent, bool canTierUp)
|
||||
{
|
||||
ent.Comp.CanTierUp = canTierUp;
|
||||
}
|
||||
|
||||
public void SetTargetProgess(Entity<MonumentComponent> ent, int targetProgress)
|
||||
{
|
||||
ent.Comp.TargetProgress = targetProgress;
|
||||
}
|
||||
|
||||
public void Disable(Entity<MonumentComponent> ent)
|
||||
{
|
||||
ent.Comp.Enabled = false;
|
||||
}
|
||||
|
||||
public void Enable(Entity<MonumentComponent> ent)
|
||||
{
|
||||
ent.Comp.Enabled = true;
|
||||
}
|
||||
|
||||
public void MonumentTier1(Entity<MonumentComponent> uid)
|
||||
{
|
||||
if (_cosmicRule.AssociatedGamerule(uid) is not { } cult)
|
||||
return;
|
||||
|
||||
UpdateMonumentAppearance(uid, false);
|
||||
|
||||
//this is probably unnecessary but I have no idea where they get added to the list atm - ruddygreat
|
||||
foreach (var glyphProto in _protoMan.EnumeratePrototypes<GlyphPrototype>().Where(proto => proto.Tier == 1))
|
||||
{
|
||||
uid.Comp.UnlockedGlyphs.Add(glyphProto.ID);
|
||||
}
|
||||
|
||||
//basically completely unnecessary, but putting this here for sanity & futureproofing - ruddygreat
|
||||
var query = EntityQueryEnumerator<CosmicCultComponent>();
|
||||
while (query.MoveNext(out var cultist, out var cultComp))
|
||||
{
|
||||
foreach (var influenceProto in _protoMan.EnumeratePrototypes<InfluencePrototype>().Where(influenceProto => influenceProto.Tier == 1))
|
||||
{
|
||||
cultComp.UnlockedInfluences.Add(influenceProto.ID);
|
||||
}
|
||||
|
||||
Dirty(cultist, cultComp);
|
||||
}
|
||||
|
||||
var objectiveQuery = EntityQueryEnumerator<CosmicTierConditionComponent>();
|
||||
while (objectiveQuery.MoveNext(out _, out var objectiveComp))
|
||||
{
|
||||
objectiveComp.Tier = 1;
|
||||
}
|
||||
}
|
||||
|
||||
public void MonumentTier2(Entity<MonumentComponent> uid)
|
||||
{
|
||||
if (_cosmicRule.AssociatedGamerule(uid) is not { } cult)
|
||||
return;
|
||||
|
||||
UpdateMonumentAppearance(uid, true);
|
||||
|
||||
foreach (var glyphProto in _protoMan.EnumeratePrototypes<GlyphPrototype>().Where(proto => proto.Tier == 2))
|
||||
{
|
||||
uid.Comp.UnlockedGlyphs.Add(glyphProto.ID);
|
||||
}
|
||||
|
||||
var objectiveQuery = EntityQueryEnumerator<CosmicTierConditionComponent>();
|
||||
while (objectiveQuery.MoveNext(out _, out var objectiveComp))
|
||||
{
|
||||
objectiveComp.Tier = 2;
|
||||
}
|
||||
|
||||
var query = EntityQueryEnumerator<CosmicCultComponent>();
|
||||
while (query.MoveNext(out var cultist, out var cultComp))
|
||||
{
|
||||
foreach (var influenceProto in _protoMan.EnumeratePrototypes<InfluencePrototype>().Where(influenceProto => influenceProto.Tier == 2))
|
||||
{
|
||||
cultComp.UnlockedInfluences.Add(influenceProto.ID);
|
||||
}
|
||||
|
||||
cultComp.EntropyBudget += (int)Math.Floor(Math.Round((double)cult.Comp.TotalCrew / 100 * 10)); // pity system. 10% of the playercount worth of entropy on tier up
|
||||
|
||||
Dirty(cultist, cultComp);
|
||||
}
|
||||
|
||||
//add the move action
|
||||
var leaderQuery = EntityQueryEnumerator<CosmicCultLeadComponent>();
|
||||
while (leaderQuery.MoveNext(out var leader, out var leaderComp))
|
||||
{
|
||||
_actions.AddAction(leader, ref leaderComp.CosmicMonumentMoveActionEntity, leaderComp.CosmicMonumentMoveAction, leader);
|
||||
}
|
||||
|
||||
Dirty(uid);
|
||||
}
|
||||
|
||||
public void MonumentTier3(Entity<MonumentComponent> uid)
|
||||
{
|
||||
if (_cosmicRule.AssociatedGamerule(uid) is not { } cult)
|
||||
return;
|
||||
|
||||
foreach (var glyphProto in _protoMan.EnumeratePrototypes<GlyphPrototype>().Where(proto => proto.Tier == 3))
|
||||
{
|
||||
uid.Comp.UnlockedGlyphs.Add(glyphProto.ID);
|
||||
}
|
||||
|
||||
UpdateMonumentAppearance(uid, true);
|
||||
|
||||
var objectiveQuery = EntityQueryEnumerator<CosmicTierConditionComponent>();
|
||||
while (objectiveQuery.MoveNext(out var _, out var objectiveComp))
|
||||
{
|
||||
objectiveComp.Tier = 3;
|
||||
}
|
||||
|
||||
var query = EntityQueryEnumerator<CosmicCultComponent>();
|
||||
while (query.MoveNext(out var cultist, out var cultComp))
|
||||
{
|
||||
EnsureComp<PressureImmunityComponent>(cultist);
|
||||
EnsureComp<TemperatureImmunityComponent>(cultist);
|
||||
|
||||
_damage.SetDamageContainerID(cultist, "BiologicalMetaphysical");
|
||||
|
||||
foreach (var influenceProto in _protoMan.EnumeratePrototypes<InfluencePrototype>().Where(influenceProto => influenceProto.Tier == 3))
|
||||
{
|
||||
cultComp.UnlockedInfluences.Add(influenceProto.ID);
|
||||
}
|
||||
|
||||
cultComp.Respiration = false;
|
||||
cultComp.EntropyBudget += Convert.ToInt16(Math.Floor(Math.Round((double)cult.Comp.TotalCrew / 100 * 10))); //pity system. 10% of the playercount worth of entropy on tier up
|
||||
Dirty(cultist, cultComp);
|
||||
}
|
||||
|
||||
//remove the move action
|
||||
var leaderQuery = EntityQueryEnumerator<CosmicCultLeadComponent>();
|
||||
while (leaderQuery.MoveNext(out var leader, out var leaderComp))
|
||||
{
|
||||
_actions.RemoveAction(leader, leaderComp.CosmicMonumentMoveActionEntity);
|
||||
}
|
||||
|
||||
Dirty(uid);
|
||||
}
|
||||
|
||||
public void ReadyFinale(Entity<MonumentComponent> uid, CosmicFinaleComponent finaleComp)
|
||||
{
|
||||
if (TryComp<CosmicCorruptingComponent>(uid, out var comp))
|
||||
_corrupting.Enable((uid, comp));
|
||||
|
||||
if (TryComp<ActivatableUIComponent>(uid, out var uiComp))
|
||||
{
|
||||
if (TryComp<UserInterfaceComponent>(uid, out var uiComp2)) //close the UI for everyone who has it open
|
||||
{
|
||||
_ui.CloseUi((uid.Owner, uiComp2), MonumentKey.Key);
|
||||
}
|
||||
|
||||
uiComp.Key = null; //kazne called this the laziest way to disable a UI ever
|
||||
}
|
||||
|
||||
finaleComp.CurrentState = FinaleState.ReadyBuffer;
|
||||
uid.Comp.Enabled = false;
|
||||
uid.Comp.TargetProgress = uid.Comp.CurrentProgress;
|
||||
|
||||
_popup.PopupCoordinates(Loc.GetString("cosmiccult-finale-ready"), Transform(uid).Coordinates, PopupType.Large);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
using Content.Server._DV.CosmicCult.Components;
|
||||
using Content.Server.Objectives.Systems;
|
||||
using Content.Shared.Objectives.Components;
|
||||
|
||||
namespace Content.Server._DV.CosmicCult;
|
||||
|
||||
public sealed class RogueAscendedObjectiveSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly NumberObjectiveSystem _number = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<RogueInfectionConditionComponent, ObjectiveGetProgressEvent>(OnGetInfectionProgress);
|
||||
}
|
||||
|
||||
private void OnGetInfectionProgress(EntityUid uid, RogueInfectionConditionComponent comp, ref ObjectiveGetProgressEvent args)
|
||||
{
|
||||
// prevent divide-by-zero
|
||||
args.Progress = _number.GetTarget(uid) == 0 ? 1f : MathF.Min(comp.MindsCorrupted / (float)_number.GetTarget(uid), 1f);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
"Cosmic Cult assets" refers to all digital images and digital audio published for the purposes of contribution towards the Cosmic Cult project, an antagonist designed for Space Station 14.
|
||||
"Cosmic Cult code" refers to all digital files containing C-Sharp code published for the purposes of contribution towards the Cosmic Cult project, an antagonist designed for Space Station 14.
|
||||
"Cosmic Cult localization" refers to all digital ".ftl" files containing language localization text published for the purposes of contribution towards the Cosmic Cult project, an antagonist designed for Space Station 14.
|
||||
|
||||
All Cosmic Cult assets, code, and localization may be utilized by and replicated to any Space Station 14 repository, regardless of repository licensing.
|
||||
Cosmic Cult assets must retain their CC-BY-SA-3.0 licenses, as dictated by said license.
|
||||
|
||||
Cosmic Cult assets, code, localization, and/or derivatives made from aforementioned are not to be used for the purpose of training "Artificial Intelligence(s)", "Neural Network(s)", "Large Language Model(s)", or any AI whatsoever.
|
||||
|
||||
By contributing to or utilizing assets from the Cosmic Cult project, you agree to comply with the statements described above.
|
||||
|
||||
- AftrLite
|
||||
|
|
@ -43,7 +43,8 @@ public sealed class GameGlobalSoundEvent : GlobalSoundEvent
|
|||
|
||||
public enum StationEventMusicType : byte
|
||||
{
|
||||
Nuke
|
||||
Nuke,
|
||||
CosmicCult, // DeltaV - Cosmic Cult
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -337,6 +337,17 @@ namespace Content.Shared.Damage
|
|||
Dirty(uid, comp);
|
||||
}
|
||||
|
||||
// Begin DeltaV Additions - We need to be able to change DamageContainer to make cultists vulnerable to Holy Damage
|
||||
public void SetDamageContainerID(Entity<DamageableComponent?> ent, string damageContainerId)
|
||||
{
|
||||
if (!_damageableQuery.Resolve(ent, ref ent.Comp))
|
||||
return;
|
||||
|
||||
ent.Comp.DamageContainerID = damageContainerId;
|
||||
Dirty(ent);
|
||||
}
|
||||
// End DeltaV Additions
|
||||
|
||||
private void DamageableGetState(EntityUid uid, DamageableComponent component, ref ComponentGetState args)
|
||||
{
|
||||
if (_netMan.IsServer)
|
||||
|
|
|
|||
|
|
@ -6,13 +6,12 @@ namespace Content.Shared.Eye
|
|||
[FlagsFor(typeof(VisibilityMaskLayer))]
|
||||
public enum VisibilityFlags : int
|
||||
{
|
||||
None = 0,
|
||||
None = 0,
|
||||
Normal = 1 << 0,
|
||||
Ghost = 1 << 1,
|
||||
Subfloor = 1 << 3, // DeltaV - 4 is occupied by PsionicInvisibility and changing that massively fucks up stuff
|
||||
// Begin DeltaV Additions
|
||||
PsionicInvisibility = 1 << 2,
|
||||
TelegnosticProjection = PsionicInvisibility | Normal
|
||||
// End DeltaV Additions
|
||||
PsionicInvisibility = 1 << 2, // DeltaV - Psionic Invisibility
|
||||
TelegnosticProjection = PsionicInvisibility | Normal, // DeltaV - Telegnostic Projection
|
||||
CosmicCultMonument = 1 << 4, // DeltaV - Cosmic Cult
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -410,6 +410,21 @@ public abstract partial class SharedMindSystem : EntitySystem
|
|||
return false;
|
||||
}
|
||||
|
||||
// Begin DeltaV - Cosmic Cult Deconversion
|
||||
public void ClearObjectives(EntityUid mind, MindComponent? comp = null)
|
||||
{
|
||||
if (!Resolve(mind, ref comp))
|
||||
return;
|
||||
|
||||
foreach (var obj in comp.Objectives)
|
||||
{
|
||||
QueueDel(obj);
|
||||
}
|
||||
comp.Objectives.Clear();
|
||||
Dirty(mind, comp);
|
||||
}
|
||||
// End DeltaV - Cosmic Cult Deconversion
|
||||
|
||||
/// <summary>
|
||||
/// Copies objectives from one mind to another, so that they are shared between two players.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -203,4 +203,43 @@ public sealed class DCCVars
|
|||
/// </summary>
|
||||
public static readonly CVarDef<int> MaxObjectiveSummaryLength =
|
||||
CVarDef.Create("game.max_objective_summary_length", 256, CVar.SERVER | CVar.REPLICATED);
|
||||
|
||||
/*
|
||||
* Cosmic Cult
|
||||
*/
|
||||
/// <summary>
|
||||
/// How much entropy a convert is worth towards the next monument tier.
|
||||
/// </summary>
|
||||
public static readonly CVarDef<int> CosmicCultistEntropyValue =
|
||||
CVarDef.Create("cosmiccult.cultist_entropy_value", 7, CVar.SERVER);
|
||||
|
||||
/// <summary>
|
||||
/// How much of the crew the cult is aiming to convert for a tier 3 monument.
|
||||
/// </summary>
|
||||
public static readonly CVarDef<int> CosmicCultTargetConversionPercent =
|
||||
CVarDef.Create("cosmiccult.target_conversion_percent", 40, CVar.SERVER);
|
||||
|
||||
/// <summary>
|
||||
/// How long the timer for the cult's stewardship vote lasts.
|
||||
/// </summary>
|
||||
public static readonly CVarDef<int> CosmicCultStewardVoteTimer =
|
||||
CVarDef.Create("cosmiccult.steward_vote_timer", 40, CVar.SERVER);
|
||||
|
||||
/// <summary>
|
||||
/// The delay between the monument getting upgraded to tier 2 and the crew learning of that fact. the monument cannot be upgraded again in this time.
|
||||
/// </summary>
|
||||
public static readonly CVarDef<int> CosmicCultT2RevealDelaySeconds =
|
||||
CVarDef.Create("cosmiccult.t2_reveal_delay_seconds", 120, CVar.SERVER);
|
||||
|
||||
/// <summary>
|
||||
/// The delay between the monument getting upgraded to tier 3 and the crew learning of that fact. the monument cannot be upgraded again in this time.
|
||||
/// </summary>
|
||||
public static readonly CVarDef<int> CosmicCultT3RevealDelaySeconds =
|
||||
CVarDef.Create("cosmiccult.t3_reveal_delay_seconds", 60, CVar.SERVER);
|
||||
|
||||
/// <summary>
|
||||
/// The delay between the monument getting upgraded to tier 3 and the finale starting.
|
||||
/// </summary>
|
||||
public static readonly CVarDef<int> CosmicCultFinaleDelaySeconds =
|
||||
CVarDef.Create("cosmiccult.extra_entropy_for_finale", 150, CVar.SERVER);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,7 @@
|
|||
using Content.Shared.DoAfter;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared._DV.CosmicCult;
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed partial class CleanseOnDoAfterEvent : SimpleDoAfterEvent;
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
|
||||
|
||||
namespace Content.Shared._DV.CosmicCult.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Component for targets being cleansed of corruption.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
[AutoGenerateComponentPause]
|
||||
public sealed partial class CleanseCultComponent : Component
|
||||
{
|
||||
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
|
||||
[AutoPausedField]
|
||||
public TimeSpan CleanseTime = default!;
|
||||
|
||||
[DataField] public TimeSpan CleanseDuration = TimeSpan.FromSeconds(25);
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
using Content.Shared.Damage;
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared._DV.CosmicCult.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Component to call back to the cosmic cult ability system regarding a collision
|
||||
/// </summary>
|
||||
[NetworkedComponent, RegisterComponent]
|
||||
public sealed partial class CosmicAstralNovaComponent : Component
|
||||
{
|
||||
[DataField]
|
||||
public bool DoStun = true;
|
||||
|
||||
[DataField]
|
||||
public DamageSpecifier CosmicNovaDamage = new()
|
||||
{
|
||||
DamageDict = new() {
|
||||
{ "Asphyxiation", 13 }
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Shared._DV.CosmicCult.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Indicates that an entity will be converted to the given prototype when corrupted by the Cosmic Cult
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed partial class CosmicCorruptibleComponent : Component
|
||||
{
|
||||
[DataField(required: true)]
|
||||
public EntProtoId ConvertTo;
|
||||
}
|
||||
|
|
@ -0,0 +1,161 @@
|
|||
using Robust.Shared.GameStates;
|
||||
using Content.Shared.StatusIcon;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Audio;
|
||||
using Content.Shared._DV.CosmicCult.Prototypes;
|
||||
using Content.Shared.Damage.Prototypes;
|
||||
using Content.Shared.Alert;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared._DV.CosmicCult.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Added to entities to tag that they are a cosmic cultist. Holds nearly all cultist-relevant data! Removal of this component is used to call for a deconversion
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
[AutoGenerateComponentState]
|
||||
public sealed partial class CosmicCultComponent : Component
|
||||
{
|
||||
#region Housekeeping
|
||||
|
||||
/// <summary>
|
||||
/// The status icon prototype displayed for cosmic cultists.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public ProtoId<FactionIconPrototype> StatusIcon = "CosmicCultIcon";
|
||||
#endregion
|
||||
|
||||
#region Ability Data
|
||||
[DataField]
|
||||
[AutoNetworkedField]
|
||||
public HashSet<ProtoId<InfluencePrototype>> UnlockedInfluences =
|
||||
[
|
||||
"InfluenceAberrantLapse",
|
||||
"InfluenceShuntSubjectivity",
|
||||
"InfluenceEschewMetabolism",
|
||||
];
|
||||
|
||||
[DataField]
|
||||
[AutoNetworkedField]
|
||||
public HashSet<EntProtoId> CosmicCultActions =
|
||||
[
|
||||
"ActionCosmicSiphon",
|
||||
"ActionCosmicGlare",
|
||||
];
|
||||
|
||||
public HashSet<EntityUid?> ActionEntities = [];
|
||||
|
||||
[DataField]
|
||||
[AutoNetworkedField]
|
||||
public HashSet<ProtoId<InfluencePrototype>> OwnedInfluences = [];
|
||||
|
||||
/// <summary>
|
||||
/// The duration of the doAfter for Siphon Entropy
|
||||
/// </summary>
|
||||
[DataField] public TimeSpan CosmicSiphonDelay = TimeSpan.FromSeconds(2);
|
||||
|
||||
/// <summary>
|
||||
/// The duration of the doAfter for Shunt Subjectivity
|
||||
/// </summary>
|
||||
[DataField] public TimeSpan CosmicBlankDelay = TimeSpan.FromSeconds(0.6f);
|
||||
|
||||
/// <summary>
|
||||
/// The duration of Shunt Subjectivity's trip to the cosmic void
|
||||
/// </summary>
|
||||
[DataField] public TimeSpan CosmicBlankDuration = TimeSpan.FromSeconds(22);
|
||||
|
||||
/// <summary>
|
||||
/// The duration of Vacuous Imposition's shield.
|
||||
/// </summary>
|
||||
[DataField] public TimeSpan CosmicImpositionDuration = TimeSpan.FromSeconds(5.8);
|
||||
|
||||
/// <summary>
|
||||
/// The duration of Null Glare's flash/disorientation.
|
||||
/// </summary>
|
||||
[DataField] public TimeSpan CosmicGlareDuration = TimeSpan.FromSeconds(8);
|
||||
|
||||
/// <summary>
|
||||
/// The range of Null Glare.
|
||||
/// </summary>
|
||||
[DataField] public int CosmicGlareRange = 8;
|
||||
|
||||
/// <summary>
|
||||
/// The movement speed penalty inflicted by Null Glare.
|
||||
/// </summary>
|
||||
[DataField] public float CosmicGlarePenalty = 0.3f;
|
||||
|
||||
/// <summary>
|
||||
/// The stun duration inflicted by Null Glare.
|
||||
/// </summary>
|
||||
[DataField] public TimeSpan CosmicGlareStun = TimeSpan.FromSeconds(0);
|
||||
|
||||
/// <summary>
|
||||
/// The amount of Entropy generated by Siphon Entropy
|
||||
/// </summary>
|
||||
[DataField] public int CosmicSiphonQuantity = 1;
|
||||
#endregion
|
||||
|
||||
#region Misc Data
|
||||
/// <summary>
|
||||
/// The amount of Entropy the user is allowed to spend at The Monument.
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField] public int EntropyBudget;
|
||||
|
||||
/// <summary>
|
||||
/// The amount of Entropy the user is currently holding on to.
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField] public int EntropyStored;
|
||||
|
||||
/// <summary>
|
||||
/// The maximum amount of Entropy the user can have at once.
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField] public int EntropyStoredCap = 14;
|
||||
|
||||
/// <summary>
|
||||
/// Wether or not this cultist has been empowered by a Malign Rift.
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField] public bool CosmicEmpowered;
|
||||
|
||||
/// <summary>
|
||||
/// Wether or not this cultist needs to respirate.
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField] public bool Respiration = true;
|
||||
|
||||
/// <summary>
|
||||
/// A string for storing what damage container this cultist had upon conversion.
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField] public ProtoId<DamageContainerPrototype> StoredDamageContainer = "Biological";
|
||||
|
||||
/// <summary>
|
||||
/// The alert for displaying the cultist's currently held Entropy.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public ProtoId<AlertPrototype> EntropyAlert = "CosmicEntropy";
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// The gamerule that this cultist is associated with
|
||||
/// </summary>
|
||||
[DataField(serverOnly: true)]
|
||||
public EntityUid CultGamerule;
|
||||
|
||||
#region VFX & SFX
|
||||
[DataField] public EntProtoId SpawnWisp = "MobCosmicWisp";
|
||||
[DataField] public EntProtoId LapseVFX = "CosmicLapseAbilityVFX";
|
||||
[DataField] public EntProtoId BlankVFX = "CosmicBlankAbilityVFX";
|
||||
[DataField] public EntProtoId GlareVFX = "CosmicGlareAbilityVFX";
|
||||
[DataField] public EntProtoId AbsorbVFX = "CosmicGenericVFX";
|
||||
[DataField] public EntProtoId ImpositionVFX = "CosmicImpositionAbilityVFX";
|
||||
[DataField] public SoundSpecifier BlankSFX = new SoundPathSpecifier("/Audio/_DV/CosmicCult/ability_blank.ogg");
|
||||
[DataField] public SoundSpecifier IngressSFX = new SoundPathSpecifier("/Audio/_DV/CosmicCult/ability_ingress.ogg");
|
||||
[DataField] public SoundSpecifier GlareSFX = new SoundPathSpecifier("/Audio/_DV/CosmicCult/ability_glare.ogg");
|
||||
[DataField] public SoundSpecifier NovaCastSFX = new SoundPathSpecifier("/Audio/_DV/CosmicCult/ability_nova_cast.ogg");
|
||||
[DataField] public SoundSpecifier ImpositionSFX = new SoundPathSpecifier("/Audio/_DV/CosmicCult/ability_imposition.ogg");
|
||||
#endregion
|
||||
}
|
||||
|
||||
[NetSerializable, Serializable]
|
||||
public enum CultAlertVisualLayers : byte
|
||||
{
|
||||
Counter,
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
using Content.Shared.StatusIcon;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Shared._DV.CosmicCult.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Added to mind role entities to tag that they are the cosmic cult leader.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent, Access(typeof(SharedCosmicCultSystem))]
|
||||
public sealed partial class CosmicCultLeadComponent : Component
|
||||
{
|
||||
public override bool SessionSpecific => true;
|
||||
|
||||
/// <summary>
|
||||
/// The status icon corresponding to the lead cultist.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public ProtoId<FactionIconPrototype> StatusIcon = "CosmicCultLeadIcon";
|
||||
|
||||
/// <summary>
|
||||
/// How long the stun will last after the user is converted.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public TimeSpan StunTime = TimeSpan.FromSeconds(3);
|
||||
|
||||
[DataField]
|
||||
public EntProtoId MonumentPrototype = "MonumentCosmicCultSpawnIn";
|
||||
|
||||
[DataField]
|
||||
public EntProtoId CosmicMonumentPlaceAction = "ActionCosmicPlaceMonument";
|
||||
|
||||
[DataField]
|
||||
public EntityUid? CosmicMonumentPlaceActionEntity;
|
||||
|
||||
[DataField]
|
||||
public EntProtoId CosmicMonumentMoveAction = "ActionCosmicMoveMonument";
|
||||
|
||||
[DataField]
|
||||
public EntityUid? CosmicMonumentMoveActionEntity;
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
using Content.Shared.Damage;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
|
||||
|
||||
namespace Content.Shared._DV.CosmicCult.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Makes the target take damage over time.
|
||||
/// Meant to be used in conjunction with statusEffectSystem.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
[AutoGenerateComponentPause]
|
||||
public sealed partial class CosmicEntropyDebuffComponent : Component
|
||||
{
|
||||
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
|
||||
[AutoPausedField]
|
||||
public TimeSpan CheckTimer = default!;
|
||||
|
||||
[DataField]
|
||||
public TimeSpan CheckWait = TimeSpan.FromSeconds(1);
|
||||
|
||||
/// <summary>
|
||||
/// The chance to recieve a message popup while under the effects of Entropic Degen.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float PopupChance = 0.05f;
|
||||
|
||||
/// <summary>
|
||||
/// The debuff applied while the component is present.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public DamageSpecifier Degen = new()
|
||||
{
|
||||
DamageDict = new()
|
||||
{
|
||||
{ "Cold", 0.25},
|
||||
{ "Asphyxiation", 1.25},
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
namespace Content.Shared._DV.CosmicCult.Components;
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed partial class CosmicEntropyMoteComponent : Component
|
||||
{
|
||||
[DataField] public int Entropy = 1;
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared._DV.CosmicCult.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Component for Cosmic Cult equipment items.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
public sealed partial class CosmicEquipmentComponent : Component;
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
using Content.Shared.Damage;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Shared._DV.CosmicCult.Components;
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed partial class CosmicGlyphAstralProjectionComponent : Component
|
||||
{
|
||||
[DataField]
|
||||
public EntProtoId SpawnProjection = "MobCosmicAstralProjection";
|
||||
|
||||
/// <summary>
|
||||
/// The duration of the astral projection
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public TimeSpan AstralDuration = TimeSpan.FromSeconds(12);
|
||||
|
||||
[DataField]
|
||||
public DamageSpecifier ProjectionDamage = new()
|
||||
{
|
||||
DamageDict = new() {
|
||||
{ "Asphyxiation", 40 }
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
using Content.Shared.Damage;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Shared._DV.CosmicCult.Components;
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed partial class CosmicGlyphComponent : Component
|
||||
{
|
||||
[DataField] public int RequiredCultists = 1;
|
||||
[DataField] public float ActivationRange = 1.55f;
|
||||
|
||||
/// <summary>
|
||||
/// Damage dealt on glyph activation.
|
||||
/// </summary>
|
||||
[DataField] public DamageSpecifier ActivationDamage = new();
|
||||
[DataField] public bool CanBeErased = true;
|
||||
[DataField] public EntProtoId GylphVFX = "CosmicGenericVFX";
|
||||
[DataField] public SoundSpecifier GylphSFX = new SoundPathSpecifier("/Audio/_DV/CosmicCult/glyph_trigger.ogg");
|
||||
}
|
||||
|
||||
public sealed class TryActivateGlyphEvent(EntityUid user, HashSet<Entity<CosmicCultComponent>> cultists) : CancellableEntityEventArgs
|
||||
{
|
||||
public EntityUid User = user;
|
||||
public HashSet<Entity<CosmicCultComponent>> Cultists = cultists;
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
using Content.Shared.Damage;
|
||||
|
||||
namespace Content.Shared._DV.CosmicCult.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Indicates a glyph entity as performing conversion effects
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed partial class CosmicGlyphConversionComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// The search range for finding conversion targets.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float ConversionRange = 0.5f;
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not we ignore mindshields or chaplain status.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public bool NegateProtection;
|
||||
|
||||
/// <summary>
|
||||
/// Healing applied on conversion.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public DamageSpecifier ConversionHeal = new()
|
||||
{
|
||||
DamageDict = new()
|
||||
{
|
||||
{ "Blunt", 50},
|
||||
{ "Slash", 50},
|
||||
{ "Piercing", 50},
|
||||
{ "Heat", 50},
|
||||
{ "Shock", 50},
|
||||
{ "Cold", 50},
|
||||
{ "Poison", 50},
|
||||
{ "Radiation", 50},
|
||||
{ "Asphyxiation", 50}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
using Content.Shared.Whitelist;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Shared._DV.CosmicCult.Components;
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed partial class CosmicGlyphTransmuteComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// The search range for finding transmutation targets.
|
||||
/// </summary>
|
||||
[DataField] public float TransmuteRange = 0.5f;
|
||||
|
||||
/// <summary>
|
||||
/// A pool of entities that we pick from when transmuting.
|
||||
/// </summary>
|
||||
[DataField(required: true)]
|
||||
public HashSet<EntProtoId> Transmutations = [];
|
||||
|
||||
/// <summary>
|
||||
/// Permissible entities for the transmutation
|
||||
/// </summary>
|
||||
[DataField(required: true)]
|
||||
public EntityWhitelist Whitelist = new();
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Shared._DV.CosmicCult.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Component for displaying Vacuous Imposition's visuals on a player.
|
||||
/// </summary>
|
||||
[NetworkedComponent, RegisterComponent]
|
||||
[AutoGenerateComponentPause]
|
||||
public sealed partial class CosmicImposingComponent : Component
|
||||
{
|
||||
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), AutoPausedField]
|
||||
public TimeSpan Expiry;
|
||||
|
||||
[DataField]
|
||||
public SpriteSpecifier Sprite = new SpriteSpecifier.Rsi(new("/Textures/_DV/CosmicCult/Effects/ability_imposition_overlay.rsi"), "vfx");
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public enum CosmicImposingKey
|
||||
{
|
||||
Key
|
||||
}
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
using Content.Shared.Atmos;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared._DV.CosmicCult.Components;
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed partial class CosmicSpireComponent : Component
|
||||
{
|
||||
|
||||
[DataField]
|
||||
public bool Enabled;
|
||||
|
||||
[DataField]
|
||||
public float DrainRate = 550;
|
||||
|
||||
[DataField]
|
||||
public float DrainThreshHold = 2500;
|
||||
|
||||
[DataField]
|
||||
public HashSet<Gas> DrainGases =
|
||||
[
|
||||
Gas.Oxygen,
|
||||
Gas.Nitrogen,
|
||||
Gas.CarbonDioxide,
|
||||
Gas.WaterVapor,
|
||||
Gas.Ammonia,
|
||||
Gas.NitrousOxide,
|
||||
];
|
||||
|
||||
[DataField]
|
||||
public GasMixture Storage = new();
|
||||
|
||||
[DataField]
|
||||
public EntProtoId EntropyMote = "MaterialCosmicCultEntropy1";
|
||||
|
||||
[DataField]
|
||||
public EntProtoId SpawnVFX = "CosmicGenericVFX";
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public enum SpireVisuals : byte
|
||||
{
|
||||
Status,
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public enum SpireStatus : byte
|
||||
{
|
||||
Off,
|
||||
On,
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Shared._DV.CosmicCult.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Component for revealing cosmic cultists to the crew.
|
||||
/// </summary>
|
||||
[NetworkedComponent, RegisterComponent]
|
||||
public sealed partial class CosmicStarMarkComponent : Component
|
||||
{
|
||||
[DataField]
|
||||
public SpriteSpecifier Sprite = new SpriteSpecifier.Rsi(new("/Textures/_DV/CosmicCult/Effects/cultrevealed.rsi"), "vfx");
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public enum CosmicRevealedKey
|
||||
{
|
||||
Key
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
using System.Numerics;
|
||||
|
||||
namespace Content.Shared._DV.CosmicCult.Components;
|
||||
|
||||
/// <summary>
|
||||
/// This is used to apply an offset to the star mark that shows up at cult tier 3.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed partial class CosmicStarMarkOffsetComponent : Component
|
||||
{
|
||||
[DataField]
|
||||
public Vector2 Offset = Vector2.Zero;
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
using Content.Shared.StatusIcon;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Shared._DV.CosmicCult.Components.Examine;
|
||||
|
||||
/// <summary>
|
||||
/// Marker component for targets under the effect of Shunt Subjectivity or Astral Projection.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
public sealed partial class CosmicBlankComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// The status icon corresponding to the effect.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public ProtoId<SsdIconPrototype> StatusIcon = "CosmicSSDIcon";
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared._DV.CosmicCult.Components.Examine;
|
||||
|
||||
/// <summary>
|
||||
/// Marker component for The Unknown. We also use this to detect its spawn through CultRule!
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
public sealed partial class CosmicGodComponent : Component;
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared._DV.CosmicCult.Components.Examine;
|
||||
|
||||
/// <summary>
|
||||
/// Marker component for targets under the effect of Abberant Lapse.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
public sealed partial class CosmicLapseComponent : Component;
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
namespace Content.Shared._DV.CosmicCult.Components;
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed partial class InfluenceStrideComponent : Component;
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
namespace Content.Shared._DV.CosmicCult.Components;
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed partial class InfluenceVitalityComponent : Component;
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared._DV.CosmicCult.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Component for handling The Monument's collision.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
[AutoGenerateComponentState]
|
||||
public sealed partial class MonumentCollisionComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Determines whether The Monument is tangible to non-cultists.
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField] public bool HasCollision;
|
||||
}
|
||||
|
|
@ -0,0 +1,147 @@
|
|||
using Content.Shared._DV.CosmicCult.Prototypes;
|
||||
using Content.Shared.Damage;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
|
||||
|
||||
namespace Content.Shared._DV.CosmicCult.Components;
|
||||
|
||||
[RegisterComponent, NetworkedComponent, Access(typeof(SharedMonumentSystem))]
|
||||
[AutoGenerateComponentState, AutoGenerateComponentPause]
|
||||
public sealed partial class MonumentComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// The sound effect played when entropy is infused into The Monument.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public SoundSpecifier InfusionSFX = new SoundPathSpecifier("/Audio/_DV/CosmicCult/insert_entropy.ogg");
|
||||
|
||||
/// <summary>
|
||||
/// the list of glyphs that this monument is allowed to scribe
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public HashSet<ProtoId<GlyphPrototype>> UnlockedGlyphs = [];
|
||||
|
||||
/// <summary>
|
||||
/// the glyph that will be scribed when the button is pressed
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public ProtoId<GlyphPrototype> SelectedGlyph;
|
||||
|
||||
/// <summary>
|
||||
/// the total amount of entropy that has been inserted into the monument
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public int TotalEntropy;
|
||||
|
||||
/// <summary>
|
||||
/// how much progress (entropy and converted crew) the cult has made
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public int CurrentProgress;
|
||||
|
||||
/// <summary>
|
||||
/// how much progress the cult need to make to tier up
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public int TargetProgress;
|
||||
|
||||
/// <summary>
|
||||
/// offset used to make the progress bar reset to 0 every time
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public int ProgressOffset;
|
||||
|
||||
/// <summary>
|
||||
/// A bool we use to set whether The Monument's UI is available or not.
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public bool Enabled = true;
|
||||
|
||||
/// <summary>
|
||||
/// how long the monument takes to transform on a tier up
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public TimeSpan TransformTime = TimeSpan.FromSeconds(2.8);
|
||||
|
||||
/// <summary>
|
||||
/// the entity for the currently scribed glyph
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public EntityUid? CurrentGlyph;
|
||||
|
||||
/// <summary>
|
||||
/// the timer used for ticking healing from vacuous vitality
|
||||
/// </summary>
|
||||
[AutoPausedField, DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
|
||||
public TimeSpan CheckTimer = default!;
|
||||
|
||||
/// <summary>
|
||||
/// the amount of time between the above timer's ticks
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public TimeSpan CheckWait = TimeSpan.FromSeconds(5);
|
||||
|
||||
/// <summary>
|
||||
/// Passive healing factor for cultists w/ the ability near the monument
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public DamageSpecifier MonumentHealing = new()
|
||||
{
|
||||
DamageDict = new()
|
||||
{
|
||||
{ "Blunt", 2},
|
||||
{ "Slash", 2 },
|
||||
{ "Piercing", 2 },
|
||||
{ "Heat", 2},
|
||||
{ "Shock", 2},
|
||||
{ "Cold", 2},
|
||||
{ "Poison", 2},
|
||||
{ "Radiation", 2},
|
||||
{ "Asphyxiation", 2 }
|
||||
}
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// wether or not there's a stage change queued
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public bool CanTierUp = true;
|
||||
|
||||
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), AutoPausedField]
|
||||
public TimeSpan? PhaseOutTimer;
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class InfluenceSelectedMessage(ProtoId<InfluencePrototype> influenceProtoId) : BoundUserInterfaceMessage
|
||||
{
|
||||
public ProtoId<InfluencePrototype> InfluenceProtoId = influenceProtoId;
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class GlyphSelectedMessage(ProtoId<GlyphPrototype> glyphProtoId) : BoundUserInterfaceMessage
|
||||
{
|
||||
public ProtoId<GlyphPrototype> GlyphProtoId = glyphProtoId;
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class GlyphRemovedMessage : BoundUserInterfaceMessage;
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public enum MonumentVisuals : byte
|
||||
{
|
||||
Monument,
|
||||
Transforming,
|
||||
FinaleReached,
|
||||
Tier3,
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public enum MonumentVisualLayers : byte
|
||||
{
|
||||
MonumentLayer,
|
||||
TransformLayer,
|
||||
FinaleLayer,
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
|
||||
|
||||
namespace Content.Shared._DV.CosmicCult.Components;
|
||||
|
||||
/// <summary>
|
||||
/// This is used to mark an entity as the end point for the "relocate monument" ability. ideally there should only ever be one of these
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
[AutoGenerateComponentPause]
|
||||
public sealed partial class MonumentMoveDestinationComponent : Component
|
||||
{
|
||||
public EntityUid? Monument;
|
||||
|
||||
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), AutoPausedField]
|
||||
public TimeSpan? PhaseInTimer;
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
namespace Content.Shared._DV.CosmicCult.Components;
|
||||
|
||||
/// <summary>
|
||||
/// a marker component used as an extra flag for an event to toggle the monument preview.
|
||||
/// could probably have a better name but idrk.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed partial class MonumentPlacementPreviewComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// the tier of the monument that the overlay added by the event with this comp should render
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public int Tier = 1;
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
|
||||
|
||||
namespace Content.Shared._DV.CosmicCult.Components;
|
||||
|
||||
[RegisterComponent, NetworkedComponent, Access(typeof(SharedMonumentSystem))]
|
||||
[AutoGenerateComponentState, AutoGenerateComponentPause]
|
||||
public sealed partial class MonumentTransformingComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// The time when insertion ends.
|
||||
/// </summary>
|
||||
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
|
||||
[AutoPausedField, AutoNetworkedField]
|
||||
public TimeSpan EndTime;
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Shared._DV.CosmicCult.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Component for revealing cosmic cultists to the crew.
|
||||
/// </summary>
|
||||
[NetworkedComponent, RegisterComponent]
|
||||
public sealed partial class RogueAscendedAuraComponent : Component
|
||||
{
|
||||
[DataField]
|
||||
public SpriteSpecifier Sprite = new SpriteSpecifier.Rsi(new("/Textures/_DV/CosmicCult/Effects/ascendantaura.rsi"), "vfx");
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public enum AscendedAuraKey
|
||||
{
|
||||
Key
|
||||
}
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
using Content.Shared.Damage;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Shared._DV.CosmicCult.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Component to designate a mob as a rogue astral ascendant.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
public sealed partial class RogueAscendedComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// The duration of our slumber DoAfter.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public TimeSpan RogueSlumberDoAfterTime = TimeSpan.FromSeconds(1);
|
||||
|
||||
/// <summary>
|
||||
/// The duration of our infection DoAfter.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public TimeSpan RogueInfectionDoAfterTime = TimeSpan.FromSeconds(8);
|
||||
|
||||
/// <summary>
|
||||
/// The duration inflicted by Slumber Shell
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public TimeSpan RogueSlumberTime = TimeSpan.FromSeconds(25);
|
||||
|
||||
[DataField]
|
||||
public SoundSpecifier InfectionSfx = new SoundPathSpecifier("/Audio/_DV/CosmicCult/ability_nova_impact.ogg");
|
||||
|
||||
[DataField]
|
||||
public SoundSpecifier ShatterSfx = new SoundPathSpecifier("/Audio/_DV/CosmicCult/ascendant_shatter.ogg");
|
||||
|
||||
[DataField]
|
||||
public SoundSpecifier MobSound = new SoundPathSpecifier("/Audio/_DV/CosmicCult/ascendant_noise.ogg");
|
||||
|
||||
[DataField]
|
||||
public EntProtoId Vfx = "CosmicGenericVFX";
|
||||
|
||||
[DataField]
|
||||
public TimeSpan StunTime = TimeSpan.FromSeconds(7);
|
||||
|
||||
[DataField]
|
||||
public DamageSpecifier InfectionHeal = new()
|
||||
{
|
||||
DamageDict = new()
|
||||
{
|
||||
{ "Blunt", 25},
|
||||
{ "Slash", 25},
|
||||
{ "Piercing", 25},
|
||||
{ "Heat", 25},
|
||||
{ "Shock", 25},
|
||||
{ "Cold", 25},
|
||||
{ "Poison", 25},
|
||||
{ "Radiation", 25},
|
||||
{ "Asphyxiation", 25}
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue