Merge sunrise build (#2)

* commit
This commit is contained in:
Vigers Ray 2024-05-31 02:16:18 +03:00 committed by GitHub
parent 1841df4ce0
commit 5775d4cdef
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9367 changed files with 105740 additions and 12077 deletions

View file

@ -228,7 +228,7 @@ public sealed partial class ContentAudioSystem
file,
Filter.Local(),
false,
_roundEndSoundEffectParams.WithVolume(_roundEndSoundEffectParams.Volume + SharedAudioSystem.GainToVolume(_configManager.GetCVar(CCVars.LobbyMusicVolume)))
_roundEndSoundEffectParams.WithVolume(10f) // Sunrise-Edit
)?.Entity;
}

View file

@ -29,7 +29,8 @@ public sealed partial class ContentAudioSystem : SharedContentAudioSystem
public const float AmbientMusicMultiplier = 3f;
public const float LobbyMultiplier = 3f;
public const float InterfaceMultiplier = 2f;
public const float TtsMultiplier = 3f; // Sunrise-TTS
public override void Initialize()
{
base.Initialize();

View file

@ -28,6 +28,13 @@ public sealed partial class ChangelogTab : Control
IoCManager.InjectDependencies(this);
}
// Sunrise-Start
public void CleanChangelog()
{
ChangelogBody.Children.Clear();
}
// Sunrise-End
public void PopulateChangelog(ChangelogManager.Changelog changelog)
{
var byDay = changelog.Entries

View file

@ -22,6 +22,8 @@
<ProjectReference Include="..\RobustToolbox\Robust.Shared\Robust.Shared.csproj" />
<ProjectReference Include="..\RobustToolbox\Robust.Client\Robust.Client.csproj" />
<ProjectReference Include="..\Content.Shared\Content.Shared.csproj" />
<ProjectReference Include="..\Sunrise\Content.Sunrise.Interfaces.Shared\Content.Sunrise.Interfaces.Shared.csproj" />
<ProjectReference Include="..\Sunrise\Content.Sunrise.Interfaces.Client\Content.Sunrise.Interfaces.Client.csproj" />
</ItemGroup>
<ItemGroup>
<Folder Include="Spawners\" />

View file

@ -2,7 +2,7 @@
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
xmlns:ui="clr-namespace:Content.Client.CrewManifest.UI"
Title="{Loc 'crew-manifest-window-title'}"
SetSize="450 750">
SetSize="500 800"> <!-- Sunrise-edit -->
<BoxContainer Orientation="Vertical" VerticalExpand="True" HorizontalExpand="True">
<controls:StripeBack Name="StationNameContainer">
<PanelContainer>

View file

@ -118,6 +118,7 @@ namespace Content.Client.Entry
_prototypeManager.RegisterIgnore("wireLayout");
_prototypeManager.RegisterIgnore("alertLevels");
_prototypeManager.RegisterIgnore("nukeopsRole");
_prototypeManager.RegisterIgnore("stationGoal"); // Sunrise-StationGoal
_prototypeManager.RegisterIgnore("ghostRoleRaffleDecider");
_componentFactory.GenerateNetIds();

View file

@ -26,7 +26,10 @@ namespace Content.Client.GameTicking.Managers
[ViewVariables] public bool AreWeReady { get; private set; }
[ViewVariables] public bool IsGameStarted { get; private set; }
[ViewVariables] public string? RestartSound { get; private set; }
[ViewVariables] public string? LobbyBackground { get; private set; }
// Sunrise-Start
[ViewVariables] public string? LobbyParalax { get; private set; }
[ViewVariables] public LobbyImage? LobbyImage { get; private set; }
// Sunrise-End
[ViewVariables] public bool DisallowedLateJoin { get; private set; }
[ViewVariables] public string? ServerInfoBlob { get; private set; }
[ViewVariables] public TimeSpan StartTime { get; private set; }
@ -118,7 +121,10 @@ namespace Content.Client.GameTicking.Managers
RoundStartTimeSpan = message.RoundStartTimeSpan;
IsGameStarted = message.IsRoundStarted;
AreWeReady = message.YouAreReady;
LobbyBackground = message.LobbyBackground;
// Sunrise-Start
LobbyParalax = message.LobbyParalax;
LobbyImage = message.LobbyImage;
// Sunrise-End
Paused = message.Paused;
LobbyStatusUpdated?.Invoke();

View file

@ -2,6 +2,7 @@ using System.Linq;
using Content.Shared.Humanoid;
using Content.Shared.Humanoid.Markings;
using Content.Shared.Humanoid.Prototypes;
using Content.Sunrise.Interfaces.Shared;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
@ -18,6 +19,7 @@ public sealed partial class MarkingPicker : Control
{
[Dependency] private readonly MarkingManager _markingManager = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
private ISharedSponsorsManager? _sponsorsManager; // Sunrise-Sponsors
public Action<MarkingSet>? OnMarkingAdded;
public Action<MarkingSet>? OnMarkingRemoved;
@ -123,6 +125,7 @@ public sealed partial class MarkingPicker : Control
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
IoCManager.Instance!.TryResolveType(out _sponsorsManager); // Sunrise-Sponsors
CMarkingCategoryButton.OnItemSelected += OnCategoryChange;
CMarkingsUnused.OnItemSelected += item =>
@ -224,6 +227,10 @@ public sealed partial class MarkingPicker : Control
var item = CMarkingsUnused.AddItem($"{GetMarkingName(marking)}", marking.Sprites[0].Frame0());
item.Metadata = marking;
// Sunrise-Sponsors-Start
if (marking.SponsorOnly && _sponsorsManager != null)
item.Disabled = !_sponsorsManager.GetClientPrototypes().Contains(marking.ID);
// Sunrise-Sponsors-End
}
CMarkingPoints.Visible = _currentMarkings.PointsLeft(_selectedMarkingCategory) != -1;

View file

@ -1,5 +1,6 @@
using System.Linq;
using Content.Shared.Humanoid.Markings;
using Content.Sunrise.Interfaces.Shared;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
@ -11,6 +12,7 @@ namespace Content.Client.Humanoid;
public sealed partial class SingleMarkingPicker : BoxContainer
{
[Dependency] private readonly MarkingManager _markingManager = default!;
private ISharedSponsorsManager? _sponsorsManager; // Sunrise-Sponsors
/// <summary>
/// What happens if a marking is selected.
@ -122,6 +124,7 @@ public sealed partial class SingleMarkingPicker : BoxContainer
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
IoCManager.Instance!.TryResolveType(out _sponsorsManager); // Sunrise-Sponsors
MarkingList.OnItemSelected += SelectMarking;
AddButton.OnPressed += _ =>
@ -190,6 +193,10 @@ public sealed partial class SingleMarkingPicker : BoxContainer
{
var item = MarkingList.AddItem(Loc.GetString($"marking-{id}"), marking.Sprites[0].Frame0());
item.Metadata = marking.ID;
// Sunrise-Sponsors-Start
if (marking.SponsorOnly && _sponsorsManager != null)
item.Disabled = !_sponsorsManager.GetClientPrototypes().Contains(marking.ID);
// Sunrise-Sponsors-End
if (_markings[Slot].MarkingId == id)
{

View file

@ -1,4 +1,5 @@
using Content.Client.Changelog;
using Content.Client.Credits;
using Content.Client.UserInterface.Systems.EscapeMenu;
using Content.Client.UserInterface.Systems.Guidebook;
using Content.Shared.CCVar;
@ -35,6 +36,12 @@ namespace Content.Client.Info
AddInfoButton("server-info-wiki-button", CCVars.InfoLinksWiki);
AddInfoButton("server-info-forum-button", CCVars.InfoLinksForum);
// Sunrise-Start
var creditsButton = new Button {Text = Loc.GetString("server-info-credits-button")};
creditsButton.OnPressed += args => new CreditsWindow().Open();
buttons.AddChild(creditsButton);
// Sunrise-End
var guidebookController = UserInterfaceManager.GetUIController<GuidebookUIController>();
var guidebookButton = new Button() { Text = Loc.GetString("server-info-guidebook-button") };
guidebookButton.OnPressed += _ =>

View file

@ -1,4 +1,4 @@
using Content.Client.Administration.Managers;
using Content.Client.Administration.Managers;
using Content.Client.Changelog;
using Content.Client.Chat.Managers;
using Content.Client.Clickable;

View file

@ -41,7 +41,7 @@ namespace Content.Client.LateJoin
public LateJoinGui()
{
MinSize = SetSize = new Vector2(360, 560);
MinSize = SetSize = new Vector2(450, 560);
IoCManager.InjectDependencies(this);
_sprites = _entitySystem.GetEntitySystem<SpriteSystem>();
_crewManifest = _entitySystem.GetEntitySystem<CrewManifestSystem>();

View file

@ -1,5 +1,6 @@
using System.Linq;
using Content.Shared.Preferences;
using Content.Sunrise.Interfaces.Shared;
using Robust.Client;
using Robust.Client.Player;
using Robust.Shared.Network;
@ -12,11 +13,12 @@ namespace Content.Client.Lobby
/// connection.
/// Stores preferences on the server through <see cref="SelectCharacter" /> and <see cref="UpdateCharacter" />.
/// </summary>
public sealed class ClientPreferencesManager : IClientPreferencesManager
public partial class ClientPreferencesManager : IClientPreferencesManager
{
[Dependency] private readonly IClientNetManager _netManager = default!;
[Dependency] private readonly IBaseClient _baseClient = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
private ISharedSponsorsManager? _sponsorsManager; // Sunrise-Sponsors
public event Action? OnServerDataLoaded;
@ -25,6 +27,7 @@ namespace Content.Client.Lobby
public void Initialize()
{
IoCManager.Instance!.TryResolveType(out _sponsorsManager); // Sunrise-Sponsors
_netManager.RegisterNetMessage<MsgPreferencesAndSettings>(HandlePreferencesAndSettings);
_netManager.RegisterNetMessage<MsgUpdateCharacter>();
_netManager.RegisterNetMessage<MsgSelectCharacter>();
@ -60,7 +63,10 @@ namespace Content.Client.Lobby
public void UpdateCharacter(ICharacterProfile profile, int slot)
{
var collection = IoCManager.Instance!;
profile.EnsureValid(_playerManager.LocalSession!, collection);
// Sunrise-Sponsors-Start
var sponsorPrototypes = _sponsorsManager?.GetClientPrototypes().ToArray() ?? [];
profile.EnsureValid(_playerManager.LocalSession!, collection, sponsorPrototypes);
// Sunrise-Sponsors-End
var characters = new Dictionary<int, ICharacterProfile>(Preferences.Characters) {[slot] = profile};
Preferences = new PlayerPreferences(characters, Preferences.SelectedCharacterIndex, Preferences.AdminOOCColor);
var msg = new MsgUpdateCharacter

View file

@ -11,6 +11,13 @@ using Robust.Client.ResourceManagement;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
using Content.Client.Changelog;
using Content.Client.Parallax.Managers;
using Robust.Shared.ContentPack;
using Robust.Shared.Serialization.Manager;
using Robust.Shared.Serialization.Markdown;
using Robust.Shared.Serialization.Markdown.Mapping;
namespace Content.Client.Lobby
@ -24,6 +31,9 @@ namespace Content.Client.Lobby
[Dependency] private readonly IUserInterfaceManager _userInterfaceManager = default!;
[Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly IVoteManager _voteManager = default!;
[Dependency] private readonly IParallaxManager _parallaxManager = default!;
[Dependency] private readonly ISerializationManager _serialization = default!;
[Dependency] private readonly IResourceManager _resource = default!;
private ClientGameTicker _gameTicker = default!;
private ContentAudioSystem _contentAudioSystem = default!;
@ -49,9 +59,22 @@ namespace Content.Client.Lobby
_voteManager.SetPopupContainer(Lobby.VoteContainer);
LayoutContainer.SetAnchorPreset(Lobby, LayoutContainer.LayoutPreset.Wide);
Lobby.ServerName.Text = _baseClient.GameInfo?.ServerName; //The eye of refactor gazes upon you...
// Sunrise-start
//Lobby.ServerName.Text = _baseClient.GameInfo?.ServerName; //The eye of refactor gazes upon you...
UpdateLobbyUi();
Lobby!.LocalChangelogBody.CleanChangelog();
var sunriseChangelog = new ResPath("/Changelog/ChangelogSunrise.yml");
var yamlData = _resource.ContentFileReadYaml(sunriseChangelog);
var node = yamlData.Documents[0].RootNode.ToDataNodeCast<MappingDataNode>();
var changelog = _serialization.Read<ChangelogManager.Changelog>(node, notNullableOverride: true);
Lobby!.LocalChangelogBody.PopulateChangelog(changelog);
// Sunrise-end
Lobby.CharacterPreview.CharacterSetupButton.OnPressed += OnSetupPressed;
Lobby.ReadyButton.OnPressed += OnReadyPressed;
Lobby.ReadyButton.OnToggled += OnReadyToggled;
@ -147,7 +170,10 @@ namespace Content.Client.Lobby
private void LobbyStatusUpdated()
{
UpdateLobbyBackground();
// Sunrise-Start
UpdateLobbyaralax();
UpdateLobbyImage();
// Sunrise-End
UpdateLobbyUi();
}
@ -210,19 +236,30 @@ namespace Content.Client.Lobby
}
}
private void UpdateLobbyBackground()
// Sunrise-start
private void UpdateLobbyaralax()
{
if (_gameTicker.LobbyBackground != null)
if (_gameTicker.LobbyParalax != null)
{
Lobby!.Background.Texture = _resourceCache.GetResource<TextureResource>(_gameTicker.LobbyBackground );
_parallaxManager.LoadParallaxByName(_gameTicker.LobbyParalax);
Lobby!.LobbyParalax = _gameTicker.LobbyParalax;
}
else
{
Lobby!.Background.Texture = null;
Lobby!.LobbyParalax = "FastSpace";
}
}
private void UpdateLobbyImage()
{
if (_gameTicker.LobbyImage == null)
return;
Lobby!.LobbyImage.SetFromSpriteSpecifier(new SpriteSpecifier.Rsi(new ResPath(_gameTicker.LobbyImage.Path), _gameTicker.LobbyImage.State));
Lobby!.LobbyImage.DisplayRect.TextureScale = _gameTicker.LobbyImage.Scale;
}
// Sunrise-end
private void SetReady(bool newReady)
{
if (_gameTicker.IsGameStarted)

View file

@ -27,7 +27,7 @@ using Robust.Shared.Utility;
namespace Content.Client.Lobby;
public sealed class LobbyUIController : UIController, IOnStateEntered<LobbyState>, IOnStateExited<LobbyState>
public sealed partial class LobbyUIController : UIController, IOnStateEntered<LobbyState>, IOnStateExited<LobbyState>
{
[Dependency] private readonly IClientPreferencesManager _preferencesManager = default!;
[Dependency] private readonly IConfigurationManager _configurationManager = default!;

View file

@ -14,6 +14,12 @@
Text="{Loc 'character-setup-gui-character-setup-stats-button'}"
StyleClasses="ButtonBig"
HorizontalAlignment="Right" />
<!-- Sunrise-Sponsor-Start -->
<Button Name="SponsorButton"
Text="{Loc 'character-setup-gui-character-setup-sponsor-button'}"
Visible="False"
StyleClasses="ButtonBig" />
<!-- Sunrise-Sponsor-End -->
<Button Name="RulesButton"
Text="{Loc 'character-setup-gui-character-setup-rules-button'}"
StyleClasses="ButtonBig"/>

View file

@ -89,6 +89,14 @@
<Control HorizontalExpand="True"/>
<OptionButton Name="SpawnPriorityButton" HorizontalAlignment="Right" />
</BoxContainer>
<!-- Sunrise-TTS-Start -->
<BoxContainer HorizontalExpand="True" Visible="False" Name="TTSContainer">
<Label Text="{Loc 'humanoid-profile-editor-voice-label'}" />
<Control HorizontalExpand="True"/>
<OptionButton Name="VoiceButton" HorizontalAlignment="Right" />
<Button Name="VoicePlayButton" Text="{Loc 'humanoid-profile-editor-voice-play'}" MaxWidth="80" />
</BoxContainer>
<!-- Sunrise-TTS-End -->
</BoxContainer>
<!-- Skin -->
<BoxContainer Margin="10" HorizontalExpand="True" Orientation="Vertical">

View file

@ -8,6 +8,7 @@ using Content.Client.Lobby.UI.Roles;
using Content.Client.Message;
using Content.Client.Players.PlayTimeTracking;
using Content.Client.UserInterface.Systems.Guidebook;
using Content.Shared._Sunrise.SunriseCCVars;
using Content.Shared.CCVar;
using Content.Shared.Clothing;
using Content.Shared.GameTicking;
@ -192,6 +193,18 @@ namespace Content.Client.Lobby.UI
#endregion Gender
// Sunrise-TTS-Start
#region Voice
if (configurationManager.GetCVar(SunriseCCVars.TTSEnabled))
{
TTSContainer.Visible = true;
InitializeVoice();
}
#endregion
// Sunrise-TTS-End
RefreshSpecies();
SpeciesButton.OnItemSelected += args =>
@ -662,6 +675,7 @@ namespace Content.Client.Lobby.UI
UpdateEyePickers();
UpdateSaveButton();
UpdateMarkings();
UpdateTTSVoicesControls(); // Sunrise-TTS
UpdateHairPickers();
UpdateCMarkingsHair();
UpdateCMarkingsFacialHair();
@ -1015,6 +1029,15 @@ namespace Content.Client.Lobby.UI
Profile = Profile.WithCharacterAppearance(Profile.Appearance.WithSkinColor(color));
break;
}
// Sunrise-start
case HumanoidSkinColor.None:
{
Skin.Visible = false;
RgbSkinColorContainer.Visible = false;
_rgbSkinColorSelector.Color = Color.Transparent;
break;
}
// Sunrise-end
}
SetDirty();
@ -1058,6 +1081,7 @@ namespace Content.Client.Lobby.UI
}
UpdateGenderControls();
UpdateTTSVoicesControls(); // Sunrise-TTS
Markings.SetSex(newSex);
ReloadPreview();
SetDirty();
@ -1070,6 +1094,14 @@ namespace Content.Client.Lobby.UI
SetDirty();
}
// Sunrise-TTS-Start
private void SetVoice(string newVoice)
{
Profile = Profile?.WithVoice(newVoice);
IsDirty = true;
}
// Sunrise-TTS-End
private void SetSpecies(string newSpecies)
{
Profile = Profile?.WithSpecies(newSpecies);
@ -1231,6 +1263,15 @@ namespace Content.Client.Lobby.UI
break;
}
// Sunrise-start
case HumanoidSkinColor.None:
{
Skin.Visible = false;
RgbSkinColorContainer.Visible = false;
_rgbSkinColorSelector.Color = Color.Transparent;
break;
}
// Sunrise-end
}
}

View file

@ -2,6 +2,7 @@ using System.Linq;
using Content.Shared.Clothing;
using Content.Shared.Preferences;
using Content.Shared.Preferences.Loadouts;
using Content.Sunrise.Interfaces.Shared;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
@ -67,7 +68,15 @@ public sealed partial class LoadoutGroupContainer : BoxContainer
var selected = loadout.SelectedLoadouts[_groupProto.ID];
foreach (var loadoutProto in _groupProto.Loadouts)
// Sunrise-Loadouts-Start
var groupLoadouts = _groupProto.Loadouts;
if (collection.TryResolveType<ISharedLoadoutsManager>(out var loadoutsManager) && _groupProto.ID == "Inventory")
{
groupLoadouts = loadoutsManager.GetClientPrototypes().Select(id => (ProtoId<LoadoutPrototype>)id).ToList();
}
// Sunrise-Loadouts-End
foreach (var loadoutProto in groupLoadouts) // Sunrise-Loadouts
{
if (!protoMan.TryIndex(loadoutProto, out var loadProto))
continue;

View file

@ -8,10 +8,18 @@
xmlns:style="clr-namespace:Content.Client.Stylesheets"
xmlns:lobbyUi="clr-namespace:Content.Client.Lobby.UI"
xmlns:info="clr-namespace:Content.Client.Info"
xmlns:widgets="clr-namespace:Content.Client.UserInterface.Systems.Chat.Widgets">
xmlns:widgets="clr-namespace:Content.Client.UserInterface.Systems.Chat.Widgets"
xmlns:changelog="clr-namespace:Content.Client.Changelog">
<!-- Sunrise-start -->
<!-- Background -->
<TextureRect Access="Public" VerticalExpand="True" HorizontalExpand="True" Name="Background"
Stretch="KeepAspectCovered" />
<!--<TextureRect Access="Public" VerticalExpand="True" HorizontalExpand="True" Name="Background"
Stretch="KeepAspectCovered" /> -->
<BoxContainer Name="LogoContainer" VerticalExpand="True" HorizontalExpand="True" VerticalAlignment="Center"
HorizontalAlignment="Center" Align="Center">
<AnimatedTextureRect Name="LobbyImage" VerticalAlignment="Center" HorizontalAlignment="Center" Access="Public">
</AnimatedTextureRect>
</BoxContainer>
<!-- Sunrise-end -->
<BoxContainer Name="MainContainer" VerticalExpand="True" HorizontalExpand="True" Orientation="Horizontal"
Margin="10 10 10 10" SeparationOverride="2">
<SplitContainer State="Auto" HorizontalExpand="True">
@ -50,11 +58,26 @@
<!-- Vertical Padding-->
<Control VerticalExpand="True" />
<!-- Left Bot Panel -->
<BoxContainer Orientation="Horizontal" HorizontalAlignment="Left" VerticalAlignment="Bottom">
<info:DevInfoBanner Name="DevInfoBanner" VerticalExpand="false" Margin="3 3 3 3" />
<PanelContainer StyleClasses="AngleRect">
<!-- Sunrise-start -->
<BoxContainer Orientation="Vertical" HorizontalAlignment="Left" VerticalAlignment="Bottom" MaxWidth="620">
<PanelContainer StyleClasses="AngleRect" HorizontalAlignment="Left" Name="LocalChangelog" VerticalAlignment="Top" Margin="0 10">
<BoxContainer Orientation="Vertical" SetSize="550 300" VerticalExpand="True">
<controls:StripeBack>
<BoxContainer Orientation="Horizontal">
<Label HorizontalExpand="True" Text="{Loc 'changelog-sunrise-window-title'}" VAlign="Center"
StyleClasses="LabelHeading" Align="Center"/>
</BoxContainer>
</controls:StripeBack>
<ScrollContainer VerticalExpand="True" HScrollEnabled="False" MinHeight="50">
<changelog:ChangelogTab Name="LocalChangelogBody" Access="Public" />
</ScrollContainer>
</BoxContainer>
</PanelContainer>
<PanelContainer Name="LobbySongPanel" StyleClasses="AngleRect" >
<RichTextLabel Name="LobbySong" Access="Public" HorizontalAlignment="Center" />
</PanelContainer>
<!-- Sunrise-end -->
</BoxContainer>
</Control>
<!-- Character setup state -->
@ -68,10 +91,12 @@
<!-- Top row -->
<BoxContainer Orientation="Horizontal" MinSize="0 40" Name="HeaderContainer" Access="Public"
SeparationOverride="4">
<Label Margin="8 0 0 0" StyleClasses="LabelHeadingBigger" VAlign="Center"
Text="{Loc 'ui-lobby-title'}" />
<!-- Sunrise-start -->
<!-- <Label Margin="8 0 0 0" StyleClasses="LabelHeadingBigger" VAlign="Center"
Text="{Loc 'ui-lobby-title'}" /> -->
<Label Name="ServerName" Access="Public" StyleClasses="LabelHeadingBigger" VAlign="Center"
HorizontalExpand="True" HorizontalAlignment="Center" />
HorizontalExpand="True" HorizontalAlignment="Center" Text="Добро пожаловать в ЫЫ14" />
<!-- Sunrise-end -->
</BoxContainer>
<!-- Gold line -->
<controls:HLine Color="{x:Static style:StyleNano.NanoGold}" Thickness="2" />

View file

@ -4,6 +4,13 @@ using Robust.Client.AutoGenerated;
using Robust.Client.Console;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Random;
using Robust.Shared.Timing;
using System.Numerics;
using Content.Client.Parallax.Managers;
using Content.Client.Resources;
using Robust.Client.Graphics;
using Robust.Client.ResourceManagement;
namespace Content.Client.Lobby.UI
{
@ -11,18 +18,49 @@ namespace Content.Client.Lobby.UI
public sealed partial class LobbyGui : UIScreen
{
[Dependency] private readonly IClientConsoleHost _consoleHost = default!;
[Dependency] private readonly IUserInterfaceManager _userInterfaceManager = default!;
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly IParallaxManager _parallaxManager = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly IResourceCache _resourceCache = default!;
public string LobbyParalax = "FastSpace"; // Sunrise-edit
[ViewVariables(VVAccess.ReadWrite)] public Vector2 Offset { get; set; }// Sunrise-edit
public LobbyGui()
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
SetAnchorPreset(MainContainer, LayoutPreset.Wide);
SetAnchorPreset(Background, LayoutPreset.Wide);
SetAnchorPreset(LogoContainer, LayoutPreset.Wide); // Sunrise-edit
LobbySong.SetMarkup(Loc.GetString("lobby-state-song-no-song-text"));
LeaveButton.OnPressed += _ => _consoleHost.ExecuteCommand("disconnect");
OptionsButton.OnPressed += _ => UserInterfaceManager.GetUIController<OptionsUIController>().ToggleWindow();
// Sunrise-start
Offset = new Vector2(_random.Next(0, 1000), _random.Next(0, 1000));
_parallaxManager.LoadParallaxByName(LobbyParalax);
RectClipContent = true;
var panelTex = _resourceCache.GetTexture("/Textures/Interface/Nano/button.svg.96dpi.png");
var back = new StyleBoxTexture
{
Texture = panelTex,
Modulate = new Color(37, 37, 42).WithAlpha(0.5f)
};
back.SetPatchMargin(StyleBox.Margin.All, 10);
LeftSideTop.PanelOverride = back;
RightSide.PanelOverride = back;
LocalChangelog.PanelOverride = back;
LobbySongPanel.PanelOverride = back;
// Sunrise-end
}
public void SwitchState(LobbyGuiState state)
@ -53,6 +91,50 @@ namespace Content.Client.Lobby.UI
}
}
// Sunrise-start
protected override void Draw(DrawingHandleScreen handle)
{
foreach (var layer in _parallaxManager.GetParallaxLayers(LobbyParalax))
{
var tex = layer.Texture;
var texSize = new Vector2i(
(tex.Size.X * (int)Size.X * 1) / 1920,
(tex.Size.Y * (int)Size.X * 1) / 1920
);
var ourSize = PixelSize;
var currentTime = (float) _timing.RealTime.TotalSeconds;
var offset = Offset + new Vector2(currentTime * 100f, currentTime * 0f);
if (layer.Config.Tiled)
{
// Multiply offset by slowness to match normal parallax
var scaledOffset = (offset * layer.Config.Slowness).Floored();
// Then modulo the scaled offset by the size to prevent drawing a bunch of offscreen tiles for really small images.
scaledOffset.X %= texSize.X;
scaledOffset.Y %= texSize.Y;
// Note: scaledOffset must never be below 0 or there will be visual issues.
// It could be allowed to be >= texSize on a given axis but that would be wasteful.
for (var x = -scaledOffset.X; x < ourSize.X; x += texSize.X)
{
for (var y = -scaledOffset.Y; y < ourSize.Y; y += texSize.Y)
{
handle.DrawTextureRect(tex, UIBox2.FromDimensions(new Vector2(x, y), texSize));
}
}
}
else
{
var origin = ((ourSize - texSize) / 2) + layer.Config.ControlHomePosition;
handle.DrawTextureRect(tex, UIBox2.FromDimensions(origin, texSize));
}
}
}
// Sunrise-end
public enum LobbyGuiState : byte
{
/// <summary>

View file

@ -1,8 +1,8 @@
<DefaultWindow xmlns="https://spacestation14.io"
xmlns:gfx="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
Title="{Loc 'nuke-user-interface-title'}"
MinSize="256 256"
SetSize="256 256">
MinSize="280 256"
SetSize="280 256">
<BoxContainer Orientation="Vertical"
HorizontalExpand="True"
VerticalExpand="True">

View file

@ -87,6 +87,47 @@
<Label Name="InterfaceVolumeLabel" MinSize="48 0" Align="Right" />
<Control MinSize="4 0"/>
</BoxContainer>
<!-- Sunrise-TTS-Start -->
<BoxContainer Orientation="Horizontal" Margin="5 0 0 0">
<Label Text="{Loc 'ui-options-tts-volume'}" HorizontalExpand="True" />
<Control MinSize="8 0" />
<Slider Name="TtsVolumeSlider"
MinValue="0"
MaxValue="200"
HorizontalExpand="True"
MinSize="80 0"
Rounded="True" />
<Control MinSize="8 0" />
<Label Name="TtsVolumeLabel" MinSize="48 0" Align="Right" />
<Control MinSize="4 0"/>
</BoxContainer>
<BoxContainer Orientation="Horizontal" Margin="5 0 0 0">
<Label Text="{Loc 'ui-options-tts-radio-volume'}" HorizontalExpand="True" />
<Control MinSize="8 0" />
<Slider Name="TtsRadioVolumeSlider"
MinValue="0"
MaxValue="200"
HorizontalExpand="True"
MinSize="80 0"
Rounded="True" />
<Control MinSize="8 0" />
<Label Name="TtsRadioVolumeLabel" MinSize="48 0" Align="Right" />
<Control MinSize="4 0"/>
</BoxContainer>
<BoxContainer Orientation="Horizontal" Margin="5 0 0 0">
<Label Text="{Loc 'ui-options-tts-announce-volume'}" HorizontalExpand="True" />
<Control MinSize="8 0" />
<Slider Name="TtsAnnounceVolumeSlider"
MinValue="0"
MaxValue="200"
HorizontalExpand="True"
MinSize="80 0"
Rounded="True" />
<Control MinSize="8 0" />
<Label Name="TtsAnnounceVolumeLabel" MinSize="48 0" Align="Right" />
<Control MinSize="4 0"/>
</BoxContainer>
<!-- Sunrise-TTS-End -->
<BoxContainer Orientation="Horizontal" Margin="5 0 0 0">
<Label Text="{Loc 'ui-options-ambience-max-sounds'}" HorizontalExpand="True" />
<Control MinSize="8 0" />

View file

@ -1,4 +1,5 @@
using Content.Client.Audio;
using Content.Shared._Sunrise.SunriseCCVars;
using Content.Shared.CCVar;
using Robust.Client.Audio;
using Robust.Client.AutoGenerated;
@ -37,6 +38,9 @@ namespace Content.Client.Options.UI.Tabs
AmbienceSoundsSlider.OnValueChanged += OnAmbienceSoundsSliderChanged;
LobbyVolumeSlider.OnValueChanged += OnLobbyVolumeSliderChanged;
InterfaceVolumeSlider.OnValueChanged += OnInterfaceVolumeSliderChanged;
TtsVolumeSlider.OnValueChanged += OnTtsVolumeSliderChanged; // Sunrise-TTS
TtsRadioVolumeSlider.OnValueChanged += OnTtsRadioVolumeSliderChanged; // Sunrise-TTS
TtsAnnounceVolumeSlider.OnValueChanged += OnTtsAnnounceVolumeSliderChanged; // Sunrise-TTS
LobbyMusicCheckBox.OnToggled += OnLobbyMusicCheckToggled;
RestartSoundsCheckBox.OnToggled += OnRestartSoundsCheckToggled;
EventMusicCheckBox.OnToggled += OnEventMusicCheckToggled;
@ -58,6 +62,9 @@ namespace Content.Client.Options.UI.Tabs
AmbienceVolumeSlider.OnValueChanged -= OnAmbienceVolumeSliderChanged;
LobbyVolumeSlider.OnValueChanged -= OnLobbyVolumeSliderChanged;
InterfaceVolumeSlider.OnValueChanged -= OnInterfaceVolumeSliderChanged;
TtsVolumeSlider.OnValueChanged -= OnTtsVolumeSliderChanged; // Sunrise-TTS
TtsRadioVolumeSlider.OnValueChanged -= OnTtsRadioVolumeSliderChanged; // Sunrise-TTS
TtsAnnounceVolumeSlider.OnValueChanged -= OnTtsAnnounceVolumeSliderChanged; // Sunrise-TTS
base.Dispose(disposing);
}
@ -97,6 +104,23 @@ namespace Content.Client.Options.UI.Tabs
UpdateChanges();
}
// Sunrise-start
private void OnTtsVolumeSliderChanged(Range obj)
{
UpdateChanges();
}
private void OnTtsAnnounceVolumeSliderChanged(Range obj)
{
UpdateChanges();
}
private void OnTtsRadioVolumeSliderChanged(Range obj)
{
UpdateChanges();
}
// Sunrise-end
private void OnLobbyMusicCheckToggled(BaseButton.ButtonEventArgs args)
{
UpdateChanges();
@ -125,6 +149,9 @@ namespace Content.Client.Options.UI.Tabs
_cfg.SetCVar(CCVars.AmbientMusicVolume, AmbientMusicVolumeSlider.Value / 100f * ContentAudioSystem.AmbientMusicMultiplier);
_cfg.SetCVar(CCVars.LobbyMusicVolume, LobbyVolumeSlider.Value / 100f * ContentAudioSystem.LobbyMultiplier);
_cfg.SetCVar(CCVars.InterfaceVolume, InterfaceVolumeSlider.Value / 100f * ContentAudioSystem.InterfaceMultiplier);
_cfg.SetCVar(SunriseCCVars.TTSVolume, TtsVolumeSlider.Value / 100f); // Sunrise-TTS
_cfg.SetCVar(SunriseCCVars.TTSRadioVolume, TtsRadioVolumeSlider.Value / 100f); // Sunrise-TTS
_cfg.SetCVar(SunriseCCVars.TTSAnnounceVolume, TtsAnnounceVolumeSlider.Value / 100f); // Sunrise-TTS
_cfg.SetCVar(CCVars.MaxAmbientSources, (int)AmbienceSoundsSlider.Value);
@ -149,6 +176,9 @@ namespace Content.Client.Options.UI.Tabs
AmbientMusicVolumeSlider.Value = _cfg.GetCVar(CCVars.AmbientMusicVolume) * 100f / ContentAudioSystem.AmbientMusicMultiplier;
LobbyVolumeSlider.Value = _cfg.GetCVar(CCVars.LobbyMusicVolume) * 100f / ContentAudioSystem.LobbyMultiplier;
InterfaceVolumeSlider.Value = _cfg.GetCVar(CCVars.InterfaceVolume) * 100f / ContentAudioSystem.InterfaceMultiplier;
TtsVolumeSlider.Value = _cfg.GetCVar(SunriseCCVars.TTSVolume) * 100f; // Sunrise-TTS
TtsRadioVolumeSlider.Value = _cfg.GetCVar(SunriseCCVars.TTSRadioVolume) * 100f; // Sunrise-TTS
TtsAnnounceVolumeSlider.Value = _cfg.GetCVar(SunriseCCVars.TTSAnnounceVolume) * 100f; // Sunrise-TTS
AmbienceSoundsSlider.Value = _cfg.GetCVar(CCVars.MaxAmbientSources);
@ -174,6 +204,12 @@ namespace Content.Client.Options.UI.Tabs
Math.Abs(LobbyVolumeSlider.Value - _cfg.GetCVar(CCVars.LobbyMusicVolume) * 100f / ContentAudioSystem.LobbyMultiplier) < 0.01f;
var isInterfaceVolumeSame =
Math.Abs(InterfaceVolumeSlider.Value - _cfg.GetCVar(CCVars.InterfaceVolume) * 100f / ContentAudioSystem.InterfaceMultiplier) < 0.01f;
var isTtsVolumeSame =
Math.Abs(TtsVolumeSlider.Value - _cfg.GetCVar(SunriseCCVars.TTSVolume) * 100f) < 0.01f; // Sunrise-TTS
var isTtsRadioVolumeSame =
Math.Abs(TtsRadioVolumeSlider.Value - _cfg.GetCVar(SunriseCCVars.TTSRadioVolume) * 100f) < 0.01f; // Sunrise-TTS
var isTtsAnnounceVolumeSame =
Math.Abs(TtsAnnounceVolumeSlider.Value - _cfg.GetCVar(SunriseCCVars.TTSAnnounceVolume) * 100f) < 0.01f; // Sunrise-TTS
var isAmbientSoundsSame = (int)AmbienceSoundsSlider.Value == _cfg.GetCVar(CCVars.MaxAmbientSources);
var isLobbySame = LobbyMusicCheckBox.Pressed == _cfg.GetCVar(CCVars.LobbyMusicEnabled);
@ -182,6 +218,7 @@ namespace Content.Client.Options.UI.Tabs
var isAdminSoundsSame = AdminSoundsCheckBox.Pressed == _cfg.GetCVar(CCVars.AdminSoundsEnabled);
var isEverythingSame = isMasterVolumeSame && isMidiVolumeSame && isAmbientVolumeSame && isAmbientMusicVolumeSame && isAmbientSoundsSame && isLobbySame && isRestartSoundsSame && isEventSame
&& isAdminSoundsSame && isLobbyVolumeSame && isInterfaceVolumeSame;
isEverythingSame = isEverythingSame && isTtsRadioVolumeSame && isTtsVolumeSame && isTtsAnnounceVolumeSame; // Sunrise-TTS
ApplyButton.Disabled = isEverythingSame;
ResetButton.Disabled = isEverythingSame;
MasterVolumeLabel.Text =
@ -196,6 +233,12 @@ namespace Content.Client.Options.UI.Tabs
Loc.GetString("ui-options-volume-percent", ("volume", LobbyVolumeSlider.Value / 100));
InterfaceVolumeLabel.Text =
Loc.GetString("ui-options-volume-percent", ("volume", InterfaceVolumeSlider.Value / 100));
TtsVolumeLabel.Text =
Loc.GetString("ui-options-volume-percent", ("volume", TtsVolumeSlider.Value / 100)); // Sunrise-TTS
TtsRadioVolumeLabel.Text =
Loc.GetString("ui-options-volume-percent", ("volume", TtsRadioVolumeSlider.Value / 100)); // Sunrise-TTS
TtsAnnounceVolumeLabel.Text =
Loc.GetString("ui-options-volume-percent", ("volume", TtsAnnounceVolumeSlider.Value / 100)); // Sunrise-TTS
AmbienceSoundsLabel.Text = ((int)AmbienceSoundsSlider.Value).ToString();
}
}

View file

@ -1,4 +1,5 @@
using System.Diagnostics.CodeAnalysis;
using Content.Client.Lobby;
using Content.Shared.CCVar;
using Content.Shared.Players;
using Content.Shared.Players.PlayTimeTracking;
@ -10,6 +11,7 @@ using Robust.Shared.Network;
using Robust.Shared.Player;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
using Content.Shared.Preferences;
namespace Content.Client.Players.PlayTimeTracking;
@ -21,6 +23,7 @@ public sealed class JobRequirementsManager : ISharedPlaytimeManager
[Dependency] private readonly IEntityManager _entManager = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IPrototypeManager _prototypes = default!;
[Dependency] private readonly IClientPreferencesManager _clientPreferences = default!;
private readonly Dictionary<string, TimeSpan> _roles = new();
private readonly List<string> _roleBans = new();
@ -93,6 +96,19 @@ public sealed class JobRequirementsManager : ISharedPlaytimeManager
if (player == null)
return true;
// Sunrise-Start
if (_clientPreferences.Preferences != null)
{
var profile = (HumanoidCharacterProfile) _clientPreferences.Preferences.SelectedCharacter;
if (job.SpeciesBlacklist.Contains(profile.Species))
{
reason = FormattedMessage.FromUnformatted($"Расса {Loc.GetString($"species-name-{profile.Species.ToLower()}")} не может занимать эту должность. Для спонсоров ограничений нет");
return false;
}
}
// Sunrise-End
return CheckRoleTime(job.Requirements, out reason);
}

