mirror of
https://github.com/DeltaV-Station/Delta-v.git
synced 2026-09-29 23:57:29 +00:00
Compare commits
17
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4702b34eb5 | ||
|
|
4d28fabc9d | ||
|
|
474a6d358b | ||
|
|
5e6b27a860 | ||
|
|
008b84902a | ||
|
|
52d887faff | ||
|
|
097d8282c8 | ||
|
|
8919a88ea0 | ||
|
|
2dcf965e41 | ||
|
|
c776571860 | ||
|
|
78dd74e5c1 | ||
|
|
b3d15cdfd4 | ||
|
|
92883eb3ff | ||
|
|
53773326df | ||
|
|
2281cdbf8d | ||
|
|
ff20b12532 | ||
|
|
8e62f86239 |
@@ -312,3 +312,6 @@ Resources/MapImages
|
||||
|
||||
# Changelog package lock
|
||||
package-lock.json
|
||||
|
||||
# Discord Bot Config
|
||||
link-config.json
|
||||
@@ -269,7 +269,7 @@ Sentencing modifiers are to be applied by the sentencing officer, judge, or arbi
|
||||
| style="border: 1px solid black;" | [[File:SL_BreakingAndEntering.png]]
|
||||
! style="border: 1px solid black;" | {{anchor|Breaking and Entering}}Breaking and Entering
|
||||
| style="border: 1px solid black;" | 5 minutes
|
||||
| style="border: 1px solid black;" | To break and enter into an area where one is not authorised nor invited.
|
||||
| style="border: 1px solid black;" | To break and enter into a high security area where one is not authorised nor invited, with intent to commit a crime within.
|
||||
|-
|
||||
| style="border: 1px solid black;" | 203
|
||||
| style="border: 1px solid black;" | [[File:SL_Rioting.png]]
|
||||
|
||||
@@ -237,12 +237,11 @@ internal sealed class AdminNameOverlay : Overlay
|
||||
}
|
||||
|
||||
// DeltaV - SSD Time START
|
||||
if (_entityManager.TryGetComponent<SSDIndicatorComponent>(entity, out var ssdIndicator)
|
||||
&& ssdIndicator.SsdSince is {} ssdSince)
|
||||
if (_entityManager.TryGetComponent<SSDIndicatorComponent>(entity, out var ssdIndicator) && ssdIndicator.IsSSD)
|
||||
{
|
||||
color = Color.MediumPurple;
|
||||
color.A = alpha;
|
||||
var ssdText = Loc.GetString("admin-overlay-ssd-time", ("time", (_timing.CurTime - ssdSince).ToString("%hh':'mm':'ss")));
|
||||
var ssdText = Loc.GetString("admin-overlay-ssd-time", ("time", (_timing.CurTime - ssdIndicator.SsdSince).ToString("%hh':'mm':'ss")));
|
||||
args.ScreenHandle.DrawString(_font, screenCoordinates + currentOffset, ssdText, uiScale, color);
|
||||
currentOffset += lineoffset;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Content.Client.Changelog;
|
||||
using Content.Client._DV.AccountLinking; // DeltaV
|
||||
using Content.Client.UserInterface.Systems.EscapeMenu;
|
||||
using Content.Client.UserInterface.Systems.Guidebook;
|
||||
using Content.Shared.CCVar;
|
||||
@@ -48,6 +49,18 @@ namespace Content.Client.Info
|
||||
changelogButton.OnPressed += args => UserInterfaceManager.GetUIController<ChangelogUIController>().ToggleWindow();
|
||||
buttons.AddChild(changelogButton);
|
||||
|
||||
// BEGIN DeltaV - Discord / Patreon Account Linking
|
||||
var linkAccount = UserInterfaceManager.GetUIController<LinkAccountUIController>();
|
||||
var linkAccountButton = new Button
|
||||
{
|
||||
Text = Loc.GetString("ui-link-discord-account"),
|
||||
};
|
||||
linkAccountButton.OnPressed += _ => linkAccount.ToggleWindow();
|
||||
buttons.AddChild(linkAccountButton);
|
||||
|
||||
AddInfoButton("ui-patreon", CCVars.InfoLinksPatreon);
|
||||
// END DeltaV
|
||||
|
||||
void AddInfoButton(string loc, CVarDef<string> cVar)
|
||||
{
|
||||
var button = new Button { Text = Loc.GetString(loc) };
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using Content.Client._DV.AccountLinking; // DeltaV
|
||||
using Content.Client.Administration.Managers;
|
||||
using Content.Client.Changelog;
|
||||
using Content.Client.Chat.Managers;
|
||||
@@ -62,6 +63,7 @@ namespace Content.Client.IoC
|
||||
collection.Register<SharedPlayerRateLimitManager, PlayerRateLimitManager>();
|
||||
collection.Register<TitleWindowManager>();
|
||||
collection.Register<ClientsidePlaytimeTrackingManager>();
|
||||
collection.Register<LinkAccountManager>(); // DeltaV - Discord Linking
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using Content.Client._DV.AccountLinking; // DeltaV
|
||||
using Content.Client.Audio;
|
||||
using Content.Client.GameTicking.Managers;
|
||||
using Content.Client.LateJoin;
|
||||
@@ -29,6 +30,7 @@ namespace Content.Client.Lobby
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
[Dependency] private readonly IVoteManager _voteManager = default!;
|
||||
[Dependency] private readonly ClientsidePlaytimeTrackingManager _playtimeTracking = default!;
|
||||
[Dependency] private readonly LinkAccountManager _linkAccount = default!; // DeltaV - Discord Linking
|
||||
|
||||
private ClientGameTicker _gameTicker = default!;
|
||||
private ContentAudioSystem _contentAudioSystem = default!;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Linq;
|
||||
using Content.Client._DV.AccountLinking; // DeltaV
|
||||
using Content.Client.Guidebook;
|
||||
using Content.Client.Humanoid;
|
||||
using Content.Client.Inventory;
|
||||
@@ -38,6 +39,7 @@ public sealed class LobbyUIController : UIController, IOnStateEntered<LobbyState
|
||||
[Dependency] private readonly IStateManager _stateManager = default!;
|
||||
[Dependency] private readonly JobRequirementsManager _requirements = default!;
|
||||
[Dependency] private readonly MarkingManager _markings = default!;
|
||||
[Dependency] private readonly LinkAccountManager _linkAccount = default!; // DeltaV - Discord Account Linking
|
||||
[UISystemDependency] private readonly HumanoidAppearanceSystem _humanoid = default!;
|
||||
[UISystemDependency] private readonly ClientInventorySystem _inventory = default!;
|
||||
[UISystemDependency] private readonly StationSpawningSystem _spawn = default!;
|
||||
@@ -75,6 +77,8 @@ public sealed class LobbyUIController : UIController, IOnStateEntered<LobbyState
|
||||
_configurationManager.OnValueChanged(CCVars.GameRoleLoadoutTimers, _ => RefreshProfileEditor());
|
||||
|
||||
_configurationManager.OnValueChanged(CCVars.GameRoleWhitelist, _ => RefreshProfileEditor());
|
||||
|
||||
_linkAccount.Updated += RefreshProfileEditor; // DeltaV - Discord Account Linking
|
||||
}
|
||||
|
||||
private LobbyCharacterPreviewPanel? GetLobbyPreview()
|
||||
|
||||
@@ -20,7 +20,6 @@
|
||||
<CheckBox Name="ShowLoocAboveHeadCheckBox" Text="{Loc 'ui-options-show-looc-on-head'}" />
|
||||
<CheckBox Name="FancySpeechBubblesCheckBox" Text="{Loc 'ui-options-fancy-speech'}" />
|
||||
<CheckBox Name="FancyNameBackgroundsCheckBox" Text="{Loc 'ui-options-fancy-name-background'}" />
|
||||
<CheckBox Name="ChatFollowButton" Text="{Loc 'ui-options-chat-follow-button'}" />
|
||||
<Label Text="{Loc 'ui-options-general-cursor'}"
|
||||
StyleClasses="LabelKeyText"/>
|
||||
<CheckBox Name="ShowHeldItemCheckBox" Text="{Loc 'ui-options-show-held-item'}" />
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Linq;
|
||||
using Content.Client._DV.AccountLinking; // DeltaV
|
||||
using Content.Client.UserInterface.Screens;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared._EE.CCVars; // EE - chat stack
|
||||
@@ -17,6 +18,7 @@ public sealed partial class MiscTab : Control
|
||||
{
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly LinkAccountManager _linkAccount = default!; // DeltaV - Discord Account Linking
|
||||
|
||||
public MiscTab()
|
||||
{
|
||||
@@ -48,7 +50,7 @@ public sealed partial class MiscTab : Control
|
||||
|
||||
// Channel can be null in replays so.
|
||||
// ReSharper disable once ConditionalAccessQualifierIsNonNullableAccordingToAPIContract
|
||||
ShowOocPatronColor.Visible = _playerManager.LocalSession?.Channel?.UserData.PatronTier is { };
|
||||
ShowOocPatronColor.Visible = _linkAccount.Tier != null; // DeltaV - Discord Account Linking
|
||||
|
||||
Control.AddOptionDropDown(CVars.InterfaceTheme, DropDownHudTheme, themeEntries);
|
||||
Control.AddOptionDropDown(CCVars.UILayout, DropDownHudLayout, layoutEntries);
|
||||
@@ -63,7 +65,6 @@ public sealed partial class MiscTab : Control
|
||||
Control.AddOptionCheckBox(CCVars.ChatEnableFancyBubbles, FancySpeechBubblesCheckBox);
|
||||
Control.AddOptionCheckBox(CCVars.ChatFancyNameBackground, FancyNameBackgroundsCheckBox);
|
||||
Control.AddOptionCheckBox(CCVars.StaticStorageUI, StaticStorageUI);
|
||||
Control.AddOptionCheckBox(CCVars.InterfaceChatFollowButton, ChatFollowButton);
|
||||
|
||||
Control.Initialize();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
using Content.Shared._DV.AccountLinking;
|
||||
using Robust.Shared.Network;
|
||||
|
||||
namespace Content.Client._DV.AccountLinking;
|
||||
|
||||
public sealed class LinkAccountManager : IPostInjectInit
|
||||
{
|
||||
[Dependency] private readonly INetManager _net = default!;
|
||||
|
||||
private readonly List<SharedPatron> _allPatrons = [];
|
||||
|
||||
public SharedPatronTier? Tier { get; private set; }
|
||||
public bool Linked { get; private set; }
|
||||
|
||||
public event Action<Guid>? CodeReceived;
|
||||
public event Action? Updated;
|
||||
|
||||
private void OnCode(LinkAccountCodeMsg message)
|
||||
{
|
||||
CodeReceived?.Invoke(message.Code);
|
||||
}
|
||||
|
||||
private void OnStatus(LinkAccountStatusMsg ev)
|
||||
{
|
||||
Tier = ev.Patron?.Tier;
|
||||
Linked = ev.Patron?.Linked ?? false;
|
||||
Updated?.Invoke();
|
||||
}
|
||||
|
||||
private void OnPatronList(PatronListMsg ev)
|
||||
{
|
||||
_allPatrons.Clear();
|
||||
_allPatrons.AddRange(ev.Patrons);
|
||||
}
|
||||
|
||||
public IReadOnlyList<SharedPatron> GetPatrons()
|
||||
{
|
||||
return _allPatrons;
|
||||
}
|
||||
|
||||
void IPostInjectInit.PostInject()
|
||||
{
|
||||
_net.RegisterNetMessage<LinkAccountCodeMsg>(OnCode);
|
||||
_net.RegisterNetMessage<LinkAccountRequestMsg>();
|
||||
_net.RegisterNetMessage<LinkAccountStatusMsg>(OnStatus);
|
||||
_net.RegisterNetMessage<PatronListMsg>(OnPatronList);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
using Content.Shared._DV.AccountLinking;
|
||||
|
||||
namespace Content.Client._DV.AccountLinking;
|
||||
|
||||
public sealed class LinkAccountSystem : EntitySystem;
|
||||
@@ -0,0 +1,107 @@
|
||||
using Content.Client.Lobby.UI;
|
||||
using Content.Client.Message;
|
||||
using Content.Shared._DV.CCVars;
|
||||
using Content.Shared._DV.AccountLinking;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controllers;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Client._DV.AccountLinking;
|
||||
|
||||
public sealed class LinkAccountUIController : UIController
|
||||
{
|
||||
[Dependency] private readonly IClipboardManager _clipboard = default!;
|
||||
[Dependency] private readonly IConfigurationManager _config = default!;
|
||||
[Dependency] private readonly LinkAccountManager _linkAccount = default!;
|
||||
[Dependency] private readonly INetManager _net = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly IUriOpener _uriOpener = default!;
|
||||
|
||||
private LinkAccountWindow? _window;
|
||||
private TimeSpan _disableUntil;
|
||||
|
||||
private Guid _code;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
_linkAccount.CodeReceived += OnCode;
|
||||
_linkAccount.Updated += OnUpdated;
|
||||
}
|
||||
|
||||
private void OnCode(Guid code)
|
||||
{
|
||||
_code = code;
|
||||
|
||||
if (_window == null)
|
||||
return;
|
||||
|
||||
_window.CopyButton.Disabled = false;
|
||||
}
|
||||
|
||||
private void OnUpdated()
|
||||
{
|
||||
if (UIManager.ActiveScreen is not LobbyGui gui)
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
public void ToggleWindow()
|
||||
{
|
||||
if (_window == null)
|
||||
{
|
||||
_window = new LinkAccountWindow();
|
||||
_window.OnClose += () => _window = null;
|
||||
_window.Label.SetMarkupPermissive($"{Loc.GetString("ui-link-discord-account-text")}");
|
||||
if (_linkAccount.Linked)
|
||||
_window.Label.SetMarkupPermissive($"{Loc.GetString("ui-link-discord-account-already-linked")}\n\n{Loc.GetString("ui-link-discord-account-text")}");
|
||||
|
||||
_window.CopyButton.OnPressed += _ =>
|
||||
{
|
||||
_clipboard.SetText(_code.ToString());
|
||||
_window.CopyButton.Text = Loc.GetString("ui-link-discord-account-copied");
|
||||
_window.CopyButton.Disabled = true;
|
||||
_disableUntil = _timing.RealTime.Add(TimeSpan.FromSeconds(3));
|
||||
};
|
||||
|
||||
var messageLink = _config.GetCVar(DCCVars.DiscordAccountLinkingMessageLink);
|
||||
if (string.IsNullOrEmpty(messageLink))
|
||||
{
|
||||
_window.LinkButton.Visible = false;
|
||||
_window.CopyButton.RemoveStyleClass("OpenRight");
|
||||
}
|
||||
else
|
||||
{
|
||||
_window.LinkButton.Visible = true;
|
||||
_window.LinkButton.OnPressed += _ => _uriOpener.OpenUri(messageLink);
|
||||
_window.CopyButton.AddStyleClass("OpenRight");
|
||||
}
|
||||
|
||||
_window.OpenCentered();
|
||||
|
||||
if (_code == default)
|
||||
_window.CopyButton.Disabled = true;
|
||||
|
||||
_net.ClientSendMessage(new LinkAccountRequestMsg());
|
||||
return;
|
||||
}
|
||||
|
||||
_window.Close();
|
||||
_window = null;
|
||||
}
|
||||
|
||||
public override void FrameUpdate(FrameEventArgs args)
|
||||
{
|
||||
if (_window == null)
|
||||
return;
|
||||
|
||||
var time = _timing.RealTime;
|
||||
if (_disableUntil != default && time > _disableUntil)
|
||||
{
|
||||
_disableUntil = default;
|
||||
_window.CopyButton.Text = Loc.GetString("ui-link-discord-account-copy");
|
||||
_window.CopyButton.Disabled = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<linkAccount:LinkAccountWindow
|
||||
xmlns="https://spacestation14.io"
|
||||
xmlns:linkAccount="clr-namespace:Content.Client._DV.AccountLinking"
|
||||
Title="{Loc 'ui-link-discord-account'}">
|
||||
<BoxContainer Orientation="Vertical">
|
||||
<RichTextLabel Name="Label" Access="Public" Margin="0 0 0 5" />
|
||||
<BoxContainer Orientation="Horizontal" HorizontalAlignment="Center"
|
||||
HorizontalExpand="True">
|
||||
<Button Name="CopyButton" Access="Public" StyleClasses="OpenRight"
|
||||
Text="{Loc 'ui-link-discord-account-copy'}" HorizontalExpand="True" />
|
||||
<Button Name="LinkButton" Access="Public" StyleClasses="OpenLeft"
|
||||
Text="{Loc 'ui-link-discord-account-open-channel'}"
|
||||
HorizontalExpand="True" />
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
</linkAccount:LinkAccountWindow>
|
||||
@@ -0,0 +1,15 @@
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
|
||||
namespace Content.Client._DV.AccountLinking;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class LinkAccountWindow : DefaultWindow
|
||||
{
|
||||
public LinkAccountWindow()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
using Content.Shared._DV.Psionics.Systems.PsionicPowers;
|
||||
|
||||
namespace Content.Client._DV.Psionics.Systems.PsionicPowers;
|
||||
|
||||
// This does nothing here. The code is all in the shared/server version.
|
||||
public sealed class FracturedFormPowerSystem : SharedFracturedFormPowerSystem;
|
||||
@@ -20,11 +20,7 @@ public sealed class StellarInteractionParticleSystem : EntitySystem
|
||||
|
||||
private const string AnimateKey = "particle-animation";
|
||||
|
||||
private static readonly Dictionary<StellarInteractionParticleType, EntProtoId> InteractionParticleIds = new ()
|
||||
{
|
||||
{ StellarInteractionParticleType.Use, "StellarInteractionParticleUse" },
|
||||
{ StellarInteractionParticleType.Pull, "StellarInteractionParticlePull" },
|
||||
};
|
||||
private static readonly EntProtoId InteractionParticleId = "StellarInteractionParticle";
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -42,11 +38,6 @@ public sealed class StellarInteractionParticleSystem : EntitySystem
|
||||
if (!Exists(performer) || !Exists(target))
|
||||
return;
|
||||
|
||||
if (ev.Type == StellarInteractionParticleType.Pull)
|
||||
{
|
||||
(performer, target) = (target, performer);
|
||||
}
|
||||
|
||||
var performerXform = Transform(performer);
|
||||
var targetXform = Transform(target);
|
||||
if (performerXform.MapID == MapId.Nullspace || targetXform.MapID == MapId.Nullspace)
|
||||
@@ -56,7 +47,7 @@ public sealed class StellarInteractionParticleSystem : EntitySystem
|
||||
return;
|
||||
|
||||
var performerTargetDelta = targetXform.LocalPosition - performerXform.LocalPosition;
|
||||
var particle = Spawn(InteractionParticleIds[ev.Type], performerXform.Coordinates);
|
||||
var particle = Spawn(InteractionParticleId, performerXform.Coordinates);
|
||||
|
||||
if (used is { } usedEntity && Exists(usedEntity) && TryComp<SpriteComponent>(usedEntity, out var usedSprite))
|
||||
{
|
||||
@@ -67,16 +58,10 @@ public sealed class StellarInteractionParticleSystem : EntitySystem
|
||||
}
|
||||
|
||||
var spriteColor = Comp<SpriteComponent>(particle).Color;
|
||||
var animation = ev.Type switch
|
||||
{
|
||||
StellarInteractionParticleType.Use => GetUseAnimation(performerTargetDelta, spriteColor),
|
||||
StellarInteractionParticleType.Pull => GetPullAnimation(performerTargetDelta, spriteColor),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(ev), $"Interaction particle event has unknown particle type {ev.Type}"),
|
||||
};
|
||||
_animation.Play(particle, animation, AnimateKey);
|
||||
_animation.Play(particle, GetAnimation(performerTargetDelta, spriteColor), AnimateKey);
|
||||
}
|
||||
|
||||
private Animation GetUseAnimation(Vector2 endOffset, Color color)
|
||||
private Animation GetAnimation(Vector2 endOffset, Color color)
|
||||
{
|
||||
var startRotation = _random.NextAngle(Angle.FromDegrees(-40), Angle.FromDegrees(40));
|
||||
var endRotation = Angle.Zero;
|
||||
@@ -141,44 +126,4 @@ public sealed class StellarInteractionParticleSystem : EntitySystem
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
private Animation GetPullAnimation(Vector2 endOffset, Color color)
|
||||
{
|
||||
var rotationLength = TimeSpan.FromMilliseconds(8f * (1000f / 12f));
|
||||
|
||||
var startOffset = new Vector2();
|
||||
var offsetLength = TimeSpan.FromMilliseconds(4f * (1000f / 12f));
|
||||
|
||||
var endColor = color.WithAlpha(0f);
|
||||
|
||||
return new Animation
|
||||
{
|
||||
Length = rotationLength,
|
||||
|
||||
AnimationTracks =
|
||||
{
|
||||
new AnimationTrackComponentProperty()
|
||||
{
|
||||
ComponentType = typeof(SpriteComponent),
|
||||
Property = nameof(SpriteComponent.Offset),
|
||||
KeyFrames =
|
||||
{
|
||||
new AnimationTrackProperty.KeyFrame(startOffset, 0f),
|
||||
new AnimationTrackProperty.KeyFrame(endOffset, (float)rotationLength.TotalSeconds, Easings.InOutCirc),
|
||||
},
|
||||
},
|
||||
new AnimationTrackComponentProperty()
|
||||
{
|
||||
ComponentType = typeof(SpriteComponent),
|
||||
Property = nameof(SpriteComponent.Color),
|
||||
KeyFrames =
|
||||
{
|
||||
new AnimationTrackProperty.KeyFrame(color, 0f),
|
||||
new AnimationTrackProperty.KeyFrame(color, (float)offsetLength.TotalSeconds),
|
||||
new AnimationTrackProperty.KeyFrame(endColor, (float)rotationLength.TotalSeconds, Easings.InOutCirc),
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,279 @@
|
||||
using System.Collections.Immutable;
|
||||
using System.Reflection;
|
||||
using Content.DiscordBot.Modules;
|
||||
using Content.Server.Database;
|
||||
using Discord;
|
||||
using Discord.Commands;
|
||||
using Discord.Interactions;
|
||||
using Discord.WebSocket;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Content.DiscordBot;
|
||||
|
||||
public sealed class CommandHandler(DiscordSocketClient client, InteractionService interaction, ServerDbContext db, Config config)
|
||||
{
|
||||
private ImmutableDictionary<ulong, PatronTier>? _patronTiers;
|
||||
private ImmutableArray<PatronTier> _tierPriority;
|
||||
private Task? _refreshPatronsTask;
|
||||
|
||||
public int Running = 1;
|
||||
|
||||
public async Task InstallCommandsAsync()
|
||||
{
|
||||
var patronTiers = await db.PatronTiers.ToListAsync();
|
||||
_tierPriority = [..patronTiers.OrderBy(t => t.Priority)];
|
||||
_patronTiers = patronTiers.ToImmutableDictionary(t => t.DiscordRole, t => t);
|
||||
|
||||
client.Ready += ReadyAsync;
|
||||
client.SlashCommandExecuted += HandleCommandAsync;
|
||||
client.ButtonExecuted += HandleButtonAsync;
|
||||
client.ModalSubmitted += HandleModalAsync;
|
||||
client.GuildMemberUpdated += HandleGuildMemberUpdated;
|
||||
|
||||
// If you get a log that says '[ERROR]' here, you probably don't have the corrent intents
|
||||
await client.LoginAsync(TokenType.Bot, config.Token);
|
||||
await client.StartAsync();
|
||||
|
||||
interaction.AddModalInfo<LinkAccountModal>();
|
||||
|
||||
_refreshPatronsTask = Task.Run(async () => await RefreshPatrons());
|
||||
}
|
||||
|
||||
private async Task ReadyAsync()
|
||||
{
|
||||
await interaction.AddModulesAsync(Assembly.GetEntryAssembly(), null);
|
||||
await interaction.RegisterCommandsToGuildAsync(config.Guild);
|
||||
}
|
||||
|
||||
private async Task HandleGuildMemberUpdated(Cacheable<SocketGuildUser, ulong> old, SocketGuildUser user)
|
||||
{
|
||||
if (_patronTiers == null)
|
||||
return;
|
||||
|
||||
var rolesChanged = !old.HasValue || old.Value.Roles.Count != user.Roles.Count || !old.Value.Roles.SequenceEqual(user.Roles);
|
||||
if (!rolesChanged)
|
||||
return;
|
||||
|
||||
var wasPatron = old.HasValue || old.Value.Roles.Any(r => _patronTiers.ContainsKey(r.Id));
|
||||
var isPatron = user.Roles.Any(r => _patronTiers.ContainsKey(r.Id));
|
||||
if (wasPatron && !isPatron)
|
||||
{
|
||||
var linked = await db.DiscordLinkedAccounts
|
||||
.Include(l => l.Player)
|
||||
.ThenInclude(p => p.Patron)
|
||||
.ThenInclude(p => p!.Tier)
|
||||
.Where(l => l.Player.Patron != null)
|
||||
.FirstOrDefaultAsync(p => p.DiscordId == user.Id);
|
||||
|
||||
if (linked?.Player.Patron is { } patron)
|
||||
{
|
||||
db.Patrons.Remove(patron);
|
||||
await db.SaveChangesAsync();
|
||||
await Logger.Info($"Removed patron {user.Username}:{linked.DiscordId}:{linked.Player.LastSeenUserName} with tier {patron.Tier.Name}");
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (isPatron)
|
||||
{
|
||||
foreach (var tier in _tierPriority)
|
||||
{
|
||||
if (user.Roles.Any(r => r.Id == tier.DiscordRole))
|
||||
{
|
||||
var linked = await db.DiscordLinkedAccounts
|
||||
.Include(l => l.Player)
|
||||
.ThenInclude(p => p.Patron)
|
||||
.FirstOrDefaultAsync(p => p.DiscordId == user.Id);
|
||||
|
||||
if (linked?.Player is not { } player)
|
||||
return;
|
||||
|
||||
player.Patron ??= db.Patrons.Add(new Patron { PlayerId = player.UserId }).Entity;
|
||||
player.Patron.TierId = tier.Id;
|
||||
await db.SaveChangesAsync();
|
||||
await Logger.Info($"Updated patron {user.Username}:{linked.DiscordId}:{linked.Player.LastSeenUserName} with tier {tier.Name}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task HandleCommandAsync(SocketSlashCommand message)
|
||||
{
|
||||
|
||||
if (message.GuildId != config.Guild)
|
||||
return;
|
||||
|
||||
// Create a WebSocket-based command context based on the message
|
||||
var context = new SocketInteractionContext(client, message);
|
||||
|
||||
// Execute the command with the command context we just
|
||||
// created, along with the service provider for precondition checks.
|
||||
await interaction.ExecuteCommandAsync(context, null);
|
||||
}
|
||||
|
||||
private async Task HandleButtonAsync(SocketMessageComponent component)
|
||||
{
|
||||
switch (component.Data.CustomId)
|
||||
{
|
||||
case "link-ss14-account":
|
||||
await component.RespondWithModalAsync<LinkAccountModal>("link-ss14-account");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task HandleModalAsync(SocketModal modal)
|
||||
{
|
||||
switch (modal.Data.CustomId)
|
||||
{
|
||||
case "link-ss14-account":
|
||||
if (modal.GuildId is not { } guildId)
|
||||
break;
|
||||
|
||||
var codeStr = modal.Data.Components.First(c => c.CustomId == "account_code").Value.Trim();
|
||||
if (string.IsNullOrWhiteSpace(codeStr))
|
||||
break;
|
||||
|
||||
await modal.DeferAsync(true);
|
||||
if (!Guid.TryParse(codeStr, out var code))
|
||||
{
|
||||
await modal.FollowupAsync($"{codeStr} isn't a valid code! Get one in-game from the lobby at the top left of the screen.", ephemeral: true);
|
||||
}
|
||||
|
||||
var author = modal.User;
|
||||
var authorId = author.Id;
|
||||
var discord = await db.DiscordAccounts
|
||||
.Include(d => d.LinkedAccount)
|
||||
.ThenInclude(l => l.Player)
|
||||
.ThenInclude(p => p.Patron)
|
||||
.FirstOrDefaultAsync(a => a.Id == authorId);
|
||||
var codes = await db.DiscordLinkingCodes
|
||||
.Include(l => l.Player)
|
||||
.ThenInclude(player => player.Patron)
|
||||
.FirstOrDefaultAsync(p => p.Code == code);
|
||||
|
||||
if (codes == null)
|
||||
{
|
||||
await modal.FollowupAsync($"No player found with code {codeStr}, join the game server and get another code before trying again, or ask for help in another channel.", ephemeral: true);
|
||||
break;
|
||||
}
|
||||
|
||||
if (codes.CreationTime < DateTime.UtcNow.Subtract(TimeSpan.FromDays(1)))
|
||||
{
|
||||
await modal.FollowupAsync($"Code {codeStr} were generated too long ago, join the game server and get another code before trying again.", ephemeral: true);
|
||||
}
|
||||
|
||||
if (discord?.LinkedAccount is { } linked)
|
||||
{
|
||||
if (linked.Player.Patron is { } patron)
|
||||
db.Patrons.Remove(patron);
|
||||
|
||||
linked.Player.Patron = null;
|
||||
db.DiscordLinkedAccounts.Remove(linked);
|
||||
}
|
||||
|
||||
discord ??= db.DiscordAccounts.Add(new DiscordAccount { Id = authorId }).Entity;
|
||||
discord.LinkedAccount = db.DiscordLinkedAccounts.Add(new DiscordLinkedAccount { Discord = discord }).Entity;
|
||||
discord.LinkedAccount.Player = codes.Player;
|
||||
|
||||
var roles = client.GetGuild(guildId).GetUser(authorId).Roles.Select(r => r.Id).ToArray();
|
||||
var tiers = await db.PatronTiers
|
||||
.Where(t => roles.Contains(t.DiscordRole))
|
||||
.ToListAsync();
|
||||
if (tiers.Count == 0)
|
||||
{
|
||||
discord.LinkedAccount.Player.Patron = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
tiers.Sort((a, b) => a.Priority.CompareTo(b.Priority));
|
||||
var tier = tiers[0];
|
||||
discord.LinkedAccount.Player.Patron = db.Patrons.Add(new Patron { Tier = tier }).Entity;
|
||||
discord.LinkedAccount.Player.Patron.Tier = tier;
|
||||
}
|
||||
|
||||
db.DiscordLinkedAccountLogs.Add(new DiscordLinkedAccountLogs
|
||||
{
|
||||
Discord = discord,
|
||||
Player = discord.LinkedAccount.Player,
|
||||
});
|
||||
|
||||
db.ChangeTracker.DetectChanges();
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
var msg = $"Linked SS14 account with name {codes.Player.LastSeenUserName}";
|
||||
if (codes.Player.Patron != null)
|
||||
msg += $" and tier {codes.Player.Patron.Tier.Name}";
|
||||
|
||||
await modal.FollowupAsync(msg, ephemeral: true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task RefreshPatrons()
|
||||
{
|
||||
while (Interlocked.CompareExchange(ref Running, 1, 1) == 1)
|
||||
{
|
||||
try
|
||||
{
|
||||
var patrons = await db.DiscordLinkedAccounts
|
||||
.Include(l => l.Player)
|
||||
.ThenInclude(p => p.Patron)
|
||||
.ThenInclude(p => p!.Tier)
|
||||
.ToListAsync();
|
||||
|
||||
foreach (var linked in patrons)
|
||||
{
|
||||
try
|
||||
{
|
||||
var user = await client.Rest.GetGuildUserAsync(config.Guild, linked.DiscordId);
|
||||
if (user == null)
|
||||
{
|
||||
if (linked.Player.Patron != null)
|
||||
{
|
||||
linked.Player.Patron = null;
|
||||
await Logger.Info($"Removed patron {linked.DiscordId}:{linked.Player.LastSeenUserName}");
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
var isPatron = false;
|
||||
foreach (var tier in _tierPriority)
|
||||
{
|
||||
if (user.RoleIds.Contains(tier.DiscordRole))
|
||||
{
|
||||
isPatron = true;
|
||||
if (linked.Player.Patron?.Tier.DiscordRole == tier.DiscordRole)
|
||||
break;
|
||||
|
||||
linked.Player.Patron ??= db.Patrons.Add(new Patron { PlayerId = linked.PlayerId })
|
||||
.Entity;
|
||||
linked.Player.Patron.TierId = tier.Id;
|
||||
await Logger.Info($"Updated patron {user.Username}:{linked.DiscordId}:{linked.Player.LastSeenUserName} with tier {tier.Name}");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!isPatron && linked.Player.Patron != null)
|
||||
{
|
||||
linked.Player.Patron = null;
|
||||
await Logger.Info($"Removed patron {user.Username}:{linked.DiscordId}:{linked.Player.LastSeenUserName}");
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
await Logger.Error($"Error updating patron with discord id {linked.DiscordId} and player id {linked.PlayerId}", e);
|
||||
}
|
||||
}
|
||||
|
||||
await db.SaveChangesAsync();
|
||||
await Task.Delay(60000); // 60 seconds
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
await Logger.Error("Error refreshing patrons", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
namespace Content.DiscordBot;
|
||||
|
||||
public sealed class Config
|
||||
{
|
||||
public string Token { get; set; } = string.Empty;
|
||||
|
||||
public string DatabaseString { get; set; } = string.Empty;
|
||||
|
||||
public string DatabaseContext { get; set; } = "postgres";
|
||||
|
||||
/// <summary>
|
||||
/// Discord "guild" ID (which is just the server ID). Default is DeltaV's server ID.
|
||||
/// </summary>
|
||||
public ulong Guild { get; set; } = 968983104247185448UL;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<OutputPath>..\bin\Content.DiscordBot\</OutputPath>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<WarningsAsErrors>nullable</WarningsAsErrors>
|
||||
<RootNamespace>Content.DiscordBot</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Discord.Net" Version="3.15.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Content.Server.Database\Content.Server.Database.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,21 @@
|
||||
using Discord;
|
||||
|
||||
namespace Content.DiscordBot;
|
||||
|
||||
public static class Logger
|
||||
{
|
||||
public static Task Log(LogMessage msg)
|
||||
{
|
||||
return Console.Out.WriteLineAsync($"[{msg.Severity.ToString().ToUpper()}] {msg.Message}");
|
||||
}
|
||||
|
||||
public static Task Info(string msg)
|
||||
{
|
||||
return Console.Out.WriteLineAsync($"[INFO] {msg}");
|
||||
}
|
||||
|
||||
public static Task Error(string msg, Exception e)
|
||||
{
|
||||
return Console.Out.WriteLineAsync($"[ERROR] {msg}\n{e}");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using Discord;
|
||||
using Discord.Interactions;
|
||||
|
||||
namespace Content.DiscordBot.Modules;
|
||||
|
||||
public sealed class AccountLinkingModule : InteractionModuleBase<SocketInteractionContext>
|
||||
{
|
||||
[SlashCommand("discordlink", "Creates the message with the linking button.")]
|
||||
[RequireTeam]
|
||||
public Task CreateAsync()
|
||||
{
|
||||
var component = new ComponentBuilder()
|
||||
.WithButton("Link your SS14 account here!", "link-ss14-account")
|
||||
.Build();
|
||||
|
||||
return RespondAsync(String.Empty, components: component);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
using Discord.Interactions;
|
||||
|
||||
namespace Content.DiscordBot.Modules;
|
||||
|
||||
public class LinkAccountModal : IModal
|
||||
{
|
||||
public string Title => "Link SS14 Account";
|
||||
|
||||
[InputLabel("SS14 Linking Code (Top Left in the Lobby)")]
|
||||
[RequiredInput]
|
||||
[ModalTextInput("account_code")]
|
||||
public string Code { get; set; } = string.Empty;
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
using System.Text.Json;
|
||||
using Content.DiscordBot;
|
||||
using Content.Server.Database;
|
||||
using Discord;
|
||||
using Discord.Interactions;
|
||||
using Discord.WebSocket;
|
||||
using Microsoft.Data.Sqlite;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
var client = new DiscordSocketClient(new DiscordSocketConfig { GatewayIntents = GatewayIntents.GuildMembers });
|
||||
client.Log += Logger.Log;
|
||||
|
||||
Console.Out.WriteLine($"[INIT] Working Directory: {Directory.GetCurrentDirectory()}");
|
||||
|
||||
var config = new Config();
|
||||
if (File.Exists("link-config.json"))
|
||||
{
|
||||
Console.Out.WriteLine("[INIT] link-config.json found. Deserializing.");
|
||||
config = await JsonSerializer.DeserializeAsync<Config>(File.OpenRead("link-config.json")) ?? new Config();
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
if (Environment.GetEnvironmentVariable("DISCORD_TOKEN") is { } envToken)
|
||||
config.Token = envToken;
|
||||
|
||||
if (Environment.GetEnvironmentVariable("DATABASE_STRING") is { } dbString)
|
||||
config.DatabaseString = dbString;
|
||||
|
||||
if (Environment.GetEnvironmentVariable("DATABASE_CONTEXT") is { } context)
|
||||
config.DatabaseContext = context;
|
||||
|
||||
if (Environment.GetEnvironmentVariable("GUILD") is { } guildId)
|
||||
{
|
||||
try
|
||||
{
|
||||
config.Guild = ulong.Parse("guildId");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.Out.WriteLine($"[INIT] Unable to parse guild ID. Defaulting to {config.Guild}. Exception: {ex.GetType()} {ex.Message}");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if (string.IsNullOrWhiteSpace(config.Token))
|
||||
throw new ArgumentException("[INIT] No Discord Bot token found.");
|
||||
|
||||
|
||||
if (string.IsNullOrWhiteSpace(config.DatabaseString))
|
||||
throw new ArgumentException("[INIT] No database connection string found.");
|
||||
|
||||
// Override for local sqlite development
|
||||
var usePostgres = true;
|
||||
if (!string.IsNullOrWhiteSpace(config.DatabaseContext) && config.DatabaseContext.Equals("sqlite"))
|
||||
usePostgres = false;
|
||||
|
||||
Console.Out.WriteLine($"[INIT] Guild ID: {config.Guild}");
|
||||
|
||||
ServerDbContext db;
|
||||
if (usePostgres)
|
||||
{
|
||||
Console.Out.WriteLine("[INIT] Connecting to postgres...");
|
||||
var builder = new DbContextOptionsBuilder<PostgresServerDbContext>();
|
||||
builder.UseNpgsql(config.DatabaseString);
|
||||
db = new PostgresServerDbContext(builder.Options);
|
||||
// await db.Database.MigrateAsync();
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.Out.WriteLine("[INIT] Connecting to sqlite...");
|
||||
var builder = new DbContextOptionsBuilder<SqliteServerDbContext>();
|
||||
builder.UseSqlite(new SqliteConnection(config.DatabaseString));
|
||||
db = new SqliteServerDbContext(builder.Options);
|
||||
}
|
||||
|
||||
Console.Out.WriteLine("[INIT] Database connection successful.");
|
||||
|
||||
var interaction = new InteractionService(client);
|
||||
var handler = new CommandHandler(client, interaction, db, config);
|
||||
|
||||
AppDomain.CurrentDomain.ProcessExit += (_, _) => Interlocked.Decrement(ref handler.Running);
|
||||
|
||||
Console.Out.WriteLine("[INIT] Starting Discord Bot.");
|
||||
await handler.InstallCommandsAsync();
|
||||
|
||||
// Block this task until the program is closed.
|
||||
await Task.Delay(-1);
|
||||
@@ -0,0 +1,32 @@
|
||||
# Content.DiscordBot
|
||||
|
||||
|
||||
### Configuration (Debug)
|
||||
* `DISCORD_TOKEN`: Discord application token provided @ `https://discord.com/developers/applications/<APP_ID>/bot`
|
||||
* `DATABASE_STRING`: Connection string to the database
|
||||
* For local development, you'll want it to be `Data Source=./bin/Content.Server/data/preferences.db` if you're using sqlite.
|
||||
* `DATABASE_CONTEXT`: Default is `postgres`. If testing locally with sqlite, use `sqlite`.
|
||||
* `GUILD`: The Discord Server ID. DeltaV's discord is `968983104247185448`.
|
||||
* You can find this by turning Developer mode on in Discord, right-clicking on a server, and copying the server ID.
|
||||
|
||||
### Configuration (Production)
|
||||
Requires a `link-config.json` file in the working directory.
|
||||
```json
|
||||
{
|
||||
"Token": "DISCORD_BOT_TOKEN",
|
||||
"DatabaseString": "DB_CONNECTION_STRING"
|
||||
}
|
||||
```
|
||||
|
||||
Here's a quick bash command to create it in your current working directory. Windows users will have to fend for themselves.
|
||||
```bash
|
||||
cat << EOF > link-config.json
|
||||
{
|
||||
"Token": "DISCORD_BOT_TOKEN",
|
||||
"DatabaseString": "DB_CONNECTION_STRING"
|
||||
}
|
||||
EOF
|
||||
```
|
||||
|
||||
You can also override `Guild` and `DatabaseContext` if you wanted to by including those JSON properties.
|
||||
|
||||
@@ -245,7 +245,6 @@ namespace Content.IntegrationTests.Tests
|
||||
"TimedDespawnDetailed", // DeltaV
|
||||
// makes an announcement on mapInit.
|
||||
"AnnounceOnSpawn",
|
||||
"ESTimedDespawn" // DeltaV
|
||||
};
|
||||
|
||||
Assert.That(server.CfgMan.GetCVar(CVars.NetPVS), Is.False);
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Content.Server.Database;
|
||||
|
||||
[Table("discord_accounts")]
|
||||
public sealed class DiscordAccount
|
||||
{
|
||||
[Key]
|
||||
public ulong Id { get; set; }
|
||||
|
||||
public DiscordLinkedAccount LinkedAccount { get; set; } = default!;
|
||||
public List<DiscordLinkedAccountLogs> LinkedAccountLogs { get; set; } = default!;
|
||||
}
|
||||
|
||||
[Table("discord_linked_accounts")]
|
||||
public sealed class DiscordLinkedAccount
|
||||
{
|
||||
[Key]
|
||||
public Guid PlayerId { get; set; }
|
||||
|
||||
public Player Player { get; set; } = default!;
|
||||
|
||||
public ulong DiscordId { get; set; }
|
||||
|
||||
public DiscordAccount Discord { get; set; } = default!;
|
||||
}
|
||||
|
||||
[Table("patreon_tiers")]
|
||||
public sealed class PatronTier
|
||||
{
|
||||
[Key]
|
||||
public int Id { get; set; }
|
||||
|
||||
public bool ShowOnCredits { get; set; }
|
||||
|
||||
public string Name { get; set; } = default!;
|
||||
|
||||
public ulong DiscordRole { get; set; }
|
||||
|
||||
public int Priority { get; set; }
|
||||
|
||||
public List<Patron> Patrons { get; set; } = default!;
|
||||
}
|
||||
|
||||
[Table("patreon_patrons")]
|
||||
[Index(nameof(TierId))]
|
||||
public sealed class Patron
|
||||
{
|
||||
[Key]
|
||||
public Guid PlayerId { get; set; }
|
||||
|
||||
public Player Player { get; set; } = default!;
|
||||
|
||||
public int TierId { get; set; }
|
||||
|
||||
public PatronTier Tier { get; set; } = default!;
|
||||
}
|
||||
|
||||
[Table("discord_linking_codes")]
|
||||
[Index(nameof(Code))]
|
||||
public sealed class DiscordLinkingCodes
|
||||
{
|
||||
[Key]
|
||||
public Guid PlayerId { get; set; }
|
||||
|
||||
public Player Player { get; set; } = default!;
|
||||
|
||||
public Guid Code { get; set; }
|
||||
|
||||
public DateTime CreationTime { get; set; }
|
||||
}
|
||||
|
||||
[Table("discord_linked_accounts_logs")]
|
||||
[Index(nameof(PlayerId))]
|
||||
[Index(nameof(DiscordId))]
|
||||
[Index(nameof(At))]
|
||||
public sealed class DiscordLinkedAccountLogs
|
||||
{
|
||||
[Key]
|
||||
public int Id { get; set; }
|
||||
|
||||
public Guid PlayerId { get; set; }
|
||||
|
||||
public Player Player { get; set; } = default!;
|
||||
|
||||
public ulong DiscordId { get; set; }
|
||||
|
||||
public DiscordAccount Discord { get; set; } = default!;
|
||||
|
||||
public DateTime At { get; set; }
|
||||
}
|
||||
+2433
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,207 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Content.Server.Database.Migrations.Postgres
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class DiscordLinking : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "cur_time",
|
||||
table: "admin_log");
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "discord_accounts",
|
||||
columns: table => new
|
||||
{
|
||||
discord_accounts_id = table.Column<ulong>(type: "INTEGER", nullable: false)
|
||||
.Annotation("Sqlite:Autoincrement", true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_discord_accounts", x => x.discord_accounts_id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "discord_linking_codes",
|
||||
columns: table => new
|
||||
{
|
||||
player_id = table.Column<Guid>(type: "TEXT", nullable: false),
|
||||
code = table.Column<Guid>(type: "TEXT", nullable: false),
|
||||
creation_time = table.Column<DateTime>(type: "TEXT", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_discord_linking_codes", x => x.player_id);
|
||||
table.ForeignKey(
|
||||
name: "FK_discord_linking_codes_player_player_id1",
|
||||
column: x => x.player_id,
|
||||
principalTable: "player",
|
||||
principalColumn: "user_id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "patreon_tiers",
|
||||
columns: table => new
|
||||
{
|
||||
patreon_tiers_id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||
.Annotation("Sqlite:Autoincrement", true),
|
||||
show_on_credits = table.Column<bool>(type: "INTEGER", nullable: false),
|
||||
name = table.Column<string>(type: "TEXT", nullable: false),
|
||||
discord_role = table.Column<ulong>(type: "INTEGER", nullable: false),
|
||||
priority = table.Column<int>(type: "INTEGER", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_patreon_tiers", x => x.patreon_tiers_id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "discord_linked_accounts",
|
||||
columns: table => new
|
||||
{
|
||||
player_id = table.Column<Guid>(type: "TEXT", nullable: false),
|
||||
discord_id = table.Column<ulong>(type: "INTEGER", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_discord_linked_accounts", x => x.player_id);
|
||||
table.ForeignKey(
|
||||
name: "FK_discord_linked_accounts_discord_accounts_discord_id",
|
||||
column: x => x.discord_id,
|
||||
principalTable: "discord_accounts",
|
||||
principalColumn: "discord_accounts_id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_discord_linked_accounts_player_player_id1",
|
||||
column: x => x.player_id,
|
||||
principalTable: "player",
|
||||
principalColumn: "user_id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "discord_linked_accounts_logs",
|
||||
columns: table => new
|
||||
{
|
||||
discord_linked_accounts_logs_id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||
.Annotation("Sqlite:Autoincrement", true),
|
||||
player_id = table.Column<Guid>(type: "TEXT", nullable: false),
|
||||
discord_id = table.Column<ulong>(type: "INTEGER", nullable: false),
|
||||
at = table.Column<DateTime>(type: "TEXT", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_discord_linked_accounts_logs", x => x.discord_linked_accounts_logs_id);
|
||||
table.ForeignKey(
|
||||
name: "FK_discord_linked_accounts_logs_discord_accounts_discord_id",
|
||||
column: x => x.discord_id,
|
||||
principalTable: "discord_accounts",
|
||||
principalColumn: "discord_accounts_id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_discord_linked_accounts_logs_player_player_id1",
|
||||
column: x => x.player_id,
|
||||
principalTable: "player",
|
||||
principalColumn: "user_id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "patreon_patrons",
|
||||
columns: table => new
|
||||
{
|
||||
player_id = table.Column<Guid>(type: "TEXT", nullable: false),
|
||||
tier_id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_patreon_patrons", x => x.player_id);
|
||||
table.ForeignKey(
|
||||
name: "FK_patreon_patrons_patreon_tiers_tier_id",
|
||||
column: x => x.tier_id,
|
||||
principalTable: "patreon_tiers",
|
||||
principalColumn: "patreon_tiers_id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_patreon_patrons_player_player_id1",
|
||||
column: x => x.player_id,
|
||||
principalTable: "player",
|
||||
principalColumn: "user_id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_discord_linked_accounts_discord_id",
|
||||
table: "discord_linked_accounts",
|
||||
column: "discord_id",
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_discord_linked_accounts_logs_at",
|
||||
table: "discord_linked_accounts_logs",
|
||||
column: "at");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_discord_linked_accounts_logs_discord_id",
|
||||
table: "discord_linked_accounts_logs",
|
||||
column: "discord_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_discord_linked_accounts_logs_player_id",
|
||||
table: "discord_linked_accounts_logs",
|
||||
column: "player_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_discord_linking_codes_code",
|
||||
table: "discord_linking_codes",
|
||||
column: "code");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_patreon_patrons_tier_id",
|
||||
table: "patreon_patrons",
|
||||
column: "tier_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_patreon_tiers_discord_role",
|
||||
table: "patreon_tiers",
|
||||
column: "discord_role",
|
||||
unique: true);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "discord_linked_accounts");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "discord_linked_accounts_logs");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "discord_linking_codes");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "patreon_patrons");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "discord_accounts");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "patreon_tiers");
|
||||
|
||||
migrationBuilder.AddColumn<long>(
|
||||
name: "cur_time",
|
||||
table: "admin_log",
|
||||
type: "INTEGER",
|
||||
nullable: false,
|
||||
defaultValue: 0L);
|
||||
}
|
||||
}
|
||||
}
|
||||
+248
-2
@@ -1,5 +1,6 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using System.Text.Json;
|
||||
using Content.Server.Database;
|
||||
@@ -20,7 +21,7 @@ namespace Content.Server.Database.Migrations.Postgres
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "9.0.1")
|
||||
.HasAnnotation("ProductVersion", "10.0.6")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||
|
||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||
@@ -739,6 +740,151 @@ namespace Content.Server.Database.Migrations.Postgres
|
||||
b.ToTable("dv_seen_tips", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Content.Server.Database.Patron", b =>
|
||||
{
|
||||
b.Property<Guid>("PlayerId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("player_id");
|
||||
|
||||
b.Property<int>("TierId")
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("tier_id");
|
||||
|
||||
b.HasKey("PlayerId")
|
||||
.HasName("PK_patreon_patrons");
|
||||
|
||||
b.HasIndex("TierId")
|
||||
.HasDatabaseName("IX_patreon_patrons_tier_id");
|
||||
|
||||
b.ToTable("patreon_patrons", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Content.Server.Database.PatronTier", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("patreon_tiers_id");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<decimal>("DiscordRole")
|
||||
.HasColumnType("numeric(20,0)")
|
||||
.HasColumnName("discord_role");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("text")
|
||||
.HasColumnName("name");
|
||||
|
||||
b.Property<int>("Priority")
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("priority");
|
||||
|
||||
b.Property<bool>("ShowOnCredits")
|
||||
.HasColumnType("boolean")
|
||||
.HasColumnName("show_on_credits");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("PK_patreon_tiers");
|
||||
|
||||
b.HasIndex("DiscordRole")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("patreon_tiers", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Content.Server.Database.DiscordAccount", b =>
|
||||
{
|
||||
b.Property<decimal>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("numeric(20,0)")
|
||||
.HasColumnName("discord_accounts_id");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("PK_discord_accounts");
|
||||
|
||||
b.ToTable("discord_accounts", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Content.Server.Database.DiscordLinkedAccount", b =>
|
||||
{
|
||||
b.Property<Guid>("PlayerId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("player_id");
|
||||
|
||||
b.Property<decimal>("DiscordId")
|
||||
.HasColumnType("numeric(20,0)")
|
||||
.HasColumnName("discord_id");
|
||||
|
||||
b.HasKey("PlayerId")
|
||||
.HasName("PK_discord_linked_accounts");
|
||||
|
||||
b.HasIndex("DiscordId")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("discord_linked_accounts", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Content.Server.Database.DiscordLinkedAccountLogs", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("discord_linked_accounts_logs_id");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<DateTime>("At")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("at");
|
||||
|
||||
b.Property<decimal>("DiscordId")
|
||||
.HasColumnType("numeric(20,0)")
|
||||
.HasColumnName("discord_id");
|
||||
|
||||
b.Property<Guid>("PlayerId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("player_id");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("PK_discord_linked_accounts_logs");
|
||||
|
||||
b.HasIndex("At")
|
||||
.HasDatabaseName("IX_discord_linked_accounts_logs_at");
|
||||
|
||||
b.HasIndex("DiscordId")
|
||||
.HasDatabaseName("IX_discord_linked_accounts_logs_discord_id");
|
||||
|
||||
b.HasIndex("PlayerId")
|
||||
.HasDatabaseName("IX_discord_linked_accounts_logs_player_id");
|
||||
|
||||
b.ToTable("discord_linked_accounts_logs", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Content.Server.Database.DiscordLinkingCodes", b =>
|
||||
{
|
||||
b.Property<Guid>("PlayerId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("player_id");
|
||||
|
||||
b.Property<Guid>("Code")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("code");
|
||||
|
||||
b.Property<DateTime>("CreationTime")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("creation_time");
|
||||
|
||||
b.HasKey("PlayerId")
|
||||
.HasName("PK_discord_linking_codes");
|
||||
|
||||
b.HasIndex("Code")
|
||||
.HasDatabaseName("IX_discord_linking_codes_code");
|
||||
|
||||
b.ToTable("discord_linking_codes", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Content.Server.Database.IPIntelCache", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
@@ -901,7 +1047,7 @@ namespace Content.Server.Database.Migrations.Postgres
|
||||
.HasColumnType("text")
|
||||
.HasColumnName("admin_ooc_color");
|
||||
|
||||
b.PrimitiveCollection<string[]>("ConstructionFavorites")
|
||||
b.PrimitiveCollection<List<string>>("ConstructionFavorites")
|
||||
.IsRequired()
|
||||
.HasColumnType("text[]")
|
||||
.HasColumnName("construction_favorites");
|
||||
@@ -1840,6 +1986,85 @@ namespace Content.Server.Database.Migrations.Postgres
|
||||
.HasConstraintName("FK_dv_seen_tips_player_player_id");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Content.Server.Database.Patron", b =>
|
||||
{
|
||||
b.HasOne("Content.Server.Database.Player", "Player")
|
||||
.WithOne("Patron")
|
||||
.HasForeignKey("Content.Server.Database.Patron", "PlayerId")
|
||||
.HasPrincipalKey("Content.Server.Database.Player", "UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired()
|
||||
.HasConstraintName("FK_patreon_patrons_player_player_id1");
|
||||
|
||||
b.HasOne("Content.Server.Database.PatronTier", "Tier")
|
||||
.WithMany("Patrons")
|
||||
.HasForeignKey("TierId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired()
|
||||
.HasConstraintName("FK_patreon_patrons_patreon_tiers_tier_id");
|
||||
|
||||
b.Navigation("Player");
|
||||
|
||||
b.Navigation("Tier");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Content.Server.Database.DiscordLinkedAccount", b =>
|
||||
{
|
||||
b.HasOne("Content.Server.Database.DiscordAccount", "Discord")
|
||||
.WithOne("LinkedAccount")
|
||||
.HasForeignKey("Content.Server.Database.DiscordLinkedAccount", "DiscordId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired()
|
||||
.HasConstraintName("FK_discord_linked_accounts_discord_accounts_discord_id");
|
||||
|
||||
b.HasOne("Content.Server.Database.Player", "Player")
|
||||
.WithOne("LinkedAccount")
|
||||
.HasForeignKey("Content.Server.Database.DiscordLinkedAccount", "PlayerId")
|
||||
.HasPrincipalKey("Content.Server.Database.Player", "UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired()
|
||||
.HasConstraintName("FK_discord_linked_accounts_player_player_id1");
|
||||
|
||||
b.Navigation("Discord");
|
||||
|
||||
b.Navigation("Player");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Content.Server.Database.DiscordLinkedAccountLogs", b =>
|
||||
{
|
||||
b.HasOne("Content.Server.Database.DiscordAccount", "Discord")
|
||||
.WithMany("LinkedAccountLogs")
|
||||
.HasForeignKey("DiscordId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired()
|
||||
.HasConstraintName("FK_discord_linked_accounts_logs_discord_accounts_discord_id");
|
||||
|
||||
b.HasOne("Content.Server.Database.Player", "Player")
|
||||
.WithMany("LinkedAccountLogs")
|
||||
.HasForeignKey("PlayerId")
|
||||
.HasPrincipalKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired()
|
||||
.HasConstraintName("FK_discord_linked_accounts_logs_player_player_id1");
|
||||
|
||||
b.Navigation("Discord");
|
||||
|
||||
b.Navigation("Player");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Content.Server.Database.DiscordLinkingCodes", b =>
|
||||
{
|
||||
b.HasOne("Content.Server.Database.Player", "Player")
|
||||
.WithOne("LinkingCodes")
|
||||
.HasForeignKey("Content.Server.Database.DiscordLinkingCodes", "PlayerId")
|
||||
.HasPrincipalKey("Content.Server.Database.Player", "UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired()
|
||||
.HasConstraintName("FK_discord_linking_codes_player_player_id1");
|
||||
|
||||
b.Navigation("Player");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Content.Server.Database.Job", b =>
|
||||
{
|
||||
b.HasOne("Content.Server.Database.Profile", "Profile")
|
||||
@@ -2169,6 +2394,19 @@ namespace Content.Server.Database.Migrations.Postgres
|
||||
b.Navigation("BanHits");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Content.Server.Database.PatronTier", b =>
|
||||
{
|
||||
b.Navigation("Patrons");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Content.Server.Database.DiscordAccount", b =>
|
||||
{
|
||||
b.Navigation("LinkedAccount")
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("LinkedAccountLogs");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Content.Server.Database.Player", b =>
|
||||
{
|
||||
b.Navigation("AdminLogs");
|
||||
@@ -2206,6 +2444,14 @@ namespace Content.Server.Database.Migrations.Postgres
|
||||
b.Navigation("AdminWatchlistsReceived");
|
||||
|
||||
b.Navigation("JobWhitelists");
|
||||
|
||||
b.Navigation("LinkedAccount");
|
||||
|
||||
b.Navigation("LinkedAccountLogs");
|
||||
|
||||
b.Navigation("LinkingCodes");
|
||||
|
||||
b.Navigation("Patron");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Content.Server.Database.Preference", b =>
|
||||
|
||||
+2428
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,207 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Content.Server.Database.Migrations.Sqlite
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class DiscordLinking : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "cur_time",
|
||||
table: "admin_log");
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "discord_accounts",
|
||||
columns: table => new
|
||||
{
|
||||
discord_accounts_id = table.Column<ulong>(type: "INTEGER", nullable: false)
|
||||
.Annotation("Sqlite:Autoincrement", true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_discord_accounts", x => x.discord_accounts_id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "discord_linking_codes",
|
||||
columns: table => new
|
||||
{
|
||||
player_id = table.Column<Guid>(type: "TEXT", nullable: false),
|
||||
code = table.Column<Guid>(type: "TEXT", nullable: false),
|
||||
creation_time = table.Column<DateTime>(type: "TEXT", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_discord_linking_codes", x => x.player_id);
|
||||
table.ForeignKey(
|
||||
name: "FK_discord_linking_codes_player_player_id1",
|
||||
column: x => x.player_id,
|
||||
principalTable: "player",
|
||||
principalColumn: "user_id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "patreon_tiers",
|
||||
columns: table => new
|
||||
{
|
||||
patreon_tiers_id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||
.Annotation("Sqlite:Autoincrement", true),
|
||||
show_on_credits = table.Column<bool>(type: "INTEGER", nullable: false),
|
||||
name = table.Column<string>(type: "TEXT", nullable: false),
|
||||
discord_role = table.Column<ulong>(type: "INTEGER", nullable: false),
|
||||
priority = table.Column<int>(type: "INTEGER", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_patreon_tiers", x => x.patreon_tiers_id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "discord_linked_accounts",
|
||||
columns: table => new
|
||||
{
|
||||
player_id = table.Column<Guid>(type: "TEXT", nullable: false),
|
||||
discord_id = table.Column<ulong>(type: "INTEGER", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_discord_linked_accounts", x => x.player_id);
|
||||
table.ForeignKey(
|
||||
name: "FK_discord_linked_accounts_discord_accounts_discord_id",
|
||||
column: x => x.discord_id,
|
||||
principalTable: "discord_accounts",
|
||||
principalColumn: "discord_accounts_id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_discord_linked_accounts_player_player_id1",
|
||||
column: x => x.player_id,
|
||||
principalTable: "player",
|
||||
principalColumn: "user_id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "discord_linked_accounts_logs",
|
||||
columns: table => new
|
||||
{
|
||||
discord_linked_accounts_logs_id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||
.Annotation("Sqlite:Autoincrement", true),
|
||||
player_id = table.Column<Guid>(type: "TEXT", nullable: false),
|
||||
discord_id = table.Column<ulong>(type: "INTEGER", nullable: false),
|
||||
at = table.Column<DateTime>(type: "TEXT", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_discord_linked_accounts_logs", x => x.discord_linked_accounts_logs_id);
|
||||
table.ForeignKey(
|
||||
name: "FK_discord_linked_accounts_logs_discord_accounts_discord_id",
|
||||
column: x => x.discord_id,
|
||||
principalTable: "discord_accounts",
|
||||
principalColumn: "discord_accounts_id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_discord_linked_accounts_logs_player_player_id1",
|
||||
column: x => x.player_id,
|
||||
principalTable: "player",
|
||||
principalColumn: "user_id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "patreon_patrons",
|
||||
columns: table => new
|
||||
{
|
||||
player_id = table.Column<Guid>(type: "TEXT", nullable: false),
|
||||
tier_id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_patreon_patrons", x => x.player_id);
|
||||
table.ForeignKey(
|
||||
name: "FK_patreon_patrons_patreon_tiers_tier_id",
|
||||
column: x => x.tier_id,
|
||||
principalTable: "patreon_tiers",
|
||||
principalColumn: "patreon_tiers_id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_patreon_patrons_player_player_id1",
|
||||
column: x => x.player_id,
|
||||
principalTable: "player",
|
||||
principalColumn: "user_id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_discord_linked_accounts_discord_id",
|
||||
table: "discord_linked_accounts",
|
||||
column: "discord_id",
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_discord_linked_accounts_logs_at",
|
||||
table: "discord_linked_accounts_logs",
|
||||
column: "at");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_discord_linked_accounts_logs_discord_id",
|
||||
table: "discord_linked_accounts_logs",
|
||||
column: "discord_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_discord_linked_accounts_logs_player_id",
|
||||
table: "discord_linked_accounts_logs",
|
||||
column: "player_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_discord_linking_codes_code",
|
||||
table: "discord_linking_codes",
|
||||
column: "code");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_patreon_patrons_tier_id",
|
||||
table: "patreon_patrons",
|
||||
column: "tier_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_patreon_tiers_discord_role",
|
||||
table: "patreon_tiers",
|
||||
column: "discord_role",
|
||||
unique: true);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "discord_linked_accounts");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "discord_linked_accounts_logs");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "discord_linking_codes");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "patreon_patrons");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "discord_accounts");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "patreon_tiers");
|
||||
|
||||
migrationBuilder.AddColumn<long>(
|
||||
name: "cur_time",
|
||||
table: "admin_log",
|
||||
type: "INTEGER",
|
||||
nullable: false,
|
||||
defaultValue: 0L);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,7 @@ namespace Content.Server.Database.Migrations.Sqlite
|
||||
protected override void BuildModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder.HasAnnotation("ProductVersion", "9.0.1");
|
||||
modelBuilder.HasAnnotation("ProductVersion", "10.0.6");
|
||||
|
||||
modelBuilder.Entity("Content.Server.Database.Admin", b =>
|
||||
{
|
||||
@@ -698,6 +698,147 @@ namespace Content.Server.Database.Migrations.Sqlite
|
||||
b.ToTable("dv_seen_tips", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Content.Server.Database.Patron", b =>
|
||||
{
|
||||
b.Property<Guid>("PlayerId")
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("player_id");
|
||||
|
||||
b.Property<int>("TierId")
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("tier_id");
|
||||
|
||||
b.HasKey("PlayerId")
|
||||
.HasName("PK_patreon_patrons");
|
||||
|
||||
b.HasIndex("TierId")
|
||||
.HasDatabaseName("IX_patreon_patrons_tier_id");
|
||||
|
||||
b.ToTable("patreon_patrons", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Content.Server.Database.PatronTier", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("patreon_tiers_id");
|
||||
|
||||
b.Property<ulong>("DiscordRole")
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("discord_role");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("name");
|
||||
|
||||
b.Property<int>("Priority")
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("priority");
|
||||
|
||||
b.Property<bool>("ShowOnCredits")
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("show_on_credits");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("PK_patreon_tiers");
|
||||
|
||||
b.HasIndex("DiscordRole")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("patreon_tiers", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Content.Server.Database.DiscordAccount", b =>
|
||||
{
|
||||
b.Property<ulong>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("discord_accounts_id");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("PK_discord_accounts");
|
||||
|
||||
b.ToTable("discord_accounts", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Content.Server.Database.DiscordLinkedAccount", b =>
|
||||
{
|
||||
b.Property<Guid>("PlayerId")
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("player_id");
|
||||
|
||||
b.Property<ulong>("DiscordId")
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("discord_id");
|
||||
|
||||
b.HasKey("PlayerId")
|
||||
.HasName("PK_discord_linked_accounts");
|
||||
|
||||
b.HasIndex("DiscordId")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("discord_linked_accounts", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Content.Server.Database.DiscordLinkedAccountLogs", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("discord_linked_accounts_logs_id");
|
||||
|
||||
b.Property<DateTime>("At")
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("at");
|
||||
|
||||
b.Property<ulong>("DiscordId")
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("discord_id");
|
||||
|
||||
b.Property<Guid>("PlayerId")
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("player_id");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("PK_discord_linked_accounts_logs");
|
||||
|
||||
b.HasIndex("At")
|
||||
.HasDatabaseName("IX_discord_linked_accounts_logs_at");
|
||||
|
||||
b.HasIndex("DiscordId")
|
||||
.HasDatabaseName("IX_discord_linked_accounts_logs_discord_id");
|
||||
|
||||
b.HasIndex("PlayerId")
|
||||
.HasDatabaseName("IX_discord_linked_accounts_logs_player_id");
|
||||
|
||||
b.ToTable("discord_linked_accounts_logs", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Content.Server.Database.DiscordLinkingCodes", b =>
|
||||
{
|
||||
b.Property<Guid>("PlayerId")
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("player_id");
|
||||
|
||||
b.Property<Guid>("Code")
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("code");
|
||||
|
||||
b.Property<DateTime>("CreationTime")
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("creation_time");
|
||||
|
||||
b.HasKey("PlayerId")
|
||||
.HasName("PK_discord_linking_codes");
|
||||
|
||||
b.HasIndex("Code")
|
||||
.HasDatabaseName("IX_discord_linking_codes_code");
|
||||
|
||||
b.ToTable("discord_linking_codes", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Content.Server.Database.IPIntelCache", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
@@ -1759,6 +1900,85 @@ namespace Content.Server.Database.Migrations.Sqlite
|
||||
.HasConstraintName("FK_dv_seen_tips_player_player_id");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Content.Server.Database.Patron", b =>
|
||||
{
|
||||
b.HasOne("Content.Server.Database.Player", "Player")
|
||||
.WithOne("Patron")
|
||||
.HasForeignKey("Content.Server.Database.Patron", "PlayerId")
|
||||
.HasPrincipalKey("Content.Server.Database.Player", "UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired()
|
||||
.HasConstraintName("FK_patreon_patrons_player_player_id1");
|
||||
|
||||
b.HasOne("Content.Server.Database.PatronTier", "Tier")
|
||||
.WithMany("Patrons")
|
||||
.HasForeignKey("TierId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired()
|
||||
.HasConstraintName("FK_patreon_patrons_patreon_tiers_tier_id");
|
||||
|
||||
b.Navigation("Player");
|
||||
|
||||
b.Navigation("Tier");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Content.Server.Database.DiscordLinkedAccount", b =>
|
||||
{
|
||||
b.HasOne("Content.Server.Database.DiscordAccount", "Discord")
|
||||
.WithOne("LinkedAccount")
|
||||
.HasForeignKey("Content.Server.Database.DiscordLinkedAccount", "DiscordId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired()
|
||||
.HasConstraintName("FK_discord_linked_accounts_discord_accounts_discord_id");
|
||||
|
||||
b.HasOne("Content.Server.Database.Player", "Player")
|
||||
.WithOne("LinkedAccount")
|
||||
.HasForeignKey("Content.Server.Database.DiscordLinkedAccount", "PlayerId")
|
||||
.HasPrincipalKey("Content.Server.Database.Player", "UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired()
|
||||
.HasConstraintName("FK_discord_linked_accounts_player_player_id1");
|
||||
|
||||
b.Navigation("Discord");
|
||||
|
||||
b.Navigation("Player");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Content.Server.Database.DiscordLinkedAccountLogs", b =>
|
||||
{
|
||||
b.HasOne("Content.Server.Database.DiscordAccount", "Discord")
|
||||
.WithMany("LinkedAccountLogs")
|
||||
.HasForeignKey("DiscordId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired()
|
||||
.HasConstraintName("FK_discord_linked_accounts_logs_discord_accounts_discord_id");
|
||||
|
||||
b.HasOne("Content.Server.Database.Player", "Player")
|
||||
.WithMany("LinkedAccountLogs")
|
||||
.HasForeignKey("PlayerId")
|
||||
.HasPrincipalKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired()
|
||||
.HasConstraintName("FK_discord_linked_accounts_logs_player_player_id1");
|
||||
|
||||
b.Navigation("Discord");
|
||||
|
||||
b.Navigation("Player");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Content.Server.Database.DiscordLinkingCodes", b =>
|
||||
{
|
||||
b.HasOne("Content.Server.Database.Player", "Player")
|
||||
.WithOne("LinkingCodes")
|
||||
.HasForeignKey("Content.Server.Database.DiscordLinkingCodes", "PlayerId")
|
||||
.HasPrincipalKey("Content.Server.Database.Player", "UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired()
|
||||
.HasConstraintName("FK_discord_linking_codes_player_player_id1");
|
||||
|
||||
b.Navigation("Player");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Content.Server.Database.Job", b =>
|
||||
{
|
||||
b.HasOne("Content.Server.Database.Profile", "Profile")
|
||||
@@ -2088,6 +2308,19 @@ namespace Content.Server.Database.Migrations.Sqlite
|
||||
b.Navigation("BanHits");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Content.Server.Database.PatronTier", b =>
|
||||
{
|
||||
b.Navigation("Patrons");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Content.Server.Database.DiscordAccount", b =>
|
||||
{
|
||||
b.Navigation("LinkedAccount")
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("LinkedAccountLogs");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Content.Server.Database.Player", b =>
|
||||
{
|
||||
b.Navigation("AdminLogs");
|
||||
@@ -2125,6 +2358,14 @@ namespace Content.Server.Database.Migrations.Sqlite
|
||||
b.Navigation("AdminWatchlistsReceived");
|
||||
|
||||
b.Navigation("JobWhitelists");
|
||||
|
||||
b.Navigation("LinkedAccount");
|
||||
|
||||
b.Navigation("LinkedAccountLogs");
|
||||
|
||||
b.Navigation("LinkingCodes");
|
||||
|
||||
b.Navigation("Patron");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Content.Server.Database.Preference", b =>
|
||||
|
||||
@@ -48,6 +48,15 @@ namespace Content.Server.Database
|
||||
public DbSet<IPIntelCache> IPIntelCache { get; set; } = null!;
|
||||
public DbSet<DVModel.SeenTip> DVSeenTips { get; set; } = null!; // DeltaV - Tips
|
||||
|
||||
// BEGIN DeltaV - Discord / Patreon Account Linking
|
||||
public DbSet<DiscordAccount> DiscordAccounts { get; set; } = default!;
|
||||
public DbSet<DiscordLinkedAccount> DiscordLinkedAccounts { get; set; } = default!;
|
||||
public DbSet<PatronTier> PatronTiers { get; set; } = default!;
|
||||
public DbSet<Patron> Patrons { get; set; } = default!;
|
||||
public DbSet<DiscordLinkingCodes> DiscordLinkingCodes { get; set; } = default!;
|
||||
public DbSet<DiscordLinkedAccountLogs> DiscordLinkedAccountLogs { get; set; } = default!;
|
||||
// END DeltaV
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
modelBuilder.Entity<Preference>()
|
||||
@@ -395,6 +404,61 @@ namespace Content.Server.Database
|
||||
.OwnsOne(p => p.HWId)
|
||||
.Property(p => p.Type)
|
||||
.HasDefaultValue(HwidType.Legacy);
|
||||
|
||||
// BEGIN DeltaV - Discord / Patreon Account Linking
|
||||
modelBuilder.Entity<DiscordLinkedAccount>()
|
||||
.HasOne(l => l.Player)
|
||||
.WithOne(p => p.LinkedAccount)
|
||||
.HasForeignKey<DiscordLinkedAccount>(l => l.PlayerId)
|
||||
.HasPrincipalKey<Player>(p => p.UserId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
modelBuilder.Entity<DiscordLinkedAccount>()
|
||||
.HasOne(l => l.Discord)
|
||||
.WithOne(d => d.LinkedAccount)
|
||||
.HasForeignKey<DiscordLinkedAccount>(l => l.DiscordId)
|
||||
.HasPrincipalKey<DiscordAccount>(d => d.Id)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
modelBuilder.Entity<Patron>()
|
||||
.HasOne(p => p.Player)
|
||||
.WithOne(p => p.Patron)
|
||||
.HasForeignKey<Patron>(p => p.PlayerId)
|
||||
.HasPrincipalKey<Player>(p => p.UserId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
modelBuilder.Entity<Patron>()
|
||||
.HasOne(p => p.Tier)
|
||||
.WithMany(t => t.Patrons)
|
||||
.HasForeignKey(p => p.TierId)
|
||||
.HasPrincipalKey(p => p.Id)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
modelBuilder.Entity<PatronTier>()
|
||||
.HasIndex(t => t.DiscordRole)
|
||||
.IsUnique();
|
||||
|
||||
modelBuilder.Entity<DiscordLinkingCodes>()
|
||||
.HasOne(l => l.Player)
|
||||
.WithOne(p => p.LinkingCodes)
|
||||
.HasForeignKey<DiscordLinkingCodes>(l => l.PlayerId)
|
||||
.HasPrincipalKey<Player>(p => p.UserId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
modelBuilder.Entity<DiscordLinkedAccountLogs>()
|
||||
.HasOne(l => l.Player)
|
||||
.WithMany(p => p.LinkedAccountLogs)
|
||||
.HasForeignKey(l => l.PlayerId)
|
||||
.HasPrincipalKey(p => p.UserId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
modelBuilder.Entity<DiscordLinkedAccountLogs>()
|
||||
.HasOne(l => l.Discord)
|
||||
.WithMany(p => p.LinkedAccountLogs)
|
||||
.HasForeignKey(l => l.DiscordId)
|
||||
.HasPrincipalKey(p => p.Id)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
// END DeltaV
|
||||
}
|
||||
|
||||
public virtual IQueryable<AdminLog> SearchLogs(IQueryable<AdminLog> query, string searchText)
|
||||
@@ -621,6 +685,13 @@ namespace Content.Server.Database
|
||||
public List<ServerRoleBan> AdminServerRoleBansCreated { get; set; } = null!;
|
||||
public List<ServerRoleBan> AdminServerRoleBansLastEdited { get; set; } = null!;
|
||||
public List<RoleWhitelist> JobWhitelists { get; set; } = null!;
|
||||
|
||||
// BEGIN DeltaV - Discord / Patreon Account Linking
|
||||
public DiscordLinkedAccount? LinkedAccount { get; set; }
|
||||
public Patron? Patron { get; set; }
|
||||
public DiscordLinkingCodes? LinkingCodes { get; set; }
|
||||
public List<DiscordLinkedAccountLogs> LinkedAccountLogs { get; set; } = default!;
|
||||
// END DeltaV
|
||||
}
|
||||
|
||||
[Table("whitelist")]
|
||||
|
||||
@@ -87,7 +87,7 @@ public sealed partial class AnomalySystem : SharedAnomalySystem
|
||||
if (anomaly.Comp.CurrentBehavior is not null)
|
||||
RemoveBehavior(anomaly, anomaly.Comp.CurrentBehavior.Value);
|
||||
|
||||
EndAnomaly(anomaly, spawnCore: false, forced: true);
|
||||
EndAnomaly(anomaly, spawnCore: false);
|
||||
}
|
||||
|
||||
private void OnStartCollide(Entity<AnomalyComponent> anomaly, ref StartCollideEvent args)
|
||||
@@ -143,7 +143,7 @@ public sealed partial class AnomalySystem : SharedAnomalySystem
|
||||
return 0;
|
||||
|
||||
var multiplier = 1f;
|
||||
if (component.AlwaysGrow || component.Stability > component.GrowthThreshold) // DeltaV - Add AlwaysGrow
|
||||
if (component.Stability > component.GrowthThreshold)
|
||||
multiplier = component.GrowingPointMultiplier; //more points for unstable
|
||||
|
||||
//penalty of up to 50% based on health
|
||||
@@ -253,11 +253,7 @@ public sealed partial class AnomalySystem : SharedAnomalySystem
|
||||
else
|
||||
{
|
||||
string stateLoc;
|
||||
// DeltaV - Colossus Additions START
|
||||
if (anomalyComp.AlwaysGrow)
|
||||
stateLoc = Loc.GetString("anomaly-scanner-stability-high");
|
||||
// DeltaV - Colossus Additions END
|
||||
else if (anomalyComp.Stability < anomalyComp.DecayThreshold) // DeltaV - Add else
|
||||
if (anomalyComp.Stability < anomalyComp.DecayThreshold)
|
||||
stateLoc = Loc.GetString("anomaly-scanner-stability-low");
|
||||
else if (anomalyComp.Stability > anomalyComp.GrowthThreshold)
|
||||
stateLoc = Loc.GetString("anomaly-scanner-stability-high");
|
||||
@@ -349,15 +345,6 @@ public sealed partial class AnomalySystem : SharedAnomalySystem
|
||||
msg.AddMarkupOrThrow(Loc.GetString("anomaly-behavior-unknown"));
|
||||
else
|
||||
{
|
||||
// DeltaV - Colossus Additions START
|
||||
if (anomalyComp.AlwaysGrow)
|
||||
{
|
||||
msg.AddMarkupOrThrow("- " + Loc.GetString("anomaly-behavior-always-grow"));
|
||||
if (anomalyComp.CurrentBehavior != null)
|
||||
msg.PushNewline();
|
||||
}
|
||||
// DeltaV - Colossus Additions END
|
||||
|
||||
if (anomalyComp.CurrentBehavior != null)
|
||||
{
|
||||
var behavior = _prototype.Index(anomalyComp.CurrentBehavior.Value);
|
||||
@@ -367,7 +354,7 @@ public sealed partial class AnomalySystem : SharedAnomalySystem
|
||||
var mod = Math.Floor((behavior.EarnPointModifier) * 100);
|
||||
msg.AddMarkupOrThrow("- " + Loc.GetString("anomaly-behavior-point", ("mod", mod)));
|
||||
}
|
||||
else if(!anomalyComp.AlwaysGrow) // DeltaV - Add condition, previously regular else
|
||||
else
|
||||
{
|
||||
msg.AddMarkupOrThrow(Loc.GetString("anomaly-behavior-balanced"));
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ using Content.Server.Administration.Logs;
|
||||
using Content.Server.Administration.Managers;
|
||||
using Content.Server.Administration.Systems;
|
||||
using Content.Server.Discord.DiscordLink;
|
||||
using Content.Server.Ghost;
|
||||
using Content.Server.Players.RateLimiting;
|
||||
using Content.Server.Preferences.Managers;
|
||||
using Content.Shared.Administration;
|
||||
@@ -16,7 +15,6 @@ using Content.Shared.Mind;
|
||||
using Content.Shared.Players; // DeltaV - OOC muting
|
||||
using Content.Shared.Players.RateLimiting;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Replays;
|
||||
@@ -49,7 +47,6 @@ internal sealed partial class ChatManager : IChatManager
|
||||
[Dependency] private readonly ISharedPlayerManager _player = default!;
|
||||
[Dependency] private readonly DiscordChatLink _discordLink = default!;
|
||||
[Dependency] private readonly ILogManager _logManager = default!;
|
||||
[Dependency] private readonly ILocalizationManager _localizationManager = default!;
|
||||
|
||||
private ISawmill _sawmill = default!;
|
||||
|
||||
@@ -92,10 +89,10 @@ internal sealed partial class ChatManager : IChatManager
|
||||
DispatchServerAnnouncement(Loc.GetString(val ? "chat-manager-admin-ooc-chat-enabled-message" : "chat-manager-admin-ooc-chat-disabled-message"));
|
||||
}
|
||||
|
||||
public void DeleteMessagesBy(NetUserId uid)
|
||||
{
|
||||
if (!_players.TryGetValue(uid, out var user))
|
||||
return;
|
||||
public void DeleteMessagesBy(NetUserId uid)
|
||||
{
|
||||
if (!_players.TryGetValue(uid, out var user))
|
||||
return;
|
||||
|
||||
var msg = new MsgDeleteChatMessagesBy { Key = user.Key, Entities = user.Entities };
|
||||
_netManager.ServerSendToAll(msg);
|
||||
@@ -345,35 +342,12 @@ internal sealed partial class ChatManager : IChatManager
|
||||
|
||||
#region Utility
|
||||
|
||||
private bool IsValidWarpDestination(EntityUid source)
|
||||
{
|
||||
if (!source.Valid)
|
||||
return false;
|
||||
|
||||
if (!_entityManager.TryGetComponent(source, out TransformComponent? transform))
|
||||
return false;
|
||||
|
||||
return transform.MapID != MapId.Nullspace;
|
||||
}
|
||||
|
||||
public string PrependFollowButtonIfAppropriate(string wrappedMessage, EntityUid source, INetChannel recipient)
|
||||
{
|
||||
if (IsValidWarpDestination(source) && ShouldShowFollowButton(recipient))
|
||||
{
|
||||
var btnText = _localizationManager.GetString("chat-manager-follow-button");
|
||||
return $"[cmdlink=\"{btnText}\" command=\"{GhostFollowEntityCommand.CommandName} {_entityManager.GetNetEntity(source)}\" /] " + wrappedMessage;
|
||||
}
|
||||
|
||||
return wrappedMessage;
|
||||
}
|
||||
|
||||
public void ChatMessageToOne(ChatChannel channel, string message, string wrappedMessage, EntityUid source, bool hideChat, INetChannel client, Color? colorOverride = null, bool recordReplay = false, string? audioPath = null, float audioVolume = 0, NetUserId? author = null)
|
||||
{
|
||||
var user = author == null ? null : EnsurePlayer(author);
|
||||
var netSource = _entityManager.GetNetEntity(source);
|
||||
user?.AddEntity(netSource);
|
||||
|
||||
wrappedMessage = PrependFollowButtonIfAppropriate(wrappedMessage, source, client);
|
||||
var msg = new ChatMessage(channel, message, wrappedMessage, netSource, user?.Key, hideChat, colorOverride, audioPath, audioVolume);
|
||||
_netManager.ServerSendMessage(new MsgChatMessage() { Message = msg }, client);
|
||||
|
||||
@@ -396,12 +370,8 @@ internal sealed partial class ChatManager : IChatManager
|
||||
var netSource = _entityManager.GetNetEntity(source);
|
||||
user?.AddEntity(netSource);
|
||||
|
||||
foreach (var client in clients)
|
||||
{
|
||||
var customWrapMessage = PrependFollowButtonIfAppropriate(wrappedMessage, source, client);
|
||||
var msg = new ChatMessage(channel, message, customWrapMessage, netSource, user?.Key, hideChat, colorOverride, audioPath, audioVolume);
|
||||
_netManager.ServerSendMessage(new MsgChatMessage { Message = msg }, client);
|
||||
}
|
||||
var msg = new ChatMessage(channel, message, wrappedMessage, netSource, user?.Key, hideChat, colorOverride, audioPath, audioVolume);
|
||||
_netManager.ServerSendToMany(new MsgChatMessage() { Message = msg }, clients);
|
||||
|
||||
if (!recordReplay)
|
||||
return;
|
||||
@@ -409,7 +379,6 @@ internal sealed partial class ChatManager : IChatManager
|
||||
if ((channel & ChatChannel.AdminRelated) == 0 ||
|
||||
_configurationManager.GetCVar(CCVars.ReplayRecordAdminChat))
|
||||
{
|
||||
var msg = new ChatMessage(channel, message, wrappedMessage, netSource, user?.Key, hideChat, colorOverride, audioPath, audioVolume);
|
||||
_replay.RecordServerMessage(msg);
|
||||
}
|
||||
}
|
||||
@@ -470,22 +439,6 @@ internal sealed partial class ChatManager : IChatManager
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private bool ShouldShowFollowButton(INetChannel recipient)
|
||||
{
|
||||
if (!_player.TryGetSessionByChannel(recipient, out var session))
|
||||
return false;
|
||||
|
||||
if (_entityManager.TrySystem(out GhostSystem? ghost))
|
||||
{
|
||||
if (!ghost.CanGhostWarp(session, out _))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return _netConfigManager.GetClientCVar(recipient, CCVars.InterfaceChatFollowButton);
|
||||
}
|
||||
}
|
||||
|
||||
public enum OOCChatType : byte
|
||||
|
||||
@@ -49,7 +49,5 @@ namespace Content.Server.Chat.Managers
|
||||
/// <param name="player">The player sending a chat message.</param>
|
||||
/// <returns>False if the player has violated rate limits and should be blocked from sending further messages.</returns>
|
||||
RateLimitStatus HandleRateLimit(ICommonSession player);
|
||||
|
||||
string PrependFollowButtonIfAppropriate(string wrappedMessage, EntityUid source, INetChannel recipient);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ using Content.Shared.Humanoid.Markings;
|
||||
using Content.Shared.Preferences;
|
||||
using Content.Shared.Preferences.Loadouts;
|
||||
using Content.Shared.Roles;
|
||||
using Content.Shared.Traits;
|
||||
//using Content.Shared.Traits; // DV - Traits
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.Network;
|
||||
@@ -1924,8 +1924,60 @@ INSERT INTO player_round (players_id, rounds_id) VALUES ({players[player]}, {id}
|
||||
.Where(s => s.PlayerUserId == player)
|
||||
.ExecuteDeleteAsync();
|
||||
}
|
||||
#endregion
|
||||
|
||||
// BEGIN DeltaV - Discord / Patreon Account Linking
|
||||
#region DeltaV - Account Linking
|
||||
|
||||
public async Task<Guid?> GetLinkingCode(Guid player)
|
||||
{
|
||||
await using var db = await GetDb();
|
||||
var linking = await db.DbContext.DiscordLinkingCodes.FirstOrDefaultAsync(l => l.PlayerId == player);
|
||||
return linking?.Code;
|
||||
}
|
||||
|
||||
public async Task SetLinkingCode(Guid player, Guid code)
|
||||
{
|
||||
await using var db = await GetDb();
|
||||
var linking = await db.DbContext.DiscordLinkingCodes.FirstOrDefaultAsync(l => l.PlayerId == player);
|
||||
if (linking == null)
|
||||
{
|
||||
linking = new DiscordLinkingCodes { PlayerId = player };
|
||||
db.DbContext.DiscordLinkingCodes.Add(linking);
|
||||
}
|
||||
|
||||
linking.Code = code;
|
||||
linking.CreationTime = DateTime.UtcNow;
|
||||
await db.DbContext.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public async Task<bool> HasLinkedAccount(Guid player, CancellationToken cancel)
|
||||
{
|
||||
await using var db = await GetDb(cancel);
|
||||
return await db.DbContext.DiscordLinkedAccounts.AnyAsync(l => l.PlayerId == player, cancel);
|
||||
|
||||
}
|
||||
|
||||
public async Task<Patron?> GetPatron(Guid player, CancellationToken cancel)
|
||||
{
|
||||
await using var db = await GetDb(cancel);
|
||||
var patron = await db.DbContext.Patrons
|
||||
.Include(p => p.Tier)
|
||||
.FirstOrDefaultAsync(p => p.PlayerId == player, cancellationToken: cancel);
|
||||
return patron;
|
||||
}
|
||||
|
||||
public async Task<List<Patron>> GetAllPatrons()
|
||||
{
|
||||
await using var db = await GetDb();
|
||||
return await db.DbContext.Patrons
|
||||
.Include(p => p.Player)
|
||||
.Include(p => p.Tier)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
#endregion
|
||||
// END DeltaV
|
||||
|
||||
public abstract Task SendNotification(DatabaseNotification notification);
|
||||
|
||||
|
||||
@@ -354,6 +354,21 @@ namespace Content.Server.Database
|
||||
|
||||
#endregion
|
||||
|
||||
// BEGIN DeltaV - Discord / Patreon Account Linking
|
||||
#region DeltaV - Discord Linking
|
||||
|
||||
Task<Guid?> GetLinkingCode(Guid player);
|
||||
|
||||
Task SetLinkingCode(Guid player, Guid code);
|
||||
|
||||
Task<bool> HasLinkedAccount(Guid player, CancellationToken cancel);
|
||||
|
||||
Task<Patron?> GetPatron(Guid player, CancellationToken cancel);
|
||||
|
||||
Task<List<Patron>> GetAllPatrons();
|
||||
#endregion
|
||||
// END DeltaV
|
||||
|
||||
#region DB Notifications
|
||||
|
||||
void SubscribeToNotifications(Action<DatabaseNotification> handler);
|
||||
@@ -1127,6 +1142,41 @@ namespace Content.Server.Database
|
||||
}
|
||||
}
|
||||
|
||||
// BEGIN DeltaV - Discord Linking
|
||||
#region DeltaV - Discord Linking
|
||||
public Task<Guid?> GetLinkingCode(Guid player)
|
||||
{
|
||||
DbReadOpsMetric.Inc();
|
||||
return RunDbCommand(() => _db.GetLinkingCode(player));
|
||||
}
|
||||
|
||||
public Task SetLinkingCode(Guid player, Guid code)
|
||||
{
|
||||
DbWriteOpsMetric.Inc();
|
||||
return RunDbCommand(() => _db.SetLinkingCode(player, code));
|
||||
}
|
||||
|
||||
public Task<bool> HasLinkedAccount(Guid player, CancellationToken cancel)
|
||||
{
|
||||
DbReadOpsMetric.Inc();
|
||||
return RunDbCommand(() => _db.HasLinkedAccount(player, cancel));
|
||||
}
|
||||
|
||||
public Task<Patron?> GetPatron(Guid player, CancellationToken cancel)
|
||||
{
|
||||
DbReadOpsMetric.Inc();
|
||||
return RunDbCommand(() => _db.GetPatron(player, cancel));
|
||||
}
|
||||
|
||||
public Task<List<Patron>> GetAllPatrons()
|
||||
{
|
||||
DbReadOpsMetric.Inc();
|
||||
return RunDbCommand(() => _db.GetAllPatrons());
|
||||
}
|
||||
#endregion
|
||||
// END DeltaV
|
||||
|
||||
|
||||
// Wrapper functions to run DB commands from the thread pool.
|
||||
// This will avoid SynchronizationContext capturing and avoid running CPU work on the main thread.
|
||||
// For SQLite, this will also enable read parallelization (within limits).
|
||||
|
||||
@@ -382,6 +382,11 @@ public sealed partial class ExplosionSystem : SharedExplosionSystem
|
||||
|
||||
var visualEnt = CreateExplosionVisualEntity(pos, queued.Proto.ID, spaceMatrix, spaceData, gridData.Values, iterationIntensity);
|
||||
|
||||
// camera shake
|
||||
// ES START
|
||||
// CameraShake(iterationIntensity.Count * 4f, pos, queued.TotalIntensity);
|
||||
// ES END
|
||||
|
||||
//For whatever bloody reason, sound system requires ENTITY coordinates.
|
||||
var mapEntityCoords = _transformSystem.ToCoordinates(_map.GetMap(pos.MapId), pos);
|
||||
|
||||
@@ -411,7 +416,12 @@ public sealed partial class ExplosionSystem : SharedExplosionSystem
|
||||
? queued.Proto.SmallSoundFar
|
||||
: queued.Proto.SoundFar;
|
||||
|
||||
CameraShake(iterationIntensity, pos, queued); // Starlight
|
||||
// ES START
|
||||
var farTranslationShake = iterationIntensity.Count < queued.Proto.SmallSoundIterationThreshold
|
||||
? new ESScreenshakeParameters() { Trauma = 0.4f, DecayRate = 0.2f, Frequency = 0.014f }
|
||||
: new ESScreenshakeParameters() { Trauma = 0.6f, DecayRate = 0.05f, Frequency = 0.014f };
|
||||
_shake.Screenshake(filter, farTranslationShake, null);
|
||||
// ES END
|
||||
|
||||
_audio.PlayGlobal(farSound, farFilter, true, farSound.Params);
|
||||
|
||||
@@ -434,13 +444,8 @@ public sealed partial class ExplosionSystem : SharedExplosionSystem
|
||||
_damageableSystem);
|
||||
}
|
||||
|
||||
private void CameraShake(List<float> rangeList, MapCoordinates epicenter, QueuedExplosion queued) // Starlight - replace range with rangeList, totalIntensity with queued
|
||||
private void CameraShake(float range, MapCoordinates epicenter, float totalIntensity)
|
||||
{
|
||||
// Starlight BEGIN
|
||||
var range = rangeList.Count * 4f;
|
||||
var totalIntensity = queued.TotalIntensity * 10f;
|
||||
// Starlight END
|
||||
|
||||
var players = Filter.Empty();
|
||||
players.AddInRange(epicenter, range, _playerManager, EntityManager);
|
||||
|
||||
@@ -458,40 +463,7 @@ public sealed partial class ExplosionSystem : SharedExplosionSystem
|
||||
var distance = delta.Length();
|
||||
var effect = 5 * MathF.Pow(totalIntensity, 0.5f) * (1 - distance / range);
|
||||
if (effect > 0.01f)
|
||||
{
|
||||
// DeltaV - Camera kick falloff START
|
||||
// _recoilSystem.KickCamera(uid, -delta.Normalized() * effect);
|
||||
|
||||
// Exponential decay: N(t) = N₀ ⋅ e^(-λ ⋅ t)
|
||||
// In this case, N = effect and we aren't decaying over time but with increasing distance from the epicenter,
|
||||
// so t = distance / range. Higher values of λ lead to faster decay, lower values to slower decay.
|
||||
//
|
||||
// severity is a fixed factor used to decrease the overall severity of the camera kick,
|
||||
// as the values were so high by default that decay was only becoming really noticeable near range.
|
||||
//
|
||||
// these changes are made here instead of directly assigning to the effect variable
|
||||
// above to maintain the same overall effect range as before.
|
||||
const float severity = 0.033f;
|
||||
const float lambda = 4f;
|
||||
_recoilSystem.KickCamera(uid, -delta.Normalized() * effect * severity * MathF.Exp(-lambda * (distance / range)));
|
||||
// DeltaV END
|
||||
|
||||
// Starlight START
|
||||
var shakeParams = rangeList.Count < queued.Proto.SmallSoundIterationThreshold
|
||||
? new ESScreenshakeParameters() { Trauma = 0.4f, DecayRate = 0.2f, Frequency = 0.014f }
|
||||
: new ESScreenshakeParameters() { Trauma = 0.6f, DecayRate = 0.05f, Frequency = 0.014f };
|
||||
|
||||
// DeltaV - Screenshake falloff START
|
||||
// Linear falloff with increasing distance from epicenter,
|
||||
// capped at certain values to avoid diminishing the effect completely near range.
|
||||
shakeParams.DecayRate *= MathF.Min(1 + distance / range, 1.75f);
|
||||
shakeParams.Frequency *= MathF.Max(1 - distance / range, 0.33f);
|
||||
shakeParams.Trauma *= MathF.Max(1 - distance / range, 0.33f);
|
||||
// DeltaV END
|
||||
|
||||
_shake.Screenshake(players, shakeParams, null);
|
||||
// Starlight END
|
||||
}
|
||||
_recoilSystem.KickCamera(uid, -delta.Normalized() * effect);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@ using Content.Shared.Database;
|
||||
using Content.Shared.DeviceNetwork;
|
||||
using Content.Shared.DeviceNetwork.Components;
|
||||
using Content.Shared.DeviceNetwork.Events;
|
||||
using Content.Shared.DeviceNetwork.Systems; // DeltaV - map init ordering
|
||||
using Content.Shared.Emag.Systems;
|
||||
using Content.Shared.Fax;
|
||||
using Content.Shared.Fax.Components;
|
||||
@@ -65,7 +64,7 @@ public sealed class FaxSystem : EntitySystem
|
||||
|
||||
// Hooks
|
||||
SubscribeLocalEvent<FaxMachineComponent, ComponentInit>(OnComponentInit);
|
||||
SubscribeLocalEvent<FaxMachineComponent, MapInitEvent>(OnMapInit, after: [typeof(SharedDeviceNetworkSystem)]); // DeltaV - map init order, we need address assigned first
|
||||
SubscribeLocalEvent<FaxMachineComponent, MapInitEvent>(OnMapInit);
|
||||
SubscribeLocalEvent<FaxMachineComponent, ComponentRemove>(OnComponentRemove);
|
||||
|
||||
SubscribeLocalEvent<FaxMachineComponent, EntInsertedIntoContainerMessage>(OnItemSlotChanged);
|
||||
@@ -302,11 +301,6 @@ public sealed class FaxSystem : EntitySystem
|
||||
if (!args.Data.TryGetValue(FaxConstants.FaxPaperNameData, out string? name) ||
|
||||
!args.Data.TryGetValue(FaxConstants.FaxPaperContentData, out string? content))
|
||||
return;
|
||||
// Begin DeltaV - we removed the power requirement from device network but we still don't want
|
||||
// unpowered faxes to happen
|
||||
if (TryComp<ApcPowerReceiverComponent>(uid, out var receiver) && !receiver.Powered)
|
||||
return;
|
||||
// End DeltaV
|
||||
|
||||
args.Data.TryGetValue(FaxConstants.FaxPaperLabelData, out string? label);
|
||||
args.Data.TryGetValue(FaxConstants.FaxPaperStampStateData, out string? stampState);
|
||||
@@ -588,15 +582,6 @@ public sealed class FaxSystem : EntitySystem
|
||||
return;
|
||||
|
||||
var faxName = Loc.GetString("fax-machine-popup-source-unknown");
|
||||
|
||||
// BEGIN DeltaV - Refresh Fax IDs before recieving
|
||||
// This will seem odd that we're doing the same if statement twice BUT if its not in the known fax machine list, the
|
||||
// sending fax might not have been online when the current fax machine was refreshed/init'd. So, if its already known,
|
||||
// we don't need to refresh. But if its not known, let's attempt to refresh first, then check the fromAddress in the KnownFaxes.
|
||||
if (fromAddress != null && !component.KnownFaxes.TryGetValue(fromAddress, out var _))
|
||||
Refresh(uid, component);
|
||||
// END DeltaV
|
||||
|
||||
if (fromAddress != null && component.KnownFaxes.TryGetValue(fromAddress, out var fax)) // If message received from unknown fax address
|
||||
faxName = fax;
|
||||
|
||||
|
||||
@@ -35,17 +35,17 @@ public sealed partial class PuddleSystem
|
||||
if (!entity.Comp.SpillWhenThrown || Openable.IsClosed(entity.Owner))
|
||||
return;
|
||||
|
||||
// DeltaV - Beer Goggles Safe Throw
|
||||
if ( args.User is { } user && _safeSolutionThrower.GetSafeThrow(user))
|
||||
{
|
||||
_physics.SetAngularVelocity(entity, 0);
|
||||
Transform(entity).LocalRotation = Angle.Zero;
|
||||
return;
|
||||
}
|
||||
// END DeltaV
|
||||
|
||||
if (TrySplashSpillAt(entity.Owner, Transform(entity).Coordinates, out _, out var solution) && args.User != null)
|
||||
{
|
||||
// DeltaV - Beer Goggles Safe Throw
|
||||
if (_safeSolutionThrower.GetSafeThrow(args.User.Value))
|
||||
{
|
||||
_physics.SetAngularVelocity(entity, 0);
|
||||
Transform(entity).LocalRotation = Angle.Zero;
|
||||
return;
|
||||
}
|
||||
// END DeltaV
|
||||
|
||||
AdminLogger.Add(LogType.Landed,
|
||||
$"{ToPrettyString(entity.Owner):entity} spilled a solution {SharedSolutionContainerSystem.ToPrettyString(solution):solution} on landing");
|
||||
}
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
using Content.Shared.Administration;
|
||||
using Robust.Shared.Console;
|
||||
|
||||
namespace Content.Server.Ghost;
|
||||
|
||||
[AnyCommand]
|
||||
internal sealed partial class GhostFollowEntityCommand : LocalizedEntityCommands
|
||||
{
|
||||
public const string CommandName = "ghost_follow_entity";
|
||||
|
||||
[Dependency] private GhostSystem _ghost = null!;
|
||||
|
||||
public override string Command => CommandName;
|
||||
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
if (args.Length != 1 || shell.Player is not { } player)
|
||||
return;
|
||||
|
||||
var target = args[0];
|
||||
if (!NetEntity.TryParse(target, out var targetEnt))
|
||||
return;
|
||||
|
||||
_ghost.GhostWarpRequest(player, targetEnt);
|
||||
}
|
||||
}
|
||||
@@ -299,22 +299,10 @@ namespace Content.Server.Ghost
|
||||
|
||||
#region Warp
|
||||
|
||||
public bool CanGhostWarp(ICommonSession session, out EntityUid entity)
|
||||
{
|
||||
if (session.AttachedEntity is not { Valid: true } sessionEntity
|
||||
|| !_ghostQuery.HasComp(sessionEntity))
|
||||
{
|
||||
entity = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
entity = sessionEntity;
|
||||
return true;
|
||||
}
|
||||
|
||||
private void OnGhostWarpsRequest(GhostWarpsRequestEvent msg, EntitySessionEventArgs args)
|
||||
{
|
||||
if (!CanGhostWarp(args.SenderSession, out var entity))
|
||||
if (args.SenderSession.AttachedEntity is not {Valid: true} entity
|
||||
|| !_ghostQuery.HasComp(entity))
|
||||
{
|
||||
Log.Warning($"User {args.SenderSession.Name} sent a {nameof(GhostWarpsRequestEvent)} without being a ghost.");
|
||||
return;
|
||||
@@ -324,33 +312,30 @@ namespace Content.Server.Ghost
|
||||
RaiseNetworkEvent(response, args.SenderSession.Channel);
|
||||
}
|
||||
|
||||
public void GhostWarpRequest(ICommonSession player, NetEntity target)
|
||||
{
|
||||
if (!CanGhostWarp(player, out var attached))
|
||||
{
|
||||
Log.Warning($"User {player.Name} tried to warp to {target} without being a ghost.");
|
||||
return;
|
||||
}
|
||||
|
||||
var realTarget = GetEntity(target);
|
||||
|
||||
if (!Exists(realTarget))
|
||||
{
|
||||
Log.Warning($"User {player.Name} tried to warp to an invalid entity id: {target}");
|
||||
return;
|
||||
}
|
||||
|
||||
WarpTo(attached, realTarget);
|
||||
}
|
||||
|
||||
private void OnGhostWarpToTargetRequest(GhostWarpToTargetRequestEvent msg, EntitySessionEventArgs args)
|
||||
{
|
||||
GhostWarpRequest(args.SenderSession, msg.Target);
|
||||
if (args.SenderSession.AttachedEntity is not {Valid: true} attached
|
||||
|| !_ghostQuery.HasComp(attached))
|
||||
{
|
||||
Log.Warning($"User {args.SenderSession.Name} tried to warp to {msg.Target} without being a ghost.");
|
||||
return;
|
||||
}
|
||||
|
||||
var target = GetEntity(msg.Target);
|
||||
|
||||
if (!Exists(target))
|
||||
{
|
||||
Log.Warning($"User {args.SenderSession.Name} tried to warp to an invalid entity id: {msg.Target}");
|
||||
return;
|
||||
}
|
||||
|
||||
WarpTo(attached, target);
|
||||
}
|
||||
|
||||
private void OnGhostnadoRequest(GhostnadoRequestEvent msg, EntitySessionEventArgs args)
|
||||
{
|
||||
if (CanGhostWarp(args.SenderSession, out var uid))
|
||||
if (args.SenderSession.AttachedEntity is not {} uid
|
||||
|| !_ghostQuery.HasComp(uid))
|
||||
{
|
||||
Log.Warning($"User {args.SenderSession.Name} tried to ghostnado without being a ghost.");
|
||||
return;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using Content.Server._DV.AccountLinking; // DeltaV
|
||||
using Content.Server.Administration;
|
||||
using Content.Server.Administration.Logs;
|
||||
using Content.Server.Administration.Managers;
|
||||
@@ -81,5 +82,6 @@ internal static class ServerContentIoC
|
||||
deps.Register<CVarControlManager>();
|
||||
deps.Register<DiscordLink>();
|
||||
deps.Register<DiscordChatLink>();
|
||||
deps.Register<LinkAccountManager>(); // DeltaV - Discord Account Linking
|
||||
}
|
||||
}
|
||||
|
||||
@@ -211,7 +211,7 @@ namespace Content.Server.Medical.BiomassReclaimer
|
||||
_solution.ResolveSolution(toProcess, stream.BloodSolutionName, ref stream.BloodSolution, out var solution))
|
||||
{
|
||||
component.BloodReagents = solution.Clone();
|
||||
//component.BloodReagents.ScaleSolution(50 / component.BloodReagents.Volume); // Delta V - This doesn't need to be here. It just always makes the solution ~50u but also might divide by 0. Just use the current blood level so more blood = more mess.
|
||||
component.BloodReagents.ScaleSolution(50 / component.BloodReagents.Volume);
|
||||
}
|
||||
if (TryComp<ButcherableComponent>(toProcess, out var butcherableComponent))
|
||||
{
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
using Content.Server.Administration.Logs;
|
||||
using Content.Server.Chat.Managers;
|
||||
using Content.Server.Chat.Systems;
|
||||
using Content.Server.Ghost;
|
||||
using Content.Server.Power.Components;
|
||||
using Content.Shared._DV.Chat;
|
||||
using Content.Shared.Chat;
|
||||
@@ -30,8 +28,6 @@ public sealed class RadioSystem : EntitySystem
|
||||
[Dependency] private readonly IPrototypeManager _prototype = default!;
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly ChatSystem _chat = default!;
|
||||
[Dependency] private readonly IChatManager _chatManager = default!;
|
||||
[Dependency] private readonly GhostSystem _ghost = default!;
|
||||
|
||||
// set used to prevent radio feedback loops.
|
||||
private readonly HashSet<string> _messages = new();
|
||||
@@ -59,25 +55,8 @@ public sealed class RadioSystem : EntitySystem
|
||||
|
||||
private void OnIntrinsicReceive(EntityUid uid, IntrinsicRadioReceiverComponent component, ref RadioReceiveEvent args)
|
||||
{
|
||||
if (!TryComp(uid, out ActorComponent? actor))
|
||||
return;
|
||||
|
||||
var msg = args.ChatMsg;
|
||||
if (_ghost.CanGhostWarp(actor.PlayerSession, out _))
|
||||
{
|
||||
msg = new MsgChatMessage
|
||||
{
|
||||
Message = new ChatMessage(args.ChatMsg.Message)
|
||||
{
|
||||
WrappedMessage = _chatManager.PrependFollowButtonIfAppropriate(
|
||||
args.ChatMsg.Message.WrappedMessage,
|
||||
args.MessageSource,
|
||||
actor.PlayerSession.Channel),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
_netMan.ServerSendMessage(msg, actor.PlayerSession.Channel);
|
||||
if (TryComp(uid, out ActorComponent? actor))
|
||||
_netMan.ServerSendMessage(args.ChatMsg, actor.PlayerSession.Channel);
|
||||
}
|
||||
|
||||
// DeltaV
|
||||
@@ -209,7 +188,7 @@ public sealed class RadioSystem : EntitySystem
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="TelecomServerComponent"/>
|
||||
public bool HasActiveServer(MapId mapId, string channelId) // DeltaV - we need this
|
||||
private bool HasActiveServer(MapId mapId, string channelId)
|
||||
{
|
||||
var servers = EntityQuery<TelecomServerComponent, EncryptionKeyHolderComponent, ApcPowerReceiverComponent, TransformComponent>();
|
||||
foreach (var (_, keys, power, transform) in servers)
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using System.Linq; // DeltaV
|
||||
using System.Numerics;
|
||||
using Content.Server.Atmos.EntitySystems;
|
||||
using Content.Server.Chat.Systems;
|
||||
@@ -9,8 +8,6 @@ using Content.Server.Popups;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Chat;
|
||||
using Content.Shared.Dataset;
|
||||
using Content.Shared.Mobs; // DeltaV
|
||||
using Content.Shared.Mobs.Components; // DeltaV
|
||||
using Content.Shared.Nutrition.Components;
|
||||
using Content.Shared.Nutrition.EntitySystems;
|
||||
using Content.Shared.Pointing;
|
||||
@@ -50,33 +47,14 @@ namespace Content.Server.RatKing
|
||||
if (!TryComp<HungerComponent>(uid, out var hunger))
|
||||
return;
|
||||
|
||||
// DeltaV - modify cost of Raise Army based on the amount of alive servants
|
||||
// Check on how many servants are alive to calculate the cost of a new servant
|
||||
var aliveServants = component.Servants.Count(servant
|
||||
=> TryComp<MobStateComponent>(servant, out var mobState) && mobState.CurrentState == MobState.Alive);
|
||||
|
||||
// calculate the cost multiplier
|
||||
var multiplier = aliveServants switch
|
||||
{
|
||||
< 5 => 1.0f, // 1-5 servants: 10 hunger
|
||||
< 10 => 1.5f, // 6-10 servants: 15 hunger
|
||||
< 15 => 2.5f, // 11-15 servants: 25 hunger
|
||||
< 20 => 5.0f, // 16-20 servants: 50 hunger
|
||||
< 25 => 10.0f, // 21-25 servants: 100 hunger
|
||||
_ => 15.0f, // Above 25 servants: 150 hunger
|
||||
};
|
||||
var hungerPerArmyUseAdjusted = component.HungerPerArmyUse * multiplier;
|
||||
|
||||
//make sure the hunger doesn't go into the negatives
|
||||
if (_hunger.GetHunger(hunger) < hungerPerArmyUseAdjusted)
|
||||
if (_hunger.GetHunger(hunger) < component.HungerPerArmyUse)
|
||||
{
|
||||
_popup.PopupEntity(Loc.GetString("rat-king-too-hungry"), uid, uid);
|
||||
return;
|
||||
}
|
||||
args.Handled = true;
|
||||
_hunger.ModifyHunger(uid, - hungerPerArmyUseAdjusted, hunger);
|
||||
// DeltaV - end to the modified cost of Raise Army
|
||||
|
||||
_hunger.ModifyHunger(uid, -component.HungerPerArmyUse, hunger);
|
||||
var servant = Spawn(component.ArmyMobSpawnId, Transform(uid).Coordinates);
|
||||
var comp = EnsureComp<RatKingServantComponent>(servant);
|
||||
comp.King = uid;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using Content.Server._DV.Psionics.Systems;
|
||||
using Content.Server.Administration.Managers;
|
||||
using Content.Server.Atmos.Components;
|
||||
using Content.Server.Body.Components;
|
||||
@@ -13,6 +14,7 @@ using Content.Server.NPC.HTN;
|
||||
using Content.Server.NPC.Systems;
|
||||
using Content.Server.StationEvents.Components;
|
||||
using Content.Server.Speech.Components;
|
||||
using Content.Shared._DV.Psionics.Components; // DeltaV
|
||||
using Content.Shared.Body.Components;
|
||||
using Content.Shared.CombatMode;
|
||||
using Content.Shared.CombatMode.Pacification;
|
||||
@@ -25,6 +27,7 @@ using Content.Shared.Mobs.Components;
|
||||
using Content.Shared.Movement.Pulling.Components;
|
||||
using Content.Shared.Movement.Systems;
|
||||
using Content.Shared.NameModifier.EntitySystems;
|
||||
using Content.Shared.NPC.Components; // DeltaV
|
||||
using Content.Shared.NPC.Systems;
|
||||
using Content.Shared.Nutrition.AnimalHusbandry;
|
||||
using Content.Shared.Nutrition.Components;
|
||||
@@ -69,6 +72,7 @@ public sealed partial class ZombieSystem
|
||||
[Dependency] private readonly NPCSystem _npc = default!;
|
||||
[Dependency] private readonly TagSystem _tag = default!;
|
||||
[Dependency] private readonly ISharedPlayerManager _player = default!;
|
||||
[Dependency] private readonly PsionicSystem _psionic = default!; // DeltaV
|
||||
|
||||
private static readonly ProtoId<TagPrototype> InvalidForGlobalSpawnSpellTag = "InvalidForGlobalSpawnSpell";
|
||||
private static readonly ProtoId<TagPrototype> CannotSuicideTag = "CannotSuicide";
|
||||
@@ -130,9 +134,6 @@ public sealed partial class ZombieSystem
|
||||
//you're a real zombie now, son.
|
||||
var zombiecomp = AddComp<ZombieComponent>(target);
|
||||
|
||||
// DeltaV - Save factions, psionics, etc. before modifications are made.
|
||||
PreserveEntityComponentState((target, zombiecomp));
|
||||
|
||||
//we need to basically remove all of these because zombies shouldn't
|
||||
//get diseases, breath, be thirst, be hungry, die in space, get double sentience, have offspring or be paraplegic.
|
||||
RemComp<RespiratorComponent>(target);
|
||||
@@ -145,6 +146,12 @@ public sealed partial class ZombieSystem
|
||||
RemComp<ComplexInteractionComponent>(target);
|
||||
RemComp<SentienceTargetComponent>(target);
|
||||
|
||||
// DeltaV Start - Prevent Psionic Zombies
|
||||
RemComp<PotentialPsionicComponent>(target);
|
||||
if (HasComp<PsionicComponent>(target))
|
||||
_psionic.MindBreakEntity(target, false, true);
|
||||
// DeltaV End - Prevent Psionic Zombies
|
||||
|
||||
//funny voice
|
||||
var accentType = "zombie";
|
||||
if (TryComp<ZombieAccentOverrideComponent>(target, out var accent))
|
||||
@@ -238,9 +245,6 @@ public sealed partial class ZombieSystem
|
||||
//Should prevent instances of zombies using comms for information they shouldnt be able to have.
|
||||
_inventory.TryUnequip(target, "ears", true, true);
|
||||
|
||||
// DeltaV - Extra zombification removals, etc.
|
||||
ZombifyEntityDV((target, zombiecomp), mobState);
|
||||
|
||||
//popup
|
||||
_popup.PopupEntity(Loc.GetString("zombie-transform", ("target", target)), target, PopupType.LargeCaution);
|
||||
|
||||
@@ -257,6 +261,7 @@ public sealed partial class ZombieSystem
|
||||
|
||||
_faction.ClearFactions(target, dirty: false);
|
||||
_faction.AddFaction(target, ZombieFaction);
|
||||
EnsureComp<NoFriendlyFireComponent>(target); // DeltaV - prevent shitters biting other zombies
|
||||
|
||||
//gives it the funny "Zombie ___" name.
|
||||
_nameMod.RefreshNameModifiers(target);
|
||||
|
||||
@@ -78,8 +78,6 @@ namespace Content.Server.Zombies
|
||||
SubscribeLocalEvent<IncurableZombieComponent, MapInitEvent>(OnPendingMapInit);
|
||||
|
||||
SubscribeLocalEvent<ZombifyOnDeathComponent, MobStateChangedEvent>(OnDamageChanged);
|
||||
|
||||
InitializeDV(); // DeltaV - Additional Subscriptions for ZombieComponent
|
||||
}
|
||||
|
||||
private void OnBeforeRemoveAnomalyOnDeath(Entity<PendingZombieComponent> ent, ref BeforeRemoveAnomalyOnDeathEvent args)
|
||||
@@ -327,7 +325,7 @@ namespace Content.Server.Zombies
|
||||
// Remove the role when getting cloned, getting gibbed and borged, or leaving the body via any other method.
|
||||
private void OnMindRemoved(Entity<ZombieComponent> ent, ref MindRemovedMessage args)
|
||||
{
|
||||
_role.MindRemoveRole<ZombieRoleComponent>((args.Mind.Owner, args.Mind.Comp));
|
||||
_role.MindRemoveRole<ZombieRoleComponent>((args.Mind.Owner, args.Mind.Comp));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Content.Server.Database;
|
||||
using Content.Shared._DV.AccountLinking;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Server._DV.AccountLinking;
|
||||
|
||||
public sealed class LinkAccountManager : IPostInjectInit
|
||||
{
|
||||
[Dependency] private readonly IServerDbManager _db = default!;
|
||||
[Dependency] private readonly INetManager _net = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly UserDbDataManager _userDb = default!;
|
||||
|
||||
private readonly Dictionary<NetUserId, TimeSpan> _lastRequest = new();
|
||||
private readonly TimeSpan _minimumWait = TimeSpan.FromSeconds(0.5);
|
||||
private readonly Dictionary<NetUserId, SharedPatronFull> _connected = new();
|
||||
private readonly List<SharedPatron> _allPatrons = [];
|
||||
|
||||
private async Task LoadData(ICommonSession player, CancellationToken cancel)
|
||||
{
|
||||
var patron = await _db.GetPatron(player.UserId, cancel);
|
||||
var linked = await _db.HasLinkedAccount(player.UserId, cancel);
|
||||
cancel.ThrowIfCancellationRequested();
|
||||
|
||||
var tier = patron?.Tier;
|
||||
var sharedTier = tier == null
|
||||
? null
|
||||
: new SharedPatronTier(
|
||||
tier.ShowOnCredits,
|
||||
tier.Name
|
||||
);
|
||||
|
||||
_connected[player.UserId] = new SharedPatronFull(sharedTier, linked);
|
||||
}
|
||||
|
||||
private void FinishLoad(ICommonSession player)
|
||||
{
|
||||
SendPatronStatus(player);
|
||||
}
|
||||
|
||||
private void ClientDisconnected(ICommonSession player)
|
||||
{
|
||||
_connected.Remove(player.UserId);
|
||||
}
|
||||
|
||||
private void SendPatronStatus(ICommonSession player)
|
||||
{
|
||||
var connected = _connected.GetValueOrDefault(player.UserId);
|
||||
var msg = new LinkAccountStatusMsg { Patron = connected, };
|
||||
_net.ServerSendMessage(msg, player.Channel);
|
||||
SendPatrons(player);
|
||||
}
|
||||
|
||||
private void OnRequest(LinkAccountRequestMsg message)
|
||||
{
|
||||
var user = message.MsgChannel.UserId;
|
||||
var time = _timing.RealTime;
|
||||
if (_lastRequest.TryGetValue(user, out var last) &&
|
||||
last + _minimumWait > time)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_lastRequest[user] = time;
|
||||
|
||||
var code = Guid.NewGuid();
|
||||
_db.SetLinkingCode(user, code);
|
||||
|
||||
var response = new LinkAccountCodeMsg { Code = code };
|
||||
_net.ServerSendMessage(response, message.MsgChannel);
|
||||
}
|
||||
|
||||
public async Task RefreshAllPatrons()
|
||||
{
|
||||
var patrons = await _db.GetAllPatrons();
|
||||
|
||||
_allPatrons.Clear();
|
||||
foreach (var patron in patrons)
|
||||
{
|
||||
_allPatrons.Add(new SharedPatron(patron.Player.LastSeenUserName, patron.Tier.Name));
|
||||
}
|
||||
}
|
||||
|
||||
public void SendPatronsToAll()
|
||||
{
|
||||
var msg = new PatronListMsg { Patrons = _allPatrons };
|
||||
_net.ServerSendToAll(msg);
|
||||
}
|
||||
|
||||
public void SendPatrons(ICommonSession player)
|
||||
{
|
||||
var msg = new PatronListMsg { Patrons = _allPatrons };
|
||||
_net.ServerSendMessage(msg, player.Channel);
|
||||
}
|
||||
|
||||
public SharedPatronFull? GetPatron(ICommonSession player)
|
||||
{
|
||||
return GetPatron(player.UserId);
|
||||
}
|
||||
|
||||
public SharedPatronFull? GetPatron(NetUserId userId)
|
||||
{
|
||||
return _connected.GetValueOrDefault(userId);
|
||||
}
|
||||
|
||||
void IPostInjectInit.PostInject()
|
||||
{
|
||||
_net.RegisterNetMessage<LinkAccountRequestMsg>(OnRequest);
|
||||
_net.RegisterNetMessage<LinkAccountCodeMsg>();
|
||||
_net.RegisterNetMessage<LinkAccountStatusMsg>();
|
||||
_net.RegisterNetMessage<PatronListMsg>();
|
||||
_userDb.AddOnLoadPlayer(LoadData);
|
||||
_userDb.AddOnFinishLoad(FinishLoad);
|
||||
_userDb.AddOnPlayerDisconnect(ClientDisconnected);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
using Content.Server.GameTicking;
|
||||
|
||||
namespace Content.Server._DV.AccountLinking;
|
||||
|
||||
public sealed class LinkAccountSystem : EntitySystem
|
||||
{
|
||||
|
||||
[Dependency] private readonly LinkAccountManager _linkAccount = default!;
|
||||
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<GameRunLevelChangedEvent>(OnGameRunLevelChanged);
|
||||
|
||||
ReloadPatrons();
|
||||
}
|
||||
|
||||
private void OnGameRunLevelChanged(GameRunLevelChangedEvent ev)
|
||||
{
|
||||
switch (ev.New)
|
||||
{
|
||||
case GameRunLevel.InRound:
|
||||
break;
|
||||
case GameRunLevel.PreRoundLobby:
|
||||
case GameRunLevel.PostRound:
|
||||
ReloadPatrons();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private async void ReloadPatrons()
|
||||
{
|
||||
try
|
||||
{
|
||||
await _linkAccount.RefreshAllPatrons();
|
||||
_linkAccount.SendPatronsToAll();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error($"Error reloading Patrons list:\n{e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,12 @@ namespace Content.Server._DV.CartridgeLoader.Cartridges;
|
||||
[RegisterComponent, Access(typeof(NanoChatCartridgeSystem))]
|
||||
public sealed partial class NanoChatCartridgeComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Station entity to keep track of.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public EntityUid? Station;
|
||||
|
||||
/// <summary>
|
||||
/// The NanoChat card to keep track of.
|
||||
/// </summary>
|
||||
|
||||
@@ -2,8 +2,9 @@ using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using Content.Server.Administration.Logs;
|
||||
using Content.Server.CartridgeLoader;
|
||||
using Content.Server.Power.Components;
|
||||
using Content.Server.Radio;
|
||||
using Content.Server.Radio.EntitySystems;
|
||||
using Content.Server.Station.Systems;
|
||||
using Content.Shared.Access.Components;
|
||||
using Content.Shared.CartridgeLoader;
|
||||
using Content.Shared.CCVar;
|
||||
@@ -29,7 +30,7 @@ public sealed class NanoChatCartridgeSystem : EntitySystem
|
||||
[Dependency] private readonly IPrototypeManager _prototype = default!;
|
||||
[Dependency] private readonly SharedNanoChatSystem _nanoChat = default!;
|
||||
[Dependency] private readonly SharedUserInterfaceSystem _ui = default!;
|
||||
[Dependency] private readonly RadioSystem _radio = default!;
|
||||
[Dependency] private readonly StationSystem _station = default!;
|
||||
|
||||
private EntityQuery<PdaComponent> _pdaQuery;
|
||||
private EntityQuery<NanoChatCardComponent> _cardQuery;
|
||||
@@ -455,7 +456,9 @@ public sealed class NanoChatCartridgeSystem : EntitySystem
|
||||
{
|
||||
// First verify we can send from this device
|
||||
var channel = _prototype.Index(sender.Comp.RadioChannel);
|
||||
if (!CanSend(sender))
|
||||
var sendAttemptEvent = new RadioSendAttemptEvent(channel, sender);
|
||||
RaiseLocalEvent(ref sendAttemptEvent);
|
||||
if (sendAttemptEvent.Cancelled)
|
||||
return (true, new List<Entity<NanoChatCardComponent>>());
|
||||
|
||||
var foundRecipients = new List<Entity<NanoChatCardComponent>>();
|
||||
@@ -484,19 +487,26 @@ public sealed class NanoChatCartridgeSystem : EntitySystem
|
||||
if (receiverCart.Card != recipient.Owner)
|
||||
continue;
|
||||
|
||||
var receiverMapId = Transform(receiverUid).MapID;
|
||||
var senderMapId = Transform(sender).MapID;
|
||||
// Check if devices are on same station/map
|
||||
var recipientStation = _station.GetOwningStation(receiverUid);
|
||||
var senderStation = _station.GetOwningStation(sender);
|
||||
|
||||
// Must be on the same map unless long range is allowed.
|
||||
if (!channel.LongRange && receiverMapId != senderMapId)
|
||||
// Both entities must be on a station
|
||||
if (recipientStation == null || senderStation == null)
|
||||
continue;
|
||||
|
||||
// Check if telecommunications are active on both ends
|
||||
if (!_radio.HasActiveServer(receiverMapId, receiverCart.RadioChannel) || !_radio.HasActiveServer(senderMapId, sender.Comp.RadioChannel))
|
||||
// Must be on same map/station unless long range allowed
|
||||
if (!channel.LongRange && recipientStation != senderStation)
|
||||
continue;
|
||||
|
||||
// Needs telecomms
|
||||
if (!HasActiveServer(senderStation.Value) || !HasActiveServer(recipientStation.Value))
|
||||
continue;
|
||||
|
||||
// Check if recipient can receive
|
||||
if (!CanReceive(sender, receiverUid))
|
||||
var receiveAttemptEv = new RadioReceiveAttemptEvent(channel, sender, receiverUid);
|
||||
RaiseLocalEvent(ref receiveAttemptEv);
|
||||
if (receiveAttemptEv.Cancelled)
|
||||
continue;
|
||||
|
||||
// Found valid cartridge that can receive
|
||||
@@ -509,26 +519,21 @@ public sealed class NanoChatCartridgeSystem : EntitySystem
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests if a NanoChat cartridge can send messages
|
||||
/// Checks if there are any active telecomms servers on the given station
|
||||
/// </summary>
|
||||
/// <param name="sender">The NanoChat cartridge trying to send</param>
|
||||
private bool CanSend(Entity<NanoChatCartridgeComponent> sender)
|
||||
private bool HasActiveServer(EntityUid station)
|
||||
{
|
||||
var sendAttemptEvent = new RadioSendAttemptEvent(_prototype.Index(sender.Comp.RadioChannel), sender);
|
||||
RaiseLocalEvent(ref sendAttemptEvent);
|
||||
return !sendAttemptEvent.Cancelled;
|
||||
}
|
||||
// I have no idea why this isn't public in the RadioSystem
|
||||
var query =
|
||||
EntityQueryEnumerator<TelecomServerComponent, EncryptionKeyHolderComponent, ApcPowerReceiverComponent>();
|
||||
|
||||
/// <summary>
|
||||
/// Tests if a receiver can receive from a given NanoChat cartridge
|
||||
/// </summary>
|
||||
/// <param name="sender">The NanoChat cartridge trying to send</param>
|
||||
/// <param name="receiver">The receiver cartridge trying to receive</param>
|
||||
private bool CanReceive(Entity<NanoChatCartridgeComponent> sender, EntityUid receiver)
|
||||
{
|
||||
var receiveAttemptEv = new RadioReceiveAttemptEvent(_prototype.Index(sender.Comp.RadioChannel), sender, receiver);
|
||||
RaiseLocalEvent(ref receiveAttemptEv);
|
||||
return !receiveAttemptEv.Cancelled;
|
||||
while (query.MoveNext(out var uid, out _, out _, out var power))
|
||||
{
|
||||
if (_station.GetOwningStation(uid) == station && power.Powered)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -683,15 +688,16 @@ public sealed class NanoChatCartridgeSystem : EntitySystem
|
||||
private void UpdateUI(Entity<NanoChatCartridgeComponent> ent, EntityUid loader)
|
||||
{
|
||||
List<NanoChatRecipient>? contacts;
|
||||
|
||||
if (CanSend(ent) && _radio.HasActiveServer(Transform(ent).MapID, ent.Comp.RadioChannel))
|
||||
if (_station.GetOwningStation(loader) is { } station)
|
||||
{
|
||||
ent.Comp.Station = station;
|
||||
|
||||
contacts = [];
|
||||
|
||||
var query = AllEntityQuery<NanoChatCardComponent, IdCardComponent>();
|
||||
while (query.MoveNext(out var entityId, out var nanoChatCard, out var idCardComponent))
|
||||
{
|
||||
if (nanoChatCard.ListNumber && nanoChatCard.Number is uint nanoChatNumber && idCardComponent.FullName is string fullName)
|
||||
if (nanoChatCard.ListNumber && nanoChatCard.Number is uint nanoChatNumber && idCardComponent.FullName is string fullName && _station.GetOwningStation(entityId) == station)
|
||||
{
|
||||
contacts.Add(new NanoChatRecipient(nanoChatNumber, fullName));
|
||||
}
|
||||
@@ -700,7 +706,7 @@ public sealed class NanoChatCartridgeSystem : EntitySystem
|
||||
var borgQuery = AllEntityQuery<NanoChatCardComponent, BorgChassisComponent>();
|
||||
while (borgQuery.MoveNext(out var borgId, out var borgChatCard, out var _))
|
||||
{
|
||||
if (borgChatCard.ListNumber && borgChatCard.Number is uint nanoChatNumber)
|
||||
if (borgChatCard.ListNumber && borgChatCard.Number is uint nanoChatNumber && _station.GetOwningStation(borgId) == station)
|
||||
{
|
||||
contacts.Add(new NanoChatRecipient(nanoChatNumber, MetaData(borgId).EntityName));
|
||||
}
|
||||
@@ -709,7 +715,7 @@ public sealed class NanoChatCartridgeSystem : EntitySystem
|
||||
var aiQuery = AllEntityQuery<NanoChatCardComponent, StationAiHeldComponent>();
|
||||
while (aiQuery.MoveNext(out var aiId, out var aiChatCard, out var _))
|
||||
{
|
||||
if (aiChatCard.ListNumber && aiChatCard.Number is uint nanoChatNumber)
|
||||
if (aiChatCard.ListNumber && aiChatCard.Number is uint nanoChatNumber && _station.GetOwningStation(aiId) == station)
|
||||
{
|
||||
contacts.Add(new NanoChatRecipient(nanoChatNumber, MetaData(aiId).EntityName));
|
||||
}
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
using Content.Server._DV.CosmicCult.Components;
|
||||
using Content.Shared._DV.CosmicCult.Components;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Damage.Components;
|
||||
using Content.Shared.Damage.Systems;
|
||||
using Content.Shared.Weapons.Melee;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server._DV.CosmicCult.Abilities.Colossus;
|
||||
|
||||
public sealed class CosmicColossusBuffsSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly DamageableSystem _damage = default!;
|
||||
[Dependency] private readonly IPrototypeManager _proto = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<MultiplyAttackRateOnSupercriticalComponent, CosmicColossusEffigySupercriticalEvent>(HandleAttackRate);
|
||||
SubscribeLocalEvent<FlatAttackBonusOnSupercriticalComponent, CosmicColossusEffigySupercriticalEvent>(HandleDamageBonus);
|
||||
SubscribeLocalEvent<MultiplyCorruptingSpeedOnSupercriticalComponent, CosmicColossusEffigySupercriticalEvent>(HandleCorruptingSpeed);
|
||||
SubscribeLocalEvent<HealOnSupercriticalComponent, CosmicColossusEffigySupercriticalEvent>(HandleHeal);
|
||||
}
|
||||
|
||||
private void HandleAttackRate(Entity<MultiplyAttackRateOnSupercriticalComponent> ent,
|
||||
ref CosmicColossusEffigySupercriticalEvent args)
|
||||
{
|
||||
if (TryComp<MeleeWeaponComponent>(ent, out var weapon))
|
||||
{
|
||||
weapon.AttackRate *= ent.Comp.Multiplier;
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleDamageBonus(Entity<FlatAttackBonusOnSupercriticalComponent> ent,
|
||||
ref CosmicColossusEffigySupercriticalEvent args)
|
||||
{
|
||||
if (!TryComp<CosmicColossusComponent>(ent, out var colossusComp))
|
||||
return;
|
||||
|
||||
colossusComp.BonusDamage +=
|
||||
new DamageSpecifier(_proto.Index(ent.Comp.BonusDamageType), ent.Comp.BonusDamage);
|
||||
}
|
||||
|
||||
private void HandleCorruptingSpeed(Entity<MultiplyCorruptingSpeedOnSupercriticalComponent> ent,
|
||||
ref CosmicColossusEffigySupercriticalEvent args)
|
||||
{
|
||||
if (TryComp<CosmicCorruptingComponent>(ent, out var corrupting))
|
||||
{
|
||||
corrupting.CorruptionSpeed *= ent.Comp.Multiplier;
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleHeal(Entity<HealOnSupercriticalComponent> ent, ref CosmicColossusEffigySupercriticalEvent args)
|
||||
{
|
||||
if (TryComp<DamageableComponent>(ent, out var damageable))
|
||||
{
|
||||
_damage.TryChangeDamage(ent.Owner,
|
||||
-damageable.Damage * Math.Abs(ent.Comp.DamageFractionHealed), // just in case someone sets a negative value in YML
|
||||
true);
|
||||
}
|
||||
}
|
||||
}
|
||||
-8
@@ -1,8 +0,0 @@
|
||||
using Content.Server._DV.CosmicCult.Components;
|
||||
|
||||
namespace Content.Server._DV.CosmicCult.Abilities.Colossus;
|
||||
|
||||
[ByRefEvent]
|
||||
public readonly record struct CosmicColossusEffigySupercriticalEvent(
|
||||
Entity<CosmicEffigyComponent> Effigy
|
||||
);
|
||||
@@ -1,24 +1,15 @@
|
||||
using System.Numerics;
|
||||
using Content.Server._DV.CosmicCult.Abilities.Colossus;
|
||||
using Content.Server._DV.CosmicCult.Components;
|
||||
using Content.Server.Actions;
|
||||
using Content.Server.Chat.Managers;
|
||||
using Content.Server.Objectives.Components;
|
||||
using Content.Server.Objectives.Systems;
|
||||
using Content.Server.Popups;
|
||||
using Content.Shared._DV.CosmicCult;
|
||||
using Content.Shared._DV.CosmicCult.Components;
|
||||
using Content.Shared.Anomaly.Components;
|
||||
using Content.Shared.Maps;
|
||||
using Content.Shared.Mind;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Warps;
|
||||
using Robust.Server.Audio;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Server._DV.CosmicCult.Abilities;
|
||||
|
||||
@@ -32,93 +23,12 @@ public sealed class CosmicEffigySystem : EntitySystem
|
||||
[Dependency] private readonly SharedMapSystem _map = default!;
|
||||
[Dependency] private readonly SharedMindSystem _mind = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||
[Dependency] private readonly AudioSystem _audio = default!;
|
||||
[Dependency] private readonly CosmicCultObjectiveSystem _cultObjective = default!;
|
||||
[Dependency] private readonly IGameTiming _time = default!;
|
||||
[Dependency] private readonly IChatManager _chat = default!;
|
||||
[Dependency] private readonly IPlayerManager _player = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<CosmicColossusComponent, EventCosmicColossusEffigy>(OnColossusEffigy);
|
||||
SubscribeLocalEvent<CosmicEffigyComponent, AnomalySupercriticalEvent>(OnSupercritical);
|
||||
SubscribeLocalEvent<CosmicEffigyComponent, AnomalyShutdownEvent>(OnAnomShutdown);
|
||||
}
|
||||
|
||||
private void OnAnomShutdown(Entity<CosmicEffigyComponent> ent, ref AnomalyShutdownEvent args)
|
||||
{
|
||||
if (args.Forced || args.Supercritical || !Exists(ent.Comp.Colossus) || !TryComp<CosmicColossusComponent>(ent.Comp.Colossus, out var colossusComp))
|
||||
return;
|
||||
|
||||
colossusComp.DeathTimer = _time.CurTime;
|
||||
colossusComp.Timed = true;
|
||||
}
|
||||
|
||||
private void OnSupercritical(Entity<CosmicEffigyComponent> ent, ref AnomalySupercriticalEvent args)
|
||||
{
|
||||
if (!Exists(ent.Comp.Colossus)
|
||||
|| !TryComp<CosmicColossusComponent>(ent.Comp.Colossus, out var colossusComp)
|
||||
|| !_mind.TryGetMind(ent.Comp.Colossus.Value, out _, out var mind))
|
||||
return;
|
||||
|
||||
var colossus = ent.Comp.Colossus.Value;
|
||||
|
||||
var ev = new CosmicColossusEffigySupercriticalEvent(ent);
|
||||
RaiseLocalEvent(colossus, ref ev);
|
||||
|
||||
var transform = Transform(ent.Comp.Colossus.Value);
|
||||
Spawn(colossusComp.BuffVfx, transform.Coordinates);
|
||||
|
||||
if (colossusComp.CompletedEffigies == 0)
|
||||
{
|
||||
_audio.PlayStatic(colossusComp.ReawakenSfx,
|
||||
Filter.BroadcastMap(transform.MapID),
|
||||
transform.Coordinates,
|
||||
true);
|
||||
}
|
||||
else
|
||||
{
|
||||
_audio.PlayPvs(colossusComp.ReawakenSfx, ent);
|
||||
}
|
||||
|
||||
colossusComp.CompletedEffigies += 1;
|
||||
|
||||
if (colossusComp.CompletedEffigies >= colossusComp.MaxEffigies)
|
||||
{
|
||||
colossusComp.Timed = false;
|
||||
_popup.PopupEntity(Loc.GetString("colossus-buff-final-popup"), colossus, PopupType.Large);
|
||||
return;
|
||||
}
|
||||
|
||||
_popup.PopupEntity(Loc.GetString("colossus-buff-popup"), colossus, PopupType.Large);
|
||||
|
||||
var objIndex = mind.Objectives.FindIndex(HasComp<CosmicEffigyConditionComponent>);
|
||||
if (objIndex == -1 ||
|
||||
!TryComp<CosmicEffigyConditionComponent>(mind.Objectives[objIndex], out var conditionComp))
|
||||
{
|
||||
Log.Error($"Failed to find effigy objective on {ToPrettyString(colossus)}!");
|
||||
return;
|
||||
}
|
||||
|
||||
var objective = mind.Objectives[objIndex];
|
||||
if (_cultObjective.RandomizeEffigyTarget(objective, conditionComp, setDescription: true) is not {} nextTarget)
|
||||
{
|
||||
Log.Error("Failed to randomize effigy objective location!");
|
||||
return;
|
||||
}
|
||||
|
||||
if (mind.UserId is { } userId)
|
||||
{
|
||||
_chat.DispatchServerMessage(_player.GetSessionById(userId), Loc.GetString("colossus-next-target", ("location", nextTarget)));
|
||||
}
|
||||
|
||||
_codeCondition.SetCompleted(objective, false);
|
||||
|
||||
colossusComp.EffigyPlaceActionEntity = _actions.AddAction(colossus, colossusComp.EffigyPlaceAction);
|
||||
colossusComp.DeathTimer = _time.CurTime + colossusComp.DeathWaitEffigy;
|
||||
colossusComp.Timed = true;
|
||||
}
|
||||
|
||||
private void OnColossusEffigy(Entity<CosmicColossusComponent> ent, ref EventCosmicColossusEffigy args)
|
||||
@@ -128,16 +38,8 @@ public sealed class CosmicEffigySystem : EntitySystem
|
||||
|
||||
_actions.RemoveAction(ent.Owner, ent.Comp.EffigyPlaceActionEntity);
|
||||
_codeCondition.SetCompleted(ent.Owner, ent.Comp.EffigyObjective);
|
||||
var effigy = Spawn(ent.Comp.EffigyPrototype, pos);
|
||||
Spawn(ent.Comp.EffigyPrototype, pos);
|
||||
ent.Comp.Timed = false;
|
||||
|
||||
if (!TryComp<CosmicEffigyComponent>(effigy, out var effigyComp))
|
||||
{
|
||||
Log.Error("Colossus tried to place Effigy prototype missing CosmicEffigyComponent!");
|
||||
return;
|
||||
}
|
||||
|
||||
effigyComp.Colossus = ent.Owner;
|
||||
}
|
||||
|
||||
private bool VerifyPlacement(Entity<CosmicColossusComponent> ent, out EntityCoordinates outPos)
|
||||
|
||||
@@ -1,56 +0,0 @@
|
||||
using Content.Shared.Damage.Prototypes;
|
||||
using Content.Shared.FixedPoint;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server._DV.CosmicCult.Components;
|
||||
|
||||
/// <summary>
|
||||
/// The owning colossus's attack rate will be multiplied by
|
||||
/// the given multiplier once its effigy goes supercritical.
|
||||
/// Component meant to be applied to Cosmic Colossus.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed partial class MultiplyAttackRateOnSupercriticalComponent : Component
|
||||
{
|
||||
[DataField]
|
||||
public float Multiplier = 1.1f;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The owning colossus's corrupting speed will be multiplied by
|
||||
/// the given multiplier once its effigy goes supercritical.
|
||||
/// Component meant to be applied to Cosmic Colossus.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed partial class MultiplyCorruptingSpeedOnSupercriticalComponent : Component
|
||||
{
|
||||
[DataField]
|
||||
public float Multiplier = 0.9f;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The owning colossus will receive a flat bonus to
|
||||
/// its melee attack damage once its effigy goes supercritical.
|
||||
/// Component meant to be applied to Cosmic Colossus.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed partial class FlatAttackBonusOnSupercriticalComponent : Component
|
||||
{
|
||||
[DataField]
|
||||
public FixedPoint2 BonusDamage = 10;
|
||||
|
||||
[DataField]
|
||||
public ProtoId<DamageTypePrototype> BonusDamageType = "Blunt";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The owning colossus will be healed for `-CurrentDamage * Abs(DamageFractionHealed)` damage
|
||||
/// once its effigy goes supercritical.
|
||||
/// Component meant to be applied to Cosmic Colossus.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed partial class HealOnSupercriticalComponent : Component
|
||||
{
|
||||
[DataField]
|
||||
public float DamageFractionHealed = 1.0f;
|
||||
}
|
||||
@@ -1,4 +1,3 @@
|
||||
using Content.Server._DV.CosmicCult.Abilities.Colossus;
|
||||
using Content.Server._DV.CosmicCult.EntitySystems;
|
||||
using Content.Shared.Maps;
|
||||
using Robust.Shared.Prototypes;
|
||||
@@ -6,7 +5,7 @@ using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
|
||||
|
||||
namespace Content.Server._DV.CosmicCult.Components;
|
||||
|
||||
[RegisterComponent, Access(typeof(CosmicCorruptingSystem), typeof(CosmicColossusBuffsSystem))]
|
||||
[RegisterComponent, Access(typeof(CosmicCorruptingSystem))]
|
||||
[AutoGenerateComponentPause]
|
||||
public sealed partial class CosmicCorruptingComponent : Component
|
||||
{
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
using Content.Server._DV.CosmicCult.Abilities;
|
||||
using Content.Server._DV.CosmicCult.EntitySystems;
|
||||
|
||||
namespace Content.Server._DV.CosmicCult.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Component for cosmic effigy anomalies, spawned by Cosmic Colossi.
|
||||
/// </summary>
|
||||
/// <seelso cref="CosmicEffigySystem"/>.
|
||||
/// <seelso cref="CosmicColossusSystem"/>.
|
||||
[RegisterComponent]
|
||||
public sealed partial class CosmicEffigyComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// The colossus that placed this effigy.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public EntityUid? Colossus;
|
||||
}
|
||||
@@ -35,12 +35,6 @@ public sealed class CosmicCultObjectiveSystem : EntitySystem
|
||||
if (args.Cancelled || !_roles.MindHasRole<CosmicColossusRoleComponent>(args.MindId))
|
||||
return;
|
||||
|
||||
if (RandomizeEffigyTarget(uid, comp) is null)
|
||||
args.Cancelled = true;
|
||||
}
|
||||
|
||||
public string? RandomizeEffigyTarget(EntityUid uid, CosmicEffigyConditionComponent comp, bool setDescription = false)
|
||||
{
|
||||
var warps = new List<EntityUid>();
|
||||
var query = EntityQueryEnumerator<WarpPointComponent>();
|
||||
var effigyBlacklist = comp.Blacklist;
|
||||
@@ -57,22 +51,10 @@ public sealed class CosmicCultObjectiveSystem : EntitySystem
|
||||
|
||||
if (warps.Count <= 0)
|
||||
{
|
||||
return null;
|
||||
args.Cancelled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
var newWarp = _random.Pick(warps);
|
||||
var warpComp = Comp<WarpPointComponent>(newWarp);
|
||||
|
||||
comp.EffigyTarget = newWarp;
|
||||
|
||||
if (setDescription)
|
||||
{
|
||||
_metaData.SetEntityDescription(uid,
|
||||
warpComp.Location != null
|
||||
? Loc.GetString("objective-condition-effigy", ("location", warpComp.Location))
|
||||
: Loc.GetString("objective-condition-effigy-no-target"));
|
||||
}
|
||||
return warpComp.Location;
|
||||
comp.EffigyTarget = _random.Pick(warps);
|
||||
}
|
||||
|
||||
private void OnEffigyAfterAssign(EntityUid uid, CosmicEffigyConditionComponent comp, ref ObjectiveAfterAssignEvent args)
|
||||
|
||||
@@ -13,7 +13,6 @@ using Content.Shared.Popups;
|
||||
using Content.Shared.Station.Components;
|
||||
using Content.Shared.Throwing;
|
||||
using Content.Shared.Warps;
|
||||
using Content.Shared.Weapons.Melee.Events;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Physics.Components;
|
||||
@@ -42,7 +41,6 @@ public sealed class CosmicColossusSystem : EntitySystem
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<CosmicColossusComponent, ComponentInit>(OnSpawn);
|
||||
SubscribeLocalEvent<CosmicColossusComponent, MobStateChangedEvent>(OnMobStateChanged);
|
||||
SubscribeLocalEvent<CosmicColossusComponent, MeleeHitEvent>(OnMeleeHit);
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
@@ -84,7 +82,7 @@ public sealed class CosmicColossusSystem : EntitySystem
|
||||
|
||||
private void OnSpawn(Entity<CosmicColossusComponent> ent, ref ComponentInit args) // I WANT THIS BIG GUY HURLED TOWARDS THE STATION
|
||||
{
|
||||
ent.Comp.DeathTimer = _timing.CurTime + ent.Comp.DeathWaitSpawn;
|
||||
ent.Comp.DeathTimer = _timing.CurTime + ent.Comp.DeathWait;
|
||||
var station = _station.GetStationInMap(Transform(ent).MapID);
|
||||
if (TryComp<StationDataComponent>(station, out var stationData))
|
||||
{
|
||||
@@ -117,9 +115,4 @@ public sealed class CosmicColossusSystem : EntitySystem
|
||||
RemComp<WarpPointComponent>(ent);
|
||||
RemComp<CosmicCorruptingComponent>(ent);
|
||||
}
|
||||
|
||||
private void OnMeleeHit(Entity<CosmicColossusComponent> colossus, ref MeleeHitEvent args)
|
||||
{
|
||||
args.BonusDamage += colossus.Comp.BonusDamage;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,212 @@
|
||||
using Content.Server.Cloning;
|
||||
using Content.Server.Mind;
|
||||
using Content.Server.Station.Systems;
|
||||
using Content.Shared._DV.Psionics.Components;
|
||||
using Content.Shared._DV.Psionics.Components.PsionicPowers;
|
||||
using Content.Shared._DV.Psionics.Systems.PsionicPowers;
|
||||
using Content.Shared._DV.Species;
|
||||
using Content.Shared.Bed.Sleep;
|
||||
using Content.Shared.Body.Components;
|
||||
using Content.Shared.Humanoid.Prototypes;
|
||||
using Content.Shared.Mobs.Components;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Preferences;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Server.GameStates;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
namespace Content.Server._DV.Psionics.Systems.PsionicPowers;
|
||||
|
||||
public sealed class FracturedFormPowerSystem : SharedFracturedFormPowerSystem
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _prototype = default!;
|
||||
[Dependency] private readonly IPlayerManager _player = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
[Dependency] private readonly CloningSystem _cloning = default!;
|
||||
[Dependency] private readonly MindSystem _mind = default!;
|
||||
[Dependency] private readonly StationSpawningSystem _stationSpawning = default!;
|
||||
[Dependency] private readonly StationSystem _station = default!;
|
||||
[Dependency] private readonly TransformSystem _transform = default!;
|
||||
[Dependency] private readonly PvsOverrideSystem _pvsOverride = default!;
|
||||
|
||||
// holy initialize performance? but better for it to happen once than the double dict lookup every tick!!
|
||||
private EntityQuery<FracturedFormBodyComponent> _bodyQuery;
|
||||
private EntityQuery<SleepingComponent> _sleepingQuery;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
_bodyQuery = GetEntityQuery<FracturedFormBodyComponent>();
|
||||
_sleepingQuery = GetEntityQuery<SleepingComponent>();
|
||||
}
|
||||
|
||||
protected override void OnPowerInit(Entity<FracturedFormPowerComponent> power, ref MapInitEvent args)
|
||||
{
|
||||
base.OnPowerInit(power, ref args);
|
||||
|
||||
// The next random swap is between 5 and 20 minutes.
|
||||
var randomTime = Random.Next(power.Comp.NextSwapMinTime, power.Comp.NextSwapMaxTime);
|
||||
power.Comp.NextSwap = Timing.CurTime + randomTime;
|
||||
power.Comp.NextVoluntarySwap = Timing.CurTime + power.Comp.VoluntarySwapCooldown;
|
||||
|
||||
// Don't generate a new body if we're already part of a network.
|
||||
if (HasComp<FracturedFormBodyComponent>(power))
|
||||
return;
|
||||
|
||||
// Don't make bodies if there is no body. This is solely for test fails.
|
||||
if (!HasComp<BodyComponent>(power))
|
||||
return;
|
||||
|
||||
var bodyComp = AddComp<FracturedFormBodyComponent>(power);
|
||||
bodyComp.ControllingForm = power.Owner;
|
||||
power.Comp.Bodies.Add(power);
|
||||
var body = GenerateForm(power);
|
||||
// hide the SSD indicator.
|
||||
if (SsdQuery.TryComp(body, out var ssdComp))
|
||||
ssdComp.IsSSD = false;
|
||||
}
|
||||
|
||||
private EntityUid GenerateForm(Entity<FracturedFormPowerComponent> original)
|
||||
{
|
||||
// Form:
|
||||
// - Same appearance as original
|
||||
// - Different apperance, still humanoid
|
||||
// Equipment:
|
||||
// - Same as original body
|
||||
// - Nude and helpless
|
||||
|
||||
var xform = Transform(original);
|
||||
|
||||
var hasGear = Random.Prob(original.Comp.HasGearChance);
|
||||
|
||||
if (Random.Prob(original.Comp.DifferentSpeciesChance) || !_cloning.TryCloning(original, _transform.GetMapCoordinates(original), hasGear ? original.Comp.CopyClothed : original.Comp.CopyNaked, out var newBody)) // Slightly lower chance to copy the original body
|
||||
{
|
||||
// Either the dice rolled poorly, or the cloning failed. Either way, make a new body instead. (Or try to)
|
||||
var validSpecies = new List<ProtoId<SpeciesPrototype>>();
|
||||
var speciesPrototypes = _prototype.EnumeratePrototypes<SpeciesPrototype>();
|
||||
foreach (var proto in speciesPrototypes)
|
||||
{
|
||||
var speciesEntityPrototype = _prototype.Index<EntityPrototype>(proto.Prototype);
|
||||
// If they have the PotentialPsionicComponent, they can be psionic.
|
||||
if (proto.RoundStart && speciesEntityPrototype.TryGetComponent<PotentialPsionicComponent>(out _, Factory) && !SpeciesHiderSystem.IsHidden(proto.ID))
|
||||
validSpecies.Add(proto.ID);
|
||||
}
|
||||
var species = Random.Pick(validSpecies);
|
||||
var character = HumanoidCharacterProfile.RandomWithSpecies(species);
|
||||
newBody = _stationSpawning.SpawnPlayerMob(xform.Coordinates, hasGear ? original.Comp.VisitorJob : original.Comp.NakedJob, character, _station.GetOwningStation(original.Owner));
|
||||
if (newBody is not { } bodyV || Deleted(bodyV))
|
||||
{
|
||||
Log.Error($"Failed to create a new body for {ToPrettyString(original)}. This is a bug.");
|
||||
return EntityUid.Invalid;
|
||||
}
|
||||
}
|
||||
|
||||
if (newBody is not { } body || Deleted(body))
|
||||
return default!;
|
||||
|
||||
var bodyComp = AddComp<FracturedFormBodyComponent>(body);
|
||||
original.Comp.Bodies.Add(body);
|
||||
bodyComp.ControllingForm = original.Owner;
|
||||
|
||||
if (_player.TryGetSessionByEntity(original, out var session))
|
||||
_pvsOverride.AddSessionOverride(body, session);
|
||||
|
||||
Dirty(original);
|
||||
|
||||
return body;
|
||||
}
|
||||
|
||||
private bool TryGetValidBody(Entity<FracturedFormPowerComponent> psionic, out EntityUid validBody)
|
||||
{
|
||||
foreach (var body in psionic.Comp.Bodies)
|
||||
{
|
||||
if (!IsValidBody(psionic, body))
|
||||
continue;
|
||||
|
||||
validBody = body;
|
||||
return true;
|
||||
}
|
||||
validBody = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
private void Swap(Entity<FracturedFormPowerComponent> psionic)
|
||||
{
|
||||
if (!TryGetValidBody(psionic, out var targetBody))
|
||||
return;
|
||||
|
||||
_audio.PlayPvs(psionic.Comp.SwapSound, psionic);
|
||||
// Transfer mind if present
|
||||
if (MindContainerQuery.TryComp(psionic, out var mindContainer) && mindContainer.Mind.HasValue)
|
||||
_mind.TransferTo(mindContainer.Mind.Value, targetBody);
|
||||
// Wake up the new body
|
||||
Sleeping.TryWaking(targetBody);
|
||||
// Remove the action.
|
||||
Action.RemoveAction(psionic.Comp.ActionEntity);
|
||||
// Create new component on target and copy data
|
||||
var duplicate = EnsureComp<FracturedFormPowerComponent>(targetBody);
|
||||
duplicate.Bodies = psionic.Comp.Bodies;
|
||||
// Update all body references
|
||||
foreach (var body in duplicate.Bodies)
|
||||
{
|
||||
if (_bodyQuery.TryComp(body, out var bodyComp))
|
||||
bodyComp.ControllingForm = targetBody;
|
||||
}
|
||||
|
||||
if (_player.TryGetSessionByEntity(targetBody, out var session))
|
||||
{
|
||||
_pvsOverride.AddSessionOverride(psionic, session);
|
||||
_pvsOverride.RemoveSessionOverride(targetBody, session);
|
||||
}
|
||||
|
||||
RemCompDeferred<FracturedFormPowerComponent>(psionic);
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
|
||||
List<Entity<FracturedFormPowerComponent>> swapTargets = [];
|
||||
|
||||
var entities = EntityQueryEnumerator<FracturedFormPowerComponent, MobStateComponent>();
|
||||
while (entities.MoveNext(out var uid, out var comp, out var mobState))
|
||||
{
|
||||
// Check sleep warning
|
||||
if (!comp.SleepWarned && Timing.CurTime > comp.NextSwap - comp.WarningTimeBeforeSleep)
|
||||
{
|
||||
comp.SleepWarned = true;
|
||||
Popup.PopupEntity(Loc.GetString("psionic-power-fractured-form-sleepy"), uid, uid, PopupType.LargeCaution);
|
||||
Chat.TryEmoteWithChat(uid, "Yawn");
|
||||
}
|
||||
// Swap check
|
||||
if ((_sleepingQuery.HasComp(uid) || MobState.IsIncapacitated(uid, mobState)) && Timing.CurTime > comp.NextVoluntarySwap
|
||||
|| Timing.CurTime > comp.NextSwap)
|
||||
swapTargets.Add((uid, comp));
|
||||
}
|
||||
|
||||
foreach (var target in swapTargets)
|
||||
{
|
||||
Swap(target);
|
||||
}
|
||||
|
||||
// Process bodies
|
||||
var bodies = EntityQueryEnumerator<FracturedFormBodyComponent, MobStateComponent>();
|
||||
while (bodies.MoveNext(out var uid, out var comp, out var mobState))
|
||||
{
|
||||
// Put to sleep if no sleeping component and no mind
|
||||
if (!_sleepingQuery.HasComp(uid) && !_mind.GetMind(uid).HasValue && !FracturedQuery.HasComp(uid))
|
||||
Sleeping.TrySleeping((uid, mobState));
|
||||
// Cleanup invalid bodies
|
||||
if (!comp.ControllingForm.IsValid()
|
||||
|| Deleted(comp.ControllingForm)
|
||||
|| !FracturedQuery.HasComp(comp.ControllingForm))
|
||||
{
|
||||
RemCompDeferred<FracturedFormBodyComponent>(uid);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,124 +0,0 @@
|
||||
using Content.Server._DV.Psionics.Systems;
|
||||
using Content.Server.Antag;
|
||||
using Content.Shared._DV.Movement.Components;
|
||||
using Content.Shared._DV.Psionics.Components;
|
||||
using Content.Shared.CharacterInfo;
|
||||
using Content.Shared.Cloning.Events;
|
||||
using Content.Shared.CombatMode.Pacification;
|
||||
using Content.Shared.Mobs.Components;
|
||||
using Content.Shared.Movement.Components;
|
||||
using Content.Shared.Movement.Systems;
|
||||
using Content.Shared.NPC.Components;
|
||||
using Content.Shared.Radio.Components;
|
||||
using Content.Shared.Roles.Components;
|
||||
using Content.Shared.Zombies;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server.Zombies;
|
||||
|
||||
public sealed partial class ZombieSystem
|
||||
{
|
||||
//private static readonly string MindRoleInitialInfected = "MindRoleInitialInfected";
|
||||
private static readonly EntProtoId InitialInfectedFailureSurviveObjective = "InitialInfectedFailureSurviveObjective";
|
||||
|
||||
[Dependency] private readonly AntagSelectionSystem _antag = default!;
|
||||
[Dependency] private readonly PsionicSystem _psionic = default!; // DeltaV
|
||||
[Dependency] private readonly SharedJetpackSystem _jetpack = default!; // DeltaV - Prevent Jetpacks on Zombies
|
||||
|
||||
private void InitializeDV()
|
||||
{
|
||||
// This needs to be done before CloneEvent or else the proper attributes won't be restored or modified on
|
||||
// the new entity.
|
||||
SubscribeLocalEvent<ZombieComponent, CloningAttemptEvent>(OnBeforeUnzombify);
|
||||
SubscribeLocalEvent<InitialInfectedComponent, CloningEvent>(OnInitialInfectedCloning);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// DeltaV - Extra things we want to ensure, remove, or take care of when someone is zombified.
|
||||
/// </summary>
|
||||
private void ZombifyEntityDV(Entity<ZombieComponent> ent, MobStateComponent? mobState = null)
|
||||
{
|
||||
// Remove innate radio
|
||||
RemComp<ActiveRadioComponent>(ent); // If the zombie has an innate radio, get rid of it.
|
||||
|
||||
// Remove headsets in pockets
|
||||
for (var i = 1; i <= 4; i++) // Arachnids have 4 pockets
|
||||
{
|
||||
if (_inventory.TryGetSlotEntity(ent, $"pocket{i}", out var headset) && HasComp<HeadsetComponent>(headset))
|
||||
_inventory.TryUnequip(ent, $"pocket{i}", true, true);
|
||||
}
|
||||
|
||||
// Prevent Psionic Zombies
|
||||
RemComp<PotentialPsionicComponent>(ent);
|
||||
_psionic.MindBreakEntity(ent.Owner, false, true);
|
||||
|
||||
// Prevent Jetpacks on Zombies
|
||||
if (TryComp<JetpackUserComponent>(ent, out var jetpackUser))
|
||||
{
|
||||
if (TryComp<JetpackComponent>(jetpackUser.Jetpack, out var jetpack))
|
||||
_jetpack.SetEnabled(jetpackUser.Jetpack, jetpack, false, ent);
|
||||
}
|
||||
RemComp<AutomaticJetpackUserComponent>(ent);
|
||||
|
||||
// Prevent shitters biting other zombies
|
||||
EnsureComp<NoFriendlyFireComponent>(ent);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// DeltaV - We need to save some extra things to the ZombieComponent so we can restore them when
|
||||
/// they are about to be cloned.
|
||||
/// </summary>
|
||||
/// <param name="ent"></param>
|
||||
private void PreserveEntityComponentState(Entity<ZombieComponent> ent)
|
||||
{
|
||||
SaveFactionsBeforeZombification(ent);
|
||||
|
||||
// Zombification removes pacifism but lets track if they have it so the pacifist players will be... pacified.
|
||||
ent.Comp.WasPacisfistBeforeZombification = HasComp<PacifiedComponent>(ent);
|
||||
}
|
||||
|
||||
private void SaveFactionsBeforeZombification(Entity<ZombieComponent> ent)
|
||||
{
|
||||
if (!TryComp<NpcFactionMemberComponent>(ent, out var faction))
|
||||
return;
|
||||
|
||||
ent.Comp.FactionsBeforeZombification.AddRange(faction.Factions);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// DeltaV - Restores certain components to their previous state that the upstream unzombify doesn't handle.
|
||||
/// We need to do this before CloningEvent so the cloned body will have the updated components BEFORE cloning happens.
|
||||
/// </summary>
|
||||
/// <param name="ent"></param>
|
||||
/// <param name="args"></param>
|
||||
private void OnBeforeUnzombify(Entity<ZombieComponent> ent, ref CloningAttemptEvent args)
|
||||
{
|
||||
// Restore factions
|
||||
_faction.ClearFactions(ent.Owner); // Should only be zombie, but might as well clear the whole list in case.
|
||||
ent.Comp.FactionsBeforeZombification.ForEach(previousFaction => _faction.AddFaction(ent.Owner, previousFaction));
|
||||
|
||||
// Restore pacifism
|
||||
if (ent.Comp.WasPacisfistBeforeZombification)
|
||||
EnsureComp<PacifiedComponent>(ent);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// DeltaV - Adds a survival objective to Initial Infected who are cloned. Chances are that the crew has recovered, so
|
||||
/// bioterrorism time is probably over.
|
||||
/// </summary>
|
||||
/// <param name="ent"></param>
|
||||
/// <param name="args"></param>
|
||||
private void OnInitialInfectedCloning(Entity<InitialInfectedComponent> ent, ref CloningEvent args)
|
||||
{
|
||||
// Change II objectives so they no longer have infect objectives. Now, they'll have survival objectives. Live to infect another day. :)
|
||||
_mind.TryGetMind(ent.Owner, out var mindId, out var mindContainer);
|
||||
|
||||
// If we can't find the mind, oh well. We tried.
|
||||
if (mindContainer is not { } mind)
|
||||
return;
|
||||
|
||||
if (_role.MindHasRole<InitialInfectedRoleComponent>(mindId))
|
||||
_mind.TryAddObjective(mindId, mind, InitialInfectedFailureSurviveObjective);
|
||||
}
|
||||
}
|
||||
@@ -285,11 +285,6 @@ public sealed partial class AnomalyComponent : Component
|
||||
|
||||
[DataField]
|
||||
public bool DeleteEntity = true;
|
||||
|
||||
// DeltaV - Colossus additions START
|
||||
[DataField]
|
||||
public bool AlwaysGrow;
|
||||
// DeltaV - Colossus additions END
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -313,7 +308,7 @@ public readonly record struct AnomalySupercriticalEvent(EntityUid Anomaly, float
|
||||
/// <param name="Anomaly">The anomaly being shut down.</param>
|
||||
/// <param name="Supercritical">Whether or not the anomaly shut down passively or via a supercritical event.</param>
|
||||
[ByRefEvent]
|
||||
public readonly record struct AnomalyShutdownEvent(EntityUid Anomaly, bool Supercritical, bool Forced); // DeltaV - Add Forced
|
||||
public readonly record struct AnomalyShutdownEvent(EntityUid Anomaly, bool Supercritical);
|
||||
|
||||
/// <summary>
|
||||
/// Event broadcast when an anomaly's severity is changed.
|
||||
|
||||
@@ -84,7 +84,7 @@ public abstract partial class SharedAnomalySystem : EntitySystem // DeltaV - Mad
|
||||
Log.Info($"Performing anomaly pulse. Entity: {ToPrettyString(uid)}");
|
||||
|
||||
// if we are above the growth threshold, then grow before the pulse
|
||||
if (component.AlwaysGrow || component.Stability > component.GrowthThreshold) // DeltaV - Add AlwaysGrow
|
||||
if (component.Stability > component.GrowthThreshold)
|
||||
{
|
||||
ChangeAnomalySeverity(uid, GetSeverityIncreaseFromGrowth(component), component);
|
||||
}
|
||||
@@ -192,8 +192,7 @@ public abstract partial class SharedAnomalySystem : EntitySystem // DeltaV - Mad
|
||||
/// <param name="supercritical">Whether or not the anomaly ended via supercritical event</param>
|
||||
/// <param name="spawnCore">Create anomaly cores based on the result of completing an anomaly?</param>
|
||||
/// <param name="logged">Whether or not the anomaly decaying/going supercritical is logged</param>
|
||||
/// <param name="forced">Whether or not the anomaly shutdown was caused by component shutdown</param> // DeltaV - Add forced
|
||||
public void EndAnomaly(EntityUid uid, AnomalyComponent? component = null, bool supercritical = false, bool spawnCore = true, bool logged = false, bool forced = false) // DeltaV - Add forced
|
||||
public void EndAnomaly(EntityUid uid, AnomalyComponent? component = null, bool supercritical = false, bool spawnCore = true, bool logged = false)
|
||||
{
|
||||
if (logged)
|
||||
{
|
||||
@@ -208,7 +207,7 @@ public abstract partial class SharedAnomalySystem : EntitySystem // DeltaV - Mad
|
||||
if (!Resolve(uid, ref component))
|
||||
return;
|
||||
|
||||
var ev = new AnomalyShutdownEvent(uid, supercritical, forced); // DeltaV - Add forced
|
||||
var ev = new AnomalyShutdownEvent(uid, supercritical);
|
||||
RaiseLocalEvent(uid, ref ev, true);
|
||||
|
||||
if (Terminating(uid) || _net.IsClient)
|
||||
@@ -346,7 +345,7 @@ public abstract partial class SharedAnomalySystem : EntitySystem // DeltaV - Mad
|
||||
|
||||
// if the stability is under the death threshold,
|
||||
// update it every second to start killing it slowly.
|
||||
if (!anomaly.AlwaysGrow && anomaly.Stability < anomaly.DecayThreshold) // DeltaV - Add AlwaysGrow
|
||||
if (anomaly.Stability < anomaly.DecayThreshold)
|
||||
{
|
||||
ChangeAnomalyHealth(ent, anomaly.HealthChangePerSecond * frameTime, anomaly);
|
||||
}
|
||||
@@ -480,14 +479,6 @@ public abstract partial class SharedAnomalySystem : EntitySystem // DeltaV - Mad
|
||||
if (!Resolve(ent, ref ent.Comp, logMissing: false))
|
||||
return false;
|
||||
|
||||
// DeltaV - Colossus Additions START
|
||||
if (ent.Comp.AlwaysGrow)
|
||||
{
|
||||
visual = AnomalyStabilityVisuals.Growing;
|
||||
return true;
|
||||
}
|
||||
// DeltaV - Colossus Additions END
|
||||
|
||||
visual = AnomalyStabilityVisuals.Stable;
|
||||
if (ent.Comp.Stability <= ent.Comp.DecayThreshold)
|
||||
{
|
||||
|
||||
@@ -135,10 +135,4 @@ public sealed partial class CCVars
|
||||
/// </summary>
|
||||
public static readonly CVarDef<int> AdminOverlayStackMax =
|
||||
CVarDef.Create("ui.admin_overlay_stack_max", 3, CVar.CLIENTONLY | CVar.ARCHIVE);
|
||||
|
||||
/// <summary>
|
||||
/// If true, ghosts will see an "(F)" button next to chat messages, which can be used to follow the sender.
|
||||
/// </summary>
|
||||
public static readonly CVarDef<bool> InterfaceChatFollowButton =
|
||||
CVarDef.Create("ui.chat_follow_button", true, CVar.CLIENT | CVar.REPLICATED | CVar.ARCHIVE);
|
||||
}
|
||||
|
||||
@@ -53,20 +53,6 @@ namespace Content.Shared.Chat
|
||||
AudioPath = audioPath;
|
||||
AudioVolume = audioVolume;
|
||||
}
|
||||
|
||||
public ChatMessage(ChatMessage copyFrom)
|
||||
{
|
||||
Channel = copyFrom.Channel;
|
||||
Message = copyFrom.Message;
|
||||
WrappedMessage = copyFrom.WrappedMessage;
|
||||
SenderEntity = copyFrom.SenderEntity;
|
||||
SenderKey = copyFrom.SenderKey;
|
||||
HideChat = copyFrom.HideChat;
|
||||
MessageColorOverride = copyFrom.MessageColorOverride;
|
||||
AudioPath = copyFrom.AudioPath;
|
||||
AudioVolume = copyFrom.AudioVolume;
|
||||
Read = copyFrom.Read;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -30,11 +30,6 @@ public sealed class InteractUsingEvent : HandledEntityEventArgs
|
||||
/// </summary>
|
||||
public EntityCoordinates ClickLocation { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Stellar - set to false if this interaction shouldn't have an interaction particle
|
||||
/// </summary>
|
||||
public bool InteractionParticle = true;
|
||||
|
||||
public InteractUsingEvent(EntityUid user, EntityUid used, EntityUid target, EntityCoordinates clickLocation)
|
||||
{
|
||||
// Interact using should not have the same used and target.
|
||||
|
||||
@@ -31,7 +31,6 @@ using Content.Shared.UserInterface;
|
||||
using Content.Shared.Verbs;
|
||||
using Content.Shared.Wall;
|
||||
using Content.Shared._Goobstation.DoAfter; // Goobstation
|
||||
using Content.Shared.Inventory.VirtualItem; // Stellar - interaction particles
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Input;
|
||||
@@ -1074,8 +1073,8 @@ namespace Content.Shared.Interaction
|
||||
var userInteractUsingEvent = new UserInteractUsingEvent(user, used, target, clickLocation);
|
||||
RaiseLocalEvent(user, userInteractUsingEvent, true);
|
||||
|
||||
DoContactInteraction(user, used, null, true, interactUsingEvent, interactionParticles: interactUsingEvent.InteractionParticle); // Stellar - interaction particles
|
||||
DoContactInteraction(user, target, used, true, interactUsingEvent, interactionParticles: interactUsingEvent.InteractionParticle); // Stellar - interaction particles
|
||||
DoContactInteraction(user, used, null, true, interactUsingEvent); // Stellar - interaction particles
|
||||
DoContactInteraction(user, target, used, true, interactUsingEvent); // Stellar - interaction particles
|
||||
// Contact interactions are currently only used for forensics, so we don't raise used -> target
|
||||
if (interactUsingEvent.Handled || userInteractUsingEvent.Handled)
|
||||
return true;
|
||||
@@ -1438,8 +1437,7 @@ namespace Content.Shared.Interaction
|
||||
/// <param name="predicted">Whether this interaction is predicted. <see cref="uidA"/> is assumed to be the client entity.</param>
|
||||
/// <param name="args">Optional handleable entity event to check.</param>
|
||||
/// <param name="interactionParticles">Whether to spawn interaction particles on this contact.</param>
|
||||
/// <param name="interactionParticleType">The type of interaction particle to spawn for this event.</param>
|
||||
public void DoContactInteraction(EntityUid uidA, EntityUid? uidB, EntityUid? used, bool predicted, HandledEntityEventArgs? args = null, bool interactionParticles = true, StellarInteractionParticleType interactionParticleType = StellarInteractionParticleType.Use) // Stellar/ES - interaction particles
|
||||
public void DoContactInteraction(EntityUid uidA, EntityUid? uidB, EntityUid? used, bool predicted, HandledEntityEventArgs? args = null, bool interactionParticles = true) // Stellar/ES - interaction particles
|
||||
{
|
||||
if (uidB == null || args?.Handled == false)
|
||||
return;
|
||||
@@ -1461,7 +1459,7 @@ namespace Content.Shared.Interaction
|
||||
RaiseLocalEvent(uidB.Value, ev);
|
||||
|
||||
// Begin Stellar/ES Additions - Interaction particles
|
||||
if (!interactionParticles || HasComp<VirtualItemComponent>(uidB))
|
||||
if (!interactionParticles)
|
||||
return;
|
||||
|
||||
if (_net.IsServer)
|
||||
@@ -1470,11 +1468,11 @@ namespace Content.Shared.Interaction
|
||||
? Filter.PvsExcept(uidA, entityManager: EntityManager)
|
||||
: Filter.Pvs(uidA, entityManager: EntityManager);
|
||||
|
||||
RaiseNetworkEvent(new StellarInteractionParticleEvent(GetNetEntity(uidA), GetNetEntity(used), GetNetEntity(uidB.Value), false, interactionParticleType), filter);
|
||||
RaiseNetworkEvent(new StellarInteractionParticleEvent(GetNetEntity(uidA), GetNetEntity(used), GetNetEntity(uidB.Value), false), filter);
|
||||
}
|
||||
else if (_gameTiming.IsFirstTimePredicted)
|
||||
{
|
||||
var evt = new StellarInteractionParticleEvent(GetNetEntity(uidA), GetNetEntity(used), GetNetEntity(uidB.Value), true, interactionParticleType);
|
||||
var evt = new StellarInteractionParticleEvent(GetNetEntity(uidA), GetNetEntity(used), GetNetEntity(uidB.Value), true);
|
||||
RaiseLocalEvent(evt);
|
||||
}
|
||||
// End Stellar/ES Additions - Interaction particles
|
||||
|
||||
@@ -122,11 +122,6 @@ public sealed class MindExamineSystem : EntitySystem
|
||||
else
|
||||
ent.Comp.State = MindState.None;
|
||||
|
||||
// DeltaV - SSD Recency START
|
||||
var ev = new MindStateUpdatedEvent(ent.Comp.State);
|
||||
RaiseLocalEvent(ent, ref ev);
|
||||
// DeltaV END
|
||||
|
||||
Dirty(ent);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using Content.Shared._ST.Interaction; // Stellar - interaction particles
|
||||
using Content.Shared.ActionBlocker;
|
||||
using Content.Shared.Administration.Logs;
|
||||
using Content.Shared.Alert;
|
||||
@@ -546,7 +545,7 @@ public sealed class PullingSystem : EntitySystem
|
||||
|
||||
// Pulling confirmed
|
||||
|
||||
_interaction.DoContactInteraction(pullerUid, pullableUid,null, true, interactionParticleType: StellarInteractionParticleType.Pull); // Stellar - Interaction particles
|
||||
_interaction.DoContactInteraction(pullableUid, pullerUid, null, true); // Stellar - Interaction particles
|
||||
|
||||
// Use net entity so it's consistent across client and server.
|
||||
pullableComp.PullJointId = $"pull-joint-{GetNetEntity(pullableUid)}";
|
||||
|
||||
@@ -6,7 +6,7 @@ using Content.Shared.Humanoid;
|
||||
using Content.Shared.Humanoid.Prototypes;
|
||||
using Content.Shared.Preferences.Loadouts;
|
||||
using Content.Shared.Roles;
|
||||
using Content.Shared.Traits;
|
||||
//using Content.Shared.Traits; // DeltaV - Traits Rework
|
||||
using Robust.Shared.Collections;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Enums;
|
||||
|
||||
@@ -18,13 +18,11 @@ public sealed partial class RatKingComponent : Component
|
||||
[DataField("actionRaiseArmyEntity")]
|
||||
public EntityUid? ActionRaiseArmyEntity;
|
||||
|
||||
// Delta-V - switched to a base cost modified by the amount of living servants
|
||||
/// <summary>
|
||||
/// The amount of hunger one use of Raise Army consumes
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField("hungerPerArmyUse", required: true)]
|
||||
public float HungerPerArmyUse = 10f;
|
||||
// end DeltaV
|
||||
public float HungerPerArmyUse = 25f;
|
||||
|
||||
/// <summary>
|
||||
/// The entity prototype of the mob that Raise Army summons
|
||||
|
||||
@@ -50,12 +50,11 @@ public sealed partial class SSDIndicatorComponent : Component
|
||||
// DeltaV - SSD Recency Additions START
|
||||
|
||||
/// <summary>
|
||||
/// The time at which the player became SSD.
|
||||
/// This will remain unset on SSD entities that never had minds attached, such as newly spawn ghost roles.
|
||||
/// The time at which the Entity became SSD.
|
||||
/// </summary>
|
||||
[AutoNetworkedField, AutoPausedField]
|
||||
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
|
||||
public TimeSpan? SsdSince;
|
||||
public TimeSpan SsdSince = TimeSpan.Zero;
|
||||
|
||||
/// <summary>
|
||||
/// The icon displayed next to the associated entity when it is recently SSD (stage 2).
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
using Content.Shared._DV.CCVars; // DeltaV - SSD Recency
|
||||
using Content.Shared._DV.Mind; // DeltaV - SSD Recency
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Examine; // DeltaV - SSD Recency
|
||||
using Content.Shared.Mobs.Systems; // DeltaV - SSD Recency
|
||||
using Content.Shared.Mind.Components; // DeltaV - SSD Recency
|
||||
using Content.Shared.StatusEffectNew;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Player;
|
||||
@@ -22,20 +19,18 @@ public sealed class SSDIndicatorSystem : EntitySystem
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly StatusEffectsSystem _statusEffects = default!;
|
||||
[Dependency] private readonly MobStateSystem _mobState = default!; // DeltaV - SSD Recency
|
||||
|
||||
private bool _icSsdSleep;
|
||||
private float _icSsdSleepTime;
|
||||
|
||||
private TimeSpan _cryoableSsdSeconds; // DeltaV - SSD Recency
|
||||
private TimeSpan _recentSsdSeconds; // DeltaV - SSD Recency
|
||||
private TimeSpan _cryoableSsdSeconds; // DeltaV
|
||||
private TimeSpan _recentSsdSeconds; // DeltaV
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<SSDIndicatorComponent, PlayerAttachedEvent>(OnPlayerAttached);
|
||||
SubscribeLocalEvent<SSDIndicatorComponent, PlayerDetachedEvent>(OnPlayerDetached);
|
||||
SubscribeLocalEvent<SSDIndicatorComponent, MapInitEvent>(OnMapInit);
|
||||
SubscribeLocalEvent<SSDIndicatorComponent, MindStateUpdatedEvent>(OnMindStateUpdated); // DeltaV - SSD Recency
|
||||
|
||||
_cfg.OnValueChanged(CCVars.ICSSDSleep, obj => _icSsdSleep = obj, true);
|
||||
_cfg.OnValueChanged(CCVars.ICSSDSleepTime, obj => _icSsdSleepTime = obj, true);
|
||||
@@ -48,15 +43,12 @@ public sealed class SSDIndicatorSystem : EntitySystem
|
||||
// DeltaV - SSD Recency START
|
||||
private void OnExamine(Entity<SSDIndicatorComponent> ent, ref ExaminedEvent args)
|
||||
{
|
||||
if (ent.Comp.SsdSince is not { } ssdSince)
|
||||
return;
|
||||
|
||||
if (_mobState.IsDead(ent))
|
||||
if (!ent.Comp.IsSSD)
|
||||
return;
|
||||
|
||||
using (args.PushGroup(nameof(SSDIndicatorComponent)))
|
||||
{
|
||||
var timestamp = (_timing.CurTime - ssdSince).ToString("%hh':'mm':'ss");
|
||||
var timestamp = (_timing.CurTime - ent.Comp.SsdSince).ToString("%hh':'mm':'ss");
|
||||
args.PushMarkup(Loc.GetString("ssd-examine-duration", ("time", timestamp)));
|
||||
args.PushMarkup(Loc.GetString($"ssd-examine-{GetStage(ent).ToString().ToLower()}"));
|
||||
}
|
||||
@@ -66,28 +58,17 @@ public sealed class SSDIndicatorSystem : EntitySystem
|
||||
{
|
||||
var curTime = _timing.CurTime;
|
||||
|
||||
if (ent.Comp.SsdSince + _recentSsdSeconds >= curTime)
|
||||
if (ent.Comp.SsdSince + _cryoableSsdSeconds < curTime)
|
||||
{
|
||||
return SsdStage.VeryRecent;
|
||||
return SsdStage.Cryoable;
|
||||
}
|
||||
|
||||
if (ent.Comp.SsdSince + _cryoableSsdSeconds >= curTime)
|
||||
if (ent.Comp.SsdSince + _recentSsdSeconds < curTime)
|
||||
{
|
||||
return SsdStage.Recent;
|
||||
}
|
||||
|
||||
return SsdStage.Cryoable;
|
||||
}
|
||||
|
||||
public void OnMindStateUpdated(Entity<SSDIndicatorComponent> ent, ref MindStateUpdatedEvent args)
|
||||
{
|
||||
if (args.State is MindState.SSD or MindState.DeadSSD) {
|
||||
if (ent.Comp.SsdSince is null)
|
||||
ent.Comp.SsdSince = _timing.CurTime;
|
||||
}
|
||||
else
|
||||
ent.Comp.SsdSince = null;
|
||||
Dirty(ent, ent.Comp);
|
||||
return SsdStage.VeryRecent;
|
||||
}
|
||||
// DeltaV END
|
||||
|
||||
@@ -115,6 +96,8 @@ public sealed class SSDIndicatorSystem : EntitySystem
|
||||
component.FallAsleepTime = _timing.CurTime + TimeSpan.FromSeconds(_icSsdSleepTime);
|
||||
}
|
||||
|
||||
component.SsdSince = _timing.CurTime; // DeltaV - SSD Recency
|
||||
|
||||
Dirty(uid, component);
|
||||
}
|
||||
|
||||
|
||||
@@ -62,7 +62,6 @@ public abstract class SharedEmitSoundSystem : EntitySystem
|
||||
if (_whitelistSystem.IsWhitelistFail(component.Blacklist, args.User))
|
||||
{
|
||||
TryEmitSound(uid, component, args.User);
|
||||
args.InteractionParticle = true; // Stellar - interaction particles
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -106,7 +106,6 @@ public abstract partial class SharedStackSystem : EntitySystem
|
||||
|
||||
var localRotation = Transform(args.Used).LocalRotation;
|
||||
_storage.PlayPickupAnimation(args.Used, popupPos, userCoords, localRotation, args.User);
|
||||
args.InteractionParticle = false; // Stellar
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -52,11 +52,6 @@ public sealed class AfterActivatableUIOpenEvent(EntityUid user) : EntityEventArg
|
||||
/// The player that opened the UI.
|
||||
/// </summary>
|
||||
public readonly EntityUid User = user;
|
||||
|
||||
/// <summary>
|
||||
/// Stellar - if an interaction particle should be played for this event.
|
||||
/// </summary>
|
||||
public bool InteractionParticle;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -21,7 +21,6 @@ public sealed partial class ActivatableUISystem : EntitySystem
|
||||
[Dependency] private readonly SharedPopupSystem _popupSystem = default!;
|
||||
[Dependency] private readonly SharedHandsSystem _hands = default!;
|
||||
[Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!;
|
||||
[Dependency] private readonly SharedInteractionSystem _interaction = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -73,7 +72,7 @@ public sealed partial class ActivatableUISystem : EntitySystem
|
||||
|
||||
args.Verbs.Add(new ActivationVerb
|
||||
{
|
||||
Act = () => InteractUI(args.User, (uid, component)), // Stellar - interaction particles
|
||||
Act = () => InteractUI(args.User, uid, component),
|
||||
Text = Loc.GetString(component.VerbText),
|
||||
// TODO VERB ICON find a better icon
|
||||
Icon = new SpriteSpecifier.Texture(new ResPath("/Textures/Interface/VerbIcons/settings.svg.192dpi.png")),
|
||||
@@ -87,7 +86,7 @@ public sealed partial class ActivatableUISystem : EntitySystem
|
||||
|
||||
args.Verbs.Add(new Verb
|
||||
{
|
||||
Act = () => InteractUI(args.User, (uid, component)), // Stellar - interaction particles
|
||||
Act = () => InteractUI(args.User, uid, component),
|
||||
Text = Loc.GetString(component.VerbText),
|
||||
// TODO VERB ICON find a better icon
|
||||
Icon = new SpriteSpecifier.Texture(new ResPath("/Textures/Interface/VerbIcons/settings.svg.192dpi.png")),
|
||||
@@ -134,8 +133,7 @@ public sealed partial class ActivatableUISystem : EntitySystem
|
||||
if (component.RequiredItems != null)
|
||||
return;
|
||||
|
||||
var interactionParticle = false; // Stellar - interaction particles
|
||||
args.Handled = InteractUI(args.User, uid, component, ref interactionParticle); // Stellar - interaction particles
|
||||
args.Handled = InteractUI(args.User, uid, component);
|
||||
}
|
||||
|
||||
private void OnActivate(EntityUid uid, ActivatableUIComponent component, ActivateInWorldEvent args)
|
||||
@@ -149,7 +147,7 @@ public sealed partial class ActivatableUISystem : EntitySystem
|
||||
if (component.RequiredItems != null)
|
||||
return;
|
||||
|
||||
args.Handled = InteractUI(args.User, uid, component, ref args.InteractionParticle); // Stellar - interaction particles
|
||||
args.Handled = InteractUI(args.User, uid, component);
|
||||
}
|
||||
|
||||
private void OnInteractUsing(EntityUid uid, ActivatableUIComponent component, InteractUsingEvent args)
|
||||
@@ -166,7 +164,7 @@ public sealed partial class ActivatableUISystem : EntitySystem
|
||||
if (_whitelistSystem.IsWhitelistFail(component.RequiredItems, args.Used))
|
||||
return;
|
||||
|
||||
args.Handled = InteractUI(args.User, uid, component, ref args.InteractionParticle); // Stellar - interaction particles
|
||||
args.Handled = InteractUI(args.User, uid, component);
|
||||
}
|
||||
|
||||
private void OnUIClose(EntityUid uid, ActivatableUIComponent component, BoundUIClosedEvent args)
|
||||
@@ -182,23 +180,13 @@ public sealed partial class ActivatableUISystem : EntitySystem
|
||||
SetCurrentSingleUser(uid, null, component);
|
||||
}
|
||||
|
||||
// Begin Stellar - interaction particles
|
||||
private void InteractUI(EntityUid user, Entity<ActivatableUIComponent> ui)
|
||||
{
|
||||
var interactionParticle = false;
|
||||
InteractUI(user, ui, ui, ref interactionParticle);
|
||||
_interaction.DoContactInteraction(user, ui, null, true, interactionParticles: interactionParticle);
|
||||
}
|
||||
// End Stellar - interaction particles
|
||||
|
||||
private bool InteractUI(EntityUid user, EntityUid uiEntity, ActivatableUIComponent aui, ref bool interactionParticle) // Stellar - interaction particles
|
||||
private bool InteractUI(EntityUid user, EntityUid uiEntity, ActivatableUIComponent aui)
|
||||
{
|
||||
if (aui.Key == null || !_uiSystem.HasUi(uiEntity, aui.Key))
|
||||
return false;
|
||||
|
||||
if (_uiSystem.IsUiOpen(uiEntity, aui.Key, user))
|
||||
{
|
||||
interactionParticle = false; // Stellar - interaction particles
|
||||
_uiSystem.CloseUi(uiEntity, aui.Key, user);
|
||||
return true;
|
||||
}
|
||||
@@ -255,8 +243,6 @@ public sealed partial class ActivatableUISystem : EntitySystem
|
||||
var aae = new AfterActivatableUIOpenEvent(user);
|
||||
RaiseLocalEvent(uiEntity, aae);
|
||||
|
||||
interactionParticle = aae.InteractionParticle; // Stellar
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -31,7 +31,6 @@ using Content.Shared.Weapons.Melee.Events;
|
||||
using Content.Shared.Weapons.Ranged.Components;
|
||||
using Content.Shared.Weapons.Ranged.Events;
|
||||
using Content.Shared.Weapons.Ranged.Systems;
|
||||
using Content.Shared.Wieldable.Components; // Starlight | ES Screenshake
|
||||
using Content.Shared.Zombies; // DeltaV - Buff Zombies
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
@@ -598,7 +597,18 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem
|
||||
if (damageResult.GetTotal() > FixedPoint2.Zero)
|
||||
{
|
||||
DoDamageEffect(targets, user, targetXform);
|
||||
DoScreenshake(meleeUid, damageResult, user, targets); // Starlight | ES Screenshake
|
||||
|
||||
// ES START
|
||||
// dog shit copy plaste but thats melee for you
|
||||
var userShakeRotation = new ESScreenshakeParameters()
|
||||
{ Trauma = 0.08f, DecayRate = 1.0f, Frequency = 0.009f };
|
||||
var otherShakeTranslation = new ESScreenshakeParameters() { Trauma = 0.45f, DecayRate = 1.1f, Frequency = 0.04f };
|
||||
_shake.Screenshake(user, null, userShakeRotation);
|
||||
foreach (var shakeTarget in targets)
|
||||
{
|
||||
_shake.Screenshake(shakeTarget, otherShakeTranslation, null);
|
||||
}
|
||||
// ES END
|
||||
}
|
||||
}
|
||||
|
||||
@@ -764,10 +774,21 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem
|
||||
_meleeSound.PlayHitSound(target, user, GetHighestDamageSound(appliedDamage, _protoManager), hitEvent.HitSoundOverride, component);
|
||||
}
|
||||
|
||||
// ES START
|
||||
// dog shit copy plaste but thats melee for you
|
||||
var userShakeRotation = new ESScreenshakeParameters()
|
||||
{ Trauma = 0.08f, DecayRate = 1.0f, Frequency = 0.009f };
|
||||
var otherShakeTranslation = new ESScreenshakeParameters() { Trauma = 0.45f, DecayRate = 1.1f, Frequency = 0.04f };
|
||||
_shake.Screenshake(user, null, userShakeRotation);
|
||||
foreach (var shakeTarget in targets)
|
||||
{
|
||||
_shake.Screenshake(shakeTarget, otherShakeTranslation, null);
|
||||
}
|
||||
// ES END
|
||||
|
||||
if (appliedDamage.GetTotal() > FixedPoint2.Zero)
|
||||
{
|
||||
DoDamageEffect(targets, user, Transform(targets[0]));
|
||||
DoScreenshake(meleeUid, damage, user, targets); // Starlight | ES Screenshake
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -1083,55 +1104,4 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Starlight begin | ES Screenshake
|
||||
private void DoScreenshake(EntityUid weapon, DamageSpecifier damage, EntityUid attacker, List<EntityUid> targets)
|
||||
{
|
||||
if(damage.GetTotal()>4) // only show to others if it hurts real bad // DeltaV - reduce from 8 to 4
|
||||
{
|
||||
var otherTranslation = new ESScreenshakeParameters
|
||||
{
|
||||
Trauma = 0.45f,
|
||||
DecayRate = 1.1f,
|
||||
Frequency = 0.04f,
|
||||
};
|
||||
foreach(var target in targets)
|
||||
_shake.Screenshake(target, otherTranslation, null);
|
||||
}
|
||||
|
||||
// only show to attacker if they put real oompf into it, or the weapon is just THAT strong
|
||||
// var bluntRequirement = damage.DamageDict.TryGetValue(BluntDamageName, out var blunt) && blunt >= 20; // DeltaV - unused
|
||||
var isWielding = TryComp<WieldableComponent>(weapon, out var wieldable) && wieldable.Wielded;
|
||||
|
||||
// DeltaV - unused
|
||||
// if (!bluntRequirement && !wieldRequirement)
|
||||
// return;
|
||||
|
||||
// DeltaV - heavy/light screenshake variants START
|
||||
ESScreenshakeParameters userRotation;
|
||||
if (damage.GetTotal() >= 15 || isWielding)
|
||||
{
|
||||
// heavy damage or two-handed
|
||||
userRotation = new ESScreenshakeParameters
|
||||
{
|
||||
Trauma = 0.08f,
|
||||
DecayRate = 1,
|
||||
Frequency = 0.009f,
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
// light damage
|
||||
userRotation = new ESScreenshakeParameters
|
||||
{
|
||||
Trauma = 0.06f,
|
||||
DecayRate = 1,
|
||||
Frequency = 0.0045f,
|
||||
};
|
||||
}
|
||||
// DeltaV END
|
||||
|
||||
_shake.Screenshake(attacker, null, userRotation);
|
||||
}
|
||||
//Starlight end
|
||||
}
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
using Lidgren.Network;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared._DV.AccountLinking;
|
||||
|
||||
public sealed class LinkAccountCodeMsg : NetMessage
|
||||
{
|
||||
public Guid Code;
|
||||
|
||||
public override MsgGroups MsgGroup => MsgGroups.Core;
|
||||
|
||||
public override void ReadFromBuffer(NetIncomingMessage buffer, IRobustSerializer serializer)
|
||||
{
|
||||
Code = buffer.ReadGuid();
|
||||
}
|
||||
|
||||
public override void WriteToBuffer(NetOutgoingMessage buffer, IRobustSerializer serializer)
|
||||
{
|
||||
buffer.Write(Code);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using Lidgren.Network;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared._DV.AccountLinking;
|
||||
|
||||
public sealed class LinkAccountRequestMsg : NetMessage
|
||||
{
|
||||
public override MsgGroups MsgGroup => MsgGroups.Core;
|
||||
|
||||
public override void ReadFromBuffer(NetIncomingMessage buffer, IRobustSerializer serializer)
|
||||
{
|
||||
}
|
||||
|
||||
public override void WriteToBuffer(NetOutgoingMessage buffer, IRobustSerializer serializer)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
using System.IO;
|
||||
using Lidgren.Network;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared._DV.AccountLinking;
|
||||
|
||||
public sealed class LinkAccountStatusMsg : NetMessage
|
||||
{
|
||||
public override MsgGroups MsgGroup => MsgGroups.Core;
|
||||
|
||||
public SharedPatronFull? Patron;
|
||||
|
||||
public override void ReadFromBuffer(NetIncomingMessage buffer, IRobustSerializer serializer)
|
||||
{
|
||||
var isPatron = buffer.ReadBoolean();
|
||||
if (!isPatron)
|
||||
return;
|
||||
|
||||
buffer.ReadPadBits();
|
||||
var length = buffer.ReadVariableInt32();
|
||||
using var stream = new MemoryStream(length);
|
||||
buffer.ReadAlignedMemory(stream, length);
|
||||
Patron = serializer.Deserialize<SharedPatronFull>(stream);
|
||||
}
|
||||
|
||||
public override void WriteToBuffer(NetOutgoingMessage buffer, IRobustSerializer serializer)
|
||||
{
|
||||
if (Patron == null)
|
||||
{
|
||||
buffer.Write(false);
|
||||
return;
|
||||
}
|
||||
|
||||
buffer.Write(true);
|
||||
buffer.WritePadBits();
|
||||
using (var stream = new MemoryStream())
|
||||
{
|
||||
serializer.Serialize(stream, Patron);
|
||||
buffer.WriteVariableInt32((int) stream.Length);
|
||||
stream.TryGetBuffer(out var segment);
|
||||
buffer.Write(segment);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
using Lidgren.Network;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared._DV.AccountLinking;
|
||||
|
||||
public sealed class PatronListMsg : NetMessage
|
||||
{
|
||||
public override MsgGroups MsgGroup => MsgGroups.Core;
|
||||
|
||||
public List<SharedPatron> Patrons = default!;
|
||||
|
||||
public override void ReadFromBuffer(NetIncomingMessage buffer, IRobustSerializer serializer)
|
||||
{
|
||||
var count = buffer.ReadVariableInt32();
|
||||
Patrons = new List<SharedPatron>(count);
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
var name = buffer.ReadString();
|
||||
var tier = buffer.ReadString();
|
||||
Patrons.Add(new SharedPatron(name, tier));
|
||||
}
|
||||
}
|
||||
|
||||
public override void WriteToBuffer(NetOutgoingMessage buffer, IRobustSerializer serializer)
|
||||
{
|
||||
buffer.WriteVariableInt32(Patrons.Count);
|
||||
foreach (var patron in Patrons)
|
||||
{
|
||||
buffer.Write(patron.Name);
|
||||
buffer.Write(patron.Tier);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared._DV.AccountLinking;
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class SharedPatron(string name, string tier)
|
||||
{
|
||||
public readonly string Name = name;
|
||||
public readonly string Tier = tier;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared._DV.AccountLinking;
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed record SharedPatronFull(
|
||||
SharedPatronTier? Tier,
|
||||
bool Linked
|
||||
);
|
||||
@@ -0,0 +1,9 @@
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared._DV.AccountLinking;
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed record SharedPatronTier(
|
||||
bool ShowOnCredits,
|
||||
string Tier
|
||||
);
|
||||
@@ -0,0 +1,13 @@
|
||||
using Robust.Shared.Configuration;
|
||||
|
||||
namespace Content.Shared._DV.CCVars;
|
||||
|
||||
public sealed partial class DCCVars
|
||||
{
|
||||
/// <summary>
|
||||
/// Redirects you to the channel in discord to link your account.
|
||||
/// </summary>
|
||||
public static readonly CVarDef<string> DiscordAccountLinkingMessageLink =
|
||||
CVarDef.Create("discord.discord_account_linking_message_link", "", CVar.REPLICATED | CVar.SERVER);
|
||||
|
||||
}
|
||||
@@ -1,5 +1,3 @@
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Damage.Prototypes;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Prototypes;
|
||||
@@ -38,8 +36,6 @@ public sealed partial class CosmicColossusComponent : Component
|
||||
|
||||
[DataField] public EntProtoId Attack1Vfx = "CosmicColossusAttack1Vfx";
|
||||
|
||||
[DataField] public EntProtoId BuffVfx = "ColossusBuffVfx";
|
||||
|
||||
[DataField] public EntProtoId TileDetonations = "MobTileDamageZone";
|
||||
|
||||
[DataField] public EntProtoId EffigyPrototype = "CosmicEffigy";
|
||||
@@ -56,23 +52,13 @@ public sealed partial class CosmicColossusComponent : Component
|
||||
|
||||
[DataField] public TimeSpan HibernationWait = TimeSpan.FromSeconds(30);
|
||||
|
||||
[DataField] public TimeSpan DeathWaitSpawn = TimeSpan.FromMinutes(15);
|
||||
|
||||
[DataField] public TimeSpan DeathWaitEffigy = TimeSpan.FromMinutes(10);
|
||||
[DataField] public TimeSpan DeathWait = TimeSpan.FromMinutes(15);
|
||||
|
||||
[DataField] public bool Attacking;
|
||||
|
||||
[DataField] public bool Hibernating;
|
||||
|
||||
[DataField] public bool Timed;
|
||||
|
||||
[DataField] public short CompletedEffigies;
|
||||
|
||||
[DataField] public short MaxEffigies = 3;
|
||||
|
||||
[DataField] public DamageSpecifier BonusDamage = new();
|
||||
|
||||
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
namespace Content.Shared._DV.Mind;
|
||||
|
||||
using Content.Shared.Mind.Components;
|
||||
|
||||
[ByRefEvent]
|
||||
public record struct MindStateUpdatedEvent(MindState State);
|
||||
@@ -1,4 +1,3 @@
|
||||
using Content.Shared.ActionBlocker;
|
||||
using Content.Shared._DV.Movement.Components;
|
||||
using Content.Shared.Movement.Components;
|
||||
using Content.Shared.Movement.Events;
|
||||
@@ -8,19 +7,12 @@ namespace Content.Shared.Movement.Systems;
|
||||
public abstract partial class SharedJetpackSystem
|
||||
{
|
||||
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||
[Dependency] private readonly ActionBlockerSystem _actionBlocker = default!;
|
||||
|
||||
private void OnJetpackToggle(Entity<JetpackComponent> jetpack, ref ToggleJetpackEvent args)
|
||||
{
|
||||
if (args.Handled)
|
||||
return;
|
||||
|
||||
if (!_actionBlocker.CanComplexInteract(args.Performer))
|
||||
{
|
||||
_popup.PopupClient(Loc.GetString("jetpack-too-complex"), jetpack, args.Performer);
|
||||
return;
|
||||
}
|
||||
|
||||
jetpack.Comp.AutomaticMode = !jetpack.Comp.AutomaticMode;
|
||||
jetpack.Comp.AutomaticUser = args.Performer;
|
||||
Dirty(jetpack);
|
||||
|
||||
@@ -42,9 +42,6 @@ public sealed class PagerSystem : EntitySystem
|
||||
|
||||
private void OnAfterInteract(Entity<PagerComponent> ent, ref AfterInteractEvent args)
|
||||
{
|
||||
if (!args.CanReach)
|
||||
return;
|
||||
|
||||
if (args.Handled || !TryComp<DeviceNetworkComponent>(args.Target, out var targetNetwork) || !HasComp<PageSenderComponent>(args.Target))
|
||||
return;
|
||||
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared._DV.Psionics.Components.PsionicPowers;
|
||||
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
public sealed partial class FracturedFormBodyComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// The entity uid of the form that is currently being controlled.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public EntityUid ControllingForm { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
using Content.Shared.Cloning;
|
||||
using Content.Shared.Roles;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Shared._DV.Psionics.Components.PsionicPowers;
|
||||
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, AutoGenerateComponentPause]
|
||||
public sealed partial class FracturedFormPowerComponent : BasePsionicPowerComponent
|
||||
{
|
||||
public override EntProtoId ActionProtoId { get; set; } = "ActionFracturedForm";
|
||||
|
||||
public override string PowerName { get; set; } = "psionic-power-name-fractured-form";
|
||||
|
||||
public override int MinGlimmerChanged { get; set; } = 5;
|
||||
|
||||
public override int MaxGlimmerChanged { get; set; } = 15;
|
||||
|
||||
/// <summary>
|
||||
/// The chance for the clone to have the starting gear of the psionic.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float HasGearChance = 0.4f;
|
||||
|
||||
/// <summary>
|
||||
/// The chance for the clone to have a different species than the psionic.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float DifferentSpeciesChance = 0.6f;
|
||||
|
||||
/// <summary>
|
||||
/// These are settings for creating the second body.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public ProtoId<CloningSettingsPrototype> CopyNaked = "CloningPod";
|
||||
|
||||
/// <summary>
|
||||
/// These are settings for creating the second body.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public ProtoId<CloningSettingsPrototype> CopyClothed = "Antag";
|
||||
|
||||
/// <summary>
|
||||
/// These are settings for creating the second body.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public ProtoId<JobPrototype> VisitorJob = "Passenger";
|
||||
|
||||
/// <summary>
|
||||
/// These are settings for creating the second body.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public ProtoId<JobPrototype>? NakedJob; // Scary null, but we explicitly want no job for naked spawns.
|
||||
|
||||
/// <summary>
|
||||
/// The minimum time between forced swaps.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public TimeSpan NextSwapMinTime = TimeSpan.FromMinutes(5);
|
||||
|
||||
/// <summary>
|
||||
/// The maximum time between swaps.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public TimeSpan NextSwapMaxTime = TimeSpan.FromMinutes(20);
|
||||
|
||||
/// <summary>
|
||||
/// The current timer for when the next swap is forced.
|
||||
/// </summary>
|
||||
[DataField, AutoPausedField]
|
||||
public TimeSpan NextSwap = TimeSpan.FromMinutes(5);
|
||||
|
||||
/// <summary>
|
||||
/// The minimum time between voluntary swaps by sleeping.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public TimeSpan VoluntarySwapCooldown = TimeSpan.FromSeconds(10);
|
||||
|
||||
/// <summary>
|
||||
/// The minimum time between voluntary swaps by sleeping.
|
||||
/// </summary>
|
||||
[DataField, AutoPausedField]
|
||||
public TimeSpan NextVoluntarySwap = TimeSpan.FromSeconds(10);
|
||||
|
||||
/// <summary>
|
||||
/// The length of the DoAfter for swapping bodies.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float ManualSwapTime = 5f;
|
||||
|
||||
/// <summary>
|
||||
/// The time the psionic needs to be asleep to swap bodies.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public TimeSpan SwapTimeAsleep = TimeSpan.FromSeconds(3);
|
||||
|
||||
/// <summary>
|
||||
/// The time length where the warning is sent before the forced swap.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public TimeSpan WarningTimeBeforeSleep = TimeSpan.FromSeconds(5);
|
||||
|
||||
/// <summary>
|
||||
/// A boolean check for when the person has been warned.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public bool SleepWarned;
|
||||
|
||||
/// <summary>
|
||||
/// The bodies that the psionic user can swap into.
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public List<EntityUid> Bodies { get; set; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// The sound that plays when the swap happens.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public SoundSpecifier SwapSound = new SoundPathSpecifier("/Audio/Effects/teleport_arrival.ogg")
|
||||
{
|
||||
Params = AudioParams.Default.WithVariation(0.05f),
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
using Content.Shared.Actions;
|
||||
|
||||
namespace Content.Shared._DV.Psionics.Events.PowerActionEvents;
|
||||
|
||||
public sealed partial class FracturedFormPowerActionEvent : InstantActionEvent;
|
||||
@@ -0,0 +1,7 @@
|
||||
using Content.Shared.DoAfter;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared._DV.Psionics.Events.PowerDoAfterEvents;
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed partial class FracturedFormDoAfterEvent : SimpleDoAfterEvent;
|
||||
@@ -0,0 +1,150 @@
|
||||
using Content.Shared._DV.Psionics.Components.PsionicPowers;
|
||||
using Content.Shared._DV.Psionics.Events;
|
||||
using Content.Shared._DV.Psionics.Events.PowerActionEvents;
|
||||
using Content.Shared._DV.Psionics.Events.PowerDoAfterEvents;
|
||||
using Content.Shared.Bed.Sleep;
|
||||
using Content.Shared.Chat;
|
||||
using Content.Shared.DoAfter;
|
||||
using Content.Shared.Examine;
|
||||
using Content.Shared.Mind.Components;
|
||||
using Content.Shared.Mobs.Components;
|
||||
using Content.Shared.Mobs.Systems;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.SSDIndicator;
|
||||
using Robust.Shared.Player;
|
||||
|
||||
namespace Content.Shared._DV.Psionics.Systems.PsionicPowers;
|
||||
|
||||
public abstract class SharedFracturedFormPowerSystem : BasePsionicPowerSystem<FracturedFormPowerComponent, FracturedFormPowerActionEvent>
|
||||
{
|
||||
[Dependency] protected readonly SharedChatSystem Chat = default!;
|
||||
[Dependency] protected readonly MobStateSystem MobState = default!;
|
||||
[Dependency] protected readonly SleepingSystem Sleeping = default!;
|
||||
|
||||
protected EntityQuery<FracturedFormPowerComponent> FracturedQuery;
|
||||
private EntityQuery<ForcedSleepingStatusEffectComponent> _forcedSleepQuery;
|
||||
protected EntityQuery<MindContainerComponent> MindContainerQuery;
|
||||
private EntityQuery<MobStateComponent> _mobStateQuery;
|
||||
protected EntityQuery<SSDIndicatorComponent> SsdQuery;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<FracturedFormPowerComponent, FracturedFormDoAfterEvent>(OnDoAfter);
|
||||
SubscribeLocalEvent<FracturedFormBodyComponent, PlayerDetachedEvent>(OnPlayerDetached, after: [typeof(SSDIndicatorSystem)]);
|
||||
SubscribeLocalEvent<FracturedFormBodyComponent, ExaminedEvent>(OnExamine);
|
||||
|
||||
FracturedQuery = GetEntityQuery<FracturedFormPowerComponent>();
|
||||
_forcedSleepQuery = GetEntityQuery<ForcedSleepingStatusEffectComponent>();
|
||||
MindContainerQuery = GetEntityQuery<MindContainerComponent>();
|
||||
_mobStateQuery = GetEntityQuery<MobStateComponent>();
|
||||
SsdQuery = GetEntityQuery<SSDIndicatorComponent>();
|
||||
}
|
||||
|
||||
protected override void OnPowerUsed(Entity<FracturedFormPowerComponent> psionic, ref FracturedFormPowerActionEvent args)
|
||||
{
|
||||
if (!CanSwap(psionic))
|
||||
{
|
||||
Popup.PopupClient(Loc.GetString("psionic-power-fractured-form-nobodies"), psionic, args.Performer, PopupType.Large);
|
||||
return;
|
||||
}
|
||||
|
||||
Chat.TryEmoteWithChat(psionic.Owner, "Yawn");
|
||||
Popup.PopupClient(Loc.GetString("psionic-power-fractured-form-sleepy"), psionic, args.Performer, PopupType.LargeCaution);
|
||||
|
||||
var doAfterArgs = new DoAfterArgs(EntityManager, args.Performer, psionic.Comp.ManualSwapTime, new FracturedFormDoAfterEvent(), psionic);
|
||||
|
||||
if (!DoAfter.TryStartDoAfter(doAfterArgs, out var doAfterId))
|
||||
return;
|
||||
|
||||
psionic.Comp.SaveDoAfterId(doAfterId.Value);
|
||||
Dirty(psionic);
|
||||
AfterPowerUsed(psionic, args.Performer);
|
||||
}
|
||||
|
||||
private void OnDoAfter(Entity<FracturedFormPowerComponent> psionic, ref FracturedFormDoAfterEvent args)
|
||||
{
|
||||
if (args.Cancelled || args.Handled)
|
||||
return;
|
||||
args.Handled = true;
|
||||
psionic.Comp.RemoveSavedDoAfterId();
|
||||
|
||||
Dirty(psionic);
|
||||
Sleeping.TrySleeping(psionic.Owner);
|
||||
}
|
||||
|
||||
private void OnPlayerDetached(Entity<FracturedFormBodyComponent> body, ref PlayerDetachedEvent args)
|
||||
{
|
||||
if (!SsdQuery.TryComp(body, out var comp)
|
||||
|| FracturedQuery.HasComp(body))
|
||||
return;
|
||||
|
||||
comp.IsSSD = false;
|
||||
}
|
||||
|
||||
private void OnExamine(Entity<FracturedFormBodyComponent> psionic, ref ExaminedEvent args)
|
||||
{
|
||||
if (HasComp<FracturedFormPowerComponent>(psionic))
|
||||
return;
|
||||
|
||||
if (TryComp<FracturedFormPowerComponent>(args.Examiner, out var fracturedHost) && fracturedHost.Bodies.Contains(psionic.Owner))
|
||||
args.PushMarkup($"[color=yellow]{Loc.GetString("psionic-power-fractured-form-examine-self", ("ent", psionic))}[/color]");
|
||||
else
|
||||
args.PushMarkup($"[color=yellow]{Loc.GetString("psionic-power-fractured-form-ssd", ("ent", psionic))}[/color]");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// We're overriding the dispelled method to introduce some special behavior.
|
||||
/// </summary>
|
||||
protected override void OnDispelled(Entity<FracturedFormPowerComponent> psionic, ref DispelledEvent args)
|
||||
{
|
||||
if (args.Handled)
|
||||
return;
|
||||
args.Handled = true;
|
||||
|
||||
// This resets the swap timer.
|
||||
var randomTime = Random.Next(psionic.Comp.NextSwapMinTime, psionic.Comp.NextSwapMaxTime);
|
||||
psionic.Comp.NextSwap = Timing.CurTime + randomTime;
|
||||
|
||||
if (psionic.Comp.GetDoAfterId() is not { } doAfterId)
|
||||
{
|
||||
Popup.PopupClient(Loc.GetString("psionic-power-fractured-form-dispelled"), psionic, PopupType.MediumCaution);
|
||||
return;
|
||||
}
|
||||
|
||||
DoAfter.Cancel(doAfterId);
|
||||
Popup.PopupClient(Loc.GetString("psionic-dispelled"), psionic, PopupType.MediumCaution);
|
||||
psionic.Comp.RemoveSavedDoAfterId();
|
||||
}
|
||||
|
||||
protected bool IsValidBody(Entity<FracturedFormPowerComponent> psionic, EntityUid otherBody)
|
||||
{
|
||||
if (otherBody == psionic.Owner)
|
||||
return false;
|
||||
if (!psionic.Comp.Bodies.Contains(otherBody))
|
||||
return false;
|
||||
if (!MindContainerQuery.TryComp(otherBody, out var cmind))
|
||||
return false;
|
||||
if (cmind.HasMind)
|
||||
return false;
|
||||
if (_forcedSleepQuery.HasComp(otherBody))
|
||||
return false;
|
||||
if (!_mobStateQuery.TryComp(otherBody, out var mobState))
|
||||
return false;
|
||||
if (MobState.IsIncapacitated(otherBody, mobState))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool CanSwap(Entity<FracturedFormPowerComponent> psionic)
|
||||
{
|
||||
foreach (var body in psionic.Comp.Bodies)
|
||||
{
|
||||
if (IsValidBody(psionic, body))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
using Content.Shared.Trigger.Components.Effects;
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared._DV.Trigger.Components.Effects;
|
||||
|
||||
/// <summary>
|
||||
/// is used for implants and such so the right paramters can be passed
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
|
||||
public sealed partial class CreateHitmanCardOnTriggerComponent : BaseXOnTriggerComponent;
|
||||
@@ -1,30 +0,0 @@
|
||||
using Content.Shared._DV.Trigger.Components.Effects;
|
||||
using Content.Shared.Trigger;
|
||||
using Content.Shared.Hands.EntitySystems;
|
||||
|
||||
namespace Content.Shared._DV.Trigger.Systems;
|
||||
|
||||
public sealed class CreateHitmanCardOnTriggerSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||
[Dependency] private readonly SharedHandsSystem _hands = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<CreateHitmanCardOnTriggerComponent, TriggerEvent>(OnTrigger);
|
||||
}
|
||||
|
||||
//just spawns a card on the given target. uses predicated or spawn next to based on if. as trigger effect doesnt seem to have predicted on it ill just check if this is sever first and if not assume its predicted.
|
||||
private void OnTrigger(Entity<CreateHitmanCardOnTriggerComponent> ent, ref TriggerEvent args)
|
||||
{
|
||||
if (args.Handled || args.User is not { } user)
|
||||
return;
|
||||
|
||||
var mapCoords = _transform.GetMapCoordinates(ent);
|
||||
var card = EntityManager.PredictedSpawn("HitmanBusinessCard", mapCoords);
|
||||
_hands.TryPickupAnyHand(user, card);
|
||||
args.Handled = true;
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
using Content.Shared.NPC.Prototypes;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Shared.Zombies;
|
||||
|
||||
public sealed partial class ZombieComponent
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// DeltaV - The list of factions that an entity is part of from before they were zombified.
|
||||
/// The zombification process removes them, but cloning a zombie copies the NPCFactionMemberComponent
|
||||
/// so they are still considered a zombie faction-wise, so we need to keep track of what they had right
|
||||
/// before the factions are removed.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public List<ProtoId<NpcFactionPrototype>> FactionsBeforeZombification = new();
|
||||
|
||||
/// <summary>
|
||||
/// DeltaV - Tracks if the entity was a pacifist before turning into a zombie. Although, there are other
|
||||
/// scenarios where the person might be pacified but those are usually temporary.
|
||||
/// </summary>
|
||||
public bool WasPacisfistBeforeZombification = false;
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user