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")]
|
||||
|
||||
@@ -262,32 +262,6 @@ public sealed partial class AdminVerbSystem
|
||||
Message = string.Join(": ", ntAgent, Loc.GetString("admin-verb-text-make-NTAgent")),
|
||||
};
|
||||
args.Verbs.Add(agent);
|
||||
|
||||
args.Verbs.Add(new ()
|
||||
{
|
||||
Text = Loc.GetString("admin-verb-make-HeadInsurgent.text"),
|
||||
Category = VerbCategory.Antag,
|
||||
Icon = new SpriteSpecifier.Rsi(new ResPath("/Textures/Interface/Misc/job_icons.rsi"), "HeadRevolutionary"),
|
||||
Act = () =>
|
||||
{
|
||||
_antag.ForceMakeAntag<NTAgentRuleComponent>(targetPlayer, "UprisingRule", "HeadInsurgent");
|
||||
},
|
||||
Impact = LogImpact.High,
|
||||
Message = Loc.GetString("admin-verb-make-HeadInsurgent.message"),
|
||||
});
|
||||
|
||||
args.Verbs.Add(new ()
|
||||
{
|
||||
Text = Loc.GetString("admin-verb-make-HeadLoyalist.text"),
|
||||
Category = VerbCategory.Antag,
|
||||
Icon = new SpriteSpecifier.Rsi(new ResPath("/Textures/Interface/Misc/job_icons.rsi"), "Nanotrasen"),
|
||||
Act = () =>
|
||||
{
|
||||
_antag.ForceMakeAntag<NTAgentRuleComponent>(targetPlayer, "UprisingRule", "HeadLoyalist");
|
||||
},
|
||||
Impact = LogImpact.High,
|
||||
Message = Loc.GetString("admin-verb-make-HeadLoyalist.message"),
|
||||
});
|
||||
// End DeltaV Additions
|
||||
// start DeltaV Additions - add hitman
|
||||
var hitmanName = Loc.GetString("admin-verb-make-hitman");
|
||||
|
||||
@@ -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"));
|
||||
}
|
||||
|
||||
@@ -23,8 +23,7 @@ public sealed partial class AntagSelectionSystem
|
||||
/// </summary>
|
||||
public bool TryGetNextAvailableDefinition(Entity<AntagSelectionComponent> ent,
|
||||
[NotNullWhen(true)] out AntagSelectionDefinition? definition,
|
||||
int? players = null,
|
||||
string? preferredRole = null) // DeltaV - preferrec roles
|
||||
int? players = null)
|
||||
{
|
||||
definition = null;
|
||||
|
||||
@@ -40,11 +39,6 @@ public sealed partial class AntagSelectionSystem
|
||||
// It needs to track selected minds for each definition independently.
|
||||
foreach (var def in ent.Comp.Definitions)
|
||||
{
|
||||
// Begin DeltaV - preferred roles
|
||||
if (preferredRole is not null && !def.PrefRoles.Contains(preferredRole))
|
||||
continue;
|
||||
// End DeltaV - preferred roles
|
||||
|
||||
var target = GetTargetAntagCount(ent, null, def);
|
||||
|
||||
if (mindCount < target)
|
||||
@@ -342,11 +336,11 @@ public sealed partial class AntagSelectionSystem
|
||||
/// This technically is a gamerule-ent-less way to make an entity an antag.
|
||||
/// You should almost never be using this.
|
||||
/// </summary>
|
||||
public void ForceMakeAntag<T>(ICommonSession? player, string defaultRule, string? preferredRole = null) where T : Component // DeltaV - preferred antag roles
|
||||
public void ForceMakeAntag<T>(ICommonSession? player, string defaultRule) where T : Component
|
||||
{
|
||||
var rule = ForceGetGameRuleEnt<T>(defaultRule);
|
||||
|
||||
if (!TryGetNextAvailableDefinition(rule, out var def, preferredRole: preferredRole)) // DeltaV - preferred antag roles
|
||||
if (!TryGetNextAvailableDefinition(rule, out var def))
|
||||
def = rule.Comp.Definitions.Last();
|
||||
MakeAntag(rule, player, def.Value);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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))
|
||||
{
|
||||
|
||||
@@ -475,7 +475,7 @@ public sealed class NukeSystem : EntitySystem
|
||||
/// <summary>
|
||||
/// Force a nuclear bomb to start a countdown timer
|
||||
/// </summary>
|
||||
public void ArmBomb(EntityUid uid, NukeComponent? component = null, string? announcementId = null) // DeltaV - custom announcements
|
||||
public void ArmBomb(EntityUid uid, NukeComponent? component = null)
|
||||
{
|
||||
if (!Resolve(uid, ref component))
|
||||
return;
|
||||
@@ -500,7 +500,7 @@ public sealed class NukeSystem : EntitySystem
|
||||
_selectedNukeSong = _audio.ResolveSound(component.ArmMusic);
|
||||
|
||||
// warn a crew
|
||||
var announcement = Loc.GetString(announcementId ?? "nuke-component-announcement-armed", // DeltaV - custom announcements
|
||||
var announcement = Loc.GetString("nuke-component-announcement-armed",
|
||||
("time", (int) component.RemainingTime),
|
||||
("location", FormattedMessage.RemoveMarkupOrThrow(_navMap.GetNearestBeaconString((uid, nukeXform)))));
|
||||
var sender = Loc.GetString("nuke-component-announcement-sender");
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,84 +0,0 @@
|
||||
using Content.Server.Revolutionary.Components;
|
||||
using Content.Shared._DV.Uprising;
|
||||
using Content.Shared.Cuffs.Components;
|
||||
using Content.Shared.Mind.Components;
|
||||
using Content.Shared.Mobs;
|
||||
using Content.Shared.Mobs.Components;
|
||||
using Content.Shared.Objectives.Components;
|
||||
using Content.Shared.Roles;
|
||||
using Content.Shared.Station;
|
||||
|
||||
namespace Content.Server.Uprising;
|
||||
|
||||
public sealed class CommandStatusObjectiveSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly SharedRoleSystem _role = default!;
|
||||
[Dependency] private readonly SharedStationSystem _station = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<CommandStatusObjectiveComponent, ObjectiveGetProgressEvent>(OnGetProgress);
|
||||
}
|
||||
|
||||
private bool CheckIncapacitated(EntityUid uid)
|
||||
{
|
||||
if (TryComp<CuffableComponent>(uid, out var cuffed) && cuffed.CuffedHandCount > 0)
|
||||
return true;
|
||||
|
||||
if (!TryComp<MobStateComponent>(uid, out var state))
|
||||
return true;
|
||||
|
||||
if (state.CurrentState is MobState.Dead or MobState.Invalid)
|
||||
return true;
|
||||
|
||||
if (_station.GetOwningStation(uid) == null)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void OnGetProgress(Entity<CommandStatusObjectiveComponent> ent, ref ObjectiveGetProgressEvent args)
|
||||
{
|
||||
var total = 0;
|
||||
var counted = 0;
|
||||
|
||||
var query = AllEntityQuery<MindContainerComponent, CommandStaffComponent>();
|
||||
while (query.MoveNext(out var uid, out var mind, out _))
|
||||
{
|
||||
if (mind.Mind is not { } mindUid)
|
||||
continue;
|
||||
|
||||
total++;
|
||||
|
||||
var converted = false;
|
||||
foreach (var role in ent.Comp.ConvertedRoles)
|
||||
{
|
||||
if (!EntityManager.ComponentFactory.TryGetRegistration(role, out var roleReg))
|
||||
{
|
||||
Log.Error($"Role component not found for CrewConversionObjective: {role}");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!_role.MindHasRole(mindUid, roleReg.Type, out _))
|
||||
continue;
|
||||
|
||||
converted = true;
|
||||
break;
|
||||
}
|
||||
|
||||
var incapacitated = CheckIncapacitated(uid);
|
||||
|
||||
if (converted && !incapacitated || !converted && ent.Comp.ShouldUnconvertedBeIncapacitated && incapacitated)
|
||||
{
|
||||
counted++;
|
||||
}
|
||||
}
|
||||
|
||||
if (total == 0)
|
||||
return;
|
||||
|
||||
args.Progress = (float)counted / (float)total;
|
||||
}
|
||||
}
|
||||
@@ -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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,8 +0,0 @@
|
||||
namespace Content.Server._DV.Uprising;
|
||||
|
||||
[RegisterComponent, Access(typeof(UprisingRuleSystem))]
|
||||
public sealed partial class UprisingConsoleComponent : Component
|
||||
{
|
||||
[DataField]
|
||||
public TimeSpan Delay = TimeSpan.FromSeconds(5);
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
using Content.Shared._DV.Roles;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
|
||||
|
||||
namespace Content.Server._DV.Uprising;
|
||||
|
||||
[RegisterComponent, Access(typeof(UprisingRuleSystem)), AutoGenerateComponentPause]
|
||||
public sealed partial class UprisingRuleComponent : Component
|
||||
{
|
||||
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), AutoPausedField]
|
||||
public TimeSpan? NukeAnnouncementAt;
|
||||
|
||||
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), AutoPausedField]
|
||||
public TimeSpan? NukeTimeAt;
|
||||
|
||||
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), AutoPausedField]
|
||||
public TimeSpan? FirstWarningAt;
|
||||
|
||||
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), AutoPausedField]
|
||||
public TimeSpan? ImpendingWarningAt;
|
||||
|
||||
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), AutoPausedField]
|
||||
public TimeSpan? FinalWarningAt;
|
||||
|
||||
[DataField]
|
||||
public TimeSpan NukeAnnouncementDelay = TimeSpan.FromMinutes(30);
|
||||
|
||||
[DataField]
|
||||
public TimeSpan NukeTimeDelay = TimeSpan.FromMinutes(90);
|
||||
|
||||
[DataField]
|
||||
public TimeSpan FirstWarning = TimeSpan.FromMinutes(30);
|
||||
|
||||
[DataField]
|
||||
public TimeSpan ImpendingWarning = TimeSpan.FromMinutes(15);
|
||||
|
||||
[DataField]
|
||||
public TimeSpan FinalWarning = TimeSpan.FromMinutes(5);
|
||||
|
||||
[DataField]
|
||||
public float NukeDuration = 60f;
|
||||
|
||||
[DataField]
|
||||
public (UprisingSide, TimeSpan)? SideWinsAt;
|
||||
|
||||
[DataField]
|
||||
public UprisingSide? SideWon;
|
||||
|
||||
[DataField]
|
||||
public Dictionary<UprisingSide, TimeSpan> SideTimes = new()
|
||||
{
|
||||
{ UprisingSide.Insurgent , TimeSpan.FromMinutes(3) },
|
||||
{ UprisingSide.Loyalist , TimeSpan.FromMinutes(3) },
|
||||
};
|
||||
}
|
||||
@@ -1,341 +0,0 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Content.Server.Antag;
|
||||
using Content.Server.GameTicking;
|
||||
using Content.Server.GameTicking.Rules;
|
||||
using Content.Server.Nuke;
|
||||
using Content.Server.Pinpointer;
|
||||
using Content.Server.Roles;
|
||||
using Content.Shared._DV.Roles;
|
||||
using Content.Shared._DV.Uprising;
|
||||
using Content.Shared.Chat;
|
||||
using Content.Shared.Containers.ItemSlots;
|
||||
using Content.Shared.DoAfter;
|
||||
using Content.Shared.GameTicking.Components;
|
||||
using Content.Shared.Mind;
|
||||
using Content.Shared.Objectives.Components;
|
||||
using Content.Shared.Objectives.Systems;
|
||||
using Content.Shared.Roles;
|
||||
using Content.Shared.Verbs;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Server._DV.Uprising;
|
||||
|
||||
public sealed class UprisingRuleSystem : GameRuleSystem<UprisingRuleComponent>
|
||||
{
|
||||
[Dependency] private readonly SharedChatSystem _chat = default!;
|
||||
[Dependency] private readonly NukeSystem _nuke = default!;
|
||||
[Dependency] private readonly ItemSlotsSystem _itemSlots = default!;
|
||||
[Dependency] private readonly NavMapSystem _navMap = default!;
|
||||
[Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
|
||||
[Dependency] private readonly SharedMindSystem _mind = default!;
|
||||
[Dependency] private readonly SharedObjectivesSystem _objectives = default!;
|
||||
[Dependency] private readonly SharedRoleSystem _role = default!;
|
||||
[Dependency] private readonly AntagSelectionSystem _antag = default!;
|
||||
[Dependency] private readonly ISharedPlayerManager _player = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<LoyalistRoleComponent, GetBriefingEvent>(OnGetLoyalistBriefing);
|
||||
SubscribeLocalEvent<InsurgentRoleComponent, GetBriefingEvent>(OnGetInsurgentBriefing);
|
||||
|
||||
SubscribeLocalEvent<UprisingVictoryObjectiveComponent, ObjectiveGetProgressEvent>(OnUprisingVictoryProgress);
|
||||
|
||||
SubscribeLocalEvent<UprisingConsoleComponent, GetVerbsEvent<AlternativeVerb>>(OnGetConsoleVerbs);
|
||||
SubscribeLocalEvent<UprisingConsoleComponent, UprisingArmDoAfter>(OnArm);
|
||||
SubscribeLocalEvent<UprisingConsoleComponent, UprisingDisarmDoAfter>(OnDisarm);
|
||||
}
|
||||
|
||||
private void OnUprisingVictoryProgress(Entity<UprisingVictoryObjectiveComponent> ent, ref ObjectiveGetProgressEvent args)
|
||||
{
|
||||
if (GetRule() is not { } rule)
|
||||
return;
|
||||
|
||||
if (rule.Comp1.SideWinsAt is var (side, _) && ent.Comp.Side == side)
|
||||
args.Progress = 0.5f;
|
||||
else if (rule.Comp1.SideWon == ent.Comp.Side)
|
||||
args.Progress = 1f;
|
||||
else
|
||||
args.Progress = 0f;
|
||||
}
|
||||
|
||||
protected override void Started(EntityUid uid, UprisingRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args)
|
||||
{
|
||||
base.Started(uid, component, gameRule, args);
|
||||
|
||||
component.NukeAnnouncementAt = Timing.CurTime + component.NukeAnnouncementDelay;
|
||||
component.NukeTimeAt = Timing.CurTime + component.NukeTimeDelay;
|
||||
component.FirstWarningAt = component.NukeTimeAt - component.FirstWarning;
|
||||
component.ImpendingWarningAt = component.NukeTimeAt - component.ImpendingWarning;
|
||||
component.FinalWarningAt = component.NukeTimeAt - component.FinalWarning;
|
||||
}
|
||||
|
||||
protected override void ActiveTick(EntityUid uid, UprisingRuleComponent component, GameRuleComponent gameRule, float frameTime)
|
||||
{
|
||||
base.ActiveTick(uid, component, gameRule, frameTime);
|
||||
|
||||
if (component.NukeAnnouncementAt is { } nukeAnnouncement && nukeAnnouncement <= Timing.CurTime)
|
||||
{
|
||||
var time = component.NukeTimeAt.HasValue ? component.NukeTimeAt.Value - Timing.CurTime : component.NukeTimeDelay;
|
||||
_chat.DispatchGlobalAnnouncement(Loc.GetString("uprising-announcement-nuke-announcement", ("time", (int)Math.Round(time.TotalMinutes))), colorOverride: Color.Gold);
|
||||
component.NukeAnnouncementAt = null;
|
||||
}
|
||||
|
||||
if (component.FirstWarningAt is { } firstWarning && firstWarning <= Timing.CurTime)
|
||||
{
|
||||
var time = component.NukeTimeAt.HasValue ? component.NukeTimeAt.Value - Timing.CurTime : component.FirstWarning;
|
||||
_chat.DispatchGlobalAnnouncement(Loc.GetString("uprising-announcement-first-warning", ("time", (int)Math.Round(time.TotalMinutes))), colorOverride: Color.Gold);
|
||||
component.FirstWarningAt = null;
|
||||
}
|
||||
|
||||
if (component.ImpendingWarningAt is { } impendingWarning && impendingWarning <= Timing.CurTime)
|
||||
{
|
||||
var time = component.NukeTimeAt.HasValue ? component.NukeTimeAt.Value - Timing.CurTime : component.ImpendingWarning;
|
||||
_chat.DispatchGlobalAnnouncement(Loc.GetString("uprising-announcement-impending-warning", ("time", (int)Math.Round(time.TotalMinutes))), colorOverride: Color.Gold);
|
||||
component.ImpendingWarningAt = null;
|
||||
}
|
||||
|
||||
if (component.FinalWarningAt is { } finalWarning && finalWarning <= Timing.CurTime)
|
||||
{
|
||||
var time = component.NukeTimeAt.HasValue ? component.NukeTimeAt.Value - Timing.CurTime : component.FinalWarning;
|
||||
_chat.DispatchGlobalAnnouncement(Loc.GetString("uprising-announcement-final-warning", ("time", (int)Math.Round(time.TotalMinutes))), colorOverride: Color.Gold);
|
||||
component.FinalWarningAt = null;
|
||||
}
|
||||
|
||||
if (component.NukeTimeAt is { } nukeTime && nukeTime <= Timing.CurTime)
|
||||
{
|
||||
component.NukeTimeAt = null;
|
||||
var query = EntityQueryEnumerator<NukeComponent>();
|
||||
while (query.MoveNext(out var bombUid, out var bomb))
|
||||
{
|
||||
_nuke.SetRemainingTime(bombUid, component.NukeDuration, bomb);
|
||||
_itemSlots.SetLock(bombUid, bomb.DiskSlot, false);
|
||||
_itemSlots.TryEject(bombUid, bomb.DiskSlot, null, out _, true);
|
||||
_itemSlots.SetLock(bombUid, bomb.DiskSlot, true);
|
||||
_nuke.ArmBomb(bombUid, bomb, "uprising-announcement-nuke-armed");
|
||||
}
|
||||
}
|
||||
|
||||
if (component.SideWinsAt is var (winningSide, winningTime) && winningTime <= Timing.CurTime)
|
||||
{
|
||||
component.SideWinsAt = null;
|
||||
var query = EntityQueryEnumerator<NukeComponent>();
|
||||
while (query.MoveNext(out var bombUid, out var bomb))
|
||||
{
|
||||
_nuke.DisarmBomb(bombUid, bomb);
|
||||
}
|
||||
|
||||
component.SideWon = winningSide;
|
||||
_chat.DispatchGlobalAnnouncement(
|
||||
Loc.GetString($"uprising-announcement-victory-announcement-{winningSide}"),
|
||||
Loc.GetString($"uprising-announcement-victory-announcement-{winningSide}.sender"),
|
||||
colorOverride: Color.FromXaml(Loc.GetString($"uprising-announcement-victory-announcement-{winningSide}.color")));
|
||||
|
||||
GameTicker.EndRound();
|
||||
}
|
||||
}
|
||||
|
||||
protected override void AppendRoundEndText(EntityUid uid,
|
||||
UprisingRuleComponent component,
|
||||
GameRuleComponent gameRule,
|
||||
ref RoundEndTextAppendEvent args)
|
||||
{
|
||||
if (component.SideWinsAt is var (partialSide, _))
|
||||
{
|
||||
args.AddLine(Loc.GetString($"uprising-outcome-{partialSide}-partial"));
|
||||
}
|
||||
else if (component.SideWon is { } winningSide)
|
||||
{
|
||||
args.AddLine(Loc.GetString($"uprising-outcome-{winningSide}-full"));
|
||||
}
|
||||
else
|
||||
{
|
||||
args.AddLine(Loc.GetString("uprising-outcome-none"));
|
||||
}
|
||||
|
||||
args.AddLine(Loc.GetString("uprising-leads"));
|
||||
|
||||
var antags = _antag.GetAntagIdentifiers(uid);
|
||||
var leads = new HashSet<EntityUid>();
|
||||
|
||||
foreach (var (mind, sessionData, name) in antags)
|
||||
{
|
||||
leads.Add(mind);
|
||||
|
||||
if (_role.MindHasRole<UprisingRoleComponent>(mind, out var it))
|
||||
args.AddLine(Loc.GetString($"uprising-leads-user.{it.Value.Comp2.Side}", ("name", name), ("user", sessionData.UserName)));
|
||||
else
|
||||
args.AddLine(Loc.GetString("uprising-leads-user.Unknown", ("name", name), ("user", sessionData.UserName)));
|
||||
}
|
||||
args.AddLine(string.Empty);
|
||||
|
||||
var lines = new Dictionary<UprisingSide, List<string>>();
|
||||
foreach (var value in Enum.GetValues<UprisingSide>())
|
||||
{
|
||||
lines[value] = new();
|
||||
}
|
||||
|
||||
var minds = EntityQueryEnumerator<MindComponent>();
|
||||
while (minds.MoveNext(out var mindUid, out var mindComponent))
|
||||
{
|
||||
if (leads.Contains(mindUid))
|
||||
continue;
|
||||
|
||||
if (!_role.MindHasRole<UprisingRoleComponent>((mindUid, mindComponent), out var role) || mindComponent.OriginalOwnerUserId is not { } owner)
|
||||
continue;
|
||||
|
||||
if (!_player.TryGetPlayerData(owner, out var data))
|
||||
continue;
|
||||
|
||||
lines[role.Value.Comp2.Side].Add(Loc.GetString($"uprising-members-user", ("name", mindComponent.CharacterName ?? string.Empty), ("user", data.UserName)));
|
||||
}
|
||||
|
||||
foreach (var value in Enum.GetValues<UprisingSide>())
|
||||
{
|
||||
args.AddLine(Loc.GetString($"uprising-members.{value}"));
|
||||
foreach (var line in lines[value])
|
||||
{
|
||||
args.AddLine(line);
|
||||
}
|
||||
args.AddLine(string.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnGetLoyalistBriefing(Entity<LoyalistRoleComponent> ent, ref GetBriefingEvent args)
|
||||
{
|
||||
args.Append(Loc.GetString("antag-Loyalist.briefing"));
|
||||
}
|
||||
|
||||
private void OnGetInsurgentBriefing(Entity<InsurgentRoleComponent> ent, ref GetBriefingEvent args)
|
||||
{
|
||||
args.Append(Loc.GetString("antag-Insurgent.briefing"));
|
||||
}
|
||||
|
||||
private Entity<UprisingRuleComponent, GameRuleComponent>? GetRule()
|
||||
{
|
||||
var enumerator = QueryActiveRules();
|
||||
while (enumerator.MoveNext(out var uid, out _, out var uprising, out var gamerule))
|
||||
{
|
||||
return (uid, uprising, gamerule);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private void StartWinning(UprisingSide side, EntityUid console)
|
||||
{
|
||||
if (GetRule() is not { } rule)
|
||||
return;
|
||||
|
||||
var location = _navMap.GetNearestBeaconString(console);
|
||||
var announcement = Loc.GetString($"uprising-announcement-start-victory-announcement-{side}",
|
||||
("time", (int) Math.Round(rule.Comp1.SideTimes[side].TotalSeconds)),
|
||||
("location", FormattedMessage.RemoveMarkupOrThrow(location)));
|
||||
|
||||
_chat.DispatchGlobalAnnouncement(
|
||||
announcement,
|
||||
Loc.GetString($"uprising-announcement-start-victory-announcement-{side}.sender"),
|
||||
colorOverride: Color.FromXaml(Loc.GetString($"uprising-announcement-start-victory-announcement-{side}.color")));
|
||||
|
||||
rule.Comp1.SideWinsAt = (side, Timing.CurTime + rule.Comp1.SideTimes[side]);
|
||||
}
|
||||
|
||||
private void StopWinning()
|
||||
{
|
||||
if (GetRule() is not { } rule || rule.Comp1.SideWinsAt is not var (side, time))
|
||||
return;
|
||||
|
||||
rule.Comp1.SideTimes[side] = Timing.CurTime - time;
|
||||
|
||||
_chat.DispatchGlobalAnnouncement(
|
||||
Loc.GetString($"uprising-announcement-interrupted-victory-announcement-{side}"),
|
||||
Loc.GetString($"uprising-announcement-interrupted-victory-announcement-{side}.sender"),
|
||||
colorOverride: Color.FromXaml(Loc.GetString($"uprising-announcement-interrupted-victory-announcement-{side}.color")));
|
||||
|
||||
rule.Comp1.SideWinsAt = null;
|
||||
}
|
||||
|
||||
private void OnGetConsoleVerbs(Entity<UprisingConsoleComponent> ent, ref GetVerbsEvent<AlternativeVerb> args)
|
||||
{
|
||||
if (GetRule() is not { } rule)
|
||||
return;
|
||||
|
||||
var user = args.User;
|
||||
if (rule.Comp1.SideWinsAt is var (disarmSide, _))
|
||||
{
|
||||
args.Verbs.Add(new ()
|
||||
{
|
||||
Act = () => StartDisarming(ent, user),
|
||||
Text = Loc.GetString($"uprising-disarm-verb.{disarmSide}"),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (!CheckCanArm(args.User, out var armSide))
|
||||
return;
|
||||
|
||||
args.Verbs.Add(new()
|
||||
{
|
||||
Act = () => StartArming(ent, user),
|
||||
Text = Loc.GetString($"uprising-arm-verb.{armSide}"),
|
||||
});
|
||||
}
|
||||
|
||||
private bool CheckCanArm(EntityUid user, [NotNullWhen(true)] out UprisingSide? side)
|
||||
{
|
||||
side = null;
|
||||
if (_mind.GetMind(user) is not { } mind || !TryComp<MindComponent>(mind, out var mindComp))
|
||||
return false;
|
||||
|
||||
if (!_role.MindHasRole<UprisingRoleComponent>(mind, out var uprisingRole))
|
||||
return false;
|
||||
|
||||
foreach (var objective in mindComp.Objectives)
|
||||
{
|
||||
if (!HasComp<UprisingArmRequiredObjectiveComponent>(objective))
|
||||
continue;
|
||||
|
||||
if (_objectives.GetProgress(objective, (mind, mindComp)) is not >= 1)
|
||||
return false;
|
||||
}
|
||||
|
||||
side = uprisingRole.Value.Comp2.Side;
|
||||
return true;
|
||||
}
|
||||
|
||||
private void StartDisarming(Entity<UprisingConsoleComponent> ent, EntityUid user)
|
||||
{
|
||||
_doAfter.TryStartDoAfter(new DoAfterArgs(EntityManager,
|
||||
user,
|
||||
ent.Comp.Delay,
|
||||
new UprisingDisarmDoAfter(),
|
||||
ent.Owner));
|
||||
}
|
||||
|
||||
private void StartArming(Entity<UprisingConsoleComponent> ent, EntityUid user)
|
||||
{
|
||||
_doAfter.TryStartDoAfter(new DoAfterArgs(EntityManager,
|
||||
user,
|
||||
ent.Comp.Delay,
|
||||
new UprisingArmDoAfter(),
|
||||
ent.Owner));
|
||||
}
|
||||
|
||||
private void OnArm(Entity<UprisingConsoleComponent> ent, ref UprisingArmDoAfter args)
|
||||
{
|
||||
if (args.Handled || args.Cancelled || !CheckCanArm(args.User, out var side))
|
||||
return;
|
||||
|
||||
StartWinning(side.Value, ent);
|
||||
}
|
||||
|
||||
private void OnDisarm(Entity<UprisingConsoleComponent> ent, ref UprisingDisarmDoAfter args)
|
||||
{
|
||||
if (args.Handled || args.Cancelled)
|
||||
return;
|
||||
|
||||
StopWinning();
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
using Content.Shared.Actions;
|
||||
using Content.Shared.Roles.Components;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Shared.Uprising;
|
||||
|
||||
public sealed partial class ConversionAcceptActionEvent : InstantActionEvent
|
||||
{
|
||||
[DataField(required: true)]
|
||||
public EntProtoId MindRole;
|
||||
|
||||
[DataField(required: true)]
|
||||
public EntProtoId<MindRoleComponent> RemoveMindRole;
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
using Content.Shared.Mind;
|
||||
using Content.Shared.Mind.Components;
|
||||
using Content.Shared.Roles;
|
||||
using Robust.Shared.Network;
|
||||
|
||||
namespace Content.Shared.Uprising;
|
||||
|
||||
public sealed class ConversionAcceptActionSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly SharedMindSystem _mind = default!;
|
||||
[Dependency] private readonly SharedRoleSystem _role = default!;
|
||||
[Dependency] private readonly INetManager _net = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<MindContainerComponent, ConversionAcceptActionEvent>(OnAcceptConversion);
|
||||
}
|
||||
|
||||
private void OnAcceptConversion(Entity<MindContainerComponent> ent, ref ConversionAcceptActionEvent args)
|
||||
{
|
||||
if (_mind.GetMind(ent) is not { } mind)
|
||||
return;
|
||||
|
||||
args.Handled = true;
|
||||
if (_net.IsServer)
|
||||
{
|
||||
_role.MindAddRole(mind, args.MindRole);
|
||||
_role.MindRemoveRole(mind, args.RemoveMindRole);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
using Content.Shared.Actions;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Shared.Uprising;
|
||||
|
||||
public sealed partial class ConversionOfferActionEvent : EntityTargetActionEvent
|
||||
{
|
||||
[DataField(readOnly: true)]
|
||||
public List<Type> IncompatibleMindRoleTypes;
|
||||
|
||||
[DataField]
|
||||
public EntProtoId AcceptAction;
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
using Content.Shared.Actions;
|
||||
using Content.Shared.Mind;
|
||||
using Content.Shared.Roles;
|
||||
using Content.Shared.Roles.Components;
|
||||
|
||||
namespace Content.Shared.Uprising;
|
||||
|
||||
public sealed class ConversionOfferActionSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly SharedActionsSystem _actions = default!;
|
||||
[Dependency] private readonly SharedMindSystem _mind = default!;
|
||||
[Dependency] private readonly SharedRoleSystem _role = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<MindRoleComponent, ConversionOfferActionEvent>(OnOffer);
|
||||
}
|
||||
|
||||
private void OnOffer(Entity<MindRoleComponent> ent, ref ConversionOfferActionEvent args)
|
||||
{
|
||||
if (_mind.GetMind(args.Target) is not { } mind)
|
||||
return;
|
||||
|
||||
foreach (var incompatible in args.IncompatibleMindRoleTypes)
|
||||
{
|
||||
if (_role.MindHasRole(mind, incompatible, out _))
|
||||
return;
|
||||
}
|
||||
|
||||
_actions.AddAction(args.Target, args.AcceptAction);
|
||||
args.Handled = true;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
);
|
||||
@@ -1,6 +0,0 @@
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared._DV.Actions;
|
||||
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
public sealed partial class DeleteOnPerformedActionComponent : Component;
|
||||
@@ -1,18 +0,0 @@
|
||||
using Content.Shared.Actions.Events;
|
||||
|
||||
namespace Content.Shared._DV.Actions;
|
||||
|
||||
public sealed class DeleteOnPerformedActionSystem : EntitySystem
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<DeleteOnPerformedActionComponent, ActionPerformedEvent>(OnActionPerformed);
|
||||
}
|
||||
|
||||
private void OnActionPerformed(Entity<DeleteOnPerformedActionComponent> ent, ref ActionPerformedEvent args)
|
||||
{
|
||||
PredictedQueueDel(ent);
|
||||
}
|
||||
}
|
||||
@@ -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]
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user