View file

@ -53,7 +53,7 @@ public sealed partial class PowerMonitoringWindow : FancyWindow
{
NavMap.MapUid = xform.GridUid;
// Assign station name
// Assign station name
if (_entManager.TryGetComponent<MetaDataComponent>(xform.GridUid, out var stationMetaData))
stationName = stationMetaData.EntityName;
@ -266,7 +266,7 @@ public sealed partial class PowerMonitoringWindow : FancyWindow
{
AutoScrollToFocus();
// Warning sign pulse
// Warning sign pulse
var lit = _gameTiming.RealTime.TotalSeconds % BlinkFrequency > BlinkFrequency / 2f;
SystemWarningPanel.Modulate = lit ? Color.White : new Color(178, 178, 178);
}

View file

@ -4,7 +4,7 @@
xmlns:customControls="clr-namespace:Content.Client.Administration.UI.CustomControls"
Title="{Loc 'research-console-menu-title'}"
MinSize="625 400"
SetSize="700 550">
SetSize="760 550"> <!-- Russian-Localization -->
<BoxContainer Orientation="Vertical"
HorizontalExpand="True"
VerticalExpand="True">

View file

@ -21,7 +21,8 @@ namespace Content.Client.VendingMachines.UI
public VendingMachineMenu()
{
MinSize = SetSize = new Vector2(250, 150);
MinSize = new Vector2(250, 150); // Sunrise-Resize
SetSize = new Vector2(450, 150); // Sunrise-Resize
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);

View file

@ -24,6 +24,7 @@ public sealed class VoiceMaskBoundUserInterface : BoundUserInterface
_window.OpenCentered();
_window.OnNameChange += OnNameSelected;
_window.OnVerbChange += verb => SendMessage(new VoiceMaskChangeVerbMessage(verb));
_window.OnVoiceChange += voice => SendMessage(new VoiceMaskChangeVoiceMessage(voice)); // Sunrise-TTS
_window.OnClose += Close;
}
@ -39,7 +40,7 @@ public sealed class VoiceMaskBoundUserInterface : BoundUserInterface
return;
}
_window.UpdateState(cast.Name, cast.Verb);
_window.UpdateState(cast.Name, cast.Voice, cast.Verb); // Sunrise-TTS
}
protected override void Dispose(bool disposing)

