parent
1841df4ce0
commit
5775d4cdef
9367 changed files with 105740 additions and 12077 deletions
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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\" />
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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 += _ =>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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>();
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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!;
|
||||
|
|
|
|||
|
|
@ -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"/>
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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" />
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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" />
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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 = "";
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Content.Client/_Sunrise/Synth/SynthMonitorMenu.xaml
Normal file
11
Content.Client/_Sunrise/Synth/SynthMonitorMenu.xaml
Normal 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>
|
||||
84
Content.Client/_Sunrise/Synth/SynthMonitorMenu.xaml.cs
Normal file
84
Content.Client/_Sunrise/Synth/SynthMonitorMenu.xaml.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
78
Content.Client/_Sunrise/TTS/HumanoidProfileEditor.TTS.cs
Normal file
78
Content.Client/_Sunrise/TTS/HumanoidProfileEditor.TTS.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
144
Content.Client/_Sunrise/TTS/TTSSystem.cs
Normal file
144
Content.Client/_Sunrise/TTS/TTSSystem.cs
Normal 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++;
|
||||
}
|
||||
}
|
||||
1338
Content.Server.Database/Migrations/Postgres/20221214230019_TTSVoice.Designer.cs
generated
Normal file
1338
Content.Server.Database/Migrations/Postgres/20221214230019_TTSVoice.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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");
|
||||
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
||||
|
|
|
|||
1272
Content.Server.Database/Migrations/Sqlite/20221214230014_TTSVoice.Designer.cs
generated
Normal file
1272
Content.Server.Database/Migrations/Sqlite/20221214230014_TTSVoice.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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");
|
||||
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
||||
|
|
|
|||
|
|
@ -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!;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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!;
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -77,6 +77,11 @@ namespace Content.Server.Body.Components
|
|||
|
||||
[ViewVariables]
|
||||
public RespiratorStatus Status = RespiratorStatus.Inhaling;
|
||||
|
||||
// Sunrise-Start
|
||||
[ViewVariables]
|
||||
public bool HasImmunity = false;
|
||||
// Sunrise-End
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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" },
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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\" />
|
||||
|
|
|
|||
|
|
@ -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")}
|
||||
""";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
Loading…
Add table
Reference in a new issue