mirror of
https://github.com/DeltaV-Station/Delta-v.git
synced 2026-09-29 23:57:29 +00:00
Compare commits
50
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4702b34eb5 | ||
|
|
4d28fabc9d | ||
|
|
474a6d358b | ||
|
|
5e6b27a860 | ||
|
|
008b84902a | ||
|
|
52d887faff | ||
|
|
097d8282c8 | ||
|
|
8919a88ea0 | ||
|
|
2dcf965e41 | ||
|
|
c776571860 | ||
|
|
78dd74e5c1 | ||
|
|
b3d15cdfd4 | ||
|
|
92883eb3ff | ||
|
|
53773326df | ||
|
|
2281cdbf8d | ||
|
|
ff20b12532 | ||
|
|
8e62f86239 | ||
|
|
e2e5c40f83 | ||
|
|
d0c3cff669 | ||
|
|
ed1fa64a6e | ||
|
|
cb245477b8 | ||
|
|
0fdfef7175 | ||
|
|
646d538c4a | ||
|
|
6ebcf78d08 | ||
|
|
2b7e3c8058 | ||
|
|
4736a8db6f | ||
|
|
6057388bae | ||
|
|
50a651beea | ||
|
|
7e1308dd12 | ||
|
|
338ff271c7 | ||
|
|
fddd9079d3 | ||
|
|
bedc17ae73 | ||
|
|
ec80e99e26 | ||
|
|
4ceb567439 | ||
|
|
6a9984475c | ||
|
|
030eb95c63 | ||
|
|
00abed028d | ||
|
|
0f3d319a6d | ||
|
|
a9ddc102e7 | ||
|
|
a51896b08d | ||
|
|
356106342f | ||
|
|
a3b450ac07 | ||
|
|
4ddb5901b0 | ||
|
|
972d852bf4 | ||
|
|
ef0d317d12 | ||
|
|
003e08b583 | ||
|
|
74739709c4 | ||
|
|
61840fd5ab | ||
|
|
90f7aa7999 | ||
|
|
2260af7ce1 |
@@ -312,3 +312,6 @@ Resources/MapImages
|
||||
|
||||
# Changelog package lock
|
||||
package-lock.json
|
||||
|
||||
# Discord Bot Config
|
||||
link-config.json
|
||||
@@ -8,12 +8,14 @@ using Content.Shared.CCVar;
|
||||
using Content.Shared.Ghost;
|
||||
using Content.Shared.Mind;
|
||||
using Content.Shared.Roles;
|
||||
using Content.Shared.SSDIndicator; // DeltaV - SSD time indicator
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Timing; // DeltaV - SSD time indicator
|
||||
|
||||
namespace Content.Client.Administration;
|
||||
|
||||
@@ -26,6 +28,7 @@ internal sealed class AdminNameOverlay : Overlay
|
||||
private readonly IUserInterfaceManager _userInterfaceManager;
|
||||
private readonly SharedRoleSystem _roles;
|
||||
private readonly IPrototypeManager _prototypeManager;
|
||||
private readonly IGameTiming _timing; // DeltaV - Add timing
|
||||
private readonly Font _font;
|
||||
private readonly Font _fontBold;
|
||||
private AdminOverlayAntagFormat _overlayFormat;
|
||||
@@ -53,7 +56,8 @@ internal sealed class AdminNameOverlay : Overlay
|
||||
IUserInterfaceManager userInterfaceManager,
|
||||
IConfigurationManager config,
|
||||
SharedRoleSystem roles,
|
||||
IPrototypeManager prototypeManager)
|
||||
IPrototypeManager prototypeManager,
|
||||
IGameTiming timing) // DeltaV - Add timing
|
||||
{
|
||||
_system = system;
|
||||
_entityManager = entityManager;
|
||||
@@ -62,6 +66,7 @@ internal sealed class AdminNameOverlay : Overlay
|
||||
_userInterfaceManager = userInterfaceManager;
|
||||
_roles = roles;
|
||||
_prototypeManager = prototypeManager;
|
||||
_timing = timing; // DeltaV - Add timing
|
||||
ZIndex = 200;
|
||||
// Setting these to a specific ttf would break the antag symbols
|
||||
_font = resourceCache.NotoStack();
|
||||
@@ -231,6 +236,17 @@ internal sealed class AdminNameOverlay : Overlay
|
||||
currentOffset += lineoffset;
|
||||
}
|
||||
|
||||
// DeltaV - SSD Time START
|
||||
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 - ssdIndicator.SsdSince).ToString("%hh':'mm':'ss")));
|
||||
args.ScreenHandle.DrawString(_font, screenCoordinates + currentOffset, ssdText, uiScale, color);
|
||||
currentOffset += lineoffset;
|
||||
}
|
||||
// DeltaV END
|
||||
|
||||
// Determine antag symbol
|
||||
string? symbol;
|
||||
switch (_overlaySymbolStyle)
|
||||
|
||||
@@ -5,6 +5,7 @@ using Robust.Client.ResourceManagement;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Timing; // DeltaV - SSD time indicator
|
||||
|
||||
namespace Content.Client.Administration.Systems
|
||||
{
|
||||
@@ -19,6 +20,7 @@ namespace Content.Client.Administration.Systems
|
||||
[Dependency] private readonly IConfigurationManager _configurationManager = default!;
|
||||
[Dependency] private readonly SharedRoleSystem _roles = default!;
|
||||
[Dependency] private readonly IPrototypeManager _proto = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!; // DeltaV - added for SSD time indicator
|
||||
|
||||
private AdminNameOverlay _adminNameOverlay = default!;
|
||||
|
||||
@@ -36,7 +38,8 @@ namespace Content.Client.Administration.Systems
|
||||
_userInterfaceManager,
|
||||
_configurationManager,
|
||||
_roles,
|
||||
_proto);
|
||||
_proto,
|
||||
_timing); // DeltaV - Add timing
|
||||
_adminManager.AdminStatusUpdated += OnAdminStatusUpdated;
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -19,6 +19,7 @@ public sealed class SSDIndicatorSystem : EntitySystem
|
||||
[Dependency] private readonly IPrototypeManager _prototype = default!;
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
[Dependency] private readonly MobStateSystem _mobState = default!;
|
||||
[Dependency] private readonly Shared.SSDIndicator.SSDIndicatorSystem _shared = default!; // DeltaV - SSD Recency, don't want to rename the upstream class
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -35,13 +36,26 @@ public sealed class SSDIndicatorSystem : EntitySystem
|
||||
!HasComp<ActiveNPCComponent>(uid) &&
|
||||
HasComp<MindExaminableComponent>(uid))
|
||||
{
|
||||
// Begin DeltaV Addition
|
||||
// Begin DeltaV Additions
|
||||
var ev = new ShowSSDIndicatorEvent();
|
||||
RaiseLocalEvent(uid, ref ev);
|
||||
if (ev.Hidden)
|
||||
return;
|
||||
// End DeltaV Addition
|
||||
args.StatusIcons.Add(_prototype.Index(component.Icon));
|
||||
|
||||
// SSD Recency Indicator
|
||||
var stage = _shared.GetStage(new Entity<SSDIndicatorComponent>(uid, component));
|
||||
var icon = stage switch
|
||||
{
|
||||
SsdStage.VeryRecent => component.VeryRecentIcon,
|
||||
SsdStage.Recent => component.RecentIcon,
|
||||
SsdStage.Cryoable => component.Icon,
|
||||
_ => throw new InvalidOperationException($"{ToPrettyString(uid)} has an invalid SSD stage {stage}."),
|
||||
};
|
||||
|
||||
args.StatusIcons.Add(_prototype.Index(icon));
|
||||
// End DeltaV Additions
|
||||
|
||||
// args.StatusIcons.Add(_prototype.Index(component.Icon)); // DeltaV - commented out. status icon now added above
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -63,16 +63,16 @@ public sealed class StellarInteractionParticleSystem : EntitySystem
|
||||
|
||||
private Animation GetAnimation(Vector2 endOffset, Color color)
|
||||
{
|
||||
var startRotation = _random.NextAngle(Angle.FromDegrees(-80), Angle.FromDegrees(80));
|
||||
var startRotation = _random.NextAngle(Angle.FromDegrees(-40), Angle.FromDegrees(40));
|
||||
var endRotation = Angle.Zero;
|
||||
var startScale = new Vector2(0.3f, 0.3f);
|
||||
var endScale = new Vector2(1f, 1f);
|
||||
var rotationLength = TimeSpan.FromMilliseconds(600);
|
||||
|
||||
var startOffset = new Vector2();
|
||||
var offsetLength = TimeSpan.FromMilliseconds(200);
|
||||
var offsetLength = TimeSpan.FromMilliseconds(250);
|
||||
|
||||
var startColor = color.WithAlpha(color.A * 0.9f);
|
||||
var startColor = color.WithAlpha(color.A * 0.7f);
|
||||
var endColor = color.WithAlpha(0f);
|
||||
var colorLength = rotationLength + offsetLength;
|
||||
|
||||
@@ -89,7 +89,7 @@ public sealed class StellarInteractionParticleSystem : EntitySystem
|
||||
KeyFrames =
|
||||
{
|
||||
new AnimationTrackProperty.KeyFrame(startRotation, 0f),
|
||||
new AnimationTrackProperty.KeyFrame(endRotation, (float)rotationLength.TotalSeconds, Easings.OutBounce),
|
||||
new AnimationTrackProperty.KeyFrame(endRotation, (float)rotationLength.TotalSeconds, Easings.OutBack),
|
||||
},
|
||||
},
|
||||
new AnimationTrackComponentProperty()
|
||||
@@ -99,7 +99,7 @@ public sealed class StellarInteractionParticleSystem : EntitySystem
|
||||
KeyFrames =
|
||||
{
|
||||
new AnimationTrackProperty.KeyFrame(startScale, 0f),
|
||||
new AnimationTrackProperty.KeyFrame(endScale, (float)rotationLength.TotalSeconds, Easings.OutBounce),
|
||||
new AnimationTrackProperty.KeyFrame(endScale, (float)rotationLength.TotalSeconds, Easings.OutBack),
|
||||
},
|
||||
},
|
||||
new AnimationTrackComponentProperty()
|
||||
@@ -109,7 +109,7 @@ public sealed class StellarInteractionParticleSystem : EntitySystem
|
||||
KeyFrames =
|
||||
{
|
||||
new AnimationTrackProperty.KeyFrame(startOffset, 0f),
|
||||
new AnimationTrackProperty.KeyFrame(endOffset, (float)offsetLength.TotalSeconds, Easings.OutBounce),
|
||||
new AnimationTrackProperty.KeyFrame(endOffset, (float)offsetLength.TotalSeconds, Easings.OutBack),
|
||||
},
|
||||
},
|
||||
new AnimationTrackComponentProperty()
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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")]
|
||||
|
||||
@@ -89,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);
|
||||
|
||||
@@ -93,7 +93,6 @@ public sealed class ChatSanitizationManager : IChatSanitizationManager
|
||||
Entry("('=", "chatsan-tearfully-smiles"),
|
||||
Entry("['=", "chatsan-tearfully-smiles"),
|
||||
Entry("?", "chatsan-confused"), //DeltaV
|
||||
Entry("!", "chatsan-surprised"), //DeltaV
|
||||
Entry("…", "chatsan-sighs"), //DeltaV (note that ... doesn't work as that turns into a radio message)
|
||||
];
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ using Content.Server.GameTicking;
|
||||
using Content.Server.Speech.EntitySystems;
|
||||
using Content.Shared.Speech.Hushing; // DeltaV
|
||||
using Content.Server.Nyanotrasen.Chat;
|
||||
using Content.Server.Speech.Components; // DeltaV
|
||||
using Content.Server.Speech.Prototypes;
|
||||
using Content.Server.Station.Systems;
|
||||
using Content.Shared.ActionBlocker;
|
||||
@@ -35,6 +36,7 @@ using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Replays;
|
||||
using Robust.Shared.Utility;
|
||||
using Content.Shared._DV.Chat; // DeltaV - chat enhancements
|
||||
|
||||
namespace Content.Server.Chat.Systems;
|
||||
|
||||
@@ -215,7 +217,7 @@ public sealed partial class ChatSystem : SharedChatSystem
|
||||
// Was there an emote in the message? If so, send it.
|
||||
if (player != null && emoteStr != message && emoteStr != null)
|
||||
{
|
||||
SendEntityEmote(source, emoteStr, range, nameOverride, ignoreActionBlocker);
|
||||
SendEntityEmote(source, emoteStr, range, nameOverride, null, ignoreActionBlocker); // DeltaV - Had to change up for SendEntityEmote
|
||||
}
|
||||
|
||||
// This can happen if the entire string is sanitized out.
|
||||
@@ -252,9 +254,18 @@ public sealed partial class ChatSystem : SharedChatSystem
|
||||
case InGameICChatType.Whisper:
|
||||
SendEntityWhisper(source, message, range, null, nameOverride, hideLog, ignoreActionBlocker);
|
||||
break;
|
||||
case InGameICChatType.Emote:
|
||||
SendEntityEmote(source, message, range, nameOverride, hideLog: hideLog, ignoreActionBlocker: ignoreActionBlocker);
|
||||
break;
|
||||
case InGameICChatType.Emote: // DeltaV - Emote now has different types of emotes.
|
||||
var type = ProcessEmoteMessage(source, message, out var modMessage);
|
||||
if (type == EmoteType.Audible || type == EmoteType.AudiblePossessive)
|
||||
{
|
||||
if (checkRadioPrefix && TryProcessRadioMessage(source, modMessage, out var outputMessage, out var channel, capitalize: false))
|
||||
SendAudibleEntityEmote(source, outputMessage, range, nameOverride, channel, type, hideLog: hideLog, ignoreActionBlocker: ignoreActionBlocker);
|
||||
else
|
||||
SendAudibleEntityEmote(source, modMessage, range, nameOverride, null, type, hideLog: hideLog, ignoreActionBlocker: ignoreActionBlocker);
|
||||
}
|
||||
else
|
||||
SendEntityEmote(source, modMessage, range, nameOverride, type, hideLog: hideLog, ignoreActionBlocker: ignoreActionBlocker);
|
||||
break; // DeltaV - End
|
||||
//Nyano - Summary: case adds the telepathic chat sending ability.
|
||||
case InGameICChatType.Telepathic:
|
||||
_nyanoChatSystem.SendTelepathicChat(source, message, range == ChatTransmitRange.HideChat);
|
||||
@@ -629,6 +640,7 @@ public sealed partial class ChatSystem : SharedChatSystem
|
||||
string action,
|
||||
ChatTransmitRange range,
|
||||
string? nameOverride,
|
||||
EmoteType? emoteType, // DeltaV
|
||||
bool hideLog = false,
|
||||
bool checkEmote = true,
|
||||
bool ignoreActionBlocker = false,
|
||||
@@ -642,11 +654,21 @@ public sealed partial class ChatSystem : SharedChatSystem
|
||||
var ent = Identity.Entity(source, EntityManager);
|
||||
string name = FormattedMessage.EscapeText(nameOverride ?? Name(ent));
|
||||
|
||||
// Begin DeltaV
|
||||
string wrappedMessage;
|
||||
|
||||
// Emotes use Identity.Name, since it doesn't actually involve your voice at all.
|
||||
var wrappedMessage = Loc.GetString("chat-manager-entity-me-wrap-message",
|
||||
("entityName", name),
|
||||
("entity", ent),
|
||||
("message", FormattedMessage.RemoveMarkupOrThrow(action)));
|
||||
if (emoteType == EmoteType.Possessive) // DeltaV - Emote types now get checked.
|
||||
wrappedMessage = Loc.GetString("chat-manager-entity-me-possessive-wrap-message",
|
||||
("entityName", name),
|
||||
("entity", ent),
|
||||
("message", FormattedMessage.RemoveMarkupOrThrow(action)));
|
||||
else
|
||||
wrappedMessage = Loc.GetString("chat-manager-entity-me-wrap-message",
|
||||
("entityName", name),
|
||||
("entity", ent),
|
||||
("message", FormattedMessage.RemoveMarkupOrThrow(action)));
|
||||
// End DeltaV
|
||||
|
||||
if (checkEmote &&
|
||||
!TryEmoteChatInput(source, action))
|
||||
@@ -660,6 +682,59 @@ public sealed partial class ChatSystem : SharedChatSystem
|
||||
_adminLogger.Add(LogType.Chat, LogImpact.Low, $"Emote from {source}: {action}");
|
||||
}
|
||||
|
||||
// DeltaV - Added this to differentiate between emotes that can be heard over radio and those which can't.
|
||||
protected override void SendAudibleEntityEmote(
|
||||
EntityUid source,
|
||||
string action,
|
||||
ChatTransmitRange range,
|
||||
string? nameOverride,
|
||||
RadioChannelPrototype? channel,
|
||||
EmoteType? emoteType,
|
||||
bool hideLog = false,
|
||||
bool checkEmote = true,
|
||||
bool ignoreActionBlocker = false,
|
||||
NetUserId? author = null
|
||||
)
|
||||
{
|
||||
if (!_actionBlocker.CanSpeak(source) && !ignoreActionBlocker)
|
||||
return;
|
||||
|
||||
EmoteType type = emoteType ?? EmoteType.Audible;
|
||||
|
||||
// get the entity's apparent name (if no override provided).
|
||||
var ent = Identity.Entity(source, EntityManager);
|
||||
string name = FormattedMessage.EscapeText(nameOverride ?? Name(ent));
|
||||
|
||||
string wrappedMessage;
|
||||
|
||||
// Audible emotes use Identity.Name, since that is the status quo. Emotes like scream doesn't currently reveal identities so this won't either. [This may be changed with feedback as you can audibly emote over radios]
|
||||
if (emoteType == EmoteType.AudiblePossessive)
|
||||
wrappedMessage = Loc.GetString("chat-manager-entity-me-audible-possessive-wrap-message",
|
||||
("entityName", name),
|
||||
("entity", ent),
|
||||
("message", FormattedMessage.RemoveMarkupOrThrow(action)));
|
||||
else
|
||||
wrappedMessage = Loc.GetString("chat-manager-entity-me-audible-wrap-message",
|
||||
("entityName", name),
|
||||
("entity", ent),
|
||||
("message", FormattedMessage.RemoveMarkupOrThrow(action)));
|
||||
|
||||
if (checkEmote &&
|
||||
!TryEmoteChatInput(source, action))
|
||||
return;
|
||||
|
||||
SendInVoiceRange(ChatChannel.Emotes, action, wrappedMessage, source, range, author);
|
||||
|
||||
var ev = new EntityAudiblyEmotedEvent(source, action, channel, type);
|
||||
RaiseLocalEvent(source, ref ev, true);
|
||||
if (!hideLog)
|
||||
if (name != Name(source))
|
||||
_adminLogger.Add(LogType.Chat, LogImpact.Low, $"Emote from {source} as {name}: {action}");
|
||||
else
|
||||
_adminLogger.Add(LogType.Chat, LogImpact.Low, $"Emote from {source}: {action}");
|
||||
}
|
||||
// DeltaV - End
|
||||
|
||||
// ReSharper disable once InconsistentNaming
|
||||
private void SendLOOC(EntityUid source, ICommonSession player, string message, bool hideChat)
|
||||
{
|
||||
@@ -889,6 +964,7 @@ public sealed partial class ChatSystem : SharedChatSystem
|
||||
var recipients = new Dictionary<ICommonSession, ICChatRecipientData>();
|
||||
var ghostHearing = GetEntityQuery<GhostHearingComponent>();
|
||||
var xforms = GetEntityQuery<TransformComponent>();
|
||||
var blockListening = GetEntityQuery<BlockListeningComponent>(); // DeltaV - block listening
|
||||
|
||||
var transformSource = xforms.GetComponent(source);
|
||||
var sourceMapId = transformSource.MapID;
|
||||
@@ -904,6 +980,11 @@ public sealed partial class ChatSystem : SharedChatSystem
|
||||
if (transformEntity.MapID != sourceMapId)
|
||||
continue;
|
||||
|
||||
// Begin DeltaV - block listening
|
||||
if (blockListening.HasComponent(playerEntity))
|
||||
continue;
|
||||
// End DeltaV - block listening
|
||||
|
||||
var observer = ghostHearing.HasComponent(playerEntity);
|
||||
|
||||
// even if they are a ghost hearer, in some situations we still need the range
|
||||
@@ -971,4 +1052,4 @@ public sealed class CheckTargetedSpeechEvent : EntityEventArgs
|
||||
{
|
||||
public List<EntityUid> Targets = new List<EntityUid>();
|
||||
}
|
||||
// END Mono
|
||||
// END Mono
|
||||
|
||||
@@ -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).
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ using Content.Shared.Radio.Components;
|
||||
using Content.Shared.Radio.EntitySystems;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Player;
|
||||
using Content.Shared._DV.Chat;
|
||||
|
||||
namespace Content.Server.Radio.EntitySystems;
|
||||
|
||||
@@ -26,6 +27,7 @@ public sealed class HeadsetSystem : SharedHeadsetSystem
|
||||
SubscribeLocalEvent<HeadsetComponent, EncryptionChannelsChangedEvent>(OnKeysChanged);
|
||||
|
||||
SubscribeLocalEvent<WearingHeadsetComponent, EntitySpokeEvent>(OnSpeak);
|
||||
SubscribeLocalEvent<WearingHeadsetComponent, EntityAudiblyEmotedEvent>(OnAudibleEmote); // DeltaV
|
||||
}
|
||||
|
||||
private void OnKeysChanged(EntityUid uid, HeadsetComponent component, EncryptionChannelsChangedEvent args)
|
||||
@@ -48,22 +50,43 @@ public sealed class HeadsetSystem : SharedHeadsetSystem
|
||||
EnsureComp<ActiveRadioComponent>(uid).Channels = new(keyHolder.Channels);
|
||||
}
|
||||
|
||||
private void OnSpeak(EntityUid uid, WearingHeadsetComponent component, EntitySpokeEvent args)
|
||||
// DeltaV
|
||||
// WARNING - Be very careful when modifying this method.
|
||||
// The implementation right now has null forgiving operatiors in OnSpeak() and OnAudibleEmote() since this only returns true if channel is not null
|
||||
private bool CheckRadioCapable(EntityUid uid, WearingHeadsetComponent headset, RadioChannelPrototype? channel)
|
||||
{
|
||||
if (args.Channel != null
|
||||
&& TryComp(component.Headset, out EncryptionKeyHolderComponent? keys)
|
||||
&& keys.Channels.Contains(args.Channel.ID))
|
||||
if (channel != null
|
||||
&& TryComp(headset.Headset, out EncryptionKeyHolderComponent? keys)
|
||||
&& keys.Channels.Contains(channel.ID))
|
||||
{
|
||||
// Begin DeltaV Additions: No using headsets if you lost your hands or are cuffed
|
||||
if (!TryComp<HandsComponent>(uid, out var hands) || hands.Count < 1 ||
|
||||
TryComp<CuffableComponent>(uid, out var cuffable) && _cuffable.IsCuffed((uid, cuffable)))
|
||||
{
|
||||
_popup.PopupEntity(Loc.GetString("headset-cant-reach"), uid, uid, PopupType.SmallCaution);
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
// End DeltaV Additions
|
||||
|
||||
_radio.SendRadioMessage(uid, args.Message, args.Channel, component.Headset);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void OnAudibleEmote(EntityUid uid, WearingHeadsetComponent component, EntityAudiblyEmotedEvent args)
|
||||
{
|
||||
if (CheckRadioCapable(uid, component, args.Channel))
|
||||
{
|
||||
_radio.SendRadioMessage(uid, args.Message, args.Channel!, component.Headset, emType: args.Type);
|
||||
args.Channel = null;
|
||||
}
|
||||
}
|
||||
// DeltaV - End
|
||||
|
||||
private void OnSpeak(EntityUid uid, WearingHeadsetComponent component, EntitySpokeEvent args)
|
||||
{
|
||||
if (CheckRadioCapable(uid, component, args.Channel)) // DeltaV - Put all the radio checks into the CheckRadioCapable() method.
|
||||
{
|
||||
_radio.SendRadioMessage(uid, args.Message, args.Channel!, component.Headset); // DeltaV - Made the args.Channel null ignorant as CheckRadioCapable() guarantees it not null.
|
||||
args.Channel = null; // prevent duplicate messages from other listeners.
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using Content.Server.Administration.Logs;
|
||||
using Content.Server.Chat.Systems;
|
||||
using Content.Server.Power.Components;
|
||||
using Content.Shared._DV.Chat;
|
||||
using Content.Shared.Chat;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.Radio;
|
||||
@@ -38,6 +39,7 @@ public sealed class RadioSystem : EntitySystem
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<IntrinsicRadioReceiverComponent, RadioReceiveEvent>(OnIntrinsicReceive);
|
||||
SubscribeLocalEvent<IntrinsicRadioTransmitterComponent, EntitySpokeEvent>(OnIntrinsicSpeak);
|
||||
SubscribeLocalEvent<IntrinsicRadioTransmitterComponent, EntityAudiblyEmotedEvent>(OnIntrinsicAudibleEmote); // DeltaV - Robots should be allowed to emote over radio.
|
||||
|
||||
_exemptQuery = GetEntityQuery<TelecomExemptComponent>();
|
||||
}
|
||||
@@ -57,12 +59,22 @@ public sealed class RadioSystem : EntitySystem
|
||||
_netMan.ServerSendMessage(args.ChatMsg, actor.PlayerSession.Channel);
|
||||
}
|
||||
|
||||
// DeltaV
|
||||
private void OnIntrinsicAudibleEmote(EntityUid uid, IntrinsicRadioTransmitterComponent component, EntityAudiblyEmotedEvent args)
|
||||
{
|
||||
if (args.Channel != null && component.Channels.Contains(args.Channel.ID))
|
||||
{
|
||||
SendRadioMessage(uid, args.Message, args.Channel, uid, emType: args.Type);
|
||||
}
|
||||
}
|
||||
// DeltaV - End
|
||||
|
||||
/// <summary>
|
||||
/// Send radio message to all active radio listeners
|
||||
/// </summary>
|
||||
public void SendRadioMessage(EntityUid messageSource, string message, ProtoId<RadioChannelPrototype> channel, EntityUid radioSource, bool escapeMarkup = true)
|
||||
public void SendRadioMessage(EntityUid messageSource, string message, ProtoId<RadioChannelPrototype> channel, EntityUid radioSource, bool escapeMarkup = true, EmoteType? emType = null) // DeltaV - EmoteType? added.
|
||||
{
|
||||
SendRadioMessage(messageSource, message, _prototype.Index(channel), radioSource, escapeMarkup: escapeMarkup);
|
||||
SendRadioMessage(messageSource, message, _prototype.Index(channel), radioSource, escapeMarkup: escapeMarkup, emType: emType);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -70,7 +82,7 @@ public sealed class RadioSystem : EntitySystem
|
||||
/// </summary>
|
||||
/// <param name="messageSource">Entity that spoke the message</param>
|
||||
/// <param name="radioSource">Entity that picked up the message and will send it, e.g. headset</param>
|
||||
public void SendRadioMessage(EntityUid messageSource, string message, RadioChannelPrototype channel, EntityUid radioSource, bool escapeMarkup = true)
|
||||
public void SendRadioMessage(EntityUid messageSource, string message, RadioChannelPrototype channel, EntityUid radioSource, bool escapeMarkup = true, EmoteType? emType = null) // DeltaV - EmoteType? added.
|
||||
{
|
||||
// TODO if radios ever garble / modify messages, feedback-prevention needs to be handled better than this.
|
||||
if (!_messages.Add(message))
|
||||
@@ -92,14 +104,31 @@ public sealed class RadioSystem : EntitySystem
|
||||
? FormattedMessage.EscapeText(message)
|
||||
: message;
|
||||
|
||||
var wrappedMessage = Loc.GetString(speech.Bold ? "chat-radio-message-wrap-bold" : "chat-radio-message-wrap",
|
||||
("color", channel.Color),
|
||||
("fontType", speech.FontId),
|
||||
("fontSize", speech.FontSize),
|
||||
("verb", Loc.GetString(_random.Pick(speech.SpeechVerbStrings))),
|
||||
("channel", $"\\[{channel.LocalizedName}\\]"),
|
||||
("name", name),
|
||||
("message", content));
|
||||
// DeltaV - This change is to change up how the messages are wrapped up. Basically changing the formatting depending on the emote type.
|
||||
string wrappedMessage;
|
||||
|
||||
if (emType == EmoteType.Audible)
|
||||
wrappedMessage = Loc.GetString("chat-radio-message-audible-emote-wrap",
|
||||
("color", channel.Color),
|
||||
("channel", $"\\[{channel.LocalizedName}\\]"),
|
||||
("name", name),
|
||||
("message", content));
|
||||
else if (emType == EmoteType.AudiblePossessive)
|
||||
wrappedMessage = Loc.GetString("chat-radio-message-audible-possessive-emote-wrap",
|
||||
("color", channel.Color),
|
||||
("channel", $"\\[{channel.LocalizedName}\\]"),
|
||||
("name", name),
|
||||
("message", content));
|
||||
else
|
||||
wrappedMessage = Loc.GetString(speech.Bold ? "chat-radio-message-wrap-bold" : "chat-radio-message-wrap",
|
||||
("color", channel.Color),
|
||||
("fontType", speech.FontId),
|
||||
("fontSize", speech.FontSize),
|
||||
("verb", Loc.GetString(_random.Pick(speech.SpeechVerbStrings))),
|
||||
("channel", $"\\[{channel.LocalizedName}\\]"),
|
||||
("name", name),
|
||||
("message", content));
|
||||
// DeltaV - End
|
||||
|
||||
// most radios are relayed to chat, so lets parse the chat message beforehand
|
||||
var chat = new ChatMessage(
|
||||
|
||||
@@ -27,6 +27,11 @@ public sealed partial class BorgSystem
|
||||
var query = EntityQueryEnumerator<BorgTransponderComponent, BorgChassisComponent, DeviceNetworkComponent, MetaDataComponent>();
|
||||
while (query.MoveNext(out var uid, out var comp, out var chassis, out var device, out var meta))
|
||||
{
|
||||
// DeltaV Begin
|
||||
if (!comp.Active)
|
||||
return;
|
||||
// DeltaV End
|
||||
|
||||
if (comp.NextDisable is { } nextDisable && now >= nextDisable)
|
||||
DoDisable((uid, comp, chassis, meta));
|
||||
|
||||
@@ -83,6 +88,11 @@ public sealed partial class BorgSystem
|
||||
|
||||
private void OnPacketReceived(Entity<BorgTransponderComponent> ent, ref DeviceNetworkPacketEvent args)
|
||||
{
|
||||
// DeltaV Begin
|
||||
if (!ent.Comp.Active)
|
||||
return;
|
||||
// DeltaV End
|
||||
|
||||
var payload = args.Data;
|
||||
if (!payload.TryGetValue(DeviceNetworkConstants.Command, out string? command))
|
||||
return;
|
||||
@@ -197,4 +207,17 @@ public sealed partial class BorgSystem
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// DeltaV Begin
|
||||
/// <summary>
|
||||
/// DeltaV - sets if the borg transponder is active
|
||||
/// </summary>
|
||||
public void SetTransponderActive(Entity<BorgTransponderComponent?> ent, bool active)
|
||||
{
|
||||
if (!Resolve(ent, ref ent.Comp))
|
||||
return;
|
||||
|
||||
ent.Comp.Active = active;
|
||||
}
|
||||
// DeltaV End
|
||||
}
|
||||
|
||||
@@ -29,12 +29,14 @@ public sealed partial class SpeechWireAction : ComponentWireAction<SpeechCompone
|
||||
public override bool Cut(EntityUid user, Wire wire, SpeechComponent component)
|
||||
{
|
||||
_speech.SetSpeech(wire.Owner, false, component);
|
||||
EntityManager.GetComponentOrNull<Components.UnblockableSpeechComponent>(user)?.Active = false; // DeltaV - we need this to override unblockable speech
|
||||
return true;
|
||||
}
|
||||
|
||||
public override bool Mend(EntityUid user, Wire wire, SpeechComponent component)
|
||||
{
|
||||
_speech.SetSpeech(wire.Owner, true, component);
|
||||
EntityManager.GetComponentOrNull<Components.UnblockableSpeechComponent>(user)?.Active = true; // DeltaV - we need this to override unblockable speech
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -3,5 +3,9 @@ namespace Content.Server.Speech.Components
|
||||
[RegisterComponent]
|
||||
public sealed partial class UnblockableSpeechComponent : Component
|
||||
{
|
||||
// Begin DeltaV
|
||||
[DataField]
|
||||
public bool Active = true;
|
||||
// End DeltaV
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ namespace Content.Server.Speech.EntitySystems
|
||||
|
||||
private void OnCheck(EntityUid uid, UnblockableSpeechComponent component, CheckIgnoreSpeechBlockerEvent args)
|
||||
{
|
||||
args.IgnoreBlocker = true;
|
||||
args.IgnoreBlocker = component.Active; // DeltaV
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,5 +1,6 @@
|
||||
using Content.Server._DV.Psionics.Systems;
|
||||
using Content.Shared._DV.Psionics.Components;
|
||||
using Content.Shared._DV.Traits.Assorted;
|
||||
using Content.Shared.Mind;
|
||||
using Content.Shared.Roles;
|
||||
using Content.Shared.Roles.Components;
|
||||
@@ -17,10 +18,12 @@ public sealed partial class ParadoxCloneRuleSystem
|
||||
private void FilterTargets(HashSet<Entity<MindComponent>> minds)
|
||||
{
|
||||
// TODO: use generic IMindFilter
|
||||
// no picking other antags or non-crew
|
||||
// no picking other antags or non-crew and entities with no paradox clone trait
|
||||
minds.RemoveWhere(mind => _role.MindIsAntagonist(mind) ||
|
||||
!_role.MindHasRole<JobRoleComponent>((mind, mind), out var role) ||
|
||||
role?.Comp1.JobPrototype == null);
|
||||
role?.Comp1.JobPrototype == null ||
|
||||
(mind.Comp.OwnedEntity is { } entity && HasComp<NoParadoxCloneComponent>(entity))
|
||||
);
|
||||
}
|
||||
|
||||
private void PostClone(EntityUid mob)
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
using Content.Server.Wires;
|
||||
using Content.Shared._DV.Silicons.Borgs;
|
||||
using Content.Shared._DV.Silicons;
|
||||
using Content.Shared.Wires;
|
||||
|
||||
namespace Content.Server._DV.Silicons;
|
||||
|
||||
/// <summary>
|
||||
/// Controls whether the cyborg's ID chip slot is active
|
||||
/// </summary>
|
||||
public sealed partial class BorgIdChipWireAction : ComponentWireAction<IdChipSlotComponent>
|
||||
{
|
||||
public override string Name { get; set; } = "wire-name-borg-id-chip";
|
||||
public override Color Color { get; set; } = Color.Thistle;
|
||||
public override object StatusKey => BorgWireActionKey.ChipKey;
|
||||
|
||||
public override StatusLightState? GetLightState(Wire wire, IdChipSlotComponent component)
|
||||
{
|
||||
return component.Active ? StatusLightState.On : StatusLightState.Off;
|
||||
}
|
||||
|
||||
public override bool Cut(EntityUid user, Wire wire, IdChipSlotComponent component)
|
||||
{
|
||||
EntityManager.System<IdChipSlotSystem>()
|
||||
.SetActive((wire.Owner, component), false);
|
||||
return true;
|
||||
}
|
||||
|
||||
public override bool Mend(EntityUid user, Wire wire, IdChipSlotComponent component)
|
||||
{
|
||||
EntityManager.System<IdChipSlotSystem>()
|
||||
.SetActive((wire.Owner, component), true);
|
||||
return true;
|
||||
}
|
||||
|
||||
public override void Pulse(EntityUid user, Wire wire, IdChipSlotComponent component)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
using Content.Server.Silicons.Borgs;
|
||||
using Content.Server.Wires;
|
||||
using Content.Shared._DV.Silicons;
|
||||
using Content.Shared.Silicons.Borgs.Components;
|
||||
using Content.Shared.Wires;
|
||||
|
||||
namespace Content.Server._DV.Silicons;
|
||||
|
||||
/// <summary>
|
||||
/// Controls whether the cyborg's transponder is active
|
||||
/// </summary>
|
||||
public sealed partial class BorgTransponderWireAction : ComponentWireAction<BorgTransponderComponent>
|
||||
{
|
||||
public override string Name { get; set; } = "wire-name-borg-transponder";
|
||||
public override Color Color { get; set; } = Color.YellowGreen;
|
||||
public override object StatusKey => BorgWireActionKey.TransponderKey;
|
||||
|
||||
public override StatusLightState? GetLightState(Wire wire, BorgTransponderComponent component)
|
||||
{
|
||||
return component.Active ? StatusLightState.On : StatusLightState.Off;
|
||||
}
|
||||
|
||||
public override bool Cut(EntityUid user, Wire wire, BorgTransponderComponent component)
|
||||
{
|
||||
EntityManager.System<BorgSystem>()
|
||||
.SetTransponderActive((wire.Owner, component), false);
|
||||
return true;
|
||||
}
|
||||
|
||||
public override bool Mend(EntityUid user, Wire wire, BorgTransponderComponent component)
|
||||
{
|
||||
EntityManager.System<BorgSystem>()
|
||||
.SetTransponderActive((wire.Owner, component), true);
|
||||
return true;
|
||||
}
|
||||
|
||||
public override void Pulse(EntityUid user, Wire wire, BorgTransponderComponent component)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,7 @@ namespace Content.Server._DV.Silicons.Laws;
|
||||
public sealed class SlavedBorgSystem : SharedSlavedBorgSystem
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _proto = default!;
|
||||
[Dependency] private readonly SharedSiliconLawSystem _siliconLaws = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -24,12 +25,12 @@ public sealed class SlavedBorgSystem : SharedSlavedBorgSystem
|
||||
|
||||
private void OnGetSiliconLaws(Entity<SlavedBorgComponent> ent, ref GetSiliconLawsEvent args)
|
||||
{
|
||||
if (ent.Comp.Added || !TryComp<SiliconLawProviderComponent>(ent, out var provider))
|
||||
if (ent.Comp.HasBeenAdded || !TryComp<SiliconLawProviderComponent>(ent, out var provider))
|
||||
return;
|
||||
|
||||
if (provider.Lawset is {} lawset)
|
||||
if (provider.Lawset is {} lawset && ent.Comp.ShouldBeAdded)
|
||||
AddLaw(lawset, ent.Comp.Law);
|
||||
ent.Comp.Added = true; // prevent opening the ui adding more law 0's
|
||||
ent.Comp.HasBeenAdded = true; // prevent opening the ui adding more law 0's
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -39,4 +40,30 @@ public sealed class SlavedBorgSystem : SharedSlavedBorgSystem
|
||||
{
|
||||
lawset.Laws.Insert(0, _proto.Index(law).ShallowClone());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets whether the slaving is active.
|
||||
/// </summary>
|
||||
public void SetShouldBeAdded(Entity<SlavedBorgComponent?> ent, bool active)
|
||||
{
|
||||
if (!Resolve(ent, ref ent.Comp))
|
||||
return;
|
||||
|
||||
if (!TryComp<SiliconLawProviderComponent>(ent, out var provider) || provider.Lawset is not { } lawset)
|
||||
return;
|
||||
|
||||
if (!ent.Comp.ShouldBeAdded && active)
|
||||
{
|
||||
AddLaw(lawset, ent.Comp.Law);
|
||||
ent.Comp.HasBeenAdded = true;
|
||||
_siliconLaws.NotifyLawsChanged(ent);
|
||||
}
|
||||
else if (ent.Comp.ShouldBeAdded && !active)
|
||||
{
|
||||
lawset.Laws.Remove(_proto.Index(ent.Comp.Law));
|
||||
ent.Comp.HasBeenAdded = false;
|
||||
_siliconLaws.NotifyLawsChanged(ent);
|
||||
}
|
||||
ent.Comp.ShouldBeAdded = active;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
using Content.Server.Wires;
|
||||
using Content.Shared._DV.Silicons;
|
||||
using Content.Shared.PowerCell;
|
||||
using Content.Shared.PowerCell.Components;
|
||||
using Content.Shared.Wires;
|
||||
|
||||
namespace Content.Server._DV.Silicons;
|
||||
|
||||
/// <summary>
|
||||
/// Controls whether the power cell slot is active
|
||||
/// </summary>
|
||||
public sealed partial class PowerCellSlotWireAction : ComponentWireAction<PowerCellSlotComponent>
|
||||
{
|
||||
public override string Name { get; set; } = "wire-name-power-cell";
|
||||
public override Color Color { get; set; } = Color.BurlyWood;
|
||||
public override object StatusKey => BorgWireActionKey.CellKey;
|
||||
|
||||
public override StatusLightState? GetLightState(Wire wire, PowerCellSlotComponent component)
|
||||
{
|
||||
return component.Active ? StatusLightState.On : StatusLightState.Off;
|
||||
}
|
||||
|
||||
public override bool Cut(EntityUid user, Wire wire, PowerCellSlotComponent component)
|
||||
{
|
||||
EntityManager.System<PowerCellSystem>()
|
||||
.SetSlotActive((wire.Owner, component), false);
|
||||
return true;
|
||||
}
|
||||
|
||||
public override bool Mend(EntityUid user, Wire wire, PowerCellSlotComponent component)
|
||||
{
|
||||
EntityManager.System<PowerCellSystem>()
|
||||
.SetSlotActive((wire.Owner, component), true);
|
||||
return true;
|
||||
}
|
||||
|
||||
public override void Pulse(EntityUid user, Wire wire, PowerCellSlotComponent component)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
using Content.Server._DV.Silicons.Laws;
|
||||
using Content.Server.Wires;
|
||||
using Content.Shared.Doors;
|
||||
using Content.Shared._DV.Silicons.Laws;
|
||||
using Content.Shared._DV.Silicons;
|
||||
using Content.Shared.Wires;
|
||||
|
||||
namespace Content.Server._DV.Silicons;
|
||||
|
||||
/// <summary>
|
||||
/// Controls whether the cyborg's transponder is active
|
||||
/// </summary>
|
||||
public sealed partial class SlavedBorgWireAction : ComponentWireAction<SlavedBorgComponent>
|
||||
{
|
||||
public override string Name { get; set; } = "wire-name-slaved-borg";
|
||||
public override Color Color { get; set; } = Color.Coral;
|
||||
public override object StatusKey => BorgWireActionKey.SlavedKey;
|
||||
|
||||
public override StatusLightState? GetLightState(Wire wire, SlavedBorgComponent component)
|
||||
{
|
||||
return component.ShouldBeAdded ? StatusLightState.On : StatusLightState.Off;
|
||||
}
|
||||
|
||||
public override bool Cut(EntityUid user, Wire wire, SlavedBorgComponent component)
|
||||
{
|
||||
EntityManager.System<SlavedBorgSystem>()
|
||||
.SetShouldBeAdded((wire.Owner, component), false);
|
||||
return true;
|
||||
}
|
||||
|
||||
public override bool Mend(EntityUid user, Wire wire, SlavedBorgComponent component)
|
||||
{
|
||||
EntityManager.System<SlavedBorgSystem>()
|
||||
.SetShouldBeAdded((wire.Owner, component), true);
|
||||
return true;
|
||||
}
|
||||
|
||||
public override void Pulse(EntityUid user, Wire wire, SlavedBorgComponent component)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
using System.Collections.Frozen;
|
||||
using System.Collections.Immutable;
|
||||
using Content.Shared._DV.Chat; // DeltaV - chat
|
||||
using Content.Shared.Chat.Prototypes;
|
||||
using Content.Shared.Speech;
|
||||
using Robust.Shared.Audio;
|
||||
@@ -98,7 +99,7 @@ public abstract partial class SharedChatSystem
|
||||
{
|
||||
// not all emotes are loc'd, but for the ones that are we pass in entity
|
||||
var action = Loc.GetString(_random.Pick(emote.ChatMessages), ("entity", source));
|
||||
SendEntityEmote(source, action, range, nameOverride, hideLog: hideLog, checkEmote: false, ignoreActionBlocker: ignoreActionBlocker);
|
||||
SendEntityEmote(source, action, range, nameOverride, null, hideLog: hideLog, checkEmote: false, ignoreActionBlocker: ignoreActionBlocker); // DeltaV
|
||||
}
|
||||
|
||||
return didEmote;
|
||||
@@ -208,6 +209,48 @@ public abstract partial class SharedChatSystem
|
||||
return true;
|
||||
}
|
||||
|
||||
// DeltaV
|
||||
/// <summary>
|
||||
/// Checks which type of emote the message contains and returns the type while outputting the string without the prefixes.
|
||||
/// </summary>
|
||||
public static EmoteType? ProcessEmoteMessage(EntityUid source, string input, out string output)
|
||||
{
|
||||
output = input.Trim();
|
||||
EmoteType? type = null;
|
||||
|
||||
if (input.Length == 0)
|
||||
return type;
|
||||
|
||||
if (!(input.StartsWith(AudibleEmotePrefix) || input.StartsWith(PossessiveEmotePrefix)))
|
||||
{
|
||||
type = EmoteType.Normal;
|
||||
return type;
|
||||
}
|
||||
|
||||
var emoteType = input[0];
|
||||
output = input[1..].TrimStart();
|
||||
|
||||
if (emoteType == AudibleEmotePrefix)
|
||||
{
|
||||
emoteType = input[1]; // Check for if we want AudiblePossessiveEmote
|
||||
type = EmoteType.Audible;
|
||||
}
|
||||
|
||||
if (emoteType == PossessiveEmotePrefix)
|
||||
{
|
||||
if (type == EmoteType.Audible)
|
||||
{
|
||||
type = EmoteType.AudiblePossessive;
|
||||
output = input[2..].TrimStart();
|
||||
}
|
||||
else
|
||||
type = EmoteType.Possessive;
|
||||
}
|
||||
|
||||
return type;
|
||||
}
|
||||
// DeltaV - End
|
||||
|
||||
/// <summary>
|
||||
/// Creates and raises <see cref="BeforeEmoteEvent"/> and then <see cref="EmoteEvent"/> to let other systems do things like play audio.
|
||||
/// In the case that the Before event is cancelled, EmoteEvent will NOT be raised, and will optionally show a message to the player
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System.Collections.Frozen;
|
||||
using System.Text.RegularExpressions;
|
||||
using Content.Shared._DV.Chat;
|
||||
using Content.Shared.ActionBlocker;
|
||||
using Content.Shared.Chat.Prototypes;
|
||||
using Content.Shared.Popups;
|
||||
@@ -29,6 +30,8 @@ public abstract partial class SharedChatSystem : EntitySystem
|
||||
public const char OOCPrefix = '[';
|
||||
public const char EmotesPrefix = '@';
|
||||
public const char EmotesAltPrefix = '*';
|
||||
public const char AudibleEmotePrefix = '!'; // DeltaV - You may now scream audibly!
|
||||
public const char PossessiveEmotePrefix = '\''; // DeltaV - You may now be possessive of things! Whatever that means.
|
||||
public const char AdminPrefix = ']';
|
||||
public const char WhisperPrefix = ',';
|
||||
public const char TelepathicPrefix = '='; //Nyano - Summary: Adds the telepathic channel's prefix.
|
||||
@@ -152,6 +155,7 @@ public abstract partial class SharedChatSystem : EntitySystem
|
||||
string input,
|
||||
out string output,
|
||||
out RadioChannelPrototype? channel,
|
||||
bool capitalize = true, // DeltaV - We might not want to capitalize the first letter if we send in emotes.
|
||||
bool quiet = false)
|
||||
{
|
||||
output = input.Trim();
|
||||
@@ -162,7 +166,7 @@ public abstract partial class SharedChatSystem : EntitySystem
|
||||
|
||||
if (input.StartsWith(RadioCommonPrefix))
|
||||
{
|
||||
output = SanitizeMessageCapital(input[1..].TrimStart());
|
||||
output = capitalize ? SanitizeMessageCapital(input[1..].TrimStart()) : input[1..].TrimStart(); // DeltaV
|
||||
channel = _prototypeManager.Index<RadioChannelPrototype>(CommonChannel);
|
||||
return true;
|
||||
}
|
||||
@@ -172,7 +176,7 @@ public abstract partial class SharedChatSystem : EntitySystem
|
||||
|
||||
if (input.Length < 2 || char.IsWhiteSpace(input[1]))
|
||||
{
|
||||
output = SanitizeMessageCapital(input[1..].TrimStart());
|
||||
output = capitalize ? SanitizeMessageCapital(input[1..].TrimStart()) : input[1..].TrimStart(); // DeltaV
|
||||
if (!quiet)
|
||||
_popup.PopupEntity(Loc.GetString("chat-manager-no-radio-key"), source, source);
|
||||
return true;
|
||||
@@ -180,7 +184,7 @@ public abstract partial class SharedChatSystem : EntitySystem
|
||||
|
||||
var channelKey = input[1];
|
||||
channelKey = char.ToLower(channelKey);
|
||||
output = SanitizeMessageCapital(input[2..].TrimStart());
|
||||
output = capitalize ? SanitizeMessageCapital(input[2..].TrimStart()) : input[2..].TrimStart(); // DeltaV
|
||||
|
||||
if (channelKey == DefaultChannelKey)
|
||||
{
|
||||
@@ -314,11 +318,29 @@ public abstract partial class SharedChatSystem : EntitySystem
|
||||
return rawmsg.Substring(tagStart, tagEnd - tagStart);
|
||||
}
|
||||
|
||||
// DeltaV
|
||||
protected virtual void SendAudibleEntityEmote(
|
||||
EntityUid source,
|
||||
string action,
|
||||
ChatTransmitRange range,
|
||||
string? nameOverride,
|
||||
RadioChannelPrototype? channel,
|
||||
EmoteType? emoteType,
|
||||
bool hideLog = false,
|
||||
bool checkEmote = true,
|
||||
bool ignoreActionBlocker = false,
|
||||
NetUserId? author = null
|
||||
)
|
||||
{
|
||||
}
|
||||
// DeltaV - End
|
||||
|
||||
protected virtual void SendEntityEmote(
|
||||
EntityUid source,
|
||||
string action,
|
||||
ChatTransmitRange range,
|
||||
string? nameOverride,
|
||||
EmoteType? emoteType,
|
||||
bool hideLog = false,
|
||||
bool checkEmote = true,
|
||||
bool ignoreActionBlocker = false,
|
||||
|
||||
@@ -17,6 +17,17 @@ namespace Content.Shared.Humanoid
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
|
||||
// DeltaV - i hate this hack but https://github.com/space-wizards/RobustToolbox/issues/6576
|
||||
private ISawmill _locSawmill = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
_locSawmill = Logger.GetSawmill("loc");
|
||||
}
|
||||
// End DeltaV - i hate this hack
|
||||
|
||||
public string GetName(string species, Gender? gender = null)
|
||||
{
|
||||
// if they have an old species or whatever just fall back to human I guess?
|
||||
@@ -47,31 +58,67 @@ namespace Content.Shared.Humanoid
|
||||
return Loc.GetString("namepreset-lastfirst",
|
||||
("last", GetLastName(speciesProto)), ("first", GetFirstName(speciesProto, gender)));
|
||||
case SpeciesNaming.FirstLast:
|
||||
// Begin DeltaV - more complex naming
|
||||
default:
|
||||
{
|
||||
var firstId = GetFirstNameId(speciesProto, gender);
|
||||
var lastId = GetLastNameId(speciesProto);
|
||||
var plural = false;
|
||||
|
||||
var oldLevel =_locSawmill.Level;
|
||||
_locSawmill.Level = LogLevel.Fatal; // this is a hack to avoid testfails because TryGetString still logs errors for not-found stuff anyways (wtf)
|
||||
if (Loc.TryGetString($"{lastId}.plural", out var pluralStr))
|
||||
plural = pluralStr == "true";
|
||||
|
||||
var last = Loc.GetString(lastId);
|
||||
|
||||
if (Loc.TryGetString($"{firstId}.intersperse", out var firstIntersperse, ("last", last), ("lastPlural", plural)))
|
||||
{
|
||||
_locSawmill.Level = oldLevel;
|
||||
return firstIntersperse;
|
||||
}
|
||||
|
||||
_locSawmill.Level = oldLevel;
|
||||
return Loc.GetString("namepreset-firstlast",
|
||||
("first", GetFirstName(speciesProto, gender)), ("last", GetLastName(speciesProto)));
|
||||
("first", Loc.GetString(firstId)),
|
||||
("last", last),
|
||||
("lastPlural", plural));
|
||||
}
|
||||
// End DeltaV - more complex naming
|
||||
}
|
||||
}
|
||||
|
||||
// Begin DeltaV - we want IDs
|
||||
public string GetFirstName(SpeciesPrototype speciesProto, Gender? gender = null)
|
||||
{
|
||||
return Loc.GetString(GetFirstNameId(speciesProto, gender));
|
||||
}
|
||||
|
||||
public string GetFirstNameId(SpeciesPrototype speciesProto, Gender? gender = null)
|
||||
{
|
||||
switch (gender)
|
||||
{
|
||||
case Gender.Male:
|
||||
return _random.Pick(_prototypeManager.Index(speciesProto.MaleFirstNames));
|
||||
return _random.PickId(_prototypeManager.Index(speciesProto.MaleFirstNames));
|
||||
case Gender.Female:
|
||||
return _random.Pick(_prototypeManager.Index(speciesProto.FemaleFirstNames));
|
||||
return _random.PickId(_prototypeManager.Index(speciesProto.FemaleFirstNames));
|
||||
default:
|
||||
if (_random.Prob(0.5f))
|
||||
return _random.Pick(_prototypeManager.Index(speciesProto.MaleFirstNames));
|
||||
return _random.PickId(_prototypeManager.Index(speciesProto.MaleFirstNames));
|
||||
else
|
||||
return _random.Pick(_prototypeManager.Index(speciesProto.FemaleFirstNames));
|
||||
return _random.PickId(_prototypeManager.Index(speciesProto.FemaleFirstNames));
|
||||
}
|
||||
}
|
||||
|
||||
public string GetLastNameId(SpeciesPrototype speciesProto)
|
||||
{
|
||||
return _random.PickId(_prototypeManager.Index(speciesProto.LastNames));
|
||||
}
|
||||
|
||||
public string GetLastName(SpeciesPrototype speciesProto)
|
||||
{
|
||||
return _random.Pick(_prototypeManager.Index(speciesProto.LastNames));
|
||||
return Loc.GetString(GetLastNameId(speciesProto));
|
||||
}
|
||||
// End DeltaV - we want IDs
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,6 +28,11 @@ public sealed class ActivateInWorldEvent : HandledEntityEventArgs, ITargetedInte
|
||||
/// </summary>
|
||||
public bool WasLogged { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Stellar - set to false if this interaction shouldn't have an interaction particle
|
||||
/// </summary>
|
||||
public bool InteractionParticle = true;
|
||||
|
||||
public ActivateInWorldEvent(EntityUid user, EntityUid target, bool complex)
|
||||
{
|
||||
User = user;
|
||||
|
||||
@@ -1189,7 +1189,7 @@ namespace Content.Shared.Interaction
|
||||
RaiseLocalEvent(used, activateMsg, true);
|
||||
if (activateMsg.Handled)
|
||||
{
|
||||
DoContactInteraction(user, used, null, true); // Interaction particles
|
||||
DoContactInteraction(user, used, null, true, interactionParticles: activateMsg.InteractionParticle); // Stellar - Interaction particles
|
||||
if (!activateMsg.WasLogged)
|
||||
_adminLogger.Add(LogType.InteractActivate, LogImpact.Low, $"{ToPrettyString(user):user} activated {ToPrettyString(used):used}");
|
||||
|
||||
|
||||
@@ -23,5 +23,12 @@ public sealed partial class PowerCellSlotComponent : Component
|
||||
[DataField, AutoNetworkedField]
|
||||
public bool FitsInCharger = true;
|
||||
|
||||
// Begin DeltaV
|
||||
/// <summary>
|
||||
/// DeltaV - power cell slots
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public bool Active = true;
|
||||
// End DeltaV
|
||||
}
|
||||
|
||||
|
||||
@@ -39,6 +39,14 @@ public sealed partial class PowerCellSystem
|
||||
return false;
|
||||
}
|
||||
|
||||
// Begin DeltaV
|
||||
if (!ent.Comp.Active)
|
||||
{
|
||||
battery = null;
|
||||
return false;
|
||||
}
|
||||
// End DeltaV
|
||||
|
||||
if (!_itemSlots.TryGetSlot(ent.Owner, ent.Comp.CellSlotId, out var slot))
|
||||
{
|
||||
battery = null;
|
||||
@@ -153,6 +161,21 @@ public sealed partial class PowerCellSystem
|
||||
[PublicAPI]
|
||||
public bool HasCharge(Entity<PowerCellSlotComponent?> ent, float charge, EntityUid? user = null, bool predicted = false)
|
||||
{
|
||||
// Begin DeltaV
|
||||
if (Resolve(ent, ref ent.Comp, false) && !ent.Comp.Active)
|
||||
{
|
||||
if (user == null)
|
||||
return false;
|
||||
|
||||
if (predicted)
|
||||
_popup.PopupClient(Loc.GetString("power-cell-disabled"), ent.Owner, user.Value);
|
||||
else
|
||||
_popup.PopupEntity(Loc.GetString("power-cell-disabled"), ent.Owner, user.Value);
|
||||
|
||||
return false;
|
||||
}
|
||||
// End DeltaV
|
||||
|
||||
if (!TryGetBatteryFromSlot(ent, out var battery))
|
||||
{
|
||||
if (user == null)
|
||||
@@ -192,6 +215,21 @@ public sealed partial class PowerCellSystem
|
||||
[PublicAPI]
|
||||
public bool TryUseCharge(Entity<PowerCellSlotComponent?> ent, float charge, EntityUid? user = null, bool predicted = false)
|
||||
{
|
||||
// Begin DeltaV
|
||||
if (Resolve(ent, ref ent.Comp, false) && !ent.Comp.Active)
|
||||
{
|
||||
if (user == null)
|
||||
return false;
|
||||
|
||||
if (predicted)
|
||||
_popup.PopupClient(Loc.GetString("power-cell-disabled"), ent.Owner, user.Value);
|
||||
else
|
||||
_popup.PopupEntity(Loc.GetString("power-cell-disabled"), ent.Owner, user.Value);
|
||||
|
||||
return false;
|
||||
}
|
||||
// End DeltaV
|
||||
|
||||
if (!TryGetBatteryFromSlot(ent, out var battery))
|
||||
{
|
||||
if (user == null)
|
||||
@@ -247,6 +285,29 @@ public sealed partial class PowerCellSystem
|
||||
|
||||
return _battery.GetMaxUses(battery.Value.AsNullable(), cost);
|
||||
}
|
||||
|
||||
// Begin DeltaV
|
||||
/// <summary>
|
||||
/// Sets whether a power cell slot is capable of drawing from a battery
|
||||
/// </summary>
|
||||
[PublicAPI]
|
||||
public void SetSlotActive(Entity<PowerCellSlotComponent?> ent, bool active)
|
||||
{
|
||||
if (!Resolve(ent, ref ent.Comp))
|
||||
return;
|
||||
|
||||
ent.Comp.Active = active;
|
||||
Dirty(ent, ent.Comp);
|
||||
|
||||
if (!active)
|
||||
{
|
||||
var emptyEv = new PowerCellSlotEmptyEvent();
|
||||
RaiseLocalEvent(ent, ref emptyEv);
|
||||
}
|
||||
|
||||
_battery.RefreshChargeRate(ent.Owner);
|
||||
}
|
||||
// End DeltaV
|
||||
}
|
||||
|
||||
// Begin DeltaV - event-based search for battery
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -14,13 +14,21 @@ namespace Content.Shared.Random.Helpers
|
||||
return random.Pick(prototype.Values);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// DeltaV - Randomly selects an entry from <paramref name="prototype"/> and returns the result.
|
||||
/// </summary>
|
||||
public static string PickId(this IRobustRandom random, LocalizedDatasetPrototype prototype)
|
||||
{
|
||||
var index = random.Next(prototype.Values.Count);
|
||||
return prototype.Values[index];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Randomly selects an entry from <paramref name="prototype"/>, attempts to localize it, and returns the result.
|
||||
/// </summary>
|
||||
public static string Pick(this IRobustRandom random, LocalizedDatasetPrototype prototype)
|
||||
{
|
||||
var index = random.Next(prototype.Values.Count);
|
||||
return Loc.GetString(prototype.Values[index]);
|
||||
return Loc.GetString(random.PickId(prototype)); // DeltaV - we need LocIds too
|
||||
}
|
||||
|
||||
public static string Pick(this IWeightedRandomPrototype prototype, System.Random random)
|
||||
|
||||
@@ -46,4 +46,48 @@ public sealed partial class SSDIndicatorComponent : Component
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public TimeSpan UpdateInterval = TimeSpan.FromSeconds(1);
|
||||
|
||||
// DeltaV - SSD Recency Additions START
|
||||
|
||||
/// <summary>
|
||||
/// The time at which the Entity became SSD.
|
||||
/// </summary>
|
||||
[AutoNetworkedField, AutoPausedField]
|
||||
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
|
||||
public TimeSpan SsdSince = TimeSpan.Zero;
|
||||
|
||||
/// <summary>
|
||||
/// The icon displayed next to the associated entity when it is recently SSD (stage 2).
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public ProtoId<SsdIconPrototype> RecentIcon = "RecentSSDIcon";
|
||||
|
||||
/// <summary>
|
||||
/// The icon displayed next to the associated entity when it is very recently SSD (stage 1).
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public ProtoId<SsdIconPrototype> VeryRecentIcon = "VeryRecentSSDIcon";
|
||||
|
||||
// DeltaV END
|
||||
}
|
||||
|
||||
// DeltaV - SSD Recency START
|
||||
// If you change this enum, remember to update `Resources/Locale/en-US/_DV/ssdIndicator/examine.ftl`
|
||||
public enum SsdStage : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// Stage 1: SSD Indicator is red. They might just be recovering from a crash/timeout.
|
||||
/// </summary>
|
||||
VeryRecent,
|
||||
|
||||
/// <summary>
|
||||
/// Stage 2: SSD Indicator is yellow. They've been gone for a bit, but they shouldn't be moved to cryo yet.
|
||||
/// </summary>
|
||||
Recent,
|
||||
|
||||
/// <summary>
|
||||
/// Stage 3: SSD Indicator is green/default. They've been gone for a long time, they can be moved to cryo.
|
||||
/// </summary>
|
||||
Cryoable,
|
||||
}
|
||||
// DeltaV END
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using Content.Shared._DV.CCVars; // DeltaV - SSD Recency
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Examine; // DeltaV - SSD Recency
|
||||
using Content.Shared.StatusEffectNew;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Player;
|
||||
@@ -21,6 +23,9 @@ public sealed class SSDIndicatorSystem : EntitySystem
|
||||
private bool _icSsdSleep;
|
||||
private float _icSsdSleepTime;
|
||||
|
||||
private TimeSpan _cryoableSsdSeconds; // DeltaV
|
||||
private TimeSpan _recentSsdSeconds; // DeltaV
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<SSDIndicatorComponent, PlayerAttachedEvent>(OnPlayerAttached);
|
||||
@@ -29,8 +34,44 @@ public sealed class SSDIndicatorSystem : EntitySystem
|
||||
|
||||
_cfg.OnValueChanged(CCVars.ICSSDSleep, obj => _icSsdSleep = obj, true);
|
||||
_cfg.OnValueChanged(CCVars.ICSSDSleepTime, obj => _icSsdSleepTime = obj, true);
|
||||
_cfg.OnValueChanged(DCCVars.SsdIndicatorCryoableAfterSeconds, obj => _cryoableSsdSeconds = TimeSpan.FromSeconds(obj), true); // DeltaV - SSD Recency
|
||||
_cfg.OnValueChanged(DCCVars.SsdIndicatorRecentAfterSeconds, obj => _recentSsdSeconds = TimeSpan.FromSeconds(obj), true); // DeltaV - SSD Recency
|
||||
|
||||
SubscribeLocalEvent<SSDIndicatorComponent, ExaminedEvent>(OnExamine); // DeltaV - SSD Recency
|
||||
}
|
||||
|
||||
// DeltaV - SSD Recency START
|
||||
private void OnExamine(Entity<SSDIndicatorComponent> ent, ref ExaminedEvent args)
|
||||
{
|
||||
if (!ent.Comp.IsSSD)
|
||||
return;
|
||||
|
||||
using (args.PushGroup(nameof(SSDIndicatorComponent)))
|
||||
{
|
||||
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()}"));
|
||||
}
|
||||
}
|
||||
|
||||
public SsdStage GetStage(Entity<SSDIndicatorComponent> ent)
|
||||
{
|
||||
var curTime = _timing.CurTime;
|
||||
|
||||
if (ent.Comp.SsdSince + _cryoableSsdSeconds < curTime)
|
||||
{
|
||||
return SsdStage.Cryoable;
|
||||
}
|
||||
|
||||
if (ent.Comp.SsdSince + _recentSsdSeconds < curTime)
|
||||
{
|
||||
return SsdStage.Recent;
|
||||
}
|
||||
|
||||
return SsdStage.VeryRecent;
|
||||
}
|
||||
// DeltaV END
|
||||
|
||||
private void OnPlayerAttached(EntityUid uid, SSDIndicatorComponent component, PlayerAttachedEvent args)
|
||||
{
|
||||
component.IsSSD = false;
|
||||
@@ -55,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);
|
||||
}
|
||||
|
||||
|
||||
@@ -77,4 +77,10 @@ public sealed partial class BorgTransponderComponent : Component
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public bool FakeDisabled;
|
||||
|
||||
/// <summary>
|
||||
/// DeltaV - whether the transponder is active
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public bool Active = true;
|
||||
}
|
||||
|
||||
@@ -718,8 +718,13 @@ public abstract class SharedStrippableSystem : EntitySystem
|
||||
if (args.Handled || !args.Complex || args.Target == args.User)
|
||||
return;
|
||||
|
||||
// Begin Stellar Changes - don't play an interact particle for examining the strip UI
|
||||
if (TryOpenStrippingUi(args.User, (uid, component)))
|
||||
{
|
||||
args.Handled = true;
|
||||
args.InteractionParticle = false;
|
||||
}
|
||||
// End Stellar Changes - don't play an interact particle for examining the strip UI
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
using Lidgren.Network;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared._DV.AccountLinking;
|
||||
|
||||
public sealed class LinkAccountCodeMsg : NetMessage
|
||||
{
|
||||
public Guid Code;
|
||||
|
||||
public override MsgGroups MsgGroup => MsgGroups.Core;
|
||||
|
||||
public override void ReadFromBuffer(NetIncomingMessage buffer, IRobustSerializer serializer)
|
||||
{
|
||||
Code = buffer.ReadGuid();
|
||||
}
|
||||
|
||||
public override void WriteToBuffer(NetOutgoingMessage buffer, IRobustSerializer serializer)
|
||||
{
|
||||
buffer.Write(Code);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using Lidgren.Network;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared._DV.AccountLinking;
|
||||
|
||||
public sealed class LinkAccountRequestMsg : NetMessage
|
||||
{
|
||||
public override MsgGroups MsgGroup => MsgGroups.Core;
|
||||
|
||||
public override void ReadFromBuffer(NetIncomingMessage buffer, IRobustSerializer serializer)
|
||||
{
|
||||
}
|
||||
|
||||
public override void WriteToBuffer(NetOutgoingMessage buffer, IRobustSerializer serializer)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
using System.IO;
|
||||
using Lidgren.Network;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared._DV.AccountLinking;
|
||||
|
||||
public sealed class LinkAccountStatusMsg : NetMessage
|
||||
{
|
||||
public override MsgGroups MsgGroup => MsgGroups.Core;
|
||||
|
||||
public SharedPatronFull? Patron;
|
||||
|
||||
public override void ReadFromBuffer(NetIncomingMessage buffer, IRobustSerializer serializer)
|
||||
{
|
||||
var isPatron = buffer.ReadBoolean();
|
||||
if (!isPatron)
|
||||
return;
|
||||
|
||||
buffer.ReadPadBits();
|
||||
var length = buffer.ReadVariableInt32();
|
||||
using var stream = new MemoryStream(length);
|
||||
buffer.ReadAlignedMemory(stream, length);
|
||||
Patron = serializer.Deserialize<SharedPatronFull>(stream);
|
||||
}
|
||||
|
||||
public override void WriteToBuffer(NetOutgoingMessage buffer, IRobustSerializer serializer)
|
||||
{
|
||||
if (Patron == null)
|
||||
{
|
||||
buffer.Write(false);
|
||||
return;
|
||||
}
|
||||
|
||||
buffer.Write(true);
|
||||
buffer.WritePadBits();
|
||||
using (var stream = new MemoryStream())
|
||||
{
|
||||
serializer.Serialize(stream, Patron);
|
||||
buffer.WriteVariableInt32((int) stream.Length);
|
||||
stream.TryGetBuffer(out var segment);
|
||||
buffer.Write(segment);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
using Lidgren.Network;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared._DV.AccountLinking;
|
||||
|
||||
public sealed class PatronListMsg : NetMessage
|
||||
{
|
||||
public override MsgGroups MsgGroup => MsgGroups.Core;
|
||||
|
||||
public List<SharedPatron> Patrons = default!;
|
||||
|
||||
public override void ReadFromBuffer(NetIncomingMessage buffer, IRobustSerializer serializer)
|
||||
{
|
||||
var count = buffer.ReadVariableInt32();
|
||||
Patrons = new List<SharedPatron>(count);
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
var name = buffer.ReadString();
|
||||
var tier = buffer.ReadString();
|
||||
Patrons.Add(new SharedPatron(name, tier));
|
||||
}
|
||||
}
|
||||
|
||||
public override void WriteToBuffer(NetOutgoingMessage buffer, IRobustSerializer serializer)
|
||||
{
|
||||
buffer.WriteVariableInt32(Patrons.Count);
|
||||
foreach (var patron in Patrons)
|
||||
{
|
||||
buffer.Write(patron.Name);
|
||||
buffer.Write(patron.Tier);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared._DV.AccountLinking;
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class SharedPatron(string name, string tier)
|
||||
{
|
||||
public readonly string Name = name;
|
||||
public readonly string Tier = tier;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared._DV.AccountLinking;
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed record SharedPatronFull(
|
||||
SharedPatronTier? Tier,
|
||||
bool Linked
|
||||
);
|
||||
@@ -0,0 +1,9 @@
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared._DV.AccountLinking;
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed record SharedPatronTier(
|
||||
bool ShowOnCredits,
|
||||
string Tier
|
||||
);
|
||||
@@ -0,0 +1,13 @@
|
||||
using Robust.Shared.Configuration;
|
||||
|
||||
namespace Content.Shared._DV.CCVars;
|
||||
|
||||
public sealed partial class DCCVars
|
||||
{
|
||||
/// <summary>
|
||||
/// Redirects you to the channel in discord to link your account.
|
||||
/// </summary>
|
||||
public static readonly CVarDef<string> DiscordAccountLinkingMessageLink =
|
||||
CVarDef.Create("discord.discord_account_linking_message_link", "", CVar.REPLICATED | CVar.SERVER);
|
||||
|
||||
}
|
||||
@@ -316,4 +316,19 @@ public sealed partial class DCCVars
|
||||
/// </summary>
|
||||
public static readonly CVarDef<bool> EsScreenshakeDisabled =
|
||||
CVarDef.Create("deltav.es_screenshake.disabled", false, CVar.CLIENTONLY | CVar.ARCHIVE);
|
||||
|
||||
/// <summary>
|
||||
/// The total time a player has to be SSD to be considered cryoable (stage 3).
|
||||
/// Default is 20 minutes. Value should be bigger than <see cref="SsdIndicatorRecentAfterSeconds"/>.
|
||||
/// </summary>
|
||||
public static readonly CVarDef<float> SsdIndicatorCryoableAfterSeconds =
|
||||
CVarDef.Create("deltav.ssd.cryoable_after_seconds", 1200f, CVar.SERVER | CVar.REPLICATED);
|
||||
|
||||
/// <summary>
|
||||
/// The total time a player has to be SSD to be considered recently SSD (stage 2).
|
||||
/// If the player has been SSD for less than this time, they are considered "very recently" SSD (stage 1).
|
||||
/// Default is 5 minutes. Value should be smaller than <see cref="SsdIndicatorCryoableAfterSeconds"/>.
|
||||
/// </summary>
|
||||
public static readonly CVarDef<float> SsdIndicatorRecentAfterSeconds =
|
||||
CVarDef.Create("deltav.ssd.recent_after_seconds", 300f, CVar.SERVER | CVar.REPLICATED);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
using Content.Shared.Radio;
|
||||
|
||||
namespace Content.Shared._DV.Chat;
|
||||
|
||||
[ByRefEvent]
|
||||
public record struct EntityAudiblyEmotedEvent(EntityUid Source, string Message, RadioChannelPrototype? Channel, EmoteType? Type);
|
||||
@@ -0,0 +1,15 @@
|
||||
namespace Content.Shared._DV.Chat;
|
||||
|
||||
|
||||
// Note for future: If you want to make this more robust to handle more types of emotes while still being able to check off audible as an option for it then it would
|
||||
// probably be better to make it a struct and have audible be a flag for it and then the type of emote. This would avoid having to make two different types for audible and visual.
|
||||
/// <summary>
|
||||
/// Different ways of emoting. For that little extra in RP!
|
||||
/// </summary>
|
||||
public enum EmoteType : byte
|
||||
{
|
||||
Normal, // Character emotes
|
||||
Audible, // Character screams
|
||||
Possessive, // Character's emote
|
||||
AudiblePossessive // Character's scream
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared._DV.Silicons;
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public enum BorgWireActionKey : byte
|
||||
{
|
||||
SlavedKey,
|
||||
TransponderKey,
|
||||
ChipKey,
|
||||
CellKey,
|
||||
}
|
||||
@@ -9,7 +9,7 @@ namespace Content.Shared._DV.Silicons.Borgs;
|
||||
/// Adds an id chip slot for a borg which will control its access.
|
||||
/// Enables an id chip's access when inserted to an id chip slot.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent, Access(typeof(IdChipSlotSystem))]
|
||||
[RegisterComponent, NetworkedComponent, Access(typeof(IdChipSlotSystem)), AutoGenerateComponentState]
|
||||
public sealed partial class IdChipSlotComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
@@ -35,6 +35,12 @@ public sealed partial class IdChipSlotComponent : Component
|
||||
/// </summary>
|
||||
[DataField(required: true)]
|
||||
public EntityWhitelist Whitelist = new();
|
||||
|
||||
/// <summary>
|
||||
/// Whether the ID chip slot is active
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public bool Active = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -73,7 +73,7 @@ public sealed class IdChipSlotSystem : EntitySystem
|
||||
|
||||
private void OnGetAdditionalAccess(Entity<IdChipSlotComponent> ent, ref GetAdditionalAccessEvent args)
|
||||
{
|
||||
if (ent.Comp.Chip is {} chip)
|
||||
if (ent.Comp is { Active: true, Chip: {} chip })
|
||||
args.Entities.Add(chip);
|
||||
}
|
||||
|
||||
@@ -83,7 +83,7 @@ public sealed class IdChipSlotSystem : EntitySystem
|
||||
return;
|
||||
|
||||
// enable its access so the borg can use it
|
||||
_access.SetAccessEnabled(args.Entity, true);
|
||||
_access.SetAccessEnabled(args.Entity, ent.Comp.Active);
|
||||
}
|
||||
|
||||
private void OnChipRemoved(Entity<IdChipSlotComponent> ent, ref EntRemovedFromContainerMessage args)
|
||||
@@ -105,4 +105,14 @@ public sealed class IdChipSlotSystem : EntitySystem
|
||||
_container.Remove(chip, ent.Comp.Container);
|
||||
_hands.TryPickupAnyHand(args.Actor, chip);
|
||||
}
|
||||
|
||||
public void SetActive(Entity<IdChipSlotComponent?> ent, bool active)
|
||||
{
|
||||
if (!Resolve(ent, ref ent.Comp))
|
||||
return;
|
||||
|
||||
ent.Comp.Active = active;
|
||||
_access.SetAccessEnabled(ent.Owner, active && ent.Comp.Chip is not null);
|
||||
Dirty(ent);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,8 +19,14 @@ public sealed partial class SlavedBorgComponent : Component
|
||||
public ProtoId<SiliconLawPrototype> Law;
|
||||
|
||||
/// <summary>
|
||||
/// Prevents adding the same law twice.
|
||||
/// Prevents adding the same law 0 mutliple times during law updates.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public bool Added;
|
||||
public bool HasBeenAdded;
|
||||
|
||||
/// <summary>
|
||||
/// Whether the law 0 should be added to the lawset.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public bool ShouldBeAdded = true;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared._DV.Traits.Assorted;
|
||||
|
||||
/// <summary>
|
||||
/// This entity will not be candidate for a paradox clone.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
public sealed partial class NoParadoxCloneComponent : Component;
|
||||
@@ -0,0 +1,9 @@
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared._DV.Whitelist.Holoprojectors;
|
||||
|
||||
/// <summary>
|
||||
/// Marker component for holoradiationblockers, used for reclaiming charges of the projector.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
public sealed partial class HoloRadiationblockerComponent : Component;
|
||||
@@ -95,3 +95,8 @@
|
||||
license: "CC-BY-NC-3.0"
|
||||
copyright: "Superposition by Amie Waters, converted to mono"
|
||||
source: "https://amiewaters.bandcamp.com/track/superposition-2"
|
||||
|
||||
- files: ["waitingroomabovethestars.ogg", "waitingroomabovethestars_mono.ogg"]
|
||||
license: "CC-BY-SA-4.0"
|
||||
copyright: "by This_Is_Gray for DeltaV, converted to mono"
|
||||
source: "https://github.com/DeltaV-Station/Delta-v.git"
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -13,3 +13,8 @@
|
||||
license: "CC-BY-NC-SA-4.0"
|
||||
copyright: "flying off the album TWENTY by Oliver Buckland."
|
||||
source: "https://oliver-buckland.bandcamp.com/album/twenty"
|
||||
|
||||
- files: ["waitingroomabovethestars.ogg"]
|
||||
license: "CC-BY-SA-4.0"
|
||||
copyright: "by This_Is_Gray for DeltaV"
|
||||
source: "https://github.com/DeltaV-Station/Delta-v.git"
|
||||
|
||||
Binary file not shown.
@@ -307,5 +307,12 @@ Entries:
|
||||
id: 33
|
||||
time: '2026-05-28T16:56:20.0000000+00:00'
|
||||
url: https://github.com/DeltaV-Station/Delta-v/pull/5858
|
||||
- author: DisposableCrewmember42
|
||||
changes:
|
||||
- message: The admin overlay now shows how long people have been SSD
|
||||
type: Add
|
||||
id: 34
|
||||
time: '2026-06-06T19:14:04.0000000+00:00'
|
||||
url: https://github.com/DeltaV-Station/Delta-v/pull/5699
|
||||
Name: DeltaVAdmin
|
||||
Order: 5
|
||||
|
||||
@@ -1,120 +1,4 @@
|
||||
Entries:
|
||||
- author: Colin-Tel, BigMaxPorter
|
||||
changes:
|
||||
- message: The laser carbine now does the intended 20 heat damage.
|
||||
type: Fix
|
||||
id: 1871
|
||||
time: '2025-11-28T08:24:56.0000000+00:00'
|
||||
url: https://github.com/DeltaV-Station/Delta-v/pull/4790
|
||||
- author: Toby222
|
||||
changes:
|
||||
- message: Cardboard boxes are now affected by gravity
|
||||
type: Fix
|
||||
id: 1872
|
||||
time: '2025-11-28T08:47:19.0000000+00:00'
|
||||
url: https://github.com/DeltaV-Station/Delta-v/pull/4794
|
||||
- author: Toby222
|
||||
changes:
|
||||
- message: Skimmer now does its one and only job again, lazy bugger
|
||||
type: Fix
|
||||
id: 1873
|
||||
time: '2025-11-28T09:36:55.0000000+00:00'
|
||||
url: https://github.com/DeltaV-Station/Delta-v/pull/4795
|
||||
- author: Toby222
|
||||
changes:
|
||||
- message: Flintlock is now craftable as intended
|
||||
type: Fix
|
||||
id: 1874
|
||||
time: '2025-11-28T09:41:03.0000000+00:00'
|
||||
url: https://github.com/DeltaV-Station/Delta-v/pull/4796
|
||||
- author: Toby222
|
||||
changes:
|
||||
- message: Removed the invisible, intangible bear inside all fishing crates that
|
||||
was trying to steal your fish
|
||||
type: Fix
|
||||
id: 1875
|
||||
time: '2025-11-28T09:49:21.0000000+00:00'
|
||||
url: https://github.com/DeltaV-Station/Delta-v/pull/4797
|
||||
- author: SirWarock
|
||||
changes:
|
||||
- message: The faithful rejoice! Your deity has chosen to to protect you as long
|
||||
as you hold your religious tome!
|
||||
type: Tweak
|
||||
id: 1876
|
||||
time: '2025-11-28T19:03:43.0000000+00:00'
|
||||
url: https://github.com/DeltaV-Station/Delta-v/pull/4726
|
||||
- author: Vapetastic-Gaming
|
||||
changes:
|
||||
- message: Submarine Playtest Fixes and Tweaks, it is now in current rotation, with
|
||||
a minimum 80 population !!
|
||||
type: Tweak
|
||||
id: 1877
|
||||
time: '2025-11-29T03:30:17.0000000+00:00'
|
||||
url: https://github.com/DeltaV-Station/Delta-v/pull/4554
|
||||
- author: snowywinters
|
||||
changes:
|
||||
- message: Now normal command turrets alert the AI to an intruder
|
||||
type: Fix
|
||||
id: 1878
|
||||
time: '2025-11-29T19:57:59.0000000+00:00'
|
||||
url: https://github.com/DeltaV-Station/Delta-v/pull/4803
|
||||
- author: kotobdev
|
||||
changes:
|
||||
- message: The FG13 Xiuhcoatl is now substantially more deadly. It also costs less!
|
||||
type: Tweak
|
||||
id: 1879
|
||||
time: '2025-11-29T22:27:08.0000000+00:00'
|
||||
url: https://github.com/DeltaV-Station/Delta-v/pull/4749
|
||||
- author: ShepardToTheStars
|
||||
changes:
|
||||
- message: While trying to stand-up, you can now press your Toggle Crawling keybind
|
||||
to get up faster at the cost of stamina. Of course, you still still need a free
|
||||
hand to do this.
|
||||
type: Tweak
|
||||
id: 1880
|
||||
time: '2025-11-30T16:24:25.0000000+00:00'
|
||||
url: https://github.com/DeltaV-Station/Delta-v/pull/4802
|
||||
- author: TheGrimbeeper
|
||||
changes:
|
||||
- message: Artifacts can now be triggered by ambient radiation, not just uranium
|
||||
spears
|
||||
type: Fix
|
||||
id: 1881
|
||||
time: '2025-11-30T16:30:34.0000000+00:00'
|
||||
url: https://github.com/DeltaV-Station/Delta-v/pull/4817
|
||||
- author: pootslap
|
||||
changes:
|
||||
- message: Characters must now be 40 to be considered middle-aged and 70 to be considered
|
||||
old.
|
||||
type: Tweak
|
||||
id: 1882
|
||||
time: '2025-11-30T16:31:43.0000000+00:00'
|
||||
url: https://github.com/DeltaV-Station/Delta-v/pull/4783
|
||||
- author: Proxysseia
|
||||
changes:
|
||||
- message: Crew of legally docked foreign vessels now has rights
|
||||
type: Add
|
||||
id: 1883
|
||||
time: '2025-11-30T16:37:35.0000000+00:00'
|
||||
url: https://github.com/DeltaV-Station/Delta-v/pull/4763
|
||||
- author: verybigman311
|
||||
changes:
|
||||
- message: Rejoice! Four new cocktails utilizing the long-neglected Angobitters
|
||||
are now available.
|
||||
type: Add
|
||||
- message: You can now ask for Mliko (glorified beer foam) at your local bar.
|
||||
type: Add
|
||||
- message: Famous drinks such as Pilk, Moonshine, and Hooch have been given new
|
||||
sprites.
|
||||
type: Add
|
||||
- message: Alterations have been made to the Grog, Posca, and Gin Fizz recipes.
|
||||
type: Tweak
|
||||
- message: Numerous beverages will now form puddles of the correct color when thrown
|
||||
violently against the ground.
|
||||
type: Fix
|
||||
id: 1884
|
||||
time: '2025-11-30T16:41:31.0000000+00:00'
|
||||
url: https://github.com/DeltaV-Station/Delta-v/pull/4780
|
||||
- author: verybigman311
|
||||
changes:
|
||||
- message: Lava Fountains are now an available decoration.
|
||||
@@ -4400,4 +4284,115 @@
|
||||
id: 2371
|
||||
time: '2026-06-02T15:16:39.0000000+00:00'
|
||||
url: https://github.com/DeltaV-Station/Delta-v/pull/5944
|
||||
- author: turtlemutt
|
||||
changes:
|
||||
- message: Swapped out Lighthouse security's red with the new blue.
|
||||
type: Tweak
|
||||
id: 2372
|
||||
time: '2026-06-03T18:43:56.0000000+00:00'
|
||||
url: https://github.com/DeltaV-Station/Delta-v/pull/5898
|
||||
- author: AeraAuling
|
||||
changes:
|
||||
- message: Holy and Shadow have been renamed to be a bit less good and evil
|
||||
type: Tweak
|
||||
id: 2373
|
||||
time: '2026-06-04T10:58:56.0000000+00:00'
|
||||
url: https://github.com/DeltaV-Station/Delta-v/pull/5952
|
||||
- author: sowelipililimute
|
||||
changes:
|
||||
- message: Interaction particles are now less energetic.
|
||||
type: Tweak
|
||||
- message: Interaction particles no longer spawn when opening the strip menu.
|
||||
type: Fix
|
||||
id: 2374
|
||||
time: '2026-06-05T12:46:01.0000000+00:00'
|
||||
url: https://github.com/DeltaV-Station/Delta-v/pull/5933
|
||||
- author: sowelipililimute
|
||||
changes:
|
||||
- message: Dionaea now have a new name generation algorithim.
|
||||
type: Tweak
|
||||
id: 2375
|
||||
time: '2026-06-05T13:33:57.0000000+00:00'
|
||||
url: https://github.com/DeltaV-Station/Delta-v/pull/5836
|
||||
- author: Stxcking
|
||||
changes:
|
||||
- message: 'Reverted and Rework Upstream Medical Borg Changes. Media provided in
|
||||
PR:'
|
||||
type: Tweak
|
||||
- message: Medical Borg can now see Triage Icons.
|
||||
type: Fix
|
||||
id: 2376
|
||||
time: '2026-06-05T15:05:53.0000000+00:00'
|
||||
url: https://github.com/DeltaV-Station/Delta-v/pull/5656
|
||||
- author: Stxcking
|
||||
changes:
|
||||
- message: Added the No Paradox Clone Trait!
|
||||
type: Add
|
||||
id: 2377
|
||||
time: '2026-06-05T15:17:50.0000000+00:00'
|
||||
url: https://github.com/DeltaV-Station/Delta-v/pull/5937
|
||||
- author: rebe83
|
||||
changes:
|
||||
- message: Nuclear operatives, listening post operators, and ninjas now have randomized
|
||||
appearances.
|
||||
type: Tweak
|
||||
id: 2378
|
||||
time: '2026-06-05T15:26:25.0000000+00:00'
|
||||
url: https://github.com/DeltaV-Station/Delta-v/pull/5922
|
||||
- author: Stxcking
|
||||
changes:
|
||||
- message: Added RGB plushies, gay wackpack, LGBT handheld flags, and more pride
|
||||
theme items to the Pride-o-Mat!
|
||||
type: Tweak
|
||||
id: 2379
|
||||
time: '2026-06-05T15:28:44.0000000+00:00'
|
||||
url: https://github.com/DeltaV-Station/Delta-v/pull/5946
|
||||
- author: Stxcking
|
||||
changes:
|
||||
- message: Added Small Nuclear Reactor (7,500) and Small Gas Turbine (5,000) Flatpacks
|
||||
for Purchase!
|
||||
type: Add
|
||||
id: 2380
|
||||
time: '2026-06-05T15:32:57.0000000+00:00'
|
||||
url: https://github.com/DeltaV-Station/Delta-v/pull/5945
|
||||
- author: einknusprigestoast
|
||||
changes:
|
||||
- message: Added a Chameleon armored Vest for Syndicate Agents
|
||||
type: Add
|
||||
id: 2381
|
||||
time: '2026-06-05T17:34:10.0000000+00:00'
|
||||
url: https://github.com/DeltaV-Station/Delta-v/pull/5921
|
||||
- author: sowelipililimute
|
||||
changes:
|
||||
- message: NT Cyborgs now have a wire panel that allow enabling/disabling the transponder,
|
||||
law 0, and AI PDA access.
|
||||
type: Add
|
||||
id: 2382
|
||||
time: '2026-06-05T21:13:49.0000000+00:00'
|
||||
url: https://github.com/DeltaV-Station/Delta-v/pull/5948
|
||||
- author: Stxcking
|
||||
changes:
|
||||
- message: Radiation Barrier Projectors Actually Creates Holo Radiation Barriers
|
||||
Instead of Holo Wet Floor Signs !
|
||||
type: Fix
|
||||
id: 2383
|
||||
time: '2026-06-06T09:53:19.0000000+00:00'
|
||||
url: https://github.com/DeltaV-Station/Delta-v/pull/5787
|
||||
- author: IAmNotGray
|
||||
changes:
|
||||
- message: Added a new jukebox song called "Waiting Room Above The Stars."
|
||||
type: Add
|
||||
id: 2384
|
||||
time: '2026-06-06T12:07:56.0000000+00:00'
|
||||
url: https://github.com/DeltaV-Station/Delta-v/pull/5620
|
||||
- author: DisposableCrewmember42
|
||||
changes:
|
||||
- message: The SSD indicator now changes color depending on duration.
|
||||
type: Add
|
||||
- message: The examine text of SSD players now includes the SSD duration and information
|
||||
on what (not) to do with them.
|
||||
type: Add
|
||||
id: 2385
|
||||
time: '2026-06-06T19:14:04.0000000+00:00'
|
||||
url: https://github.com/DeltaV-Station/Delta-v/pull/5699
|
||||
Order: 1
|
||||
|
||||
@@ -1036,4 +1036,13 @@
|
||||
id: 120
|
||||
time: '2026-05-31T19:28:53.0000000+00:00'
|
||||
url: https://github.com/DeltaV-Station/Delta-v/pull/5847
|
||||
- author: biddygelson
|
||||
changes:
|
||||
- message: 'Academy: added secure windoors to security disposal chutes.'
|
||||
type: Add
|
||||
- message: 'Academy: Escape pods are now the 5x7 DV variant'
|
||||
type: Tweak
|
||||
id: 121
|
||||
time: '2026-06-05T15:07:41.0000000+00:00'
|
||||
url: https://github.com/DeltaV-Station/Delta-v/pull/5880
|
||||
Order: 3
|
||||
|
||||
@@ -59,4 +59,7 @@ storage_limit = 3
|
||||
|
||||
[audio] # DeltaV
|
||||
attenuation = 4 # inversedistanceclamped 1 << 2, see audioparams.cs
|
||||
z_offset = -2
|
||||
z_offset = -2
|
||||
|
||||
[discord]
|
||||
discord_account_linking_message_link = "https://discord.com/channels/968983104247185448/1513638637018021978"
|
||||
@@ -112,3 +112,6 @@ max_announcement_length = 512
|
||||
[audio]
|
||||
attenuation = 4 # inversedistanceclamped 1 << 2, see audioparams.cs
|
||||
z_offset = -2
|
||||
|
||||
[discord]
|
||||
discord_account_linking_message_link = "https://discord.com/channels/968983104247185448/1513638637018021978"
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,9 @@
|
||||
ui-discord = Discord
|
||||
|
||||
ui-link-discord-account = Link Discord Account
|
||||
ui-link-discord-account-text = Click the button below to copy your code.
|
||||
Then go to the #account-linking channel to link your accounts.
|
||||
ui-link-discord-account-copy = Copy code
|
||||
ui-link-discord-account-copied = Copied!
|
||||
ui-link-discord-account-already-linked = [bold]Your account is already linked![/bold]
|
||||
ui-link-discord-account-open-channel = Open channel
|
||||
@@ -0,0 +1 @@
|
||||
ui-patreon = Patreon
|
||||
@@ -1 +1,2 @@
|
||||
admin-overlay-watchlisted-username-suffix = [WL]
|
||||
admin-overlay-ssd-time = SSD {$time}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
armor-stamina-projectile-coefficient-value = - [color=yellow]Stamina projectile[/color] damage reduced by [color=lightblue]{$value}%[/color].
|
||||
armor-stamina-melee-coefficient-value = - [color=yellow]Stamina melee[/color] damage reduced by [color=lightblue]{$value}%[/color].
|
||||
armor-damage-type-shadow = Shadow
|
||||
armor-damage-type-soul = Soul
|
||||
armor-damage-type-holy = Radiant
|
||||
|
||||
held-armor-examine = [color=cyan]Holding[/color] it provides the following protection:
|
||||
|
||||
|
||||
@@ -39,3 +39,17 @@ chat-speech-verb-feroxi-1 = blubs
|
||||
chat-speech-verb-feroxi-2 = swishes
|
||||
chat-speech-verb-feroxi-3 = gnashes
|
||||
chat-speech-verb-feroxi-4 = growls
|
||||
|
||||
chat-manager-entity-me-possessive-wrap-message = [italic]{ PROPER($entity) ->
|
||||
*[false] The {$entityName}'s {$message}[/italic]
|
||||
[true] {CAPITALIZE($entityName)}'s {$message}[/italic]
|
||||
}
|
||||
|
||||
chat-manager-entity-me-audible-wrap-message = [italic]{ PROPER($entity) ->
|
||||
*[false] The {$entityName} {$message}[/italic]
|
||||
[true] {CAPITALIZE($entityName)} {$message}[/italic]
|
||||
}
|
||||
chat-manager-entity-me-audible-possessive-wrap-message = [italic]{ PROPER($entity) ->
|
||||
*[false] The {$entityName}'s {$message}[/italic]
|
||||
[true] {CAPITALIZE($entityName)}'s {$message}[/italic]
|
||||
}
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
damage-type-ion = Ion
|
||||
damage-type-shadow = Shadow
|
||||
damage-type-soul = Soul
|
||||
damage-type-radiant = Radiant
|
||||
|
||||
@@ -0,0 +1,311 @@
|
||||
dv-names-diona-first-1 = To Sleep Beneath
|
||||
dv-names-diona-first-2 = Wind Over the
|
||||
dv-names-diona-first-3 = Embrace of the
|
||||
dv-names-diona-first-4 = Dreams of the
|
||||
dv-names-diona-first-5 = Witnessing
|
||||
dv-names-diona-first-6 = To Walk Beneath the
|
||||
dv-names-diona-first-7 = Approaching
|
||||
dv-names-diona-first-8 = Growing
|
||||
dv-names-diona-first-9 = Eternal
|
||||
dv-names-diona-first-10 = Persistent
|
||||
dv-names-diona-first-11 = Whispering to the
|
||||
dv-names-diona-first-12 = Fading
|
||||
dv-names-diona-first-13 = Remembering
|
||||
dv-names-diona-first-14 = Dancing Beneath the
|
||||
dv-names-diona-first-15 = Silent
|
||||
dv-names-diona-first-16 = Falling Toward
|
||||
dv-names-diona-first-17 = Fragments of
|
||||
dv-names-diona-first-18 = Seeking
|
||||
dv-names-diona-first-19 = Carried by the
|
||||
dv-names-diona-first-20 = A New
|
||||
dv-names-diona-first-21 = The Peace in
|
||||
dv-names-diona-first-22 = Knowledge of
|
||||
dv-names-diona-first-23 = Resilience within
|
||||
dv-names-diona-first-24 = Decaying of the
|
||||
dv-names-diona-first-25 = The Balancing of
|
||||
dv-names-diona-first-26 = Clicks of
|
||||
dv-names-diona-first-27 = Boiling
|
||||
dv-names-diona-first-28 = Distant
|
||||
dv-names-diona-first-29 = Glacial
|
||||
dv-names-diona-first-30 = Breaking through
|
||||
dv-names-diona-first-31 = The Sounds of
|
||||
dv-names-diona-first-32 = Ephemeral
|
||||
dv-names-diona-first-33 =
|
||||
.intersperse = As {$last} { $lastPlural ->
|
||||
[true] Descend
|
||||
*[other] Descends
|
||||
}
|
||||
dv-names-diona-first-34 = Sunlight
|
||||
dv-names-diona-first-35 = Moonlight
|
||||
dv-names-diona-first-36 =
|
||||
.intersperse = As { $last } { $lastPlural ->
|
||||
[true] Bloom
|
||||
*[other] Blooms
|
||||
}
|
||||
dv-names-diona-first-37 =
|
||||
.intersperse = { $last } { $lastPlural ->
|
||||
[true] Scream
|
||||
*[other] Screams
|
||||
}
|
||||
dv-names-diona-first-38 =
|
||||
.intersperse = { $last } { $lastPlural ->
|
||||
[true] Mend
|
||||
*[other] Mends
|
||||
}
|
||||
dv-names-diona-first-39 = Shimmering
|
||||
dv-names-diona-first-40 = Murmurs of the
|
||||
dv-names-diona-first-41 = Unraveling
|
||||
dv-names-diona-first-42 = Dissolving into
|
||||
dv-names-diona-first-43 = Breathing the
|
||||
dv-names-diona-first-44 = Hollow
|
||||
dv-names-diona-first-45 = Glimmering
|
||||
dv-names-diona-first-46 = Veiled in
|
||||
dv-names-diona-first-47 = Suspended in
|
||||
dv-names-diona-first-48 = Echoing through
|
||||
dv-names-diona-first-49 = Unspooling the
|
||||
dv-names-diona-first-50 = Drifting Between
|
||||
dv-names-diona-first-51 = Woven from
|
||||
dv-names-diona-first-52 = Pulsing
|
||||
dv-names-diona-first-53 = Crystalline
|
||||
dv-names-diona-first-54 = Flickering
|
||||
dv-names-diona-first-55 = Churning
|
||||
dv-names-diona-first-56 = Wreathed in
|
||||
dv-names-diona-first-57 = Hushed
|
||||
dv-names-diona-first-58 = Luminous
|
||||
dv-names-diona-first-59 =
|
||||
.intersperse = { $last } { $lastPlural ->
|
||||
[true] Dreamt
|
||||
*[other] Dreamt
|
||||
}
|
||||
dv-names-diona-first-60 =
|
||||
.intersperse = { $last } { $lastPlural ->
|
||||
[true] Wane
|
||||
*[other] Wanes
|
||||
}
|
||||
dv-names-diona-first-61 =
|
||||
.intersperse = As { $last } { $lastPlural ->
|
||||
[true] Collapse
|
||||
*[other] Collapses
|
||||
}
|
||||
dv-names-diona-first-62 =
|
||||
.intersperse = { $last } { $lastPlural ->
|
||||
[true] Weep
|
||||
*[other] Weeps
|
||||
}
|
||||
dv-names-diona-first-63 =
|
||||
.intersperse = { $last } { $lastPlural ->
|
||||
[true] Echo
|
||||
*[other] Echoes
|
||||
}
|
||||
dv-names-diona-first-64 =
|
||||
.intersperse = As { $last } { $lastPlural ->
|
||||
[true] Dissolve
|
||||
*[other] Dissolves
|
||||
}
|
||||
dv-names-diona-first-65 = Orbiting the
|
||||
dv-names-diona-first-66 = Vacuous
|
||||
dv-names-diona-first-67 = Inquiries of the
|
||||
dv-names-diona-first-68 = Memories of
|
||||
dv-names-diona-first-69 = Incredible
|
||||
dv-names-diona-first-70 =
|
||||
.intersperse = { $last } Lost
|
||||
dv-names-diona-first-71 = Joy Among the
|
||||
dv-names-diona-first-72 = Peak
|
||||
dv-names-diona-first-73 = Pondering
|
||||
dv-names-diona-first-74 = Tracing the
|
||||
dv-names-diona-first-75 = Questioning the
|
||||
dv-names-diona-first-76 = Drawn Toward
|
||||
dv-names-diona-first-77 = Watching
|
||||
dv-names-diona-first-78 = Listening for
|
||||
dv-names-diona-first-79 = Reaching for the
|
||||
dv-names-diona-first-80 = Wandering
|
||||
dv-names-diona-first-81 = Marveling at
|
||||
dv-names-diona-first-82 = Peering into
|
||||
dv-names-diona-first-83 = The Wonder of
|
||||
dv-names-diona-first-84 = Gazing at the
|
||||
dv-names-diona-first-85 = Yearning to Know
|
||||
dv-names-diona-first-86 = Following the
|
||||
dv-names-diona-first-87 = Awaiting
|
||||
dv-names-diona-first-88 = Discovering
|
||||
dv-names-diona-first-89 = Contemplating
|
||||
dv-names-diona-first-90 = Wondering at the
|
||||
dv-names-diona-first-91 = Stirred by
|
||||
dv-names-diona-first-92 = Noticing
|
||||
dv-names-diona-first-93 = Turning Toward
|
||||
dv-names-diona-first-94 = Searching for the
|
||||
dv-names-diona-first-95 = The First
|
||||
dv-names-diona-first-96 =
|
||||
.intersperse = { $last } { $lastPlural ->
|
||||
[true] Beckon
|
||||
*[other] Beckons
|
||||
}
|
||||
dv-names-diona-first-97 =
|
||||
.intersperse = { $last } { $lastPlural ->
|
||||
[true] Call
|
||||
*[other] Calls
|
||||
}
|
||||
dv-names-diona-first-98 =
|
||||
.intersperse = The { $last } { $lastPlural ->
|
||||
[true] Whisper
|
||||
*[other] Whispers
|
||||
}
|
||||
dv-names-diona-first-99 =
|
||||
.intersperse = As { $last } { $lastPlural ->
|
||||
[true] Awaken
|
||||
*[other] Awakens
|
||||
}
|
||||
dv-names-diona-first-100 =
|
||||
.intersperse = { $last } { $lastPlural ->
|
||||
[true] Speak
|
||||
*[other] Speaks
|
||||
}
|
||||
dv-names-diona-first-101 =
|
||||
.intersperse = { $last } { $lastPlural ->
|
||||
[true] Sing
|
||||
*[other] Sings
|
||||
}
|
||||
dv-names-diona-first-102 = Lost
|
||||
dv-names-diona-first-103 = Learning from
|
||||
dv-names-diona-first-104 = Lessons of
|
||||
dv-names-diona-first-105 = Curious About the
|
||||
dv-names-diona-first-106 = Measuring the
|
||||
dv-names-diona-first-107 = Cataloging
|
||||
dv-names-diona-first-108 = Forgetting
|
||||
dv-names-diona-first-109 = Unknown
|
||||
dv-names-diona-first-110 = Reading the
|
||||
dv-names-diona-first-111 = Translating
|
||||
dv-names-diona-first-112 = Holding
|
||||
|
||||
dv-names-diona-last-1 = Void
|
||||
dv-names-diona-last-2 = Sky
|
||||
dv-names-diona-last-3 = Encroaching Night
|
||||
dv-names-diona-last-4 = Planetsong
|
||||
dv-names-diona-last-5 = Starsong
|
||||
dv-names-diona-last-6 = Wandering Star
|
||||
dv-names-diona-last-7 = Empty Days
|
||||
.plural = true
|
||||
dv-names-diona-last-8 = Daybreak
|
||||
dv-names-diona-last-9 = Nightfall
|
||||
dv-names-diona-last-10 = Rain
|
||||
dv-names-diona-last-11 = Rifts
|
||||
.plural = true
|
||||
dv-names-diona-last-12 = Hope
|
||||
dv-names-diona-last-13 = Dreams
|
||||
.plural = true
|
||||
dv-names-diona-last-14 = Despair
|
||||
dv-names-diona-last-15 = Planetfall
|
||||
dv-names-diona-last-16 = Depth
|
||||
dv-names-diona-last-17 = Silence
|
||||
dv-names-diona-last-18 = Bloom
|
||||
dv-names-diona-last-19 = End
|
||||
dv-names-diona-last-20 = Flames
|
||||
.plural = true
|
||||
dv-names-diona-last-21 = Radiance
|
||||
dv-names-diona-last-22 = Horizon
|
||||
dv-names-diona-last-23 = Rising Tide
|
||||
dv-names-diona-last-24 = Starfire
|
||||
dv-names-diona-last-25 = Dissonant Echoes
|
||||
.plural = true
|
||||
dv-names-diona-last-26 = Life
|
||||
dv-names-diona-last-27 = Switches
|
||||
.plural = true
|
||||
dv-names-diona-last-28 = Eternity
|
||||
dv-names-diona-last-29 = Galaxies
|
||||
.plural = true
|
||||
dv-names-diona-last-30 = Glass
|
||||
dv-names-diona-last-31 = Distant Hymns
|
||||
.plural = true
|
||||
dv-names-diona-last-32 = Tidal Lock
|
||||
dv-names-diona-last-33 = Cold Embers
|
||||
.plural = true
|
||||
dv-names-diona-last-34 = Petrichor
|
||||
dv-names-diona-last-35 = Signals
|
||||
dv-names-diona-last-36 = Deep Hum
|
||||
dv-names-diona-last-37 = Coiled Roots
|
||||
.plural = true
|
||||
dv-names-diona-last-38 = Moss
|
||||
dv-names-diona-last-39 = Joy
|
||||
dv-names-diona-last-40 = Equilibrium
|
||||
dv-names-diona-last-41 = Envy
|
||||
dv-names-diona-last-42 = Cruelty
|
||||
dv-names-diona-last-43 = Sorrows
|
||||
.plural = true
|
||||
dv-names-diona-last-44 = Fear
|
||||
dv-names-diona-last-45 = Audacity
|
||||
dv-names-diona-last-46 = Integrity
|
||||
dv-names-diona-last-47 = Paradise
|
||||
dv-names-diona-last-48 = Luminosity
|
||||
dv-names-diona-last-49 = Wonder
|
||||
dv-names-diona-last-50 = Riddle
|
||||
dv-names-diona-last-51 = Yearning
|
||||
dv-names-diona-last-52 = Witness
|
||||
dv-names-diona-last-53 = Omens
|
||||
.plural = true
|
||||
dv-names-diona-last-54 = Beacon
|
||||
dv-names-diona-last-55 = Glimmer
|
||||
dv-names-diona-last-56 = Susurrus
|
||||
dv-names-diona-last-57 = Gossamer
|
||||
dv-names-diona-last-58 = Umbra
|
||||
dv-names-diona-last-59 = Zenith
|
||||
dv-names-diona-last-60 = Nadir
|
||||
dv-names-diona-last-61 = Solstice
|
||||
dv-names-diona-last-62 = Reverie
|
||||
dv-names-diona-last-63 = Ember
|
||||
dv-names-diona-last-64 = Cinder
|
||||
dv-names-diona-last-65 = Spores
|
||||
.plural = true
|
||||
dv-names-diona-last-66 = Thaw
|
||||
dv-names-diona-last-67 = Frost
|
||||
dv-names-diona-last-68 = Slumber
|
||||
dv-names-diona-last-69 = Ache
|
||||
dv-names-diona-last-70 = Shiver
|
||||
dv-names-diona-last-71 = Tremors
|
||||
.plural = true
|
||||
dv-names-diona-last-72 = Pyre
|
||||
dv-names-diona-last-73 = Resin
|
||||
dv-names-diona-last-74 = Sap
|
||||
dv-names-diona-last-75 = Nectar
|
||||
dv-names-diona-last-76 = Amber
|
||||
dv-names-diona-last-77 = Obsidian
|
||||
dv-names-diona-last-78 = Geode
|
||||
dv-names-diona-last-79 = Threshold
|
||||
dv-names-diona-last-80 = Pollen
|
||||
dv-names-diona-last-81 = Thorns
|
||||
.plural = true
|
||||
dv-names-diona-last-82 = Vine
|
||||
dv-names-diona-last-83 = Friends
|
||||
.plural = true
|
||||
dv-names-diona-last-84 = Partners
|
||||
.plural = true
|
||||
dv-names-diona-last-85 = Acceptance
|
||||
dv-names-diona-last-86 = Kindness
|
||||
dv-names-diona-last-87 = Belonging
|
||||
dv-names-diona-last-88 = Shelter
|
||||
dv-names-diona-last-89 = Warmth
|
||||
dv-names-diona-last-90 = Trust
|
||||
dv-names-diona-last-91 = Gentleness
|
||||
dv-names-diona-last-92 = Gratitude
|
||||
dv-names-diona-last-93 = Comfort
|
||||
dv-names-diona-last-94 = Stillness
|
||||
dv-names-diona-last-95 = Distance
|
||||
dv-names-diona-last-96 = Passage
|
||||
dv-names-diona-last-97 = Reflection
|
||||
dv-names-diona-last-98 = Drift
|
||||
dv-names-diona-last-99 = Stasis
|
||||
dv-names-diona-last-100 = Pause
|
||||
dv-names-diona-last-101 = Rejection
|
||||
dv-names-diona-last-102 = Neglect
|
||||
dv-names-diona-last-103 = Scorn
|
||||
dv-names-diona-last-104 = Isolation
|
||||
dv-names-diona-last-105 = Burden
|
||||
dv-names-diona-last-106 = Dread
|
||||
dv-names-diona-last-107 = Grief
|
||||
dv-names-diona-last-108 = Wilt
|
||||
dv-names-diona-last-109 = Loss
|
||||
dv-names-diona-last-110 = Mystery
|
||||
dv-names-diona-last-111 = Paradox
|
||||
dv-names-diona-last-112 = Possibility
|
||||
dv-names-diona-last-113 = Memory
|
||||
dv-names-diona-last-114 = Origin
|
||||
dv-names-diona-last-115 = Absence
|
||||
dv-names-diona-last-116 = Presence
|
||||
@@ -1,3 +1,6 @@
|
||||
chat-radio-message-audible-emote-wrap = [color={$color}]{$channel} {$name} {$message}[/color]
|
||||
chat-radio-message-audible-possessive-emote-wrap = [color={$color}]{$channel} {$name}'s {$message}[/color]
|
||||
|
||||
chat-radio-justice = Justice
|
||||
chat-radio-prison = Prison
|
||||
chat-radio-admeme-1 = Channel 1
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user