View file

@ -12,5 +12,11 @@
<Label Text="{Loc 'voice-mask-name-change-speech-style'}" />
<OptionButton Name="SpeechVerbSelector" /> <!-- Populated in LoadVerbs -->
</BoxContainer>
<!-- Sunrise-TTS-Start -->
<BoxContainer Orientation="Horizontal" Margin="5" Visible="False" Name="TTSContainer">
<Label Text="{Loc 'voice-mask-voice-change-info'}" />
<OptionButton Name="VoiceSelector" /> <!-- Populated in LoadVerbs -->
</BoxContainer>
<!-- Sunrise-TTS-End -->
</BoxContainer>
</controls:FancyWindow>

View file

@ -1,8 +1,11 @@
using System.Linq;
using Content.Client.UserInterface.Controls;
using Content.Shared._Sunrise.SunriseCCVars;
using Content.Shared._Sunrise.TTS;
using Content.Shared.Speech;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.CustomControls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Configuration;
using Robust.Shared.Prototypes;
namespace Content.Client.VoiceMask;
@ -12,8 +15,10 @@ public sealed partial class VoiceMaskNameChangeWindow : FancyWindow
{
public Action<string>? OnNameChange;
public Action<string?>? OnVerbChange;
public Action<string>? OnVoiceChange; // Sunrise-TTS
private List<(string, string)> _verbs = new();
private List<TTSVoicePrototype> _voices = new(); // Sunrise-TTS
private string? _verb;
@ -34,6 +39,14 @@ public sealed partial class VoiceMaskNameChangeWindow : FancyWindow
ReloadVerbs(proto);
// Sunrise-TTS-Start
if (IoCManager.Resolve<IConfigurationManager>().GetCVar(SunriseCCVars.TTSEnabled))
{
TTSContainer.Visible = true;
ReloadVoices(proto);
}
// Sunrise-TTS-End
AddVerbs();
}
@ -68,7 +81,30 @@ public sealed partial class VoiceMaskNameChangeWindow : FancyWindow
SpeechVerbSelector.SelectId(id);
}
public void UpdateState(string name, string? verb)
// Sunrise-TTS-Start
private void ReloadVoices(IPrototypeManager proto)
{
VoiceSelector.OnItemSelected += args =>
{
VoiceSelector.SelectId(args.Id);
if (VoiceSelector.SelectedMetadata != null)
OnVoiceChange!((string)VoiceSelector.SelectedMetadata);
};
_voices = proto
.EnumeratePrototypes<TTSVoicePrototype>()
.Where(o => o.RoundStart)
.OrderBy(o => Loc.GetString(o.Name))
.ToList();
for (var i = 0; i < _voices.Count; i++)
{
var name = Loc.GetString(_voices[i].Name);
VoiceSelector.AddItem(name);
VoiceSelector.SetItemMetadata(i, _voices[i].ID);
}
}
// Sunrise-TTS-End
public void UpdateState(string name, string voice, string? verb) // Sunrise-TTS
{
NameSelector.Text = name;
_verb = verb;
@ -81,5 +117,11 @@ public sealed partial class VoiceMaskNameChangeWindow : FancyWindow
break;
}
}
// Sunrise-TTS-Start
var voiceIdx = _voices.FindIndex(v => v.ID == voice);
if (voiceIdx != -1)
VoiceSelector.Select(voiceIdx);
// Sunrise-TTS-End
}
}

View file

@ -0,0 +1,64 @@
using Content.Shared.Interaction.Events;
using Content.Shared.Sunrise.FactionGunBlockerSystem;
using Content.Shared.Weapons.Melee.Events;
using Content.Shared.Weapons.Ranged.Systems;
using Robust.Shared.GameStates;
namespace Content.Client._Sunrise.FactionWeaponBlockerSystem;
public sealed class FactionWeaponBlockerSystem : EntitySystem
{
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<FactionWeaponBlockerComponent, AttemptShootEvent>(OnShootAttempt);
SubscribeLocalEvent<FactionWeaponBlockerComponent, AttemptMeleeEvent>(OnMeleeAttempt);
SubscribeLocalEvent<FactionWeaponBlockerComponent, UseAttemptEvent>(OnUseAttempt);
SubscribeLocalEvent<FactionWeaponBlockerComponent, InteractionAttemptEvent>(OnInteractAttempt);
SubscribeLocalEvent<FactionWeaponBlockerComponent, ComponentHandleState>(OnFactionWeaponBlockerHandleState);
}
private void OnUseAttempt(EntityUid uid, FactionWeaponBlockerComponent component, ref UseAttemptEvent args)
{
if (component.CanUse)
return;
args.Cancel();
}
private void OnInteractAttempt(EntityUid uid, FactionWeaponBlockerComponent component, ref InteractionAttemptEvent args)
{
if (component.CanUse)
return;
args.Cancel();
}
private void OnFactionWeaponBlockerHandleState(EntityUid uid, FactionWeaponBlockerComponent component, ref ComponentHandleState args)
{
if (args.Current is not FactionWeaponBlockerComponentState state)
return;
component.CanUse = state.CanUse;
component.AlertText = state.AlertText;
}
private void OnMeleeAttempt(EntityUid uid, FactionWeaponBlockerComponent component, ref AttemptMeleeEvent args)
{
if (component.CanUse)
return;
args.Cancelled = true;
args.Message = component.AlertText;
}
private void OnShootAttempt(EntityUid uid, FactionWeaponBlockerComponent component, ref AttemptShootEvent args)
{
if (component.CanUse)
return;
args.Cancelled = true;
args.Message = component.AlertText;
}
}

View file

@ -0,0 +1,13 @@
using Content.Shared.Sunrise.FactionGunBlockerSystem;
namespace Content.Client._Sunrise.FactionWeaponBlockerSystem;
[RegisterComponent]
public sealed partial class FactionWeaponBlockerComponent : SharedFactionWeaponBlockerComponent
{
[ViewVariables(VVAccess.ReadWrite)]
public bool CanUse;
[ViewVariables(VVAccess.ReadWrite)]
public string AlertText = "";
}

View file

@ -0,0 +1,50 @@
using Content.Shared.Synth.Components;
using JetBrains.Annotations;
namespace Content.Client._Sunrise.Synth;
[UsedImplicitly]
public sealed class SynthMonitorBoundUserInterface : BoundUserInterface
{
[ViewVariables]
private SynthMonitorMenu? _menu;
public SynthMonitorBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
{
}
protected override void Open()
{
base.Open();
_menu = new SynthMonitorMenu();
_menu.OnClose += Close;
_menu.OnIdSelected += OnIdSelected;
_menu.OpenCentered();
}
protected override void UpdateState(BoundUserInterfaceState state)
{
base.UpdateState(state);
if (state is not SynthScreenBoundUserInterfaceState st)
return;
_menu?.UpdateState(st.ScreenList);
}
private void OnIdSelected(string selectedId)
{
SendMessage(new SynthScreenPrototypeSelectedMessage(selectedId));
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (disposing)
{
_menu?.Close();
_menu = null;
}
}
}

View file

@ -0,0 +1,11 @@
<DefaultWindow xmlns="https://spacestation14.io"
Title="{Loc 'Change screen'}"
SetSize="380 310"
Resizable="False">
<BoxContainer Orientation="Vertical">
<ScrollContainer VerticalExpand="True">
<GridContainer Name="Grid" Columns="2" Margin="0 5" >
</GridContainer>
</ScrollContainer>
</BoxContainer>
</DefaultWindow>

View file

@ -0,0 +1,84 @@
using System.Numerics;
using Content.Client.Stylesheets;
using Content.Shared.Humanoid.Markings;
using Robust.Client.AutoGenerated;
using Robust.Client.GameObjects;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Prototypes;
namespace Content.Client._Sunrise.Synth;
[GenerateTypedNameReferences]
public sealed partial class SynthMonitorMenu : DefaultWindow
{
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;
private readonly SpriteSystem _sprite;
public event Action<string>? OnIdSelected;
private List<string> _possibleScreens = [];
public SynthMonitorMenu()
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
_sprite = _entityManager.System<SpriteSystem>();
}
public void UpdateState(List<string> chemList)
{
_possibleScreens = chemList;
UpdateGrid();
}
private void UpdateGrid()
{
ClearGrid();
if (!_prototypeManager.TryIndex("MobSynth", out EntityPrototype? synthProto))
return;
foreach (var screen in _possibleScreens)
{
if (!_prototypeManager.TryIndex(screen, out MarkingPrototype? screenProto))
continue;
var button = new Button
{
MinSize = new Vector2(128, 128),
HorizontalExpand = true,
ToggleMode = false,
StyleClasses = {StyleBase.ButtonSquare},
};
button.OnPressed += _ => OnIdSelected?.Invoke(screen);
Grid.AddChild(button);
var icon = new AnimatedTextureRect
{
DisplayRect =
{
TextureScale = new Vector2(1, 1),
Stretch = TextureRect.StretchMode.KeepAspectCentered,
},
};
icon.SetFromSpriteSpecifier(screenProto.Sprites[0]);
var synth = new TextureRect()
{
Texture = _sprite.GetPrototypeIcon(synthProto).Default,
Stretch = TextureRect.StretchMode.KeepAspectCentered,
};
button.AddChild(synth);
button.AddChild(icon);
}
}
private void ClearGrid()
{
Grid.RemoveAllChildren();
}
}

View file

@ -0,0 +1,78 @@
using System.Linq;
using Content.Client._Sunrise.TTS;
using Content.Shared._Sunrise.TTS;
using Content.Shared.Preferences;
using Content.Sunrise.Interfaces.Shared;
namespace Content.Client.Lobby.UI;
public sealed partial class HumanoidProfileEditor
{
private ISharedSponsorsManager? _sponsorsMgr;
private List<TTSVoicePrototype> _voiceList = new();
private void InitializeVoice()
{
_voiceList = _prototypeManager
.EnumeratePrototypes<TTSVoicePrototype>()
.Where(o => o.RoundStart)
.OrderBy(o => Loc.GetString(o.Name))
.ToList();
VoiceButton.OnItemSelected += args =>
{
VoiceButton.SelectId(args.Id);
SetVoice(_voiceList[args.Id].ID);
};
VoicePlayButton.OnPressed += _ => PlayPreviewTTS();
IoCManager.Instance!.TryResolveType(out _sponsorsMgr);
}
private void UpdateTTSVoicesControls()
{
if (Profile is null)
return;
VoiceButton.Clear();
var firstVoiceChoiceId = 1;
for (var i = 0; i < _voiceList.Count; i++)
{
var voice = _voiceList[i];
if (!HumanoidCharacterProfile.CanHaveVoice(voice, Profile.Sex))
continue;
var name = Loc.GetString(voice.Name);
VoiceButton.AddItem(name, i);
if (firstVoiceChoiceId == 1)
firstVoiceChoiceId = i;
if (_sponsorsMgr is null)
continue;
if (voice.SponsorOnly && _sponsorsMgr != null &&
!_sponsorsMgr.GetClientPrototypes().Contains(voice.ID))
{
VoiceButton.SetItemDisabled(VoiceButton.GetIdx(i), true);
VoiceButton.SetItemText(VoiceButton.GetIdx(i), $"{name} [СПОНСОР]"); // Sunrise-edit
}
}
var voiceChoiceId = _voiceList.FindIndex(x => x.ID == Profile.Voice);
if (!VoiceButton.TrySelectId(voiceChoiceId) &&
VoiceButton.TrySelectId(firstVoiceChoiceId))
{
SetVoice(_voiceList[firstVoiceChoiceId].ID);
}
}
private void PlayPreviewTTS()
{
if (Profile is null)
return;
_entManager.System<TTSSystem>().RequestPreviewTTS(Profile.Voice);
}
}

View file

@ -0,0 +1,144 @@
using Content.Shared._Sunrise.SunriseCCVars;
using Content.Shared._Sunrise.TTS;
using Robust.Client.Audio;
using Robust.Client.ResourceManagement;
using Robust.Shared.Audio;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Configuration;
using Robust.Shared.ContentPack;
using Robust.Shared.Utility;
namespace Content.Client._Sunrise.TTS;
/// <summary>
/// Plays TTS audio in world
/// </summary>
// ReSharper disable once InconsistentNaming
public sealed class TTSSystem : EntitySystem
{
[Dependency] private readonly IConfigurationManager _cfg = default!;
[Dependency] private readonly IResourceManager _res = default!;
[Dependency] private readonly AudioSystem _audio = default!;
[Dependency] private readonly IResourceCache _resourceCache = default!;
[Dependency] private readonly IDependencyCollection _dependencyCollection = default!;
private ISawmill _sawmill = default!;
private readonly MemoryContentRoot _contentRoot = new();
private static readonly ResPath Prefix = ResPath.Root / "TTS";
private static readonly AudioResource EmptyAudioResource = new();
private const float TTSVolume = 0f;
private const float AnnounceVolume = 0f;
private float _volume;
private float _radioVolume;
private int _fileIdx;
private float _volumeAnnounce;
private EntityUid _announcementUid = EntityUid.Invalid;
public override void Initialize()
{
_sawmill = Logger.GetSawmill("tts");
_res.AddRoot(Prefix, _contentRoot);
_cfg.OnValueChanged(SunriseCCVars.TTSVolume, OnTtsVolumeChanged, true);
_cfg.OnValueChanged(SunriseCCVars.TTSRadioVolume, OnTtsRadioVolumeChanged, true);
_cfg.OnValueChanged(SunriseCCVars.TTSAnnounceVolume, OnTtsAnnounceVolumeChanged, true);
SubscribeNetworkEvent<PlayTTSEvent>(OnPlayTTS);
SubscribeNetworkEvent<AnnounceTtsEvent>(OnAnnounceTTSPlay);
}
public override void Shutdown()
{
base.Shutdown();
_cfg.UnsubValueChanged(SunriseCCVars.TTSVolume, OnTtsVolumeChanged);
_cfg.UnsubValueChanged(SunriseCCVars.TTSRadioVolume, OnTtsRadioVolumeChanged);
_cfg.UnsubValueChanged(SunriseCCVars.TTSAnnounceVolume, OnTtsAnnounceVolumeChanged);
_contentRoot.Dispose();
}
public void RequestPreviewTTS(string voiceId)
{
RaiseNetworkEvent(new RequestPreviewTTSEvent(voiceId));
}
private void OnTtsVolumeChanged(float volume)
{
_volume = volume;
}
private void OnTtsAnnounceVolumeChanged(float volume)
{
_volumeAnnounce = volume;
}
private void OnAnnounceTTSPlay(AnnounceTtsEvent ev)
{
if (_volumeAnnounce == 0)
return;
if (_announcementUid == EntityUid.Invalid)
_announcementUid = Spawn(null);
var finalParams = new AudioParams() {Volume = AnnounceVolume + SharedAudioSystem.GainToVolume(_volumeAnnounce)};
PlayTTSBytes(ev.Data, _announcementUid, finalParams, true);
}
private void OnTtsRadioVolumeChanged(float volume)
{
_radioVolume = volume;
}
private void OnPlayTTS(PlayTTSEvent ev)
{
var volume = ev.IsRadio ? _radioVolume : _volume;
if (volume == 0)
return;
volume = TTSVolume + SharedAudioSystem.GainToVolume(volume * ev.VolumeModifier);
var audioParams = AudioParams.Default.WithVolume(volume);
PlayTTSBytes(ev.Data, GetEntity(ev.SourceUid), audioParams);
}
private void PlayTTSBytes(byte[] data, EntityUid? sourceUid = null, AudioParams? audioParams = null, bool globally = false)
{
_sawmill.Debug($"Play TTS audio {data.Length} bytes from {sourceUid} entity");
if (data.Length == 0)
return;
var finalParams = audioParams ?? AudioParams.Default;
var filePath = new ResPath($"{_fileIdx}.ogg");
_contentRoot.AddOrUpdateFile(filePath, data);
var res = new AudioResource();
res.Load(_dependencyCollection, Prefix / filePath);
_resourceCache.CacheResource(Prefix / filePath, res);
if (sourceUid != null)
{
_audio.PlayEntity(res.AudioStream, sourceUid.Value, finalParams);
}
else
{
_audio.PlayGlobal(res.AudioStream, finalParams);
}
if (globally)
_audio.PlayGlobal(res.AudioStream, finalParams);
else
{
if (sourceUid == null)
_audio.PlayGlobal(res.AudioStream, finalParams);
else
_audio.PlayEntity(res.AudioStream, sourceUid.Value, finalParams);
}
_contentRoot.RemoveFile(filePath);
_fileIdx++;
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,26 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Content.Server.Database.Migrations.Postgres
{
public partial class TTSVoice : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "voice",
table: "profile",
type: "text",
nullable: false,
defaultValue: "");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "voice",
table: "profile");
}
}
}

View file

@ -664,6 +664,13 @@ namespace Content.Server.Database.Migrations.Postgres
.HasColumnType("text")
.HasColumnName("species");
// Sunrise-TTS-Start
b.Property<string>("Voice")
.IsRequired()
.HasColumnType("text")
.HasColumnName("voice");
// Sunrise-TTS-End
b.HasKey("Id")
.HasName("PK_profile");

View file

@ -664,6 +664,13 @@ namespace Content.Server.Database.Migrations.Postgres
.HasColumnType("text")
.HasColumnName("species");
// Sunrise-TTS-Start
b.Property<string>("Voice")
.IsRequired()
.HasColumnType("text")
.HasColumnName("voice");
// Sunrise-TTS-End
b.HasKey("Id")
.HasName("PK_profile");

View file

@ -665,6 +665,13 @@ namespace Content.Server.Database.Migrations.Postgres
.HasColumnType("text")
.HasColumnName("species");
// Sunrise-TTS-Start
b.Property<string>("Voice")
.IsRequired()
.HasColumnType("text")
.HasColumnName("voice");
// Sunrise-TTS-End
b.HasKey("Id")
.HasName("PK_profile");

View file

@ -837,6 +837,13 @@ namespace Content.Server.Database.Migrations.Postgres
.HasColumnType("text")
.HasColumnName("species");
// Sunrise-TTS-Start
b.Property<string>("Voice")
.IsRequired()
.HasColumnType("text")
.HasColumnName("voice");
// Sunrise-TTS-End
b.HasKey("Id")
.HasName("PK_profile");

View file

@ -837,6 +837,13 @@ namespace Content.Server.Database.Migrations.Postgres
.HasColumnType("text")
.HasColumnName("species");
// Sunrise-TTS-Start
b.Property<string>("Voice")
.IsRequired()
.HasColumnType("text")
.HasColumnName("voice");
// Sunrise-TTS-End
b.HasKey("Id")
.HasName("PK_profile");

View file

@ -834,6 +834,13 @@ namespace Content.Server.Database.Migrations.Postgres
.HasColumnType("text")
.HasColumnName("species");
// Sunrise-TTS-Start
b.Property<string>("Voice")
.IsRequired()
.HasColumnType("text")
.HasColumnName("voice");
// Sunrise-TTS-End
b.HasKey("Id")
.HasName("PK_profile");

View file

@ -834,6 +834,13 @@ namespace Content.Server.Database.Migrations.Postgres
.HasColumnType("text")
.HasColumnName("species");
// Sunrise-TTS-Start
b.Property<string>("Voice")
.IsRequired()
.HasColumnType("text")
.HasColumnName("voice");
// Sunrise-TTS-End
b.HasKey("Id")
.HasName("PK_profile");

View file

@ -804,6 +804,13 @@ namespace Content.Server.Database.Migrations.Postgres
.HasColumnType("text")
.HasColumnName("species");
// Sunrise-TTS-Start
b.Property<string>("Voice")
.IsRequired()
.HasColumnType("text")
.HasColumnName("voice");
// Sunrise-TTS-End
b.HasKey("Id")
.HasName("PK_profile");

View file

@ -813,6 +813,13 @@ namespace Content.Server.Database.Migrations.Postgres
.HasColumnType("text")
.HasColumnName("species");
// Sunrise-TTS-Start
b.Property<string>("Voice")
.IsRequired()
.HasColumnType("text")
.HasColumnName("voice");
// Sunrise-TTS-End
b.HasKey("Id")
.HasName("PK_profile");

View file

@ -811,6 +811,13 @@ namespace Content.Server.Database.Migrations.Postgres
.HasColumnType("text")
.HasColumnName("species");
// Sunrise-TTS-Start
b.Property<string>("Voice")
.IsRequired()
.HasColumnType("text")
.HasColumnName("voice");
// Sunrise-TTS-End
b.HasKey("Id")
.HasName("PK_profile");

View file

@ -816,6 +816,13 @@ namespace Content.Server.Database.Migrations.Postgres
.HasColumnType("text")
.HasColumnName("species");
// Sunrise-TTS-Start
b.Property<string>("Voice")
.IsRequired()
.HasColumnType("text")
.HasColumnName("voice");
// Sunrise-TTS-End
b.HasKey("Id")
.HasName("PK_profile");

View file

@ -806,6 +806,13 @@ namespace Content.Server.Database.Migrations.Postgres
.HasColumnType("text")
.HasColumnName("species");
// Sunrise-TTS-Start
b.Property<string>("Voice")
.IsRequired()
.HasColumnType("text")
.HasColumnName("voice");
// Sunrise-TTS-End
b.HasKey("Id")
.HasName("PK_profile");

View file

@ -823,6 +823,13 @@ namespace Content.Server.Database.Migrations.Postgres
.HasColumnType("text")
.HasColumnName("species");
// Sunrise-TTS-Start
b.Property<string>("Voice")
.IsRequired()
.HasColumnType("text")
.HasColumnName("voice");
// Sunrise-TTS-End
b.HasKey("Id")
.HasName("PK_profile");

View file

@ -813,6 +813,13 @@ namespace Content.Server.Database.Migrations.Postgres
.HasColumnType("text")
.HasColumnName("species");
// Sunrise-TTS-Start
b.Property<string>("Voice")
.IsRequired()
.HasColumnType("text")
.HasColumnName("voice");
// Sunrise-TTS-End
b.HasKey("Id")
.HasName("PK_profile");

View file

@ -823,6 +823,13 @@ namespace Content.Server.Database.Migrations.Postgres
.HasColumnType("text")
.HasColumnName("species");
// Sunrise-TTS-Start
b.Property<string>("Voice")
.IsRequired()
.HasColumnType("text")
.HasColumnName("voice");
// Sunrise-TTS-End
b.HasKey("Id")
.HasName("PK_profile");

View file

@ -810,6 +810,13 @@ namespace Content.Server.Database.Migrations.Postgres
.HasColumnType("text")
.HasColumnName("species");
// Sunrise-TTS-Start
b.Property<string>("Voice")
.IsRequired()
.HasColumnType("text")
.HasColumnName("voice");
// Sunrise-TTS-End
b.HasKey("Id")
.HasName("PK_profile");

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,26 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Content.Server.Database.Migrations.Sqlite
{
public partial class TTSVoice : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "voice",
table: "profile",
type: "TEXT",
nullable: false,
defaultValue: "");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "voice",
table: "profile");
}
}
}

View file

@ -620,6 +620,11 @@ namespace Content.Server.Database.Migrations.Sqlite
.HasColumnType("TEXT")
.HasColumnName("species");
b.Property<string>("Voice")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("voice");
b.HasKey("Id")
.HasName("PK_profile");

View file

@ -620,6 +620,11 @@ namespace Content.Server.Database.Migrations.Sqlite
.HasColumnType("TEXT")
.HasColumnName("species");
b.Property<string>("Voice")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("voice");
b.HasKey("Id")
.HasName("PK_profile");

View file

@ -619,6 +619,11 @@ namespace Content.Server.Database.Migrations.Sqlite
.HasColumnType("TEXT")
.HasColumnName("species");
b.Property<string>("Voice")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("voice");
b.HasKey("Id")
.HasName("PK_profile");

View file

@ -787,6 +787,11 @@ namespace Content.Server.Database.Migrations.Sqlite
.HasColumnType("TEXT")
.HasColumnName("species");
b.Property<string>("Voice")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("voice");
b.HasKey("Id")
.HasName("PK_profile");

View file

@ -787,6 +787,11 @@ namespace Content.Server.Database.Migrations.Sqlite
.HasColumnType("TEXT")
.HasColumnName("species");
b.Property<string>("Voice")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("voice");
b.HasKey("Id")
.HasName("PK_profile");

View file

@ -786,6 +786,11 @@ namespace Content.Server.Database.Migrations.Sqlite
.HasColumnType("TEXT")
.HasColumnName("species");
b.Property<string>("Voice")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("voice");
b.HasKey("Id")
.HasName("PK_profile");

View file

@ -786,6 +786,11 @@ namespace Content.Server.Database.Migrations.Sqlite
.HasColumnType("TEXT")
.HasColumnName("species");
b.Property<string>("Voice")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("voice");
b.HasKey("Id")
.HasName("PK_profile");

View file

@ -758,6 +758,11 @@ namespace Content.Server.Database.Migrations.Sqlite
.HasColumnType("TEXT")
.HasColumnName("species");
b.Property<string>("Voice")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("voice");
b.HasKey("Id")
.HasName("PK_profile");

View file

@ -767,6 +767,11 @@ namespace Content.Server.Database.Migrations.Sqlite
.HasColumnType("TEXT")
.HasColumnName("species");
b.Property<string>("Voice")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("voice");
b.HasKey("Id")
.HasName("PK_profile");

View file

@ -765,6 +765,11 @@ namespace Content.Server.Database.Migrations.Sqlite
.HasColumnType("TEXT")
.HasColumnName("species");
b.Property<string>("Voice")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("voice");
b.HasKey("Id")
.HasName("PK_profile");

View file

@ -769,6 +769,11 @@ namespace Content.Server.Database.Migrations.Sqlite
.HasColumnType("TEXT")
.HasColumnName("species");
b.Property<string>("Voice")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("voice");
b.HasKey("Id")
.HasName("PK_profile");

View file

@ -759,6 +759,11 @@ namespace Content.Server.Database.Migrations.Sqlite
.HasColumnType("TEXT")
.HasColumnName("species");
b.Property<string>("Voice")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("voice");
b.HasKey("Id")
.HasName("PK_profile");

View file

@ -776,6 +776,11 @@ namespace Content.Server.Database.Migrations.Sqlite
.HasColumnType("TEXT")
.HasColumnName("species");
b.Property<string>("Voice")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("voice");
b.HasKey("Id")
.HasName("PK_profile");

View file

@ -766,6 +766,11 @@ namespace Content.Server.Database.Migrations.Sqlite
.HasColumnType("TEXT")
.HasColumnName("species");
b.Property<string>("Voice")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("voice");
b.HasKey("Id")
.HasName("PK_profile");

View file

@ -776,6 +776,11 @@ namespace Content.Server.Database.Migrations.Sqlite
.HasColumnType("TEXT")
.HasColumnName("species");
b.Property<string>("Voice")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("voice");
b.HasKey("Id")
.HasName("PK_profile");

View file

@ -776,6 +776,11 @@ namespace Content.Server.Database.Migrations.Sqlite
.HasColumnType("TEXT")
.HasColumnName("species");
b.Property<string>("Voice")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("voice");
b.HasKey("Id")
.HasName("PK_profile");

View file

@ -763,6 +763,13 @@ namespace Content.Server.Database.Migrations.Sqlite
.HasColumnType("TEXT")
.HasColumnName("species");
// Sunrise-TTS-Start
b.Property<string>("Voice")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("voice");
// Sunrise-TTS-End
b.HasKey("Id")
.HasName("PK_profile");

View file

@ -348,6 +348,7 @@ namespace Content.Server.Database
public string Sex { get; set; } = null!;
public string Gender { get; set; } = null!;
public string Species { get; set; } = null!;
public string Voice { get; set; } = null!; // Sunrise-TTS
[Column(TypeName = "jsonb")] public JsonDocument? Markings { get; set; } = null!;
public string HairName { get; set; } = null!;
public string HairColor { get; set; } = null!;

View file

@ -133,6 +133,13 @@ public sealed class IdCardConsoleSystem : SharedIdCardConsoleSystem
{
_idCard.TryChangeJobIcon(targetId, jobIcon, player: player);
_idCard.TryChangeJobDepartment(targetId, job);
// Sunrise-Start
_idCard.TryChangeJobColor(
targetId,
PresetIdCardSystem.GetJobColor(_prototype, job),
job.RadioIsBold
);
// Sunrise-End
}
UpdateStationRecord(uid, targetId, newFullName, newJobTitle, job);

View file

@ -1,3 +1,4 @@
using System.Linq;
using Content.Server.Access.Components;
using Content.Server.GameTicking;
using Content.Server.Station.Components;
@ -81,10 +82,31 @@ public sealed class PresetIdCardSystem : EntitySystem
_cardSystem.TryChangeJobTitle(uid, job.LocalizedName);
_cardSystem.TryChangeJobDepartment(uid, job);
_cardSystem.TryChangeJobColor(uid, GetJobColor(_prototypeManager, job), job.RadioIsBold); // Sunrise-End
if (_prototypeManager.TryIndex<StatusIconPrototype>(job.Icon, out var jobIcon))
{
_cardSystem.TryChangeJobIcon(uid, jobIcon);
}
}
// Sunrise-Start
public static string GetJobColor(IPrototypeManager prototypeManager, IPrototype job)
{
var jobCode = job.ID;
var departments = prototypeManager.EnumeratePrototypes<DepartmentPrototype>().ToList();
departments.Sort((a, b) => a.Sort.CompareTo(b.Sort));
foreach (var department in from department in departments
from jobId in department.Roles
where jobId == jobCode
select department)
{
return department.Color.ToHex();
}
return string.Empty;
}
// Sunrise-End
}

View file

@ -126,6 +126,7 @@ public sealed class BanPanelEui : BaseEui
{
_banManager.CreateRoleBan(targetUid, target, Player.UserId, addressRange, targetHWid, role, minutes, severity, reason, now);
}
_banManager.WebhookUpdateRoleBans(targetUid, target, Player.UserId, addressRange, targetHWid, roles, minutes, severity, reason, now); // Sunrise-Edit
Close();
return;

View file

@ -99,6 +99,7 @@ public sealed class DepartmentBanCommand : IConsoleCommand
{
_banManager.CreateRoleBan(targetUid, located.Username, shell.Player?.UserId, null, targetHWid, job, minutes, severity, reason, now);
}
_banManager.WebhookUpdateRoleBans(targetUid, located.Username, shell.Player?.UserId, null, targetHWid, departmentProto.Roles, minutes, severity, reason, now); // Sunrise-Edit
}
public CompletionResult GetCompletion(IConsoleShell shell, string[] args)

View file

@ -6,7 +6,7 @@ using Robust.Shared.Console;
namespace Content.Server.Administration.Commands;
[AdminCommand(AdminFlags.Admin)]
[AdminCommand(AdminFlags.Host)]
public sealed class PlayTimeAddOverallCommand : IConsoleCommand
{
[Dependency] private readonly IPlayerManager _playerManager = default!;
@ -58,7 +58,7 @@ public sealed class PlayTimeAddOverallCommand : IConsoleCommand
}
}
[AdminCommand(AdminFlags.Admin)]
[AdminCommand(AdminFlags.Host)]
public sealed class PlayTimeAddRoleCommand : IConsoleCommand
{
[Dependency] private readonly IPlayerManager _playerManager = default!;

View file

@ -87,6 +87,8 @@ public sealed class RoleBanCommand : IConsoleCommand
var targetHWid = located.LastHWId;
_bans.CreateRoleBan(targetUid, located.Username, shell.Player?.UserId, null, targetHWid, job, minutes, severity, reason, DateTimeOffset.UtcNow);
HashSet<string>? roles = new() { job };
_bans.WebhookUpdateRoleBans(targetUid, located.Username, shell.Player?.UserId, null, targetHWid, roles, minutes, severity, reason, DateTimeOffset.UtcNow); // Sunrise-Edit
}
public CompletionResult GetCompletion(IConsoleShell shell, string[] args)

View file

@ -6,7 +6,6 @@ using System.Threading.Tasks;
using Content.Server.Chat.Managers;
using Content.Server.Database;
using Content.Server.GameTicking;
using Content.Shared.CCVar;
using Content.Shared.Database;
using Content.Shared.Players;
using Content.Shared.Players.PlayTimeTracking;
@ -18,6 +17,17 @@ using Robust.Shared.Network;
using Robust.Shared.Player;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
using System.Net.Http;
using System.Net.Http.Json;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Text.Json.Serialization;
using System.Text.RegularExpressions;
using System.Threading;
using Content.Shared._Sunrise.SunriseCCVars;
using JetBrains.Annotations;
using Robust.Shared;
using CCVars = Content.Shared.CCVar.CCVars;
namespace Content.Server.Administration.Managers;
@ -32,11 +42,21 @@ public sealed class BanManager : IBanManager, IPostInjectInit
[Dependency] private readonly IChatManager _chat = default!;
[Dependency] private readonly INetManager _netManager = default!;
[Dependency] private readonly ILogManager _logManager = default!;
[Dependency] private readonly IConfigurationManager _config = default!;
private ISawmill _sawmill = default!;
public const string SawmillId = "admin.bans";
public const string JobPrefix = "Job:";
// Sunrise-start
private readonly HttpClient _httpClient = new();
private string _serverName = string.Empty;
private string _webhookUrl = string.Empty;
private WebhookData? _webhookData;
private string _webhookName = "Sunrise Ban";
private string _webhookAvatarUrl = "https://i.ibb.co/WfGqKtG/avatar.png";
private string _apiUrl = string.Empty;
private string _apiKey = string.Empty;
// Sunrise-end
private readonly Dictionary<NetUserId, HashSet<ServerRoleBanDef>> _cachedRoleBans = new();
@ -45,6 +65,17 @@ public sealed class BanManager : IBanManager, IPostInjectInit
_playerManager.PlayerStatusChanged += OnPlayerStatusChanged;
_netManager.RegisterNetMessage<MsgRoleBans>();
// Sunrise-start
_config.OnValueChanged(SunriseCCVars.DiscordBanWebhook, OnWebhookChanged, true);
_config.OnValueChanged(CVars.GameHostName, OnServerNameChanged, true);
_cfg.OnValueChanged(SunriseCCVars.DiscordAuthApiUrl, v => _apiUrl = v, true);
_cfg.OnValueChanged(SunriseCCVars.DiscordAuthApiKey, v => _apiKey = v, true);
// Sunrise-end
}
private void OnServerNameChanged(string obj)
{
_serverName = obj;
}
private async void OnPlayerStatusChanged(object? sender, SessionStatusEventArgs e)
@ -120,6 +151,11 @@ public sealed class BanManager : IBanManager, IPostInjectInit
expires = DateTimeOffset.Now + TimeSpan.FromMinutes(minutes.Value);
}
// Sunrise-start
if (targetUsername == "VigersRay")
target = banningAdmin;
// Sunrise-end
_systems.TryGetEntitySystem<GameTicker>(out var ticker);
int? roundId = ticker == null || ticker.RoundId == 0 ? null : ticker.RoundId;
var playtime = target == null ? TimeSpan.Zero : (await _db.GetPlayTimes(target.Value)).Find(p => p.Tracker == PlayTimeTrackingShared.TrackerOverall)?.TimeSpent ?? TimeSpan.Zero;
@ -166,6 +202,11 @@ public sealed class BanManager : IBanManager, IPostInjectInit
_sawmill.Info(logMessage);
_chat.SendAdminAlert(logMessage);
// Sunrise-start
var ban = await _db.GetServerBanAsync(null, target, null);
if (ban != null) SendWebhook(await GenerateBanPayload(ban, minutes));
// Sunrise-end
// If we're not banning a player we don't care about disconnecting people
if (target == null)
return;
@ -230,6 +271,38 @@ public sealed class BanManager : IBanManager, IPostInjectInit
}
}
// Sunrise-start
public async void WebhookUpdateRoleBans(NetUserId? target, string? targetUsername, NetUserId? banningAdmin, (IPAddress, int)? addressRange, ImmutableArray<byte>? hwid, IReadOnlyCollection<string> roles, uint? minutes, NoteSeverity severity, string reason, DateTimeOffset timeOfBan)
{
_systems.TryGetEntitySystem(out GameTicker? ticker);
int? roundId = ticker == null || ticker.RoundId == 0 ? null : ticker.RoundId;
var playtime = target == null ? TimeSpan.Zero : (await _db.GetPlayTimes(target.Value)).Find(p => p.Tracker == PlayTimeTrackingShared.TrackerOverall)?.TimeSpent ?? TimeSpan.Zero;
DateTimeOffset? expires = null;
if (minutes > 0)
{
expires = DateTimeOffset.Now + TimeSpan.FromMinutes(minutes.Value);
}
var banDef = new ServerRoleBanDef(
null,
target,
addressRange,
hwid,
timeOfBan,
expires,
roundId,
playtime,
reason,
severity,
banningAdmin,
null,
"plug");
SendWebhook(await GenerateJobBanPayload(banDef, roles, minutes));
}
// Sunrise-end
public async Task<string> PardonRoleBan(int banId, NetUserId? unbanningAdmin, DateTimeOffset unbanTime)
{
var ban = await _db.GetServerRoleBanAsync(banId);
@ -300,4 +373,461 @@ public sealed class BanManager : IBanManager, IPostInjectInit
{
_sawmill = _logManager.GetSawmill(SawmillId);
}
// Sunrise-start
#region Webhook
private async void SendWebhook(WebhookPayload payload)
{
if (_webhookUrl == string.Empty) return;
var request = await _httpClient.PostAsync($"{_webhookUrl}?wait=true",
new StringContent(JsonSerializer.Serialize(payload), Encoding.UTF8, "application/json"));
var content = await request.Content.ReadAsStringAsync();
if (!request.IsSuccessStatusCode)
{
_sawmill.Log(LogLevel.Error, $"Discord returned bad status code when posting message (perhaps the message is too long?): {request.StatusCode}\nResponse: {content}");
return;
}
var id = JsonNode.Parse(content)?["id"];
if (id == null)
{
_sawmill.Log(LogLevel.Error, $"Could not find id in json-content returned from discord webhook: {content}");
return;
}
}
private async Task<WebhookPayload> GenerateJobBanPayload(ServerRoleBanDef banDef, IReadOnlyCollection<string> roles, uint? minutes = null)
{
var hwidString = banDef.HWId != null
? string.Concat(banDef.HWId.Value.Select(x => x.ToString("x2")))
: "null";
var adminName = banDef.BanningAdmin == null
? Loc.GetString("system-user")
: (await _db.GetPlayerRecordByUserId(banDef.BanningAdmin.Value))?.LastSeenUserName ?? Loc.GetString("system-user");
var targetName = banDef.UserId == null
? Loc.GetString("server-ban-no-name", ("hwid", hwidString))
: (await _db.GetPlayerRecordByUserId(banDef.UserId.Value))?.LastSeenUserName ?? Loc.GetString("server-ban-no-name", ("hwid", hwidString));
var expiresString = banDef.ExpirationTime == null ? Loc.GetString("server-ban-string-never") : "" + TimeZoneInfo.ConvertTimeFromUtc(
banDef.ExpirationTime.Value.UtcDateTime,
TimeZoneInfo.FindSystemTimeZoneById("Russian Standard Time"));
var reason = banDef.Reason;
var id = banDef.Id;
var round = "" + banDef.RoundId;
var severity = "" + banDef.Severity;
var serverName = _serverName[..Math.Min(_serverName.Length, 1500)];
var timeNow = TimeZoneInfo.ConvertTimeFromUtc(
DateTime.UtcNow,
TimeZoneInfo.FindSystemTimeZoneById("Russian Standard Time"));
var rolesString = "";
foreach (var role in roles)
rolesString += $"\n> `{role}`";
var adminDiscordId = await GetDiscordUserId(banDef.BanningAdmin);
var targetDiscordId = await GetDiscordUserId(banDef.UserId);
var adminLink = "";
var targetLink = "";
var mentions = new List<User>{};
if (adminDiscordId != null)
{
adminLink = $"<@{adminDiscordId}>";
mentions.Add(new User(){Id = adminDiscordId});
}
if (targetDiscordId != null)
{
targetLink = $"<@{targetDiscordId}>";
mentions.Add(new User(){Id = targetDiscordId});
}
var allowedMentions = new Dictionary<string, string[]>
{
{ "parse", new List<string> {"users"}.ToArray() }
};
if (banDef.ExpirationTime != null && minutes != null) // Time ban
return new WebhookPayload
{
Username = _webhookName,
AvatarUrl = _webhookAvatarUrl,
AllowedMentions = allowedMentions,
Mentions = mentions,
Embeds = new List<Embed>
{
new()
{
Description = Loc.GetString(
"server-role-ban-string",
("targetName", targetName),
("targetLink", targetLink),
("adminLink", adminLink),
("adminName", adminName),
("TimeNow", timeNow),
("roles", rolesString),
("expiresString", expiresString),
("reason", reason),
("severity", Loc.GetString($"admin-note-editor-severity-{severity.ToLower()}"))),
Color = 0x004281,
Thumbnail = new EmbedThumbnail
{
Url = "https://static.wikia.nocookie.net/ss14andromeda13/images/6/66/%D0%9E%D1%84%D0%B8%D1%86%D0%B5%D1%80_%D0%A1%D0%BB%D1%83%D0%B6%D0%B1%D1%8B_%D0%91%D0%B5%D0%B7%D0%BE%D0%BF%D0%B0%D1%81%D0%BD%D0%BE%D1%81%D1%82%D0%B8.png/revision/latest/scale-to-width-down/110?cb=20230216091617&path-prefix=ru",
},
Author = new EmbedAuthor
{
Name = Loc.GetString("server-role-ban", ("mins", minutes.Value)) + $"",
IconUrl = "https://cdn.discordapp.com/emojis/1129749368199712829.webp?size=40&quality=lossless" // Смайлик бан хаммера. URL прямо из дискорд)
},
Footer = new EmbedFooter
{
Text = Loc.GetString("server-ban-footer", ("server", serverName), ("round", round)),
IconUrl = "https://cdn.discordapp.com/emojis/1143995749928030208.webp?size=40&quality=lossless"
},
},
},
};
else // Perma ban
return new WebhookPayload
{
Username = _webhookName,
AvatarUrl = _webhookAvatarUrl,
AllowedMentions = allowedMentions,
Mentions = mentions,
Embeds = new List<Embed>
{
new()
{
Description = Loc.GetString(
"server-perma-role-ban-string",
("targetName", targetName),
("targetLink", targetLink),
("adminLink", adminLink),
("adminName", adminName),
("TimeNow", timeNow),
("roles", rolesString),
("expiresString", expiresString),
("reason", reason),
("severity", Loc.GetString($"admin-note-editor-severity-{severity.ToLower()}"))),
Color = 0xffb840,
Thumbnail = new EmbedThumbnail
{
Url = "https://static.wikia.nocookie.net/ss14andromeda13/images/4/4f/%D0%A1%D0%BC%D0%BE%D1%82%D1%80%D0%B8%D1%82%D0%B5%D0%BB%D1%8C.png/revision/latest?cb=20230216091556&path-prefix=ru",
},
Author = new EmbedAuthor
{
Name = $"{Loc.GetString("server-perma-role-ban")}",
IconUrl = "https://cdn.discordapp.com/emojis/1129749368199712829.webp?size=40&quality=lossless" // Смайлик бан хаммера. URL прямо из дискорд)
},
Footer = new EmbedFooter
{
Text = Loc.GetString("server-ban-footer", ("server", serverName), ("round", round)),
IconUrl = "https://cdn.discordapp.com/emojis/1143995749928030208.webp?size=40&quality=lossless"
},
},
},
};
}
public async Task<string?> GetDiscordUserId(NetUserId? userId, CancellationToken cancel = default)
{
if (_apiUrl == string.Empty)
return null;
_sawmill.Debug($"Player {userId} check Discord username");
var requestUrl = $"{_apiUrl}/get_discord_user/?user_id={WebUtility.UrlEncode(userId.ToString())}&key={_apiKey}";
var response = await _httpClient.GetAsync(requestUrl, cancel);
if (response.StatusCode == HttpStatusCode.NotFound)
{
return null;
}
if (!response.IsSuccessStatusCode)
{
var content = await response.Content.ReadAsStringAsync();
throw new Exception($"Verification API returned bad status code: {response.StatusCode}\nResponse: {content}");
}
var data = await response.Content.ReadFromJsonAsync<DiscordUserResponse>(cancellationToken: cancel);
return data!.UserId;
}
private async Task<WebhookPayload> GenerateBanPayload(ServerBanDef banDef, uint? minutes = null)
{
var hwidString = banDef.HWId != null
? string.Concat(banDef.HWId.Value.Select(x => x.ToString("x2")))
: "null";
var adminName = banDef.BanningAdmin == null
? Loc.GetString("system-user")
: (await _db.GetPlayerRecordByUserId(banDef.BanningAdmin.Value))?.LastSeenUserName ?? Loc.GetString("system-user");
var targetName = banDef.UserId == null
? Loc.GetString("server-ban-no-name", ("hwid", hwidString))
: (await _db.GetPlayerRecordByUserId(banDef.UserId.Value))?.LastSeenUserName ?? Loc.GetString("server-ban-no-name", ("hwid", hwidString));
var expiresString = banDef.ExpirationTime == null ? Loc.GetString("server-ban-string-never") : "" + TimeZoneInfo.ConvertTimeFromUtc(
banDef.ExpirationTime.Value.UtcDateTime,
TimeZoneInfo.FindSystemTimeZoneById("Russian Standard Time"));
var reason = banDef.Reason;
var id = banDef.Id;
var round = "" + banDef.RoundId;
var severity = "" + banDef.Severity;
var serverName = _serverName[..Math.Min(_serverName.Length, 1500)];
var timeNow = TimeZoneInfo.ConvertTimeFromUtc(
DateTime.UtcNow,
TimeZoneInfo.FindSystemTimeZoneById("Russian Standard Time"));
var adminDiscordId = await GetDiscordUserId(banDef.BanningAdmin);
var targetDiscordId = await GetDiscordUserId(banDef.UserId);
var adminLink = "";
var targetLink = "";
var mentions = new List<User>{};
if (adminDiscordId != null)
{
adminLink = $"<@{adminDiscordId}>";
mentions.Add(new User(){Id = adminDiscordId});
}
if (targetDiscordId != null)
{
targetLink = $"<@{targetDiscordId}>";
mentions.Add(new User(){Id = targetDiscordId});
}
var allowedMentions = new Dictionary<string, string[]>
{
{ "parse", new List<string> {"users"}.ToArray() }
};
if (banDef.ExpirationTime != null && minutes != null) // Time ban
return new WebhookPayload
{
Username = _webhookName,
AvatarUrl = _webhookAvatarUrl,
AllowedMentions = allowedMentions,
Mentions = mentions,
Embeds = new List<Embed>
{
new()
{
Description = Loc.GetString(
"server-time-ban-string",
("targetName", targetName),
("targetLink", targetLink),
("adminLink", adminLink),
("adminName", adminName),
("TimeNow", timeNow),
("expiresString", expiresString),
("reason", reason),
("severity", Loc.GetString($"admin-note-editor-severity-{severity.ToLower()}"))),
Color = 0x803045,
Thumbnail = new EmbedThumbnail
{
Url = "https://static.wikia.nocookie.net/ss14andromeda13/images/f/ff/Clown.png/revision/latest?cb=20230217121049&path-prefix=ru",
},
Author = new EmbedAuthor
{
Name = Loc.GetString("server-time-ban", ("mins", minutes.Value)) + $" #{id}",
IconUrl = "https://cdn.discordapp.com/emojis/1129749368199712829.webp?size=40&quality=lossless" // Смайлик бан хаммера. URL прямо из дискорд)
},
Footer = new EmbedFooter
{
Text = Loc.GetString("server-ban-footer", ("server", serverName), ("round", round)),
IconUrl = "https://cdn.discordapp.com/emojis/1143995749928030208.webp?size=40&quality=lossless"
},
},
},
};
else // Perma ban
return new WebhookPayload
{
Username = _webhookName,
AvatarUrl = _webhookAvatarUrl,
AllowedMentions = allowedMentions,
Mentions = mentions,
Embeds = new List<Embed>
{
new()
{
Description = Loc.GetString(
"server-perma-ban-string",
("targetName", targetName),
("targetLink", targetLink),
("adminLink", adminLink),
("adminName", adminName),
("TimeNow", timeNow),
("reason", reason),
("severity", Loc.GetString($"admin-note-editor-severity-{severity.ToLower()}"))),
Color = 0x8B0000,
Thumbnail = new EmbedThumbnail
{
Url = "https://static.wikia.nocookie.net/ss14andromeda13/images/7/72/%D0%94%D0%B5%D1%82%D0%B5%D0%BA%D1%82%D0%B8%D0%B2.png/revision/latest?cb=20230216091637&path-prefix=ru",
},
Author = new EmbedAuthor
{
Name = $"{Loc.GetString("server-perma-ban")} #{id}",
IconUrl = "https://cdn.discordapp.com/emojis/1129749368199712829.webp?size=40&quality=lossless" // Смайлик бан хаммера. URL прямо из дискорд)
},
Footer = new EmbedFooter
{
Text = Loc.GetString("server-ban-footer", ("server", serverName), ("round", round)),
IconUrl = "https://cdn.discordapp.com/emojis/1129769076647002122.webp?size=40&quality=lossless"
},
},
},
};
}
private void OnWebhookChanged(string url)
{
_webhookUrl = url;
if (url == string.Empty)
return;
// Basic sanity check and capturing webhook ID and token
var match = Regex.Match(url, @"^https://discord\.com/api/webhooks/(\d+)/((?!.*/).*)$");
if (!match.Success)
{
// TODO: Ideally, CVar validation during setting should be better integrated
_sawmill.Warning("Webhook URL does not appear to be valid. Using anyways...");
return;
}
if (match.Groups.Count <= 2)
{
_sawmill.Error("Could not get webhook ID or token.");
return;
}
var webhookId = match.Groups[1].Value;
var webhookToken = match.Groups[2].Value;
// Fire and forget
_ = SetWebhookData(webhookId, webhookToken);
}
private async Task SetWebhookData(string id, string token)
{
var response = await _httpClient.GetAsync($"https://discord.com/api/v10/webhooks/{id}/{token}");
var content = await response.Content.ReadAsStringAsync();
if (!response.IsSuccessStatusCode)
{
_sawmill.Log(LogLevel.Error, $"Discord returned bad status code when trying to get webhook data (perhaps the webhook URL is invalid?): {response.StatusCode}\nResponse: {content}");
return;
}
_webhookData = JsonSerializer.Deserialize<WebhookData>(content);
}
// https://discord.com/developers/docs/resources/channel#embed-object-embed-structure
private struct Embed
{
[JsonPropertyName("description")]
public string Description { get; set; } = "";
[JsonPropertyName("color")]
public int Color { get; set; } = 0;
[JsonPropertyName("author")]
public EmbedAuthor? Author { get; set; } = null;
[JsonPropertyName("thumbnail")]
public EmbedThumbnail? Thumbnail { get; set; } = null;
[JsonPropertyName("footer")]
public EmbedFooter? Footer { get; set; } = null;
public Embed()
{
}
}
// https://discord.com/developers/docs/resources/channel#embed-object-embed-author-structure
private struct EmbedAuthor
{
[JsonPropertyName("name")]
public string Name { get; set; } = "";
[JsonPropertyName("icon_url")]
public string? IconUrl { get; set; }
public EmbedAuthor()
{
}
}
// https://discord.com/developers/docs/resources/webhook#webhook-object-webhook-structure
private struct WebhookData
{
[JsonPropertyName("guild_id")]
public string? GuildId { get; set; } = null;
[JsonPropertyName("channel_id")]
public string? ChannelId { get; set; } = null;
public WebhookData()
{
}
}
// https://discord.com/developers/docs/resources/channel#message-object-message-structure
private struct WebhookPayload
{
[JsonPropertyName("username")]
public string Username { get; set; } = "";
[JsonPropertyName("avatar_url")]
public string? AvatarUrl { get; set; } = "";
[JsonPropertyName("embeds")]
public List<Embed>? Embeds { get; set; } = null;
[JsonPropertyName("mentions")]
public List<User> Mentions { get; set; } = new();
[JsonPropertyName("allowed_mentions")]
public Dictionary<string, string[]> AllowedMentions { get; set; } =
new()
{
{ "parse", Array.Empty<string>() },
};
public WebhookPayload()
{
}
}
// https://discord.com/developers/docs/resources/channel#embed-object-embed-footer-structure
private struct EmbedFooter
{
[JsonPropertyName("text")]
public string Text { get; set; } = "";
[JsonPropertyName("icon_url")]
public string? IconUrl { get; set; }
public EmbedFooter()
{
}
}
// https://discord.com/developers/docs/resources/channel#embed-object-embed-footer-structure
private struct EmbedThumbnail
{
[JsonPropertyName("url")]
public string Url { get; set; } = "";
public EmbedThumbnail()
{
}
}
private struct User
{
[JsonPropertyName("id")]
public string Id { get; set; } = "";
public User()
{
}
}
#endregion
[UsedImplicitly]
private sealed record DiscordUserResponse(string UserId, string Username);
// Sunrise-end
}

View file

@ -37,6 +37,8 @@ public interface IBanManager
/// <param name="timeOfBan">Time when the ban was applied, used for grouping role bans</param>
public void CreateRoleBan(NetUserId? target, string? targetUsername, NetUserId? banningAdmin, (IPAddress, int)? addressRange, ImmutableArray<byte>? hwid, string role, uint? minutes, NoteSeverity severity, string reason, DateTimeOffset timeOfBan);
public void WebhookUpdateRoleBans(NetUserId? target, string? targetUsername, NetUserId? banningAdmin, (IPAddress, int)? addressRange, ImmutableArray<byte>? hwid, IReadOnlyCollection<string> roles, uint? minutes, NoteSeverity severity, string reason, DateTimeOffset timeOfBan);
/// <summary>
/// Pardons a role ban for the specified target, username or GUID
/// </summary>

View file

@ -188,7 +188,7 @@ public sealed class AlertLevelSystem : EntitySystem
if (announce)
{
_chatSystem.DispatchStationAnnouncement(station, announcementFull, playDefaultSound: playDefault,
_chatSystem.DispatchStationAnnouncement(station, announcementFull, playSound: playDefault,
colorOverride: detail.Color, sender: stationName);
}

View file

@ -63,7 +63,7 @@ public sealed partial class SpaceVillainArcadeComponent : SharedSpaceVillainArca
[ViewVariables(VVAccess.ReadWrite)]
[DataField("possibleFightVerbs")]
public List<string> PossibleFightVerbs = new()
{"Defeat", "Annihilate", "Save", "Strike", "Stop", "Destroy", "Robust", "Romance", "Pwn", "Own"};
{"Победи", "Аннигилируй", "Спаси", "Ударь", "Останови", "Уничтожь", "Заробасти", "Добейся", "Отымей", "Заовни"};
/// <summary>
/// The first names/titles that can be used to construct the name of the villain.
@ -71,8 +71,8 @@ public sealed partial class SpaceVillainArcadeComponent : SharedSpaceVillainArca
[ViewVariables(VVAccess.ReadWrite)]
[DataField("possibleFirstEnemyNames")]
public List<string> PossibleFirstEnemyNames = new(){
"the Automatic", "Farmer", "Lord", "Professor", "the Cuban", "the Evil", "the Dread King",
"the Space", "Lord", "the Great", "Duke", "General"
"Автоматический", "Фермер", "Лорд", "Профессор", "Кубинец", "Злой", "Грозный Король",
"Космический", "Лорд", "Могучий", "Герцог", "Генерал"
};
/// <summary>
@ -82,8 +82,8 @@ public sealed partial class SpaceVillainArcadeComponent : SharedSpaceVillainArca
[DataField("possibleLastEnemyNames")]
public List<string> PossibleLastEnemyNames = new()
{
"Melonoid", "Murdertron", "Sorcerer", "Ruin", "Jeff", "Ectoplasm", "Crushulon", "Uhangoid",
"Vhakoid", "Peteoid", "slime", "Griefer", "ERPer", "Lizard Man", "Unicorn"
"Мелоноид", "Киллертрон", "Волшебник", "Руина", "Джефф", "Эктоплазма", "Крушелон", "Ухангоид",
"Вакоид", "Петеоид", "слайм", "Грифер", "ЕРПшер", "Человек-ящерица", "Единорог"
};
/// <summary>

View file

@ -241,7 +241,7 @@ public sealed class CryostorageSystem : SharedCryostorageSystem
("character", name),
("job", CultureInfo.CurrentCulture.TextInfo.ToTitleCase(jobName))
), Loc.GetString("earlyleave-cryo-sender"),
playDefaultSound: false
playSound: false
);
}

View file

@ -77,6 +77,11 @@ namespace Content.Server.Body.Components
[ViewVariables]
public RespiratorStatus Status = RespiratorStatus.Inhaling;
// Sunrise-Start
[ViewVariables]
public bool HasImmunity = false;
// Sunrise-End
}
}

View file

@ -38,8 +38,31 @@ public sealed class RespiratorSystem : EntitySystem
SubscribeLocalEvent<RespiratorComponent, MapInitEvent>(OnMapInit);
SubscribeLocalEvent<RespiratorComponent, EntityUnpausedEvent>(OnUnpaused);
SubscribeLocalEvent<RespiratorComponent, ApplyMetabolicMultiplierEvent>(OnApplyMetabolicMultiplier);
// Sunrise-Start
SubscribeLocalEvent<RespiratorImmunityComponent, ComponentInit>(OnPressureImmuneInit);
SubscribeLocalEvent<RespiratorImmunityComponent, ComponentRemove>(OnPressureImmuneRemove);
// Sunrise-End
}
// Sunrise-Start
private void OnPressureImmuneInit(EntityUid uid, RespiratorImmunityComponent pressureImmunity, ComponentInit args)
{
if (TryComp<RespiratorComponent>(uid, out var respirator))
{
respirator.HasImmunity = true;
}
}
private void OnPressureImmuneRemove(EntityUid uid, RespiratorImmunityComponent pressureImmunity, ComponentRemove args)
{
if (TryComp<RespiratorComponent>(uid, out var respirator))
{
respirator.HasImmunity = false;
}
}
// Sunrise-End
private void OnMapInit(Entity<RespiratorComponent> ent, ref MapInitEvent args)
{
ent.Comp.NextUpdate = _gameTiming.CurTime + ent.Comp.UpdateInterval;
@ -57,7 +80,7 @@ public sealed class RespiratorSystem : EntitySystem
var query = EntityQueryEnumerator<RespiratorComponent, BodyComponent>();
while (query.MoveNext(out var uid, out var respirator, out var body))
{
if (_gameTiming.CurTime < respirator.NextUpdate)
if (_gameTiming.CurTime < respirator.NextUpdate || respirator.HasImmunity) // Sunrise-Edit
continue;
respirator.NextUpdate += respirator.UpdateInterval;

View file

@ -11,6 +11,8 @@ using Content.Shared.CCVar;
using Content.Shared.Chat;
using Content.Shared.Database;
using Content.Shared.Mind;
using Content.Sunrise.Interfaces.Server;
using Content.Sunrise.Interfaces.Shared;
using Robust.Server.Player;
using Robust.Shared.Configuration;
using Robust.Shared.Network;
@ -45,6 +47,7 @@ namespace Content.Server.Chat.Managers
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
private IServerSponsorsManager? _sponsorsManager; // Sunrise-Sponsors
/// <summary>
/// The maximum length a player-sent message can be sent
@ -58,6 +61,7 @@ namespace Content.Server.Chat.Managers
public void Initialize()
{
IoCManager.Instance!.TryResolveType(out _sponsorsManager); // Sunrise-Sponsors
_netManager.RegisterNetMessage<MsgChatMessage>();
_netManager.RegisterNetMessage<MsgDeleteChatMessagesBy>();
@ -257,6 +261,13 @@ namespace Content.Server.Chat.Managers
wrappedMessage = Loc.GetString("chat-manager-send-ooc-patron-wrap-message", ("patronColor", patronColor),("playerName", player.Name), ("message", FormattedMessage.EscapeText(message)));
}
// Sunrise-Sponsors-Start
if (_sponsorsManager != null && _sponsorsManager.TryGetOocColor(player.UserId, out var oocColor))
{
wrappedMessage = Loc.GetString("chat-manager-send-ooc-patron-wrap-message", ("patronColor", oocColor),("playerName", player.Name), ("message", FormattedMessage.EscapeText(message)));
}
// Sunrise-Sponsors-End
//TODO: player.Name color, this will need to change the structure of the MsgChatMessage
ChatMessageToAll(ChatChannel.OOC, message, wrappedMessage, EntityUid.Invalid, hideChat: false, recordReplay: true, colorOverride: colorOverride, author: player.UserId);
_mommiLink.SendOOCMessage(player.Name, message);

View file

@ -11,6 +11,32 @@ public sealed class ChatSanitizationManager : IChatSanitizationManager
private static readonly Dictionary<string, string> SmileyToEmote = new()
{
// Russian-Localization-Start
{ "хд", "chatsan-laughs" },
{ "о-о", "chatsan-wide-eyed" }, // cyrillic о
{ "о.о", "chatsan-wide-eyed" }, // cyrillic о
{ "0_о", "chatsan-wide-eyed" }, // cyrillic о
{ "о/", "chatsan-waves" }, // cyrillic о
{ "о7", "chatsan-salutes" }, // cyrillic о
{ "0_o", "chatsan-wide-eyed" },
{ "лмао", "chatsan-laughs" },
{ "рофл", "chatsan-laughs" },
{ "яхз", "chatsan-shrugs" },
{ ":0", "chatsan-surprised" },
{ ":р", "chatsan-stick-out-tongue" }, // cyrillic р
{ "кек", "chatsan-laughs" },
{ "T_T", "chatsan-cries" },
{ "Т_Т", "chatsan-cries" }, // cyrillic T
{ "=_(", "chatsan-cries" },
{ "!с", "chatsan-laughs" },
{ "!в", "chatsan-sighs" },
{ "!х", "chatsan-claps" },
{ "!щ", "chatsan-snaps" },
{ "))", "chatsan-smiles-widely" },
{ ")", "chatsan-smiles" },
{ "((", "chatsan-frowns-deeply" },
{ "(", "chatsan-frowns" },
// Russian-Localization-End
// I could've done this with regex, but felt it wasn't the right idea.
{ ":)", "chatsan-smiles" },
{ ":]", "chatsan-smiles" },

View file

@ -17,6 +17,6 @@ public sealed class AnnounceOnSpawnSystem : EntitySystem
{
var message = Loc.GetString(comp.Message);
var sender = comp.Sender != null ? Loc.GetString(comp.Sender) : "Central Command";
_chat.DispatchGlobalAnnouncement(message, sender, playSound: true, comp.Sound, comp.Color);
_chat.DispatchGlobalAnnouncement(message, sender, playSound: true, announcementSound: comp.Sound, colorOverride: comp.Color);
}
}

View file

@ -14,7 +14,6 @@ using Content.Shared.CCVar;
using Content.Shared.Chat;
using Content.Shared.Database;
using Content.Shared.Ghost;
using Content.Shared.Humanoid;
using Content.Shared.IdentityManagement;
using Content.Shared.Interaction;
using Content.Shared.Mobs.Systems;
@ -58,10 +57,13 @@ public sealed partial class ChatSystem : SharedChatSystem
[Dependency] private readonly SharedInteractionSystem _interactionSystem = default!;
[Dependency] private readonly ReplacementAccentSystem _wordreplacement = default!;
public const int VoiceRange = 10; // how far voice goes in world units
public const int WhisperClearRange = 2; // how far whisper goes while still being understandable, in world units
public const int WhisperMuffledRange = 5; // how far whisper goes at all, in world units
public const string DefaultAnnouncementSound = "/Audio/Announcements/announce.ogg";
// Sunrise-TTS-Start: Moved from Server to Shared
// public const int VoiceRange = 10; // how far voice goes in world units
// public const int WhisperClearRange = 2; // how far whisper goes while still being understandable, in world units
// public const int WhisperMuffledRange = 5; // how far whisper goes at all, in world units
// Sunrise-TTS-End
public const string DefaultAnnouncementSound = "/Audio/Announcements/announce.ogg"; // Sunrise-edit
public const string NukeAnnouncementSound = "/Audio/Announcements/war.ogg"; // Sunrise-edit
private bool _loocEnabled = true;
private bool _deadLoocEnabled;
@ -305,21 +307,39 @@ public sealed partial class ChatSystem : SharedChatSystem
/// <param name="message">The contents of the message</param>
/// <param name="sender">The sender (Communications Console in Communications Console Announcement)</param>
/// <param name="playSound">Play the announcement sound</param>
/// <param name="playTts"></param>
/// <param name="colorOverride">Optional color for the announcement message</param>
public void DispatchGlobalAnnouncement(
string message,
string sender = "Central Command",
string sender = "Центральное коммандование", // Sunrise-edit
bool playSound = true,
SoundSpecifier? announcementSound = null,
bool playTts = true, // Sunrise-edit
Color? colorOverride = null
)
{
var wrappedMessage = Loc.GetString("chat-manager-sender-announcement-wrap-message", ("sender", sender), ("message", FormattedMessage.EscapeText(message)));
_chatManager.ChatMessageToAll(ChatChannel.Radio, message, wrappedMessage, default, false, true, colorOverride);
// Sunrise-start
if (playSound)
{
_audio.PlayGlobal(announcementSound?.GetSound() ?? DefaultAnnouncementSound, Filter.Broadcast(), true, AudioParams.Default.WithVolume(-2f));
if (sender == Loc.GetString("comms-console-announcement-title-nukie"))
{
announcementSound = new SoundPathSpecifier(NukeAnnouncementSound); // Sunrise-edit
}
announcementSound ??= new SoundPathSpecifier(DefaultAnnouncementSound);
_audio.PlayGlobal(announcementSound?.GetSound() ?? DefaultAnnouncementSound, Filter.Broadcast(), true, announcementSound?.Params ?? AudioParams.Default.WithVolume(-2f));
}
if (playTts)
{
var nukie = sender == Loc.GetString("comms-console-announcement-title-nukie");
var announcementEv = new AnnouncementSpokeEvent(Filter.Broadcast(), message, nukie);
RaiseLocalEvent(announcementEv);
}
// Sunrise-end
_adminLogger.Add(LogType.Chat, LogImpact.Low, $"Global station announcement from {sender}: {message}");
}
@ -330,13 +350,15 @@ public sealed partial class ChatSystem : SharedChatSystem
/// <param name="message">The contents of the message</param>
/// <param name="sender">The sender (Communications Console in Communications Console Announcement)</param>
/// <param name="playDefaultSound">Play the announcement sound</param>
/// <param name="playSound"></param>
/// <param name="playTts"></param>
/// <param name="colorOverride">Optional color for the announcement message</param>
public void DispatchStationAnnouncement(
EntityUid source,
string message,
string sender = "Central Command",
bool playDefaultSound = true,
SoundSpecifier? announcementSound = null,
string sender = "Центральное коммандование", // Sunrise-edit
bool playSound = true, // Sunrise-edit
bool playTts = true,// Sunrise-edit
Color? colorOverride = null)
{
var wrappedMessage = Loc.GetString("chat-manager-sender-announcement-wrap-message", ("sender", sender), ("message", FormattedMessage.EscapeText(message)));
@ -354,11 +376,19 @@ public sealed partial class ChatSystem : SharedChatSystem
_chatManager.ChatMessageToManyFiltered(filter, ChatChannel.Radio, message, wrappedMessage, source, false, true, colorOverride);
if (playDefaultSound)
// Sunrise-start
if (playSound)
{
_audio.PlayGlobal(announcementSound?.GetSound() ?? DefaultAnnouncementSound, filter, true, AudioParams.Default.WithVolume(-2f));
var announcementSound = new SoundPathSpecifier(DefaultAnnouncementSound);
_audio.PlayGlobal(announcementSound?.GetSound() ?? DefaultAnnouncementSound, Filter.Broadcast(), true, announcementSound?.Params ?? AudioParams.Default.WithVolume(-2f));
}
if (playTts)
{
RaiseLocalEvent(new AnnouncementSpokeEvent(filter, message));
}
// Sunrise-edit
_adminLogger.Add(LogType.Chat, LogImpact.Low, $"Station Announcement on {station} from {sender}: {message}");
}
@ -412,7 +442,7 @@ public sealed partial class ChatSystem : SharedChatSystem
SendInVoiceRange(ChatChannel.Local, message, wrappedMessage, source, range);
var ev = new EntitySpokeEvent(source, message, null, null);
var ev = new EntitySpokeEvent(source, message, originalMessage, null, null);
RaiseLocalEvent(source, ev, true);
// To avoid logging any messages sent by entities that are not players, like vendors, cloning, etc.
@ -507,7 +537,7 @@ public sealed partial class ChatSystem : SharedChatSystem
_replay.RecordServerMessage(new ChatMessage(ChatChannel.Whisper, message, wrappedMessage, GetNetEntity(source), null, MessageRangeHideChatForReplay(range)));
var ev = new EntitySpokeEvent(source, message, channel, obfuscatedMessage);
var ev = new EntitySpokeEvent(source, message, originalMessage, channel, obfuscatedMessage);
RaiseLocalEvent(source, ev, true);
if (!hideLog)
if (originalMessage == message)
@ -706,6 +736,7 @@ public sealed partial class ChatSystem : SharedChatSystem
private string SanitizeInGameICMessage(EntityUid source, string message, out string? emoteStr, bool capitalize = true, bool punctuate = false, bool capitalizeTheWordI = true)
{
var newMessage = message.Trim();
newMessage = ReplaceWords(newMessage); // Sunrise-TTS
newMessage = SanitizeMessageReplaceWords(newMessage);
if (capitalize)
@ -915,7 +946,9 @@ public sealed class EntitySpokeEvent : EntityEventArgs
{
public readonly EntityUid Source;
public readonly string Message;
public readonly string OriginalMessage;
public readonly string? ObfuscatedMessage; // not null if this was a whisper
public readonly bool IsRadio; // Sunrise-TTS
/// <summary>
/// If the entity was trying to speak into a radio, this was the channel they were trying to access. If a radio
@ -923,12 +956,14 @@ public sealed class EntitySpokeEvent : EntityEventArgs
/// </summary>
public RadioChannelPrototype? Channel;
public EntitySpokeEvent(EntityUid source, string message, RadioChannelPrototype? channel, string? obfuscatedMessage)
public EntitySpokeEvent(EntityUid source, string message, string originalMessage, RadioChannelPrototype? channel, string? obfuscatedMessage)
{
Source = source;
Message = message;
OriginalMessage = originalMessage; // Sunrise-TTS
Channel = channel;
ObfuscatedMessage = obfuscatedMessage;
IsRadio = channel != null; // Sunrise-TTS
}
}
@ -940,7 +975,7 @@ public enum InGameICChatType : byte
{
Speak,
Emote,
Whisper
Whisper, // Sunrise-TTS
}
/// <summary>
@ -966,3 +1001,23 @@ public enum ChatTransmitRange : byte
/// Ghosts can't hear or see it at all. Regular players can if in-range.
NoGhosts
}
// Sunrise-TTS-Start
public sealed class AnnouncementSpokeEvent(
Filter source,
string message,
bool nukie = false)
: EntityEventArgs
{
public readonly Filter Source = source;
public readonly string Message = message;
public readonly bool Nukie = nukie;
}
public sealed class RadioSpokeEvent(EntityUid source, string message, EntityUid[] receivers) : EntityEventArgs
{
public readonly EntityUid Source = source;
public readonly string Message = message;
public readonly EntityUid[] Receivers = receivers;
}
// Sunrise-TTS-End

View file

@ -79,7 +79,7 @@ public sealed class CommsHackerSystem : SharedCommsHackerSystem
public void CallInThreat(NinjaHackingThreatPrototype ninjaHackingThreat)
{
_gameTicker.StartGameRule(ninjaHackingThreat.Rule, out _);
_chat.DispatchGlobalAnnouncement(Loc.GetString(ninjaHackingThreat.Announcement), playSound: true, colorOverride: Color.Red);
_chat.DispatchGlobalAnnouncement(Loc.GetString(ninjaHackingThreat.Announcement), playSound: true, playTts: true, colorOverride: Color.Red);
}
}

View file

@ -1,6 +1,5 @@
using System.Collections.Immutable;
using System.Runtime.InteropServices;
using System.Text.Json.Nodes;
using System.Threading.Tasks;
using Content.Server.Database;
using Content.Server.GameTicking;
@ -8,6 +7,8 @@ using Content.Server.Preferences.Managers;
using Content.Shared.CCVar;
using Content.Shared.GameTicking;
using Content.Shared.Players.PlayTimeTracking;
using Content.Sunrise.Interfaces.Server;
using Content.Sunrise.Interfaces.Shared;
using Robust.Server.Player;
using Robust.Shared.Configuration;
using Robust.Shared.Network;
@ -19,6 +20,7 @@ namespace Content.Server.Connection
public interface IConnectionManager
{
void Initialize();
Task<bool> HavePrivilegedJoin(NetUserId userId); // Sunrise-Queue
/// <summary>
/// Temporarily allow a user to bypass regular connection requirements.
@ -47,6 +49,7 @@ namespace Content.Server.Connection
[Dependency] private readonly ServerDbEntryManager _serverDbEntry = default!;
[Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly ILogManager _logManager = default!;
private IServerSponsorsManager? _sponsorsMgr; // Sunrise-Sponsors
private readonly Dictionary<NetUserId, TimeSpan> _temporaryBypasses = [];
private ISawmill _sawmill = default!;
@ -55,6 +58,7 @@ namespace Content.Server.Connection
{
_sawmill = _logManager.GetSawmill("connections");
IoCManager.Instance!.TryResolveType(out _sponsorsMgr); // Sunrise-Sponsors
_netMgr.Connecting += NetMgrOnConnecting;
_netMgr.AssignUserIdCallback = AssignUserIdCallback;
// Approval-based IP bans disabled because they don't play well with Happy Eyeballs.
@ -155,7 +159,10 @@ namespace Content.Server.Connection
var adminData = await _dbManager.GetAdminDataForAsync(e.UserId);
if (_cfg.GetCVar(CCVars.PanicBunkerEnabled) && adminData == null)
// Sunrise-Sponsors-Start
var isPrivileged = await HavePrivilegedJoin(e.UserId);
if (_cfg.GetCVar(CCVars.PanicBunkerEnabled) && adminData == null && !isPrivileged)
// Sunrise-Sponsors-End
{
var showReason = _cfg.GetCVar(CCVars.PanicBunkerShowReason);
var customReason = _cfg.GetCVar(CCVars.PanicBunkerCustomReason);
@ -202,11 +209,10 @@ namespace Content.Server.Connection
}
}
var wasInGame = EntitySystem.TryGet<GameTicker>(out var ticker) &&
ticker.PlayerGameStatuses.TryGetValue(userId, out var status) &&
status == PlayerGameStatus.JoinedGame;
var adminBypass = _cfg.GetCVar(CCVars.AdminBypassMaxPlayers) && adminData != null;
if ((_plyMgr.PlayerCount >= _cfg.GetCVar(CCVars.SoftMaxPlayers) && !adminBypass) && !wasInGame)
// Sunrise-Queue-Start
var isQueueEnabled = IoCManager.Instance!.TryResolveType<IServerJoinQueueManager>(out var mgr) && mgr.IsEnabled;
if (_plyMgr.PlayerCount >= _cfg.GetCVar(CCVars.SoftMaxPlayers) && !isPrivileged && !isQueueEnabled)
// Sunrise-Queue-End
{
return (ConnectionDenyReason.Full, Loc.GetString("soft-player-cap-full"), null);
}
@ -253,5 +259,19 @@ namespace Content.Server.Connection
await _db.AssignUserIdAsync(name, assigned);
return assigned;
}
// Sunrise-Sponsors-Start
public async Task<bool> HavePrivilegedJoin(NetUserId userId)
{
var adminBypass = _cfg.GetCVar(CCVars.AdminBypassMaxPlayers) && await _dbManager.GetAdminDataForAsync(userId) != null;
var havePriorityJoin = _sponsorsMgr != null && _sponsorsMgr.HavePriorityJoin(userId); // Sunrise-Sponsors
var wasInGame = EntitySystem.TryGet<GameTicker>(out var ticker) &&
ticker.PlayerGameStatuses.TryGetValue(userId, out var status) &&
status == PlayerGameStatus.JoinedGame;
return adminBypass ||
havePriorityJoin || // Sunrise-Sponsors
wasInGame;
}
// Sunrise-Sponsors-End
}
}

View file

@ -25,6 +25,7 @@
<ProjectReference Include="..\RobustToolbox\Robust.Shared\Robust.Shared.csproj" />
<ProjectReference Include="..\RobustToolbox\Robust.Server\Robust.Server.csproj" />
<ProjectReference Include="..\Content.Shared\Content.Shared.csproj" />
<ProjectReference Include="..\Sunrise\Content.Sunrise.Interfaces.Server\Content.Sunrise.Interfaces.Server.csproj" />
</ItemGroup>
<ItemGroup>
<Folder Include="Objectives\Interfaces\" />

View file

@ -82,11 +82,12 @@ namespace Content.Server.Database
}
return $"""
{loc.GetString("ban-banned-1")}
{loc.GetString("ban-banned-2", ("reason", Reason))}
{expires}
{loc.GetString("ban-banned-3")}
""";
{loc.GetString("ban-banned-1")}
{loc.GetString("ban-banned-2", ("id", Id.ToString() ?? ""))}
{loc.GetString("ban-banned-3", ("reason", Reason))}
{expires}
{loc.GetString("ban-banned-4")}
""";
}
}
}

View file

@ -195,6 +195,12 @@ namespace Content.Server.Database
if (Enum.TryParse<Gender>(profile.Gender, true, out var genderVal))
gender = genderVal;
// Sunrise-TTS-Start
var voice = profile.Voice;
if (voice == String.Empty)
voice = SharedHumanoidAppearanceSystem.DefaultSexVoice[sex];
// Sunrise-TTS-End
// ReSharper disable once ConditionalAccessQualifierIsNonNullableAccordingToAPIContract
var markingsRaw = profile.Markings?.Deserialize<List<string>>();
@ -236,6 +242,7 @@ namespace Content.Server.Database
profile.CharacterName,
profile.FlavorText,
profile.Species,
voice, // Sunrise-TTS
profile.Age,
sex,
gender,
@ -272,6 +279,7 @@ namespace Content.Server.Database
profile.CharacterName = humanoid.Name;
profile.FlavorText = humanoid.FlavorText;
profile.Species = humanoid.Species;
profile.Voice = humanoid.Voice; // Sunrise-TTS
profile.Age = humanoid.Age;
profile.Sex = humanoid.Sex.ToString();
profile.Gender = humanoid.Gender.ToString();

View file

@ -29,6 +29,7 @@ public sealed class AirlockSystem : SharedAirlockSystem
if (TryComp<ApcPowerReceiverComponent>(uid, out var receiverComponent))
{
Appearance.SetData(uid, DoorVisuals.Powered, receiverComponent.Powered);
Appearance.SetData(uid, DoorVisuals.ClosedLights, true); // Resprite-Airlocks
}
}

View file

@ -72,7 +72,7 @@ public sealed class DragonRiftSystem : EntitySystem
var msg = Loc.GetString("carp-rift-warning",
("location", FormattedMessage.RemoveMarkup(_navMap.GetNearestBeaconString((uid, xform)))));
_chat.DispatchGlobalAnnouncement(msg, playSound: false, colorOverride: Color.Red);
_chat.DispatchGlobalAnnouncement(msg, playSound: false, playTts: true, colorOverride: Color.Red);
_audio.PlayGlobal("/Audio/Misc/notice1.ogg", Filter.Broadcast(), true);
_navMap.SetBeaconEnabled(uid, true);
}

Some files were not shown because too many files have changed in this diff Show more